Publishing this image by hand kept hitting the same four walls — Cloudflare's 413, Docker parsing a bare MagicDNS name as a Docker Hub namespace, a PAT missing write:package, and multi-arch. push-image.sh made that survivable; this removes the manual step entirely. It builds on GitHub rather than our own Gitea Actions because the runners cannot build images: their labels are `ubuntu-latest:host` (host mode, no job containers) and the container drops ALL capabilities with allowPrivilegeEscalation:false, while rootless buildah needs newuidmap/user namespaces for RUN steps. Building there would mean weakening the posture the hardening established. It publishes to ghcr.io rather than the Gitea registry because Cloudflare's body cap is what caused the 413 in the first place, and because the cluster already pulls zegit's own images from ghcr.io through an existing github-registry-credentials pull secret — an established path rather than a new one. A master push validates that both arches still build but publishes nothing; only an explicit dispatch names a tag and pushes, so a branch push cannot silently mint a version. The final step reads the published index back and fails if either architecture is absent, since a single-arch image under this tag would CrashLoop on half the nodes. Action SHAs match the pins zegit already uses, rather than introducing a second set of versions to track.
97 lines
4.3 KiB
Markdown
97 lines
4.3 KiB
Markdown
# 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
|
|
|
|
`<base-runner-version>-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.
|