Two commits: the one-line fix that unbreaks zegit's Gitea CI, and a script so publishing this image stops being a four-obstacle exercise.
git-daemon
Alpine splits git-http-backendout of the git package — git alone ships git-remote-http/https but not the CGI backend. So the runner had git 2.52.0 and no backend, and zegit's internal/forgeprobe smart-HTTP tests died with fork/exec git-http-backend: no such file or directory, reporting three FAILURES for a missing tool.
That turned Gitea CI red on every zegit master commit while GitHub stayed green, for hours, unnoticed — GitHub is the release oracle, which is exactly why a red mirror is invisible.
Verified on the real base image, not alpine:3: on code.forgejo.org/forgejo/runner:12.11.1 (Alpine 3.23), apk add git git-daemon puts the backend at /usr/libexec/git-core/git-http-backend, and git --exec-path resolves there — the exact lookup the test harness performs. So the tests run rather than skip, coverage returns over the 85% floor, and cover-check stops failing without widening any gate.
push-image.sh
Publishing by hand hit 413 (Cloudflare body cap), a Docker Hub auth refusal (a bare MagicDNS name is parsed as a Docker Hub namespace, since a registry host needs a dot or colon), insufficient_scope (needs write:package), and multi-arch.
The image reference must keep the hostname gitea.langenoja.se because that is what the cluster pulls — so the script keeps the NAME and changes only the ROUTE, pinning it to the Istio gateway on the LAN. That gateway serves a *.langenoja.se wildcard cert, so TLS validates normally.
Assumptions & Decisions
Gateway IP 10.40.0.10 is a default, overridable via GATEWAY_IP. Verified: GET /v2/ → 401 with ssl_verify_result=0.
The pin carries a marker comment so a leftover (run died before its trap) is distinguishable from a deliberate one. Without it, a leftover survived every later run and had to be reverted by hand — which happened. --unpin clears one; an unmarked pin is never touched.
Rejected: the Tailscale route. gitea-tailscale-http exposes port 3000 plain HTTP, nothing on 443, so it cannot serve a correctly-named registry push.
Rejected: insecure-registries. Unnecessary once the gateway's real cert is used, and it would weaken every pull from that host.
Multi-arch is not optional — forgejo-runner is on arm64, zegit-runner on amd64, neither sets a nodeSelector.
Not done here: in-cluster CI for this build. It needs buildah (the runners are host-mode, no docker daemon) and arch-labelled runners that do not exist yet.
After merge: ./push-image.sh 12.11.1-ci4, then bump -ci3 → -ci4 in flux-config's deployment-zegit-runner.yaml and deployment-forgejo-runner.yaml.
Two commits: the one-line fix that unbreaks zegit's Gitea CI, and a script so publishing this image stops being a four-obstacle exercise.
## `git-daemon`
Alpine splits `git-http-backend` **out of** the `git` package — `git` alone ships `git-remote-http/https` but not the CGI backend. So the runner had git 2.52.0 and no backend, and zegit's `internal/forgeprobe` smart-HTTP tests died with `fork/exec git-http-backend: no such file or directory`, reporting three FAILURES for a missing tool.
That turned **Gitea CI red on every zegit master commit while GitHub stayed green**, for hours, unnoticed — GitHub is the release oracle, which is exactly why a red mirror is invisible.
Verified on the real base image, not `alpine:3`: on `code.forgejo.org/forgejo/runner:12.11.1` (Alpine 3.23), `apk add git git-daemon` puts the backend at `/usr/libexec/git-core/git-http-backend`, and `git --exec-path` resolves there — the exact lookup the test harness performs. So the tests run rather than skip, coverage returns over the 85% floor, and `cover-check` stops failing **without widening any gate**.
## `push-image.sh`
Publishing by hand hit 413 (Cloudflare body cap), a Docker Hub auth refusal (a bare MagicDNS name is parsed as a Docker Hub *namespace*, since a registry host needs a dot or colon), `insufficient_scope` (needs `write:package`), and multi-arch.
The image reference must keep the hostname `gitea.langenoja.se` because that is what the cluster pulls — so the script keeps the NAME and changes only the ROUTE, pinning it to the Istio gateway on the LAN. That gateway serves a `*.langenoja.se` wildcard cert, so TLS validates normally.
## Assumptions & Decisions
- Gateway IP `10.40.0.10` is a default, overridable via `GATEWAY_IP`. Verified: `GET /v2/` → 401 with `ssl_verify_result=0`.
- The pin carries a marker comment so a leftover (run died before its trap) is distinguishable from a deliberate one. Without it, a leftover survived every later run and had to be reverted by hand — which happened. `--unpin` clears one; an unmarked pin is never touched.
- Rejected: the Tailscale route. `gitea-tailscale-http` exposes port 3000 **plain HTTP**, nothing on 443, so it cannot serve a correctly-named registry push.
- Rejected: `insecure-registries`. Unnecessary once the gateway's real cert is used, and it would weaken every pull from that host.
- Multi-arch is not optional — `forgejo-runner` is on arm64, `zegit-runner` on amd64, neither sets a `nodeSelector`.
- Not done here: in-cluster CI for this build. It needs buildah (the runners are host-mode, no docker daemon) and arch-labelled runners that do not exist yet.
After merge: `./push-image.sh 12.11.1-ci4`, then bump `-ci3` → `-ci4` in flux-config's `deployment-zegit-runner.yaml` and `deployment-forgejo-runner.yaml`.
Alpine splits git-http-backend OUT of the `git` package: git alone ships
git-remote-http/https but not the CGI backend. The runner therefore had git
2.52.0 and no git-http-backend, so zegit's internal/forgeprobe smart-HTTP tests
failed at request time with "fork/exec git-http-backend: no such file or
directory" and reported three FAILURES for a missing tool.
That turned Gitea CI red on every zegit master commit while GitHub stayed
green, for hours, with nobody looking — GitHub is the release oracle, which is
exactly why a red mirror is invisible.
Verified against the real base image, not just alpine:3 — on
code.forgejo.org/forgejo/runner:12.11.1 (Alpine 3.23), `apk add git git-daemon`
puts git-http-backend at /usr/libexec/git-core/git-http-backend, and
`git --exec-path` resolves to that directory, which is the lookup the test
harness performs.
Publishing this image by hand hit four obstacles in a row, each of which
recurs on every rebuild:
413 Payload Too Large gitea.langenoja.se is behind Cloudflare, which
caps request bodies below a registry layer
Docker Hub auth refusal the bare MagicDNS name was parsed as a Docker Hub
NAMESPACE — a registry host is only recognised
when it contains a dot or a colon
insufficient_scope the PAT needs write:package, not read:package
wrong-arch pulls forgejo-runner runs on arm64 and zegit-runner on
amd64, neither pins a nodeSelector, and both use
this one tag
The image reference must keep the hostname gitea.langenoja.se, because that is
what the cluster pulls. So the script keeps the NAME and changes only the
ROUTE: it pins that hostname to the Istio gateway on the LAN for the duration
of the push. The gateway serves a *.langenoja.se wildcard cert, so TLS
validates normally — no insecure-registries needed.
The pin carries a marker comment, which makes three cases distinguishable: a
leftover from a run that died before its trap fired (reuse, then remove), a
pin the operator added deliberately (never touch), and one pointing somewhere
other than the gateway (refuse to run). Without the marker a leftover pin was
indistinguishable from a deliberate one, so it survived every later run and
had to be reverted by hand. `--unpin` clears a leftover without building, and
the trap covers INT and TERM as well as EXIT.
It also verifies the published manifest really lists two architectures rather
than trusting the push's exit code.
markus
merged commit 8c356b7e95 into master2026-08-20 12:25:30 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Two commits: the one-line fix that unbreaks zegit's Gitea CI, and a script so publishing this image stops being a four-obstacle exercise.
git-daemonAlpine splits
git-http-backendout of thegitpackage —gitalone shipsgit-remote-http/httpsbut not the CGI backend. So the runner had git 2.52.0 and no backend, and zegit'sinternal/forgeprobesmart-HTTP tests died withfork/exec git-http-backend: no such file or directory, reporting three FAILURES for a missing tool.That turned Gitea CI red on every zegit master commit while GitHub stayed green, for hours, unnoticed — GitHub is the release oracle, which is exactly why a red mirror is invisible.
Verified on the real base image, not
alpine:3: oncode.forgejo.org/forgejo/runner:12.11.1(Alpine 3.23),apk add git git-daemonputs the backend at/usr/libexec/git-core/git-http-backend, andgit --exec-pathresolves there — the exact lookup the test harness performs. So the tests run rather than skip, coverage returns over the 85% floor, andcover-checkstops failing without widening any gate.push-image.shPublishing by hand hit 413 (Cloudflare body cap), a Docker Hub auth refusal (a bare MagicDNS name is parsed as a Docker Hub namespace, since a registry host needs a dot or colon),
insufficient_scope(needswrite:package), and multi-arch.The image reference must keep the hostname
gitea.langenoja.sebecause that is what the cluster pulls — so the script keeps the NAME and changes only the ROUTE, pinning it to the Istio gateway on the LAN. That gateway serves a*.langenoja.sewildcard cert, so TLS validates normally.Assumptions & Decisions
10.40.0.10is a default, overridable viaGATEWAY_IP. Verified:GET /v2/→ 401 withssl_verify_result=0.--unpinclears one; an unmarked pin is never touched.gitea-tailscale-httpexposes port 3000 plain HTTP, nothing on 443, so it cannot serve a correctly-named registry push.insecure-registries. Unnecessary once the gateway's real cert is used, and it would weaken every pull from that host.forgejo-runneris on arm64,zegit-runneron amd64, neither sets anodeSelector.After merge:
./push-image.sh 12.11.1-ci4, then bump-ci3→-ci4in flux-config'sdeployment-zegit-runner.yamlanddeployment-forgejo-runner.yaml.Publishing this image by hand hit four obstacles in a row, each of which recurs on every rebuild: 413 Payload Too Large gitea.langenoja.se is behind Cloudflare, which caps request bodies below a registry layer Docker Hub auth refusal the bare MagicDNS name was parsed as a Docker Hub NAMESPACE — a registry host is only recognised when it contains a dot or a colon insufficient_scope the PAT needs write:package, not read:package wrong-arch pulls forgejo-runner runs on arm64 and zegit-runner on amd64, neither pins a nodeSelector, and both use this one tag The image reference must keep the hostname gitea.langenoja.se, because that is what the cluster pulls. So the script keeps the NAME and changes only the ROUTE: it pins that hostname to the Istio gateway on the LAN for the duration of the push. The gateway serves a *.langenoja.se wildcard cert, so TLS validates normally — no insecure-registries needed. The pin carries a marker comment, which makes three cases distinguishable: a leftover from a run that died before its trap fired (reuse, then remove), a pin the operator added deliberately (never touch), and one pointing somewhere other than the gateway (refuse to run). Without the marker a leftover pin was indistinguishable from a deliberate one, so it survived every later run and had to be reverted by hand. `--unpin` clears a leftover without building, and the trap covers INT and TERM as well as EXIT. It also verifies the published manifest really lists two architectures rather than trusting the push's exit code.