# zegit-forgejo-runner Custom toolchain image for the cluster's self-hosted **Gitea Actions (Forgejo) runner**, which runs in *host mode* (Docker disabled) — workflow steps execute inside this image, so it must carry node/git/make/etc. that the stock `code.forgejo.org/forgejo/runner` image lacks. This repo lives only on Gitea (`zegit-zoo/zegit-forgejo-runner`). The cluster-side deployment and rationale live in `flux-config` (`runners.md`, `applications/forgejo-runner/`). ## Build & push The runner needs this image to run *any* job, so the **first** build must be done by hand (CI can't bootstrap itself). The cluster is mixed amd64/arm64, so build multi-arch: ```sh docker buildx create --use --name zegit-ci 2>/dev/null || true docker login gitea.langenoja.se # username + a Gitea personal access token docker buildx build --platform linux/amd64,linux/arm64 \ -t gitea.langenoja.se/zegit-zoo/zegit-forgejo-runner:12.11.1-ci --push . ``` The Gitea package is **public**, so the `forgejo-runner-system` namespace pulls it without an imagePullSecret. ## Wire it into the cluster After the push succeeds, in `flux-config` `applications/forgejo-runner/base/deployment-forgejo-runner.yaml` set the `image:` on **both** the `register` initContainer and the `forgejo-runner` daemon to `gitea.langenoja.se/zegit-zoo/zegit-forgejo-runner:12.11.1-ci`, then commit (Flux reconciles). Do this only *after* the image exists, or the pod ImagePullBackOffs. ## Tag scheme `-ci` (e.g. `12.11.1-ci`). Must satisfy the cluster's Gatekeeper `enforce-semantic-image-tags` constraint — never `:latest`. ## Publishing a new image ./push-image.sh 12.11.1-ci4 The registry hostname in the image reference must stay `gitea.langenoja.se`, because that is what the cluster pulls. The script therefore keeps the NAME and changes only the ROUTE: it pins that hostname to the Istio gateway on the LAN (`10.40.0.10`) for the duration of the push, then removes the pin. That matters because `gitea.langenoja.se` resolves to Cloudflare, which caps request bodies below a registry layer and returns **413 Payload Too Large**. The gateway serves a `*.langenoja.se` wildcard cert, so TLS still validates normally — no `insecure-registries`, no `--tls-verify=false`. If a run is killed before it can clean up, the pin is left behind. It carries a marker comment, so the next run recognises it as its own and removes it — or you can clear it directly: ./push-image.sh --unpin A pin you added yourself (no marker) is never touched, and the script refuses to run if an unmarked pin points somewhere other than the gateway. Requirements: a Gitea PAT with **`write:package`** (`read:package` is pull-only), LAN reachability of the gateway, and qemu/binfmt registered for the arm64 build. Multi-arch is not optional. `forgejo-runner` runs on arm64 nodes and `zegit-runner` on amd64, neither deployment sets a `nodeSelector`, and both use this single tag. Two traps the script exists to prevent: pushing to the bare MagicDNS name (`gitea-system-gitea-tailscale-http`) makes Docker treat it as a **Docker Hub namespace**, since a registry host is only recognised when it contains a dot or colon; and `docker push $_` pushes `.`, because `$_` is the last ARGUMENT of the build command, not its tag. ### Automated builds `.github/workflows/build-image.yml` builds and publishes to **ghcr.io/zegit-zoo/zegit-forgejo-runner** on GitHub. Run it from the Actions tab (**build image** → Run workflow) with the tag, e.g. `12.11.1-ci4`. A push to `master` that touches the Dockerfile only VALIDATES that both architectures still build — it publishes nothing, because guessing a tag from a branch push would silently mint versions. It builds on GitHub rather than our own Gitea Actions because the runners are host mode (`ubuntu-latest:host`, no job containers) and drop ALL capabilities with `allowPrivilegeEscalation: false`; rootless buildah needs user namespaces for its RUN steps, so building there would mean weakening that posture. It publishes to ghcr.io rather than the Gitea registry because `gitea.langenoja.se` sits behind Cloudflare, whose body cap returns **413** on a registry layer — and the cluster already pulls zegit's own images from ghcr.io with an existing `github-registry-credentials` pull secret. `push-image.sh` remains for building by hand when you need it.