Commit Graph
18 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5 0f0492aa59 perf(images): build each architecture natively and publish one tag per distro
Building every architecture in one job through QEMU took the image job
from under ten minutes to over two hours: each arm64 layer after
'COPY .' reran emulated on every commit, on a four-vCPU runner.

Each architecture builds on its own runner again, but push.sh now
pushes by digest only and records the digest per distro and
architecture, so no per-architecture tag exists. A merge job collects
every architecture's digests and manifest_all.sh publishes the plain
tag as one manifest list, refusing a distro that lacks a digest for any
architecture in the pool. The build cache is per architecture again so
the two jobs do not overwrite each other's cache. The binfmt mirror
entry goes with the emulation, and ADR 0002 records the measured
reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 23:04:22 +02:00
kevinveenbirkenbachandClaude Opus 5 270e75d080 feat(images): build every architecture of a distro in one job and one tag
The CI images were built by one job per architecture, each pushing a
<tag>-<arch> tag, and a third job merged them into the plain tag. One
job on an amd64 runner now registers QEMU through a pinned
tonistiigi/binfmt and hands the whole architecture pool to a single
buildx --platform build, so each distro gets exactly one tag, a
manifest list from the start, and one build cache.

binfmt installs with 'all' because that list leaves out the native
architecture; naming amd64 explicitly on an amd64 host would route the
host's own binaries through QEMU. The registry assertion moves into
the build job, manifest_all.sh goes, and the cleanup stops protecting
per-architecture children that no longer exist. ADR 0002 records the
changed decision.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 20:06:03 +02:00
kevinveenbirkenbachandClaude Opus 5 3f973eb137 fix(images): resolve the base image per distro, not once for the default
Every distribution built FROM `base-debian`, so the arch, ubuntu, fedora and
centos images were all asking the registry for the debian parent.

push.sh exports INFINITO_DISTRO from MATRIX_DISTRO and then sources the env
layer, which is correct as far as it goes: load.sh snapshots caller-set values
and restores them, so the distro survives. INFINITO_PARENT_IMAGE is not set by
the caller, though, and arrives already materialised in .env, rendered for
whichever distro generated that file. Setting the distro in a loop therefore
never reaches the reference derived from it.

The new resolver renders it through utils.distros.base_image() rather than
composing the string, because test_distros_spot rejects an image reference
built anywhere but the SPOT. It sits beside local.sh, ci.sh and service_ref.sh,
which resolve the other image names the same way.

Verified: arch, fedora and ubuntu now resolve to three different parents where
they previously resolved to one. shellcheck is clean and test_distros_spot
passes, which is what proves the resolver stays inside the SPOT.

Not verified: the build step that calls it. push.sh runs only in CI, and the
base images are not published yet, so a real run still stops at the pull before
it reaches this line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 10:26:26 +02:00
kevinveenbirkenbachandClaude Opus 5 2a7455bd90 feat(ci): spread the deploy matrix over amd64 and arm64
Both architectures were claimed to work and only one was ever exercised. The
deploy matrix now carries an architecture the way it carries mode, tor, distro
and filesystem, so every run proves both.

A job cannot ask for an architecture the way it asks for a distro, because the
architecture is the machine rather than something the job installs on it. The
matrix therefore carries a runner label and `runs-on` reads it, and an
architecture without a label in RUNNERS is a build error rather than a row that
lands on the wrong hardware and still reports green. assert_architecture.sh
checks the runner it actually got, because a label that stops resolving to
arm64 would otherwise go unnoticed forever.

Assignment is rotation, not chance: two architectures against five distros and
fifteen distro/filesystem positions are coprime enough that every combination
comes round. A role narrows the pool through `architectures` in its
meta/services.yml; without one it takes whatever the rotation hands it. An
empty intersection aborts instead of silently removing the role from the run.

Images are built per architecture and pushed under an architecture-suffixed
tag, then merged into the plain tag by manifest_all.sh; pushing the plain tag
from both builds would let the last one win and send half the rows to the wrong
platform. cleanup.sh now protects the `ci-<sha>-<architecture>` children of a
protected tag, because the plain tag is an index over them and deleting a child
leaves it unpullable on that platform.

label.py is split out of axes.py, which the 500-line limit no longer fits.
Writing a job title and reading one back belong together: a retrigger that
recovers an axis wrongly replays a different deploy than the one that failed,
and a drifting artifact name makes the failure reporter download nothing. The
leading `.*` in LABEL_RE is greedy on purpose so it anchors on the last mode
glyph; a reusable-workflow caller path carries a glyph of its own, and
test_selection.py guards the case.

Verified: 313 unit tests and 48 subtests across utils/github/variant,
utils/roles and cli/meta/ci. Locally, a deploy pinned to arm64 reaches ok=1444
and its image builds log aarch64-linux-gnu paths.

Not verified here: actionlint over the seven changed workflows, and a full
`make test`. The matrix doubles the row count, so runtime and runner
availability change for every CI run from here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 07:02:44 +02:00
kevinveenbirkenbachandClaude Opus 5 7b37a7970f fix(image): let a failed cache export fail the build
ignore-error traded a loud fault for a silent one. The cache writes to
the same GHCR repository as the image and under the same packages:write
token, so every shared failure mode -- permissions, outage, rate limit --
already fails --push. What remains is buildkit's own cache manifest type,
which a registry can reject while accepting an ordinary image push.

That case is exactly the one worth hearing about. Swallowed, it leaves CI
green while the cache never fills and every build silently pays the full
855-899s again; raised, the first run answers it once and for good.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 21:04:15 +02:00
kevinveenbirkenbachandClaude Opus 5 ef45d69dc3 perf(image): build the collections into a layer the source cannot invalidate
COPY . preceded every expensive RUN, so each commit rebuilt the whole
chain and ansible-galaxy ran on every build of every distro. The
collection install now sits in its own layer fed only by default.env,
scripts/install, the dev-python shell scripts, requirements/ and the
three files the version check imports. Everything that needs the full
tree, the project's own distro package included, stays behind COPY ..

Measured on a role-only change: the collection layer stays CACHED and
the build drops to 3m10s against a CI baseline of 855-899s. An
unchanged rebuild is 15 cached layers in 1.5s.

The gha cache imported and exported nothing in a plain run step, so the
export moves to the registry the job already authenticates against.
ignore-error keeps a cache write failure from taking the build with it,
which it otherwise does: proven against an unreachable registry, where
the build fails hard without it and exits 0 with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 20:54:49 +02:00
kevinveenbirkenbachandClaude Opus 4.8 9f8aa6e9b6 refactor(env): SPOT the install scripts and derive the nfs export base from group_vars
INFINITO_PYTHON_INSTALL_SCRIPT and INFINITO_DOCKER_CLI_INSTALL_SCRIPT join the default.env SPOT: the Dockerfile consumes them as required args (:? in the RUN as last defense), all three image builders (compose.yml, build.sh, push.sh) pass them hard-required, the shell consumers read the SPOT via default.env grep and the Makefile via BASH_ENV. INFINITO_SWARM_NFS_EXPORT_BASE is no longer a static default: a new env handler derives it from the deploy SPOT group_vars/all/15_storage.yml, 00_topology greps that file (and the STATE_SUBDIR constant) directly, and the swarm test extras stop restating the group_vars value. Stale hadolint directives are restored and recognised; leftover narration comments in the touched files are dropped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 23:04:40 +02:00
kevinveenbirkenbachandClaude Opus 4.7 00c4246c3a refactor(env): introduce INFINITO_VENV_DIR SPOT, fix dev image build
Adds INFINITO_VENV_DIR=/opt/venvs/infinito to default.env (registered
in passthrough's STATIC_KEYS so make dotenv propagates it to .env) and
threads it through the build chain: compose.yml forwards it as a build
arg; scripts/image/push.sh asserts and --build-args it; the Dockerfile
declares the matching ARG/ENV and derives PYTHON/PIP/PATH from the
variable. scripts/meta/env/python.sh now prefers the value when set
before its fallback discovery so the SPOT is effective in env
resolution.

Fixes the dev image build regression where 'infinito --version' in
scripts/docker/entry.sh exited 127. Root cause: the Dockerfile RUN
SHELL uses bash -lc, which sources /etc/profile and resets PATH to
the Debian default, dropping ${INFINITO_VENV_DIR}/bin from it. The
fix invokes the script via its absolute path "${INFINITO_VENV_DIR}/bin/infinito"
so the post-install version check no longer depends on a PATH that
the login-shell init has just clobbered.

scripts/lint/wrapper.sh's existing PATH-reset note now references the
variable instead of the literal path. test-install-make.yml and
test-install-pkgmgr.yml read INFINITO_VENV_DIR from default.env up
front, pass it into their pkgmgr containers, and source the venv
activate script via the variable.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 22:12:19 +02:00
kevinveenbirkenbachandClaude Opus 4.7 183f41845b refactor(env): SPOT pkgmgr base image via handler, drop slim variant
default.env decomposed into INFINITO_PARENT_IMAGE_{OWNER,TAG,VARIANT}
atoms. New utils/env/handlers/infinito_parent_image.py owns the URL
composition `ghcr.io/{owner}/pkgmgr-{distro}:{tag}` as the single
definition site; scripts/image/{push,build}.sh consume the handler-
emitted INFINITO_PARENT_IMAGE from .env instead of inlining their own
format string.

Side effect: `compose up --build` now picks the parent image that
matches INFINITO_DISTRO. Previously default.env's hardcoded
pkgmgr-arch:stable won regardless, so debian-named containers were
silently built FROM the arch base. scripts/image/build.sh already
overrode per-distro, so its behavior is unchanged.

Other cleanups landed in the same pass:
- Slim variant removed end-to-end (Dockerfile stage, --variant CLI
  flag, slim suffix logic). Zero in-repo callers used it.
- compose.yml build-args INFINITO_PARENT_IMAGE / INFINITO_SRC_DIR now
  carry the same `:?Run 'make dotenv'…` guard as the runtime entries,
  so a missing .env fails fast with a clear message instead of with
  buildkit's `base name should not be blank`.

Note: fork-CI (pull_request_target trusted-helpers path) will stay red
until main's scripts/image/push.sh and scripts/meta/wait/images.sh
adopt the INFINITO_* names too — that is the deliberate cost of
keeping a single SPOT instead of carrying a bridge alias.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 10:44:50 +02:00
kevinveenbirkenbachandClaude Opus 4.7 42865145cb fix(image-push): self-source env/load.sh for INFINITO_SRC_DIR
Commit 5e0036dfa added a strict-read `${INFINITO_SRC_DIR:?...}` to the
docker buildx build-arg in push.sh. The CI workflow images-build-ci
invokes push.sh directly without sourcing scripts/meta/env/load.sh, so
the variable was never populated and the run aborted on attempt 1/7
(run 26200267778, job 77088881319). Sourcing load.sh inline keeps push.sh
self-sufficient regardless of caller and matches the existing pattern in
the lint/install/test wrappers under scripts/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 04:01:39 +02:00
kevinveenbirkenbachandClaude Opus 4.7 5e0036dfa9 fix(image-build): pass INFINITO_SRC_DIR as build-arg
Commit a77a57040 dropped the inline default from the Dockerfile's
INFINITO_SRC_DIR ARG so env/default.env stays the only SPOT. The
compose-driven build path already forwards the value via
compose.yml's build.args, but the standalone scripts/image/{build,push}.sh
paths (local `make build`, CI `🏗️ Build (<distro>)`) had not been
updated. Without --build-arg INFINITO_SRC_DIR=..., the ARG resolves
to empty inside the image, so `RUN /bin/bash ${INFINITO_SRC_DIR}/roles/...`
becomes `/bin/bash /roles/...` and exits 127.

Both scripts now assert INFINITO_SRC_DIR (sourced from
scripts/meta/env/load.sh) and pass it through.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 02:55:23 +02:00
kevinveenbirkenbachandClaude Opus 4.7 a77a570409 refactor(env): consolidate INFINITO_* namespace and centralize path/image SPOTs
Closes a stack of env-pipeline regressions left over from the recent
INFINITO_* namespace refactor by:

* DOMAIN_PRIMARY now reads INFINITO_DOMAIN strictly (was: legacy DOMAIN
  env var → defaulted to infinito.localhost, which never resolved
  because CoreDNS serves the INFINITO_DOMAIN value, infinito.example).
* group_vars/all/08_networks.yml IPs (ip4/ip6/dns) now read
  INFINITO_IP4 / INFINITO_IP6 / INFINITO_DNS_IP.
* PLAYWRIGHT_KEEP_ALL renamed to INFINITO_PLAYWRIGHT_KEEP end-to-end
  (env-builder, role default, run_one.yml docker exec, playwright.config.js,
  lint allowlist, docs anchor, CI workflow vars).
* PKGMGR_IMAGE renamed to INFINITO_PARENT_IMAGE; Dockerfile ARG drops
  the inline default so env/default.env is the only SPOT.
* INFINITO_SRC_DIR centralized: Dockerfile ARG (no default) + ENV;
  compose forwards it as build-arg, volume mount path, and container env;
  every Python CLI + shell helper + CI workflow now reads it strictly
  (os.environ[...] / "${INFINITO_SRC_DIR:?...}") instead of the literal
  /opt/src/infinito.
* New entries in env/default.env: INFINITO_IP6=::1, INFINITO_PARENT_IMAGE,
  INFINITO_PLAYWRIGHT_KEEP=false. All three propagated through
  utils/env/handlers/passthrough.STATIC_KEYS and (where consumed in the
  container) through compose.yml's environment block.

Validated end-to-end with `make test` (2360 tests across lint, lint-*,
test-lint, test-external, test-unit, test-integration).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 02:30:34 +02:00
kevinveenbirkenbach 003a3a0b84 Merge branch 'main' into feature/lint
Resolve merge conflicts, keep the cleanup script move to scripts/image, and fix the resulting lint regressions.
2026-03-21 01:36:58 +01:00
kevinveenbirkenbach 0466359631 Merge branch 'main' into feature/lint
# Conflicts:
#	scripts/image/push.sh
#	scripts/meta/env.sh
#	scripts/meta/wait/mirrors.sh
#	scripts/tests/deploy/ci/all_distros.sh
2026-03-19 03:21:31 +01:00
kevinveenbirkenbach 292feaa5da Add highest missing release workflow 2026-03-18 18:01:49 +01:00
kevinveenbirkenbach a715970506 Refactor lint tooling and shell scripts 2026-03-18 12:37:31 +01:00
kevinveenbirkenbach 67d17d8488 Merge branch 'main' into feature/web-app-mattermost
# Conflicts:
#	scripts/ci/wait_prebuilt_ci_image.sh
#	scripts/meta/wait/mirrors.sh
2026-03-18 11:33:27 +01:00
kevinveenbirkenbach ca1bf7832e Reorganize image scripts and PR wait logic 2026-03-18 11:26:12 +01:00