Mark the erpnext primary entry enabled+shared so other roles can declare it as a dependency (erpnext: {enabled, shared}); the service registry now resolves it to web-app-erpnext, matching the web-app-matomo provider pattern.
Drop svc-db-mariadb from run_after — it crossed deploy types (server -> universal), which the service-loader rejects once the entry becomes a provider. The MariaDB dependency and ordering are already handled by the mariadb: shared service flag.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-ups after the bridgev2 config migration, each verified in the live DiD stack: (1) URL-encode the bridge DB password in the postgres uri (the credential contains '/', which the stricter bridgev2 Go URL parser rejected as an invalid port) so signal/whatsapp/slack connect to postgres and generate their registration; (2) drop the telegram bridge from MATRIX_BRIDGES when its Telegram api_id/api_hash credentials are absent (telegram requires them at startup and crash-loops without — unlike the other bridges — blocking synapse); (3) give the bridge healthcheck a 5m start_period so the registration generation has grace before it is marked unhealthy.
(4) Two-phase synapse start: drop the synapse->bridges service_healthy depends_on (a single 'compose up -d' did not respect the start_period and aborted with 'dependency ... unhealthy' before the bridges generated registration) and instead restart synapse after the Ansible 'wait for registration files' step so it loads the freshly written appservice registrations. The bridges generate registration independently of synapse, so synapse no longer needs to block on their health.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The earlier mu-plugin pointed http_request_args['sslcertificates'] at the OS bundle (/etc/ssl/certs/ca-certificates.crt), which does NOT carry the internal root CA unless update-ca-certificates ran in the container — so the OIDC token exchange to the self-signed internal Keycloak still failed with 'cURL error 60: SSL certificate problem' in CI run 28044348006. Read the env-provided bundle first (CURL_CA_BUNDLE / CA_TRUST_CERT / SSL_CERT_FILE), which sys-svc-compose-ca injects into every container pointing at the bind-mounted /tmp/infinito/ca/root-ca.crt (compose_ca.py), falling back to the OS bundle.
Verified chain: daggerhart-openid-connect-generic issues its token/userinfo requests via wp_remote_post/wp_remote_get (the WP HTTP API), so the http_request_args filter applies and WP's curl uses CURLOPT_CAINFO = the internal root CA, which verifies the Keycloak cert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In the SSO-disabled matrix variant, loginToOdoo waited for the auth_oauth SSO button (which never renders), so every addon spec timed out in clickOdooSsoButton — the run's actual failure (the earlier stock.spec union fix was a no-op there, since stock is addon-gated off in that variant). Branch loginToOdoo on the sso service flag: when SSO is disabled, log in natively via the Odoo /web/login form (input[name=login]/[name=password] with the provisioned administrator credentials, scoped .oe_login_form submit), so the native admin login is genuinely exercised and the addon specs run instead of skipping. When SSO is enabled the existing OIDC/Keycloak flow is unchanged.
Selectors verified against the deployed Odoo 19 login form captured in the CI trace (name=login/password, .oe_login_form button[type=submit]). Admin creds are the same lookup('users','administrator') values provisioned into res_users by tasks/05_admin.yml.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The mautrix bridge images (dock.mau.dev/mautrix/<name>:latest) moved to the bridgev2/megabridge architecture, but the role's config templates were still the legacy mautrix-go format. On boot the bridgev2 binary aborts with 'Legacy bridge config detected, but hacky network config migrator is not set', crash-loops, never writes registration.yaml, stays unhealthy, and holds synapse in Created (depends_on service_healthy) — failing the matrix deploy in CI run 28044348006.
Rewrite the 4 cutover bridges' configs to the bridgev2 layout (top-level database/network/matrix/double_puppet/encryption), preserving every templated value: homeserver address/domain, the postgres DB uri with its credential lookup, the appservice id/port/bot, the permissions map, and the raw go-template guards. The default entrypoint still generates registration.yaml, so no compose/registration changes are needed. facebook/instagram/imessage stay on the legacy format (their images are legacy-frozen and deploy healthy). Render + YAML validated; actual bridge boot is verified by CI.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
make roundtrip with no apps= now iterates every application, most-complex first (complexity --sort total --order desc --format string). Also marks roundtrip.sh executable and moves the roundtrip target into alphabetical order.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
complexity --format string prints only the role names, one per line, so the list feeds straight into make roundtrip apps=... A unit test covers the new format.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
make roundtrip apps="<app> ..." iterates each role through every deploy mode in order (compose, then swarm), stopping at the first failure. Per-step logs under TMPDIR; each validated swarm cluster is released unless keep=true. The mode sequence is overridable (modes="compose swarm") so kubernetes can be appended later.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every swarm-test cluster is named so parallel clusters get distinct container and network names. topology.sh and extend_inventory require SWARM_NAME; act-swarm-zombie defaults it to the app id (name= overrides), the CI workflow derives it from the matrix app, and act-swarm-down/shell/playwright require name=. The bare swarm-mgr-01 names are gone (node refs now carry the cluster prefix).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
down_act_outer: match the act outer runner by name prefix instead of a hardcoded image ancestor, so it works regardless of ACT_PLATFORM_IMAGE. 13_cleanup: warn loudly when a node survives 'docker rm -f' (kernel D-state / stuck NFS, reboot needed) instead of swallowing the error.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A killed prior act-swarm run leaves the nfs-server container behind, so 'docker run --name nfs-server' then conflicts on the next run. Remove any existing one first.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
matomo: force-restart the swarm service after patching config.ini.php DB creds (the compose-up handler is a no-op in swarm), extracted into 10_resolve_host_cid. nextcloud: gate the redis PONG wait on the engine being local. prometheus: config 0444 (swarm config objects are root) and gate the host-monitor binds plus privileged cadvisor/node-exporter to compose only. dashboard: probe the public icon URL in swarm.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Widen the openresty fan-in overlay /28 -> /24 (the /28 exhausted VIPs under mailu's services, deadlocking the stack). Pre-create NFS backing dirs for every volume of unpinned roles (placement decides, not the legacy per-volume nfs flag). Make email 'external' cluster-wide so swarm workers relay through the central mailu instead of a localhost root sender. Constructor: auto-place svc-prx-openresty, gate out unconsumed central engines, and load the svc-dns role group.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Consume central redis and unbound (drop embedded sidecars). Swarm: resolve central mariadb/redis overlay VIPs into admin extra_hosts so the DNSSEC resolver does not NXDOMAIN internal names; pre-create nginx/certs bind dirs plus a placeholder cert before stack-up; recreate the API token when a fresh cluster lacks it; wait for antispam via container exec; disable OIDC SSL verify under self-signed TLS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
About 38 web-app/web-svc roles now consume the central svc-db-* and svc-dns engines via lookup('engine', ...) instead of embedded sidecars: meta/services flags, variant matrices, and env/conf hosts point at the shared services. Lets these apps run unpinned and NFS-shareable in swarm.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DNSSEC-validating resolver as a central engine, mirroring the svc-db pattern (meta/services flags, overlay topology, per-consumer 02_init). Replaces per-app embedded unbound sidecars (e.g. mailu).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shared single-host engine roles consumed via lookup('engine', ...), mirroring the central postgres/mariadb pattern: meta/services flags, overlay topology in meta/server, compose templates, per-consumer provisioning in 02_init. Lets apps drop their embedded sidecars and become unpinned/NFS-shareable in swarm.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Foundation for the central svc-db-*/svc-dns-* engine roles: the engine lookup and engines descriptor, per-consumer engine credentials in the inventory manager, shared-net overlay rendering, container_depends_on, the sys-svc-engine provisioning dispatcher, and the compose_volumes NFS-placement split. base.yml.j2 gates the embedded redis sidecar on lookup('engine','redis',application_id,'local').
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
site_check treated any HTTP 200 as a usable API and included inject_apply, which dereferences site_check.json. When Matomo answers 200 with an HTML body (e.g. its installer page during a provider/consumer ordering window) the result has no .json attribute and the play crashes ('dict has no attribute json'). Require site_check.json to be defined for the register/inject path and degrade (disable the snippet, reapply later) otherwise, completing the best-effort degrade contract.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Matomo image entrypoint re-owns config.ini.php to root on every container start, so the post-sed chown in 07_patch_database (which runs before 04 flushes handlers and the stack redeploys Matomo) is overwritten by the recreate, leaving the config root-owned when 09 runs its API config pass (HTTP 500, config not writable). Move the chown to 04 after the post-recreate health probe and before the API pass, so it lands after the last config-rooting restart and persists through 09.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The OIDC login redirected to Keycloak and back with a valid code, but the server-side token exchange (daggerhart-openid-connect-generic wp_remote_post to the self-signed internal Keycloak) failed with 'cURL error 60: SSL certificate problem: unable to get local issuer certificate' (60x in CI run 28018750880), so the user was never logged in and never redirected to /wp-admin. WordPress's HTTP API (WpOrg\Requests) pins CURLOPT_CAINFO to its own bundled wp-includes/certificates/ca-bundle.crt and ignores the OS trust store and the CURL_CA_BUNDLE/SSL_CERT_FILE env vars, so the earlier ca-certificates Dockerfile change had no effect.
Add a mu-plugin that filters http_request_args to set sslcertificates to the OS CA bundle (/etc/ssl/certs/ca-certificates.crt), which the compose CA-injection populates with the internal root CA, so wp_remote_* TLS verifies the internal cert. Not a green-wash: no assertion weakened, no_sslverify left false (verification stays ON), the test still requires a real OIDC round-trip to /wp-admin.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
stock.spec.js applied .first() to each .or() operand (breadcrumb and appTitle) before combining them, so breadcrumb.or(appTitle) was a union of two locators that resolved to 2 visible elements (the stock.menu_stock_root brand link AND the 'Inventory Overview' breadcrumb) — expect().toBeVisible() requires exactly one match and threw a Playwright strict-mode violation. The two matches actually PROVE the stock module installed; the assertion message's 'module failed to install' premise was wrong. Mirror the passing account.spec.js pattern: drop the per-branch .first() and apply a single .first() after .or(). The assertion intent (Inventory identity must render) is preserved — no green-wash.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
At HEAD the synapse service emitted the mautrix-* bridge dependencies as 6-space keys with no depends_on parent, so they nested under healthcheck: and 'docker compose config' rejected them (services.synapse.healthcheck additional properties 'mautrix-*' not allowed, rc=1) — which made the sys-svc-compose CA-trust handler exit rc=2 and failed the matrix deploy in CI run 28018750880.
Route the bridge gating through the container_depends_on lookup's extra= argument instead of a hand-written depends_on block, so the bridges merge with any db/redis/sso deps into a SINGLE depends_on mapping. Fixes the invalid-compose failure and removes the latent duplicate-depends_on risk a hand-written block plus a non-empty container_depends_on would produce. Render-verified for bridges-present and bridges-empty.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pi-hole v6 returns 308 (HTTP→HTTPS redirect) in addition to the
platform's DEFAULT_OK codes (200, 301, 302). Without 308 in the
accepted status_codes, the webserver health checker would flag
pihole.infinito.example as unhealthy.
- native.spec.js: update button selector from /sign in|login/i to
/log in|sign in/i — Pi-hole v6's submit button text is
'Log in (uses cookie)', not 'Sign in' or 'Login'
- 01_setup.yml: remove pihole setpassword CLI task — Pi-hole v6 blocks
this command when FTLCONF_webserver_api_password is set via env var
('set by environment variable; please unset it to use this function').
Password is now set at container startup via the env var, which is
the v6-idiomatic approach.
Three fixes:
1. guest.spec.js: skip instead of fail when OIDC_ISSUER_URL is unset
(variant-1 has SSO disabled so no Keycloak redirect is expected;
the old expect().toBeTruthy() caused a hard failure instead of skip)
2. playwright.env.j2: only emit OIDC_ISSUER_URL when SSO is proxy-gated
(was unconditionally set, causing native.spec.js to wrongly skip in
variant-1 and oauth2.spec.js/guest.spec.js to fail on missing redirect)
3. compose.yml.j2: Pi-hole v6 compatibility fixes
- Replace WEBPASSWORD with FTLCONF_webserver_api_password (v6 dropped
legacy env vars; WEBPASSWORD is silently ignored in v6)
- Add FTLCONF_webserver_domain={{ domain }} to whitelist the nginx
proxy Host header; Pi-hole v6 FTL returns 403 for unrecognised Host
headers (DNS rebinding protection) — this was causing the baseline
playwright.spec.js to fail with HTTP 403 in variant-1
Playwright's default testMatch only discovers files matching
*.spec.js / *.test.js. test-guest.js, test-oauth2.js, and
test-native.js never matched this pattern and were silently never
executed in any CI variant -- only playwright.spec.js's 2 baseline
tests ever ran (confirmed: 0 occurrences of these files in any
playwright-junit.xml across variant-0/variant-1, sync/async).
Renamed to guest.spec.js, oauth2.spec.js, native.spec.js so the SSO
persona scenarios (Keycloak redirect, admin/biber login, native login)
actually execute and validate the sso-proxy CA trust fix.