Commit Graph
110 Commits
Author SHA1 Message Date
kevinveenbirkenbach d235dd7d7e Merge branch 'feature/swarm-nfs' into feature/svc-net-tor 2026-08-07 17:28:13 +02:00
kevinveenbirkenbachandClaude Opus 5 71888d86af refactor(ci): prefix workflows by trigger
The workflow directory mixed entry points, reusable stages and scheduled jobs into one flat list sorted by topic, so the first question about any file - what starts it - was the one the name did not answer. Every workflow now carries one of three prefixes, derived from its on: block: cron- for a schedule trigger, call- for one only reachable through workflow_call, entry- for everything a repository event or a human starts. Scheduled workflows additionally carry an alarm clock as the first character of their name.

Two lints keep it that way. One derives the expected prefix from the triggers and compares it against the file name; it flagged all 17 offenders before the rename and none after. The other resolves every .github/workflows/<file> string in the repository against the directory, which is what a rename silently breaks in Markdown links, Python constants and shell API URLs alike.

Renaming pulled 69 pre-existing comments into the comment lint's changed-file scope. The vendored CodeQL template got the nocheck marker its scorecard sibling already carried, two workflow headers moved above name: where they count as file headers, and the remaining banners were deleted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 17:05:51 +02:00
kevinveenbirkenbachandClaude Opus 5 2e3438cc99 feat(ci): rerun the failed jobs of cancelled runs
A run cancelled by a concurrency group loses the work it had already finished. An hourly sweep now walks every branch, and when its latest run ended cancelled it restarts only the failed jobs through gh run rerun --failed, keeping the successful ones.

CI_RERUN_INTERVAL_MINUTES, CI_RERUN_MAX_ATTEMPTS and CI_RERUN_MAX_AGE_HOURS tune the sweep. A cron expression cannot read a repository variable, so the workflow is triggered every full hour and a gate lets only the hours through that open the configured window; the gate reads the hour, not the minute, so a late start still counts for its hour. An unset attempt ceiling means no ceiling, bounded only by the 50 reruns GitHub allows per run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 17:05:51 +02:00
kevinveenbirkenbachandClaude Opus 5 00c02251c0 Merge branch 'feature/swarm-nfs' into feature/svc-net-tor
Every conflicted hunk resolves onto the swarm-nfs side, because on all six
files it is the strict superset: it carries the display-name codec
(utils/roles/display.py) the CI role selections now encode through, and the
sequencing glyph move from 910748ec4. svc-net-tor holds no unique content on
any of them -- runs.py even had non-conflicted hunks already calling `codec`,
so keeping its side would have left a NameError.

The lint fallout is settled in the same commit, as 4afc9b770 did for the
previous merge: the shell-or-true lint lives on svc-net-tor (c505ce746) and
swarm-nfs brings two unmarked `|| true` sites with it. Both get a nocheck
reason rather than a rewrite, because neither swallows anything -- the
slaptest call is the measured exception of c4bbbba93 and has_cn_config decides
right below it, and the dockerd restart is followed by an is-active poll that
fatals. In both the exit code was never the authority.

make quality green end to end.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 07:47:15 +02:00
kevinveenbirkenbachandClaude Opus 5 add00234c9 feat(ci): name deploy jobs after the role, not its id
CI deploy jobs were titled with the raw role id, so a run page read as a
column of dash-joined prefixes. They now carry a display name: the category
path as hanzi labels, then the role's README heading or the part of the id
the categories do not already say, whichever is shorter.

utils/roles/display.py owns both directions. Encoding happens once, at
discovery time, as a `label` key on every matrix entry; the workflows render
`<glyph> ${{ matrix.label }}` and keep the glyph as a literal because the
retrigger reads the deploy mode off it.

Decoding is what makes the retrigger keep working. It accepts a display name
with or without its variant shard and a bare role id unchanged, so job names
from runs that predate this still parse and a hand-typed dispatch never has
to spell hanzi. The parser therefore stopped matching an id-shaped tail and
asks the codec instead; a job the codec does not recognise is skipped rather
than counted as a role.

The role selections go out as display names too, so the run title reads like
the jobs it spawns. Four consumers decode them back, each the single place
the value is actually used: the whitelist resolver before it checks the role
directory, the discovery query on its env, and the sequencing and plan CLIs
on their arguments.

Every fragment is joined by `·`, including before the title, and the heading
loses its whitespace and any emoji variation selector. A display name holding
a space would make the space-separated whitelist and priority inputs
unparseable with more than one role.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 20:09:53 +02:00
kevinveenbirkenbachandClaude Opus 5 3883d73c43 feat(ci): retrigger reproduces the source run
The REST API answers inputs: null for a finished workflow_dispatch, so
the run name is the only record of what a manual run was dispatched
with. It carried one value, distros; everything else silently fell back
to the workflow defaults, and a failure that only reproduces on swarm
with btrfs came back green from a run that deployed all three modes on
whatever filesystem it drew.

Render every input as its own <emoji><value> segment instead, omitted
when it holds its default, so the title carries exactly what deviates
and an absent input means 'leave it on the default'. run_name derives
those segments from the workflow rather than knowing them, so the format
cannot drift away from the parser without a failing test.

distros no longer defaults to debian -- empty draws one at random, the
way untagged pushes already do. That leaves nothing in the title, so the
effective list is recovered from the discover job names, matched by
content against the meta/distros.yml SPOT rather than by job-name shape.

Priority roles that fell behind the budget cut in every mode hold no job
at all, so failed_roles cannot see them: they are neither green nor red,
they never ran. Carry them along or they never deploy.

--run now resolves before the selection, so it reproduces a run whatever
picks the roles, not only with --failed.

tests/utils/ci_*.py move into tests/utils/ci/ -- two files sharing a
delimiter-bounded prefix are a folder in disguise, and the structure
lint says so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 10:47:40 +02:00
kevinveenbirkenbach eb921895ff Merge branch 'feature/swarm-nfs' into feature/svc-net-tor 2026-08-06 10:43:55 +02:00
kevinveenbirkenbachandClaude Opus 5 afe20c0c76 feat(ci): retrigger reproduces the source run
The REST API answers inputs: null for a finished workflow_dispatch, so
the run name is the only record of what a manual run was dispatched
with. It carried one value, distros; everything else silently fell back
to the workflow defaults, and a failure that only reproduces on swarm
with btrfs came back green from a run that deployed all three modes on
whatever filesystem it drew.

Render every input as its own <emoji><value> segment instead, omitted
when it holds its default, so the title carries exactly what deviates
and an absent input means 'leave it on the default'. run_name derives
those segments from the workflow rather than knowing them, so the format
cannot drift away from the parser without a failing test.

distros no longer defaults to debian -- empty draws one at random, the
way untagged pushes already do. That leaves nothing in the title, so the
effective list is recovered from the discover job names, matched by
content against the meta/distros.yml SPOT rather than by job-name shape.

Priority roles that fell behind the budget cut in every mode hold no job
at all, so failed_roles cannot see them: they are neither green nor red,
they never ran. Carry them along or they never deploy.

--run now resolves before the selection, so it reproduces a run whatever
picks the roles, not only with --failed.

tests/utils/ci_*.py move into tests/utils/ci/ -- two files sharing a
delimiter-bounded prefix are a folder in disguise, and the structure
lint says so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 10:01:42 +02:00
kevinveenbirkenbachandClaude Opus 5 0bed81c048 Merge branch 'feature/swarm-nfs' into feature/svc-net-tor
Twelve conflicts, resolved by intent rather than by side.

Six shared one shape: the incoming side adds the new rescue_index.sh call but
drops this branch's `# nocheck: shell-or-true` markers. The markers carry a
lint contract here, so the incoming structure was taken and the markers put
back — dedicated.sh, guide/one.sh, guide/host_deploy.sh, swarm/routine/00_one.sh
and trigger_units.sh, where the incoming version additionally writes the full
journal to a file instead of only echoing a tail.

web-app-matrix keeps HEAD's _synapse_extra_args accumulator (the superset: CA
mount plus the onion case) but adopts the incoming improvement inside it — the
inlined regex_replace gives way to lookup('subnet_gateway').

handlers/__init__.py drops rescue_diagnostics_dir from the old import group
(the incoming side moved it under .infinito.rescue, keeping both would import
it twice) and keeps tor_socks_port, a HEAD-only handler ORDERED_HANDLERS still
needs.

svc-bkp-volume-2-local takes the incoming side throughout: both branches landed
the same feature, but swarm-nfs evolved it — the knob moved from the global
STORAGE_BACKUP_SNAPSHOT_MODE to services.volume-2-local.snapshot_mode, plus the
subvolume helper, 04_snapshot_subject.yml, meta/packages.yml and baudolo 3.2.2.
Three files that left behind were removed: files/baudolo_snapshot.py and the two
worktree tests at their pre-refactor paths, all byte-identical to their moved
counterparts.

Six follow-on failures the merge produced, fixed at the root:

- STORAGE_BACKUP_SNAPSHOT_MODE lost its last reader with the config-lookup
  design and is gone from group_vars.
- utils/github/variant_bundle_limits.py sat next to variant_bundles.py, which
  the prefix lint forbids; the pair now lives in utils/github/variant/ as
  bundles.py and bundle_limits.py, with the -m entry point, the ruff
  per-file-ignore, the design-doc link and the mirroring test path updated.
- The incoming WordPress addon spec navigated with a raw page.goto and carried
  three bare timeout literals; both route through gotoOnion and resolveTimeout
  now, which is what makes navigation survive onion transport.
- Six unmarked `|| true` got a concrete reason each: docker.socket is absent on
  distros that ship docker without it, the loop device is already detached when
  the probe failed before attaching, and modprobe zfs may fail because the
  /dev/zfs check decides.

make quality green across all four targets. The cold-domains budget test that
had to be waived here passes on its own now — 19d56f07e raises it to 100s.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 03:00:02 +02:00
kevinveenbirkenbachandClaude Opus 5 6c816e893e refactor(github): group the variant modules and drop the blind-except
variant_bundle_limits.py next to variant_bundles.py formed the prefix group
'variant', which the structure lint reads as a folder in disguise. It stayed
green locally because that lint asks git ls-files: the new file was untracked
through every quality run and only became visible once committed. CI saw it
immediately - the sole failure of run 30675284663.

Both move to utils/github/variant/ as bundles.py and limits.py, and the test
mirrors along.

The BLE001 per-file ignore is removed rather than repointed. parse_size raises
humanfriendly.InvalidSize for a malformed string and TypeError only for a
non-string, and the value reaching it is always str - so the catch-all was
never needed. Measured, not assumed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 02:46:36 +02:00
kevinveenbirkenbachandClaude Opus 5 363a4be068 feat(ci): index the rescue tree at every collection site
The rescue artifact is a tarball of whatever the collectors managed to grab, so
reading a failed run meant downloading it to find out which files exist at all.
Run rescue_index.sh right after each collection instead, so the job log names
the tree the artifact contains.

All four sites call it without a fallback, which is what the preceding commit's
exit-status contract is for. host_deploy.sh and one.sh hoist the rescue path
into a variable first - it is now needed twice, once for the collector's
INFINITO_RESCUE_DIAGNOSTICS_DIR and once for the index.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 02:13:29 +02:00
kevinveenbirkenbach 0e15eada75 Merge branch 'feature/swarm-nfs' of github.com:kevinveenbirkenbach/infinito-nexus-core into feature/svc-net-tor 2026-07-30 17:49:40 +02:00
kevinveenbirkenbachandClaude Opus 5 bc8e45b934 refactor(deps): make pyproject the single source for Python deps
The CI deploy bootstrap hand-listed two packages and let the ubuntu-latest runner image supply the rest, so a new host-side import surfaced as a red deploy job instead of a failed install. Size [project.dependencies] to what runs outside the project image and move the deployment stack into a deploy extra, then have every install site read that instead of naming packages itself: the image build and make install-python take the deploy extra, the CI host and the mirror and instructions workflows take the base.

Declare cryptography, which utils/manager/value_generator.py imports directly and which only reached the environment transitively through ansible. Drop numpy, which nothing in the repository imports. Guard the boundary with a lint test that blocks every deploy package and asserts the runner-host CLI still builds its parser.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 16:20:09 +02:00
kevinveenbirkenbach e04c448cdd Merge branch 'feature/swarm-nfs' into feature/svc-net-tor
Three conflicts, none resolved by picking a side wholesale:

- Makefile: took the incoming ACT_INPUTS, which routes through SWARM_DISTROS
  instead of inlining the same expression a second time.
- test-e2e-playwright/02_run_one.yml: took the incoming rc handling. An absent
  rc now counts as failure; the previous default(0) let an aborted Playwright
  run pass silently, contradicting the debug message right above it.
- web-app-peertube spec: combined both. Kept gotoOnion and resolveTimeout,
  which this branch's lint enforces, and adopted the incoming retry-inside-poll
  for the OIDC button plus its networkidle wait.
2026-07-30 15:38:15 +02:00
kevinveenbirkenbachandClaude Opus 5 36cd39533a fix(ci): stop reporting success for work that did not happen
The pipeline gate only listed twelve of seventeen jobs in its needs, leaving out wait-fork-prereq-run, fork-prereqs-ready, build-ci-images, images-mirror-missing and test-dns. A failure in any of them turns its dependents into skipped rather than failure, and the gate's check matches only failure and cancelled, so the run would have concluded green without images ever being built.

A caller-supplied whitelist was also passed through verbatim. Run 30492550883 was dispatched with web-app-prometheu; discovery dropped the unknown id without a warning, the empty resolution became [], the deploy workflows skipped their whole matrix on that condition and the parent job still concluded success. Validate every id against roles/<id> and exit naming the offenders, so a typo cannot quietly remove an application from a run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 15:08:51 +02:00
kevinveenbirkenbachandClaude Opus 5 accb7a33c5 style(swarm-ci): justify the trap-local '|| true' the merge brought in
The swarm branch predates the shell-or-true lint, so its EXIT-trap collectors
arrived unmarked. They are the legitimate case: best-effort diagnostics and
teardown that must not mask the original exit code, which the trailing
return "${rc}" preserves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 17:02:15 +02:00
kevinveenbirkenbach 3f84626883 Merge branch 'feature/swarm-nfs' of github.com:kevinveenbirkenbach/infinito-nexus-core into feature/svc-net-tor 2026-07-29 16:30:43 +02:00
kevinveenbirkenbachandClaude Opus 5 f482b979bd refactor(ci): rename env/guide jobs, drop roles_only
test-environment.yml and test-guide.yml become test-workspace.yml and
test-instructions.yml. Orchestrator job ids, the distros-SPOT matrix
lint, the act example, the diagnostics slug and the rescue artefact
names follow.

Replace the roles_only input with per-job workspace/instructions
tri-state inputs (auto|true|false). 'auto' derives the former
roles_only skip straight from whitelist: both jobs run on a global
scope (whitelist empty or __ALL__) and are skipped on a role-scoped
run, which keeps the PR behaviour identical because the diff resolver
never emits an empty whitelist. entry-manual exposes both as choice
dropdowns defaulting to false, so a manual run opts in explicitly.
The now redundant roles_only output is dropped from
pr_affected_roles.sh and subset_roles.py.

Branch-protection required-status-check names change along with the
job renames.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 12:55:04 +02:00
kevinveenbirkenbachandClaude Opus 5 ef0f03ea98 fix(guide): full-sync arch in the boot image instead of a partial -Sy
The guide job builds its boot image from a pinned pkgmgr-arch parent that
can be weeks old, and `pacman -Sy systemd` against it is the partial
upgrade arch upstream documents as unsupported. It pulled systemd 261.2-1
next to the image's systemd-sysvcompat, which pins systemd exactly, so the
transaction could not be satisfied and every arch guide run died before
the README sequence started.

Refreshing archlinux-keyring first and then running a full -Syu --needed
fixes it; the cache wipe matches what the apt and dnf branches already do.
The upgrade lands in the PREP container, which runs sleep infinity, so
systemd is only ever replaced before it becomes PID 1 in the committed
image.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 09:09:40 +02:00
kevinveenbirkenbachandClaude Opus 5 dce60df815 feat(ci): replay the guide on every distro
guide_select picked the role, the mode and the distro in one draw, so a run proved the README instructions worked on exactly one randomly chosen distribution. The distro list was also a literal in the workflow, which meant the manual-CI distros input never reached the guide at all.

The distro draw is removed. guide_select still picks the role and derives the mode, while the shared loop from 1bbe7a0eb drives the distro dimension, and ci-orchestrator forwards its distros input.

The four mode-specific steps collapse into guide/one.sh, which the loop runs once per distro: it renders the pkgmgr base image for the current distro, then either execs host_deploy.sh or brings the compose server up and replays the README block against it.

one.sh releases the compose server in an EXIT trap. Without it the second distro would start against the stack the first one left behind. Rescue diagnostics gain a distro segment for the same reason the swarm collectors did.

The job timeout goes from 90 to 345 minutes because up to five rounds now have to fit; the loop's own budget still decides how many actually run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 21:06:47 +02:00
kevinveenbirkenbachandClaude Opus 5 ebd7d2e72b refactor(meta): centralize category/distro SPOTs
categories.yml moves from roles/ to the repository-root meta/, joining packages.yml. utils/roles/categories.py owns its location plus the tree-walking helpers that were stranded in entity/name.py, so no caller spells the path or duplicates the lookup.

meta/distros.yml becomes the SPOT for every distribution: os_family, Galaxy platform name, dev-environment base image, and the pkgmgr/pkgmgr-virgin/environment reference templates. It replaces seven scattered copies (DISTRO_FAMILY, VALID_DISTROS, _DISTROS, _CANONICAL_PLATFORMS, two shell arrays, a Makefile literal). INFINITO_DISTROS is no longer a static default but generated by utils/env/handlers/infinito/distros.py; the shell consumers read it through the env layer.

Drops dead CLI surface found along the way: --categories-file and --roles-dir on the provision CLI plus three unused parameters of generate_dynamic_inventory, and the positional roles_file of the invokable categories command.

tests/lint/repository/test_distros_spot.py pins the copies that cannot call the SPOT (workflow literals, docs table) and forbids composing a pkgmgr reference outside it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 16:15:56 +02:00
kevinveenbirkenbach 71c9f10dfd Merge branch 'feature/swarm-nfs' into feature/svc-net-tor 2026-07-28 11:22:47 +02:00
kevinveenbirkenbachandClaude Opus 5 e409674718 refactor(meta): centralize category/distro SPOTs
categories.yml moves from roles/ to the repository-root meta/, joining packages.yml. utils/roles/categories.py owns its location plus the tree-walking helpers that were stranded in entity/name.py, so no caller spells the path or duplicates the lookup.

meta/distros.yml becomes the SPOT for every distribution: os_family, Galaxy platform name, dev-environment base image, and the pkgmgr/pkgmgr-virgin/environment reference templates. It replaces seven scattered copies (DISTRO_FAMILY, VALID_DISTROS, _DISTROS, _CANONICAL_PLATFORMS, two shell arrays, a Makefile literal). INFINITO_DISTROS is no longer a static default but generated by utils/env/handlers/infinito/distros.py; the shell consumers read it through the env layer.

Drops dead CLI surface found along the way: --categories-file and --roles-dir on the provision CLI plus three unused parameters of generate_dynamic_inventory, and the positional roles_file of the invokable categories command.

tests/lint/repository/test_distros_spot.py pins the copies that cannot call the SPOT (workflow literals, docs table) and forbids composing a pkgmgr reference outside it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 11:19:30 +02:00
kevinveenbirkenbachandClaude Fable 5 62cf9bd358 style(runner): mark rollback or-true with nocheck reasons
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 09:04:21 +02:00
kevinveenbirkenbach 0107915009 Merge remote-tracking branch 'fork/feature/swarm-nfs' into feature/svc-net-tor 2026-07-24 20:11:01 +02:00
kevinveenbirkenbachandClaude Opus 4.8 51dbc46876 ci(runner): relocate Docker data-root onto /mnt when roomier
Swarm's nested DinD-node daemons plus the backup DR drill (LUKS loop image,
per-node registry pulls) fill the ~50 GB root fs and trip the <6 GB abort
guard in utils/tests/swarm/matrix.py, while /mnt is a separate ~74 GB
partition sitting idle behind only a swapfile.

Add a runner step that moves the host Docker data-root to /mnt/docker when
/mnt is a separate, roomier partition (detection mirrors enlarge_swap.sh),
before any Docker usage. Best-effort: any failure rolls Docker back onto /
and the step still exits 0, so it can never turn a green deploy red. Wired
into the compose, swarm, and host deploy workflows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 20:05:59 +02:00
kevinveenbirkenbachandClaude Fable 5 4afc9b7702 chore(lint): settle the lint fallout of the swarm-nfs merge
Mark the merged best-effort '|| true' sites (guide EXIT trap, baseline
fetch fallback) with shell-or-true nocheck reasons, parenthesize the
implicitly concatenated message strings ISC004 enforces since the ruff
0.16 pin, and drop the regex narration comments the comment lint flags
on the now-changed test files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 19:13:30 +02:00
kevinveenbirkenbach 1a0af0ce09 Merge remote-tracking branch 'fork/feature/swarm-nfs' into feature/svc-net-tor
# Conflicts:
#	roles/sys-ctl-mtn-cert-deploy/tasks/main.yml
#	roles/sys-svc-compose-ca/tasks/main.yml
#	roles/web-app-dashboard/templates/javascript/oidc.js.j2
#	roles/web-app-mastodon/tasks/01_overrides.yml
#	roles/web-app-peertube/templates/compose.yml.j2
#	scripts/github/guide/compose_deploy.sh
#	scripts/github/guide/host_deploy.sh
#	scripts/make/alias.sh
#	tests/lint/ansible/roles/test_service_core_first_task_run_once.py
2026-07-24 04:30:56 +02:00
kevinveenbirkenbachandClaude Opus 4.8 6b1dd1c1d3 refactor(guide): reuse the rescue-diagnostics collector and extract the compose replay
host_deploy.sh drops its bespoke dump: the EXIT trap now runs
utils/diagnostics/container.py on failure before removing the boot
container, because the workflow's failure() collect step only fires
after this script has already torn the container down. That yields the
full snapshot (logs, inspect, in-container systemctl and journal, DiD
recursion) instead of three ad-hoc files, and successful runs skip the
capture entirely.

The compose-mode README replay moves out of test-guide.yml into
scripts/github/guide/compose_deploy.sh unchanged; the workflow step
becomes a one-liner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:01:31 +02:00
kevinveenbirkenbachandClaude Opus 4.8 0d71c041e7 fix(guide): capture host-mode boot diagnostics before teardown
The host-deploy EXIT trap now dumps failed units, the tail of the
journal, and the container log from the pkgmgr boot container into the
rescue-diagnostics base before removing the container, so a failed guide
run leaves a readable artifact instead of tearing down silently. The
base directory is hard-required from the workflow environment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:01:31 +02:00
kevinveenbirkenbachandClaude Fable 5 ea54eb609c refactor(guide): extract the compose README deploy into a script
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 16:36:12 +02:00
kevinveenbirkenbachandClaude Fable 5 62a46e472f Merge branch 'feature/swarm-nfs' into feature/svc-net-tor
Resolves the runner Dockerfile to the lint-standard Acquire::Retries=3
and keeps the collabora TODO wording; hardens the merged tree for the
stricter lints (apt retries in the tor Dockerfile, shell-or-true
markers, comment cleanup).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 16:27:25 +02:00
kevinveenbirkenbachandClaude Opus 4.8 fb78b17901 fix(guide): resolve *.localhost inside the host-mode boot container
The hlth-webserver health service HEAD-requests the deploy's *.infinito.localhost vhosts, but the guide boot container has no resolver mapping for them (no CoreDNS sidecar, no dev-flow dns setup), so the service failed every start and burned 25x60s retries. Install libnss-myhostname on the apt branch and ensure the myhostname NSS module is listed in nsswitch.conf on every distro; it resolves any *.localhost name to loopback where the openresty vhosts answer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 23:22:48 +02:00
kevinveenbirkenbachandClaude Opus 4.8 34669658ef fix(guide): install systemd in the initless pkgmgr image before booting it
The host-mode guide booted the pkgmgr distro image with --entrypoint
/sbin/init, but that image ships no init at all (it is a package-manager CLI
tool), so runc rejected the container with exit 127. Install systemd in a
throwaway container, commit an image that carries /sbin/init, and boot that
instead. Also read the post-loop readiness state into a variable so a
degraded systemd is not masked by pipefail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 11:14:44 +02:00
kevinveenbirkenbachandClaude Opus 4.8 fc1b35e9a7 fix(guide): unmask systemd-logind before enabling root linger
The host-deploy base image ships systemd-logind masked, so loginctl
enable-linger cannot activate org.freedesktop.login1 over D-Bus and the
guide fails after the image pull with "Could not activate remote peer".
Unmask and start logind before enabling linger; it is a no-op where logind
is already active.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 03:01:35 +02:00
kevinveenbirkenbachandClaude Opus 4.8 fb731041f4 refactor(ci): extract fork-prereq gate to a script; drop gate jobs
Move the inline fork-prerequisites result assertion out of the
fork-prereqs-ready job into scripts/github/gate/fork_prereqs.sh (result
passed via the RESULT env var; the job now checks out the repo).

The staged diff also drops the code-quality-gate and test-install-gate
aggregator jobs and wires their members (lint, test, security-codeql,
test-install-make, test-install-pkgmgr) directly into the done job's needs,
which still hard-fails on any upstream failure or cancellation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 03:01:35 +02:00
kevinveenbirkenbachandClaude Opus 4.8 972596a758 fix(guide): document the administrator SSH key step and seed it in CI via --vars
The README Production block went provision -> deploy with no admin-key step, so user-administrator failed early (authorized_keys empty) for both the Guide CI jobs and any real user copying the README. Add a visible 'provision --vars' step that sets users.administrator.authorized_keys (merged into host_vars) in both Production variants of the role README template, regenerate all role READMEs, and replace the invisible CI payload splice with a visible substitution of the documented <your-ssh-public-key> placeholder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 05:45:26 +02:00
kevinveenbirkenbachandClaude Fable 5 d88f272f9f Merge branch 'feature/swarm-nfs' into feature/svc-net-tor
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 15:31:08 +02:00
kevinveenbirkenbachandClaude Opus 4.8 0ee8d9cde9 fix(ci): extract the guide host-deploy step into a script
The inline run: block for the host-mode deploy exceeded the 11-line
inline-script-block cap. Move the body to scripts/github/guide/host_deploy.sh
and reduce the workflow step to a single bash invocation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 02:39:28 +02:00
kevinveenbirkenbachandClaude Fable 5 407b8a8a1f fix(e2e): use the tls lookup instead of the unregistered urlsplit filter; autoformat
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 21:05:59 +02:00
kevinveenbirkenbachandClaude Fable 5 c505ce7466 test(lint): forbid unmarked '|| true' in shell scripts; grandfather the corpus
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 20:39:16 +02:00
kevinveenbirkenbachandClaude Fable 5 40e553069d refactor(structure): nest prefix-grouped sibling files into subfolders
Move files that share a delimiter-bounded name prefix within a folder (threshold 2) into a subfolder named after the prefix, dropping the prefix from the filename. Covers plugins/filter (docker, generate, has, merge, get/all), env handler subpackages, utils/roles/entity, utils/tests/swarm/write, role SQL/scripts/templates, keycloak/mailu task-includes, shared tasks/utils/load, and CI scripts. Update every import, query_file/script/include_tasks/src/lookup target, compose bind mount, and python -m path to the new locations.

Extend the prefix-grouped-files lint to threshold 2 with dir-level .nocheck and file-head nocheck opt-outs, and skip groups whose shortest member's stem equals the shared prefix. Keep flat only where a loader forbids nesting: WordPress mu-plugins and the docs/requirements namespace. Ansible filter discovery is recursive, so nested filters stay Jinja-resolvable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 20:00:03 +02:00
kevinveenbirkenbach 9b2f870777 Merge branch 'main' into feature/swarm-nfs 2026-07-01 05:37:36 +02:00
Alejandro Roman 8f64fbb69e Merge remote-tracking branch 'upstream/main' into feature/self-host-runners 2026-06-30 10:20:37 +02:00
kevinveenbirkenbachandClaude Opus 4.8 80e03b4c20 fix(ci): resolve subset roles with venv python
The 🧩 Subset path in pr_affected_roles.sh exec'd a bare `python`, the setup-python interpreter without PyYAML, so subset_roles.py failed with `ModuleNotFoundError: No module named 'yaml'`. Source scripts/meta/env/load.sh so ${PYTHON} resolves to the venv (where PyYAML is installed), matching the diff resolver path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 01:42:47 +02:00
kevinveenbirkenbachandClaude Opus 4.8 6bbe3f61e3 fix(ci): collect inventory from host inventory dir, not container/tmp
The compose and swarm inventory collectors hard-failed at the 'Zip generated
inventory' step because they looked in the wrong place. Inventory is written on
the runner host at $INFINITO_INVENTORY_DIR (resolve.sh yields
$HOME/inventories/github in CI), but:

- zip_compose_for_upload.sh ran 'docker cp' from infinito_nexus_* containers,
  which dedicated.sh's cleanup trap removes before the always() step runs, so it
  never found a container.
- zip_swarm_for_upload.sh globbed /tmp/inv*, which the matrix orchestrator never
  populates; it defaults its base to $INFINITO_INVENTORY_DIR.

Both now source scripts/meta/env/load.sh to resolve INFINITO_INVENTORY_DIR and
zip that dir plus its per-round '-<n>' variants.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 00:59:49 +02:00
kevinveenbirkenbachandClaude Opus 4.8 b6e79878fd fix(ci): make 🧩 Subset resolver comply with repo lint rules
The merged subset feature failed test-lint: a .py under scripts/, a local parents[N] PROJECT_ROOT, a direct yaml.safe_load, an uncached read, and narration comments.

- move scripts/meta/resolve/pr/subset_roles.py -> cli.meta.ci.subset_roles (scripts/ is .sh-only); dispatcher invokes it via python -m
- parse via utils.cache.yaml.load_yaml_str; import utils.PROJECT_ROOT
- test invokes the module via -m against real roles; nocheck the tempfile read
- strip narration comments; docs document HOW not WHY

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 00:59:49 +02:00
kevinveenbirkenbach 19b344d1dc Merge branch 'main' into feature/swarm-nfs 2026-06-30 00:59:49 +02:00
kevinveenbirkenbachandClaude Opus 4.8 0d1e718abf feat(ci): add 🧩 Subset label for explicit role selection on PRs
The maintainer-only manual CI dispatch (workflow_dispatch) cannot be triggered by fork contributors. The 🧩 Subset label closes that gap: when present on a PR, the detect-affected-roles job resolves the deploy whitelist from an explicit roles: YAML block in the PR body instead of the diff, giving fork PRs the same narrowed matrix a maintainer would dispatch manually. Without the label, behavior is unchanged (the dispatcher falls through to the diff resolver, output byte-identical).

- subset_roles.py: parse and strictly validate the PR-body roles block (invalid YAML / empty / unknown id / path-traversal all hard-fail)
- pr_affected_roles.sh: single dispatcher selecting subset vs diff resolver
- entry-pull-request-change.yml: wire the label and PR body into the job
- PR templates, pipeline/workflows docs and READMEs, integration tests

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 00:59:48 +02:00
Alejandro Roman d832e88cec Merge remote-tracking branch 'upstream/main' into feature/self-host-runners 2026-06-30 00:14:59 +02:00