Compare commits

..
Author SHA1 Message Date
agent 6b6743113f docs: correct the registry notes in the Dockerfile header
Two things were wrong, and the second actively misled.

The publish instructions described pushing to the Gitea registry by hand.
Publishing now runs on GitHub Actions to ghcr.io, because Cloudflare's
request-body cap returns 413 on a registry layer and our own runners cannot
build images (host mode, all capabilities dropped). The example tag had also
been mangled to "12.11.1-ci".

"Package visibility is PUBLIC, so the runner namespace pulls without a secret"
was false. The Gitea package is private — flux-config's kustomization says so
in as many words — and the runner Deployments have always carried a pull
secret. Reading that line led me to conclude the ghcr package would need no
credential; it does, and forgejo-runner-system now holds
github-registry-credentials for exactly that.

Verified with `docker build --check`: no warnings.
2026-08-20 15:42:26 +02:00
markus f2b9661be2 Merge pull request 'ci: build and publish the image on GitHub to ghcr.io' (#2) from claude/ci/build-on-github into master
build image / build (push) Skipped
Reviewed-on: #2
2026-08-20 12:46:03 +00:00
agent f0922508a8 ci: build and publish the image on GitHub to ghcr.io
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.
2026-08-20 14:44:59 +02:00
markus 8c356b7e95 Merge pull request 'fix: add git-daemon so git-http-backend exists, plus a publish script' (#1) from claude/fix/git-http-backend into master
Reviewed-on: #1
2026-08-20 12:25:29 +00:00
3 changed files with 126 additions and 7 deletions

No files matched your search

+93
View File
@@ -0,0 +1,93 @@
name: build image
# Publishes the runner toolchain image to ghcr.io.
#
# Why GitHub rather than our own Gitea Actions: the runners are host mode
# (`ubuntu-latest:host`, no job containers) and their container drops ALL
# capabilities with allowPrivilegeEscalation:false. Rootless buildah needs
# newuidmap/user namespaces for its RUN steps, so building images there would
# mean weakening the posture that hardening established. GitHub runners have
# docker + buildx + QEMU already.
#
# Why ghcr.io rather than the Gitea registry: gitea.langenoja.se sits behind
# Cloudflare, whose request-body cap returns 413 on a registry layer. The
# cluster already pulls zegit's own images from ghcr.io and the runner
# deployments already carry a github-registry-credentials pull secret, so this
# reuses an established, trusted path instead of inventing one.
on:
workflow_dispatch:
inputs:
tag:
description: 'Image tag, e.g. 12.11.1-ci4 (upstream runner version + ci revision)'
required: true
type: string
push:
branches: [master]
paths:
- 'Dockerfile'
- '.github/workflows/build-image.yml'
permissions:
contents: read
packages: write
env:
IMAGE: ghcr.io/${{ github.repository }}
jobs:
build:
# GitHub-only: the Gitea mirror has no ghcr.io and no GITHUB_TOKEN.
if: ${{ github.server_url == 'https://github.com' }}
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
# arm64 is not optional: forgejo-runner runs on arm64 nodes and
# zegit-runner on amd64, neither deployment sets a nodeSelector, and both
# use this one tag.
- uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# A master push VALIDATES the Dockerfile still builds on both arches but
# publishes nothing — only an explicit dispatch names a tag and pushes.
# Guessing a tag from a branch push would silently mint versions.
- id: meta
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "push=true" >> "$GITHUB_OUTPUT"
echo "tags=${IMAGE}:${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
else
echo "push=false" >> "$GITHUB_OUTPUT"
echo "tags=${IMAGE}:ci-validation" >> "$GITHUB_OUTPUT"
fi
- uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ steps.meta.outputs.push }}
tags: ${{ steps.meta.outputs.tags }}
provenance: false
sbom: false
# Read the published index back rather than trusting the push's exit
# code, and fail if either architecture is absent — a single-arch image
# under this tag would CrashLoop on half the nodes.
- name: Verify both architectures were published
if: ${{ steps.meta.outputs.push == 'true' }}
run: |
set -euo pipefail
out=$(docker buildx imagetools inspect "${IMAGE}:${{ inputs.tag }}")
echo "$out"
for want in amd64 arm64; do
echo "$out" | grep -q "linux/$want" || { echo "::error::published image is missing linux/$want"; exit 1; }
done
echo "✓ ${IMAGE}:${{ inputs.tag }} carries linux/amd64 and linux/arm64"
+12 -7
View File
@@ -6,15 +6,20 @@
# The runner pod is non-root with all caps dropped, so it cannot apt/apk-add at
# runtime -- the tools must be baked in here.
#
# Build multi-arch (cluster is mixed amd64/arm64) and push to the Gitea registry:
# PUBLISHING IS AUTOMATED. Run the "build image" workflow on GitHub with a tag
# (e.g. 12.11.1-ci4); it builds both architectures and pushes to
# ghcr.io/zegit-zoo/zegit-forgejo-runner. ./push-image.sh remains for building
# by hand. README.md explains why the registry moved off Gitea (Cloudflare's
# request-body cap returns 413 on a registry layer) and why the build cannot run
# on our own runners (host mode, all capabilities dropped).
#
# 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 .
# Then in flux-config set the image on BOTH deployment-zegit-runner and
# deployment-forgejo-runner, digest-pinned.
#
# Then in flux-config set both containers' image: to the tag above and commit.
# Package visibility is PUBLIC, so the runner namespace pulls without a secret.
# The image is NOT public. Pulls need a registry credential in
# forgejo-runner-system -- github-registry-credentials for ghcr.io. An earlier
# version of this comment claimed the package was public and needed no secret;
# that was wrong for the Gitea registry too, which is private.
FROM code.forgejo.org/forgejo/runner:12.11.1
USER root
+21
View File
@@ -73,3 +73,24 @@ Two traps the script exists to prevent: pushing to the bare MagicDNS name
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.