Give each reverse-proxied app a swarm-resolvable upstream: add services.<entity>.ports.internal.http to the role metadata and rename the main compose service from the generic 'application' to the entity name, so its short DNS alias on the shared overlay is unique. Thread the service name through compose, vars (*_SERVICE_NAME), volume mounts and the oauth2 origin host. Consolidate the split image/ports blocks in bridgy-fed and postmarks into the entity block.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Centralise the nginx proxy_pass directive in one source of truth (utils/networks/proxy.py + plugins/lookup/proxy_pass.py). Compose keeps the literal 127.0.0.1:<local> upstream; swarm resolves the service over the shared overlay at request time (variable upstream + http resolver) so a rescheduling backend never blocks openresty startup. Converts the html/media/upload/ws location partials, the keycloak oauth2 and login-iframe endpoints, and the logout endpoint to the lookup; adds the swarm resolver and the front-proxy service_key plumbing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
sys-svc-webserver-core rewrites nginx.conf AFTER openresty first deploys. An
immutable swarm `config` froze the bootstrap content (openresty kept serving
the `listen 8080; return 200` stub and never bound 80/443). A bind mount
reflects the live host file on `service update --force`, so the real vhost
config loads. openresty is manager-pinned, so the host file is always present
where the task lands.
Runtime-confirmed (swarm, attempt 28/29): openresty served the real config and
bound 80/443, vs the bootstrap-only stub under type: config.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Load openresty via the sys-svc-compose network/routine.yml helper (same
pattern as the db/ollama roles) so the shared `openresty` overlay is created
attachable + swarm-scoped before the stack deploys; emit the top-level
networks block via compose_networks.
- Add an opt-in `order:` to the swarm update_config and set it to `stop-first`
for openresty: it publishes 80/443 in host mode, so a default rolling update
deadlocks (the new task cannot bind ports the old task still holds).
- Relax the network-include lint to accept a single compose_networks lookup in
a host-mode template (openresty is mode-conditional: host in compose, overlay
in swarm).
- Whitelist openresty's path_join network/routine.yml include in
.ansible-lint-ignore (same dynamic-include pattern as the db/ollama roles).
Runtime-confirmed (swarm, attempt 32): openresty created its overlay, attached
consumers, reached backends, and redeployed cleanly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the registry-driven overlay renderer so the central openresty proxy
reaches every web-facing backend over a shared swarm overlay:
- Add consumer kind `web_facing` (matches web-app-*/web-svc-*) so every
web-facing app attaches its primary service to the openresty overlay in
swarm mode; openresty resolves them by swarm service DNS.
- openresty's overlay declares `collect_proxy_resolvable`, harvesting the
keycloak beacon's `auth.<domain>` alias onto its own attachment so each
per-app sso-proxy resolves the auth host to openresty.
- Fix the default-net name collision: a shared_net provider rendering its own
compose must not also emit `name:` on its default overlay (the external net
already carries that name), which made `docker stack deploy` fail with
"network <x> already exists".
Runtime-confirmed: openresty reached keycloak over the overlay (302, no more
connection-refused) once consumers attached.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wrap each `ansible-galaxy collection install` invocation (Galaxy and
the Git fallback) with `timeout --foreground 15m` so a stalled upstream
fetch can no longer hang the whole `act` run indefinitely. Without this
guard, an attempt was observed to spin for ~51 minutes inside the
Discover-apps step before being killed externally.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
OpenSSL 3.x rejects a trust-anchor CA without keyUsage, but cert-gen.sh only regenerated on missing/expiring certs, so a CA created before the keyUsage addext was added (10y validity) never refreshed and broke all MITM-proxied HTTPS verification in the dev cache. Add a keyUsage guard so a deficient CA is regenerated even while still time-valid.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Split the generator data into integration_matrix_data.py and regenerate 027-integration-matrix.md (82x82, 523 wired + 96 upstream edges). Artifact is generated, not hand-edited.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
desk-chromium, desk-firefox, desk-gnome declare extensions as mechanism: extension (no bridges); templates derive the install lists from addons; desk-gnome variant axis drives enable/disable via the addon enabled flag. Playwright exemption documented (Decision 11).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mattermost manages plugins at runtime via named volumes; no meta/addons declared. README documents the exemption.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pretix-oidc declared as mechanism: plugin with the 2.3.1 pin as a string version and bridges [sso].
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docker_manager, discourse-activity-pub, discourse-akismet, discourse-ldap-auth (bridges ldap) declared as mechanism: plugin; config.yml.j2 + vars/main.yml read the new addons path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Six mautrix network bridges declared as mechanism: bridge, enabled: false, with DB creds referenced from meta/schema.yml. MATRIX_BRIDGES derived from addons; vars/bridges.yml, filter_plugins/bridge_filters.py and its unit test removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OpenIDConnect (bridges sso) and PluggableAuth declared as mechanism: extension; extension list derived from addons in vars/main.yml.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Declares integration_* addons (discourse/github/gitlab/google/mastodon/openai/openproject/peertube) per the 027 integration matrix. Disabled by default; not yet end-to-end wired/tested (acceptance criteria 265-272 remain open).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each Nextcloud app declared as a per-file addon; sociallogin bridges sso, user_ldap bridges ldap, bbb/onlyoffice/richdocuments/spreed/whiteboard/xwiki bridge their partner services. Per-plugin runtime config absorbed from vars/plugins/* (now deleted); install/configure tasks read the addon config via include_vars for lazy var rendering.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Seven core modules declared as mechanism: module, required: true, group: core; module lists derived from addons in vars/main.yml.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ldapauth addon bridges [ldap]; ldap+sso service blocks stay in meta/services.yml; tasks/04_addons.yml reads the new addons path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
No-SSO roles authenticate via their own username/password form. The admin persona only treated a completed Keycloak round-trip as proof of reaching an authenticated surface, so a role like Snipe-IT — whose only logout control sits inside a collapsed account dropdown the visible-marker poll never sees — failed the gate even after a successful native login.
Track native-login success symmetrically: a real password form was found and submitted (loginAttempted), the form is gone afterwards, and the page parked on the role's canonical domain. The loginAttempted guard prevents a form-less public landing on the canonical domain from passing the gate without authentication. Also strip the verbose mid-code comments from the persona helper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OpenProject's /login renders a header Sign-in toggle plus a duplicate pulldown form, so a generic Sign-in button click hits the toggle and never submits the credentials. Press Enter on the password field to post the enclosing form (the button click stays as a fallback). Also drop the bare account token from the admin auth marker so an unauthenticated Create-a-new-account link no longer masks a failed login as no-logout-control.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Postmarks admin auth is the SSO trusted-header bridge only; with SSO off there is no native admin login for the persona to drive, so the no-SSO variant failed to reach an authenticated surface. Gate PERSONA_ADMINISTRATOR_BLOCKED on services.sso.enabled.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In the no-SSO variant the admin persona drives native login, but it was rejected ('Invalid user or password') even though the seeded credentials match. Set the user status to active in ensure_admin_user.rb so a freshly-created admin can authenticate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Synapse S3 media offload (synapse-s3-storage-provider) is wired only in the ansible flavor; the compose flavor stores media on local disk, so the matrix bucket never grows and the avatar seaweedfs check failed on variant-2 (compose). Expose MATRIX_FLAVOR and skip the test unless the flavor is ansible.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Baserow's admin is SSO-gated; with SSO off there is no provisioned native frontend admin (first run forces /signup), so the persona can never authenticate and the no-SSO variant timed out. Gate PERSONA_ADMINISTRATOR_BLOCKED on services.sso.enabled, mirroring the biber gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On busy runners the mailu admin/MariaDB container can be OOM-killed or briefly refuse connections mid user-loop (flask mailu user -> ConnectionRefused rc=1 / SIGKILL rc=137), which failed co-deployed roles (opentalk, fusiondirectory). Add retries/delay/until to the user-create and password-change commands so a transient outage is waited out; 'already exists' still short-circuits as success.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Switch the MDAD upstream from the kevinveenbirkenbach fork back to the canonical spantaleev/matrix-docker-ansible-deploy repo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Synapse's default rc_login (burst 3, ~0.17/s) is too strict for the SSO e2e suite, where consecutive persona sign-ins drain the burst and leave later sign-ins cycling consent <-> M_LIMIT_EXCEEDED until the test is torn down (Matrix avatar never uploaded). Set matrix_synapse_rc_login with a higher burst via the MDAD ansible flavor vars.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
After the proxy SSO handoff the page URL is already /admin, so url().replace(/\$/,'')+'/admin' produced /admin/admin (HTTP 404) and the logout probe found no control. Resolve /admin against the origin with new URL() so it always lands on <origin>/admin, whose navbar carries the native logout link.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The native-login block was skipped when the non-waiting isVisible check ran before the Vue form mounted, so the admin never authenticated and the company-settings logo upload never happened (bucket stayed empty). Wait for the email field + Vue hydration, fill and submit, then confirm the URL leaves /auth/login.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When no header/nav anchor exists the logout affordance is appended to document.body as a fixed-position control. Move it from top-right to bottom-left so it no longer overlaps Baserow's top-right '+ Add new' action, and drop a duplicate display:inline-flex.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
YOURLS' native admin authenticates against the role-local credentials.administrator_password, not the global user password. Export it as ADMIN_NATIVE_PASSWORD (env keys kept in ASC order) so the admin persona's native-login fallback can sign in at /admin/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ensure_admin_user.rb sets the admin password only when the user is new or the current password does not match (avoids re-validating against former-password rules on every redeploy) and clears force_password_change so native login does not hit the change-password interstitial. playwright.env.j2 blocks the biber persona in the no-SSO variant where biber has no account.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ActiveStorage presigned the browser PUT against the internal AWS_ENDPOINT (http://seaweedfs:8333), which the browser cannot reach and CSP disallows, so avatar uploads never landed in the bucket (objects 161->161). Use the public S3 endpoint (objstore public_url minus the bucket path) and allow the SeaweedFS S3 host in connect-src.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gunicorn runs --preload, so workers fork before the Social Login Key is applied and serve /login from a stale provider cache that clear_cache() (a separate one-shot process) cannot refresh. apply_oidc_settings.py now reports whether the key changed; 03_oidc.yml restarts the backend only on a real change (no steady-state bounce). The e2e helper asserts the SSO button is visible and clicks it instead of falling back to the 500ing oauth2_logins callback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When SSO is disabled the OIDC step is a no-op, so drive the role's own username/password form with :visible selectors (skips hidden anti-autofill decoys such as snipe-it's password_fake) and a path-walk over /login,/admin/,/admin. The landing-page probe waits 10s; per-path probes 5s so a slow form at / is not abandoned. Fixes no-SSO admin journeys (snipe-it, yourls, openproject).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Background workers (PeerTube transcode, Pixelfed media jobs) rewrite existing objects during the poll window, so accepting any changed mtime made those consumers false-green. Default is now a new key only (the original count-growth semantics); single-slot consumers that overwrite one fixed key in place (fider tenant logo) pass expectInPlaceOverwrite to also accept a changed mtime.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>