Commit Graph
54 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5.5 c2577560f8 fix(swarm): bound the in-container probes and retry only safe fider POSTs
Review follow-up on the in-container probes of gitea, shopware and fider.

- fider: the tenant POST over /dev/tcp waited on `head` without a bound,
  so a peer that accepts and stays silent blocked the task for good. It
  now runs under `timeout` (TIMEOUT_FACTOR-scaled 30 s, the old uri
  socket timeout) and reads the status line with the `read` builtin, so
  the bound kills the whole probe. Only outcomes Fider never committed
  are retried: a refused connect and a 5xx (Fider rolls the request
  transaction back on a handler error). A timeout or a closed socket
  after the request was sent fails the task instead of resending the
  POST; the next run's tenant count decides again.
- fider: both bootstrap tasks get `no_log` behind MASK_CREDENTIALS_IN_LOGS,
  as the body carries the administrator's name and email.
- shopware: each wget probe runs under `timeout`, as busybox wget waits
  900 s per read by default.
- shopware, gitea: the first readiness wait derives its retries from
  lookup('timeout', 300) again, the budget of the wait_for it replaced,
  so TIMEOUT_FACTOR scales it.

Refs #1089

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 01:41:55 +01:00
kevinveenbirkenbachandClaude Opus 5.5 c59e2838d6 fix(swarm): reach gitea, shopware and fider from inside their containers
In swarm, `docker stack deploy` drops the host IP of a short port mapping,
so a `127.0.0.1:<ports.local>` publish lands on the ingress routing mesh
on every node. The provisioning of these three roles only worked because
the mesh answered on the host:

- web-app-gitea waited for `DOCKER_REACH_HOST:<ports.local.http>` with
  wait_for and polled `/api/v1/version` there with uri_retry.
- web-app-shopware waited for `DOCKER_REACH_HOST:<ports.local.http>`.
- web-app-fider bootstrapped its first tenant in swarm by POSTing to
  `127.0.0.1:<ports.local.http>` on the hosting node.

As long as these calls depend on the mesh, container_ports cannot stop
publishing loopback ports in swarm. The probes now run inside the
service's own container through the resolved `<PREFIX>_LOCAL_CID` on
`<PREFIX>_HOST_NODE`, against the internal port:

- Gitea uses the curl it ships. The first wait accepts any HTTP answer,
  as wait_for did. The post-migration wait still requires 200.
- Shopware runs the wget probe its compose healthcheck already uses.
- Fider ships no HTTP client, so bash writes the tenant POST to
  /dev/tcp. The body comes in on stdin and the status line is checked
  for 200/201.

Fider's compose path is unchanged. The compose render of all three roles
is unchanged. The host-side ingress guard from svc-swarm-node stays in
place until the remaining host-side consumers are moved.

Refs #1089

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 00:01:20 +01:00
kevinveenbirkenbachandClaude Opus 5 bc1901c5fc fix(web-app-shopware): fit the adminopenauth task name into the 80-char lint
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 23:28:16 +02:00
kevinveenbirkenbachandClaude Opus 5 acc9e25c0f refactor(volumes): one canonical mount surface, context-wired lookups
The mount pipeline was split across two layers: compose_volumes and
container_volumes lived as filter modules that ansible's filter loader
rejected on every start ("has no attribute 'FilterModule'", two warnings
per run), while the same-named lookups wrapped them thinly. The filter
bodies now live inside their lookups (the tor_socks precedent), the
filter files are gone, and the render tests moved next to the lookup
tests.

As lookups they see the templating context, so the application_id term
every call site passed back to its own role is gone too: both lookups
read application_id from context and fail loudly when it is missing.
107 templates and nfs_prep.yml drop the term; the calls collapse to
lookup('compose_volumes') and lookup('container_volumes', service_name).

The container_volumes call is now canonical - four leading spaces,
service_name in the service position, | indent(4) - enforced by a new
lint (test_container_volumes_call_shape). Normalizing the 85 literal
call sites surfaced that erpnext keyed its meta/volumes.yml mounts by
container name instead of compose service key, the only role to do so;
its meta now uses the service keys. wordpress's column-0 call, which
rendered its first line at top level, is indented like every other.

Template logic that only restated meta/volumes.yml is deleted: mailu fed
compose_volumes an extra_volumes dict whose eight names were looked up
from the very meta file compose_volumes reads itself; zammad's init and
railsserver extra mounts were the volumes.yml entries minus the two
services, which the meta now lists (the when-gated OIDC binds included).
Conditionality that meta cannot express stays in code, but in role-local
lookup plugins instead of template loops: matrix_bridge_mounts renders
the per-bridge registration mounts, nextcloud_extra_volumes the
feature-gated whiteboard/recording volumes (type: volume entries are
declared unconditionally by compose_volumes, so gating must happen
before it).

Parity is proven, not assumed: every mechanically rewritten template is
byte-identical to HEAD after the transformation regexes (95/95), and the
semantic roles render identically through the real functions against
HEAD's meta - zammad across six sso/tls polarities, all seven erpnext
services, mailu's top level. make test-lint passes complete.

Also: suitecrm's compose template pins container_extra_hosts with
host_alias (the SOCKS proxy URL host.docker.internal never resolved in
compose+tor), and the five disabled mautrix bridge addons use the
empty-default config lookup so templating the applications dict stops
emitting 125 errors per batch in every CI job.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 23:28:16 +02:00
kevinveenbirkenbachandClaude Opus 5 dcb921c260 fix(web-app-shopware): address the OIDC provider by its service name
container_address resolves a container id for exec and emits a $(...) subshell in swarm; setup/oidc.yml passes the value through shlex.quote, so the substitution never ran and client.php wrote the literal text into token_endpoint and userinfo_endpoint. The deploy stayed green and every admin SSO login failed at the code-to-token exchange. container_service is the maintained lookup for the addressable form and yields keycloak in compose, keycloak_keycloak in swarm.

init.sh no longer waits on the S3 endpoint: c5a149b11 unmounted the config it guarded, while a hard fail there still took web, worker and scheduler with it. The admin-bundle check now runs unconditionally — it was skipped exactly when objstore was on, i.e. in the only variant with SSO, although the bundles are local either way. The two objstore env vars lost their last consumer with it.

no_log now sits on the two tasks that carry the admin password and the OIDC client secret; handing it through include_tasks apply: does not censor them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 18:02:30 +02:00
kevinveenbirkenbachandClaude Opus 5 46686d72ad fix(web-app-shopware): keep OIDC admins in possession of their rights
AdminOpenAuth clears the admin flag and the ACL roles on every login that no role-assignment rule matched (UserResolver::getUserChangeSet). The Playwright login therefore demoted the provisioned administrator, the admin backend rendered empty, and the next provisioning run answered 403 FRAMEWORK__MISSING_PRIVILEGE_ERROR. The client now carries a rule with userBecomeAdmin, and a separate step re-asserts the flag for accounts an earlier login already demoted, which no console command can do.

discoveryDocumentUrl stays empty because a successful discovery assigns the document over the explicit endpoints and moves the back channel onto the .onion host cURL refuses to resolve.

system:install --basic-setup provisions admin/shopware with a published password; that account is locked on first install. shopware.yaml is mounted again after being rendered but unmounted since the mounts moved into meta/volumes.yml. files/ is split into php/ and shell/ trees.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 18:02:28 +02:00
kevinveenbirkenbachandClaude Opus 5 2580de62da fix(web-app-shopware): move the internal OIDC base into a variable
The previous commit glued a literal http:// onto a lookup inside the shell cmd, which test_literal_protocol_with_lookup rejects: the protocol must come from the tls lookup rather than be guessed. Here the exception is real -- this is a container-to-container hop that must bypass the canonical URL, because cURL refuses the .onion name the tls lookup returns -- so the value moves into SHOPWARE_OIDC_INTERNAL_BASE where the suppression sits on the offending line as an actual YAML comment.

The first attempt put the nocheck inside the cmd folded block, where # is not a comment: YAML folds it into the string, so the deploy would have run container exec with a dozen words of prose as arguments while the lint reported green. Reading the folded scalar back is what caught it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 18:02:25 +02:00
kevinveenbirkenbachandClaude Opus 5 47601d59d0 fix(web-app-shopware): split the OIDC front and back channel
The keycloak provider derives every endpoint from one auth-server-url, so both the browser redirect and the server-side calls pointed at the onion. cURL refuses .onion outright under RFC 7686 -- it never opens a socket -- so discovery failed, the authorization endpoint stayed empty and the login button redirected to itself.

The generic open_id_connect provider takes the endpoints individually. The authorization endpoint keeps the onion, which is the address the browser reaches through Tor; token and userinfo point at the internal service name, which cURL accepts. discoveryDocumentUrl still names the onion and therefore fails, which is what leaves the explicit endpoints in place -- discoverWellKnown overwrites them whenever it succeeds, and Keycloak advertises unusable values under every address it is asked through.

Verified in the running container before any redeploy: patching the live client row to this shape makes GET /admin/open-auth/<id>/remote answer 307 to the Keycloak authorization endpoint instead of to itself, and the browser now completes the Keycloak login -- the spec fails afterwards on the admin menu rather than on the login form. The payload was checked against a stub first and matches the live patch byte for byte.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 18:02:25 +02:00
kevinveenbirkenbachandClaude Opus 5 0962037687 fix(web-app-shopware): drop the calls to a console command Shopware does not ship
bin/console user:update does not exist. The installed shopware/core ships exactly UserCreateCommand, UserChangePasswordCommand and UserListCommand. Both call sites hid the failure: one with || true, the other with || true and 2>/dev/null on top.

rename_admin_user.sh consisted of nothing else. It found the default admin user, printed Renaming: admin -> administrator and then did nothing, so the message was a false statement on every deploy. Script and task are removed. In ensure/admin_user.sh the user:update --email call goes too; user:create already passes --email and that works -- the administrator in the running stack carries administrator@infinito.example, read back through the admin API.

This is behaviour-identical: the removed calls never did anything. What it does change is visibility -- the Shopware default user admin with user@example.com survives every install, which is exactly what the rename was meant to prevent. Shopware offers no console command for that, so closing it needs the admin API and a decision about the account; that is deliberately not part of this commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 18:02:23 +02:00
kevinveenbirkenbachandClaude Opus 5 cace98976b fix(web-app-shopware): let the OIDC client reach a plaintext identity provider
AdminOpenAuth ships heptacom.admin_open_auth.oidc.http_client with protocols: [https]. An onion carries no TLS -- Tor encrypts the circuit itself -- so guzzle aborted OIDC discovery inside CurlFactory before opening a socket. Discovery never ran, getAuthorizationEndpoint stayed empty, and getLoginUrl returned a bare query string that the browser resolved against the current URL: the admin login button redirected to itself until the test timed out on a blank page. No request ever reached the reverse proxy, and the only trace was a warning in the container-local var/log/dev.log.

A project-level config/packages template redefines that one service id. It grants http only when the resolved provider URL is itself plaintext, so a clearnet deployment keeps the https-only guarantee untouched; the condition keys on the actual scheme rather than on the tor flag, because the scheme is what guzzle rejects. Discovery, token, userinfo and jwks all flow through this single client, so one override suffices; base_http_client and saml.http_client are left alone. Deployment and mount mirror framework.yaml.

Four independent skeptics attacked the earlier claim that this was a hard third-party constraint and refuted it: the pin is an ordinary Symfony service id, Shopware loads project configuration after plugin bundles, and config/packages/http_discovery.yaml demonstrably compiles its services block into the running container. Both branches of the template condition were rendered through Ansible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 18:02:23 +02:00
kevinveenbirkenbachandClaude Opus 5 8d03cc71d3 fix(web-app-shopware): clear the cache after the OIDC client exists, not before
plugin:install carried --clearCache, so the cache was emptied while the AdminOpenAuth client row did not exist yet. Whatever the plugin had cached about having no configuration survived the only clear the deploy performed. The clear moves into its own task behind the provisioning step, which is the first moment there is anything worth caching.

This does NOT make variant 0 green, and the ordering was verified not to: after deploying it, GET /admin/open-auth/<id>/remote still answers 307 to itself and the reverse proxy still logs zero discovery requests. The blocker sits one layer down -- the plugin pins its Guzzle clients to protocols: [https] in its own DI definition while an onion service runs without TLS, so the discovery request is rejected before it reaches the network. Shopware records that as a warning in var/log/dev.log: the scheme http is not allowed by the protocols request option.

The ordering is still correct on its own terms: once that blocker is resolved, clearing before the write would be a no-op again and the failure would return as a follow-up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 18:02:22 +02:00
kevinveenbirkenbachandClaude Opus 5 d5b6e90a8c feat(web-app-shopware): authenticate the admin against Keycloak via AdminOpenAuth
meta/services.yml pinned sso.flavor: oauth2, so an oauth2-proxy fronted the whole role. That gated the storefront too and left /admin behind a proxy the administrator persona could not log out of, while tasks/setup/oidc.yml -- the only OIDC wiring -- installed a literal placeholder plugin name behind an || true and wrote InfxOidc.config.* keys that no plugin reads. tasks/03_setup.yml and tasks/04_cleanup.yml were included by nothing, so none of it ever ran.

The flavor moves to oidc, which drops the proxy on its own because sso_proxy.yml keys off is_proxy_gated: the storefront becomes public and /admin authenticates through heptacom/shopware-platform-admin-open-auth against Keycloak. The plugin is baked into files/Dockerfile beside the existing composer require, installed and activated by tasks/setup/oidc.yml, and its Keycloak client row is written idempotently by files/ensure/oidc_client.php over the Admin API -- GET by provider, then PATCH or POST -- because the plugin keeps its configuration in the heptacom_admin_open_auth_client DAL entity, not in system_config.

The web service gains container_extra_hosts. With the proxy gone the token exchange runs inside the shopware container, which under swarm resolves clearnet only and could not reach the keycloak onion.

The LDAP plugin tasks are deleted rather than wired. No admin-LDAP extension for 6.7 is installable unattended -- the only real one is a paid Store extension needing a licence token -- and arming tasks/03_setup.yml would have fired the INFX_LDAP_PLUGIN placeholder on every deploy that ships OpenLDAP, since meta/services.yml derives ldap.enabled from group membership. Directory identities reach the backend through the Keycloak LDAP federation instead.

PERSONA_BIBER_BLOCKED becomes unconditional: biber has an account in no variant. test-admin-native.js keeps the authenticated journey and now drives the AdminOpenAuth provider link when SSO is on.

Verified by execution: the provisioning payload and both idempotency branches against a stub; composer resolving 9.1.0 against shopware/core 6.7.13.0 without conflict; the install gate rejecting a no-plugins-found run and accepting both success shapes.

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

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:01:31 +02:00
kevinveenbirkenbachandClaude Opus 4.8 32d479c248 fix(keycloak,shopware): double the converge wait budget for manager ops
Same class as the mattermost bump: the 60x5s override lost against slow first boots under CI load (keycloak gated on health=healthy); 120x5s stays well under the resolve util's 600x2s default and still fails hard after the budget.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 14:44:35 +02:00
kevinveenbirkenbachandClaude Opus 4.8 5ab047c4f4 fix(web-app-shopware): run the admin scripts via stdin on the resolved node
01_admin.yml redirected controller-side files ({{ role_path }}/files/*.sh) into container exec; delegated to a swarm node that path does not exist ('/home/runner/...: No such file or directory'). Resolve the web service's hosting node + local cid first, delegate the include there via apply, and feed the scripts through the shell module's stdin so the controller reads them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 03:21:55 +02:00
kevinveenbirkenbachandClaude Opus 4.8 96aa7784b6 feat(sys-svc-compose): scale all task timeouts via TIMEOUT_FACTOR lookup
Add a 'timeout' lookup plugin that returns round(base * TIMEOUT_FACTOR)
(group_vars default 1, so value-identical to the old literals). One knob
in group_vars stretches every wait in the stack for a slow uplink:
TIMEOUT_FACTOR: 2 doubles them all.

- plugins/lookup/timeout.py + unit tests (identity, factor scaling,
  string coercion, error paths).
- group_vars/all/17_resource.yml: TIMEOUT_FACTOR: 1.
- Convert all 34 literal 'timeout:' task keywords / module args across 25
  task+handler files to "{{ lookup('timeout', <base>) | int }}".
- tests/lint/repository/yaml/test_timeout_uses_lookup.py enforces the
  lookup in every task/handler timeout (opt-out: # nocheck: timeout-lookup).
- swarm.yml pre-deploy build handler: add the missing timeout (same
  network-build hang vector as compose.yml's Build compose).
- Drop 13 pre-existing narration comments in the touched files (comment
  lint enforces whole-file on any edited file).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:57:30 +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 d074d7c278 test(lint): hoist uniform per-task keywords to include_tasks apply:
Add tests/lint/ansible/tasks/test_include_uniform_apply_keyword.py: when a file pulled in via a dynamic include_tasks carries the same hoistable task keyword on every top-level task, that keyword should be set once via apply: on the include site instead of being repeated per task. The rule is blacklist-based: it flags any uniform Ansible task keyword except the per-task-binding ones (name, register, args, vars, when, loop), and never flags the module itself.

Hoist every existing occurrence to apply: across the affected roles (delegate_to, run_once, become, notify, no_log, changed_when, delay, async, poll), dropping the per-task duplication. Behaviour is identical because apply: propagates each keyword to every included task; no_log masking via apply: was empirically verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 00:44:37 +02:00
kevinveenbirkenbachandClaude Opus 4.8 51c3d41ede Merge origin/main into feature/swarm-nfs
Reconcile main's object-store feature and pre-swarm deploy tasks with the branch's compose/networks/volumes SPOT and swarm lints.

objstore (seaweedfs/minio): standalone compose + embedded service templates migrated to the container_image/container_volumes/container_networks/compose_volumes/compose_networks/depends_on lookups; meta/volumes.yml on the canonical dict schema with s3.json and nginx.conf as type:config; embedded services made swarm-compatible via compose_only/compose_restart.

deploy tasks (erpnext, opentalk, seaweedfs, shopware, snipe-it, wordpress, yourls): container exec routed through the container_address resolver and gated on IS_STACK_HOST, shell module plus stack_host_template, no chdir or compose-verb forms.

Also removed orphaned vars, restored MAILU_INSTANCE_DIR, freed the seaweedfs subnet (192.168.206.0/24), stripped literal quotes from the nextcloud/bookwyrm env templates, and routed main's objstore compose_volumes tests through dir_var_lib.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 21:08:49 +02:00
kevinveenbirkenbachandClaude Opus 4.7 e7460890d9 refactor(tasks): drop redundant compose-instance chdir from container exec tasks
Across 12 task files (gitea x5, shopware x5, wordpress install, mattermost
setup), strip the leftover `chdir: "{{ lookup('container', application_id,
'directories.instance') }}"` arg from shell tasks that already drive the
container via `container exec` through the wrapper. The chdir points at
the compose project root, which is only useful when calling docker-compose
CLI verbs. With `container exec` the cwd is the container's own working
directory; the host-side chdir does nothing. Empty `args:` blocks left
behind by the removal are collapsed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-14 19:08:45 +02:00
kevinveenbirkenbachandClaude Opus 4.8 f17d68fae3 feat(web-app): SeaweedFS S3 object-store integration for 9 Tier A roles
Wire nine database-backed web-app roles to the shared SeaweedFS object store
via the engine-agnostic objstore lookup, following the existing consumer recipe
(declare the seaweedfs service, render the app S3 config from
lookup('objstore', application_id, ...), add the e2e test). Each app's S3
config keys were verified against the upstream docs/source, and all honor
SeaweedFS path-style addressing + the internal http endpoint:

- fider:     BLOB_STORAGE=s3 + BLOB_STORAGE_S3_*
- snipe-it:  split s3_private/s3_public disks, single bucket via BUCKET_ROOT, *_AWS_PATH_STYLE
- mobilizon: Uploader.S3 + ExAws (private)
- decidim:   ActiveStorage amazon service, force_path_style
- taiga:     django-storages, AWS_S3_ADDRESSING_STYLE=path
- shopware:  Flysystem amazon-s3 adapter, use_path_style_endpoint
- magento:   remote_storage driver aws-s3 + endpoint
- matrix:    s3_storage_provider module baked into the image + media_storage_providers
- gitlab:    consolidated object storage, single bucket split into per-type virtual buckets, path_style

Both object-store guards (test_database_requires_seaweedfs,
test_seaweedfs_consumer_playwright) pass; autoformat clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 15:29:55 +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
kevinveenbirkenbach 1da3c2eda7 fix(web-app): gate manager-only role bodies behind IS_STACK_HOST
Across roles/web-app-*, the typical body shape is:
  - include_role: sys-stk-full (or sys-svc-compose)   cluster prereqs
  - <host-fs writes, container exec, compose exec>    stack-host only

Under swarm-mode delegation the second group only makes sense on the
host that holds the compose stack (manager), but the role plays on
every host in groups[<role>] so workers abort on missing paths.

Two workflow sweeps (gate-manager-ops + gate-manager-ops-01core, 58
agents total, discover/plan/adversarial-verify per role) plus three
manual fixes surfaced 27 roles to refactor. Each role keeps
sys-stk-full ungated so worker-side prereqs (sys-svc-container, NFS
client) still bootstrap; the trailing manager-only block either moves
into a new tasks/02_manager_ops.yml included with
when: IS_STACK_HOST | bool (SUB_FILE pattern) or gets the gate
inlined on the one or two trailing tasks (INLINE pattern). In
compose mode IS_STACK_HOST stays true so single-host deploys are
unchanged.

Manual catches the workflow agents missed:
- web-app-fediwall: utils/once/flag.yml lives AT THE END of 01_core,
  not at the start; preserved verbatim outside the gate so workers
  still flip the run_once_<role> fact.
- web-app-xwiki: a pre-existing block(...) wraps sys-stk-full plus
  _flush_config.yml; gated only the _flush_config.yml include so
  sys-stk-full keeps running on workers.
- web-app-dashboard: 02_javascript.yml (creates /opt/compose/dashboard/
  JS dirs) ran BEFORE sys-stk-full and was ungated; added the gate.

Roles touched (INLINE): akaunting, jenkins, magento, mobilizon
Roles touched (SUB_FILE): baserow, dashboard, decidim, erpnext,
  fediwall, friendica, gitea, jitsi, keycloak, listmonk, littlejs,
  mailu, matomo, moodle, odoo, openproject, opentalk, pixelfed,
  shopware, suitecrm, taiga, xwiki, zammad
2026-06-05 12:18:32 +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 b1b73bc1df refactor(lint): tighten inline-script lint to 11 lines + extract overflows
Drops `MAX_LINES` from 12 to 11 in `test_inline_script_block_size.py`,
catching the five blocks that were sitting exactly on the previous
threshold. Pure-bash blocks move to dedicated `.sh` files invoked via
`ansible.builtin.script:` or `run: bash …`; the keycloak heredoc with a
templated JSON payload splits into a `set_fact` plus a folded
`printf | kcadm … -f -` one-liner.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:06:32 +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 951f6d36d2 chore(lint): unify autoformat/galaxy parallelism into one PARALLEL env var
* scripts/lint/autoformat.sh: rename AUTOFORMAT_PARALLEL to PARALLEL,
  declare it at file start with default `true`; still env-overridable.
  Reword the worker comment so the literal `# shellcheck` token no
  longer appears at start-of-line and trips SC1072/SC1073.
* scripts/lint/galaxy.sh: drop the GALAXY_FATAL / GALAXY_NAMESPACE /
  GALAXY_PARALLEL env vars (over-engineered for a single repo). Failure
  is always fatal. NAMESPACE is an internal constant. Add the same
  PARALLEL env var (default `true`, env-overridable); falsy demotes the
  xargs fan-out to one worker.
* docs/contributing/environment/variables.md: collapse the four
  AUTOFORMAT_*/GALAXY_* rows into one `PARALLEL` row covering both
  consumers.
* docs/contributing/tools/make.md: refer to `PARALLEL=0` everywhere;
  add the lint-galaxy parallelism note.

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:59:24 +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 d1617f42d2 fix(roles): satisfy test_role_local_literal_paths — declare oidc/ldap services + move template lookups to vars
Two coupled changes that fix the 15 ``test_role_local_literal_paths``
failures (``services.{oidc,ldap}.{enabled,shared}`` paths called via
``lookup('config', application_id, …)`` in 13 web-app roles without a
matching declaration in ``meta/services.yml``):

1. **Declare the missing service flags** in each role's
   ``meta/services.yml``, using the canonical
   ``"{{ '<provider>' in group_names }}"`` schema so the flag tracks
   the enclosing playbook's group composition:

   - ``oidc``  → ``"{{ 'web-app-keycloak' in group_names }}"``
     (web-app-{gitea, friendica, bookwyrm})
   - ``ldap``  → ``"{{ 'svc-db-openldap' in group_names }}"``
     (web-app-{minio, espocrm, openwebui, baserow, bookwyrm,
     discourse, shopware, odoo, flowise, jenkins, akaunting,
     bluesky})

2. **Move every ``lookup('config', application_id,
   'services.{oidc,ldap}.enabled')`` call out of templates/tasks
   into the role's ``vars/main.yml`` as a named constant** (canonical
   ``<APP>_OIDC_ENABLED`` / ``<APP>_LDAP_ENABLED`` shape), and rewire
   the template/task to read the constant. Affected files:

   - ``web-app-jenkins`` — ``casc.yaml.j2`` + ``playwright.env.j2``
     now read ``JENKINS_OIDC_ENABLED`` / ``JENKINS_LDAP_ENABLED``.
   - ``web-app-shopware`` — ``tasks/{02_setup,03_cleanup}.yml``
     ``when:`` guards now read ``SHOPWARE_OIDC_ENABLED`` /
     ``SHOPWARE_LDAP_ENABLED``.
   - ``web-app-{bookwyrm, baserow, flowise, akaunting, bluesky}`` —
     ``playwright.env.j2`` now reads ``<APP>_LDAP_ENABLED``.

   Centralising the lookup makes it static-analysable
   (``test_role_local_literal_paths`` walks vars/main.yml; templates
   are runtime-only) and matches the existing convention every other
   web-app role already follows.

Verification: ``test_role_local_literal_paths`` passes.

Follow-up: the broader integration suite now flags 13 roles in
``test_every_dynamic_flag_has_true_and_false_variant`` because the
newly-declared ``oidc.{enabled,shared}`` / ``ldap.{enabled,shared}``
flags are dynamic but lack matching ``services.<flag>: true`` /
``services.<flag>: false`` entries in each role's
``meta/variants.yml``. That is a separate variant-coverage chore I'll
hand-fix in the next commit; this commit fixes the immediate
``test_role_local_literal_paths`` regression as requested.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:09:13 +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
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
kevinveenbirkenbach a55c99ef62 refactor(compose): replace docker lookup with container and unify compose paths
- Rename lookup plugin from `docker` to `container`
- Switch all lookups from `lookup('docker', ...)` to `lookup('container', ...)`
- Normalize compose file keys:
  - docker_compose → compose
  - docker_compose_override → compose_override
  - docker_compose_ca_override → compose_ca_override
- Centralize compose path resolution via docker_paths_utils
- Update compose_file_args to always derive paths from DIR_COMPOSITIONS
- Align all roles, templates, and vars with container/compose naming
- Update and rename unit tests accordingly

This prepares the codebase for engine-agnostic container backends
(e.g. Docker ↔ Podman) and removes legacy docker-specific assumptions.

https://chatgpt.com/share/6987a7cc-d374-800f-99ad-bb4c8f92c59e
2026-02-07 21:59:39 +01:00
kevinveenbirkenbach b78ab29218 Renamed config entry from docker to compose
https://chatgpt.com/share/69870f54-d708-800f-9870-e9ae5a4bff44
2026-02-07 11:09:36 +01:00
kevinveenbirkenbach 9fa9692d7d refactor(compose): replace docker compose calls with compose wrapper
- Switch all docker compose invocations to the compose wrapper
- Update comments, task names, and log messages accordingly
- Simplify handlers and tasks to rely on wrapper-managed flags and env files
- Align tests and documentation with compose-based workflow

https://chatgpt.com/share/69866ede-be2c-800f-94b0-bffcf8fed260
2026-02-06 23:44:39 +01:00
kevinveenbirkenbach 5ad0ecfd99 refactor(compose): switch build calls to compose wrapper
Replace all `docker compose build` invocations with the `compose` wrapper
to ensure automatic env-file detection and override handling. Update
handlers, notify targets, direct commands, and log labels consistently
across roles.

https://chatgpt.com/share/69866b76-ce6c-800f-ac9e-dba6bba27008
2026-02-06 23:30:04 +01: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 5100c2c1ce refactor(config): replace get_app_conf filter with config lookup
Replace usages of the get_app_conf filter with the config lookup plugin
across roles and templates. Adjust quoting where required to keep YAML
and Jinja syntax valid. No functional changes intended.

https://chatgpt.com/share/69860678-cef8-800f-8402-7fe6231fdc2d
2026-02-06 16:19:14 +01:00
kevinveenbirkenbach 26a5b159e0 refactor(docker-lookup): replace want= syntax with path-based access
- Replace lookup('docker', ..., want='...') with lookup('docker', ..., 'path')
- Allow both top-level keys (directories/files) and dotted paths
- Simplify docker lookup API and reduce lazy dict propagation
- Update unit tests to reflect new lookup semantics

https://chatgpt.com/share/6984ccea-8290-800f-a6d7-ec0e94df95a0
2026-02-05 18:45:43 +01:00
kevinveenbirkenbach 56aea31667 refactor(docker): replace docker_compose vars with docker lookup across roles
Remove implicit docker_compose variable usage and resolve all docker paths
(files, directories, env) via lookup('docker', application_id).
This prevents scope leakage, centralizes path resolution, and makes docker
context deterministic across roles and handlers.

https://chatgpt.com/share/6984ccea-8290-800f-a6d7-ec0e94df95a0
2026-02-05 18:15:10 +01:00
kevinveenbirkenbach 8a81630391 Added newline at file and POSIX compatible 2026-02-04 19:49:56 +01:00
kevinveenbirkenbach 737adaa567 fix(shell): quote all password variables in shell tasks
Ensure all password usages in shell commands are safely quoted using the
`| quote` filter to prevent shell injection issues and test failures.

Affected roles:
- svc-db-postgres
- web-app-gitea
- web-app-mediawiki
- web-app-moodle
- web-app-shopware
- web-app-xwiki

https://chatgpt.com/share/6974dd33-9470-800f-b469-d31fabcd59e1
2026-01-24 15:54:29 +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 04c9490745 Solved unit and integration tests 2026-01-10 13:07:12 +01:00
kevinveenbirkenbach bf4af7c823 Renamed domain variables 2025-12-29 20:37:47 +01:00
kevinveenbirkenbach 1104d4710d Normalize docker service reachability via DOCKER_REACH_HOST
Introduce DOCKER_REACH_HOST as a single source of truth for service
reachability checks (wait_for), decoupled from DOCKER_BIND_HOST.

- Default reach host is 127.0.0.1 for host-based execution
- Automatically override to Docker bridge gateway when running
  inside a container (Docker socket mounted)
- Add custom docker_bridge_gateway module for reliable detection
- Update all wait_for tasks to use DOCKER_REACH_HOST
- Remove ad-hoc debug TCP probing logic

This ensures consistent behavior across host runs and
container-based control nodes without breaking existing setups.

https://chatgpt.com/share/694aaf8b-1fe4-800f-88ac-b5f6e508a2e3
2025-12-23 16:04:36 +01:00
kevinveenbirkenbach 493d5bbbda refactor(web-app-shopware): externalize trusted proxy and host configuration with mounted framework.yaml
- added new file roles/web-app-shopware/files/framework.yaml defining trusted_proxies and trusted_headers for Symfony
 - mounted framework.yaml into /var/www/html/config/packages/ in docker-compose
 - exposed new role vars SHOPWARE_FRAMEWORK_HOST/DOCKER for mounting path
 - rendered framework.yaml via Ansible copy task with proper permissions
 - adjusted env.j2 to set TRUSTED_PROXIES and TRUSTED_HOSTS dynamically from domains and networks
 - added SHOPWARE_DOMAIN var to vars/main.yml
 - removed inline framework.yaml creation from Dockerfile (now managed via mount)
 - updated proxy template (html.conf.j2) to include X-Forwarded-Ssl header
 - improved init.sh permission handling for shared volumes

See ChatGPT conversation for implementation details and rationale:
https://chatgpt.com/share/690d4fe7-2830-800f-8b6d-b868e7fe0e97
2025-11-07 02:48:49 +01:00
kevinveenbirkenbach 0bf286f62a Enhance Shopware role: fix init script permissions, CSP for data: fonts, and unify shell usage
- Added 'font-src data:' to CSP whitelist to allow inline fonts in Admin UI
- Refactored init.sh to run as root only for volume permission setup, then drop privileges to www-data
- Unified all bash invocations to sh for POSIX compliance
- Added missing 'bundles' named volume and mount to Docker Compose
- Set init container to run as root (0:0) for permission setup
- Added admin user rename step via Ansible task

See discussion: https://chatgpt.com/share/69087361-859c-800f-862c-7413350cca3e
2025-11-03 10:18:45 +01:00