Commit Graph
74 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5.5 fe486a21e5 fix(web-app-peertube): back-fill the application defaults only when missing
With the psql command tags gone the OIDC plugin task stays `ok`, but PeerTube
was still force-recreated on every deploy: "Back-fill column defaults so
bootstrap INSERT keeps NOT NULL" runs an unconditional `ALTER TABLE …
SET DEFAULT`, database_query reports every non-SELECT as changed, and the
task notifies compose-up, which runs `compose up -d --force-recreate`.

A SELECT on information_schema.columns now lists the bootstrap columns
(`nodeVersion`, `nodeABIVersion`) that still have no default, and the ALTER
with its compose-up notification runs only when that list is not empty. A
fresh database still gets the defaults before PeerTube's bootstrap INSERT
retries; every later deploy skips the task. The unused register of the
ALTER task is gone, and both read-only checks now live under
files/sql/check-application/ as the sibling-prefix rule requires.

Refs #1164

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 20:36:32 +02:00
kevinveenbirkenbachandClaude Opus 5 f19d5e3477 test(lint): refuse a loop_var that shadows a declared variable
Ansible warns that the name is already in use and carries on. Carrying on is the problem: inside the loop the original value is gone, so every lookup, template and nested include reading that name sees the loop item instead, and the task still reports success. The damage lands in a log nobody greps.

The lint collects every key declared in group_vars and in each role's vars or defaults, then reports any loop_var matching one. A denylist of known-bad names would have found application_id and missed domain, which is the same class in four more places.

All seven existing sites are marked, each with the reason read at the source rather than inferred. The three application_id loops pass the role id as the item, and the include_vars that follows loads that role's own application_id with the same value, so both meanings coincide by construction. The four domain loops hand the vhost to sys-stk-front-proxy, whose tasks read front_proxy_domain from exactly that name, so the shadowing is the interface.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 21:23:17 +02:00
kevinveenbirkenbachandClaude Opus 5 a46348b6cf refactor(handlers): bind notify to a listen topic, not to a name
A handler name was doing two jobs at once: the line an operator reads in the
play output, and the identifier a `notify:` binds to. The two pull in opposite
directions -- the log wants "restart the SSH service, with diagnostics", the
binding wants a token that survives quoting, YAML flow style and a shell
round-trip -- and a name that changes for readability silently changes which
handler runs.

Every handler that a notify reached by name now carries a `listen:` with that
exact wording, so the binding is explicit before anything is renamed. The
topics themselves become identifiers: `compose up` reads `compose-up`,
`Restart NFS ganesha` reads `restart-nfs-ganesha`, 22 of them across both
sides of every notify. Where the topic is assembled at runtime, both branches
moved together: the conditional in sys-service carries two literals, and the
LDAP renderer interpolates `import-{{ folder }}-ldif-files`, which is what
makes those handlers reachable at all.

Freed from the binding, the names take the emoji prefix every other task line
already has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 03:38:42 +02:00
kevinveenbirkenbachandClaude Opus 5 8ffb1f5674 refactor(peertube): make the role clearnet-only
PeerTube distributes video over WebRTC and the role proxies its tracker socket on purpose (templates/proxy.conf.j2:48, with a raised read timeout because peers ping every fifteen minutes). Tor carries no UDP and an onion client cannot hold peer connections, so an onion PeerTube is the app without the feature it exists for. What remained was plain HTTP video at Tor throughput, and a login path that only worked through a sed over the vendored dist of a third-party plugin.

That patch is gone with this change, along with PEERTUBE_OIDC_ALLOW_INSECURE_ISSUER, the variable that gated it, and both tor pins in meta/variants.yml. The root TODO entry describing it is removed too - it carried a three-step exit path (upstream MR, fork, delete) that no longer needs walking. The https-on-onion entry keeps its analysis and loses only the sentence that named peertube among the per-consumer workarounds.

The discovery gate stays. Failing the deploy when the plugin registered without building a client is not tor-specific and was the honest half of e64ed1cd9.

tor.enabled becomes a literal false rather than the block being deleted. Two lints define the contract: test_tor_contract requires every domain-serving role to declare the block, and test_sso_variant_requires_tor names a literal false as the first of three cases that may opt out of pinning the onion on with a reason. Deleting the block failed both; this is the language they specify.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 14:40:48 +02:00
kevinveenbirkenbachandClaude Opus 5 e64ed1cd93 fix(peertube): let OIDC discovery reach a plaintext onion issuer
openid-client v6 refuses a non-HTTPS issuer, and an onion-exclusive Keycloak has
none: the onion publishes only HiddenServicePort 80, and https there is blocked
on four separate things (see TODO.md). PeerTube's plugin therefore threw
OAUTH_HTTP_REQUEST_FORBIDDEN during discovery, never built a client, and answered
2326 logins with "OpenID provider unavailable" — while `test -d` on the plugin
directory reported the deploy as ok.

Two changes, one honest and one not:

The gate at the end of enable.yml now fails when the container log carries
"Initial OpenID discovery failed", so a plugin that registered without building a
client stops the deploy instead of surfacing hours later in the browser suite.

The sed over the installed dist is a hack and is marked as one in TODO.md. It
widens the trigger of the escape hatch upstream already implements — the plugin
gates `openid.allowInsecureRequests` on NODE_ENV alone, and setting NODE_ENV=dev
in production would swap PeerTube's whole runtime profile. The `grep -q` after
the substitution is what keeps it tolerable: an upstream release that moves the
line fails the deploy loudly rather than silently doing nothing.

Both are driven by one variable, PEERTUBE_OIDC_INSECURE_ISSUER, which requires
SSO enabled AND tor enabled for peertube AND an .onion issuer, so no deploy
without Tor can execute the patch or ever fail on it. Verified with Ansible's own
templar across seven cases, including https://onionsoup.example.com/ to catch a
too-greedy match. The sed itself was run against the real 2.0.1 tarball: exactly
one hit, `node --check` clean, and the patched expression exercised in four
environment combinations.

Exit path in TODO.md: the plugin is an official Framasoft one (AGPL-3.0 in the
repo root, the npm package merely ships no license field), and the proper fix is
a `registerSetting({ name: 'allow-insecure-issuer' })` OR-ed into the same
condition. Issue text and patch are written; upstream filing is waiting on a
framagit account.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 21:53:31 +02:00
kevinveenbirkenbachandClaude Opus 5 673e994aa7 feat(lint): pin type and resolver on every container inspect
An untyped inspect searches every docker object kind, and a role's overlay network carries the same entity name as its main container, so the call can resolve the network and report a missing State key instead of naming the container. That is how the openresty assert died on every swarm job of run 30709070050.

Extend container-exec-resolver to cover inspect, stripping raw markers and quoted spans first so the flag walker survives Go template format strings. Add container-inspect-type, scoped to roles/ because the collision follows the entity naming convention and the outer act namespace carries no role networks. Sweep 40 call sites; suppress the delegate_to-resolved CIDs and the compose co-location probes with a stated reason.

Move the five container rules into tests/lint/ansible/shell/container/ and drop the container_ prefix from their filenames. Register both markers in the suppression docs, where the resolver marker was missing entirely.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 20:51:56 +02:00
kevinveenbirkenbachandClaude Opus 5 914d90cd8d fix(swarm): make the forced recreation survive a paused update and prove it worked
The new handler had neither retries nor an assertion. update_config carries no failure_action, so Docker's default is pause: one flapping healthcheck inside the monitor window pauses the rolling update, docker service update exits non-zero, pipefail propagates it and any_errors_fatal kills the whole deploy - where the neighbouring converge handler would have retried. Give it five attempts fifteen seconds apart, matching how the registry-sync handler beside it already treats a transient action.

The registration probe only notified; nothing checked that the recreation actually registered the plugin. A plugin that can never register - corrupt pnpm tree, engine mismatch, invalid library code - therefore reported changed on every run, recreated the stack every time and still left the play green. Flush the recreation, re-resolve the container id the recreation invalidated, and assert the registration with a bounded wait, so that case fails loudly in the run where it happens instead of churning forever in silence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 15:08:51 +02:00
kevinveenbirkenbachandClaude Opus 5 cfd6f08795 fix(swarm): recreate tasks so in-container changes reach the process
docker stack deploy leaves tasks running when the service spec is unchanged, so a change made inside a container never reaches the process. PeerTube's CLI installs plugins with register:false, so the OIDC plugin sat installed and enabled in the database while the running server had loaded nothing: /login rendered only the local form and both OIDC specs timed out through all three attempts. Compose never showed it because its handler force-recreates.

Add a container recreate notify. In swarm it forces a task recreation per stack service and waits for convergence; in compose it maps onto the existing --force-recreate, so a role states the intent once and both modes honour it.

In the peertube role the install probe now tests node_modules/<npm-name>, where pnpm puts the plugin code; the old path was the data directory that registerPlugin creates at boot, so it never existed and the plugin was reinstalled on every run. The settings upsert derives changed from the rowcount via RETURNING, because database_query reports changed from the kind of statement rather than from what it touched. A registration probe notifies when the running process has not loaded the plugin, so an already-broken instance heals itself instead of walking past both earlier probes. The plugin:enable task is gone: npm has no such script, the call always failed and failed_when swallowed it.

The spec now polls the button lookup instead of counting once twenty milliseconds after goto, while still asserting on the URL so a failure keeps printing the address the browser actually sat on.

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

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:01:31 +02:00
kevinveenbirkenbachandClaude Fable 5 c51c0de9ba refactor(diagnostics): recursive workflow-level rescue snapshot, no in-play rescue blocks
Replace scripts/system/diagnostics/rescue.sh with utils/diagnostics/
container.py: one workflow call on failure captures the current level
(containers with logs+inspect, swarm services, journal, host resources,
in-play role dumps) and recurses itself through every DiD boundary via
self-copy, from the runner down to the innermost runtime (RESCUE_MAX_DEPTH).
compose and swarm now share an identical failure step; the swarm per-node
collector wrapper is gone. All 17 script-only Ansible rescue: blocks are
deleted and their bare block: wrappers inlined (plus a wordpress leftover);
the three justified blocks (postgres pg_hba, xwiki extensions, keycloak
cross-node recovery) keep their evidence tasks and now end in a fail task.
Two lints enforce the state: rescue: is forbidden without a
'# nocheck: rescue-<reason>' first-task marker, and a block without
rescue/always/when or another scoping key must be inlined.
sys-service-terminator hoists become to the include apply. Also move
routine/08_cleanup.sh to utils/clean/teardown.sh and topology/ to
utils/topology/ so routine/ holds only the sequenced flow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 03:15:53 +02:00
kevinveenbirkenbachandClaude Opus 4.8 094c02a69d feat(diagnostics): single SPOT rescue script with CI-uploaded snapshots
Every Ansible rescue: block used to carry ad-hoc per-role failure dumps that
drifted apart and captured different subsets. Replace them all with one
diagnostics script (scripts/system/diagnostics/rescue.sh) that snapshots
containers (full logs plus inspect per container), swarm services (ps plus
logs per service), journal, host resources, runtime info and stats into a
per-run folder and always exits 1 so the deploy still fails. A new lint
enforces the shape: every rescue ends with the script, at most one
nocheck-justified extra task before it (postgres pg_hba dump, xwiki
extension-repo probe), and a nocheck rescue-recovery exemption for the one
block that must genuinely recover: keycloak object updates, where a
cross-node reschedule needs a swarm-wide node re-resolve plus a delegate_to
change that only a rescue can express; its recovery is kept and now also
captures diagnostics before the terminal failure.

The snapshot folder path is a group_vars SPOT: DIR_RESCUE_DIAGNOSTICS and
DIR_VAR_LIB live in group_vars/all/05_paths.yml as plain strings; new env
handlers derive INFINITO_RESCUE_DIAGNOSTICS_DIR and INFINITO_DIR_VAR_LIB
from it for the generated .env, replacing the default.env entries, and the
play-level environment forwards the value to every task host so rescue.sh
needs no second default. CI uploads the folder like the inventory and
playwright artifacts: compose copies it out of the deploy container in
dedicated.sh, swarm captures per node (manager and both workers) into
node-named subfolders.

Deleting the ad-hoc dumps orphaned helper files (postgres inspect_container,
terminator docker_diagnostics, peertube diagnostics, matomo bootstrap debug,
xwiki and openresty rescue includes) and 14 role vars; all removed. Generic
pieces of those dumps (runtime version and info, stats, dmesg oom evidence,
swapon) moved into the SPOT script instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 03:04:06 +02:00
kevinveenbirkenbachandClaude Fable 5 0e9c1c9200 fix(peertube): route domains through sys-stk-front-proxy
The role rendered its vhost directly via stack_host_template, bypassing
sys-svc-webserver-https and sys-front-tls entirely, so no HTTPS server
block or certificate ever existed for peertube domains. Swap the loop
body to include_role: sys-stk-front-proxy per domain (mastodon pattern)
and rename the template to proxy.conf.j2 so the front proxy's fileglob
discovery picks it up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 02:37:27 +02:00
kevinveenbirkenbachandClaude Opus 4.8 c6a9eeef0b fix(web-app-peertube): run OIDC plugin container ops on the container's node in swarm
oidc/enable.yml ran the plugin check/install/enable and the two mem_limit container updates against PEERTUBE_CONTAINER_ADDRESS on the manager, but peertube lives on a worker in swarm -> "No such container". Worse, the two updates used command:, which never expands the $(...) swarm address (passed literally). Resolve the hosting node + local CID (resolve_host_cid) and delegate_to it for all five container ops, and convert the two command: tasks to shell:. Compose unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:57:31 +02:00
kevinveenbirkenbachandClaude Opus 4.8 8877c1f5dc fix(web-app-peertube): target the swarm container address for mem updates
The container update mem-limit tasks used the raw compose name, which does not exist as a container in swarm. Use PEERTUBE_CONTAINER_ADDRESS like the other container ops in the file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:28:42 +02:00
kevinveenbirkenbachandClaude Opus 4.8 95f414d196 fix(swarm): run in-container exec/inspect on the task's hosting node
container exec/inspect ran on the swarm manager while the task may run on
a worker, yielding No such container or an empty container id. Use the
existing resolve_host_cid helper (HOST_NODE + LOCAL_CID + delegate_to)
for the ollama model pre-pull, the taiga admin migrations and the
peertube post-backfill health inspect, matching the fider/matomo/decidim
pattern. The helper resolves to the local host + compose cid in compose
mode, so that path is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 12:24:18 +02:00
kevinveenbirkenbachandClaude Opus 4.8 cddb0dc59b feat(lint): require emoji-prefixed task/step/job names + convert existing
Add tests/lint/repository/test_name_emoji_and_length.py: every Ansible task
name and GitHub Actions workflow/job/step name must start with an emoji (first
char not 0-9/A-Za-z) and be <= 80 chars.

- emit emoji names at the source: cli/build/include generator now writes
  "🔧 setup <app>" / "🚿 flush handlers after <app>" so generated
  tasks/groups/*-roles.yml satisfy the convention too
- convert existing task/step/job/workflow names to emoji-prefixed, choosing a
  task-fitting icon per action (📦 install, ✅ ensure, 🔍 probe, 🥾 bootstrap,
  🚀 deploy, 🧩 service-module load, ⬇️ checkout, 📂 include-routines, ...)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 22:45:06 +02:00
kevinveenbirkenbachandClaude Opus 4.8 36ebf34b64 Merge branch 'main' into feature/swarm-nfs
Resolve 69 conflicts across ~70 files: union main's image-version bumps and new service fields (bond, resource limits) with the swarm branch's default_placement / NFS / IS_STACK_HOST config; retire the three per-deploy-type test workflows main still edited (consolidated into compose+swarm); adopt main's run-once 01_core dispatchers while re-applying each role's swarm guard. Fix the post-merge lint surfaced by main's stricter rules: bond + resource-budget keys, container-exec stack-host gating + resolver routing, leaky/dead proxy vars, suitecrm once-flag, xwiki variant nocheck, nextcloud oversized-block to gated include, matrix-mdad capped at 3g. make autoformat clean, make test green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 17:13:55 +02:00
kevinveenbirkenbachandClaude Opus 4.8 56fed2f670 fix(web-app-peertube): read the schema SQL on the controller, not the remote host
database_query opens query_file with Path().open() on the managed host. In the swarm test the managed host is a remote SSH node that does not carry the repo files, so the controller-side path '{{ role_path }}/files/sql/...' does not exist there and the query aborts with 'could not read query_file ...: No such file or directory'. Pass the SQL as content read on the controller via lookup('file', ...) instead, which works in both compose and swarm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:08:29 +02:00
kevinveenbirkenbachandClaude Opus 4.8 fb5c575823 fix(web-app-peertube): run the application-schema patch only on the stack host
The schema back-fill uses database_query, whose module opens query_file on the remote host. In swarm the play targets all nodes, so the task ran on workers that have no checked-out repo and failed with 'could not read query_file check-application-table.sql'. Move the schema patch and the OIDC plugin lifecycle into 02_stack_host.yml, included from main.yml when IS_STACK_HOST, so they run only on the stack host that owns the repo; renumber the task includes (01_create-domains, 03_fix-application-schema). Compose (IS_STACK_HOST true) is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:15:39 +02:00
kevinveenbirkenbachandClaude Opus 4.8 3bfeba1243 fix(roles): set the run-once flag as the first 01_core task for shared providers
test_service_core_first_task_run_once requires tasks/01_core.yml to set the
run_once flag (include_tasks: utils/once/flag.yml) as its very first task so the
guard is in place before the role body runs and the variant-aware loader cannot
recurse. Move the flag include from the end to the top in the shared-provider
roles that had it last (bigbluebutton, flowise, gitlab, matrix, mattermost,
moodle, openproject, openwebui, peertube, suitecrm, xwiki).

Also give the template-only providers svc-db-redis and svc-db-memcached the
run-once guard (main.yml includes 01_core.yml when run_once_<role> is undefined;
01_core.yml sets the flag) so they satisfy the shared-provider run_once guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:17 +02:00
kevinveenbirkenbachandClaude Opus 4.8 fc5ad87dc5 feat(roles): make referenced partner services proper shared providers
Declare the 14 services that consuming roles reference but no role provided
(bigbluebutton, xwiki, openproject, gitlab, mattermost, matrix, zammad,
openwebui, flowise, peertube, moodle, suitecrm, redis, memcached) as shared
providers: set enabled/shared/bond on each entity so the service registry
registers them and the dangling references resolve.

Add test_shared_service_provider.py (lint): fail when a referenced shared
service has no provider and its name matches a role's entity/provider that does
not register it (suppressable via '# nocheck: shared-provider').

Guard the 9 web-app shared services with the run-once 01_core.yml pattern
(main.yml includes 01_core.yml only when run_once_<role> is undefined; the core
flags itself via utils/once/flag.yml) so they deploy once when pulled in by
multiple consumers. svc-db-redis/memcached are template-only (no runtime tasks).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:17 +02:00
kevinveenbirkenbachandClaude Opus 4.7 a4cb5b89e1 feat(swarm): swarm-aware container exec via container_address lookup
Adds a mode-aware `container_address` lookup plugin and a
`resolve-container-id` helper script that resolve swarm task IDs at
exec time (compose mode returns bare names unchanged). Migrates ~25
roles + ~28 task files from `command:` to `shell:` so the lookup's
`$(...)` subshell evaluates correctly, and extends `database` lookup
with an `address` key for cross-role DB exec.

Three new lint tests enforce the contract: every container exec
target goes through the resolver, lives behind an IS_STACK_HOST gate
(walks the include graph one hop deep), and uses `shell:` rather than
`command:` when the target is a Jinja reference. Existing nocheck
suppressions on bare-name DB exec sites (nextcloud redis, listmonk,
moodle OIDC, mailu) are replaced with the lookup-based form.

Fixes the systemd Environment= injection in sys-ctl-cln-disc-space
(it never expanded $(...)) by passing STACK / SERVICE_KEY parts and
resolving inside the script. Extracts shopware admin and mediawiki
extension install bodies into dedicated .sh files invoked via env.
Rewrites password-bearing shell tasks to either `| quote` directly or
the `-e VAR=... | quote` + `sh -c '... ${VAR} ...'` env-bridge pattern.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 03:46:28 +02:00
kevinveenbirkenbachandClaude Opus 4.7 05f6a97bd8 refactor(stack_host_template): generalise IS_STACK_HOST template wrapper across the codebase
Renames the local action plugin from `conf_template` to
`stack_host_template` so the name carries the actual semantics
(IS_STACK_HOST gate, not nginx config), broadens the lint guard to
every bare `template:` task, and migrates every consumer found in the
audit.

Plugin / tests / lint:
  * plugins/action/conf_template.py -> plugins/action/stack_host_template.py
    (drop nginx-specific docstring; trust_as_template gate unchanged)
  * test_conf_template.py            -> test_stack_host_template.py
  * test_conf_template_migration.py  -> test_stack_host_template_migration.py
    Scope expanded from `.conf.j2` to every `template:` task; marker
    renamed to `nocheck: stack-host-template`.

Migrations (~50 tasks now using stack_host_template:):
  * sys-svc-compose/04_files.yml: compose.yml, .env, Dockerfile,
    compose.override (every per-app compose-tree file)
  * web-app-keycloak: sso_proxy upstream-config (fixes the friendica
    CI failure that prompted this refactor) + realm-import
  * web-app-taiga, web-app-matrix, web-app-opentalk, web-app-pgadmin,
    web-app-moodle, web-app-listmonk, web-app-littlejs, web-app-fediwall,
    web-app-wordpress, web-app-discourse, web-app-bigbluebutton,
    web-app-jenkins, web-app-jitsi, web-app-mobilizon, web-app-suitecrm,
    web-app-decidim, web-app-dashboard, web-app-nextcloud, web-app-xwiki,
    web-app-mailu, web-app-magento, web-app-odoo, web-app-prometheus,
    web-app-matrix, web-app-peertube, web-app-mailu (+ existing `conf_template:`
    callers updated to the new name)
  * sys-front-tls, svc-db-openldap, sys-svc-webserver-core,
    sys-svc-rdbms (dedicated DB env + initdb), sys-front-inj-css,
    sys-front-inj-dashboard, sys-front-inj-all, sys-stk-front-proxy,
    web-svc-cdn-css, web-svc-xmpp, web-svc-legal, web-svc-simpleicons,
    web-opt-rdr-domains

Nochecks (per-host or already-structurally-stack-host):
  * sys-svc-mail-smtp/postfix, sys-svc-sshd, sys-svc-container/daemon.json
  * sys-timer, sys-service, sys-svc-mail-msmtp, sys-svc-journalctl,
    sys-daemon, sys-svc-compose/utils/_render_one_template (delegate_to
    stack host with peer slurp dance)
  * dev-locales, dev-nix, desk-*, user, user-backup, user-workstation
  * svc-storage-nfs-server/exports, test-e2e-playwright stage dirs

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 15:45:38 +02:00
kevinveenbirkenbachandClaude Opus 4.7 2921ab6f03 feat(ansible): conf_template action plugin enforces IS_STACK_HOST gate
Adds plugins/action/conf_template.py, a drop-in `template:` replacement
that returns `skipped: true` whenever IS_STACK_HOST evaluates to false.
Routes the rendered Jinja through ansible.module_utils.parsing.convert_bool
so both Python bool and string "True"/"False" templar outputs resolve
correctly. Inherits every standard template module argument.

Migrates seven nginx-tree consumers that previously aborted on workers
with "Destination directory does not exist": sys-front-tls,
svc-db-openldap, sys-svc-webserver-core (nginx.conf + default_server.conf),
web-app-matrix (synapse vhost), web-app-peertube (per-domain vhost),
web-opt-rdr-domains (redirect vhost), and web-app-prometheus.

Tags twelve legitimate bare `template:` callers with
`# nocheck: conf-template`: per-host system configs (locale, nix, systemd
manager defaults, journald, msmtprc) and per-app compose-dir configs
(magento x2, mailu x2, moodle, odoo, taiga) that intentionally bypass the
project's nginx tree.

Adds tests/lint/ansible/modules/test_conf_template_migration.py as the
drift guard: any future `template:` task whose `src:` ends in `.conf.j2`
must adopt conf_template or carry the nocheck marker. Adds seven unit
tests for the plugin covering skip semantics, dispatch, mode defaults,
mode overrides, full-arg passthrough, and the string-bool templar path.

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

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

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:43:43 +02:00
kevinveenbirkenbachandClaude Opus 4.7 4b05105e17 feat(database_query): replace static no_log: True with caller-controlled mask_values param
- `library/database_query.py`: drop `no_log: True` from `config` /
  `named_args` argument_spec; add `mask_values` bool param (default
  True). When True, the module copies their string values into
  `module.no_log_values` at runtime — same effective redaction as
  before, but now toggleable per task.
- Roll out `mask_values: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"` to
  every `database_query` call (38 sites across web-app-{odoo,
  openproject, peertube}) so the playbook constant in
  group_vars/all/00_general.yml drives the redaction end-to-end. Set
  it to false on demand for debugging unredacted rc/stderr.

Bundled: roles/web-app-openproject/tasks/02_ldap.yml also drops the
`async:`/`poll:` lines from Update/Create LDAP auth source. The
combination of `async` + `database_query` local-action + `no_log`
masked the async wrapper's result fetch with "Action failed: Unknown
error" while the SQL had actually succeeded (verified: 1 row in
`ldap_auth_sources` after the failing run). Sync execution matches the
pattern used by every other `database_query` caller in the repo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 01:15:04 +02:00
kevinveenbirkenbachandClaude Opus 4.7 052b9a6af4 fix(web-app-peertube/fix-application-schema): coerce rows to bool in until clause
Newer Ansible refuses to bool-coerce list values in conditional contexts
(`Conditional result (False) was derived from value of type 'list'`).
The `until:` block of the wait-for-application-table task used
`(_peertube_app_table_check.rows | default([]))` directly as a sub-clause,
which is a bare list. Wrap with `| length > 0` for an explicit bool result.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:54:10 +02:00
kevinveenbirkenbachandClaude Opus 4.7 017f67cd53 feat(plugins): introduce database_query module + migrate all SQL query tasks
Replaces controller-side `community.postgresql.postgresql_query` /
`community.mysql.mysql_query` calls with a project-local `database_query`
Ansible module that wraps `container exec -i <container> <psql|mysql>`
and pipes SQL via stdin. The module routes via the DB container's own
network so the same task works identically in shared mode (central
`svc-db-*` container) and non-shared mode (per-role `<entity>-database`
container) — the `container` field of `lookup('database', …)` is
shared-aware and the module just reads it. SQL payloads come from a
file (`query_file:`, preferred) or as a raw string (`query:`, escape
hatch). `named_args:` substitutes `%(name)s` placeholders with engine-
appropriate escaping (postgres single-quote-doubling, mariadb backslash
escapes), so role tasks stop string-interpolating Jinja into SQL.

New backends extend the module's `_ENGINES` dispatch table; everything
else is engine-agnostic. The module ships at `plugins/modules/` (Ansible
collection-style layout) — the shebang lint test learned to recognise
that path as a valid module location alongside the existing per-role
`library/` layout.

Two lint tests enforce the convention:

* `tests/lint/ansible/modules/test_no_raw_db_query_module_usage.py`
  forbids `community.postgresql.postgresql_query` /
  `community.mysql.mysql_query` in role tasks. Opt out per-line with
  `# nocheck: database-query` (used by `svc-db-postgres/tasks/02_init.yml`
  where the role manages its own DB and the `database` lookup would be
  self-referential).
* `tests/lint/ansible/modules/test_no_raw_database_query_string.py`
  forbids inline `query:` on `database_query:` tasks. Opt out with
  `# nocheck: database-query-raw` (no opt-outs in the current tree —
  every SQL payload reaches the module through a `.sql` file under
  `files/sql/`).

Role migrations:

* `web-app-peertube` — 3 tasks migrated, 3 new `.sql` files,
  `templates/upsert-oidc-plugin.sql.j2` deleted in favour of
  `files/sql/upsert-oidc-plugin.sql` + `named_args:`.
* `web-app-openproject` — 3 tasks migrated, 3 new `.sql` files for
  the LDAP-auth-source check/update/insert chain.
* `web-app-odoo` — 33 tasks migrated, 9 new `.sql` files for the
  LDAP / admin / init-database flows; 17 pre-existing OIDC `.sql`
  files renamed snake_case → kebab-case and relocated from
  `files/oidc/` to `files/sql/oidc/` (via `git mv` so rename history
  stays intact).

Unit tests cover every dispatch-table entry, the named_args escaper
(strings, ints, floats, bools, None, single-quote and backslash
injection vectors), and the password-redactor that hides
PGPASSWORD/MYSQL_PWD assignments from the module's returned `cmd`
field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 20:12:08 +02:00
kevinveenbirkenbachandClaude Opus 4.7 2a023ca6a1 chore(lint): autoformat strict-failure semantics + claude.sh rename
* scripts/lint/autoformat.sh:
  - rename run_sort_claude_settings worker to run_claude (matches the
    renamed scripts/lint/claude.sh).
  - drop the EXIT trap that wiped status_dir; cleanup happens explicitly
    after `wait` returns, so a parallel worker that interrupts the
    parent never races a vanished status dir.
  - each worker now captures its tool's exit code and writes one of
    OK / SKIP / FAIL. Tolerated `expected violations remain`-style exits
    (markdownlint-cli2 rc≤1, ansible-lint rc≤2) still count as OK; only
    real failures count as FAIL.
  - all workers run to completion regardless of individual tool failures
    (so the user always sees the full picture); after the summary, the
    script exits 1 if any worker reported FAIL.
* scripts/lint/sort_claude_settings.sh -> scripts/lint/claude.sh
  (history preserved via git mv); update test_claude_settings_sorted.py
  fix-hint accordingly.

Plus working-tree drift across roles/, group_vars/, inventories/,
requirements/, and .pre-commit-config.yaml accumulated from the
make-test setup phase regenerating generated artefacts during recent
test runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 01:30:49 +02:00
kevinveenbirkenbachandClaude Opus 4.7 c52e4269bf chore(tooling): integrate mbake (Makefile linter/formatter) + working-tree drift sync
Makefile lint integration:
* scripts/install/lint.sh: replace the prior checkmake binary install with
  ensure_mbake (mbake is pip-installable, parses our Makefile correctly
  whereas checkmake bailed on .ONESHELL: / .SHELLFLAGS: / ifneq).
* scripts/lint/makefile.sh: new runner — `mbake format --check Makefile`
  reports formatting drift and missing .PHONY declarations.
* scripts/lint/autoformat.sh: add a two-pass `mbake format Makefile`
  (mbake's formatter is not strictly idempotent on the first pass).
* Makefile: split the multi-line `.PHONY:` block into 18 single-line
  `.PHONY:` declarations so mbake's parser can validate it; add the
  previously-missing `install-python-dev` and `build-dependency` PHONY
  targets; add `lint-makefile` aggregate-and-direct target.
* .pre-commit-config.yaml: new make-lint-makefile hook scoped to
  `^Makefile$`.
* .github/workflows/lint-makefile.yml: new CI workflow (pip-install mbake
  + make lint-makefile).
* .github/workflows/ci-orchestrator.yml: wire lint-makefile into
  lint-gate.needs.

Misc:
* .gitignore: add roles/importer_result.json (tooling artefact written by
  galaxy-importer when it runs in roles/).

Plus working-tree drift across roles/, group_vars/, inventories/, and
tasks/ that accumulated from the make-test setup phase regenerating
generated artefacts during recent test runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:32:44 +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 e40ff954f0 fix(web-app-peertube): back-fill application-table NOT NULL defaults
Peertube v8.x (and intermittently under the mutable `production-bookworm`
tag) ships a migration that declares `application.nodeVersion` and
`application.nodeABIVersion` as NOT NULL without column defaults, while
the bootstrap `Application.loadApplication()` only INSERTs
`migrationVersion` into the single-row `application` table. On a fresh
database that INSERT fails with

  null value in column "nodeVersion" of relation "application"
  violates not-null constraint

and peertube crash-loops before it can add the OIDC plugin or serve
traffic. The failure blocks both the local fresh-purged deploy and the
corresponding CI job.

Patch it at the database level once peertube has created the table on
its first crashing startup: wait for `public.application` to exist, run
an ALTER TABLE that sets defaults on the two offending columns
(`nodeVersion='', nodeABIVersion=0`), and wait for the container to
come back healthy. Peertube itself overwrites the values with real
node/ABI version strings on every subsequent boot, so the defaults only
cover the bootstrap INSERT.

Validated via make deploy-fresh-purged-apps APPS="web-app-joomla
web-app-peertube" SERVICES_DISABLED="matomo,email" FULL_CYCLE=true:
Pass 1 ok=1576 failed=0, Pass 2 ok=1444 failed=0, peertube 3/3
Playwright scenarios green in both passes.
2026-04-23 12:45:37 +02:00
kevinveenbirkenbach 9b5de4e477 refactor(runtime): replace DictRenderer and domains fact with cached lookups
Removes the placeholder interpreter (utils/dict_renderer.py) in favor of
standard {{ lookup('config', ...) }} / {{ lookup('domain', ...) }} calls
inside role configs, and eliminates the `domains` set_fact stage in
tasks/stages/01_constructor.yml by introducing a cached get_merged_domains
helper plus a new `domains` lookup plugin (full map + by-app subtree).

The `domain` lookup no longer depends on a pre-computed `domains` variable;
it delegates to get_merged_domains, which respects inventory-level overrides
from both `applications:` (authoritative canonical source) and `domains:`
(direct override map). Caching mirrors the applications pipeline — keyed on
(roles_dir, variables signature, override fingerprint) with a re-entry guard.

Replaced `{{ domains[app_id] }}` / `{{ domains }}` usages across web-app-matrix,
-bluesky, -mastodon, -peertube, -keycloak, test-e2e-playwright, and
group_vars. Added two performance tests for lookup('domains') mirroring the
applications cache assertions (≥50x warm-to-hot ratio, 120s cold budget).
2026-04-21 13:29:30 +02:00
kevinveenbirkenbachandClaude Opus 4.7 97ee7e65ea Bump peertube cgroup mem_limit during plugin install
Plugin install via `container exec` shares the cgroup with the long-running
peertube node process. Combined RSS was tripping the 8g mem_limit and
SIGKILLing peertube, which surfaced as "Container is restarting" on
subsequent exec attempts.

- Temp raise mem_limit + unlimited swap for the install window, restore
  the compose-defined value on the happy path only
- Derive the bumped ceiling from PEERTUBE_MEM_LIMIT + install heap cap
  via a new role-local `install_mem_limit` filter (overhead=4 covers
  native RSS and spawned npm/node-gyp workers)
- Extract the SQL upsert into templates/upsert-oidc-plugin.sql.j2
- Move enable/disable OIDC tasks under tasks/oidc/ and rename the
  diagnostics script
- Document the extract-code convention in docs/contributing/tools/ansible.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 22:55:08 +02:00
kevinveenbirkenbachandClaude Opus 4.7 6b5e4356a6 Add peertube plugin-install diagnostics on failure
Wraps the auth-openid-connect plugin install in block/rescue so that when
the container exec trips (e.g. "Container is restarting", cgroup OOM kill),
a dedicated script dumps container state, health, resource limits, stats,
last 250 log lines, host memory+swap, and dmesg OOM-killer evidence before
the task fails explicitly. Makes the actual cause visible in CI without
another round-trip.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 22:05:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 af4e4b6bea fix(web-app-peertube): cap plugin-install heap + retry on transient OOM
`npm run plugin:install` inherits the container-wide NODE_OPTIONS (~35%
of mem_limit, multi-GB) and pushes constrained CI runners over their
host RAM, getting SIGKILLed (rc=137) a few seconds in. Override
NODE_OPTIONS just for the install exec with a tight heap cap
(PEERTUBE_OIDC_PLUGIN_INSTALL_MAX_OLD_SPACE_MB, default 1024 MB) and
add 3 retries (15s delay) to absorb transient host memory pressure.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 03:22:40 +02:00
kevinveenbirkenbach 0a7b4efa22 Replaced docker compose pull by compose pull 2026-02-06 23:26:20 +01:00
kevinveenbirkenbach c1d50d78da refactor(container): replace BIN_CONTAINER usages with system-wide container wrapper
https://chatgpt.com/share/698660b7-4c78-800f-b25f-af5408cff034
2026-02-06 23:00:21 +01:00
kevinveenbirkenbach 46238d4c89 refactor: use BIN_CONTAINER wrapper for container exec commands
https://chatgpt.com/share/698657ee-e708-800f-ba5d-4c7c5198a5fe
2026-02-06 22:06:42 +01:00
kevinveenbirkenbach 48237d758c Rename nginx_paths lookup to nginx and update all references
* Rename lookup plugin: lookup_plugins/nginx_paths.py -> lookup_plugins/nginx.py
* Update lookup usage from lookup('nginx_paths', ...) to lookup('nginx', ...) across roles/templates/vars
* Update unit tests accordingly (imports + patch targets)

https://chatgpt.com/share/6985e705-4040-800f-a779-347f06e46f82
2026-02-06 14:05:04 +01:00
kevinveenbirkenbach a367892481 fix(nginx_paths): enforce strict want-path ordering for domain lookups
Update all remaining nginx_paths lookups to use the strict positional API:
lookup('nginx_paths', want_path, domain)

This removes legacy argument ordering (domain first) and aligns all callers
with the documented nginx_paths contract.

https://chatgpt.com/share/6985d642-b40c-800f-9cdf-ed54fc484a92
2026-02-06 13:41:10 +01:00
kevinveenbirkenbach 5e33e70892 refactor(database lookup usage): switch conditionals to enabled flag
Replace indirect type!='' checks with explicit database.enabled lookups
to improve semantics, readability, and alignment with strict lookup API.

https://chatgpt.com/share/6985d642-b40c-800f-9cdf-ed54fc484a92
2026-02-06 13:25:37 +01:00
kevinveenbirkenbach 6af75fc8ce Renamed webserver_websocket_port to websocket_port 2026-02-05 17:02:17 +01:00
kevinveenbirkenbach f6f96a11ac Refactor: use database lookup helper everywhere
https://chatgpt.com/share/6983bcf6-85bc-800f-a0fc-5684b9078469
2026-02-04 22:52:08 +01:00
kevinveenbirkenbach 8a81630391 Added newline at file and POSIX compatible 2026-02-04 19:49:56 +01:00
kevinveenbirkenbach 82e2cde747 refactor(nginx): replace NGINX vars with nginx_paths lookup and add http/https support
- Drop legacy NGINX path definitions from group_vars
- Add nginx_paths lookup plugin (paths + ensure dirs + http_includes)
- Update roles/templates to use nginx_paths for config/data/cache/user paths
- Add per-domain HTTP->HTTPS redirect + ACME challenge include
- Add unit tests for nginx_paths lookup
- Clean up obsolete sys-svc-letsencrypt role files

https://chatgpt.com/share/697d4466-af54-800f-a243-b28367638c85
2026-01-31 00:52:42 +01:00
kevinveenbirkenbach 9fdb0cddd0 Replace global WEB_PROTOCOL / WEB_PORT / WEBSOCKET_PROTOCOL logic
with explicit per-application TLS resolution.

Key changes:
- Introduce sys-front-tls-layer as the single TLS abstraction
- Centralize certificate paths via TLS_CERT_FILE / TLS_KEY_FILE / TLS_CA_FILE
- Replace sys-svc-letsencrypt SSL templates with generic TLS layer includes
- Add tls_* Jinja filters (enabled, mode, protocol, port, websocket)
- Resolve TLS mode per application (letsencrypt | self_signed | off)
- Remove implicit defaults and WEB_* globals to fail fast on misconfiguration
- Support self-signed certificates with SAN awareness
- Make TLS behavior explicit and consistent across all roles

This decouples application logic from certificate providers and
allows switching TLS flavor without touching app roles.

https://chatgpt.com/share/69727c61-8e68-800f-a267-473a7e627978
2026-01-22 20:36:49 +01:00
kevinveenbirkenbach 5f60e7ba2a refactor(database): introduce database_reach_host and decouple from DOCKER_REACH_HOST
Add dedicated database_reach_host variable to sys-svc-rdbms and use it for
direct database access in application roles.

This separates control-plane database access from generic Docker reach logic
and improves portability and CI stability.

Updated:
- sys-svc-rdbms vars: add database_reach_host
- web-app-openproject: use database_reach_host for LDAP DB queries
- web-app-peertube: use database_reach_host for OIDC DB updates

https://chatgpt.com/share/696863e8-4370-800f-bea0-ad48b98ba0f6
2026-01-15 04:49:55 +01:00
kevinveenbirkenbach 989c0c0fa2 refactor(stacks): merge stateful/stateless backend+full stacks into sys-stk-backend/sys-stk-full
- Replace sys-stk-back-stateful + sys-stk-back-stateless with unified sys-stk-backend role
- Replace sys-stk-full-stateful + sys-stk-full-stateless with unified sys-stk-full role
- Add reusable task helpers: _load_service.yml (optional deps) and 01_database.yml (DB bootstrap)
- Switch optional dependency loading (OpenLDAP, Ollama) to service_load_enabled (enabled && shared) + run_once guard
- Remove legacy OLLAMA_LOCAL_ENABLED flag; rely on docker.services.ollama.enabled/shared config
- Update all affected app/service roles and web-app template to use new stack role names

https://chatgpt.com/share/69685fb2-cb1c-800f-a9e8-2f5f765a1a53
2026-01-15 04:31:54 +01:00
kevinveenbirkenbach 616a629ed1 Removed falsy TLS gatekeeper 2026-01-09 22:42:36 +01:00