Gitea expands a job's matrix when the run is created, before plan has any
outputs, so fromJSON(needs.plan.outputs.matrix) collapsed to a single empty
"Build ${{ matrix.key }}" job and nothing was ever built. The matrix is now
the fixed list of image keys; plan emits every image's spec with a build flag
and each matrix job looks its own entry up, no-opping when it wasn't picked.
Also document that both registry tokens need write:package -- the vault token
was read-only, so the gitea_ci_build_local bootstrap failed its push.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
4.5 KiB
Markdown
97 lines
4.5 KiB
Markdown
# 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:
|
|
|
|
```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.
|