Commit Graph
363 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5.5 e6f552ca0a fix(install): provision minitest next to ruby on Arch and the RedHat family
The Manjaro workspace job failed test-unit-ruby with "cannot load such
file -- minitest". Debian vendors minitest in libruby, but Arch ships it
as ruby-minitest and Fedora and CentOS Stream as rubygem-minitest, none
of which ruby pulls in. ensure_ruby_present now probes
`require "minitest"` and installs the distro package when the probe
fails, then fails loudly if minitest is still missing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 23:45:54 +02:00
kevinveenbirkenbachandClaude Opus 5.5 2756fd56f1 fix(cache): install CA anchors where every update-ca-trust family reads them
The Fedora and CentOS Stream workspace jobs failed TLS to pypi.org,
registry.npmjs.org and repo.packagist.org (CERTIFICATE_VERIFY_FAILED,
curl error 7). package-frontend/ca.sh put the frontend CA into the Arch
anchor dir /etc/ca-certificates/trust-source/anchors, which p11-kit on
the RedHat family never reads.

Probing for the anchor dir does not fix it: with-ca-trust.sh creates
both /etc/pki/ca-trust/source/anchors and
/etc/ca-certificates/trust-source/anchors on every update-ca-trust host,
so the probe in registry-ca.sh already picks the RedHat dir on a reused
Arch runner. Both scripts now install into both dirs, rebuild only when
one of them changed, and take CA_ROOT so unit tests run them against a
fake root.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 23:45:53 +02:00
kevinveenbirkenbach ec2a4b96f0 Merge pull request #930 from infinito-nexus/fix/web-app-nextcloud/onlyoffice-settings-race
fix(web-app-nextcloud): write onlyoffice's server URL last and recheck it
2026-10-15 22:36:45 +02:00
kevinveenbirkenbach c38f95457b Merge pull request #955 from infinito-nexus/fix/constructor/bootstrap-carrier/925
perf(constructor): carry the applications SPOT into the bootstrap roles
2026-10-15 17:54:46 +02:00
kevinveenbirkenbach 06a74e7658 Merge pull request #869 from infinito-nexus/fix/web-app-stalwart/relay-rule/793
fix(web-app-stalwart): authenticate bots with an app password under SSO
2026-10-15 15:42:19 +02:00
kevinveenbirkenbach 932c1fc70a Merge pull request #932 from infinito-nexus/fix/plugins/onion-apex-redirect/924
fix(lookup): keep the onion apex from redirecting to a clearnet homepage
2026-10-15 11:48:14 +02:00
kevinveenbirkenbach 84f6e86d8e Merge remote-tracking branch 'origin/main' into fix/lint/mermaid-browser-revision 2026-10-15 11:23:26 +02:00
kevinveenbirkenbachandClaude Opus 5.5 33bb7e99e9 fix(lint): resolve puppeteer-core like node and keep other browser revisions
- browser_revision() reads revisions.js from the puppeteer-core that
  node resolves for mmdc's puppeteer: puppeteer/node_modules first, then
  the node_modules directories above it. When mermaid-cli pins another
  puppeteer-core, npm nests puppeteer's own copy, and the sibling held a
  different revision.
- A failed browser install removes only the directories of the pinned
  revision before the retry. Removing the whole chrome-headless-shell
  cache also deleted browsers that other puppeteer installs share.

Addresses the CodeRabbit review on #958.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 11:23:22 +02:00
kevinveenbirkenbach b1d38f49d6 Merge pull request #934 from infinito-nexus/fix/act/scope-outer-teardown
fix(act): remove only the own worktree's act containers
2026-10-15 10:51:48 +02:00
kevinveenbirkenbach 51d75fb804 Merge pull request #908 from infinito-nexus/fix/env/distro-derived-keys/794
fix(env): derive the image and container names from the distro under test
2026-10-15 10:50:59 +02:00
kevinveenbirkenbach 706745162b Merge remote-tracking branch 'origin/main' into fix/lint/mermaid-browser-revision 2026-10-15 10:36:09 +02:00
kevinveenbirkenbachandClaude Opus 5.5 cce4373abd fix(web-app-nextcloud): bound the onlyoffice check by a wall-clock deadline
`retries: 30` with `delay: 10` only bounded the wait while each
`occ onlyoffice:documentserver --check` failed fast. onlyoffice v10.2.1
gives the health check and the version command 60 s each and the
conversion 120 s, so a document server that accepts connections but
does not answer held the deploy for up to two hours before it failed.

The retries now also stop once a deadline of
`lookup('timeout', 300)` seconds, set right before the check, has
passed. A per-attempt `timeout:` would not bound the total: ansible-core
2.21 leaves the `until` loop on the first timed-out attempt, so only a
limit below onlyoffice's 60 s would end a hanging server's loop.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 04:04:33 +02:00
kevinveenbirkenbachandClaude Opus 5.5 c74a31e83e fix(lint): install the browser revision mmdc's own puppeteer pins
install-lint provisioned chrome-headless-shell with `npx --yes
puppeteer browsers install`, which runs the latest puppeteer from the
registry. mmdc runs with the puppeteer npm installed next to it as a
peer dependency when mmdc itself was installed, and looks for exactly
the revision that puppeteer-core pins. Once puppeteer 25.14.0 was out,
npx installed 155.0.8059.79 while mmdc with puppeteer-core 25.13.0
wanted 155.0.8059.39, and lint-mermaid failed on every file. Because
browser_installed() accepted any chrome-headless-shell, a stack with
the wrong revision never repaired itself.

The browser is now installed through the puppeteer CLI next to mmdc,
which installs its puppeteer-core's revision, and only that revision,
read from puppeteer-core's revisions.js, counts as installed.

Closes #957

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 03:21:30 +02:00
kevinveenbirkenbachandClaude Opus 5.5 778c18fb23 perf(constructor): carry the applications SPOT into the bootstrap roles
The constructor parked no applications carrier between the merge block
and the token store, so every lookup in the svc-swarm-node, svc-storage,
svc-registry and svc-virt bootstrap rendered the SPOT cold, 25 to 30 s
each on a loaded host.

A carrier there was unsafe: in swarm, svc-storage-nfs-server mounts the
NFS export over the controller's DIR_VAR_LIB, so the tokens file the
render reads can appear in the middle of that section, and the cache key
did not notice. merged_applications_cache_key now includes the tokens
file's path, mtime and size, as the users cache key already did, so a
carrier rejects itself once the tokens change. A carrier parked after
the add_host tasks then serves the bootstrap roles. It is parked only in
swarm mode: in compose the section runs in about a second, so a carrier
there would add a render without saving one.

In a swarm drill of web-app-prometheus up to the known NFS probe-write
failure, "Resolve the NFS server backend flavor" went from 29.6 s to
0.07 s, "Probe-mount the NFS export" from 26.4 s to 0.4 s, "Mount
cluster-shared infinito state on controller" from 23.7 s to 0.5 s and
"Ensure Docker-In-Docker DNS works" from 30.0 s to 3.6 s; the run to
that point took 768 s instead of 1735 s.

Closes #925

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 03:21:17 +02:00
kevinveenbirkenbach 1967a218d0 Merge remote-tracking branch 'origin/main' into dependabot/pip/ruff-0.17.0 2026-10-15 01:24:32 +02:00
kevinveenbirkenbachandClaude Opus 5.5 47565781ea style(python): import datetime as dt for ruff 0.17
ruff 0.17.0 adds `datetime` -> `dt` to the default aliases of
flake8-import-conventions, and the project selects ICN, so lint-python
failed with ICN001 in seven files.

The files now import `datetime as dt`. ruff's unsafe fix also renamed
two local names in utils/github/playwright_summary.py into the alias:
the `_to_iso_z` parameter and the parsed value in
`_parse_iso_timestamp`. Both are now called `moment`; with the bare fix,
nine tests of test_playwright_summary.py failed with UnboundLocalError
and AttributeError.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 01:24:31 +02:00
kevinveenbirkenbach e8ffe2536e Merge remote-tracking branch 'origin/main' into HEAD 2026-10-15 01:07:59 +02:00
kevinveenbirkenbach b637742f96 Merge remote-tracking branch 'origin/main' into fix/env/distro-derived-keys/794 2026-10-15 01:07:53 +02:00
kevinveenbirkenbachandClaude Opus 5.5 87c9a5f41b test(act): mark the docker stub log read as outside the file cache
The teardown test reads the call log its docker stub writes into a
temporary directory. test_no_uncached_file_reads flagged that read in
CI; the path-keyed read cache does not fit a file written per run, so
the read keeps Path.read_text with the cache-read opt-out, as the other
stub-log tests do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 01:07:50 +02:00
kevinveenbirkenbach 06084d560a Merge remote-tracking branch 'origin/main' into fix/act/scope-outer-teardown 2026-10-15 01:07:50 +02:00
kevinveenbirkenbach 565e15f2a5 Merge remote-tracking branch 'origin/main' into fix/plugins/onion-apex-redirect/924 2026-10-15 01:07:47 +02:00
kevinveenbirkenbach 7344565df1 Merge remote-tracking branch 'origin/main' into fix/web-app-nextcloud/onlyoffice-settings-race 2026-10-15 01:07:41 +02:00
kevinveenbirkenbach 365ea9e764 Merge pull request #911 from infinito-nexus/fix/web-app-prometheus/blackbox-verify/851
fix(web-app-prometheus): verify publicly trusted probe targets while the private CA is missing
2026-10-15 00:17:47 +02:00
kevinveenbirkenbachandClaude Opus 5.5 46373c1ba3 fix(act): remove only the own worktree's act containers
down_act_outer.sh removed every container named act-*, so starting or
releasing a swarm drill in one worktree killed the act container of a
drill another worktree had running.

workflow.sh now labels act's job containers with
infinito.worktree=${INFINITO_CONTAINER} through the default
ACT_CONTAINER_OPTIONS, and down_act_outer.sh removes only containers
carrying the own label. Both scripts refuse to run without
INFINITO_CONTAINER, so the filter never widens to every act container;
make exports BASH_ENV, which loads it from .env.

Worktrees without a custom.env share the default container name and
therefore still share one label among themselves.

Closes #933

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 13:50:07 +02:00
kevinveenbirkenbachandClaude Opus 5.5 45732329b6 fix(lookup): keep the onion apex from redirecting to a clearnet homepage
On a Tor node current_play_redirect_domains schedules the bare node
onion as a redirect source targeting DOMAIN_HOMEPAGE. Without
web-app-dashboard, DOMAIN_HOMEPAGE falls back to SOFTWARE_DOMAIN
(infinito.nexus), so web-opt-rdr-domains rendered a 301 from
http://<node>.onion/ to http://infinito.nexus/: an onion visitor sent
to a clearnet host over plain HTTP.

The onion apex entry is now added only when DOMAIN_HOMEPAGE is an
onion host. Otherwise the onion apex gets no redirect and the default
server answers.

Closes #924

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 13:35:29 +02:00
kevinveenbirkenbachandClaude Opus 5.5 bfaa834f29 fix(web-app-nextcloud): write onlyoffice's server URL last and recheck it
The role writes onlyoffice's settings with one occ call per key,
DocumentServerUrl first and jwt_secret fourth. Nextcloud loads app
config once per process, so a cron.php that starts inside that batch
runs onlyoffice's EditorsCheck job, registered when the app was
enabled, with the document server URL but without the secret. The
document server answers "Invalid token", EditorsCheck stores it in
settings_error, and onlyoffice stops loading its Files integration;
EditorsCheck itself skips every later run while that error is set.

Seen in run 38455782184 (Nextcloud#4, swarm): cron.php started at
06:50:00 inside the batch that ran from 06:49:59 to 06:50:04, logged
"Invalid token" at 06:50:22, and both Playwright passes found .docx
files offered for download only.

DocumentServerUrl is now written last, so a process that cached the
config before the batch ended sees no URL and EditorsCheck returns
early. After the batch, `occ onlyoffice:documentserver --check` sets
settings_error from a fresh check: a redeploy clears an error latched
earlier, and a document server that stays unreachable fails the
deploy. A unit test keeps the URL last.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 13:34:52 +02:00
kevinveenbirkenbach a3c8f31d07 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	roles/web-app-stalwart/README.md
2026-10-14 13:34:01 +02:00
kevinveenbirkenbach 67b508cc5d Merge pull request #918 from infinito-nexus/fix/images/retired-role-discovery
fix(images): leave eol roles out of image discovery
2026-10-14 10:04:03 +02:00
kevinveenbirkenbach e6b88662be Merge pull request #919 from infinito-nexus/fix/ci/release-backfill-floor
fix(ci): stop the release backfill below the oldest buildable tag
2026-10-14 10:02:04 +02:00
kevinveenbirkenbach 7b80a807ab Merge pull request #909 from infinito-nexus/fix/lint/mermaid-browser-install/795
fix(lint): provision the mermaid browser in install-lint, not in the parallel lint run
2026-10-14 08:59:04 +02:00
kevinveenbirkenbachandClaude Opus 5.5 078d0a0682 fix(lint): provision mmdc's browser only when it is missing
install-lint provisioned chrome-headless-shell on every run, and `make lint`
runs install-lint once per lint target in parallel whenever its stamp is not
fresh. On CI the stamp never is (eslint lives in node_modules, not on PATH),
so one lint job provisioned 16 times (run 38455552981). As root, puppeteer's
--install-deps runs `apt-get satisfy` even for an installed browser, and two
of those at once fail on the dpkg lock; a failed provisioning then deleted the
shared browser cache while lint-mermaid could be rendering with it.

The browser is now provisioned only when it is not installed, and --force
drops its cache so `make install-lint-force` still reinstalls it with its
libraries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 08:56:59 +02:00
kevinveenbirkenbachandClaude Opus 5.5 fd9286b24f test(lint): mark the stub call log as written by the test
test_no_uncached_file_reads rejects Path.read_text in tests unless the file is
written in the test itself. The mermaid script test reads the call log its
stubs append to during the run, so that read carries `# nocheck: cache-read`.
The mermaid cli test is formatted with ruff.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 01:32:35 +02:00
kevinveenbirkenbachandClaude Opus 5.5 ee8d2bd983 test(env): check each distro's parent image against its own reference
The loop test only required three different parent images for three distros,
which a swap between two of them would still pass. Each iteration now also
records the owner and tag it sees, and its image must equal
pkgmgr_image(distro, owner, tag). With the loader of main the test fails on
arch with pkgmgr-debian:stable.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 01:32:24 +02:00
kevinveenbirkenbachandClaude Opus 5.5 1d970fe5bd fix(env): keep shellcheck and the cache-read lint green on the distro switch
The loader copies INFINITO_DISTRO into a loader-local variable before it
compares the value with the one in .env. With INFINITO_DISTRO read directly in
that condition, `shellcheck -x scripts/meta/resolve/apps.sh` reported SC2153
for INFINITO_DISTROS, which apps.sh takes from the sourced .env; with the copy
it reports nothing, as on main.

The loader test checks that .env is unchanged with filecmp against the file it
was copied from, instead of reading it twice with Path.read_bytes, which
test_no_uncached_file_reads rejects.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 01:32:24 +02:00
kevinveenbirkenbachandClaude Opus 5.5 d412b23409 fix(ci): stop the release backfill below the oldest buildable tag
The release backfill dispatches the highest version tag whose images are
missing. Every tag from v15.0.0 down to v9.0.2 has its images now, so since
2026-10-05 it picks v9.0.1 every day, and each of the nine runs failed in
"Build missing CI images / Image" at
`entry.sh: line 19: infinito: command not found`. The v9.0.1 tree cannot
build its own image; c680be12ea fixed that build in v9.0.2, and a tag cannot
take the fix.

highest_release_tag.sh now stops at v9.0.2: no tag below it is picked, so the
backfill reports nothing missing instead of retrying v9.0.1. v9.0.0 has the
same Dockerfile, Makefile and scripts/docker as v9.0.1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 01:32:05 +02:00
kevinveenbirkenbachandClaude Opus 5.5 8a29ba0bfd fix(images): leave eol roles out of image discovery
The scheduled mirror job has failed every day since 2026-09-25 on the same two
images: quay.io/minio/minio and quay.io/minio/mc answer `unauthorized` to
anonymous pulls ("2/197 failed", run 38352569503). Both belong to
web-app-minio, an eol role outside the lifecycle envelope that default.env
tests, so no CI deploy pulls them.

iter_role_images now skips every role that is_maintained() reports as
unmaintained, the check the version updaters already apply to the same list.
On the tree the discovery yields 187 images instead of 192; the five it drops
belong to the four eol roles web-app-confluence, web-app-jira, web-app-minio
and web-app-phpldapadmin. The mirror sync, resolver and wait read this list,
and their descriptions and mirror.md now say so.

The roles directory is resolved first: is_maintained reads a relative path as
a role name and would keep every role.

475ab9b386 on feature/store-i18n takes the same approach with a lifecycle
reader of its own; main already has is_maintained for it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 01:19:28 +02:00
kevinveenbirkenbachandClaude Opus 5.5 15bba26ef3 fix(web-app-prometheus): verify publicly trusted probe targets while the private CA is missing
The readiness module http_ready rendered insecure_skip_verify from
TLS_MODE and lookup('ca_injected', application_id) (#851). The lookup is
true only when Prometheus itself has a domain, TLS and the self-signed
mode, so in a self-signed deployment where Prometheus runs in another
mode the exporter had no CA file and skipped verification. All targets
share the module, so a target with a publicly trusted certificate was
probed without verification too, and anyone on the probe's path could
answer for a failed application.

http_ready now always verifies. A second module, http_ready_private_ca,
skips verification, and only the targets that need it select it: those
in the self-signed mode while the CA is not mounted into Prometheus,
through the __param_module label, which overrides the job's module for
that target. The default self-signed deployment mounts the CA and keeps
every target on the verifying module.

Verified on a local self-signed Tor round, where the CA is mounted:
the rendered blackbox.yml holds http_ready with insecure_skip_verify:
false and http_ready_private_ca with true, and no target of the scrape
configuration carries the module label. All targets of that round are
http onion names, so the https path itself ran only in the unit tests:
tests/unit/python/roles/web-app-prometheus pass, among them a publicly
trusted target that keeps the verifying module while the CA is missing,
a self-signed target that switches to the private-CA module, and the
verifying module rendering insecure_skip_verify: false without the CA.
Against the templates of main the three new cases fail.

Closes #851

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 21:31:58 +02:00
kevinveenbirkenbachandClaude Opus 5.5 ba8f4b5f3a fix(sys-svc-container): keep the installed Docker apt key when its refresh is refused
A local deploy stopped four times in two days in "Ensure Docker apt key
and sources are consistent" (#849). apt_repo.sh downloads the Docker GPG
key on every run, and when download.docker.com refused the connection,
curl's four attempts were over within about six seconds and the script
returned 1, although the key from an earlier run was installed and is
all the following apt operations need.

install_key now fails only when no key is installed. With a key in
place, a refused download prints a warning and keeps it. A downloaded
key that differs still replaces the installed one, and the 0644 mode is
enforced in every case. The keyring path became an argument:
ensure-key-and-sanitize passes the keyring it already hands to
sanitize_sources, so the role's call still resolves to
/etc/apt/keyrings/docker.asc, and a test can point the script at a
temporary keyring.

Verified: tests/unit/python/roles/sys-svc-container/files/test_apt_repo.py
stubs curl and sudo. A refused download keeps the installed key and ends
with changed=0, a refused download without a key exits 1, and a new key
replaces the installed one. A copy of the script whose keep branch always
errors, the old behaviour, fails the first test. Two local deploys ran
the new script with rc 0 and an empty stderr and left the installed key
untouched. shellcheck is clean.

Not verified live: the refused download itself, which needs
download.docker.com to refuse a connection during a deploy. A host
without a key still needs the download and still fails when it is
refused.

Closes #849

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 21:31:47 +02:00
kevinveenbirkenbachandClaude Opus 5.5 c8cb7fda8c fix(lint): provision the mermaid browser in install-lint, not in the parallel lint run
make lint runs install-lint once and then dispatches its fifteen targets
in parallel. scripts/lint/mermaid.sh provisioned chrome-headless-shell
inside that parallel phase with `npx puppeteer browsers install
--install-deps`, whose `apt-get satisfy` carries no lock timeout. In a
fresh stack container another target held the apt lock, the shared
libraries stayed missing, the script only warned, and all 289 Markdown
files then failed on libnspr4.so (#795).

utils/install/lint/mermaid_cli.py now provisions the browser right after
mmdc, inside the serial install-lint step, as root with --install-deps
and as a user without it, because puppeteer refuses to install libraries
without root. A failed install deletes the chrome-headless-shell cache
folder and retries once under a 900 second timeout per attempt; a cut-off
download otherwise leaves a folder without its executable, and puppeteer
refuses every later install of that build. The install-lint stamp counts
as fresh only while the browser executable exists, so a recreated
container provisions it again.

mermaid.sh no longer calls apt-get or npx. It renders one minimal diagram
first and, when that fails, exits with mmdc's output instead of reporting
every Markdown file.

Verified: tests/unit/python/scripts/lint/test_mermaid.py stubs mmdc,
npx and apt-get. With a browser that cannot load libnspr4.so the script
exits 1 with that line, before any Markdown file renders, and mmdc is the
only tool it calls. Against main's mermaid.sh the same test fails: it
calls apt-get and npx and reports the files one by one. The installer and
stamp cases pass, shellcheck is clean.

Not verified: a full make lint in a recreated stack container. The race
reproduced in one of two runs in the issue.

feature/store-i18n changed the provisioning in mermaid.sh with 2f5f52bd1a
and dc67bce5f0 (timeout, deleting a cut-off browser). Both behaviours
moved into mermaid_cli.py here, so its merge takes this version of
mermaid.sh.

Closes #795

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 21:31:36 +02:00
kevinveenbirkenbachandClaude Opus 5.5 1079f48feb fix(env): derive the image and container names from the distro under test
A compose deploy job labelled centos or arch built its stack image from
pkgmgr-debian and ran it as infinito_nexus_debian (#794). distros.sh
exports INFINITO_DISTRO per iteration and sources load.sh again, but the
loader returned early because INFINITO_ENV_LOADED and the .env mtime were
unchanged. A full reload would not have helped either: the debian values
were already exported, so the restore step kept them as caller intent.

load.sh now records the distro it loaded for. Another INFINITO_DISTRO
defeats the early return and drops INFINITO_CONTAINER,
INFINITO_PARENT_IMAGE and INFINITO_IMAGE, the three keys the env handlers
derive from the distro. When the requested distro also differs from the
one .env was generated for, the loader sources a set that
`python -m cli.meta.env --output` generates for it in a private temp
directory and leaves .env untouched, so a local multi-distro run does not
switch the checkout's distro. A caller value set for the loaded distro
still wins.

Verified: tests/unit/python/scripts/meta/env/test_load.py and the new
distros.sh case pass. Against main's loader both fail with the symptom of
the issue: INFINITO_DISTRO=centos next to infinito_nexus_debian and
pkgmgr-debian:stable, and distros.sh handing debian, arch and centos the
same debian parent. shellcheck is clean.

Consequence: CI jobs labelled arch, ubuntu, fedora or centos now build
and deploy that distro, so a failure that shows up there was masked
before.

feature/store-i18n carries 3f973eb137, which resolves the parent image
for scripts/image/push.sh on its own, and 05e4dbafcb, which inserts the
INFINITO_INSTANCE slot check into the block this commit restructures. The
merge keeps the slot check ahead of the distro check.

Closes #794

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 21:30:48 +02:00
kevinveenbirkenbach 29fee68f3c Merge branch 'main' into HEAD 2026-10-13 15:10:08 +02:00
kevinveenbirkenbach 4b302bc2ba Merge pull request #872 from infinito-nexus/update/version-sources-web-app-n8n-20261008-279346d
update(versions): update pins with a declared source (web-app-n8n)
2026-10-13 14:23:12 +02:00
kevinveenbirkenbachandClaude Opus 5.5 f7987679dd fix(ci): read every page of the job listing before reporting failed roles
report_failures asked `gh api --paginate` for the run's jobs with the jq
filter `[.jobs[] | {name, conclusion}]`. gh applies that filter to each page
and prints one array per page, so a run with more jobs than one page holds
produced two JSON documents, and json.loads stopped with "Extra data: line 2
column 1". Main run 38218402254 (113 jobs) failed its "Report main role
failures" job that way, and no issue was opened for its failed roles.

The listing now emits one job object per line and parses the lines one by
one, the same way logs/download/github.py lists jobs.

Verified: the old parse of the real output of run 38218402254 raises the
same "Extra data: line 2 column 1 (char 8491)" as CI; list_jobs on that run
returns all 113 jobs, 16 of them failed, grouped into 12 failed roles.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 00:46:28 +02:00
kevinveenbirkenbachandClaude Opus 5.5 78c526d759 Merge remote-tracking branch 'origin/main' into HEAD
roles/web-app-n8n/meta/services.yml conflicted on the n8n pin: main already
carries 2.43.4 from #900, newer than this branch's 2.43.2, so main's side is
kept. The provisioning fixes of this branch are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 18:56:57 +02:00
kevinveenbirkenbachandClaude Opus 5.5 24d7872fde Merge remote-tracking branch 'origin/main' into HEAD
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 18:54:31 +02:00
kevinveenbirkenbachandClaude Opus 5.5 e31b9557d2 fix(env): keep the host .env when the runner container runs make setup
The repository is bind-mounted into the infinito container, and every deploy
runs `make setup` there, which depends on `dotenv`. The generator therefore
rewrote the host's .env with the values the container sees: the host lost
INFINITO_RUNNING_ON_GITHUB=true that way, and the guide replay pulled
alpine:latest past the GHCR mirror.

compose.yml now marks the infinito service with INFINITO_DOTENV_HOST_OWNED,
and `python -m cli.meta.env` keeps an existing .env while that marker is
"true". A missing .env is still generated, and `make dotenv-force` (env -i)
still rewrites it. The key stays out of the env registry: only compose.yml
sets it, it is no configuration value, and a .env entry could freeze the
host .env.

test_distros now also strips INFINITO_ENV_LOADED. load.sh returns early
while that guard is set and the mtime of .env is unchanged, so the test only
reloaded the budget keys it strips because the container kept rewriting .env.

Verified: unit tests cover a kept .env, a missing one being generated, and a
rewrite without the marker. Local compose deploys print "Kept .env: the host
checkout owns it" where they printed "Wrote .env" before, the host .env
checksum stayed the same across a deploy, and the four keys holding host
paths resolve inside the container under the same paths.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 18:35:22 +02:00
kevinveenbirkenbachandClaude Opus 5.5 eda6249d7b fix(sys-svc-compose): probe provider networks by their exact name
The network probe used community.docker.docker_network_info, whose
get_network() scans GET /networks and returns the first entry whose Name
equals the requested name OR whose Id starts with it, in a list order that
changes between calls. In main run 38038541631 (Zammad#1, swarm) the
central redis overlay got the ID n8nwzb1q39m3u4k7awwwmt6yg, the probe for
the not yet created provider network "n8n" returned it as existing, the
create step was skipped, docker stack deploy attached hermes to the redis
overlay as "n8n", and the oauth2-proxy then reached the password-protected
central redis by its bare host name: "NOAUTH Authentication required".
The same scan can also return the ID-prefix twin of a network that does
exist, so comparing the returned name alone would turn that network into
"missing" and fail the create with "already exists".

Both probes now run `container network ls --filter name=<entity> --format
'{{json .}}'`, whose name filter never matches an ID. The new lookup
network_driver picks the entry named exactly after the entity and returns
its driver, '' when there is none; the drift checks, their messages and the
create steps all read that one entry. No docker module is left in the file,
so its two docker-module-uses-container-cli suppressions go too.

Verified: on the host daemon GET /networks/<id prefix> returns a network of
another name and docker network ls --filter name=<id prefix> returns
nothing. In ansible-playbook against real docker network ls output the
lookup returned the exact network's driver in 20 of 20 probes beside an
ID-prefix twin, an empty driver (create path) for a missing network and for
a name only contained in a sibling, and the drift path for a foreign
driver. Unit tests cover the same cases; with a substring match instead of
the exact name the two sibling cases fail.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 18:35:22 +02:00
kevinveenbirkenbach 208dfa723f Merge remote-tracking branch 'origin/main' into HEAD 2026-10-10 10:38:29 +02:00
kevinveenbirkenbach 56bc07f363 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-10 10:38:20 +02:00
kevinveenbirkenbachandClaude Opus 5.5 1ba796d7f1 fix(ci): pull the images the runner uses itself from the GHCR mirror
The GHCR mirror covered role images only. The images the runner pulls on
its own went straight to Docker Hub: coredns from compose.yml, the cache
stack in compose/cache.override.yml and alpine:latest, the init image of
scripts/docker/cache/package-frontend/certs.sh. With the anonymous
githubactions quota spent, the guide step of Postmarks#0 (main run
37954203718) and Shopware#2 (#872) died on "toomanyrequests" while pulling
alpine:latest, after coredns had hit the same limit.

iter_runner_images (utils/docker/image/discovery.py) finds those images,
and the mirror sync and the fork-PR wait now cover them next to the role
images. On a real GitHub runner compose_file_args layers a generated
build/compose/runner-mirror.override.yml that points only the services of
the layered compose files at their mirror, and the certificate generator
gets the mirrored init image. Local runs and act are unchanged, and the
coredns literal stays in compose.yml for Dependabot. test-dns now waits for
images-mirror-missing like the deploy and workspace jobs, since it brings up
the stack too.

Verified with docker compose config on a simulated GitHub runner: the
service set is unchanged and only the runner images point at
ghcr.io/infinito-nexus/core/mirror/docker.io/..., and an existing role mirror
under that scheme pulls anonymously.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 10:37:41 +02:00