feat(images): build, verify and publish the three images in CI; Renovate carries the digests
gate / gate (pull_request) Failing after 1m56s
images / images (pull_request) Failing after 6s
gate / conformance (pull_request) Has been skipped

Operator rule 2026-10-29: image creation, hosting and lifecycle belong to CI/CD, never to a
workstation build/push. Nothing had ever been pushed to the Gitea registry.

- .gitea/workflows/images.yaml + scripts/images.sh: PRs build+verify (no credential); main
  builds, verifies and pushes toolbox, agent-runner (FROM toolbox) and conformance-runner as
  sha-<commit> + main, reading the digest back from the registry. Only changed images build.
- Each image's own build-image.sh does the build and in-image verification, now engine-neutral
  (DinD: no podman-only calls, probe over stdin not a bind mount). New toolbox build-image.sh.
- Digest references carry `# renovate-digest:` markers; renovate.json5 gains a custom.regex
  manager for them. The worker Job template is deliberately untracked (it would loop).
- renovate.yaml pins renovate-version 44.167.0: the action's default (39.264.0) rejects the
  current config's managerFilePatterns, so every run would have died at CONFIG_VALIDATION.
- Push credential: REGISTRY_PUSH_TOKEN secret; the Actions token has no package scope in
  Gitea 1.24.3 (container.go Authenticate). Retention: infra/gitea/registry-retention.sh.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
jakman85andClaude Opus 5.5 committed 2026-10-29 21:34:06 -05:00
1 parent 1301af9a4c
commit 0da34ddbc6
24 files changed
+1280 -210

No files matched your search

+14 -7
View File
@@ -45,11 +45,18 @@ waive the sensitive-path human review on its own pull request.
Both are deliberate, and both fail **visibly** rather than silently:
1. **The image does not exist.** `platform/pm-agent/deployment.yaml` and
`PM_WORKER_IMAGE` in `configmap.yaml` carry an all-zero digest placeholder. Build it with
`infra/agent/build-image.sh` (which verifies it and refuses to push), push it as the
operator, then update **both** places with the digest `podman push --digestfile` returned,
in one pull request. Until then: `ImagePullBackOff`.
1. **The image is published by CI, not by a person.** `platform/pm-agent/deployment.yaml` and
`PM_WORKER_IMAGE` in `configmap.yaml` carry an all-zero digest placeholder until the first
publish. `.gitea/workflows/images.yaml` builds `agent-runner` with
`infra/agent/build-image.sh` (the same script, and the same in-image verification, you can
run locally), and on a push to `main` publishes it to
`gitea.home.keithjackson.dev/home-network/agent-runner` under `sha-<commit>` and `main`.
Renovate then follows the `main` tag through the `# renovate-digest:` marker above **both**
lines and moves them together in one pull request. Nobody builds-and-pushes this image from
a workstation, and nobody pastes a digest. Until the first publish, and until that PR
merges: `ImagePullBackOff`. The one operator prerequisite on the publishing side is the
`REGISTRY_PUSH_TOKEN` Actions secret (see the header of `.gitea/workflows/images.yaml`);
without it the publish job fails loudly rather than skipping.
**Two things have to be true before any digest will pull, and neither is declared in this
repository.** Both were measured after PR #57 merged — this service is the first workload
@@ -78,8 +85,8 @@ Both are deliberate, and both fail **visibly** rather than silently:
account whose token carries `read:package`, referenced from the Deployment and the
committed worker Job template — not built yet, because minting that token is yours.
The same two apply to `conformance-runner`: same registry path, same push-then-pin model,
never pulled either.
The same two apply to `conformance-runner`: same registry path, same publish-then-Renovate
model, never pulled either.
2. **The credentials are placeholders.** `pm-agent-credentials.sops.yaml` is encrypted to the
repository's age recipient but holds `REPLACE-ME-…` strings. The hand-off —
which Gitea account to create, which scopes, and why it must *not* be an administrator —
+7 -6
View File
@@ -52,12 +52,13 @@ data:
# contract. A tag here is refused at startup (and would be DENIED by admission anyway).
#
# PLACEHOLDER UNTIL THE IMAGE IS PUBLISHED. The all-zero digest below is deliberately
# impossible: `infra/agent/build-image.sh` builds and verifies the image, the OPERATOR
# pushes it, and the authoritative digest from `--digestfile` replaces this value in a
# reviewed pull request (the same two-step the conformance-runner image follows, and for
# the same reason: a registry credential is not an agent's to hold). Until then the
# dispatcher starts, fails to pull, and says so — which is the visible failure, not a
# silent one.
# impossible. CI builds, verifies and — on main — publishes agent-runner
# (.gitea/workflows/images.yaml), and Renovate replaces this value with the digest the
# REGISTRY returned for the image's `main` tag, in a pull request that also moves the
# Deployment's image (same marker, same branch). Nobody pushes this image from a workstation.
# Until the first publish the dispatcher starts, fails to pull, and says so — which is the
# visible failure, not a silent one.
# renovate-digest: image=gitea.home.keithjackson.dev/home-network/agent-runner tag=main
PM_WORKER_IMAGE: "gitea.home.keithjackson.dev/home-network/agent-runner@sha256:0000000000000000000000000000000000000000000000000000000000000000"
# --- the model -------------------------------------------------------------
+5 -2
View File
@@ -66,9 +66,12 @@ spec:
- name: dispatch
# THE SAME IMAGE THE WORKERS RUN, at the same digest. They share the committed Job
# template and the env-name contract baked into it, so a mismatched pair would
# disagree about the shape the admission policy enforces. Replaced together, in one
# pull request, with the digest `infra/agent/build-image.sh` printed — the all-zero
# disagree about the shape the admission policy enforces. Replaced together, in ONE
# Renovate pull request: both lines carry the same marker, so Renovate treats them as
# one dependency and bumps them in the same branch. The digest comes from CI's publish
# of agent-runner's `main` tag (.gitea/workflows/images.yaml) — the all-zero
# placeholder below is impossible on purpose (see configmap.yaml PM_WORKER_IMAGE).
# renovate-digest: image=gitea.home.keithjackson.dev/home-network/agent-runner tag=main
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:0000000000000000000000000000000000000000000000000000000000000000
imagePullPolicy: IfNotPresent
command: