loadConfiguration(); } public function buildFromSource(bool $force = false): array { $repo = $this->sourceRepo ?: $this->githubRepo; $branch = $this->sourceBranch ?: $this->githubBranch ?: 'main'; if (! $repo) { return ['success' => false, 'error' => 'Geen source repo geconfigureerd']; } $sourcePath = $this->emulatorSourcePath; $serviceName = $this->emulatorService; Log::info('[EmulatorBuild] Starting source build', [ 'repo' => $repo, 'branch' => $branch, 'path' => $sourcePath, ]); $this->ensureJavaInstalled(); Process::timeout(10)->run('mkdir -p ' . escapeshellarg(dirname((string) $sourcePath))); Process::timeout(10)->run('mkdir -p ' . escapeshellarg((string) $this->jarPath)); Process::timeout(10)->run('chown -R www-data:www-data ' . escapeshellarg(dirname((string) $sourcePath))); $maxRetries = 2; $lastError = ''; for ($retry = 0; $retry <= $maxRetries; $retry++) { Process::timeout(10)->run('chown -R www-data:www-data ' . escapeshellarg(dirname((string) $sourcePath)) . ' 2>/dev/null || true'); Process::timeout(10)->run('chown -R www-data:www-data ' . escapeshellarg((string) $sourcePath) . ' 2>/dev/null || true'); if ($retry > 0) { Log::info('[EmulatorBuild] Retry attempt', ['attempt' => $retry]); Process::timeout(30)->run('rm -rf ' . escapeshellarg((string) $sourcePath)); } try { $existsCheck = Process::timeout(5)->run('[ -d ' . escapeshellarg((string) $sourcePath) . " ] && echo 'exists'"); $sourceExists = $existsCheck->successful() && trim($existsCheck->output()) === 'exists'; $gitCheck = Process::timeout(5)->run('[ -d ' . escapeshellarg((string) $sourcePath) . "/.git ] && echo 'git'"); $isGitRepo = $gitCheck->successful() && trim($gitCheck->output()) === 'git'; if ($sourceExists && $isGitRepo) { Log::info('[EmulatorBuild] Pulling latest changes'); $commands = [ 'cd ' . escapeshellarg((string) $sourcePath) . ' && git fetch origin', 'cd ' . escapeshellarg((string) $sourcePath) . ' && git checkout ' . escapeshellarg($branch), 'cd ' . escapeshellarg((string) $sourcePath) . ' && git pull origin ' . escapeshellarg($branch), ]; } else { Log::info('[EmulatorBuild] Cloning repository'); $commands = [ 'sudo rm -rf ' . escapeshellarg((string) $sourcePath) . ' 2>/dev/null || rm -rf ' . escapeshellarg((string) $sourcePath), 'mkdir -p ' . escapeshellarg(dirname((string) $sourcePath)), 'git clone --branch ' . escapeshellarg($branch) . ' --depth 1 https://github.com/' . escapeshellarg($repo) . '.git ' . escapeshellarg((string) $sourcePath), 'chown -R www-data:www-data ' . escapeshellarg((string) $sourcePath), ]; } $command = implode(' && ', $commands); $result = Process::timeout(300)->run($command); if ($result->failed()) { $lastError = 'Git clone/pull failed: ' . substr($result->errorOutput(), 0, 300); Log::warning('[EmulatorBuild] Git operation failed', ['error' => $lastError, 'attempt' => $retry]); continue; } $buildCommands = $this->getBuildCommands($sourcePath); Log::info('[EmulatorBuild] Running build', ['command' => $buildCommands]); $buildResult = Process::timeout(600)->run('cd ' . escapeshellarg((string) $sourcePath) . ' && ' . $buildCommands); $hasSignalError = str_contains($buildResult->errorOutput(), 'signal') || str_contains($buildResult->output(), 'signal'); if ($hasSignalError) { Log::warning('[EmulatorBuild] Build process received signal, checking if JAR was built anyway'); } $jarPath = $this->findBuiltJar($sourcePath); if ($jarPath) { Log::info('[EmulatorBuild] JAR found despite build status', ['jar' => $jarPath]); } elseif ($buildResult->failed() && ! $hasSignalError) { $lastError = 'Build failed: ' . substr($buildResult->errorOutput(), 0, 500); Log::warning('[EmulatorBuild] Build failed', ['error' => $lastError, 'attempt' => $retry]); if ($retry < $maxRetries) { $cleanCommands = [ 'cd ' . escapeshellarg((string) $sourcePath) . ' && mvn clean 2>/dev/null || ./gradlew clean 2>/dev/null || true', ]; Process::timeout(60)->run(implode(' && ', $cleanCommands)); continue; } continue; } if (! $jarPath) { $lastError = 'Build succeeded but JAR not found. Check build output.'; Log::warning('[EmulatorBuild] JAR not found', ['attempt' => $retry]); continue; } return $this->deployJar($jarPath, $serviceName); } catch (\Exception $e) { $lastError = $e->getMessage(); Log::error('[EmulatorBuild] Source build exception', ['error' => $lastError, 'attempt' => $retry]); } } return [ 'success' => false, 'error' => 'Build mislukt na ' . ($maxRetries + 1) . ' pogingen. Laatste fout: ' . $lastError, ]; } public function getBuildCommands(string $sourcePath): string { $pomPath = $sourcePath; $pomCheck = Process::timeout(5)->run("[ -f {$pomPath}/pom.xml ] && echo 'pom'"); if (! $pomCheck->successful() || trim($pomCheck->output()) !== 'pom') { $pomPath = $sourcePath . '/Emulator'; $pomCheck = Process::timeout(5)->run("[ -f {$pomPath}/pom.xml ] && echo 'pom'"); } $gradlewPath = $sourcePath . '/gradlew'; $gradlewCheck = Process::timeout(5)->run("[ -f {$gradlewPath} ] && echo 'gradlew'"); $gradlePath = $sourcePath . '/build.gradle'; $gradleCheck = Process::timeout(5)->run("[ -f {$gradlePath} ] && echo 'gradle'"); if ($pomCheck->successful() && trim($pomCheck->output()) === 'pom') { return "cd {$pomPath} && mvn clean package -DskipTests 2>&1"; } if ($gradlewCheck->successful() && trim($gradlewCheck->output()) === 'gradlew') { return "cd {$sourcePath} && chmod +x gradlew && ./gradlew clean build -x test 2>&1"; } if ($gradleCheck->successful() && trim($gradleCheck->output()) === 'gradle') { return "cd {$sourcePath} && gradle clean build -x test 2>&1"; } return "cd {$sourcePath} && ls -la"; } public function findBuiltJar(string $sourcePath): ?string { $patterns = [ $sourcePath . '/target/*.jar', $sourcePath . '/build/libs/*.jar', $sourcePath . '/*/*.jar', $sourcePath . '/*.jar', $sourcePath . '/Emulator/target/*.jar', $sourcePath . '/Emulator/build/libs/*.jar', $sourcePath . '/Emulator/*/*.jar', ]; foreach ($patterns as $pattern) { $result = Process::timeout(10)->run('ls -t ' . $pattern . ' 2>/dev/null | head -1'); if ($result->successful()) { $jarPath = trim($result->output()); if ($jarPath !== '' && $jarPath !== '0' && str_contains($jarPath, '.jar')) { return $jarPath; } } } return null; } private function deployJar(string $jarPath, string $serviceName): array { $jarName = basename($jarPath); $version = $this->extractVersionFromFilename($jarName); if ($version === '' || $version === '0') { $version = date('Y.m.d'); } $deployCommands = [ 'mkdir -p ' . escapeshellarg((string) $this->jarPath), 'chown -R www-data:www-data ' . escapeshellarg((string) $this->jarPath), 'if ls ' . escapeshellarg((string) $this->jarPath) . '/*.jar 1>/dev/null 2>&1; then mkdir -p ' . escapeshellarg((string) $this->jarPath) . '/backup && mv ' . escapeshellarg((string) $this->jarPath) . '/*.jar ' . escapeshellarg((string) $this->jarPath) . '/backup/; fi', 'cp -f ' . escapeshellarg($jarPath) . ' ' . escapeshellarg((string) $this->jarPath) . '/' . escapeshellarg($jarName), 'chown www-data:www-data ' . escapeshellarg((string) $this->jarPath) . '/' . escapeshellarg($jarName), 'chmod 755 ' . escapeshellarg((string) $this->jarPath) . '/' . escapeshellarg($jarName), 'ls -la ' . escapeshellarg((string) $this->jarPath) . '/', 'systemctl restart ' . escapeshellarg((string) $serviceName) . ' 2>&1 || service ' . escapeshellarg((string) $serviceName) . ' restart 2>&1 || true', ]; $deployCommand = implode(' && ', $deployCommands); Log::info('[EmulatorBuild] Deploying jar', [ 'source' => $jarPath, 'destination' => "{$this->jarPath}/{$jarName}", ]); $deployResult = Process::timeout(60)->run($deployCommand); if (! $deployResult->successful()) { return [ 'success' => false, 'error' => 'Deploy failed: ' . substr($deployResult->errorOutput(), 0, 300), ]; } $sourceService = new EmulatorSourceService; $sourceInfo = $sourceService->checkForUpdates(); $installedDate = $sourceInfo['latest_timestamp'] ?? time(); $this->settings->set('emulator_version', $version); $this->settings->set('emulator_jar_installed_date', (string) $installedDate); $this->settings->set('emulator_source_commit', $sourceInfo['latest_sha'] ?? 'unknown'); $this->settings->set('emulator_source_date', (string) $installedDate); $currentBranch = $this->sourceBranch ?: $this->githubBranch ?: 'main'; $this->settings->set('emulator_installed_branch', $currentBranch); $sqlService = new EmulatorSqlService; $sqlService->runUpdates(); Log::info('[EmulatorBuild] Source build successful'); return [ 'success' => true, 'version' => $version, 'jar' => $jarName, 'built' => true, 'message' => "āœ… Emulator vanaf source gebouwd!\nšŸ“¦ {$jarName}\nšŸ”„ Service herstart", ]; } private function ensureJavaInstalled(): void { $javaCheck = Process::timeout(5)->run('java -version 2>&1'); if (! $javaCheck->successful()) { Log::warning('[EmulatorBuild] Java not found, attempting to install'); Process::timeout(180)->run('apt-get update && apt-get install -y default-jdk 2>&1'); } $mavenCheck = Process::timeout(5)->run('which mvn'); if (! $mavenCheck->successful()) { Log::warning('[EmulatorBuild] Maven not found, attempting to install'); Process::timeout(180)->run('apt-get install -y maven 2>&1'); } } }