Files
Atomcms-edit/app/Services/Emulator/EmulatorBuildService.php
T
root f5666c104d refactor: integrate diagnostics into Commandocentrum and split EmulatorUpdateService
- Add DiagnosticRunner integration to Commandocentrum for system health display
- Refactor EmulatorUpdateService from 2524 lines to 395 lines (facade pattern)
- Extract EmulatorStatusService, EmulatorJarService, EmulatorSourceService
- Extract EmulatorBuildService, EmulatorSqlService, EmulatorBackupService
- Add shared EmulatorConfiguration trait for dependency injection
- Preserve backward compatibility on all public methods
2026-05-19 20:20:43 +02:00

274 lines
12 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Emulator;
use App\Services\Emulator\EmulatorSqlService;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Process;
class EmulatorBuildService
{
use EmulatorConfiguration;
public function __construct()
{
$this->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');
}
}
}