error('NETGESCON_UPDATE_URL non configurato.'); return self::FAILURE; } $channel = (string) ($this->option('channel') ?: config('distribution.default_channel', 'free')); $code = (string) ($this->option('code') ?: config('distribution.licensed_code', '')); $auth = (string) ($this->option('auth') ?: config('distribution.licensed_auth', '')); $nodeCode = (string) config('distribution.node_code', ''); $manifestUrl = $baseUrl . '/api/v1/distribution/updates/manifest'; $query = ['channel' => $channel]; if ($channel === 'licensed') { if ($code === '' || $auth === '') { $this->error('Canale licensed richiede --code e --auth (o variabili NETGESCON_LICENSED_*).'); return self::FAILURE; } $query['code'] = $code; $query['auth'] = $auth; } $timeout = (int) config('distribution.http_timeout_seconds', 60); $this->info("Controllo aggiornamenti su {$manifestUrl} (channel={$channel})..."); $response = Http::timeout($timeout) ->acceptJson() ->withHeaders(array_filter([ 'X-Netgescon-Node' => $nodeCode !== '' ? $nodeCode : null, ])) ->get($manifestUrl, $query); if (! $response->successful()) { $this->error('Manifest non raggiungibile: HTTP ' . $response->status()); return self::FAILURE; } /** @var array $manifest */ $manifest = $response->json(); $remoteVersion = (string) ($manifest['version'] ?? ''); $packageUrl = (string) ($manifest['package_url'] ?? ''); $hash = strtolower((string) ($manifest['sha256'] ?? '')); $migrateRequired = (bool) ($manifest['migrate_required'] ?? false); if ($remoteVersion === '' || $packageUrl === '' || $hash === '') { $this->error('Manifest incompleto: richiesti version/package_url/sha256.'); return self::FAILURE; } $currentVersion = (string) config('netgescon.version', '0.0.0'); $hasNewVersion = version_compare($remoteVersion, $currentVersion, '>'); $this->line('Versione corrente: ' . $currentVersion); $this->line('Versione remota: ' . $remoteVersion); if (! $hasNewVersion && ! $this->option('force')) { $this->info('Nessun aggiornamento necessario.'); return self::SUCCESS; } if (! $this->option('apply')) { $this->info('Update disponibile. Esegui con --apply per installarlo.'); return self::SUCCESS; } if ($this->option('dry-run')) { $this->warn('Dry-run: update rilevato, nessuna modifica applicata.'); return self::SUCCESS; } $tmpRoot = storage_path('app/update-temp'); $backupRoot = storage_path('app/update-backups'); File::ensureDirectoryExists($tmpRoot); File::ensureDirectoryExists($backupRoot); $stamp = date('Ymd-His'); $zipPath = $tmpRoot . '/netgescon-update-' . $stamp . '.zip'; $extractDir = $tmpRoot . '/extract-' . $stamp; $envBackup = $backupRoot . '/.env-' . $stamp; $this->info('Download pacchetto...'); $download = Http::timeout($timeout)->withOptions(['stream' => true])->get($packageUrl); if (! $download->successful()) { $this->error('Download fallito: HTTP ' . $download->status()); return self::FAILURE; } File::put($zipPath, $download->body()); $calcHash = strtolower(hash_file('sha256', $zipPath) ?: ''); if ($calcHash !== $hash) { $this->error('Hash SHA256 non valido. Atteso=' . $hash . ' calcolato=' . $calcHash); return self::FAILURE; } $this->info('Backup .env...'); if (File::exists(base_path('.env'))) { File::copy(base_path('.env'), $envBackup); } $this->info('Estrazione pacchetto...'); File::ensureDirectoryExists($extractDir); $zip = new ZipArchive(); if ($zip->open($zipPath) !== true) { $this->error('Impossibile aprire ZIP update.'); return self::FAILURE; } $zip->extractTo($extractDir); $zip->close(); $this->info('Applicazione file (esclusi .env e storage)...'); $this->syncDir($extractDir, base_path()); if (File::exists($envBackup)) { File::copy($envBackup, base_path('.env')); } $this->call('optimize:clear'); if ($migrateRequired) { $this->warn('Manifest richiede migrate: esecuzione php artisan migrate --force'); $this->call('migrate', ['--force' => true]); } $this->info('Aggiornamento applicato con successo.'); return self::SUCCESS; } private function syncDir(string $from, string $to): void { $items = File::allFiles($from); foreach ($items as $item) { $path = $item->getPathname(); $relative = ltrim(str_replace($from, '', $path), '/'); if ($relative === '.env' || str_starts_with($relative, 'storage/') || str_starts_with($relative, '.git/')) { continue; } $target = $to . '/' . $relative; File::ensureDirectoryExists(dirname($target)); File::copy($path, $target); } } }