Compare commits

...
Author SHA1 Message Date
agent b38f4760e7 ci: read the org-scoped ZEGIT_CI_PACKAGE_TOKEN, not a repo-local name
The publish path failed at registry login on every dispatch (runs 18,
20) because it read `secrets.ZEGIT_PACKAGE_TOKEN`, a name that exists
nowhere. The package-write token every zegit-zoo image workflow uses is
ZEGIT_CI_PACKAGE_TOKEN, held above repository scope, so this repository
can read it under that name — no second token, no second copy to rotate.
2026-09-08 09:05:43 +02:00
markus 02cd70092e ci: build without an exporter on validation runs; assert chromium and psql (#11)
build image (gitea) / build (push) Successful in 7m34s
2026-09-08 06:23:51 +00:00
agent c11e2b19f8 ci: build without an exporter on validation runs; assert chromium and psql
buildctl v0.32 has no "cacheonly" exporter, so every push-triggered
validation build since this workflow was added failed with 'exporter
"cacheonly" could not be found' (run 16, 2026-09-08) — unnoticed, since
nothing depended on it. A build with no --output still builds every
stage on both architectures. The post-publish toolchain assertion now
also requires chromium-browser and psql (12.11.1-ci7).
2026-09-08 08:08:17 +02:00
markus cada2273bb Merge pull request 'build: chromium, font-noto and postgresql-client for the browser gates' (#10) from claude/build/ci7-browser-psql into master
build image / build (push) Skipped
build image (gitea) / build (push) Failing after 9s
Reviewed-on: #10
2026-09-08 05:52:37 +00:00
agent 7052b41f5e build: chromium, font-noto and postgresql-client for the browser gates
zegit's `browser tests` (chromedp + CDP virtual authenticator) and
`lighthouse` jobs were github.com-only, so when `ci` was routed to this
runner on 2026-08-31 they stopped running anywhere. Chromium 149 (Alpine
3.23) with font-noto lets both run here; psql lets the lighthouse job
create and drop its own database on ci-postgres instead of migrating the
shared maintenance database from under the integration suite's template.

Verified locally (docker build, amd64): chromium-browser 149.0.7827.53,
psql 18.6, 45 font files, node 24.18.1, still uid 1000.
2026-09-08 07:51:10 +02:00
markus ad19f03297 Merge pull request 'chore(ci): assert the busybox/coreutils layer, and record the derived audit' (#9) from claude/chore/derive-toolchain-assertion into master
build image / build (push) Skipped
Reviewed-on: #9
2026-08-31 19:00:56 +00:00
agent 47d5342dc4 chore(ci): assert the busybox/coreutils layer, and record the derived audit
The toolchain check's own comment said the quiet part: "the list only covers what
someone remembered to add". So the list was derived instead of remembered — every
command invoked from a `run:` block across zegit's Gitea-executed workflows and
composite actions was extracted and checked against this image.

Result: no missing apk package. Everything resolves to one of three sources —
baked in here, provided by busybox/coreutils, or downloaded per-run and
immediately version-checked (go via setup-go, buildctl, flux, cosign, syft,
goreleaser). The download path needs nothing from this image beyond curl and tar,
both already asserted.

What the audit did find is that the busybox/coreutils layer was entirely
unasserted. Dropping `coreutils` from the apk line would pass this check while
base64, mktemp, seq and sort silently vanished — and on this repo's own history,
their absence would be narrated as whatever the gate reaching for them happened
to be doing, not as a missing tool. That is how openssl became "signature FAILED
verification" and jq became "not a signed DSSE envelope".

Verified by executing the assertion, not by reading it: extracted the inner
script and ran it against a PATH with base64 deliberately absent. It printed
base64=MISSING and the outer `grep -q MISSING` failed the build. Against a full
PATH it reports zero MISSING.
2026-08-31 20:53:32 +02:00
markus 48d5ccebdc Merge pull request 'fix: combine per-arch tags with imagetools, not docker manifest' (#8) from claude/fix/push-image-manifest-list into master
Reviewed-on: #8
2026-08-31 12:46:48 +00:00
agent 30dabb5056 fix: combine per-arch tags with imagetools, not docker manifest
docker manifest create refuses a source that is itself a manifest list:
  ERROR: ...:<tag>-amd64 is a manifest list
With Docker containerd image store, docker build --platform linux/amd64
produces an OCI index rather than a plain manifest, so BOTH per-arch tags are
lists and the combine fails — after the slow multi-hundred-MB pushes have
already succeeded. The failure mode is nasty: the per-arch tags are published
and the PLAIN tag, the one both Deployments reference, is absent. Hit on
12.11.1-ci6, 2026-08-31.

buildx imagetools create accepts indexes as sources and combines registry-side,
so it works with either image store. Verification switches to imagetools
inspect and now asserts both linux/amd64 and linux/arm64 specifically, rather
than counting two distinct architecture strings.
2026-08-31 14:44:59 +02:00
4 changed files with 73 additions and 17 deletions

No files matched your search

+17 -10
View File
@@ -90,11 +90,11 @@ jobs:
- name: Registry login
if: ${{ steps.meta.outputs.push == 'true' }}
env:
TOKEN: ${{ secrets.ZEGIT_PACKAGE_TOKEN }}
TOKEN: ${{ secrets.ZEGIT_CI_PACKAGE_TOKEN }}
run: |
set -euo pipefail
if [ -z "${TOKEN:-}" ]; then
echo "::error::ZEGIT_PACKAGE_TOKEN is not set, so this dispatch would build an image and push nothing. Issue a package-write token and store it as this secret."
echo "::error::ZEGIT_CI_PACKAGE_TOKEN is not visible to this run. It is the org-scoped package-write token every zegit-zoo image workflow uses (docs: flux-config docs/rotation/forge-secrets.yaml); this repository must be able to read the org secret, and the name must match."
exit 1
fi
mkdir -p "$HOME/.docker"
@@ -106,17 +106,25 @@ jobs:
run: |
set -euo pipefail
REF="${REGISTRY}/${IMAGE_PATH}:${{ steps.meta.outputs.tag }}"
# No --output on a validation run: buildctl v0.32 has no "cacheonly"
# exporter (run 16, 2026-09-08: 'exporter "cacheonly" could not be
# found'), so every push-triggered validation since this workflow
# was added had failed — silently, since nothing depended on it. A
# build without an exporter still builds every stage on both arches.
if [ "${{ steps.meta.outputs.push }}" = "true" ]; then
out="type=image,name=${REF},push=true"
else
out="type=cacheonly"
fi
buildctl build --progress=plain \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=. \
--opt platform=linux/amd64,linux/arm64 \
--output "$out"
--output "type=image,name=${REF},push=true"
else
buildctl build --progress=plain \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=. \
--opt platform=linux/amd64,linux/arm64
fi
# The image is a TOOLCHAIN, so "it built" is not the property that matters
# — "it still contains what the gates shell out to" is. build-image.yml
@@ -142,7 +150,7 @@ jobs:
echo "FROM ${REF}"
echo 'RUN set -eu; \'
echo ' python3 -c "import yaml, sys; print(\"python3+yaml\", sys.version.split()[0])"; \'
echo ' for t in gcc node bash make curl tar openssl jq; do \'
echo ' for t in gcc node bash make curl tar openssl jq chromium-browser psql; do \'
echo ' command -v "$t" >/dev/null || { echo "MISSING: $t" >&2; exit 1; }; \'
echo ' echo "ok: $t"; \'
echo ' done; \'
@@ -154,8 +162,7 @@ jobs:
--frontend dockerfile.v0 \
--local context="$work" \
--local dockerfile="$work" \
--opt platform=linux/amd64,linux/arm64 \
--output type=cacheonly
--opt platform=linux/amd64,linux/arm64
echo "✓ toolchain intact on linux/amd64 and linux/arm64"
- name: Summary
+21
View File
@@ -109,6 +109,22 @@ jobs:
# missing tool, blocking v0.0.134 and v0.0.135. The list only covers what
# someone remembered to add, so add to it whenever a gate grows a new
# shell dependency.
#
# DERIVED once, 2026-08-31, rather than remembered: every command invoked
# from a `run:` block across zegit's Gitea-executed workflows and its
# composite actions was extracted and checked against this list. Result:
# no missing apk package. Everything resolved to one of three sources —
# baked in here; provided by busybox/coreutils; or downloaded per-run and
# immediately version-checked (go via setup-go, buildctl, flux, cosign,
# syft, goreleaser). The download path needs nothing from this image
# beyond curl and tar, both already asserted.
#
# What the audit DID find is that the busybox/coreutils layer was entirely
# unasserted, so dropping `coreutils` from the apk line above would pass
# this check while base64, mktemp, seq and sort silently vanished — and by
# the history above, their absence would be narrated as whatever the gate
# using them happened to be doing. They are asserted below now. They are
# cheap to check and the failure they prevent is expensive to read.
- name: Verify the image still contains the toolchain the gates need
if: ${{ steps.meta.outputs.push == 'true' }}
run: |
@@ -129,6 +145,11 @@ jobs:
command -v tar >/dev/null && echo "tar=ok" || echo "tar=MISSING"
command -v openssl >/dev/null && echo "openssl=ok" || echo "openssl=MISSING"
command -v jq >/dev/null && echo "jq=ok" || echo "jq=MISSING"
# busybox/coreutils layer: used across zegit's gates for base64
# decoding of DSSE payloads, temp dirs, poll loops and sorting.
for t in base64 mktemp seq sort sed grep cut head wc uname date; do
command -v "$t" >/dev/null && echo "$t=ok" || echo "$t=MISSING"
done
[ -x "$(git --exec-path)/git-http-backend" ] \
&& echo "git-http-backend=ok" || echo "git-http-backend=MISSING"
' 2>&1) || { echo "::error::could not run $REF on $plat"; fail=1; continue; }
+17 -1
View File
@@ -58,6 +58,19 @@ USER root
# validate-admission.sh and feature-lint.sh all shell out to
# python3, and credentials-coverage parses manifests with yaml
#
# chromium, font-noto -> the browser gates (2026-09-08): zegit's `browser
# tests` (chromedp + a CDP virtual authenticator over the WebAuthn
# ceremony pages) and `lighthouse` (treosh/lighthouse-ci-action
# via chrome-launcher) were github.com-only jobs, so when `ci`
# was routed to this runner on 2026-08-31 they stopped running
# anywhere. chromedp finds `chromium`/`chromium-browser` on PATH;
# chrome-launcher honours CHROME_PATH. font-noto so text renders
# (an empty font set makes layout/contrast audits meaningless).
# postgresql-client -> `psql`, so the lighthouse job can CREATE/DROP its own
# database on the runner's ci-postgres (ZEGIT_TESTPG_DSN) instead
# of migrating the shared maintenance database from under the
# integration suite's template.
#
# Go is intentionally NOT baked in: actions/setup-go fetches the pinned version
# per-run (node + tar are present), so the image needs no rebuild on Go bumps.
RUN apk add --no-cache \
@@ -75,7 +88,10 @@ RUN apk add --no-cache \
python3 \
py3-yaml \
openssl \
jq
jq \
chromium \
font-noto \
postgresql-client
# Match the runner pod's runAsUser (1000); keep the image non-root by default.
USER 1000
+18 -6
View File
@@ -99,15 +99,27 @@ for arch in amd64 arm64; do
docker push "$REPO:$TAG-$arch" || die "push failed for $arch"
done
docker manifest rm "$REPO:$TAG" >/dev/null 2>&1 || true
docker manifest create "$REPO:$TAG" --amend "$REPO:$TAG-amd64" --amend "$REPO:$TAG-arm64"
docker manifest push "$REPO:$TAG"
# `docker manifest create` REFUSES a source that is itself a manifest list:
# ERROR: gitea.langenoja.se/...:<tag>-amd64 is a manifest list
# With Docker's containerd image store, `docker build --platform linux/amd64`
# produces an OCI index rather than a plain manifest, so both per-arch tags are
# lists and the combine step fails after the (slow, large) pushes have already
# succeeded — leaving the per-arch tags published and the plain tag absent,
# which is the one the Deployments reference. Seen on 12.11.1-ci6, 2026-08-31.
#
# `buildx imagetools create` accepts indexes as sources and does the combine
# registry-side, so it works with either image store.
docker buildx imagetools create -t "$REPO:$TAG" "$REPO:$TAG-amd64" "$REPO:$TAG-arm64" \
|| die "failed to combine the per-arch tags into $REPO:$TAG"
# Verify by reading it back, rather than trusting the push's exit code.
echo "verifying the published manifest lists both architectures"
got=$(docker manifest inspect "$REPO:$TAG" | grep -o '"architecture": *"[a-z0-9]*"' | sort -u | wc -l)
[ "$got" -ge 2 ] || die "published manifest does not list two architectures"
docker manifest inspect "$REPO:$TAG" | grep -o '"architecture": *"[a-z0-9]*"' | sort -u
inspect=$(docker buildx imagetools inspect "$REPO:$TAG") \
|| die "published $REPO:$TAG cannot be read back"
got=$(echo "$inspect" | grep -o 'linux/[a-z0-9]*' | sort -u | grep -cE 'linux/(amd64|arm64)')
[ "$got" -ge 2 ] || die "published manifest does not list both architectures:
$inspect"
echo "$inspect" | grep -o 'linux/[a-z0-9]*' | sort -u
cat <<EOF