From a7e71bb39c66901032179d27e311ed8054b1036c Mon Sep 17 00:00:00 2001 From: Anton Afanasyeu Date: Fri, 5 Jun 2026 16:31:21 +0200 Subject: [PATCH] sync BE --- .../backend/config/config.example.php | 1 + .../build_console/backend/public/index.php | 1 + .../backend/src/BuildRepository.php | 2 +- .../build_console/backend/src/BuildRunner.php | 40 +++++++++++++++- .../backend/views/build_detail.php | 7 +++ scripts/docker-build-runner.sh | 47 ++++++++++++++----- 6 files changed, 83 insertions(+), 15 deletions(-) diff --git a/examples/build_console/backend/config/config.example.php b/examples/build_console/backend/config/config.example.php index 30f42c0..62c55aa 100644 --- a/examples/build_console/backend/config/config.example.php +++ b/examples/build_console/backend/config/config.example.php @@ -24,6 +24,7 @@ return [ ], 'build' => [ 'docker_image' => 'androidcast-ci:latest', + 'docker_bin' => '/usr/bin/docker', 'ci_version' => '00.01.00.1000', 'repo_root' => '/workspace', 'artifacts_root' => '/workspace/out/builds', diff --git a/examples/build_console/backend/public/index.php b/examples/build_console/backend/public/index.php index 817dde9..03698bb 100644 --- a/examples/build_console/backend/public/index.php +++ b/examples/build_console/backend/public/index.php @@ -74,6 +74,7 @@ if ($view === 'build' && isset($_GET['id'])) { BuildRunner::finalizeFromArtifacts((int) $build['id']); $build = BuildRepository::getById((int) $_GET['id']); } + $health = BuildRepository::healthSummary(); $view = 'build'; $pageTitle = 'Build ' . ($build['build_code'] ?? ''); require __DIR__ . '/../views/layout.php'; diff --git a/examples/build_console/backend/src/BuildRepository.php b/examples/build_console/backend/src/BuildRepository.php index 914c04b..c4c6868 100644 --- a/examples/build_console/backend/src/BuildRepository.php +++ b/examples/build_console/backend/src/BuildRepository.php @@ -116,7 +116,7 @@ SQL); $running = (int) $pdo->query("SELECT COUNT(*) FROM builds WHERE status = 'running'")->fetchColumn(); $failed = (int) $pdo->query("SELECT COUNT(*) FROM builds WHERE status = 'failed' AND created_at > (NOW() - INTERVAL 7 DAY)")->fetchColumn(); $passed = (int) $pdo->query("SELECT COUNT(*) FROM builds WHERE status = 'passed' AND created_at > (NOW() - INTERVAL 7 DAY)")->fetchColumn(); - $dockerOk = trim((string) shell_exec('docker info >/dev/null 2>&1 && echo ok')) === 'ok'; + $dockerOk = BuildRunner::dockerAvailable(); return [ 'docker' => $dockerOk ? 'ok' : 'down', 'running' => $running, diff --git a/examples/build_console/backend/src/BuildRunner.php b/examples/build_console/backend/src/BuildRunner.php index c6ad85e..7a6ca93 100644 --- a/examples/build_console/backend/src/BuildRunner.php +++ b/examples/build_console/backend/src/BuildRunner.php @@ -2,8 +2,37 @@ declare(strict_types=1); final class BuildRunner { + public static function dockerBinary(): string { + $configured = trim((string) cfg('build.docker_bin', '')); + if ($configured !== '' && is_executable($configured)) { + return $configured; + } + foreach (['/usr/bin/docker', '/usr/local/bin/docker'] as $path) { + if (is_executable($path)) { + return $path; + } + } + $which = trim((string) shell_exec('command -v docker 2>/dev/null')); + return $which !== '' ? $which : 'docker'; + } + public static function dockerAvailable(): bool { - return trim((string) shell_exec('docker info >/dev/null 2>&1 && echo ok')) === 'ok'; + $bin = escapeshellarg(self::dockerBinary()); + return trim((string) shell_exec("{$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]; + if (!is_executable($bin)) { + $parts[] = 'bin_not_executable'; + } + if (!is_readable($sock)) { + $parts[] = 'sock_not_readable'; + } + $parts[] = self::dockerAvailable() ? 'info_ok' : 'info_failed'; + return implode(', ', $parts); } public static function generateBuildCode(): string { @@ -91,7 +120,13 @@ YAML; BuildRepository::update($id, ['log_path' => $logPath]); if (!self::dockerAvailable()) { - self::failBuild($id, 'Docker daemon not available', $logPath); + $detail = self::dockerCheckDetail(); + self::failBuild( + $id, + 'Docker daemon not available (' . $detail . '). ' + . 'Ensure docker service is running and PHP-FPM user (nginx) is in group docker, then restart php-fpm.', + $logPath + ); return BuildRepository::getById($id) ?? ['id' => $id, 'build_code' => $buildCode, 'status' => 'failed']; } @@ -106,6 +141,7 @@ YAML; $env = [ 'BUILD_ID=' . $id, 'BUILD_LOG_FILE=' . escapeshellarg($logPath), + 'BUILD_LOG_STDOUT_ONLY=1', 'BUILD_WORK_DIR=' . escapeshellarg($repo), 'BUILD_OUT_DIR=' . escapeshellarg($dir), 'ANDROIDCAST_CI_VERSION=' . escapeshellarg((string) cfg('build.ci_version', '00.01.00.1000')), diff --git a/examples/build_console/backend/views/build_detail.php b/examples/build_console/backend/views/build_detail.php index fdc7f34..775b5cb 100644 --- a/examples/build_console/backend/views/build_detail.php +++ b/examples/build_console/backend/views/build_detail.php @@ -14,6 +14,13 @@ if ($pipelineYaml === '') { ·

+ +

Ecosystem health reports Docker: ok now. The log below is from this failed run — use Re-run (do not only refresh).

+

Actions

diff --git a/scripts/docker-build-runner.sh b/scripts/docker-build-runner.sh index eb97f0f..f816f8e 100755 --- a/scripts/docker-build-runner.sh +++ b/scripts/docker-build-runner.sh @@ -5,6 +5,7 @@ # ./scripts/docker-build-runner.sh --build-id 42 --workdir /path/to/repo # # Env: see ci-build-and-publish-ota.sh +# When invoked from PHP (BUILD_LOG_STDOUT_ONLY=1), log via stdout only — parent redirects to build.log. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" @@ -14,27 +15,49 @@ BUILD_ID="${BUILD_ID:-local}" LOG_FILE="${BUILD_LOG_FILE:-${ROOT}/out/builds/${BUILD_ID}/build.log}" WORK_DIR="${BUILD_WORK_DIR:-${ROOT}}" OUT_DIR="${BUILD_OUT_DIR:-${WORK_DIR}/out/builds/${BUILD_ID}}" +LOG_STDOUT_ONLY="${BUILD_LOG_STDOUT_ONLY:-0}" mkdir -p "$(dirname "$LOG_FILE")" "$OUT_DIR" -echo "[runner] image=${IMAGE} ci_version=${CI_VERSION} build_id=${BUILD_ID}" | tee -a "$LOG_FILE" -echo "[runner] workdir=${WORK_DIR} out=${OUT_DIR}" | tee -a "$LOG_FILE" +log() { + if [[ "$LOG_STDOUT_ONLY" == "1" ]]; then + echo "$@" + else + echo "$@" | tee -a "$LOG_FILE" + fi +} + +run_logged() { + if [[ "$LOG_STDOUT_ONLY" == "1" ]]; then + "$@" + else + "$@" 2>&1 | tee -a "$LOG_FILE" + fi +} + +# Git 2.35+ refuses repos owned by another uid (PHP-FPM nginx vs deploy user). +git_safe() { + git -c safe.directory="${WORK_DIR}" "$@" +} + +log "[runner] image=${IMAGE} ci_version=${CI_VERSION} build_id=${BUILD_ID}" +log "[runner] workdir=${WORK_DIR} out=${OUT_DIR}" if [[ -n "${GIT_REF:-}" ]] && [[ -d "${WORK_DIR}/.git" ]]; then - echo "[runner] syncing git ref=${GIT_REF}" | tee -a "$LOG_FILE" - git -C "${WORK_DIR}" fetch origin --prune 2>&1 | tee -a "$LOG_FILE" || true - git -C "${WORK_DIR}" checkout "${GIT_REF}" 2>&1 | tee -a "$LOG_FILE" || true - git -C "${WORK_DIR}" pull --rebase origin "${GIT_REF}" 2>&1 | tee -a "$LOG_FILE" || true - git -C "${WORK_DIR}" rev-parse HEAD 2>&1 | tee -a "$LOG_FILE" || true + log "[runner] syncing git ref=${GIT_REF}" + run_logged git_safe -C "${WORK_DIR}" fetch origin --prune || true + run_logged git_safe -C "${WORK_DIR}" checkout "${GIT_REF}" + run_logged git_safe -C "${WORK_DIR}" pull --rebase origin "${GIT_REF}" || true + run_logged git_safe -C "${WORK_DIR}" rev-parse HEAD fi -docker build \ +run_logged docker build \ --build-arg ANDROIDCAST_CI_VERSION="${CI_VERSION}" \ -t "$IMAGE" \ -f "${ROOT}/Dockerfile" \ - "${ROOT}" 2>&1 | tee -a "$LOG_FILE" + "${ROOT}" -docker run --rm \ +run_logged docker run --rm \ -v "${WORK_DIR}:/workspace" \ -v "${ROOT}/out:/workspace/out" \ -w /workspace \ @@ -50,6 +73,6 @@ docker run --rm \ -e GIT_SHA="${GIT_SHA:-}" \ -e RELEASE_NOTES="${RELEASE_NOTES:-}" \ "$IMAGE" \ - ./scripts/ci-build-and-publish-ota.sh 2>&1 | tee -a "$LOG_FILE" + ./scripts/ci-build-and-publish-ota.sh -echo "[runner] finished build_id=${BUILD_ID}" | tee -a "$LOG_FILE" +log "[runner] finished build_id=${BUILD_ID}"