Files
Bastian de BylandClaude Fable 5.1 bc423a1c7c
CI Images / Plan (push) Successful in 35s
CI Images / Build ci (push) Successful in 7m53s
CI Images / Build espidf (push) Successful in 18m27s
CI Images / Build platformio (push) Successful in 25m48s
chore(gitea-actions): bump ESP-IDF CI image to v5.5.1
esp32-vi-can-obd2 needs the IDF 5.5 TWAI node driver. ci-images.yml publishes
gitbot/gitea-ci-espidf:v5.5.1 from this pin; the v5.4.1 tag stays in the
registry for esp-mg-tpms.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 17:08:58 -04:00
..

gitea-actions

Runs the Gitea Actions runners on home.debyl.io. One act_runner process per Gitea instance (git.debyl.io, git.skudak.com), both as the gitea-runner user, both backed by the same rootless podman image store.

CI job images

Jobs do not run on the host. Each one gets an ephemeral container from one of three images:

runs-on / container: Image Used by
fedora, ubuntu-latest, ubuntu-22.04 git.debyl.io/gitbot/gitea-ci:latest Go / node / web jobs, docker build
container: image: git.debyl.io/gitbot/gitea-ci-espidf:<esp_idf_version> esp-mg-tpms, skudak/esp32-stm32-vcu
container: image: git.debyl.io/gitbot/gitea-ci-platformio:<pio_espressif32_version> skudak/esp32-web-interface

This role does not build them. .gitea/workflows/ci-images.yml builds files/Containerfile.* and pushes to the Gitea registry; the role logs gitea-runner in and pulls. Version pins live in defaults/main.yml and are read by both the role and the workflow, so a bump moves the image tag in one place.

Why the registry

The images used to exist only as localhost/gitea-ci* in the runner's store. The nightly prune (roles/podman, podman_prune_ci_until: 48h) deletes any CI-user image older than that which no container holds, so after an idle weekend every job failed in under a second on docker pull localhost/gitea-ci:latest, and the only fix was re-running this role and waiting out a full rebuild.

Two things now keep that from happening:

  • A registry copy. force_pull stays false, which in act_runner means pull only when missing — so a present image is never re-fetched, and a pruned one is restored by the next job without anyone noticing.
  • A prune exemption. Each Containerfile declares LABEL io.debyl.ci-base="true", and the prune skips that label (podman_prune_ci_keep_label). Its until counts from build time, not pull time, so without this a re-pulled image would be deleted again the same night — a 7.8 GB ESP-IDF download every single day.

The label is declared in the Containerfile rather than passed as --label so neither builder can omit it; the workflow re-checks it with docker inspect before pushing.

Authentication

Both the role and act_runner read /home/gitea-runner/.docker/config.json. act_runner uses it for the job-image pull it performs when a label's image is missing; podman falls back to the same file. The role writes it from gitea_registry_username / gitea_registry_token (vault), so one login covers both. The skudak runner pulls from git.debyl.io too — same host, same user, same file.

The workflow pushes with a REGISTRY_TOKEN secret on bastian/deploy_home, belonging to the same gitbot user: Gitea authorises a package push by the token's owner, not by the path, so pushing to gitbot/ means logging in as gitbot.

Both tokens need the write:package scope, not just read:package: the workflow pushes with REGISTRY_TOKEN, and the gitea_ci_build_local bootstrap below pushes with the vault token. A read-only token logs in and pulls fine but fails the push with authentication required (Gitea logs reqPackageAccess).

Rebuilding

Normally nothing to do — edit a files/Containerfile.* or a version pin, push to master, and the workflow rebuilds only the affected images. It also rebuilds everything weekly so base-image updates land without a commit, and takes a workflow_dispatch with an image selector.

Pull requests build but do not push, under a throwaway :pr-<n> tag that is deleted afterwards. The build runs in the live runner's image store, so a PR tagged with the real name would hand every later job on this host an unmerged image.

Bootstrap / CI is down

The workflow that builds gitea-ci runs in gitea-ci, so a registry that has never held it cannot bootstrap itself. Build on the host instead:

make deploy TAGS=gitea-actions EXTRA_VARS="gitea_ci_build_local=true"

That builds all three from the same Containerfiles and pushes them. One run is enough even on a cold registry: tasks/main.yml imports images.yml before runner.yml, so the images are published before the runner labels are flipped to point at them.

The alternative first-time path is to merge the workflow and dispatch it while the deployed labels still say localhost/ — the job then builds inside the old local image and seeds the registry — then run a plain make deploy TAGS=gitea-actions to switch the labels over.