Commit Graph
14682 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 4.7 55c1bceeb1 fix(sys-front-inj-all): move body_filter Lua to *_by_lua_file to fix prometheus vhost long-bracket parse error
OpenResty's inline *_by_lua_block parser is the nginx config parser; it
can't reliably handle Lua long brackets ([=[...]=]) embedded in large
rendered snippets. The prometheus role's javascript.js.j2 (logout-icon
DOM-mutation script) collapses to a ~3.4 KB one-liner wrapped in
head_snippets[#... + 1] = [=[<huge_JS>]=], which trips the parser with
"missing the closing long bracket "]=]", the inlined Lua code may be too
long" — the wording is OpenResty's own hint to switch to the file form.

Rendering the body_filter as an external per-app Lua file routes through
Lua's native parser instead, eliminating the limit.

- plugins/lookup/nginx.py: expose <conf_dir>/lua/ as
  directories.configuration.lua + add to directories.ensure so
  sys-svc-webserver-core auto-creates it
- roles/sys-front-inj-all/templates/body_filter.lua.j2: new — pure Lua
  body (formerly inside body_filter_by_lua_block { ... }) plus the
  push_snippets Jinja macro
- roles/sys-front-inj-all/templates/location.lua.j2: replace inline
  body_filter_by_lua_block with body_filter_by_lua_file pointing at the
  per-app .lua
- roles/sys-front-inj-all/tasks/main.yml: render the per-app Lua file
  after the feature include_roles (those set the *_code_one_liner vars
  the sub.j2 templates depend on); notify restart openresty

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 01:08:55 +02:00
kevinveenbirkenbachandClaude Opus 4.7 3d05803dc7 refactor(svc-db-mariadb, web-app-decidim): migrate docker_container_exec to container wrapper + extract SQL
Lint scanner now flags `community.docker.docker_container_exec` — it has
a 1:1 `compose exec`/`container exec` replacement and pinning to Docker
defeats engine-swappability. Other `community.docker.*` modules (network
create, host_info, container start with image+args) have no clean wrapper
equivalent and stay legal.

Migrations:
- svc-db-mariadb (01_core, 02_init, 03_reset_root_password): replace
  module calls with `container exec -i` via `ansible.builtin.command`,
  pipe SQL via stdin. Health probes share a single SELECT 1 file.
- web-app-decidim (01_setup): same migration, preserving env vars as
  `-e KEY=VAL` argv elements and stdin for rails-runner snippets.

Extract all SQL to `roles/svc-db-mariadb/files/sql/*.sql` as plain SQL
(no Jinja). Values flow as MariaDB user variables (`@db_name`, `@db_pwd`,
`@root_pwd`, …) via a `SET` prefix in the task stdin. Identifier
substitution (db/user/encoding names) uses `PREPARE` + `CONCAT(@var, …)`
since `@vars` cannot bind identifiers directly. Password values use
`QUOTE(@var)` inside SQL and Jinja `replace("'", "''")` on the task side.

Pin MariaDB to "11.8" (current LTS major). Required for `CREATE DATABASE`
in prepared statements, supported since 10.5.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 00:39:36 +02:00
kevinveenbirkenbachandClaude Opus 4.7 f5e009f362 fix(web-app-baserow): explicit migrate + idempotent admin bootstrap via compose wrapper
Avoid Playwright HTTP 500 race: the all-in-one Baserow container's entrypoint
can still be running Django migrations while the playbook continues. Run
`compose exec … manage.py migrate --noinput` with retries before bootstrapping
the admin so auth_user / core_workspace exist, then restart the service. Inline
the previous helper scripts as YAML tasks (drop migrate_via_compose_env.py
entirely; replace bootstrap_admin_via_compose_env.py with a small Django shell
snippet loaded via lookup('file', …)). Use the `compose` wrapper instead of raw
`docker compose` to keep the engine swappable. Unit-test the bootstrap snippet.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 00:39:36 +02:00
kevinveenbirkenbach f9f843468e Merge branch 'feature/web-app-kix' of https://github.com/kevinveenbirkenbach/infinito-nexus-core into feature/web-app-kix 2026-05-18 22:36:05 +02:00
kevinveenbirkenbachandClaude Opus 4.7 82ffc02516 fix(scripts/lint/galaxy): skip dunder dirs in role discovery
`roles/__pycache__/` is created whenever Python imports the `roles`
package (it has an `__init__.py`), then `find` picks it up and
galaxy-importer fails the run with `FAIL __pycache__`. Same root cause
as the test_mapping.py fix in 635a0808d.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:36:05 +02:00
kevinveenbirkenbachandClaude Opus 4.7 94018a1235 fix(tests/integration/roles/meta): skip dunder dirs in role discovery
`roles/__pycache__/` gets created whenever Python imports the `roles`
package (it has an `__init__.py`), then iterdir() picks it up as a role
and the mandatory-files check fails for the synthetic "tooling" role.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:36:05 +02:00
kevinveenbirkenbachandClaude Opus 4.7 e746368c20 fix(sys-svc-proxy): CSP is SPOT — strip deprecated security headers + add regression lint
Browser vendors deprecated X-Frame-Options/X-XSS-Protection/X-Download-Options/
X-Permitted-Cross-Domain-Policies; CSP (rendered from each role's
meta/server.yml via build_csp_header) is the modern replacement. To make the
SPOT actually win against upstream apps (e.g. Keycloak/Quarkus still emits
X-Frame-Options), the proxy now hides those headers on every location type.
Includes a fail-loud lint that catches any future add_header / Header set/add
re-introduction in .conf / .conf.j2 / .htaccess / .j2 / .jinja / .tmpl files.

- sys-svc-proxy: new upstream_strips.conf.j2 partial bundles HSTS + the 4
  deprecated header strips; included in html/media/ws/upload + web-svc-logout
  + web-app-oauth2-proxy locations (nginx array-replacement inheritance forces
  per-location placement)
- web-app-keycloak: meta/server.yml frame-ancestors allows the primary-domain
  wildcard with the dynamic TLS protocol so dashboard silent-check-sso iframe
  loads cleanly
- web-app-dashboard/playwright: drop isOidcSilentCheckNoise — root cause is
  now fixed, no noise filter needed
- web-app-dashboard/nginx + web-app-nextcloud/nginx: remove the deprecated
  add_header lines themselves
- tests + docs: new lint registers `deprecated-http-header` suppression rule

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:36:05 +02:00
kevinveenbirkenbachandClaude Opus 4.7 abe91862d7 refactor(web-app-dashboard/playwright): SPOT console-cleanliness in test-no-console-noise.js
Seven scenario tests carried a trailing
``expectNoUnexpectedDiagnostics(diagnostics, { ignoreMatomoConsoleNoise: true })``
that overlapped the dedicated ``test-no-console-noise.js`` scenario.
The tail was both a hidden Single-Responsibility violation (each test
checked its own concern *plus* console cleanliness) and a latent
race: a test that ran long enough to see Keycloak's silent-check
iframe failure would trip on the unfiltered OIDC noise — the exact
failure that took down ``test-header-navbar-logos.js`` in CI run
26045851566.

Drop the tail from every consumer. ``test-no-console-noise.js`` is
now the sole owner of the diagnostic check (with both
``ignoreMatomoConsoleNoise`` and ``ignoreOidcSilentCheckNoise``
filters enabled), so each remaining test passes or fails for exactly
the reason its name promises.

For two of the seven (``test-iframe-sync.js`` and
``test-simpleicons-cards.js``) ``attachDiagnostics`` was only kept
around for the tail check — it goes too. The other five keep
``diagnostics`` because they still feed its ``.responses`` array to
``waitForResourceResponse``.

7 files, 16 deletions, 0 insertions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:36:05 +02:00
kevinveenbirkenbachandClaude Opus 4.7 adb901c7ce refactor(web-app-dashboard/playwright): declutter _shared.js + split out console-cleanliness check
Console cleanliness moved to its own scenario
---------------------------------------------

``test-header-navbar-logos.js`` failed in CI when port-ui's Keycloak
silent-check-sso iframe was blocked by frame-ancestor CSP — the
``expectNoUnexpectedDiagnostics`` call at the test's end caught a
console.error logged by chrome for the blocked iframe (chrome-error://
chromewebdata + ``Framing 'https://auth.…'``) and failed every retry.
That diagnostic was a generic "no surprises" tail, unrelated to the
logo assertions the test is named after.

- Drop the diagnostic-tail call from the logo test; it now does
  exactly what its name says.
- New ``test-no-console-noise.js`` runs the diagnostic check as its
  own scenario, with two noise filters enabled
  (``ignoreMatomoConsoleNoise`` + ``ignoreOidcSilentCheckNoise``).
- New ``isOidcSilentCheckNoise`` helper in ``_shared.js`` matches the
  ``chrome-error://chromewebdata`` URL plus the ``Framing '…'`` /
  ``silent-check-sso`` text patterns. These are test-env config
  artefacts (Keycloak ``frame-ancestors`` not whitelisted), not
  dashboard regressions.
- ``expectNoUnexpectedDiagnostics`` learns a corresponding
  ``ignoreOidcSilentCheckNoise`` option.

_shared.js no longer hides single-consumer code
-----------------------------------------------

Audited every export of ``_shared.js`` against every ``test-*.js``
consumer; ten symbols had exactly one caller and were therefore not
"shared". Moved each to the test file that owns it:

- ``runGuestFlow`` / ``runBiberFlow`` / ``runAdminFlow`` →
  matching ``test-*-persona.js`` (now ``require("./personas")`` direct)
- ``assertCspMetaParity`` / ``assertCspResponseHeader`` /
  ``expectNoCspViolations`` / ``installCspViolationObserver`` plus
  ``canonicalDomain`` → ``test-csp-canonical-domain.js`` (CSP observer
  install is now a local ``test.beforeEach`` in that file)
- ``dashboardTargetRoles`` parser → ``test-tile-reachability.js``
- ``cdnBaseUrl`` + each prefix (sharedCss / roleCss / sharedJs) →
  ``test-shared-css.js`` / ``test-cdn-role-stylesheet.js`` /
  ``test-logout-js-injection.js``
- ``platformLogoUrl`` + ``platformFaviconUrl`` →
  ``test-header-navbar-logos.js``
- ``appBaseUrl`` / ``oidcIssuerUrl`` / ``loginUsername`` /
  ``loginPassword`` / ``expectedOidcAuthUrl`` → ``test-oidc-login.js``

Dropped completely as dead test code: ``companyLogoUrl`` (no
spec ever read it) and its ``COMPANY_LOGO_URL`` env render in
``playwright.env.j2``. ``DASHBOARD_COMPANY_LOGO_URL`` in
``vars/main.yml`` stays — ``config.yaml.j2`` still consumes it for
the port-ui company.logo.source field.

``_shared.js`` is down to genuinely-shared state: ``dashboardJsBaseUrl``
+ ``matomoBaseUrl`` (each ≥ 2 callers), the diagnostic plumbing
(``attachDiagnostics`` / ``expectNoUnexpectedDiagnostics`` /
``waitForDashboardReady`` / ``waitForResourceResponse``), the noise
predicates, ``beforeEach``, and the ``skipUnlessServiceEnabled``
re-export. 116 LOC of state/exports collapse to 63.

Hitchhikers
-----------

- ``vars/main.yml`` loses an outdated docstring above the branding
  SPOTs (the SPOTs themselves stay; ``config.yaml.j2`` consumes them).
- ``sys-svc-compose/defaults/main.yml``: linter re-joined the
  multi-line ``compose_repository_pull`` default onto one line; pure
  formatting, no semantic change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:36:05 +02:00
kevinveenbirkenbachandClaude Opus 4.7 106be5245f lint(meta): forbid application_id reference in role meta/*.yml
`application_id` is not bound when the applications registry is built
(meta/*.yml is rendered before any role-vars binding), so
`lookup('config', application_id, ...)` returns the raw unrendered
Jinja string instead of the resolved value. The lint test enumerates
`roles/*/meta/*.yml`, picks each line that contains a Jinja expression
(`{{ ... }}` / `{% ... %}`), and fails if `application_id` appears as
a variable inside it (kwarg form `application_id='web-app-…'` is
correctly excluded).

The failure message points at every offending file:line and tells the
author to spell out the role id explicitly
(`lookup('config', 'web-app-nextcloud', …)`).

Also reduces the gitea credentials.native_metrics_token description to
the value's meaning ("Bearer token authorizing scrape requests to
Gitea's /metrics endpoint") and drops the inventory-path / lookup-
mechanism text that previously tripped the lint until the Jinja-only
filter went in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:36:04 +02:00
kevinveenbirkenbachandClaude Opus 4.7 7ef0fca5c0 fix(web-app-nextcloud): hardcode role-id in legacy_login_mask lookup + anchor submit-button regex
meta/services.yml — switch `lookup('config', application_id, ...)` to the
spelled-out role id `'web-app-nextcloud'`. `application_id` is undefined
when meta/services.yml is rendered (the applications registry is built
before any role-vars binding), which made the lookup return the raw
unrendered Jinja string instead of the resolved oidc.enabled value, and
in turn flipped legacy_login_mask.enabled to false in variant 1 where
it should have been true. The matching `twofactor_*` plugin entries in
the same file already use the hardcoded form for the same reason.

files/playwright/_shared.js — anchor `nextcloudCredentialSubmitPattern`
to `^(sign in|log in)$` so it matches only the credential-form submit
button. With auth.webauthn.enabled=true (the post-fix variant-1 state)
Nextcloud renders an additional `Log in with a device` button next to
the credential `Log in`, which the previous unanchored pattern matched
too — strict-mode violation, test fail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:36:04 +02:00
kevinveenbirkenbachandClaude Opus 4.7 7b58beba72 fix(web-app-matrix): wait for Synapse readiness before create admin account
`compose up` returns as soon as containers are CREATED, not when
Synapse has finished initial migration + HTTP listener bind. The
`create admin account` task then fired against `http://localhost:8008`
and got `ConnectionRefusedError: [Errno 111] Connection refused`,
failing the matrix-variant-1 redeploy.

Insert a `uri_retry` probe on `/_matrix/client/versions` (60 retries × 2s
= 120s window) between the bridge-registration wait and the admin
creation. Pre-existing single-variant deploys never hit this because
`services.matrix.setup: false` skipped the admin creation entirely;
the flag was removed in 616f5c840, which made admin/chatgptbot run on
every deploy and surfaced the race.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:36:04 +02:00
kevinveenbirkenbach dca0b2d999 Merge pull request #247 from kevinveenbirkenbach/feature/educational-bundle
feat(bundles): add deploy-bundles / redeploy-bundles flow
2026-05-18 20:47:47 +02:00
kevinveenbirkenbach ec7e8c2645 Merge branch 'main' into feature/web-app-kix 2026-05-18 20:00:46 +02:00
kevinveenbirkenbach db79a75666 Merge branch 'main' into feature/educational-bundle 2026-05-18 19:45:18 +02:00
kevinveenbirkenbachandClaude Opus 4.7 fd978a2897 feat(bundles): add deploy-bundles / redeploy-bundles flow
- new utils.inventory.bundle_apps resolver aggregates and dedupes role
  groups from inventories/bundles/{servers,workstations}/<name>/inventory.yml
- new make targets deploy-bundles (fresh-purged) and redeploy-bundles
  (reuse-kept), driven by BUNDLES=... env, wrapped by
  scripts/tests/deploy/local/deploy/{bundles,redeploy-bundles}.sh
- unit tests for the resolver under tests/unit/utils/inventory/
- document the new targets in docs/contributing/tools/make.md,
  docs/contributing/actions/testing.md, inventories/bundles/README.md
- collapse multi-line Makefile comments to one-liners for consistency
- disable nextcloud bbb plugin auto-enable (commented out)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 19:45:18 +02:00
kevinveenbirkenbach 5413299963 Merge pull request #246 from infinito-nexus/update/docker-image-versions-20260517
update(images): update Docker image versions
2026-05-18 19:45:17 +02:00
kevinveenbirkenbachandClaude Opus 4.7 db05d89844 feat(education-suite): add web-app-openwebui and web-app-mailu
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 19:45:17 +02:00
kevinveenbirkenbach 2298abdd16 Merge pull request #244 from infinito-nexus/update/docker-image-versions-20260513
update(images): update Docker image versions
2026-05-18 19:45:17 +02:00
github-actions[bot] 271b3e0e36 update(images): update Docker image versions 2026-05-18 19:45:17 +02:00
kevinveenbirkenbach bb42ab06f3 Merge pull request #245 from infinito-nexus/update/docker-image-versions-20260514
update(images): update Docker image versions
2026-05-18 19:45:17 +02:00
github-actions[bot] 692a413f76 update(images): update Docker image versions 2026-05-18 19:45:17 +02:00
github-actions[bot] 50b1fc2564 update(images): update Docker image versions 2026-05-18 19:45:17 +02:00
kevinveenbirkenbach 1db195878e fix(web-app-hugo): allow unsafe-eval in CSP script-src
CSP checker also blocked 'eval' from main.<hash>.js (GA4 bootstrap
uses Function-constructor). Pairs with the googletagmanager whitelist
from de79dc404; together they cover both CSP DOM blocks observed in
run 26018860043.
2026-05-18 18:13:09 +02:00
kevinveenbirkenbachandClaude Opus 4.7 e331951efc feat(web-app-bluesky): native admin login when oauth2 is disabled + spec split
Bridge the architectural gap that left bluesky without any usable login
path when services.oauth2.enabled is false. The login-broker stays the
upstream when OIDC is on (broker auto-provisions PDS accounts via the
encrypted-app-password handoff). When OIDC is off, the broker is bypassed
and the stock social-app is served directly; the PDS administrator is
provisioned by Ansible so the operator can sign in with handle+password.

Routing (tasks/01_core.yml):
- Front-proxy http_port now picks `BLUESKY_BROKER_PORT` when
  services.oauth2.enabled is true, `BLUESKY_WEB_PORT` (social-app)
  otherwise. Previously the broker was always upstream, so variant-1
  deploys returned a `Refusing handoff` 401 on every request.

PDS provisioning (tasks/07_native_admin.yml, new):
- uri_retry waits on PDS `/xrpc/_health`, then POSTs createAccount with
  `kc-administrator.<handle-domain>` (PDS rejects bare `administrator`
  as a reserved handle, hence the `kc-` prefix the broker already uses
  for OIDC-derived accounts).
- Idempotent: 200 = changed, 400 with "Handle already taken" = ok.
- Gated on `BLUESKY_WEB_ENABLED and not services.oauth2.enabled`.

Broker hardening (files/login-broker/server.js):
- 401 response (missing `X-Forwarded-User`) now `text/html` instead of
  `text/plain`. Headless Chromium does not fire `DOMContentLoaded`
  reliably on non-HTML 401 documents — Playwright `page.goto(...,
  { waitUntil: "domcontentloaded" })` hung the full 60s test timeout in
  the variant-1 guest-persona test.

Playwright spec (files/playwright/):
- Split single-file playwright.spec.js (203 lines) into _shared.js +
  seven test-*.js modules following the matrix/taiga/nextcloud/pixelfed
  pattern. Each scenario is now individually grep-able from the
  reporter output.
- Add test-native-admin.js: verifies broker bypass (landing body
  contains `<title>Bluesky</title>`, NOT `Refusing handoff`) plus PDS
  createSession returns 200 + accessJwt for the deployed admin handle.
  Uses Playwright's APIRequestContext directly — driving the stock
  social-app login UI (hosting-provider switcher + welcome modal +
  React form state) was too brittle.

Vars + env (vars/main.yml, templates/playwright.env.j2):
- `BLUESKY_ADMIN_HANDLE` and `BLUESKY_PDS_INTERNAL_URL` added for the
  Ansible task.
- `ADMIN_HANDLE` and `PDS_BASE_URL` exposed to Playwright env.

Verification:
- Variant 1 (live, in-container): 3 passed, 4 skipped, 0 failed.
- Variant 0 / Variant 2: spec bodies unchanged from pre-refactor,
  Ansible conditional invariant on the OIDC-on branch — but not
  re-deployed for live verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:13:08 +02:00
kevinveenbirkenbachandClaude Opus 4.7 2305f08553 chore(.claude/settings): keep permissions.allow ASCII-sorted (googletagmanager.com)
The runtime auto-allow appended ``WebFetch(domain:www.googletagmanager.com)``
at the end of the array after the previous WebFetch against the GTM
domain. ``test_arrays_are_ascending`` failed; resort the entry into
the correct alphabetical slot between ``www.google.com`` and
``www.jenkins.io``. No semantic change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:13:08 +02:00
kevinveenbirkenbachandClaude Opus 4.7 54dd4438e2 fix(web-app-nextcloud): show native login form when OIDC is disabled
Couple `legacy_login_mask.enabled` to `services.oidc.enabled` instead
of hard-coding it to false. The default flowed through to
`hide_login_form=true` in system_config, which hid the native
username/password form even on deployments without Keycloak — leaving
no usable login path in the OIDC-off variant (Playwright native-login
test timed out waiting for a form that was never rendered).

With the new formula:
- OIDC on  → legacy_login_mask.enabled=false → hide_login_form=true  (SSO only)
- OIDC off → legacy_login_mask.enabled=true  → hide_login_form=false (native form)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:13:08 +02:00
kevinveenbirkenbachandClaude Opus 4.7 e720ce4394 fix(web-app-matrix): harden user provisioning + split spec into per-scenario files
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>
2026-05-18 18:13:08 +02:00
kevinveenbirkenbachandClaude Opus 4.7 5ce43530da fix(web-app-hugo): nocheck the googletagmanager URL — script CDN, no landing page
``tests.external.repository.test_urls_reachable`` probed
``https://www.googletagmanager.com`` (added to the CSP whitelist for
Hugo's GTM integration) and flagged HTTP 404 as a hard failure. The
bare domain returns 404 by design — GTM serves only script paths like
``/gtm.js?id=…``, not a homepage.

Annotate both whitelist entries with ``# nocheck: url`` so the
reachability lint skips them. The CSP whitelist itself is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 17:24:14 +02:00
kevinveenbirkenbachandClaude Opus 4.7 f3a9b42442 fix(web-app-dashboard/playwright): broaden simpleicon card selector for port-ui 2.0.0
port-ui 2.0.0's probe-first asset resolver embeds reachable simpleicon
URLs directly into ``<img src>`` instead of caching under
``/static/cache/<hash>``. The old locator
``img[src*='/static/cache/']`` only matched port-ui 1.x's cache-only
output, so the simpleicons card test ran into a 60s timeout in every
test-development distro and dragged down 11 ``test-deploy-server``
jobs that share the dashboard playwright stage (CI run 26031703776).

Empirically verified against the live-rendered dashboard at
``http://localhost:5001/`` — every card-image-top ``<img>`` is now
served from ``https://…`` (cloud.veen.world / file.infinito.nexus),
zero ``/static/cache/`` paths remain.

Replace the prefix-restricted match with three explicit ``[src^=]``
alternatives so the selector covers every plausible source — the
inline-SVG branch stays untouched:

- ``img[src^='http://']``  / ``img[src^='https://']`` — port-ui 2.0.0
  direct-embed of an external (or same-origin) URL
- ``img[src^='/static/']`` — port-ui's own static handler, covering
  both the legacy ``/static/cache/<hash>`` cache path and the
  ``/static/simpleicons/<icon>`` volume-mount path the dashboard role
  ships to port-ui

A bare ``<img src="">`` (port-ui's ``onerror`` fallback before swapping
to the ``<i>`` class icon) is excluded by all three prefix matches, so
the test still distinguishes image-backed cards from class-only ones.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 17:24:14 +02:00
kevinveenbirkenbachandClaude Opus 4.7 0fba43d612 fix(web-app-pixelfed): read post-login CSRF from meta tag, not pre-login locator
The native-login test's logout POST reused `csrfInput.getAttribute("value")`
after submitting the login form. The `csrfInput` locator is scoped to the
`/login` DOM that no longer exists post-auth, so the value resolved to null
(or the locator timed out waiting for an `input[name="_token"]` that the
authenticated layout never renders) and the logout POST went out with an
empty `_token` — Laravel 419 / Test-Timeout 300s in the baseline run.

Read the regenerated CSRF token from the authenticated page's
`<meta name="csrf-token">` head tag instead. Laravel rotates the token on
`Auth::login()` anyway, so the pre-login `_token` would not have been
accepted by the logout endpoint even if the locator had resolved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 17:24:14 +02:00
kevinveenbirkenbach 1c25b1cc9e fix(web-app-hugo): whitelist googletagmanager in CSP script-src
CSP checker blocked https://www.googletagmanager.com/gtag/js?id=G-MBZGKNMDWC
on hugo.{{ DOMAIN_PRIMARY }} during variant-1 sync deploy
(run 26018860043). Add the host to script-src and script-src-elem.
2026-05-18 17:24:14 +02:00
kevinveenbirkenbachandClaude Opus 4.7 5ca53b2ea6 fix(web-app-taiga): admin PW env, native-logout assert, themed-routes CSS gate
- tasks/01_administrator.yml: pass DJANGO_SUPERUSER_PASSWORD to
  createsuperuser; without it Django sets an unusable password and
  every API/native login attempt 401s.
- files/playwright/_shared.js: logoutFromTaigaNative asserts at /login
  directly; Taiga redirects logged-out users to /discover where the
  form is gated behind a Login link.
- files/playwright/test-themed-routes.js + templates/playwright.env.j2:
  gate gradient assertions on CSS_SERVICE_ENABLED so the LDAP-only
  variant (stock Taiga UI, no web-svc-css) falls back to a readiness
  check instead of failing on missing injected theming.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:43:43 +02:00
kevinveenbirkenbachandClaude Opus 4.7 d7591cb44a lint(compose-repository): enforce Jinja + paired URL/ref; fix 3 violators
New lint
--------

``tests/lint/ansible/modules/test_compose_repository_url_ref.py`` walks
every dict in every project ``.yml``/``.yaml`` and asserts:

1. ``compose_repository_url`` MUST be a Jinja expression — keeps repo
   coordinates in ``meta/services.yml`` (looked up via vars) instead
   of pasted into task files.
2. ``compose_repository_ref`` MUST also be a Jinja expression for the
   same SPOT reason.
3. Every ``compose_repository_url`` MUST be paired with a
   ``compose_repository_ref`` in the same dict — pinning the clone
   source without pinning the ref leaves checkouts at upstream HEAD
   (non-reproducible deploys).

Violations fixed
----------------

The lint surfaced three offenders, all migrated to the established
``repository:``/``ref:`` SPOT pattern under the role's primary entity
in ``meta/services.yml`` (akaunting/bigbluebutton/roulette-wheel
already use this shape):

- **web-app-taiga**: added ``repository:`` + ``ref:`` to the ``taiga``
  service block; tasks now use ``TAIGA_REPOSITORY`` / ``TAIGA_REF``
  vars; restores the previously missing ref pinning.
- **web-app-openproject**: added ``repository:`` + ``ref:`` to the
  ``openproject`` service block (the ref keeps the
  ``stable/{{ OPENPROJECT_VERSION }}`` Jinja form, just SPOT-relocated).
- **web-app-littlejs**: added ``repository:`` + ``ref:`` to the
  ``littlejs`` service block; the existing
  ``LITTLEJS_REPOSITORY_BASE`` (used by ``nav_bottom.html.j2`` for the
  human-readable upstream link) is now derived from the new
  ``LITTLEJS_REPOSITORY`` via ``regex_replace('\\.git$', '')`` so the
  literal hostname is gone too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:43:43 +02:00
kevinveenbirkenbachandClaude Opus 4.7 19254a23b4 refactor(sys-svc-compose): rename docker_compose_flush_handlers → compose_handlers_flush
Pure 1:1 rename (69 insertions / 69 deletions across 66 files) to
finish the ``docker_*`` → ``compose_*`` alignment started in
8373468e9. The flag configures whether the compose include flushes
handlers right after run; the ``docker_`` prefix was misleading
(handlers are Ansible-side, not docker-side) and the variable is part
of the same ``sys-svc-compose`` SPOT cluster as ``compose_repository_*``.

Touches the role default, the implementation
(``sys-svc-compose/tasks/utils/up.yml``), all 60+ consumer roles,
the network-utility wrapper + its README, and the
``test_network_create_via_util.py`` lint (both the docstring guidance
and the asserted diff).

Breaking change for any external override that still sets the old
name — no alias is provided.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:43:43 +02:00
kevinveenbirkenbachandClaude Opus 4.7 9855ee7a40 refactor(sys-svc-compose): drop COMPOSE_LOAD_SERVICES dev-skip toggle
The flag existed to let operators set ``COMPOSE_LOAD_SERVICES: false``
in inventory/host_vars to skip the dependency service-loader loop and
speed up dev iterations. Repo-wide grep finds zero consumers outside
the two files touched here, so the knob has no callers — the
``_service_loader_preloading`` guard already covers the only path
that genuinely needs to bypass the loop.

Removes the orphaned default and simplifies the ``when:`` clause to
the single remaining condition.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:43:43 +02:00
kevinveenbirkenbachandClaude Opus 4.7 0cded595a2 fix(sys-svc-compose): align defaults key with consumers (compose_repository_pull)
The 8373468e9 rename refactor renamed
``docker_git_repository_pull → compose_*_repository`` in the role's
defaults but ended up as ``compose_pull_repository`` here while the
20+ consumer roles (set via ``include_role`` vars) all wrote
``compose_repository_pull``. ``compose_pull_repository`` was therefore
declared in defaults but never referenced anywhere — the
``test_role_and_group_vars_used`` lint caught it — and the dynamic
default would never have applied to consumers that didn't set the
flag explicitly.

Restore the consumer-side naming as the SPOT and drop the orphaned
defaults key.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:43:43 +02:00
kevinveenbirkenbachandClaude Opus 4.7 e97b1095e6 refactor(web-app-dashboard): split playwright spec, SPOT-ify branding, bump port-ui 2.0.0
Playwright spec split
---------------------

The 747-line ``playwright.spec.js`` monolith is split into a thin
orchestrator + ``_shared.js`` (env, ``beforeEach``, common helpers) +
one ``test-*.js`` per scenario, matching the convention used by
web-app-fediwall/friendica/nextcloud. Each scenario file holds its own
specialised helpers (e.g. the auth/dropdown helpers live in
``test-oidc-login.js``, image-loaded assertions live in
``test-header-navbar-logos.js``); only helpers used by ≥ 2 tests stay
in ``_shared.js``. ``dashboard-card-flow.js`` becomes
``test-tile-reachability.js`` (git-detected rename) — the
``runDashboardCardScenario`` helper had a single consumer, so the
indirection is gone.

SPOT for branding URLs
----------------------

The ``'https:' ~ source if source.startswith('//') else source``
transformation lived inline in ``config.yaml.j2`` and was duplicated in
``playwright.env.j2`` when the logo assertion was added. Three new
SPOTs in ``vars/main.yml``:

- ``DASHBOARD_PLATFORM_LOGO_URL``
- ``DASHBOARD_PLATFORM_FAVICON_URL``
- ``DASHBOARD_COMPANY_LOGO_URL``

Both templates now reference these vars; the inline transformations
and the now-redundant ``{% set ... %}`` lines in ``config.yaml.j2``
are removed. The slurp + parse of the rendered config in
``tasks/01_core.yml`` stays because it covers the host-override case
(``DASHBOARD_CONFIG_INV_PATH`` template may use different branding),
but is now documented as the override-discovery path rather than the
default source of truth.

port-ui 2.0.0 + test alignment
------------------------------

- ``meta/services.yml``: port-ui pinned ``1.2.0 → 2.0.0`` (probe-first
  asset resolver, see kevinveenbirkenbach/port-ui#2.0.0).
- ``test-header-navbar-logos.js``: drop the ``asset`` skip-gate (port-ui
  >= 2.0.0 embeds the URL directly instead of producing the broken
  ``/static/<absolute-url>``), replace the shape regex with an
  exact-match against the SPOT ``PLATFORM_LOGO_URL`` env var, and add
  a favicon assertion that checks the rendered ``<link rel="icon">``
  href against the SPOT ``PLATFORM_FAVICON_URL``.
- ``test-cdn-role-stylesheet.js``: keep the ``asset`` skip-gate — that
  test exercises ``web-svc-cdn`` which depends on ``web-svc-asset`` as
  its file backend, unrelated to the port-ui bug.
- ``playwright.env.j2``: rendered ``ASSET_SERVICE_ENABLED``,
  ``PLATFORM_LOGO_URL``, ``PLATFORM_FAVICON_URL``, ``COMPANY_LOGO_URL``.
- ``meta/services.yml``: drop the now-stale
  ``# nocheck: playwright-service-flag`` annotation on the ``asset``
  service block.

.claude/settings.json
---------------------

Resort ``permissions.allow`` after the runtime appended
``WebFetch(domain:cloud.veen.world)`` at the end of the array during
the asset-resolver work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:43:43 +02:00
kevinveenbirkenbachandClaude Opus 4.7 3f6e2dd68d refactor(sys-svc-compose): rename docker_git_repository_* → compose_*
Pure 1:1 rename (60 insertions / 60 deletions across 30 files) to
align the variable names with the role that owns them and to drop the
misleading ``docker_git_`` prefix — these knobs configure the
compose-stack's git clone step, not the docker CLI:

- docker_git_repository_address → compose_repository_url
- docker_git_repository_version → compose_repository_ref
- docker_git_repository_pull    → compose_pull_repository
- docker_repository_path        → compose_repository_path

``ref`` better captures git refs (branches/tags/SHAs are not
"versions"); ``url`` is the idiomatic name for the clone source.

Touches every consumer (web-app-akaunting/bigbluebutton/dashboard/
fediwall/hugo/littlejs/mini-qr/minio/openproject/openwebui/
roulette-wheel/taiga + svc-db-openldap/svc-prx-openresty/sys-svc-rdbms
+ web-svc-collabora/coturn/logout/onlyoffice + sys-front-inj-all), the
role's own defaults/tasks, the bigbluebutton compose_mods filter and
its unit test, the declared-in-services lint, and the hugo
requirements doc.

Breaking change for any external override that still sets the old
names — no alias is provided.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:43:43 +02:00
kevinveenbirkenbachandClaude Opus 4.7 d3e69f4969 style(meta): add navigator YAML doc header; wrap baserow URL
- web-app-navigator/meta/variants.yml: add ``---`` document header to
  match the convention used by other variant files.
- web-app-baserow/vars/main.yml: wrap the long ``BASEROW_DATABASE_URL``
  inside the trailing ``{{ … }}`` so the Jinja expression keeps the
  same render but the line fits within the linter's length budget.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:43:42 +02:00
kevinveenbirkenbachandClaude Opus 4.7 7779fea529 fix(dockerfiles): purge apt lists before update + lint + taiga rename
Move `rm -rf /var/lib/apt/lists/*` before `apt-get update` in every
Debian-based Dockerfile so a stale base-image-baked InRelease
(Valid-Until expired) cannot abort the build layer with exit 100. The
trailing rm at end-of-RUN never ran because `apt-get update` aborted
first; the leading rm forces a fresh fetch from the configured source.

Affected: moodle, bridgy-fed, bookwyrm (2 RUN), postmarks, taiga
backend, svc-db-openldap, svc-db-postgres, jenkins, joomla, mediawiki,
odoo, suitecrm (2 RUN), wordpress.

Add tests/lint/docker/dockerfile/test_apt_update_purge_lists.py to
enforce the contract repo-wide: every `RUN` containing `apt-get
update` MUST `rm -rf /var/lib/apt/lists/*` first in the same RUN, and
MUST NOT pass `Acquire::Check-Valid-Until=false` (which would silence
apt's replay-protection rather than refresh the list).

Rename roles/web-app-taiga/templates/Dockerfile.{back,front}.j2 to
Dockerfile.{backend,frontend}.j2 and update the matching tasks/
main.yml template refs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:43:42 +02:00
kevinveenbirkenbachandClaude Opus 4.7 bc2bc5400b fix(web-app-baserow): defensive env so variant-1 backend actually binds :8000
In CI run 26005522256, baserow Round 2 (variant 1, fresh sidecar
postgres) responded with status 500 + "connect ECONNREFUSED
127.0.0.1:8000" from baserow's internal Caddy. Container was up,
sidecar postgres + redis were Healthy, migrations succeeded (auth_user
existed, bootstrap admin worked), but gunicorn never came online on
:8000.

Cross-referenced baserow's official docs and the all-in-one image's
startup scripts:
- deploy/all-in-one/baserow.sh + supervisor/start.sh: embedded-postgres
  trigger is DATABASE_HOST==localhost AND DATABASE_URL empty. Our
  DATABASE_HOST=database avoids that, but DISABLE_EMBEDDED_PSQL/REDIS
  removes the path-of-least-resistance fallback inside supervisor.
- backend/docker/docker-entrypoint.sh: wait_for_postgres tries
  BASEROW_POSTGRES_STARTUP_CHECK_ATTEMPTS×2s before giving up (default
  5×2s = 10s, too tight for a fresh sidecar's first migration round).
- supervisor.conf: [program:backend] has startsecs=30, startretries=3.
  A long template-import pass (SYNC_TEMPLATES_ON_STARTUP=true is the
  default) after migrations can starve the gunicorn bind on :8000 and
  leave Caddy returning ECONNREFUSED.

Changes:
- vars/main.yml: introduce BASEROW_DATABASE_USER/NAME/HOST/PORT/PASSWORD
  + BASEROW_DATABASE_URL and BASEROW_REDIS_HOST/PORT/URL as single
  SPOTs derived from lookup('database', ...). env.j2 renders all of
  them from these vars instead of duplicating the lookup calls.
- templates/env.j2: emit discrete DATABASE_* AND DATABASE_URL together
  (docs note URL completely overrides discrete, but the all-in-one
  startup script also reads DATABASE_HOST directly to decide on
  embedded mode, so both must be present). Same shape for REDIS_*.
- templates/env.j2: add DISABLE_EMBEDDED_PSQL=yes, DISABLE_EMBEDDED_
  REDIS=yes, SYNC_TEMPLATES_ON_STARTUP=false, BASEROW_TRIGGER_SYNC_
  TEMPLATES_AFTER_MIGRATION=false, BASEROW_DISABLE_PLUGIN_INSTALL_ON_
  STARTUP=yes, BASEROW_POSTGRES_STARTUP_CHECK_ATTEMPTS=30.

Verified each name and semantics against
baserow/docs/installation/configuration.md and the actual source code
in baserow/deploy/all-in-one/. Notable: the docs name the variable
POSTGRES_STARTUP_CHECK_ATTEMPTS at line 108 without the BASEROW_
prefix, but the source uses BASEROW_POSTGRES_STARTUP_CHECK_ATTEMPTS —
source wins.

Caveat: EMAIL_SMTP is intentionally NOT emitted in the disabled
branch. The docs (line 335) state any non-empty value enables SMTP,
so an "EMAIL_SMTP=FALSE" defensive default would have inverted the
intent. Leaving the variable unset is the documented way to fall
back to console-only email logging.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:43:42 +02:00
kevinveenbirkenbachandClaude Opus 4.7 5bbcf166f2 chore(meta/services): silence email lint for confluence, jira, bridgy-fed
These three roles cannot wire email purely via env vars:

- web-app-confluence / web-app-jira: Atlassian images expose no
  ATL_SMTP_* env vars; SMTP must be configured post-startup via the
  admin UI or REST API.
- web-app-bridgy-fed: self-built python image without an env-driven
  SMTP path in the upstream config.

Opt out per the documented silencer (``# nocheck: email`` +
``enabled: false`` + ``shared: false``), drop the now-stale
``web-app-mailu`` from ``run_after``, and remove the matching
true/false ``email:`` entries from each ``meta/variants.yml`` so the
dynamic-flag variant coverage check no longer requires them.

bridgy-fed additionally gets a ``# nocheck: playwright-service-flag``
annotation because the role ships a ``templates/playwright.env.j2``
that would otherwise demand an ``EMAIL_SERVICE_ENABLED=`` flag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:43:42 +02:00
kevinveenbirkenbachandClaude Opus 4.7 970ec6d5c8 feat(web-app-pgadmin): wire SMTP via PGADMIN_CONFIG_MAIL_* env
pgAdmin's PGADMIN_CONFIG_* vars pass through ast.literal_eval, so
strings round-trip through ``tojson | dotenv_quote`` to land as valid
Python literals after compose's .env parser unescapes them.

Adds the matching ``email:`` service block, true/false variant
coverage and appends ``web-app-mailu`` to ``run_after``.

Drive-by: resort ``.claude/settings.json`` permissions.allow after
the runtime appended ``WebFetch(domain:atlassian.github.io)`` at the
end (``test_arrays_are_ascending`` demands alphabetical order).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:43:42 +02:00
kevinveenbirkenbachandClaude Opus 4.7 30a6f34677 fix(sys-svc-compose): skip stale ca.override.yml in wrapper file detection
Auxiliary compose calls inside a role's task list (e.g. taiga's
`compose -f compose-inits.yml run --rm manager migrate` before
flush_handlers) used to crash with
  service "<X>" has neither an image nor a build context specified:
  invalid compose project
whenever the on-disk compose.ca.override.yml still declared services
from a previous matrix-deploy variant that the freshly-rendered
compose.yml no longer ships. The handler-driven regeneration only
fires at flush_handlers — too late for that call.

Teach the compose wrapper's `detect_compose_files()` to drop a stale
override (services in override that are absent from compose.yml) from
its auto-detected file list and warn on stderr. The next handler run
regenerates the override on disk and subsequent compose calls pick
up the fresh one normally; the skipped call runs without CA injection,
which is harmless for the docker-internal-DB migrations that own
this code path.

Empirically verified against the live taiga stale state (database
service in override, not in compose.yml v2): the patched
/usr/bin/compose drops the override from the merged project, the
migrate task proceeds, and no validation crash occurs. 7 new unit
tests cover the stale-detection helper plus the wrapper's skip/keep
branches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 08:47:33 +02:00
kevinveenbirkenbachandClaude Opus 4.7 e2c5f63307 style(meta/variants): add YAML document header to yourls variant
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 08:47:33 +02:00
kevinveenbirkenbachandClaude Opus 4.7 7877162ae8 fix(web-svc-asset,dashboard): central asset URL fallback so variant-1 stops 404-ing
Round 2 of matrix-deploy disables web-app-dashboard's `asset` service
(meta/variants.yml variant 1) and purges the local file.infinito vhost
between rounds. The branding-wait task then 404'd because the URL was
still built from a missing local provider. The same pattern broke 5
test-development distros and the deploy-dashboard job in run
26005522256.

- roles/web-svc-asset/meta/services.yml: `url` now resolves locally via
  lookup('tls', 'web-svc-file', 'url.base') when web-svc-asset is in
  group_names, and falls back to `https://file.{{ SOFTWARE_DOMAIN }}/assets`
  otherwise. The fallback is the central canonical instance, which any
  host can reach without local deployment.
- roles/web-app-dashboard/meta/services.yml: pin `cdn` to static
  `enabled: true, shared: true` with `# nocheck: dynamic-flag` — the
  dashboard renders DASHBOARD_JS_BASE_URL into javascript.js
  unconditionally and the runtime JS load has no fallback path, so
  declaring cdn dynamic was dishonest.
- roles/web-app-dashboard/meta/variants.yml: drop the now-static `cdn`
  entries from both variants; keep `asset` (still dynamic, now
  exercised by both branches without 404).
- roles/web-app-dashboard/tasks/01_core.yml: collapse the three-way
  `dashboard_asset_url_base` derivation to a single lookup since the
  asset role now always returns a fully qualified https URL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 08:47:33 +02:00
kevinveenbirkenbachandClaude Opus 4.7 0700aaf6d7 fix(web-app-bookwyrm): use explicit /venv/bin paths and drop bash -l
`bash -lc` triggers a login shell that sources /etc/profile, which
resets PATH to /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
and strips the Dockerfile's `ENV PATH=/venv/bin:$PATH`. The bookwyrm
app then crashes with `ModuleNotFoundError: No module named 'environs'`
and the celery worker dies with `celery: command not found`, both
containers restart-loop, the bookwyrm vhost returns 502 and the deploy
times out on the playwright readiness probe (CI run 26005522256 / job
76437356563 reproduces the same symptom).

Switch the app command to `bash -c` (non-login) and address `python`
and `celery` via their absolute /venv/bin/ paths so the venv is used
regardless of PATH state. The worker now invokes celery directly,
skipping the bash wrapper entirely.

Empirically verified inside the running infinito stack by patching
the rendered compose.yml + compose.ca.override.yml in place and
recreating bookwyrm + bookwyrm-worker via `compose up -d --force-recreate`:
- migrations run cleanly (oauth2_provider, sessions, ...)
- gunicorn boots on 0.0.0.0:8000
- bookwyrm reports healthy via the curl healthcheck
- worker stays Up (previously stuck Restarting (127))

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 08:47:33 +02:00
kevinveenbirkenbachandClaude Opus 4.7 69f5435359 chore(web-app-friendica): pin docker image to 2026.01
The `latest` tag currently resolves to the same release, but pinning
removes the implicit drift to future stable bumps (`2026.04` ships as
release candidate as of 2026-05-13 and would otherwise become `latest`
once promoted). 2026.01 is the version that just passed CI run
26005522256 against this branch's HEAD.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 08:47:33 +02:00
kevinveenbirkenbachandClaude Opus 4.7 4c57726250 fix(web-app-friendica): repair variant-transition DB + ldapauth state
- Remove stale 0-byte ldapauth.config.php leftover when LDAP is disabled
  in a matrix variant (docker bind-mount target survives in the html
  volume after the bind mount is dropped, breaking bin/console).
- Force bin/console dbstructure update before addon sync; matrix variant
  rounds can leave the friendica html volume's local.config.php in place
  while the shared MariaDB has been re-provisioned without the friendica
  tables, so addon enable then writes to non-existent tables and the
  next playwright login times out.
- Add autoload-aware probe_hook_table.php; the previous inline PHP probe
  always returned rc=255 on Friendica\\Module\\Register::OPEN and only
  worked accidentally as a 120s sleep via retries.
- Reorganize role files into files/php/ + files/shell/ subdirs and drop
  the multi-line explanatory comments now that the why-lines live in the
  commit body.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 08:47:32 +02:00