Commit Graph
14682 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 4.8 0bbd6c43ff fix(postgres): pin provider context on 02_init for nested/looped consumers
svc-db-postgres 02_init.yml asserts application_id == 'svc-db-postgres' AND database_consumer_id \!= 'svc-db-postgres'. When 01_core is skipped (run-once), the include_role application_id var does not reliably reach 02_init through a consumer's nested looped include (web-app-matrix's per-bridge DB seeding), so the assert fails. Pin application_id on the 02_init include so it holds for any caller. On the matrix side, freeze the consumer id eagerly before the provider include so database_consumer_id stays the matrix consumer instead of lazily resolving to svc-db-postgres once application_id is pinned.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:17:59 +02:00
kevinveenbirkenbachandClaude Opus 4.8 bf32cab5ef fix(nextcloud): store the zammad OAuth client_id as a sensitive app value
The integration_zammad Connect flow reached the partner /oauth/authorize with a malformed client_id, so Zammad's Doorkeeper rejected the unknown client UID and 302-redirected to its homepage (the spec then captured the partner root, not the authorize page). The encrypted-appconfig helper stored the credentials via setValueString(..., lazy: true) WITHOUT sensitive: true, which mangled the client_id on read. Store it sensitive so the provisioned client_id round-trips intact and the OAuth authorize carries the correct UID.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:17:59 +02:00
kevinveenbirkenbachandClaude Opus 4.8 fd8af82e95 fix(joomla): resolve the OIDC CA bundle from the compose-ca env vars
The Joomla OIDC plugin's server-side discovery/token cURL to Keycloak hardcoded the CA path to /etc/ssl/certs/ca-certificates.crt, which only carries the Infinito root CA once the CA-trust wrapper has run update-ca-certificates in the container. The wrapper is skipped on Joomla because the locally-built joomla_custom image does not exist when the CA-trust override is generated, so PHP cURL never trusts auth.infinito.example and OIDC fails with cURL error 60. Resolve the CA bundle from the CURL_CA_BUNDLE/CA_TRUST_CERT/SSL_CERT_FILE env vars (which compose-ca sets and bind-mounts reliably regardless of the wrapper), falling back to the system bundle. setVerifyHost/setVerifyPeer stay true — TLS verification is not relaxed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:17:59 +02:00
kevinveenbirkenbachandClaude Opus 4.8 343d54d3c2 fix(pixelfed): install the internal CA into the OS bundle for PHP libcurl OIDC
Pixelfed's Laravel OIDC token exchange to Keycloak uses php-curl, which verifies against libcurl's OS bundle (/etc/ssl/certs/ca-certificates.crt), not the CURL_CA_BUNDLE/SSL_CERT_FILE env vars the compose_ca wrapper exports. The custom docker-entrypoint now installs the injected internal CA via update-ca-certificates (installing ca-certificates first if absent) so the OIDC callback's server-side TLS succeeds instead of failing with cURL error 60.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:17:59 +02:00
kevinveenbirkenbachandClaude Opus 4.8 9829342483 fix(wordpress): install ca-certificates so the CA-trust wrapper can trust the internal CA
The wordpress (php-apache/Debian) image lacks ca-certificates, so the sys-ca-selfsigned with-ca-trust.sh wrapper's update-ca-certificates path is skipped and the internal self-signed CA never lands in /etc/ssl/certs/ca-certificates.crt — the bundle PHP libcurl verifies against. The daggerhart OIDC plugin then fails server-side TLS with 'cURL error 60: unable to get local issuer certificate' (confirmed in the Playwright artifact). Add ca-certificates so update-ca-certificates runs and the CA is trusted. compose_ca.py alone is insufficient because PHP libcurl uses the OS bundle, not the CURL_CA_BUNDLE/SSL_CERT_FILE env vars.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:17:58 +02:00
kevinveenbirkenbachandClaude Opus 4.8 00f0f9a4ed test(odoo): navigate the account spec to /odoo/invoicing (the real account.move action)
The account addon spec opened /odoo/accounting (the journal dashboard, which in Odoo 19 has no .o_account_dashboard and not the asserted invoice vocabulary) and timed out even though the account module is installed. Navigate to /odoo/invoicing — the canonical account.move customer-invoices action — and assert the invoices list/kanban plus account-specific vocabulary (customer invoice / register payment / invoice date / tax excluded). Verified green live against Odoo 19 via 'make compose-playwright role=web-app-odoo'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:17:58 +02:00
infinito-nexus-updater[bot] 3c0f7c57db update(images): update Docker image versions 2026-06-23 08:14:52 +00:00
kevinveenbirkenbach 17ac477894 Merge pull request #332 from infinito-nexus/dependabot/npm_and_yarn/globals-17.7.0
chore(deps-dev): bump globals from 15.15.0 to 17.7.0
2026-06-23 09:50:05 +02:00
kevinveenbirkenbachandClaude Opus 4.8 be2e90adda fix(sys-svc-objstore): make the provider repo-context reset lint-compliant
The lazy object-store provider include cleared compose_repository_url with an inline literal "" to stop the consumer's repo coordinates leaking into the provider, but the compose-repository lint requires the URL to be a Jinja expression paired with compose_repository_ref. Express the empty clearing as "{{ '' }}" and pair it with an empty compose_repository_ref — same runtime effect (the object-store provider is image-based, no git clone), now lint-clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 03:23:05 +02:00
kevinveenbirkenbachandClaude Opus 4.8 ca77eb9176 fix(bridgy-fed): normalize the brevity git pin to resolve a pip URL conflict
Upstream requirements.txt pins brevity at git+...@new-tlds while granary requires brevity at the same git URL without a ref; pip treats the two direct URLs as conflicting and aborts the build. Strip the @new-tlds ref so both requirers agree (brevity master is new-tlds minus one trivial additive commit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:50:58 +02:00
kevinveenbirkenbachandClaude Opus 4.8 900e224348 test(odoo): assert account-specific surface, not a generic Odoo shell
The account addon spec asserted bare .o_control_panel/.o_breadcrumb, which render on every Odoo page, so it passed green even when the account module was enabled-but-not-installed. Require Accounting domain vocabulary (Customer Invoices / Vendor Bills / Chart of Accounts / Journal Entries) and text-gate the generic containers, mirroring the crm/stock specs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:50:58 +02:00
kevinveenbirkenbachandClaude Opus 4.8 34383502e9 fix(mailu): retry token create/delete against the admin API readiness race
The admin gunicorn at localhost:8080/api/v1 may not be ready when the token tasks run; add until/retries(30)/delay(2). Create uses curl -sS -f so the until requires an HTTP 2xx, and failed_when still fails the play loudly after retries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:50:57 +02:00
kevinveenbirkenbachandClaude Opus 4.8 cd8bc45d6f fix(mastodon): allow inline script-src-elem for the server-rendered SPA
Mastodon emits inline initial-state/bootstrap <script> blocks server-side; the reverse proxy cannot nonce/hash upstream HTML, so the CSP healthcheck flagged a refused-to-execute violation. Set script-src-elem.unsafe-inline (script-src-attr stays hardened), matching every fediverse SPA sibling (pixelfed/bookwyrm/friendica/mobilizon).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:50:57 +02:00
kevinveenbirkenbachandClaude Opus 4.8 dac01cfaf1 fix(xwiki): guard missing-extensions assert against empty plugin set
When no XWiki addons are enabled (XWIKI_PLUGINS empty), the probe-results set_fact loop runs zero times and _xwiki_probe_results stays undefined, so the selectattr in the 'all requested extensions installed' assert raised AnsibleUndefinedVariable. Default it to [] so 'nothing requested' means 'nothing missing'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:50:57 +02:00
kevinveenbirkenbachandClaude Opus 4.8 196074cdd0 fix(nextcloud): register both oauth-redirect variants for gitlab/mattermost/openproject
Nextcloud runs pretty-URLs (front_controller_active), so each integration app emits its oauth-redirect WITHOUT /index.php at runtime. The partner OAuth apps were registered with only the /index.php variant, so exact-match validation rejected the real callback and the per-user connect silently failed while the deploy and Playwright specs went green. Register both variants (the no-/index.php one first). Build the list on the consumer side (Doorkeeper newline-join for gitlab, native JSON callback_urls array for mattermost, Doorkeeper for openproject) to avoid newline-in-argv.

SuiteCRM is unaffected (OAuth2 password grant, no redirect_uri); the Nextcloud->OpenProject client callback is an OpenProject URL and is left untouched. Also drop the Doorkeeper>=5 hashed-secret fallback in the gitlab provisioner so a missing plaintext secret fails loudly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:50:57 +02:00
kevinveenbirkenbachandClaude Opus 4.8 4c1b41418d feat(nextcloud): zammad OAuth provisioning + strengthen peertube addon spec
Provision a Doorkeeper OAuth application on the partner Zammad and write its client_id/client_secret into integration_zammad (Nextcloud-encrypted), so the per-user OAuth connect flow has the credentials the generic SPOT cannot supply. Register both the pretty-URL and /index.php oauth-redirect variants, and drop the hashed-secret fallback so a missing plaintext secret fails loudly instead of persisting an unusable hash.

Strengthen the integration_peertube spec: pin the admin 'instances' value to the exact deployed partner host (new PEERTUBE_BASE_URL) instead of merely 'not Nextcloud', and add a functional OCS search-providers check that fails when the PeerTube provider is not registered. PeerTube has no per-user OAuth (verified upstream: no client_id/oauth_instance_url config, no /oauth/authorize route), so the removed Connect->/oauth/authorize assertion tested a non-existent flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:50:57 +02:00
kevinveenbirkenbachandClaude Opus 4.8 e39d77c82a fix(compose-ca): keep CA-trust wrapper on probe timeout, don't degrade to env-only
The image probe (docker inspect + 'docker run --entrypoint /bin/sh') conflated a timeout/kill under load with 'image has no /bin/sh', skipping the CA-trust wrapper entrypoint and leaving only env vars (CURL_CA_BUNDLE/SSL_CERT_FILE) that PHP libcurl ignores -> cURL 60 on server-side OIDC. has_sh is now tri-state: a timeout (rc==124) maps to None ('assume shell present', apply the wrapper); only a clean non-zero rc disables it. Hang protection (timeout+killpg) unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:50:57 +02:00
kevinveenbirkenbachandClaude Opus 4.8 cf4d0169dc fix(sys-svc-objstore): don't leak consumer repo/db context into the lazy provider
A consumer (e.g. web-app-openproject) sets compose_repository_url + pull=true
and database_consumer_id; sys-svc-objstore then lazily loads the shared
object-store provider (web-app-seaweedfs), which inherited those and tried to
clone the consumer's repo / target the consumer's DB under its own
application_id, failing the play. Pin the provider's own context (empty repo
url, no pull, provider's own database_consumer_id) on the lazy provider
include so it runs self-contained; the consumer's own clone is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:50:57 +02:00
kevinveenbirkenbachandClaude Opus 4.8 a08ec88c03 feat(deploy): always run the full variant matrix (remove runtime time-cut)
Drop the RuntimeBudget guard and its INFINITO_MAX_RUNTIME /
INFINITO_VARIANT_TIME_OVERFLOW env handlers: the deploy round loop now runs
every variant unconditionally instead of skipping (cut) or aborting (fail)
when the projected runtime would exceed the budget. Removes runtime_budget.py,
both env handlers + their registration, the budget calls in deploy.py, the
obsolete unit tests + the TestHandlerBudget class, and the docs reference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:50:56 +02:00
dependabot[bot] 94060c4412 chore(deps-dev): bump globals from 15.15.0 to 17.7.0
Bumps [globals](https://github.com/sindresorhus/globals) from 15.15.0 to 17.7.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v15.15.0...v17.7.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-version: 17.7.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-23 00:06:40 +00:00
kevinveenbirkenbachandClaude Opus 4.8 a9072d251c fix(ci): use venv python in discover step so variant-bundle expander has deps
The deploy-matrix discovery ran `python3 -m utils.github.variant_bundles`
with the runner system python, which lacks PyYAML, so importing get_variants
failed (ModuleNotFoundError: No module named yaml). Source
scripts/meta/env/python.sh to use the venv `make install` populates (pip
install -e . puts PyYAML there), matching how the rest of the toolchain
resolves PYTHON.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:17:54 +02:00
kevinveenbirkenbachandClaude Opus 4.8 87f2ffc745 fix(test): notification-2FA spec asserts registration, not the enable control
The personal-security enable control for twofactor_nextcloud_notification
only renders once the provider is activatable, which needs a SECOND active
session to receive the approval notification — impossible in a single-session
E2E (the provider state is correctly false there). The addon hook already
proves registration via occ twofactorauth:state; the browser-observable
equivalent is that the provider state element is surfaced, so assert
toBeAttached, not toBeVisible. Verified in-container: nextcloud v1 suite
31 passed, 0 failed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 13:36:18 +02:00
kevinveenbirkenbach 06c686a3bd fixup! fix(deploy): bound CA-trust inject so it can't hang the handler 2026-06-22 13:04:44 +02:00
kevinveenbirkenbachandClaude Opus 4.8 5246c6b86d fix(env): shorten INFINITO_VARIANT_TIME_OVERFLOW comment under the .env lint
The handler COMMENT rendered a 188-char line in .env; the env-file-schema
lint caps non-nocheck comment lines at 140. Trim it. Also collapses two
ruff-format lines in the variant-split unit tests. Follow-up to the
runner-split variant feature; no behaviour change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 12:49:19 +02:00
kevinveenbirkenbachandClaude Opus 4.8 4329a0a1f4 fix(tls): always emit HSTS, including on non-2xx responses
add_header without `always` only emits on 2xx/3xx, so error/startup
pages (e.g. an app's "initializing" 5xx) lacked Strict-Transport-Security
— which failed the xwiki HSTS E2E even though the settled site was fine.
`always` emits the header on every response; it only broadens emission
(never removes it), so it is strictly safe across all HTTPS vhosts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 12:49:19 +02:00
kevinveenbirkenbachandClaude Opus 4.8 e029dccacc fix(deploy): bound CA-trust inject so it can't hang the handler
compose_ca.py ran ~3 docker calls per service sequentially; on a DiD
daemon saturated by a many-service app's compose up (e.g. BigBlueButton,
16 services) the inject blew the CA-trust handler's timeout and failed
the whole deploy.

- run(): Popen + kill the whole process group on timeout so a wedged
  docker/buildx grandchild cannot hold the captured pipe open and defeat
  the timeout (subprocess.run's own timeout was inert in that case).
- /bin/sh probe: DEVNULL + short bound, so a wedged probe has no pipe to
  block on.
- gather_image_meta(): read per-image (inspect + probe) CONCURRENTLY
  (8 workers) up front; the render loop then only consults the cache.
- unreadable image (missing, or inspect stalled under load) -> env-only
  injection (mount CA + set SSL_*/CURL_CA_BUNDLE env, skip the wrapper);
  never build or hard-fail, so the inject always completes.
- handlers/main.yml: timeout backstop on the "Generate CA trust override"
  handler.
- test_compose_ca.py: updated to the new run() signature + gather_image_meta.

Proven on the heaviest round: nextcloud variant-0 full_cycle went green
(failed=0, sync + async) with BigBlueButton + xwiki + all bonded partners.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 12:49:18 +02:00
kevinveenbirkenbachandClaude Opus 4.8 35c2ff5f26 feat(deploy): runner-split variant bundles + unified variant selector
Thread a single `variant` selector (CSV) end-to-end so CI can split a
role's matrix deploy across runners.

- variant_select.py (SPOT, shared by init + deploy): one --variant taking
  a CSV (2 pins one round, 0,1,2 is a runner bundle, empty = full matrix);
  replaces the old separate --variant/--variants.
- planner.filter_plan_to_variants filters the plan to a round subset.
- CI discovery (utils/github/variant_bundles.py) splits roles with more
  than INFINITO_VARIANT_BUNDLE_SIZE (default 3) variants into bundles of
  consecutive indices, one matrix runner each (5 -> 0,1,2 + 3,4), threaded
  via the variant env. test-deploy-{local,universal,workstation,server}.yml
  switch to matrix.include with a comma-free variant_slug for artifact/job
  names (GitHub expressions have no replace()).
- runtime_budget honours INFINITO_VARIANT_TIME_OVERFLOW (cut|fail) when a
  runner cannot finish its slice within INFINITO_MAX_RUNTIME.

Unit tests + docs (variants.md, deploy.md) included.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 14:52:50 +02:00
kevinveenbirkenbachandClaude Opus 4.8 4136641164 fix(nextcloud): gate fileslibreofficeedit on OnlyOffice absence
OnlyOffice declares fileslibreofficeedit (and richdocuments) incompatible and
disables them on install, so the LibreOffice editor's E2E spec failed wherever
OnlyOffice was co-deployed. Enable fileslibreofficeedit only when the OnlyOffice
addon is off (addons.onlyoffice.enabled), and add the required service-coupled
addon integration hook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 12:36:09 +02:00
kevinveenbirkenbach 868f3c107d Merge pull request #329 from infinito-nexus/update/docker-image-versions-20260619-0762d5d
update(images): update Docker image versions
2026-06-21 12:14:29 +02:00
kevinveenbirkenbach 40061b9b8a Update services.yml 2026-06-21 12:14:14 +02:00
kevinveenbirkenbach 21e484bf30 Merge origin/main into feature/026-unified-addon-syntax
Resolve conflicts: keep 026's bond/field/addon additions (services.yml, variants.yml,
yourls leaky-proxy fix, nextcloud per-file addons), take main's 027 integration-matrix
(generated artifact). Verified: all 44 main inline-plugins are covered by meta/addons/.
2026-06-21 06:32:17 +02:00
kevinveenbirkenbachandClaude Opus 4.8 266697d302 fix(nextcloud): override only dynamic variant flags; repack to 5 variants
mariadb.enabled is a static literal in meta/services.yml, so variants must not
pin it (test_variants_no_static_override). Regenerate variants.yml overriding
only the dynamic flag per service (mariadb keeps shared-only). With mariadb
correctly always-on and flowise+openwebui sharing ollama, the bond<1 partners
repack into 4 bins -> 5 variants (v0 coupled baseline + 4 partner bins), each
under the 73GB/37GB host budget.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:19 +02:00
kevinveenbirkenbachandClaude Opus 4.8 ea37027f49 fix(test): use ROLE_FILE_VARS_MAIN constant instead of hard-coded vars path
test_file_spot flags hard-coded role-file paths; import the SPOT constant from
utils.roles.mapping and build the path from it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:19 +02:00
kevinveenbirkenbachandClaude Opus 4.8 9ceeabb4d6 fix(deploy): read INFINITO_MAX_RUNTIME without an empty-string default
The empty-string fallback in os.environ.get(KEY, '') is forbidden for INFINITO_*
vars (test_no_infinito_get_defaults) — defaults live in default.env/handler. Read
it with no default and let _parse_duration_seconds accept None.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:19 +02:00
kevinveenbirkenbachandClaude Opus 4.8 6734bcc4ee test(variants): raise per-host budget to 37GB reservation / 73GB limit
The bond<1 partner bins (and the coupled v0 baseline) sit just under these
limits; the previous 32/72 cap was the single-all-on-host figure that the
6-variant distribution replaces. Honest accounting (every enabled service still
counts) — no bond-zeroing or per-variant exemption.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:19 +02:00
kevinveenbirkenbachandClaude Opus 4.8 b28436e8f9 test(variants): make the closure bond-aware + add the bond-baseline contract
test_order now only pulls bond>=1 (tightly-coupled, same-host) services into a
role's closure; bond<1 partners deploy on their own host and are excluded, so a
later variant may enable them without variant 0 having to list them.

New test_bond_baseline enforces the placement: a bond>=1 dynamic flag must be
pinned true in variant 0 (the host baseline); a bond<1 flag must be pinned true
in at least one variant. Escape: '# nocheck: bond-baseline'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:19 +02:00
kevinveenbirkenbachandClaude Opus 4.8 6b7c79edb1 test(lint): forbid addon 'enabled' deriving from group_names
An addon's enabled flag must defer to the owning role's service flag
(lookup config services.<x>.enabled), a literal true/false, or api lookups —
never a raw '<role>' in group_names test. group_names belongs in
meta/services.yml (the SPOT); addons read the resolved flag so one source
decides partner presence. New lint scans roles/*/meta/addons/*.yml (87 files)
with a '# nocheck: addon-enabled-group-names' escape.

deck.yml is the one legitimate exception (anti-dependency on web-app-taiga,
which has no nextcloud service flag) and is nochecked accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:19 +02:00
kevinveenbirkenbachandClaude Opus 4.8 71870fd46b fix(nextcloud): disable moodle + suitecrm bridges (no nc-33 plugin)
integration_moodle has no Nextcloud-33-compatible release on the app store
(absent for nc 30/32/33) and integration_suitecrm requires nc <= 24, so both
bridges cannot work on the deployed Nextcloud 33. Set enabled/shared to false
(nocheck: dynamic-flag, like minio) so neither partner is pulled or bridged,
with a TODO to re-enable once a compatible plugin ships. Verified against the
Nextcloud app store nc-33 compatibility list; all other bridges (discourse,
matrix, peertube, mattermost, mastodon, zammad, openai, openproject, gitlab,
github, google, jira, bbb, xwiki) have real nc-33 releases and stay enabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:18 +02:00
kevinveenbirkenbachandClaude Opus 4.8 ae7e45dddb feat(deploy): runtime-budget guard stops the variant matrix before INFINITO_MAX_RUNTIME
Add INFINITO_MAX_RUNTIME (env handler: 6h on real GitHub Actions, 48h otherwise)
and a RuntimeBudget guard in the dev-deploy variant loop. Before each next
variant round the guard projects elapsed + longest round so far + 30m buffer;
if it would exceed the budget it stops and emits a CI warning annotation
(never fails — the rounds that ran stay deployed).

- utils/env/handlers/infinito_max_runtime.py: new handler (canonical SPOT for
  the var; uses ctx.on_gha, materialised into .env by the generator).
- cli/administration/deploy/development/runtime_budget.py: RuntimeBudget +
  duration parser (6h/48h/90m/3600/3600s), extracted to keep deploy.py lean.
- deploy.py: loop uses budget.exhausted()/start_round()/end_round().
- tests for both the handler and RuntimeBudget.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:18 +02:00
kevinveenbirkenbachandClaude Opus 4.8 662eb302f0 feat(test): add '# nocheck: variant-limit' escape to the variant-count cap
test_limit.py hard-capped meta/variants.yml at MAX_VARIANTS (3) with no opt-out.
Allow a role whose extra matrix rounds are justified (e.g. distributing many
bridged partners so each variant stays within the memory budget) to opt out via
a '# nocheck: variant-limit' comment in its meta/variants.yml.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:18 +02:00
kevinveenbirkenbachandClaude Opus 4.8 0bff2b86b4 test(variants): raise per-variant mem_limit budget to 72GB
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:18 +02:00
kevinveenbirkenbachandClaude Opus 4.8 e8766cb427 feat(nextcloud): set bridged-partner service bonds to reflect loose host coupling
Bridged partner services (openproject, gitlab, mattermost, matrix, zammad,
openwebui, flowise, mastodon, peertube, moodle, suitecrm) get bond: 0.5 and
discourse bond: 0.3 — they are loosely coupled (separate host), unlike the
tightly-coupled bond: 1 infra. Services stay dynamic ({{ '<role>' in
group_names }}).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:18 +02:00
kevinveenbirkenbachandClaude Opus 4.8 c3f59a706e fix(roles): exempt shared-provider entities from the playwright-service-flag check
Making the partner roles shared providers (4243d76c1) added enabled: to their
primary entity, which trips the playwright-service-flag rule (an entity with
enabled: needs a <NAME>_SERVICE_ENABLED flag in templates/playwright.env.j2).
The primary entity is the role's own app, covered by the role's own Playwright
spec, so add '# nocheck: playwright-service-flag' on each entity:
bigbluebutton, flowise, matrix, mattermost, moodle, openproject, openwebui,
peertube, xwiki, zammad.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:18 +02:00
kevinveenbirkenbachandClaude Opus 4.8 ffac55c1d5 fix(proxy): stop cross-role webserver_* var leak (config fields + task-scoping + front-proxy reset + lint)
A role's vars/main.yml is auto-loaded into play scope and persists, so proxy
override vars (webserver_*) leaked into the central front-proxy render of OTHER
roles (re-evaluated against the wrong application_id) — silently applying one
role's value elsewhere, or hard-failing when the lazy lookup hit a role lacking
the key (the seaweedfs<->matrix client_max_body_size crash).

- Templates (sys-svc-proxy html/upload/ws_generic, matrix synapse homeserver)
  resolve client_max_body_size from the role's own server config
  (lookup('config', application_id, 'server.client_max_body_size', '100m')).
- Move every role-level webserver_* out of vars/main.yml into task-scoped
  include params (9 roles); they apply only to that role's proxy and can't leak.
- sys-stk-front-proxy resets webserver_* to safe defaults via task-scoped vars
  on the render tasks, so a leaked value from any other source is neutralized;
  a role's own include params (#20) still override the reset (precedence-tested,
  direct + nested).
- Add test_no_leaky_proxy_role_vars lint forbidding webserver_* in vars/main.yml
  (suppressable via '# nocheck: leaky-proxy-var').

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:18 +02:00
kevinveenbirkenbachandClaude Opus 4.8 cada7fc931 fix(seaweedfs): declare server.client_max_body_size so the config lookup resolves
web-app-seaweedfs overrides meta/server.yml without a client_max_body_size key.
Consumers that resolve webserver_client_max_body_size via
lookup('config', application_id, 'server.client_max_body_size') (e.g.
web-app-matrix, whose lazy role var is evaluated while seaweedfs' own proxy is
rendered with application_id=web-app-seaweedfs) then hit a hard lookup failure.

Declare the field on seaweedfs itself (0 = unbounded, matching its S3 object
store nature and the proxy's api branch) instead of papering over it with a
lookup default, so every consumer resolves seaweedfs' own value.

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

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:17 +02:00
kevinveenbirkenbachandClaude Opus 4.8 db46b05f62 test(variants): guard each variant against the host memory budget
Add an integration guard that, for every role and every meta/variants.yml
entry, sums the deduplicated resource footprint (own containers plus shared
dependencies, each service counted once) via the shared
utils.roles.applications.services.resources implementation and fails when a
variant exceeds mem_reservation 32 GB or mem_limit 48 GB, to avoid an OOM kill
at deploy time. The failure message recommends disabling services in the
offending variant or moving them to other variants.

Currently surfaces 6 over-budget variants (nextcloud, shopware, openproject,
flowise, gitlab, openwebui v0) that still need their footprint trimmed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:17 +02:00
kevinveenbirkenbachandClaude Opus 4.8 fc9bb0dd9d refactor(ressources): move resource collection/aggregation to utils (single spot)
Move the parse/collect/aggregate logic out of the ressources CLI into
utils/roles/applications/services/resources.py so the CLI and the upcoming
variant resource-budget guard share one implementation instead of duplicating
it. The CLI's query/__main__ and the unit tests now import from utils; the
per-module CLI files (parse.py, collect.py, aggregate.py) are removed.

No behavior change: the 43 ressources unit tests pass.

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

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

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:17 +02:00
kevinveenbirkenbachandClaude Opus 4.8 092a5c8888 refactor(cli): split + extend the role-resources tool
Rework cli/meta/roles/applications/ressources into single-responsibility
modules (parse, collect, aggregate, query, render) behind a thin __main__,
with mirrored unit tests, and extend it:

- resolve template-gated shared services to the provider role's entity via the
  service registry, so a role's full dependency footprint is listed
- bond + depth columns; entries without bond default to bond 1
- load each service once by default; --unshared lists every occurrence
- --order [asc|desc] FIELD, --filter 'bond<=0.5 & cpus>=1 & mem_limit>=512m'
- --depth N (0 = unlimited recursion over shared deps)
- --variant N applies the same meta/variants.yml overlay as inventory creation,
  reusing utils.cache.applications.get_variants (single source, no duplication)
- --sum [FIELD...] shows a SUM row over all or selected fields

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