Commit Graph
69 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5.5 2348d48110 fix(web-app-shopware): move PHP base to docker-base 8.3.36-caddy
The bare ghcr.io/shopware/docker-base:8.3 tag is frozen. Its digest
sha256:14826eb9 is the same as 8.3.25, built 2025-09-05, and carries
PHP_VERSION=8.3.25. Upstream has published only flavoured tags since
8.3.26. 8.3.36-caddy (sha256:b1fdca6e, built 2026-10-30, also the
current 8.3-caddy) carries PHP_VERSION=8.3.36. It has the same
supervisord entrypoint, www-data user and exposed ports 8000/9000 as
the bare tag. That clears the 8.3.36 security floor that hlth-runtime
reports for shopware-web, -worker and -scheduler.

The flavoured tag needs two follow-ups:

- SHOPWARE_COMPOSER_PLATFORM_PHP drops the "-<flavor>" suffix before it
  pads major.minor. Without that, composer gets platform.php
  "8.3.36-caddy". It now renders 8.3.36, and 8.3 still renders 8.3.0.
- shopware/docker 6ae1717 ("feat: remove default env vars") removed
  SHOPWARE_ES_ENABLED=0, SHOPWARE_ES_INDEXING_ENABLED=0 and
  SHOPWARE_ES_INDEX_PREFIX= from the image. Shopware 6.7.13.0 reads
  them through %env()% with no container default, so env.j2 now sets
  the values the old image shipped. SHOPWARE_HTTP_DEFAULT_TTL keeps its
  7200 default from core services.xml. Nothing in Shopware reads
  DATABASE_PORT, and the role uses DATABASE_URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 00:45:19 +01:00
infinito-nexus-updater[bot] 1ba7903f31 update(images): update Docker image versions (web-app-shopware) 2026-10-30 04:58:18 +00:00
kevinveenbirkenbachandClaude Opus 5.5 0f7fb92e12 refactor(roles): take seven roles' memory parameters from the container limit
Memcached, Joomla, Moodle, Odoo, Shopware, Snipe-IT and SuiteCRM set
their cache size, PHP memory_limit, OPcache, worker limits and search
heap as literals next to a mem_limit they never read. They now ask
lookup('service_memory_mb') for a share of that limit. The shares are
chosen so every rendered value stays what it was.

Where the parameter lives in a file that cannot hold a template, the
value is handed in: Shopware's init script reads INIT_PHP_MEMORY_LIMIT
and refuses to start without it, SuiteCRM's image takes it as a build
argument. Shopware's worker gets --memory-limit as a command appended
to its entrypoint, and Moodle's OPcache size moves from the static ini
into the rendered one.

The performance.php.memory_limit keys of Joomla and Moodle are gone
from services.yml; nothing else read them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 23:24:22 +02:00
kevinveenbirkenbach aa55c8c8bd Merge branch 'main' into feature/role-stalwart-mail-swarm 2026-10-02 17:40:09 +02:00
infinito-nexus-updater[bot] 2f47588040 update(images): update Docker image versions (web-app-shopware) 2026-09-30 14:59:27 +00:00
Alejandro Roman 4b9a834546 Merge remote-tracking branch 'upstream/main' into feature/role-stalwart-mail-swarm 2026-09-21 03:25:20 +04:00
kevinveenbirkenbachandClaude Opus 5 96408f23a0 perf(healthcheck): let the start window outlast a real boot
start_interval is the cadence while start_period runs, and only while it runs.
Once the window closes the probe falls back to interval, so a service that
becomes ready just after it pays a whole tick anyway and the cadence never
reached it. Six of the seven flavors were shaped that way, Curl worst: a thirty
second window against a one minute interval.

Measured across 151 converge waits in four job logs, 63 sit exactly on a tick
boundary, 36 in the thirty second step and 27 in the sixty second one.
Attributed by application, web-app-seaweedfs is the largest at 1501s over
sixteen waits with a median of 57.7s: it probes with curl and fell straight into
the gap. web-app-zammad probes with connect, whose window is fifteen minutes,
and its 154s median is real boot time with nothing to recover.

So the window moves to ten minutes and the steady state interval to two, and the
flavor overrides that sat below them go: Curl, Wget and Connect lose interval
1m, Http its twenty second window, Nc its ten. Connect keeps fifteen minutes.

Neither number costs anything while a container works. start_period delays
nothing, it only stops failures counting, and interval no longer touches
convergence at all since that hangs on the window alone; it is steady state
load, which drops from 120 probes an hour per service to 30.

Forty six role files follow. Twenty seven declarations go that repeat the new
default or undercut it, an undercut window being the defect itself. Eight
windows longer than ten minutes keep their value and now say why. The literal
blocks and the three Dockerfiles this repository builds spell every timing out,
because outside the lookup an omission takes docker's value and not the
platform's.

A fourth lint rule keeps the shape: start_period must outlast one interval,
checked against the effective values, so raising interval without the window
cannot quietly bring the cliff back.

Two faults in the lint surfaced while building it and are fixed here. It
compared timings as strings, so 600s and 10m read as different values, and it
matched the healthcheck key exactly, so a line carrying a trailing nocheck was
skipped whole, which is how the two swarm-init partials went unexamined.

One regression, bounded. A container that starts and never turns healthy is
neither rejected nor errored, so stack_ready.sh only calls it fatal after three
killed tasks, and each cycle now spans the longer window. That reaches twenty
minutes rather than six, capped there by swarm_stack_converge_retries at 600
with a two second delay, and it lands only on runs that were failing anyway.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 21:54:26 +02:00
kevinveenbirkenbachandClaude Opus 5 19f4ac3390 refactor(healthcheck): drop timings that repeat their flavor default
The lookup writes overrides.get(key, effective_default), so a service that
repeats what it would have inherited changes nothing and only hides the day the
default moves. Forty seven such lines stood in the tree.

Almost all of them came from the migration that moved the literal blocks into
meta/services.yml. The values were carried over verbatim so the move could be
proven lossless, which was right for the proof and left the no-ops behind.

The defaults they are measured against are the effective ones, not the base
class. Curl probes every minute, Connect grants a fifteen minute start window,
and a prefix raises timeout, retries and start_period further. Comparing against
Probe alone would have called out every service that simply agrees with its
flavor.

Eight services lose interval: 10s as well and fall back to their flavor's
cadence, thirty seconds, or a minute for decidim's curl. Since start_interval
exists, interval no longer touches convergence at all: it is steady state load
for the life of the deployment. That takes the five databases, matomo, snipe-it
and decidim from 360 probes an hour each to 120. It buys that with slower
failure detection, 180s to 540s for the databases, which is acceptable because
an unhealthy container fails the deploy whichever number stands there.

Twelve deviations that do cost something now carry their reason. Where the
mechanism is known it is named: ejabberdctl and rabbitmq-diagnostics start an
Erlang CLI node, mmctl spawns a binary and talks to the local socket, php artisan
boots Laravel on every probe, keycloak's management endpoint answers from the
JVM.

Two say instead that the value was carried over and never measured. './fider
ping' and pgrep both fit the five second default and no reason for ten is
visible, so the comment records that there is no evidence rather than inventing
one. They mark where a measurement belongs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 21:02:02 +02:00
kevinveenbirkenbachandClaude Opus 5 d0a8608bfa perf(healthcheck): give every probe the start cadence 42ebd9ea71 added
42ebd9ea71 gave the healthcheck SPOT start_interval, the cadence docker uses
while start_period is still running. A block a template writes out itself
bypasses the SPOT and never gained it. Measured across the repository: 46 such
blocks, 40 of them already carrying start_period, not one carrying
start_interval.

Without it a probe fires only every interval, so a service ready after two
seconds is reported healthy at the next tick and a stack converges no faster
than its coarsest quantisation. That lands in sys-svc-compose's swarm converge
wait, 1551.7s per CI job over 38 stack deploys.

Twenty five of the blocks move into meta/services.yml and render through
container_healthcheck. Each keeps an explicit test rather than picking a flavor:
a flavor would replace the command instead of carrying it across.

The move is lossless by construction. probes.py block() writes
overrides.get(key, default) for interval, timeout, retries, start_period and
start_interval, so a declaration repeating the literal values renders the same
block plus the missing cadence. Checked per block against the old values: the
only difference is start_interval 5s, nothing changed, nothing lost.

Twenty one stay literal. Seventeen interpolate a template variable, whose value
resolves in a scope meta/services.yml does not share, so moving them would be a
rewrite rather than a transcription. Four interpolate nothing and still cannot
move: svc-db-memcached and svc-db-redis probe every second in their swarm
service partial, where a 5s start cadence would report healthy later than the
block already does, and the sys-svc-rdbms and keycloak sso_proxy partials render
for the consuming role's application_id, so their declaration has no single
meta/services.yml to live in. Each carries a nocheck naming its reason.

All twenty one gain start_interval. Three also gain start_period, without which
the key does nothing at all, since it only applies while the start period runs.
That widens their failure tolerance rather than narrowing it: bluesky from 90 to
120 seconds, magento from 50 to 80, the mariadb partial from 180 to 210.

pixelfed spelled its probe as a bare string and now spells it CMD-SHELL. Docker
reads both as a shell command; only the YAML shape changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 20:31:07 +02:00
Alejandro Roman 333079cc2a feat(UI): fix migration, errors happening in local deploy and swarm integraton + merged main 2026-08-30 21:39:52 +04:00
Alejandro Roman 7332bd66ae Merge upstream/main into feature/role-stalwart-mail-swarm 2026-08-18 01:42:23 +04: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 e16eb5728d test(lint): drop the minimal variant's exemption from the onion
72fd9c870 required the onion in every variant but exempted variant 1, on the
argument that the minimal round is the one place the stack runs without it. That
argument does not hold: the onion is part of the deployment under test, not a
feature bolted onto the full shape. A round that skips it is not a smaller
deployment, it is a different one.

So the exemption is gone and 95 variant-1 entries pin the onion on. Two
exclusions remain, both read from data rather than a comment: a role with no tor
block has no onion to pin, and a role whose meta/services.yml pins tor.enabled to
a literal false has already declared it has none.

That put the rule in conflict with test_coverage, which requires both polarities
of every dynamic flag: with tor on everywhere, the false side has no variant left
to live in. 94 roles take the exemption that contract already provides for
exactly this - "flags whose group-derived value is a hard invariant that variants
must never override", the same one 4e6ef2542 used for the seaweedfs frontend.
web-app-nextcloud needs none: its variant 8 pairs the clearnet-only PeerTube and
keeps the false polarity.

Measured, because the declaration is not what CI runs. assign_tor gives the onion
to every second capable entry with the run number as the parity switch, so more
capable variants do not mean proportionally more onion jobs: across five roles
and 21 entries the count goes 8 -> 10 on an even run and 9 on an odd one. The
clearnet path also survives - checkmk reports v1=false on run 0 and v1=true on
run 1, so both states are still covered across two consecutive runs. What the
94 markers give up is a declarative guarantee in variants.yml, not the coverage
itself.

The real cost is weight, not job count. web-app-nextcloud variant 1 goes from
3.09GB reservation / 7.27GB limit to 18.26GB / 43.01GB once svc-net-tor and its
closure join, so the cheap round is no longer cheap. Every variant stays under
the 32/64GB budget, and the same effect applies to the other 94 unmeasured.

Verified: tests/lint 336 passed / 307 subtests, tests/integration/roles/meta/
variants plus the role-meta lints 39 passed. The rule was checked in both
directions - it reported the variant-1 offenders before the flips and none after.

Not measured: what the heavier rounds do to wall-clock, against swarm jobs capped
at 355 minutes and one that has hit that ceiling before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 06:09:02 +02:00
kevinveenbirkenbachandClaude Opus 5 b2a0a317fa refactor(tor): loosen the onion bond to 0.5
Every variants.yml header states the rule the bond feeds: variant 0 carries each
tightly-coupled (bond>=1, same-host) service, while loosely-coupled (bond<1,
separate-host) partners are excluded from the closure and distributed across
later variants to keep every host under budget. tor was declared bond: 1 in all
101 consumers, so it had to sit in every variant-0 closure.

It does not belong there. The onion is a separate-host provider reached over the
node's hidden service, the same shape as the bridged partners that already carry
0.5, and f1dff50fb established both the reasoning and the precedent when it
dropped xwiki and bigbluebutton from 1 to 0.5: bridged partners with their own
role and their own spec, whose bond declaration was the outlier rather than
their placement.

This also unblocks the variant-level tor contract. Requiring a non-baseline
variant to pin the onion on while tor still counted as tightly coupled would
have pulled variant 0's closure requirements along with it.

Verified: tests/lint 336 passed / 307 subtests and tests/integration/roles 107
passed / 610 subtests, including test_resource_budget over every variant's
deduplicated footprint and the bond-aware test_order closure.

Not measured, and the honest cost: f1dff50fb's own caveat applies unchanged
here. Whether redistributing a service out of the baseline closure buys back
wall-clock is not established; role-runtimes has the per-role seconds and no run
has been made with this shape.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 06:09:01 +02:00
kevinveenbirkenbachandClaude Opus 5 e96c752549 Merge fix/playwright/env-template-gate into feature/svc-net-tor
75 commits: the socialhome role reimplemented against the project it actually
deploys, native-login specs for funkwhale and shopware, a window.open IdP
handoff and a settings-link fallback in the shared persona helpers, a swarm
converge budget that gives up on a terminal task, and a long tail of role
fixes.

Eleven conflicts. The shared persona helpers (keycloak.js, logout.js) take the
incoming structure — they gained real behaviour this branch never had — with
this branch's resolveTimeout convention re-applied over it and
confirmKeycloakLogoutIfPrompted re-attached at every return point; without it
the Keycloak SSO session outlives the logout. playwright.config.js keeps this
branch's version: both sides set actionTimeout, but ours routes through an env
var and 15s is too short behind an onion. socialhome's spec takes the incoming
content — 2dbd6eb10 found the role described the wrong upstream project
entirely — and keeps gotoOnion and expectHstsWhenTls, since demanding HSTS over
a plaintext onion is a false failure. Both READMEs were regenerated rather than
merged by hand.

Three collisions arrived with no conflict marker at all, because each side
changed a different place in a different file:

- group_vars/all/18_swarm.yml declared swarm_stack_converge_retries twice.
  Same value, so nothing breaks today; editing the documented one would have
  silently changed nothing.
- svc-net-tor and web-app-socialhome both claimed 192.168.74.0/24. Socialhome,
  the newer role, moves to 192.168.75.0/24.
- web-app-socialhome/meta/schema.yml arrived as a new file, so ef5ea7cb5's
  rename never saw it. Its credential would not have resolved and the role
  would have deployed with an unusable administrator account.

The incoming specs also needed this branch's conventions: raw timeouts wrapped,
four page.goto calls routed through gotoOnion, a request.get given an
onion-aware timeout, and 21 roles marked playwright-service-flag over the tor
block this branch added to them.

make quality green on the merge result.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 14:20:19 +02:00
kevinveenbirkenbachandClaude Opus 5 6b711a2940 fix(web-app-shopware): take the upstream twig 3.28 fix
The admin returned HTTP 500 with 'TwigFeaturesWithInheritanceExtension::
include(): Return value must be of type string, Twig\Markup returned'.
Twig 3.28 dropped the return type from CoreExtension::include(); 6.7.8.2
still declares ': string'. The template ships no composer.lock, so the
build resolves twig freely and picks up 3.28.

Upstream fixed the signature to ': string|Markup' and shipped it from
6.7.12.1 on, so this is a version bump rather than a transitive pin the
repo cannot see. Untested beyond the signature check: five patch releases
of delta, and 6.7.13.0 adds a shopware.github.io composer repository the
package cache does not proxy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 01:14:29 +02:00
kevinveenbirkenbach 25f89cf8a7 Merge branch 'main' of github.com:infinito-nexus/core into fix/playwright/env-template-gate
# Conflicts:
#	package.json
2026-08-08 18:48:34 +02:00
kevinveenbirkenbach 2789d10287 Merge branch 'main' into feature/svc-net-tor 2026-08-08 11:16:05 +02:00
infinito-nexus-updater[bot] a0a89e7bb6 update(images): update Docker image versions (web-app-shopware) 2026-08-08 06:05:02 +00:00
kevinveenbirkenbachandClaude Opus 5 92faba5d2d feat(playwright): make the env template mandatory so specs actually run
test_has_spec.py forces every roles/web-app-* to ship a Playwright spec and
calls a missing one a hard error, because the persona contract must never be
silently absent from the deploy capstone. Nothing forced the other half. The
runner discovers roles by rglob('templates/playwright.env.j2')
(discover_playwright_roles.py:53), so a spec without that file is staged
nowhere and executed never.

21 of 76 web-app roles were in exactly that state. Verified rather than
inferred: running the discovery filter directly returns 61 roles and none of
the 21 is among them, `make compose-playwright` refuses without a rendered
.env from a prior deploy, and `git log --all` on those paths shows the file
never existed on any branch -- the single hit is web-app-gitlab on a fork
branch. Their specs have never run once. 15 of the 21 deploy in CI today,
reporting green while their only functional verification never fires.

The new lint mirrors test_has_spec and carries a per-role opt-out that is
used nowhere: uniform beats exceptions.

Every one of the 21 now has a template written against its own
meta/services.yml rather than a shared skeleton -- espocrm splits
ADMIN_PASSWORD from ADMIN_NATIVE_PASSWORD because OIDC_ALLOW_ADMIN_USER
leaves both login paths open, the four permanently auth-less roles declare
their personas blocked instead of shipping a knowingly-red spec, and
web-app-mastodon gained a real test-seaweedfs.js because no honest
suppression existed for a role that does route Paperclip to S3.

web-app-magento moves pre-alpha -> alpha here: it now has a spec that runs,
which is what the lifecycle gate is about.

Six meta/services.yml files change only where adding a template falsified an
existing suppression -- gitlab and shopware both claimed the role 'ships no
templates/playwright.env.j2', which stopped being true in this commit.

Not resolved here, and the adversarial review says so plainly: the specs are
enabled, not proven. None has executed against a live stack. The review found
three classes of defect that land in follow-up commits rather than being
smuggled in silently -- persona blocks that are missing or should be tied to
sso.enabled, eleven suppressions naming a sys-svc-mail test suite that does
not exist, and eight claiming a CSP assertion that cannot fire because no
consumer role renders CSS_BASE_URL.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 14:17:55 +02:00
kevinveenbirkenbach e04c448cdd Merge branch 'feature/swarm-nfs' into feature/svc-net-tor
Three conflicts, none resolved by picking a side wholesale:

- Makefile: took the incoming ACT_INPUTS, which routes through SWARM_DISTROS
  instead of inlining the same expression a second time.
- test-e2e-playwright/02_run_one.yml: took the incoming rc handling. An absent
  rc now counts as failure; the previous default(0) let an aborted Playwright
  run pass silently, contradicting the debug message right above it.
- web-app-peertube spec: combined both. Kept gotoOnion and resolveTimeout,
  which this branch's lint enforces, and adopted the incoming retry-inside-poll
  for the OIDC button plus its networkidle wait.
2026-07-30 15:38:15 +02:00
kevinveenbirkenbachandClaude Opus 5 028f4b85f4 chore(storage): set every image-backed min_storage to its image size plus 200MB
The declared values were guesses that ran up to 60x the size of the image they belong to: seaweedfs asked for 30GB against a 104MB image, nextcloud for 20GB against 353MB. Because min_storage feeds the storage guard, the runner packer and the sufficient-storage filter, those numbers made hosts look too small and pruned caches that deploys did not need to lose.

Every service that declares min_storage together with image and version now carries the measured compressed transfer size of that image plus 200MB of headroom. Sizes were read from the registries through utils.docker.registry.manifest_transfer_size, the same path the headroom test uses, so the declarations and the gate agree by construction. Values are written in MB throughout, matching the existing 500MB / 100MB / 50MB style.

119 declarations across 89 roles. Range: 206MB (memcached) to 3473MB (ollama); four exceed 1GB. The 84 min_storage entries that name no image are untouched, since there is nothing to measure them against.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 15:08:51 +02:00
Alejandro Roman ac101d2efa Merge kevinveenbirkenbach/feature/swarm-nfs into stalwart integration branch 2026-07-21 10:00:17 +02:00
kevinveenbirkenbachandClaude Fable 5 d88f272f9f Merge branch 'feature/swarm-nfs' into feature/svc-net-tor
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 15:31:08 +02:00
kevinveenbirkenbachandClaude Fable 5 407b8a8a1f fix(e2e): use the tls lookup instead of the unregistered urlsplit filter; autoformat
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 21:05:59 +02:00
Alejandro Roman ab25684714 adrees all commens and fix CI usses on prev run 2026-07-16 19:19:23 +02:00
kevinveenbirkenbachandClaude Opus 4.8 7a6268fa96 refactor(deploy): replace skip with services modes
Per-role deploy-mode opt-out moves from meta/tests.yml 'skip:' to meta/services.yml <entity>.modes.{compose,swarm}.enabled on the primary entity. get_role_skip now derives from it; get_role_mode_enabled added. A new lint requires every role shipping templates/*compose*.yml.j2 to declare both mode flags. Migrates 107 services.yml and drops 11 skip-only tests.yml. Also removes the variant_status deployability gate so swarm runs one job per variant unconditionally (variant_status.py and deployable_variant_indices deleted, complexity model simplified). Adds the i8matix alias.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 03:44:17 +02:00
kevinveenbirkenbachandClaude Fable 5 4a23e0cb09 feat(services): declare locally-built images via services.<key>.custom
The custom flag moves from 33 hardcoded template kwargs into the merged
service config: 'custom: true' names the shared <entity>_custom build,
a string names a second distinct '<string>_custom' image inside one app
(nextcloud whiteboard). The image lookup reads the declaration as a
fallback, so config consumers (e.g. backup image matching) resolve the
exact deployed reference from one declaration point. nextcloud cron now
runs the nextcloud_custom build like the app service instead of the
upstream image.

A new services lint couples the declaration to reality in both
directions: custom requires a role Dockerfile, and a role Dockerfile
requires a custom declaration. The eight legacy roles deploying local
builds under upstream tags or ad-hoc image= overrides are ratcheted via
'# nocheck: dockerfile-custom'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 19:48:53 +02:00
kevinveenbirkenbachandClaude Fable 5 cf32840586 feat(lint): require *_PORT vars to resolve via lookup, migrate literals
New lint rejects literal port numbers in roles/*/vars/main.yml: the port
belongs in meta/services.yml (services.<svc>.ports.<scope>.<name>) where
band governance and stack rendering read it; vars resolve it via
lookup('config', ...). Migrated the 16 remaining literals across
elasticsearch, registry-docker, erpnext, flowise, funkwhale, magento,
moodle, nextcloud, penpot and shopware.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 03:15:53 +02:00
kevinveenbirkenbachandClaude Opus 4.8 f76bfcf531 refactor(svc-bkp-volume-2-local): rename role from svc-bkp-container-2-local
Rename the role, its test tree, the primary entity key
(volume-2-local), the derived vars (BKP_VOLUME_2_LOCAL_*,
SYS_SERVICE_BACKUP_VOLUME_2_LOCAL), the run-once fact, and every
group_names consumer flag, include path, doc, and lint reference. The
container_backup service key and the on-disk backup-docker-to-local
tree keep their names: both are contracts with existing consumer
entries, snapshot trees, and the baudolo pull tooling.

Operators must rename the svc-bkp-container-2-local inventory group and
disable the previous versioned units on already-provisioned hosts; the
pre-deploy snapshot glob derives its base from the new role name and
will not find them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 18:52:02 +02:00
kevinveenbirkenbachandClaude Opus 4.8 4473edafb2 feat(roles): declare the container_backup consumer flag on stack roles with volumes
Every invokable stack role that declares named volumes in meta/volumes.yml now carries the standard container_backup consumer entry: enabled resolves via 'svc-bkp-container-2-local' in group_names, shared stays literally true since there is no per-consumer backup instance. Playwright/dynamic-flag/bond-baseline suppressions ride on the block per the established idioms; stale explanatory comments in the touched files are dropped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 23:04:40 +02:00
kevinveenbirkenbachandClaude Opus 4.8 4e005841da fix(shopware): wire redis sessions so web/worker can scale, pin singleton sidecars
cache and messenger already use the mounted REDIS_URL, but the framework.yaml omitted a session handler, so sessions were node-local files and logins/carts broke behind the swarm VIP. Set framework.session.handler_id to the existing REDIS_URL so sessions are shared, and drop the entity replicas:1 pin so web and worker default to the topology count. Re-pin the genuine singletons: init (one-shot install/migrate), scheduler (double-dispatches on >1 poller) and opensearch (discovery.type=single-node). Compose is byte-identical (replicas ignored). Confidence ~94% (session cross-replica reasoned, not live-verified).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 02:23:40 +02:00
kevinveenbirkenbachandClaude Opus 4.8 0a7164af48 feat(tor): per-role onion service block in meta/services.yml
Domain-serving roles reference the svc-net-tor provider via a tor: block. Consumers declare enabled (dynamic group_names gate) and shared: true (static — onion is always the shared node model, inline nocheck: dynamic-flag). The provider svc-net-tor additionally declares exclusive/primary defaults that consumers inherit via resolve_service_config unless overridden.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 23:53:16 +02:00
kevinveenbirkenbachandClaude Opus 4.8 945fbccc1e refactor(swarm): move service_replicas into services.yml
Replace the per-role service_replicas Jinja variable with a per-service
replicas: field in meta/services.yml. deploy.yml.j2 resolves it via
resource_filter (with entity fallback), the same path already used for
cpus/mem, so services.yml is the single source of truth for replica
overrides.

- 13 web roles migrated to per-service (or entity-level) replicas: 1
- matomo bootstrap -> replicas: 0, mastodon migrate -> replicas: 1
- minio/seaweedfs provider entries -> replicas: 1, which also pins the
  central single-writer object-store providers instead of relying on the
  topology default
- service_replicas removed entirely; dropped its dead reference from the
  compose-structure lint allowlist and updated requirement 023 wording

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 05:05:11 +02:00
kevinveenbirkenbachandClaude Fable 5 30afef6bbe fix(resources): entity fallback, short compose service names + SPOT vars
resource_filter now falls back service_name -> entity_name -> hard
default, so entity-level limits keep applying to per-service compose
entries. Compose service names drop the entity prefix (kix, jitsi,
zammad, bluesky); the docker container_name keeps it via compose_only.
A new lint forbids '<entity>-'-prefixed service names.

Service names and internal ports are declared once: vars/main.yml
holds <ROLE>_<SERVICE>_SERVICE constants (container names derived via
entity_name), meta/services.yml declares ports.internal consumed
through config lookups. sso-proxy upstreams stack-qualify the origin
host in swarm because short aliases are ambiguous on shared overlays.
Stale proxy.service_key entries fixed for erpnext/shopware/nextcloud.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 17:05:56 +02:00
Alejandro Roman 9962429e59 Added playwright testing 2026-06-30 20:37:50 +02:00
Alejandro Roman 2f693f00e6 feat(stalwart):Created the role and the connection like mailu had it 2026-06-30 16:17:48 +02:00
kevinveenbirkenbachandClaude Opus 4.8 36ebf34b64 Merge branch 'main' into feature/swarm-nfs
Resolve 69 conflicts across ~70 files: union main's image-version bumps and new service fields (bond, resource limits) with the swarm branch's default_placement / NFS / IS_STACK_HOST config; retire the three per-deploy-type test workflows main still edited (consolidated into compose+swarm); adopt main's run-once 01_core dispatchers while re-applying each role's swarm guard. Fix the post-merge lint surfaced by main's stricter rules: bond + resource-budget keys, container-exec stack-host gating + resolver routing, leaky/dead proxy vars, suitecrm once-flag, xwiki variant nocheck, nextcloud oversized-block to gated include, matrix-mdad capped at 3g. make autoformat clean, make test green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 17:13:55 +02:00
kevinveenbirkenbachandClaude Opus 4.8 6ca9a506f8 test(lint): extend swarm-internal-port rule to all published kinds
Generalize tests/lint/roles/test_swarm_internal_ports.py from http-only to every
published port kind (http, websocket, ssh, ...) referenced in a compose port
mapping (ports.<local|public>.<kind>): each must declare the matching
ports.internal.<kind> so the role's swarm port surface is complete and
consistent. Fix the roles it flagged:

- pihole, shopware: ports.internal.http (80, 8000)
- erpnext, mastodon, odoo: ports.internal.websocket (9000, 4000, 8072)
- gitea, gitlab: ports.internal.ssh (22)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 03:30:11 +02:00
kevinveenbirkenbachandClaude Opus 4.8 f4774ec145 refactor(apps): remap consumers to central engines
About 38 web-app/web-svc roles now consume the central svc-db-* and svc-dns engines via lookup('engine', ...) instead of embedded sidecars: meta/services flags, variant matrices, and env/conf hosts point at the shared services. Lets these apps run unpinned and NFS-shareable in swarm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 19:29:39 +02:00
kevinveenbirkenbachandClaude Opus 4.8 3fa781004d feat(roles): add bond integration-importance factor to shared services
`bond` is a 0..1 factor for how strongly a service is tied to the overall
integration: bond: 1 = always required, bond: 0 = omittable. Groundwork for
tooling that later scores service importance and decides what can be dropped.
Named `bond` (not `bound`) to avoid clashing with the resource bounds/limits.

Add test_service_bond.py (lint): every enabled + shared service must declare a
valid `bond` (number in 0..1). Seed `bond: 1` as a placeholder on all 638
enabled+shared service entries across 88 roles, to be refined later.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:03:17 +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.8 cb0780bf9b 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-17 11:30:36 +02:00
kevinveenbirkenbachandClaude Opus 4.8 1ed0afcd61 test(objstore): require seaweedfs for database-backed web-app roles
Add a meta lint asserting every web-app role with postgres or mariadb declares a seaweedfs object-store service, exemptable via the seaweedfs-required nocheck marker. Mark the 33 database-backed roles that do not use object storage, and register the rule in the suppression catalog.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:33 +02:00
kevinveenbirkenbach 29519603ef Merge remote-tracking branch 'origin/main' into feature/swarm-nfs 2026-06-14 20:32:59 +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.8 bc075906d7 test(objstore): require seaweedfs for database-backed web-app roles
Add a meta lint asserting every web-app role with postgres or mariadb declares a seaweedfs object-store service, exemptable via the seaweedfs-required nocheck marker. Mark the 33 database-backed roles that do not use object storage, and register the rule in the suppression catalog.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 21:00:13 +02:00
infinito-nexus-updater[bot] c0bfe446dc update(images): update Docker image versions 2026-06-10 08:07:28 +00:00
kevinveenbirkenbachandClaude Opus 4.7 895261f107 feat(sys-svc-container): topology-driven swarm replicas
Default service_replicas to (groups[application_id] | length) instead of
hard-coded 1. Single-host inventories still get replicas=1; 3-host swarm
inventories now get replicas=3 for every stateless service that renders
the deploy: block. Stateful services (DBs etc.) are unaffected because
deploy.yml.j2 gates the block on DEPLOYMENT_MODE == 'swarm' and stateful
services run as plain compose.

Simplify deploy.yml.j2 (the | default fallback in the template is now
redundant) and drop the explicit replicas:3 + SHOPWARE_WORKER_REPLICAS
plumbing from web-app-shopware - the new default covers it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-05 18:19:03 +02:00
kevinveenbirkenbach e423504919 Merge branch 'main' into update/docker-image-versions-20260529-0fa0da1 2026-06-01 11:35:28 +02:00