`INFINITO_PACKAGE_CACHE_MAX_AGE_MIN` was 129600 (90 days), 13× longer
than the 7-day `Valid-Until` window Debian and Ubuntu generate by
default in their apt `Release` files. Nexus held cached `Release`
manifests for 90 days, so once the upstream `Valid-Until` elapsed the
manifest stayed in the cache for another ~83 days and every Docker
build that ran `apt-get update` against the package-cache aborted with
E: Release file for http://deb.debian.org/debian/dists/.../InRelease
is expired (invalid since 5h 30min 54s).
Drop the default to 8640 minutes (6 days). Stays one day below the
7-day `Valid-Until` so Nexus always re-validates the cached `Release`
against upstream before apt would see it expired. The single value
still applies to every proxy repo (apt, yum, pypi, npm, gem, go, helm,
raw-*), but the `Valid-Until` enforcement only exists in apt — for
the other repos the change merely makes the cache 84 days fresher
instead of staler, no regression.
Update both docs sites that document the default:
- docs/contributing/artefact/files/compose.yml.md (env-var table)
- docs/contributing/environment/cache.md (operator narrative)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes 67 live references to docs/requirements/<NNN>-*.md files across
43 contributor docs, role READMEs, code comments, task files, and tests.
Requirement docs are initial-creation-only artefacts; live references
freeze a snapshot that rots once the per-feature SPOT (a contributor
doc, a code module, a lint test) takes over the live behaviour.
In the same pass, fixes documentation.md violations on the touched
files: drops em-dashes used as thought breaks, removes "SPOT for ..."
self-labels, strips historical phrasing ("the legacy", "previously",
"used to", "is no longer used", "matching the historical"), and
reflows soft-wrapped sentences so each sentence stays on one logical
line.
The `test_inventory_vars_file_spot.py` whitelist for the literal
inventory path drops the docs/requirements/003 entry and instead
skips docs/requirements/ entirely; future requirement docs that
mention the literal path are then ignored automatically.
Verified by tests/lint/repository/test_no_stale_requirement_refs.py
(green tree-wide).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Module moves (with imports, paths, argv literals, scripts, docs, and
parents[N] formulas updated in lockstep):
- cli/deploy -> cli/administration/deploy
- cli/validate/inventory -> cli/administration/inventory/validate
- cli/vault -> cli/administration/secrets/vault
- cli/administration/inventory/encrypt -> cli/administration/secrets/seal
- cli/mirror -> cli/contributing/mirror
The new cli/administration/secrets cluster collocates the whole-file
ansible-vault wrapper next to the inventory-aware per-field sealer.
The user-facing CLI subcommand paths follow the package layout, e.g.
"infinito administration deploy dedicated <inv>" and
"infinito administration inventory validate <inv>".
Help system redesign in cli/core/{discovery,help,app}.py:
- Discovery now distinguishes runnable commands (directories with
__main__.py) from category folders (directories that only group
further commands). Empty support packages are filtered out.
- Each category folder MAY carry a README.md; its H1 heading is used
as the title and its first paragraph as the one-line description in
the help output.
- "infinito" / "infinito --help" prints only the top-level entries
(one level deep). "infinito <category>" navigates into a folder and
shows its sub-folders and commands one level deep, with the README
title as the page heading and the invocation as a dim subtitle.
- New "--tree [N]" flag walks the tree under the (optional) path
prefix recursively. N caps the depth (1-based); omit N for an
unbounded walk. The output uses Unicode tree connectors, an emoji
per entry (folder for categories, gear for commands), a globally
aligned description column, and multi-line wrapped descriptions
with tree-continuity bars on continuation lines.
- Category and command listings use the full invocation
("infinito meta callorder") rather than the bare leaf name, so the
output is copy-pasteable.
Convention doc + 17 CLI READMEs:
- New docs/contributing/artefact/files/cli/readme_md.md describes the
H1+paragraph layout the help system reads, and how it differs from
role/docs READMEs.
- README.md added for cli/{administration, build, contributing, meta}
and their immediate sub-folders, plus cli/meta/roles/applications
and its resolution sub-folder. Each follows the "title with trailing
emoji + single descriptive paragraph" convention.
Mirror resolver/sync description strings (the help system reads them
from argparse --help output) added so they no longer surface as
"options:" in the global index. cli/mirror/cleanup already had a
description and is unchanged.
Fixes uncovered by the move:
- import unittest.mock added to four dedicated-deploy tests so
unittest.mock.patch resolves under stdlib unittest discovery
(Python 3.13 no longer surfaces unittest.mock through "import
unittest" alone).
- parents[N] index in the new
cli/administration/inventory/validate/__init__.py corrected from
3 to 4 to match the deeper layout.
- test_paths.INVENTORY_VALIDATOR_PATH assertion and test_runner
substring assertions updated to match the new validator path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extend the local cache stack with three independent capabilities:
1. Inner-`dockerd` build coverage for HTTP-only package mirrors.
`roles/sys-svc-compose/files/compose.py` (the runner's `compose`
wrapper) auto-generates a transient `compose.cache.override.yml`
per app when `INFINITO_PACKAGE_CACHE_FRONTEND_IP` is exported. The
override pins `build.extra_hosts` for `deb.debian.org`,
`archive.ubuntu.com`, and `security.ubuntu.com` to the frontend.
`pull.py` now delegates to the wrapper so build/pull operations see
the same `-f` set the wrapper auto-detects. nginx in the frontend
gains a port-80 listener with path rewrites for those hostnames,
and `package.sh` registers two new Nexus proxies
(`apt-debian-security`, `apt-ubuntu-security`) for the security
suites whose upstream URL prefix differs from the main archive.
Alpine is intentionally excluded from the inner-build extra_hosts:
3.18+ defaults to HTTPS, and inner-`dockerd` build containers do
not import the frontend CA. Routing apk through the frontend would
trip TLS verification.
2. General cache freshness env-var. `scripts/meta/env/cache/package.sh`
exports `INFINITO_PACKAGE_CACHE_MAX_AGE_MIN` (default `129600`,
i.e. 90 days). The bootstrap helper consumes it for every Nexus
proxy's `contentMaxAge`, `metadataMaxAge`, and
`negativeCache.timeToLive`, replacing the previous 1440-minute
literal. Lower the value when chasing fast-moving upstreams.
3. SPOT cache documentation at `docs/contributing/environment/cache.md`.
`compose.yml.md`, `variables.md`, the per-service READMEs, and
`requirements/012-...` now link to it instead of restating the
stack's behaviour. Net documentation lines drop substantially while
the cache page becomes the single user-facing reference.
Also: code-comment cleanup per `docs/contributing/documentation.md`
guidelines across all touched .py / .sh / .yml files. Multi-paragraph
docstrings, banner separators, and restate-the-obvious comments are
removed; one-line WHY comments remain where the rationale is not
already visible in the code.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add a TLS-terminating reverse proxy in front of the Sonatype Nexus 3 OSS
pull-through cache so the `infinito` runner can hit upstream
package-manager URLs (pypi.org, registry.npmjs.org, rubygems.org,
repo.packagist.org, proxy.golang.org, dl-cdn.alpinelinux.org,
files.pythonhosted.org, raw.githubusercontent.com, codeload.github.com)
unchanged and still flow through Nexus.
Mechanism:
* scripts/docker/cache/package-frontend-certs.sh — host-side helper,
idempotently generates a dedicated CA + per-hostname leaf certs.
* compose/package-cache-frontend/upstreams.conf — nginx server-blocks
that match SNI per hostname and reverse-proxy onto the matching
Nexus repo path.
* compose/cache.override.yml `infinito.extra_hosts` — DNS-hijack so
the runner's clients resolve each upstream onto the frontend's
static IP.
* scripts/docker/cache/package-frontend-ca.sh — runner-side helper,
drops the CA into the system trust store post-startup so apt/pip/
curl/etc. accept TLS against the hijacked hostnames.
* cli/deploy/development/compose.py — orchestrates cert gen before
compose-up, CA install after health.
Scope today: runner-only. App-service Dockerfile builds executed by
the inner dockerd are NOT routed through the frontend; that requires
build-time CA injection and is deferred.
Also extends scripts/docker/cache/package.sh with five additional
Nexus proxies registered for transparent use: gem-proxy, go-proxy,
yum-rocky, yum-fedora, raw-packagist, raw-alpine, raw-codeload-github.
Refactor: move all cache services + runner-side cache mounts/depends_on/
extra_hosts from compose.yml into compose/cache.override.yml. Base
compose.yml now describes only the bare CI/runtime stack (coredns +
infinito). The override file is the single gate for the cache stack;
`profiles: ["cache"]` attributes and `--profile cache` flag emission
were redundant once file inclusion controls existence and removed
accordingly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Brings up the package-cache compose service alongside the existing
registry-cache, gated by the same `cache` profile. Bootstrap helper
(scripts/docker/cache/package.sh) auto-rotates the admin password from
the well-known default, accepts the Sonatype CE EULA (3.71+ blocks
artifact downloads with HTTP 403 until /v1/system/eula is POSTed —
disclaimer must be sent verbatim with its unicode quotes), enables
anonymous read, creates one default blobstore, and registers the MVP
proxy set: apt-debian, apt-ubuntu, pypi-proxy, npm-proxy, helm-bitnami,
raw-githubusercontent. The runner gets pip.conf / .npmrc /
sources.list snippets bind-mounted in (each defaulting to /dev/null
when the cache profile is inactive) so the deploy is byte-identical
with cache off.
Cache plumbing:
- scripts/meta/env/cache/{registry,package}.sh: per-cache env defaults,
hostname-derived admin password seed (uses `hostname` command, not
$HOSTNAME shell var, so the value is identical across interactive
shells, non-interactive subshells, and Python subprocesses).
- scripts/docker/cache/{registry-ca,package}.sh: container-side
bootstrap helpers, both moved under scripts/docker/cache/.
- scripts/system/cache/clean.sh + `make cache-clean`: wipes both
/var/cache/infinito/core/cache/{registry,package} dirs and stops
the cache containers.
- cli/deploy/development/compose.py: Profile gate exports the three
new INFINITO_PACKAGE_CACHE_* client-snippet paths and runs the
bootstrap helper after the package-cache reports healthy.
Compose service uses sonatype/nexus3:3.91.1-ubi pinned, runs as
root (UBI image refuses to chown a fresh bind-mount), and forces
NEXUS_SECURITY_RANDOMPASSWORD=false so the bootstrap can log in with
admin/admin123 before rotating; the random-password mode otherwise
auto-completes onboarding silently and leaves no readable secret on
disk for an automated bootstrap to pick up.
Tests: tests/unit/cli/deploy/development/test_compose.py grows two
new assertions (cache services listed in `up` args when local; build
tests pin CI=true to retain ci-only assertions).
Closes req-012.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The body of registry_cache.sh is already idempotent: every assignment
uses `: "${VAR:=default}"`, the `df`-based size calculation is gated
on `[[ -z "${INFINITO_REGISTRY_CACHE_MAX_SIZE:-}" ]]`, and re-sourcing
re-evaluates the same values to the same result. The
INFINITO_ENV_REGISTRY_CACHE_LOADED guard was pattern-mimicry from
defaults.sh / runtime.sh / etc., where it serves a real purpose
(skipping non-trivial init blocks). Here it skipped a no-op and just
added noise to the env-var namespace.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The three INFINITO_REGISTRY_CACHE_* variables previously carried their
defaults in compose.yml as `${VAR:-...}`, which baked /tmp paths and
a static 32g cache size into the YAML. Move the defaults into a new
shell module that is sourced via `scripts/meta/env/all.sh` and tighten
compose.yml to require the variables strictly via `${VAR:?...}`.
Why
* Adapt cache size to the host. Hard-coding 32g hurts hosts where
`/tmp` is a small tmpfs (typical Linux: ~half of RAM); the cache
would either be capped above the partition or starve other tmpfs
users. Computing the default as half the free disk space at the
cache path leaves a usable budget for the rest of the system on
small hosts and grows on large ones.
* Single source of values. With defaults in shell, the compose YAML
no longer carries duplicated tunables; `compose.yml` just consumes
what the env layer provides. Same pattern as the existing
INFINITO_CONTAINER strict consumption.
Changes
* New scripts/meta/env/registry_cache.sh exports the three vars.
CACHE_MAX_SIZE = `df --output=avail -B1G "$path" / 2`, walking up to
the closest existing ancestor when the cache path itself does not
exist yet (it is created on first start by `bind:
create_host_path: true`). Minimum 1g, fallback 2g if `df` fails.
* scripts/meta/env/all.sh sources the new module so the Makefile-driven
flow picks it up automatically.
* compose.yml: three `${VAR:-default}` -> `${VAR:?Source scripts/meta/env/registry_cache.sh ...}`
so the absence of the env layer fails fast with a precise message.
* Stale block comment above the registry-cache service (CI-only,
/tmp-defaults, etc.) collapsed to a one-line pointer to the README,
per documentation.md "default to no comments / explain only the
non-obvious why".
* docs/contributing/environment/variables.md and
docs/contributing/artefact/files/compose.yml.md describe the new
defaults source and the half-free-disk computation.
Verified
`bash -c 'source scripts/meta/env/all.sh && docker compose --profile ci --profile cache config'`
resolves the three vars (CACHE_MAX_SIZE: 3g on a host with 7G free
in /tmp). Without sourcing, compose fails with the exact
"Source scripts/meta/env/registry_cache.sh ..." message.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related improvements anchored on the same principle: the env var
INFINITO_DISTRO is the single source of truth for the development distro,
and INFINITO_CONTAINER is purely derived from it.
1. Fix the matrix-deploy purge failure (CI run 24942172018, job 73038526513,
web-app-wordpress @ fedora):
scripts/meta/env/defaults.sh eager-exported INFINITO_CONTAINER once
inside its load-once guard; ci/all.sh's iteration loop re-exported
INFINITO_DISTRO per round but the cached INFINITO_CONTAINER stayed at
the initial-default infinito_nexus_debian, so _purge_app_entities()
between rounds tried to docker exec into a container from the wrong
distro and failed with "No such container".
Move the INFINITO_CONTAINER derivation OUTSIDE the load-once guard so
every re-source picks up a CONTAINER name in lock-step with the current
INFINITO_DISTRO. ci/all.sh now re-sources defaults.sh after each
export INFINITO_DISTRO=\$distro to trigger that re-derivation. defaults.sh
stays the single spot that owns the formula.
2. Retire the --distro CLI arg from cli.deploy.development {up,down,stop,
restart,init,deploy,exec,logs,build}. The arg duplicated INFINITO_DISTRO
(its default already read from the env), creating a hidden second source
of truth that drifted from the env across matrix iterations. Replace it
with cli.deploy.development.common.resolve_distro() which reads
INFINITO_DISTRO strictly (raises SystemExit on unset/invalid), and
simplify make_compose() to take no args. All bash callers
(scripts/tests/deploy/{ci,local}/**, Makefile restart target) drop the
redundant --distro \"\${INFINITO_DISTRO}\" pass-through.
Verified: make test passes (1553 unit + 168 integration). The mechanism of
the WP fix is verified by a synthetic distro-cycling test of defaults.sh
re-derivation; the original CI multi-distro repro can only be confirmed
end-to-end by the next CI push (local single-distro deploys do not exercise
the stale-env path).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Playwright E2E gate previously fired through `MODE_CI`, a boolean
that OR-ed three env-var markers (`GITHUB_ACTIONS`, `ACT`,
`INFINITO_MAKE_DEPLOY`) AND-ed with an `INFINITO_SKIP_E2E` opt-out.
That mechanism duplicated information `RUNTIME` already carries:
- `make deploy-*` already bakes `RUNTIME=dev` into the inventory at
init time (`RUNTIME_VARS_JSON='{"RUNTIME":"dev"}'`).
- CI runners (act / GitHub Actions) detect `RUNTIME=act|github` from
their canonical env markers via `cli.meta.runtime.detect_runtime`
during init.
- A bare `ansible-playbook` from a production shell stays at the
default `RUNTIME=host`.
Switching the gate to `RUNTIME in ['dev', 'act', 'github']` therefore
covers every case `MODE_CI` covered, AND folds the gate into the
single existing runtime SoT rather than maintaining a parallel one.
Changes:
- `tasks/stages/02_server.yml` E2E include now keys on `RUNTIME`.
- `group_vars/all/01_modes.yml` drops the `MODE_CI` block; a short
note explains the retirement.
- `scripts/meta/env/ci.sh` deleted (it only existed to export
`INFINITO_MAKE_DEPLOY=1`).
- The 5 `scripts/tests/deploy/local/deploy/*.sh` entry-points stop
sourcing `ci.sh`.
- `cli/deploy/development/deploy.py` no longer forwards the four
retired env markers into the container; the runtime decision is
already serialised into the inventory the deploy stage consumes.
- `scripts/tests/deploy/local/deploy/reuse-kept-app.sh` drops the
same four `-e` flags from its `docker exec`.
- `docs/requirements/006-playwright-service-gated-tests.md` gets a
prepended "MODE_CI retired" update note; the historical sections
remain for context but the dead `ci.sh` link target was reduced to
inline code.
Behaviour: any runtime where E2E was fired before still fires it now,
and the previous opt-outs have a single replacement: pass
`RUNTIME=host` (or `--vars '{"RUNTIME":"host"}'`) to skip Playwright on
a deploy that would otherwise have run it.
Introduces per-role `meta/variants.yml` for declaring multi-variant
deploys (e.g. WordPress Single-Site vs. Multisite) and reshapes the
development deploy CLI to materialise one inventory folder per variant
and iterate them with co-located sync+async passes.
Why:
- The previous single-inventory + Pass-1-then-Pass-2 split deployed each
variant twice and ran the async pass against a host left in a
different variant's state, masking real bugs.
- Inventory and runtime concerns were tangled: variant selection lived
as a runtime extra-var and required cross-stage coordination.
How:
- `roles/<role>/meta/variants.yml` (file format docs at
docs/contributing/artefact/files/role/variants.md): YAML list of
deep-merge overrides on top of `config/main.yml`. Empty entry `{}` is
variant 0 (= legacy payload). 6 roles get their dev overrides moved
here from inventories/dev.yml; WordPress declares 2 variants.
- `utils/runtime_data.py` gains `_build_variants` / `get_variants` and
drops the retired `_active_variants` runtime selector. Loader always
returns variant 0 as the default; non-zero variants reach
`get_merged_applications` as inventory-level `applications.<app>`
overrides baked at init time.
- New `cli/deploy/development/inventory.py`: `DevInventorySpec`,
`plan_dev_inventory_matrix` (pure planner shared between init and
deploy), `build_dev_inventory_matrix` (materialises one folder per
round at `<dir>-<R>`, or `<dir>` when single-variant). Single-folder
paths preserve full backward compat.
- `cli/deploy/development/deploy.py`: matrix loop with per-round
cleanup (only apps whose variant changed AND that are about to
re-deploy), `--variant <idx>` (or `VARIANT=` env) to pin a single
round, and `--full-cycle` (or `FULL_CYCLE=true`) which interleaves
sync + async per round so the async re-deploy targets exactly the
host state the matching sync deploy just produced.
- `cli/deploy/development/exec.py` gains repeatable `--env KEY=VAL` so
bash callers can inject per-run context into in-container helpers
without inlining heredocs.
- `scripts/meta/env/inventory.sh` exports `INVENTORY_VARS_FILE`
(Python mirror in `cli.deploy.development.common.DEV_INVENTORY_VARS_FILE`;
test_inventory_vars_file_spot.py locks them against drift).
- `make deploy-fresh-purged-apps` and `scripts/tests/deploy/ci/dedicated.sh`
switch to a single init + single deploy `--full-cycle`. The
`make deploy-reuse-*` bash scripts honour `VARIANT=` by suffixing
`INVENTORY_DIR`/`INVENTORY_FILE`.
- The 5 `scripts/tests/deploy/local/deploy/*.sh` scripts now delegate
their in-container heredocs to dedicated helpers in
`scripts/tests/deploy/local/utils/`, removing the bash-in-bash
quoting hell.
Tests:
- `tests/unit/utils/test_variants.py` (renamed from
`test_application_variants.py`): loader edge cases, variant-zero
default, retired-`_active_variants` guard.
- `tests/unit/cli/deploy/development/test_inventory.py` (new): planner,
variant baking, builder, `filter_plan_to_variant`, `DevInventorySpec`.
- `tests/unit/cli/deploy/development/test_matrix_deploy.py` (new):
per-round deploy filter, cleanup decisions, `--variant` pinning,
`--full-cycle` per-round interleave.
- `tests/unit/cli/deploy/development/test_inventory_vars_file_spot.py`
(new): drift guard for the bash <-> Python literal pair.
- `tests/unit/cli/deploy/development/test_exec.py` extended for `--env`.
Docs:
- `docs/contributing/artefact/files/role/variants.md` (new): file format.
- `docs/contributing/design/matrix-deploy.md` (new): runtime mechanism
(folder layout, --variant, --full-cycle, cleanup logic).
- `docs/contributing/actions/deploy.md` (new): contributor dev-deploy
flow (make targets, --variant pinning).
- `docs/administration/deploy.md` slimmed to admin-only (was mixed
admin+dev), cross-links to the new contributor doc.
- `docs/agents/action/iteration/role.md` keeps only agent-iteration
recipes; mechanism explanation moved out and linked.
- `docs/contributing/documentation.md` adds the "no self-SPOT
declaration" rule; `applications.md` renamed its SoT header for
consistency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the `DEPLOY_CI_EXPORTS := INFINITO_MAKE_DEPLOY=1` declaration
from the Makefile and the per-recipe `$(DEPLOY_CI_EXPORTS)` prefix
from every `deploy-*` target. The marker now lives in a dedicated
on-demand env module `scripts/meta/env/ci.sh` and is sourced by every
deploy entry-point under `scripts/tests/deploy/local/deploy/*.sh`,
which keeps the export aligned with the rest of the project's env
layer (`scripts/meta/env/all.sh` and friends).
`ci.sh` is intentionally NOT composed into `all.sh`: the marker
flips `MODE_CI` to `true` and MUST surface only when a deploy
actually runs, not for unrelated `make test*` / `make build*`
recipes that share the same `BASH_ENV`.
`docs/requirements/006-playwright-service-gated-tests.md` and the
`scripts/meta/env/README.md` document the new SPOT and the
always-on / on-demand distinction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract inventory/host_vars printing into an inspect() function called
after each deploy. Define HOST_VARS_FILE as a SPOT in inventory.sh.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Define INVENTORY_FILE="${INVENTORY_DIR}/devices.yml" once in
scripts/meta/env/inventory.sh and export it. Remove all local
inv_file definitions; assert INVENTORY_FILE as required in each
consumer script. Rename CMD variable in container.sh from INSPECT_CMD.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add IS_WSL2 export to runtime.sh and replace all inline
grep-based WSL2 guards in administration scripts with a
source + variable check for consistency.
Affected scripts:
- scripts/administration/systemd/enable/wsl2.sh
- scripts/administration/tls/trust/wsl2.sh
- scripts/administration/network/dns/setup/wsl.sh
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>