# 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-mg-tpms, skudak/esp32-stm32-vcu | | `container: image:` | `git.debyl.io/gitbot/gitea-ci-platformio:` | 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-` 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: ```sh 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.