From 36892aef771b22e24dd7af98b6e57dc0f3bcbe6a Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Sat, 17 Oct 2026 23:07:12 +0300 Subject: [PATCH 1/8] docs: define autonomous review workflow --- AGENTS.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 6d5c131..9d87b08 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,9 @@ Python, SQLite и OpenSearch. Production пока не развернут. [деплой](docs/operations/DEPLOYMENT.md), [backup](docs/operations/BACKUP.md), [rollback](docs/operations/ROLLBACK.md), [восстановление](docs/operations/RECOVERY.md). - Работай в feature/fix/refactor-ветке. `main` защищена; изменения вноси через - PR, не сливай его автоматически. Сохраняй незакоммиченные изменения. + PR. DEV не одобряет и не сливает собственный PR. Условный merge выполняет + только REVIEWER по правилам раздела «Автономный workflow PR» ниже. Сохраняй + незакоммиченные изменения. - SSH alias — `akyldash_vps`; используй только read-only диагностику. Не давай `codex` общий sudo или доступ к Docker socket. Root-действия требует владелец. Для Gitea используй `gitea-api`, не читай файлы с учётными данными. @@ -34,3 +36,36 @@ Python, SQLite и OpenSearch. Production пока не развернут. для нового хоста. - После каждого изменения инфраструктуры обновляй `docs/operations/CURRENT_STATE.md` по фактическим проверкам. + +## Автономный workflow PR + +- Центральный оркестратор: thread `01a144a5-8b95-77c3-b311-9a9afd062ca8`, + host local. DEV (этот агент) выполняет только явно назначенную пользователем + задачу в `admin/akyldash`; REVIEWER: thread + `01a144f9-5b51-7923-8398-61d4a6a98306`, host + `remote-ssh-codex-managed:b61555d7-8d1a-4cfa-988b-c8da4791d426`. +- Обмен между чатами идёт через центральный relay: участники завершают каждый + этап структурированным отчётом с полями `task`, `repo`, `branch`, `pr`, + `head_sha`, `stage`, `checks`, `findings`, `next_action`. Не настраивай + отдельные webhook, daemon или транспорт. +- Веди только одну пользовательскую задачу и один PR одновременно. Не + подхватывай старые PR самостоятельно; дождись конкретного назначения. +- DEV исследует, изменяет, проверяет, коммитит и отправляет изменения, создаёт + или обновляет PR к `main`. После каждого push REVIEWER независимо проверяет + весь актуальный diff и повторно учитывает предыдущие findings. Не запускай + замену этому REVIEWER и не одобряй свой PR. +- После успешного первоначального review DEV вносит значимые изменения в + существующий changelog, коммитит и отправляет их в тот же PR. Затем REVIEWER + повторно проверяет полный итоговый diff. Для документационной задачи не + придумывай изменения кода; соблюдай правила версии, только если они явно + применимы. +- Слияние зарегистрированной задачи разрешено только REVIEWER и только после + формального `APPROVED` review текущего `head_sha`, успешных required checks и + прохождения всех действующих gates/branch protection. Approval в комментарии + не является разрешением на merge. При изменении head или base требуется + актуализировать review и проверки. Запрещено обходить защиту ветки, обязательные + проверки или требуемое review. +- Все Gitea API-вызовы выполняй только через `gitea-api` с личным окружением; + Git используй с обычным credential helper. Не читай credentials из файлов + репозитория. Запускай Git- и API-команды отдельно, без объединения с другими + командами. -- 2.54.0 From 69c62466ca8c3e4e030d8fa164e73848b4e0c40d Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Sat, 17 Oct 2026 23:13:07 +0300 Subject: [PATCH 2/8] docs: complete autonomous review gates --- AGENTS.md | 76 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9d87b08..a1e3fff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,32 +39,56 @@ Python, SQLite и OpenSearch. Production пока не развернут. ## Автономный workflow PR -- Центральный оркестратор: thread `01a144a5-8b95-77c3-b311-9a9afd062ca8`, - host local. DEV (этот агент) выполняет только явно назначенную пользователем - задачу в `admin/akyldash`; REVIEWER: thread - `01a144f9-5b51-7923-8398-61d4a6a98306`, host - `remote-ssh-codex-managed:b61555d7-8d1a-4cfa-988b-c8da4791d426`. -- Обмен между чатами идёт через центральный relay: участники завершают каждый - этап структурированным отчётом с полями `task`, `repo`, `branch`, `pr`, - `head_sha`, `stage`, `checks`, `findings`, `next_action`. Не настраивай - отдельные webhook, daemon или транспорт. -- Веди только одну пользовательскую задачу и один PR одновременно. Не - подхватывай старые PR самостоятельно; дождись конкретного назначения. -- DEV исследует, изменяет, проверяет, коммитит и отправляет изменения, создаёт - или обновляет PR к `main`. После каждого push REVIEWER независимо проверяет - весь актуальный diff и повторно учитывает предыдущие findings. Не запускай - замену этому REVIEWER и не одобряй свой PR. -- После успешного первоначального review DEV вносит значимые изменения в - существующий changelog, коммитит и отправляет их в тот же PR. Затем REVIEWER - повторно проверяет полный итоговый diff. Для документационной задачи не - придумывай изменения кода; соблюдай правила версии, только если они явно - применимы. -- Слияние зарегистрированной задачи разрешено только REVIEWER и только после - формального `APPROVED` review текущего `head_sha`, успешных required checks и - прохождения всех действующих gates/branch protection. Approval в комментарии - не является разрешением на merge. При изменении head или base требуется - актуализировать review и проверки. Запрещено обходить защиту ветки, обязательные - проверки или требуемое review. +- Маршруты ниже указаны из контекста оркестратора/root: оркестратор — thread + `01a144a5-8b95-77c3-b311-9a9afd062ca8`, host `local`; DEV — thread + `01a138f1-3b34-7c91-a9ed-4b43c3c85f1f`, host + `remote-ssh-codex-managed:712b8d4e-24e1-4dd5-92af-85d1fbd26449`, Gitea + `codex` (ID 6); REVIEWER — thread `01a144f9-5b51-7923-8398-61d4a6a98306`, + host `remote-ssh-codex-managed:b61555d7-8d1a-4cfa-988b-c8da4791d426`, Gitea + `agent` (ID 7). Если этот же чат отображает REVIEWER как `local` внутри его + собственного app-server, это self-label и не является маршрутом из контекста + оркестратора. +- DEV принимает только явно зарегистрированную оркестратором человеческую + задачу с полями `task`, `repo`, `branch`, `pr`, `head_sha`, `phase`. Репозиторий + должен быть `admin/akyldash`; работай только в назначенной ветке и PR. Веди + одну задачу/PR за раз и не подхватывай старые PR самостоятельно. +- Допустимые фазы: `initial_review` — реализация и устранение review findings; + `final_review_and_merge` — только после первоначального успешного review, + changelog-коммита и отдельного назначения оркестратора. Не переходи к следующей + фазе без назначения. Если задача приостановлена, останови любые действия; + после resume перепроверь назначение, PR, head/base, checks и review с нуля. +- Повторная доставка одного назначения идемпотентна по ключу + `task`+`pr`+`head_sha`+`stage`: не дублируй commit, push, review-запрос или + merge; сообщи оркестратору о уже выполненном результате. Новый head или stage + образует новое назначение и требует повторной проверки. +- Межчатовое чтение недоступно: оркестратор через central relay сам читает + результаты и выдаёт назначения. Завершай каждый этап отчётом с полями `task`, + `repo`, `branch`, `pr`, `head_sha`, `stage`, `checks`, `findings`, + `next_action`. Не настраивай отдельные webhook, daemon или транспорт. +- DEV исследует, изменяет, проверяет, коммитит и отправляет изменения; REVIEWER + не меняет код и независимо проверяет полный diff от base после каждого push, + включая предыдущие findings. DEV не одобряет и не сливает свой PR. +- Только после первоначального успешного формального review DEV добавляет + значимые изменения в существующий changelog, коммитит и отправляет их в тот же + PR. Затем REVIEWER полностью проверяет итоговый diff. Для docs-only задачи не + придумывай изменения кода; правила версии применяй только если они явно + относятся к этой документационной правке. +- Слияние может выполнить только REVIEWER для зарегистрированной человеческой + задачи, в фазе `final_review_and_merge`, после явного назначения оркестратора. + Непосредственно перед ним REVIEWER подтверждает `admin/akyldash`, PR, base + `main`, ожидаемую ветку, автора PR `codex` (ID 6), неизменность base и head, + причём head должен совпадать с зарегистрированным `head_sha`, включая + `head_commit_id` для merge-запроса. Требуются все успешные required CI/test + checks и gates, отсутствие pending/failed/unknown checks, открытых + findings и активных `REQUEST_CHANGES`, плюс актуальный формальный `APPROVED` + от REVIEWER для точного текущего commit с параметрами `commit_id == head_sha`, + `stale == false`, `dismissed == false`. При изменении head или base review и + проверки выполняются заново. Комментарий с approval не является формальным + review. + Используй только обычный server-side merge с `head_commit_id`; не используй + `force_merge` и не обходи branch protection. Если любой существенный gate, + статус или защита неизвестны либо не проверяются — не сливай PR и сообщи + оркестратору, что именно осталось неизвестным. - Все Gitea API-вызовы выполняй только через `gitea-api` с личным окружением; Git используй с обычным credential helper. Не читай credentials из файлов репозитория. Запускай Git- и API-команды отдельно, без объединения с другими -- 2.54.0 From 3a89dc2196530139c6b471242ca3c2e1a04a8b03 Mon Sep 17 00:00:00 2001 From: codex Date: Sun, 18 Oct 2026 08:17:57 +0300 Subject: [PATCH 3/8] ci: build production images with isolated Kaniko --- .gitea/actions/caddy-validate/Dockerfile | 1 + .gitea/actions/caddy-validate/action.yml | 11 +++++ .gitea/actions/kaniko-build/Dockerfile | 1 + .gitea/actions/kaniko-build/action.yml | 18 ++++++++ .gitea/workflows/ci.yaml | 22 +++++++--- docs/operations/CI_RUNNER.md | 42 ++++++++++++------ docs/operations/CURRENT_STATE.md | 55 ++++++++++++------------ infra/runner/test-ci-container-builds.sh | 29 +++++++++++++ 8 files changed, 132 insertions(+), 47 deletions(-) create mode 100644 .gitea/actions/caddy-validate/Dockerfile create mode 100644 .gitea/actions/caddy-validate/action.yml create mode 100644 .gitea/actions/kaniko-build/Dockerfile create mode 100644 .gitea/actions/kaniko-build/action.yml create mode 100644 infra/runner/test-ci-container-builds.sh diff --git a/.gitea/actions/caddy-validate/Dockerfile b/.gitea/actions/caddy-validate/Dockerfile new file mode 100644 index 0000000..8026e7e --- /dev/null +++ b/.gitea/actions/caddy-validate/Dockerfile @@ -0,0 +1 @@ +FROM caddy:2.11.6-alpine@sha256:881bbc60f9986d5ab8e7cfd6cf7e4ef3c9c0439fef2429d035d065577882f028 diff --git a/.gitea/actions/caddy-validate/action.yml b/.gitea/actions/caddy-validate/action.yml new file mode 100644 index 0000000..af58b79 --- /dev/null +++ b/.gitea/actions/caddy-validate/action.yml @@ -0,0 +1,11 @@ +name: Validate Caddyfile +description: Run the pinned Caddy validator against the checked-out Caddyfile +runs: + using: docker + image: Dockerfile + args: + - validate + - --config + - /github/workspace/deploy/production/Caddyfile + - --adapter + - caddyfile diff --git a/.gitea/actions/kaniko-build/Dockerfile b/.gitea/actions/kaniko-build/Dockerfile new file mode 100644 index 0000000..5e732d4 --- /dev/null +++ b/.gitea/actions/kaniko-build/Dockerfile @@ -0,0 +1 @@ +FROM ghcr.io/osscontainertools/kaniko:v1.28.5-debug@sha256:c73ca67a3b494bce79a8fb6090c4f73c18997ab272d80bffc460a62343aeb412 diff --git a/.gitea/actions/kaniko-build/action.yml b/.gitea/actions/kaniko-build/action.yml new file mode 100644 index 0000000..b85a09a --- /dev/null +++ b/.gitea/actions/kaniko-build/action.yml @@ -0,0 +1,18 @@ +name: Build image with Kaniko in a rootless container +description: Validate a Dockerfile by building it without publishing the image or cache +inputs: + dockerfile: + description: Dockerfile path relative to the checked-out repository + required: true + destination: + description: Image reference required by Kaniko; the image is not pushed + required: true +runs: + using: docker + image: Dockerfile + args: + - --context=dir:///github/workspace + - --dockerfile=/github/workspace/${{ inputs.dockerfile }} + - --destination=${{ inputs.destination }} + - --no-push + - --no-push-cache diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index c188117..0e716b5 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -7,7 +7,7 @@ on: jobs: tests-and-images: - runs-on: ubuntu-latest + runs-on: akyldash-ci steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -34,6 +34,8 @@ jobs: bash infra/runner/test-runner-config.sh bash infra/runner/test-install-runner-idempotency.sh bash infra/runner/test-install-runner-guard.sh + - name: Check CI container operation isolation + run: bash infra/runner/test-ci-container-builds.sh - name: Check infrastructure shell syntax run: | bash -n infra/bootstrap/bootstrap-ubuntu-24.04.sh infra/bootstrap/host-guard.sh \ @@ -43,7 +45,8 @@ jobs: infra/bootstrap/test-audit-host.sh infra/bootstrap/test-verify-helper.sh \ infra/runner/install-runner.sh infra/runner/install-runner-lib.sh \ infra/runner/act-runner-healthcheck.sh infra/runner/test-runner-config.sh \ - infra/runner/test-install-runner-idempotency.sh infra/runner/test-install-runner-guard.sh + infra/runner/test-install-runner-idempotency.sh infra/runner/test-install-runner-guard.sh \ + infra/runner/test-ci-container-builds.sh python3 -c "import ast,pathlib; ast.parse(pathlib.Path('infra/runner/secure-runner-image.py').read_text())" - name: Run rollback helper tests run: python3 -m unittest discover -s infra/deploy -p 'test_*.py' -v @@ -52,8 +55,15 @@ jobs: secret_file=$(mktemp) trap 'rm -f "$secret_file"' EXIT REVIEW_SECRET_FILE="$secret_file" docker compose --env-file deploy/production/.env.example -f deploy/production/compose.yaml config --quiet - docker run --rm --entrypoint caddy -v "$PWD/deploy/production/Caddyfile:/etc/caddy/Caddyfile:ro" caddy:2.11.6-alpine@sha256:881bbc60f9986d5ab8e7cfd6cf7e4ef3c9c0439fef2429d035d065577882f028 validate --config /etc/caddy/Caddyfile --adapter caddyfile + - name: Validate Caddyfile in isolated container action + uses: ./.gitea/actions/caddy-validate - name: Build production images - run: | - docker build -f deploy/production/Dockerfile.backend -t akyldash-backend:ci . - docker build -f deploy/production/Dockerfile.opensearch -t akyldash-opensearch:ci . + uses: ./.gitea/actions/kaniko-build + with: + dockerfile: deploy/production/Dockerfile.backend + destination: akyldash-backend:ci + - name: Build OpenSearch production image + uses: ./.gitea/actions/kaniko-build + with: + dockerfile: deploy/production/Dockerfile.opensearch + destination: akyldash-opensearch:ci diff --git a/docs/operations/CI_RUNNER.md b/docs/operations/CI_RUNNER.md index 38890e4..392e483 100644 --- a/docs/operations/CI_RUNNER.md +++ b/docs/operations/CI_RUNNER.md @@ -42,10 +42,23 @@ systemd timer checks the runner service and the rootless Docker API each minute; on failure it restarts the fixed runner service. No UFW changes or public metrics ports are needed. Runner cache is disabled. -The label `akyldash-ci` is used by `runner-smoke.yaml` on pull requests, pushes -to `main`, and manual dispatch. Existing CI image-build work remains on its current -`ubuntu-latest` target until it has been tested separately against the -isolated runner. +The CI workflow and `runner-smoke.yaml` target the repository runner label +`akyldash-ci`. CI keeps the backend, Telegram, backup/restore, deployment, +bootstrap, runner, Compose, and Caddy checks. Caddy validation runs in a pinned +container action. Both production Dockerfiles are built in a separate pinned +Kaniko container action using the runner's rootless container engine; the job +container receives no Docker socket. Kaniko uses `--no-push` and +`--no-push-cache`, so CI validates the full backend and OpenSearch builds but +does not publish images or export build output. The two builds run sequentially +within one job; peak memory and disk use still require a runner canary. No registry credentials, +production secrets, elevated container options, or arbitrary host mounts are configured. + +The Kaniko action uses the `linux/amd64` v1.28.5-debug manifest +`sha256:c73ca67a3b494bce79a8fb6090c4f73c18997ab272d80bffc460a62343aeb412`, +listed in the [official GitHub Packages entry](https://github.com/osscontainertools/kaniko/pkgs/container/kaniko/1271366984?tag=v1.28.5-debug). +The pinned Caddy image digest remains the same as the former `docker run` +validation. act_runner starts these container actions; they do not expose its +rootless Docker socket to workflow steps. Production deployment is not automated. ## Installation and registration @@ -136,9 +149,10 @@ that token; do not put it in command-line arguments or environment variables. present in the job container. 3. Confirm the run reports the expected runner, succeeds, and the runner service and health timer remain active. -4. Only after the repository owner reviews this boundary should CI jobs be moved - to `akyldash-ci`. Do not add a production deploy workflow or secrets to this - runner until a separate restricted deploy mechanism is reviewed. +4. Inspect the `CI` workflow run and confirm Caddy plus both Kaniko actions pass + on `akyldash-ci`. The Kaniko jobs are intentionally build-only: publishing + images requires a separately reviewed registry workflow and credentials. + Do not add production deploy workflows or secrets to this runner. ## Upgrade @@ -170,9 +184,11 @@ automatic recovery step. ## Status -The root installer and registration are incomplete. The owner reports that the -15 GiB ext4 loop-backed mount is active on `/dev/loop0` after manually running -`systemctl daemon-reload`, `enable`, and `start` when the first installer run -stopped at mount activation. This PR has not changed the VPS and does not claim -that remaining runner components are installed, a repository token is -available, the runner is Online, or a workflow has run. +The owner reports that the repository-scoped runner is registered, appears +Idle in Gitea, and its rootless Docker daemon works. Gitea reported the smoke +workflow successful on PR head `69c62466` (run 62). The main CI job on that head +remained queued as run 61 because it requested `ubuntu-latest` instead of +`akyldash-ci`. This PR changes the target label and replaces job-container +Docker operations with digest-pinned Caddy and Kaniko container actions. The +new actions and their resource fit have not yet been verified on the VPS runner; +no VPS changes are part of this PR. diff --git a/docs/operations/CURRENT_STATE.md b/docs/operations/CURRENT_STATE.md index e65ef8e..a27d4bd 100644 --- a/docs/operations/CURRENT_STATE.md +++ b/docs/operations/CURRENT_STATE.md @@ -1,6 +1,6 @@ # Infrastructure current state -Last audit: 2026-10-12. Source: read-only SSH session as `codex`, owner-reported +Last host audit: 2026-10-12. Gitea Actions status checked 2026-10-18. Source: read-only SSH session as `codex`, owner-reported UFW state, local repository inspection, and Gitea API. Root-only Docker state, UFW rule details, and protected file metadata remain unverified. @@ -49,9 +49,11 @@ UFW rule details, and protected file metadata remain unverified. PRs enabled. `main` is protected and requires one approval; the current API principal cannot push or merge directly to `main`. - The repository runner-list API returns `403` because this API principal is - not the repository owner. The Actions task API currently reports no workflow - runs; this does not establish runner availability. Runner inventory and - package publishing permissions remain unverified. + not the repository owner. On PR head `69c62466`, Gitea reported runner smoke + success (run 62) while the main CI job remained queued (run 61) because its + workflow label did not match the registered runner. The owner reports the + repository runner is registered and Idle and rootless Docker is working. + Runner inventory and package publishing permissions remain unverified. - No public application listener is observed on TCP `80`/`443`. Docker container/volume state still requires the root audit. Repository Compose describes Caddy `2.11.6` pinned by digest, Python Search API `0.11.3`, SQLite @@ -80,31 +82,19 @@ UFW rule details, and protected file metadata remain unverified. `status` helper, guarded rollback helper, and installer with exact sudoers command rules. None is installed on the VPS. CI contains no production deployment job; starting/stopping services remains owner-run. -- A repository-scoped official `act_runner` `0.2.13` installation is prepared - in `infra/runner/`: rootless Docker under `akyldash-runner`, 15 GiB ext4 - loop-backed image/workspace storage, capacity 1, per-job limits (2 CPU, +- A repository-scoped official `act_runner` `0.2.13` installation is + configured in `infra/runner/`: rootless Docker under `akyldash-runner`, 15 GiB + ext4 loop-backed image/workspace storage, capacity 1, per-job limits (2 CPU, 3 GiB RAM, 512 processes), account slice limits (2.5 CPU, 4 GiB RAM, 2048 - tasks), rotated file logs, and a systemd health timer. Jobs use the - `akyldash-ci` label and do not receive the rootless or production Docker - socket. The owner reports a partial runner installation: the 15 GiB ext4 - loop-backed store is active on `/dev/loop0` after manually enabling and - starting its mount unit when the installer stopped. Remaining components, - repository registration, and Online state are unverified; this PR makes no - VPS changes. The reviewed installer locks the runner home to root ownership - (`0711`) so CI cannot replace the image or mountpoint, while writable config - and runtime state remain in runner-owned subdirectories. It reuses legacy - `0755` config directories, creates the image through a pinned file descriptor, - rejects path replacement, and requires mount options `nosuid,nodev`. - The reported installation blocker is that rootless setup receives this - protected directory as `HOME`; the installer assigns writable runner-owned - HOME/XDG paths, persists them in the `docker.service` user-unit drop-in, and - restarts an active daemon and checks the running process environment. Local - runner guards pass; - the change has not been run on the VPS. -- Gitea repository runner inventory could not be read by the current API - principal (403). The owner has not supplied runner state, and no runner - Online status or runner-hosted workflow is claimed. The manual - `runner-smoke.yaml` workflow is prepared but has not run. + tasks), rotated file logs, and a systemd health timer. Jobs target the + `akyldash-ci` label and do not receive either Docker socket. The owner reports + that runner registration and rootless Docker are working. Gitea reported the + smoke workflow successful for PR head `69c62466` (run 62). The CI job on that + head was queued as run 61 because the workflow still requested `ubuntu-latest`; + this PR updates the label and moves container-dependent validation into + digest-pinned actions. No VPS changes are made by this PR; the updated actions + and their fit within the configured 3 GiB / 2 CPU per-job limits remain to be + verified by the new CI run. - An isolated local Compose smoke test built backend/OpenSearch images, reached healthy status for OpenSearch, backend, and Caddy, and returned HTTP 200 for `/review` through Caddy. Its containers, networks, and volumes were @@ -150,3 +140,12 @@ UFW rule details, and protected file metadata remain unverified. remains manual and has no CI job. 5. Test backup/restore and rollback only with synthetic data in an isolated environment before any production deployment. + +## CI workflow change in this PR + +The branch targets `akyldash-ci` and uses digest-pinned isolated actions for +Caddy validation and the two production Dockerfile builds. Kaniko image +publishing and cache publishing are disabled. This repository change does not +modify the VPS or grant workflow containers a Docker socket. The Gitea CI run +must confirm action execution and resource fit before this becomes validated +runner state. diff --git a/infra/runner/test-ci-container-builds.sh b/infra/runner/test-ci-container-builds.sh new file mode 100644 index 0000000..b4499bd --- /dev/null +++ b/infra/runner/test-ci-container-builds.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +workflow=.gitea/workflows/ci.yaml +kaniko_action=.gitea/actions/kaniko-build/action.yml + +grep -Fq 'runs-on: akyldash-ci' "$workflow" +grep -Fq 'uses: ./.gitea/actions/caddy-validate' "$workflow" +[[ $(grep -Fc 'uses: ./.gitea/actions/kaniko-build' "$workflow") -eq 2 ]] +grep -Fq 'dockerfile: deploy/production/Dockerfile.backend' "$workflow" +grep -Fq 'dockerfile: deploy/production/Dockerfile.opensearch' "$workflow" +grep -Fq 'docker compose --env-file deploy/production/.env.example -f deploy/production/compose.yaml config --quiet' "$workflow" +grep -Fq ' - validate' .gitea/actions/caddy-validate/action.yml +grep -Fq '/github/workspace/deploy/production/Caddyfile' .gitea/actions/caddy-validate/action.yml +grep -Fq ' - caddyfile' .gitea/actions/caddy-validate/action.yml +if grep -Eq 'docker (build|run)([[:space:]]|$)' "$workflow"; then + echo 'workflow must not invoke docker build/run in a job container' >&2 + exit 1 +fi +grep -Fq -- '--no-push' "$kaniko_action" +grep -Fq -- '--no-push-cache' "$kaniko_action" +grep -Fq 'FROM ghcr.io/osscontainertools/kaniko:v1.28.5-debug@sha256:c73ca67a3b494bce79a8fb6090c4f73c18997ab272d80bffc460a62343aeb412' .gitea/actions/kaniko-build/Dockerfile +grep -Fq 'FROM caddy:2.11.6-alpine@sha256:881bbc60f9986d5ab8e7cfd6cf7e4ef3c9c0439fef2429d035d065577882f028' .gitea/actions/caddy-validate/Dockerfile +if grep -REn 'docker.sock|privileged:|container.options:|^[[:space:]]*volumes:' "$workflow" .gitea/actions/kaniko-build .gitea/actions/caddy-validate; then + echo 'workflow and container actions must not request sockets, privileges, or additional host mounts' >&2 + exit 1 +fi + +echo 'PASS CI container operations use pinned isolated actions without socket or publish access' -- 2.54.0 From 436806a834735c14874accebea48aa6274511393 Mon Sep 17 00:00:00 2001 From: codex Date: Sun, 18 Oct 2026 08:32:48 +0300 Subject: [PATCH 4/8] ci: exercise root guards under unprivileged uid --- .gitea/workflows/ci.yaml | 5 +- docs/operations/CI_RUNNER.md | 21 +++++--- docs/operations/CURRENT_STATE.md | 51 +++++++++++--------- infra/bootstrap/test-audit-host.sh | 7 ++- infra/bootstrap/test-root-guard.sh | 33 +++++++++++++ infra/deploy/test-production-config-guard.sh | 7 ++- infra/runner/test-ci-container-builds.sh | 17 ++++++- infra/runner/test-install-runner-guard.sh | 7 ++- infra/test/run-as-unprivileged.sh | 13 +++++ 9 files changed, 121 insertions(+), 40 deletions(-) create mode 100644 infra/bootstrap/test-root-guard.sh create mode 100644 infra/test/run-as-unprivileged.sh diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 0e716b5..6d0e6fb 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -19,6 +19,8 @@ jobs: run: python3 -m unittest discover -s tools/telegram-bot -p 'test_*.py' -v - name: Run infrastructure backup tests run: python3 -m unittest discover -s infra/backup -p 'test_*.py' -v + - name: Check root guard UID decisions + run: bash infra/bootstrap/test-root-guard.sh - name: Check recovery host guard run: bash infra/bootstrap/test-host-guard.sh - name: Check verifier helper argument guard @@ -42,7 +44,8 @@ jobs: infra/bootstrap/verify.sh infra/bootstrap/verify-helper.sh infra/bootstrap/audit-host.sh \ infra/deploy/install-helper.sh infra/deploy/compose-helper.sh infra/deploy/test-compose-helper.sh \ infra/deploy/prepare-production-config.sh infra/deploy/test-production-config-guard.sh \ - infra/bootstrap/test-audit-host.sh infra/bootstrap/test-verify-helper.sh \ + infra/bootstrap/test-audit-host.sh infra/bootstrap/test-root-guard.sh \ + infra/bootstrap/test-verify-helper.sh infra/test/run-as-unprivileged.sh \ infra/runner/install-runner.sh infra/runner/install-runner-lib.sh \ infra/runner/act-runner-healthcheck.sh infra/runner/test-runner-config.sh \ infra/runner/test-install-runner-idempotency.sh infra/runner/test-install-runner-guard.sh \ diff --git a/docs/operations/CI_RUNNER.md b/docs/operations/CI_RUNNER.md index 392e483..75923b2 100644 --- a/docs/operations/CI_RUNNER.md +++ b/docs/operations/CI_RUNNER.md @@ -185,10 +185,17 @@ automatic recovery step. ## Status The owner reports that the repository-scoped runner is registered, appears -Idle in Gitea, and its rootless Docker daemon works. Gitea reported the smoke -workflow successful on PR head `69c62466` (run 62). The main CI job on that head -remained queued as run 61 because it requested `ubuntu-latest` instead of -`akyldash-ci`. This PR changes the target label and replaces job-container -Docker operations with digest-pinned Caddy and Kaniko container actions. The -new actions and their resource fit have not yet been verified on the VPS runner; -no VPS changes are part of this PR. +Idle in Gitea, and its rootless Docker daemon works; the report date was not +recorded. Gitea status checked 2026-10-18 showed smoke run 62 successful and CI +run 61 pending on head `69c62466`. That workflow requested `ubuntu-latest`, +while the smoke workflow used `akyldash-ci`; the label difference is a +plausible explanation for the pending status, but the runner inventory API +returned 403 and the scheduler's reason was not independently confirmed. + +On head `3a89dc21`, Gitea reported CI run 63 failed at `Check host audit root +guard` and smoke run 64 succeeded. The job had not reached Caddy or Kaniko, so +their execution and resource fit remain unverified. This PR changes the root +guard tests to use `setpriv` for their negative path when a job starts as UID 0. +A separate test evaluates the extracted host-audit guard with simulated UID 0 +and UID 65534; the real audit, config setup, and runner installer are never +executed as root by those tests. No VPS changes are part of this PR. diff --git a/docs/operations/CURRENT_STATE.md b/docs/operations/CURRENT_STATE.md index a27d4bd..f2aed84 100644 --- a/docs/operations/CURRENT_STATE.md +++ b/docs/operations/CURRENT_STATE.md @@ -49,11 +49,13 @@ UFW rule details, and protected file metadata remain unverified. PRs enabled. `main` is protected and requires one approval; the current API principal cannot push or merge directly to `main`. - The repository runner-list API returns `403` because this API principal is - not the repository owner. On PR head `69c62466`, Gitea reported runner smoke - success (run 62) while the main CI job remained queued (run 61) because its - workflow label did not match the registered runner. The owner reports the - repository runner is registered and Idle and rootless Docker is working. - Runner inventory and package publishing permissions remain unverified. + not the repository owner. Gitea status checked 2026-10-18 showed smoke run 62 + successful and CI run 61 pending on PR head `69c62466`. That workflow used + `ubuntu-latest`, while the smoke workflow used `akyldash-ci`; this label + difference is a plausible explanation, not a confirmed scheduler diagnosis. + The owner reports that the runner is registered and Idle and rootless Docker + works (report date not recorded). Runner inventory and package publishing + permissions remain unverified. - No public application listener is observed on TCP `80`/`443`. Docker container/volume state still requires the root audit. Repository Compose describes Caddy `2.11.6` pinned by digest, Python Search API `0.11.3`, SQLite @@ -88,13 +90,14 @@ UFW rule details, and protected file metadata remain unverified. 3 GiB RAM, 512 processes), account slice limits (2.5 CPU, 4 GiB RAM, 2048 tasks), rotated file logs, and a systemd health timer. Jobs target the `akyldash-ci` label and do not receive either Docker socket. The owner reports - that runner registration and rootless Docker are working. Gitea reported the - smoke workflow successful for PR head `69c62466` (run 62). The CI job on that - head was queued as run 61 because the workflow still requested `ubuntu-latest`; - this PR updates the label and moves container-dependent validation into - digest-pinned actions. No VPS changes are made by this PR; the updated actions - and their fit within the configured 3 GiB / 2 CPU per-job limits remain to be - verified by the new CI run. + that runner registration and rootless Docker work (report date not recorded). + Gitea status checked 2026-10-18 showed smoke run 62 successful and CI run 61 + pending on PR head `69c62466`. The workflow/label difference may explain that + pending status, but the scheduler reason was not independently verified. On + head `3a89dc21`, CI run 63 failed at the host audit root guard and smoke run 64 + succeeded; Caddy and Kaniko steps were skipped. No VPS changes are made by + this PR. The new actions and fit within the configured 3 GiB / 2 CPU per-job + limits remain unverified. - An isolated local Compose smoke test built backend/OpenSearch images, reached healthy status for OpenSearch, backend, and Caddy, and returned HTTP 200 for `/review` through Caddy. Its containers, networks, and volumes were @@ -112,8 +115,10 @@ UFW rule details, and protected file metadata remain unverified. and restore of real production data remain unverified because production is not deployed. - Cloudflare edge response was checked; origin DNS target, origin certificate, - Full (strict), runner registration/Online state, registry publishing, and production - deployment have not been verified. + Full (strict), registry publishing, successful Caddy/Kaniko action execution, + runner resource fit, and production deployment have not been verified. The + owner-reported Idle state has not been independently checked through runner + inventory because the API returned 403. - This audit made no host changes. No package or service was installed by this audit; the owner performed the initial bootstrap beforehand. @@ -131,13 +136,10 @@ UFW rule details, and protected file metadata remain unverified. 3. Run `sudo bash /opt/akyldash/current/infra/deploy/install-helper.sh` after reviewing the checkout; it grants only fixed verification, Compose `config`/`status`, and guarded rollback commands. -4. After PR review and root audit, run - `sudo bash /opt/akyldash/current/infra/runner/install-runner.sh`. In the - repository Settings → Actions → Runners create a repository-level runner; - enter the token only at the interactive registration prompt as documented - in [CI_RUNNER.md](CI_RUNNER.md). Then enable the runner and health timer, - confirm **Online**, and dispatch the smoke workflow. Production deployment - remains manual and has no CI job. +4. After PR review, inspect the latest CI and smoke runs on `akyldash-ci`. + Confirm both pinned container actions complete within the configured per-job + CPU, memory, and disk limits. Production deployment remains manual and has + no CI job. 5. Test backup/restore and rollback only with synthetic data in an isolated environment before any production deployment. @@ -146,6 +148,7 @@ UFW rule details, and protected file metadata remain unverified. The branch targets `akyldash-ci` and uses digest-pinned isolated actions for Caddy validation and the two production Dockerfile builds. Kaniko image publishing and cache publishing are disabled. This repository change does not -modify the VPS or grant workflow containers a Docker socket. The Gitea CI run -must confirm action execution and resource fit before this becomes validated -runner state. +modify the VPS or grant workflow containers a Docker socket. Gitea CI run 63 failed at the host audit root-guard test before reaching +Caddy or Kaniko actions. The smoke run succeeded. This PR updates the root-guard +tests; a later successful run must verify the container actions and resource +fit before they are considered validated runner behavior. diff --git a/infra/bootstrap/test-audit-host.sh b/infra/bootstrap/test-audit-host.sh index 63a4adf..f053383 100644 --- a/infra/bootstrap/test-audit-host.sh +++ b/infra/bootstrap/test-audit-host.sh @@ -1,10 +1,13 @@ #!/usr/bin/env bash set -euo pipefail +script_dir=$(dirname "${BASH_SOURCE[0]}") +source "$script_dir/../test/run-as-unprivileged.sh" + output=$(mktemp) trap 'rm -f "$output"' EXIT -if bash "$(dirname "$0")/audit-host.sh" >"$output" 2>&1; then - echo 'Host audit unexpectedly succeeded without root.' >&2 +if run_as_unprivileged bash "$script_dir/audit-host.sh" >"$output" 2>&1; then + echo 'Host audit unexpectedly succeeded as an unprivileged UID.' >&2 exit 1 fi grep -qx 'Run this read-only audit as root.' "$output" diff --git a/infra/bootstrap/test-root-guard.sh b/infra/bootstrap/test-root-guard.sh new file mode 100644 index 0000000..fcc2e2f --- /dev/null +++ b/infra/bootstrap/test-root-guard.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir=$(dirname "${BASH_SOURCE[0]}") +guard=$(mktemp) +output=$(mktemp) +trap 'rm -f "$guard" "$output"' EXIT + +awk ' + /^if \[\[ \$EUID -ne 0 \]\]; then$/ { capture = 1 } + capture { print } + capture && /^fi$/ { found = 1; exit } + END { if (!found) exit 1 } +' "$script_dir/audit-host.sh" | sed 's/\$EUID/\$TEST_EUID/g' >"$guard" || { + echo 'Could not extract the host audit root guard.' >&2 + exit 1 +} +if ! grep -Fq 'if [[ $TEST_EUID -ne 0 ]]; then' "$guard"; then + echo 'Extracted guard does not use the simulated effective UID.' >&2 + exit 1 +fi +if TEST_EUID=0 bash "$guard" >"$output" 2>&1; then + [[ ! -s $output ]] || { cat "$output" >&2; exit 1; } +else + echo 'Host audit root guard rejected simulated UID 0.' >&2 + exit 1 +fi +if TEST_EUID=65534 bash "$guard" >"$output" 2>&1; then + echo 'Host audit root guard accepted simulated non-root UID.' >&2 + exit 1 +fi +grep -qx 'Run this read-only audit as root.' "$output" +echo 'PASS host audit root guard accepts UID 0 and rejects non-root UIDs' diff --git a/infra/deploy/test-production-config-guard.sh b/infra/deploy/test-production-config-guard.sh index cf7ed3e..1ab96bb 100644 --- a/infra/deploy/test-production-config-guard.sh +++ b/infra/deploy/test-production-config-guard.sh @@ -1,10 +1,13 @@ #!/usr/bin/env bash set -euo pipefail +script_dir=$(dirname "${BASH_SOURCE[0]}") +source "$script_dir/../test/run-as-unprivileged.sh" + output=$(mktemp) trap 'rm -f "$output"' EXIT -if bash "$(dirname "$0")/prepare-production-config.sh" >"$output" 2>&1; then - echo 'Production config setup unexpectedly succeeded without root.' >&2 +if run_as_unprivileged bash "$script_dir/prepare-production-config.sh" >"$output" 2>&1; then + echo 'Production config setup unexpectedly succeeded as an unprivileged UID.' >&2 exit 1 fi grep -qx 'Run this configuration step as root.' "$output" diff --git a/infra/runner/test-ci-container-builds.sh b/infra/runner/test-ci-container-builds.sh index b4499bd..1ed053e 100644 --- a/infra/runner/test-ci-container-builds.sh +++ b/infra/runner/test-ci-container-builds.sh @@ -17,8 +17,21 @@ if grep -Eq 'docker (build|run)([[:space:]]|$)' "$workflow"; then echo 'workflow must not invoke docker build/run in a job container' >&2 exit 1 fi -grep -Fq -- '--no-push' "$kaniko_action" -grep -Fq -- '--no-push-cache' "$kaniko_action" +check_kaniko_publish_flags() { + grep -Fxq -- ' - --no-push' "$1" && + grep -Fxq -- ' - --no-push-cache' "$1" +} +if ! check_kaniko_publish_flags "$kaniko_action"; then + echo 'Kaniko must disable image and cache publishing with separate arguments.' >&2 + exit 1 +fi +without_no_push=$(mktemp) +trap 'rm -f "$without_no_push"' EXIT +sed '/^ - --no-push$/d' "$kaniko_action" >"$without_no_push" +if check_kaniko_publish_flags "$without_no_push"; then + echo 'CI isolation test accepted --no-push-cache without standalone --no-push.' >&2 + exit 1 +fi grep -Fq 'FROM ghcr.io/osscontainertools/kaniko:v1.28.5-debug@sha256:c73ca67a3b494bce79a8fb6090c4f73c18997ab272d80bffc460a62343aeb412' .gitea/actions/kaniko-build/Dockerfile grep -Fq 'FROM caddy:2.11.6-alpine@sha256:881bbc60f9986d5ab8e7cfd6cf7e4ef3c9c0439fef2429d035d065577882f028' .gitea/actions/caddy-validate/Dockerfile if grep -REn 'docker.sock|privileged:|container.options:|^[[:space:]]*volumes:' "$workflow" .gitea/actions/kaniko-build .gitea/actions/caddy-validate; then diff --git a/infra/runner/test-install-runner-guard.sh b/infra/runner/test-install-runner-guard.sh index 4e52819..aa268d4 100644 --- a/infra/runner/test-install-runner-guard.sh +++ b/infra/runner/test-install-runner-guard.sh @@ -1,10 +1,13 @@ #!/usr/bin/env bash set -Eeuo pipefail +script_dir=$(dirname "${BASH_SOURCE[0]}") +source "$script_dir/../test/run-as-unprivileged.sh" + output=$(mktemp) trap 'rm -f "$output"' EXIT -if bash "$(dirname "$0")/install-runner.sh" >"$output" 2>&1; then - echo 'Runner installer unexpectedly succeeded without root.' >&2 +if run_as_unprivileged bash "$script_dir/install-runner.sh" >"$output" 2>&1; then + echo 'Runner installer unexpectedly succeeded as an unprivileged UID.' >&2 exit 1 fi grep -qx 'ERROR: run this script as root' "$output" diff --git a/infra/test/run-as-unprivileged.sh b/infra/test/run-as-unprivileged.sh new file mode 100644 index 0000000..47c5618 --- /dev/null +++ b/infra/test/run-as-unprivileged.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +run_as_unprivileged() { + if [[ $EUID -eq 0 ]]; then + if ! command -v setpriv >/dev/null 2>&1; then + echo 'setpriv is required to run guard tests as an unprivileged UID.' >&2 + return 127 + fi + setpriv --reuid=65534 --regid=65534 --clear-groups -- "$@" + else + "$@" + fi +} -- 2.54.0 From d96fc4d36b09c76ae379792044a1703637f57c5a Mon Sep 17 00:00:00 2001 From: codex Date: Sun, 18 Oct 2026 08:37:44 +0300 Subject: [PATCH 5/8] ci: set container action entrypoints --- .gitea/actions/caddy-validate/action.yml | 1 + .gitea/actions/kaniko-build/action.yml | 1 + docs/operations/CI_RUNNER.md | 23 +++++++++-------- docs/operations/CURRENT_STATE.md | 17 +++++++------ infra/runner/test-ci-container-builds.sh | 32 +++++++++++++++++------- 5 files changed, 48 insertions(+), 26 deletions(-) diff --git a/.gitea/actions/caddy-validate/action.yml b/.gitea/actions/caddy-validate/action.yml index af58b79..107661f 100644 --- a/.gitea/actions/caddy-validate/action.yml +++ b/.gitea/actions/caddy-validate/action.yml @@ -3,6 +3,7 @@ description: Run the pinned Caddy validator against the checked-out Caddyfile runs: using: docker image: Dockerfile + entrypoint: /usr/bin/caddy args: - validate - --config diff --git a/.gitea/actions/kaniko-build/action.yml b/.gitea/actions/kaniko-build/action.yml index b85a09a..c79057e 100644 --- a/.gitea/actions/kaniko-build/action.yml +++ b/.gitea/actions/kaniko-build/action.yml @@ -10,6 +10,7 @@ inputs: runs: using: docker image: Dockerfile + entrypoint: /kaniko/executor args: - --context=dir:///github/workspace - --dockerfile=/github/workspace/${{ inputs.dockerfile }} diff --git a/docs/operations/CI_RUNNER.md b/docs/operations/CI_RUNNER.md index 75923b2..1f8747c 100644 --- a/docs/operations/CI_RUNNER.md +++ b/docs/operations/CI_RUNNER.md @@ -45,9 +45,10 @@ metrics ports are needed. Runner cache is disabled. The CI workflow and `runner-smoke.yaml` target the repository runner label `akyldash-ci`. CI keeps the backend, Telegram, backup/restore, deployment, bootstrap, runner, Compose, and Caddy checks. Caddy validation runs in a pinned -container action. Both production Dockerfiles are built in a separate pinned -Kaniko container action using the runner's rootless container engine; the job -container receives no Docker socket. Kaniko uses `--no-push` and +container action with explicit `/usr/bin/caddy` entrypoint. Both production +Dockerfiles are built in a separate pinned Kaniko container action with explicit +`/kaniko/executor` entrypoint using the runner's rootless container engine; the +job container receives no Docker socket. Kaniko uses `--no-push` and `--no-push-cache`, so CI validates the full backend and OpenSearch builds but does not publish images or export build output. The two builds run sequentially within one job; peak memory and disk use still require a runner canary. No registry credentials, @@ -192,10 +193,12 @@ while the smoke workflow used `akyldash-ci`; the label difference is a plausible explanation for the pending status, but the runner inventory API returned 403 and the scheduler's reason was not independently confirmed. -On head `3a89dc21`, Gitea reported CI run 63 failed at `Check host audit root -guard` and smoke run 64 succeeded. The job had not reached Caddy or Kaniko, so -their execution and resource fit remain unverified. This PR changes the root -guard tests to use `setpriv` for their negative path when a job starts as UID 0. -A separate test evaluates the extracted host-audit guard with simulated UID 0 -and UID 65534; the real audit, config setup, and runner installer are never -executed as root by those tests. No VPS changes are part of this PR. +On head `3a89dc21`, CI run 63 failed at the host audit root guard and smoke run +64 succeeded. On head `436806a8`, CI run 65 failed in the Caddy container action +with `exec: "validate": executable file not found`; Kaniko was skipped and smoke +run 66 succeeded. This PR sets explicit Caddy and Kaniko entrypoints. Successful +execution and resource fit still require a new runner canary. Root-guard tests +use `setpriv` for negative cases when jobs start as UID 0 and separately test +the host-audit guard with simulated UID 0 and UID 65534; the real host audit, +production config setup, and runner installer are never executed as root by +those tests. No VPS changes are part of this PR. diff --git a/docs/operations/CURRENT_STATE.md b/docs/operations/CURRENT_STATE.md index f2aed84..4e2b2c5 100644 --- a/docs/operations/CURRENT_STATE.md +++ b/docs/operations/CURRENT_STATE.md @@ -95,9 +95,11 @@ UFW rule details, and protected file metadata remain unverified. pending on PR head `69c62466`. The workflow/label difference may explain that pending status, but the scheduler reason was not independently verified. On head `3a89dc21`, CI run 63 failed at the host audit root guard and smoke run 64 - succeeded; Caddy and Kaniko steps were skipped. No VPS changes are made by - this PR. The new actions and fit within the configured 3 GiB / 2 CPU per-job - limits remain unverified. + succeeded. On head `436806a8`, CI run 65 failed in the Caddy container action + with `exec: "validate": executable file not found`; Kaniko was skipped and + smoke run 66 succeeded. This PR pins explicit action entrypoints. No VPS + changes are made; successful image builds and fit within the configured 3 GiB + / 2 CPU per-job limits remain unverified. - An isolated local Compose smoke test built backend/OpenSearch images, reached healthy status for OpenSearch, backend, and Caddy, and returned HTTP 200 for `/review` through Caddy. Its containers, networks, and volumes were @@ -148,7 +150,8 @@ UFW rule details, and protected file metadata remain unverified. The branch targets `akyldash-ci` and uses digest-pinned isolated actions for Caddy validation and the two production Dockerfile builds. Kaniko image publishing and cache publishing are disabled. This repository change does not -modify the VPS or grant workflow containers a Docker socket. Gitea CI run 63 failed at the host audit root-guard test before reaching -Caddy or Kaniko actions. The smoke run succeeded. This PR updates the root-guard -tests; a later successful run must verify the container actions and resource -fit before they are considered validated runner behavior. +modify the VPS or grant workflow containers a Docker socket. CI run 65 failed +in the Caddy action because the runner tried to execute `validate` directly; +Kaniko was skipped. This PR sets explicit action entrypoints. A successful run +must verify Caddy, both image builds, and resource fit before these actions are +considered validated runner behavior. diff --git a/infra/runner/test-ci-container-builds.sh b/infra/runner/test-ci-container-builds.sh index 1ed053e..e388c87 100644 --- a/infra/runner/test-ci-container-builds.sh +++ b/infra/runner/test-ci-container-builds.sh @@ -10,25 +10,39 @@ grep -Fq 'uses: ./.gitea/actions/caddy-validate' "$workflow" grep -Fq 'dockerfile: deploy/production/Dockerfile.backend' "$workflow" grep -Fq 'dockerfile: deploy/production/Dockerfile.opensearch' "$workflow" grep -Fq 'docker compose --env-file deploy/production/.env.example -f deploy/production/compose.yaml config --quiet' "$workflow" -grep -Fq ' - validate' .gitea/actions/caddy-validate/action.yml -grep -Fq '/github/workspace/deploy/production/Caddyfile' .gitea/actions/caddy-validate/action.yml -grep -Fq ' - caddyfile' .gitea/actions/caddy-validate/action.yml +caddy_action=.gitea/actions/caddy-validate/action.yml +grep -Fxq ' entrypoint: /usr/bin/caddy' "$caddy_action" +grep -Fxq ' - validate' "$caddy_action" +grep -Fxq ' - --config' "$caddy_action" +grep -Fxq ' - /github/workspace/deploy/production/Caddyfile' "$caddy_action" +grep -Fxq ' - --adapter' "$caddy_action" +grep -Fxq ' - caddyfile' "$caddy_action" if grep -Eq 'docker (build|run)([[:space:]]|$)' "$workflow"; then echo 'workflow must not invoke docker build/run in a job container' >&2 exit 1 fi -check_kaniko_publish_flags() { - grep -Fxq -- ' - --no-push' "$1" && +check_kaniko_action() { + grep -Fxq ' entrypoint: /kaniko/executor' "$1" && + grep -Fxq ' - --context=dir:///github/workspace' "$1" && + grep -Fxq ' - --dockerfile=/github/workspace/${{ inputs.dockerfile }}' "$1" && + grep -Fxq ' - --destination=${{ inputs.destination }}' "$1" && + grep -Fxq -- ' - --no-push' "$1" && grep -Fxq -- ' - --no-push-cache' "$1" } -if ! check_kaniko_publish_flags "$kaniko_action"; then - echo 'Kaniko must disable image and cache publishing with separate arguments.' >&2 +if ! check_kaniko_action "$kaniko_action"; then + echo 'Kaniko action must define its entrypoint, build arguments, and no-push flags.' >&2 exit 1 fi +without_entrypoint=$(mktemp) without_no_push=$(mktemp) -trap 'rm -f "$without_no_push"' EXIT +trap 'rm -f "$without_entrypoint" "$without_no_push"' EXIT +sed '/^ entrypoint: \/kaniko\/executor$/d' "$kaniko_action" >"$without_entrypoint" +if check_kaniko_action "$without_entrypoint"; then + echo 'CI isolation test accepted a Kaniko action without its executor entrypoint.' >&2 + exit 1 +fi sed '/^ - --no-push$/d' "$kaniko_action" >"$without_no_push" -if check_kaniko_publish_flags "$without_no_push"; then +if check_kaniko_action "$without_no_push"; then echo 'CI isolation test accepted --no-push-cache without standalone --no-push.' >&2 exit 1 fi -- 2.54.0 From 5d72bb092dc7835320bc2c9540e0da00fda1f81d Mon Sep 17 00:00:00 2001 From: codex Date: Sun, 18 Oct 2026 08:46:05 +0300 Subject: [PATCH 6/8] fix: pass runner workspace to container actions --- .gitea/actions/caddy-validate/action.yml | 6 +++- .gitea/actions/kaniko-build/action.yml | 7 +++-- .gitea/workflows/ci.yaml | 4 +++ docs/operations/CI_RUNNER.md | 13 +++++++-- docs/operations/CURRENT_STATE.md | 18 ++++++++---- infra/runner/test-ci-container-builds.sh | 36 ++++++++++++++++++++---- 6 files changed, 66 insertions(+), 18 deletions(-) diff --git a/.gitea/actions/caddy-validate/action.yml b/.gitea/actions/caddy-validate/action.yml index 107661f..7ce9ca7 100644 --- a/.gitea/actions/caddy-validate/action.yml +++ b/.gitea/actions/caddy-validate/action.yml @@ -1,5 +1,9 @@ name: Validate Caddyfile description: Run the pinned Caddy validator against the checked-out Caddyfile +inputs: + workspace: + description: Repository workspace path supplied by the workflow + required: true runs: using: docker image: Dockerfile @@ -7,6 +11,6 @@ runs: args: - validate - --config - - /github/workspace/deploy/production/Caddyfile + - ${{ inputs.workspace }}/deploy/production/Caddyfile - --adapter - caddyfile diff --git a/.gitea/actions/kaniko-build/action.yml b/.gitea/actions/kaniko-build/action.yml index c79057e..2c1877d 100644 --- a/.gitea/actions/kaniko-build/action.yml +++ b/.gitea/actions/kaniko-build/action.yml @@ -1,6 +1,9 @@ name: Build image with Kaniko in a rootless container description: Validate a Dockerfile by building it without publishing the image or cache inputs: + workspace: + description: Repository workspace path supplied by the workflow + required: true dockerfile: description: Dockerfile path relative to the checked-out repository required: true @@ -12,8 +15,8 @@ runs: image: Dockerfile entrypoint: /kaniko/executor args: - - --context=dir:///github/workspace - - --dockerfile=/github/workspace/${{ inputs.dockerfile }} + - --context=dir://${{ inputs.workspace }} + - --dockerfile=${{ inputs.workspace }}/${{ inputs.dockerfile }} - --destination=${{ inputs.destination }} - --no-push - --no-push-cache diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 6d0e6fb..4382f2f 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -60,13 +60,17 @@ jobs: REVIEW_SECRET_FILE="$secret_file" docker compose --env-file deploy/production/.env.example -f deploy/production/compose.yaml config --quiet - name: Validate Caddyfile in isolated container action uses: ./.gitea/actions/caddy-validate + with: + workspace: ${{ github.workspace }} - name: Build production images uses: ./.gitea/actions/kaniko-build with: + workspace: ${{ github.workspace }} dockerfile: deploy/production/Dockerfile.backend destination: akyldash-backend:ci - name: Build OpenSearch production image uses: ./.gitea/actions/kaniko-build with: + workspace: ${{ github.workspace }} dockerfile: deploy/production/Dockerfile.opensearch destination: akyldash-opensearch:ci diff --git a/docs/operations/CI_RUNNER.md b/docs/operations/CI_RUNNER.md index 1f8747c..73387ab 100644 --- a/docs/operations/CI_RUNNER.md +++ b/docs/operations/CI_RUNNER.md @@ -45,7 +45,10 @@ metrics ports are needed. Runner cache is disabled. The CI workflow and `runner-smoke.yaml` target the repository runner label `akyldash-ci`. CI keeps the backend, Telegram, backup/restore, deployment, bootstrap, runner, Compose, and Caddy checks. Caddy validation runs in a pinned -container action with explicit `/usr/bin/caddy` entrypoint. Both production +container action with explicit `/usr/bin/caddy` entrypoint. The workflow passes +Gitea's `${{ github.workspace }}` value into each container action; Caddy and +Kaniko use that supplied path for repository files instead of assuming a +container workspace location. Both production Dockerfiles are built in a separate pinned Kaniko container action with explicit `/kaniko/executor` entrypoint using the runner's rootless container engine; the job container receives no Docker socket. Kaniko uses `--no-push` and @@ -196,8 +199,12 @@ returned 403 and the scheduler's reason was not independently confirmed. On head `3a89dc21`, CI run 63 failed at the host audit root guard and smoke run 64 succeeded. On head `436806a8`, CI run 65 failed in the Caddy container action with `exec: "validate": executable file not found`; Kaniko was skipped and smoke -run 66 succeeded. This PR sets explicit Caddy and Kaniko entrypoints. Successful -execution and resource fit still require a new runner canary. Root-guard tests +run 66 succeeded. On head `d96fc4d3`, CI run 67 failed because the Caddy action +looked for `/github/workspace/deploy/production/Caddyfile`, which was not +mounted at that fixed path; Kaniko was skipped and smoke run 68 succeeded. This +PR passes `${{ github.workspace }}` to the container actions and uses it for the +Caddyfile, Kaniko context, and Dockerfile paths. A new runner canary must verify +those paths and resource fit. Root-guard tests use `setpriv` for negative cases when jobs start as UID 0 and separately test the host-audit guard with simulated UID 0 and UID 65534; the real host audit, production config setup, and runner installer are never executed as root by diff --git a/docs/operations/CURRENT_STATE.md b/docs/operations/CURRENT_STATE.md index 4e2b2c5..7d058e7 100644 --- a/docs/operations/CURRENT_STATE.md +++ b/docs/operations/CURRENT_STATE.md @@ -97,9 +97,12 @@ UFW rule details, and protected file metadata remain unverified. head `3a89dc21`, CI run 63 failed at the host audit root guard and smoke run 64 succeeded. On head `436806a8`, CI run 65 failed in the Caddy container action with `exec: "validate": executable file not found`; Kaniko was skipped and - smoke run 66 succeeded. This PR pins explicit action entrypoints. No VPS - changes are made; successful image builds and fit within the configured 3 GiB - / 2 CPU per-job limits remain unverified. + smoke run 66 succeeded. On head `d96fc4d3`, CI run 67 failed because the + Caddy action's fixed `/github/workspace/...` path did not exist; Kaniko was + skipped and smoke run 68 succeeded. The current branch passes + `${{ github.workspace }}` to Caddy and Kaniko actions; a new runner run must + verify workspace resolution, image builds, and fit within the configured + 3 GiB / 2 CPU per-job limits. No VPS changes are made. - An isolated local Compose smoke test built backend/OpenSearch images, reached healthy status for OpenSearch, backend, and Caddy, and returned HTTP 200 for `/review` through Caddy. Its containers, networks, and volumes were @@ -152,6 +155,9 @@ Caddy validation and the two production Dockerfile builds. Kaniko image publishing and cache publishing are disabled. This repository change does not modify the VPS or grant workflow containers a Docker socket. CI run 65 failed in the Caddy action because the runner tried to execute `validate` directly; -Kaniko was skipped. This PR sets explicit action entrypoints. A successful run -must verify Caddy, both image builds, and resource fit before these actions are -considered validated runner behavior. +Kaniko was skipped. CI run 67 then found that the action's fixed +`/github/workspace` path did not contain the repository; Kaniko was skipped. +The branch now passes Gitea's `${{ github.workspace }}` value into all three +container action invocations. A successful run must verify Caddy, both image +builds, and resource fit before these actions are considered validated runner +behavior. diff --git a/infra/runner/test-ci-container-builds.sh b/infra/runner/test-ci-container-builds.sh index e388c87..4556c90 100644 --- a/infra/runner/test-ci-container-builds.sh +++ b/infra/runner/test-ci-container-builds.sh @@ -3,18 +3,30 @@ set -euo pipefail workflow=.gitea/workflows/ci.yaml kaniko_action=.gitea/actions/kaniko-build/action.yml +caddy_action=.gitea/actions/caddy-validate/action.yml grep -Fq 'runs-on: akyldash-ci' "$workflow" grep -Fq 'uses: ./.gitea/actions/caddy-validate' "$workflow" [[ $(grep -Fc 'uses: ./.gitea/actions/kaniko-build' "$workflow") -eq 2 ]] +check_workspace_wiring() { + local workflow_file=$1 caddy_file=${2:-$caddy_action} kaniko_file=${3:-$kaniko_action} + [[ $(grep -Fc 'workspace: ${{ github.workspace }}' "$workflow_file") -eq 3 ]] && + grep -Fxq ' - ${{ inputs.workspace }}/deploy/production/Caddyfile' "$caddy_file" && + grep -Fxq ' - --context=dir://${{ inputs.workspace }}' "$kaniko_file" && + grep -Fxq ' - --dockerfile=${{ inputs.workspace }}/${{ inputs.dockerfile }}' "$kaniko_file" && + ! grep -Fq '/github/workspace' "$caddy_file" "$kaniko_file" +} +if ! check_workspace_wiring "$workflow"; then + echo 'Container actions must receive github.workspace and use it for repository paths.' >&2 + exit 1 +fi grep -Fq 'dockerfile: deploy/production/Dockerfile.backend' "$workflow" grep -Fq 'dockerfile: deploy/production/Dockerfile.opensearch' "$workflow" grep -Fq 'docker compose --env-file deploy/production/.env.example -f deploy/production/compose.yaml config --quiet' "$workflow" -caddy_action=.gitea/actions/caddy-validate/action.yml grep -Fxq ' entrypoint: /usr/bin/caddy' "$caddy_action" grep -Fxq ' - validate' "$caddy_action" grep -Fxq ' - --config' "$caddy_action" -grep -Fxq ' - /github/workspace/deploy/production/Caddyfile' "$caddy_action" +grep -Fxq ' - ${{ inputs.workspace }}/deploy/production/Caddyfile' "$caddy_action" grep -Fxq ' - --adapter' "$caddy_action" grep -Fxq ' - caddyfile' "$caddy_action" if grep -Eq 'docker (build|run)([[:space:]]|$)' "$workflow"; then @@ -23,8 +35,8 @@ if grep -Eq 'docker (build|run)([[:space:]]|$)' "$workflow"; then fi check_kaniko_action() { grep -Fxq ' entrypoint: /kaniko/executor' "$1" && - grep -Fxq ' - --context=dir:///github/workspace' "$1" && - grep -Fxq ' - --dockerfile=/github/workspace/${{ inputs.dockerfile }}' "$1" && + grep -Fxq ' - --context=dir://${{ inputs.workspace }}' "$1" && + grep -Fxq ' - --dockerfile=${{ inputs.workspace }}/${{ inputs.dockerfile }}' "$1" && grep -Fxq ' - --destination=${{ inputs.destination }}' "$1" && grep -Fxq -- ' - --no-push' "$1" && grep -Fxq -- ' - --no-push-cache' "$1" @@ -35,7 +47,9 @@ if ! check_kaniko_action "$kaniko_action"; then fi without_entrypoint=$(mktemp) without_no_push=$(mktemp) -trap 'rm -f "$without_entrypoint" "$without_no_push"' EXIT +without_workspace=$(mktemp) +wrong_workspace=$(mktemp) +trap 'rm -f "$without_entrypoint" "$without_no_push" "$without_workspace" "$wrong_workspace"' EXIT sed '/^ entrypoint: \/kaniko\/executor$/d' "$kaniko_action" >"$without_entrypoint" if check_kaniko_action "$without_entrypoint"; then echo 'CI isolation test accepted a Kaniko action without its executor entrypoint.' >&2 @@ -46,6 +60,16 @@ if check_kaniko_action "$without_no_push"; then echo 'CI isolation test accepted --no-push-cache without standalone --no-push.' >&2 exit 1 fi +sed '/workspace: \${{ github.workspace }}/d' "$workflow" >"$without_workspace" +if check_workspace_wiring "$without_workspace"; then + echo 'CI isolation test accepted container actions without workflow workspace inputs.' >&2 + exit 1 +fi +sed 's#${{ inputs.workspace }}#/github/workspace#g' "$kaniko_action" >"$wrong_workspace" +if check_workspace_wiring "$workflow" "$caddy_action" "$wrong_workspace"; then + echo 'CI isolation test accepted a fixed Kaniko workspace root.' >&2 + exit 1 +fi grep -Fq 'FROM ghcr.io/osscontainertools/kaniko:v1.28.5-debug@sha256:c73ca67a3b494bce79a8fb6090c4f73c18997ab272d80bffc460a62343aeb412' .gitea/actions/kaniko-build/Dockerfile grep -Fq 'FROM caddy:2.11.6-alpine@sha256:881bbc60f9986d5ab8e7cfd6cf7e4ef3c9c0439fef2429d035d065577882f028' .gitea/actions/caddy-validate/Dockerfile if grep -REn 'docker.sock|privileged:|container.options:|^[[:space:]]*volumes:' "$workflow" .gitea/actions/kaniko-build .gitea/actions/caddy-validate; then @@ -53,4 +77,4 @@ if grep -REn 'docker.sock|privileged:|container.options:|^[[:space:]]*volumes:' exit 1 fi -echo 'PASS CI container operations use pinned isolated actions without socket or publish access' +echo 'PASS CI container actions use the supplied workspace and remain isolated without socket or publish access' -- 2.54.0 From cf99f4532f40eba4ecda0b6a587f4f4edcba9724 Mon Sep 17 00:00:00 2001 From: codex Date: Sun, 18 Oct 2026 08:51:21 +0300 Subject: [PATCH 7/8] docs: record successful isolated CI build --- CHANGELOG.md | 1 + docs/operations/CI_RUNNER.md | 13 ++++++++----- docs/operations/CURRENT_STATE.md | 26 +++++++++++++------------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8f1394..6bac0d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Не выпущено +- CI actions теперь используют workspace, переданный Gitea, для Caddyfile и Kaniko build context; CI run 69 и smoke run 70 прошли на self-hosted runner. - Runner installer сохраняет `HOME` и XDG-пути в user-unit `docker.service`, перезапускает активный daemon и проверяет окружение его процесса. - Runner installer защищает image и mountpoint root-owned home-каталогом, оставляет регистрацию и workspace в runner-owned каталоге, безопасно форматирует образ по открытому FD и проверяет `nosuid,nodev`. - Backend `0.11.3`: ключ подписи поисковых снимков читается из Docker secret-файла, а не из аргументов процесса. Добавлена базовая инфраструктура VPS, Caddy HTTPS proxy с разделёнными Compose-сетями и CI-проверка конфигурации; публикация образов и production-деплой остаются выключены до проверки runner, registry, backup/restore и ограниченного deploy-helper. diff --git a/docs/operations/CI_RUNNER.md b/docs/operations/CI_RUNNER.md index 73387ab..522814a 100644 --- a/docs/operations/CI_RUNNER.md +++ b/docs/operations/CI_RUNNER.md @@ -54,7 +54,9 @@ Dockerfiles are built in a separate pinned Kaniko container action with explicit job container receives no Docker socket. Kaniko uses `--no-push` and `--no-push-cache`, so CI validates the full backend and OpenSearch builds but does not publish images or export build output. The two builds run sequentially -within one job; peak memory and disk use still require a runner canary. No registry credentials, +within one job. CI run 69 completed both builds under the configured per-job +limits; this confirms the workflow path and resource fit for that run, not +host-wide resource metrics. No registry credentials, production secrets, elevated container options, or arbitrary host mounts are configured. The Kaniko action uses the `linux/amd64` v1.28.5-debug manifest @@ -201,10 +203,11 @@ On head `3a89dc21`, CI run 63 failed at the host audit root guard and smoke run with `exec: "validate": executable file not found`; Kaniko was skipped and smoke run 66 succeeded. On head `d96fc4d3`, CI run 67 failed because the Caddy action looked for `/github/workspace/deploy/production/Caddyfile`, which was not -mounted at that fixed path; Kaniko was skipped and smoke run 68 succeeded. This -PR passes `${{ github.workspace }}` to the container actions and uses it for the -Caddyfile, Kaniko context, and Dockerfile paths. A new runner canary must verify -those paths and resource fit. Root-guard tests +mounted at that fixed path; Kaniko was skipped and smoke run 68 succeeded. On +head `5d72bb09`, CI run 69 and smoke run 70 succeeded. CI validated the Caddyfile +and built the backend and OpenSearch images using `${{ github.workspace }}` +within the configured per-job limits. Image publishing, host-wide resource +metrics, and production deployment remain unverified. Root-guard tests use `setpriv` for negative cases when jobs start as UID 0 and separately test the host-audit guard with simulated UID 0 and UID 65534; the real host audit, production config setup, and runner installer are never executed as root by diff --git a/docs/operations/CURRENT_STATE.md b/docs/operations/CURRENT_STATE.md index 7d058e7..c96e005 100644 --- a/docs/operations/CURRENT_STATE.md +++ b/docs/operations/CURRENT_STATE.md @@ -99,10 +99,11 @@ UFW rule details, and protected file metadata remain unverified. with `exec: "validate": executable file not found`; Kaniko was skipped and smoke run 66 succeeded. On head `d96fc4d3`, CI run 67 failed because the Caddy action's fixed `/github/workspace/...` path did not exist; Kaniko was - skipped and smoke run 68 succeeded. The current branch passes - `${{ github.workspace }}` to Caddy and Kaniko actions; a new runner run must - verify workspace resolution, image builds, and fit within the configured - 3 GiB / 2 CPU per-job limits. No VPS changes are made. + skipped and smoke run 68 succeeded. On head `5d72bb09`, CI run 69 and smoke + run 70 succeeded. CI validated the Caddyfile and built the backend and + OpenSearch images using `${{ github.workspace }}` within the configured + 3 GiB / 2 CPU per-job limits. Image publishing, host-wide resource metrics, + and production deployment remain unverified. No VPS changes are made. - An isolated local Compose smoke test built backend/OpenSearch images, reached healthy status for OpenSearch, backend, and Caddy, and returned HTTP 200 for `/review` through Caddy. Its containers, networks, and volumes were @@ -120,8 +121,8 @@ UFW rule details, and protected file metadata remain unverified. and restore of real production data remain unverified because production is not deployed. - Cloudflare edge response was checked; origin DNS target, origin certificate, - Full (strict), registry publishing, successful Caddy/Kaniko action execution, - runner resource fit, and production deployment have not been verified. The + Full (strict), registry publishing, host-wide runner metrics, and production + deployment have not been verified. The owner-reported Idle state has not been independently checked through runner inventory because the API returned 403. - This audit made no host changes. No package or service was installed by this @@ -141,10 +142,9 @@ UFW rule details, and protected file metadata remain unverified. 3. Run `sudo bash /opt/akyldash/current/infra/deploy/install-helper.sh` after reviewing the checkout; it grants only fixed verification, Compose `config`/`status`, and guarded rollback commands. -4. After PR review, inspect the latest CI and smoke runs on `akyldash-ci`. - Confirm both pinned container actions complete within the configured per-job - CPU, memory, and disk limits. Production deployment remains manual and has - no CI job. +4. After merging the reviewed PR, confirm a subsequent CI and smoke run on + `akyldash-ci`. Image publishing and production deployment remain manual and + are not part of CI. 5. Test backup/restore and rollback only with synthetic data in an isolated environment before any production deployment. @@ -158,6 +158,6 @@ in the Caddy action because the runner tried to execute `validate` directly; Kaniko was skipped. CI run 67 then found that the action's fixed `/github/workspace` path did not contain the repository; Kaniko was skipped. The branch now passes Gitea's `${{ github.workspace }}` value into all three -container action invocations. A successful run must verify Caddy, both image -builds, and resource fit before these actions are considered validated runner -behavior. +container action invocations. CI run 69 verified Caddy, both image builds, and +fit within the configured per-job limits; smoke run 70 passed. Host-wide runner +metrics, image publishing, and production deployment remain unverified. -- 2.54.0 From 554a7ab5ffd7c240c120ec3f4e926aac4d0edaf6 Mon Sep 17 00:00:00 2001 From: codex Date: Sun, 18 Oct 2026 08:55:16 +0300 Subject: [PATCH 8/8] docs: qualify runner resource verification --- docs/operations/CI_RUNNER.md | 11 ++++++----- docs/operations/CURRENT_STATE.md | 12 +++++++----- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/docs/operations/CI_RUNNER.md b/docs/operations/CI_RUNNER.md index 522814a..b6bd997 100644 --- a/docs/operations/CI_RUNNER.md +++ b/docs/operations/CI_RUNNER.md @@ -54,9 +54,9 @@ Dockerfiles are built in a separate pinned Kaniko container action with explicit job container receives no Docker socket. Kaniko uses `--no-push` and `--no-push-cache`, so CI validates the full backend and OpenSearch builds but does not publish images or export build output. The two builds run sequentially -within one job. CI run 69 completed both builds under the configured per-job -limits; this confirms the workflow path and resource fit for that run, not -host-wide resource metrics. No registry credentials, +within one job. CI run 69 completed both builds on the runner. Actual per-action +resource-limit enforcement and peak memory or disk usage were not independently +measured or verified. No registry credentials, production secrets, elevated container options, or arbitrary host mounts are configured. The Kaniko action uses the `linux/amd64` v1.28.5-debug manifest @@ -205,8 +205,9 @@ run 66 succeeded. On head `d96fc4d3`, CI run 67 failed because the Caddy action looked for `/github/workspace/deploy/production/Caddyfile`, which was not mounted at that fixed path; Kaniko was skipped and smoke run 68 succeeded. On head `5d72bb09`, CI run 69 and smoke run 70 succeeded. CI validated the Caddyfile -and built the backend and OpenSearch images using `${{ github.workspace }}` -within the configured per-job limits. Image publishing, host-wide resource +and built the backend and OpenSearch images using `${{ github.workspace }}`. +Actual per-action resource-limit enforcement and peak usage were not +independently measured or verified. Image publishing, host-wide resource metrics, and production deployment remain unverified. Root-guard tests use `setpriv` for negative cases when jobs start as UID 0 and separately test the host-audit guard with simulated UID 0 and UID 65534; the real host audit, diff --git a/docs/operations/CURRENT_STATE.md b/docs/operations/CURRENT_STATE.md index c96e005..a15d875 100644 --- a/docs/operations/CURRENT_STATE.md +++ b/docs/operations/CURRENT_STATE.md @@ -101,9 +101,10 @@ UFW rule details, and protected file metadata remain unverified. Caddy action's fixed `/github/workspace/...` path did not exist; Kaniko was skipped and smoke run 68 succeeded. On head `5d72bb09`, CI run 69 and smoke run 70 succeeded. CI validated the Caddyfile and built the backend and - OpenSearch images using `${{ github.workspace }}` within the configured - 3 GiB / 2 CPU per-job limits. Image publishing, host-wide resource metrics, - and production deployment remain unverified. No VPS changes are made. + OpenSearch images using `${{ github.workspace }}`. Actual per-action + resource-limit enforcement and peak usage were not independently measured or + verified. Image publishing, host-wide resource metrics, and production + deployment remain unverified. No VPS changes are made. - An isolated local Compose smoke test built backend/OpenSearch images, reached healthy status for OpenSearch, backend, and Caddy, and returned HTTP 200 for `/review` through Caddy. Its containers, networks, and volumes were @@ -158,6 +159,7 @@ in the Caddy action because the runner tried to execute `validate` directly; Kaniko was skipped. CI run 67 then found that the action's fixed `/github/workspace` path did not contain the repository; Kaniko was skipped. The branch now passes Gitea's `${{ github.workspace }}` value into all three -container action invocations. CI run 69 verified Caddy, both image builds, and -fit within the configured per-job limits; smoke run 70 passed. Host-wide runner +container action invocations. CI run 69 validated Caddy and completed both +image builds; smoke run 70 passed. Actual per-action resource-limit enforcement +and peak usage were not independently measured or verified. Host-wide runner metrics, image publishing, and production deployment remain unverified. -- 2.54.0