Commit Graph
14682 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 4.7 14fc419cbf fix(roles/sys-ctl-cln-faild-bkps): scope required_by to svc-bkp
Cleanup of failed backups is a backup-category concern, not a web one;
align with the sibling `sys-ctl-cln-bkps`, which already pins
`required_by.categories: [svc-bkp]`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 06:48:13 +02:00
kevinveenbirkenbachandClaude Opus 4.7 4104eb1b8a style(roles/web-app-*/variants): single-space before # nocheck comment
Autoformat byproduct — normalize the double space before the trailing
`# nocheck: variants-static-override` marker across seven role variants.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:31:16 +02:00
kevinveenbirkenbachandClaude Opus 4.7 88bac5b8cf docs(make-exec-rename): update remaining make exec references
Trailing textual references missed in the prior cmd-migration commit:
three requirement docs and two lint-test docstrings still pointed at
`make exec`, but the target has always been `make compose-exec`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:31:16 +02:00
kevinveenbirkenbachandClaude Opus 4.7 bdea4987e7 chore(claude/settings): align allowlist with cmd + quay reachability
`make exec` was a leftover from before the target was renamed to
`compose-exec`; quay.io is reached by the new image-reachability test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:31:16 +02:00
kevinveenbirkenbachandClaude Opus 4.7 63c6912fae feat(test): add live registry-reachability probe for image pins
Closes the gap left by test_image_versions, which silently skips any
non-semver pin (latest, stable, branch tags). The new test_image_reachable
walks every entry from iter_role_images and probes the Docker Registry v2
manifest endpoint via HTTPS with anonymous bearer-token negotiation
(docker.io, ghcr.io, quay.io, mcr.microsoft.com). HTTP 404 hard-fails as
a broken pin; auth/rate-limit/5xx/network errors warn. Bounded by per-
request, per-probe, and global deadlines and parallelized via
INFINITO_WORKER_FETCH.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:31:16 +02:00
kevinveenbirkenbachandClaude Opus 4.7 490f0919ab refactor(make): replace INFINITO_CMD with cmd parameter
INFINITO_CMD is a per-invocation argument, not a config value — drop it
from default.env, the passthrough STATIC_KEYS, and the dotenv-generator
test, and rename to lowercase `cmd` in Makefile targets, exec scripts,
internal callers, and user-facing docs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:31:15 +02:00
kevinveenbirkenbachandClaude Opus 4.7 19efbf83e7 fix(roles/web-app-fusiondirectory): pin image to 1.5 (no upstream latest)
nfrastack/fusiondirectory publishes no `latest` tag on Docker Hub, so the
mirror sync failed with `manifest unknown`. Pin to the rolling `1.5` tag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:57:56 +02:00
kevinveenbirkenbachandClaude Opus 4.7 7b77b28c26 refactor(cli/meta/services/called): use hyphen separator in category keys
`categories_of()` now emits the sub-level key with the same hyphen
the role-id already uses (`web-app`, `svc-bkp`, `sys-ctl`) instead of
the dot-form (`web.app`, `svc.bkp`, `sys.ctl`). Reads cleanly against
the role naming convention and removes the only place in the
matching layer where role-id segments got rewritten.

Updates the matching unit tests and the only consumer annotation
that referenced the dot form (`sys-ctl-cln-bkps`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:46 +02:00
kevinveenbirkenbachandClaude Opus 4.7 4f55c1dbf4 fix(roles): mark sys-ctl-cln-certs nocheck + add cln-faild-bkps dep to local-2-device
sys-ctl-cln-certs is gated at runtime by
`lookup('tls', application_id, 'mode') == 'letsencrypt'` inside
mtn-cert-renew's include chain, which the role-id/category-match
required_by mechanism cannot express. Mark the role with
`# nocheck: required-by-coverage` and the reason inline.

svc-bkp-local-2-device picks up sys-ctl-cln-faild-bkps as an explicit
dependency so failed snapshots on the external device are torn down
the same way they are for the container-2-local path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:46 +02:00
kevinveenbirkenbachandClaude Opus 4.7 fa4d4dfa66 refactor(roles/sys-ctl-cln-bkps): absorb sys-timer-cln-bkps wrapper
sys-timer-cln-bkps was a 4-line bundle role that did nothing the
sys-service include in sys-ctl-cln-bkps cannot do via its built-in
`system_service_timer_enabled` toggle: set the timer service to
`sys-ctl-cln-bkps` and include `sys-timer`. Drop the wrapper and turn
the timer on directly from sys-ctl-cln-bkps with a new
`SYS_CTL_CLN_BKPS_TIMER_ENABLED` default (true) so inventories can
disable the on-calendar firing without losing the service unit.

The README cross-link in svc-bkp-remote-2-local now points at
sys-ctl-cln-bkps; no other consumers referenced the deleted role.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:46 +02:00
kevinveenbirkenbachandClaude Opus 4.7 f63118f34d feat(lint): enforce auth-isolation in variants.yml
Auth-isolated matrix variants (exactly one of LDAP / OIDC-OAuth2
enabled, and no non-auth `enabled: true` alongside) must explicitly
pin every non-auth service-key in the role's variant union to literal
`enabled: false, shared: false`. Without this, the variant's
auth-only test scope balloons into a full integration deploy via the
services.yml-default propagation, wasting CI minutes and obscuring
the regression signal.

Adds `tests/integration/roles/meta/variants/test_auth_isolation.py`
(rule key `variants-auth-isolation`) plus the matching catalog entry.
The trigger is narrow on purpose: a variant that enables auth
alongside other services is a deliberate integration test, not an
auth-isolated one, and the rule does not fire there.

Mass-fixes the 24 legacy auth-isolated variants the rule turned up:
each gets the missing `enabled: false, shared: false` declarations
appended to its `services:` block. Where the false-override clashes
with a static-literal base in meta/services.yml, the override line
also picks up a `# nocheck: variants-static-override` marker. The
svc-bkp-container-2-local variant 2 additionally carries
`# nocheck: variants-services-match` on the new keys because they
are resolver-only matrix hooks with no consumer contract in the base
meta.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:46 +02:00
kevinveenbirkenbachandClaude Opus 4.7 515b735522 feat(lint): add variants-services-match nocheck opt-out
The variants → services match guard (`test_services_match.py`) used to
have no exemption mechanism, forcing every variant service-key to also
appear as a top-level entry in `meta/services.yml`. That contract is
correct for application roles whose variants pin per-app service
flags, but it forces noise on invokable infrastructure roles whose
variants exist only to pin the round's companion topology — there the
service-key is a resolver hook, not a consumer-side contract, and
adding a stub in `meta/services.yml` just duplicates the variant
declaration.

Add a `# nocheck: variants-services-match` opt-out (same line or line
above the key) so legitimate resolver-only matrix hooks can stay in
variants.yml without a synthetic services.yml stub. Mirrors the
`variants-static-override` parser pattern (text walk + per-leaf line
indices + is_suppressed_at).

Adopts the new exemption on `svc-bkp-container-2-local`:
meta/services.yml drops the gitea / keycloak / postgres / mariadb /
ldap stubs, meta/variants.yml carries the nocheck marker on each
variant service-key entry. The resolver closure per round stays
identical (16 / 3 / 2 roles).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:46 +02:00
kevinveenbirkenbachandClaude Opus 4.7 a08db7fa11 refactor(roles/svc-bkp-*): rename backup roles + make container/local-2-remote invokable
Consolidates the backup-role topology around four directional roles
under the existing svc.bkp category:

  sys-ctl-bkp-docker-2-loc  →  svc-bkp-container-2-local
  svc-bkp-loc-2-usb         →  svc-bkp-local-2-device
  svc-bkp-rmt-2-loc         →  svc-bkp-remote-2-local
  sys-bkp-provider          →  deleted (purely a bundler with no own logic;
                                the two pieces it pulled in — user-backup
                                and sys-timer-cln-bkps — are now declared
                                directly by callers that need them)

Every renamed role moves into svc.bkp (already invokable per
roles/categories.yml), gets an `application_id` in vars/main.yml, and
declares the per-app primary-entity service flags + resource limits the
invokable-role lint requires.

Companion changes:

  - svc-bkp-container-2-local: meta/variants.yml declares three matrix
    rounds (gitea+keycloak, postgres+mariadb, openldap) that pin the
    companion topology via service-key flags the CombinedResolver
    translates into provider-role includes.

  - sys-svc-compose: container-2-local is no longer in the docker-mgmt
    include loop; it joins via a separate `'svc-bkp-container-2-local'
    in group_names` gate so the 3 backup roles are deployable
    independently (each addresses its own failure mode).

  - The three `04_seed-database-to-backup.yml` includes (web-app-matrix,
    web-app-bigbluebutton, sys-svc-rdbms) are gated by the same
    group_names check so consumer apps only seed when the container
    backup is actually in scope.

  - web-app-gitea picks up `enabled: true, shared: true, provides:
    gitea` on its primary entity so the service registry can pull it in
    via a `services.gitea.enabled+shared` flag (needed for the
    container-backup variant 0).

  - group_vars constants and the pyproject ruff per-file-ignore + the
    .ansible-lint-ignore paths follow the role-id rename.

  - The remote-2-local script picks up the snake_case rename
    (`pull-specific-host.py` → `pull_specific_host.py`) and the
    `os.makedirs` → `Path.mkdir` conversion so the role no longer needs
    the PTH/N999 ruff escape hatch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 181d151124 refactor(web-app-gitea): register as provider + 01_core/main split + atomar playwright tests
Registers web-app-gitea as a service provider in the registry by adding
`enabled: true, shared: true, provides: gitea` to its `gitea:` primary
entity in meta/services.yml, so consumers (e.g. matrix-deploy variants)
can pull the role in via a `services.gitea.enabled+shared` flag instead
of needing an explicit `--id`.

Aligns the tasks layout with the project's invokable-role convention:
the bulk of the role's logic moves from `tasks/main.yml` to
`tasks/01_core.yml` (with `include_tasks: utils/once/flag.yml` as the
first task), and `tasks/main.yml` becomes a thin guard that loads
01_core under `when: run_once_web_app_gitea is not defined`. The
remaining numbered task files shift up by one (01_database → 02,
02_setup → 03, 03_cleanup → 04) so 01 stays the canonical core file
the lint expects.

Splits the playwright spec into atomar `test-*.js` modules following
the mattermost/taiga convention: `_shared.js` carries env validation,
the healthz URL helper, and persona re-exports; `playwright.spec.js`
becomes a thin orchestrator that wires the `ignoreHTTPSErrors` setting,
the `beforeEach` env-presence guard, and `require()`s each test module
in turn.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 10f320df98 fix(roles): close preload-context coverage gaps for compose+inj-all
Two roles dropped their post-preload coverage because work that
needed to re-evaluate after the preload-context flag flipped was
trapped behind a once-gate from the first (preload) invocation:

sys-svc-compose/01_core.yml: the docker-mgmt include loop
(cln-docker, bkp-docker-2-loc, hlth-container, hlth-volumes,
rpr-container-hard) lived inside 01_core, which is itself gated by
`run_once_sys_svc_compose`. First call ran during preload, set the
once-flag, and the loop's `not _service_loader_preloading` gate
fired False; subsequent compose invocations skipped 01_core entirely
so the loop never re-fired. Move the loop into main.yml outside the
once-gate so the preload-gate is the only thing left in front of
it.

sys-front-inj-all/main.yml: the `sys-svc-webserver-core` include
sat inside an outer `block: when: run_once_sys_front_inj_all is not
defined`. Preload set that flag and every later invocation skipped
the block, so webserver-core was never re-entered outside preload
and its own hlth-include retry never saw `preload=false`. Drop the
outer block (the flag had no other consumers) and call webserver-
core unconditionally; idempotency for its heavy work is already
handled by `run_once_sys_svc_webserver_core` inside webserver-core
itself. The role legitimately has no role-level once semantics (CDN,
inj-*, body_filter.lua are per-application work) so mark it
`# nocheck: run-once` with the reason inline.

Transitive misses (cln-anon-volumes, rpr-container-soft,
cln-faild-bkps) resolve once their includers (cln-docker /
hlth-container / bkp-docker-2-loc) start running again.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 255c61eec3 fix(utils/github/profile): strip ANSIBLE_LOG_PATH prefix in role_summary
The previous regex anchored the `name` capture at line start and used
lazy `.+?`, which in production captured the full
`<ts> p=<pid> u=<user> n=ansible INFO| <role>` prefix as the role name
instead of just `<role>`. The CI step summary then rendered every row
with the timestamped log header and the `total` filter silently failed
(because `name` was no longer equal to `"total"`).

Add an optional non-capturing `(?:.*\bINFO\|\s+)?` group so the
ANSIBLE_LOG_PATH prefix is consumed when present and `name` captures
just the role token (or `role : task`). Test sample didn't carry the
log prefix, so the bug slipped through — add a prefixed-format test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 c3ff093df0 refactor(scripts/deploy/local): mirror host wrappers with container/ subtree
Renames the three in-container deploy helpers out of `utils/` (where
they lived next to unrelated bash libs) into a `container/` subtree
that mirrors the host-side `apps/` layout one-to-one:

  utils/fresh-kept-all-init-and-deploy.sh  -> container/initialize/all.sh
  utils/reuse-kept-all-deploy.sh           -> container/update/all.sh
  utils/reuse-kept-app-deploy.sh           -> container/update/selection.sh

The `fresh-/reuse-/kept-` prefixes were overloaded ("kept" was
universal, "fresh/reuse" duplicated the verb already encoded in
initialize/update). New names use the same verbs + scope as the host
wrappers, so the host/container halves of each flow share a name.

Updates the three host wrappers under apps/{initialize,update}/ to
point at the new in-container paths and modernizes their
cache-retry keys (`fresh-kept-all` -> `initialize-all`, `reuse-all` ->
`update-all`, `reuse-<app>` -> `update-<app>`).

Adds READMEs for container/, container/initialize/, container/update/
following the project documentation conventions (H1 + trailing emoji,
RFC 2119 keywords, env-var tables, cross-links to the matching host
wrappers) and extends deploy/README.md with a pointer to the new
subtree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 053fedcbeb refactor(required_by-lint): SPOT helpers + yaml-aware mailu test
- Move role_is_invokable / role_has_required_by / role_has_nocheck out
  of plugins/filter (where they were flagged as test-only filters) into
  utils/roles/required_by_coverage.py. All three use load_yaml_any,
  read_text, and ROLE_FILE_META_SERVICES from the canonical SPOTs.
- Refactor the verifier (cli.meta.roles.services.called) the same way:
  iterate roles/*/ directly, look up meta/services.yml via the constant,
  parse with load_yaml_any, and tolerate a missing roles_dir (unit tests
  point at a fake /repo).
- Make the mailu-dependency integration test yaml-aware for meta/
  services.yml: walk the parsed tree and skip values inside any
  `required_by` subtree. `required_by` is inverse coverage metadata for
  the deploy verifier, not a "consumer of mailu" reference, so previous
  regex-on-text matching produced false positives for cert-deploy.
- sys-ctl-mtn-cert-renew is gated by `lookup('tls', application_id,
  'mode') == 'letsencrypt'`, which the verifier can't express via
  role-id/category match — mark nocheck with the reason inline.
- Add README.md for cli/meta/roles/services (CLI-category convention).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 8235907ee9 refactor(cli/deploy): move required_by verifier into dedicated runner
Hooks the verifier into `cli.administration.deploy.dedicated.runner.
run_ansible_playbook` so every deploy path that reaches the dedicated
runner is checked — including `compose-deploy mode=update`, bundle
redeploys, and direct production calls, which previously bypassed the
dev-wrapper's verifier.

Runner pins ANSIBLE_LOG_PATH, captures pre-run log size, then verifies
the post-run slice host-locally (no more `docker exec tail` indirection
from the dev wrapper). Dev wrapper drops its redundant verifier call.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 f93f9ada7c feat(lint): require required_by on non-invokable roles (Bundle C)
Adds a static lint that hard-fails when a non-invokable role's
meta/services.yml declares neither `required_by` nor `nocheck:
required-by-coverage`. This protects the runtime verifier
(cli.meta.roles.services.called) from silently losing coverage when new
infrastructure roles ship without a coverage contract.

New filter plugins (reusable from Ansible + Python tests):
  - role_is_invokable: walks categories.yml; true if any node on the
    role's category path is `invokable: true`.
  - role_has_required_by: true if any entity in meta/services.yml declares
    a non-empty `required_by.categories` or `required_by.roles`.
  - role_has_nocheck: true if meta/services.yml carries
    `# nocheck: <check_id>` (whole-word match).

New test: tests/lint/ansible/roles/meta/test_required_by_for_non_invokable.py
fails CI with a per-file list and a fix recipe (declare required_by OR add
the nocheck marker — both already shipped in Bundle B and Bundle D).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 4bd23d5f1b chore(roles): mark non-invokable roles with nocheck:required-by-coverage (Bundle D)
Adds `# nocheck: required-by-coverage — TODO: declare proper required_by`
markers to meta/services.yml of 74 non-invokable system roles (dev-*, pkgmgr*,
sys-aur*, sys-front-inj-*, sys-front-tls-*, sys-svc-{cdn,certbot,certs,
cln-domains,dns,journalctl,mail*,proxy,rdbms,ssh,sshd}, sys-timer*, sys-stk-*,
user-*, …).

This is the passthrough layer that lets the upcoming `required_by` lint
(Bundle C) hard-fail any *new* non-invokable role without an explicit coverage
declaration, while existing roles get a documented TODO grace period instead
of an immediate CI break.

No runtime effect — comments only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 1866d624e3 feat(roles): expand required_by[web] for deploy verifier (Bundle B)
Annotates 23 non-invokable system roles with `required_by: { categories: [web] }`
in their meta/services.yml so the runtime verifier
(cli.meta.roles.services.called) flags coverage regressions when any web-app
deploy fails to trigger them.

Covers: docker mgmt (cln/hlth/rpr/bkp), compose chain (svc-compose, svc-compose-ca,
svc-container), stack assembly (stk-full, stk-front-proxy), front-inj-all,
alm (compose/email), cln (anon-volumes, bkps, certs, disc-space, faild-bkps),
hlth (container, disc-space, journalctl, volumes), rpr (container-hard/-soft),
mtn-cert (deploy/renew).

Scope deliberately narrowed to [web] only — svc is reserved for explicit
service-only deploys to avoid false positives.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 274e4fd6f8 feat(roles): mark webserver+CSP infra as required_by web for deploy verifier
Adds `required_by: { categories: [web] }` to the four roles that MUST run
on every web-app/web-svc deploy: sys-svc-webserver-{core,https} (the
nginx/openresty stack) and sys-ctl-hlth-{webserver,csp} (the deploy-time
coverage checks).

Activates the cli.meta.roles.services.called verifier (added in the
previous commit). Without this annotation the verifier finds no required
roles and silently passes; with it, every web-* deploy round/pass asserts
that these four roles actually executed in the Ansible log, catching
silent coverage regressions like the one fixed in
158681821 (sys-svc-webserver-core: hlth-include moved out of once-flag).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 8bf4a5f08e feat(cli/meta): add cli.meta.roles.services.called verifier + dev-deploy hook
Asserts at deploy time that every role declaring `required_by` in its
meta/services.yml actually executed (at least one TASK block ending with
a non-skip status of `ok` / `changed` / `fatal` / `included`). Catches
silent coverage regressions where a once-flag swallows an include before
the role's body can run.

CLI: `python -m cli.meta.roles.services.called --logfile X --apps a,b,c`
Library: `cli.meta.roles.services.called.verify(...)` supports per-pass
slicing via `log_byte_offset` and reads either host-side log files or
in-container logs when `container=` is given.

Hooked into `cli.administration.deploy.development.deploy._run_deploy`
so matrix-deploy runs (CI + local make compose-deploy mode=initialize/
reinstall) snapshot the log offset before each compose.exec and verify
the resulting slice after rc=0. Defaults ANSIBLE_LOG_PATH to
/tmp/infinito-deploy.log so the check also runs locally without env-var
setup.

Includes 35 self-contained unit tests covering category derivation,
TASK-block status parsing (incl. ANSI escape stripping + substring
role-id guard + multi-skip edge case), required_role_ids YAML scan,
host log slicing with byte offsets, end-to-end verify, and CLI argparse.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 f1004a698f fix(sys-svc-webserver-core): retry hlth-include on every invocation
The hlth-include used to live inside 01_core.yml, which only runs once per
play because of the role-level run_once_sys_svc_webserver_core flag. When
the first webserver-core invocation fell inside a service-loader preload
context, the preload gate skipped both hlth-webserver and hlth-csp; every
subsequent invocation then short-circuited at the run_once gate, so the
health roles never got a second chance. This silently dropped CSP coverage
for multi-primary matrix rounds (e.g. yourls Round-2 with matomo + mailu +
keycloak + yourls co-deployed).

Move the hlth-include into tasks/main.yml outside the run_once_*-gated
01_core path. The preload gate stays, so checks still wait for the system
to be assembled; the hlth roles own run_once_sys_ctl_hlth_* flags handle
double-fire idempotency. With this in place every non-preload webserver-
core hit reactivates the include, so the first post-preload invocation
during the primary app's main deploy phase reliably triggers hlth-csp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 f9ef4bbbd9 feat(web-app-openwebui): native/LDAP/OIDC persona tests + role-mapping & first-run UX fixes
- Adds per-persona scenarios: native admin signup+sign-in, admin/biber LDAP, admin/biber OIDC, guest.
- env.j2: DEFAULT_USER_ROLE=user pulled out of OIDC block so native/LDAP users skip the pending gate.
- env.j2: LDAP_SEARCH_FILTER → (objectClass=*) standalone clause; OpenWebUI builds the uid= predicate itself.
- env.j2: OAUTH_ADMIN_ROLES prefixed with `/` to match Keycloak's full-path group claim.
- playwright.env.j2: expose ADMIN_EMAIL for the email-keyed native signup helper.
- _shared.js: API-driven ensureNativeAdminExists, idempotent on 400 "email taken" and 403 "access prohibited".
- _shared.js: signInViaLdap drives the LDAP-default Authenticate form (no mode picker).
- _shared.js: click-driven openwebuiLogout (avatar → Sign Out) with first-login modal dismissal.
- _shared.js: expectSignInRequiredAfterLogout drives Keycloak end-session confirm with contract-permitted cleanup fallback.
- _shared.js: actionableClick fires native DOM clicks for off-viewport SSO/LDAP submit buttons.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 bb804d5133 fix(sys-ctl-hlth-csp): pin csp-checker image to 2.1.1
The previous :stable tag points at a build whose with-ca-trust.sh
populated the host NSS DB at \$HOME/.pki/nssdb, which Chromium 113+ no
longer consults thanks to the Chrome Root Store. The CSP probe therefore
failed every internal target with ERR_CERT_AUTHORITY_INVALID, which
surfaced as `Final-run service failed: hlth-csp.…service` on every CI
deploy whose matrix topology happened to trigger sys-ctl-hlth-csp outside
the service-loader preload context (e.g. web-app-hugo, web-app-mini-qr
in their isolated round-2 variant).

csp-checker 2.1.1 bypasses cert validation at the Puppeteer layer
(ignoreHTTPSErrors + acceptInsecureCerts + --ignore-certificate-errors),
which restores reachability of self-signed internal domains and unblocks
the affected deploys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 3b1e6bc9ef fix(deploy): default INFINITO_DEBUG=true via default.env + expose debug= on compose-deploy
The deleted `deploy-reuse-kept-apps` Makefile target hardcoded
`INFINITO_DEBUG=true` inline; the new `compose-deploy` recipe dropped
that without a replacement, so `apps/update/selection.sh` (strict
`${INFINITO_DEBUG:?}` guard) crashed in the test-development CI flow.

Make default.env the single source of truth: pin INFINITO_DEBUG=true so
BASH_ENV → load.sh propagates a valid value through every Make recipe,
and expose `debug=` on compose-deploy alongside the other short
aliases so callers can override per invocation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 99646f4001 fix(web-svc-css): pin cdn/matomo statically, keep only prometheus in matrix
web-svc-css is a pure redirect vhost to web-svc-cdn (tasks/01_core.yml).
That makes cdn a hard dependency, not a matrix dimension, and leaves the
redirect with no own page views for matomo to track. Hardcode both,
shrink variants.yml to the one truly optional observability peer
(prometheus). Fixes the round-1 Playwright failure where cdn was purged
under the css variant despite css being unreachable without it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:44 +02:00
kevinveenbirkenbachandClaude Opus 4.7 df0940e363 fix(web-app-fusiondirectory): retarget docker image to nfrastack after maintainer rebrand
`tiredofit/fusiondirectory` is gone from Docker Hub; the maintainer rebranded
to `nfrastack`. README link 404'd (caught by test-external) and the image
reference in meta/services.yml pointed at the same dead repository.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:44 +02:00
kevinveenbirkenbachandClaude Opus 4.7 aacca6025d docs(playwright-specs): require dedicated LDAP scenario per persona, drop contradictory variant-pinning sentence
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:44 +02:00
kevinveenbirkenbachandClaude Opus 4.7 e5786f980e refactor(web-app-openwebui): split playwright.spec.js into per-test files
Mirror the web-app-wordpress/web-app-mattermost shape: extract env and
helpers into _shared.js and move each scenario into its own
test-*.js module that registers via the shared context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 04:23:44 +02:00
kevinveenbirkenbachandClaude Opus 4.7 38588af4eb style(test-e2e-playwright): wrap long Jinja line in vars/main.yml
Autoformat side-effect: TEST_E2E_PLAYWRIGHT_IMAGE exceeded the YAML
line-length budget. The rendered value is unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:38:13 +02:00
kevinveenbirkenbachandClaude Opus 4.7 280fb2f410 fix(cli/build/include): tolerate cross-UID overwrites of generated playbook entries
`cli build include` writes tasks/groups/*-roles.yml. Both
test-integration (in-container, nobody UID) and lint-ansible: setup
(host UID) run setup in parallel via scripts/make/parallel.sh and
race on the same file; `open(..., 'w')` then trips PermissionError
on the second writer. Unlink the file first when it exists but is
not writable — directory-level delete permission is host-owned in
our layout, so both UIDs can safely recreate.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:36:06 +02:00
kevinveenbirkenbachandClaude Opus 4.7 81a4f7fdde feat(make): add clean-container-owned target
Adds a Makefile target plus helper script that removes container-owned
generated artefacts (build/install.stamp, tasks/groups/*-roles.yml)
which the host user cannot rm directly. The helper auto-starts a
stopped infinito compose container before deletion.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:26:20 +02:00
kevinveenbirkenbachandClaude Opus 4.7 d63ffd3671 fix(web-app-dashboard): assert Font Awesome fallback for card icons
The disabled-simpleicons branch of test-icons.js looked for a
`<img src*='favicon.ico'>` that the card template never renders;
the Jinja card.html.j2 falls through to `<i class="fa …">` when
neither cache nor external_url is set. Match the real DOM and
filter to `:visible` so an `onerror` hidden FA fallback can't
shadow the actual icon via `.first()`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 13:06:38 +02:00
kevinveenbirkenbachandClaude Opus 4.7 54f88275ce feat(cli/console): stateful navigation, ls, and modular split
Reworks the REPL into a stateful shell that tracks a category path: typing a bare category name cd's into it, `/` returns to root, `..` walks one level up (chained as `../..` etc.), path tokens with slashes or a leading `..` navigate, `infinito [path]` always jumps absolute, and `ls` renders the current folder with a 🗂️/⚙️ emoji column, name, and a 30-char-truncated description (folders sorted before commands). Executable leaves (sub-packages with `__main__.py`) run instead of cd-ing into them. The prompt prints `🖥️  infinito <space-separated path>> `; the screen clears (incl. scrollback via `\033[3J`) before each command run, and the run echoes the resolved invocation in the fresh frame so the user sees what is about to execute.

Splits `repl.py` (~250 lines) into focused modules: `constants.py` (sentinels), `navigation.py` (path/category resolve + prompt + normalize), `ls.py` (folder rendering + truncate), `runner.py` (subprocess dispatch + clear_screen), `banner.py` (startup banner). `repl.py` is now a thin `main()` loop with backward-compat re-exports so external consumers keep the `repl.<symbol>` surface. Mirrors the split on the test side into `test_navigation.py`, `test_runner.py`, `test_ls.py`, and a `test_repl.py` that owns only main-loop integration with a shared `_MainLoopHarness` mixin.

Adds `scripts/tests/environment/11_console.sh` as a CI smoke that feeds a navigation sequence through the REPL and asserts every prompt shape plus the `ls` emoji output, so a regression in the prompt format, the path resolver, or the folder rendering is caught by the environment-test orchestrator.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 13:06:38 +02:00
kevinveenbirkenbachandClaude Opus 4.7 a79af1791c style(workflow): shorten dns test job name
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 11:23:25 +02:00
kevinveenbirkenbachandClaude Opus 4.7 32ae01a4ed fix(web-app-wordpress): drop requirement-tag from RBAC teardown comment
The split-out test-rbac-roles.js carried over a "requirement 004 forbids removing them" wording from the monolithic spec. `test_no_req_references_in_code` forbids requirement-tagged comments in non-Markdown source files since requirement numbers rot when requirements are renumbered; restate the invariant ("if biber was already a member at start, leave the membership untouched") in present-state language.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 11:23:25 +02:00
kevinveenbirkenbachandClaude Opus 4.7 ad1cbffc30 refactor(web-app-wordpress): split playwright.spec.js into per-test files
The monolithic spec mixed a CSP baseline, an OIDC round-trip, an RBAC-by-group loop, a Multisite placeholder, a WP→Discourse round-trip, and three persona shims behind ~500 lines of shared Keycloak / WordPress / Discourse helpers. Splits the surface into per-test modules around a `_shared.js` that owns the env asserts, the helper library, and the `beforeEach` setup; the orchestrator is now a 15-line `playwright.spec.js`.

Carries the `test.skip(\!discourseApiKey, …)` gate from the previous commit so a `mode=update apps=<other>` run skips the WP→Discourse test with a self-describing reason instead of racing into a teardown failure.

`lint-playwright` confirms Playwright discovers all 10 tests across 8 files; ESLint clean (only the pre-existing playwright/expect-expect warnings on persona shims, identical to other roles).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 11:23:25 +02:00
kevinveenbirkenbachandClaude Opus 4.7 5196283f6c refactor(test-e2e-playwright): bake @playwright/test into a shared node_modules
Per-role Playwright runs no longer call `npm install` against
registry.npmjs.org on every invocation. A new `01_setup.yml` runs once
per host/version, populates `_shared/node_modules/` inside the Playwright
container, and stamps the installed version in `.installed-version`.
Each `02_run_one.yml` mounts that tree read-only at `/e2e/node_modules`
and the container command becomes `npx --no-install playwright test`.
This eliminates the npmjs.org TLS-handshake flake that took out the
keycloak playwright in a recent variant-2 update, saves ~4 s per role,
and gives version bumps a single rebuild point.

Three retry layers cover transient registry failures: Ansible (3× with
5 s delay), npm fetch (5× with 10-60 s backoff), and image pull (3×).

Stage-base / shared-dir / stamp / node_modules paths live as SPOT
constants in `vars/main.yml` so call sites never reconstruct them.

Renames `tasks/run_one.yml` → `tasks/02_run_one.yml` (and adds
`01_setup.yml`) to make the include order visible by filename; updates
cross-references in `AGENTS.md`, the two `docs/contributing/...` pages,
`dump_disk_diagnostics.sh`, and the lint test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 11:23:24 +02:00
kevinveenbirkenbachandClaude Opus 4.7 b0f79b3669 ci(test-environment): free host disk and bump CI cache caps to keep large distros from running out of space
Add jlumbroso/free-disk-space before checkout (swap-storage off since this
workflow has no enlarge_swap.sh replacement) and raise registry-cache to 16g
and package blobstore to 8g to absorb post-prune in-job re-pulls. CentOS runs
were hitting "no space left on device" during layer registration; reclaiming
the host's unused toolchains and default Docker images fixes the root cause.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 10:39:55 +02:00
kevinveenbirkenbachandClaude Opus 4.7 0d6441e090 style(sys-ctl-hlth-csp): wrap long HEALTH_CSP_IMAGE expression for yamllint
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 10:24:32 +02:00
kevinveenbirkenbachandClaude Opus 4.7 7ad7b5e76c feat(lint): add lint-playwright for per-role spec parse checks
Each role's Playwright spec is staged the same way `test-e2e-playwright` does at deploy time (central config + personas tree + service-gating) and `npx playwright test --list` runs against it. `--list` skips the browser launch and any test body, so this catches refactor regressions like missing helpers, broken exports, or syntax errors in seconds instead of waiting for a full deploy. Stub env values keyed off the variable-name suffix satisfy the few specs that build URLs or call `.replace()` at module-load time.

Parallelism caps at `INFINITO_WORKER_CPU` (same env knob as `scripts/make/parallel.sh`); output is replayed in `SPEC_FILES` order after all workers drain so stdout stays deterministic. `@playwright/test` ships as a devDep via the existing eslint `npm ci`. Workflow wires into `ci-orchestrator.yml` behind the `lint-gate`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 10:24:32 +02:00
kevinveenbirkenbachandClaude Opus 4.7 a7c520d97f refactor(web-app-mattermost): split playwright.spec.js into per-test files
The single spec mixed an SSO login test, a biber-DM-administrator test, and the three persona shims; each rebuilt its own env-var bindings and helpers. Splits the surface into thin per-test modules around a `_shared.js` that owns the env asserts, the popup/channel-view/logout helpers, and the SSO bootstrap.

`startMattermostSsoFlow` now polls `/login` for up to 120 s instead of waiting 30 s once, so the v1 bulk reinstall no longer flakes when the Mattermost SPA takes longer than 30 s to mount `#input_loginId` under concurrent deploy load.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 10:24:31 +02:00
kevinveenbirkenbachandClaude Opus 4.7 14dca321f7 style(sys-ctl-cln-bkps): wrap print_used_disc_space f-string for ruff format
`ruff format --check` (0.15.14, the version CI installs) wants the
f-string in `print(...)` wrapped onto its own line because the
inline form exceeds the line-length threshold. Auto-fix only — no
behaviour change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 09:14:24 +02:00
kevinveenbirkenbach 47a10d8515 Merge branch 'feature/web-app-kix' of https://github.com/kevinveenbirkenbach/infinito-nexus-core into feature/web-app-kix 2026-05-22 09:09:33 +02:00
kevinveenbirkenbachandClaude Opus 4.7 0838e8e3b6 fix(sys-ctl-mtn-cert-deploy): run cert-deploy systemd unit before alm-compose chain
Swap the order of the two top-level steps in 01_core.yml so the
sys-service include (which sets up + force-flushes the cert-deploy
oneshot, copying TLS material into the Mailu compose volume and
restarting mailu_front) runs *before* sys-ctl-alm-compose is pulled in.

Previously, the alm-compose dependency dragged in
sys-svc-mail-msmtp -> hlth-msmtp.<release>.service, which sends mail via
127.0.0.1:465 (implicit TLS). With cert-deploy still queued, mailu_front
had no /certs/cert.pem at boot, dovecot in the front container exited
fatally, no SMTPS listener bound, and msmtp died with
EX_PROTOCOL "TLS handshake failed". That aborted the playbook and in
turn killed the post-deploy Playwright gate for every Mailu-dependent
role (mailu, wordpress, dashboard, ...). Reordering keeps the
alm-compose chain's mail-aware children happy without changing any
behaviour of the cert-deploy logic itself.

Verified by full_cycle bulk reinstall variant 0+1 (PASS 1+2 each):
- variant 0 PASS 1: ok=5331 failed=0 rescued=1
- variant 0 PASS 2: ok=5141 failed=0
- variant 1 PASS 1: ok=3290 failed=0 rescued=1
- variant 1 PASS 2: ok=3140 failed=0
The hlth-msmtp TLS abort that killed the unfixed run no longer recurs;
mailu_front comes up healthy with dovecot bound to 465/993/10143 before
the first msmtp probe runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 09:09:33 +02:00
kevinveenbirkenbachandClaude Opus 4.7 dcb16027dc fix(sys-ctl-cln-bkps): format disk percent as float to avoid ValueError
psutil.disk_usage().percent returns a float, so the `:d` format spec
raised `ValueError: Unknown format code 'd' for object of type 'float'`
and failed the cln-bkps systemd unit during deploy. Use `:.0f` to render
the rounded percentage without an int cast.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 03:50:52 +02:00
kevinveenbirkenbachandClaude Opus 4.7 7b9c0d2404 refactor(profile): emit per-role runtimes for every role instead of a top-N slice
`role_summary.py` no longer truncates to a configurable top-N: drops
`DEFAULT_TOP_N`, `TOP_N_ENV_KEY`, and `_read_top_n`; `_format_table`
renders all rows from `parse_role_times`. Renames the Markdown
heading and the corresponding GitHub Actions step in the four
`test-deploy-*` workflows from `Top role runtimes` to
`Role runtimes` for accuracy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 03:00:42 +02:00