Commit Graph
14682 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 4.7 e729b54a3b test(lint): web-app-* logout, dashboard, oidc, oauth2 integration guards
Two new lint tests under tests/lint/ansible/roles/web-app/integration/
mirroring the existing email-integration pattern:

* test_logout_dashboard.py — every web-app-* role MUST declare
  services.logout AND services.dashboard activated (truthy via
  literal `true` or the dynamic `"{{ '<role>' in group_names }}"`
  form). Per-service opt-out via `# noqa: logout` /
  `# noqa: dashboard` directly above the key, paired with both
  flags `false`. Provider role web-app-dashboard self-exempt.
* test_sso.py — every web-app-* role MUST front its login flow
  with services.oidc OR services.oauth2 activated. If oidc is
  intentionally opted out (`# noqa: oidc` above the key + both
  flags false), then oauth2 must take over. Roles with no login
  flow at all opt out of BOTH (e.g. static-content sites). Provider
  roles web-app-keycloak / web-app-oauth2-proxy self-exempt.

Co-located reorganisation: the existing email lint moves with them
into the new web-app/integration/ subfolder, dropping the redundant
`web_app_` prefix and `_integration` suffix per the new naming
scheme. test_email.py renamed via git mv so blame is preserved.

Suppression catalog (docs/contributing/actions/testing/suppression.md)
gains four new rule keys (`logout`, `dashboard`, `oidc`, `oauth2`),
all with the "line above" placement semantic identical to `email`.

services.yml changes to satisfy the new guards:

* logout opt-out (`# noqa: logout` above + both flags false) added
  to: web-app-keycloak (the IdP itself), and the minimal/no-login
  apps web-app-bridgy-fed, -chess, -hugo, -littlejs, -mig, -mini-qr,
  -navigator, -postmarks, -roulette-wheel, -socialhome, -sphinx.
* dashboard opt-out added to: web-app-bigbluebutton, -espocrm,
  -matomo, -oauth2-proxy, -phpmyadmin (with explicit shared: false
  added where missing), and full block created for -fusiondirectory.
* SSO opt-out (oidc + oauth2 both `# noqa`'d, both flags false)
  added to roles without a login flow (the same 11 minimal apps)
  and to roles where Keycloak SSO isn't wired up: -confluence, -jira,
  -magento, -matomo, -shopware. matomo's existing TODO comment about
  enabling oauth2 is preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:25:58 +02:00
github-actions[bot] 5f45011b99 update(images): update Docker image versions 2026-05-06 21:05:24 +00:00
kevinveenbirkenbach 14f836336d Merge pull request #238 from infinito-nexus/update/docker-image-versions-20260506
update(images): update Docker image versions
2026-05-06 22:59:37 +02:00
kevinveenbirkenbachandClaude Opus 4.7 e5de4e085a refactor(meta): wire services.<key>.{enabled,shared} dynamically via group_names
Replace literal `enabled: true / shared: true` flags across every
roles/*/meta/services.yml with a Jinja `"{{ '<role>' in group_names }}"`
form so the per-role services map becomes the single source of truth
for shared-service co-deployment, on a per-host basis. Inventory build
already runs through the templar before reaching the static `is True`
check, so runtime behaviour is unchanged; the new form only opens up
static-analysis and CLI tooling to the group-aware semantics.

Mechanism + lint:

- New integration test tests/integration/roles/meta/test_services_
  dynamic_flags.py enforces the dynamic shape per top-level service
  entry. Registered service keys must reference the registry-resolved
  provider role; covered keys (e.g. oauth2 -> web-app-keycloak via
  the new `covers:` block) reference the covering role.
- Two suppression placements documented in docs/contributing/actions/
  testing/suppression.md: per-flag inline `# nocheck: dynamic-flag`
  for databases (`enabled: true` stays literal, `shared` resolves
  dynamically) and block-level above the key for entries whose both
  flags legitimately stay literal (`css`, `javascript`, role-local
  `redis`, `recaptcha`, `hcaptcha`, role-local containers with
  `shared: false`).

Registry + helpers:

- utils/service_registry.py: new `is_explicit_truth(value)` helper
  accepts `True` literal OR a Jinja string carrying `in group_names`.
  Adopted by `resolve_service_dependency_roles_from_config`,
  `ServicesResolver._is_enabled_shared`, `test_web_app_email_
  integration`, `test_prometheus`, `test_universal_logout`, and the
  run_after-explicit guard so static analysis keeps resolving deps.
- New `covers:` field on a primary service entity declares
  transitive equivalents (web-app-keycloak.covers: [oauth2]).
  Exposed via `build_role_to_covered_keys` and
  `build_covered_key_to_role`, replacing the previously hard-coded
  `_TRANSITIVE_SERVICE_EQUIVALENTS` / `_SPECIAL_REFERENCE_ROLE` maps
  in the run_after and dynamic-flag tests.

Bulk migration:

- All 80 roles/*/meta/services.yml files migrated to the dynamic
  form. DB consumer keys (mariadb, postgres, ldap) keep
  `enabled: true  # nocheck: dynamic-flag` literal with a dynamic
  `shared`. Block-nocheck applied to css/javascript/recaptcha/
  hcaptcha and role-local containers (e.g. mailu's admin/imap/smtp/
  antispam/antivirus/oletools/webdav/fetchmail/webmail/resolver/
  front, nextcloud's talk).
- web-app-prometheus's primary entity now declares
  `provides: prometheus` + dynamic flags so the registry recognises
  it as a service provider. Consequence: tasks/01_core.yml split out
  with `- include_tasks: utils/once/flag.yml` as the first task and
  tasks/main.yml reduced to the standard run_once-guarded loader.
- Seven minimal no-login apps (web-app-hugo, -bridgy-fed, -chess,
  -littlejs, -navigator, -mini-qr, -mig) gained an explicit
  `logout: { enabled: false, shared: false }` opt-out (with
  `# nocheck: dynamic-flag` block-marker) to satisfy the universal-
  logout schema check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:52:55 +02:00
kevinveenbirkenbachandClaude Opus 4.7 b73f92d551 fix(meta): make run_after deps explicit via shared services
Resolve every violation flagged by tests/integration/roles/dependencies/
run_after/test_services_explicit.py: each run_after entry must be
backed by an enabled+shared services flag in the dependent role.

- Activate oauth2 (enabled+shared=true) where the role consumes the
  Keycloak-fronted oauth2-proxy: friendica, funkwhale, fusiondirectory,
  gitea (allocate oauth2 ports 16501 friendica, 16502 funkwhale).
- Flip oauth2.shared / oidc.shared / ldap.shared to true where the
  service is actually consumed: akaunting, baserow, bluesky, bookwyrm,
  flowise, jenkins, joomla, web-svc-xmpp.
- Drop run_after entries to providers whose service is not enabled:
  web-app-keycloak from confluence, jira, magento, matrix, shopware,
  web-svc-xmpp; web-app-matomo from fusiondirectory, matrix;
  web-app-opencloud from opentalk; desk-gnome from
  desk-gnome-caffeine/-extensions/-terminal.
- Re-flow a continuation in test_services_explicit.py.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 21:17:05 +02:00
kevinveenbirkenbachandClaude Opus 4.7 04c5eee5a9 test(integration): guard run_after entries with explicit services declarations
Future direction: the inventory builder is moving away from
``run_after`` as a dependency-injection mechanism. ``run_after``
keeps its ordering-only role; "what gets co-deployed with this
role" is decided by the per-role ``services`` map. This test
catches roles whose ``run_after`` smuggles a dep in that no
``services.<key>`` entry covers.

Per ``run_after: [Y]`` the consumer MUST also declare a
``services.<key>`` entry where ``<key>`` is either Y's primary
service-registry key or one of the explicitly listed transitive
equivalents (e.g. ``services.oauth2`` covers
``run_after: [web-app-keycloak]`` since oauth2-proxy itself
depends on Keycloak), with both ``enabled`` and ``shared`` set to
``true`` or to a Jinja conditional containing
``in group_names``.

Currently fails for ~22 roles whose run_after entries pull in
deps without an explicit services flag. Fixing each is a
mechanical migration tracked separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 20:51:24 +02:00
kevinveenbirkenbachandClaude Opus 4.7 cb4cc393d0 test(lint): forbid hard-coded NETWORK_PUBLIC_DNS_RESOLVERS IPs
Roles, playbooks, templates and scripts MUST consume the central
`NETWORK_PUBLIC_DNS_RESOLVERS` list from `group_vars/all/08_networks.yml`
instead of pasting `1.1.1.1` / `8.8.8.8` / `1.0.0.1` literally, so a
single edit propagates everywhere those resolvers are used.

- tests/lint/repository/test_no_hardcoded_dns_resolvers.py: scans the
  repo for the literal IPs declared in NETWORK_PUBLIC_DNS_RESOLVERS,
  fails on any occurrence not covered by `# noqa: hardcoded-dns-resolver`.
- roles/sys-svc-container/vars/main.yml: `SYS_SVC_DOCKER_AUTOMTU_TARGETS`
  no longer hard-codes `1.1.1.1`; the list is now built from
  `['registry-1.docker.io', 'github.com'] + NETWORK_PUBLIC_DNS_RESOLVERS`.
- roles/user/templates/bashrc.j2: `ip route get 8.8.8.8` ->
  `ip route get {{ NETWORK_PUBLIC_DNS_RESOLVERS[0] }}`.
- group_vars/all/08_networks.yml: drop literal IP examples from the
  resolver-block doc comment.
- compose/coredns/Corefile.tmpl, scripts/system/network/dns/setup/wsl.sh,
  roles/sys-ctl-mtn-cert-deploy/SETUP.md: tag the legitimate literal
  occurrences with `# noqa: hardcoded-dns-resolver`. These are
  substitution points that cannot consume the variable (CoreDNS
  forward directive, host-bootstrap shell fallback, Markdown example).
- docs/contributing/actions/testing/suppression.md: catalog the new
  `hardcoded-dns-resolver` rule with position semantics and example.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 20:27:38 +02:00
kevinveenbirkenbachandClaude Opus 4.7 bec81768fd fix(lint): moodle resource limits + replace rglob with iter_project_files
Two failures from make test on feature/web-app-fediwall:

- test_compose_resource_limits flagged web-app-moodle nginx and cron
  services missing cpus / mem_reservation / mem_limit / pids_limit /
  min_storage. Added conservative defaults (1GB / 0.2 cpu / 128m / 256m /
  256 pids) matching other lightweight sidecar services.

- test_no_raw_project_walk flagged
  tests/lint/ansible/test_no_lookup_config_jinja_default.py:40 using
  base.rglob("*"). Route through utils.cache.files.iter_project_files
  (filters by SCAN_DIRS prefix) to share the cached single-pass walk.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 20:08:50 +02:00
kevinveenbirkenbachandClaude Opus 4.7 1e94b14dd4 refactor(meta): split database PORT_BAND into mariadb + postgres + disjoint lint
The single `local.database` PORT_BAND covered both 3306 (MariaDB)
and 5432 (PostgreSQL), which let any port in 3306-5432 pass band
membership without distinguishing the engine. Replace with two
explicit single-port bands so the (scope, category) tuple
identifies the engine unambiguously:

- group_vars/all/08_networks.yml: `local.database` ->
  `local.mariadb` (3306-3306) and `local.postgres` (5432-5432).
- roles/svc-db-mariadb/meta/services.yml: ports.local.database
  -> ports.local.mariadb.
- roles/svc-db-postgres/meta/services.yml: ports.local.database
  -> ports.local.postgres.
- roles/svc-db-{mariadb,postgres}/vars/main.yml: lookups follow
  the new path.

Also add `TestPortBandsDisjoint.test_no_overlapping_bands_in_same_scope`
in tests/lint/repository/test_role_meta_layout.py: rejects any
two PORT_BANDS entries within the same scope whose ranges
intersect, with a precise overlap range in the failure message.
Cross-scope overlaps remain intentional (different bind contexts).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:56:36 +02:00
kevinveenbirkenbachandClaude Opus 4.7 d97b16e9f1 test(lint): add CLI suggest hint to port and subnet collision failures
When `test_role_meta_layout.TestHostBoundPortCollisions`,
`TestPortBandMembership`, or
`test_unique_valid_and_mapped.TestNetworksUniqueValidAndMapped`
fails, surface the canonical resolver command in the failure
message:

- ports: `infinito meta ports suggest --scope <local|public> --category <http|oauth2|...> --count 1`
- subnets: `infinito meta networks suggest --clients <N> --count 1`

Plus a hint that an exhausted `PORT_BANDS.<scope>.<category>` band
can be widened in `group_vars/all/08_networks.yml`. Reduces the
diagnose-to-fix loop for contributors hitting these lints.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:38:21 +02:00
github-actions[bot] e9cfbd24c0 update(images): update Docker image versions 2026-05-06 17:37:43 +00:00
kevinveenbirkenbach c9c8535034 Merge branch 'main' into feature/web-app-kix 2026-05-06 19:27:05 +02:00
kevinveenbirkenbach d05f35f167 Merge branch 'main' into feature/web-app-fediwall 2026-05-06 19:16:51 +02:00
kevinveenbirkenbachandClaude Opus 4.7 2a9c5d4ec2 fix(meta): resolve subnet collisions on 192.168.105.{48,64}/28
Integration test test_no_overlapping_subnets / test_unique_subnets flagged:
- web-app-opencloud vs web-app-postmarks both at 192.168.105.48/28
- web-app-opentalk  vs web-svc-xmpp     both at 192.168.105.64/28

Move the newer roles to free /28 slots in the same /22 band:
- postmarks 192.168.105.48/28  → 192.168.105.96/28
- xmpp      192.168.105.64/28  → 192.168.105.112/28

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:16:00 +02:00
kevinveenbirkenbach 4c50d5a6b5 Merge pull request #237 from infinito-nexus/update/docker-image-versions-20260506
update(images): update Docker image versions
2026-05-06 19:11:38 +02:00
kevinveenbirkenbach 1193bbd057 Merge pull request #233 from kevinveenbirkenbach/feature/moodle-self-built
feat(req-015): self-built Moodle image with OIDC + LDAP variants
2026-05-06 19:10:52 +02:00
kevinveenbirkenbach 152a9b8d8e Merge branch 'main' into feature/moodle-self-built 2026-05-06 19:10:28 +02:00
github-actions[bot] fc281bded8 update(images): update Docker image versions 2026-05-06 17:10:19 +00:00
kevinveenbirkenbach d42707948b Merge branch 'main' into feature/web-app-fediwall 2026-05-06 19:08:39 +02:00
kevinveenbirkenbachandClaude Opus 4.7 bf5488ba95 fix(meta): resolve subnet collisions on 192.168.105.{48,64}/28
Integration test test_no_overlapping_subnets / test_unique_subnets flagged:
- web-app-opencloud vs web-app-postmarks both at 192.168.105.48/28
- web-app-opentalk  vs web-svc-xmpp     both at 192.168.105.64/28

Move the newer roles to free /28 slots in the same /22 band:
- postmarks 192.168.105.48/28  → 192.168.105.96/28
- xmpp      192.168.105.64/28  → 192.168.105.112/28

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:07:19 +02:00
kevinveenbirkenbachandClaude Opus 4.7 370b998635 feat(web-app-kix): KIX Service Management integration (req-015)
Deploys KIX Start as a first-class web-app-* role behind
sys-stk-front-proxy + web-app-oauth2-proxy + Keycloak realm-level
2FA at kix.<DOMAIN_PRIMARY>. The 4-container upstream stack
(proxy, backend, frontend, db) reuses the shared svc-db-redis for
OAuth2-proxy session storage and KIX cache.

Auth chain:
- OAuth2-proxy `allowed_groups` admits members of either
  `/roles/web-app-kix/administrator` or `/roles/web-app-kix/user`
  (the user role is declared via `meta/rbac.yml`).
- A custom kix-proxy nginx default.conf is bind-mounted (the
  upstream image ships an empty one) and forwards everything
  through the frontend Node server, plus copies the OAuth2-proxy
  X-Forwarded-User into a Remote-User upstream header.
- KIX `Auth::LDAP` + `Auth::Sync::LDAP` against svc-db-openldap
  with one backend per role group (admin GroupDN and user GroupDN).
  KIX UsageContext must be a single-string value; comma-list
  silently disables the backend.

Validated end-to-end via deploy-fresh-purged-apps APPS=web-app-kix
FULL_CYCLE=true (Pass 1 + Pass 2 both failed=0). Playwright 3/3
in both passes:
- TLS+HSTS at the canonical URL.
- administrator full flow: dashboard -> KIX card -> OAuth2-proxy
  -> Keycloak -> KIX-LDAP login form -> KIX UI -> universal logout
  -> re-anonymous redirect back to the OAuth2-proxy gate.
- biber full flow: same, with a Keycloak admin REST API fixture
  that puts biber into /roles/web-app-kix/user (mirrors what an
  operator does manually via the Keycloak UI).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:03:20 +02:00
kevinveenbirkenbach 3eb178fcc0 Merge Rebase 2026-05-06 18:58:53 +02:00
kevinveenbirkenbachandClaude Opus 4.7 a59b4cce61 fix(meta): resolve host-bound port collisions on 8071/8072
req-009 lint flagged two collisions on main:
- port 8071: opencloud vs postmarks → postmarks 8071→8075
- port 8072: opentalk/controller vs bluesky/broker → bluesky/broker 8072→8076

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 18:52:01 +02:00
kevinveenbirkenbachandClaude Opus 4.7 2a78c6f0a4 fix(meta): resolve host-bound port collisions on 8071/8072
req-009 lint flagged two collisions on main:
- port 8071: opencloud vs postmarks → postmarks 8071→8075
- port 8072: opentalk/controller vs bluesky/broker → bluesky/broker 8072→8076

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 18:47:14 +02:00
kevinveenbirkenbach d239b2ab19 Merge branch 'main' into feature/web-app-fediwall 2026-05-06 18:33:32 +02:00
kevinveenbirkenbachandClaude Opus 4.7 fa569612b1 fix(opentalk): add missing min_storage to livekit/minio/rabbit/recorder
The compose-resource-limits lint flagged 4 missing min_storage keys for
self-declared services. Set:
- livekit:  1GB  (media server, mostly memory-bound)
- minio:   20GB  (object storage for recordings, matches web-app-minio)
- rabbit:   2GB  (queue persistence)
- recorder: 2GB  (local recording cache)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 17:04:09 +02:00
kevinveenbirkenbach cca665e0d7 Merge branch 'main' into feature/web-app-fediwall 2026-05-06 16:57:00 +02:00
kevinveenbirkenbach 877b222028 Merge pull request #236 from infinito-nexus/update/docker-image-versions-20260506
update(images): update Docker image versions
2026-05-06 16:57:00 +02:00
github-actions[bot] 051e0b05d1 update(images): update Docker image versions 2026-05-06 16:57:00 +02:00
kevinveenbirkenbach 915d324570 Merge pull request #234 from kevinveenbirkenbach/feature/opencloud-opentalk-iam
feat(req-015): web-app-opencloud + web-app-opentalk with shared IAM
2026-05-06 16:56:59 +02:00
kevinveenbirkenbachandClaude Opus 4.7 5877355f2a fix(discourse): set DISCOURSE_FORCE_HTTPS env so assets compile with https URLs
The existing `rails r SiteSetting.force_https = true` exec runs in the
`run:` block, AFTER Discourse precompiles its assets. As a result the
generated stylesheets contain `@font-face { src: url(http://forum.../...) }`
references, which the CSP `font-src 'self' https://forum.../` blocks at
runtime, failing the Playwright `discourse enforces Content-Security-Policy`
test (CI runs 25387069690 / 25402722355).

Setting `DISCOURSE_FORCE_HTTPS` in the env section lets Discourse pick up
the value before asset precompilation, so font and asset URLs are baked
as https from the start.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 16:56:59 +02:00
kevinveenbirkenbachandClaude Opus 4.7 7460d81c9a fix(opencloud-spec): drop manual context wrapper in biber SSO test
Manual `browser.newContext()` + `try/finally context.close()` caused the
test to hang on close while the OpenCloud SPA kept the page alive with
periodic OIDC token-refresh and MutationObserver activity, exhausting
the 5-minute test timeout (CI run 25387069690). Switch to the default
`page` fixture, matching the administrator test; Playwright already
provides per-test context isolation and handles teardown.

Verified with full make deploy-fresh-purged-apps APPS=web-app-opencloud
FULL_CYCLE=true SERVICES_DISABLED=matomo,email — both passes green:
admin ~2.5s, biber ~1.5s.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 16:56:59 +02:00
kevinveenbirkenbach 087d1b71d0 Merge branch 'main' into feature/opencloud-opentalk-iam 2026-05-06 16:56:59 +02:00
kevinveenbirkenbach 73302cb992 Merge branch 'main' into feature/opencloud-opentalk-iam 2026-05-06 16:56:59 +02:00
kevinveenbirkenbach 54b608be5e Merge branch 'main' into feature/opencloud-opentalk-iam 2026-05-06 16:56:59 +02:00
kevinveenbirkenbachandClaude Opus 4.7 b3e582d70a test(lint): forbid literal http(s):// glued in front of a lookup-derived domain
Adds tests/lint/repository/test_literal_protocol_with_lookup.py which
fails when a file hand-writes the protocol next to a lookup-derived
domain. The protocol must come from the same source as the domain so
TLS-on / TLS-off / self-signed / public-CA stays consistent across the
stack; the `tls` lookup plugin emits both at once via the
`url.base` / `protocols.web` / `protocols.websocket` keys.

Mechanics
- Three regex patterns cover the realistic shapes:
  1. `https?://{{ ... lookup( ... }}`            (Jinja interpolation)
  2. `"https?://" ~ lookup(...)`                 (Jinja concat with lookup)
  3. `"https?://" ~ ... canonical.<key>`         (concat with canonical access)
- Internal docker-network upstreams that are genuinely plaintext
  (`http://<container>:<port>`) are not detected because the regex
  requires a `lookup(` or `canonical.` token; they would only need
  the noqa marker if the upstream resolution itself uses one of those.
- Per-line opt-out: `# noqa: literal-protocol-lookup` (or `nocheck`).
- File reads route through `utils.cache.files.read_text` and the
  per-file scan result is cached process-wide via `lru_cache`.

Repo cleanup
- web-app-discourse and web-app-littlejs CSP entries now use
  `lookup('tls', '<role>', 'url.base')` instead of pinning `http://` /
  `https://` next to `lookup('domain', ...)`.
- web-app-matrix and web-svc-cdn Playwright env templates compose the
  base URL through the `tls` lookup so the test runner uses the same
  protocol the live stack serves.
- web-app-oauth2-proxy carries a single `# noqa: literal-protocol-lookup`
  on the upstream line; that one is intentionally plaintext because
  oauth2-proxy talks to its sibling upstream over the internal docker
  network with no TLS.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 16:56:59 +02:00
kevinveenbirkenbachandClaude Opus 4.7 2c94c0095b test(lint): enforce single-source-of-truth for domain literals
Adds tests/lint/repository/test_domain_primary_spot.py which fails when a
file constructs a hostname via a literal `<sub>.{{ DOMAIN_PRIMARY }}` outside
the role's `meta/server.yml` (or `meta/variants.yml`) `domains.canonical` /
`domains.aliases` blocks. Every other reference MUST flow through the
project's lookup plugins so the canonical/aliases entries stay the SPOT.

Mechanics
- Regex matches `<word>.{{ DOMAIN_PRIMARY }}` patterns; wildcard fragments
  (`.{{ DOMAIN_PRIMARY }}`, `*.{{ DOMAIN_PRIMARY }}`) and bare
  `{{ DOMAIN_PRIMARY }}` references are intentionally NOT flagged
- meta/server.yml and meta/variants.yml: a state-machine line scanner
  exempts list items and dict entries inside `canonical:` / `aliases:`
  blocks (handles compact, expanded, list, and dict YAML styles)
- README.md is exempt; docs/ and files/ subtrees inside roles are skipped
- Per-line opt-out via `# noqa: domain-spot` (or `# nocheck: domain-spot`)
- File reads route through utils.cache.files.read_text and the helpers
  themselves are wrapped in lru_cache so repeated path lookups during
  the traversal pay the parse cost once

Repo cleanup
- 11 role meta/server.yml files now reference Keycloak, Collabora,
  OnlyOffice, Nextcloud, and their own canonical hostnames through
  `lookup('domain', '<role>')` (and `lookup('config', '<role>',
  'server.domains.canonical.<key>')` for the dict-canonical roles
  Matrix and Bluesky) instead of duplicating the SPOT
- `roles/web-app-mattermost/tasks/main.yml` keeps a single
  `# noqa: domain-spot` on the legacy domain cleanup task; the
  hardcoded historical hostname must persist there to remove stale
  on-disk nginx configs from the rename

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 16:56:59 +02:00
kevinveenbirkenbachandClaude Opus 4.7 2c3c82f60b fix(sys-svc-container): pin Ubuntu's docker-compose-v2 out of selection
Both Ubuntu's universe package docker-compose-v2 and Docker upstream's
docker-compose-plugin ship /usr/libexec/docker/cli-plugins/docker-compose
without a Conflicts: marker between them. Any apt transaction on Ubuntu
26.04 (resolute) that selects both — for example bootstrap_docker_repo
followed by the local infinito-nexus deb pulling ansible — aborts at the
dpkg unpack phase.

The Ubuntu/Debian branch of install-cli.sh now drops a priority -1
preference for docker-compose-v2 (Ubuntu only) and purges any
pre-installed copy before the docker-compose-plugin install. apt then
picks the upstream package unambiguously and the conflict cannot
re-emerge through transitive deps later.

Verified via `make act-workflow` against the ubuntu:latest matrix slot
of test-environment.yml (1h28m, exit 0).

Tracked upstream: https://bugs.launchpad.net/ubuntu/+source/docker-compose-v2/+bug/2151249

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 16:56:58 +02:00
kevinveenbirkenbachandClaude Opus 4.7 2a582d3aec feat(ci): diff-driven app whitelist for deploy tests
Narrow the test-deploy-{server,universal,workstation} matrix to roles
actually impacted by the branch's diff against origin/main, expanded
transitively over run_after + dependencies + shared services.

Why: full-matrix deploy CI on every PR is wasteful; explicit per-PR
whitelisting is error-prone. The resolver derives the affected set
automatically while remaining fail-safe.

Behaviour (in resolve_effective_whitelist.sh precedence order):
- whitelist input "__ALL__"  -> empty whitelist (force full deploy)
- whitelist input non-empty  -> verbatim
- whitelist input empty      -> diff vs origin/main
  - empty diff or any path outside roles/<role>/   -> __ALL__
  - all paths under roles/<role>/                  -> reverse closure

Fail-safe: any seed that is not modellable in CombinedResolver
(no application_id and not referenced by any role's run_after) and
any resolver runtime error MUST trigger a fall-back to __ALL__.
The closure never silently shrinks the deploy matrix.

test-deploy-local.yml is intentionally exempt (local/manual dispatch
keeps explicit whitelist semantics).

Documented in pipeline.md and workflows.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 16:56:58 +02:00
kevinveenbirkenbachandClaude Opus 4.7 1d27a34883 fix(iam): make OpenCloud and OpenTalk Playwright OIDC scenarios pass and add OpenTalk recorder
Iterates the role-local Playwright specs from a structural skeleton into
fully green end-to-end scenarios that exercise the SSO handoff for both
the administrator and the biber LDAP persona, against the actual
deployed stack, and adds optional meeting recording for OpenTalk.

OpenCloud (web-app-opencloud)
- Add Keycloak's URL to connect-src and frame-src in the role CSP via
  lookup('tls', 'web-app-keycloak', 'url.base') so the SPA can hit the
  OIDC discovery and authorize endpoints from the browser
- Switch to read-only LDAP semantics: PROXY_AUTOPROVISION_ACCOUNTS=false,
  GRAPH_USERNAME_MATCH removed, GRAPH_ASSIGN_DEFAULT_USER_ROLE=true
- Set OC_LDAP_DISABLE_USER_MECHANISM=none so the user-lookup filter
  stops referencing the OpenCloud-only `openclouduserenabled` attribute
  that the central LDAP schema does not define
- Pin the image to opencloudeu/opencloud:4.0.6
- Spec primes localStorage forceAllowOldBrowser, bypasses the "browser
  not supported" splash, submits via Enter, asserts the Files-view banner

OpenTalk (web-app-opentalk)
- Provision a dedicated confidential Keycloak client `opentalk-controller`
  via kcadm so the controller can introspect access tokens; the shared
  realm client stays public for the browser PKCE flow. The service
  account is granted the realm-management view-users / query-users roles
  for the Keycloak admin Web API user-search backend
- Inject an nginx vhost extension via webserver_extra_configuration that
  routes /v1/, /signaling, and /livekit on open.talk.<DOMAIN> to the
  controller and LiveKit upstreams; the default route stays on the
  frontend
- Fix the frontend env vars to upstream names (OIDC_ISSUER, BASE_URL=""),
  pin the frontend image to v2.8.1
- Spec races between auto-redirect and explicit "Sign in" CTA, submits
  via Enter, asserts the post-login Home-link in the dashboard
- Add OpenTalk recorder (registry.opencode.de/opentalk/recorder v0.16.2)
  plus RabbitMQ broker (rabbitmq:4.2.4); wire the controller to publish
  recording jobs on the `opentalk_recorder` queue and the recorder to
  consume them; provision a dedicated confidential `opentalk-recorder`
  Keycloak client for the recorder service account
- Reference Keycloak and the cross-app domain through lookup('tls', ...)
  and lookup('domain', ...) in meta/server.yml instead of literal
  `auth.<DOMAIN>` / `open.cloud.<DOMAIN>` strings

Cross-cutting
- Rename canonical hostnames to the <vendor>.<category> pattern:
  - web-app-opentalk:    talk.<DOMAIN>  -> open.talk.<DOMAIN>
  - web-app-bigbluebutton: meet.<DOMAIN> -> bbb.talk.<DOMAIN>
- Restructure both READMEs to the per-role README contract; drop emojis
  from headings; strip em dashes / history phrases / self-SPOT
  declarations from IAM.md / LDAP.md / TODO.md
- Replace technical galaxy_info descriptions with user-facing copy so
  the dashboard cards read clearly

Validated end-to-end via the Role Loop (deploy-fresh-kept-apps then
deploy-reuse-kept-apps with SERVICES_DISABLED=matomo,email): all
seven OpenTalk containers + the OpenCloud container come up healthy,
scripts/tests/e2e/rerun-spec.sh web-app-opencloud and
scripts/tests/e2e/rerun-spec.sh web-app-opentalk both finish with
`2 passed`, and `make test` is green (1604 + 162 tests).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 16:56:58 +02:00
kevinveenbirkenbachandClaude Opus 4.7 8b30e66589 feat(iam): add web-app-opencloud and web-app-opentalk with shared OIDC + LDAP
Implements requirement 015. Both new roles join the central Keycloak
realm and consume the central OpenLDAP through the same patterns as
web-app-nextcloud, so users can move between Files (OpenCloud) and
Video (OpenTalk) without separate accounts.

OpenCloud (web-app-opencloud)
- Single-binary deploy of opencloudeu/opencloud:4.0.6 at open.cloud.<DOMAIN>
- External Keycloak as IdP (OC_EXCLUDE_RUN_SERVICES=idp,
  PROXY_AUTOPROVISION_ACCOUNTS=true, PROXY_ROLE_ASSIGNMENT_DRIVER=oidc)
- Direct OpenLDAP user/group source via OC_LDAP_*
- Co-deployable with web-app-nextcloud (next.cloud.<DOMAIN> stays put)
- WEB_OPTION_OPENTALK_URL surfaces the OpenTalk meeting launcher into
  the Files UI when web-app-opentalk is in scope

OpenTalk (web-app-opentalk)
- Controller + LiveKit + web-frontend + dedicated Redis + dedicated MinIO
  (controller refuses to start without an S3 backend) at talk.<DOMAIN>
- Uses shared svc-db-postgres for relational data and shared
  web-svc-coturn for STUN/TURN (no dedicated TURN provisioning)
- Native Keycloak integration via controller.toml [oidc.*] +
  [user_search] backend = "keycloak_webapi"; LDAP is consumed
  transitively through Keycloak's federation
- Frontend env exposes SHARED_FOLDER_OPENCLOUD_URL when web-app-opencloud
  is in scope, wiring the in-meeting OpenCloud file picker
- Bucket bootstrap is idempotent: an Ansible task waits for MinIO and
  creates the configured bucket via mc

Cross-cutting
- Extends PORT_BANDS public.relay to 65535 so LiveKit's media UDP range
  (60000-60999) can coexist with web-svc-coturn (20000-39999),
  web-app-bigbluebutton (40000-49999), and web-app-nextcloud's onboard
  TURN (50000-59999)
- All credentials (OIDC client secrets, LDAP bind, TURN auth_secret,
  MinIO root, LiveKit API, OpenCloud JWT/transfer) are sourced via
  lookup('config', ..., 'credentials.*'); none are committed in plain text
- Each role ships IAM/LDAP docs mirroring web-app-nextcloud and
  role-local Playwright specs that cover the administrator AND biber
  personas through OIDC

Validated end-to-end via the Role Loop (deploy-fresh-purged-apps then
deploy-reuse-kept-apps with SERVICES_DISABLED=matomo,email): all five
OpenTalk containers + the OpenCloud container come up cleanly, the
controller successfully completes OIDC discovery against Keycloak, and
both canonical hostnames return HTTP 200 through the central reverse
proxy. Browser-validated assertions (admin role mapping, full SSO logout
propagation, OpenCloud<->OpenTalk cross-launch from a real LDAP user)
remain as TODOs in each role's TODO.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 16:56:58 +02:00
kevinveenbirkenbachandClaude Opus 4.7 22af634834 docs(requirements): refine 015 with concrete tech choices
- Pin OpenCloud to opencloudeu/opencloud image (Heinlein fork)
- Note OpenCloud uses embedded KV store, no external SQL backend
- Pick LiveKit as OpenTalk signaling backend (upstream default)
- Reuse svc-db-postgres for OpenTalk and web-svc-coturn for STUN/TURN
- Set canonical hostnames: open.cloud.* and talk.*; keep next.cloud.* for Nextcloud (parallel operation)
- Reference svc-db-openldap explicitly and use application_administrators LDAP group per req 004
- Require both roles enabled by default (not in SERVICES_DISABLED)
- Add TURN shared secret to no-plaintext list

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 16:56:58 +02:00
kevinveenbirkenbachandClaude Opus 4.7 3214875eb6 docs(requirements): add 015 OpenCloud + OpenTalk IAM integration
Specifies new web-app-opencloud and web-app-opentalk roles that
integrate with the central Keycloak (OIDC) and OpenLDAP services
analogously to web-app-nextcloud, including SSO, LDAP-driven
auto-provisioning, group-based admin mapping, OpenCloud<->OpenTalk
file/meeting interop, propagated logout, and Playwright coverage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 16:56:58 +02:00
kevinveenbirkenbachandClaude Opus 4.7 0eebe3de7e test(yaml): route fixture writes through utils.cache.yaml.dump_yaml
The lint test test_no_direct_yaml_calls forbids direct yaml.safe_dump
calls — fixtures must go through utils.cache.yaml so the cache stays
consistent with on-disk state. Replace the three yaml.safe_dump call
sites in tests/unit/cli/meta/applications/resolution/affected/test_main.py
with dump_yaml(path, data) and drop the now-unused yaml import.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 16:56:21 +02:00
kevinveenbirkenbach f06add3298 Merge pull request #226 from kevinveenbirkenbach/feature/alpha-to-beta
feat(req-013): alpha-to-beta promotion for 13 in-scope roles
2026-05-06 16:38:05 +02:00
kevinveenbirkenbach f4d33063ae Merge branch 'main' into feature/alpha-to-beta 2026-05-06 16:37:34 +02:00
kevinveenbirkenbach fc826bebc7 Merge pull request #232 from kevinveenbirkenbach/feature/web-app-hugo
feat(web-app-hugo): add static-site role per req-016
2026-05-06 16:32:14 +02:00
kevinveenbirkenbach 8267269b5c Merge branch 'main' into feature/moodle-self-built 2026-05-06 16:09:25 +02:00
kevinveenbirkenbach abc07bdada Merge pull request #236 from infinito-nexus/update/docker-image-versions-20260506
update(images): update Docker image versions
2026-05-06 16:06:07 +02:00
kevinveenbirkenbachandClaude Opus 4.7 88bd00bead fix(web-app-moodle): drop msmtp from FPM healthcheck
The msmtp send was gated into the same `&&` chain as the FPM TCP probe,
so a slow / unreachable mail relay (mailu warm-up, cross-network blackhole,
transient TLS) marked the container `unhealthy` after retries=5×interval=1m,
which cascaded to nginx/cron via `condition: service_healthy` and aborted
`compose up` with "dependency failed: container moodle is unhealthy"
(CI run 25402487957 / job 74512434167).

Healthcheck is a liveness probe, not a config validator: keep just the
TCP probe to the FPM port. Email connectivity is exercised end-to-end by
the Playwright mailu spec post-deploy.

Verified with two full-cycle fresh deploys (matomo+email disabled and
matomo disabled / email enabled) — both passes green, all variants and
Playwright specs (incl. mailu admin SSO + biber→admin email) passing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 16:05:15 +02:00