loadMissing('order', 'agent'); $references = match ($task->type) { ServerTask::TYPE_DOMAIN_ADD => $this->persistDomainTask($task), ServerTask::TYPE_SITE_DELETE => $this->persistSiteDeleteTask($task), ServerTask::TYPE_SSL_REQUEST => $this->persistSslTask($task), ServerTask::TYPE_SSL_RENEW => $this->persistSslRenewTask($task), ServerTask::TYPE_DATABASE_CREATE => $this->persistDatabaseTask($task), ServerTask::TYPE_PHP_CONFIGURE => $this->persistPhpTask($task), ServerTask::TYPE_CRON_LIST, ServerTask::TYPE_CRON_UPSERT, ServerTask::TYPE_CRON_REMOVE => $this->persistCronTask($task), ServerTask::TYPE_LOG_READ => $this->persistLogTask($task), ServerTask::TYPE_FILE_LIST, ServerTask::TYPE_FILE_READ, ServerTask::TYPE_FILE_WRITE => $this->persistFileTask($task), default => [], }; if ($references !== []) { $result = (array) ($task->result ?? []); $result['model_refs'] = array_merge((array) ($result['model_refs'] ?? []), $references); $task->forceFill(['result' => $result])->save(); } return $task->fresh(); } /** * @return array */ private function persistDomainTask(ServerTask $task): array { $result = (array) ($task->result ?? []); $domainName = Str::lower((string) ($result['domain'] ?? data_get($task->payload, 'domain', ''))); if ($domainName === '') { return []; } $order = $task->order()->first(); $domain = Domain::query()->where('host', $domainName)->first(); if ($domain === null && $order !== null) { $domain = Domain::create([ 'user_id' => $order->user_id, 'host' => $domainName, 'type' => 'server', 'source' => 'server_manager', 'status' => 'pending', 'onboarding_mode' => Domain::MODE_MANUAL_DNS, 'onboarding_state' => Domain::STATE_CONNECTED, 'dns_mode' => 'manual', 'connected_at' => now(), 'verification_meta' => [ 'source' => 'server_agent_task', 'customer_hosting_order_id' => $task->customer_hosting_order_id, ], ]); } elseif ($domain !== null) { $verificationMeta = array_merge((array) ($domain->verification_meta ?? []), [ 'server_agent_task_id' => $task->id, 'customer_hosting_order_id' => $task->customer_hosting_order_id, ]); $domain->fill([ 'user_id' => $domain->user_id ?: $order?->user_id, 'source' => $domain->source ?: 'server_manager', 'connected_at' => $domain->connected_at ?? now(), 'verification_meta' => $verificationMeta, ])->save(); } $site = ServerSite::query()->updateOrCreate( [ 'customer_hosting_order_id' => $task->customer_hosting_order_id, 'domain' => $domainName, ], [ 'server_agent_id' => $task->server_agent_id, 'latest_server_task_id' => $task->id, 'domain_id' => $domain?->id, 'document_root' => (string) ($result['document_root'] ?? data_get($task->payload, 'document_root', '')) ?: null, 'php_version' => (string) data_get($task->payload, 'php_version', '') ?: null, 'php_socket' => (string) ($result['php_socket'] ?? '') ?: null, 'status' => 'active', 'metadata' => array_filter([ 'task_type' => $task->type, 'server_task_id' => $task->id, 'payload' => $task->payload, 'result' => Arr::except($result, ['message', 'model_refs']), ], static fn ($value): bool => $value !== null && $value !== []), ] ); return array_filter([ 'domain_id' => $domain?->id, 'server_site_id' => $site->id, ]); } /** * @return array */ private function persistSiteDeleteTask(ServerTask $task): array { $result = (array) ($task->result ?? []); $domainName = Str::lower((string) ($result['domain'] ?? data_get($task->payload, 'domain', ''))); if ($domainName === '') { return []; } $site = ServerSite::query() ->where('customer_hosting_order_id', $task->customer_hosting_order_id) ->where('domain', $domainName) ->first(); if ($site === null) { return []; } $metadata = array_merge((array) ($site->metadata ?? []), [ 'removed_at' => now()->toIso8601String(), 'removed_by_task_id' => $task->id, ]); $site->fill([ 'server_agent_id' => $task->server_agent_id, 'latest_server_task_id' => $task->id, 'status' => 'removed', 'ssl_status' => null, 'metadata' => $metadata, ])->save(); return [ 'server_site_id' => $site->id, ]; } /** * @return array */ private function persistSslTask(ServerTask $task): array { $result = (array) ($task->result ?? []); $domainName = Str::lower((string) ($result['domain'] ?? data_get($task->payload, 'domain', ''))); if ($domainName === '') { return []; } $site = ServerSite::query() ->where('customer_hosting_order_id', $task->customer_hosting_order_id) ->where('domain', $domainName) ->first(); if ($site === null) { $site = ServerSite::create([ 'customer_hosting_order_id' => $task->customer_hosting_order_id, 'server_agent_id' => $task->server_agent_id, 'latest_server_task_id' => $task->id, 'domain' => $domainName, 'status' => 'active', 'ssl_status' => (string) ($result['ssl'] ?? 'issued'), 'ssl_provisioned_at' => now(), 'metadata' => [ 'created_from' => 'ssl.request', 'server_task_id' => $task->id, ], ]); } else { $site->fill([ 'server_agent_id' => $task->server_agent_id, 'latest_server_task_id' => $task->id, 'ssl_status' => (string) ($result['ssl'] ?? 'issued'), 'ssl_provisioned_at' => now(), ])->save(); } if ($site->domain_id) { $domain = Domain::query()->find($site->domain_id); } else { $domain = Domain::query()->where('host', $domainName)->first(); } if ($domain !== null) { $domain->fill([ 'ssl_status' => (string) ($result['ssl'] ?? 'issued'), 'ssl_provisioned_at' => now(), ])->save(); if ($site->domain_id === null) { $site->forceFill(['domain_id' => $domain->id])->save(); } } return array_filter([ 'domain_id' => $domain->id ?? null, 'server_site_id' => $site->id, ]); } /** * @return array */ private function persistSslRenewTask(ServerTask $task): array { $expiresAt = now()->addDays(90); ServerSite::query() ->where('customer_hosting_order_id', $task->customer_hosting_order_id) ->whereIn('ssl_status', ['issued', 'active']) ->update([ 'ssl_expires_at' => $expiresAt, 'ssl_provisioned_at' => now(), ]); $domainIds = ServerSite::query() ->where('customer_hosting_order_id', $task->customer_hosting_order_id) ->whereNotNull('domain_id') ->pluck('domain_id'); if ($domainIds->isNotEmpty()) { Domain::query() ->whereIn('id', $domainIds) ->where('ssl_status', 'active') ->update(['ssl_expires_at' => $expiresAt]); } return []; } /** * @return array */ private function persistDatabaseTask(ServerTask $task): array { $result = (array) ($task->result ?? []); $databaseName = (string) ($result['database'] ?? data_get($task->payload, 'name', '')); if ($databaseName === '') { return []; } $database = ServerDatabase::query()->updateOrCreate( [ 'customer_hosting_order_id' => $task->customer_hosting_order_id, 'name' => $databaseName, ], [ 'server_agent_id' => $task->server_agent_id, 'latest_server_task_id' => $task->id, 'username' => (string) ($result['username'] ?? $databaseName) ?: null, 'password_encrypted' => isset($result['password']) && $result['password'] !== '' ? encrypt((string) $result['password']) : null, 'engine' => 'mariadb', 'status' => 'active', 'metadata' => array_filter([ 'server_task_id' => $task->id, 'payload' => $task->payload, 'result' => Arr::except($result, ['message', 'model_refs', 'password']), ], static fn ($value): bool => $value !== null && $value !== []), ] ); return [ 'server_database_id' => $database->id, ]; } /** * @return array */ private function persistPhpTask(ServerTask $task): array { $result = (array) ($task->result ?? []); $domainName = Str::lower((string) ($result['domain'] ?? data_get($task->payload, 'domain', ''))); if ($domainName === '') { return []; } $site = ServerSite::query()->updateOrCreate( [ 'customer_hosting_order_id' => $task->customer_hosting_order_id, 'domain' => $domainName, ], [ 'server_agent_id' => $task->server_agent_id, 'latest_server_task_id' => $task->id, 'php_version' => (string) ($result['php_version'] ?? data_get($task->payload, 'php_version', '')) ?: null, 'php_socket' => (string) ($result['php_socket'] ?? '') ?: null, 'status' => 'active', 'metadata' => array_filter([ 'php_configured_at' => now()->toIso8601String(), 'server_task_id' => $task->id, 'result' => Arr::except($result, ['message', 'model_refs']), ], static fn ($value): bool => $value !== null && $value !== []), ] ); return [ 'server_site_id' => $site->id, ]; } /** * @return array */ private function persistCronTask(ServerTask $task): array { $entries = array_values(array_filter((array) data_get($task->result, 'entries', []), 'is_array')); $order = $task->order()->first(); if ($order !== null) { $meta = (array) ($order->meta ?? []); $panelState = (array) ($meta['server_panel'] ?? []); $panelState['managed_cron_entries'] = $entries; $panelState['managed_cron_synced_at'] = now()->toIso8601String(); $meta['server_panel'] = $panelState; $order->update(['meta' => $meta]); } return [ 'cron_entry_count' => count($entries), ]; } /** * @return array */ private function persistLogTask(ServerTask $task): array { $result = (array) ($task->result ?? []); $contentBase64 = (string) ($result['content_base64'] ?? ''); $content = $contentBase64 !== '' ? base64_decode($contentBase64, true) : false; $content = is_string($content) ? $content : ''; $target = (string) ($result['target'] ?? data_get($task->payload, 'target', 'log')); $domain = (string) ($result['domain'] ?? data_get($task->payload, 'domain', '')); $snapshotKey = $target.($domain !== '' ? ':'.$domain : ''); $order = $task->order()->first(); if ($order !== null) { $meta = (array) ($order->meta ?? []); $panelState = (array) ($meta['server_panel'] ?? []); $panelState['log_snapshots'] = array_merge((array) ($panelState['log_snapshots'] ?? []), [ $snapshotKey => [ 'target' => $target, 'domain' => $domain !== '' ? $domain : null, 'lines' => (int) ($result['lines'] ?? data_get($task->payload, 'lines', 0)), 'path' => $result['path'] ?? null, 'read_at' => now()->toIso8601String(), 'content_preview' => $this->previewForContent($content), ], ]); $meta['server_panel'] = $panelState; $order->update(['meta' => $meta]); } return [ 'log_snapshot_key' => $snapshotKey, ]; } /** * @return array */ private function persistFileTask(ServerTask $task): array { $result = (array) ($task->result ?? []); return match ($task->type) { ServerTask::TYPE_FILE_LIST => $this->persistFileListTask($task, $result), ServerTask::TYPE_FILE_READ => $this->persistFileReadTask($task, $result), ServerTask::TYPE_FILE_WRITE => $this->persistFileWriteTask($task, $result), default => [], }; } /** * @param array $result * @return array */ private function persistFileListTask(ServerTask $task, array $result): array { $basePath = (string) data_get($task->payload, 'path', ''); $entries = array_values(array_filter((array) ($result['entries'] ?? []), 'is_array')); $recordIds = []; if ($basePath !== '') { $rootRecord = ServerFile::query()->updateOrCreate( [ 'customer_hosting_order_id' => $task->customer_hosting_order_id, 'path' => $basePath, ], [ 'server_agent_id' => $task->server_agent_id, 'latest_server_task_id' => $task->id, 'kind' => 'directory', 'exists' => true, 'metadata' => [ 'entry_count' => count($entries), 'server_task_id' => $task->id, ], 'last_synced_at' => now(), ] ); $recordIds[] = $rootRecord->id; } foreach ($entries as $entry) { $path = (string) ($entry['path'] ?? ''); if ($path === '') { continue; } $record = ServerFile::query()->updateOrCreate( [ 'customer_hosting_order_id' => $task->customer_hosting_order_id, 'path' => $path, ], [ 'server_agent_id' => $task->server_agent_id, 'latest_server_task_id' => $task->id, 'kind' => $this->normalizeFileKind((string) ($entry['type'] ?? 'f')), 'exists' => true, 'size_bytes' => is_numeric($entry['size'] ?? null) ? (int) $entry['size'] : null, 'metadata' => [ 'listed_name' => $entry['name'] ?? basename($path), 'server_task_id' => $task->id, ], 'last_synced_at' => now(), ] ); $recordIds[] = $record->id; } return [ 'server_file_ids' => array_values(array_unique($recordIds)), ]; } /** * @param array $result * @return array */ private function persistFileReadTask(ServerTask $task, array $result): array { $path = (string) ($result['path'] ?? data_get($task->payload, 'path', '')); if ($path === '') { return []; } $contentBase64 = (string) ($result['content_base64'] ?? ''); $content = $contentBase64 !== '' ? base64_decode($contentBase64, true) : false; $content = is_string($content) ? $content : ''; $file = ServerFile::query()->updateOrCreate( [ 'customer_hosting_order_id' => $task->customer_hosting_order_id, 'path' => $path, ], [ 'server_agent_id' => $task->server_agent_id, 'latest_server_task_id' => $task->id, 'kind' => 'file', 'exists' => true, 'size_bytes' => strlen($content), 'content_hash' => $content !== '' ? hash('sha256', $content) : null, 'content_preview' => $this->previewForContent($content), 'metadata' => [ 'encoding' => 'base64', 'server_task_id' => $task->id, ], 'last_synced_at' => now(), ] ); return [ 'server_file_id' => $file->id, ]; } /** * @param array $result * @return array */ private function persistFileWriteTask(ServerTask $task, array $result): array { $path = (string) ($result['path'] ?? data_get($task->payload, 'path', '')); if ($path === '') { return []; } $content = (string) data_get($task->payload, 'content', ''); $file = ServerFile::query()->updateOrCreate( [ 'customer_hosting_order_id' => $task->customer_hosting_order_id, 'path' => $path, ], [ 'server_agent_id' => $task->server_agent_id, 'latest_server_task_id' => $task->id, 'kind' => 'file', 'exists' => true, 'size_bytes' => strlen($content), 'content_hash' => $content !== '' ? hash('sha256', $content) : null, 'content_preview' => $this->previewForContent($content), 'metadata' => [ 'server_task_id' => $task->id, 'write_source' => 'server_agent', ], 'last_synced_at' => now(), ] ); return [ 'server_file_id' => $file->id, ]; } private function normalizeFileKind(string $type): string { return match ($type) { 'd' => 'directory', 'l' => 'symlink', default => 'file', }; } private function previewForContent(string $content): ?string { if ($content === '') { return null; } if (preg_match('//u', $content) !== 1) { return Str::limit(base64_encode(substr($content, 0, 2000)), 4000, ''); } return (string) Str::of($content) ->replaceMatches('/[^\P{C}\t\r\n]/u', '') ->limit(4000, ''); } }