Adds a single apt-cacher-ng container (sameersbn/apt-cacher-ng:3.7.4-20220421)
on the swarm-lab network and routes every node's apt traffic through it via
/etc/apt/apt.conf.d/01-cacher. With four nodes provisioning the same
infinito-nexus.deb deps in parallel (~200 MB / node), the cache turns the
second-through-fourth fetch into a hot serve from the local proxy.
The workflow restores ${{ runner.temp }}/apt-cacher-ng across runs via
actions/cache@v4 (keyed on packaging/debian/control), so on warm GHA runs
the cache is also populated for the first node — driving bootstrap from
~11 min toward sub-3 min once the cache is hot. The cache restore step is
guarded by `if: \!env.ACT` so act runs still exercise the cold path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ACT_INPUTS="key=value [key2=value2]" expands to repeated --input flags so
make act-workflow can drive workflow_dispatch-style inputs without falling
back to a manual act invocation. Needed for the swarm pilot, where the
discover job's `whitelist` input gates the per-app matrix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 192.168.105.160/28 slot is also claimed by the open web-app-jitsi PR
(#287). CI's network-uniqueness lint catches the overlap once both branches
are checked together. Move to .176/28 (next free slot in the 105.x band).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`docker exec journalctl` fails when the container is already dead (which
is the actual failure mode: jrei/systemd-ubuntu:24.04 exits with code 255
~0.4s after start on the current GHA runner image). `docker logs` works
on stopped containers and surfaces what systemd printed before it gave up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a manager-pinned, NFS-backed `registry:2` service plus a transparent
retag/push/rewrite step so swarm workers can pull custom-built images from a
local registry, replacing the `docker save | docker load` pilot crutch.
Mechanism: after `compose build`, the new `swarm_registry_push.py` reads the
rendered compose.yml, retags each service that declares both `build:` and
`image:` with `<manager>:5000/<image>`, pushes the retag, and rewrites the
`image:` field in place. Subsequent `docker stack deploy --resolve-image
never` then references the registry-qualified name, so workers pull from the
cluster-local registry over HTTP via `insecure-registries`.
No per-role changes required — the rewrite is centralized in
sys-svc-compose handlers and skips services without a `build:` directive.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New requirement for a `web-app-erpnext` role that integrates ERPNext
(Frappe Framework) into Infinito.Nexus with OIDC-direct SSO against
Keycloak, reusing central `svc-db-mariadb` and `svc-db-redis` (three
Redis logical roles split by DB number 0/1/2 for cache/queue/socketio).
Captures 13 proposed design decisions for operator review before
implementation: upstream `frappe/erpnext` image pinning, central-service
reuse (Decision #7), wizard-bypass `bench` bootstrap (Decision #9),
Keycloak-group → Frappe-role reconciliation (Decision #5), Mailu IMAP
inbound + SMTP outbound (Decision #8), and the standard
V1/V2/V3 variant matrix (Decision #11) mirroring web-app-kix /
web-app-zammad. Out of scope for v1: multi-site / multi-tenant Frappe
(Decision #12).
Modeled on the layout precedents from web-app-odoo (ERP shape,
dual HTTP + WebSocket vhost) and web-app-zammad (central-service
reuse pattern, three-variant matrix).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Both DBs now follow the svc-db-mariadb pattern (service_is_stateful: true).
sys-svc-container/templates/deploy.yml.j2 already gates the deploy: block
on the flag; the DB then runs as a plain compose container pinned to the
manager via group membership instead of being scheduled as a swarm
service (which would corrupt the volume on a worker replica).
Unlocks the swarm-test matrix for every postgres/openldap-consuming app.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous 60s window flaked on GHA matrix runs because jrei/systemd-ubuntu:24.04 cold-starts D-Bus + targets slowly under parallel load. 03_start_swarm_nodes.sh additionally let downstream steps run against a non-ready node instead of failing.
Both scripts now exit non-zero with journalctl + State diagnostics on timeout. Override via SWARM_PILOT_SYSTEMD_TIMEOUT for local debug.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The lint-ansible workflow runs ansible-lint with INFINITO_LINT_RUNNER=host
(outside the compose stack container). roles/svc-storage-nfs-client uses
ansible.posix.mount for the NFS probe-mount; the host runner could not
resolve the module so the syntax-check failed CI while local make lint
passed (the container already had the collection via galaxy bootstrap).
Adding ansible.posix to requirements.galaxy.yml installs it everywhere
the existing ansible-galaxy install step runs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- meta/volumes.yml: declare zammad_storage + zammad_search named volumes
- vars/main.yml: ZAMMAD_STORAGE_VOLUME / ZAMMAD_SEARCH_VOLUME constants reading from meta
- compose.yml.j2: extra_volumes uses the constants instead of empty dicts
- tasks/01_core.yml: 4 host-rendered init files (auto_wizard.json, zz_omniauth_allow_get.rb, telegram_helper_stub.rb, zz_oidc_group_role_mapping.rb) migrate from inline template: tasks to the central render_replicated_templates.yml helper so the bind-mounted host paths resolve on every swarm peer
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the test-deploy-server iteration scaffold: discover + matrix on
the same app list (INFINITO_DEPLOY_TYPE=server), one swarm-deploy run
per app. Pilot scripts derive per-app context from APP_ID + role meta
(ENTITY, SERVICE_NAME, DB_DEP, CUSTOM_IMAGE_REPO, PRIMARY_NFS_VOLUME)
so they adapt to roles other than mediawiki.
- .github/workflows/test-deploy-swarm-nfs.yml -> test-deploy-swarm.yml
- scripts/tests/deploy/swarm-nfs-pilot/ -> scripts/tests/deploy/swarm/
- new _context.sh helper sourced by 07/08/11–16
- 07_provision_inventory + 08_extend_inventory + 11_deploy build their
--include list from DB_DEP + APP_ID
- 12_wait_converge skips DB readiness when DB_DEP=none
- 13_seed_content seeds NFS marker only when meta/volumes.yml carries an
nfs-flagged volume
- 14_drain_worker skips custom-image distribution when role has no
build context
- 15_assert_state asserts marker survival only when an NFS volume exists
- ci-orchestrator wires the renamed job + passes whitelist through
- naming-pattern alignment across test-deploy-{server,universal,workstation,swarm}
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the svc-docker-swarm, svc-docker-swarm-manager, svc-storage-nfs-server
and svc-storage-nfs-client roles, the compose_volumes filter NFS support,
the test-deploy-swarm-nfs CI workflow with its swarm-nfs-pilot script set,
and the deploy_mode-aware compose/swarm wiring across mediawiki, mariadb
and sys-svc-* helpers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`make setup` regenerates `.dockerignore` from `.gitignore` on every run.
The earlier commit that added `.claude/hooks` to `.gitignore` did not
re-emit the matching `.dockerignore`, so each CI invocation rewrote the
file — and the `pre-commit` framework reported `make test` as failed
("files were modified by this hook"), even though every test target
passed. Land the synced output here so the diff stays empty on a clean
deploy.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`make setup` regenerates `.dockerignore` from `.gitignore` on every run.
The earlier commit that added `.claude/hooks` to `.gitignore` did not
re-emit the matching `.dockerignore`, so each CI invocation rewrote the
file — and the `pre-commit` framework reported `make test` as failed
("files were modified by this hook"), even though every test target
passed. Land the synced output here so the diff stays empty on a clean
deploy.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CI run 26721196005 failed at:
TASK [sys-service : sys-service | Fail after shared service diagnostics]
msg: Unable to start service hlth-msmtp.9.0.2.infinito.nexus.service.
Two breakages in the start sequence:
1. `sys-ctl-hlth-msmtp/files/script.sh` called `hostname` in `$(...)`.
The CI deploy containers ship without that binary, so `set -eu`
aborted before msmtp ever saw stdin. Fall back to `${HOSTNAME:-...}`
then `uname -n` then a literal "unknown".
2. `sys-svc-mail-msmtp/templates/msmtprc.conf.j2` hard-coded the trust
file path per RedHat family — the file was missing on the deploy
image and msmtp aborted with `Error while reading file. exit=66`.
Probe a candidate list at deploy time (`SYS_SVC_MAIL_MSMTP_CA_BUNDLE_CANDIDATES`),
pick the first existing path, surface it as
`sys_svc_mail_msmtp_ca_bundle`, and install `ca-certificates`
ahead of the probe.
`make test` 5/5 green (186 OK).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CI run 26721196005 failed at:
TASK [sys-service : sys-service | Fail after shared service diagnostics]
msg: Unable to start service hlth-msmtp.9.0.2.infinito.nexus.service.
Two breakages in the start sequence:
1. `sys-ctl-hlth-msmtp/files/script.sh` called `hostname` in `$(...)`.
The CI deploy containers ship without that binary, so `set -eu`
aborted before msmtp ever saw stdin. Fall back to `${HOSTNAME:-...}`
then `uname -n` then a literal "unknown".
2. `sys-svc-mail-msmtp/templates/msmtprc.conf.j2` hard-coded the trust
file path per RedHat family — the file was missing on the deploy
image and msmtp aborted with `Error while reading file. exit=66`.
Probe a candidate list at deploy time (`SYS_SVC_MAIL_MSMTP_CA_BUNDLE_CANDIDATES`),
pick the first existing path, surface it as
`sys_svc_mail_msmtp_ca_bundle`, and install `ca-certificates`
ahead of the probe.
`make test` 5/5 green (186 OK).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cherry-pick of bdc5945bc from feature/web-app-zammad, retargeted at
roles/dev-nix/tasks/02a_nix_install_only.yml (the include_tasks
companion file that 201670975 introduced on this branch).
Same root cause + fix as on the zammad branch:
TASK [dev-nix : Set build group override for Nix installer when nixbld exists]
Error while resolving value for 'dev_nix_installer_build_group_id':
object of type 'NoneType' has no attribute 1
`getent` with `fail_key: false` inserts the key with a `None` value
when the entry is missing, so the membership test `'nixbld' in
ansible_facts.getent_group` evaluates True but `getent_group.nixbld[1]`
crashes on None. The guard now explicitly checks the value with
`is not none` for both `nixbld` (group) and `nixbld1` (passwd).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CI run 26643336674 (feature/web-app-zammad) failed at:
TASK [dev-nix : Set build group override for Nix installer when nixbld exists]
Error while resolving value for 'dev_nix_installer_build_group_id':
object of type 'NoneType' has no attribute 1
Root cause: `getent` with `fail_key: false` does NOT drop the key when
the entry is missing — it inserts the key with a `None` value. The
existing `when:` guard `'nixbld' in ansible_facts.getent_group`
therefore evaluates True (the membership test only checks the key, not
the value), and `getent_group.nixbld[1]` then crashes when the value
is None.
Fix: replace the membership check with an explicit `is not none`
assertion for both `nixbld` (group entry) and `nixbld1` (passwd entry).
A short comment above each `when:` block names the trap so the next
reader does not re-introduce the membership-only check.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pure whitespace reflow applied by `make autoformat` — column-aligned
mapping values were collapsed to single-space alignment so the files
match the project-wide YAML formatter output. No behavior change.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Follow-up to the initial web-app-jitsi commit. `make test` flagged 11
contract violations on the newly-added role; each is addressed here.
- group_vars/all/08_networks.yml: declare PORT_BANDS.public.media
(10000–10099) so the JVB media plane port (UDP/10000) falls in band.
- roles/web-app-jitsi/templates/env.j2: pipe every Jicofo/JVB/Jigasi/Jibri
XMPP password through `| dotenv_quote`.
- roles/web-app-jitsi/meta/services.yml: make `email` flag dynamic
(`'web-app-mailu' in group_names`) and add `# nocheck: dynamic-flag`
+ `# nocheck: playwright-service-flag` above `javascript:` (gated on
sso.enabled, not on group_names; runtime DOM patch, no probe-able
surface flag).
- roles/web-app-jitsi/meta/variants.yml: re-add explicit
email.enabled / email.shared pins per variant (variant-coverage rule
requires every dynamic flag to have both true- and false-pinning
variants).
- roles/web-app-jitsi/meta/schema.yml: drop unused jwt_app_secret
credential.
- roles/web-app-jitsi/vars/main.yml: drop unreferenced JITSI_HOSTNAME,
JITSI_WEB_PROTOCOL, JITSI_JWT_APP_SECRET.
- roles/web-app-jitsi/templates/playwright.env.j2: drop unused
ADMIN_EMAIL / JITSI_BASE_URL / OIDC_ISSUER_URL keys and collapse the
PERSONA_*_BLOCKED comment block to one line per key (env-file schema
rule allows at most one comment line above an entry).
- roles/web-app-jitsi/README.md: replace the dead
jitsi-contrib/prosody-plugins/tree/main/token_lobby URL with the
repository root.
Final result: `make test` 5/5 targets ✅ (186 tests OK),
`make autoformat` clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two pre-existing lint failures on main (introduced in 45eea2892) blocked
`make test` from going green:
- test_excessive_repeated_when_in_tasks_files flagged the four
`when: dev_nix_binary_path_before | length == 0` repeats across
Purge / getent group / getent passwd / Run-installer tasks.
- test_blocks_with_when_and_sections_have_max_three_tasks rejects any
`block:` carrying a block-level `when:` and more than 3 tasks per
section, because the block-level `when:` does NOT skip parse-time
resolution of inner tasks.
Final shape: the six install tasks live in
`02a_nix_install_only.yml` and are pulled in via a single
`include_tasks` guarded by the original `when:`. When the binary is
already present, Ansible never opens the included file at all.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pure whitespace reflow applied by `make autoformat` — column-aligned
mapping values were collapsed to single-space alignment so the files
match the project-wide YAML formatter output. No behavior change.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the four-container Jitsi Meet stack (web, prosody, jicofo, jvb) with a
3-variant matrix: V1 oauth2-proxy gate + Keycloak LDAP federation, V2
no-auth anonymous, V3 direct prosody mod_auth_ldap2 against svc-db-openldap.
Auth model is oauth2-proxy at the reverse proxy because Jitsi has no
native OIDC adapter — JWT-via-Keycloak would require a custom token
server outside this repo. The role injects a fixed-position
`[data-injected-logout]` button via templates/javascript.js.j2 so the
no-built-in-logout React SPA satisfies the persona contract's
no-direct-URL logout rule. V2/V3 declare PERSONA_BIBER_BLOCKED /
PERSONA_ADMINISTRATOR_BLOCKED because the shared persona helpers only
model the OIDC redirect chain.
Final FULL-matrix `mode=reinstall full_cycle=true` run green across all
three variants (V1: 6/6 pass, V2/V3: 2 pass + 4 blocked skips).
Also lands utils/parse_trace.py — a small zipfile helper used during
the iteration to extract URL traces from playwright .trace bundles.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adding the purge task pushed dev-nix/tasks/02_non_arch_installer.yml
to 4 tasks gated by `when: dev_nix_binary_path_before | length == 0`,
tripping tests.integration.roles.when.test_duplication.
A `block:` would satisfy that lint but exceeds the project convention
of max 3 entries per block. Move the six install-branch tasks (purge,
nixbld group+uid probes, override set_facts, installer run) into a
dedicated tasks/03_install_official.yml and call it once via
include_tasks with the original guard `when:` on the include.
Behavior unchanged; inner tasks no longer re-evaluate the binary-path
condition each call.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Nix 2.32.4 installer allocates build users ascending from
NIX_FIRST_BUILD_UID. The pkgmgr-centos:stable base image ships
nixbld1..nixbld32 with descending UIDs (RHEL `useradd -r` default:
nixbld1=997, nixbld2=996, ...) and no nix binary, so the role
detects no install, runs the installer, which then aborts on
"nixbld2 already exists, but with the UID '996'".
Add purge_stale_nixbld.sh, called via ansible.builtin.script
before the installer. Three guards must all hold to purge:
1. No nix binary in candidate paths.
2. nixbld2 UID < nixbld1 UID (broken descending layout).
3. /nix/store does not exist (no live store to orphan).
Idempotent. Distro-agnostic at the script level: Debian/Ubuntu stay
[UNCHANGED] because guard 2 fails (nixbld users absent); Arch never
hits this code path. Production hosts with /nix/store present but
nixbld users get [FAIL] (rc=1) instead of silent destruction.
Fixes the 31/32 test-deploy-server / test-deploy-universal failures
observed in run 26634712613.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Defines the v1 scope for multi-node Docker Swarm support: group-based
trigger (svc-docker-swarm + svc-docker-swarm-manager), three new roles
(svc-docker-swarm, svc-storage-nfs-server, svc-storage-nfs-client),
per-volume NFS opt-in (DB volumes stay local + pinned), overlay
networks with default encryption, and openresty kept as the swarm-mode
edge (pinned to manager, L7 LB via tasks.<service> upstream blocks) to
preserve the sys-front-inj-* injection pipeline unchanged. Pilot role
is web-app-mediawiki, validated by a 3-node DinD CI workflow whose
inventory is bootstrapped via the canonical inventory provision CLI.
Procedure block follows the autonomous-run pattern from 014.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Consolidate update, security-codeql, and images-cleanup-ci onto the same
Saturday slot (`0 0 * * 6`). Drop stale cron comments.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Completes the oidc → services.sso migration started in a3b585cfa:
- meta/variants.yml: rename oidc: keys to sso: in all three variants
(V1, V2, V3) so dynamic-flag coverage matches the new services.yml
shape and variant-services-match accepts the references
- templates/playwright.env.j2: emit SSO_SERVICE_ENABLED (sourced from
services.sso.enabled) instead of OIDC_SERVICE_ENABLED, matching the
unified env-key contract that fider/decidim already follow
- files/playwright/{_shared,test-login-*,test-rest-api-ticket-create,
test-ticket-create-as-customer}.js: gate via isServiceEnabled("sso")
/ skipUnlessServiceEnabled("sso") so service-gating registers the
service the SSO_SERVICE_ENABLED key exposes
`make test` now passes end-to-end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migrates the role from the pre-v9.0.0 legacy oidc/oauth2 top-level
service blocks to the unified services.sso shape with flavor: oidc.
Drops the playwright.spec.js require for the test-alias-domain module
that was deleted in 82491791e, and removes the README link to the
TODO.md file dropped in c48386eee. Archives the in-flight 022 zammad
requirement doc into the README index since the role itself is now
landed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>