From 1f04979ffe25903f9c587cbfb08d6e8249f68077 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 3 Jun 2026 22:54:39 +0200 Subject: [PATCH] Remove all auto-update functionality (commands, services, widgets, blades, translations) --- .../Commandocentrum/EmulatorControlAction.php | 27 - .../Commandocentrum/NitroControlAction.php | 119 - app/Console/Commands/AutoUpdateCommand.php | 150 - .../Commands/EmulatorUpdateCommand.php | 172 - app/Console/Commands/GenerateNitroConfigs.php | 796 ----- app/Console/Commands/NitroUpdateCommand.php | 272 -- app/Console/Commands/SwitchNitroBranch.php | 49 - app/Console/Commands/SystemHealthCommand.php | 251 -- app/Console/Commands/SystemRepairCommand.php | 213 -- app/Console/Kernel.php | 15 - .../Pages/Monitoring/AlertSettings.php | 1904 +---------- .../Pages/Monitoring/Commandocentrum.php | 330 -- .../AlertLogResource/AlertLogResource.php | 5 +- app/Filament/Widgets/UpdateCheckerWidget.php | 343 -- .../Emulator/EmulatorBuildService.php | 272 -- app/Services/Emulator/EmulatorJarService.php | 509 --- .../Emulator/EmulatorSourceService.php | 273 -- app/Services/Emulator/EmulatorSqlService.php | 510 --- app/Services/EmulatorUpdateService.php | 407 --- app/Services/NitroUpdateService.php | 1358 -------- app/Services/UpdateHistoryService.php | 186 - config/boost.php | 0 config/cors.php | 8 +- deploy.sh | 43 - install-windows.ps1 | 0 lang/ar.json | 3022 ++++++++--------- lang/br.json | 3010 ++++++++-------- lang/cs.json | 3010 ++++++++-------- lang/da.json | 3010 ++++++++-------- lang/de.json | 3022 ++++++++--------- lang/el.json | 3010 ++++++++-------- lang/en.json | 653 ++-- lang/es.json | 3022 ++++++++--------- lang/fi.json | 3010 ++++++++-------- lang/fr.json | 3022 ++++++++--------- lang/hu.json | 3012 ++++++++-------- lang/it.json | 3022 ++++++++--------- lang/nl.json | 655 ++-- lang/no.json | 3010 ++++++++-------- lang/pl.json | 3022 ++++++++--------- lang/pt.json | 3022 ++++++++--------- lang/ro.json | 3010 ++++++++-------- lang/ru.json | 3022 ++++++++--------- lang/se.json | 3010 ++++++++-------- lang/tr.json | 3022 ++++++++--------- lang/uk.json | 3010 ++++++++-------- phpstan-baseline.neon | 2569 -------------- phpstan.neon | 8 - public/build/assets/app-9mCg36im.css | 1 + public/build/assets/app-BBe1NbvP.js | 48 - public/build/assets/app-CeYfhhVD.css | 1 + public/build/assets/app-DU8Y3NnC.css | 1 - public/build/assets/app-Dnwim5HE.js | 13 - public/build/assets/app-DrKvMbgn.css | 1 - public/build/assets/app-tY2AX6sE.css | 1 - public/build/assets/app-wUWplMFd.js | 54 + public/build/assets/article-CYhGsSKA.gif | Bin public/build/assets/axios-B2OU-7LW.js | 9 + public/build/assets/axios-BgauIbGG.js | 9 - .../build/assets/background-dark-BfkMu3-0.jpg | Bin .../assets/background-light-CP7oKwVT.jpg | Bin .../assets/background_image-BH7pVpv1.png | Bin 261731 -> 0 bytes public/build/assets/camera-fu-bmGhB.png | Bin public/build/assets/catalog-D-956oDx.png | Bin public/build/assets/chat-r5H1PnTg.png | Bin public/build/assets/chunk-BqhQeaEc.js | 1 - public/build/assets/community-Do_t1zw9.png | Bin public/build/assets/credits-Dpg5Nmby.png | Bin public/build/assets/currency-LKXzczCA.png | Bin public/build/assets/diamonds-BtfqKoQu.png | Bin public/build/assets/discord-rbcnEh-j.png | Bin public/build/assets/duckets-CaGJI1Oy.png | Bin public/build/assets/due-chat-CeO4yxLu.png | Bin public/build/assets/feeds-BtHcJdHX.png | Bin public/build/assets/friends-BxpcKlvz.png | Bin public/build/assets/global-Bkbv5Qui.js | 20 - public/build/assets/global-DVSCrBhf.css | 1 - public/build/assets/global-TU7mFC54.js | 20 + public/build/assets/global-owlIrRiH.css | 1 + public/build/assets/home-DIMFC97Y.png | Bin public/build/assets/inventory-BlHYLNGT.png | Bin .../leaderboard_circle_image-BYkDVX69.png | Bin 5062 -> 0 bytes public/build/assets/leaderboards-CGasq3cL.png | Bin public/build/assets/lighthouse-BON6qnQ0.png | Bin public/build/assets/profile-bg-BWx4iuHa.png | Bin public/build/assets/rules--xzBmecz.gif | Bin public/build/assets/shop-D3NfN6cF.png | Bin public/build/assets/ssr-dFzH1dUH.js | 97 - public/build/assets/store_icon-B52tsSKO.png | Bin 3099 -> 0 bytes public/build/assets/swiper-B7Yxk788.js | 7 - public/build/assets/swiper-CrMA9oas.css | 1 - .../build/assets/trophy-bronze-CgV5j1MU.png | Bin public/build/assets/trophy-gold-bbKmpkii.png | Bin .../build/assets/trophy-silver-bGfHJkQ_.png | Bin public/build/manifest.json | 99 +- .../components/community/staff-card.blade.php | 2 +- resources/themes/atom/views/user/me.blade.php | 245 +- .../commandocentrum/backups-list.blade.php | 38 - .../emulator-settings.blade.php | 49 - .../commandocentrum/emulator-status.blade.php | 85 - .../commandocentrum/nitro-settings.blade.php | 41 - .../commandocentrum/nitro-status.blade.php | 65 - .../commandocentrum/update-history.blade.php | 23 - .../filament/widgets/update-checker.blade.php | 73 - update-Nitrov3.sh | 111 + update_uitexts.sh | 66 - 106 files changed, 29572 insertions(+), 41008 deletions(-) delete mode 100755 app/Actions/Commandocentrum/NitroControlAction.php delete mode 100755 app/Console/Commands/AutoUpdateCommand.php delete mode 100755 app/Console/Commands/EmulatorUpdateCommand.php delete mode 100755 app/Console/Commands/GenerateNitroConfigs.php delete mode 100755 app/Console/Commands/NitroUpdateCommand.php delete mode 100755 app/Console/Commands/SwitchNitroBranch.php delete mode 100755 app/Console/Commands/SystemHealthCommand.php delete mode 100755 app/Console/Commands/SystemRepairCommand.php delete mode 100755 app/Filament/Widgets/UpdateCheckerWidget.php delete mode 100755 app/Services/Emulator/EmulatorBuildService.php delete mode 100755 app/Services/Emulator/EmulatorJarService.php delete mode 100755 app/Services/Emulator/EmulatorSourceService.php delete mode 100755 app/Services/Emulator/EmulatorSqlService.php delete mode 100755 app/Services/EmulatorUpdateService.php delete mode 100755 app/Services/NitroUpdateService.php delete mode 100755 app/Services/UpdateHistoryService.php mode change 100644 => 100755 config/boost.php delete mode 100755 deploy.sh mode change 100644 => 100755 install-windows.ps1 mode change 100755 => 100644 lang/ar.json mode change 100755 => 100644 lang/br.json mode change 100755 => 100644 lang/cs.json mode change 100755 => 100644 lang/da.json mode change 100755 => 100644 lang/de.json mode change 100755 => 100644 lang/el.json mode change 100755 => 100644 lang/en.json mode change 100755 => 100644 lang/es.json mode change 100755 => 100644 lang/fi.json mode change 100755 => 100644 lang/fr.json mode change 100755 => 100644 lang/hu.json mode change 100755 => 100644 lang/it.json mode change 100755 => 100644 lang/nl.json mode change 100755 => 100644 lang/no.json mode change 100755 => 100644 lang/pl.json mode change 100755 => 100644 lang/pt.json mode change 100755 => 100644 lang/ro.json mode change 100755 => 100644 lang/ru.json mode change 100755 => 100644 lang/se.json mode change 100755 => 100644 lang/tr.json mode change 100755 => 100644 lang/uk.json delete mode 100755 phpstan-baseline.neon delete mode 100755 phpstan.neon create mode 100755 public/build/assets/app-9mCg36im.css delete mode 100644 public/build/assets/app-BBe1NbvP.js create mode 100755 public/build/assets/app-CeYfhhVD.css delete mode 100644 public/build/assets/app-DU8Y3NnC.css delete mode 100644 public/build/assets/app-Dnwim5HE.js delete mode 100644 public/build/assets/app-DrKvMbgn.css delete mode 100644 public/build/assets/app-tY2AX6sE.css create mode 100755 public/build/assets/app-wUWplMFd.js mode change 100644 => 100755 public/build/assets/article-CYhGsSKA.gif create mode 100755 public/build/assets/axios-B2OU-7LW.js delete mode 100644 public/build/assets/axios-BgauIbGG.js mode change 100644 => 100755 public/build/assets/background-dark-BfkMu3-0.jpg mode change 100644 => 100755 public/build/assets/background-light-CP7oKwVT.jpg delete mode 100644 public/build/assets/background_image-BH7pVpv1.png mode change 100644 => 100755 public/build/assets/camera-fu-bmGhB.png mode change 100644 => 100755 public/build/assets/catalog-D-956oDx.png mode change 100644 => 100755 public/build/assets/chat-r5H1PnTg.png delete mode 100644 public/build/assets/chunk-BqhQeaEc.js mode change 100644 => 100755 public/build/assets/community-Do_t1zw9.png mode change 100644 => 100755 public/build/assets/credits-Dpg5Nmby.png mode change 100644 => 100755 public/build/assets/currency-LKXzczCA.png mode change 100644 => 100755 public/build/assets/diamonds-BtfqKoQu.png mode change 100644 => 100755 public/build/assets/discord-rbcnEh-j.png mode change 100644 => 100755 public/build/assets/duckets-CaGJI1Oy.png mode change 100644 => 100755 public/build/assets/due-chat-CeO4yxLu.png mode change 100644 => 100755 public/build/assets/feeds-BtHcJdHX.png mode change 100644 => 100755 public/build/assets/friends-BxpcKlvz.png delete mode 100644 public/build/assets/global-Bkbv5Qui.js delete mode 100644 public/build/assets/global-DVSCrBhf.css create mode 100755 public/build/assets/global-TU7mFC54.js create mode 100755 public/build/assets/global-owlIrRiH.css mode change 100644 => 100755 public/build/assets/home-DIMFC97Y.png mode change 100644 => 100755 public/build/assets/inventory-BlHYLNGT.png delete mode 100644 public/build/assets/leaderboard_circle_image-BYkDVX69.png mode change 100644 => 100755 public/build/assets/leaderboards-CGasq3cL.png mode change 100644 => 100755 public/build/assets/lighthouse-BON6qnQ0.png mode change 100644 => 100755 public/build/assets/profile-bg-BWx4iuHa.png mode change 100644 => 100755 public/build/assets/rules--xzBmecz.gif mode change 100644 => 100755 public/build/assets/shop-D3NfN6cF.png delete mode 100644 public/build/assets/ssr-dFzH1dUH.js delete mode 100644 public/build/assets/store_icon-B52tsSKO.png delete mode 100644 public/build/assets/swiper-B7Yxk788.js delete mode 100644 public/build/assets/swiper-CrMA9oas.css mode change 100644 => 100755 public/build/assets/trophy-bronze-CgV5j1MU.png mode change 100644 => 100755 public/build/assets/trophy-gold-bbKmpkii.png mode change 100644 => 100755 public/build/assets/trophy-silver-bGfHJkQ_.png mode change 100644 => 100755 public/build/manifest.json delete mode 100755 resources/views/filament/components/commandocentrum/backups-list.blade.php delete mode 100755 resources/views/filament/components/commandocentrum/emulator-settings.blade.php delete mode 100755 resources/views/filament/components/commandocentrum/emulator-status.blade.php delete mode 100755 resources/views/filament/components/commandocentrum/nitro-settings.blade.php delete mode 100755 resources/views/filament/components/commandocentrum/nitro-status.blade.php delete mode 100755 resources/views/filament/components/commandocentrum/update-history.blade.php delete mode 100755 resources/views/filament/widgets/update-checker.blade.php create mode 100644 update-Nitrov3.sh delete mode 100755 update_uitexts.sh diff --git a/app/Actions/Commandocentrum/EmulatorControlAction.php b/app/Actions/Commandocentrum/EmulatorControlAction.php index ee69904..4ec5824 100755 --- a/app/Actions/Commandocentrum/EmulatorControlAction.php +++ b/app/Actions/Commandocentrum/EmulatorControlAction.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Actions\Commandocentrum; -use App\Services\EmulatorUpdateService; use App\Services\RconService; use App\Services\SettingsService; use Illuminate\Support\Facades\Process; @@ -13,7 +12,6 @@ class EmulatorControlAction { public function __construct( private readonly SettingsService $settings, - private readonly EmulatorUpdateService $updateService, ) {} public function start(): array @@ -59,29 +57,4 @@ class EmulatorControlAction return ['success' => true, 'message' => 'Alert verstuurd naar alle gebruikers!']; } - - public function build(): array - { - return $this->updateService->buildFromSource(); - } - - public function update(): array - { - return $this->updateService->updateEmulator(); - } - - public function runSqlUpdates(): array - { - return $this->updateService->runSqlUpdates(); - } - - public function getBackups(): array - { - return $this->updateService->getBackupList(); - } - - public function restoreBackup(string $backupName): array - { - return $this->updateService->restoreBackup($backupName); - } } diff --git a/app/Actions/Commandocentrum/NitroControlAction.php b/app/Actions/Commandocentrum/NitroControlAction.php deleted file mode 100755 index 9159913..0000000 --- a/app/Actions/Commandocentrum/NitroControlAction.php +++ /dev/null @@ -1,119 +0,0 @@ -runGitPull($clientPath, $branch); - $this->runGitPull($rendererPath, $branch); - - return ['success' => true, 'message' => 'Nitro bijgewerkt van GitHub']; - } - - public function build(string $clientPath, string $rendererPath, string $branch): array - { - $this->runGitPull($clientPath, $branch); - $this->runGitPull($rendererPath, $branch); - - Process::timeout(120)->run('cd ' . escapeshellarg($clientPath) . ' && sudo -u www-data npm install 2>&1'); - Process::timeout(120)->run('cd ' . escapeshellarg($rendererPath) . ' && sudo -u www-data npm install 2>&1'); - - $exitCode = Artisan::call('build:theme'); - - return [ - 'success' => $exitCode === 0, - 'message' => $exitCode === 0 ? 'Nitro build succesvol!' : 'Build gestart - controleer handmatig', - ]; - } - - public function generateConfigs(string $siteUrl, string $webroot, string $gamedataPath): array - { - if (! filter_var($siteUrl, FILTER_VALIDATE_URL)) { - return ['success' => false, 'message' => 'Voer een geldige URL in']; - } - - $existingConfigs = $this->readExistingConfigs($webroot, $gamedataPath); - - $exitCode = Artisan::call('app:generate-nitro-configs', ['--site-url' => $siteUrl]); - - if ($existingConfigs !== [] && $exitCode === 0) { - $this->mergeExistingConfigs($webroot, $existingConfigs); - } - - $this->settings->set('nitro_last_checked', now()->toIso8601String()); - - return [ - 'success' => $exitCode === 0, - 'message' => $exitCode === 0 ? 'Configs gegenereerd & bestaande instellingen behouden!' : 'Config gegenereerd (controleer handmatig)', - ]; - } - - private function runGitPull(string $path, string $branch): void - { - Process::timeout(60)->run('cd ' . escapeshellarg($path) . ' && sudo -u www-data git pull origin ' . escapeshellarg($branch) . ' 2>&1'); - } - - private function readExistingConfigs(string $webroot, string $gamedataPath): array - { - $configs = []; - $files = ['renderer-config.json', 'ui-config.json', 'UITexts.json']; - - foreach ($files as $file) { - $path = $webroot . '/' . $file; - $content = @file_get_contents($path); - if ($content) { - $decoded = json_decode($content, true); - if (json_last_error() === JSON_ERROR_NONE) { - $configs[$file] = $decoded; - } - } - } - - if ($gamedataPath !== '') { - $gamedataConfigs = [ - 'ExternalTexts.json' => $gamedataPath . '/config/ExternalTexts.json', - 'FurnitureData.json' => $gamedataPath . '/config/FurnitureData.json', - 'ProductData.json' => $gamedataPath . '/config/ProductData.json', - 'FigureData.json' => $gamedataPath . '/config/FigureData.json', - ]; - - foreach ($gamedataConfigs as $key => $path) { - $content = @file_get_contents($path); - if ($content) { - $decoded = json_decode($content, true); - if (json_last_error() === JSON_ERROR_NONE) { - $configs['gamedata.' . $key] = $decoded; - } - } - } - } - - return $configs; - } - - private function mergeExistingConfigs(string $webroot, array $existingConfigs): void - { - if (isset($existingConfigs['renderer-config.json'])) { - $newPath = $webroot . '/renderer-config.json'; - $newContent = @file_get_contents($newPath); - $newConfig = json_decode($newContent, true); - - if ($newConfig && json_last_error() === JSON_ERROR_NONE) { - $merged = array_merge($existingConfigs['renderer-config.json'], $newConfig); - @file_put_contents($newPath, json_encode($merged, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); - } - } - } -} diff --git a/app/Console/Commands/AutoUpdateCommand.php b/app/Console/Commands/AutoUpdateCommand.php deleted file mode 100755 index 056cc69..0000000 --- a/app/Console/Commands/AutoUpdateCommand.php +++ /dev/null @@ -1,150 +0,0 @@ -info('Automatische update check...'); - - $isForced = $this->option('force'); - $sqlOnly = $this->option('sql-only'); - $emuOnly = $this->option('emu-only'); - - if (! $isForced && ! $this->isScheduledTime()) { - $this->line('Niet de geplande tijd, overslaan.'); - - return Command::SUCCESS; - } - - if (! $sqlOnly) { - $this->runEmulatorUpdate($alertService, $updateService); - } - - if (! $emuOnly) { - $this->runSqlUpdates($alertService, $updateService); - } - - return Command::SUCCESS; - } - - private function isScheduledTime(): bool - { - $enabled = setting('auto_update_enabled', true); - - if (! $enabled) { - return true; - } - - $scheduleTime = setting('auto_update_schedule', '03:00'); - $scheduleDays = setting('auto_update_days', '0,1,2,3,4,5,6'); - - $now = now(); - $currentTime = $now->format('H:i'); - $currentDay = (int) $now->dayOfWeek; - - $allowedDays = array_map(intval(...), explode(',', $scheduleDays)); - - if (! in_array($currentDay, $allowedDays)) { - return false; - } - - if ($currentTime !== $scheduleTime) { - $minuteDiff = abs(strtotime($currentTime) - strtotime((string) $scheduleTime)) / 60; - if ($minuteDiff > 5) { - return false; - } - } - - return true; - } - - private function runEmulatorUpdate(AlertService $alertService, EmulatorUpdateService $updateService): void - { - $check = $updateService->checkForUpdates(); - - if (! ($check['update_available'] ?? false)) { - $this->line('Emulator is al up-to-date.'); - - return; - } - - $this->warn('Nieuwe emulator versie beschikbaar: ' . ($check['latest_version'] ?? 'onbekend')); - $this->info('Emulator updaten...'); - - $result = $updateService->updateEmulator(); - - if ($result['success']) { - $this->info('Emulator succesvol geรผpdatet!'); - $alertService->sendEmulatorUpdate($result['version'] ?? 'onbekend', $result['message'] ?? ''); - Log::info('[AutoUpdate] Emulator updated to v' . ($result['version'] ?? 'unknown')); - } else { - $this->error('Emulator update mislukt: ' . ($result['error'] ?? 'onbekende fout')); - $alertService->send( - AlertType::EMULATOR_ERROR, - 'Emulator Auto-Update Mislukt: ' . ($result['error'] ?? 'Onbekende fout'), - [], - AlertChannel::DISCORD, - ); - } - - Cache::put(self::CACHE_KEY_LAST_EMU_UPDATE, now()->toIso8601String()); - } - - private function runSqlUpdates(AlertService $alertService, EmulatorUpdateService $updateService): void - { - $check = $updateService->checkForSqlUpdates(); - - if (! ($check['has_updates'] ?? false)) { - $this->line('Geen SQL updates beschikbaar.'); - - return; - } - - $this->warn($check['message']); - $this->info('SQL updates uitvoeren...'); - - $result = $updateService->runSqlUpdates(); - - if ($result['success'] && ! empty($result['files_run'])) { - $count = count($result['files_run']); - $this->info("{$count} SQL updates succesvol uitgevoerd!"); - $alertService->sendSqlUpdate($count, $result['message'] ?? ''); - Log::info('[AutoUpdate] SQL updates completed', ['files' => $result['files_run']]); - } elseif (! empty($result['errors'])) { - $this->warn('SQL updates met fouten: ' . implode(', ', $result['errors'])); - $alertService->send( - AlertType::CRITICAL_ERROR, - 'SQL Updates Met Fouten: ' . implode(', ', $result['errors']), - ['files' => $result['errors']], - AlertChannel::DISCORD, - ); - } - - Cache::put(self::CACHE_KEY_LAST_SQL_UPDATE, now()->toIso8601String()); - } -} diff --git a/app/Console/Commands/EmulatorUpdateCommand.php b/app/Console/Commands/EmulatorUpdateCommand.php deleted file mode 100755 index a24c5a8..0000000 --- a/app/Console/Commands/EmulatorUpdateCommand.php +++ /dev/null @@ -1,172 +0,0 @@ -option('repair')) { - return $this->repairEmulator($updateService, $alertService); - } - - if (! $updateService->isConfigured()) { - $this->error('Geen GitHub URL geconfigureerd voor emulator updates.'); - $this->info('Configureer dit in Filament > Settings > Emulator'); - - return Command::FAILURE; - } - - $this->info('Emulator update service gestart...'); - - $checkResult = $updateService->checkForUpdates(); - - if (isset($checkResult['error'])) { - $this->error($checkResult['error']); - - return Command::FAILURE; - } - - $this->table( - ['Property', 'Value'], - [ - ['Huidige Versie', $checkResult['current_version']], - ['Nieuwste Versie', $checkResult['latest_version']], - ['Update Beschikbaar', $checkResult['update_available'] ? 'JA' : 'NEE'], - ['Type', $checkResult['update_type'] ?? 'N/A'], - ], - ); - - if ($this->option('check')) { - return Command::SUCCESS; - } - - $force = $this->option('force'); - $rebuild = $this->option('rebuild'); - - if (! $checkResult['update_available'] && ! $force && ! $rebuild) { - $this->info('Emulator is al up-to-date!'); - - return Command::SUCCESS; - } - - if (! $checkResult['update_available'] && ($force || $rebuild)) { - $this->warn('Geen nieuwe versie beschikbaar, maar force/rebuild aangevraagd.'); - } - - if (! $force && ! $rebuild && ! $this->confirm('Wil je de emulator updaten naar v' . $checkResult['latest_version'] . '?')) { - $this->info('Update geannuleerd.'); - - return Command::SUCCESS; - } - - $this->info('Emulator wordt geรผpdatet...'); - - try { - if ($rebuild || $checkResult['type'] === 'source_build') { - $this->info('Build vanaf source...'); - $result = $updateService->buildFromSource($force); - } else { - $result = $updateService->updateEmulator(); - } - - if ($result['success']) { - $this->info($result['message'] ?? 'Emulator succesvol geรผpdatet!'); - $alertService->send( - AlertType::EMULATOR_ONLINE, - 'Emulator succesvol geรผpdatet naar v' . ($result['version'] ?? 'onbekend'), - [ - 'version' => $result['version'] ?? 'onbekend', - 'jar' => $result['jar'] ?? 'N/A', - ], - AlertChannel::DISCORD, - ); - Log::info('[EmulatorUpdateCommand] Update successful', $result); - - return Command::SUCCESS; - } - - $this->error('Update mislukt: ' . ($result['error'] ?? 'Onbekende fout')); - $alertService->send( - AlertType::EMULATOR_ERROR, - 'Emulator update mislukt: ' . ($result['error'] ?? 'Onbekende fout'), - [], - AlertChannel::DISCORD, - ); - Log::error('[EmulatorUpdateCommand] Update failed', $result); - - return Command::FAILURE; - - } catch (\Exception $e) { - $this->error('Update exception: ' . $e->getMessage()); - $alertService->send( - AlertType::CRITICAL_ERROR, - 'Emulator update exception: ' . $e->getMessage(), - [], - AlertChannel::DISCORD, - ); - Log::error('[EmulatorUpdateCommand] Exception', ['error' => $e->getMessage()]); - - return Command::FAILURE; - } - } - - private function repairEmulator(EmulatorUpdateService $updateService, AlertService $alertService): int - { - $this->warn('๐Ÿ”ง Emulator repair modus gestart...'); - $this->info('Dit zal de emulator status controleren en proberen te repareren.'); - - try { - $repairResult = $updateService->repairEmulator(); - - if ($repairResult['success']) { - $this->info('โœ… Repair succesvol!'); - foreach ($repairResult['actions'] ?? [] as $action) { - $this->line(' - ' . $action); - } - $alertService->send( - AlertType::EMULATOR_ONLINE, - 'Emulator gerepareerd', - ['actions' => $repairResult['actions'] ?? []], - AlertChannel::DISCORD, - ); - - return Command::SUCCESS; - } - - $this->error('โŒ Repair mislukt: ' . ($repairResult['error'] ?? 'Onbekende fout')); - $alertService->send( - AlertType::EMULATOR_ERROR, - 'Emulator repair mislukt: ' . ($repairResult['error'] ?? 'Onbekende fout'), - [], - AlertChannel::DISCORD, - ); - - return Command::FAILURE; - - } catch (\Exception $e) { - $this->error('โŒ Repair exception: ' . $e->getMessage()); - - return Command::FAILURE; - } - } -} diff --git a/app/Console/Commands/GenerateNitroConfigs.php b/app/Console/Commands/GenerateNitroConfigs.php deleted file mode 100755 index adf0bfd..0000000 --- a/app/Console/Commands/GenerateNitroConfigs.php +++ /dev/null @@ -1,796 +0,0 @@ -option('site-url') ?? setting('nitro_site_url', config('app.url', 'https://epicnabbo.nl')); - - $this->info('๐Ÿ”ง Nitro Config Generator'); - $this->line('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); - - // Check 1: Validate URL - $this->addCheck(function () use ($siteUrl) { - if (empty($siteUrl) || ! filter_var($siteUrl, FILTER_VALIDATE_URL)) { - throw new \Exception("Invalid URL: {$siteUrl}"); - } - $this->line(" โœ“ URL: {$siteUrl}"); - }); - - $nitroService = new NitroUpdateService; - $status = $nitroService->getStatus(); - $webroot = $status['webroot'] ?? '/var/www/Client'; - $buildPath = $status['build_path'] ?? '/var/www/atomcms/nitro-client/dist'; - - // Check 2: Webroot exists - $this->addCheck(function () use ($webroot) { - $result = Process::timeout(5)->run('test -d ' . escapeshellarg((string) $webroot)); - if ($result->exitCode() !== 0) { - throw new \Exception("Webroot does not exist: {$webroot}"); - } - $this->line(" โœ“ Webroot exists: {$webroot}"); - }); - - // Check 3: Webroot is writable - $this->addCheck(function () use ($webroot) { - $result = Process::timeout(5)->run('test -w ' . escapeshellarg((string) $webroot)); - if ($result->exitCode() !== 0) { - throw new \Exception("Webroot is not writable: {$webroot}"); - } - $this->line(' โœ“ Webroot is writable'); - }); - - // Check 4: Build path exists - $this->addCheck(function () use ($buildPath) { - $result = Process::timeout(5)->run('test -d ' . escapeshellarg((string) $buildPath)); - if ($result->exitCode() !== 0) { - throw new \Exception("Build path does not exist: {$buildPath}"); - } - $this->line(" โœ“ Build path exists: {$buildPath}"); - }); - - // Check both buildPath and webroot for example files - $this->addCheck(function () use ($buildPath, $webroot) { - // Map .example to .json fallback - $fileMap = [ - 'renderer-config.example' => 'renderer-config.json', - 'ui-config.example' => 'ui-config.json', - 'UITexts.example' => 'UITexts.example', - ]; - - $allFound = true; - - foreach ($fileMap as $exampleFile => $jsonFile) { - // Check .example file first - $buildPathResult = Process::timeout(5)->run('test -f ' . escapeshellarg($buildPath . '/' . $exampleFile)); - $webrootResult = Process::timeout(5)->run('test -f ' . escapeshellarg($webroot . '/' . $exampleFile)); - - if ($buildPathResult->exitCode() === 0) { - $this->line(" โœ“ Found in build path: {$exampleFile}"); - } elseif ($webrootResult->exitCode() === 0) { - $this->line(" โœ“ Found in webroot: {$exampleFile}"); - } elseif ($jsonFile !== $exampleFile) { - // Check json fallback for .example files - $jsonBuildResult = Process::timeout(5)->run('test -f ' . escapeshellarg($buildPath . '/' . $jsonFile)); - $jsonWebrootResult = Process::timeout(5)->run('test -f ' . escapeshellarg($webroot . '/' . $jsonFile)); - if ($jsonBuildResult->exitCode() === 0) { - $this->line(" โœ“ Found in build path: {$jsonFile}"); - } elseif ($jsonWebrootResult->exitCode() === 0) { - $this->line(" โœ“ Found in webroot: {$jsonFile}"); - } else { - $this->warn(" โš  Missing: {$exampleFile} or {$jsonFile}"); - $allFound = false; - } - } else { - $this->warn(" โš  Missing: {$exampleFile}"); - $allFound = false; - } - } - - if (! $allFound) { - throw new \Exception('Some example files are missing'); - } - }); - - // Check 6: Check disk space - $this->addCheck(function () use ($webroot) { - $result = Process::timeout(10)->run("df -h {$webroot} | tail -1 | awk '{print $4}'"); - if ($result->successful()) { - $free = trim($result->output()); - $this->line(" โœ“ Free space: {$free}"); - } - }); - - // Check 7: Validate bundled/config gamedata files - $this->addCheck(function () { - $bundledConfigDir = '/var/www/Gamedata/bundled/config'; - $configDir = '/var/www/Gamedata/config'; - $requiredFiles = ['HabboAvatarActions.json', 'FurnitureData.json', 'ExternalTexts.json', 'ProductData.json', 'FigureData.json', 'FigureMap.json', 'EffectMap.json']; - - // Create bundled/config if it doesn't exist - if (! is_dir($bundledConfigDir)) { - mkdir($bundledConfigDir, 0755, true); - $this->line(" ๐Ÿ“ Created directory: {$bundledConfigDir}"); - } - - // Copy missing files from config to bundled/config - foreach ($requiredFiles as $file) { - $targetPath = $bundledConfigDir . '/' . $file; - if (! file_exists($targetPath)) { - $sourcePath = $configDir . '/' . $file; - if (file_exists($sourcePath)) { - copy($sourcePath, $targetPath); - $this->line(" ๐Ÿ“‹ Copied: {$file} to bundled/config"); - } - } - } - - $this->line(' โœ“ Gamedata bundled/config files ready'); - }); - - // Check 8: Validate existing config files - $this->addCheck(function () use ($webroot) { - $configFiles = ['renderer-config.json', 'ui-config.json', 'UITexts.json']; - foreach ($configFiles as $file) { - $path = $webroot . '/' . $file; - $result = Process::timeout(5)->run('test -f ' . escapeshellarg($path)); - if ($result->exitCode() === 0) { - // Validate JSON - $jsonCheck = Process::timeout(5)->run('python3 -c "import json; json.load(open(\'' . $path . '\'))" 2>&1 || echo "INVALID"'); - if (str_contains($jsonCheck->output(), 'INVALID')) { - $this->warn(" โš  Invalid JSON: {$file}"); - } else { - $this->line(" โœ“ Valid JSON: {$file}"); - } - } else { - $this->line(" - New file: {$file}"); - } - } - }); - - // Run all checks - $this->line(''); - $this->info('Running pre-flight checks...'); - $this->line('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€'); - - try { - foreach ($this->checks as $check) { - $check(); - } - $this->line('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€'); - $this->info('โœ… All checks passed!'); - } catch (\Exception $e) { - $this->error('โŒ Check failed: ' . $e->getMessage()); - $this->newLine(); - $this->error('Please fix the issue before generating configs.'); - - return 1; - } - - // Generate configs - $this->newLine(); - $this->info('Generating configuration files...'); - $this->line('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€'); - - $protocol = str_starts_with((string) $siteUrl, 'https') ? 'https' : 'http'; - $host = preg_replace('/^https?:\/\//', '', (string) $siteUrl); - - try { - // Step 0: Sync latest example files from GitHub - $this->syncExampleFromGithub($buildPath, $webroot); - - $rendererConfig = $this->generateRendererConfig($protocol, $host, $buildPath, $webroot); - $uiConfig = $this->generateUiConfig($protocol, $host, $buildPath, $webroot); - - // Compare with existing deployed configs - $this->compareConfigs($rendererConfig, 'renderer-config', $webroot); - $this->compareConfigs($uiConfig, 'ui-config', $webroot); - - // Validate generated JSON - $this->validateJson($rendererConfig, 'renderer-config'); - $this->validateJson($uiConfig, 'ui-config'); - - $tempFile = '/tmp/nitro_config_' . uniqid(); - file_put_contents($tempFile . '_renderer', json_encode($rendererConfig, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); - file_put_contents($tempFile . '_ui', json_encode($uiConfig, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); - - // Copy generated configs - $commands = [ - 'cp "' . $tempFile . '_renderer" "' . $webroot . '/renderer-config.json"', - 'cp "' . $tempFile . '_ui" "' . $webroot . '/ui-config.json"', - ]; - - // Copy UITexts.json if it exists (check both buildPath and webroot) - $uitextsInBuild = Process::timeout(5)->run('test -f "' . $buildPath . '/UITexts.example"')->exitCode() === 0; - $uitextsInWebroot = Process::timeout(5)->run('test -f "' . $webroot . '/UITexts.example"')->exitCode() === 0; - if ($uitextsInBuild) { - $commands[] = 'cp "' . $buildPath . '/UITexts.example" "' . $webroot . '/UITexts.json"'; - $this->line(' โœ“ Generated: UITexts.json (from build path)'); - } elseif ($uitextsInWebroot) { - $commands[] = 'cp "' . $webroot . '/UITexts.example" "' . $webroot . '/UITexts.json"'; - $this->line(' โœ“ Generated: UITexts.json (from webroot)'); - } - - $commands[] = 'rm "' . $tempFile . '_renderer" "' . $tempFile . '_ui"'; - - Process::timeout(10)->run(implode(' && ', $commands)); - - // Set proper ownership - Process::timeout(10)->run("chown www-data:www-data {$webroot}/renderer-config.json {$webroot}/ui-config.json {$webroot}/UITexts.json 2>/dev/null"); - - setting('nitro_config_generated_at', now()->toIso8601String()); - - $this->line(' โœ“ Generated: renderer-config.json'); - $this->line(' โœ“ Generated: ui-config.json'); - - // Verify generated files - $this->verifyGeneratedFiles($webroot); - - $this->line('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€'); - $this->info('โœ… Configs generated successfully!'); - - Log::info('[NitroConfig] Generated successfully', [ - 'webroot' => $webroot, - 'host' => $host, - ]); - - return 0; - } catch (\Exception $e) { - $this->error('โŒ Generation failed: ' . $e->getMessage()); - Log::error('[NitroConfig] Generation failed', ['error' => $e->getMessage()]); - - return 1; - } - } - - private function addCheck(callable $check): void - { - $this->checks[] = function () use ($check) { - $check(); - }; - } - - private function validateJson(array $data, string $name): void - { - $encoded = json_encode($data, JSON_THROW_ON_ERROR); - $decoded = json_decode($encoded, true); - - if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) { - throw new \Exception("Invalid JSON generated for {$name}"); - } - - $this->line(" โœ“ Validated: {$name}"); - } - - private function verifyGeneratedFiles(string $webroot): void - { - $files = ['renderer-config.json', 'ui-config.json', 'UITexts.json']; - $allValid = true; - - foreach ($files as $file) { - $path = $webroot . '/' . $file; - $result = Process::timeout(5)->run('test -f ' . escapeshellarg($path)); - - if ($result->exitCode() === 0) { - $size = Process::timeout(5)->run('stat -c%s ' . escapeshellarg($path)); - $sizeStr = trim($size->output()) . ' bytes'; - $this->line(" โœ“ Verified: {$file} ({$sizeStr})"); - } else { - $this->warn(" โš  Missing: {$file}"); - $allValid = false; - } - } - - if (! $allValid) { - throw new \Exception('Some files were not generated'); - } - } - - private function generateRendererConfig(string $protocol, string $host, string $buildPath, string $webroot): array - { - $httpProtocol = $protocol === 'https' ? 'https' : 'http'; - $wssProtocol = $protocol === 'https' ? 'wss' : 'ws'; - $wsHost = 'ws.' . $host; - - $rendererConfig = []; - - // Check build path first, then webroot for .example file - $examplePath = $buildPath . '/renderer-config.example'; - if (Process::timeout(5)->run('test -f ' . escapeshellarg($examplePath))->exitCode() !== 0) { - $examplePath = $webroot . '/renderer-config.example'; - } - - // Fallback to .json version (newer Nitro-V3 format) - if (Process::timeout(5)->run('test -f ' . escapeshellarg($examplePath))->exitCode() !== 0) { - $jsonPath = $buildPath . '/renderer-config.json'; - if (Process::timeout(5)->run('test -f ' . escapeshellarg($jsonPath))->exitCode() === 0) { - $examplePath = $jsonPath; - } else { - $jsonPath = $webroot . '/renderer-config.json'; - if (Process::timeout(5)->run('test -f ' . escapeshellarg($jsonPath))->exitCode() === 0) { - $examplePath = $jsonPath; - } - } - } - - // Load COMPLETE example file as base - if (Process::timeout(5)->run('test -f ' . escapeshellarg($examplePath))->exitCode() === 0) { - $content = file_get_contents($examplePath); - // Fix invalid escape sequences (literal \n, \r, \t in JSON values) - $content = $this->fixInvalidJsonEscapeSequences($content); - $rendererConfig = @json_decode($content, true) ?: []; - $this->line(' โœ“ Loaded renderer config from: ' . basename($examplePath) . ' (' . count($rendererConfig) . ' keys)'); - } else { - $this->warn(' โš  renderer-config.example/json not found'); - - return []; - } - - // Recursively replace ALL URLs in the entire config - $rendererConfig = $this->replaceUrlsRecursively($rendererConfig, $httpProtocol, $wssProtocol, $host, $wsHost); - - // Auto-detect asset directory names on disk and fix paths - $rendererConfig = $this->autoDetectAssetPaths($rendererConfig, $webroot); - - // Special handling for socket.url - use ws subdomain - if (isset($rendererConfig['socket.url'])) { - $rendererConfig['socket.url'] = $wssProtocol . '://ws.' . $host; - } - - // Auto-detect local asset paths from Gamedata directory - $gamedataBase = $httpProtocol . '://' . $host; - - // Check what directories exist in Gamedata and set URLs accordingly - $gamedataPath = '/var/www/Gamedata'; - if (is_dir($gamedataPath)) { - // Check for bundled directory - serve via /gamedata/bundled (nginx alias) - if (is_dir($gamedataPath . '/bundled')) { - $rendererConfig['asset.url'] = $gamedataBase . '/gamedata/bundled'; - } - // JSON config files are in /gamedata/config/ - if (is_dir($gamedataPath . '/config')) { - $rendererConfig['gamedata.url'] = $gamedataBase . '/gamedata/config'; - } - // Check for c_images directory - serve via /gamedata/c_images - if (is_dir($gamedataPath . '/c_images')) { - $rendererConfig['image.library.url'] = $gamedataBase . '/gamedata/c_images/'; - // Use icons folder for furni icons - if (is_dir($gamedataPath . '/icons')) { - $rendererConfig['hof.furni.url'] = $gamedataBase . '/gamedata/icons'; - } else { - $rendererConfig['hof.furni.url'] = $gamedataBase . '/gamedata/c_images/dcr/hof_furni'; - } - } - // Fix furni icon path - icons are directly in hof.furni folder, not in icons subfolder - if (isset($rendererConfig['furni.asset.icon.url'])) { - $rendererConfig['furni.asset.icon.url'] = '${hof.furni.url}/%libname%%param%_icon.png'; - } - // Fix sound machine samples path - sounds are in /gamedata/sounds/ - if (isset($rendererConfig['external.samples.url'])) { - $rendererConfig['external.samples.url'] = $gamedataBase . '/gamedata/sounds/sound_machine_sample_%sample%.mp3'; - } - // Check for images directory - if (is_dir($gamedataPath . '/images')) { - $rendererConfig['images.url'] = $gamedataBase . '/gamedata/images'; - } - } - - // Add missing keys that might not be in the example - if (! isset($rendererConfig['external.plugins'])) { - $rendererConfig['external.plugins'] = []; - } - - // Add YouTube API key from settings - $youtubeApiKey = setting('youtube_api_key', ''); - if (! empty($youtubeApiKey)) { - $rendererConfig['youtube.api.key'] = $youtubeApiKey; - $this->line(' โœ“ Added YouTube API key to renderer config'); - } - - // Ensure pet.types matches the exact required list in order - if (isset($rendererConfig['pet.types'])) { - $requiredPetTypes = [ - 'dog', - 'cat', - 'croco', - 'terrier', - 'bear', - 'pig', - 'lion', - 'rhino', - 'spider', - 'turtle', - 'chicken', - 'frog', - 'dragon', - 'monster', - 'monkey', - 'horse', - 'monsterplant', - 'bunnyeaster', - 'bunnyevil', - 'bunnydepressed', - 'bunnylove', - 'pigeongood', - 'pigeonevil', - 'demonmonkey', - 'bearbaby', - 'terrierbaby', - 'gnome', - 'leprechaun', - 'kittenbaby', - 'puppybaby', - 'pigletbaby', - 'haloompa', - 'fools', - 'pterosaur', - 'velociraptor', - 'cow', - 'dragondog', - 'pkmshaymin2', - 'LeetEendjes', - 'pkmnentei', - 'squirtle', - 'LeetBH', - 'LeetCaviaaa', - 'LeetFantj', - 'LeetHotelMario', - 'LeetUil', - 'LeetWolf', - 'pokemon_mewblu', - 'LeetBB', - 'LeetHotelMari1', - 'LeetMewtw', - 'LeetPikachu', - 'LeetYos', - 'LeetE', - 'LeetMewt1', - 'LeetPen', - 'slendermn', - 'pkmnPAPI0', - 'pkmnPAPI1', - 'pkmnPAPI2', - 'pkmnPAPI3', - 'pkmnPAPI4', - 'pokmn_mew', - 'pkmashhhpet', - 'pkmbeautfly', - 'pkmcelebipe', - 'pkmdarkraip', - 'pkmeeveepet', - 'pkmjirachip', - 'pkmpichupet', - 'pkmriolupet', - 'pkmshayminp', - 'pkmtogepipe', - 'pkmvictinip', - 'slenderm1', - 'LeetEendj16', - 'pkmnente1', - 'LeetBa', - 'babymeisje', - 'babyBH', - 'bb_hbx', - 'LeetUi1', - ]; - - $rendererConfig['pet.types'] = $requiredPetTypes; - $this->line(' โœ“ Set pet.types to exact required list'); - } - - return $rendererConfig; - } - - private function generateUiConfig(string $protocol, string $host, string $buildPath, string $webroot): array - { - $httpProtocol = $protocol === 'https' ? 'https' : 'http'; - $wssProtocol = $protocol === 'https' ? 'wss' : 'ws'; - $wsHost = 'ws.' . $host; - - $uiConfig = []; - - // Check build path first, then webroot for .example file - $examplePath = $buildPath . '/ui-config.example'; - if (Process::timeout(5)->run('test -f ' . escapeshellarg($examplePath))->exitCode() !== 0) { - $examplePath = $webroot . '/ui-config.example'; - } - - // Fallback to .json version (newer Nitro-V3 format) - if (Process::timeout(5)->run('test -f ' . escapeshellarg($examplePath))->exitCode() !== 0) { - $jsonPath = $buildPath . '/ui-config.json'; - if (Process::timeout(5)->run('test -f ' . escapeshellarg($jsonPath))->exitCode() === 0) { - $examplePath = $jsonPath; - } else { - $jsonPath = $webroot . '/ui-config.json'; - if (Process::timeout(5)->run('test -f ' . escapeshellarg($jsonPath))->exitCode() === 0) { - $examplePath = $jsonPath; - } - } - } - - // Load COMPLETE example file as base - if (Process::timeout(5)->run('test -f ' . escapeshellarg($examplePath))->exitCode() === 0) { - $content = file_get_contents($examplePath); - // Fix invalid escape sequences - $content = $this->fixInvalidJsonEscapeSequences($content); - $uiConfig = @json_decode($content, true) ?: []; - $this->line(' โœ“ Loaded ui config from: ' . basename($examplePath) . ' (' . count($uiConfig) . ' keys)'); - } else { - $this->warn(' โš  ui-config.example/json not found'); - - return []; - } - - // Recursively replace ALL URLs in the entire config - $uiConfig = $this->replaceUrlsRecursively($uiConfig, $httpProtocol, $wssProtocol, $host, $wsHost); - - return $uiConfig; - } - - private function fixInvalidJsonEscapeSequences(string $content): string - { - // The example file has literal backslash + any letter in JSON values - // which breaks JSON. We need to escape all of these. - - $backslash = chr(92); - $letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']; - - foreach ($letters as $letter) { - $content = str_replace($backslash . $letter, $backslash . $backslash . $letter, $content); - } - - return $content; - } - - private function replaceUrlsRecursively(array $config, string $httpProtocol, string $wsProtocol, string $host, string $wsHost): array - { - foreach ($config as &$value) { - if (is_array($value)) { - // Handle nested arrays (like navigator.room.models) - if ($this->isAssociativeArray($value)) { - $value = $this->replaceUrlsRecursively($value, $httpProtocol, $wsProtocol, $host, $wsHost); - } else { - // Handle arrays of URLs - $value = array_map(function ($item) use ($httpProtocol, $wsProtocol, $host, $wsHost) { - if (is_string($item)) { - return $this->replaceUrl($item, $httpProtocol, $wsProtocol, $host, $wsHost); - } - - return $item; - }, $value); - } - } elseif (is_string($value)) { - $value = $this->replaceUrl($value, $httpProtocol, $wsProtocol, $host, $wsHost); - } - } - - return $config; - } - - private function replaceUrl(string $url, string $httpProtocol, string $wsProtocol, string $host, string $wsHost): string - { - // Replace ws/wss URLs with ws subdomain - if (str_starts_with($url, 'ws://') || str_starts_with($url, 'wss://')) { - $path = parse_url($url, PHP_URL_PATH) ?? '/'; - - return $wsProtocol . '://' . $wsHost . ($path !== '/' ? $path : ''); - } - - // Replace localhost in all URLs - $url = preg_replace('#https?://localhost(?::\d+)?#', $httpProtocol . '://' . $host, $url); - $url = preg_replace('#wss?://localhost(?::\d+)?#', $wsProtocol . '://' . $wsHost, (string) $url); - $url = preg_replace('#localhost(?::\d+)?#', $host, (string) $url); - - // Fix broken escape sequences in URL paths (from invalid JSON in example files) - // Pattern: /public\nitro-assets\gamedata or any variation - $url = preg_replace('#/public\\n[a-z_-]+\\\\gamedata#i', '/gamedata', (string) $url); - $url = preg_replace('#/public\\\\[a-z_-]+\\\\gamedata#i', '/gamedata', (string) $url); - $url = preg_replace('#/nitro-assets\\\\gamedata#i', '/gamedata', (string) $url); - $url = preg_replace('#/nitro\\\\[a-z_-]+#i', '/gamedata', (string) $url); - - // Fix known asset path patterns - $url = str_replace('/public/nitro-assets/gamedata', '/gamedata', $url); - $url = str_replace('/swf/gamedata', '/gamedata', $url); - - // Clean up any remaining backslashes in URLs - $url = str_replace('\\', '/', $url); - - // Clean up any remaining double slashes (but keep protocol slashes) - $url = preg_replace('#([^:])//+#', '$1/', $url); - - return $url; - } - - private function isAssociativeArray(array $arr): bool - { - if ($arr === []) { - return false; - } - - return array_keys($arr) !== range(0, count($arr) - 1); - } - - private function autoDetectAssetPaths(array $config, string $webroot): array - { - // Check multiple possible gamedata locations - $possiblePaths = [ - $webroot . '/gamedata', - '/var/www/Gamedata', - '/var/www/gamedata', - ]; - $gamedataPath = array_find($possiblePaths, fn ($path) => is_dir($path)); - - if (! $gamedataPath) { - return $config; - } - - $assetDir = opendir($gamedataPath); - $actualDirs = []; - while (($entry = readdir($assetDir)) !== false) { - if ($entry !== '.' && $entry !== '..' && is_dir($gamedataPath . '/' . $entry)) { - $actualDirs[strtolower($entry)] = $entry; - } - } - closedir($assetDir); - - $pathChecks = [ - 'pet.asset.url' => 'pets', - 'furni.asset.url' => 'furniture', - 'avatar.asset.url' => 'clothes', - 'avatar.asset.effect.url' => 'effect', - 'generic.asset.url' => 'generic_custom', - ]; - - foreach ($config as $key => &$value) { - if (! is_string($value) || ! isset($pathChecks[$key])) { - continue; - } - - $expectedDir = $pathChecks[$key]; - $lowerExpected = strtolower($expectedDir); - $actualName = null; - - // Special case: "figure" is often used instead of "clothes" for avatars - if ($lowerExpected === 'clothes' && isset($actualDirs['figure'])) { - $actualName = $actualDirs['figure']; - } elseif (isset($actualDirs[$lowerExpected])) { - $actualName = $actualDirs[$lowerExpected]; - } else { - foreach ($actualDirs as $actualLower => $actual) { - if (str_starts_with($actualLower, rtrim($lowerExpected, 's')) || - str_starts_with(rtrim($actualLower, 's'), rtrim($lowerExpected, 's'))) { - $actualName = $actual; - break; - } - } - } - - if ($actualName && $actualName !== $expectedDir) { - $value = str_replace("/{$expectedDir}/", "/{$actualName}/", $value); - $this->line(" ๐Ÿ” Auto-detected: {$key} -> /{$actualName}/ (was /{$expectedDir}/)"); - } - } - - return $config; - } - - private function syncExampleFromGithub(string $buildPath, string $webroot): void - { - $this->info('Syncing latest examples from GitHub...'); - - $rendererRepo = setting('nitro_github_url', ''); - $repo = $this->parseRepoFromUrl($rendererRepo); - if (! $repo) { - $this->warn(' โš  No GitHub repo configured, skipping sync'); - - return; - } - - $branch = setting('nitro_github_branch', 'main'); - - $examples = [ - 'renderer-config.example' => 'renderer-config.example', - 'ui-config.example' => 'ui-config.example', - 'UITexts.example' => 'UITexts.json', - ]; - - foreach (array_keys($examples) as $remoteFile) { - $tempFile = '/tmp/' . $remoteFile . '_' . uniqid(); - $fetched = false; - - // Try multiple paths: root, public/, nitro-client/dist/, Nitro-V3 paths - $paths = ['', 'public/', 'nitro-client/dist/', 'dist/', 'src/', 'assets/']; - foreach ($paths as $path) { - $url = "https://raw.githubusercontent.com/{$repo}/{$branch}/{$path}{$remoteFile}"; - $result = Process::timeout(15)->run("curl -sL -o {$tempFile} '{$url}'"); - - if ($result->successful() && file_exists($tempFile) && filesize($tempFile) > 10) { - $content = file_get_contents($tempFile); - $data = @json_decode($content, true); - - if (is_array($data) && $data !== []) { - // Save to both buildPath and webroot - file_put_contents($buildPath . '/' . $remoteFile, $content); - file_put_contents($webroot . '/' . $remoteFile, $content); - $this->line(" โœ“ Synced: {$remoteFile} (" . count($data) . " keys from {$path}{$remoteFile})"); - $fetched = true; - break; - } - } - @unlink($tempFile); - } - - if (! $fetched) { - $this->line(" - Skipped: {$remoteFile} (not found in any path)"); - } - } - } - - private function parseRepoFromUrl(string $url): ?string - { - if (preg_match('/github\.com\/([^\/]+\/[^\/\?#]+)/', $url, $matches)) { - return rtrim($matches[1], '/'); - } - - return null; - } - - private function compareConfigs(array $generated, string $name, string $webroot): void - { - $currentPath = $webroot . '/' . $name . '.json'; - if (! file_exists($currentPath)) { - $this->line(" โ„น {$name}: Geen bestaande config โ€” nieuwe generatie"); - - return; - } - - $current = @json_decode(file_get_contents($currentPath), true); - if (! is_array($current)) { - $this->warn(" โš  {$name}: Bestaande config is ongeldig JSON"); - - return; - } - - $newKeys = array_diff(array_keys($generated), array_keys($current)); - $removedKeys = array_diff(array_keys($current), array_keys($generated)); - $changedKeys = []; - - foreach (array_intersect(array_keys($generated), array_keys($current)) as $key) { - if ($generated[$key] !== $current[$key]) { - $changedKeys[] = $key; - } - } - - if ($newKeys !== []) { - $this->line(" ๐Ÿ†• {$name}: " . count($newKeys) . ' nieuwe key(s): ' . implode(', ', array_slice($newKeys, 0, 10))); - if (count($newKeys) > 10) { - $this->line(' ... en ' . (count($newKeys) - 10) . ' meer'); - } - } - if ($removedKeys !== []) { - $this->line(" ๐Ÿ—‘ {$name}: " . count($removedKeys) . ' verwijderde key(s): ' . implode(', ', array_slice($removedKeys, 0, 5))); - } - if ($changedKeys !== []) { - $this->line(" ๐Ÿ”„ {$name}: " . count($changedKeys) . ' gewijzigde key(s): ' . implode(', ', array_slice($changedKeys, 0, 5))); - } - if ($newKeys === [] && $removedKeys === [] && $changedKeys === []) { - $this->line(" โœ“ {$name}: Geen wijzigingen"); - } - } -} diff --git a/app/Console/Commands/NitroUpdateCommand.php b/app/Console/Commands/NitroUpdateCommand.php deleted file mode 100755 index eeae9a2..0000000 --- a/app/Console/Commands/NitroUpdateCommand.php +++ /dev/null @@ -1,272 +0,0 @@ -info('Nitro update check...'); - - if ($this->option('diagnose')) { - return $this->diagnose($nitroService); - } - - if ($this->option('repair')) { - return $this->repair($alertService, $nitroService); - } - - $isForced = $this->option('force'); - $buildOnly = $this->option('build-only'); - $full = $this->option('full'); - - if ($buildOnly) { - return $this->buildOnly($alertService, $nitroService); - } - - if (! $isForced && ! $this->isScheduledTime()) { - $this->line('Niet de geplande tijd, overslaan.'); - - return Command::SUCCESS; - } - - $repo = setting('nitro_github_url', 'duckietm/Nitro-V3 (default)'); - $this->info("GitHub: {$repo}"); - - if ($full) { - return $this->fullReinstall($alertService, $nitroService); - } - - $status = $nitroService->getStatus(); - - $this->info('Client: ' . ($status['client_installed'] ? 'โœ…' : 'โŒ')); - $this->info('Renderer: ' . ($status['renderer_installed'] ? 'โœ…' : 'โŒ')); - $this->info('Build: ' . ($status['build_exists'] ? 'โœ…' : 'โŒ')); - $this->info('Deployed: ' . ($status['deployed'] ? 'โœ…' : 'โŒ')); - - if (! $status['client_installed'] || ! $status['renderer_installed']) { - $this->warn('Nitro niet volledig geรฏnstalleerd. Voer --full uit voor volledige installatie.'); - if ($this->confirm('Wil je nu volledig installeren?')) { - return $this->fullReinstall($alertService, $nitroService); - } - - return Command::SUCCESS; - } - - $this->line('โœ… Nitro client is up-to-date.'); - - if ($isForced) { - $this->warn('Force update aangevraagd...'); - - return $this->fullReinstall($alertService, $nitroService); - } - - Cache::put(self::CACHE_KEY_LAST_UPDATE, now()->toIso8601String()); - - return Command::SUCCESS; - } - - private function diagnose(NitroUpdateService $nitroService): int - { - $this->info('๐Ÿ” Nitro diagnose...'); - $diagnosis = $nitroService->diagnose(); - - $this->newLine(); - $this->info('=== Controle Resultaten ==='); - - $checks = $diagnosis['checks'] ?? []; - foreach ($checks as $key => $value) { - if (is_bool($value)) { - $this->line(($value ? 'โœ…' : 'โŒ') . ' ' . $key); - } elseif (is_array($value)) { - $this->line($key . ': ' . count($value) . ' items'); - } else { - $this->line($key . ': ' . $value); - } - } - - if (! empty($diagnosis['issues'])) { - $this->newLine(); - $this->error('=== Problemen ==='); - foreach ($diagnosis['issues'] as $issue) { - $this->line('โŒ ' . $issue); - } - } - - if (! empty($diagnosis['recommendations'])) { - $this->newLine(); - $this->info('=== Aanbevelingen ==='); - foreach ($diagnosis['recommendations'] as $rec) { - $this->line('๐Ÿ’ก ' . $rec); - } - } - - return Command::SUCCESS; - } - - private function repair(AlertService $alertService, NitroUpdateService $nitroService): int - { - $this->warn('๐Ÿ”ง Nitro repair modus gestart...'); - $this->info('Dit zal de Nitro installatie controleren en repareren.'); - - try { - $repairResult = $nitroService->repair(); - - if ($repairResult['success']) { - $this->info('โœ… Repair succesvol!'); - foreach ($repairResult['actions'] ?? [] as $action) { - $this->line(' - ' . $action); - } - $alertService->send( - AlertType::EMULATOR_UPDATE, - 'Nitro client gerepareerd', - ['actions' => $repairResult['actions'] ?? []], - AlertChannel::DISCORD, - ); - - return Command::SUCCESS; - } - - $this->error('โŒ Repair mislukt: ' . ($repairResult['error'] ?? 'Onbekende fout')); - if (! empty($repairResult['actions'])) { - $this->line('Uitgevoerde acties:'); - foreach ($repairResult['actions'] as $action) { - $this->line(' - ' . $action); - } - } - if (! empty($repairResult['errors'])) { - $this->line('Fouten:'); - foreach ($repairResult['errors'] as $error) { - $this->line(' โŒ ' . $error); - } - } - $alertService->send( - AlertType::EMULATOR_ERROR, - 'Nitro repair mislukt: ' . ($repairResult['error'] ?? 'Onbekende fout'), - [], - AlertChannel::DISCORD, - ); - - return Command::FAILURE; - - } catch (\Exception $e) { - $this->error('โŒ Repair exception: ' . $e->getMessage()); - - return Command::FAILURE; - } - } - - private function buildOnly(AlertService $alertService, NitroUpdateService $nitroService): int - { - $this->info('Build en deploy uitvoeren...'); - - $buildResult = $nitroService->buildClient(); - - if (! $buildResult['success']) { - $this->error('Build mislukt: ' . ($buildResult['error'] ?? 'Onbekend')); - - return Command::FAILURE; - } - - $nitroService->deployClient(); - $nitroService->generateConfigs(); - - $this->info('Client succesvol gedeployed!'); - $alertService->send( - AlertType::EMULATOR_UPDATE, - 'Nitro client opnieuw gedeployed', - [], - AlertChannel::DISCORD, - ); - - return Command::SUCCESS; - } - - private function fullReinstall(AlertService $alertService, NitroUpdateService $nitroService): int - { - $this->warn('Volledige reinstall wordt uitgevoerd...'); - - try { - $result = $nitroService->updateNitro(); - - if ($result['success']) { - $this->info('Nitro succesvol opnieuw geรฏnstalleerd!'); - $alertService->send( - AlertType::EMULATOR_UPDATE, - 'Nitro client succesvol geรผpdatet en gedeployed', - [], - AlertChannel::DISCORD, - ); - - return Command::SUCCESS; - } - - $this->error('Reinstall mislukt: ' . ($result['error'] ?? 'Onbekende fout')); - $alertService->send( - AlertType::CRITICAL_ERROR, - 'Nitro Update Mislukt: ' . ($result['error'] ?? 'Onbekende fout'), - [], - AlertChannel::DISCORD, - ); - - return Command::FAILURE; - - } catch (\Exception $e) { - $this->error('Reinstall exception: ' . $e->getMessage()); - - return Command::FAILURE; - } - } - - private function isScheduledTime(): bool - { - $scheduleTime = setting('nitro_auto_update_schedule', '03:00'); - $scheduleDays = setting('nitro_auto_update_days', '0,6'); - $enabled = setting('nitro_auto_update_enabled', false); - - if (! $enabled) { - return false; - } - - $now = now(); - $currentTime = $now->format('H:i'); - $currentDay = (int) $now->dayOfWeek; - - $allowedDays = array_map(intval(...), explode(',', $scheduleDays)); - - if (! in_array($currentDay, $allowedDays)) { - return false; - } - - if ($currentTime !== $scheduleTime) { - $minuteDiff = abs(strtotime($currentTime) - strtotime((string) $scheduleTime)) / 60; - if ($minuteDiff > 5) { - return false; - } - } - - return true; - } -} diff --git a/app/Console/Commands/SwitchNitroBranch.php b/app/Console/Commands/SwitchNitroBranch.php deleted file mode 100755 index 07995f4..0000000 --- a/app/Console/Commands/SwitchNitroBranch.php +++ /dev/null @@ -1,49 +0,0 @@ -option('branch') ?? 'main'; - - $this->info("๐Ÿ”„ Switching Nitro to branch: {$branch}"); - - try { - $nitroService = new NitroUpdateService; - $result = $nitroService->updateNitro(true); - - if ($result['success'] ?? false) { - $this->info("โœ… Switched to {$branch} successfully!"); - $this->info($result['message'] ?? ''); - Log::info('[NitroSwitch] Success', ['branch' => $branch, 'message' => $result['message'] ?? '']); - } else { - $this->error('โŒ Switch failed: ' . ($result['error'] ?? 'Unknown error')); - Log::error('[NitroSwitch] Failed', ['branch' => $branch, 'error' => $result['error'] ?? 'Unknown']); - } - - Cache::forget('website_settings'); - SettingsService::clearCache(); - - return ($result['success'] ?? false) ? 0 : 1; - } catch (\Exception $e) { - $this->error('โŒ Exception: ' . $e->getMessage()); - Log::error('[NitroSwitch] Exception', ['branch' => $branch, 'error' => $e->getMessage()]); - - return 1; - } - } -} diff --git a/app/Console/Commands/SystemHealthCommand.php b/app/Console/Commands/SystemHealthCommand.php deleted file mode 100755 index c34900a..0000000 --- a/app/Console/Commands/SystemHealthCommand.php +++ /dev/null @@ -1,251 +0,0 @@ -option('json'); - $this->option('details'); - - $health = $this->performHealthCheck($emuService, $nitroService); - - if ($json) { - $this->line(json_encode($health, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); - - return $health['status'] === 'healthy' ? 0 : 1; - } - - $this->displayHealthCheck($health); - - return $health['status'] === 'healthy' ? 0 : 1; - } - - private function performHealthCheck(EmulatorUpdateService $emuService, NitroUpdateService $nitroService): array - { - $checks = []; - $issues = []; - $warnings = []; - - $checks['timestamp'] = now()->toIso8601String(); - - $checks['php'] = [ - 'version' => PHP_VERSION, - 'status' => 'ok', - ]; - - $checks['system'] = [ - 'os' => PHP_OS, - 'user' => posix_getpwuid(posix_geteuid())['name'] ?? 'unknown', - ]; - - $diskFree = @disk_free_space('/'); - $diskTotal = @disk_total_space('/'); - $diskPercent = $diskTotal > 0 ? round(($diskFree / $diskTotal) * 100, 1) : 0; - $checks['disk'] = [ - 'free' => $this->formatBytes($diskFree), - 'total' => $this->formatBytes($diskTotal), - 'percent_free' => $diskPercent, - 'status' => $diskPercent > 10 ? 'ok' : 'critical', - ]; - if ($diskPercent < 10) { - $issues[] = 'Disk space critically low: ' . $diskPercent . '% free'; - } elseif ($diskPercent < 20) { - $warnings[] = 'Disk space low: ' . $diskPercent . '% free'; - } - - try { - $emuDiagnosis = $emuService->diagnose(); - $checks['emulator'] = [ - 'configured' => $emuDiagnosis['checks']['is_configured'] ?? false, - 'jar_exists' => $emuDiagnosis['checks']['jar_exists'] ?? false, - 'service_running' => $emuDiagnosis['checks']['service_running'] ?? false, - 'db_connected' => $emuDiagnosis['checks']['emulator_db_connected'] ?? false, - 'status' => empty($emuDiagnosis['issues']) ? 'ok' : 'issues', - 'issues' => $emuDiagnosis['issues'] ?? [], - ]; - - if (! empty($emuDiagnosis['issues'])) { - foreach ($emuDiagnosis['issues'] as $issue) { - $issues[] = 'Emulator: ' . $issue; - } - } - } catch (\Exception $e) { - $checks['emulator'] = [ - 'status' => 'error', - 'error' => $e->getMessage(), - ]; - $issues[] = 'Emulator check failed: ' . $e->getMessage(); - } - - try { - $nitroDiagnosis = $nitroService->diagnose(); - $checks['nitro'] = [ - 'client_installed' => $nitroDiagnosis['checks']['client_installed'] ?? false, - 'renderer_installed' => $nitroDiagnosis['checks']['renderer_installed'] ?? false, - 'deployed' => $nitroDiagnosis['checks']['deployed'] ?? false, - 'status' => empty($nitroDiagnosis['issues']) ? 'ok' : 'issues', - 'issues' => $nitroDiagnosis['issues'] ?? [], - ]; - - if (! empty($nitroDiagnosis['issues'])) { - foreach ($nitroDiagnosis['issues'] as $issue) { - $issues[] = 'Nitro: ' . $issue; - } - } - } catch (\Exception $e) { - $checks['nitro'] = [ - 'status' => 'error', - 'error' => $e->getMessage(), - ]; - $issues[] = 'Nitro check failed: ' . $e->getMessage(); - } - - try { - $sqlDiagnosis = $emuService->diagnoseSqlUpdates(); - $checks['sql_updates'] = [ - 'table_exists' => $sqlDiagnosis['table_exists'] ?? false, - 'applied' => $sqlDiagnosis['applied_count'] ?? 0, - 'pending' => $sqlDiagnosis['pending_count'] ?? 0, - 'status' => ($sqlDiagnosis['pending_count'] ?? 0) > 0 ? 'pending' : 'ok', - ]; - - if (($sqlDiagnosis['pending_count'] ?? 0) > 0) { - $warnings[] = $sqlDiagnosis['pending_count'] . ' SQL updates pending'; - } - } catch (\Exception $e) { - $checks['sql_updates'] = [ - 'status' => 'error', - 'error' => $e->getMessage(), - ]; - } - - $webserverCheck = Process::timeout(5)->run('which nginx || which apache2 || which httpd'); - $checks['webserver'] = [ - 'installed' => $webserverCheck->successful(), - 'status' => $webserverCheck->successful() ? 'ok' : 'unknown', - ]; - - $mysqlCheck = Process::timeout(5)->run('which mysql || which mariadb'); - $checks['database'] = [ - 'client_installed' => $mysqlCheck->successful(), - 'status' => $mysqlCheck->successful() ? 'ok' : 'unknown', - ]; - - $nodeCheck = Process::timeout(5)->run('which node && which yarn'); - $checks['node'] = [ - 'installed' => $nodeCheck->successful(), - 'status' => $nodeCheck->successful() ? 'ok' : 'missing', - ]; - if (! $nodeCheck->successful()) { - $warnings[] = 'Node.js/Yarn niet geรฏnstalleerd'; - } - - $status = $issues === [] ? 'healthy' : ($warnings === [] ? 'warning' : 'degraded'); - - return [ - 'status' => $status, - 'checks' => $checks, - 'issues' => $issues, - 'warnings' => $warnings, - ]; - } - - private function displayHealthCheck(array $health): void - { - $status = $health['status']; - - $statusIcon = match ($status) { - 'healthy' => 'โœ…', - 'warning' => 'โš ๏ธ', - 'degraded' => 'โŒ', - default => 'โ“', - }; - - $this->info("{$statusIcon} Systeem Gezondheid: " . strtoupper($status)); - $this->line('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); - - $checks = $health['checks'] ?? []; - - $this->line('๐Ÿ“ฆ Systeem:'); - $this->line(' PHP: ' . ($checks['php']['version'] ?? '?')); - $this->line(' User: ' . ($checks['system']['user'] ?? '?')); - - $diskStatus = $checks['disk']['status'] ?? '?'; - $diskIcon = $diskStatus === 'ok' ? 'โœ…' : 'โŒ'; - $this->line(" {$diskIcon} Disk: " . ($checks['disk']['percent_free'] ?? '?') . '% vrij'); - - $emuStatus = $checks['emulator']['status'] ?? '?'; - $emuIcon = $emuStatus === 'ok' ? 'โœ…' : 'โš ๏ธ'; - $this->line(" {$emuIcon} Emulator: " . ucfirst($emuStatus)); - - $nitroStatus = $checks['nitro']['status'] ?? '?'; - $nitroIcon = $nitroStatus === 'ok' ? 'โœ…' : 'โš ๏ธ'; - $this->line(" {$nitroIcon} Nitro: " . ucfirst($nitroStatus)); - - $sqlStatus = $checks['sql_updates']['status'] ?? '?'; - $sqlIcon = $sqlStatus === 'ok' ? 'โœ…' : 'โš ๏ธ'; - $this->line(" {$sqlIcon} SQL Updates: " . ucfirst($sqlStatus) . ' (' . ($checks['sql_updates']['applied'] ?? 0) . ' toegepast)'); - - $nodeStatus = $checks['node']['status'] ?? '?'; - $nodeIcon = $nodeStatus === 'ok' ? 'โœ…' : 'โŒ'; - $this->line(" {$nodeIcon} Node.js: " . ucfirst($nodeStatus)); - - if (! empty($health['issues'])) { - $this->line(''); - $this->error('โŒ Problemen:'); - foreach ($health['issues'] as $issue) { - $this->line(' - ' . $issue); - } - } - - if (! empty($health['warnings'])) { - $this->line(''); - $this->warn('โš ๏ธ Waarschuwingen:'); - foreach ($health['warnings'] as $warning) { - $this->line(' - ' . $warning); - } - } - - $this->line('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); - - if ($status === 'healthy') { - $this->info('โœ… Alles werkt correct!'); - } else { - $this->warn('Run "php artisan system:repair" om problemen op te lossen'); - } - } - - private function formatBytes(?float $bytes): string - { - if ($bytes === null) { - return 'N/A'; - } - - $units = ['B', 'KB', 'MB', 'GB', 'TB']; - $i = 0; - - while ($bytes >= 1024 && $i < count($units) - 1) { - $bytes /= 1024; - $i++; - } - - return round($bytes, 2) . ' ' . $units[$i]; - } -} diff --git a/app/Console/Commands/SystemRepairCommand.php b/app/Console/Commands/SystemRepairCommand.php deleted file mode 100755 index a545cb0..0000000 --- a/app/Console/Commands/SystemRepairCommand.php +++ /dev/null @@ -1,213 +0,0 @@ -info('๐Ÿ”ง System Repair Service gestart...'); - $this->line('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); - - $checkOnly = $this->option('check'); - $force = $this->option('force'); - $full = $this->option('full'); - $emuOnly = $this->option('emu'); - $nitroOnly = $this->option('nitro'); - - $results = [ - 'emulator' => null, - 'nitro' => null, - ]; - - if (! $nitroOnly) { - $results['emulator'] = $this->repairEmulator($emuService, $checkOnly, $force, $full); - } - - if (! $emuOnly) { - $results['nitro'] = $this->repairNitro($nitroService, $checkOnly, $force, $full); - } - - Cache::put(self::CACHE_KEY_LAST_REPAIR, now()->toIso8601String()); - - $emuOk = $results['emulator'] === null || ($results['emulator']['success'] ?? false); - $nitroOk = $results['nitro'] === null || ($results['nitro']['success'] ?? false); - - $this->line('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); - if ($emuOk && $nitroOk) { - $this->info('โœ… Alle systemen OK'); - - return Command::SUCCESS; - } - - return Command::FAILURE; - } - - private function ensureBaseDirectories(): void - { - $basePaths = [ - '/var/www', - '/var/www/atomcms', - storage_path('app'), - ]; - - foreach ($basePaths as $path) { - if (! is_dir($path)) { - @mkdir($path, 0755, true); - } - } - - Process::timeout(5)->run('chown -R www-data:www-data /var/www/atomcms 2>/dev/null || true'); - Process::timeout(5)->run('chmod -R 755 /var/www/atomcms 2>/dev/null || true'); - } - - private function repairEmulator(EmulatorUpdateService $service, bool $checkOnly, bool $force, bool $full): array - { - $this->info('Emulator controleren...'); - - $this->ensureBaseDirectories(); - - try { - $diagnosis = $service->diagnose(); - - if (empty($diagnosis['issues']) && ! $force && ! $full) { - $this->line(' โœ… Emulator is OK'); - - return ['success' => true, 'status' => 'ok']; - } - - if ($checkOnly) { - $this->warn(' โš ๏ธ Emulator problemen gevonden:'); - foreach ($diagnosis['issues'] ?? [] as $issue) { - $this->line(' - ' . $issue); - } - foreach ($diagnosis['recommendations'] ?? [] as $rec) { - $this->line(' ๐Ÿ’ก ' . $rec); - } - - return ['success' => false, 'status' => 'issues_found', 'issues' => $diagnosis['issues']]; - } - - $this->warn(' ๐Ÿ”ง Emulator wordt gerepareerd...'); - - if ($full) { - $this->line(' ๐Ÿ“ฆ Volledige reset...'); - $repairResult = $service->repairEmulator(); - } else { - $repairResult = $service->repairEmulator(); - } - - if ($repairResult['success']) { - $this->info(' โœ… Emulator gerepareerd!'); - foreach ($repairResult['actions'] ?? [] as $action) { - $this->line(' - ' . $action); - } - - return ['success' => true, 'status' => 'repaired', 'actions' => $repairResult['actions']]; - } - - $this->error(' โŒ Emulator repair mislukt: ' . ($repairResult['error'] ?? 'Onbekend')); - if (! empty($repairResult['actions'])) { - $this->line(' Uitgevoerde acties:'); - foreach ($repairResult['actions'] as $action) { - $this->line(' - ' . $action); - } - } - - return ['success' => false, 'status' => 'failed', 'error' => $repairResult['error']]; - - } catch (\Exception $e) { - $this->error(' โŒ Emulator exception: ' . $e->getMessage()); - Log::error('[SystemRepair] Emulator exception', ['error' => $e->getMessage()]); - - return ['success' => false, 'status' => 'exception', 'error' => $e->getMessage()]; - } - } - - private function repairNitro(NitroUpdateService $service, bool $checkOnly, bool $force, bool $full): array - { - $this->info('Nitro controleren...'); - - $this->ensureBaseDirectories(); - - try { - $diagnosis = $service->diagnose(); - - if (empty($diagnosis['issues']) && ! $force && ! $full) { - $this->line(' โœ… Nitro is OK'); - - return ['success' => true, 'status' => 'ok']; - } - - if ($checkOnly) { - $this->warn(' โš ๏ธ Nitro problemen gevonden:'); - foreach ($diagnosis['issues'] ?? [] as $issue) { - $this->line(' - ' . $issue); - } - foreach ($diagnosis['recommendations'] ?? [] as $rec) { - $this->line(' ๐Ÿ’ก ' . $rec); - } - - return ['success' => false, 'status' => 'issues_found', 'issues' => $diagnosis['issues']]; - } - - $this->warn(' ๐Ÿ”ง Nitro wordt gerepareerd...'); - - if ($full) { - $this->line(' ๐Ÿ“ฆ Volledige reset...'); - $repairResult = $service->updateNitro(); - } else { - $repairResult = $service->repair(); - } - - if ($repairResult['success']) { - $this->info(' โœ… Nitro gerepareerd!'); - foreach ($repairResult['actions'] ?? [] as $action) { - $this->line(' - ' . $action); - } - - return ['success' => true, 'status' => 'repaired', 'actions' => $repairResult['actions']]; - } - - $this->error(' โŒ Nitro repair mislukt: ' . ($repairResult['error'] ?? 'Onbekend')); - if (! empty($repairResult['actions'])) { - $this->line(' Uitgevoerde acties:'); - foreach ($repairResult['actions'] as $action) { - $this->line(' - ' . $action); - } - } - - return ['success' => false, 'status' => 'failed', 'error' => $repairResult['error']]; - - } catch (\Exception $e) { - $this->error(' โŒ Nitro exception: ' . $e->getMessage()); - Log::error('[SystemRepair] Nitro exception', ['error' => $e->getMessage()]); - - return ['success' => false, 'status' => 'exception', 'error' => $e->getMessage()]; - } - } -} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index afa8406..7d0c3e3 100755 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -4,16 +4,10 @@ declare(strict_types=1); namespace App\Console; -use App\Console\Commands\AutoUpdateCommand; use App\Console\Commands\DDoSDetectionCommand; use App\Console\Commands\EmulatorMonitorCommand; -use App\Console\Commands\EmulatorUpdateCommand; use App\Console\Commands\FixCodeCommand; -use App\Console\Commands\GenerateNitroConfigs; -use App\Console\Commands\NitroUpdateCommand; use App\Console\Commands\SystemCheckCommand; -use App\Console\Commands\SystemHealthCommand; -use App\Console\Commands\SystemRepairCommand; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; @@ -28,9 +22,6 @@ class Kernel extends ConsoleKernel $schedule->command('maintenance:check-scheduled')->everyMinute()->withoutOverlapping(); $schedule->command('monitor:emulator')->everyMinute()->withoutOverlapping(); $schedule->command('monitor:ddos')->everyFiveMinutes()->withoutOverlapping(); - $schedule->command('update:auto')->everyMinute()->withoutOverlapping(); - $schedule->command('nitro:auto')->everyMinute()->withoutOverlapping(); - $schedule->command('system:repair')->everyTenMinutes()->withoutOverlapping(); } #[\Override] @@ -42,12 +33,6 @@ class Kernel extends ConsoleKernel $this->commands[] = FixCodeCommand::class; $this->commands[] = EmulatorMonitorCommand::class; $this->commands[] = DDoSDetectionCommand::class; - $this->commands[] = EmulatorUpdateCommand::class; - $this->commands[] = AutoUpdateCommand::class; - $this->commands[] = NitroUpdateCommand::class; - $this->commands[] = SystemRepairCommand::class; - $this->commands[] = SystemHealthCommand::class; - $this->commands[] = GenerateNitroConfigs::class; require base_path('routes/console.php'); } diff --git a/app/Filament/Pages/Monitoring/AlertSettings.php b/app/Filament/Pages/Monitoring/AlertSettings.php index 2448c96..ca66ae7 100755 --- a/app/Filament/Pages/Monitoring/AlertSettings.php +++ b/app/Filament/Pages/Monitoring/AlertSettings.php @@ -10,12 +10,8 @@ use App\Enums\AlertType; use App\Models\Miscellaneous\AlertLog; use App\Models\Miscellaneous\WebsiteSetting; use App\Services\AlertService; -use App\Services\EmulatorUpdateService; -use App\Services\NitroUpdateService; use App\Services\RconService; use App\Services\SettingsService; -use App\Services\SystemFixService; -use App\Services\UpdateHistoryService; use Carbon\Carbon; use Filament\Actions\Action; use Filament\Forms\Components\Placeholder; @@ -91,20 +87,6 @@ final class AlertSettings extends Page implements HasForms 'emulator_database_username' => $this->getSetting('emulator_database_username', ''), 'emulator_database_password' => $this->getSetting('emulator_database_password', ''), 'emulator_version' => $this->getSetting('emulator_version', 'Onbekend'), - 'auto_update_enabled' => $this->getSettingBool('auto_update_enabled'), - 'auto_update_schedule' => $this->getSetting('auto_update_schedule', '03:00'), - 'auto_update_days' => $this->getSetting('auto_update_days', '0,6'), - 'nitro_client_path' => $this->getSetting('nitro_client_path', '/var/www/atomcms/nitro-client'), - 'nitro_renderer_path' => $this->getSetting('nitro_renderer_path', '/var/www/atomcms/nitro-renderer'), - 'nitro_build_path' => $this->getSetting('nitro_build_path', '/var/www/atomcms/nitro-client/dist'), - 'nitro_webroot' => $this->getSetting('nitro_webroot', '/var/www/Client'), - 'nitro_github_branch' => $this->getSetting('nitro_github_branch', 'main'), - 'nitro_github_url' => $this->getSetting('nitro_github_url', ''), - 'nitro_site_url' => $this->getSetting('nitro_site_url', $this->getCurrentSiteUrl()), - 'nitro_auto_update_configs' => $this->getSettingBool('nitro_auto_update_configs'), - 'nitro_auto_update_enabled' => $this->getSettingBool('nitro_auto_update_enabled'), - 'nitro_auto_update_schedule' => $this->getSetting('nitro_auto_update_schedule', '03:00'), - 'nitro_auto_update_days' => $this->getSetting('nitro_auto_update_days', '0,6'), 'hotel_alert_message' => '', ]; } @@ -363,375 +345,11 @@ final class AlertSettings extends Page implements HasForms ]) ->default(AlertSeverity::ERROR->value), ]), - Section::make('Emulator Updates (.jar)') - ->description('100% automatisch emulator updaten vanaf GitHub') - ->icon('heroicon-o-archive-box-arrow-down') - ->columns(2) - ->afterHeader([ - Action::make('check_updates') - ->label('Check Updates') - ->action('checkEmulatorUpdates') - ->color('info'), - Action::make('save_update') - ->label('Opslaan') - ->action('saveEmulatorUpdate') - ->color('primary'), - ]) - ->schema([ - TextInput::make('emulator_github_url') - ->label('GitHub Repository URL') - ->placeholder('https://github.com/gebruiker/emulator-repo') - ->hint('Basis URL van je GitHub repo (voor pre-compiled JARs)') - ->columnSpanFull(), - TextInput::make('emulator_source_repo') - ->label('Source Repository URL (voor build vanaf source)') - ->placeholder('https://github.com/gebruiker/emulator-source') - ->hint('GitHub URL voor als je de emulator vanaf source wilt builden') - ->columnSpanFull(), - TextInput::make('emulator_jar_direct_url') - ->label('Directe .jar URL (optioneel)') - ->placeholder('https://github.com/user/repo/raw/main/Latest_Compiled_Version/emulator.jar') - ->hint('Als de auto-detect niet werkt, vul hier de directe URL in') - ->columnSpanFull(), - TextInput::make('emulator_jar_path') - ->label('Emulator Folder Pad') - ->placeholder('/root/emulator'), - TextInput::make('emulator_source_path') - ->label('Source Code Pad (voor build vanaf source)') - ->placeholder('/var/www/emulator-source') - ->hint('Locatie waar de source code wordt gekloond'), - TextInput::make('emulator_service_name') - ->label('Emulator Service Naam') - ->placeholder('arcturus'), - TextInput::make('emulator_database_host') - ->label('Database Host') - ->placeholder('127.0.0.1'), - TextInput::make('emulator_database_port') - ->label('Database Port') - ->placeholder('3306') - ->default('3306'), - TextInput::make('emulator_database_name') - ->label('Database Naam') - ->placeholder('habbo'), - TextInput::make('emulator_database_username') - ->label('Database Gebruiker') - ->placeholder('root'), - TextInput::make('emulator_database_password') - ->label('Database Wachtwoord') - ->password() - ->placeholder('โ€ขโ€ขโ€ขโ€ขโ€ขโ€ข'), - Select::make('emulator_github_branch') - ->label('Branch') - ->options(function () { - $url = setting('emulator_github_url', ''); - $repo = $this->parseRepoFromUrl($url); - if (! $repo) { - return ['main' => 'main']; - } - $branches = $this->fetchBranches($repo); - if ($branches === []) { - return ['main' => 'main']; - } - return $branches; - }) - ->default('main') - ->hint('Branches worden automatisch herkend vanuit GitHub'), - Placeholder::make('installed_jar') - ->label('') - ->content(function () { - $label = '
'; - $label .= ''; - $label .= 'Huidige .jar'; - $label .= ''; - $label .= '
'; - return new HtmlString($label . $this->getInstalledJarHtml()); - }), - Placeholder::make('current_version') - ->label('') - ->content(function () { - $label = '
'; - $label .= ''; - $label .= 'Versie'; - $label .= ''; - $label .= '
'; - return new HtmlString($label . $this->getEmulatorVersionHtml()); - }), - Placeholder::make('update_status') - ->label('') - ->content(function () { - $label = '
'; - $label .= ''; - $label .= 'Update Status'; - $label .= ''; - $label .= '
'; - return new HtmlString($label . $this->getUpdateStatusHtml()); - }), - Action::make('run_update') - ->label('๐Ÿ”„ Update Nu (100% Automatisch)') - ->action('runFullUpdate') - ->color('success') - ->visible(fn () => $this->isUpdateAvailable()), - Action::make('reset_update_date') - ->label('Reset Update Datum') - ->action('resetUpdateDate') - ->color('warning') - ->icon('heroicon-o-arrow-path'), - Action::make('force_check') - ->label('๐Ÿ”„ Force Check') - ->action('forceUpdateCheck') - ->color('info') - ->icon('heroicon-o-magnifying-glass'), - Action::make('switch_to_main') - ->label('๐Ÿ”„ Switch to Branch') - ->action('switchEmulatorBranch') - ->color('warning') - ->icon('heroicon-o-arrow-uturn-left') - ->visible(fn () => strtolower((string) setting('emulator_github_branch', 'main')) !== strtolower((string) setting('emulator_installed_branch', 'main'))), - Placeholder::make('debug_status') - ->label('') - ->content(function () { - $label = '
'; - $label .= ''; - $label .= 'Debug Info'; - $label .= ''; - $label .= '
'; - return new HtmlString($label . $this->getDebugStatusHtml()); - }) - ->columnSpanFull(), - ]), - Section::make('Automatische Updates') - ->description('Stel een automatische schema in voor emulator en SQL updates') - ->icon('heroicon-o-clock') - ->columns(3) - ->afterHeader([ - Action::make('save_auto_update') - ->label('Opslaan') - ->action('saveAutoUpdate') - ->color('primary'), - ]) - ->schema([ - Toggle::make('auto_update_enabled') - ->label('Automatische Updates Inschakelen') - ->columnSpan(3), - TextInput::make('auto_update_schedule') - ->label('Tijd (HH:MM)') - ->placeholder('03:00') - ->columnSpan(1), - TextInput::make('auto_update_days') - ->label('Dagen (0-6, kommagescheiden)') - ->placeholder('0,6') - ->hint('0 = Zondag, 1 = Maandag, ..., 6 = Zaterdag') - ->columnSpan(2), - Placeholder::make('auto_update_next') - ->label('') - ->content(function () { - $label = '
'; - $label .= ''; - $label .= 'Volgende Geplande Update'; - $label .= ''; - $label .= '
'; - - return new HtmlString($label . $this->getNextAutoUpdateHtml()); - }), - ]), - Section::make('Database Updates (SQL)') - ->description('Automatisch SQL updates uitvoeren van GitHub') - ->icon('heroicon-o-server') - ->columns(2) - ->afterHeader([ - Action::make('check_sql_updates') - ->label('Check SQL Updates') - ->action('checkSqlUpdates') - ->color('info'), - Action::make('run_sql_updates') - ->label('Run SQL Updates') - ->action('runSqlUpdates') - ->color('warning'), - Action::make('fix_all') - ->label('Auto Fix All') - ->action('fixAll') - ->color('success') - ->icon('heroicon-o-wrench'), - ]) - ->schema([ - Placeholder::make('sql_status') - ->label('') - ->content(function () { - $label = '
'; - $label .= ''; - $label .= 'SQL Status'; - $label .= ''; - $label .= '
'; - - return new HtmlString($label . $this->getSqlStatusHtml()); - }), - Placeholder::make('sql_last_update') - ->label('') - ->content(function () { - $label = '
'; - $label .= ''; - $label .= 'Laatste SQL Update'; - $label .= ''; - $label .= '
'; - - return new HtmlString($label . $this->getSqlLastUpdateHtml()); - }), - ]), - Section::make('Nitro Client Updates') - ->description('Automatisch Nitro client en renderer updaten en builden') - ->icon('heroicon-o-device-phone-mobile') - ->columns(2) - ->afterHeader([ - Action::make('check_nitro_updates') - ->label('Check Updates') - ->action('checkNitroUpdates') - ->color('info'), - Action::make('build_nitro') - ->label('Build & Deploy') - ->action('buildNitro') - ->color('warning'), - Action::make('save_nitro') - ->label('Opslaan') - ->action('saveNitro') - ->color('primary'), - Action::make('switch_nitro_main') - ->label('๐Ÿ”„ Switch to Branch') - ->action('switchNitroBranch') - ->color('warning') - ->icon('heroicon-o-arrow-uturn-left') - ->visible(fn () => strtolower((string) setting('nitro_github_branch', 'main')) !== strtolower((string) setting('nitro_installed_branch', 'main'))), - ]) - ->schema([ - TextInput::make('nitro_github_url') - ->label('GitHub Client URL') - ->placeholder('https://github.com/gebruiker/Nitro-V3') - ->hint('Auto-detecteert ook de renderer repo') - ->columnSpanFull(), - TextInput::make('nitro_client_path') - ->label('Client Pad') - ->placeholder('/var/www/atomcms/nitro-client'), - TextInput::make('nitro_renderer_path') - ->label('Renderer Pad') - ->placeholder('/var/www/atomcms/nitro-renderer'), - TextInput::make('nitro_build_path') - ->label('Build Output Pad') - ->placeholder('/var/www/atomcms/nitro-client/dist'), - TextInput::make('nitro_webroot') - ->label('Web Root Pad') - ->placeholder('/var/www/Client'), - Select::make('nitro_github_branch') - ->label('Branch') - ->options(function () { - $url = setting('nitro_github_url', ''); - $repo = $this->parseRepoFromUrl($url); - if (! $repo) { - return ['main' => 'main']; - } - $branches = $this->fetchBranches($repo); - if ($branches === []) { - return ['main' => 'main']; - } - - return $branches; - }) - ->default('main') - ->hint('Branches worden automatisch herkend vanuit GitHub'), - Placeholder::make('nitro_status') - ->label('') - ->content(function () { - $label = '
'; - $label .= ''; - $label .= 'Status'; - $label .= ''; - $label .= '
'; - - return new HtmlString($label . $this->getNitroStatusHtml()); - }), - Placeholder::make('nitro_auto_schedule') - ->label('') - ->content(function () { - $label = '
'; - $label .= ''; - $label .= 'Auto Update Schema'; - $label .= ''; - $label .= '
'; - - return new HtmlString($label . $this->getNitroScheduleHtml()); - }), - Toggle::make('nitro_auto_update_enabled') - ->label('Automatische Updates Inschakelen') - ->columnSpanFull(), - TextInput::make('nitro_auto_update_schedule') - ->label('Tijd (HH:MM)') - ->placeholder('03:00') - ->columnSpan(1), - TextInput::make('nitro_auto_update_days') - ->label('Dagen (0-6, kommagescheiden)') - ->placeholder('0,6') - ->hint('0 = Zondag, 1 = Maandag, ..., 6 = Zaterdag') - ->columnSpan(2), - Placeholder::make('nitro_auto_update_next') - ->label('') - ->content(function () { - $label = '
'; - $label .= ''; - $label .= 'Volgende Geplande Update'; - $label .= ''; - $label .= '
'; - - return new HtmlString($label . $this->getNitroAutoUpdateNextHtml()); - }), - ]), - Section::make('Nitro Config Generator') - ->description('Genereer en beheer Nitro client configuraties automatisch') - ->icon('heroicon-o-cog-6-tooth') - ->columns(2) - ->afterHeader([ - Action::make('generate_nitro_configs') - ->label('Genereer Configs') - ->action('generateNitroConfigs') - ->color('success'), - Action::make('save_nitro_url') - ->label('Opslaan URL') - ->action('saveNitroUrl') - ->color('primary'), - ]) - ->schema([ - TextInput::make('nitro_site_url') - ->label('Website URL') - ->placeholder('https://epicnabbo.nl') - ->hint('Basis URL voor alle client configuraties') - ->columnSpanFull(), - Toggle::make('nitro_auto_update_configs') - ->label('Automatisch configs bijwerken na deploy') - ->helperText('Bij elke deploy worden de URLs automatisch bijgewerkt'), - Placeholder::make('nitro_config_status') - ->label('') - ->content(function () { - $label = '
'; - $label .= ''; - $label .= 'Config Status'; - $label .= ''; - $label .= '
'; - - return new HtmlString($label . $this->getNitroConfigStatusHtml()); - }), - Placeholder::make('nitro_config_preview') - ->label('') - ->content(function () { - $label = '
'; - $label .= 'Actuele Configs'; - $label .= ''; - $label .= '
'; - - return new HtmlString($label . $this->getNitroConfigPreviewHtml()); - }), - ]), Section::make('Statistieken') ->description('Overzicht van recente alerts') ->icon('heroicon-o-chart-bar') @@ -740,15 +358,7 @@ final class AlertSettings extends Page implements HasForms ->label('') ->content(fn () => $this->getStatsHtml()), ]), - Section::make('Update Geschiedenis') - ->description('Overzicht van alle updates') - ->icon('heroicon-o-clock') - ->schema([ - Placeholder::make('update_history') - ->label('') - ->content(fn () => $this->getUpdateHistoryHtml()) - ->columnSpanFull(), - ]), + ]) ->statePath('data'); } @@ -858,228 +468,11 @@ final class AlertSettings extends Page implements HasForms $this->saveSettings(['alert_ddos_enabled', 'alert_ddos_threshold', 'alert_ddos_auto_block']); } - public function getUpdateHistoryHtml(): HtmlString - { - $historyService = app(UpdateHistoryService::class); - - return new HtmlString($historyService->getHtml()); - } - public function saveErrors(): void { $this->saveSettings(['alert_errors_enabled', 'alert_error_threshold', 'alert_min_severity']); } - public function saveEmulatorUpdate(): void - { - $this->saveSettings(['emulator_github_url', 'emulator_source_repo', 'emulator_jar_direct_url', 'emulator_jar_path', 'emulator_source_path', 'emulator_service_name', 'emulator_github_branch', 'emulator_database_host', 'emulator_database_port', 'emulator_database_name', 'emulator_database_username', 'emulator_database_password']); - Cache::forget('emulator_latest_version'); - Cache::forget('website_settings'); - SettingsService::clearCache(); - - Notification::make() - ->success() - ->title(__('Saved')) - ->body(__('Emulator update settings have been saved.')) - ->send(); - } - - public function saveAutoUpdate(): void - { - $this->saveSettings(['auto_update_enabled', 'auto_update_schedule', 'auto_update_days']); - - Notification::make() - ->success() - ->title(__('Saved')) - ->body(__('Automatic update schedule has been saved.')) - ->send(); - } - - public function runFullUpdate(): void - { - Cache::forget('website_settings'); - SettingsService::clearCache(); - - $updateService = new EmulatorUpdateService; - - if (! $updateService->isConfigured()) { - Notification::make() - ->warning() - ->title(__('Not configured')) - ->body(__('Please enter a GitHub URL and service name first.')) - ->send(); - - return; - } - - Notification::make() - ->info() - ->title('Emulator Updaten...') - ->body(__('Downloading, installing and restarting emulator...')) - ->send(); - - $result = $updateService->updateEmulator(); - - if ($result['success']) { - Notification::make() - ->success() - ->title('Update Succes!') - ->body($result['message']) - ->send(); - - $alertService = app(AlertService::class); - $alertService->send( - AlertType::EMULATOR_ONLINE, - "Emulator succesvol geรผpdatet naar v{$result['version']}", - ['version' => $result['version'], 'jar' => $result['jar'] ?? 'unknown'], - ); - } else { - Notification::make() - ->danger() - ->title(__('Update Failed')) - ->body($result['error'] ?? 'Er is iets misgegaan') - ->send(); - } - - $this->fillForm(); - } - - public function checkEmulatorUpdates(): void - { - Cache::forget('website_settings'); - SettingsService::clearCache(); - - $updateService = new EmulatorUpdateService; - - if (! $updateService->isConfigured()) { - Notification::make() - ->warning() - ->title(__('Not configured')) - ->body(__('Please enter a GitHub URL first and save.')) - ->send(); - - return; - } - - $this->fillForm(); - $check = $updateService->checkForUpdates(); - - if (isset($check['error'])) { - Notification::make() - ->danger() - ->title(__('Error')) - ->body($check['error']) - ->send(); - - return; - } - - if ($check['update_available']) { - Notification::make() - ->success() - ->title('Update Beschikbaar!') - ->body("Versie {$check['latest_version']} is beschikbaar (huidig: {$check['current_version']})") - ->send(); - } else { - Notification::make() - ->info() - ->title('Up-to-date') - ->body("Emulator is al up-to-date: v{$check['latest_version']}") - ->send(); - } - - $this->fillForm(); - } - - public function runEmulatorUpdate(): void - { - $this->downloadEmulatorJar(); - } - - public function isUpdateAvailable(): bool - { - Cache::forget('website_settings'); - SettingsService::clearCache(); - $updateService = new EmulatorUpdateService; - $check = $updateService->checkForUpdates(); - - $hasUpdate = $check['update_available'] ?? false; - $hasUrl = ($check['jar_url'] ?? null) !== null; - $isSourceBuild = ($check['type'] ?? '') === 'source_build'; - - return $hasUpdate && ($hasUrl || $isSourceBuild); - } - - public function getInstalledJarHtml(): HtmlString - { - $updateService = new EmulatorUpdateService; - $jarFiles = $updateService->getInstalledJarInfo(); - - if ($jarFiles !== []) { - $html = '
'; - foreach ($jarFiles as $jar) { - $name = e($jar['name']); - $size = e($jar['size']); - $html .= "
๐Ÿ“ฆ {$size} {$name}
"; - } - $html .= '
'; - - return new HtmlString($html); - } - - $jar = $updateService->getInstalledJar(); - if ($jar) { - return new HtmlString("๐Ÿ“ฆ {$jar}"); - } - - return new HtmlString('Geen .jar gevonden'); - } - - public function getEmulatorVersionHtml(): HtmlString - { - $updateService = new EmulatorUpdateService; - $version = $updateService->getInstalledVersion(); - - return new HtmlString("v{$version}"); - } - - public function getUpdateStatusHtml(): HtmlString - { - $updateService = new EmulatorUpdateService; - $check = $updateService->checkForUpdates(); - - if (! $updateService->isConfigured()) { - return new HtmlString('Configureer een GitHub URL'); - } - - if (isset($check['error'])) { - return new HtmlString('' . __('Error') . ': ' . $check['error'] . ''); - } - - if ($check['type'] === 'manual') { - return new HtmlString('โš ๏ธ Geen releases - handmatige download nodig'); - } - - if ($check['update_available']) { - $jarInfo = ''; - if ($check['jar_size'] ?? null) { - $jarInfo = " ({$check['jar_size']})"; - } - - $sourceBuild = ($check['type'] ?? '') === 'source_build'; - $sourceText = $sourceBuild ? ' (build vanaf source)' : ''; - - return new HtmlString("โœ… Update beschikbaar: v{$check['latest_version']}{$jarInfo}{$sourceText}"); - } - - $rateLimitedWarning = ''; - if ($check['type'] === 'direct_url' && ($check['gitHub_rate_limited'] ?? false)) { - $rateLimitedWarning = ' (โš ๏ธ GitHub API rate limited - kan updates met zelfde versie niet detecteren)'; - } - - return new HtmlString('โœ… Up-to-date (v' . $check['latest_version'] . ')' . $rateLimitedWarning); - } - private function saveSettings(array $keys): void { foreach ($keys as $key) { @@ -1097,256 +490,20 @@ final class AlertSettings extends Page implements HasForms ->send(); } - public function resetUpdateDate(): void - { - Cache::forget('website_settings'); - SettingsService::clearCache(); - $updateService = new EmulatorUpdateService; - $updateService->resetInstalledDate(); - - Notification::make() - ->success() - ->title('Reset Voltooid') - ->body(__('Update date reset. The next check will detect a new update.')) - ->send(); - - $this->fillForm(); - } - - public function forceUpdateCheck(): void - { - Cache::forget('website_settings'); - SettingsService::clearCache(); - - $updateService = new EmulatorUpdateService; - - // Clear all cached update data - $updateService->resetInstalledDate(); - - // Clear direct URL cached data - $directUrl = setting('emulator_jar_direct_url'); - if ($directUrl) { - WebsiteSetting::where('key', 'emulator_direct_url_info_' . md5((string) $directUrl))->delete(); - } - - // Re-check - $check = $updateService->checkForUpdates(); - - if ($check['update_available'] ?? false) { - Notification::make() - ->success() - ->title('Update Gevonden!') - ->body('Er is een nieuwe emulator versie: v' . ($check['latest_version'] ?? '?')) - ->send(); - } else { - $message = 'Geen update gevonden (v' . ($check['latest_version'] ?? setting('emulator_version', '?')) . ')'; - if ($check['type'] === 'direct_url' && ($check['gitHub_rate_limited'] ?? false)) { - $message .= ' - GitHub API rate limited'; - } - Notification::make() - ->info() - ->title('Check Result') - ->body($message) - ->send(); - } - - $this->fillForm(); - } - - public function switchEmulatorBranch(): void - { - $updateService = new EmulatorUpdateService; - $targetBranch = setting('emulator_github_branch', 'main'); - - // Clear all caches - DB::table('website_settings') - ->where('key', 'emulator_github_branch') - ->update(['value' => $targetBranch]); - - Cache::forget('website_settings'); - Cache::flush(); - SettingsService::clearCache(); - $updateService->resetInstalledDate(); - - $directUrl = setting('emulator_jar_direct_url'); - if ($directUrl) { - WebsiteSetting::where('key', 'emulator_direct_url_info_' . md5((string) $directUrl))->delete(); - } - - // Check and install from selected branch - $check = $updateService->checkForUpdates(); - - if (($check['update_available'] ?? false) && in_array($check['type'] ?? '', ['direct_url', 'github_folder'])) { - $result = $updateService->performUpdate($check); - - if ($result['success'] ?? false) { - Notification::make() - ->success() - ->title("๐Ÿ”„ Switched to {$targetBranch}!") - ->body('Emulator is bijgewerkt') - ->send(); - } else { - Notification::make() - ->danger() - ->title('Update mislukt') - ->body($result['error'] ?? 'Onbekende fout') - ->send(); - } - } else { - // Force set installed branch even if no update - setting('emulator_installed_branch', $targetBranch); - SettingsService::clearCache(); - Notification::make() - ->info() - ->title("Branch naar {$targetBranch} gezet") - ->body('Geen nieuwe update beschikbaar') - ->send(); - } - - $this->fillForm(); - } - public function clearAllLogs(): void { - $updateService = new EmulatorUpdateService; - $result = $updateService->clearAllLogs(); AlertLog::truncate(); Cache::flush(); Notification::make() ->success() ->title('๐Ÿ—‘๏ธ Alle Logs Geleegd!') - ->body($result['message'] . ': ' . implode(', ', $result['cleared'])) + ->body('Alle logs zijn gewist.') ->send(); $this->fillForm(); } - public function getDebugStatusHtml(): HtmlString - { - $updateService = new EmulatorUpdateService; - $debug = $updateService->debugStatus(); - - $html = ''; - - $html .= '
'; - - // JAR Files Section - $html .= '
'; - $html .= '
JAR Bestanden
'; - if (! empty($debug['jar_files'])) { - foreach ($debug['jar_files'] as $jar) { - $html .= '
'; - $html .= '
๐Ÿ“ฆ
'; - $html .= '
'; - $html .= '
' . e($jar['name']) . '
'; - $html .= '
'; - $html .= '
' . e($jar['size']) . '
'; - $html .= '
'; - } - } else { - $html .= '
Geen JAR gevonden
'; - } - $html .= '
' . e($debug['jar_path'] ?? '') . '
'; - $html .= '
'; - - // Stats Grid - $installedOk = ! empty($debug['installed_date_formatted']); - $commitOk = ! empty($debug['source_commit']); - $dateOk = ! empty($debug['source_date_formatted']); - $allOk = $installedOk && $commitOk && $dateOk; - - $version = e($debug['emulator_version'] ?: 'Onbekend'); - $commit = $debug['source_commit'] ? substr(e($debug['source_commit']), 0, 8) : 'Onbekend'; - $commitColor = $commitOk ? 'success' : 'neutral'; - $dateColor = $dateOk ? 'success' : 'neutral'; - - $html .= '
'; - $html .= '
'; - $html .= '
'; - $html .= '
' . $version . '
'; - $html .= '
Versie
'; - $html .= '
'; - $html .= '
'; - $html .= '
' . ($allOk ? '100%' : '!') . '
'; - $html .= '
Tracking
'; - $html .= '
'; - $html .= '
'; - $html .= '
' . ($installedOk ? substr((string) $debug['installed_date_formatted'], 0, 10) : 'โ€”') . '
'; - $html .= '
Geรฏnstalleerd
'; - $html .= '
'; - $html .= '
'; - $html .= '
' . ($dateOk ? substr((string) $debug['source_date_formatted'], 0, 10) : 'โ€”') . '
'; - $html .= '
Source Datum
'; - $html .= '
'; - $html .= '
'; - - // Commit Info - $html .= '
'; - $html .= '
'; - $html .= '
Git Commit
'; - $html .= '
'; - $html .= '
SHA
'; - $html .= '
' . $commit . '
'; - $html .= '
'; - $html .= '
'; - $html .= '
Geรฏnstalleerd
'; - $html .= '
' . ($debug['installed_date_formatted'] ?: 'Nooit') . '
'; - $html .= '
'; - $html .= '
'; - - // Repos - $html .= '
'; - $html .= '
'; - $html .= '
Repositories
'; - $html .= '
'; - $html .= '
JAR Repo
'; - $repo = e($debug['github_repo'] ?: 'Niet geconfigureerd'); - $html .= '
' . $repo . '
'; - $html .= '
'; - $html .= '
'; - $html .= '
Source Repo
'; - $sourceRepo = e($debug['source_repo'] ?: 'Niet geconfigureerd'); - $html .= '
' . (strlen($sourceRepo) > 25 ? substr($sourceRepo, 0, 22) . 'โ€ฆ' : $sourceRepo) . '
'; - $html .= '
'; - $html .= '
'; - $html .= '
Branch
'; - $branch = e($debug['github_branch'] ?: 'main'); - $html .= '
' . $branch . '
'; - $html .= '
'; - $html .= '
'; - - $html .= '
'; - - return new HtmlString($html); - } - public function getOnlineUsersHtml(): HtmlString { try { @@ -1771,608 +928,6 @@ final class AlertSettings extends Page implements HasForms ->send(); } - public function checkSqlUpdates(): void - { - Cache::forget('website_settings'); - SettingsService::clearCache(); - $updateService = new EmulatorUpdateService; - $result = $updateService->checkForSqlUpdates(); - - if ($result['has_updates'] ?? false) { - Notification::make() - ->warning() - ->title('SQL Updates Beschikbaar!') - ->body($result['message']) - ->send(); - } else { - Notification::make() - ->info() - ->title('Geen SQL Updates') - ->body($result['message'] ?? 'Alle SQL updates zijn al toegepast') - ->send(); - } - - $this->fillForm(); - } - - public function fixAll(): void - { - Cache::forget('website_settings'); - SettingsService::clearCache(); - - $fixService = new SystemFixService; - $results = $fixService->checkAndFixAll(); - - $errors = []; - $fixed = []; - $ok = []; - - foreach ($results as $result) { - if ($result['status'] === 'error') { - $errors[] = $result['item'] . ': ' . $result['message']; - } elseif ($result['status'] === 'fixed') { - $fixed[] = $result['item']; - } else { - $ok[] = $result['item']; - } - } - - if ($errors !== []) { - Notification::make() - ->danger() - ->title('Systeem Fix Fouten') - ->body(implode("\n", $errors)) - ->send(); - } elseif ($fixed !== []) { - Notification::make() - ->success() - ->title('Systeem Gefixt!') - ->body(count($fixed) . ' items automatisch gerepareerd: ' . implode(', ', $fixed)) - ->send(); - } else { - Notification::make() - ->info() - ->title('Systeem OK') - ->body('Alle checks geslaagd - geen reparaties nodig') - ->send(); - } - - $this->fillForm(); - } - - public function runSqlUpdates(): void - { - Cache::forget('website_settings'); - SettingsService::clearCache(); - $updateService = new EmulatorUpdateService; - - Notification::make() - ->info() - ->title('SQL Updates Uitvoeren...') - ->body('Database updates worden toegepast.') - ->send(); - - $result = $updateService->runSqlUpdates(); - - if ($result['success']) { - Notification::make() - ->success() - ->title('SQL Updates Succes!') - ->body($result['message']) - ->send(); - - if ($result['sql_updated'] ?? false) { - $alertService = app(AlertService::class); - $alertService->send( - AlertType::EMULATOR_ONLINE, - 'SQL database updates succesvol uitgevoerd', - ['files' => implode(', ', $result['files_run'] ?? [])], - ); - } - } else { - Notification::make() - ->danger() - ->title(__('SQL Update Failed')) - ->body($result['error'] ?? 'Er is iets misgegaan') - ->send(); - } - - $this->fillForm(); - } - - public function getSqlStatusHtml(): HtmlString - { - Cache::forget('website_settings'); - $updateService = new EmulatorUpdateService; - $result = $updateService->checkForSqlUpdates(); - - if ($result['has_updates'] ?? false) { - return new HtmlString('โš ๏ธ ' . $result['message'] . ''); - } - - return new HtmlString('โœ… ' . ($result['message'] ?? 'Up-to-date') . ''); - } - - public function getNextAutoUpdateHtml(): HtmlString - { - $enabled = $this->getSettingBool('auto_update_enabled'); - - if (! $enabled) { - return new HtmlString('Automatische updates uitgeschakeld'); - } - - $scheduleTime = $this->getSetting('auto_update_schedule', '03:00'); - $scheduleDays = $this->getSetting('auto_update_days', '0,6'); - $allowedDays = array_map(intval(...), explode(',', $scheduleDays)); - - $dayNames = ['Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag']; - $days = implode(', ', array_map(fn ($d) => $dayNames[$d] ?? $d, $allowedDays)); - - $now = now(); - $today = $now->dayOfWeek; - Carbon::parse($scheduleTime, $now->timezone); - - $nextRun = $now->copy(); - $daysUntil = 0; - - for ($i = 0; $i <= 7; $i++) { - $checkDay = ($today + $i) % 7; - if (in_array($checkDay, $allowedDays)) { - $daysUntil = $i; - break; - } - } - - $nextRun->addDays($daysUntil)->setTimeFromTimeString($scheduleTime); - - return new HtmlString('๐Ÿ“… ' . $nextRun->format('d M Y, H:i') . ' (' . $days . ')'); - } - - public function getSqlLastUpdateHtml(): HtmlString - { - $updateService = new EmulatorUpdateService; - $lastUpdate = $updateService->getLastSqlUpdate(); - - if ($lastUpdate) { - return new HtmlString('' . Carbon::parse($lastUpdate)->format('d M Y, H:i') . ''); - } - - return new HtmlString('Nog nooit uitgevoerd'); - } - - public function getNitroAutoUpdateNextHtml(): HtmlString - { - $enabled = $this->getSettingBool('nitro_auto_update_enabled'); - - if (! $enabled) { - return new HtmlString('Automatische Nitro updates uitgeschakeld'); - } - - $scheduleTime = $this->getSetting('nitro_auto_update_schedule', '03:00'); - $scheduleDays = $this->getSetting('nitro_auto_update_days', '0,6'); - $allowedDays = array_map(intval(...), explode(',', $scheduleDays)); - - $dayNames = ['Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag']; - $days = implode(', ', array_map(fn ($d) => $dayNames[$d] ?? $d, $allowedDays)); - - $now = now(); - $today = $now->dayOfWeek; - Carbon::parse($scheduleTime, $now->timezone); - - $nextRun = $now->copy(); - $daysUntil = 0; - - for ($i = 0; $i <= 7; $i++) { - $checkDay = ($today + $i) % 7; - if (in_array($checkDay, $allowedDays)) { - $daysUntil = $i; - break; - } - } - - $nextRun->addDays($daysUntil)->setTimeFromTimeString($scheduleTime); - - return new HtmlString('๐Ÿ“… ' . $nextRun->format('d M Y, H:i') . ' (' . $days . ')'); - } - - public function saveNitro(): void - { - $this->saveSettings(['nitro_github_url', 'nitro_client_path', 'nitro_renderer_path', 'nitro_build_path', 'nitro_webroot', 'nitro_auto_update_enabled', 'nitro_auto_update_schedule', 'nitro_auto_update_days', 'nitro_github_branch']); - Cache::forget('website_settings'); - SettingsService::clearCache(); - - Notification::make() - ->success() - ->title(__('Saved')) - ->body('Nitro instellingen zijn opgeslagen.') - ->send(); - } - - public function switchNitroBranch(): void - { - $targetBranch = setting('nitro_github_branch', 'main'); - - // Set branch in DB - DB::table('website_settings') - ->where('key', 'nitro_github_branch') - ->update(['value' => $targetBranch]); - - Cache::forget('website_settings'); - Cache::flush(); - SettingsService::clearCache(); - - // Run update in background to prevent gateway timeout - $logFile = '/tmp/nitro-switch-' . date('Ymd-His') . '.log'; - $artisan = base_path('artisan'); - $php = config('app.php_binary', '/usr/bin/php'); - - Process::timeout(5)->run( - "nohup {$php} {$artisan} app:switch-nitro-branch --branch=" . escapeshellarg((string) $targetBranch) . " > {$logFile} 2>&1 &", - ); - - Notification::make() - ->success() - ->title("๐Ÿ”„ Switching to {$targetBranch}...") - ->body('Build draait op de achtergrond. Resultaat verschijnt vanzelf.') - ->send(); - } - - public function checkNitroUpdates(): void - { - Cache::forget('website_settings'); - SettingsService::clearCache(); - - // Update last checked time - $settings = app(SettingsService::class); - $settings->set('nitro_last_checked', now()->toIso8601String()); - - $nitroService = new NitroUpdateService; - $result = $nitroService->checkForUpdates(); - - if ($result['has_updates'] ?? false) { - $messages = []; - if ($result['client_update'] ?? false) { - $messages[] = 'Client update beschikbaar'; - } - if ($result['renderer_update'] ?? false) { - $messages[] = 'Renderer update beschikbaar'; - } - - Notification::make() - ->warning() - ->title('Nitro Updates Beschikbaar!') - ->body(implode(', ', $messages)) - ->send(); - } else { - Notification::make() - ->info() - ->title('Nitro Up-to-date') - ->body('Client en renderer zijn up-to-date.') - ->send(); - } - - $this->fillForm(); - } - - public function buildNitro(): void - { - Cache::forget('website_settings'); - SettingsService::clearCache(); - $nitroService = new NitroUpdateService; - - Notification::make() - ->info() - ->title('Nitro Build & Deploy...') - ->body('Client wordt gebouwd en gedeployed.') - ->send(); - - $result = $nitroService->updateNitro(); - - if ($result['success']) { - Notification::make() - ->success() - ->title('Nitro Update Succes!') - ->body($result['message'] ?? 'Client succesvol geรผpdatet en gedeployed.') - ->send(); - } else { - Notification::make() - ->danger() - ->title(__('Nitro Update Failed')) - ->body($result['error'] ?? $result['message'] ?? 'Er is iets misgegaan') - ->send(); - } - - $this->fillForm(); - } - - public function getNitroStatusHtml(): HtmlString - { - Cache::forget('nitro_status'); - - $nitroService = new NitroUpdateService; - $status = $nitroService->getStatus(); - - // Get update info from GitHub - $updateCheck = $nitroService->checkForUpdates(); - - $clientCommit = $status['client_commit'] ?? 'N/A'; - $rendererCommit = $status['renderer_commit'] ?? 'N/A'; - $latestClient = $updateCheck['client_commit'] ?? $clientCommit; - $latestRenderer = $updateCheck['renderer_commit'] ?? $rendererCommit; - - $clientUpdate = $clientCommit !== $latestClient; - $rendererUpdate = $rendererCommit !== $latestRenderer; - $hasUpdates = $clientUpdate || $rendererUpdate; - - $this->getNitroTranslations(); - - // Calculate health score with fallback checks - $checkDirShell = function (string $path): bool { - $result = Process::timeout(3)->run('test -d ' . escapeshellarg($path) . ' && echo "yes" || echo "no"'); - - return trim($result->output()) === 'yes'; - }; - $checkFileShell = function (string $path): bool { - $result = Process::timeout(3)->run('test -f ' . escapeshellarg($path) . ' && echo "yes" || echo "no"'); - - return trim($result->output()) === 'yes'; - }; - - $buildPath = $status['build_path'] ?? '/var/www/atomcms/nitro-client/dist'; - $webroot = $status['webroot'] ?? '/var/www/Client'; - $clientPath = $status['client_path'] ?? '/var/www/nitro-client'; - $rendererPath = $status['renderer_path'] ?? '/var/www/nitro-renderer'; - - $checks = [ - // With fallbacks - check status or use shell fallback - ($status['client_installed'] ?? false) || $checkDirShell($clientPath . '/.git'), - ($status['renderer_installed'] ?? false) || $checkDirShell($rendererPath . '/.git'), - ($status['build_exists'] ?? false) || $checkDirShell($buildPath), - ($status['deployed'] ?? false) || $checkFileShell($webroot . '/renderer-config.json'), - ($status['symlink_valid'] ?? false) || $checkDirShell($webroot . '/gamedata') || $checkDirShell('/var/www/Gamedata'), - ($status['client_node_modules'] ?? false) || $checkDirShell($clientPath . '/node_modules'), - ($status['renderer_node_modules'] ?? false) || $checkDirShell($rendererPath . '/node_modules'), - ($status['renderer_config_valid'] ?? false) || $checkFileShell($webroot . '/renderer-config.json'), - ($status['ui_config_valid'] ?? false) || $checkFileShell($webroot . '/ui-config.json'), - ($status['nitro_config_valid'] ?? false) || $checkFileShell($webroot . '/UITexts.json'), - ($status['has_index_js'] ?? false) || $checkFileShell($buildPath . '/index.js'), - ($status['has_renderer_js'] ?? false) || $checkFileShell($buildPath . '/renderer.js'), - ($status['has_vendor_js'] ?? false) || $checkFileShell($buildPath . '/vendor.js'), - ($status['has_css_file'] ?? false) || Process::timeout(3)->run('find ' . escapeshellarg($buildPath) . " -name '*.css' -type f 2>/dev/null | head -1")->output() !== '', - ($status['vite_config_valid'] ?? false) || $checkFileShell($clientPath . '/vite.config.js') || $checkFileShell($clientPath . '/vite.config.mjs'), - ($status['has_uitexts_json'] ?? false) || $checkFileShell($webroot . '/UITexts.json'), - ($status['has_renderer_example'] ?? false) || $checkFileShell($buildPath . '/renderer-config.example') || $checkFileShell($webroot . '/renderer-config.example'), - ($status['has_uiconfig_example'] ?? false) || $checkFileShell($buildPath . '/ui-config.example') || $checkFileShell($webroot . '/ui-config.example'), - ($status['has_image_assets'] ?? false) || $checkDirShell($webroot . '/assets') || $checkDirShell($buildPath . '/assets'), - ($status['has_favicon'] ?? false) || $checkFileShell($webroot . '/favicon.ico') || $checkFileShell($buildPath . '/favicon.ico'), - ($status['assets_writable'] ?? false) || Process::timeout(3)->run('test -w ' . escapeshellarg($webroot) . ' && echo "yes" || echo "no"')->output() === "yes\n", - ($status['has_renderer_dist'] ?? false) || $checkDirShell($rendererPath . '/node_modules'), - ($status['has_renderer_index'] ?? false) || $checkDirShell($rendererPath . '/packages'), - ($status['webroot_exists'] ?? false) || $checkDirShell($webroot), - ($status['webroot_writable'] ?? false) || Process::timeout(3)->run('test -w ' . escapeshellarg($webroot) . ' && echo "yes" || echo "no"')->output() === "yes\n", - ($status['nginx_config_valid'] ?? false) || Process::timeout(3)->run('test -f /etc/nginx/sites-enabled/cms.conf -o -f /etc/nginx/sites-enabled/atom.conf -o -f /etc/nginx/conf.d/atom.conf && echo "yes" || echo "no"')->output() === "yes\n", - // open_basedir check with direct fallback - ($status['open_basedir_fixed'] ?? false) || (in_array(ini_get('open_basedir'), ['', '0'], true) || ini_get('open_basedir') === false), - // Connection checks - use direct fallback - ($status['websocket_accessible'] ?? false) || ($status['websocket_accessible'] ?? true), - ($status['emulator_connected'] ?? false) || ($status['emulator_connected'] ?? true), - ]; - - $validChecks = count($checks); - $passedChecks = count(array_filter($checks)); - $healthScore = $validChecks > 0 ? round(($passedChecks / $validChecks) * 100) : 100; - - // Force 100% if we have the key files - if ($healthScore < 100) { - $hasKeyFiles = $checkFileShell($webroot . '/renderer-config.json') && - $checkFileShell($webroot . '/ui-config.json') && - $checkFileShell($webroot . '/UITexts.json'); - if ($hasKeyFiles) { - $healthScore = 100; - } - } - - $healthColor = match (true) { - $healthScore >= 100 => 'success', - $healthScore >= 75 => 'warning', - default => 'danger', - }; - - $healthLabel = match (true) { - $healthScore >= 100 => 'Alles OK', - $healthScore >= 75 => 'Waarschuwing', - default => 'Kritiek', - }; - - $html = ''; - - $html .= '
'; - - // Header with score - $html .= '
Nitro Client Status
' . $healthLabel . '
'; - $html .= '
' . $healthScore . '%
'; - - // Health bar - $html .= '
'; - - // Version Info Section - $html .= '
๐Ÿ“Œ Versie Informatie
'; - $html .= '
'; - - // Current Version - $html .= '
'; - $html .= '๐ŸŽฎ Client (lokaal)'; - $html .= '' . ($clientCommit !== 'N/A' ? substr((string) $clientCommit, 0, 7) : 'Niet geรฏnstalleerd'); - if ($clientUpdate) { - $html .= ' UPDATE'; - } - $html .= '
'; - - // Renderer Version - $html .= '
'; - $html .= '๐ŸŽจ Renderer (lokaal)'; - $html .= '' . ($rendererCommit !== 'N/A' ? substr((string) $rendererCommit, 0, 7) : 'Niet geรฏnstalleerd'); - if ($rendererUpdate) { - $html .= ' UPDATE'; - } - $html .= '
'; - - // Latest from GitHub - Client - $html .= '
'; - $html .= '๐ŸŒ Client (GitHub)'; - $html .= '' . ($latestClient !== 'N/A' ? substr((string) $latestClient, 0, 7) : 'N/A') . '
'; - - // Latest from GitHub - Renderer - $html .= '
'; - $html .= '๐ŸŒ Renderer (GitHub)'; - $html .= '' . ($latestRenderer !== 'N/A' ? substr((string) $latestRenderer, 0, 7) : 'N/A') . '
'; - - $html .= '
'; - - // Connection Section - $wsIcon = ($status['websocket_accessible'] ?? false) ? 'โœ…' : 'โŒ'; - $wsLabel = ($status['websocket_accessible'] ?? false) ? 'Online' : 'Offline'; - $emuIcon = ($status['emulator_connected'] ?? false) ? 'โœ…' : 'โŒ'; - $emuLabel = ($status['emulator_connected'] ?? false) ? 'Online' : 'Offline'; - $socketUrl = $status['socket_url'] ?? 'N/A'; - - $html .= '
๐ŸŒ Verbinding
'; - $html .= '
' . $wsIcon . '
WebSocket
' . $wsLabel . '
'; - $html .= '
' . $emuIcon . '
Emulator
' . $emuLabel . '
'; - $html .= '
๐Ÿ”—
Socket URL
' . htmlspecialchars($socketUrl) . '
'; - $html .= '
'; - - // System Section - $buildIcon = ($status['build_exists'] ?? false) ? 'โœ…' : 'โŒ'; - $webrootIcon = ($status['webroot_exists'] ?? false) ? 'โœ…' : 'โŒ'; - $deployedIcon = ($status['deployed'] ?? false) ? 'โœ…' : 'โŒ'; - $clientDepsIcon = ($status['client_node_modules'] ?? false) ? 'โœ…' : 'โŒ'; - $symlinkIcon = ($status['symlink_valid'] ?? false) ? 'โœ…' : 'โŒ'; - $writableIcon = ($status['webroot_writable'] ?? false) ? 'โœ…' : 'โŒ'; - - $html .= '
โš™๏ธ Systeem
'; - $html .= '
' . $buildIcon . '
Build
'; - $html .= '
' . $webrootIcon . '
Webroot
'; - $html .= '
' . $deployedIcon . '
Gedeployed
'; - $html .= '
' . $clientDepsIcon . '
Client deps
'; - $html .= '
' . $symlinkIcon . '
Symlink
'; - $html .= '
' . $writableIcon . '
Writable
'; - $html .= '
'; - - // Server Config Section - $nginxValid = ($status['nginx_config_valid'] ?? false) ? 'โœ…' : 'โŒ'; - - // Multiple fallback checks for open_basedir - $openBasedirFixed = false; - - // Check 1: ini_get directly - $openBaseDirValue = ini_get('open_basedir'); - if (in_array($openBaseDirValue, ['', '0', false], true)) { - $openBasedirFixed = true; - } - - // Check 2: from status - if (! $openBasedirFixed && ($status['open_basedir_fixed'] ?? false) === true) { - $openBasedirFixed = true; - } - - // Check 3: shell command check if PHP says it's restricted - if (! $openBasedirFixed) { - $testResult = Process::timeout(3)->run('php -r "echo ini_get(\'open_basedir\');"'); - $shellBasedir = trim($testResult->output() ?? ''); - if ($shellBasedir === '' || $shellBasedir === '0') { - $openBasedirFixed = true; - } - } - - // Check 4: check if PHP can access the paths it needs - if (! $openBasedirFixed) { - $canAccessClient = @is_dir('/var/www/nitro-client') || @is_dir('/var/www/atomcms/nitro-client'); - $canAccessRenderer = @is_dir('/var/www/nitro-renderer') || @is_dir('/var/www/atomcms/nitro-renderer'); - $canAccessWebroot = @is_dir('/var/www/Client'); - if ($canAccessClient && $canAccessRenderer && $canAccessWebroot) { - $openBasedirFixed = true; - } - } - - $openBasedir = $openBasedirFixed ? 'โœ…' : 'โŒ'; - - $webrootFileCount = $status['webroot_file_count'] ?? 0; - - $html .= '
๐Ÿ–ฅ๏ธ Server Config
'; - $html .= '
' . $nginxValid . '
nginx.conf
'; - $html .= '
' . $openBasedir . '
open_basedir
'; - $html .= '
๐Ÿ“Š
Files
' . $webrootFileCount . '
'; - $html .= '
'; - - // Storage Section - $buildSize = $status['build_size'] ?? 0; - $webrootSize = $status['webroot_size'] ?? 0; - $buildSizeStr = $this->formatBytes($buildSize); - $webrootSizeStr = $this->formatBytes($webrootSize); - - $html .= '
๐Ÿ’พ Opslag
'; - $html .= '
๐Ÿ“ฆ
Build
' . $buildSizeStr . '
'; - $html .= '
๐ŸŒ
Webroot
' . $webrootSizeStr . '
'; - $html .= '
'; - - // Footer - $lastChecked = empty($status['last_checked']) ? 'Nooit' : Carbon::parse($status['last_checked'])->diffForHumans(); - - $html .= ''; - - $html .= '
'; - - return new HtmlString($html); - } - private function formatBytes(int $bytes): string { if ($bytes >= 1073741824) { @@ -2386,466 +941,11 @@ final class AlertSettings extends Page implements HasForms return $bytes . ' B'; } - private function getNitroTranslations(): array - { - return [ - 'title' => __('nitro.title'), - 'healthy' => __('nitro.healthy'), - 'warning' => __('nitro.warning'), - 'critical' => __('nitro.critical'), - 'installed' => __('nitro.installed'), - 'missing' => __('nitro.missing'), - 'available' => __('nitro.available'), - 'latest' => __('nitro.latest'), - 'client' => __('nitro.client'), - 'renderer' => __('nitro.renderer'), - 'status' => __('nitro.status'), - 'version' => __('nitro.version'), - 'remote' => __('nitro.remote'), - 'system' => __('nitro.system'), - 'build' => __('nitro.build'), - 'webroot' => __('nitro.webroot'), - 'deployed' => __('nitro.deployed'), - 'dependencies' => __('nitro.dependencies'), - 'symlink' => __('nitro.symlink'), - 'never' => __('nitro.never'), - 'off' => __('nitro.off'), - 'checked' => __('nitro.checked'), - 'auto' => __('nitro.auto'), - 'connection' => 'Verbinding', - 'build_files' => 'Build Bestanden', - 'config_files' => 'Config Bestanden', - 'storage' => 'Opslag', - ]; - } - - public function getNitroScheduleHtml(): HtmlString - { - $enabled = setting('nitro_auto_update_enabled', false); - $time = setting('nitro_auto_update_schedule', '04:00'); - $days = setting('nitro_auto_update_days', '0,6'); - - if (! $enabled) { - return new HtmlString('Automatische Nitro updates uitgeschakeld'); - } - - $allowedDays = array_map(intval(...), explode(',', $days)); - $dayNames = ['Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag']; - $daysText = implode(', ', array_map(fn ($d) => $dayNames[$d] ?? $d, $allowedDays)); - - return new HtmlString('๐Ÿ“… ' . $time . ' (' . $daysText . ')'); - } - - public function saveNitroUrl(): void - { - $settings = app(SettingsService::class); - $siteUrl = $this->data['nitro_site_url'] ?? setting('nitro_site_url', $this->getCurrentSiteUrl()); - $autoUpdate = $this->data['nitro_auto_update_configs'] ?? false; - - $settings->set('nitro_site_url', $siteUrl); - $settings->set('nitro_auto_update_configs', $autoUpdate ? '1' : '0'); - - Notification::make() - ->success() - ->title(__('Saved')) - ->body('Nitro site URL is opgeslagen: ' . $siteUrl) - ->send(); - - $this->fillForm(); - } - - public function generateNitroConfigs(): void - { - $siteUrl = setting('nitro_site_url', $this->getCurrentSiteUrl()); - - if (empty($siteUrl) || ! filter_var($siteUrl, FILTER_VALIDATE_URL)) { - Notification::make() - ->danger() - ->title('Ongeldige URL') - ->body('Voer een geldige URL in (bijv. https://epicnabbo.nl)') - ->send(); - - return; - } - - // Use Artisan command to generate configs - $exitCode = Artisan::call('app:generate-nitro-configs', [ - '--site-url' => $siteUrl, - ]); - - // Update last checked time - $settings = app(SettingsService::class); - $settings->set('nitro_last_checked', now()->toIso8601String()); - - if ($exitCode === 0) { - Notification::make() - ->success() - ->title('Configs Gegeneerd!') - ->body('renderer-config.json, ui-config.json en UITexts.json zijn bijgewerkt voor ' . $siteUrl) - ->send(); - } else { - Notification::make() - ->danger() - ->title('Config generatie mislukt') - ->body('Er is een fout opgetreden bij het genereren van de configs.') - ->send(); - } - - $this->fillForm(); - } - private function getCurrentSiteUrl(): string { return setting('site_url', config('app.url', 'https://epicnabbo.nl')); } - private function parseRepoFromUrl(string $url): ?string - { - if (preg_match('/github\.com\/([^\/]+\/[^\/\?#]+)/', $url, $matches)) { - return rtrim($matches[1], '/'); - } - - return null; - } - - private function fetchBranches(string $repo): array - { - $cacheKey = 'git_branches_' . md5($repo); - - return Cache::remember($cacheKey, 600, function () use ($repo) { - $token = config('services.github.token', ''); - $headers = ['User-Agent' => 'atomcms']; - if ($token) { - $headers['Authorization'] = 'Bearer ' . $token; - } - - $branches = []; - try { - $response = Http::withHeaders($headers) - ->timeout(10) - ->get("https://api.github.com/repos/{$repo}/branches?per_page=100"); - - if ($response->successful()) { - foreach ($response->json() as $branch) { - $name = $branch['name'] ?? ''; - $branches[strtolower($name)] = $name; - } - } - - if ($branches === []) { - $result = Process::timeout(10)->run("git ls-remote --heads https://github.com/{$repo}.git 2>/dev/null | awk '{print \$2}' | sed 's|refs/heads/||'"); - if ($result->successful() && ! in_array(trim($result->output()), ['', '0'], true)) { - foreach (array_filter(explode("\n", trim($result->output()))) as $name) { - $branches[strtolower($name)] = $name; - } - } - } - } catch (\Exception) { - } - - return $branches; - }); - } - - public function getNitroConfigStatusHtml(): HtmlString - { - $siteUrl = setting('nitro_site_url', $this->getCurrentSiteUrl()); - $autoUpdate = setting('nitro_auto_update_configs', false); - $generatedAt = setting('nitro_config_generated_at'); - - $nitroService = new NitroUpdateService; - $webroot = $nitroService->getStatus()['webroot'] ?? '/var/www/Client'; - $mtime = null; - foreach (['renderer-config.json', 'ui-config.json', 'UITexts.json'] as $file) { - $path = $webroot . '/' . $file; - if (is_file($path) && ($t = filemtime($path)) > ($mtime ?? 0)) { - $mtime = $t; - } - } - if (! $generatedAt && $mtime) { - $generatedAt = date('c', $mtime); - } - - $html = ''; - - $html .= '
'; - - // Stats Grid - $generatedCount = 0; - $nitroService = new NitroUpdateService; - $webroot = $nitroService->getStatus()['webroot'] ?? '/var/www/Client'; - if (is_file($webroot . '/renderer-config.json')) { - $generatedCount++; - } - if (is_file($webroot . '/ui-config.json')) { - $generatedCount++; - } - if (is_file($webroot . '/UITexts.json')) { - $generatedCount++; - } - - $allGenerated = $generatedCount === 3; - $html .= '
'; - $html .= '
'; - $html .= '
' . $generatedCount . '/3
'; - $html .= '
Configs
'; - $html .= '
'; - $html .= '
'; - $html .= '
' . ($autoUpdate ? 'AAN' : 'UIT') . '
'; - $html .= '
Auto-update
'; - $html .= '
'; - $html .= '
'; - - // Site URL - $html .= '
'; - $html .= '
'; - $html .= '
Website
'; - $html .= '
'; - $html .= '
Site URL
'; - $html .= '
'; - if ($siteUrl) { - $display = strlen((string) $siteUrl) > 28 - ? '' . e($siteUrl) . '' - : e($siteUrl); - $html .= '' . $display . ''; - } else { - $html .= 'Niet ingesteld'; - } - $html .= '
'; - $html .= '
'; - $html .= '
'; - - // Config URLs (cached for 5 minutes) - $rendererConfig = Cache::remember('nitro_renderer_config', 300, function () use ($webroot) { - if (is_file($webroot . '/renderer-config.json')) { - return @json_decode(file_get_contents($webroot . '/renderer-config.json'), true) ?: []; - } - - return []; - }); - $uiConfig = Cache::remember('nitro_ui_config', 300, function () use ($webroot) { - if (is_file($webroot . '/ui-config.json')) { - return @json_decode(file_get_contents($webroot . '/ui-config.json'), true) ?: []; - } - - return []; - }); - - $html .= '
'; - $html .= '
'; - $html .= '
' . e(__('Config URLs')) . '
'; - - $configUrls = [ - [__('Socket URL'), $rendererConfig['socket.url'] ?? null], - [__('Asset URL'), $rendererConfig['asset.url'] ?? null], - [__('Images URL'), $rendererConfig['images.url'] ?? null], - [__('Camera URL'), $uiConfig['camera.url'] ?? null], - [__('Thumbnails URL'), $uiConfig['thumbnails.url'] ?? null], - [__('Group Homepage'), $uiConfig['group.homepage.url'] ?? null], - [__('Habbopages URL'), $uiConfig['habbopages.url'] ?? null], - ]; - - foreach ($configUrls as [$label, $url]) { - if (! $url) { - continue; - } - $html .= '
'; - $html .= '
' . e($label) . '
'; - $html .= '
'; - $display = strlen((string) $url) > 28 - ? '' . e($url) . '' - : e($url); - $html .= '' . $display . ''; - $html .= '
'; - $html .= '
'; - } - $html .= '
'; - - // Generation Info - $html .= '
'; - $html .= '
'; - $html .= '
Config Generatie
'; - $html .= '
'; - $html .= '
Laatst gegenereerd
'; - $html .= '
'; - if ($generatedAt) { - $timeAgo = Carbon::parse($generatedAt)->diffForHumans(); - $fullDate = Carbon::parse($generatedAt)->format('d M Y, H:i'); - $html .= '' . e($timeAgo) . ''; - } else { - $html .= 'Nooit'; - } - $html .= '
'; - $html .= '
'; - $html .= '
'; - $html .= '
Auto-update na deploy
'; - $html .= '
'; - $html .= '' . ($autoUpdate ? 'Ja' : 'Nee') . ''; - $html .= '
'; - $html .= '
'; - $html .= '
'; - - $html .= '
'; - - return new HtmlString($html); - } - - public function getNitroConfigPreviewHtml(): HtmlString - { - $nitroService = new NitroUpdateService; - $webroot = $nitroService->getStatus()['webroot'] ?? '/var/www/Client'; - - $rendererExists = is_file($webroot . '/renderer-config.json'); - $uiExists = is_file($webroot . '/ui-config.json'); - $uitextsExists = is_file($webroot . '/UITexts.json'); - - $rendererConfig = []; - $uiConfig = []; - - if ($rendererExists) { - $content = @file_get_contents($webroot . '/renderer-config.json'); - if ($content) { - $rendererConfig = @json_decode($content, true) ?: []; - } - } - - if ($uiExists) { - $content = @file_get_contents($webroot . '/ui-config.json'); - if ($content) { - $uiConfig = @json_decode($content, true) ?: []; - } - } - - $html = ''; - - $html .= '
'; - - // Config Files - $html .= '
'; - $html .= '
Config Bestanden
'; - $configs = [ - 'renderer-config.json' => $rendererExists, - 'ui-config.json' => $uiExists, - 'UITexts.json' => $uitextsExists, - ]; - foreach ($configs as $name => $exists) { - $html .= '
'; - $html .= '
' . ($exists ? 'โœ…' : 'โŒ') . '
'; - $html .= '
' . e($name) . '
'; - $html .= '
' . ($exists ? 'Present' : 'Missing') . '
'; - $html .= '
'; - } - $html .= '
'; - - // renderer-config.json - if ($rendererExists && ! empty($rendererConfig)) { - $html .= '
'; - $html .= '
'; - $html .= '
renderer-config.json
'; - $rendererFields = [ - 'socket.url' => [$rendererConfig['socket.url'] ?? null, __('Socket URL')], - 'asset.url' => [$rendererConfig['asset.url'] ?? null, __('Asset URL')], - 'gamedata.url' => [$rendererConfig['gamedata.url'] ?? null, __('Gamedata URL')], - 'images.url' => [$rendererConfig['images.url'] ?? null, __('Images URL')], - ]; - foreach ($rendererFields as [$value, $label]) { - if (! $value) { - continue; - } - $html .= '
'; - $html .= '
' . e($label) . '
'; - $html .= '
'; - $display = strlen((string) $value) > 30 - ? '' . e($value) . '' - : e($value); - $html .= '' . $display . ''; - $html .= '
'; - $html .= '
'; - } - $html .= '
'; - } - - // ui-config.json - if ($uiExists && ! empty($uiConfig)) { - $html .= '
'; - $html .= '
'; - $html .= '
ui-config.json
'; - $uiFields = [ - 'camera.url' => [$uiConfig['camera.url'] ?? null, __('Camera URL')], - 'thumbnails.url' => [$uiConfig['thumbnails.url'] ?? null, __('Thumbnails URL')], - 'group.homepage.url' => [$uiConfig['group.homepage.url'] ?? null, __('Group Homepage')], - 'habbopages.url' => [$uiConfig['habbopages.url'] ?? null, __('Habbopages URL')], - 'url.prefix' => [$uiConfig['url.prefix'] ?? null, __('URL Prefix')], - ]; - foreach ($uiFields as [$value, $label]) { - if (! $value) { - continue; - } - $html .= '
'; - $html .= '
' . e($label) . '
'; - $html .= '
'; - $display = strlen((string) $value) > 30 - ? '' . e($value) . '' - : e($value); - $html .= '' . $display . ''; - $html .= '
'; - $html .= '
'; - } - $html .= '
'; - } - - if (! $rendererExists && ! $uiExists) { - $html .= '
'; - $html .= '
โš ๏ธ
'; - $html .= 'Geen config bestanden gevonden
'; - $html .= 'Genereer configs via de knop hierboven'; - $html .= '
'; - } - - $html .= '
'; - - return new HtmlString($html); - } - private function clearSettingsCache(): void { Cache::forget('website_settings'); diff --git a/app/Filament/Pages/Monitoring/Commandocentrum.php b/app/Filament/Pages/Monitoring/Commandocentrum.php index 1e913e2..09f1d72 100755 --- a/app/Filament/Pages/Monitoring/Commandocentrum.php +++ b/app/Filament/Pages/Monitoring/Commandocentrum.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace App\Filament\Pages\Monitoring; use App\Actions\Commandocentrum\EmulatorControlAction; -use App\Actions\Commandocentrum\NitroControlAction; use App\Enums\AlertSeverity; use App\Models\Miscellaneous\WebsitePermission; use App\Models\StaffActivity; @@ -16,7 +15,6 @@ use App\Services\Diagnostics\DiagnosticRunner; use App\Services\GitHubService; use App\Services\RconService; use App\Services\SettingsService; -use App\Services\UpdateHistoryService; use BackedEnum; use Exception; use Filament\Actions\Action; @@ -106,21 +104,6 @@ final class Commandocentrum extends Page implements HasForms 'emulator_database_username' => $this->getSetting('emulator_database_username', ''), 'emulator_database_password' => $this->getSetting('emulator_database_password', ''), 'emulator_version' => $this->getSetting('emulator_version', 'Onbekend'), - 'auto_update_enabled' => $this->getSettingBool('auto_update_enabled'), - 'auto_update_schedule' => $this->getSetting('auto_update_schedule', '03:00'), - 'auto_update_days' => $this->getSetting('auto_update_days', '0,6'), - 'nitro_client_path' => $this->getSetting('nitro_client_path', $paths['nitro_client_path']), - 'nitro_renderer_path' => $this->getSetting('nitro_renderer_path', $paths['nitro_renderer_path']), - 'nitro_build_path' => $this->getSetting('nitro_build_path', $paths['nitro_build_path']), - 'nitro_webroot' => $this->getSetting('nitro_webroot', $paths['nitro_webroot']), - 'gamedata_path' => $this->getSetting('gamedata_path', $paths['gamedata_path']), - 'nitro_github_branch' => $this->getSetting('nitro_github_branch', 'main'), - 'nitro_github_url' => $this->getSetting('nitro_github_url', ''), - 'nitro_site_url' => $this->getSetting('nitro_site_url', $this->getCurrentSiteUrl()), - 'nitro_auto_update_configs' => $this->getSettingBool('nitro_auto_update_configs'), - 'nitro_auto_update_enabled' => $this->getSettingBool('nitro_auto_update_enabled'), - 'nitro_auto_update_schedule' => $this->getSetting('nitro_auto_update_schedule', '03:00'), - 'nitro_auto_update_days' => $this->getSetting('nitro_auto_update_days', '0,6'), 'hotel_alert_message' => '', ]; } @@ -233,92 +216,6 @@ final class Commandocentrum extends Page implements HasForms ->content(fn () => $this->renderEmulatorInfoView()), ]), - Section::make(__('commandocentrum.emulator_updates')) - ->description(__('commandocentrum.emulator_updates_desc')) - ->icon('heroicon-o-arrow-down-circle') - ->afterHeader([ - Action::make('check_updates') - ->label(__('commandocentrum.check_updates')) - ->color('info') - ->action('checkEmulatorUpdates'), - Action::make('build_emulator') - ->label('๐Ÿ”จ ' . __('commandocentrum.build')) - ->color('success') - ->action('buildEmulator'), - Action::make('run_sql') - ->label(__('commandocentrum.sql_updates')) - ->color('purple') - ->action('runSqlUpdates'), - Action::make('save_emulator') - ->label(__('commandocentrum.save')) - ->color('primary') - ->action('saveEmulator'), - ]) - ->schema([ - Placeholder::make('emulator_settings') - ->label('') - ->content(fn () => $this->renderEmulatorSettingsView()), - ]), - - Section::make(__('commandocentrum.emulator_backups')) - ->description(__('commandocentrum.emulator_backups_desc')) - ->icon('heroicon-s-archive-box') - ->schema([ - Placeholder::make('backups_list') - ->label('') - ->content(fn () => $this->renderBackupsListView()), - ]), - - Section::make(__('commandocentrum.nitro_client')) - ->description(__('commandocentrum.nitro_client_desc')) - ->icon('heroicon-o-cloud-arrow-down') - ->afterHeader([ - Action::make('detect_paths') - ->label('๐Ÿ” ' . __('commandocentrum.auto_detect')) - ->color('success') - ->action('detectAndSavePaths'), - Action::make('check_nitro') - ->label(__('commandocentrum.check')) - ->color('info') - ->action('checkNitroUpdates'), - Action::make('build_nitro') - ->label(__('commandocentrum.build')) - ->color('pink') - ->action('buildNitro'), - Action::make('generate_configs') - ->label(__('commandocentrum.generate_configs')) - ->color('indigo') - ->action('generateNitroConfigs'), - Action::make('save_nitro') - ->label(__('commandocentrum.save')) - ->color('primary') - ->action('saveNitro'), - ]) - ->schema([ - Placeholder::make('nitro_settings') - ->label('') - ->content(fn () => $this->renderNitroSettingsView()), - ]), - - Section::make(__('commandocentrum.auto_updates')) - ->description(__('commandocentrum.auto_updates_desc')) - ->icon('heroicon-o-clock') - ->columns(2) - ->afterHeader([ - Action::make('save_auto') - ->label(__('commandocentrum.save')) - ->color('primary') - ->action('saveAutoUpdate'), - ]) - ->schema([ - Toggle::make('auto_update_enabled') - ->label(__('commandocentrum.enable_auto_updates')), - TextInput::make('auto_update_schedule') - ->label(__('commandocentrum.schedule')), - TextInput::make('auto_update_days') - ->label(__('commandocentrum.days')), - ]), - Section::make(__('commandocentrum.clothing_sync')) ->description(__('commandocentrum.clothing_sync_desc')) ->icon('heroicon-o-user') @@ -367,15 +264,6 @@ final class Commandocentrum extends Page implements HasForms ->helperText(__('commandocentrum.discord_ranks_helper')), ]), - Section::make(__('commandocentrum.update_history')) - ->description(__('commandocentrum.update_history_desc')) - ->icon('heroicon-o-clock') - ->schema([ - Placeholder::make('history') - ->label('') - ->content(fn () => $this->renderUpdateHistoryView()), - ]), - Section::make(__('commandocentrum.social_login')) ->description(__('commandocentrum.social_login_desc')) ->icon('heroicon-o-user-circle') @@ -491,100 +379,6 @@ final class Commandocentrum extends Page implements HasForms ]); } - private function renderEmulatorSettingsView(): View - { - return view('filament.components.commandocentrum.emulator-settings', [ - 'emulatorBranchesHtml' => $this->getEmulatorBranchesHtml(), - 'emulatorStatusHtml' => $this->renderEmulatorStatusView()->render(), - ]); - } - - private function renderEmulatorStatusView(): View - { - $serviceName = $this->getSetting('emulator_service_name', 'arcturus'); - $jarPath = $this->getSetting('emulator_jar_path', '/var/www/Emulator'); - $sourcePath = $this->getSetting('emulator_source_path', '/var/www/emulator-source'); - $githubUrl = $this->getSetting('emulator_github_url', ''); - $branch = $this->getSetting('emulator_github_branch', 'main'); - - $jarExists = $this->fileExists($jarPath); - $sourceExists = $this->fileExists($sourcePath); - $sourceCommit = $this->getGitCommit($sourcePath); - $remoteVersion = $githubUrl !== '' && $githubUrl !== '0' ? $this->getRemoteCommit($githubUrl, $branch) : 'N/A'; - - $canBuild = false; - $checkDirs = [ - $sourcePath, - $sourcePath . '/Emulator', - $sourcePath . '/Emulator/Emulator', - $sourcePath . '/emulator', - $sourcePath . '/emulator/emulator', - ]; - foreach ($checkDirs as $dir) { - $check = $this->runCommand('test -f ' . escapeshellarg($dir . '/pom.xml') . ' && echo yes'); - if ($check && trim($check) === 'yes') { - $canBuild = true; - break; - } - } - - return view('filament.components.commandocentrum.emulator-status', [ - 'emulatorOnline' => $this->getEmulatorStatusText() === 'Online', - 'jarExists' => $jarExists, - 'serviceName' => $serviceName, - 'sourceCommit' => $sourceCommit, - 'remoteVersion' => $remoteVersion, - 'canBuild' => $canBuild, - 'jarPath' => $jarPath, - 'sourcePath' => $sourcePath, - ]); - } - - private function renderNitroSettingsView(): View - { - return view('filament.components.commandocentrum.nitro-settings', [ - 'nitroBranchesHtml' => $this->getNitroBranchesHtml(), - 'nitroStatusHtml' => $this->renderNitroStatusView()->render(), - ]); - } - - private function renderNitroStatusView(): View - { - $clientPath = $this->getSetting('nitro_client_path', '/var/www/nitro-client'); - $rendererPath = $this->getSetting('nitro_renderer_path', '/var/www/nitro-renderer'); - $webroot = $this->getSetting('nitro_webroot', '/var/www/Client'); - $clientGithubUrl = $this->getSetting('nitro_github_url', ''); - $rendererGithubUrl = $this->getSetting('nitro_renderer_github_url', 'https://github.com/duckietm/Nitro_Render_V3'); - - $clientCommit = $this->getGitCommit($clientPath); - $rendererCommit = $this->getGitCommit($rendererPath); - $clientRemote = $clientGithubUrl !== '' && $clientGithubUrl !== '0' ? $this->getRemoteCommit($clientGithubUrl, $this->getSetting('nitro_github_branch', 'main')) : 'N/A'; - $rendererRemote = $rendererGithubUrl !== '' && $rendererGithubUrl !== '0' ? $this->getRemoteCommit($rendererGithubUrl, $this->getSetting('nitro_renderer_github_branch', 'main')) : 'N/A'; - - return view('filament.components.commandocentrum.nitro-status', [ - 'clientExists' => $this->checkPathExists($clientPath), - 'rendererExists' => $this->checkPathExists($rendererPath), - 'webrootExists' => $this->checkPathExists($webroot), - 'clientCommit' => $clientCommit, - 'rendererCommit' => $rendererCommit, - 'clientRemote' => $clientRemote, - 'rendererRemote' => $rendererRemote, - ]); - } - - private function renderBackupsListView(): View - { - try { - $backups = app(EmulatorControlAction::class)->getBackups(); - } catch (Exception) { - $backups = []; - } - - return view('filament.components.commandocentrum.backups-list', [ - 'backups' => $backups, - ]); - } - private function renderClothingStatusView(): View { try { @@ -614,19 +408,6 @@ final class Commandocentrum extends Page implements HasForms ]); } - private function renderUpdateHistoryView(): View - { - try { - $history = app(UpdateHistoryService::class)->getRecent(10); - } catch (Exception) { - $history = []; - } - - return view('filament.components.commandocentrum.update-history', [ - 'history' => $history, - ]); - } - private function getSetting(string $key, string $default = ''): string { try { @@ -878,33 +659,6 @@ final class Commandocentrum extends Page implements HasForms } } - public function checkEmulatorUpdates(): void - { - $result = app(EmulatorControlAction::class)->update(); - $this->notify($result['success'] ?? false ? __('commandocentrum.success') : __('commandocentrum.error'), $result['message'] ?? $result['error'] ?? __('commandocentrum.unknown'), ($result['success'] ?? false) ? 'success' : 'danger'); - Cache::forget('all_updates_check'); - $this->fillForm(); - } - - public function buildEmulator(): void - { - $result = app(EmulatorControlAction::class)->build(); - $this->notify($result['success'] ?? false ? __('commandocentrum.success') : __('commandocentrum.error'), $result['message'] ?? $result['error'] ?? __('commandocentrum.unknown'), ($result['success'] ?? false) ? 'success' : 'danger'); - } - - public function runSqlUpdates(): void - { - $result = app(EmulatorControlAction::class)->runSqlUpdates(); - $this->notify($result['success'] ?? false ? __('commandocentrum.success') : __('commandocentrum.error'), $result['message'] ?? __('commandocentrum.unknown'), ($result['success'] ?? false) ? 'success' : 'danger'); - } - - public function restoreBackup(string $backupName): void - { - $result = app(EmulatorControlAction::class)->restoreBackup($backupName); - $this->notify($result['success'] ? __('commandocentrum.success') : __('commandocentrum.error'), $result['message'] ?? $result['error'] ?? __('commandocentrum.unknown'), $result['success'] ? 'success' : 'danger'); - $this->fillForm(); - } - public function saveEmulator(): void { try { @@ -924,38 +678,6 @@ final class Commandocentrum extends Page implements HasForms } } - public function checkNitroUpdates(): void - { - $clientPath = $this->getSetting('nitro_client_path', '/var/www/nitro-client'); - $rendererPath = $this->getSetting('nitro_renderer_path', '/var/www/nitro-renderer'); - $branch = $this->getSetting('nitro_github_branch', 'main'); - - $result = app(NitroControlAction::class)->pullUpdates($clientPath, $rendererPath, $branch); - $this->notify(__('commandocentrum.success'), $result['message'], 'success'); - $this->fillForm(); - } - - public function buildNitro(): void - { - $clientPath = $this->getSetting('nitro_client_path', '/var/www/nitro-client'); - $rendererPath = $this->getSetting('nitro_renderer_path', '/var/www/nitro-renderer'); - $branch = $this->getSetting('nitro_github_branch', 'main'); - - $result = app(NitroControlAction::class)->build($clientPath, $rendererPath, $branch); - $this->notify($result['success'] ? __('commandocentrum.success') : __('commandocentrum.warning'), $result['message'], $result['success'] ? 'success' : 'warning'); - } - - public function generateNitroConfigs(): void - { - $siteUrl = $this->getSetting('nitro_site_url', $this->getCurrentSiteUrl()); - $webroot = $this->getSetting('nitro_webroot', '/var/www/Client'); - $gamedataPath = $this->getSetting('gamedata_path', '/var/www/Gamedata'); - - $result = app(NitroControlAction::class)->generateConfigs($siteUrl, $webroot, $gamedataPath); - $this->notify($result['success'] ? __('commandocentrum.success') : __('commandocentrum.error'), $result['message'], $result['success'] ? 'success' : 'danger'); - $this->fillForm(); - } - public function syncClothing(): void { try { @@ -972,58 +694,6 @@ final class Commandocentrum extends Page implements HasForms } } - public function detectAndSavePaths(): void - { - try { - $paths = $this->autoDetectPaths(); - $settings = app(SettingsService::class); - - $settings->set('nitro_client_path', $paths['nitro_client_path']); - $settings->set('nitro_renderer_path', $paths['nitro_renderer_path']); - $settings->set('nitro_build_path', $paths['nitro_build_path']); - $settings->set('nitro_webroot', $paths['nitro_webroot']); - $settings->set('gamedata_path', $paths['gamedata_path']); - $settings->set('emulator_jar_path', $paths['emulator_jar_path']); - $settings->set('emulator_source_path', $paths['emulator_source_path']); - - $this->fillForm(); - $this->notify(__('commandocentrum.success'), __('commandocentrum.paths_detected'), 'success'); - } catch (Exception $e) { - $this->notify(__('commandocentrum.error'), $e->getMessage(), 'danger'); - } - } - - public function saveNitro(): void - { - try { - $settings = app(SettingsService::class); - $settings->set('nitro_client_path', $this->data['nitro_client_path'] ?? '/var/www/atomcms/nitro-client'); - $settings->set('nitro_renderer_path', $this->data['nitro_renderer_path'] ?? '/var/www/atomcms/nitro-renderer'); - $settings->set('nitro_build_path', $this->data['nitro_build_path'] ?? '/var/www/atomcms/nitro-client/dist'); - $settings->set('nitro_webroot', $this->data['nitro_webroot'] ?? '/var/www/Client'); - $settings->set('gamedata_path', $this->data['gamedata_path'] ?? '/var/www/Gamedata'); - $settings->set('nitro_github_url', $this->data['nitro_github_url'] ?? ''); - $settings->set('nitro_github_branch', $this->data['nitro_github_branch'] ?? 'main'); - $settings->set('nitro_site_url', $this->data['nitro_site_url'] ?? ''); - $this->notify(__('commandocentrum.success'), __('commandocentrum.nitro_settings_saved'), 'success'); - } catch (Exception $e) { - $this->notify(__('commandocentrum.error'), $e->getMessage(), 'danger'); - } - } - - public function saveAutoUpdate(): void - { - try { - $settings = app(SettingsService::class); - $settings->set('auto_update_enabled', ($this->data['auto_update_enabled'] ?? false) ? '1' : '0'); - $settings->set('auto_update_schedule', $this->data['auto_update_schedule'] ?? '03:00'); - $settings->set('auto_update_days', $this->data['auto_update_days'] ?? '0,6'); - $this->notify(__('commandocentrum.success'), __('commandocentrum.auto_update_saved'), 'success'); - } catch (Exception $e) { - $this->notify(__('commandocentrum.error'), $e->getMessage(), 'danger'); - } - } - public function saveAlerts(): void { try { diff --git a/app/Filament/Resources/Miscellaneous/AlertLogResource/AlertLogResource.php b/app/Filament/Resources/Miscellaneous/AlertLogResource/AlertLogResource.php index 7961312..f3b99b4 100755 --- a/app/Filament/Resources/Miscellaneous/AlertLogResource/AlertLogResource.php +++ b/app/Filament/Resources/Miscellaneous/AlertLogResource/AlertLogResource.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace App\Filament\Resources\Miscellaneous\AlertLogResource; use App\Models\Miscellaneous\AlertLog; -use App\Services\EmulatorUpdateService; use Filament\Actions\Action; use Filament\Notifications\Notification; use Filament\Resources\Resource; @@ -136,14 +135,12 @@ class AlertLogResource extends Resource ->icon('heroicon-o-trash') ->color('gray') ->action(function () { - $updateService = new EmulatorUpdateService; - $result = $updateService->clearAllLogs(); Cache::flush(); AlertLog::truncate(); Notification::make() ->success() ->title('๐Ÿ—‘๏ธ Alle Logs Geleegd!') - ->body($result['message']) + ->body('Alle logs zijn gewist.') ->send(); }) ->requiresConfirmation(), diff --git a/app/Filament/Widgets/UpdateCheckerWidget.php b/app/Filament/Widgets/UpdateCheckerWidget.php deleted file mode 100755 index 02e5bd0..0000000 --- a/app/Filament/Widgets/UpdateCheckerWidget.php +++ /dev/null @@ -1,343 +0,0 @@ -emulatorVersion = setting('emulator_version', '?'); - $this->nitroVersion = setting('nitro_client_version', '?'); - - try { - $this->onlineUsers = (int) DB::connection('mysql')->table('users')->where('online', '1')->count(); - $sizeResult = DB::connection('mysql')->select('SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 1) as db_size FROM information_schema.tables WHERE table_schema = DATABASE()'); - $this->dbSize = ($sizeResult[0]->db_size ?? '0') . ' MB'; - } catch (\Exception) { - $this->dbSize = '? MB'; - } - - $this->loadSqlStatus(); - - $cached = Cache::get('all_updates_check'); - - if ($cached !== null) { - $this->emulatorUpdate = $cached['emulator'] ?? false; - $this->latestEmulatorVersion = $cached['emulator_version'] ?? null; - $this->nitroUpdate = $cached['nitro'] ?? false; - $this->latestNitroVersion = $cached['nitro_version'] ?? null; - $this->hasAnyUpdate = $this->emulatorUpdate || $this->nitroUpdate; - - return; - } - - $this->performCheck(); - } - - private function loadSqlStatus(): void - { - try { - $updateService = new EmulatorUpdateService; - $sqlDiagnosis = $updateService->diagnoseSqlUpdates(); - - $this->sqlTableExists = $sqlDiagnosis['table_exists'] ?? false; - $this->sqlApplied = $sqlDiagnosis['applied_count'] ?? 0; - $this->sqlPending = $sqlDiagnosis['pending_count'] ?? 0; - } catch (\Exception $e) { - Log::error('[UpdateChecker] SQL status failed: ' . $e->getMessage()); - } - } - - public function performCheck(): void - { - try { - $updateService = new EmulatorUpdateService; - $check = $updateService->checkForUpdates(); - $this->emulatorUpdate = $check['update_available'] ?? false; - $this->latestEmulatorVersion = $check['latest_version'] ?? null; - } catch (\Exception $e) { - Log::error('[UpdateChecker] Emulator check failed: ' . $e->getMessage()); - } - - try { - $nitroService = new NitroUpdateService; - $nitroCheck = $nitroService->checkForUpdates(); - $this->nitroUpdate = $nitroCheck['has_updates'] ?? false; - if ($this->nitroUpdate) { - $parts = []; - if ($nitroCheck['client_update'] ?? false) { - $parts[] = 'Client'; - } - if ($nitroCheck['renderer_update'] ?? false) { - $parts[] = 'Renderer'; - } - $this->latestNitroVersion = implode(' + ', $parts); - } - } catch (\Exception $e) { - Log::error('[UpdateChecker] Nitro check failed: ' . $e->getMessage()); - } - - $this->hasAnyUpdate = $this->emulatorUpdate || $this->nitroUpdate; - - Cache::put('all_updates_check', [ - 'emulator' => $this->emulatorUpdate, - 'emulator_version' => $this->latestEmulatorVersion, - 'nitro' => $this->nitroUpdate, - 'nitro_version' => $this->latestNitroVersion, - ], now()->addMinutes(15)); - } - - public function forceCheck(): void - { - try { - Cache::forget('all_updates_check'); - $updateService = new EmulatorUpdateService; - $updateService->resetInstalledDate(); - $this->performCheck(); - - if ($this->hasAnyUpdate) { - Notification::make()->success()->title('Updates Gevonden!')->body('Klik op "Alles Updaten" om te installeren')->send(); - } else { - Notification::make()->info()->title('Geen Updates')->body('Alles is al up-to-date')->send(); - } - } catch (\Exception $e) { - Notification::make()->danger()->title('Fout')->body($e->getMessage())->send(); - } - } - - public function repairSystem(): void - { - try { - Cache::forget('all_updates_check'); - - $messages = []; - $errors = []; - - try { - $emuService = new EmulatorUpdateService; - $repairResult = $emuService->repairEmulator(); - - if ($repairResult['success']) { - $messages[] = '๐Ÿ–ฅ๏ธ Emulator: ' . ($repairResult['message'] ?? 'Gerepareerd'); - } else { - $errors[] = 'Emulator: ' . ($repairResult['error'] ?? 'Onbekende fout'); - } - } catch (\Exception $e) { - $errors[] = 'Emulator: ' . $e->getMessage(); - } - - try { - $nitroService = new NitroUpdateService; - $repairResult = $nitroService->repair(); - - if ($repairResult['success']) { - $messages[] = '๐ŸŽฎ Nitro: ' . ($repairResult['message'] ?? 'Gerepareerd'); - } else { - $errors[] = 'Nitro: ' . ($repairResult['error'] ?? 'Onbekende fout'); - } - } catch (\Exception $e) { - $errors[] = 'Nitro: ' . $e->getMessage(); - } - - if ($messages !== []) { - Notification::make()->success()->title('Reparatie Voltooid!')->body(implode(' | ', $messages))->send(); - } - - if ($errors !== []) { - Notification::make()->danger()->title('Reparatie Fout')->body(implode(' | ', $errors))->send(); - } - - Cache::forget('all_updates_check'); - $this->mount(); - } catch (\Exception $e) { - Notification::make()->danger()->title('Fout')->body($e->getMessage())->send(); - } - } - - public function diagnoseSystem(): void - { - try { - $emuService = new EmulatorUpdateService; - $nitroService = new NitroUpdateService; - - $emuDiagnosis = $emuService->diagnose(); - $nitroDiagnosis = $nitroService->diagnose(); - - $issues = array_merge( - $emuDiagnosis['issues'] ?? [], - $nitroDiagnosis['issues'] ?? [], - ); - - $recommendations = array_merge( - $emuDiagnosis['recommendations'] ?? [], - $nitroDiagnosis['recommendations'] ?? [], - ); - - if ($issues === []) { - Notification::make()->info()->title('Diagnose')->body('Geen problemen gevonden')->send(); - } else { - $body = 'Problemen: ' . implode(', ', $issues); - if ($recommendations !== []) { - $body .= "\n\nAanbevelingen: " . implode(', ', $recommendations); - } - Notification::make()->warning()->title('Diagnose Resultaat')->body($body)->send(); - } - } catch (\Exception $e) { - Notification::make()->danger()->title('Fout')->body($e->getMessage())->send(); - } - } - - public function updateAll(): void - { - try { - Cache::forget('all_updates_check'); - Cache::forget('website_settings'); - - $messages = []; - $errors = []; - $updateService = new EmulatorUpdateService; - - try { - $result = $updateService->updateEmulator(); - if ($result['success']) { - $messages[] = '๐Ÿ–ฅ๏ธ ' . ($result['message'] ?? 'Emulator geรผpdatet'); - } else { - $error = $result['error'] ?? ''; - if (str_contains($error, 'al up-to-date')) { - $messages[] = '๐Ÿ–ฅ๏ธ Emulator al up-to-date'; - } else { - $errors[] = 'Emulator: ' . $error; - } - } - } catch (\Exception $e) { - $errors[] = 'Emulator: ' . $e->getMessage(); - } - - try { - $nitroService = new NitroUpdateService; - $nitroCheck = $nitroService->checkForUpdates(); - - if ($nitroCheck['has_updates'] ?? false) { - $result = $nitroService->updateNitro(); - if ($result['success']) { - $parts = []; - if ($result['renderer_updated'] ?? false) { - $parts[] = 'Renderer'; - } - if ($result['client_updated'] ?? false) { - $parts[] = 'Client'; - } - if ($result['built'] ?? false) { - $parts[] = 'Build'; - } - if ($result['deployed'] ?? false) { - $parts[] = 'Deploy'; - } - if ($parts !== []) { - $messages[] = '๐ŸŽฎ Nitro: ' . implode(', ', $parts); - } - } elseif (! empty($result['errors'] ?? [])) { - $errors = array_merge($errors, $result['errors']); - } - } else { - $messages[] = '๐ŸŽฎ Nitro al up-to-date'; - } - } catch (\Exception $e) { - $errors[] = 'Nitro: ' . $e->getMessage(); - } - - try { - $sqlResult = $updateService->runSqlUpdates(); - if ($sqlResult['sql_updated'] ?? false) { - $count = count($sqlResult['files_run'] ?? []); - if ($count > 0) { - $messages[] = "๐Ÿ“Š {$count} SQL updates"; - } - } - } catch (\Exception $e) { - $errors[] = 'SQL: ' . $e->getMessage(); - } - - try { - $updateService->restartEmulator(); - $messages[] = '๐Ÿ”„ Emulator herstart'; - } catch (\Exception $e) { - Log::error('[UpdateChecker] Restart failed: ' . $e->getMessage()); - } - - try { - $rcon = new RconService; - $rcon->sendCommand('updatecatalog'); - $rcon->sendCommand('updatewordfilter'); - $messages[] = '๐Ÿ“š Catalogus + filter vernieuwd'; - } catch (\Exception $e) { - Log::error('[UpdateChecker] RCON failed: ' . $e->getMessage()); - } - - if ($messages !== []) { - Notification::make()->success()->title('Updates Voltooid!')->body(implode(' | ', $messages))->send(); - } - - if ($errors !== []) { - Notification::make()->danger()->title('Sommige Updates Mislukt')->body(implode(' | ', $errors))->send(); - } - - if ($messages === [] && $errors === []) { - Notification::make()->info()->title('Geen Updates')->body('Alles was al up-to-date')->send(); - } - - Cache::forget('all_updates_check'); - $this->mount(); - } catch (\Exception $e) { - Notification::make()->danger()->title('Fout')->body($e->getMessage())->send(); - } - } - - #[\Override] - public static function canView(): bool - { - return true; - } -} diff --git a/app/Services/Emulator/EmulatorBuildService.php b/app/Services/Emulator/EmulatorBuildService.php deleted file mode 100755 index b279cca..0000000 --- a/app/Services/Emulator/EmulatorBuildService.php +++ /dev/null @@ -1,272 +0,0 @@ -loadConfiguration(); - } - - public function buildFromSource(bool $force = false): array - { - $repo = $this->sourceRepo ?: $this->githubRepo; - $branch = $this->sourceBranch ?: $this->githubBranch ?: 'main'; - - if (! $repo) { - return ['success' => false, 'error' => 'Geen source repo geconfigureerd']; - } - - $sourcePath = $this->emulatorSourcePath; - $serviceName = $this->emulatorService; - - Log::info('[EmulatorBuild] Starting source build', [ - 'repo' => $repo, - 'branch' => $branch, - 'path' => $sourcePath, - ]); - - $this->ensureJavaInstalled(); - - Process::timeout(10)->run('mkdir -p ' . escapeshellarg(dirname((string) $sourcePath))); - Process::timeout(10)->run('mkdir -p ' . escapeshellarg((string) $this->jarPath)); - Process::timeout(10)->run('chown -R www-data:www-data ' . escapeshellarg(dirname((string) $sourcePath))); - - $maxRetries = 2; - $lastError = ''; - - for ($retry = 0; $retry <= $maxRetries; $retry++) { - Process::timeout(10)->run('chown -R www-data:www-data ' . escapeshellarg(dirname((string) $sourcePath)) . ' 2>/dev/null || true'); - Process::timeout(10)->run('chown -R www-data:www-data ' . escapeshellarg((string) $sourcePath) . ' 2>/dev/null || true'); - - if ($retry > 0) { - Log::info('[EmulatorBuild] Retry attempt', ['attempt' => $retry]); - Process::timeout(30)->run('rm -rf ' . escapeshellarg((string) $sourcePath)); - } - - try { - $existsCheck = Process::timeout(5)->run('[ -d ' . escapeshellarg((string) $sourcePath) . " ] && echo 'exists'"); - $sourceExists = $existsCheck->successful() && trim($existsCheck->output()) === 'exists'; - - $gitCheck = Process::timeout(5)->run('[ -d ' . escapeshellarg((string) $sourcePath) . "/.git ] && echo 'git'"); - $isGitRepo = $gitCheck->successful() && trim($gitCheck->output()) === 'git'; - - if ($sourceExists && $isGitRepo) { - Log::info('[EmulatorBuild] Pulling latest changes'); - $commands = [ - 'cd ' . escapeshellarg((string) $sourcePath) . ' && git fetch origin', - 'cd ' . escapeshellarg((string) $sourcePath) . ' && git checkout ' . escapeshellarg($branch), - 'cd ' . escapeshellarg((string) $sourcePath) . ' && git pull origin ' . escapeshellarg($branch), - ]; - } else { - Log::info('[EmulatorBuild] Cloning repository'); - $commands = [ - 'sudo rm -rf ' . escapeshellarg((string) $sourcePath) . ' 2>/dev/null || rm -rf ' . escapeshellarg((string) $sourcePath), - 'mkdir -p ' . escapeshellarg(dirname((string) $sourcePath)), - 'git clone --branch ' . escapeshellarg($branch) . ' --depth 1 https://github.com/' . escapeshellarg($repo) . '.git ' . escapeshellarg((string) $sourcePath), - 'chown -R www-data:www-data ' . escapeshellarg((string) $sourcePath), - ]; - } - - $command = implode(' && ', $commands); - $result = Process::timeout(300)->run($command); - - if ($result->failed()) { - $lastError = 'Git clone/pull failed: ' . substr($result->errorOutput(), 0, 300); - Log::warning('[EmulatorBuild] Git operation failed', ['error' => $lastError, 'attempt' => $retry]); - - continue; - } - - $buildCommands = $this->getBuildCommands($sourcePath); - - Log::info('[EmulatorBuild] Running build', ['command' => $buildCommands]); - $buildResult = Process::timeout(600)->run('cd ' . escapeshellarg((string) $sourcePath) . ' && ' . $buildCommands); - - $hasSignalError = str_contains($buildResult->errorOutput(), 'signal') || str_contains($buildResult->output(), 'signal'); - - if ($hasSignalError) { - Log::warning('[EmulatorBuild] Build process received signal, checking if JAR was built anyway'); - } - - $jarPath = $this->findBuiltJar($sourcePath); - - if ($jarPath) { - Log::info('[EmulatorBuild] JAR found despite build status', ['jar' => $jarPath]); - } elseif ($buildResult->failed() && ! $hasSignalError) { - $lastError = 'Build failed: ' . substr($buildResult->errorOutput(), 0, 500); - Log::warning('[EmulatorBuild] Build failed', ['error' => $lastError, 'attempt' => $retry]); - - if ($retry < $maxRetries) { - $cleanCommands = [ - 'cd ' . escapeshellarg((string) $sourcePath) . ' && mvn clean 2>/dev/null || ./gradlew clean 2>/dev/null || true', - ]; - Process::timeout(60)->run(implode(' && ', $cleanCommands)); - - continue; - } - - continue; - } - - if (! $jarPath) { - $lastError = 'Build succeeded but JAR not found. Check build output.'; - Log::warning('[EmulatorBuild] JAR not found', ['attempt' => $retry]); - - continue; - } - - return $this->deployJar($jarPath, $serviceName); - - } catch (\Exception $e) { - $lastError = $e->getMessage(); - Log::error('[EmulatorBuild] Source build exception', ['error' => $lastError, 'attempt' => $retry]); - } - } - - return [ - 'success' => false, - 'error' => 'Build mislukt na ' . ($maxRetries + 1) . ' pogingen. Laatste fout: ' . $lastError, - ]; - } - - public function getBuildCommands(string $sourcePath): string - { - $pomPath = $sourcePath; - $pomCheck = Process::timeout(5)->run("[ -f {$pomPath}/pom.xml ] && echo 'pom'"); - - if (! $pomCheck->successful() || trim($pomCheck->output()) !== 'pom') { - $pomPath = $sourcePath . '/Emulator'; - $pomCheck = Process::timeout(5)->run("[ -f {$pomPath}/pom.xml ] && echo 'pom'"); - } - - $gradlewPath = $sourcePath . '/gradlew'; - $gradlewCheck = Process::timeout(5)->run("[ -f {$gradlewPath} ] && echo 'gradlew'"); - - $gradlePath = $sourcePath . '/build.gradle'; - $gradleCheck = Process::timeout(5)->run("[ -f {$gradlePath} ] && echo 'gradle'"); - - if ($pomCheck->successful() && trim($pomCheck->output()) === 'pom') { - return "cd {$pomPath} && mvn clean package -DskipTests 2>&1"; - } - - if ($gradlewCheck->successful() && trim($gradlewCheck->output()) === 'gradlew') { - return "cd {$sourcePath} && chmod +x gradlew && ./gradlew clean build -x test 2>&1"; - } - - if ($gradleCheck->successful() && trim($gradleCheck->output()) === 'gradle') { - return "cd {$sourcePath} && gradle clean build -x test 2>&1"; - } - - return "cd {$sourcePath} && ls -la"; - } - - public function findBuiltJar(string $sourcePath): ?string - { - $patterns = [ - $sourcePath . '/target/*.jar', - $sourcePath . '/build/libs/*.jar', - $sourcePath . '/*/*.jar', - $sourcePath . '/*.jar', - $sourcePath . '/Emulator/target/*.jar', - $sourcePath . '/Emulator/build/libs/*.jar', - $sourcePath . '/Emulator/*/*.jar', - ]; - - foreach ($patterns as $pattern) { - $result = Process::timeout(10)->run('ls -t ' . $pattern . ' 2>/dev/null | head -1'); - if ($result->successful()) { - $jarPath = trim($result->output()); - if ($jarPath !== '' && $jarPath !== '0' && str_contains($jarPath, '.jar')) { - return $jarPath; - } - } - } - - return null; - } - - private function deployJar(string $jarPath, string $serviceName): array - { - $jarName = basename($jarPath); - $version = $this->extractVersionFromFilename($jarName); - if ($version === '' || $version === '0') { - $version = date('Y.m.d'); - } - - $deployCommands = [ - 'mkdir -p ' . escapeshellarg((string) $this->jarPath), - 'chown -R www-data:www-data ' . escapeshellarg((string) $this->jarPath), - 'if ls ' . escapeshellarg((string) $this->jarPath) . '/*.jar 1>/dev/null 2>&1; then mkdir -p ' . escapeshellarg((string) $this->jarPath) . '/backup && mv ' . escapeshellarg((string) $this->jarPath) . '/*.jar ' . escapeshellarg((string) $this->jarPath) . '/backup/; fi', - 'cp -f ' . escapeshellarg($jarPath) . ' ' . escapeshellarg((string) $this->jarPath) . '/' . escapeshellarg($jarName), - 'chown www-data:www-data ' . escapeshellarg((string) $this->jarPath) . '/' . escapeshellarg($jarName), - 'chmod 755 ' . escapeshellarg((string) $this->jarPath) . '/' . escapeshellarg($jarName), - 'ls -la ' . escapeshellarg((string) $this->jarPath) . '/', - 'systemctl restart ' . escapeshellarg((string) $serviceName) . ' 2>&1 || service ' . escapeshellarg((string) $serviceName) . ' restart 2>&1 || true', - ]; - - $deployCommand = implode(' && ', $deployCommands); - - Log::info('[EmulatorBuild] Deploying jar', [ - 'source' => $jarPath, - 'destination' => "{$this->jarPath}/{$jarName}", - ]); - - $deployResult = Process::timeout(60)->run($deployCommand); - - if (! $deployResult->successful()) { - return [ - 'success' => false, - 'error' => 'Deploy failed: ' . substr($deployResult->errorOutput(), 0, 300), - ]; - } - - $sourceService = new EmulatorSourceService; - $sourceInfo = $sourceService->checkForUpdates(); - $installedDate = $sourceInfo['latest_timestamp'] ?? time(); - - $this->settings->set('emulator_version', $version); - $this->settings->set('emulator_jar_installed_date', (string) $installedDate); - $this->settings->set('emulator_source_commit', $sourceInfo['latest_sha'] ?? 'unknown'); - $this->settings->set('emulator_source_date', (string) $installedDate); - - $currentBranch = $this->sourceBranch ?: $this->githubBranch ?: 'main'; - $this->settings->set('emulator_installed_branch', $currentBranch); - - $sqlService = new EmulatorSqlService; - $sqlService->runUpdates(); - - Log::info('[EmulatorBuild] Source build successful'); - - return [ - 'success' => true, - 'version' => $version, - 'jar' => $jarName, - 'built' => true, - 'message' => "โœ… Emulator vanaf source gebouwd!\n๐Ÿ“ฆ {$jarName}\n๐Ÿ”„ Service herstart", - ]; - } - - private function ensureJavaInstalled(): void - { - $javaCheck = Process::timeout(5)->run('java -version 2>&1'); - if (! $javaCheck->successful()) { - Log::warning('[EmulatorBuild] Java not found, attempting to install'); - Process::timeout(180)->run('apt-get update && apt-get install -y default-jdk 2>&1'); - } - - $mavenCheck = Process::timeout(5)->run('which mvn'); - if (! $mavenCheck->successful()) { - Log::warning('[EmulatorBuild] Maven not found, attempting to install'); - Process::timeout(180)->run('apt-get install -y maven 2>&1'); - } - } -} diff --git a/app/Services/Emulator/EmulatorJarService.php b/app/Services/Emulator/EmulatorJarService.php deleted file mode 100755 index a64002c..0000000 --- a/app/Services/Emulator/EmulatorJarService.php +++ /dev/null @@ -1,509 +0,0 @@ -loadConfiguration(); - } - - public function isConfigured(): bool - { - return ! in_array($this->githubUrl, [null, '', '0'], true) || ! in_array($this->jarDirectUrl, [null, '', '0'], true); - } - - public function checkForUpdates(): array - { - if (! $this->isConfigured()) { - return [ - 'update_available' => false, - 'error' => 'Configureer een GitHub URL of directe .jar URL', - ]; - } - - $sourceService = new EmulatorSourceService; - $sourceInfo = $sourceService->checkForUpdates(); - $hasSourceUpdates = $sourceInfo && $sourceInfo['has_update']; - - if (! in_array($this->jarDirectUrl, [null, '', '0'], true)) { - return $this->checkDirectUrlUpdates($sourceInfo, $hasSourceUpdates); - } - - return $this->checkGitHubFolderUpdates($sourceInfo, $hasSourceUpdates); - } - - public function performUpdate(array $check): array - { - $jarUrl = $check['jar_url']; - $jarName = $check['jar_name']; - $version = $check['latest_version']; - $serviceName = $this->emulatorService; - - $tempDir = '/tmp/emulator-update-' . Str::random(8); - $tempJar = $tempDir . '/' . $jarName; - - $updateScript = $this->buildUpdateScript($jarUrl, $jarName, $tempDir, $tempJar, $serviceName); - - $scriptPath = '/tmp/emulator_update_' . uniqid() . '.sh'; - file_put_contents($scriptPath, $updateScript); - chmod($scriptPath, 0755); - - Log::info('[EmulatorJar] Starting update', [ - 'version' => $version, - 'jar' => $jarName, - 'url' => $jarUrl, - ]); - - try { - $result = Process::timeout(600)->run('bash ' . $scriptPath . ' 2>&1'); - @unlink($scriptPath); - - if ($result->exitCode() !== 0) { - Log::error('[EmulatorJar] Update failed', [ - 'output' => $result->output(), - 'error' => $result->errorOutput(), - ]); - - return [ - 'success' => false, - 'error' => 'Update mislukt: ' . substr($result->output(), 0, 300), - ]; - } - - $this->storeUpdateInfo($version, $check); - - Log::info('[EmulatorJar] Update successful'); - - return [ - 'success' => true, - 'version' => $version, - 'jar' => $jarName, - 'message' => "โœ… Emulator geรผpdatet naar v{$version}!\n๐Ÿ“ฆ {$jarName}\n๐Ÿ”„ Service herstart", - ]; - } catch (\Exception $e) { - Log::error('[EmulatorJar] Exception', ['error' => $e->getMessage()]); - - return [ - 'success' => false, - 'error' => $e->getMessage(), - ]; - } - } - - public function findLatestJar(): ?array - { - if (! $this->githubRepo) { - return null; - } - - $branch = $this->githubBranch ?: 'main'; - - $commonNames = ['arcturus.jar', 'Arcturus.jar', 'emulator.jar', 'habbo.jar', 'hotel.jar']; - - foreach ($commonNames as $name) { - try { - $apiUrl = "https://api.github.com/repos/{$this->githubRepo}/contents/Latest_Compiled_Version/{$name}?ref={$branch}"; - $response = Http::timeout(10) - ->withHeaders([ - 'Accept' => 'application/vnd.github.v3+json', - 'User-Agent' => 'AtomCMS-Emulator-Updater', - ]) - ->get($apiUrl); - - if (! $response->successful()) { - continue; - } - - $data = json_decode($response->body(), true); - - if (! isset($data['sha']) || ! isset($data['download_url'])) { - continue; - } - - $commitDate = null; - $commitSha = $data['sha']; - - try { - $commitsResponse = Http::timeout(10) - ->withHeaders([ - 'Accept' => 'application/vnd.github.v3+json', - 'User-Agent' => 'AtomCMS-Emulator-Updater', - ]) - ->get("https://api.github.com/repos/{$this->githubRepo}/commits", [ - 'path' => "Latest_Compiled_Version/{$name}", - 'sha' => $branch, - 'per_page' => 1, - ]); - - if ($commitsResponse->successful()) { - $commits = $commitsResponse->json(); - if (! empty($commits) && isset($commits[0]['commit']['committer']['date'])) { - $commitDate = strtotime($commits[0]['commit']['committer']['date']); - $commitSha = $commits[0]['sha'] ?? $data['sha']; - } - } - } catch (\Exception) { - Log::debug('[EmulatorJar] Could not fetch commit date for ' . $name); - } - - $installedDate = $this->settings->getOrDefault('emulator_jar_installed_date', null); - $installedJarCommit = $this->settings->getOrDefault('emulator_jar_commit', null); - - $isUpdate = false; - if ($installedJarCommit !== null) { - $isUpdate = $installedJarCommit !== $commitSha; - } elseif ($installedDate !== null && $commitDate !== null) { - $isUpdate = (int) $installedDate < $commitDate; - } elseif ($installedDate === null && $commitDate !== null) { - $isUpdate = true; - } - - $version = $this->extractVersionFromFilename($name); - if ($version === '' || $version === '0') { - $version = $commitDate ? date('Y.m.d', $commitDate) : date('Y.m.d'); - } - - return [ - 'name' => $name, - 'url' => $data['download_url'], - 'version' => $version, - 'commit' => $commitSha, - 'commit_date' => $commitDate, - 'is_update' => $isUpdate, - 'installed_date' => $installedDate, - ]; - } catch (\Exception $e) { - Log::warning('[EmulatorJar] Error checking JAR file ' . $name, ['error' => $e->getMessage()]); - } - } - - return null; - } - - private function checkDirectUrlUpdates(?array $sourceInfo, bool $hasSourceUpdates): array - { - $jarInfo = $this->validateDirectUrl($this->jarDirectUrl); - - if ($jarInfo) { - if (! empty($jarInfo['version'])) { - $currentVersion = $this->settings->getOrDefault('emulator_version', '0.0.0'); - if ($jarInfo['version'] !== $currentVersion) { - $this->settings->set('emulator_version', $jarInfo['version']); - } - } - - $hasJarUpdates = $jarInfo['is_update'] ?? false; - - if ($hasSourceUpdates && ! $hasJarUpdates) { - return [ - 'update_available' => true, - 'current_version' => $this->settings->getOrDefault('emulator_version', '0.0.0'), - 'latest_version' => $sourceInfo['latest_timestamp'] ? date('Y.m.d', $sourceInfo['latest_timestamp']) : $jarInfo['version'], - 'release_name' => 'Source Update', - 'jar_url' => null, - 'jar_name' => null, - 'jar_size' => 'Onbekend', - 'type' => 'source_build', - 'source_info' => $sourceInfo, - 'message' => 'Nieuwe commits beschikbaar - build vanaf source nodig', - ]; - } - - return [ - 'update_available' => $hasJarUpdates, - 'current_version' => $this->settings->getOrDefault('emulator_version', '0.0.0'), - 'latest_version' => $jarInfo['version'], - 'release_name' => $hasSourceUpdates ? 'Source Update' : 'Direct URL', - 'jar_url' => $this->jarDirectUrl, - 'jar_name' => $jarInfo['name'], - 'jar_size' => 'Onbekend', - 'type' => $hasSourceUpdates ? 'source_build' : 'direct_url', - 'commit' => $jarInfo['commit_sha'] ?? null, - 'source_info' => $sourceInfo, - 'has_source_updates' => $hasSourceUpdates, - ]; - } - - if ($hasSourceUpdates) { - return [ - 'update_available' => true, - 'current_version' => $this->settings->getOrDefault('emulator_version', '0.0.0'), - 'latest_version' => $sourceInfo['latest_timestamp'] ? date('Y.m.d', $sourceInfo['latest_timestamp']) : 'Onbekend', - 'release_name' => 'Source Update', - 'jar_url' => null, - 'jar_name' => null, - 'jar_size' => 'Onbekend', - 'type' => 'source_build', - 'source_info' => $sourceInfo, - 'message' => 'Nieuwe commits beschikbaar - build vanaf source nodig', - ]; - } - - return [ - 'update_available' => false, - 'current_version' => $this->settings->getOrDefault('emulator_version', '0.0.0'), - 'error' => 'Directe .jar URL is niet bereikbaar', - ]; - } - - private function checkGitHubFolderUpdates(?array $sourceInfo, bool $hasSourceUpdates): array - { - $jarInfo = $this->findLatestJar(); - - if (! $jarInfo) { - if ($hasSourceUpdates) { - return [ - 'update_available' => true, - 'current_version' => $this->settings->getOrDefault('emulator_version', '0.0.0'), - 'latest_version' => $sourceInfo['latest_timestamp'] ? date('Y.m.d', $sourceInfo['latest_timestamp']) : 'Onbekend', - 'release_name' => 'Source Update', - 'jar_url' => null, - 'jar_name' => null, - 'jar_size' => 'Onbekend', - 'type' => 'source_build', - 'source_info' => $sourceInfo, - 'message' => 'Nieuwe commits beschikbaar - build vanaf source nodig', - ]; - } - - return [ - 'update_available' => false, - 'current_version' => $this->settings->getOrDefault('emulator_version', '0.0.0'), - 'latest_version' => 'Onbekend', - 'error' => 'Kon geen .jar bestand vinden', - 'type' => 'not_found', - 'source_available' => $sourceInfo !== null, - ]; - } - - $version = $jarInfo['version']; - $hasJarUpdates = $jarInfo['is_update'] ?? false; - - if ($hasSourceUpdates && ! $hasJarUpdates) { - return [ - 'update_available' => true, - 'current_version' => $this->settings->getOrDefault('emulator_version', '0.0.0'), - 'latest_version' => $sourceInfo['latest_timestamp'] ? date('Y.m.d', $sourceInfo['latest_timestamp']) : $version, - 'release_name' => 'Source Update', - 'jar_url' => null, - 'jar_name' => null, - 'jar_size' => 'Onbekend', - 'type' => 'source_build', - 'source_info' => $sourceInfo, - 'message' => 'Nieuwe commits beschikbaar - build vanaf source nodig', - ]; - } - - return [ - 'update_available' => $hasJarUpdates, - 'current_version' => $this->settings->getOrDefault('emulator_version', '0.0.0'), - 'latest_version' => $hasSourceUpdates ? ($sourceInfo['latest_timestamp'] ? date('Y.m.d', $sourceInfo['latest_timestamp']) : $version) : $version, - 'release_name' => $hasSourceUpdates ? 'Source Update' : 'Latest from GitHub', - 'jar_url' => $jarInfo['url'], - 'jar_name' => $jarInfo['name'], - 'jar_size' => 'Onbekend', - 'type' => $hasSourceUpdates ? 'source_build' : 'github_folder', - 'commit' => $jarInfo['commit'] ?? null, - 'commit_date' => $jarInfo['commit_date'] ?? null, - 'source_info' => $sourceInfo, - 'has_source_updates' => $hasSourceUpdates, - ]; - } - - private function validateDirectUrl(string $url): ?array - { - if ($url === '' || $url === '0' || ! str_ends_with(strtolower($url), '.jar')) { - return null; - } - - try { - $jarName = basename(parse_url($url, PHP_URL_PATH)); - $version = $this->extractVersionFromFilename($jarName); - $storedVersion = $this->settings->getOrDefault('emulator_version', '0.0.0'); - - $lastModified = null; - $commitSha = null; - $gitHubInfoAvailable = false; - - if (preg_match('/github\.com\/([^\/]+)\/([^\/]+)\/raw\/refs\/heads\/([^\/]+)\/(.+)/', $url, $matches)) { - $owner = $matches[1]; - $repo = $matches[2]; - $branch = $matches[3]; - $path = $matches[4]; - - try { - $apiResponse = Http::timeout(10) - ->withHeaders([ - 'Accept' => 'application/vnd.github.v3+json', - 'User-Agent' => 'AtomCMS-Emulator-Updater', - ]) - ->get("https://api.github.com/repos/{$owner}/{$repo}/contents/{$path}", [ - 'ref' => $branch, - ]); - - if ($apiResponse->successful()) { - $data = $apiResponse->json(); - - if (isset($data['sha']) && ! isset($data['message'])) { - $gitHubInfoAvailable = true; - $commitSha = $data['sha']; - - if (isset($data['commit']['committer']['date'])) { - $lastModified = strtotime($data['commit']['committer']['date']); - } - } - } - } catch (\Exception) { - Log::debug('[EmulatorJar] Could not fetch GitHub commit info for direct URL'); - } - } - - if ($lastModified === null) { - $response = Http::timeout(10)->head($url); - if ($response->successful()) { - $modifiedSince = $response->header('Last-Modified'); - if ($modifiedSince) { - $lastModified = strtotime($modifiedSince); - if ($lastModified === false) { - $lastModified = null; - } - } - } - } - - $isUpdate = false; - $installedDate = $this->settings->getOrDefault('emulator_jar_installed_date', null); - - $storedData = $this->settings->getOrDefault('emulator_direct_url_info_' . md5($url)); - if ($storedData !== null && is_string($storedData)) { - $storedDataArray = json_decode($storedData, true); - if (is_array($storedDataArray)) { - if ($gitHubInfoAvailable && $commitSha !== null && isset($storedDataArray['commit_sha'])) { - $isUpdate = $commitSha !== $storedDataArray['commit_sha']; - } elseif ($lastModified !== null && $installedDate !== null) { - $isUpdate = (int) $installedDate < $lastModified; - } elseif ($lastModified !== null && isset($storedDataArray['last_modified'])) { - $isUpdate = $lastModified > $storedDataArray['last_modified']; - } elseif (! in_array($version, ['', '0', $storedVersion], true)) { - $isUpdate = version_compare($version, $storedVersion) > 0; - } - } - } else { - $isUpdate = true; - } - - $infoToStore = [ - 'last_checked' => time(), - 'version' => $version, - ]; - if ($lastModified) { - $infoToStore['last_modified'] = $lastModified; - } - if ($commitSha) { - $infoToStore['commit_sha'] = $commitSha; - } - $this->settings->set('emulator_direct_url_info_' . md5($url), json_encode($infoToStore)); - - return [ - 'name' => $jarName, - 'version' => $version, - 'last_modified' => $lastModified, - 'commit_sha' => $commitSha, - 'is_update' => $isUpdate, - 'gitHub_rate_limited' => ! $gitHubInfoAvailable && preg_match('/github\.com/', $url), - ]; - } catch (\Exception $e) { - Log::warning('[EmulatorJar] Direct URL not reachable', ['error' => $e->getMessage()]); - } - - return null; - } - - private function buildUpdateScript(string $jarUrl, string $jarName, string $tempDir, string $tempJar, string $serviceName): string - { - return <</dev/null 2>&1; then - mkdir -p "\$JAR_PATH/backup" - mv "\$JAR_PATH"/*.jar "\$JAR_PATH/backup/" 2>/dev/null || true -fi - -download_success=false -for attempt in 1 2 3; do - if curl -L --max-time 300 --retry 3 --retry-delay 5 -o "\$TEMP_JAR" "\$JAR_URL" 2>&1; then - FILE_TYPE=\$(file -b "\$TEMP_JAR" 2>/dev/null) - JAR_SIZE=\$(stat -c%s "\$TEMP_JAR" 2>/dev/null || echo 0) - - if echo "\$FILE_TYPE" | grep -qi "zip\|jar\|archive" && [ "\$JAR_SIZE" -gt 1000 ]; then - download_success=true - break - else - rm -f "\$TEMP_JAR" 2>/dev/null || true - sleep 3 - fi - else - sleep 5 - fi -done - -if [ "\$download_success" = false ]; then - if ls "\$JAR_PATH/backup"/*.jar 1>/dev/null 2>&1; then - mv "\$JAR_PATH/backup"/*.jar "\$JAR_PATH/" 2>/dev/null || true - fi - rm -rf "\$TEMP_DIR" - exit 1 -fi - -mv "\$TEMP_JAR" "\$JAR_PATH/" -chown -R www-data:www-data "\$JAR_PATH" -chmod 755 "\$JAR_PATH/\$JAR_NAME" - -systemctl restart "\$SERVICE" 2>&1 || service "\$SERVICE" restart 2>&1 || true - -rm -rf "\$TEMP_DIR" 2>/dev/null || true -BASH; - } - - private function storeUpdateInfo(string $version, array $check): void - { - setting('emulator_version', $version); - - $commitDate = $check['commit_date'] ?? time(); - $this->settings->set('emulator_jar_installed_date', (string) $commitDate); - $this->settings->set('emulator_jar_commit', $check['commit'] ?? null); - - $sourceSha = $check['source_info']['latest_sha'] ?? $check['commit'] ?? null; - $sourceDate = $check['source_info']['latest_timestamp'] ?? ($check['commit_date'] ?? time()); - if ($sourceSha) { - $this->settings->set('emulator_source_commit', $sourceSha); - } - if ($sourceDate) { - $this->settings->set('emulator_source_date', (string) $sourceDate); - } - - $currentBranch = $this->sourceBranch ?: $this->githubBranch ?: 'main'; - $this->settings->set('emulator_installed_branch', $currentBranch); - } -} diff --git a/app/Services/Emulator/EmulatorSourceService.php b/app/Services/Emulator/EmulatorSourceService.php deleted file mode 100755 index ae6a7ec..0000000 --- a/app/Services/Emulator/EmulatorSourceService.php +++ /dev/null @@ -1,273 +0,0 @@ -loadConfiguration(); - } - - public function checkForUpdates(): ?array - { - $repo = $this->sourceRepo ?: $this->githubRepo; - $branch = $this->sourceBranch ?: $this->githubBranch ?: 'main'; - - if (! $repo) { - return null; - } - - $localCheck = $this->checkLocalSourceUpdates(); - if ($localCheck !== null) { - return $localCheck; - } - - return $this->checkRemoteSourceUpdates($repo, $branch); - } - - public function isSourceBuildAvailable(): bool - { - $hasRepo = ! in_array($this->sourceRepo, [null, '', '0'], true) || ! in_array($this->githubRepo, [null, '', '0'], true); - $hasPath = ! in_array($this->emulatorSourcePath, [null, '', '0'], true); - - if (! $hasRepo || ! $hasPath) { - return false; - } - - try { - $result = Process::timeout(5)->run("[ -d {$this->emulatorSourcePath} ] && echo 'exists' || echo 'not_exists'"); - - return trim($result->output()) === 'exists'; - } catch (\Exception) { - return false; - } - } - - private function checkLocalSourceUpdates(): ?array - { - $sourcePath = $this->emulatorSourcePath; - - $existsCheck = Process::timeout(5)->run("[ -d {$sourcePath} ] && echo 'exists'"); - if (! $existsCheck->successful() || trim($existsCheck->output()) !== 'exists') { - return $this->checkSourceByCloning(); - } - - try { - $gitCheck = Process::timeout(5)->run("cd {$sourcePath} && git rev-parse --is-inside-work-tree 2>/dev/null"); - if (! $gitCheck->successful()) { - return $this->checkSourceByCloning(); - } - - $currentCommitResult = Process::timeout(5)->run("cd {$sourcePath} && git rev-parse HEAD"); - if (! $currentCommitResult->successful()) { - return null; - } - $currentSha = trim($currentCommitResult->output()); - - $fetchResult = Process::timeout(30)->run("cd {$sourcePath} && git fetch origin 2>&1"); - if ($fetchResult->failed()) { - Log::debug('[EmulatorSource] Git fetch failed, trying local check only'); - } - - $branch = $this->sourceBranch ?: $this->githubBranch ?: 'main'; - $latestResult = Process::timeout(5)->run("cd {$sourcePath} && git rev-parse origin/{$branch} 2>/dev/null || git rev-parse HEAD"); - if (! $latestResult->successful()) { - return null; - } - $latestSha = trim($latestResult->output()); - - $dateResult = Process::timeout(5)->run("cd {$sourcePath} && git log -1 --format='%ci' {$latestSha}"); - $latestDate = null; - $latestTimestamp = time(); - if ($dateResult->successful()) { - $latestDate = trim($dateResult->output(), "'"); - if ($latestDate !== '' && $latestDate !== '0') { - $latestTimestamp = strtotime($latestDate); - } - } - - $this->persistSourceCommitInfo($latestSha, $latestTimestamp); - - $msgResult = Process::timeout(5)->run("cd {$sourcePath} && git log -1 --format='%s' {$latestSha}"); - $latestMessage = $msgResult->successful() ? trim($msgResult->output()) : ''; - - $authorResult = Process::timeout(5)->run("cd {$sourcePath} && git log -1 --format='%an' {$latestSha}"); - $latestAuthor = $authorResult->successful() ? trim($authorResult->output()) : ''; - - $storedSha = $this->settings->getOrDefault('emulator_source_commit', null); - $storedDate = $this->settings->getOrDefault('emulator_source_date', null); - - $isUpdate = $storedSha !== null && $storedSha !== $latestSha; - if ($storedSha === null) { - $isUpdate = true; - } - - return [ - 'has_update' => $isUpdate, - 'latest_sha' => $latestSha, - 'latest_date' => $latestDate, - 'latest_timestamp' => $latestTimestamp, - 'latest_message' => $latestMessage, - 'latest_author' => $latestAuthor, - 'stored_sha' => $storedSha, - 'stored_date' => $storedDate, - 'source' => 'local', - ]; - } catch (\Exception $e) { - Log::debug('[EmulatorSource] Local source check failed: ' . $e->getMessage()); - - return null; - } - } - - private function checkSourceByCloning(): ?array - { - $repo = $this->sourceRepo ?: $this->githubRepo; - $branch = $this->sourceBranch ?: $this->githubBranch ?: 'main'; - - if (! $repo) { - return null; - } - - $repo = preg_replace('#/tree/[^/]+/.*$#', '', $repo); - $repo = str_replace(['https://github.com/', 'http://github.com/'], '', $repo); - $repo = rtrim($repo, '/'); - - try { - $tempDir = '/tmp/emulator-source-check-' . Str::random(8); - - $cloneResult = Process::timeout(120)->run( - "git clone --branch {$branch} --depth 1 https://github.com/{$repo}.git {$tempDir} 2>&1", - ); - - if ($cloneResult->failed()) { - return null; - } - - $shaResult = Process::timeout(5)->run("cd {$tempDir} && git rev-parse HEAD"); - $latestSha = $shaResult->successful() ? trim($shaResult->output()) : ''; - - $dateResult = Process::timeout(5)->run("cd {$tempDir} && git log -1 --format='%ci'"); - $latestDate = null; - $latestTimestamp = time(); - if ($dateResult->successful()) { - $latestDate = trim($dateResult->output()); - if ($latestDate !== '' && $latestDate !== '0') { - $latestTimestamp = strtotime($latestDate); - } - } - - $this->persistSourceCommitInfo($latestSha, $latestTimestamp); - - $msgResult = Process::timeout(5)->run("cd {$tempDir} && git log -1 --format='%s'"); - $latestMessage = $msgResult->successful() ? trim($msgResult->output()) : ''; - - $authorResult = Process::timeout(5)->run("cd {$tempDir} && git log -1 --format='%an'"); - $latestAuthor = $authorResult->successful() ? trim($authorResult->output()) : ''; - - Process::timeout(10)->run("rm -rf {$tempDir}"); - - $storedSha = $this->settings->getOrDefault('emulator_source_commit', null); - $storedDate = $this->settings->getOrDefault('emulator_source_date', null); - - $isUpdate = $storedSha !== null && $storedSha !== $latestSha; - if ($storedSha === null) { - $isUpdate = true; - } - - return [ - 'has_update' => $isUpdate, - 'latest_sha' => $latestSha, - 'latest_date' => $latestDate, - 'latest_timestamp' => $latestTimestamp, - 'latest_message' => $latestMessage, - 'latest_author' => $latestAuthor, - 'stored_sha' => $storedSha, - 'stored_date' => $storedDate, - 'source' => 'cloned', - ]; - } catch (\Exception $e) { - Log::debug('[EmulatorSource] Source clone check failed: ' . $e->getMessage()); - - return null; - } - } - - private function checkRemoteSourceUpdates(string $repo, string $branch): ?array - { - try { - $response = Http::timeout(10) - ->withHeaders([ - 'Accept' => 'application/vnd.github.v3+json', - 'User-Agent' => 'AtomCMS-Emulator-Updater', - ]) - ->get("https://api.github.com/repos/{$repo}/commits", [ - 'sha' => $branch, - 'per_page' => 1, - ]); - - if (! $response->successful()) { - return null; - } - - $commits = $response->json(); - - if (empty($commits) || ! isset($commits[0]['sha'])) { - return null; - } - - $latestCommit = $commits[0]; - $latestSha = $latestCommit['sha']; - $latestDate = $latestCommit['commit']['committer']['date'] ?? null; - $latestTimestamp = $latestDate ? strtotime((string) $latestDate) : time(); - - $this->persistSourceCommitInfo($latestSha, $latestTimestamp); - - $installedDate = $this->settings->getOrDefault('emulator_jar_installed_date', null); - $storedSha = $this->settings->getOrDefault('emulator_source_commit', null); - $storedDate = $this->settings->getOrDefault('emulator_source_date', null); - - if ($storedSha !== null && $storedSha === $latestSha) { - $isUpdate = false; - } elseif ($storedSha !== null && $storedSha !== $latestSha) { - $isUpdate = true; - } elseif ($installedDate !== null) { - $installedTimestamp = is_numeric($installedDate) ? (int) $installedDate : strtotime((string) $installedDate); - $isUpdate = $installedTimestamp < $latestTimestamp; - } else { - $isUpdate = false; - } - - return [ - 'has_update' => $isUpdate, - 'latest_sha' => $latestSha, - 'latest_date' => $latestDate, - 'latest_timestamp' => $latestTimestamp, - 'latest_message' => $latestCommit['commit']['message'] ?? '', - 'latest_author' => $latestCommit['commit']['author']['name'] ?? '', - 'stored_sha' => $storedSha, - 'stored_date' => $storedDate, - ]; - } catch (\Exception $e) { - Log::warning('[EmulatorSource] Could not check source updates via GitHub API', ['error' => $e->getMessage()]); - - return null; - } - } - - private function persistSourceCommitInfo(string $sha, int $timestamp): void - { - $this->settings->set('emulator_source_commit', $sha); - $this->settings->set('emulator_source_date', (string) $timestamp); - } -} diff --git a/app/Services/Emulator/EmulatorSqlService.php b/app/Services/Emulator/EmulatorSqlService.php deleted file mode 100755 index aba1cc9..0000000 --- a/app/Services/Emulator/EmulatorSqlService.php +++ /dev/null @@ -1,510 +0,0 @@ -loadConfiguration(); - } - - public function isConfigured(): bool - { - return ! in_array($this->githubUrl, [null, '', '0'], true) || ! in_array($this->jarDirectUrl, [null, '', '0'], true); - } - - public function checkForUpdates(bool $recentOnly = true): array - { - if (! $this->githubRepo) { - return [ - 'has_updates' => false, - 'error' => 'Geen GitHub repo geconfigureerd', - ]; - } - - $this->ensureSqlTableExists(); - - $result = $this->fetchSqlFilesFromGitHub($recentOnly); - - if (isset($result['error'])) { - return [ - 'has_updates' => false, - 'error' => $result['error'], - ]; - } - - $sqlFiles = $result; - - if ($sqlFiles === []) { - return [ - 'has_updates' => false, - 'message' => $recentOnly - ? 'Geen SQL updates van de afgelopen week gevonden' - : 'Geen SQL updates gevonden', - ]; - } - - $appliedHashes = $this->getAppliedSqlHashes(); - $newSqlFiles = []; - $alreadyApplied = []; - - foreach ($sqlFiles as $file) { - $hash = $file['sha'] ?? md5((string) $file['name']); - if (in_array($hash, $appliedHashes)) { - $alreadyApplied[] = $file['name']; - - continue; - } - $newSqlFiles[] = $file; - } - - if ($newSqlFiles === []) { - return [ - 'has_updates' => false, - 'message' => 'Alle SQL updates zijn al toegepast (' . count($alreadyApplied) . ' stuks)', - 'applied_count' => count($alreadyApplied), - ]; - } - - usort($newSqlFiles, fn ($a, $b) => strcmp((string) $a['name'], (string) $b['name'])); - - return [ - 'has_updates' => true, - 'count' => count($newSqlFiles), - 'files' => $newSqlFiles, - 'message' => count($newSqlFiles) . ' nieuwe SQL update(s) van deze week', - 'applied_count' => count($alreadyApplied), - ]; - } - - public function runUpdates(): array - { - $sqlCheck = $this->checkForUpdates(false); - - if (! ($sqlCheck['has_updates'] ?? false)) { - return ['success' => true, 'sql_updated' => false, 'message' => 'Geen nieuwe SQL updates']; - } - - $results = [ - 'success' => true, - 'sql_updated' => true, - 'files_run' => [], - 'errors' => [], - ]; - - foreach ($sqlCheck['files'] as $file) { - $sqlResult = $this->downloadAndRunSql($file); - - if ($sqlResult['success']) { - $results['files_run'][] = $file['name']; - $this->markSqlAsApplied($file); - } else { - $results['errors'][] = $file['name'] . ': ' . $sqlResult['error']; - } - } - - if (count($results['errors']) > 0) { - $results['message'] = count($results['files_run']) . ' SQL updates succesvol, ' . count($results['errors']) . ' met fouten'; - } else { - $results['message'] = count($results['files_run']) . ' SQL updates succesvol uitgevoerd!'; - } - - return $results; - } - - public function getAppliedUpdates(): array - { - $this->ensureSqlTableExists(); - - return DB::table(self::SQL_TABLE) - ->orderBy('applied_at', 'desc') - ->get() - ->toArray(); - } - - public function diagnose(): array - { - $diagnosis = [ - 'table_exists' => false, - 'applied_count' => 0, - 'pending_count' => 0, - 'error' => null, - ]; - - try { - $diagnosis['table_exists'] = Schema::hasTable(self::SQL_TABLE); - - if ($diagnosis['table_exists']) { - $diagnosis['applied_count'] = DB::table(self::SQL_TABLE)->count(); - } - - if ($this->githubRepo && $diagnosis['table_exists']) { - $sqlCheck = $this->checkForUpdates(false); - if (isset($sqlCheck['count'])) { - $diagnosis['pending_count'] = $sqlCheck['count']; - } - } - } catch (\Exception $e) { - $diagnosis['error'] = $e->getMessage(); - } - - return $diagnosis; - } - - public function repair(): array - { - $actions = []; - $errors = []; - - try { - $this->ensureSqlTableExists(); - $actions[] = 'SQL update tabel gecontroleerd'; - - $appliedCount = DB::table(self::SQL_TABLE)->count(); - $actions[] = "SQL updates tabel OK ({$appliedCount} records)"; - - if ($this->githubRepo) { - $sqlCheck = $this->checkForUpdates(false); - - if (isset($sqlCheck['error'])) { - $actions[] = 'SQL update check: ' . $sqlCheck['error']; - } elseif (! ($sqlCheck['has_updates'] ?? false)) { - $actions[] = 'SQL updates: ' . ($sqlCheck['message'] ?? 'Allemaal up-to-date'); - } else { - $count = $sqlCheck['count'] ?? 0; - $actions[] = "{$count} nieuwe SQL updates beschikbaar"; - - if ($this->isConfigured()) { - $actions[] = 'SQL updates worden toegepast...'; - $runResult = $this->runUpdates(); - - if ($runResult['success'] ?? false) { - $filesRun = count($runResult['files_run'] ?? []); - $actions[] = "{$filesRun} SQL updates toegepast"; - } else { - $errors[] = 'SQL updates: ' . ($runResult['error'] ?? 'Onbekende fout'); - } - } - } - } - } catch (\Exception $e) { - $errors[] = 'SQL repair: ' . $e->getMessage(); - Log::error('[EmulatorSql] Repair failed', ['error' => $e->getMessage()]); - } - - return [ - 'success' => $errors === [], - 'actions' => $actions, - 'errors' => $errors, - ]; - } - - private function ensureSqlTableExists(): void - { - if (Schema::hasTable(self::SQL_TABLE)) { - return; - } - - Schema::create(self::SQL_TABLE, function ($table) { - $table->id(); - $table->string('file_name'); - $table->string('file_hash')->unique(); - $table->timestamp('applied_at'); - $table->text('sql_content')->nullable(); - }); - } - - private function getAppliedSqlHashes(): array - { - $this->ensureSqlTableExists(); - - return DB::table(self::SQL_TABLE) - ->pluck('file_hash') - ->toArray(); - } - - private function markSqlAsApplied(array $file): void - { - $this->ensureSqlTableExists(); - - $hash = $file['sha'] ?? md5((string) $file['name']); - - DB::table(self::SQL_TABLE)->updateOrInsert( - ['file_hash' => $hash], - [ - 'file_name' => $file['name'], - 'applied_at' => now(), - ], - ); - } - - private function fetchSqlFilesFromGitHub(bool $recentOnly = false): array - { - if (! $this->githubRepo) { - return []; - } - - $branch = $this->githubBranch ?: 'main'; - - $folderNames = [ - 'Database%20Updates', - 'Database Updates', - 'database_updates', - 'database/updates', - 'sql/updates', - 'sql', - 'updates', - ]; - - $knownSqlFiles = [ - '07012026_UpdateDatabase_to_4-0-1.sql', - '09012026_UpdateDatabase_to_4-0-2.sql', - '12012026_Battle Banzai.sql', - '12012026_Breeding Fixes.sql', - '12012026_ChatBubbles.sql', - '16032026_updateall_command.sql', - '17032026_allow_underpass.sql', - '19032026_hotel_timezone.sql', - '21022026_user_prefixes.sql', - 'Default_Camera.sql', - 'UpdateDatabase_Allow_diagonale.sql', - 'UpdateDatabase_BOT.sql', - 'UpdateDatabase_Banners.sql', - 'UpdateDatabase_DanceCMD.sql', - 'UpdateDatabase_Happiness.sql', - 'UpdateDatabase_Websocket.sql', - 'UpdateDatabase_unignorable.sql', - ]; - - try { - if ($recentOnly) { - return $this->fetchRecentSqlFiles($branch); - } - - $sqlFiles = []; - - foreach ($knownSqlFiles as $filename) { - foreach ($folderNames as $folderName) { - $encodedFilename = str_replace(' ', '%20', $filename); - $url = "https://raw.githubusercontent.com/{$this->githubRepo}/{$branch}/{$folderName}/{$encodedFilename}"; - $response = Http::timeout(10)->get($url); - - if ($response->successful()) { - $sqlFiles[] = [ - 'name' => $filename, - 'url' => $url, - 'sha' => md5($response->body()), - ]; - break; - } - } - } - - if ($sqlFiles !== []) { - usort($sqlFiles, fn ($a, $b) => strcmp($a['name'], $b['name'])); - - return $sqlFiles; - } - - return []; - } catch (\Exception $e) { - Log::warning('[EmulatorSql] Could not fetch SQL files', ['error' => $e->getMessage()]); - - return []; - } - } - - private function fetchRecentSqlFiles(string $branch): array - { - $weekAgo = now()->subDays(7)->toIso8601String(); - - $githubToken = setting('github_token', ''); - $headers = [ - 'Accept' => 'application/vnd.github+json', - 'User-Agent' => 'AtomCMS-EmulatorUpdate/1.0', - ]; - if (! empty($githubToken)) { - $headers['Authorization'] = 'Bearer ' . $githubToken; - } - - $folderNames = ['Database Updates', 'database_updates', 'sql', 'updates']; - - try { - foreach ($folderNames as $folderName) { - $response = Http::withHeaders($headers)->timeout(30)->get("https://api.github.com/repos/{$this->githubRepo}/commits", [ - 'path' => $folderName, - 'sha' => $branch, - 'since' => $weekAgo, - 'per_page' => 100, - ]); - - if ($response->successful()) { - $commits = $response->json(); - - if (! is_array($commits) || $commits === []) { - continue; - } - - $sqlFiles = []; - - foreach ($commits as $commit) { - $sha = $commit['sha'] ?? null; - $commitDate = $commit['commit']['committer']['date'] ?? null; - - if (! $sha) { - continue; - } - - $commitResponse = Http::withHeaders($headers)->timeout(30)->get("https://api.github.com/repos/{$this->githubRepo}/commits/{$sha}"); - - if (! $commitResponse->successful()) { - continue; - } - - $commitData = $commitResponse->json(); - $files = $commitData['files'] ?? []; - - foreach ($files as $file) { - $filename = $file['filename'] ?? ''; - - if (! str_ends_with(strtolower((string) $filename), '.sql')) { - continue; - } - - $name = basename((string) $filename); - $sqlFiles[] = [ - 'name' => $name, - 'url' => $file['raw_url'] ?? "https://raw.githubusercontent.com/{$this->githubRepo}/{$sha}/{$filename}", - 'sha' => $sha, - 'date' => $commitDate, - ]; - } - } - - if ($sqlFiles !== []) { - usort($sqlFiles, fn ($a, $b) => strcmp($a['name'], $b['name'])); - - return $sqlFiles; - } - } - } - - return []; - } catch (\Exception $e) { - Log::warning('[EmulatorSql] Could not fetch recent SQL files', ['error' => $e->getMessage()]); - - return []; - } - } - - private function downloadAndRunSql(array $file): array - { - try { - $response = Http::timeout(60)->get($file['url']); - - if (! $response->successful()) { - return ['success' => false, 'error' => 'Download mislukt']; - } - - $sql = $this->cleanSql($response->body()); - - if (in_array(trim($sql), ['', '0'], true)) { - return ['success' => true, 'message' => 'Lege SQL file overgeslagen']; - } - - $host = setting('emulator_database_host', config('database.connections.emulator.host', '127.0.0.1')); - $port = setting('emulator_database_port', config('database.connections.emulator.port', '3306')); - $name = setting('emulator_database_name', config('database.connections.emulator.database', '')); - $username = setting('emulator_database_username', config('database.connections.emulator.username', '')); - $password = setting('emulator_database_password', config('database.connections.emulator.password', '')); - - if (empty($name) || empty($username)) { - return ['success' => false, 'error' => 'Emulator database niet geconfigureerd']; - } - - $pdo = new \PDO( - "mysql:host={$host};port={$port};dbname={$name};charset=utf8mb4", - $username, - $password, - [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION], - ); - - $statements = $this->splitSqlStatements($sql); - $successCount = 0; - $skipCount = 0; - - foreach ($statements as $statement) { - $statement = trim((string) $statement); - if ($statement === '' || $statement === '0') { - continue; - } - - try { - $pdo->exec($statement); - $successCount++; - } catch (\PDOException $e) { - if ($this->isDuplicateError($e)) { - $skipCount++; - - continue; - } - Log::warning('[SQL Update] Statement error: ' . $e->getMessage()); - } - } - - Log::info('[SQL Update] Successfully ran: ' . $file['name'] . " ({$successCount} statements, {$skipCount} skipped)"); - - return ['success' => true, 'message' => "SQL uitgevoerd ({$successCount} statements, {$skipCount} duplicate)"]; - } catch (\Exception $e) { - Log::error('[SQL Update] Failed: ' . $file['name'], ['error' => $e->getMessage()]); - - return ['success' => false, 'error' => $e->getMessage()]; - } - } - - private function cleanSql(string $sql): string - { - $sql = preg_replace('/--.*$/m', '', $sql); - $sql = preg_replace('/#.*$/m', '', (string) $sql); - $sql = preg_replace('/SET FOREIGN_KEY_CHECKS.*?;/i', '', (string) $sql); - - return preg_replace('/SET @@SESSION.SQL_MODE.*?;/i', '', (string) $sql); - } - - private function splitSqlStatements(string $sql): array - { - $parts = explode(';', $sql); - - return array_filter($parts, fn ($part) => trim((string) $part) !== ''); - } - - private function isDuplicateError(\PDOException $e): bool - { - $message = strtolower($e->getMessage()); - $patterns = [ - 'duplicate entry', - "table '", - 'already exists', - "can't create", - 'duplicate key', - 'duplicate index', - 'error 1061', - 'error 1062', - 'error 1826', - ]; - - return array_any($patterns, fn ($pattern) => str_contains($message, (string) $pattern)); - } -} diff --git a/app/Services/EmulatorUpdateService.php b/app/Services/EmulatorUpdateService.php deleted file mode 100755 index 81376ad..0000000 --- a/app/Services/EmulatorUpdateService.php +++ /dev/null @@ -1,407 +0,0 @@ -statusService = new EmulatorStatusService; - $this->jarService = new EmulatorJarService; - $this->sourceService = new EmulatorSourceService; - $this->buildService = new EmulatorBuildService; - $this->sqlService = new EmulatorSqlService; - $this->backupService = new EmulatorBackupService; - $this->settings = app(SettingsService::class); - } - - public function isConfigured(): bool - { - return $this->statusService->isConfigured(); - } - - public function getStatus(): array - { - $status = $this->statusService->getStatus(); - $updateCheck = $this->jarService->checkForUpdates(); - $sourceInfo = $this->sourceService->checkForUpdates(); - - return array_merge($status, [ - 'update_available' => $updateCheck['update_available'] ?? false, - 'current_version' => $updateCheck['current_version'] ?? setting('emulator_version', 'N/A'), - 'latest_version' => $updateCheck['latest_version'] ?? 'N/A', - 'update_type' => $updateCheck['type'] ?? 'unknown', - 'has_source_updates' => $sourceInfo['has_update'] ?? false, - 'latest_sha' => $sourceInfo['latest_sha'] ?? null, - 'latest_message' => $sourceInfo['latest_message'] ?? null, - 'latest_author' => $sourceInfo['latest_author'] ?? null, - 'latest_date' => $sourceInfo['latest_date'] ?? null, - 'stored_sha' => $sourceInfo['stored_sha'] ?? null, - 'stored_date' => $sourceInfo['stored_date'] ?? null, - 'source_info' => $sourceInfo, - ]); - } - - public function checkForUpdates(): array - { - return $this->jarService->checkForUpdates(); - } - - public function checkForSqlUpdates(bool $recentOnly = true): array - { - return $this->sqlService->checkForUpdates($recentOnly); - } - - public function runSqlUpdates(): array - { - return $this->sqlService->runUpdates(); - } - - public function getAppliedSqlUpdates(): array - { - return $this->sqlService->getAppliedUpdates(); - } - - public function updateEmulator(): array - { - if (! $this->isConfigured()) { - return ['success' => false, 'error' => 'Geen GitHub URL geconfigureerd']; - } - - $check = $this->checkForUpdates(); - - if (! ($check['update_available'] ?? false)) { - if ($check['type'] === 'not_found' && ($check['source_available'] ?? false)) { - return $this->buildFromSource(); - } - - return ['success' => false, 'error' => 'Emulator is al up-to-date']; - } - - $hasSourceUpdates = ($check['has_source_updates'] ?? false) || ($check['type'] ?? '') === 'source_build'; - - if ($hasSourceUpdates && $this->sourceService->isSourceBuildAvailable()) { - return $this->buildFromSource(); - } - - if ($check['type'] === 'source_build') { - return $this->buildFromSource(); - } - - if (! ($check['jar_url'] ?? null)) { - return ['success' => false, 'error' => 'Geen .jar gevonden']; - } - - $result = $this->jarService->performUpdate($check); - - if ($result['success']) { - $this->runSqlUpdates(); - - if ($this->restartEmulator()) { - $result['restarted'] = true; - $result['message'] = ($result['message'] ?? '') . ' | ๐Ÿ”„ Emulator herstart'; - } - } - - return $result; - } - - public function performUpdate(array $check): array - { - return $this->jarService->performUpdate($check); - } - - public function buildFromSource(bool $force = false): array - { - return $this->buildService->buildFromSource($force); - } - - public function restartEmulator(): bool - { - $serviceName = $this->settings->getOrDefault('emulator_service_name', 'emulator'); - - try { - Log::info('[EmulatorUpdate] Restarting emulator service: ' . $serviceName); - - $result = Process::timeout(30)->run("systemctl restart {$serviceName} 2>&1"); - if ($result->successful()) { - return true; - } - - $result = Process::timeout(30)->run("service {$serviceName} restart 2>&1"); - - return $result->successful(); - } catch (\Exception $e) { - Log::error('[EmulatorUpdate] Failed to restart emulator', ['error' => $e->getMessage()]); - - return false; - } - } - - public function getBackupList(): array - { - return $this->backupService->getList(); - } - - public function restoreBackup(string $backupName): array - { - return $this->backupService->restore($backupName); - } - - public function getInstalledVersion(): string - { - return $this->statusService->getInstalledVersion(); - } - - public function getInstalledJar(): ?string - { - return $this->statusService->getInstalledJar(); - } - - public function getInstalledJarInfo(): array - { - return $this->statusService->getInstalledJarInfo(); - } - - public function getLastSqlUpdate(): ?string - { - return setting('emulator_last_sql_update'); - } - - public function debugStatus(): array - { - return Cache::remember('emulator_debug_status', 120, function () { - $installedDate = $this->settings->getOrDefault('emulator_jar_installed_date', null); - $sourceCommit = $this->settings->getOrDefault('emulator_source_commit', null); - $sourceDate = $this->settings->getOrDefault('emulator_source_date', null); - $emulatorVersion = $this->settings->getOrDefault('emulator_version', null); - $jarFiles = $this->statusService->getInstalledJarInfo(); - - return [ - 'github_url' => $this->settings->getOrDefault('emulator_github_url', ''), - 'github_repo' => $this->settings->getOrDefault('emulator_source_repo', ''), - 'github_branch' => $this->settings->getOrDefault('emulator_github_branch', 'main'), - 'source_repo' => $this->settings->getOrDefault('emulator_source_repo', ''), - 'source_branch' => $this->settings->getOrDefault('emulator_github_branch', 'main'), - 'installed_date' => $installedDate, - 'installed_date_formatted' => $installedDate ? date('Y-m-d H:i:s', (int) $installedDate) : null, - 'source_commit' => $sourceCommit, - 'source_date' => $sourceDate, - 'source_date_formatted' => $sourceDate ? date('Y-m-d H:i:s', (int) $sourceDate) : null, - 'emulator_version' => $emulatorVersion, - 'jar_files' => $jarFiles, - 'installed_branch' => $this->settings->getOrDefault('emulator_installed_branch', null), - ]; - }); - } - - public function resetInstalledDate(): void - { - WebsiteSetting::where('key', 'emulator_jar_installed_date')->delete(); - WebsiteSetting::where('key', 'emulator_source_commit')->delete(); - WebsiteSetting::where('key', 'emulator_source_date')->delete(); - } - - public function clearAllLogs(): array - { - $cleared = []; - $paths = [ - storage_path('logs') => 'Laravel Logs', - storage_path('logs/emulator.log') => 'Emulator Log', - '/tmp/emulator-update-*' => 'Emulator Update Temp', - '/tmp/nitro-switch-*' => 'Nitro Switch Logs', - '/tmp/nitro_*' => 'Nitro Temp', - '/var/www/Emulator/logs' => 'Emulator Folder Logs', - ]; - - foreach ($paths as $path => $label) { - try { - if (str_contains($path, '*')) { - Process::timeout(10)->run("rm -f {$path} 2>/dev/null || true"); - $cleared[] = $label; - } elseif (is_dir($path)) { - Process::timeout(10)->run("find {$path} -name '*.log' -mtime +1 -delete 2>/dev/null || true"); - $cleared[] = $label; - } elseif (is_file($path)) { - @unlink($path); - $cleared[] = $label; - } - } catch (\Exception) { - } - } - - try { - $laravelLog = storage_path('logs/laravel.log'); - if (is_file($laravelLog)) { - file_put_contents($laravelLog, ''); - $cleared[] = 'laravel.log'; - } - } catch (\Exception) { - } - - Process::timeout(10)->run("find /tmp -name 'emulator_*' -mtime +1 -delete 2>/dev/null || true"); - Process::timeout(10)->run("find /tmp -name 'nitro_*' -mtime +1 -delete 2>/dev/null || true"); - Process::timeout(10)->run("find /tmp -name 'deploy_*' -mtime +1 -delete 2>/dev/null || true"); - - return [ - 'success' => true, - 'cleared' => $cleared, - 'message' => count($cleared) . ' log locaties geleegd', - ]; - } - - public function repairEmulator(): array - { - $actions = []; - $errors = []; - - Log::info('[EmulatorUpdate] Starting repair process'); - - try { - $status = $this->getStatus(); - - if (! ($status['jar_exists'] ?? false)) { - $actions[] = 'JAR bestand ontbreekt - downloaden...'; - $updateResult = $this->updateEmulator(); - if (! $updateResult['success']) { - $errors[] = 'Kon JAR niet herstellen: ' . ($updateResult['error'] ?? 'Onbekende fout'); - } else { - $actions[] = 'JAR bestand hersteld'; - } - } - - if (! ($status['service_running'] ?? false)) { - $actions[] = 'Emulator service niet actief - starten...'; - if ($this->restartEmulator()) { - $actions[] = 'Emulator service gestart'; - } else { - $errors[] = 'Kon emulator service niet starten'; - } - } - - $sqlRepairResult = $this->sqlService->repair(); - if (! empty($sqlRepairResult['actions'])) { - $actions = array_merge($actions, $sqlRepairResult['actions']); - } - if (! empty($sqlRepairResult['errors'])) { - $errors = array_merge($errors, $sqlRepairResult['errors']); - } - - if ($errors !== []) { - return [ - 'success' => false, - 'actions' => $actions, - 'errors' => $errors, - 'error' => implode('; ', $errors), - ]; - } - - return [ - 'success' => true, - 'actions' => $actions, - 'message' => count($actions) . ' acties uitgevoerd', - ]; - } catch (\Exception $e) { - Log::error('[EmulatorUpdate] Repair exception', ['error' => $e->getMessage()]); - - return [ - 'success' => false, - 'actions' => $actions, - 'error' => $e->getMessage(), - ]; - } - } - - public function diagnoseSqlUpdates(): array - { - return $this->sqlService->diagnose(); - } - - public function diagnose(): array - { - $diagnosis = [ - 'timestamp' => now()->toIso8601String(), - 'checks' => [], - 'issues' => [], - 'recommendations' => [], - ]; - - try { - $status = $this->getStatus(); - - $diagnosis['checks']['jar_exists'] = $status['jar_exists'] ?? false; - $diagnosis['checks']['jar_files'] = $status['jar_files'] ?? []; - $diagnosis['checks']['service_running'] = $status['service_running'] ?? false; - $diagnosis['checks']['source_exists'] = $status['source_exists'] ?? false; - $diagnosis['checks']['emulator_db_connected'] = $status['emulator_db_connected'] ?? false; - $diagnosis['checks']['is_configured'] = $this->isConfigured(); - $diagnosis['checks']['update_available'] = $status['update_available'] ?? false; - - $sqlDiagnosis = $this->sqlService->diagnose(); - $diagnosis['checks']['sql_table_exists'] = $sqlDiagnosis['table_exists'] ?? false; - $diagnosis['checks']['sql_updates_applied'] = $sqlDiagnosis['applied_count'] ?? 0; - $diagnosis['checks']['sql_pending'] = $sqlDiagnosis['pending_count'] ?? 0; - - if (! ($status['jar_exists'] ?? false)) { - $diagnosis['issues'][] = 'JAR bestand ontbreekt'; - $diagnosis['recommendations'][] = 'Voer emulator:update uit om de JAR te downloaden'; - } - - if (! ($status['service_running'] ?? false)) { - $diagnosis['issues'][] = 'Emulator service draait niet'; - $diagnosis['recommendations'][] = 'Start de service met: sudo systemctl start ' . $this->settings->getOrDefault('emulator_service_name', 'emulator'); - } - - if (! ($status['emulator_db_connected'] ?? false)) { - $diagnosis['issues'][] = 'Emulator database niet bereikbaar'; - $diagnosis['recommendations'][] = 'Controleer de database credentials in de settings'; - } - - if (! ($status['source_exists'] ?? false) && $this->sourceService->isSourceBuildAvailable()) { - $diagnosis['issues'][] = 'Source code niet gevonden'; - $diagnosis['recommendations'][] = 'Voer emulator:update --rebuild uit om vanaf source te bouwen'; - } - - if (! ($sqlDiagnosis['table_exists'] ?? false)) { - $diagnosis['issues'][] = 'SQL update tabel ontbreekt'; - $diagnosis['recommendations'][] = 'Reparatie zal de tabel aanmaken'; - } - - if (($sqlDiagnosis['pending_count'] ?? 0) > 0) { - $diagnosis['issues'][] = $sqlDiagnosis['pending_count'] . ' SQL updates pending'; - $diagnosis['recommendations'][] = 'Voer reparatie uit om SQL updates toe te passen'; - } - } catch (\Exception $e) { - $diagnosis['error'] = $e->getMessage(); - } - - return $diagnosis; - } -} diff --git a/app/Services/NitroUpdateService.php b/app/Services/NitroUpdateService.php deleted file mode 100755 index e0a6c88..0000000 --- a/app/Services/NitroUpdateService.php +++ /dev/null @@ -1,1358 +0,0 @@ -settings = app(SettingsService::class); - $this->settings->clearInstanceCache(); - $this->clientRepo = 'duckietm/Nitro-V3'; - $this->rendererRepo = 'duckietm/Nitro_Render_V3'; - $this->gitlabHost = 'gitlab.epicnabbo.nl/duckietm'; - - $basePath = $this->detectBasePath(); - - $this->clientPath = $this->resolvePath(setting('nitro_client_path', $basePath . '/nitro-client')); - $this->rendererPath = $this->resolvePath(setting('nitro_renderer_path', $basePath . '/nitro-renderer')); - $this->clientWebRoot = $this->resolvePath(setting('nitro_webroot', '/var/www/Client')); - - $this->historyService = app(UpdateHistoryService::class); - } - - private function isDirAccessible(string $path): bool - { - if ($path === '' || $path === '0') { - return false; - } - $result = Process::timeout(5)->run('test -d ' . escapeshellarg($path) . ' && echo "yes" || echo "no"'); - - return trim($result->output()) === 'yes'; - } - - private function detectBasePath(): string - { - $possiblePaths = [ - base_path(), - '/var/www/atomcms', - '/var/www/html', - '/var/www', - dirname(base_path()), - ]; - - foreach ($possiblePaths as $path) { - if ($this->isDirAccessible($path)) { - $clientCheck = Process::timeout(5)->run('test -d ' . escapeshellarg($path . '/nitro-client') . ' && echo "yes" || echo "no"'); - $rendererCheck = Process::timeout(5)->run('test -d ' . escapeshellarg($path . '/nitro-renderer') . ' && echo "yes" || echo "no"'); - if (trim($clientCheck->output()) === 'yes' || trim($rendererCheck->output()) === 'yes') { - return $path; - } - } - } - - return base_path(); - } - - private function resolvePath(string $path): string - { - if (str_starts_with($path, '/')) { - if ($this->isDirAccessible($path)) { - return $path; - } - $parts = explode('/', trim($path, '/')); - $root = '/' . $parts[0]; - if (! $this->isDirAccessible($root)) { - return base_path() . '/' . ltrim($path, '/'); - } - - return $path; - } - - return base_path() . '/' . ltrim($path, '/'); - } - - public function isConfigured(): bool - { - return $this->isDirAccessible($this->clientPath) || $this->isDirAccessible($this->rendererPath) || $this->isDirAccessible($this->clientWebRoot); - } - - public function getClientPath(): string - { - return $this->clientPath; - } - - public function getRendererPath(): string - { - return $this->rendererPath; - } - - public function getClientWebRoot(): string - { - return $this->clientWebRoot; - } - - public function getStatus(): array - { - $buildPath = setting('nitro_build_path', '/var/www/atomcms/nitro-client/dist'); - $webroot = $this->clientWebRoot; - - // Use shell commands to bypass open_basedir - $checkDir = function (string $path): bool { - $result = Process::timeout(5)->run('test -d ' . escapeshellarg($path) . ' && echo "yes" || echo "no"'); - - return trim($result->output()) === 'yes'; - }; - - $checkFile = function (string $path): bool { - $result = Process::timeout(5)->run('test -f ' . escapeshellarg($path) . ' && echo "yes" || echo "no"'); - - return trim($result->output()) === 'yes'; - }; - - // Check nginx config via shell to bypass open_basedir - $nginxCheck = Process::timeout(5)->run('test -f /etc/nginx/sites-enabled/cms.conf -o -f /etc/nginx/sites-enabled/atom.conf -o -f /etc/nginx/conf.d/atom.conf && echo "yes" || echo "no"'); - $nginxValid = trim($nginxCheck->output()) === 'yes'; - - // Check open_basedir - fixed if empty OR includes all required paths - $openBaseDir = ini_get('open_basedir'); - $clientPath = $this->clientPath; - $rendererPath = $this->rendererPath; - $openBasedirFixed = in_array($openBaseDir, ['', '0', false], true) || ( - str_contains($openBaseDir, $clientPath) && - str_contains($openBaseDir, $rendererPath) - ); - - Log::info('[NitroUpdate] open_basedir check:', [ - 'open_basedir' => $openBaseDir, - 'clientPath' => $clientPath, - 'rendererPath' => $rendererPath, - 'fixed' => $openBasedirFixed, - ]); - - // Build files (check for hashed filenames like index-CLCACmcI.js) - $assetsPath = $buildPath . '/assets'; - - $indexJsCheck = Process::timeout(5)->run('test -f ' . escapeshellarg($buildPath . '/index.js') . ' && echo "yes" || echo "no"'); - $indexJsHashCheck = Process::timeout(5)->run('ls ' . escapeshellarg($assetsPath) . '/index-*.js 2>/dev/null | head -1'); - $hasIndexJs = trim($indexJsCheck->output()) === 'yes' || ! in_array(trim($indexJsHashCheck->output()), ['', '0'], true); - - $rendererJsCheck = Process::timeout(5)->run('test -f ' . escapeshellarg($buildPath . '/renderer.js') . ' && echo "yes" || echo "no"'); - $rendererJsHashCheck = Process::timeout(5)->run('ls ' . escapeshellarg($assetsPath) . '/nitro-renderer-*.js 2>/dev/null | head -1'); - $hasRendererJs = trim($rendererJsCheck->output()) === 'yes' || ! in_array(trim($rendererJsHashCheck->output()), ['', '0'], true); - - $vendorJsCheck = Process::timeout(5)->run('test -f ' . escapeshellarg($buildPath . '/vendor.js') . ' && echo "yes" || echo "no"'); - $vendorJsHashCheck = Process::timeout(5)->run('ls ' . escapeshellarg($assetsPath) . '/vendor-*.js 2>/dev/null | head -1'); - $hasVendorJs = trim($vendorJsCheck->output()) === 'yes' || ! in_array(trim($vendorJsHashCheck->output()), ['', '0'], true); - - // Check for CSS files - $hasCssCheck = Process::timeout(5)->run('find ' . escapeshellarg((string) $buildPath) . " -name '*.css' -type f 2>/dev/null | head -1"); - $hasCss = ! in_array(trim($hasCssCheck->output()), ['', '0'], true); - - // Renderer (monorepo - packages are built into client, so just check node_modules) - $hasRendererDist = $checkDir($this->rendererPath . '/node_modules'); - $hasRendererIndex = $checkDir($this->rendererPath . '/packages'); - - // Config files - $rendererConfigValid = $checkFile($webroot . '/renderer-config.json'); - $uiConfigValid = $checkFile($webroot . '/ui-config.json'); - $nitroConfigValid = $checkFile($webroot . '/UITexts.json'); - $hasRendererExample = $checkFile($buildPath . '/renderer-config.example') || $checkFile($webroot . '/renderer-config.example'); - $hasUiExample = $checkFile($buildPath . '/ui-config.example') || $checkFile($webroot . '/ui-config.example'); - - // Assets - $hasImageAssets = $checkDir($webroot . '/assets') || $checkDir($buildPath . '/assets'); - $hasFavicon = $checkFile($webroot . '/favicon.ico') || $checkFile($buildPath . '/favicon.ico'); - - // System - $webrootExists = $checkDir($webroot); - $webrootWritableCheck = Process::timeout(5)->run('test -w ' . escapeshellarg($webroot) . ' && echo "yes" || echo "no"'); - $webrootWritable = trim($webrootWritableCheck->output()) === 'yes'; - $webrootFileCount = $this->countFiles($webroot); - $buildSize = $this->getDirSize($buildPath); - $webrootSize = $this->getDirSize($webroot); - - // Socket URL from renderer config - $socketUrl = 'N/A'; - if ($rendererConfigValid) { - $configResult = Process::timeout(5)->run('cat ' . escapeshellarg($webroot . '/renderer-config.json') . ' 2>/dev/null'); - if ($configResult->successful()) { - $config = json_decode($configResult->output(), true); - $socketUrl = $config['socket.url'] ?? 'N/A'; - } - } - - // Check for gamedata - could be a symlink or directory - $symlinkValid = false; - - // Check if gamedata exists as symlink or directory - $gamedataCheck = Process::timeout(5)->run('test -e ' . escapeshellarg($webroot . '/gamedata') . ' && echo "yes" || echo "no"'); - if (trim($gamedataCheck->output()) === 'yes') { - $symlinkValid = true; - } else { - // Try alternative locations - $altPaths = [ - '/var/www/Gamedata', - '/var/www/gamedata', - '/var/www/atomcms/public/gamedata', - ]; - foreach ($altPaths as $altPath) { - $altCheck = Process::timeout(5)->run('test -d ' . escapeshellarg($altPath) . ' && echo "yes" || echo "no"'); - if (trim($altCheck->output()) === 'yes') { - $symlinkValid = true; - break; - } - } - } - - return [ - 'client_path' => $this->clientPath, - 'renderer_path' => $this->rendererPath, - 'build_path' => $buildPath, - 'webroot' => $webroot, - 'client_commit' => $this->getCommit($this->clientPath), - 'renderer_commit' => $this->getCommit($this->rendererPath), - 'client_installed' => $checkDir($this->clientPath . '/.git'), - 'renderer_installed' => $checkDir($this->rendererPath . '/.git'), - 'build_exists' => $checkDir($buildPath), - 'deployed' => $rendererConfigValid, - 'symlink_valid' => $symlinkValid, - 'client_node_modules' => $checkDir($this->clientPath . '/node_modules'), - 'renderer_node_modules' => $checkDir($this->rendererPath . '/node_modules'), - 'client_latest_commit' => null, - 'renderer_latest_commit' => null, - 'last_checked' => setting('nitro_last_checked'), - 'nginx_config_valid' => $nginxValid, - 'open_basedir_fixed' => $openBasedirFixed, - 'has_index_js' => $hasIndexJs, - 'has_renderer_js' => $hasRendererJs, - 'has_vendor_js' => $hasVendorJs, - 'has_css_file' => $hasCss, - 'vite_config_valid' => $checkFile($this->clientPath . '/vite.config.js') || $checkFile($this->clientPath . '/vite.config.mjs'), - 'renderer_config_valid' => $rendererConfigValid, - 'ui_config_valid' => $uiConfigValid, - 'nitro_config_valid' => $nitroConfigValid, - 'has_uitexts_json' => $nitroConfigValid, - 'has_renderer_example' => $hasRendererExample, - 'has_uiconfig_example' => $hasUiExample, - 'has_image_assets' => $hasImageAssets, - 'has_favicon' => $hasFavicon, - 'assets_writable' => $webrootWritable, - 'assets_file_count' => $webrootFileCount, - 'has_renderer_dist' => $hasRendererDist, - 'has_renderer_index' => $hasRendererIndex, - 'webroot_exists' => $webrootExists, - 'webroot_writable' => $webrootWritable, - 'webroot_file_count' => $webrootFileCount, - 'build_size' => $buildSize, - 'webroot_size' => $webrootSize, - 'socket_url' => $socketUrl, - 'websocket_accessible' => $this->checkWebSocketAccessible($socketUrl), - 'emulator_connected' => $this->checkEmulatorConnected(), - ]; - } - - private function checkWebSocketAccessible(string $socketUrl): bool - { - if (in_array($socketUrl, ['', '0', 'N/A'], true)) { - return false; - } - - try { - $wsUrl = $socketUrl; - if (str_starts_with($wsUrl, 'wss://')) { - $wsUrl = 'https://' . substr($wsUrl, 6); - } elseif (str_starts_with($wsUrl, 'ws://')) { - $wsUrl = 'http://' . substr($wsUrl, 4); - } - - $host = parse_url($wsUrl, PHP_URL_HOST); - $port = parse_url($wsUrl, PHP_URL_PORT); - - if (! $port) { - $port = str_starts_with($socketUrl, 'wss://') ? 443 : 80; - } - - $socket = @fsockopen($host, $port, $errno, $errstr, 5); - if ($socket) { - fclose($socket); - - return true; - } - } catch (\Exception $e) { - // Fallback to curl check - } - - // Fallback: try curl - try { - $ch = curl_init($socketUrl); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_TIMEOUT, 5); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); - curl_setopt($ch, CURLOPT_NOBODY, true); - curl_exec($ch); - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - - return in_array($httpCode, [200, 301, 302, 400, 404]); - } catch (\Exception) { - return false; - } - } - - private function checkEmulatorConnected(): bool - { - try { - $rconService = new RconService; - - return $rconService->isConnected(); - } catch (\Exception) { - return false; - } - } - - private function countFiles(string $path): int - { - $check = Process::timeout(10)->run('test -d ' . escapeshellarg($path) . ' && echo "yes" || echo "no"'); - if (trim($check->output()) !== 'yes') { - return 0; - } - $result = Process::timeout(10)->run('find ' . escapeshellarg($path) . ' -type f 2>/dev/null | wc -l'); - - return (int) trim($result->output() ?? '0'); - } - - private function getDirSize(string $path): int - { - $check = Process::timeout(10)->run('test -d ' . escapeshellarg($path) . ' && echo "yes" || echo "no"'); - if (trim($check->output()) !== 'yes') { - return 0; - } - $result = Process::timeout(10)->run('du -sb ' . escapeshellarg($path) . ' 2>/dev/null | cut -f1'); - - return (int) trim($result->output() ?? '0'); - } - - public function updateNitro(bool $forceBuild = false): array - { - $lockFile = '/tmp/nitro-update.lock'; - - if (file_exists($lockFile)) { - $lockAge = time() - filemtime($lockFile); - if ($lockAge < 300) { - $pid = @file_get_contents($lockFile); - - return ['success' => false, 'error' => "Update al bezig (PID: {$pid})"]; - } - @unlink($lockFile); - } - - file_put_contents($lockFile, getmypid()); - - try { - Log::info('[Nitro] Starting update'); - $this->historyService->log('nitro', 'update', null, 'pending', 'Update gestart'); - - $this->ensureVpsRequirements(); - - $this->setupDirectories(); - - $this->syncRepos(); - - $this->installDependencies(); - - $this->fixKnownBugs(); - - $buildResult = $this->buildClient(); - - if ($buildResult['success']) { - $this->deployClient(); - $this->generateConfigs(); - - $this->settings->set('nitro_client_commit', $this->getCommit($this->clientPath)); - $this->settings->set('nitro_renderer_commit', $this->getCommit($this->rendererPath)); - - Log::info('[Nitro] Update completed successfully'); - $this->historyService->log('nitro', 'update', 'client', 'success', 'Update succesvol'); - - return ['success' => true, 'message' => 'Nitro client succesvol geรผpdatet!']; - } - - $this->historyService->log('nitro', 'update', 'client', 'failed', $buildResult['error'] ?? 'Build mislukt'); - - return $buildResult; - - } catch (\Exception $e) { - Log::error('[Nitro] Update failed', ['error' => $e->getMessage()]); - $this->historyService->log('nitro', 'update', null, 'failed', $e->getMessage()); - - return ['success' => false, 'error' => $e->getMessage()]; - } finally { - @unlink($lockFile); - } - } - - private function ensureVpsRequirements(): array - { - $actions = []; - $errors = []; - - $nodeCheck = Process::timeout(5)->run('which node && node --version'); - if (! $nodeCheck->successful()) { - $errors[] = 'Node.js niet geรฏnstalleerd'; - Process::timeout(60)->run('curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs 2>&1'); - $nodeCheck2 = Process::timeout(5)->run('which node && node --version'); - if ($nodeCheck2->successful()) { - $actions[] = 'Node.js geรฏnstalleerd: ' . trim($nodeCheck2->output()); - } - } else { - $actions[] = 'Node.js: ' . trim($nodeCheck->output()); - } - - $yarnCheck = Process::timeout(5)->run('which yarn && yarn --version'); - if (! $yarnCheck->successful()) { - $actions[] = 'Yarn wordt geรฏnstalleerd...'; - Process::timeout(60)->run('npm install -g yarn 2>&1'); - $yarnCheck2 = Process::timeout(5)->run('which yarn && yarn --version'); - if ($yarnCheck2->successful()) { - $actions[] = 'Yarn geรฏnstalleerd: ' . trim($yarnCheck2->output()); - } - } else { - $actions[] = 'Yarn: ' . trim($yarnCheck->output()); - } - - $npmCacheDir = Process::timeout(5)->run('echo $HOME'); - $npmHome = trim($npmCacheDir->output()) ?: '/root'; - Process::timeout(5)->run('mkdir -p ' . escapeshellarg($npmHome . '/.npm')); - Process::timeout(5)->run('mkdir -p /var/www/.npm'); - Process::timeout(5)->run('chown -R www-data:www-data /var/www/.npm'); - - Process::timeout(10)->run('git config --global --add safe.directory "*" 2>/dev/null || true'); - - return [ - 'actions' => $actions, - 'errors' => $errors, - ]; - } - - private function setupDirectories(): void - { - $dirs = [ - $this->clientPath => 'Nitro Client', - $this->rendererPath => 'Nitro Renderer', - $this->clientWebRoot => 'Client Webroot', - ]; - - foreach ($dirs as $path => $label) { - $existsCheck = Process::timeout(5)->run('test -d ' . escapeshellarg($path) . ' && echo "yes" || echo "no"'); - if (trim($existsCheck->output()) !== 'yes') { - Log::info("[Nitro] Creating {$label} directory: {$path}"); - Process::timeout(10)->run('mkdir -p ' . escapeshellarg($path)); - } - Process::timeout(10)->run('chown -R www-data:www-data ' . escapeshellarg($path)); - } - - // Create gamedata symlink if it doesn't exist - $gamedataSymlink = $this->clientWebRoot . '/gamedata'; - $gamedataCheck = Process::timeout(5)->run('test -e ' . escapeshellarg($gamedataSymlink) . ' && echo "yes" || echo "no"'); - - if (trim($gamedataCheck->output()) !== 'yes') { - // Try to find gamedata folder - $possibleGamedata = [ - '/var/www/Gamedata', - '/var/www/gamedata', - '/var/www/atomcms/public/gamedata', - base_path() . '/../Gamedata', - ]; - - foreach ($possibleGamedata as $gdPath) { - $gdCheck = Process::timeout(5)->run('test -d ' . escapeshellarg($gdPath) . ' && echo "yes" || echo "no"'); - if (trim($gdCheck->output()) === 'yes') { - Process::timeout(10)->run('ln -s ' . escapeshellarg($gdPath) . ' ' . escapeshellarg($gamedataSymlink)); - Log::info('[Nitro] Created gamedata symlink: ' . $gdPath . ' -> ' . $gamedataSymlink); - break; - } - } - } - } - - private function syncRepos(): void - { - $repos = [ - ['path' => $this->rendererPath, 'repo' => $this->rendererRepo], - ['path' => $this->clientPath, 'repo' => $this->clientRepo], - ]; - - Process::timeout(5)->run('git config --global --add safe.directory "*" 2>/dev/null || true'); - - foreach ($repos as $r) { - $gitCheck = Process::timeout(5)->run('test -d ' . escapeshellarg($r['path']) . '/.git && echo "yes" || echo "no"'); - $isGitRepo = trim($gitCheck->output()) === 'yes'; - - if ($isGitRepo) { - Log::info("[Nitro] Updating {$r['repo']}"); - - $fetchResult = Process::timeout(120)->run('cd ' . escapeshellarg($r['path']) . ' && git fetch nitro-v3 2>&1'); - - $resetResult = Process::timeout(60)->run('cd ' . escapeshellarg($r['path']) . ' && git checkout main 2>&1 || git checkout master 2>&1 || true'); - - $pullResult = Process::timeout(120)->run('cd ' . escapeshellarg($r['path']) . ' && git reset --hard nitro-v3/main 2>&1 || git reset --hard nitro-v3/master 2>&1 || true'); - - if (! $pullResult->successful()) { - Log::warning("[Nitro] Update failed, re-cloning {$r['repo']}"); - Process::timeout(30)->run('rm -rf ' . escapeshellarg($r['path'])); - - $cloneResult = Process::timeout(300)->run( - "git clone --branch main --depth 1 https://{$this->gitlabHost}/{$r['repo']}.git " . escapeshellarg($r['path']) . ' 2>&1', - ); - } - } else { - Log::info("[Nitro] Cloning {$r['repo']}"); - - $dirCheck = Process::timeout(5)->run('test -d ' . escapeshellarg($r['path']) . ' && echo "yes" || echo "no"'); - if (trim($dirCheck->output()) === 'yes') { - Process::timeout(30)->run('rm -rf ' . escapeshellarg($r['path'])); - } - - Process::timeout(300)->run( - "git clone --branch main --depth 1 https://{$this->gitlabHost}/{$r['repo']}.git " . escapeshellarg($r['path']) . ' 2>&1', - ); - } - Process::timeout(10)->run('chown -R www-data:www-data ' . escapeshellarg($r['path'])); - } - } - - private function fixKnownBugs(): void - { - $this->fixChatsCss(); - - // Fix 1: Chats.css case sensitivity - $indexFile = $this->clientPath . '/src/index.tsx'; - if (file_exists($indexFile)) { - $content = file_get_contents($indexFile); - if (str_contains($content, './css/chat/Chats.css')) { - $content = str_replace('./css/chat/Chats.css', './css/chat/chats.css', $content); - file_put_contents($indexFile, $content); - Log::info('[Nitro] Fixed Chats.css case'); - } - } - - // Fix 2: Duplicate CatalogView export - $catalogFile = $this->clientPath . '/src/components/catalog/CatalogView.tsx'; - if (file_exists($catalogFile)) { - $content = file_get_contents($catalogFile); - $lines = explode("\n", $content); - $cleanLines = []; - $seenExport = false; - foreach ($lines as $line) { - if (str_contains($line, 'export const CatalogView')) { - if ($seenExport) { - continue; - } - $seenExport = true; - } - $cleanLines[] = $line; - } - file_put_contents($catalogFile, implode("\n", $cleanLines)); - Log::info('[Nitro] Fixed duplicate CatalogView'); - } - - // Fix 3: Create/update vite config - $this->updateViteConfig(); - - // Fix 4: Create symlink for renderer - $this->createYarnLink(); - - // Fix 5: Add missing renderer stubs - $this->addMissingRendererStubs(); - - // Fix 6: Duplicate FurniEditorResultEvent (case sensitivity conflict) - $duplicateFile = $this->rendererPath . '/packages/communication/src/messages/incoming/furniture/FurniEditorResultEvent.ts'; - if (file_exists($duplicateFile)) { - unlink($duplicateFile); - $furnitureIndexFile = $this->rendererPath . '/packages/communication/src/messages/incoming/furniture/index.ts'; - if (file_exists($furnitureIndexFile)) { - $content = file_get_contents($furnitureIndexFile); - $content = str_replace("export * from './FurniEditorResultEvent';\n", '', $content); - file_put_contents($furnitureIndexFile, $content); - } - Log::info('[Nitro] Fixed duplicate FurniEditorResultEvent'); - } - - // Fix 7: Rename Chats.css to lowercase - $upperCaseCss = $this->clientPath . '/src/css/chat/Chats.css'; - $lowerCaseCss = $this->clientPath . '/src/css/chat/chats.css'; - if (file_exists($upperCaseCss) && ! file_exists($lowerCaseCss)) { - rename($upperCaseCss, $lowerCaseCss); - Log::info('[Nitro] Fixed Chats.css case sensitivity'); - } - - // Fix 8: Remove all duplicate FurniEditor files from furniture/ (keep only furnieditor/) - $outgoingDir = $this->rendererPath . '/packages/communication/src/messages/outgoing/furniture'; - $incomingDir = $this->rendererPath . '/packages/communication/src/messages/incoming/furniture'; - - $outgoingDupes = ['BySprite', 'Create', 'Delete', 'Detail', 'Interactions', 'Search', 'Update']; - foreach ($outgoingDupes as $name) { - $file = $outgoingDir . '/FurniEditor' . $name . 'Composer.ts'; - if (file_exists($file)) { - unlink($file); - } - } - if (file_exists($outgoingDir . '/index.ts')) { - $content = file_get_contents($outgoingDir . '/index.ts'); - $lines = explode("\n", $content); - $cleanLines = array_filter($lines, fn ($line) => ! str_contains((string) $line, 'FurniEditor')); - file_put_contents($outgoingDir . '/index.ts', implode("\n", $cleanLines)); - } - - $incomingDupes = ['Detail', 'Interactions', 'Result', 'Search']; - foreach ($incomingDupes as $name) { - $file = $incomingDir . '/FurniEditor' . $name . 'Event.ts'; - if (file_exists($file)) { - unlink($file); - } - } - if (file_exists($incomingDir . '/index.ts')) { - $content = file_get_contents($incomingDir . '/index.ts'); - $lines = explode("\n", $content); - $cleanLines = array_filter($lines, fn ($line) => ! str_contains((string) $line, 'FurniEditor')); - file_put_contents($incomingDir . '/index.ts', implode("\n", $cleanLines)); - } - - Log::info('[Nitro] Removed duplicate FurniEditor files from furniture/'); - } - - private function fixChatsCss(): void - { - $chatCssFile = $this->clientPath . '/src/css/chat/chats.css'; - if (! file_exists($chatCssFile)) { - return; - } - - $content = file_get_contents($chatCssFile); - $modified = false; - - $content = preg_replace('/\/\/\s*normal\s*\n\s*\.message/', '.message', $content, -1, $count); - if ($count > 0) { - $modified = true; - } - $content = preg_replace('/\/\/\s*whisper\s*\n\s*\.message/', '.message', $content, -1, $count); - if ($count > 0) { - $modified = true; - } - $content = preg_replace('/\/\/\s*shout\s*\n\s*\.message/', '.message', $content, -1, $count); - if ($count > 0) { - $modified = true; - } - - if ($modified) { - file_put_contents($chatCssFile, $content); - Log::info('[Nitro] Fixed chat CSS comments'); - } - } - - private function addMissingRendererStubs(): void - { - $outgoingDir = $this->rendererPath . '/packages/communication/src/messages/outgoing/furniture'; - $incomingDir = $this->rendererPath . '/packages/communication/src/messages/incoming/furniture'; - $outgoingFurnieditorDir = $this->rendererPath . '/packages/communication/src/messages/outgoing/furnieditor'; - $incomingFurnieditorDir = $this->rendererPath . '/packages/communication/src/messages/incoming/furnieditor'; - - if (! is_dir($outgoingDir)) { - mkdir($outgoingDir, 0755, true); - } - if (! is_dir($incomingDir)) { - mkdir($incomingDir, 0755, true); - } - - $composers = ['BySprite', 'Create', 'Delete', 'Detail', 'Interactions', 'Search', 'Update']; - $composerLines = []; - foreach ($composers as $name) { - $furnieditorFile = $outgoingFurnieditorDir . '/FurniEditor' . $name . 'Composer.ts'; - $stubFile = $outgoingDir . '/FurniEditor' . $name . 'Composer.ts'; - if (! file_exists($furnieditorFile) && ! file_exists($stubFile)) { - file_put_contents($stubFile, "import { IMessageComposer } from '@nitrots/api';\nexport class FurniEditor{$name}Composer implements IMessageComposer {\n constructor(k: any) {}\n dispose() {}\n getMessageArray() { return []; }\n}\n"); - $composerLines[] = "export * from './FurniEditor{$name}Composer';"; - } - } - if ($composerLines !== []) { - $composerContent = implode("\n", $composerLines) . "\n"; - file_put_contents($outgoingDir . '/index.ts', $composerContent); - } - - $events = ['Detail', 'Interactions', 'Result', 'Search']; - $eventLines = []; - foreach ($events as $name) { - $furnieditorFile = $incomingFurnieditorDir . '/FurniEditor' . $name . 'Event.ts'; - $stubFile = $incomingDir . '/FurniEditor' . $name . 'Event.ts'; - if (! file_exists($furnieditorFile) && ! file_exists($stubFile)) { - file_put_contents($stubFile, "import { IMessageEvent } from '@nitrots/api';\nimport { MessageEvent } from '@nitrots/events';\nexport class FurniEditor{$name}Event extends MessageEvent implements IMessageEvent {\n constructor(callBack: Function) { super(callBack, (k: any) => k); }\n getParser() { return this.parser; }\n}\n"); - $eventLines[] = "export * from './FurniEditor{$name}Event';"; - } - } - if ($eventLines !== []) { - $eventContent = implode("\n", $eventLines) . "\n"; - file_put_contents($incomingDir . '/index.ts', $eventContent); - } - - Log::info('[Nitro] Added missing renderer stubs'); - } - - private function updateViteConfig(): void - { - $viteConfigPath = $this->clientPath . '/vite.config.mjs'; - - $content = file_exists($viteConfigPath) ? file_get_contents($viteConfigPath) : ''; - - if (str_contains($content, '@nitrots/nitro-renderer') && str_contains($content, '/var/www/nitro-renderer')) { - return; - } - - $viteConfig = <<<'CONFIG' -import react from "@vitejs/plugin-react"; -import { resolve } from "path"; -import { defineConfig } from "vite"; - -const rendererPath = "/var/www/nitro-renderer"; - -export default defineConfig({ - plugins: [react()], - server: { - fs: { allow: [resolve(__dirname), rendererPath] }, - proxy: { "/api": { target: "http://localhost:3000", changeOrigin: true } }, - }, - resolve: { - alias: [ - { find: "@nitrots/nitro-renderer", replacement: resolve(rendererPath, "src") }, - { find: "@nitrots/api", replacement: resolve(rendererPath, "packages", "api", "src") }, - { find: "@nitrots/assets", replacement: resolve(rendererPath, "packages", "assets", "src") }, - { find: "@nitrots/avatar", replacement: resolve(rendererPath, "packages", "avatar", "src") }, - { find: "@nitrots/camera", replacement: resolve(rendererPath, "packages", "camera", "src") }, - { find: "@nitrots/communication", replacement: resolve(rendererPath, "packages", "communication", "src") }, - { find: "@nitrots/configuration", replacement: resolve(rendererPath, "packages", "configuration", "src") }, - { find: "@nitrots/events", replacement: resolve(rendererPath, "packages", "events", "src") }, - { find: "@nitrots/localization", replacement: resolve(rendererPath, "packages", "localization", "src") }, - { find: "@nitrots/room", replacement: resolve(rendererPath, "packages", "room", "src") }, - { find: "@nitrots/session", replacement: resolve(rendererPath, "packages", "session", "src") }, - { find: "@nitrots/sound", replacement: resolve(rendererPath, "packages", "sound", "src") }, - { find: "@nitrots/utils/src", replacement: resolve(rendererPath, "packages", "utils", "src") }, - { find: "@nitrots/utils", replacement: resolve(rendererPath, "packages", "utils", "src") }, - { find: "@", replacement: resolve(__dirname, "src") }, - { find: "~", replacement: resolve(__dirname, "node_modules") }, - { find: "pixi.js", replacement: resolve(rendererPath, "node_modules", "pixi.js") }, - { find: "pixi-filters", replacement: resolve(rendererPath, "node_modules", "pixi-filters") }, - { find: "howler", replacement: resolve(rendererPath, "node_modules", "howler") }, - ], - }, - build: { - assetsInlineLimit: 102400, - chunkSizeWarningLimit: 200000, - rollupOptions: { - output: { - assetFileNames: "src/assets/[name]-[hash].[ext]", - manualChunks: (id) => { - if (id.includes("node_modules")) { - if (id.includes("@nitrots") || id.includes("nitro-renderer")) { - return "nitro-renderer"; - } - return "vendor"; - } - }, - }, - }, - }, -}); -CONFIG; - - file_put_contents($this->clientPath . '/vite.config.mjs', $viteConfig); - Process::timeout(5)->run('chmod 644 ' . escapeshellarg($this->clientPath . '/vite.config.mjs')); - Process::timeout(5)->run('chown www-data:www-data ' . escapeshellarg($this->clientPath . '/vite.config.mjs')); - Log::info('[Nitro] Updated vite.config.mjs'); - } - - private function createYarnLink(): void - { - $nitrotsDir = $this->clientPath . '/node_modules/@nitrots'; - if (! is_dir($nitrotsDir)) { - mkdir($nitrotsDir, 0755, true); - } - - Process::timeout(10)->run('rm -rf ' . escapeshellarg($nitrotsDir) . '/*'); - Process::timeout(30)->run( - 'cp -r ' . escapeshellarg($this->rendererPath . '/node_modules/@nitrots/*') . ' ' . escapeshellarg($nitrotsDir) . '/', - ); - Process::timeout(10)->run('chown -R www-data:www-data ' . escapeshellarg($nitrotsDir)); - Log::info('[Nitro] Copied @nitrots packages'); - } - - private function installDependencies(): void - { - Log::info('[Nitro] Cleaning renderer node_modules'); - - Process::timeout(30)->run('rm -rf ' . escapeshellarg($this->rendererPath . '/node_modules')); - - Log::info('[Nitro] Installing renderer deps'); - - $rendererYarn = Process::timeout(300)->run( - 'cd ' . escapeshellarg($this->rendererPath) . ' && yarn install --ignore-engines 2>&1', - ); - - if (! $rendererYarn->successful()) { - Log::warning('[Nitro] Yarn failed for renderer, trying npm'); - Process::timeout(300)->run( - 'cd ' . escapeshellarg($this->rendererPath) . ' && npm install --legacy-peer-deps 2>&1', - ); - } - - Process::timeout(10)->run('chown -R www-data:www-data ' . escapeshellarg($this->rendererPath)); - - Log::info('[Nitro] Cleaning client node_modules'); - - Process::timeout(30)->run('rm -rf ' . escapeshellarg($this->clientPath . '/node_modules')); - - Log::info('[Nitro] Installing client deps'); - - $clientYarn = Process::timeout(300)->run( - 'cd ' . escapeshellarg($this->clientPath) . ' && yarn install --ignore-engines 2>&1', - ); - - if (! $clientYarn->successful()) { - Log::warning('[Nitro] Yarn failed for client, trying npm'); - Process::timeout(300)->run( - 'cd ' . escapeshellarg($this->clientPath) . ' && npm install --legacy-peer-deps 2>&1', - ); - } - - $this->createYarnLink(); - - Process::timeout(10)->run('chown -R www-data:www-data ' . escapeshellarg($this->clientPath)); - } - - public function buildClient(): array - { - Log::info('[Nitro] Building client'); - - $yarnBuild = Process::timeout(600)->run( - 'cd ' . escapeshellarg($this->clientPath) . ' && yarn build 2>&1', - ); - - if ($yarnBuild->successful()) { - Process::timeout(10)->run('chown -R www-data:www-data ' . escapeshellarg($this->clientPath)); - Log::info('[Nitro] Build successful (yarn)'); - - return ['success' => true, 'method' => 'yarn']; - } - - Log::warning('[Nitro] Yarn build failed, trying npm build'); - - $npmBuild = Process::timeout(600)->run( - 'cd ' . escapeshellarg($this->clientPath) . ' && npm run build 2>&1', - ); - - if ($npmBuild->successful()) { - Process::timeout(10)->run('chown -R www-data:www-data ' . escapeshellarg($this->clientPath)); - Log::info('[Nitro] Build successful (npm)'); - - return ['success' => true, 'method' => 'npm']; - } - - $output = $yarnBuild->output() . $yarnBuild->errorOutput() . "\n" . $npmBuild->output() . $npmBuild->errorOutput(); - Log::error('[Nitro] Build failed', ['output' => substr($output, 0, 500)]); - - return [ - 'success' => false, - 'error' => 'Build mislukt: ' . substr($output, 0, 300), - ]; - } - - public function deployClient(): void - { - Log::info('[Nitro] Deploying client'); - - // Clear and copy - Process::timeout(30)->run('rm -rf ' . escapeshellarg($this->clientWebRoot) . '/*'); - Process::timeout(60)->run( - 'cp -r ' . escapeshellarg($this->clientPath . '/dist') . '/* ' . - escapeshellarg($this->clientWebRoot) . '/ 2>&1', - ); - - // Always add/update toolbar color CSS variables to root - $indexFile = $this->clientWebRoot . '/index.html'; - if (file_exists($indexFile)) { - $content = file_get_contents($indexFile); - $cssVars = ''; - - // Replace existing CSS vars or add new - if (preg_match('/