1
0
mirror of git://f0xx.org/ac/ac-ms-build synced 2026-08-12 18:13:16 +03:00

Builder: docker home env + Gitea CI webhook status.

Export HOME/DOCKER_CONFIG for nobody PHP-FPM, improve docker health
checks, and report build status to Gitea when enabled.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Anton Afanasyeu
2026-08-07 22:15:26 +02:00
parent 8c59662245
commit 1fa8da68d5
5 changed files with 220 additions and 4 deletions

View File

@@ -16,15 +16,31 @@ final class BuildRunner {
return $which !== '' ? $which : 'docker';
}
/** Writable HOME for docker CLI when PHP-FPM runs as nobody (default HOME=/). */
public static function dockerHomeDir(): string {
$home = trim((string) cfg('build.docker_home', '/var/www/ac/broadcast'));
return $home !== '' ? rtrim($home, '/') : '/var/www/ac/broadcast';
}
public static function dockerEnvPrefix(): string {
$home = self::dockerHomeDir();
$config = $home . '/.docker';
if (!is_dir($config)) {
@mkdir($config, 0775, true);
}
return 'HOME=' . escapeshellarg($home) . ' DOCKER_CONFIG=' . escapeshellarg($config) . ' ';
}
public static function dockerAvailable(): bool {
$bin = escapeshellarg(self::dockerBinary());
return trim((string) shell_exec("{$bin} info >/dev/null 2>&1 && echo ok")) === 'ok';
$env = self::dockerEnvPrefix();
return trim((string) shell_exec("{$env}{$bin} info >/dev/null 2>&1 && echo ok")) === 'ok';
}
public static function dockerCheckDetail(): string {
$bin = self::dockerBinary();
$sock = '/var/run/docker.sock';
$parts = ['bin=' . $bin];
$parts = ['bin=' . $bin, 'home=' . self::dockerHomeDir()];
if (!is_executable($bin)) {
$parts[] = 'bin_not_executable';
}
@@ -143,7 +159,11 @@ YAML;
));
$mobileSub = trim((string) cfg('build.mobile_subdir', 'ac-mobile-android'));
$scriptsDir = trim((string) cfg('build.scripts_dir', dirname((string) cfg('build.runner_script', ''))));
$dockerHome = self::dockerHomeDir();
$env = [
'HOME=' . escapeshellarg($dockerHome),
'DOCKER_CONFIG=' . escapeshellarg($dockerHome . '/.docker'),
'BUILDER_HOME=' . escapeshellarg($dockerHome),
'BUILD_ID=' . $id,
'BUILD_LOG_FILE=' . escapeshellarg($logPath),
'BUILD_LOG_STDOUT_ONLY=1',
@@ -189,7 +209,65 @@ YAML;
'started_at' => gmdate('Y-m-d H:i:s'),
]);
return BuildRepository::getById($id) ?? ['id' => $id, 'build_code' => $buildCode];
$build = BuildRepository::getById($id) ?? ['id' => $id, 'build_code' => $buildCode];
if (GiteaCiStatus::isEnabled() && !empty($params['git_sha'])) {
GiteaCiStatus::reportForBuild($build, 'pending', 'Build running');
}
return $build;
}
/** Cancel queued/running builds for the same branch (superseded by a newer push). */
public static function cancelSupersededForBranch(string $branch): void {
$branch = trim($branch);
if ($branch === '') {
return;
}
foreach (BuildRepository::listActiveForBranch($branch) as $id) {
$build = BuildRepository::getById($id);
if (!$build) {
continue;
}
self::stopBuild($id);
if (GiteaCiStatus::isEnabled()) {
GiteaCiStatus::reportForBuild($build, 'error', 'Superseded by newer commit');
}
}
}
/** @param array<string, mixed> $payload Gitea push webhook JSON */
public static function enqueueFromWebhook(array $payload): array {
$ref = (string) ($payload['ref'] ?? '');
$after = (string) ($payload['after'] ?? '');
if ($after === '' || str_starts_with($after, '0000000')) {
throw new InvalidArgumentException('empty_or_delete_push');
}
$branch = str_starts_with($ref, 'refs/heads/') ? substr($ref, 11) : $ref;
if ($branch === '') {
throw new InvalidArgumentException('missing_branch');
}
$repo = is_array($payload['repository'] ?? null) ? $payload['repository'] : [];
$fullName = (string) ($repo['full_name'] ?? cfg('build.gitea_default_repo', 'ac/ac-mobile-android'));
[$owner, $repoName] = array_pad(explode('/', $fullName, 2), 2, 'ac-mobile-android');
self::cancelSupersededForBranch($branch);
$params = [
'git_ref' => $branch,
'git_sha' => $after,
'branch' => $branch,
'trigger_source' => 'ci',
'gitea_owner' => $owner !== '' ? $owner : 'ac',
'gitea_repo' => $repoName,
'run_tests' => true,
'run_native' => true,
'run_apk' => true,
'auto_ota' => false,
'notify_on_fail' => true,
];
$build = self::enqueue($params, null);
if (GiteaCiStatus::isEnabled()) {
GiteaCiStatus::reportForBuild($build, 'pending', 'Build queued');
}
return $build;
}
public static function failBuild(int $id, string $message, ?string $logPath = null): void {
@@ -205,6 +283,9 @@ YAML;
]);
$build = BuildRepository::getById($id);
if ($build) {
if (GiteaCiStatus::isEnabled()) {
GiteaCiStatus::reportForBuild($build, 'failure', substr($safe, 0, 120));
}
BuildNotifier::onFailure($id, $build, $safe);
}
}
@@ -544,8 +625,15 @@ YAML;
);
}
BuildRepository::update($buildId, $update);
$build = BuildRepository::getById($buildId);
if ($build && GiteaCiStatus::isEnabled()) {
if ($status === 'passed') {
GiteaCiStatus::reportForBuild($build, 'success', 'Build passed');
} else {
GiteaCiStatus::reportForBuild($build, 'failure', 'Build failed');
}
}
if ($status === 'failed') {
$build = BuildRepository::getById($buildId);
if ($build) {
BuildNotifier::onFailure($buildId, $build, (string) ($update['error_message'] ?? 'Build failed'));
}