You've already forked Atomcms-edit
2525 lines
97 KiB
PHP
Executable File
2525 lines
97 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Miscellaneous\WebsiteSetting;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Process;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Str;
|
|
|
|
class EmulatorUpdateService
|
|
{
|
|
private const string SQL_TABLE = 'emulator_sql_updates';
|
|
|
|
private readonly ?SettingsService $settings;
|
|
|
|
private readonly ?string $githubUrl;
|
|
|
|
private readonly ?string $jarDirectUrl;
|
|
|
|
private ?string $githubRepo;
|
|
|
|
private ?string $githubBranch;
|
|
|
|
private readonly ?string $jarPath;
|
|
|
|
private readonly ?string $emulatorService;
|
|
|
|
private readonly ?string $databaseHost;
|
|
|
|
private readonly ?string $databasePort;
|
|
|
|
private readonly ?string $databaseName;
|
|
|
|
private readonly ?string $databaseUsername;
|
|
|
|
private readonly ?string $databasePassword;
|
|
|
|
private readonly ?string $emulatorSourcePath;
|
|
|
|
private ?string $sourceRepo;
|
|
|
|
private ?string $sourceBranch;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->settings = app(SettingsService::class);
|
|
$this->settings->clearInstanceCache();
|
|
$this->githubUrl = $this->settings->getOrDefault('emulator_github_url', '');
|
|
$this->jarDirectUrl = $this->settings->getOrDefault('emulator_jar_direct_url', '');
|
|
|
|
$basePath = $this->detectBasePath();
|
|
$this->jarPath = $this->resolveEmulatorPath($this->settings->getOrDefault('emulator_jar_path', $basePath . '/Emulator'));
|
|
$this->emulatorService = $this->settings->getOrDefault('emulator_service_name', $this->detectEmulatorService());
|
|
$this->databaseHost = config('database.connections.mysql.host');
|
|
$this->databasePort = config('database.connections.mysql.port', '3306');
|
|
$this->databaseName = config('database.connections.mysql.database');
|
|
$this->databaseUsername = config('database.connections.mysql.username');
|
|
$this->databasePassword = config('database.connections.mysql.password');
|
|
$this->emulatorSourcePath = $this->resolveEmulatorPath($this->settings->getOrDefault('emulator_source_path', $basePath . '/emulator-source'));
|
|
$this->sourceRepo = $this->settings->getOrDefault('emulator_source_repo', '');
|
|
|
|
$this->parseGitHubUrl($this->githubUrl);
|
|
$this->parseSourceRepo($this->sourceRepo);
|
|
$this->ensureGitSafeDirectories();
|
|
|
|
$settingBranch = $this->settings->getOrDefault('emulator_github_branch', null);
|
|
if ($settingBranch) {
|
|
$this->githubBranch = strtolower((string) $settingBranch);
|
|
$this->sourceBranch = strtolower((string) $settingBranch);
|
|
}
|
|
}
|
|
|
|
private function detectBasePath(): string
|
|
{
|
|
$possiblePaths = [
|
|
base_path(),
|
|
'/var/www/atomcms',
|
|
'/var/www/html',
|
|
'/var/www',
|
|
dirname(base_path()),
|
|
];
|
|
|
|
foreach ($possiblePaths as $path) {
|
|
if (is_dir($path)) {
|
|
return $path;
|
|
}
|
|
}
|
|
|
|
return '/var/www';
|
|
}
|
|
|
|
private function resolveEmulatorPath(string $path): string
|
|
{
|
|
if (str_starts_with($path, '/')) {
|
|
return $path;
|
|
}
|
|
|
|
return $this->detectBasePath() . '/' . ltrim($path, '/');
|
|
}
|
|
|
|
private function detectEmulatorService(): string
|
|
{
|
|
$possibleServices = ['emulator', 'arcturus', 'morningstar', 'habbo', 'hotel', 'game'];
|
|
|
|
foreach ($possibleServices as $service) {
|
|
$result = Process::timeout(5)->run("systemctl list-unit-files {$service}.service 2>/dev/null | grep -q '{$service}.service' && echo 'found'");
|
|
if ($result->successful() && trim($result->output()) === 'found') {
|
|
return $service;
|
|
}
|
|
|
|
$result = Process::timeout(5)->run("systemctl list-units --type=service --all 2>/dev/null | grep -q '{$service}.service' && echo 'found'");
|
|
if ($result->successful() && trim($result->output()) === 'found') {
|
|
return $service;
|
|
}
|
|
}
|
|
|
|
return 'emulator';
|
|
}
|
|
|
|
private function ensureGitSafeDirectories(): void
|
|
{
|
|
$this->detectBasePath();
|
|
|
|
$directories = [
|
|
base_path(),
|
|
'/var/www/atomcms',
|
|
'/var/www',
|
|
$this->emulatorSourcePath,
|
|
$this->jarPath,
|
|
dirname((string) $this->emulatorSourcePath),
|
|
dirname((string) $this->jarPath),
|
|
];
|
|
|
|
foreach (array_unique($directories) as $dir) {
|
|
if (! empty($dir) && $this->isDirAccessible($dir)) {
|
|
Process::timeout(5)->run('git config --global --add safe.directory ' . escapeshellarg($dir) . ' 2>/dev/null || true');
|
|
}
|
|
}
|
|
|
|
Process::timeout(5)->run("git config --global --add safe.directory '*' 2>/dev/null || true");
|
|
|
|
$this->ensureSudoAccess();
|
|
}
|
|
|
|
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 ensureSudoAccess(): void
|
|
{
|
|
$sudoersFile = '/etc/sudoers.d/atomcms-emulator';
|
|
$webUser = 'www-data';
|
|
|
|
$content = "# Auto-generated by AtomCMS - Do not edit\n";
|
|
$content .= "{$webUser} ALL=(ALL) NOPASSWD: /bin/systemctl start *, /bin/systemctl stop *, /bin/systemctl restart *, /usr/bin/systemctl start *, /usr/bin/systemctl stop *, /usr/bin/systemctl restart *\n";
|
|
|
|
// Check if file exists using command line (avoids open_basedir)
|
|
$checkResult = Process::timeout(5)->run("test -f {$sudoersFile} && echo 'exists' || echo 'missing'");
|
|
$fileExists = trim($checkResult->output()) === 'exists';
|
|
|
|
if (! $fileExists) {
|
|
Process::timeout(5)->run("echo '{$content}' | sudo tee {$sudoersFile} > /dev/null 2>&1");
|
|
Process::timeout(5)->run("sudo chmod 0440 {$sudoersFile} 2>/dev/null");
|
|
}
|
|
}
|
|
|
|
private function ensureJavaInstalled(): array
|
|
{
|
|
$actions = [];
|
|
$errors = [];
|
|
|
|
$javaCheck = Process::timeout(5)->run('java -version 2>&1');
|
|
if (! $javaCheck->successful()) {
|
|
$errors[] = 'Java niet geïnstalleerd';
|
|
Log::warning('[EmulatorUpdate] Java not found, attempting to install');
|
|
|
|
$installResult = Process::timeout(180)->run('apt-get update && apt-get install -y default-jdk 2>&1');
|
|
if ($installResult->successful()) {
|
|
$actions[] = 'Java (laatste versie) geïnstalleerd';
|
|
} else {
|
|
$errors[] = 'Kon Java niet installeren';
|
|
}
|
|
} else {
|
|
$javaVersion = explode("\n", $javaCheck->errorOutput())[0] ?? 'Java gevonden';
|
|
$actions[] = trim($javaVersion);
|
|
}
|
|
|
|
$mavenCheck = Process::timeout(5)->run('which mvn');
|
|
if (! $mavenCheck->successful()) {
|
|
Log::warning('[EmulatorUpdate] Maven not found, attempting to install');
|
|
$installResult = Process::timeout(180)->run('apt-get install -y maven 2>&1');
|
|
if ($installResult->successful()) {
|
|
$actions[] = 'Maven geïnstalleerd';
|
|
}
|
|
} else {
|
|
$actions[] = 'Maven gevonden';
|
|
}
|
|
|
|
$gradleCheck = Process::timeout(5)->run('which gradle');
|
|
if (! $gradleCheck->successful()) {
|
|
Log::warning('[EmulatorUpdate] Gradle not found, will use wrapper if available');
|
|
} else {
|
|
$actions[] = 'Gradle gevonden';
|
|
}
|
|
|
|
return [
|
|
'actions' => $actions,
|
|
'errors' => $errors,
|
|
];
|
|
}
|
|
|
|
public function isConfigured(): bool
|
|
{
|
|
return ! in_array($this->githubUrl, [null, '', '0'], true) || ! in_array($this->jarDirectUrl, [null, '', '0'], true);
|
|
}
|
|
|
|
public function getStatus(): array
|
|
{
|
|
$rconService = new RconService;
|
|
|
|
// Check basic status
|
|
$isConnected = $rconService->isConnected();
|
|
|
|
// Check for updates
|
|
$updateCheck = $this->checkForUpdates();
|
|
|
|
// Check JAR file
|
|
$jarExists = $this->commandFileExists($this->jarPath . '/*.jar');
|
|
$jarFiles = $this->getJarFiles();
|
|
|
|
// Check source
|
|
$sourceExists = $this->commandDirExists($this->emulatorSourcePath);
|
|
|
|
// Check service
|
|
$serviceRunning = $this->isServiceRunning();
|
|
|
|
// Check emulator database
|
|
$emulatorDbConnected = $this->testEmulatorDbConnection();
|
|
|
|
// Get commits
|
|
$sourceInfo = $this->checkForSourceUpdates();
|
|
|
|
return [
|
|
'is_connected' => $isConnected,
|
|
'service_running' => $serviceRunning,
|
|
'jar_exists' => $jarExists,
|
|
'jar_files' => $jarFiles,
|
|
'source_exists' => $sourceExists,
|
|
'source_info' => $sourceInfo,
|
|
'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,
|
|
'jar_path' => $this->jarPath,
|
|
'source_path' => $this->emulatorSourcePath,
|
|
'service_name' => $this->emulatorService,
|
|
'emulator_db_connected' => $emulatorDbConnected,
|
|
];
|
|
}
|
|
|
|
private function testEmulatorDbConnection(): bool
|
|
{
|
|
try {
|
|
$result = Process::timeout(5)->run('mysql -h ' . escapeshellarg((string) $this->databaseHost) . ' -P ' . escapeshellarg((string) $this->databasePort) . ' -u ' . escapeshellarg((string) $this->databaseUsername) . ' -p' . escapeshellarg((string) $this->databasePassword) . ' -e "SELECT 1" ' . escapeshellarg((string) $this->databaseName) . ' 2>/dev/null | head -1');
|
|
|
|
return $result->successful();
|
|
} catch (\Exception) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private function isServiceRunning(): bool
|
|
{
|
|
try {
|
|
$result = Process::timeout(5)->run('systemctl is-active ' . escapeshellarg((string) $this->emulatorService) . ' 2>/dev/null');
|
|
|
|
return trim($result->output()) === 'active';
|
|
} catch (\Exception) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private function getJarFiles(): array
|
|
{
|
|
$result = Process::timeout(5)->run('ls -1 ' . escapeshellarg((string) $this->jarPath) . '/*.jar 2>/dev/null | head -5');
|
|
if ($result->successful()) {
|
|
$files = array_filter(explode("\n", trim($result->output())));
|
|
|
|
return array_map(basename(...), $files);
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
public function checkForUpdates(): array
|
|
{
|
|
if (! $this->isConfigured()) {
|
|
return [
|
|
'update_available' => false,
|
|
'error' => 'Configureer een GitHub URL of directe .jar URL',
|
|
];
|
|
}
|
|
|
|
// Check for source updates FIRST (independent of JAR method)
|
|
$sourceInfo = $this->checkForSourceUpdates();
|
|
$hasSourceUpdates = $sourceInfo && $sourceInfo['has_update'];
|
|
|
|
if (! in_array($this->jarDirectUrl, [null, '', '0'], true)) {
|
|
$jarInfo = $this->validateDirectUrl($this->jarDirectUrl);
|
|
|
|
if ($jarInfo) {
|
|
// Update the stored version if we're checking a direct URL
|
|
if (! empty($jarInfo['version'])) {
|
|
$currentVersion = $this->settings->getOrDefault('emulator_version', '0.0.0');
|
|
if ($jarInfo['version'] !== $currentVersion) {
|
|
$this->settings->set('emulator_version', $jarInfo['version']);
|
|
}
|
|
}
|
|
|
|
// Check if source has updates even if JAR doesn't
|
|
$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,
|
|
];
|
|
}
|
|
|
|
// URL not reachable, but check source updates
|
|
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',
|
|
];
|
|
}
|
|
|
|
$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'];
|
|
$commitDate = $jarInfo['commit_date'] ?? null;
|
|
|
|
$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' => $commitDate,
|
|
'source_info' => $sourceInfo,
|
|
'has_source_updates' => $hasSourceUpdates,
|
|
];
|
|
}
|
|
|
|
public function checkForSqlUpdates(bool $recentOnly = true): array
|
|
{
|
|
if (! $this->githubRepo) {
|
|
return [
|
|
'has_updates' => false,
|
|
'error' => 'Geen GitHub repo geconfigureerd',
|
|
];
|
|
}
|
|
|
|
$this->ensureSqlTableExists();
|
|
|
|
$result = $this->fetchSqlFilesFromGitHub($recentOnly);
|
|
|
|
// Check if result is an error response
|
|
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),
|
|
];
|
|
}
|
|
|
|
private function ensureSqlTableExists(): void
|
|
{
|
|
$tableName = self::SQL_TABLE;
|
|
|
|
if (Schema::hasTable($tableName)) {
|
|
return;
|
|
}
|
|
|
|
Schema::create($tableName, 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 getAppliedSqlFiles(): array
|
|
{
|
|
$this->ensureSqlTableExists();
|
|
|
|
return DB::table(self::SQL_TABLE)
|
|
->orderBy('applied_at', 'desc')
|
|
->get()
|
|
->toArray();
|
|
}
|
|
|
|
public function runSqlUpdates(): array
|
|
{
|
|
$sqlCheck = $this->checkForSqlUpdates(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 (isset($results['errors']) && $results['errors'] !== []) {
|
|
$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!';
|
|
}
|
|
|
|
if (isset($results['files_run']) && $results['files_run'] !== [] && $this->restartEmulator()) {
|
|
$results['message'] .= ' Emulator herstart.';
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
public function restartEmulator(): bool
|
|
{
|
|
$serviceName = $this->emulatorService;
|
|
|
|
try {
|
|
Log::info('[EmulatorUpdate] Restarting emulator service: ' . $serviceName);
|
|
|
|
$commands = [
|
|
"sudo systemctl restart {$serviceName} 2>&1",
|
|
"sudo service {$serviceName} restart 2>&1",
|
|
"sudo /etc/init.d/{$serviceName} restart 2>&1",
|
|
"sudo systemctl stop {$serviceName} && sudo systemctl start {$serviceName} 2>&1",
|
|
];
|
|
|
|
foreach ($commands as $cmd) {
|
|
$output = shell_exec($cmd);
|
|
if (! str_contains($output ?? '', 'error') && ! str_contains($output ?? '', 'failed')) {
|
|
Log::info('[EmulatorUpdate] Emulator restarted successfully');
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
Log::warning('[EmulatorUpdate] All restart methods failed, trying pgrep');
|
|
|
|
$pidCheck = shell_exec("pgrep -f '{$serviceName}'");
|
|
if (! in_array(trim($pidCheck ?? ''), ['', '0'], true)) {
|
|
$pids = explode("\n", trim($pidCheck));
|
|
foreach ($pids as $pid) {
|
|
shell_exec("kill -9 {$pid} 2>&1");
|
|
}
|
|
sleep(2);
|
|
}
|
|
|
|
$startCommands = [
|
|
"sudo systemctl start {$serviceName} 2>&1",
|
|
"sudo service {$serviceName} start 2>&1",
|
|
];
|
|
|
|
foreach ($startCommands as $cmd) {
|
|
$output = shell_exec($cmd);
|
|
if (! str_contains($output ?? '', 'error') && ! str_contains($output ?? '', 'failed')) {
|
|
Log::info('[EmulatorUpdate] Emulator started successfully');
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
} catch (\Exception $e) {
|
|
Log::error('[EmulatorUpdate] Failed to restart emulator', ['error' => $e->getMessage()]);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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(),
|
|
],
|
|
);
|
|
}
|
|
|
|
public function getAppliedSqlUpdates(): array
|
|
{
|
|
$this->ensureSqlTableExists();
|
|
|
|
return $this->getAppliedSqlFiles();
|
|
}
|
|
|
|
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'];
|
|
}
|
|
|
|
// Build from source if there are source updates (regardless of type)
|
|
$hasSourceUpdates = ($check['has_source_updates'] ?? false) || ($check['type'] ?? '') === 'source_build';
|
|
|
|
if ($hasSourceUpdates && $this->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->performUpdate($check);
|
|
|
|
if ($result['success']) {
|
|
$this->runSqlUpdates();
|
|
|
|
if ($this->restartEmulator()) {
|
|
$result['restarted'] = true;
|
|
$result['message'] = ($result['message'] ?? '') . ' | 🔄 Emulator herstart';
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function downloadAndRunSql(array $file): array
|
|
{
|
|
try {
|
|
$response = Http::timeout(60)->get($file['url']);
|
|
|
|
if (! $response->successful()) {
|
|
return ['success' => false, 'error' => 'Download mislukt'];
|
|
}
|
|
|
|
$sql = $response->body();
|
|
|
|
$sql = $this->cleanSql($sql);
|
|
|
|
if (in_array(trim($sql), ['', '0'], true)) {
|
|
return ['success' => true, 'message' => 'Lege SQL file overgeslagen'];
|
|
}
|
|
|
|
// Get database credentials from settings or .env
|
|
$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. Vul de database gegevens in bij de instellingen.'];
|
|
}
|
|
|
|
$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));
|
|
}
|
|
|
|
private function fetchSqlFilesFromGitHub(bool $recentOnly = false): array
|
|
{
|
|
if (! $this->githubRepo) {
|
|
return [];
|
|
}
|
|
|
|
$branch = $this->githubBranch ?: 'main';
|
|
|
|
// Try multiple possible folder names (with URL encoding)
|
|
$folderNames = [
|
|
'Database%20Updates',
|
|
'Database Updates',
|
|
'database_updates',
|
|
'database/updates',
|
|
'sql/updates',
|
|
'sql',
|
|
'updates',
|
|
];
|
|
|
|
// Known SQL files in Arcturus-Morningstar-Extended repository
|
|
// We check raw GitHub content which has no rate limit
|
|
$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 = [];
|
|
|
|
// Check each known file exists via raw GitHub (no rate limit)
|
|
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; // Found in this folder, move to next file
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($sqlFiles !== []) {
|
|
usort($sqlFiles, fn ($a, $b) => strcmp($a['name'], $b['name']));
|
|
|
|
return $sqlFiles;
|
|
}
|
|
|
|
return [];
|
|
} catch (\Exception $e) {
|
|
Log::warning('[EmulatorUpdate] Could not fetch SQL files', ['error' => $e->getMessage()]);
|
|
|
|
return [];
|
|
}
|
|
}
|
|
|
|
private function fetchRecentSqlFiles(string $branch): array
|
|
{
|
|
$weekAgo = now()->subDays(7)->toIso8601String();
|
|
|
|
// Get GitHub token from settings if available
|
|
$githubToken = setting('github_token', '');
|
|
$headers = [
|
|
'Accept' => 'application/vnd.github+json',
|
|
'User-Agent' => 'AtomCMS-EmulatorUpdate/1.0',
|
|
];
|
|
if (! empty($githubToken)) {
|
|
$headers['Authorization'] = 'Bearer ' . $githubToken;
|
|
}
|
|
|
|
// Try multiple folder names
|
|
$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']));
|
|
Log::info('[EmulatorUpdate] Found recent SQL files in folder: ' . $folderName, ['count' => count($sqlFiles)]);
|
|
|
|
return $sqlFiles;
|
|
}
|
|
}
|
|
}
|
|
|
|
return [];
|
|
} catch (\Exception $e) {
|
|
Log::warning('[EmulatorUpdate] Could not fetch recent SQL files', ['error' => $e->getMessage()]);
|
|
|
|
return [];
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
// Build update script with comprehensive fallbacks
|
|
$updateScript = <<<BASH
|
|
#!/bin/bash
|
|
set -e
|
|
|
|
JAR_URL='{$jarUrl}'
|
|
JAR_NAME='{$jarName}'
|
|
JAR_PATH='{$this->jarPath}'
|
|
SERVICE='{$serviceName}'
|
|
TEMP_DIR='{$tempDir}'
|
|
TEMP_JAR='{$tempJar}'
|
|
|
|
echo "=== Emulator Update ==="
|
|
echo "URL: \$JAR_URL"
|
|
echo "JAR: \$JAR_NAME"
|
|
echo ""
|
|
|
|
# Check disk space (need at least 500MB)
|
|
FREE_MB=\$(df -m "\$JAR_PATH" 2>/dev/null | tail -1 | awk '{print \$4}')
|
|
if [ "\$FREE_MB" -lt 500 ] 2>/dev/null; then
|
|
echo "WARNING: Low disk space (\${FREE_MB}MB), cleaning temp..."
|
|
rm -rf /tmp/emulator-update-* /tmp/nitro_* /tmp/nitro-switch-* 2>/dev/null || true
|
|
fi
|
|
|
|
mkdir -p "\$TEMP_DIR" "\$JAR_PATH"
|
|
|
|
# Backup existing JAR
|
|
if ls "\$JAR_PATH"/*.jar 1>/dev/null 2>&1; then
|
|
mkdir -p "\$JAR_PATH/backup"
|
|
mv "\$JAR_PATH"/*.jar "\$JAR_PATH/backup/" 2>/dev/null || true
|
|
echo "Existing JAR backed up"
|
|
fi
|
|
|
|
# Download with retries
|
|
download_success=false
|
|
for attempt in 1 2 3; do
|
|
echo "Download attempt \$attempt/3..."
|
|
if curl -L --max-time 300 --retry 3 --retry-delay 5 -o "\$TEMP_JAR" "\$JAR_URL" 2>&1; then
|
|
# Validate it's actually a JAR
|
|
FILE_TYPE=\$(file -b "\$TEMP_JAR" 2>/dev/null)
|
|
JAR_SIZE=\$(stat -c%s "\$TEMP_JAR" 2>/dev/null || echo 0)
|
|
echo "Downloaded: \$JAR_SIZE bytes, type: \$FILE_TYPE"
|
|
|
|
if echo "\$FILE_TYPE" | grep -qi "zip\|jar\|archive" && [ "\$JAR_SIZE" -gt 1000 ]; then
|
|
download_success=true
|
|
break
|
|
else
|
|
echo "Invalid file (not a JAR), retrying..."
|
|
rm -f "\$TEMP_JAR" 2>/dev/null || true
|
|
sleep 3
|
|
fi
|
|
else
|
|
echo "Curl failed, retrying in 5s..."
|
|
sleep 5
|
|
fi
|
|
done
|
|
|
|
if [ "\$download_success" = false ]; then
|
|
echo "ERROR: Download failed after 3 attempts"
|
|
# Restore backup
|
|
if ls "\$JAR_PATH/backup"/*.jar 1>/dev/null 2>&1; then
|
|
mv "\$JAR_PATH/backup"/*.jar "\$JAR_PATH/" 2>/dev/null || true
|
|
echo "Backup restored"
|
|
fi
|
|
rm -rf "\$TEMP_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
# Move JAR to target
|
|
mv "$TEMP_JAR" "$JAR_PATH/"
|
|
chown -R www-data:www-data "$JAR_PATH"
|
|
chmod 755 "$JAR_PATH/$JAR_NAME"
|
|
echo "JAR installed: $JAR_PATH/$JAR_NAME"
|
|
|
|
# Restart service (try all methods)
|
|
service_restarted=false
|
|
for method in "systemctl restart \"\$SERVICE\"" "service \"\$SERVICE\" restart" "/etc/init.d/\$SERVICE restart" "kill -HUP \$(pgrep -f \"\$SERVICE\")" ; do
|
|
echo "Trying: \$method"
|
|
eval "\$method" 2>/dev/null && { service_restarted=true; break; } || true
|
|
done
|
|
if [ "\$service_restarted" = false ]; then
|
|
echo "WARNING: Service restart may have failed (check manually)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Cleanup
|
|
rm -rf "\$TEMP_DIR" 2>/dev/null || true
|
|
echo ""
|
|
echo "=== Update complete ==="
|
|
BASH;
|
|
|
|
$scriptPath = '/tmp/emulator_update_' . uniqid() . '.sh';
|
|
file_put_contents($scriptPath, $updateScript);
|
|
chmod($scriptPath, 0755);
|
|
|
|
Log::info('[EmulatorUpdate] Starting update', [
|
|
'version' => $version,
|
|
'jar' => $jarName,
|
|
'url' => $jarUrl,
|
|
]);
|
|
|
|
try {
|
|
$result = Process::timeout(600)->run('bash ' . $scriptPath . ' 2>&1');
|
|
@unlink($scriptPath);
|
|
|
|
Log::info('[EmulatorUpdate] Update exitCode', ['exitCode' => $result->exitCode()]);
|
|
|
|
if ($result->exitCode() !== 0) {
|
|
Log::error('[EmulatorUpdate] Update failed', [
|
|
'output' => $result->output(),
|
|
'error' => $result->errorOutput(),
|
|
]);
|
|
|
|
return [
|
|
'success' => false,
|
|
'error' => 'Update mislukt: ' . substr($result->output(), 0, 300),
|
|
];
|
|
}
|
|
|
|
setting('emulator_version', $version);
|
|
|
|
// Store the commit date as installed date
|
|
$commitDate = $check['commit_date'] ?? time();
|
|
$this->settings->set('emulator_jar_installed_date', (string) $commitDate);
|
|
$this->settings->set('emulator_jar_commit', $check['commit'] ?? null);
|
|
|
|
// Also track source commit info for update detection
|
|
$sourceSha = $check['source_info']['latest_sha'] ?? $check['commit'] ?? null;
|
|
$sourceDate = $check['source_info']['latest_timestamp'] ?? $commitDate ?? null;
|
|
if ($sourceSha) {
|
|
$this->settings->set('emulator_source_commit', $sourceSha);
|
|
}
|
|
if ($sourceDate) {
|
|
$this->settings->set('emulator_source_date', (string) $sourceDate);
|
|
}
|
|
|
|
// Track which branch the update came from
|
|
$currentBranch = $this->sourceBranch ?: $this->githubBranch ?: 'main';
|
|
$this->settings->set('emulator_installed_branch', $currentBranch);
|
|
|
|
Log::info('[EmulatorUpdate] 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('[EmulatorUpdate] Exception', ['error' => $e->getMessage()]);
|
|
|
|
return [
|
|
'success' => false,
|
|
'error' => $e->getMessage(),
|
|
];
|
|
}
|
|
}
|
|
|
|
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');
|
|
|
|
// Try to get commit info from GitHub API for GitHub raw URLs
|
|
$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();
|
|
|
|
// Check if we got valid data (not a rate limit error)
|
|
if (isset($data['sha']) && ! isset($data['message'])) {
|
|
$gitHubInfoAvailable = true;
|
|
$commitSha = $data['sha'];
|
|
|
|
// Try to get commit date
|
|
if (isset($data['commit']['committer']['date'])) {
|
|
$lastModified = strtotime($data['commit']['committer']['date']);
|
|
}
|
|
}
|
|
}
|
|
} catch (\Exception) {
|
|
Log::debug('[EmulatorUpdate] Could not fetch GitHub commit info for direct URL');
|
|
}
|
|
}
|
|
|
|
// Fallback: try HEAD request
|
|
if ($lastModified === null) {
|
|
$response = Http::timeout(10)->head($url);
|
|
if ($response->successful()) {
|
|
$modifiedSince = $response->header('Last-Modified');
|
|
if ($modifiedSince) {
|
|
$lastModified = strtotime($modifiedSince);
|
|
// Verify strtotime succeeded
|
|
if ($lastModified === false) {
|
|
$lastModified = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if we have stored info and if this is actually newer
|
|
$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)) {
|
|
// Compare by SHA (most reliable when GitHub API available)
|
|
if ($gitHubInfoAvailable && $commitSha !== null && isset($storedDataArray['commit_sha'])) {
|
|
$isUpdate = $commitSha !== $storedDataArray['commit_sha'];
|
|
}
|
|
// Fallback to date comparison
|
|
elseif ($lastModified !== null && $installedDate !== null) {
|
|
$isUpdate = (int) $installedDate < $lastModified;
|
|
} elseif ($lastModified !== null && isset($storedDataArray['last_modified'])) {
|
|
$isUpdate = $lastModified > $storedDataArray['last_modified'];
|
|
}
|
|
// Fallback to version comparison from filename
|
|
elseif (! in_array($version, ['', '0', $storedVersion], true)) {
|
|
$isUpdate = version_compare($version, $storedVersion) > 0;
|
|
}
|
|
}
|
|
} else {
|
|
// First time check
|
|
$isUpdate = true;
|
|
}
|
|
|
|
// Always update our stored info
|
|
$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));
|
|
|
|
Log::info('[EmulatorUpdate] Direct URL check: jar=' . $jarName . ', version=' . $version . ', stored=' . $storedVersion . ', sha=' . ($commitSha ?? 'N/A') . ', github=' . ($gitHubInfoAvailable ? 'yes' : 'no') . ', isUpdate=' . ($isUpdate ? 'true' : 'false'));
|
|
|
|
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('[EmulatorUpdate] Direct URL not reachable', ['error' => $e->getMessage()]);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private 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 {
|
|
// First get the file info for download URL
|
|
$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;
|
|
}
|
|
|
|
// Get the commit date by fetching the last commit that touched this file
|
|
$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 $e) {
|
|
Log::debug('[EmulatorUpdate] Could not fetch commit date for ' . $name);
|
|
}
|
|
|
|
// Get the installed date (set when actually installed)
|
|
$installedDate = $this->settings->getOrDefault('emulator_jar_installed_date', null);
|
|
|
|
// Also get the last checked date (for debugging)
|
|
$lastCheckedDate = $this->settings->getOrDefault("emulator_jar_{$name}_date", null);
|
|
|
|
// Update the last checked date
|
|
if ($commitDate) {
|
|
$this->settings->set("emulator_jar_{$name}_date", (string) $commitDate);
|
|
}
|
|
|
|
// Extract version from filename or use commit date
|
|
$version = $this->extractVersionFromFilename($name);
|
|
if ($version === '' || $version === '0') {
|
|
$version = $commitDate ? date('Y.m.d', $commitDate) : date('Y.m.d');
|
|
}
|
|
|
|
// Get the installed JAR commit SHA for comparison
|
|
$installedJarCommit = $this->settings->getOrDefault('emulator_jar_commit', null);
|
|
|
|
// Check if this is actually an update
|
|
$isUpdate = false;
|
|
$installedVersion = $this->settings->getOrDefault('emulator_version', '0.0.0');
|
|
|
|
// First, try to compare by SHA (most reliable)
|
|
if ($installedJarCommit !== null && $commitSha !== null) {
|
|
$isUpdate = $installedJarCommit !== $commitSha;
|
|
}
|
|
// If SHA comparison isn't possible, fall back to date comparison
|
|
elseif ($installedDate !== null && $commitDate) {
|
|
// Compare installed date with latest commit date
|
|
$isUpdate = (int) $installedDate < $commitDate;
|
|
} elseif ($installedDate === null && $commitDate) {
|
|
// Never installed - this is a first install
|
|
$isUpdate = true;
|
|
}
|
|
|
|
Log::info('[EmulatorUpdate] Checking update for ' . $name . ': installed=' . ($installedDate ?? 'null') . ', latest=' . ($commitDate ?? 'null') . ', isUpdate=' . ($isUpdate ? 'true' : 'false'));
|
|
|
|
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('[EmulatorUpdate] Error checking JAR file {$name}', ['error' => $e->getMessage()]);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function checkForSourceUpdates(): ?array
|
|
{
|
|
$repo = $this->sourceRepo ?: $this->githubRepo;
|
|
$branch = $this->sourceBranch ?: $this->githubBranch ?: 'main';
|
|
|
|
if (! $repo) {
|
|
return null;
|
|
}
|
|
|
|
// Try local git check first if source is already cloned
|
|
$localCheck = $this->checkLocalSourceUpdates();
|
|
if ($localCheck !== null) {
|
|
return $localCheck;
|
|
}
|
|
|
|
// Fallback to GitHub API
|
|
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);
|
|
|
|
// Compare SHAs first - if they're the same, no update
|
|
if ($storedSha !== null && $storedSha === $latestSha) {
|
|
$isUpdate = false;
|
|
} elseif ($storedSha !== null && $storedSha !== $latestSha) {
|
|
$isUpdate = true;
|
|
} elseif ($installedDate !== null) {
|
|
// Fallback to date comparison
|
|
$installedTimestamp = is_numeric($installedDate) ? (int) $installedDate : strtotime((string) $installedDate);
|
|
$isUpdate = $installedTimestamp < $latestTimestamp;
|
|
} else {
|
|
// Never installed - this is a first install
|
|
$isUpdate = false;
|
|
}
|
|
|
|
Log::info('[EmulatorUpdate] Source update check (GitHub API): repo=' . $repo . ', installed=' . ($installedDate ?? 'null') . ', latest=' . $latestTimestamp . ' (' . $latestDate . '), isUpdate=' . ($isUpdate ? 'true' : '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('[EmulatorUpdate] Could not check source updates via GitHub API', ['error' => $e->getMessage()]);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private function checkLocalSourceUpdates(): ?array
|
|
{
|
|
$sourcePath = $this->emulatorSourcePath;
|
|
|
|
// Use command line to check if directory exists (bypasses open_basedir)
|
|
$existsCheck = Process::timeout(5)->run("[ -d {$sourcePath} ] && echo 'exists'");
|
|
if (! $existsCheck->successful() || trim($existsCheck->output()) !== 'exists') {
|
|
// Try to clone the repo first
|
|
return $this->checkSourceByCloning();
|
|
}
|
|
|
|
try {
|
|
// Check if it's a git repo
|
|
$gitCheck = Process::timeout(5)->run("cd {$sourcePath} && git rev-parse --is-inside-work-tree 2>/dev/null");
|
|
if (! $gitCheck->successful()) {
|
|
// Not a git repo - try to clone fresh
|
|
Log::info('[EmulatorUpdate] Source path exists but not a git repo, will clone fresh');
|
|
|
|
return $this->checkSourceByCloning();
|
|
}
|
|
|
|
// Get current commit
|
|
$currentCommitResult = Process::timeout(5)->run("cd {$sourcePath} && git rev-parse HEAD");
|
|
if (! $currentCommitResult->successful()) {
|
|
return null;
|
|
}
|
|
$currentSha = trim($currentCommitResult->output());
|
|
|
|
// Get latest commit from remote
|
|
$fetchResult = Process::timeout(30)->run("cd {$sourcePath} && git fetch origin 2>&1");
|
|
if ($fetchResult->failed()) {
|
|
Log::debug('[EmulatorUpdate] Git fetch failed, trying local check only');
|
|
}
|
|
|
|
// Get latest commit on current branch
|
|
$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());
|
|
|
|
// Get commit date
|
|
$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 = '';
|
|
if ($msgResult->successful()) {
|
|
$latestMessage = trim($msgResult->output());
|
|
}
|
|
|
|
// Get author
|
|
$authorResult = Process::timeout(5)->run("cd {$sourcePath} && git log -1 --format='%an' {$latestSha}");
|
|
$latestAuthor = '';
|
|
if ($authorResult->successful()) {
|
|
$latestAuthor = trim($authorResult->output());
|
|
}
|
|
|
|
$storedSha = $this->settings->getOrDefault('emulator_source_commit', null);
|
|
$storedDate = $this->settings->getOrDefault('emulator_source_date', null);
|
|
$installedDate = $this->settings->getOrDefault('emulator_jar_installed_date', null);
|
|
|
|
// Check if there's an update - compare stored SHA with latest SHA
|
|
$isUpdate = false;
|
|
if ($storedSha !== null && $storedSha !== $latestSha) {
|
|
$isUpdate = true;
|
|
} elseif ($storedSha === null) {
|
|
// First time - consider it an update
|
|
$isUpdate = true;
|
|
}
|
|
|
|
Log::info('[EmulatorUpdate] Source update check (local git): stored=' . $storedSha . ', latest=' . $latestSha . ', isUpdate=' . ($isUpdate ? 'true' : 'false'));
|
|
|
|
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('[EmulatorUpdate] 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) {
|
|
Log::debug('[EmulatorUpdate] No repo configured for source check');
|
|
|
|
return null;
|
|
}
|
|
|
|
// Clean up repo URL if it contains tree/branch info
|
|
$repo = preg_replace('#/tree/[^/]+/.*$#', '', $repo);
|
|
$repo = str_replace(['https://github.com/', 'http://github.com/'], '', $repo);
|
|
$repo = rtrim($repo, '/');
|
|
|
|
Log::info('[EmulatorUpdate] Checking source updates by cloning', ['repo' => $repo, 'branch' => $branch]);
|
|
|
|
try {
|
|
$tempDir = '/tmp/emulator-source-check-' . Str::random(8);
|
|
|
|
// Clone with depth 1
|
|
$cloneResult = Process::timeout(120)->run(
|
|
"git clone --branch {$branch} --depth 1 https://github.com/{$repo}.git {$tempDir} 2>&1",
|
|
);
|
|
|
|
if ($cloneResult->failed()) {
|
|
Log::debug('[EmulatorUpdate] Could not clone source for checking: ' . $cloneResult->errorOutput());
|
|
|
|
return null;
|
|
}
|
|
|
|
// Get latest commit info
|
|
$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()) : '';
|
|
|
|
// Cleanup
|
|
Process::timeout(10)->run("rm -rf {$tempDir}");
|
|
|
|
$storedSha = $this->settings->getOrDefault('emulator_source_commit', null);
|
|
$storedDate = $this->settings->getOrDefault('emulator_source_date', null);
|
|
$installedDate = $this->settings->getOrDefault('emulator_jar_installed_date', null);
|
|
|
|
// Check if there's an update - compare stored SHA with latest SHA
|
|
$isUpdate = false;
|
|
if ($storedSha !== null && $storedSha !== $latestSha) {
|
|
$isUpdate = true;
|
|
} elseif ($storedSha === null) {
|
|
// First time - consider it an update
|
|
$isUpdate = true;
|
|
}
|
|
|
|
Log::info('[EmulatorUpdate] Source update check (cloned): stored=' . $storedSha . ', latest=' . $latestSha . ', isUpdate=' . ($isUpdate ? 'true' : 'false'));
|
|
|
|
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('[EmulatorUpdate] Source clone check failed: ' . $e->getMessage());
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
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('[EmulatorUpdate] Starting source build', [
|
|
'repo' => $repo,
|
|
'branch' => $branch,
|
|
'path' => $sourcePath,
|
|
]);
|
|
|
|
$this->ensureJavaInstalled();
|
|
|
|
// Pre-flight: Ensure directories exist and fix permissions
|
|
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)));
|
|
|
|
// Auto-retry logic
|
|
$maxRetries = 2;
|
|
$lastError = '';
|
|
|
|
for ($retry = 0; $retry <= $maxRetries; $retry++) {
|
|
// Always fix permissions before any operation (handles root-owned files from previous builds)
|
|
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('[EmulatorUpdate] Retry attempt', ['attempt' => $retry]);
|
|
// Force re-clone on retry
|
|
Process::timeout(30)->run('rm -rf ' . escapeshellarg((string) $sourcePath));
|
|
}
|
|
|
|
try {
|
|
// Use command line to check if directory exists and is a git repo (bypasses open_basedir)
|
|
$existsCheck = Process::timeout(5)->run('[ -d ' . escapeshellarg((string) $sourcePath) . " ] && echo 'exists'");
|
|
$sourceExists = $existsCheck->successful() && trim($existsCheck->output()) === 'exists';
|
|
|
|
// Also check if it's a valid git repository
|
|
$gitCheck = Process::timeout(5)->run('[ -d ' . escapeshellarg((string) $sourcePath) . "/.git ] && echo 'git'");
|
|
$isGitRepo = $gitCheck->successful() && trim($gitCheck->output()) === 'git';
|
|
|
|
if ($sourceExists && $isGitRepo) {
|
|
Log::info('[EmulatorUpdate] 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('[EmulatorUpdate] 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('[EmulatorUpdate] Git operation failed', ['error' => $lastError, 'attempt' => $retry]);
|
|
|
|
continue; // Retry
|
|
}
|
|
|
|
$buildCommands = $this->getBuildCommands($sourcePath);
|
|
|
|
Log::info('[EmulatorUpdate] Running build', ['command' => $buildCommands]);
|
|
$buildResult = Process::timeout(600)->run('cd ' . escapeshellarg((string) $sourcePath) . ' && ' . $this->getBuildCommands($sourcePath));
|
|
|
|
$hasSignalError = str_contains($buildResult->errorOutput(), 'signal') || str_contains($buildResult->output(), 'signal');
|
|
|
|
// Check for signal errors - still try to find JAR
|
|
if ($hasSignalError) {
|
|
Log::warning('[EmulatorUpdate] Build process received signal, checking if JAR was built anyway');
|
|
}
|
|
|
|
// Even if build "failed", check if JAR exists (signal errors may be misleading)
|
|
$jarPath = $this->findBuiltJar($sourcePath);
|
|
|
|
if ($jarPath) {
|
|
Log::info('[EmulatorUpdate] JAR found despite build status', ['jar' => $jarPath]);
|
|
} elseif ($buildResult->failed() && ! $hasSignalError) {
|
|
$lastError = 'Build failed: ' . substr($buildResult->errorOutput(), 0, 500);
|
|
Log::warning('[EmulatorUpdate] Build failed', ['error' => $lastError, 'attempt' => $retry]);
|
|
|
|
// Try to clean and rebuild
|
|
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; // Retry
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (! $jarPath) {
|
|
$lastError = 'Build succeeded but JAR not found. Check build output.';
|
|
Log::warning('[EmulatorUpdate] JAR not found', ['attempt' => $retry]);
|
|
|
|
continue;
|
|
}
|
|
|
|
$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) . '/',
|
|
'# Restart service (try all methods)
|
|
service_restarted=false
|
|
for method in "systemctl restart ' . escapeshellarg((string) $serviceName) . '" "service ' . escapeshellarg((string) $serviceName) . ' restart" "/etc/init.d/' . escapeshellarg((string) $serviceName) . ' restart" "kill -HUP \$(pgrep -f ' . escapeshellarg((string) $serviceName) . ')" ; do
|
|
echo "Trying: $method"
|
|
eval "$method" 2>/dev/null && { service_restarted=true; break; } || true
|
|
done
|
|
if [ "$service_restarted" = false ]; then
|
|
echo "WARNING: Service restart may have failed (check manually)" >&2
|
|
exit 1
|
|
fi',
|
|
];
|
|
|
|
$deployCommand = implode(' && ', $deployCommands);
|
|
|
|
Log::info('[EmulatorUpdate] Deploying jar', [
|
|
'source' => $jarPath,
|
|
'destination' => "{$this->jarPath}/{$jarName}",
|
|
]);
|
|
|
|
$deployResult = Process::timeout(60)->run($deployCommand);
|
|
|
|
Log::info('[EmulatorUpdate] Deploy result', [
|
|
'output' => $deployResult->output(),
|
|
'error' => $deployResult->errorOutput(),
|
|
'successful' => $deployResult->successful(),
|
|
]);
|
|
|
|
if (! $deployResult->successful()) {
|
|
$lastError = 'Deploy failed: ' . substr($deployResult->errorOutput(), 0, 300);
|
|
Log::warning('[EmulatorUpdate] Deploy failed', ['error' => $lastError, 'attempt' => $retry]);
|
|
|
|
if ($retry < $maxRetries) {
|
|
continue; // Retry
|
|
}
|
|
|
|
// All retries exhausted
|
|
return [
|
|
'success' => false,
|
|
'error' => 'Build mislukt na ' . ($maxRetries + 1) . ' pogingen. Laatste fout: ' . $lastError,
|
|
];
|
|
}
|
|
|
|
$sourceInfo = $this->checkForSourceUpdates();
|
|
$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);
|
|
|
|
$this->runSqlUpdates();
|
|
|
|
Log::info('[EmulatorUpdate] Source build successful');
|
|
|
|
return [
|
|
'success' => true,
|
|
'version' => $version,
|
|
'jar' => $jarName,
|
|
'built' => true,
|
|
'message' => "✅ Emulator vanaf source gebouwd!\n📦 {$jarName}\n🔄 Service herstart",
|
|
];
|
|
|
|
} catch (\Exception $e) {
|
|
$lastError = $e->getMessage();
|
|
Log::error('[EmulatorUpdate] Source build exception', ['error' => $lastError, 'attempt' => $retry]);
|
|
}
|
|
}
|
|
|
|
// All retries exhausted
|
|
return [
|
|
'success' => false,
|
|
'error' => 'Build mislukt na ' . ($maxRetries + 1) . ' pogingen. Laatste fout: ' . $lastError,
|
|
];
|
|
}
|
|
|
|
private function getBuildCommands(string $sourcePath): string
|
|
{
|
|
// Check for pom.xml in both sourcePath and sourcePath/Emulator
|
|
$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') {
|
|
Log::info('[EmulatorUpdate] Building with Maven (pom.xml found in ' . $pomPath . ')');
|
|
|
|
return "cd {$pomPath} && mvn clean package -DskipTests 2>&1";
|
|
}
|
|
|
|
if ($gradlewCheck->successful() && trim($gradlewCheck->output()) === 'gradlew') {
|
|
Log::info('[EmulatorUpdate] Building with Gradle (gradlew found)');
|
|
|
|
return "cd {$sourcePath} && chmod +x gradlew && ./gradlew clean build -x test 2>&1";
|
|
}
|
|
|
|
if ($gradleCheck->successful() && trim($gradleCheck->output()) === 'gradle') {
|
|
Log::info('[EmulatorUpdate] Building with Gradle (build.gradle found)');
|
|
|
|
return "cd {$sourcePath} && gradle clean build -x test 2>&1";
|
|
}
|
|
|
|
Log::warning('[EmulatorUpdate] No build system found in source directory');
|
|
|
|
return "cd {$sourcePath} && ls -la";
|
|
}
|
|
|
|
private function findBuiltJar(string $sourcePath): ?string
|
|
{
|
|
$patterns = [
|
|
$sourcePath . '/target/*.jar',
|
|
$sourcePath . '/build/libs/*.jar',
|
|
$sourcePath . '/*/*.jar',
|
|
$sourcePath . '/*.jar',
|
|
// Also check in Emulator subdirectory
|
|
$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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Check via command line to bypass open_basedir restriction
|
|
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 extractVersionFromFilename(string $filename): string
|
|
{
|
|
$filename = basename($filename, '.jar');
|
|
|
|
if (preg_match('/[\d]+\.[\d]+\.[\d]+/', $filename, $matches)) {
|
|
return $matches[0];
|
|
}
|
|
|
|
if (preg_match('/v?([\d]+)/', $filename, $matches)) {
|
|
return $matches[1];
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
private function parseGitHubUrl(string $url): void
|
|
{
|
|
$parsed = $this->parseGithubRepoUrl($url);
|
|
$this->githubRepo = $parsed['repo'];
|
|
$this->githubBranch = $parsed['branch'];
|
|
}
|
|
|
|
private function parseSourceRepo(string $url): void
|
|
{
|
|
$parsed = $this->parseGithubRepoUrl($url);
|
|
$this->sourceRepo = $parsed['repo'];
|
|
$this->sourceBranch = $parsed['branch'];
|
|
}
|
|
|
|
private function parseGithubRepoUrl(string $url): array
|
|
{
|
|
if ($url === '' || $url === '0') {
|
|
return ['repo' => null, 'branch' => 'main'];
|
|
}
|
|
|
|
if (preg_match('/github\.com\/([^\/]+)\/([^\/\?#]+)/', $url, $matches)) {
|
|
$repo = $matches[1] . '/' . $matches[2];
|
|
$branch = 'main';
|
|
|
|
if (preg_match('/\/tree\/([^\/]+)/', $url, $branchMatch)) {
|
|
$branch = $branchMatch[1];
|
|
}
|
|
|
|
return ['repo' => $repo, 'branch' => $branch];
|
|
}
|
|
|
|
return ['repo' => null, 'branch' => 'main'];
|
|
}
|
|
|
|
public function getInstalledVersion(): string
|
|
{
|
|
return setting('emulator_version', 'Onbekend');
|
|
}
|
|
|
|
public function getInstalledJar(): ?string
|
|
{
|
|
try {
|
|
$result = Process::timeout(5)->run("ls -1 {$this->jarPath}/*.jar 2>/dev/null | head -1");
|
|
|
|
if ($result->successful()) {
|
|
$jarPath = trim($result->output());
|
|
if ($jarPath !== '' && $jarPath !== '0' && str_contains($jarPath, '.jar')) {
|
|
return basename($jarPath);
|
|
}
|
|
}
|
|
} catch (\Exception) {
|
|
}
|
|
|
|
return setting('emulator_version', 'Onbekend') !== 'Onbekend'
|
|
? 'Emulator v' . setting('emulator_version')
|
|
: null;
|
|
}
|
|
|
|
public function getInstalledJarInfo(): array
|
|
{
|
|
$files = [];
|
|
|
|
try {
|
|
$result = Process::timeout(5)->run("ls -1lh {$this->jarPath}/*.jar 2>/dev/null");
|
|
if ($result->successful()) {
|
|
$lines = array_filter(explode("\n", trim($result->output())));
|
|
foreach ($lines as $line) {
|
|
$line = trim($line);
|
|
if ($line === '' || $line === '0') {
|
|
continue;
|
|
}
|
|
$parts = preg_split('/\s+/', $line);
|
|
$filename = end($parts);
|
|
if (str_contains((string) $filename, '.jar')) {
|
|
$size = $parts[4] ?? '?';
|
|
$files[] = [
|
|
'name' => basename((string) $filename),
|
|
'size' => $size,
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($files === []) {
|
|
$result = Process::timeout(5)->run("ls {$this->jarPath}/*.jar 2>/dev/null");
|
|
if ($result->successful()) {
|
|
$lines = array_filter(explode("\n", trim($result->output())));
|
|
foreach ($lines as $line) {
|
|
$line = trim($line);
|
|
if (str_contains($line, '.jar')) {
|
|
$files[] = [
|
|
'name' => basename($line),
|
|
'size' => '?',
|
|
];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (\Exception) {
|
|
}
|
|
|
|
return $files;
|
|
}
|
|
|
|
public function getLastSqlUpdate(): ?string
|
|
{
|
|
return setting('emulator_last_sql_update');
|
|
}
|
|
|
|
public function getBackupList(): array
|
|
{
|
|
$backups = [];
|
|
|
|
try {
|
|
$backupDir = $this->jarPath . '/backup';
|
|
if (! is_dir($backupDir)) {
|
|
return $backups;
|
|
}
|
|
|
|
$result = Process::timeout(5)->run("ls -1t {$backupDir}/*.jar 2>/dev/null");
|
|
if ($result->successful()) {
|
|
$lines = array_filter(explode("\n", trim($result->output())));
|
|
foreach ($lines as $line) {
|
|
$line = trim($line);
|
|
if ($line === '' || $line === '0' || ! str_contains($line, '.jar')) {
|
|
continue;
|
|
}
|
|
|
|
$filename = basename($line);
|
|
$version = $this->extractVersionFromFilename($filename);
|
|
|
|
preg_match('/(\d{4}-\d{2}-\d{2}_\d{2}-\d{2})/', $line, $dateMatches);
|
|
$date = $dateMatches[1] ?? date('Y-m-d_H-i');
|
|
|
|
$backups[] = [
|
|
'name' => $filename,
|
|
'jar' => $filename,
|
|
'date' => $date,
|
|
'version' => $version,
|
|
];
|
|
}
|
|
}
|
|
} catch (\Exception) {
|
|
}
|
|
|
|
return $backups;
|
|
}
|
|
|
|
public function restoreBackup(string $backupName): array
|
|
{
|
|
try {
|
|
$backupDir = $this->jarPath . '/backup';
|
|
$backupPath = $backupDir . '/' . $backupName;
|
|
$targetPath = $this->jarPath . '/' . $backupName;
|
|
|
|
if (! file_exists($backupPath)) {
|
|
return ['success' => false, 'error' => 'Backup niet gevonden'];
|
|
}
|
|
|
|
if (file_exists($targetPath)) {
|
|
unlink($targetPath);
|
|
}
|
|
|
|
copy($backupPath, $targetPath);
|
|
chmod($targetPath, 0755);
|
|
|
|
return ['success' => true, 'message' => 'Backup hersteld: ' . $backupName];
|
|
} catch (\Exception $e) {
|
|
return ['success' => false, 'error' => $e->getMessage()];
|
|
}
|
|
}
|
|
|
|
public function debugStatus(): array
|
|
{
|
|
return Cache::remember('emulator_debug_status', 120, function () {
|
|
$this->ensureInstalledDateIsSet();
|
|
|
|
$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->getInstalledJarInfo();
|
|
|
|
return [
|
|
'github_url' => $this->githubUrl,
|
|
'github_repo' => $this->githubRepo,
|
|
'github_branch' => $this->githubBranch,
|
|
'source_repo' => $this->sourceRepo,
|
|
'source_branch' => $this->sourceBranch,
|
|
'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,
|
|
'jar_path' => $this->jarPath,
|
|
'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();
|
|
}
|
|
|
|
private function commandFileExists(string $path): bool
|
|
{
|
|
$result = Process::timeout(5)->run('ls ' . $path . ' 2>/dev/null | head -1');
|
|
|
|
return $result->successful() && trim($result->output()) !== '';
|
|
}
|
|
|
|
private function commandDirExists(string $path): bool
|
|
{
|
|
$result = Process::timeout(5)->run('[ -d ' . escapeshellarg($path) . ' ] && echo "1" || echo "0"');
|
|
|
|
return trim($result->output()) === '1';
|
|
}
|
|
|
|
private function persistSourceCommitInfo(string $sha, int $timestamp): void
|
|
{
|
|
$this->settings->set('emulator_source_commit', $sha);
|
|
$this->settings->set('emulator_source_date', (string) $timestamp);
|
|
}
|
|
|
|
private function ensureInstalledDateIsSet(): void
|
|
{
|
|
if ($this->settings->getOrDefault('emulator_jar_installed_date', null) !== null) {
|
|
return;
|
|
}
|
|
|
|
if (! $this->commandFileExists($this->jarPath . '/*.jar')) {
|
|
return;
|
|
}
|
|
|
|
$installedVersion = $this->settings->getOrDefault('emulator_version', null);
|
|
if (! $installedVersion) {
|
|
return;
|
|
}
|
|
|
|
$jarInfo = $this->findLatestJar();
|
|
if ($jarInfo && ($jarInfo['commit_date'] ?? null)) {
|
|
$this->settings->set('emulator_jar_installed_date', (string) $jarInfo['commit_date']);
|
|
|
|
return;
|
|
}
|
|
|
|
$sourceInfo = $this->checkForSourceUpdates();
|
|
if ($sourceInfo && ($sourceInfo['latest_timestamp'] ?? null)) {
|
|
$this->settings->set('emulator_jar_installed_date', (string) $sourceInfo['latest_timestamp']);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
}
|
|
}
|
|
|
|
// Also clear Laravel log
|
|
try {
|
|
$laravelLog = storage_path('logs/laravel.log');
|
|
if (is_file($laravelLog)) {
|
|
file_put_contents($laravelLog, '');
|
|
$cleared[] = 'laravel.log';
|
|
}
|
|
} catch (\Exception) {
|
|
}
|
|
|
|
// Clear tmp files older than 1 day
|
|
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';
|
|
}
|
|
}
|
|
|
|
$emulatorPath = $this->jarPath;
|
|
$pathExists = $this->commandDirExists(dirname((string) $emulatorPath));
|
|
if (! $pathExists) {
|
|
$actions[] = 'Emulator pad bestaat niet - aanmaken...';
|
|
Process::timeout(10)->run('mkdir -p ' . escapeshellarg(dirname((string) $emulatorPath)));
|
|
Process::timeout(10)->run('chown -R www-data:www-data ' . escapeshellarg(dirname((string) $emulatorPath)));
|
|
$actions[] = 'Emulator pad aangemaakt';
|
|
}
|
|
|
|
if (! ($status['emulator_db_connected'] ?? false)) {
|
|
$actions[] = 'Emulator database niet bereikbaar - controleer connectie';
|
|
$errors[] = 'Emulator database connectie probleem';
|
|
}
|
|
|
|
$sqlRepairResult = $this->repairSqlUpdates();
|
|
if (! empty($sqlRepairResult['actions'])) {
|
|
$actions = array_merge($actions, $sqlRepairResult['actions']);
|
|
}
|
|
if (! empty($sqlRepairResult['errors'])) {
|
|
$errors = array_merge($errors, $sqlRepairResult['errors']);
|
|
}
|
|
|
|
$this->ensureGitSafeDirectories();
|
|
|
|
$sudoersFile = '/etc/sudoers.d/atomcms-emulator';
|
|
$checkResult = Process::timeout(5)->run("test -f {$sudoersFile} && echo 'exists' || echo 'missing'");
|
|
if (trim($checkResult->output()) !== 'exists') {
|
|
$actions[] = 'Sudoers configuratie ontbreekt - toevoegen...';
|
|
$this->ensureSudoAccess();
|
|
$actions[] = 'Sudoers configuratie toegevoegd';
|
|
}
|
|
|
|
if ($errors !== []) {
|
|
Log::warning('[EmulatorUpdate] Repair completed with errors', ['errors' => $errors]);
|
|
|
|
return [
|
|
'success' => false,
|
|
'actions' => $actions,
|
|
'errors' => $errors,
|
|
'error' => implode('; ', $errors),
|
|
];
|
|
}
|
|
|
|
Log::info('[EmulatorUpdate] Repair completed successfully', ['actions' => $actions]);
|
|
|
|
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 repairSqlUpdates(): 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->checkForSqlUpdates(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->runSqlUpdates();
|
|
|
|
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('[EmulatorUpdate] SQL repair failed', ['error' => $e->getMessage()]);
|
|
}
|
|
|
|
return [
|
|
'success' => $errors === [],
|
|
'actions' => $actions,
|
|
'errors' => $errors,
|
|
];
|
|
}
|
|
|
|
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;
|
|
$diagnosis['checks']['jar_path'] = $this->jarPath;
|
|
$diagnosis['checks']['source_path'] = $this->emulatorSourcePath;
|
|
$diagnosis['checks']['service_name'] = $this->emulatorService;
|
|
|
|
$sqlDiagnosis = $this->diagnoseSqlUpdates();
|
|
$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->emulatorService;
|
|
}
|
|
|
|
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->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';
|
|
}
|
|
|
|
if (! ($status['update_available'] ?? false) && $this->isConfigured()) {
|
|
$diagnosis['recommendations'][] = 'Emulator is up-to-date';
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
$diagnosis['error'] = $e->getMessage();
|
|
}
|
|
|
|
return $diagnosis;
|
|
}
|
|
|
|
public function diagnoseSqlUpdates(): 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->checkForSqlUpdates(false);
|
|
if (isset($sqlCheck['count'])) {
|
|
$diagnosis['pending_count'] = $sqlCheck['count'];
|
|
}
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
$diagnosis['error'] = $e->getMessage();
|
|
}
|
|
|
|
return $diagnosis;
|
|
}
|
|
}
|