`test_public_http_urls_are_reachable` flagged
`http://deb.debian.org/debian-security` as a hard fail (HTTP 404),
breaking the test-external CI gate.
The URL is correct — apt clients fetch concrete files underneath
`dists/bookworm-security/`, all of which resolve to 200. Only a bare
GET on the redirector path returns 404 because the redirector does
not serve a directory index. The same convention (`# nocheck: url`)
already covers raw-packagist on line 121 for the same reason.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The package-cache active probe in
scripts/tests/environment/utils/cache.sh had `releases/40/...`
hardcoded as the upstream path for the `yum-fedora` Nexus proxy.
Fedora 40 went EOL in May 2025 and dl.fedoraproject.org stopped
serving its repodata, so the probe failed every Test Environment
(fedora:latest) CI job with:
[probe] package-cache: GET /repository/yum-fedora/releases/40/Everything/x86_64/os/repodata/repomd.xml
curl: (22) The requested URL returned error: 404
[FAIL] package-cache: proxy 'yum-fedora' did not serve
'releases/40/Everything/x86_64/os/repodata/repomd.xml'
Resolve the version dynamically from the runtime image's
`/etc/os-release` instead. The new `_resolve_fedora_release` helper
runs `docker run --rm --entrypoint /bin/cat <image> /etc/os-release`
and lifts `VERSION_ID` via awk, so `fedora:latest` is tracked
automatically and future EOL transitions no longer require a code
change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
probe_did_inner_build() in scripts/tests/environment/utils/cache.sh
hardcoded the compose network as `infinito-nexus_default`. Compose
derives the network name from the project name, which defaults to the
host CWD basename — `infinito-nexus` locally, `infinito-nexus-core` in
the CI fork, something else again in Act runners and worktrees. CI
therefore failed the test-environment matrix on every distro with
`network infinito-nexus_default not found` even though the cache
stack itself was healthy.
Look up the network the running `infinito-package-cache-frontend`
container is actually attached to via `docker inspect` and use that.
Fail loudly if discovery returns empty rather than silently skipping
the probe.
Verified via `make act-workflow ACT_WORKFLOW=.github/workflows/test-environment.yml ACT_JOB=test-environment ACT_MATRIX='dev_runtime_image:debian:bookworm'` — Job succeeded, probe step reports `[OK] DiD inner-build flowed through cache frontend`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the previous OR-based assertion in scripts/tests/environment
(which trivially passed because the Nexus log always grows from
health probes) with a richer set of helpers split across two files
under utils/ for SRP/SPOT.
scripts/tests/environment/utils/cache.sh (new):
* expected_system_proxy_for_runtime maps DEV_RUNTIME_IMAGE to the
Nexus proxy that handles its system package manager (apt-debian,
apt-ubuntu, yum-fedora, yum-rocky; empty for Manjaro/Arch where
pacman is not cached).
* nexus_repo_requests counts only /repository/<name>/... entries in
the Nexus request log, optionally scoped to one proxy. Filters out
health probes, admin REST calls, and Web-UI traffic.
* registry_cache_flows counts every cache-flow status (HIT, MISS,
REVALIDATED, UPDATING, STALE) so cold deploys also count.
* assert_caches_used fails with a per-cache breakdown when any of
three independent conditions is violated: total Nexus repo
traffic = 0, the runtime's expected system proxy has 0 cached
requests, or the deploy itself produced 0 registry-cache flows.
* probe_caches actively fetches a known upstream resource through
each cache (apt-debian/dists/bookworm/Release for the package
cache, /ca.crt for the registry cache).
* probe_did_inner_build drives a throwaway docker build whose
apt-get update runs against deb.debian.org. The build container
is placed on the compose network and pinned via --add-host to the
frontend IP, exactly like the runtime override that
roles/sys-svc-compose/files/compose.py injects for app builds.
After the build the test snapshots
``docker logs infinito-package-cache-frontend`` and asserts the
frontend recorded new requests, proving DiD apt traffic flows
through nginx -> Nexus apt-debian and not directly to upstream.
DOCKER_BUILDKIT=0 because BuildKit refuses --network=<compose-net>.
scripts/tests/environment/utils/common.sh (renamed from lib.sh):
keeps the bootstrap, constants, inventory inspect, and
assert_http_status; cache helpers moved out so the file is
single-concern.
scripts/tests/environment/07_deploy_minimal.sh sources both files;
order is snapshot, deploy, probe_caches, probe_did_inner_build,
snapshot, assert_caches_used. Active probes contribute traffic that
the AND assertion verifies, so the assertion is robust on deploys
that do not naturally trigger inner-build apt traffic.
.github/workflows/test-environment.yml: propagate DEV_RUNTIME_IMAGE
into the inner ``docker run`` so per-distro probe target and proxy
expectation resolve correctly inside the orchestrator.
.gitignore + .dockerignore: also ignore .claude/settings.local.json,
which compose mounts as /dev/null and which the classic docker
builder would otherwise try (and fail) to read into the build
context.
Verified end-to-end with five iterations of
``make act-workflow ACT_WORKFLOW=.github/workflows/test-environment.yml
ACT_JOB=test-environment ACT_MATRIX='dev_runtime_image:debian:bookworm'``.
The final iteration reports +81 apt-debian, +235 registry-cache
flows, +13 frontend log lines from the DiD probe, and "[OK] both
cache stacks observed activity" plus "[OK] DiD inner-build flowed
through cache frontend".
Introduce a single grammar for per-line, per-block, and per-file
suppression markers across the test suite, replacing seven ad-hoc
syntaxes that had drifted apart.
Helper
utils/annotations/suppress.py exposes is_suppressed_at,
is_suppressed_in_head, is_suppressed_anywhere, line_has_rule, and
suppressed_line_numbers. Markers may use either `noqa` or `nocheck`
(case-insensitive synonyms), live inside `#`, `//`, `{# ... #}`, or
`<\!-- ... -->` comments, and combine multiple kebab-case rules
comma-separated. 17 unit tests cover the grammar in isolation.
Refactored consumers
- tests/external/repository/test_urls_reachable.py (rule: url)
- tests/external/docker/test_image_versions.py via
utils/docker/version_updater.py (rule: docker-version)
- tests/lint/repository/test_no_direct_yaml_calls.py (rule:
direct-yaml)
- tests/lint/ansible/test_service_shared_consistency.py (rule:
shared)
- tests/lint/ansible/roles/test_web_app_email_integration.py (rule:
email)
- tests/lint/repository/test_python_file_size.py (rule: file-size)
- tests/lint/ansible/test_run_once_tags.py +
tests/integration/roles/run_once/test_schema.py (rules: run-once,
run-once-suffix)
Marker migration
12 role tasks/main.yml files: `# run_once_<key>: deactivated`
-> `# nocheck: run-once`. roles/web-app-suitecrm/meta/services.yml
drops a malformed inline `noqa: intentional legacy pin` from a TODO
comment so the new parser does not pick it up as a stray rule.
Caching wins
Test files that previously loaded YAML / read files via raw
yaml.safe_load + Path.read_text now route through utils.cache.yaml
(load_yaml_any / load_yaml_str) and utils.cache.files.read_text:
- test_service_shared_consistency.py
- test_web_app_email_integration.py
- tests/integration/roles/run_once/test_schema.py
- utils/docker/version_updater.py
This both speeds up repeated reads and stays consistent with the
direct-yaml lint policy.
Documentation
New SPOT page at docs/contributing/actions/testing/suppression.md
with grammar, position semantics, the catalog of rules, examples,
and an "adding a new rule" checklist. lint.md and external.md drop
their per-rule tables and link to the new page. All touched docs
conform to docs/contributing/documentation.md (no em-dash thought
breaks, no SPOT self-labels, RFC-2119 keywords used consistently).
Settings sort tooling
scripts/lint/sort_claude_settings.sh wraps utils/claude/settings_sort.py
and is wired into scripts/lint/autoformat.sh. The tooling fixes the
ASCII-ascending requirement enforced by
tests/lint/repository/test_claude_settings_sorted.py; the test's
failure message now points operators at the script. .claude/settings.json
is committed in sorted form.
The Nexus 3 raw-packagist proxy points at https://repo.packagist.org/
as the upstream root. That root returns HTTP 404 (Packagist serves
content under /packages/, /p2/, etc., not at /), so the external URL
reachability test fails the run even though the host is healthy and
the proxy works. Mark the line with `# nocheck: url` to skip the
literal-URL probe; the upstream is still validated indirectly when
the cache stack pulls real packages.
`07_deploy_minimal.sh` now snapshots Nexus request-log line count and
registry-cache HIT count before the deploy, runs the deploy, snapshots
again, and fails if neither counter grew. Catches regressions where
the runner-side wiring stops routing through the cache stack.
Helpers `cache_snapshot` and `assert_cache_used` live in `lib.sh` so
other environment scripts can reuse the same check.
Co-Authored-By: Claude Opus 4.7 <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>
Move the `docker compose -f ...` flag list and the cache-override
env-var setup into single helpers in `cli/deploy/development/common.py`:
* `compose_file_args()` — decides whether to layer
`compose/cache.override.yml` on top of `compose.yml`.
* `cache_env_overrides()` — produces the env-var dict the
override file requires strictly via `${VAR:?…}`.
Both `cli.deploy.development.compose` (up flow) and
`cli.deploy.development.down` (down flow) now call these helpers, so
up and down resolve to the same compose-file set and the same env
vars. Previously the cache env-var setup was duplicated inline in
compose.py and missing entirely from down.py, which made `make down`
fail under the `cache` profile with a confusing "required variable
missing" error before even reaching the orphan-cleanup step.
Single SPOT discipline: the host CA directory default lives only in
`scripts/meta/env/cache/package.sh` (sourced via BASH_ENV by every
Makefile recipe). `cache_env_overrides()` refuses to fall back to a
duplicate literal — a missing env var raises SystemExit pointing at
the env script. Tests that bypass the env layer set the var
explicitly so the SPOT-strict guard does not raise.
Containerize cert-gen: the original
`scripts/docker/cache/package-frontend-certs.sh` ran `mkdir`/`openssl`
host-side, which fails when `/var/cache/infinito` is not writable from
the invoking shell (sandboxed tooling, non-root operators). Move the
openssl logic into `compose/package-cache-frontend/cert-gen.sh` and
have the host wrapper docker-run it inside a throw-away alpine
container so the docker daemon (root) creates the bind-mount source
paths. Same pattern as how rpardini's registry-cache generates its CA
in-container.
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>
Two design docs under docs/contributing/design/:
- variants.md (renamed from matrix-deploy.md): explains the
folder-per-round wrapper that consumes per-role meta/variants.yml
and iterates variants at deploy time. Headings normalised to
sentence-case; the "legacy unsuffixed inventory path" wording
rewritten to drop the historical-context phrasing forbidden by
documentation.md.
- inventory.md (new): explains how a single round's inventory is
assembled out of meta/services.yml plus the variant payloads,
which inclusion mechanisms exist (operator --apps, run_after walk,
shared-service auto-include via strict `is True`), what lives in
the inventory file vs. host_vars/<host>.yml, and when Jinja
actually gets rendered (only at play runtime, via
utils.cache.applications._render_with_templar). Worked example
documents the WordPress + Discourse wiring as a reference case.
Cross-links updated wherever they pointed at matrix-deploy.md or
its concept name as a file:
- docs/agents/action/iteration/role.md
- docs/contributing/actions/deploy.md
- docs/contributing/artefact/files/plugins/lookup/applications.md
- docs/contributing/artefact/files/role/variants.md
- scripts/tests/deploy/local/deploy/reuse-kept-app.sh (path comment)
Plain-text occurrences of "matrix-deploy" as a concept term inside
Python comments, log messages, and test docstrings stay as-is; only
file-link references are migrated.
Co-Authored-By: Claude Opus 4.7 (1M context) <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>
Introduces a `registry-cache` service (rpardini/docker-registry-proxy) in
the `ci` profile that MITM-caches every outbound docker registry pull
made by `infinito`'s inner dockerd, drastically cutting bandwidth on
re-deploys, parallel CI shards, and local fresh-purges.
Wiring (all bind-mounted via compose.yml, no Dockerfile or entry.sh
staging, so edits hot-reload on next container start):
- CA bundle at /opt/registry-cache-ca (read-only),
- systemd drop-in at /etc/systemd/system/docker.service.d/registry-cache-proxy.conf,
- registry-cache-ca.sh at /usr/local/bin/registry-cache-ca.sh.
The drop-in registers registry-cache-ca.sh as ExecStartPre. Compose
gates `infinito` on `registry-cache` `service_healthy` so the CA is on
disk before dockerd boots; the install script supports both Debian
(`update-ca-certificates`) and Arch/Fedora (`update-ca-trust extract`)
trust stores, detected by command availability.
Also reorganizes compose/ into per-service subdirectories
(compose/coredns/, compose/registry-cache/) with README.md per directory
following the documentation guidelines, adds scripts/docker/README.md,
and nests the registry-cache host paths under /tmp/infinito/core/
(mirror/ for blobs, ca/ for the MITM CA bundle).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Single atomic landing for three back-to-back role-meta refactor requirements
(see docs/requirements/008-role-meta-layout.md, 009-per-role-networks-and-ports.md,
010-role-meta-runafter-lifecycle-migration.md).
req-008 — role meta layout
* Move every role's metadata under meta/<topic>.yml with file-root
convention (no wrapping key matching the basename):
- schema/main.yml + config/main.yml.credentials → meta/schema.yml
- users/main.yml → meta/users.yml
- config/main.yml.server → meta/server.yml
- config/main.yml.rbac → meta/rbac.yml
- config/main.yml.compose.services + every other top-level key
of config/main.yml inlined under <entity>.<key>
→ meta/services.yml
- config/main.yml.compose.volumes → meta/volumes.yml
* Drop the legacy schema/, users/, config/ directories from every role.
* meta/schema.yml: support nested credential keys, optional `default:`
field (Jinja literal, not rendered/validated/regenerated), implicit
`algorithm: plain`.
* Materialised paths drop the `compose.` prefix:
compose.services.<e>.<…> → services.<e>.<…>
compose.volumes.<k> → volumes.<k>
<former-top-level>.<…> → services.<entity_name>.<former-top-level>.<…>
* Update every Python consumer (utils/cache, utils/manager, utils/applications,
plugins/lookup, plugins/filter, cli/*) plus every Jinja template, vars/*.yml
and task file to the new paths.
* No legacy fallback. Repository-wide grep for `compose.services.` /
`compose.volumes.` / `schema/main.yml` / `users/main.yml` / `config/main.yml`
is empty outside docs/requirements/ and tasks/utils/migrate_meta_layout.py.
* Add tests/lint/repository/test_role_meta_layout.py guarding against the
old paths returning, and an explicit lint guard that meta/variants.yml
files do not carry a `compose:` wrapper.
req-009 — per-role networks/ports
* Move group_vars/all/08_networks.yml.defaults_networks.local.<role>.{subnet,
dns_resolver} into roles/<role>/meta/server.yml.networks.local.{subnet,
dns_resolver}.
* Delete group_vars/all/09_ports.yml entirely; ports now live on the
service entity that exposes them at
roles/<role>/meta/services.yml.<entity>.ports.{inter,local,public}.
* Rename + int-cast every former top-level `services.<entity>.port`
field to `services.<entity>.ports.inter` (single int).
* Always-category-keyed maps for ports.local / ports.public; relay
spans use {start, end} integers.
* Add PORT_BANDS map to group_vars/all/08_networks.yml — single source
of truth for the per-<scope>.<category> band ranges, consumed by both
the new CLI helpers and the lint test.
* New CLI helpers (cli meta ports suggest, cli meta networks suggest)
walk the live role tree gap-first then increment-fallback, with
deterministic output and non-zero exit on capacity overflow.
* cli create role consumes both helpers when scaffolding a new role.
* Lint extensions: bare-int port shape rejection, host-bound port
collision check (single ints + relay span integers, inter excluded),
band-membership check with documented allow-list for the legacy
web-app-bigbluebutton.bigbluebutton.local.http: 48087 entry.
req-010 — run_after / lifecycle migration
* Move galaxy_info.run_after and galaxy_info.lifecycle out of
meta/main.yml into meta/services.yml.<primary_entity>.{run_after,
lifecycle}; <primary_entity> is the value returned by
get_entity_name(role_name).
* For multi-entity roles whose primary entity has no compose service
(web-app-bluesky → bluesky, web-app-matrix → matrix, desk/dev/drv/
gen roles), create a dedicated metadata-only top-level entry under
<primary_entity>.
* Introduce utils/roles/meta_lookup.py with get_role_run_after /
get_role_lifecycle that resolve <primary_entity> once and read from
meta/services.yml; degrade gracefully when meta/services.yml is
absent OR the field is absent; raise MetaServicesShapeError when the
file exists but is malformed.
* Update every consumer (utils/service_registry, utils/invokable,
cli/meta/applications/resolution/run_after/__main__, cli/meta/roles/
lifecycle_filter/__main__, cli/meta/applications/resolution/combined,
cli/build/{tree,graph,role_include}, cli/deploy/development/{common,
init,deps,deploy}, plugins/filter/canonical_domains_map, etc.) to
use the helper.
* Lint guards: no run_after/lifecycle in meta/main.yml; lifecycle
values restricted to {planned,pre-alpha,alpha,beta,stable,deprecated};
run_after/lifecycle never on a non-primary entity; empty run_after
lists rejected.
Also folded into this change set
* services_disabler now writes `services.<svc>.{enabled,shared}` into
host_vars instead of the old `compose.services.<svc>.{...}` path; the
discover/find pair walks meta/services.yml and only treats entries
carrying enabled/shared/provides as provider declarations (so
metadata-only entries from req-010 do not create spurious provider
rows).
* service_registry skips `run_after` deps whose target was filtered
out via SERVICES_DISABLED instead of failing the whole load.
* cli/mirror/resolver and cli/create/inventory/mirror_overrides emit/
consume the new applications.<role>.services.<svc>.image shape
(mirror.md updated to match).
* utils/domains/list.build_applications_from_roles now reads
meta/server.yml directly (it had been silently returning {} since
config/main.yml stopped existing).
* utils/roles/resource_validation.filter_roles_by_min_storage reads
meta/services.yml.<entity>.min_storage instead of the legacy
compose.services path.
* web-app-bluesky pre-clones the social-app build context BEFORE
sys-svc-compose so the build context is on disk by the time
sys-stk-front-proxy/tasks/main.yml flushes the docker build handler.
* 4 meta/variants.yml files (svc-ai-ollama, web-app-phpmyadmin,
web-app-wordpress, web-app-keycloak) lose their `compose: services:`
wrapper so deep-merge over meta/services.yml lands on the new
file-root shape.
Validation
* Unit tests: 1571 passed.
* Lint tests: 50 passed.
* Integration tests: 161 passed (1 skipped).
* Full-cycle fresh-purged-apps deploy succeeded for the 8-app validation
set: web-app-{nextcloud,wordpress,yourls,bigbluebutton,bluesky,gitea,
discourse,mattermost}.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to d002796a5. The formula `infinito_nexus_${INFINITO_DISTRO}` is
now defined in exactly one place — `scripts/meta/env/defaults.sh:18`,
exported outside the load-once guard so it tracks INFINITO_DISTRO across
matrix iterations (caller re-sources defaults.sh after distro changes).
Every other site reads INFINITO_CONTAINER strictly from env; nothing
re-derives the formula, no lazy/colon-dash fallbacks, no per-call defaults.
Production code aligned:
- cli/deploy/development/common.py: add resolve_container() (strict env
reader, raises SystemExit on unset). make_compose() calls it as an
early-fail guard so a missing defaults.sh source surfaces here instead
of as a confusing compose.yml interpolation error later.
- cli/deploy/development/deploy.py: stop re-deriving from compose.distro;
use resolve_container().
- compose.yml: container_name reads ${INFINITO_CONTAINER:?...} strictly,
so callers that bypass defaults.sh fail fast with a clear hint.
- scripts/meta/resolve/image/local.sh: drop ${INFINITO_DISTRO:-debian}
fallback; require INFINITO_DISTRO via :? error.
- scripts/tests/deploy/ci/dedicated.sh: drop local _container fallback.
- scripts/tests/deploy/local/deploy/fresh-kept-app.sh: drop redundant
${INFINITO_DISTRO:-debian} reassignment after sourcing env/all.sh
(defaults.sh already defaults the value).
- scripts/tests/deploy/local/deploy/fresh-purged-app.sh: drop
target_container fallback; INFINITO_CONTAINER is already in env.
- scripts/tests/deploy/local/exec/container.sh: drop the lazy
${INFINITO_CONTAINER:=infinito_nexus_...} default; defaults.sh source
in env/all.sh already guarantees the value.
- scripts/tests/dns.sh: source defaults.sh so callers only need to set
INFINITO_DISTRO (the implicit dependency was previously a footgun).
- scripts/system/tls/trust/{wsl2,linux}.sh: same — source defaults.sh,
drop the ${INFINITO_CONTAINER:-infinito_nexus_debian} fallback in
linux.sh.
- .github/workflows/test-dns.yml: drop redundant
INFINITO_CONTAINER: infinito_nexus_${{ matrix.distro }} from per-step
env blocks (3 sites). The env now flows from INFINITO_DISTRO via the
defaults.sh sourced inside the scripts.
- tests/unit/cli/deploy/development/test_matrix_deploy.py: replace the
module-level os.environ.setdefault('INFINITO_CONTAINER', 'infinito_nexus_arch')
shim — which itself encoded the formula — with a module-level patch of
resolve_container that returns an opaque test-fixture string. Drop the
now-unused distro="arch" field from the _args() Namespace fixture
(deploy.handler stopped consuming it after the --distro arg removal).
Documentation aligned:
- docs/contributing/environment/variables.md: rewrite the INFINITO_CONTAINER
row to spell out the SPOT contract — set automatically by defaults.sh,
never re-derived elsewhere, callers re-source defaults.sh after
changing INFINITO_DISTRO mid-process.
- docs/contributing/artefact/files/compose.yml.md: replace the
INFINITO_DISTRO row (which described the now-removed
`infinito_nexus_${INFINITO_DISTRO:-debian}` interpolation) with an
INFINITO_CONTAINER row noting the strict ${INFINITO_CONTAINER:?...} read.
Verified:
- Formula derivation: 1 production hit (the SPOT in defaults.sh:18).
- INFINITO_CONTAINER fallback patterns (:-/:=): 0.
- --distro CLI arg refs: 0 (one comment explaining the removal).
- make test green: 1553 unit + 168 integration tests, no skipped suites.
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>
Four independent bugs were each blocking the autonomous
fresh-purged-apps iteration of web-app-{openwebui,wordpress,nextcloud,
phpmyadmin,yourls}; bundling them so the pipeline keeps running.
- cli/core/discovery.py: stop walking argv prefixes at the first flag
or path-shaped arg and ignore OSError so the matrix-init's
`infinito create inventory <abs-path> --vars '<huge JSON>'` no longer
hits ENAMETOOLONG inside `is_dir()`. New regression tests pin both.
- utils/cache/data.py: when this module moved to utils/cache/data.py,
PROJECT_ROOT silently slid one level deep, pointing ROLES_DIR at a
non-existent `<repo>/utils/roles`. Implicit lookups returned empty
defaults so `lookup('users','contact')` failed at deploy time as if
the user weren't defined. Use parents[2] and pin the invariant in a
new regression test.
- roles/web-app-keycloak/vars/main.yml: KEYCLOAK_PER_APP_LDAP_ROLES_MAPPER
inherited `subComponents`/`id` from the realm-import dictionary's base
`ldap-roles` mapper. kcadm rejects those keys on `create components`
("Unrecognized field 'subComponents'"), with stderr swallowed by
`2>/dev/null` in upsert_app_mapper.sh. Strip both keys exactly like
update/05_ldap.yml already does for the shared mapper.
- scripts/tests/deploy/local/deploy/fresh-purged-app.sh: pass
`RUNTIME=dev` through `--vars`, otherwise the host process running
init falls back to `detect_runtime() == "host"` and the Playwright
E2E gate (`RUNTIME in [dev, act, github]`) never fires.
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>
Drop the project-local copy of scripts/git/sign-push.sh in favour of
the git-maintainer-tools CLIs (git-setup-remotes, git-sign-push),
which now ship as a standalone PyPI/GitHub package so the same helpers
can be reused across every maintainer repo without vendoring shell
scripts.
The package is declared in pyproject.toml's `dev` extras and lands on
$PATH via `make install-python-dev` (which already runs
`pip install -e .[dev]`). Consumers invoke the CLIs directly; there
are no make wrappers because doing so would just duplicate help text
the tool itself already prints.
Fallout:
* scripts/git/sign-push.sh deleted.
* Makefile: sign-push target removed (no replacement target).
* .claude/settings.json: deny rule for `make sign-push*` replaced
with direct `git-setup-remotes*` / `git-sign-push*` denies so the
sandbox blocks the real binaries, not just the make alias.
* CLAUDE.md: operator instruction points at the bare `git-sign-push`
CLI + `make install-python-dev` for setup.
* docs/contributing/artefact/git/remotes.md: new tool-focused page.
Describes the layout and the two CLIs; does not duplicate raw git
commands already covered by git-maintainer-tools.
* docs/contributing/artefact/git/README.md: trimmed per readme_md.md
(docs READMEs must not act as a file index).
* docs/contributing/tools/make.md: Git section now documents the
external CLIs and the install entry point, not a wrapper target.
Implements requirements 005, 006, and 007.
005: per-application Keycloak `group-ldap-mapper`s anchor each app's
role groups under `/roles/<application_id>/<role_name>` (and, for
tenant-aware apps, `/roles/<application_id>/<tenant>/<role_name>`).
The flat-CN LDAP layout keeps each CN globally unique so Keycloak's
group-tree resolver never collides; per-app mappers use the LDAP
`description` as the visible Keycloak group name to keep the path
free of redundant prefixes. Migration purges legacy pre-005 entries
and forces a Keycloak `logout-all` so stale tokens drop pre-005
group paths.
006: per-service `<SERVICE>_SERVICE_ENABLED` env flags and the
`service-gating.js` helper let role specs `skipUnlessServiceEnabled`
cleanly when a shared service is missing or `SERVICES_DISABLED` is
set; `MODE_CI` markers (`INFINITO_MAKE_DEPLOY`, `GITHUB_ACTIONS`,
`ACT`) propagate through the deploy CLI into the test stage.
007: WordPress->Discourse round-trip Playwright scenario gated on the
`web-app-discourse` deployment.
WordPress Playwright runs 5/5 green on the single-site flow; the
multisite + Discourse scenarios are correctly skipped when their
service prerequisites are absent. The `rbac_group_path` lookup is the
single sanctioned producer for OIDC group paths and is wired into
every consumer (yourls, lam, minio, openwebui, pgadmin, phpldapadmin,
phpmyadmin, prometheus, xwiki).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move installation.md and rename administration.md to deploy.md under
docs/administration/, delete docs/guides/, and add a scope-only
README.md per the docs-README rules. Update README.md, SUPPORT.md,
and scripts/tests/environment/README.md cross-links.
Refresh roles/web-app-decidim/README.md to match the current OIDC
patch set (decidim-core omniauth.rb + omniauth_helper.rb only, no
secrets.yml on Rails 7.2, ENV-prefixed credential names).
- Resolve merge conflict in web-app-peertube/vars/main.yml; migrate
remaining `applications |` and `users.*` references to
`lookup('applications')` / `lookup('users', ...)` in web-app-odoo and
web-app-decidim (compose, env, playwright, setup tasks).
- web-app-peertube: split OIDC tasks into oidc/{enable,disable}.yml,
add install_mem_limit filter + PEERTUBE_MEM_LIMIT /
PEERTUBE_OIDC_PLUGIN_INSTALL_MEM_LIMIT vars to raise the cgroup ceiling
during the one-off `npm run plugin:install` exec (prevents OOM-SIGKILL
that was surfacing as "Container is restarting"); add
upsert-oidc-plugin.sql.j2 and diagnostics.sh.
- CI: test-deploy-{local,server,universal,workstation}.yml tweaks plus
scripts/github/{dump_runner_state,enlarge_swap}.sh for constrained
runners.
- cli/meta/applications/ressources: new CLI entry with unit tests.
- Misc config/docs updates across dashboard, mailu, mattermost, asset,
cdn, file roles and contributing docs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Resolve conflicts by migrating lookup plugins to utils.applications.config
(get_app_conf -> get) while preserving branch's runtime applications/users
lookups. Notable changes:
- plugins/lookup/{config,nginx,service}.py, roles/web-app-dashboard/
lookup_plugins/docker_cards.py: switch to utils.applications.config.get,
keep utils.runtime_lookup_data.get_merged_applications/_users.
- roles/web-app-matrix/docs/Installation.md: use lookup('config', ...)
syntax with markdown link formatting.
- scripts/tests/e2e/rerun-spec.sh: take main's single-source image tag
derivation from roles/test-e2e-playwright/defaults/main.yml.
- tests/integration/test_get_app_conf_paths.py -> test_config_lookup_paths.py:
migrate scan pattern to lookup('config', ...) and skip docs/ directory.
- tests/unit/plugins/lookup/test_config_lookup.py: union of new imports.
- tests/unit/plugins/lookup/test_cert.py: reset runtime caches in
setUp/tearDown to avoid id()-based fingerprint cross-test pollution.
- Add __init__.py in 7 test directories left behind after prior cleanup.
- Delete dead duplicate group_vars/all/{04_users,05_applications}.yml
(untracked on disk; content long migrated to roles/*/{config,users}/main.yml).
Swap size was free(/)-25, which took ~99 GB on the 145 GB root fs.
Remaining ~25 GB then ran out mid-deploy during Playwright image
pulls (ENOSPC). Replace with a fixed 16 GB cap and use mountpoint
to detect a real /mnt partition instead of [ -d /mnt ], which
matched the empty placeholder dir on single-partition runners.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Surfaces df, failed units, and hlth-disc-space status/journal from
inside the infinito container so failure-time dumps reflect the same
filesystem view that the disk-space health check evaluates, not just
the host.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Extracts disk/memory/docker/swap dump into a reusable helper,
calls it pre/post swap resize, and runs it on deploy failure
across all four deploy workflows. Also adds SKIP_SWAP=1
short-circuit for A/B probing swap-related regressions.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Introduce scripts/github/enlarge_swap.sh that consumes all remaining
space on / minus a 25 GB buffer (Docker layers, checkout, runtime
state), falling back to /mnt when / is too small. Replaces the
default ~4 GB runner swap so transient memory spikes (e.g. peertube
plugin install, issue #162) don't trip the host OOM-killer on the
16 GB GitHub-hosted runner.
Wire the script into all four deploy test workflows (local, universal,
server, workstation) and flip free-disk-space to the aggressive config
(tool-cache, large-packages, docker-images, swap-storage all true):
the deploy actually runs inside the infinito container, so host-side
toolchains, language runtimes, and the default Docker layer cache are
dead weight. Update the workflow SPOT doc accordingly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The --exec 'git commit --amend --allow-empty --no-edit -S' pattern
multiplied identical-tree empty commits during a rebase-merges run
(observed: 3 upstream empties became 16 locally). git rebase -S signs
every rewritten commit natively, without the amend loop, while still
preserving merges via --rebase-merges.
The re-sign rebase failed on empty commits pulled in from upstream
because git refused the --amend. Adding --allow-empty keeps those
commits in place while still GPG-signing them.
The old derivation read @playwright/test out of
roles/test-e2e-playwright/files/package.json and assembled the tag
as `v{version}-{distro}`. That package.json no longer exists — the
version+distro tag now lives verbatim in defaults/main.yml under
images.playwright.version, so read it from the single source of
truth instead of rebuilding a broken tag from a missing file. The
TEST_E2E_PLAYWRIGHT_IMAGE_DISTRO fallback is no longer referenced
once the distro suffix is baked into the upstream version string.
Route pushes through scripts/git/sign-push.sh so the operator's GPG key
stays outside the sandbox. Script signs only @{u}..HEAD (or merge-base
with origin/main|master on first push), skips re-signing when all
commits already carry a good signature, and uses --force-with-lease on
rewritten history. settings.json denies git push*, git send-pack*, and
make sign-push* so the agent cannot trigger a push path directly.
CLAUDE.md instructs the agent to hand off to the operator instead.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The previous symlink approach left .claude/skills as a real empty dir in
some setups, which collides with the agent sandbox (denyWithinAllow on
.claude/skills expects a file path, not a directory) and breaks every
Bash invocation. Copy the installed skills tree into .claude/skills so
both locations are real, independent copies — no symlink involved.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Treat chore/** branches like feature/fix/etc. for CI: trigger the
push pipeline on chore/** pushes and accept chore as a valid PR
branch prefix in full scope. This unblocks the chore/update-* PRs
that update.yml creates automatically, which previously failed
branch-prefix validation and never triggered the push pipeline.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add repository variable CI_RUN_ON_MAIN that gates whether pushes to
main trigger CI. When unset (default), pushes to main skip CI;
set to 'true' to enable. Pushes to other branches are unaffected.
Reorganize GitHub Actions docs into a dedicated tools/github/actions/
directory and consolidate every CI repository variable in a single
SPOT (configuration.md). Move actions.md to actions/workflows.md and
branch/configuration.md to actions/configuration.md, then update
every cross-reference. Trim README files to scope-only per
docs/contributing/artefact/files/docs/readme_md.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Move `pacman-key --init` + `--populate` ahead of the first `pacman -Syu`
call in `build_and_install_arch`. The previous guard
`pacman-key --list-sigs` always returned 0 on a fresh Manjaro/Arch image,
so the keyring init never ran and the upgrade of `archlinux-keyring`
failed with `==> ERROR: There is no secret key available to sign with.`,
breaking the manjarolinux/base test-environment matrix entry.
Refs: https://github.com/infinito-nexus/core/issues/167
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes Verification checkboxes in requirement 003. Each spec exercises
the `lookup('applications')` + `lookup('users')` pathway end-to-end:
login via lookup('users'), OIDC round-trip from dashboard (where the
app supports it), canonical-domain DOM assertion, and a full CSP
assertion covering every directive emitted by csp_filters.build_csp_header
(15 directives), meta-parity, enforced policy (not Report-Only), and
zero securitypolicyviolation events.
The dashboard spec is extended in place with the CSP + canonical-domain
assertions while keeping its existing login/logout coverage.
rerun-spec.sh is pointed at the Jinja template + the staged copy of
package.json (the repo has templates/package.json.j2, not files/package.json),
so the inner spec-only iteration loop works without re-deploying.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>