Commit Graph
9 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5.5 dd955a71a6 style(markdown): pin list markers to dash and convert every list
The 16.0.0 entry pkgmgr wrote on top of CHANGELOG.md uses "-" bullets
while every older entry uses "*". MD004's default "consistent" style takes
the marker of the first list in a file, so lint-markdown reported 400
MD004 findings in CHANGELOG.md and the pre-push gate refused the release
push.

MD004 is now pinned to "dash", the marker pkgmgr and the changelog skill
write, and make autoformat (markdownlint-cli2 --fix) converted the lists
of the 72 files that still used "*". Only list markers changed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 23:04:02 +02:00
kevinveenbirkenbach fd6bc6e7f4 fix(lint): read the mirrored-test scope from the suffix, not the directory
A role-files-layout exemption frees a tree from files/<lang>/ because
its import paths are fixed from outside. The mirrored-test lint derived
its scope from that same directory, so every layout exemption silently
became a test exemption nobody ever argued for.

The language now comes from the file suffix. Test code is excluded by
scope rather than exempted: a test-* role and a files/playwright/ or
files/test/ tree are the tests, so demanding a test of them is circular.
The eleven files this newly reaches are framework-instantiated and carry
a stated reason each.
2026-08-22 22:32:31 +02:00
kevinveenbirkenbachandClaude Opus 5 742b87e23c fix(oidc): drop the CA fallback that never carried the CA
Both plugins ended their candidate chain with a hardcoded
/etc/ssl/certs/ca-certificates.crt. Its own docstring named the condition
that would make it useful — the OS bundle carries the Infinito CA only
once update-ca-certificates has run inside the container — and that
condition never occurs alone: with-ca-trust.sh installs the anchor and
exports CURL_CA_BUNDLE/CA_TRUST_CERT/SSL_CERT_FILE together, and
compose_ca.py sets the same three on every service it covers.

Where the CA override does not apply at all, which is the shipped default
since TLS_MODE is letsencrypt, the OS store holds no internal CA either,
so the fallback could not rescue an internal endpoint. For Joomla the
outcome is byte-identical: with no CURLOPT_CAINFO set, Debian libcurl
compiles in that very path. In swarm the inject runs --no-wrapper, so
update-ca-certificates never runs and the removed line pointed at a file
that provably lacks the CA.

WordPress now verifies against its own bundled Mozilla roots instead of
the image OS store when no variable is set; nothing in the repository
writes into that store for these two images.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 19:10:16 +02:00
kevinveenbirkenbachandClaude Opus 5 575a5a87ac refactor(layout): sort shipped scripts by language and split the unit suite
Every script a role ships now lives under `files/<language>/`, and the unit
suite is split the same way: `tests/unit/python/` beside new sibling trees for
the other languages. `tests/lint/filesystem/test_role_files_layout.py` keeps it
that way; directories whose shape is dictated from outside carry a `.nocheck`
naming the constraint (Docker build contexts, WordPress mu-plugins, the Joomla
extension, Odoo addons, Playwright suites, the `files/test/test.sh` harness
that `test-e2e-cli` resolves generically).

This cannot be applied in halves. Moving a file and repointing what names it
are the same change: 14 references resolve only at deploy time, so the tree is
green either before or after, never between.

Those 14 were found by static resolution over every reference class, not by the
test suite -- a full `make quality-high` was green while all of them were
broken:

  * 7 `src:` values built from a role variable holding a bare filename
    (svc-bkp-remote-2-local, decidim, espocrm, friendica, joomla, pixelfed x2).
    The variable doubles as the destination name inside the container, so the
    language segment belongs at the call site, not in the variable.
  * `sys-service/tasks/04_base.yml` resolved every consumer's systemd script
    through `first_found(..., errors='strict')` over `files/script.{sh,py}`.
    18 roles ship exactly that and have no template fallback: the play would
    have died on each.
  * `sys-front-inj-javascript` fell back to `<role>/files/javascript.js`; the
    interface now reads `files/javascript/javascript.js`.
  * `sys-front-inj-logout/templates/head_sub.j2` built the cache-busting mtime
    path through `path_absolute(... 'files', ...)`.
  * 4 `ansible.builtin.script:` invocations naming a bare command
    (pull.py, prune.sh, set_postgres_superuser_password.sh, ganesha-serve.sh).
  * `dev-python/files/shell/install.sh` pointed at a sibling role's script.

Depth-dependent paths inside the moved trees were bumped accordingly: 59
`parents[N]` computations, 12 relative climbs, and the per-role recover CLI,
whose path `cli/administration/recover/recoverers.py` still assembled from the
pre-move location.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 01:31:09 +02:00
kevinveenbirkenbachandClaude Opus 5 d4bbb60e08 fix(joomla): keep the local-login fallback in the session, not a Secure cookie
The plugin stored the fallback sticky bit in a cookie set with 'secure' => true
unconditionally. Over a plaintext http://<onion> origin Chromium does not treat
the origin as trustworthy and drops it, so ?fallback=local set the bit, the
browser discarded it, the next request found nothing and the guard bounced the
user back to Keycloak -- a loop with no error anywhere.

Proven from the Joomla 0 trace of run 31340965955, both cookies set by the same
origin in the same request chain:
  1227964e...=dd529f27...; path=/; HttpOnly                     <- session, no secure
  kc_fallback_local=1; ...; path=/; secure; HttpOnly; SameSite=Lax
Across the 50 request Cookie headers in that trace the session comes back 3
times and kc_fallback_local never. The session is therefore the transport that
demonstrably works on this origin.

Moving the flag there also lets the expiry be checked server-side instead of
delegated to the cookie, and an elapsed flag is cleared rather than lingering
for the rest of the session.

Third instance of the same class in this run: pixelfed's Laravel
SESSION_SECURE_COOKIE and taiga's Django SESSION_COOKIE_SECURE default to true
and fail the same way over an onion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 15:31:40 +02:00
kevinveenbirkenbachandClaude Opus 5 238b8b41fa fix(joomla): route server-side OIDC discovery through the Tor SOCKS proxy
Joomla's Keycloak plugin builds a jumbojett OpenIDConnectClient, which bottoms
out in libcurl — and libcurl refuses a .onion host at name resolution, before it
ever consults /etc/hosts, extra_hosts or DNS (RFC 7686). 20ddda8c1 measured that
verbatim: `curl --noproxy '*'` exits 6, and even
`--resolve auth.<onion>:80:127.0.0.1` exits 6. Only a configured proxy makes curl
skip the check.

So the `onion_oidc_socks.yml.j2` include the role already carries is a no-op for
this client: its extra_hosts entry can never be reached. Server-side discovery
against an onion IdP could not work, whatever the scheme — this class is
unaffected by TLS on the onion, which is why it needs its own fix.

buildOidcClient now calls setHttpProxy when JOOMLA_OIDC_SOCKS_PROXY is set, and
env.j2 sets it only when the issuer is an .onion, so a clearnet deployment is
untouched and never routes discovery through Tor. web-app-nextcloud is the
working reference for the same construction. The swarm side needs nothing: the
onion_sso overlay is declared provider-side in svc-net-tor/meta/networks.yml and
attaches to every app with sso enabled.

The six narration comments that fell out of Keycloak.php were pre-existing and
lost their grandfathering when the file was touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 21:55:45 +02:00
kevinveenbirkenbachandClaude Opus 4.8 4039313266 refactor(tasks): enforce and apply a single task-file numbering scheme
Add tests/lint/repository/test_task_file_numbering.py: numbered task
files use exactly two leading digits, a numbered *_core.yml must be
00_core.yml, per directory the numbers are unique/consecutive and start
at 01 (00 with a core file), numbered includes appear in ascending
order, and shared helpers live in tasks/utils/ instead of the numbered
chain.

Apply it across every role: rename NN_core.yml to 00_core.yml with a
consecutive down-shift of the siblings, move letter-suffix children into
a subdirectory named after their parent task, relocate helpers included
from more than one place into tasks/utils/, and update every reference
(include_tasks, path_absolute, .ansible-lint-ignore, requirement docs).
Legacy narration comments in the touched files are deleted; the upstream
xwiki.properties reference block opts out via nocheck. The unused
MAILU_IP6_PUBLIC var (only ever named in now-removed comments) is dropped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:01:31 +02:00
kevinveenbirkenbachandClaude Opus 4.8 fd8af82e95 fix(joomla): resolve the OIDC CA bundle from the compose-ca env vars
The Joomla OIDC plugin's server-side discovery/token cURL to Keycloak hardcoded the CA path to /etc/ssl/certs/ca-certificates.crt, which only carries the Infinito root CA once the CA-trust wrapper has run update-ca-certificates in the container. The wrapper is skipped on Joomla because the locally-built joomla_custom image does not exist when the CA-trust override is generated, so PHP cURL never trusts auth.infinito.example and OIDC fails with cURL error 60. Resolve the CA bundle from the CURL_CA_BUNDLE/CA_TRUST_CERT/SSL_CERT_FILE env vars (which compose-ca sets and bind-mounts reliably regardless of the wrapper), falling back to the system bundle. setVerifyHost/setVerifyPeer stay true — TLS verification is not relaxed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:17:59 +02:00
kevinveenbirkenbachandClaude Opus 4.7 084e19202d feat(req-013): native OIDC for web-app-joomla via in-role plg_system_keycloak
Promotes web-app-joomla's OIDC integration from "documented future
work" to a working native plugin shipped inside the role.

* New `plg_system_keycloak` Joomla 6 plugin under
  `roles/web-app-joomla/files/joomla-oidc-plugin/`. The plugin's PHP
  dependency `jumbojett/openid-connect-php` is vendored at deploy time
  inside the Joomla container (Composer is baked into the role's
  custom image), then packaged as a ZIP and installed via the Joomla
  CLI. The repo working tree carries no `vendor/` tree.
* Modus 3 per requirement 013 (Force-Frontend, Local-Backup-Backend):
  every visit to `/` and `/administrator` redirects to Keycloak
  unless the request explicitly carries `?fallback=local` AND the
  env-var `JOOMLA_OIDC_FALLBACK_ENABLED` is `true`. The fallback hatch
  is env-toggleable so high-security deployments can flip to Modus 1
  (no local form).
* RBAC group mapping: the plugin maps the Keycloak `groups` claim
  onto Joomla's standard usergroup IDs:
    /roles/web-app-joomla/administrator -> Super Users (id 8)
    /roles/web-app-joomla/editor        -> Editor      (id 4)
    /roles/web-app-joomla               -> Registered  (id 2)
  Users whose Keycloak groups match none of these paths are refused.
* Matrix-deploy variants flip the auth-flow gate: variant 0 is the
  canonical OIDC variant (this commit's primary path), variant 1
  flips OIDC off and LDAP on so Joomla's core LDAP authentication
  plugin against svc-db-openldap is the exercised auth path.
* Role wiring:
  - `tasks/07_oidc_plugin.yml` builds + installs the plugin idempotently.
  - `templates/env.j2` propagates JOOMLA_OIDC_* env into the container.
  - `meta/services.yml` enables `oidc` with `fallback_enabled: true`.
  - `meta/rbac.yml` declares the `editor` role.
  - `templates/playwright.env.j2` keys `OIDC_SERVICE_ENABLED` off
    `services.oidc.enabled` for the Playwright test gate.
* Playwright e2e covers three scenarios:
  - native OIDC redirect from `/` to Keycloak and back to Joomla,
  - `/administrator?fallback=local` hatch bypassing Keycloak,
  - LDAP variant against the core Joomla LDAP plugin.
* Documentation:
  - `roles/web-app-joomla/README.md` rewritten per documentation.md
    and readme_md.md (software-focused description, no em-dashes,
    canonical Credits format).
  - `roles/web-app-joomla/files/joomla-oidc-plugin/README.md` covers
    plugin build, env contract, claimed routes, and watch points.
  - `docs/requirements/013-alpha-to-beta-promotion.md` joomla
    per-role-notes updated to reference the in-role plugin and the
    Modus 3 fallback contract.
* `.github/dependabot.yml` adds the composer ecosystem entry that
  covers the plugin's `composer.json`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 17:42:35 +02:00