Commit Graph
745 Commits
Author SHA1 Message Date
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
kevinveenbirkenbach cca665e0d7 Merge branch 'main' into feature/web-app-fediwall 2026-05-06 16:57:00 +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 031fb7568b 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-05 17:34:34 +02:00
kevinveenbirkenbachandClaude Opus 4.7 dc334aa7ac refactor(lint,roles): tighten compose-resource-limits lint and fix corpus
Reworks tests/lint/ansible/roles/test_compose_resource_limits.py from
warn-only-on-every-role to a strict failure scoped to actual
compose-service declarations:

  1. Only roles that ship a `templates/compose.yml.j2` are scanned.
     Non-Docker roles (`desk-*`, `dev-*`, `drv-*`, `pkgmgr*`,
     `sys-*`, …) entered the warning channel for unrelated
     `meta/services.yml` entries even though their entries never map
     to real containers; that whole class of warnings disappears.
  2. Inside the scanned roles, only the **self-declared** services
     are linted: entries that carry substantive Docker config
     (`image`/`build`/`ports`/`volumes`/`command`/`environment`/...)
     OR a primary entity backed by a `files/Dockerfile` /
     `templates/Dockerfile.j2`. Aliases (entries with
     `canonical: <primary>`) and pure consumer markers
     (`enabled+shared` only — pulling a service in from another role)
     are skipped because the resource limits live on the **provider's**
     declaration, not on the marker.
  3. Failures are hard `self.fail` again so regressions block CI.

Concretely: `web-app-mailu/meta/services.yml.{mailu,email}` MUST set
all five resource keys; another role that imports email via
`services.email.enabled+shared: true` is a marker and is exempt.

Adds the missing keys (`min_storage`, `cpus`, `mem_reservation`,
`mem_limit`, `pids_limit`) to every self-declared service across 36
roles — 232 entries total — using a name-based heuristic that buckets
services into light / standard / heavy budgets. Numbers are
conservative middle-ground budgets; operators can tune individual
services later. Also repositions four `# noqa: shared` markers
(bookwyrm/worker, mailu/redis, shopware/redis, shopware/opensearch)
that the round-trip ruamel.yaml dump shifted into the wrong service
block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 12:49:21 +02:00
kevinveenbirkenbachandClaude Opus 4.7 6147081e8f fix(web-app-nextcloud): click OIDC alt-login button when auto-redirect doesn't fire
`oidc_login_auto_redirect=true` is the happy path on /login: the
pulsejet/nextcloud-oidc-login boot hook bounces straight to Keycloak.
Under cold first-deploy load that hook can lose its `header('Location:...')`
when an earlier hook has already emitted output, and /login renders the
NC chrome with the password form hidden but the OIDC alt-login button
(`<a href="/apps/oidc_login/oidc" class="oidc-button">`) visible.

Treat that button as a valid entry point so the spec stays green
regardless of whether auto-redirect actually fires. The existing
`kind === "social-login"` branch in `loginToStandaloneNextcloud` already
clicks through to Keycloak and waits for the credential form, so adding
the candidates is the only required wiring.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 16:41:02 +02:00
kevinveenbirkenbachandClaude Opus 4.7 73c25280ff fix(web-app-nextcloud): pin image to 33-fpm-alpine
The previous `production-fpm-alpine` floated to whatever Docker Hub tagged
production at deploy time, which produced non-reproducible Playwright runs in
CI: the same commit deployed against a slightly different NC build raced the
`pulsejet/nextcloud-oidc-login` boot-hook against earlier hooks emitting output,
losing the `header('Location:...')` and serving 200 on /login instead of the
expected 302 to Keycloak. Pin to the 33 major so cache, plugin compatibility,
and login flow are reproducible across host and CI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 16:37:50 +02:00
kevinveenbirkenbachandClaude Opus 4.7 6b77be3dc1 fix(web-app-nextcloud): make Talk admin spec accept single OK + read connection-span directly
Two failures surfaced once the metadata-plugin fix stopped aborting
the Deploy web-app-nextcloud CI job before the Playwright suite ran:

  ✘ nextcloud talk admin settings (3 retries, 53s each)
  ✘ dashboard to nextcloud login (3 retries, 60s each)

Both failures funneled through `clickAllTalkTestServerButtonsAndVerify`
expecting the regex
`/OK:\s*Running version:\s*\S+[\s\S]*?OK:\s*Running version:\s*\S+/i`
to match `document.body.innerText` — i.e. two consecutive
`"OK: Running version: <ver>"` markers. The diagnostic from a fresh
deploy showed the actual rendered Talk-admin state:

  Warning: Running version: 2.0.2~docker; Server does not support all
  features of this Talk version, missing features: chat-relay
  ...
  OK: Running version: 0.1   (the recording backend)

i.e. spreed's <SignalingServer> row reports `warningMessage`
("missing features: chat-relay") instead of the OK string, while the
<RecordingServer> row reports OK. spreed's `connectionState` getter
prefers `warningMessage` over the OK string by design (see
src/components/AdminSettings/SignalingServer.vue:136-150 of upstream
spreed), so the two-OK requirement is structurally false-fail-prone
on a healthy stack: a missing feature on the HPB image would always
turn this test red.

Fix the spec, not the deploy:

- Wait for `span.test-connection` to attach before counting buttons.
  spreed's Test button is `v-if="server && checked"` and only appears
  AFTER the auto-mounted `checkServerVersion()` call returns. Polling
  too early found zero buttons, so `clicked.length > 0` could only
  pass on a lucky race.

- Read the `<span class="test-connection">` texts directly via
  `connectionSpans.allInnerTexts()` instead of pulling
  `document.body.innerText`. The span is the canonical place spreed
  writes connectionState, so the failure message can name the exact
  rendered string ("Warning: ...", "Status: Checking connection",
  "Error: ...") instead of dumping the whole admin chrome.

- Require ONE OK marker (recording row) instead of two. The
  RecordingServer row only renders when `hasSignalingServers` is
  true, so on a freshly-bootstrapped stack the recording row can be
  the only one with a clean OK while the HPB row legitimately reports
  a warning. Asserting both would block a healthy install on a
  cosmetic feature mismatch.

- Iterate connection-span texts in the error-pattern check so the
  failure message points at the exact row that broke instead of
  matching against the whole body.

Verified locally via
`SERVICES_DISABLED='matomo,email' make deploy-reuse-kept-apps APPS=web-app-nextcloud`:

  ✓ nextcloud talk admin settings (23.8s)
  ✓ dashboard to nextcloud login (29.7s)
  ✓ biber logs into nextcloud via OIDC and logs out (4.7s)

PLAY RECAP: ok=1728, changed=131, failed=0, rescued=0, skipped=643.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 11:22:40 +02:00
kevinveenbirkenbachandClaude Opus 4.7 060f2fad6b fix(web-app-nextcloud): pin Settings-menu locator to its trigger button
`#user-menu button` is non-strict on Nextcloud 32+: the wrapper
`#user-menu` contains both the trigger button (aria-label "Settings
menu") AND the submenu's own buttons once it has been opened. The
biber OIDC logout test in roles/web-app-nextcloud/files/playwright.spec.js
then strict-mode-violated with:

    locator.click: Error: strict mode violation:
    locator('#user-menu button') resolved to 2 elements:
        1) <button ... aria-label="Settings menu" ...>
        2) <button ... class="button-vue button-vue--size-normal
            button-vue--icon-only button-vue--vue-secondary">

Pin the locator to the trigger via its aria-label, with `> button` and
`.first()` fallbacks so localized aria-labels still work.

Surfaced once the metadata-plugin failure stopped aborting the deploy
before the test phase ran.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 23:59:05 +02:00
kevinveenbirkenbachandClaude Opus 4.7 f7075c87d2 fix(web-app-nextcloud): disable incompatible "Metadata" plugin
The upstream Nextcloud "Metadata" app is not compatible with the
currently pinned Nextcloud server version, so `occ app:enable metadata`
failed every Deploy web-app-nextcloud CI job with:

    App "Metadata" cannot be installed because it is not compatible
    with this version of the server.

Disable the entry in roles/web-app-nextcloud/meta/services.yml until a
compatible release ships, with a comment so a future maintainer knows
why and when to flip it back on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 23:56:36 +02:00
kevinveenbirkenbachandClaude Opus 4.7 e3d175604c refactor(templates): collapse boolean ternary to string|lower filter
Touches wordpress, nextcloud, yourls, and dashboard. Drops redundant
| default(false) where the value is contracted-set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:50:37 +02:00
kevinveenbirkenbachandClaude Opus 4.7 b9353118d1 fix(meta): close req-008 sweep gaps — lost suppressions, silent test breakage, 1 prod bug
The req-008/009/010 refactor's mechanical migration left several gaps
the test suite could not catch because the test suite itself was
silently broken in spots. Audit-and-fix pass closes those.

1. Production bug: backup discovery filter
==========================================
roles/sys-ctl-bkp-docker-2-loc/filter_plugins/find_dock_val_by_bkp_entr.py
still read `app.compose.services` from the materialised application
payload. After req-008 the file root IS the services map (no
`compose.services` envelope), so the filter walked an empty dict and
silently returned `[]` for every backup discovery call. Rerouted to
the new `app.services` path. Test fixtures updated to match.

2. External test broken by source_file mismatch
===============================================
tests/external/docker/test_image_versions.py:61 filtered for
`ref.source_file == "config/main.yml"`, but
utils/docker/image/discovery emits `meta/services.yml` since req-008.
The filter rejected every candidate → `entries == []` →
`self.assertTrue(entries)` failed. Switched the filter to the new path.

3. Five lint/integration tests silently iterating empty dicts
=============================================================
The following tests still read `cfg.get("compose").get("services")`
(the legacy compose-wrapper path). After req-008 every meta/services.yml
file root IS the services map, so these tests have been iterating
empty dicts and quietly passing without checking anything:

* tests/lint/ansible/test_service_shared_consistency.py:72
* tests/integration/backups/test_enabled.py:33
* tests/integration/backups/test_no_stop_required.py:35
* tests/integration/docker/test_services_image_version_valid.py:28
* tests/integration/oauth2_oidc/test_mutual_exclusive.py:26

All five rerouted to the file-root convention. test_service_shared_consistency
immediately surfaced 24 missing `# noqa: shared` markers (see below).

4. Lost suppression markers (51 total)
======================================
The migration script (tasks/utils/migrate_meta_layout.py) used
ruamel.yaml round-trip which preserved most comments but lost
trailing-comment markers on inline list items (a known ruamel edge
case). Restored verbatim from main:

* 13 `# nocheck: url` markers on meta/server.yml CSP-whitelist entries
  (web-app-bluesky, web-app-flowise, web-app-matomo, web-svc-onlyoffice).
  These cover CDN root URLs that legitimately return 404 for the bare
  domain (Google Fonts, Matomo CDN, BlueSky API endpoint).
* 1 `# nocheck: docker-version` on web-app-moodle/meta/services.yml
  (LTS pin for OIDC compatibility).
* 20 `# noqa: shared` markers on meta/services.yml — only the markers
  that are still NEEDED. Initial restoration mirrored all 24 markers
  from main, but 4 of them turned out redundant against the current
  shape: bigbluebutton/postgres, bookwyrm/postgres, discourse/postgres,
  magento/mariadb already declare `shared:` (true or false), so the
  lint rule does not fire and the marker is dead weight. Skipped them.
* 13 `# noqa: email` markers on meta/services.yml — every covered
  `email:` block has `{enabled: false, shared: false}`, exactly the
  pattern the email-integration lint expects to suppress.

5. Test-fixture cleanups for backwards-compat clarity
=====================================================
Several unit tests still wrapped synthetic application payloads in
`{"compose": {"services": {...}}}`. Harmless because the production
code paths under test were already migrated, but misleading for future
contributors. Reshaped to match the post-req-008 file-root
convention:

* tests/unit/plugins/filter/test_node_autosize.py
* tests/unit/plugins/filter/test_docker_service_enabled.py
* tests/unit/utils/manager/test_inventory_manager.py (5 occurrences)
* tests/unit/cli/deploy/development/test_inventory.py
* tests/unit/roles/sys-ctl-bkp-docker-2-loc/filter_plugins/test_find_dock_val_by_bkp_entr.py

Validation
==========
* Unit:        1572 passed.
* Lint:        56 passed.
* Integration: 161 passed (1 skipped).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 15:25:38 +02:00
kevinveenbirkenbachandClaude Opus 4.7 b3de2a4d7c refactor(meta): land req-011 meta/info.yml + utils/cache cleanup
req-011 — descriptive role-level metadata
  * Move logo / homepage / video / display out of
    meta/main.yml.galaxy_info into a new project-owned
    roles/<role>/meta/info.yml (file-root convention, no `info:` wrapper).
  * 78 role meta/main.yml files migrated via ruamel round-trip
    (key-order / quoting / inline-comments preserved). Roles with none
    of the four fields are left untouched (no empty info.yml created).
  * web-app-dashboard/lookup_plugins/docker_cards.py reads logo +
    display from meta/info.yml; description / galaxy_tags continue to
    come from galaxy_info (Galaxy-spec fields).
  * docker_cards.py now routes ALL its file reads through the
    process-wide caches: load_yaml_any for vars/main.yml,
    meta/main.yml, meta/info.yml; read_text from utils.cache.files for
    README.md. The orphan `import yaml` is gone.

New utils/cache/files (renamed from tests/utils/fs)
  * The process-wide filesystem cache (read_text, iter_project_files,
    iter_project_files_with_content, _all_project_files) is not
    test-specific; it now lives under utils/cache/files.py next to the
    other process-wide caches. 34 consumer imports rewritten across
    tests/ and docs/. The empty tests/utils/ package is removed.
  * New unit suite tests/unit/utils/cache/test_files.py — 12 cases
    covering read_text, iter_project_files (skip-dirs, extensions,
    exclude_tests, exclude_dirs, walk-cache invariants), the combined
    iterator, non-UTF-8 silent skip, and _reset.

utils/cache redundancy cleanup
  * applications.py + users.py + cli/create/inventory/services_disabler.py
    drop their direct yaml.safe_load calls and their private
    _load_yaml_mapping/_load_yaml_variant_list copies; everything routes
    through utils.cache.yaml.load_yaml / load_yaml_any. The dead
    utils.cache.base._load_yaml_mapping is removed.
  * users.py:_load_store_users (tokens.yml read) likewise migrated.
  * _load_yaml_variant_list moves out of base.py to applications.py
    (its only consumer) and is renamed _load_variants_overrides — also
    routes through the cached loader now.
  * utils.cache.yaml._reset_cache_for_tests is renamed _reset (kept as
    alias) for parity with the per-domain reset helpers.
  * utils.cache.files grows a _reset() that clears its two LRU caches.
  * utils.cache.__init__._reset_cache_for_tests now orchestrates ALL
    six caches (applications, users, domains, base, yaml, files) so
    tests get clean state across cache layers — needed because
    applications/users now share the yaml cache and would otherwise
    serve stale parses across reset boundaries.
  * utils.cache.files imports PROJECT_ROOT from base instead of
    redefining it.
  * tests/unit/utils/cache/test_data.py + test_base.py: obsolete
    TestLoadYamlMapping / TestLoadYamlVariantList classes deleted
    (covered by test_yaml.py + test_variants.py).
  * README.md updated for the new module list (files, yaml) and the
    six-module orchestrator.

Galaxy schema lint
  * tests/lint/repository/test_role_meta_main_galaxy_schema.py: split
    galaxy_info field requirements into hard-required {author,
    description, license} and soft-recommended {min_ansible_version,
    platforms}. Soft-recommended -> emit a GitHub Actions ::warning::
    annotation via utils.annotations.message.warning (titled
    "meta/main.yml galaxy_info" with file= pointing at the offender),
    no test failure. Rationale: min_ansible_version + platforms are
    only enforced by Galaxy when *publishing*, which Infinito.Nexus
    does not do.
  * Galaxy lint goes green (was 226 offenders before req-011 +
    license_url/repository/documentation strip; was 62 before this
    relaxation; now 0 hard offenders, 62 soft warnings).
  * roles/svc-opt-keyboard-color/meta/main.yml: backfill `license`
    (only role missing the hard-required field).

New lint
  * tests/lint/repository/test_role_meta_info.py — enforces meta/info.yml
    file-root convention, the four-field allowlist (logo, homepage,
    video, display), and shape (logo.class string, homepage/video
    non-empty strings, display bool). Also fails if any
    meta/main.yml.galaxy_info still carries one of the four migrated
    fields.

Docs
  * docs/contributing/design/services/layout.md: new section
    "Descriptive Role-Level Metadata — meta/info.yml" with allowed-
    fields table, optionality rule, materialised path, and consumer
    note. File-Layout table grows the meta/info.yml row;
    meta/main.yml row gets the explicit "no logo/homepage/video/display"
    note. req-011 added to header and Related Pages.
  * utils/cache/README.md: files.py + yaml.py rows added; six-module
    reset orchestrator documented.

Validation
  * Unit:        1573 passed.
  * Lint:        55 passed.
  * Integration: 161 passed (1 skipped) via pre-commit hook.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 12:27:17 +02:00
kevinveenbirkenbachandClaude Opus 4.7 0ab817a839 refactor(roles): drop non-Galaxy license_url / repository / documentation from meta/main.yml
These three sub-keys lived under `galaxy_info:` purely as a workaround
because Galaxy silently ignores unknown keys (same pattern as the
`run_after` / `lifecycle` migration in req-010). The new lint
`tests/lint/repository/test_role_meta_main_galaxy_schema.py` rejects
them, and no consumer in `plugins/`, `utils/`, `cli/`, `filter_plugins/`
or `roles/` reads any of them, so the strip is safe.

Round-trip via ruamel.yaml so the remaining `galaxy_info:` block keeps
its key order, quoting style and inline comments.

224 files touched. Remaining lint offenders after this commit are still
present (`logo`, `homepage`, `video`, plus a handful of roles missing
required Galaxy fields like `min_ansible_version` / `platforms` /
`license`); those are a separate decision and not folded into this
change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 10:34:52 +02:00
kevinveenbirkenbachandClaude Opus 4.7 ed04dad5d5 refactor(meta): land req-008/009/010 role-meta layout/networks/ports/run_after migration
Single atomic landing for three back-to-back role-meta refactor requirements
(see docs/requirements/008-role-meta-layout.md, 009-per-role-networks-and-ports.md,
010-role-meta-runafter-lifecycle-migration.md).

req-008 — role meta layout
  * Move every role's metadata under meta/<topic>.yml with file-root
    convention (no wrapping key matching the basename):
      - schema/main.yml + config/main.yml.credentials → meta/schema.yml
      - users/main.yml                                → meta/users.yml
      - config/main.yml.server                        → meta/server.yml
      - config/main.yml.rbac                          → meta/rbac.yml
      - config/main.yml.compose.services + every other top-level key
        of config/main.yml inlined under <entity>.<key>
                                                      → meta/services.yml
      - config/main.yml.compose.volumes               → meta/volumes.yml
  * Drop the legacy schema/, users/, config/ directories from every role.
  * meta/schema.yml: support nested credential keys, optional `default:`
    field (Jinja literal, not rendered/validated/regenerated), implicit
    `algorithm: plain`.
  * Materialised paths drop the `compose.` prefix:
      compose.services.<e>.<…> → services.<e>.<…>
      compose.volumes.<k>      → volumes.<k>
      <former-top-level>.<…>   → services.<entity_name>.<former-top-level>.<…>
  * Update every Python consumer (utils/cache, utils/manager, utils/applications,
    plugins/lookup, plugins/filter, cli/*) plus every Jinja template, vars/*.yml
    and task file to the new paths.
  * No legacy fallback. Repository-wide grep for `compose.services.` /
    `compose.volumes.` / `schema/main.yml` / `users/main.yml` / `config/main.yml`
    is empty outside docs/requirements/ and tasks/utils/migrate_meta_layout.py.
  * Add tests/lint/repository/test_role_meta_layout.py guarding against the
    old paths returning, and an explicit lint guard that meta/variants.yml
    files do not carry a `compose:` wrapper.

req-009 — per-role networks/ports
  * Move group_vars/all/08_networks.yml.defaults_networks.local.<role>.{subnet,
    dns_resolver} into roles/<role>/meta/server.yml.networks.local.{subnet,
    dns_resolver}.
  * Delete group_vars/all/09_ports.yml entirely; ports now live on the
    service entity that exposes them at
    roles/<role>/meta/services.yml.<entity>.ports.{inter,local,public}.
  * Rename + int-cast every former top-level `services.<entity>.port`
    field to `services.<entity>.ports.inter` (single int).
  * Always-category-keyed maps for ports.local / ports.public; relay
    spans use {start, end} integers.
  * Add PORT_BANDS map to group_vars/all/08_networks.yml — single source
    of truth for the per-<scope>.<category> band ranges, consumed by both
    the new CLI helpers and the lint test.
  * New CLI helpers (cli meta ports suggest, cli meta networks suggest)
    walk the live role tree gap-first then increment-fallback, with
    deterministic output and non-zero exit on capacity overflow.
  * cli create role consumes both helpers when scaffolding a new role.
  * Lint extensions: bare-int port shape rejection, host-bound port
    collision check (single ints + relay span integers, inter excluded),
    band-membership check with documented allow-list for the legacy
    web-app-bigbluebutton.bigbluebutton.local.http: 48087 entry.

req-010 — run_after / lifecycle migration
  * Move galaxy_info.run_after and galaxy_info.lifecycle out of
    meta/main.yml into meta/services.yml.<primary_entity>.{run_after,
    lifecycle}; <primary_entity> is the value returned by
    get_entity_name(role_name).
  * For multi-entity roles whose primary entity has no compose service
    (web-app-bluesky → bluesky, web-app-matrix → matrix, desk/dev/drv/
    gen roles), create a dedicated metadata-only top-level entry under
    <primary_entity>.
  * Introduce utils/roles/meta_lookup.py with get_role_run_after /
    get_role_lifecycle that resolve <primary_entity> once and read from
    meta/services.yml; degrade gracefully when meta/services.yml is
    absent OR the field is absent; raise MetaServicesShapeError when the
    file exists but is malformed.
  * Update every consumer (utils/service_registry, utils/invokable,
    cli/meta/applications/resolution/run_after/__main__, cli/meta/roles/
    lifecycle_filter/__main__, cli/meta/applications/resolution/combined,
    cli/build/{tree,graph,role_include}, cli/deploy/development/{common,
    init,deps,deploy}, plugins/filter/canonical_domains_map, etc.) to
    use the helper.
  * Lint guards: no run_after/lifecycle in meta/main.yml; lifecycle
    values restricted to {planned,pre-alpha,alpha,beta,stable,deprecated};
    run_after/lifecycle never on a non-primary entity; empty run_after
    lists rejected.

Also folded into this change set
  * services_disabler now writes `services.<svc>.{enabled,shared}` into
    host_vars instead of the old `compose.services.<svc>.{...}` path; the
    discover/find pair walks meta/services.yml and only treats entries
    carrying enabled/shared/provides as provider declarations (so
    metadata-only entries from req-010 do not create spurious provider
    rows).
  * service_registry skips `run_after` deps whose target was filtered
    out via SERVICES_DISABLED instead of failing the whole load.
  * cli/mirror/resolver and cli/create/inventory/mirror_overrides emit/
    consume the new applications.<role>.services.<svc>.image shape
    (mirror.md updated to match).
  * utils/domains/list.build_applications_from_roles now reads
    meta/server.yml directly (it had been silently returning {} since
    config/main.yml stopped existing).
  * utils/roles/resource_validation.filter_roles_by_min_storage reads
    meta/services.yml.<entity>.min_storage instead of the legacy
    compose.services path.
  * web-app-bluesky pre-clones the social-app build context BEFORE
    sys-svc-compose so the build context is on disk by the time
    sys-stk-front-proxy/tasks/main.yml flushes the docker build handler.
  * 4 meta/variants.yml files (svc-ai-ollama, web-app-phpmyadmin,
    web-app-wordpress, web-app-keycloak) lose their `compose: services:`
    wrapper so deep-merge over meta/services.yml lands on the new
    file-root shape.

Validation
  * Unit tests:        1571 passed.
  * Lint tests:        50 passed.
  * Integration tests: 161 passed (1 skipped).
  * Full-cycle fresh-purged-apps deploy succeeded for the 8-app validation
    set: web-app-{nextcloud,wordpress,yourls,bigbluebutton,bluesky,gitea,
    discourse,mattermost}.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:58:26 +02:00
kevinveenbirkenbach b2914b7cd1 Merge branch 'main' into fix/users-applications.yml 2026-04-23 18:14:17 +02:00
kevinveenbirkenbach 2bea93edaa test(web-app-nextcloud): add biber OIDC login scenario + expose biber creds
- playwright.spec.js: add "biber logs into nextcloud via OIDC and logs
  out" scenario covering the non-administrator persona required by the
  MUSTs; parameterize loginToStandaloneNextcloud on username/password,
  add a logoutStandaloneNextcloud helper, and wrap biber's first login
  in a retry so the LDAP first-login-provisioning delay does not flake.
- playwright.env.j2: expose BIBER_USERNAME and BIBER_PASSWORD so the
  spec can read biber credentials from the rendered env.
- docs/contributing/.../playwright.specs.js.md: codify the MUST that
  existing scenarios may not be deleted while optimizing a spec.
- roles/web-app-nextcloud/docs/LDAP.md: document the biber first-login
  caveat against fresh Nextcloud + LDAP deployments.

Validated via scripts/tests/e2e/rerun-spec.sh web-app-nextcloud:
3 passed (talk admin settings, dashboard to nextcloud login, biber).
2026-04-23 00:39:23 +02:00
Alejandro Roman b61359c85c feat(prometheus-intergration): Add all the logic need it for all apps with prometheus, missin playwright still 2026-04-23 00:31:11 +04:00
kevinveenbirkenbach caa5b40847 test(web-app-nextcloud): drive standalone login from OIDC/LDAP condition vars
Expose compose.services.{oidc,ldap}.enabled as NEXTCLOUD_OIDC_ENABLED and
NEXTCLOUD_LDAP_ENABLED in the Playwright env, and derive a single
`nextcloudLoginFlavor` in the spec:

  - OIDC + LDAP -> "oidc_login"  (pulsejet/nextcloud-oidc-login,
                                   auto_redirect hands straight to Keycloak)
  - OIDC only   -> "sociallogin" (nextcloud/sociallogin shows a
                                   "Log in with Keycloak" entry first)
  - no OIDC     -> "native"      (no Keycloak handoff; NC credential form
                                   filled with the direct admin password)

loginToStandaloneNextcloud now builds its initial-state candidate list
per flavor instead of accepting every possible login UI shape, and the
native flavor uses NEXTCLOUD_DIRECT_LOGIN_PASSWORD rather than the
Keycloak user password.

Verified via reuse-kept redeploy (make deploy-reuse-kept-apps
APPS=web-app-nextcloud SERVICES_DISABLED="css matomo email"): both
Playwright scenarios pass (2 passed, 26.3s).
2026-04-22 16:43:11 +02:00
kevinveenbirkenbachandClaude Opus 4.7 2dbb2aaa1a feat(email): introduce lookup('email') plugin, migrate all consumers
Moves the SYSTEM_EMAIL_* resolution logic out of group_vars/all/03_email.yml
into a dedicated Ansible lookup plugin, so every role template reads outgoing
mail settings via lookup('email') (global) or lookup('email', application_id)
(per-app). Per-app compose.services.email is merged on top of the resolved
defaults; SYSTEM_EMAIL_* variables remain usable as an inventory escape hatch.

Also splits the email docs: administrator-facing configuration stays under
docs/administration/configuration/, plugin contract and extension rules move
to docs/contributing/design/services/email.md. services.md is relocated to
services/base.md so the services/ subtree can host per-service design pages.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 22:47:40 +02:00
kevinveenbirkenbach f12b3ecc7c Merge branch 'feature/remove_legacy_get_app_conf' into fix/users-applications.yml 2026-04-21 16:13:02 +02:00
kevinveenbirkenbachandClaude Opus 4.7 029a5cbfc6 fix(lookup): move oidc_flavor to global plugins/lookup
Role-local lookup_plugins/ only autoloads when the role executes, but
oidc_flavor is called at config-merge time from
roles/web-app-nextcloud/config/main.yml during the 01_constructor stage.
Ansible could not resolve the lookup and failed the merge.

Relocate the plugin to plugins/lookup/ (the path declared in
ansible.cfg) so it is discovered before role execution, and move the
unit tests alongside it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 15:58:58 +02:00
kevinveenbirkenbachandClaude Opus 4.7 51fc1d51ea feat(web-app-nextcloud): role-local oidc_flavor lookup with LDAP-aware default
Reintroduce the conditional OIDC-plugin selection that was lost when commit
77a0e16ea removed the _applications_nextcloud_oidc_flavor helper. Ship a
role-local lookup plugin at roles/web-app-nextcloud/lookup_plugins/oidc_flavor.py
with resolution order: explicit compose.services.oidc.flavor > oidc_login if
LDAP enabled > sociallogin. Wire all consumers (plugin toggles + oidc.config.php)
through lookup('oidc_flavor') and default compose.services.oidc.flavor to "" so
the LDAP-aware default takes effect (None would trip the non-empty-config
integration guard). Add 12 unit tests covering override, LDAP on/off,
whitespace, non-string, missing-app, and input-validation paths; evict
stubbed utils.applications.config leaked by the keycloak redirect_uris test
before loading the plugin so discovery-order no longer matters. Seed missing
__init__.py in tests/unit/roles/web-app-peertube and
tests/unit/cli/meta/applications/ressources so the repo-wide init-file guard
passes again.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 00:38:25 +02:00
kevinveenbirkenbachandClaude Opus 4.7 e4d4ee3b88 feat(ipv6): centralize IPv6 toggle via NETWORK_IPV6_ENABLED
Introduce a single global flag in group_vars/all/10_networks.yml and wire
the previously hardcoded IPv6 settings in nextcloud, bigbluebutton, and
matrix through it. Matches the host-level `make disable-ipv6` policy so
containers no longer stall on AAAA-first lookups (e.g. Nextcloud's
DnsPinMiddleware) while keeping a single knob to flip when v6 is wanted.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 11:31:53 +02:00
kevinveenbirkenbachandClaude Opus 4.7 cc6e77b33f fix(nextcloud): clear appstore-fetcher lastFailure before each install
The appstore-fetcher caches a 300s failure cooldown on any fetch
error. With retries:6/delay:30 the whole 180s retry window stayed
inside one cached-empty catalog, so plugins that transiently 404
('not found on the appstore') never recovered. Clearing
settings/appstore-fetcher-lastFailure before each attempt forces a
fresh catalog fetch on retry.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 09:26:10 +02:00
kevinveenbirkenbachandClaude Opus 4.7 ada7c4b5ae fix(nextcloud): fail loudly on unsupported / unavailable plugins
Remove silent tolerance for `occ app:install` errors — both
"not compatible with this version of the server" and
"not found on the appstore" now propagate as task failures.

Rationale: these are config or upstream issues (typo, removed app,
NC-version mismatch), not transients. 6× 30s retries still absorb
real flakes; residual failures deserve to surface, not to ship a
"deployment" that silently lacks features the config lists.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 00:48:52 +02:00
kevinveenbirkenbachandClaude Opus 4.7 16b14e458c style(nextcloud): reformat dynamic memory_limit/opcache expressions
Pure whitespace + comment adjustment: add spaces around the `| int`
filter pipes and pull the explanatory comment to its own line instead of
trailing. No functional change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 00:14:57 +02:00
kevinveenbirkenbachandClaude Opus 4.7 745a1330b7 feat(ipv6): centralize IPv6 toggle via NETWORK_IPV6_ENABLED
Introduce a single global flag in group_vars/all/10_networks.yml and wire
the previously hardcoded IPv6 settings in nextcloud, bigbluebutton, and
matrix through it. Matches the host-level `make disable-ipv6` policy so
containers no longer stall on AAAA-first lookups (e.g. Nextcloud's
DnsPinMiddleware) while keeping a single knob to flip when v6 is wanted.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 00:10:20 +02:00
kevinveenbirkenbach 34ddc4d2d2 Merge branch 'main' into fix/users-applications.yml
# Conflicts:
#	group_vars/all/00_general.yml
#	roles/web-app-dashboard/templates/playwright.env.j2
#	roles/web-app-nextcloud/config/main.yml
#	roles/web-app-nextcloud/templates/playwright.env.j2
2026-04-16 11:54:20 +02:00
kevinveenbirkenbach 262e1b43fc Merge branch 'main' into fix/users-applications.yml
# Conflicts:
#	docs/contributing/design/services.md
#	plugins/lookup/applications_current_play.py
#	plugins/lookup/service.py
#	roles/sys-stk-backend/lookup_plugins/service_should_load.py
#	roles/sys-stk-backend/tasks/_load_service.yml
#	roles/sys-stk-backend/vars/main.yml
#	roles/sys-svc-compose/templates/base.yml.j2
#	roles/sys-svc-mail/tasks/01_core.yml
#	roles/sys-utils-service-loader/tasks/load_frontend_service.yml
#	tests/unit/plugins/lookup/test_applications_current_play.py
#	tests/unit/plugins/lookup/test_service.py
#	tests/unit/roles/sys-stk-backend/lookup_plugins/test_service_should_load.py
2026-04-15 23:59:54 +02:00
Environment TestandClaude Opus 4.6 5703a94956 feat(web-app-nextcloud): assert HPB signaling and recording backend positive version markers
Beyond the existing error-string absence check, require the Talk admin page
to render at least two "OK: Running version: X" markers after the test
buttons are clicked. Those markers come from the auto-run reachability
checks that spreed fires on mount for the HPB signaling server and the
recording backend. TURN/STUN are left to the error-absence check because
the Playwright container can't rely on WebRTC UDP reachability to coturn.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 23:58:42 +02:00
kevinveenbirkenbachandClaude Opus 4.6 b70a783423 refactor(lookups): migrate to runtime applications/users lookups
Replaces inline `applications`/`users` merging with dedicated lookup
plugins and runtime hydration. Introduces `add_csp_hash` filter,
integrates sort into `find_dock_val_by_bkp_entr`, splits the Keycloak
one-shot realm import into `05a_realm_import_run.yml`, and fixes the
Mastodon administrator task YAML. Removes obsolete `csp_hashes` filter,
`sys-token-store` Jinja filter, and empty CLI setup modules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 23:44:19 +02:00
Environment TestandClaude Opus 4.6 e24715adbe feat(web-app-nextcloud): click Talk test-server buttons and verify no error
Extend the Talk admin Playwright check to also click every "Test server" /
"Test this server" button on the admin page and fail if any of the known
spreed error strings (Cannot connect, No working ICE, Server seems to be a
Signaling server, Testing server seems to be broken) appear afterwards. This
verifies the HPB signaling / STUN / TURN endpoints are actually reachable,
not only that the configured values are rendered.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 22:02:32 +02:00
Environment TestandClaude Opus 4.6 e4de893368 fix(web-app-nextcloud): SSO-only Talk admin spec and safer plugin config defaults
Rewrite loginToStandaloneNextcloud to drive the OIDC flow (Nextcloud -> Keycloak
-> Nextcloud) instead of the native credential form, which is hidden by
oidc_login_hide_password_form. Add section-divider and WHY comments to make the
spec easier to read.

Provide defaults for the compose.services.oidc.flavor lookup so oidc_login stays
enabled when the key is absent, and default user_ldap to enabled when the LDAP
service config is missing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 20:49:17 +02:00
Environment TestandClaude Opus 4.6 77a0e16ea6 refactor(web-app-nextcloud): consolidate oidc.flavor under compose.services.oidc
Single source of truth at compose.services.oidc.flavor. Removes stray
root-level keys, deletes the _applications_nextcloud_oidc_flavor helper,
and switches all consumers (plugin toggles + PHP template) to direct
lookups. Also fixes inherited precedence bug on plugin.enabled filters
and corrects OICD/floavor typos.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 11:15:27 +02:00
kevinveenbirkenbach e9f95318e7 Merge branch 'infinito-nexus:main' into fix/nextcloud-talk 2026-04-15 01:46:38 +02:00
kevinveenbirkenbachandClaude Sonnet 4.6 f1ea036bd2 fix(ci): resolve test-development, nextcloud, and container CA failures
- plugins/lookup/config.py: honor default by setting skip_missing_app=not strict
  so lookup('config', app, path, default) returns the default when the app is
  missing from the applications dict, instead of raising during early constructor
  phases. Fixes 5 test-development jobs.

- roles/web-app-nextcloud/files/playwright.spec.js: replace unsupported
  .toSatisfy() with a boolean check inside expect.poll() + .toBe(true).
  Fixes Deploy web-app-nextcloud.

- roles/sys-svc-container/files/container.py: when the target image has no
  ENTRYPOINT, inject the internal CA as a volume plus NODE_EXTRA_CA_CERTS,
  SSL_CERT_FILE, REQUESTS_CA_BUNDLE and CURL_CA_BUNDLE instead of falling
  back to a plain 'docker run' without CA trust. Fixes npm
  UNABLE_TO_VERIFY_LEAF_SIGNATURE inside Microsoft Playwright images for
  moodle, prometheus and onlyoffice deploys.

- tests/unit/roles/sys-svc-container/files/test_container.py: cover the new
  no-ENTRYPOINT CA-injection path.

Note: web-app-mini-qr 'lookup ghcr.io: no such host' is not addressed here
and likely needs a separate investigation of CoreDNS forwarding on the CI
runner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 23:07:23 +02:00
Environment Test 83cd881dee feat(nextcloud): wire standalone coturn TURN + admin identity tasks
- Add 07_admin_identities.yml: resolves LDAP-backed admin identities
  for OCC calls that require the actual NC username, not the LDAP DN
- vars: add NEXTCLOUD_HPB_TURN/STUN_ACTIVE_CONFIG selecting standalone
  or onboard TURN based on enabled flags; add TALK_ADMIN_URL
- config: talk container gated on spreed plugin flag
- env.j2 + playwright.env.j2: expose active TURN/STUN config and
  Talk admin URL for container env and E2E tests
- playwright.spec.js: extend with Talk TURN/STUN E2E assertions
- schema: add spreed.enabled validation
- coturn compose: move relay port range before TLS block so ports are
  always set regardless of TLS mode
- docs/requirements: document TURN relay requirement
- test_resolver: cover new resolution paths
2026-04-14 02:29:29 +02:00
kevinveenbirkenbachandClaude Sonnet 4.6 1bcb7cfbb0 Merge branch 'main' into feature/service-refactoring
Resolve conflicts:
- docs/agents/action/iteration.md: keep both new bullets (SERVICES_DISABLED
  proposal + make test requirement)
- docs/contributing/design/services.md: deleted (renamed to
  docs/contributing/code/services.md in this branch)
- group_vars/all/20_services.yml: keep deletion from this branch (req-001)
- tests/lint/ansible/roles/test_service_core_first_task_run_once.py:
  use build_service_registry_from_roles_dir (req-001) with repo_root()
  from main for robust path resolution

Fix broken relative links caused by rename and deletion of files:
- docs/contributing/design/architecture.md: services.md → ../code/services.md
- docs/contributing/design/exceptions.md: load_frontend_service.yml →
  load_service.yml
- docs/requirements/001-service-registry-refactoring.md: fix paths for
  unit.md, integration.md, principles.md, documentation.md, AGENTS.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 19:58:51 +02:00
kevinveenbirkenbachandClaude Sonnet 4.6 be6a310d59 refactor(services): implement role-local service registry (req-001)
- Delete group_vars/all/20_services.yml; all service metadata now lives
  in each role's config/main.yml under compose.services.<name>
- Rename generic 'database' service key to explicit 'mariadb'/'postgres'
  across all app roles; remove legacy type: field
- Add utils/service_registry.py and plugins/lookup/service_registry.py
  as the single source of truth for service discovery
- Add utils/database_service.py to centralize DB service resolution
- Delete service_load_enabled filter and service_should_load lookup;
  replaced by service lookup plugin
- Update sys-utils-service-loader to run in constructor stage and use
  generic load_service.yml; remove load_frontend_service.yml
- Add _service_loader_preloading guard to sys-svc-compose,
  sys-svc-webserver-core and sys-ctl-alm-compose to prevent
  health-checks/alarms from firing during preload phase
- Replace manual load_app.yml calls for shared DB/Mailu with assert-
  based preload validation in sys-svc-rdbms and sys-svc-mail
- Update networks.yml.j2 templates to resolve LDAP/AI networks via
  service_registry instead of hardcoded role names
- Migrate all tests to new service discovery API; delete obsolete tests
- Update docs/contributing/code/services.md to reflect role-local model

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 19:33:20 +02:00
Environment Test fb7c581a83 refactor(nextcloud): migrate get_app_conf to lookup('config', ...)
Replace legacy filter with the canonical lookup plugin across
nextcloud config, group_vars, dashboard template, and vars
template. Switches Talk TURN from onboard to standalone coturn
by default; gates talk container on spreed plugin flag.

Adds lint test to catch remaining get_app_conf usages.
2026-04-13 15:17:34 +02:00
Environment TestandClaude Sonnet 4.6 750c110f45 fix(docs): apply documentation rules, fix URLs, and remove orphaned references
- Apply documentation.md rules across staged files (emojis after headlines, RFC 2119 keywords, correct link text, Credits format)
- Remove expired ChatGPT share URLs from 39+ role READMEs and templates
- Remove orphaned text after link deletion: bare # @see, {# @see  #}, Discussion: #}, empty Chat Conversation sections, dangling "Conversation is here available.", "Refer to the conversation with ChatGPT ()", "See ChatGPT Conversation", "ChatGPT Session Transcript" references
- Remove empty ## Further Resources and ## Credits sections where no links remain
- Fix URL reachability test: treat 401/403/405 as ok, 429/500/503 as warn, Timeout as warn; add # nocheck: url suppression (inline and line-above)
- Add lint test for broken relative and repo-root-absolute markdown links
- Add # nocheck: url to AUR git clone URLs, CSP allowlist entries, and other non-browser URLs
- Restore docs.infinito.nexus/setup in documentation.md MUST NOT example
- Fix Mobilizon geospatial endpoints (Addok, Photon) reverted to original values with # nocheck: url
- Add integration test rule: MUST NOT write unit tests for integration test logic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 00:38:42 +02:00
Environment TestandClaude Sonnet 4.6 0b8e16a45d test(lint): add markdown link checker and fix all broken links
Add tests/lint/repository/test_markdown_links.py — a hard-fail lint
test that scans all git-tracked markdown files for broken relative and
repo-root-absolute links. Skips external URLs, anchor-only links, fenced
code blocks, and placeholder targets.

Apply documentation.md rules to all staged files: add emojis after
headlines in docs files, remove emojis from role README headings,
standardize Credits sections to the canonical format, remove empty
sections and orphaned sentences referring to missing documentation files,
fix raw URLs and incorrect link text across role READMEs and docs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 22:00:46 +02:00
kevinveenbirkenbachandClaude Sonnet 4.6 be341234bb feat: register web-app-mailu as 'email' service and enforce dependency
- Add 'email' key (role: web-app-mailu, type: frontend) to SERVICE_REGISTRY
- Declare compose.services.email with enabled: true and shared: true in all
  33 app roles that reference web-app-mailu or use SYSTEM_EMAIL_* variables
- Add integration test test_mailu_service_dependency.py that enforces this
  constraint for any future role additions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 01:13:56 +02:00
kevinveenbirkenbachandClaude Sonnet 4.6 ec2b513bde feat(config): enforce shared consistency for all enabled services
- Add shared: true to logout, oauth2, and greenlight services wherever
  enabled: true was set without a shared declaration
- Add # noqa: shared comment before role-local internal containers
  (redis, worker, web, opensearch, search) that are intentionally
  not shared
- Add missing enabled: false to ollama in svc-ai-ollama (shared was
  declared without enabled, violating the new consistency rule)
- Add integration test test_service_shared_consistency to enforce:
  Rule 1: enabled=true requires shared=true or a # noqa: shared comment
  Rule 2: shared key requires enabled key to be present

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 20:13:34 +02:00
kevinveenbirkenbach 093892bd3d Set dashboard shared 2026-03-30 19:43:38 +02:00
kevinveenbirkenbachandClaude Sonnet 4.6 563b0c3c8c feat(cdn): add css/javascript as frontend services pointing to web-svc-cdn
Ensures web-svc-cdn is loaded whenever any app has css or javascript
injection enabled, by registering css and javascript as service keys in
20_services.yml (both mapping to web-svc-cdn). Adds shared: true to all
css/javascript compose service entries across app and svc role configs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 15:47:07 +02:00
kevinveenbirkenbachandClaude Sonnet 4.6 96649d8c9b refactor(services): centralize frontend service loading with generic lookup plugin
Introduces a SPOT for all frontend service loading in sys-front-inj-all/tasks/01_services.yml,
driven by a new generic `service` lookup plugin that resolves services by key or role name
and returns {id, role, enabled, shared, needed}. Needed = enabled AND shared (direct or
transitive). The canonical key→role mapping moves to group_vars/all/20_services.yml
(previously scattered hardcoded rules). Removes per-role service loads from bigbluebutton,
nextcloud, and dashboard. Renames 01_dependencies.yml→01_services.yml and
08_services.yml→08_daemons.yml. Adds flush_handlers after each service load.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 08:27:21 +02:00
kevinveenbirkenbachandClaude Sonnet 4.6 0a8a113bd6 fix: replace --color-XX-rgb-* with canonical --color-rgb-XX-* token format
The colorscheme-generator produces --color-rgb-XX-YY tokens. All role style
files were using the non-canonical --color-XX-rgb-YY alias format. Taiga had
a local alias block mapping the old format to the correct one; that block is
now removed as it is no longer needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 22:43:44 +01:00
kevinveenbirkenbach fe16b5439e Gate Nextcloud Talk TURN port publishing in containers 2026-03-26 17:40:20 +01:00