Commit Graph
14682 Commits
Author SHA1 Message Date
kevinveenbirkenbach 975131a8b6 Merge pull request #348 from infinito-nexus/update/docker-image-versions-20260626-6db42b6
update(images): update Docker image versions
2026-06-27 14:38:33 +02:00
kevinveenbirkenbach 14920042b0 Downgrade Nextcloud version from 34 to 33 2026-06-27 14:38:11 +02:00
kevinveenbirkenbach 9c2e8379fb Release 11.0.0 2026-06-27 14:35:36 +02:00
kevinveenbirkenbachandClaude Opus 4.8 fa48411ae5 fix(sys-svc-compose): wipe stale NFS volume state on dev/act swarm redeploy
Swarm reuses NFS-backed state across cluster rebuilds. A role then reads config persisted from a prior cluster while its database (a node-local volume) is fresh, causing install drift and stale-credential loops. Wipe each NFS-backed volume subdir before pre-create, gated to dev/act via RUNTIME so production state is never touched.

Hoist the shared swarm and NFS guards to the include's when, and fold the per-item mapping check into the loop via selectattr, so the two tasks no longer repeat identical conditions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 14:09:12 +02:00
kevinveenbirkenbach 7956303a7e Merge pull request #330 from kevinveenbirkenbach/feature/026-unified-addon-syntax
feat(026): unified addon syntax — bond-aware variants, resource budgets, addon integration tests
2026-06-27 12:09:20 +02:00
kevinveenbirkenbach ea0c570739 Merge branch 'main' into feature/026-unified-addon-syntax 2026-06-27 12:01:11 +02:00
kevinveenbirkenbachandClaude Opus 4.8 a1880485fc refactor(web-app-nextcloud/variants): isolate discourse into its own variant + number variants
Move discourse out of variant 4 (now onlyoffice + mastodon) into a new dedicated variant 7, mirroring the gitlab isolation, so its loosely-coupled (bond 0.3) bridge footprint gets its own budget-bin. Add a numbered overview comment (# Variant N) before each variant entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 12:01:11 +02:00
kevinveenbirkenbach a7f6be557e Merge pull request #347 from infinito-nexus/update/docker-image-versions-20260625-f9e8cf3
update(images): update Docker image versions
2026-06-27 12:01:11 +02:00
kevinveenbirkenbach bdb739e5c3 Downgrade Mattermost version from 11.8.2 to 11.8.1 2026-06-27 12:01:11 +02:00
kevinveenbirkenbach 1a131597a4 Merge pull request #342 from infinito-nexus/update/docker-image-versions-20260624-4393ad8
update(images): update Docker image versions
2026-06-27 12:01:10 +02:00
kevinveenbirkenbach 07814021f8 Downgrade Nextcloud version and update proxy version 2026-06-27 12:01:10 +02:00
kevinveenbirkenbach 3b103c6432 Merge pull request #341 from infinito-nexus/update/docker-image-versions-20260624-b65a611
update(images): update Docker image versions
2026-06-27 12:01:10 +02:00
kevinveenbirkenbach 15711b4711 Update services.yml 2026-06-27 12:01:10 +02:00
infinito-nexus-updater[bot] 4f7de0ac0d update(images): update Docker image versions 2026-06-27 12:01:10 +02:00
kevinveenbirkenbach e26842dac4 Merge pull request #340 from infinito-nexus/update/docker-image-versions-20260624-c93ed59
update(images): update Docker image versions
2026-06-27 12:01:10 +02:00
kevinveenbirkenbach 8e0f5f31dc Update services.yml 2026-06-27 12:01:10 +02:00
infinito-nexus-updater[bot] 4e54bc3111 update(images): update Docker image versions 2026-06-27 12:01:10 +02:00
kevinveenbirkenbach 665aa31fbf Update services.yml 2026-06-27 12:01:10 +02:00
infinito-nexus-updater[bot] 2cf3bdbedc update(images): update Docker image versions 2026-06-27 12:01:10 +02:00
infinito-nexus-updater[bot] b65dafdd49 update(images): update Docker image versions 2026-06-27 12:01:10 +02:00
kevinveenbirkenbachandClaude Opus 4.8 47729106e6 fix(swarm): unblock stack-deploy (openresty group_add) + CA-inject on verbatim env secrets
docker stack deploy rejects the compose group_add property ('Additional property group_add is not allowed'), so svc-prx-openresty drops it from the swarm compose and applies the docker.sock gid post-deploy via 'container service update --group-add' (swarm-native equivalent). The compose path (network_mode host) is unchanged; the new task is DEPLOYMENT_MODE == 'swarm' gated.

compose_ca's swarm --no-wrapper path no longer runs 'docker compose config', whose strict dotenv parser aborts on swarm env files carrying verbatim special-char secrets (e.g. a password beginning with a single quote). It reads the rendered compose files directly; wrap=False needs only service names. The compose (wrapper=True default) path keeps the original 'docker compose config' branch byte-identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 11:57:19 +02:00
kevinveenbirkenbachandClaude Opus 4.8 2e6c495efd style(compose_file_args): drop leftover comment
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 00:50:53 +02:00
kevinveenbirkenbachandClaude Opus 4.8 76935d1cbe fix(web-app-discourse): give the swarm launcher a node-local bridge network
The discourse launcher is a standalone container (docker run via ./launcher), not a swarm service, so it cannot attach to swarm-scoped overlay networks. In swarm it was left without a reachable network for its shared pg/redis/openresty peers.

The shared network util now creates a node-local bridge instead of an overlay when the role declares networks.local.force_bridge in meta/server.yml, read via the config lookup with a false default so every other caller stays byte-identical. Discourse sets force_bridge: true and connects the rebuilt launcher to the shared overlays (web/pg/redis) so swarm services stay reachable; the launcher config drops the explicit --network flags in swarm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 00:50:53 +02:00
kevinveenbirkenbachandClaude Opus 4.8 c11483fb9f fix(web-app-jenkins): pin the stateful singleton to one swarm replica
Jenkins inherited the 3-node topology default, but it is a stateful singleton (one NFS-backed JENKINS_HOME, no native active-active HA). Three replicas race on the shared home at boot (SEVERE plugin-load failures) and the OIDC state/nonce splits across the openresty VIP, so the anonymous redirect to Keycloak never completes (LDAP's single round-trip passes). Pin service_replicas=1 like matomo/erpnext; it stays a swarm service (scheduled + healed across the cluster), just not horizontally scaled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 00:50:53 +02:00
kevinveenbirkenbachandClaude Opus 4.8 5c3749c437 fix(web-app-moodle): make the code-volume bootstrap race-safe on shared NFS
moodle runs replicas:3 sharing one NFS code volume. The entrypoint guarded its cp -a with a version.php TOCTOU check, so several replicas copied concurrently; losers hit 'File exists' and, under set -euo pipefail, aborted before chown + php-fpm, leaving an incomplete/unreadable tree -> install_database.php failed Permission denied (rc 255). Serialize the bootstrap under flock, gate on an atomic .bootstrap.done sentinel, cp -an || true so a benign collision can't abort, make the tree world-readable before the install (root_squash neuters the in-container chown), and make losing replicas wait for the sentinel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 00:50:53 +02:00
kevinveenbirkenbachandClaude Opus 4.8 b5272e2e17 fix(web-app-dashboard): probe the in-cluster simpleicons URL, not the public one
Commit 9f43364c0 flipped the per-card icon HEAD probe to the public icon.<domain> URL in swarm, but the probe runs on the Ansible controller, which has no DNS/route to it -> every probe failed silently -> icon.source was never set -> cards fell back to Font Awesome (no img) and the dashboard e2e failed. Drop the probe_public flip: probe the in-cluster sync URL the controller can reach via the published swarm port, while still rewriting icon.source to the public URL the browser loads.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 00:50:53 +02:00
kevinveenbirkenbachandClaude Opus 4.8 4313fa7375 fix(sys-svc-compose): inject CA trust into swarm deploys + use compose_file_args
In swarm, OmniAuth/OpenSSL (decidim OIDC discovery) and Node (bluesky-broker -> PDS) could not trust Keycloak's internal self-signed CA: docker stack deploy never received compose.ca.override.yml, and the override's $$-escaped entrypoint wrapper is swarm-incompatible (stack deploy does not un-escape it). Add a --no-wrapper override mode (env + CA mount only: SSL_CERT_FILE/CURL_CA_BUNDLE/NODE_EXTRA_CA_CERTS) generated on the stack host for self-signed apps, and build the stack-config/stack-deploy --compose-file list via compose_file_args (new --compose-file flag kwarg) instead of hardcoding compose.yml -- which also stops swarm silently dropping a role's own compose.override.yml. Compose untouched: self-signed-gated, no-wrapper is swarm-only, lookup defaults to -f.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 00:50:53 +02:00
kevinveenbirkenbachandClaude Opus 4.8 454abfb0af fix(web-app-erpnext): pass --db-host/--db-port to bench new-site
bench new-site never passed --db-host, so Frappe defaulted to 127.0.0.1 (MySQLdb OperationalError 2002). The DB host is only written to common_site_config.json by the one-off configurator, which swarm never gates (docker stack deploy ignores depends_on; a restart:none one-off shows 0/1 forever, so the convergence wait can't gate on it either). Pass host/port explicitly; the vars already resolve to mariadb:3306.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 00:50:53 +02:00
kevinveenbirkenbachandClaude Opus 4.8 db7c2cf5f3 chore(claude): allow WebFetch to sdk.collaboraonline.com
Added to permissions.allow during the collabora swarm-convergence investigation (Collabora SDK docs lookup for the mount_jail_tree knob).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:55:57 +02:00
kevinveenbirkenbachandClaude Opus 4.8 398d4d11b0 fix: satisfy make test on bundles 1/6/7 (cached read, ansible-lint-ignore, ruff wrap)
These commits were made with --no-verify, which skipped make test, leaving three latent lint failures. utils/paths.py: use the cached utils.cache.files.read_text (verified no import cycle) instead of a plain read. .ansible-lint-ignore: add the pixelfed resolve_host_cid load-failure entry (templated playbook_dir include, false positive). library/database_query.py: ruff line-wrap. make test is now fully green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:55:57 +02:00
kevinveenbirkenbachandClaude Opus 4.8 c453cd2029 feat(sys-svc-compose): dump service ps/logs on swarm converge-timeout
wait_converged failed after 600 silent retries (rc=1, empty) with no clue why. Wrap the wait in a rescue that, on the manager, dumps 'container service ps --no-trunc' + 'container service logs --tail 50' and aborts with a root-cause message, so converge failures (e.g. collabora's coolmount permission error) surface immediately instead of after 20 minutes of silence. Swarm-gated; the success path is unchanged and compose skips the block entirely.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:55:57 +02:00
kevinveenbirkenbachandClaude Opus 4.8 7564bf1b4f fix(web-svc-collabora): skip the jail-mount syscall in swarm
docker stack deploy drops cap_add/security_opt, so collabora's coolmount (CAP_SYS_ADMIN jail-mount) fails 'Operation not permitted' and the container never goes healthy -> the swarm task reschedules forever and never converges (bigbluebutton/nextcloud/listmonk). Append --o:mount_jail_tree=false in swarm so collabora copies/links the jail tree instead of mounting it. Compose keeps the caps and is byte-identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:55:57 +02:00
kevinveenbirkenbachandClaude Opus 4.8 aa6e156a6c fix(web-app-keycloak): recover kcadm updates from cross-node reschedules
The realm-import scales keycloak 0->N (fresh reschedule across nodes), then the kcadm update block runs delegated to a fixed KEYCLOAK_HOST_NODE; the in-module recovery resolves only a node-local container id, so when a replica reschedules to another node it can never be found -> 'No such container: <dead cid>'. Wrap the update routines (extracted to 02b_update_routines.yml) in a rescue that re-resolves the hosting node + live id and re-delegates the updates, bounded to 5 attempts; compose re-raises immediately. Relax the recovery resolver to status=running so it can find the just-rescheduled, not-yet-healthy replica.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:55:57 +02:00
kevinveenbirkenbachandClaude Opus 4.8 18e53dc4c6 fix(svc-prx-openresty): grant the swarm worker docker-socket access
In swarm the openresty worker runs as nobody and cannot read the host docker socket (root:<gid> 0660), so the init_worker docker-health Lua poll fails 'Permission denied' and the /metricz up metric stays 0. Stat the socket for its live gid and group_add it to the swarm service so the worker can read it. Swarm-gated; compose uses network_mode host and is untouched. Verified live: --group-add of the socket gid removes the EACCES.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:55:56 +02:00
kevinveenbirkenbachandClaude Opus 4.8 84ed963647 fix(web-app-pixelfed): resolve the swarm container id for bootstrap execs
The bootstrap/admin tasks exec 'container exec PIXELFED_CONTAINER' (the bare service name 'pixelfed'), which is not a swarm container name -> 'No such container: pixelfed'. Adopt the matomo resolve_host_cid pattern: resolve PIXELFED_HOST_NODE + PIXELFED_LOCAL_CID once, exec against the resolved id with delegate_to the hosting node, and drop the chdir (irrelevant to container exec and absent on a delegated worker). Compose is byte-identical (HOST_NODE = inventory_hostname, LOCAL_CID = PIXELFED_CONTAINER).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:02:17 +02:00
kevinveenbirkenbachandClaude Opus 4.8 0c0ec562f5 fix(database_query): resolve the swarm container id before exec
The module execs 'container exec <container>' as an argv list (no shell), using config["container"] = the literal "postgres" in shared mode, which is not a swarm container name -> 'No such container: postgres' for peertube and ~12 other callers. The database lookup already provides a swarm-aware config["address"] = "$(resolve-container-id <stack> <key>)"; expand it here (shlex-split, run the resolver as argv) to a concrete id when present. Compose (address == container) is byte-identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:02:17 +02:00
kevinveenbirkenbachandClaude Opus 4.8 de04be1453 fix(web-app-openproject): use KEY=value env-file syntax, not YAML
docker stack deploy parses the .env file as KEY=value; three lines used YAML 'KEY: value' syntax, so RAILS_CACHE_STORE: "memcache" became a variable name containing whitespace and aborted the swarm deploy. Convert them to KEY=value and drop the duplicate OPENPROJECT_RAILS__RELATIVE__URL__ROOT (line 11 already sets it).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:02:17 +02:00
kevinveenbirkenbachandClaude Opus 4.8 3e1c49e4bf fix(web-app-matrix): create the runner build dir on STACK_HOST before copy
The ansible-flavor stages the matrix-mdad runner build context into MATRIX_MDAD_RUNNER_BUILD_DIR via stack_host_copy, but nothing created that directory first, so the copy failed 'Destination directory ... does not exist'. Ensure the dir (delegate_to STACK_HOST, run_once) before the copy loop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:02:17 +02:00
kevinveenbirkenbachandClaude Opus 4.8 45aa32e397 fix(svc-db-redis): run the readiness checks once on the stack host
The redis wait-until-running + PING checks exec via REDIS_CONTAINER_ADDRESS (resolve-container-id, manager-only). Ungated they ran on every node, so on workers resolve-container-id exits 'must run on a Swarm manager node' and the checks fail (broke svc-bkp-container-2-local). Gate them to the stack host with delegate_to + run_once, mirroring svc-db-postgres.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:02:17 +02:00
kevinveenbirkenbachandClaude Opus 4.8 0e973338d6 fix(sys-svc-compose): pre-create NFS dirs for sidecar volumes too
nfs_prep looped lookup('volume', application_id), which returns only the role's own meta/volumes.yml and omits sidecar volumes the stack also mounts (e.g. <entity>_redis from svc-db-redis / an sso-proxy). Their backing dir was never created, so swarm rejected the mount and the sidecar stayed 0/1 (fusiondirectory/postmarks/snipe-it 500/502). Loop the rendered compose_volumes set instead, which includes the sidecars; manager-pinned roles still skip via the placement gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:02:17 +02:00
kevinveenbirkenbachandClaude Opus 4.8 c9886f2f69 fix(utils-paths): resolve INFINITO_DIR_VAR_LIB from default.env when env unset
The compose-deploy inventory validator is spawned as a bare subprocess without the env build, so os.environ["INFINITO_DIR_VAR_LIB"] raised KeyError at import and aborted all compose deploys before Ansible ran. Fall back to the default.env SPOT (no hardcoded literal); read it plainly to avoid a utils.paths -> utils.cache import cycle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:02:16 +02:00
kevinveenbirkenbachandClaude Opus 4.8 504324d528 refactor(ci/variant-bundles): lower default storage cap to 350GB from a single source
Set DEFAULT_MAX_STORAGE to 350GB (was 400GB) and make it the single source of truth: the INFINITO_VARIANT_BUNDLE_MAX_STORAGE env handler's setdefault and the resolve_max_storage error message now reference DEFAULT_MAX_STORAGE instead of repeating the literal. Update the default-resolution unit test accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:37:35 +02:00
kevinveenbirkenbachandClaude Opus 4.8 8155b4a341 test(variants): enforce shared DB providers via shared-false-once lint
Add tests/lint/ansible/services/test_variants_shared_false_once.py enforcing the one-local-provider-variant limit on services.<key>.shared: false pins, document the variants-shared-false-once suppression marker, and flip the database service (mariadb/postgres/redis) to shared: true across the erpnext, funkwhale, shopware, snipe-it, suitecrm, taiga, xwiki and zammad variants to comply.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:37:35 +02:00
kevinveenbirkenbachandClaude Opus 4.8 ae6257f6fb fix(web-app-nextcloud/gitlab): set organization on the Nextcloud OAuth app for GitLab 19
GitLab 19.0 added a NOT NULL check constraint (check_77eda6baaa) on oauth_applications.organization_id. The provisioning runner created the Doorkeeper application without a resolvable organization context (Current.organization is nil in a gitlab-rails runner), so the insert produced organization_id=NULL and failed with PG::CheckViolation on GitLab 19.1. Resolve the default organization, set it on the request context, and assign it via both the association and the column; fail loudly if no organization is available.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:37:34 +02:00
kevinveenbirkenbachandClaude Opus 4.8 e140e1607a fix(svc-prx-openresty): wait for openresty reconverge so metrics don't race the e2e
The swarm openresty restart used 'container service update --force --detach' (fire-and-forget), so the deploy + e2e proceeded before openresty reconverged with prometheus.conf. The per-vhost log_by_lua then saw a nil metric_requests ('attempt to index global metric_requests') and the prometheus metricz/scrape e2e failed. Drop --detach so the update waits for convergence (bounded by the existing timeout 600). Also guard the per-vhost log_by_lua against a nil metric so any residual pre-init window can't throw.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:34:46 +02:00
kevinveenbirkenbachandClaude Opus 4.8 bb4e8bd2b8 fix(svc-storage-nfs): NFSv4 + correct probe export + soft dev/act mounts (SPOT)
The dev/act ganesha server is NFSv4-only, but the swarm-test runtime extras forced storage.nfs.version: 3, so every mount failed 'Protocol not supported'. Set version 4 (write_extras + dev swarm inventory). The client probe mounted the Root_Squash export_base and a root probe-write failed; point it at the No_Root_Squash state path. Both client mounts hardcoded 'hard', so a torn-down test server wedged nodes into D-state; gate them to soft in dev/act (reusing the controller mount's gate). Hoist the shared mount-opts string into the STORAGE_NFS_MOUNT_OPTS SPOT (group_vars/all/15_storage.yml), used by all three mounts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:34:46 +02:00
kevinveenbirkenbach 8a7362249e Update storage NFS role handling 2026-06-26 20:34:45 +02:00
kevinveenbirkenbachandClaude Opus 4.8 9f669c985b fix(tests): nocheck the CI-logs placeholder run URL so test-external skips it
The example _RUN_URL in test_download.py lacked the '# nocheck: url' marker its siblings carry, so test_public_http_urls_are_reachable probed github.com/o/r/actions/runs/123 -> HTTP 404 and failed the external suite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 17:20:52 +02:00
kevinveenbirkenbachandClaude Opus 4.8 cf765ee00c refactor: single /var/lib SPOT + swarm Playwright artifact upload
SPOT: declare the var-lib base once in default.env as INFINITO_DIR_VAR_LIB; the env build derives INFINITO_PLAYWRIGHT_REPORTS_BASE_DIR from it; reference it everywhere (utils/paths.py, scripts via ${...:?}, tests, the dev inventory's DIR_VAR_LIB injection) instead of hardcoding the literal. group_vars keeps the literal default; the env-lookup lives in the dev inventory per the no-ansible-env-lookups lint rule. pull_specific_host.py --folder is now required and the role passes {{ DIR_BACKUPS }}.

Swarm e2e: collect_playwright_reports.sh docker-cp's the reports off the manager plus a workflow upload step, so swarm Playwright traces reach CI artifacts (was compose-only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 17:01:09 +02:00
kevinveenbirkenbachandClaude Opus 4.8 6d417aa9a4 fix(test-e2e-playwright): keep reports on e2e timeout via a Playwright globalTimeout
The Ansible task timeout hard-kills the Playwright sidecar at TEST_E2E_PLAYWRIGHT_RUN_TIMEOUT, discarding every junit/html/trace and aborting the play before the archive step. Set Playwright's own globalTimeout a buffer (60s) below that cap so it self-terminates with reports, the task returns rc\!=0 instead of being killed, and the diagnostics get archived for the next run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 17:01:09 +02:00