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 === '') {
· = h($build['error_message']) ?>
+
+
+
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}"