config('host'), '/') . '/api/application/' . $url; $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . $this->config('api_key'), 'Accept' => 'application/json', ])->$method($req_url, $data); if (!$response->successful()) { $json = $response->json(); $message = $json['message'] ?? $json['error'] ?? 'Server Error'; throw new Exception($message); } return $response->json() ?? []; } /** * Get all configuration for extension * * @param array $values */ public function getConfig($values = []): array { return [ [ 'name' => 'host', 'type' => 'text', 'label' => 'Hostname', 'required' => true, 'validation' => 'url:http,https', ], [ 'name' => 'api_key', 'type' => 'text', 'label' => 'API Key', 'required' => true, ], ]; } /** * Get product config * * @param array $values */ public function getProductConfig($values = []): array { $nodes = $this->request('nodes'); $options = []; foreach ($nodes['data'] as $node) { $options[$node['id']] = $node['name']; } return [ [ 'name' => 'cpu', 'type' => 'text', 'label' => 'CPU Cores', 'required' => true, ], [ 'name' => 'ram', 'type' => 'text', 'label' => 'RAM (MiB)', 'required' => true, ], [ 'name' => 'disk', 'type' => 'text', 'label' => 'Disk (MiB)', 'required' => true, ], [ 'name' => 'bandwidth', 'type' => 'text', 'label' => 'Bandwidth (MiB)', 'required' => false, ], [ 'name' => 'rate_limit', 'type' => 'text', 'label' => 'Rate Limit (MiB/s)', 'required' => false, 'description' => 'Network speed limit in MiB/s. Set to 0 or leave empty for unlimited.', ], [ 'name' => 'snapshot', 'type' => 'text', 'label' => 'Amount of snapshots', 'required' => true, ], [ 'name' => 'backups', 'type' => 'text', 'label' => 'Amount of backups', 'required' => true, ], [ 'name' => 'node', 'type' => 'select', 'label' => 'Nodes', 'required' => true, 'options' => $options, ], [ 'name' => 'auto_assign_ip', 'type' => 'checkbox', 'label' => 'Auto assign IP from pool', 'required' => false, ], [ 'name' => 'ipv4', 'type' => 'number', 'label' => 'Amount of IPv4 addresses', 'required' => false, ], [ 'name' => 'ipv6', 'type' => 'number', 'label' => 'Amount of IPv6 addresses', 'required' => false, ], [ 'name' => 'start_on_create', 'type' => 'checkbox', 'label' => 'Start Server After Completing Installation', 'required' => false, ], ]; } public function getCheckoutConfig(Product $product): array { $node = $product->settings()->where('key', 'node')->first()->value; $os = $this->request('nodes/' . $node . '/template-groups'); $options = []; foreach ($os['data'] as $osGroup) { foreach ($osGroup['templates'] as $templateGroup) { foreach ($templateGroup as $template) { $options[$template['uuid']] = $template['name']; } } } return [ [ 'name' => 'os', 'type' => 'select', 'label' => 'Operating System', 'required' => true, 'options' => $options, ], [ 'name' => 'hostname', 'type' => 'text', 'label' => 'Hostname', 'placeholder' => 'server.example.com', 'required' => true, 'validation' => 'required|string|max:40', ], ]; } /** * Check if current configuration is valid */ public function testConfig(): bool|string { try { $this->request('servers'); return true; } catch (Exception $e) { return $e->getMessage(); } } // Convoy is really strict (The account password must contain 8 - 50 characters, 1 uppercase, 1 lowercase, 1 number and 1 special character.) private function createPassword() { $password = Str::password(); while (!preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#\$%\^&\*]).{8,50}$/', $password)) { $password = Str::password(); } return $password; } public function getOrCreateUser($user) { $users = $this->request('users', data: ['filter[email]' => $user->email]); if (count($users['data']) > 0) { return [ 'created' => false, 'id' => $users['data'][0]['id'], ]; } $password = $this->createPassword(); $user = $this->request('users', 'post', [ 'email' => $user->email, 'name' => $user->name, 'password' => $password, 'root_admin' => false, ]); return [ 'created' => true, 'id' => $user['data']['id'], 'password' => $password, ]; } /** * Create a server * * @param array $settings (product settings) * @param array $properties (checkout options) * @return bool */ public function createServer(Service $service, $settings, $properties) { $node = $properties['node'] ?? $settings['node']; $os = $properties['os']; $hostname = $properties['hostname']; $password = $properties['password'] ?? $this->createPassword(); $cpu = $properties['cpu'] ?? $settings['cpu']; $ram = $properties['ram'] ?? $settings['ram']; $disk = $properties['disk'] ?? $settings['disk']; $bandwidth = $properties['bandwidth'] ?? $settings['bandwidth']; $rateLimit = $properties['rate_limit'] ?? $settings['rate_limit']; $snapshot = $properties['snapshot'] ?? $settings['snapshot']; $backups = $properties['backups'] ?? $settings['backups']; $ipv4 = $properties['ipv4'] ?? $settings['ipv4']; $ipv6 = $properties['ipv6'] ?? $settings['ipv6']; $ips = []; if ($ipv4 > 0) { $ip = $this->request('nodes/' . $node . '/addresses', data: ['filter[server_id]' => '', 'filter[type]' => 'ipv4', 'per_page' => $ipv4]); $ips = array_merge($ips, array_column($ip['data'], 'id')); } if ($ipv6 > 0) { $ip = $this->request('nodes/' . $node . '/addresses', data: ['filter[server_id]' => '', 'filter[type]' => 'ipv6', 'per_page' => $ipv6]); $ips = array_merge($ips, array_column($ip['data'], 'id')); } $user = $this->getOrCreateUser($service->user); $data = [ 'node_id' => (int) $node, 'user_id' => $user['id'], 'name' => $hostname . ' ' . $service->user->name, 'hostname' => $hostname, 'vmid' => null, 'limits' => [ 'cpu' => (int) $cpu, 'memory' => $ram * 1024 * 1024, 'disk' => $disk * 1024 * 1024, 'snapshots' => (int) $snapshot, 'bandwidth' => (int) $bandwidth == 0 ? null : (int) $bandwidth * 1024 * 1024, 'backups' => (int) $backups, 'address_ids' => $ips, ], 'account_password' => $password, 'template_uuid' => $os, 'should_create_server' => true, 'start_on_completion' => isset($properties['start_on_create']) ? (bool) $properties['start_on_create'] : (bool) $settings['start_on_create'], ]; // Add rate limit if set if (isset($rateLimit) && $rateLimit > 0) { $data['limits']['rate_limit'] = (float) $rateLimit; } $server = $this->request('servers', 'post', $data); if (!isset($server['data'])) { throw new Exception('Failed to create server'); } $service->properties()->updateOrCreate([ 'key' => 'server_uuid', ], [ 'name' => 'Convoy Server UUID', 'value' => $server['data']['uuid'], ]); return [ 'user' => $user, 'password' => $password, 'server' => $server['data'], ]; } public function upgradeServer(Service $service, $settings, $properties) { if (!isset($properties['server_uuid'])) { throw new Exception('Server does not exist'); } $currentData = $this->request('servers/' . $properties['server_uuid']); $rateLimit = $properties['rate_limit'] ?? $settings['rate_limit']; $data = [ 'address_ids' => [], 'snapshot_limit' => (int) ($properties['snapshot'] ?? $settings['snapshot']), 'backup_limit' => (int) ($properties['backups'] ?? $settings['backups']), 'bandwidth_limit' => (int) ($properties['bandwidth'] ?? $settings['bandwidth']) * 1024 * 1024, 'cpu' => (int) ($properties['cpu'] ?? $settings['cpu']), 'memory' => (int) ($properties['ram'] ?? $settings['ram']) * 1024 * 1024, 'disk' => (int) ($properties['disk'] ?? $settings['disk']) * 1024 * 1024, ]; // Add rate limit if set if (isset($rateLimit) && $rateLimit > 0) { $data['rate_limit'] = (float) $rateLimit; } $limitIpv4 = (int) ($properties['ipv4'] ?? $settings['ipv4']); $limitIpv6 = (int) ($properties['ipv6'] ?? $settings['ipv6']); // Check if IPv4 has increased if ($limitIpv4 && $limitIpv4 > count($currentData['data']['limits']['addresses']['ipv4'])) { $ip = $this->request('nodes/' . $currentData['data']['node_id'] . '/addresses', data: ['filter[server_id]' => '', 'filter[type]' => 'ipv4', 'per_page' => $limitIpv4 - count($currentData['data']['limits']['addresses']['ipv4'])]); $data['address_ids'] = array_merge(array_column($currentData['data']['limits']['addresses']['ipv4'], 'id'), array_column($ip['data'], 'id')); } else { $data['address_ids'] = array_column($currentData['data']['limits']['addresses']['ipv4'], 'id'); } // Check if IPv6 has increased if ($limitIpv6 && $limitIpv6 > count($currentData['data']['limits']['addresses']['ipv6'])) { $ip = $this->request('nodes/' . $currentData['data']['node_id'] . '/addresses', data: ['filter[server_id]' => '', 'filter[type]' => 'ipv6', 'per_page' => $limitIpv6 - count($currentData['data']['limits']['addresses']['ipv6'])]); $data['address_ids'] = array_merge($data['address_ids'], array_column($ip['data'], 'id')); } else { $data['address_ids'] = array_merge($data['address_ids'], array_column($currentData['data']['limits']['addresses']['ipv6'], 'id')); } $data['address_ids'] = array_values(array_unique($data['address_ids'])); // Update server $server = $this->request('servers/' . $properties['server_uuid'] . '/settings/build', 'patch', $data); if (!isset($server['data'])) { throw new Exception('Failed to update server'); } return [ 'server' => $server['data'], ]; } /** * Suspend a server * * @param array $settings (product settings) * @param array $properties (checkout options) * @return bool */ public function suspendServer(Service $service, $settings, $properties) { if (!isset($properties['server_uuid'])) { throw new Exception('Server does not exist'); } $this->request('servers/' . $properties['server_uuid'] . '/settings/suspend', 'post'); } /** * Unsuspend a server * * @param array $settings (product settings) * @param array $properties (checkout options) * @return bool */ public function unsuspendServer(Service $service, $settings, $properties) { if (!isset($properties['server_uuid'])) { throw new Exception('Server does not exist'); } $this->request('servers/' . $properties['server_uuid'] . '/settings/unsuspend', 'post'); } /** * Terminate a server * * @param array $settings (product settings) * @param array $properties (checkout options) * @return bool */ public function terminateServer(Service $service, $settings, $properties) { if (!isset($properties['server_uuid'])) { throw new Exception('Server does not exist'); } $this->request('servers/' . $properties['server_uuid'], 'delete'); // Remove the server_uuid property $service->properties()->where('key', 'server_uuid')->delete(); } public function getActions(Service $service): array { return [ [ 'type' => 'button', 'label' => 'Go to Server', 'function' => 'ssoLink', ], ]; } public function ssoLink(Service $service): string { $data = $this->request('users/' . $this->getOrCreateUser($service->user)['id'] . '/generate-sso-token', 'post'); return rtrim($this->config('host'), '/') . '/authenticate?token=' . $data['data']['token']; } }