Synapse on an onion node could never complete SSO: it has no Tor, so the
http://auth.<onion> issuer was unresolvable server-side (discovery/jwks/
token), and Synapse hard-rejects non-https issuers on top.
- runner/hostpin.{sh,service}: pin the onion auth host to the runner's real
default gateway (node OpenResty) from /proc/net/route, Before=docker.service
so nested containers see it from their first request; host-gateway is
unusable in nested DinD (resolves to the inner docker0)
- Synapse bind-mounts the runner's /etc/hosts; token iss stays the onion
issuer the browser uses
- bootstrap.service: PassEnvironment (systemd units do not inherit container
env; MATRIX_MDAD_PLAYBOOK_TAGS only worked via its fallback default)
- vars.yml.j2: skip_verification on onion (Synapse's escape hatch for
issuers without https; Tor provides transport security per RFC 7686)
- 02_render_inventory: drop the MDAD bootstrap marker + bounce the runner on
inventory changes — mode=update reconfigures the nested stack at all now
- MATRIX_SERVER_NAME follows matrix_domain onto the onion so MXIDs built from
it (DM spec, bridge configs) reference users that exist
- specs: shared Firefox onion fixture (16 files), gotoElementApp reload-retry
against Element's Tor-triggered init race, seaweedfs spec gates on the
filer frontend, env uses canonical_url consumer=
Verified in-container: SSO 302 to onion Keycloak, admin+biber OIDC login,
E2EE DM admin<->biber, CSP — all green over Tor.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New lint test_no_raw_goto.py forbids raw `page.goto(...)` in role playwright specs + persona flows (the gotoOnion wrapper in personas/utils is exempt) so navigation retries transient Tor-transport errors over onion. Migrates every raw call across ~75 roles (343 sites) to `gotoOnion(page, ...)` and teaches gotoOnion to detect relative onion URLs (`isOnionCanonical`). gotoOnion is a behaviour-identical drop-in on clearnet (single attempt, same return). Verified: no-raw-goto lint green, eslint 0 errors, make test green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Role-code (meta/services.yml + tasks/01_docker.yml):
- Drop the services.matrix.setup gate. The flag defaulted to false and
silently skipped admin + chatgptbot creation, leaving Synapse with 0
users. Both tasks now run unconditionally; idempotency comes from the
failed_when filter on "User ID already taken" instead of the previous
ignore_errors: true that masked every other error class too.
- exec -it → exec -T: Ansible has no TTY, so -it failed silent and the
ignore_errors swallowed it; -T is the no-tty/no-stdin variant the
Ansible command module expects.
- Quote every template var passed to register_new_matrix_user with
`| quote`. The administrator password (`'$tr0ng€xampl3PW\!`) carries
characters that break the command module's shlex split otherwise.
- no_log: MASK_CREDENTIALS_IN_LOGS so plaintext passwords stop leaking
into Ansible output.
Playwright spec (files/playwright/):
- Split playwright.spec.js (643 lines, single file) into _shared.js +
six test-*.js modules following the taiga/nextcloud/pixelfed pattern.
Each scenario is now individually grep-able and inspectable from the
reporter output instead of all sharing the playwright.spec.js:NNN
prefix.
- Add test-login-admin-native.js: native password login for the
administrator persona, gated to OIDC-off variants
(`test.skip(oidcServiceEnabled, ...)`). Reuses the existing
signInViaElementPassword helper.
- test-dm-admin-biber.js: add `skipUnlessServiceEnabled("oidc")` —
biber only exists in Synapse via Keycloak first-login federation,
so the DM scenario is structurally impossible without OIDC.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>