Commit Graph
14682 Commits
Author SHA1 Message Date
kevinveenbirkenbach 2198817786 Merge branch 'main' into feature/swarm-nfs 2026-07-04 03:21:56 +02:00
kevinveenbirkenbachandClaude Opus 4.8 2f9a54cea7 fix(deploy): honor variants in include resolution
Inventory include sets ignored the active variant: swarm
derive_includes walked the variant-free base config (dynamic service
flags count as enabled) and the compose CombinedResolver followed
run_after as an include edge. Providers a variant pins to false
(e.g. mobilizon variant 1 sso) were still deployed.

- derive_includes: swap every app listed in INFINITO_APP_VARIANTS to
  its variant config, index 0 included (nextcloud variant 0 disables
  11 partner services)
- CombinedResolver: follow_run_after flag; include resolution passes
  False so run_after stays a pure ordering hint
- drop dead deps.py chain (resolve_deploy_ids_for_*) and unused
  resolve_with_edges; rewiring them would resurrect variant-blind
  run_after-following includes
- inventory.md: document the real include pipeline and
  is_explicit_truth semantics

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 03:21:56 +02:00
kevinveenbirkenbach 1a30c13e15 Merge pull request #375 from infinito-nexus/update/docker-image-versions-web-app-mediawiki-20260703-dd8e006
update(images): update Docker image versions (web-app-mediawiki)
2026-07-04 03:21:55 +02:00
kevinveenbirkenbachandClaude Opus 4.8 756146ad06 fix(env): keep the env generator importable on the bare bootstrap python
The INFINITO_CA_CERT_HOST handler pulled PyYAML into the handler chain via utils.cache.yaml, but python -m cli.meta.env runs before any dependency is installed (the .env bootstraps the environment that would provide yaml). Line-parse SOFTWARE_NAME from group_vars with stdlib only and fail loud when the key is missing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 03:21:55 +02:00
kevinveenbirkenbachandClaude Opus 4.8 49bc4634be fix(web-app-odoo): log to stdout so swarm task dumps show the real failure
odoo.conf routed all logging into /var/log/odoo/odoo-server.log, so the swarm task that sat 5 minutes in Starting produced an empty docker service logs dump and the actual blocker stayed invisible. Drop the logfile key: odoo falls back to stdout like the upstream image default; compose behavior only changes the log destination.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 03:21:55 +02:00
kevinveenbirkenbachandClaude Opus 4.8 23b021c6d2 fix(web-app-bluesky): declare pds as the primary service for the converge monitor
The swarm converge check derives its service name from meta services.<entity>.proxy.service_key and fell back to the non-existent bluesky_bluesky, failing the job after a fully green e2e. pds is the always-on core service; every bluesky vhost overrides its proxy key explicitly, so the declaration only feeds the monitor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 03:21:55 +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 8ed4eb5563 fix(web-app-decidim): reach the S3 object store via its in-stack endpoint
ActiveStorage pointed AWS_ENDPOINT at the public seaweedfs vhost, which resolves to 127.0.0.1 inside the container: every avatar upload died with Seahorse::Client::NetworkingError (Connection refused :443). Use the objstore lookup's internal url (http://seaweedfs:8333) like snipe-it does; presigned access stays server-side, AWS_PUBLIC is untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 03:21:55 +02:00
kevinveenbirkenbachandClaude Opus 4.8 e48f924eb8 fix(web-app-erpnext): restore the backend command dropped by stack deploy
docker stack deploy does not append the image CMD when a service overrides only the entrypoint, so the backend's gated entrypoint ran /usr/local/bin/entrypoint.sh without a command: it linked assets and exited 0 in a ~5s replace loop (every other erpnext service carries an explicit command and ran fine). Declare the upstream gunicorn invocation as the service command; in compose it overrides the image CMD with the same value, in swarm it reaches the gate as $@.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 03:21:55 +02:00
infinito-nexus-updater[bot] cd8f523bfb update(images): update Docker image versions (web-app-mediawiki) 2026-07-04 03:21:54 +02:00
kevinveenbirkenbach 6e6fd80f6c style(svc-runner): normalize nocheck comment spacing (autoformat) 2026-07-04 03:19:28 +02:00
kevinveenbirkenbachandClaude Opus 4.8 2f88f0a8b7 refactor(web-app-jellyfin): model auth plugins as meta/addons with per-addon specs
Declare the LDAP and SSO auth plugins as proper meta/addons entries
(mechanism/source/version, github-releases update monitoring, url/tag/
md5/guid under config:) and resolve the install parameters in vars from
the addon config instead of hardcoding them.

Move biber's login journeys into the mandated per-addon Playwright specs:
ldap-authentication tests the LDAP manual login, sso-authentication the
OIDC round-trip. Because a Jellyfin user binds to exactly one auth
provider, the LDAP spec skips while SSO is active — the ldap-only matrix
variant owns that path — so a single test user suffices and the specs
stay parallel-safe. playwright.spec.js keeps guest + native admin;
biber's generic persona is opted out via PERSONA_BIBER_BLOCKED.

Also fill the optional meta/info.yml fields (homepage, video, display).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 03:18:43 +02:00
kevinveenbirkenbachandClaude Opus 4.8 d4ee9a85ba refactor(compose): align templates to the service_name skeleton
Every compose.yml.j2 now declares services as '{{ service_name }}:' with
its '{% set %}' knobs on two-space lines above the key, includes at four
spaces and if/for tags aligned to the content they wrap. The layout
renders correctly because stack_host_template enables lstrip_blocks for
compose.yml.j2; indent(4, True) in the container base and '{%+' guards
in whitespace-sensitive scalars keep the shared fragments identical
under both render modes. A new lint test enforces the skeleton.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 02:36:58 +02:00
kevinveenbirkenbach 174e19121e Merge branch 'main' into feature/web-app-jellyfin 2026-07-03 23:16:39 +02:00
kevinveenbirkenbachandClaude Opus 4.8 ccd0908144 docs(agents/role-loop): add hard pre-redeploy verification gate
Before every redeploy that follows a failed deploy, require one explicit
line: either verified-in-container or a named post-deploy-state-exception.
Forbid treating the redeploy itself as the verification, and reject
state-reset friction, logical inference from a prior deploy, and
"looks correct" as reasons to skip. Playwright-spec failures must reset
any polluted state and be green via make compose-playwright first.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 23:15:31 +02:00
kevinveenbirkenbachandClaude Opus 4.8 5b5c15f5c5 feat(web-app-jellyfin): Jellyfin media server with LDAP + OIDC auth
Bring the full matrix (full-auth / no-auth / ldap-only) green:

- healthcheck: use curl (the image ships no wget)
- install the LDAP plugin from its GitHub release (repo.jellyfin.org is
  unreachable from the deploy network); version/url/md5 live in
  meta/addons/ldap-authentication.yml and reach the script via env
- configure the SSO/OIDC provider through the plugin config API as JSON
  (order-agnostic) instead of hand-written XML, which silently produced
  an empty provider map; set DisablePushedAuthorization + https
  SchemeOverride so Keycloak accepts the redirect_uri
- inject the SSO login button via the branding login-disclaimer + CustomCss
- seed the break-glass admin unconditionally and retry the first-run
  wizard until the admin token resolves (fixes the no-auth variant race)
- Playwright: role-based authenticated-chrome selector for JF 10.11,
  localStorage-clearing logout, and LDAP-before-OIDC ordering (a Jellyfin
  user binds to exactly one auth provider)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 23:15:12 +02:00
kevinveenbirkenbach feef68e705 Merge branch 'main' of github.com:infinito-nexus/core into feature/swarm-nfs 2026-07-03 22:18:21 +02:00
kevinveenbirkenbachandClaude Opus 4.8 eec20c15ca chore(claude): allow the artifact-design skill
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:21 +02:00
kevinveenbirkenbachandClaude Opus 4.8 4768ae20fc fix(web-app-suitecrm): allow 20min swarm converge for the initial install
The first install (composer, schema) exceeds the default 150x2s converge window; 600 retries matches collabora's precedent for slow first boots.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:21 +02:00
kevinveenbirkenbachandClaude Opus 4.8 ce29936ac0 fix(web-app-pixelfed): gate the worker on the app bootstrap marker
The horizon worker started in parallel with the app and hit the storage volume before the bootstrap (env setup, oauth keys, storage:link) finished; swarm ignores depends_on, so ordering needs an in-band gate. A wrapper entrypoint polls for /var/www/storage/.docker.init (touched by the bootstrap as its last step, visible through the shared pixelfed_data volume in both modes) and then execs the upstream /worker-entrypoint.sh; start_period 300s keeps the healthcheck from replacing the waiting task.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:21 +02:00
kevinveenbirkenbachandClaude Opus 4.8 24daefbb87 fix(web-app-odoo): survive the db init in the healthcheck and configure gevent_port
The first boot initializes the database for minutes while the curl healthcheck counted from the 30s default start_period; swarm replaced the task after ~3.5min (clean Shutdown/Complete in the task history), the replacement restarted the init and the stack never converged. start_period 5m gives the first task an 8min window and a replaced task resumes against the already-initialized database. longpolling_port is dead since Odoo 16; gevent_port is the key the 19.0 image actually reads (same value as the gevent default, so no behavior change).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:21 +02:00
kevinveenbirkenbachandClaude Opus 4.8 7c1f2d87d8 fix(web-app-prometheus): warm target vhosts and poll for their metric labels
The metricz spec asserted app=<role> labels once; a label only exists after the vhost logged a request (server-level log_by_lua into the shared dict), so quiet roles like web-svc-xmpp raced the 30s blackbox scrape and an openresty reconverge wipes the dict entirely. Warm every target via its unauthenticated /healthz/ready before scraping and re-warm+re-scrape under a 60s expect.poll per target.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:21 +02:00
kevinveenbirkenbachandClaude Opus 4.8 206846b768 fix(web-app-jitsi): pin to the manager while its config mounts are host-staged bind dirs
meta/volumes.yml stages six type:bind config directories on the stack host; a worker-scheduled task lacks the source paths and the stack never converges. Unpinning requires migrating them to nfs-backed named volumes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:21 +02:00
kevinveenbirkenbachandClaude Opus 4.8 d337b37e81 fix(web-app-opencloud): pin to the manager and one replica for the embedded state
OpenCloud is a single binary with embedded NATS, decomposedfs metadata and search index: mmap/lock-based stores that corrupt over NFS and lose state when swarm reschedules a node-local volume to another node. default_placement: manager opts the volumes out of the NFS driver and makes the node deterministic; one replica because the stores are single-writer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:21 +02:00
kevinveenbirkenbachandClaude Opus 4.8 11efe2cfdf feat(sys-ca-selfsigned): shared java-with-ca-trust wrapper for JVM roles
One entrypoint wrapper imports the project CA into the JVM truststore for penpot and jenkins: env-driven (CA_TRUST_CERT from sys-svc-compose-ca, set in both modes), fail-open without a cert (production), fail-loud with one; writable cacerts are patched in place, read-only layouts fall back to /tmp + JAVA_TOOL_OPTIONS. Replaces penpot's inline compose command and jenkins' role-local entrypoint, which silently skipped the import when cacerts were not writable. ca_cert_container becomes a plain constant since its only Jinja consumer moved into the wrapper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:21 +02:00
kevinveenbirkenbachandClaude Opus 4.8 2a76b19a02 feat(env): pass the root-CA host path to the trust scripts via INFINITO_CA_CERT_HOST
New env handler exports the ca_trust_paths SPOT value (SOFTWARE_NAME read from group_vars) into .env; trust/linux.sh and trust/wsl2.sh consume the variable instead of carrying the path literal and fail loud when it is unset. Stale .env files need a regeneration (make dotenv) before the trust scripts run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:20 +02:00
kevinveenbirkenbachandClaude Opus 4.8 af6f2bdcdf feat(filter): ca_trust_paths as the SPOT for the root-CA cert locations
ca_cert_host derives the host cert path from SOFTWARE_DOMAIN; ca_cert_container carries the fixed in-container bind-mount target. CA_ROOT.cert_host and the test fixtures consume the filter; the standalone runtime scripts (compose_ca.py, container.py) cannot import it and are pinned to the SPOT by the unit test instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:20 +02:00
kevinveenbirkenbachandClaude Opus 4.8 3f60a0422f feat(web-app-gitea): classify auth-source idempotency errors via gitea_auth_noop
'login source already exists' was matched inline in both the LDAP and the OIDC setup; the filter is the single marker source and normalizes case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:20 +02:00
kevinveenbirkenbachandClaude Opus 4.8 ea33483b15 feat(svc-db-openldap): classify ldapmodify idempotency errors via ldap_modify_noop
The two handler tasks carried the identical four-marker stderr chain inline and matched case-sensitively; the filter is the single marker source and normalizes case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:20 +02:00
kevinveenbirkenbachandClaude Opus 4.8 7ac39b9743 fix(web-app-discourse): allow data: script elements in the CSP
The discourse frontend loads data: script elements; the playwright persona run failed on the CSP violation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:20 +02:00
kevinveenbirkenbachandClaude Opus 4.8 61b75f7505 feat(web-app-discourse): classify idempotent network errors via docker_network_noop
The five docker network connect/disconnect tolerances were inline stderr substring chains, inconsistent between sites (the handler variants matched case-sensitively without a default). The docker_network_noop filter is the single marker source per operation; disconnect additionally tolerates absent networks/containers, which is the proven swarm failure ('No such container: postgres' when the central DB is a swarm service). Drop the now-orphaned DISCOURSE_ERROR_NOT_CONNECTED.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:20 +02:00
kevinveenbirkenbachandClaude Opus 4.8 6fb4cf462c refactor(sys-svc-compose): delegate manager-only queries to the STACK_HOST SPOT
resolve_host_cid/wait_converged/guard_instance_dir took a run_host parameter every caller had to pass; STACK_HOST already encodes the correct host in both modes (first swarm manager, degrading to inventory_hostname in compose), so the manager-only queries now delegate to it directly and the parameter plus all caller declarations are gone. Also repairs the swarm guard include: apply/delegate_to are invalid on a HandlerTaskInclude and the previous commit carried duplicate run_once/delegate_to keys, which failed the ansible syntax-check. The keycloak handler drops its masking default; the 02b conditional collapses into the SPOT.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:20 +02:00
kevinveenbirkenbachandClaude Opus 4.8 adfd2f4ce9 refactor(sys-service-loader): extract the preload sequence into its own task file
The pre-flush made the preload block exceed the 3-child lint limit; move the block body (mark, pre-flush, load, post-flush) into preload_service.yml so the block keeps only the include plus the always-clear.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:20 +02:00
kevinveenbirkenbachandClaude Opus 4.8 17335bd1d0 fix(web-app-bluesky): reach the PDS via its in-stack URL, ports from internal-port constants
The login broker made its server-side XRPC calls (createAccount/createSession/resolveHandle/updateAccountPassword) against the public PDS_URL, which routes through the front proxy and fails TLS verification under self-signed mode. Split public/internal: PDS_INTERNAL_URL http://pds:<port> for in-stack calls (falls back to PDS_URL when unset), PDS_URL stays browser-facing. All service ports in the compose template now come from vars constants reading services.*.ports.internal.http; drop the unconsumed broker port scalar from the services meta.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:20 +02:00
kevinveenbirkenbachandClaude Opus 4.8 460d7f94d6 fix(web-app-funkwhale): route the front proxy to the front service
Without proxy.service_key the vhost target falls back to the entity name 'funkwhale', but the compose stack has no such service; the webserver container is 'front'. In swarm the upstream DNS funkwhale_funkwhale resolved to nothing and the vhost answered 502.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:20 +02:00
kevinveenbirkenbachandClaude Opus 4.8 411ea5d3a2 fix(web-app-mailu): gate the embedded-redis probe at include level
delegate_to templates eagerly, before when: is evaluated; with central redis the resolve include was skipped and MAILU_REDIS_HOST_NODE undefined, so the probe task died on templating despite its false condition. Move resolve+probe+rescue into 03a_probe_local_redis.yml and gate the include itself: with central redis the file is never loaded, with embedded redis the ungated resolve always defines the variable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:20 +02:00
kevinveenbirkenbachandClaude Opus 4.8 ee7127c688 feat(sys-svc-compose): fail loud when the instance dir is missing at handler flush
Every chdir-dependent compose/swarm handler assumed the instance dir of the flush-time application_id exists; a drifted notification then died with a cryptic 'Unable to change directory'. Guard the flush with a shared stat+fail (tasks/utils/guard_instance_dir.yml, included first in both handler files) that names the app, the missing dir and the drift cause, and instructs to fix the notifying role.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:20 +02:00
kevinveenbirkenbachandClaude Opus 4.8 ccb7801a22 fix(sys-service-loader): flush pending handlers before entering a preloaded service context
A shared-service preload switches application_id mid-consumer-pass; the preloaded service's front-proxy pipeline flushes handlers (sys-stk-front-proxy:113), firing the consumer's pending compose-up notifies under the service's binding whose instance dir may not exist. Drain them under the still-correct consumer binding first.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:20 +02:00
kevinveenbirkenbachandClaude Opus 4.8 73f9939702 fix(web-app-matomo): re-render consumer injections after the final compose flush
The consumer re-render loop included sys-front-inj-all under web-svc-* application_ids in the middle of the matomo pipeline; the shared sys-svc-compose handlers rebind to the most recent role instantiation, so matomo's compose-up notifies from 06/07 flushed against a consumer's instance dir (/opt/compose/logout) that does not exist when that service is outside the play's whitelist. Move the loop into 10_reinject_consumers.yml as the last step after 08_config, behind the final flush.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:20 +02:00
kevinveenbirkenbachandClaude Opus 4.8 b1b24b9014 feat(networks): external-network lookup + probe-before-create with driver-drift fail
compute_external_network_roles() + compose_external_networks lookup expose which foreign provider networks a compose project references as external, so the swarm deploy path can pre-create them. network/create.yml probes for an existing network instead of unconditional state:present (the module's delete+recreate on attached networks crashed redeploys) and hard-fails on driver drift instead of silently skipping it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:20 +02:00
kevinveenbirkenbach a78e7fc9ca Merge pull request #375 from infinito-nexus/update/docker-image-versions-web-app-mediawiki-20260703-dd8e006
update(images): update Docker image versions (web-app-mediawiki)
2026-07-03 22:18:19 +02:00
infinito-nexus-updater[bot] d25dd65ce5 update(images): update Docker image versions (web-app-mediawiki) 2026-07-03 22:18:19 +02:00
kevinveenbirkenbach 4bdb4d5bf0 Merge pull request #375 from infinito-nexus/update/docker-image-versions-web-app-mediawiki-20260703-dd8e006
update(images): update Docker image versions (web-app-mediawiki)
2026-07-03 19:17:50 +02:00
kevinveenbirkenbach df8d6180dc Merge branch 'main' into feature/swarm-nfs
# Conflicts:
#	.github/workflows/images-build-ci.yml
#	.github/workflows/images-mirror-all.yml
#	.github/workflows/images-mirror-missing.yml
#	.github/workflows/release-highest.yml
#	.github/workflows/release-version.yml
2026-07-03 14:22:27 +02:00
kevinveenbirkenbachandClaude Opus 4.8 1adc01e0ae fix(web-app-keycloak): provision realm users + RBAC groups on the LDAP-disabled path
Without LDAP the fallback only seeded the administrator user and no RBAC groups, so oauth2-proxy's allowed_groups check 403'd every group-gated app (prometheus/seaweedfs, matomo/css downstream) and non-admin logins hit user_not_found (biber). 08_users_realm now seeds every non-reserved user with a usable password (parity with the LDAP path, which federates the same set); new 08b_rbac_groups_realm materialises the /roles/<app>/<role> group tree + memberships directly in the realm: build_realm_rbac_groups mirrors build_ldap_role_entries and restricts members to the users 08 actually seeds (by username attribute); ensure_group_path_members.sh walks the path via kcadm (children endpoint, exact user lookup, idempotent membership PUT) and hard-fails on any missed group or membership instead of masking. Both routines are gated to KEYCLOAK_LDAP_ENABLED false; LDAP-enabled deploys are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:57:31 +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 ee763bb167 fix(web-app-mailu): probe redis/admin on their hosting node in swarm
03_wait_for_redis ran container exec redis-cli ping and the admin HTTP probe on the manager, but mailu_redis/mailu_admin can be scheduled on a worker -> "No such container", exhausting all 30 retries. Resolve each service's hosting node + local CID (resolve_host_cid, prefixes MAILU_REDIS / MAILU_ADMIN) and delegate_to it for the probes and their log-collection rescues. Compose unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:57:30 +02:00
kevinveenbirkenbachandClaude Opus 4.8 9899100adb fix(web-app-espocrm): run manager-ops container execs on the container's node in swarm
02_manager_ops ran container exec for the config check, upgrade and flag-setter against ESPOCRM_CONTAINER_ADDRESS on the manager, but in swarm the espocrm container lives on a worker -> "No such container" (silently swallowed by failed_when on the earlier steps). Resolve the hosting node + local CID via resolve_host_cid and delegate_to it (also for the config-patch include), matching the erpnext pattern. Drop the now-unused ESPOCRM_CONTAINER_ADDRESS. Compose unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:57:30 +02:00
kevinveenbirkenbachandClaude Opus 4.8 8332357377 fix(email): resolve host to localhost when not external, even under TLS
email.py's environment folds TLS_ENABLED into its "external" base, so a non-external relay (no mailu deployed) under TLS resolved host to the mailu domain while auth/tls/from stayed in localhost mode: an unreachable, inconsistent msmtprc that aborted the sys-ctl-hlth-msmtp unit and the deploy (chess/bridgy-fed/hugo/legal). Gate host on external directly, like tls/auth/from, so a mailu-less deploy targets the loopback postfix that sys-svc-mail-smtp deploys under the same email.external gate. Adds a guarding unit test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:57:30 +02:00
kevinveenbirkenbachandClaude Opus 4.8 a13e5431c9 fix(web-app-matrix): pin ansible-flavor mdad runner to the swarm manager
The mdad runner bind-mounts a manager-only host path (MATRIX_MDAD_DIR), but the ansible-flavor service template emitted no deploy/placement block, so swarm scheduled it onto workers where the path is absent and every task was Rejected ("bind source path does not exist"). Include deploy.yml.j2 (as the compose-flavor already does) so the role's default_placement: manager renders. Swarm-only; compose unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:57:30 +02:00