Mounting the not-yet-existing per-bridge registration.yaml into synapse made docker create it as a directory, which crash-looped the bridge ('registration.yaml: is a directory'). Mount the existing bridge directory (mautrix/<bridge> -> /data/mautrix-<bridge>) read-only instead and point app_service_config_files at <dir>/registration.yaml, so the bridge generates the file normally and synapse loads it after the restart.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pre-creating an empty registration.yaml made the bridges skip registration generation ('appservice.as_token not configured') and left synapse broken. Reverts to the two-phase state where synapse + element + NC deploy healthy; the bridge registration/directory chicken-and-egg needs proper pre-generation (bridge -g one-off) as a follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
make autoformat output: add YAML document-start marker and normalize
quote/indentation style in meta/info.yml and tasks/01_core.yml. No
semantic change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Set services.email enabled/shared to literal false in meta/services.yml.
Add corresponding suppressions:
- nocheck: dynamic-flag (flags are now static literals)
- nocheck: email (role has no lookup('email') integration)
Drop the now-static email overrides from meta/variants.yml (a static
base value must not be overridden by a variant) and add javascript
true/false pins to close a pre-existing dynamic-flag variant-coverage
gap that test_coverage flagged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
After dropping the synapse->bridges depends_on (two-phase start), synapse starts before the bridges write registration.yaml, so docker bind-mounted the non-existent path and created it as a DIRECTORY, crash-looping the bridges ('Failed to save registration: open /data/registration.yaml: is a directory'). Pre-create empty registration.yaml files (force: false, mode 0666) before the compose up so docker mounts them as files, and wait for the bridge to write real content (search_regex: as_token, timeout 180) before restarting synapse.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The bridgev2 config migration carried over the upstream example-config's analytics block, whose url pointed at https://api.segment.io/v1/track. The token is already null (telemetry off), but the live external URL made the external-URL-reachability test fail (HTTP 400 from the segment tracking endpoint). Null the url so no external telemetry endpoint is referenced.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The HTTP landing page is served by the nginx gateway sidecar (port 80), not the
coturn TURN container, so resolve_upstream hard-failed in swarm on the missing
services.coturn.ports.internal.http. Pass proxy_service_key=nginx + proxy_internal_port=80
so the swarm upstream resolves to coturn_nginx:80, and attach the gateway to the
shared overlay via container_networks (it previously had no networks block).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
get_canonical_volumes returned {} when _CANONICAL_VOLUMES_BY_ROLE was empty even
though the defaults cache was warm (get_application_defaults no-ops on a warm
cache, so it never refilled the registry). That left nfs_prep's volume loop empty,
so the NFS subdir for shared/late-loaded services (svc-ai-ollama's ollama_models)
was never created and the swarm bind mount failed ENOENT. Force a real
_build_application_defaults on an empty registry. Adds two regression tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Iterative root-hints resolution is unsupported in nested Docker (per the role
README), so the mailu admin's DNSSEC AD-flag check fails in CI/DinD where unbound
ran iteratively. UNBOUND_FORWARD_DNS only detected WSL2 (microsoft in /proc/version);
extend it to DOCKER_IN_CONTAINER so unbound forwards to NETWORK_PUBLIC_DNS_RESOLVERS
(still DNSSEC-validating) under DinD too. Bare-metal stays iterative.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The admin's startup DNSSEC self-check loops forever (never binds :8080) when
its dns: resolver is unreachable. The unbound service entry was gated on
`'svc-dns-unbound' in group_names`, so matrix variant rounds that omit
svc-dns-unbound from the inventory never deployed the resolver and the admin
hung. Mark unbound enabled+shared unconditionally in compose so svc-dns-unbound
becomes `required` and the service-loader deploys it every round; drop the
unbound override from both variants so the services.yml default applies.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
container_address(app, key) resolves services.<key>.name; a missing key or
empty name raises at task-arg finalization and killed the compose deploys for
erpnext (no 'backend' service) and friendica (service had no name).
- web-app-friendica: add name to the friendica service
- web-app-bigbluebutton: add names for postgres/greenlight (Compose default
<project>-<service>-<index>, upstream sets no container_name)
- web-app-erpnext: short-key all 7 sub-containers (configurator/backend/
websocket/scheduler/queue-short/queue-long/frontend) with prefixed
container_names, so the swarm resolver (<stack>_<key>) and compose exec
(<container_name>) both resolve; backend addressed via container_address('backend')
- tests/lint: new guard replays container_address._resolve_bare_name over the
static services config so a missing service/name is caught before CI deploys
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The OpenResty redeploy handler ('remounts bind to live inode') fires once per app; without --detach each call blocked until the full rolling convergence, so the per-app blocks accumulated into an apparent hang. openresty actually converges (clean Shutdown->Running, old replica keeps serving during the rolling update, readiness is gated separately by the converge-wait), so add --detach to return immediately. Also add timeout: 600 (10 min) to that handler and to the three single-fire app service-update-force tasks (baserow post-migration restart, matomo install restart + bootstrap re-run) as a hard cap: a genuinely stuck redeploy now fails cleanly instead of hanging forever. 600s comfortably exceeds the normal rolling convergence (~2-5 min, matomo healthcheck start_period 60s x replicas) and matches the existing timeout: 600 already used elsewhere in baserow; the three keep their blocking so the next step still gets the service ready.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In swarm the matomo_bootstrap service inherited compose_replicas() (topology default = node count), so multiple installer replicas ran the Matomo web-installer against the shared central DB at once and raced on table creation ('[install] Please fix the following errors'). The bootstrap is a one-shot (restart_policy: no) that must run exactly once. Add a per-service replicas override: deploy.yml.j2 honours a 'service_replicas' var when defined (else the topology default, unchanged for every other service), and matomo's compose sets service_replicas=1 for the bootstrap only. The main matomo service is rendered before the set and keeps the topology default.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Discourse's app container runs via the upstream discourse_docker launcher, not compose, so when every backing engine is central the rendered stack has no service of its own and 'services:' is null - the Validate Docker Compose handler then fails with 'services must be a mapping' in swarm. Emit 'services: {}' unless a local sidecar exists. The condition mirrors base.yml.j2's render gates exactly (DB/objstore/redis local via type templates that gate on 'local' = enabled and not shared, plus the oauth2 sso-proxy sidecar).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In swarm COLLABORA_CONTAINER_ADDRESS (container_address lookup) renders a $(/usr/bin/resolve-container-id collabora collabora) subshell. With command: ansible passes it literally, so docker gets 'No such container: $(...)' (bigbluebutton fatal at the Collabora systemplate/font task). shell: runs it via /bin/sh -c so the subshell expands to the real container ID. A lint rule already requires container-exec-with-subshell tasks to use shell.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On a swarm redeploy/reschedule the typesense container gets a new overlay IP, but its persisted single-node raft state (/data/state) still references the previous IP, so raft never elects a leader and the task stays 'Starting' forever - the 'Wait until typesense converges to running' step then times out (e.g. funkwhale). --reset-peers-on-error makes typesense reset its raft peer config to the current node on that mismatch; the data is a re-indexable search cache. Replaces a previously misdiagnosed curl->/dev/tcp healthcheck change (reverted) - the healthcheck was never the cause since the container never reaches Running.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Apply the formatter's preferred multi-line 'with (...)' grouping for the mocked context managers added with the volume-lookup registry guard. No behavior change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In github CI the whole manager-pinned stack (mailu + central infra) lands on one ~16GB runner; the cumulative deploy.resources.reservations.memory exceeds the node, so swarm rejects placement with 'no suitable node ... insufficient resources on 1 node' (mailu_admin -> bluesky). Swarm reservation accounting counts physical RAM, not swap, so the enlarged runner swap cannot help. Gate the reservations block behind RUNTIME \!= 'github' (RUNTIME is baked into the inventory from detect_runtime, confirmed =github in CI); host/dev/act keep full reservations for QoS/spread.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The swarm workflow ran 3 DinD nodes plus builds on the default ~4 GB runner swap while compose/workstation/universal already enlarge it. Add the same free-disk-space (swap-storage) and enlarge_swap.sh steps, guarded with \!env.ACT, to prevent host OOM during builds. Orthogonal to swarm mem_reservation scheduling, which counts physical RAM not swap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the floating 'latest' tag on the oauth2-proxy sso_proxy sidecar with a concrete version for reproducible deploys.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In swarm keycloak is unpinned and can land on a worker, where the realm-import host bind /opt/compose/keycloak/volumes/import does not exist (it is created only on the manager), so the task is Rejected and never converges. The running 'start' service reads the realm from the database; the swarm import is an independent manager-pinned one-shot (05b) that mounts the dir itself, and the bind is only needed by the compose import path (05a 'compose run keycloak import'). Gate the mount with when: DEPLOYMENT_MODE \!= 'swarm'.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
lookup('volume', role) read the lazily-populated canonical-volumes registry directly, so a lookup running before any consumer built that role (nfs_prep's pre-create loop for an early unpinned engine such as ollama) saw an empty registry and skipped the subdir; the swarm bind mount then failed with 'no such file or directory'. Force get_application_defaults() on a miss, guarded so the populated hot path skips the deepcopy. Adds two guard tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route image/volumes/networks through the container_image, container_volumes, compose_volumes and *_networks lookups (was a raw image ref, an inline service volumes block, and the deprecated compose_volumes pipe form); add meta/volumes.yml as the volume source and drop the four now-unused PIHOLE_IMAGE/VERSION/VOLUME vars. Pi-hole sends no mail, so the email service is pinned enabled=false/shared=false (dynamic-flag nocheck) and dropped from the variant matrix.
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.
oauth2-proxy's singular 'provider-ca-file' flag has a known bug
(oauth2-proxy/oauth2-proxy#2802) and may not be a valid config key on
:latest, causing oauth2-proxy to fail config parsing and crash on
startup. This made nginx's auth_request to sso-proxy return 500 for
every pihole request (HTTP Error 500 on https://pihole.infinito.example/),
blocking even the pre-Playwright readiness check.
provider_ca_files (plural, array of strings) is the documented,
working config key for loading a custom CA for the OIDC provider
connection.
This block was added in an earlier (reverted-in-intent but never
actually removed) attempt and caused a duplicate 'sso-proxy' YAML key
(also defined via sys-svc-compose/base.yml.j2's sso_proxy include),
breaking compose_ca.py's compose config parsing entirely.
The CA trust fix now lives solely in 01_core.yml's lineinfile task,
which patches the rendered oauth2-proxy config file directly -- no
compose.yml changes needed.
- playwright.spec.js: remove duplicated SSO/persona scenarios now
covered by test-guest.js/test-oauth2.js/test-native.js. Reduce to
baseline checks (front page + TLS/HSTS, HTML content-type), matching
the pgadmin pattern.
- meta/main.yml: rewrite description to describe Pi-hole the software
(network-wide DNS sinkhole, DNS/DHCP management, web dashboard)
rather than the role/integration details.
penpot was merged using ports 8041/16506 after our last rebase.
Reassign pihole to 8078 (http) and 16507 (sso) via
infinito contributing network ports suggest.