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 && $commitSha !== null) { $isUpdate = $installedJarCommit !== $commitSha; } elseif ($installedDate !== null && $commitDate) { $isUpdate = (int) $installedDate < $commitDate; } elseif ($installedDate === null && $commitDate) { $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 || $hasSourceUpdates, '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 || $hasSourceUpdates, '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); } }