From 55ccd5f3c073a265b499696ed03c57b6baa1ae46 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 16 May 2026 10:18:42 +0200 Subject: [PATCH 1/2] ci(obs): replace rsync with rm+cp in deploy step rsync is not present in the act_runner job container image. rm -rf + cp -r gives identical semantics (including removal of deleted files) using only coreutils, which are always available. Co-Authored-By: Claude Sonnet 4.6 --- .gitea/workflows/nightly.yml | 3 ++- .gitea/workflows/release.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/nightly.yml b/.gitea/workflows/nightly.yml index 63ec0b03..396be5b3 100644 --- a/.gitea/workflows/nightly.yml +++ b/.gitea/workflows/nightly.yml @@ -136,8 +136,9 @@ jobs: # Gitea is always the single source of truth for secret rotation. # Non-secret config lives in infra/observability/obs.env (tracked in git). run: | + rm -rf /opt/familienarchiv/infra/observability mkdir -p /opt/familienarchiv/infra/observability - rsync -a --delete infra/observability/ /opt/familienarchiv/infra/observability/ + cp -r infra/observability/. /opt/familienarchiv/infra/observability/ cp docker-compose.observability.yml /opt/familienarchiv/ cat > /opt/familienarchiv/obs-secrets.env <<'EOF' GRAFANA_ADMIN_PASSWORD=${{ secrets.GRAFANA_ADMIN_PASSWORD }} diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 041ffa09..c7520e35 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -104,8 +104,9 @@ jobs: # then writes obs-secrets.env fresh from Gitea secrets. # Non-secret config lives in infra/observability/obs.env (tracked in git). run: | + rm -rf /opt/familienarchiv/infra/observability mkdir -p /opt/familienarchiv/infra/observability - rsync -a --delete infra/observability/ /opt/familienarchiv/infra/observability/ + cp -r infra/observability/. /opt/familienarchiv/infra/observability/ cp docker-compose.observability.yml /opt/familienarchiv/ cat > /opt/familienarchiv/obs-secrets.env <<'EOF' GRAFANA_ADMIN_PASSWORD=${{ secrets.GRAFANA_ADMIN_PASSWORD }} From 134f1e2ae05cc4221ab602626ca5e0f03d871ee5 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 16 May 2026 10:19:09 +0200 Subject: [PATCH 2/2] chore(runner): mount /opt/familienarchiv into job containers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The live runner config was missing /opt/familienarchiv in valid_volumes and options, so deploy steps wrote files into the ephemeral job container rather than the host — silently discarded on exit. Updated /root/docker/gitea/runner-config.yaml on the server and restarted gitea-runner. Repo file now matches the server exactly, including the network: gitea_gitea setting that was previously only on the server. DEPLOYMENT.md: clarifies that /opt/familienarchiv does not need to be in the runner container's own volumes (DooD spawns job containers from the host daemon directly); updates restart command from systemctl to docker restart; narrows the cp-r stale-file note to manual ops only (CI uses rm -rf before copying). Co-Authored-By: Claude Sonnet 4.6 --- docs/DEPLOYMENT.md | 18 ++++++++++-------- runner-config.yaml | 39 ++++++++++++++++----------------------- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 159beb7d..2c665cf4 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -199,20 +199,22 @@ curl -fsSL https://tailscale.com/install.sh | sh && tailscale up # act_runner stores job workspaces here so that docker compose bind mounts resolve # to real host paths. The path must be identical on the host and inside job containers. mkdir -p /srv/gitea-workspace -# Also add this volume line to the runner service in ~/docker/gitea/compose.yaml: -# volumes: -# - /srv/gitea-workspace:/srv/gitea-workspace -# See runner-config.yaml (workdir_parent + valid_volumes + options) and ADR-015. - # Observability config permanent directory — the nightly CI job copies # docker-compose.observability.yml and infra/observability/ here on every run. # The obs stack is always started from this path, not from the workspace. # See ADR-016 for why this directory is used instead of a server-pull approach. mkdir -p /opt/familienarchiv/infra +# Both paths must also appear in the runner service volumes in ~/docker/gitea/compose.yaml: +# volumes: +# - /srv/gitea-workspace:/srv/gitea-workspace +# /opt/familienarchiv does NOT need to be in the runner container's volumes — job +# containers are spawned by the host daemon directly (DooD), so the host path is +# accessible to them as long as runner-config.yaml lists it in valid_volumes + options. +# See runner-config.yaml (workdir_parent + valid_volumes + options) and ADR-015/016. # ⚠ IMPORTANT: after any change to runner-config.yaml (valid_volumes, options, workdir_parent), -# restart the Gitea Act runner on the host for the new config to take effect: -# systemctl restart gitea-runner +# restart the Gitea Act runner for the new config to take effect: +# docker restart gitea-runner # Until restarted, job containers are spawned with the old config and any new bind mounts # (e.g. /opt/familienarchiv) will not be available inside job steps. ``` @@ -356,7 +358,7 @@ docker compose \ up -d --wait --remove-orphans ``` -> **Note:** `cp -r` does not remove deleted files. If a config file is removed from the repo, its stale copy persists at `/opt/familienarchiv/infra/observability/` until manually deleted: +> **Note (manual ops only):** CI clears the destination with `rm -rf` before copying, so deleted files are removed automatically on the next run. If you copy manually with `cp -r` without first removing the directory, stale files from deleted configs will persist until cleaned up: > ```bash > rm /opt/familienarchiv/infra/observability/ > ``` diff --git a/runner-config.yaml b/runner-config.yaml index 07cad8d5..c72f90af 100644 --- a/runner-config.yaml +++ b/runner-config.yaml @@ -1,33 +1,26 @@ # runner-config.yaml — only the relevant section container: - # passed as DOCKER_HOST inside the job container + # join the same network Gitea is on, so job containers can resolve 'gitea' + # for actions/checkout and other internal API calls. + network: gitea_gitea + # passed as DOCKER_HOST inside the job container; act_runner auto-mounts + # this socket path into the job, so no explicit -v option is needed. docker_host: "unix:///var/run/docker.sock" - # Job workspaces are stored here on the NAS and mounted at the same - # absolute path inside job containers. Identical host ↔ container path - # is the requirement: Docker Compose resolves relative bind mounts to - # $(pwd) inside the job container and passes that absolute path to the - # host daemon — the daemon must find the file at that exact host path. - # Prerequisite: mkdir -p /srv/gitea-workspace on the host, and add - # - /srv/gitea-workspace:/srv/gitea-workspace - # to the runner service volumes in gitea's compose.yaml. + # Job workspaces are stored here and mounted at the same absolute path + # inside job containers. Identical host <-> container path is the requirement: + # Compose resolves relative bind mounts to $(pwd) inside the job container + # and passes that absolute path to the host daemon, which must find the file + # at that exact host path. Prerequisite: /srv/gitea-workspace exists on the + # host and is bind-mounted in the runner container (see compose.yaml). workdir_parent: /srv/gitea-workspace # whitelists volumes that workflow steps may bind-mount valid_volumes: - "/var/run/docker.sock" - "/srv/gitea-workspace" - "/opt/familienarchiv" - # appended to `docker run` when the runner spawns a job container - # SECURITY WARNING: This mount configuration grants CI job containers: - # 1. Root-equivalent access to the host Docker daemon (via the socket). - # 2. Read/write access to /opt/familienarchiv/ — including the main app's - # compose files, Caddy config, and observability configs. A malicious - # workflow step could overwrite any file in that directory. - # Both are acceptable ONLY because this runner is single-tenant: it executes - # code exclusively from this private repo with a fixed set of trusted authors. - # WARNING: Do NOT add this runner to any repo with external contributors or - # untrusted PRs — the blast radius includes the entire production deployment. - # See ADR-016 for the reasoning behind the /opt/familienarchiv mount. - options: "-v /var/run/docker.sock:/var/run/docker.sock -v /srv/gitea-workspace:/srv/gitea-workspace -v /opt/familienarchiv:/opt/familienarchiv" - # keep network mode default (bridge) — Testcontainers handles its own networking + # mount the workspace and the permanent obs/config directory into job containers. + # /opt/familienarchiv is the stable path CI copies configs to (ADR-016); it must + # be mounted here so deploy steps can write through to the host filesystem. + options: "-v /srv/gitea-workspace:/srv/gitea-workspace -v /opt/familienarchiv:/opt/familienarchiv" + # keep behavior default — Testcontainers handles its own networking force_pull: false -