Commit Graph
98 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5.5 2b04853701 fix(conferencing): give Matrix, Jitsi and BigBlueButton distinct domains
Since #1180, web-app-matrix declares a canonical `jitsi` domain for its
internal Jitsi, also when it uses the shared web-app-jitsi (only the web
health check skips it then). canonical_domains_map checks every
canonical domain for uniqueness, so a host that serves web-app-jitsi on
jitsi.<domain> fails the constructor with
"Domain 'jitsi.infinito.nexus' is already configured for 'web-app-jitsi'
and 'web-app-matrix'" (echoserver, 2026-10-23).

The defaults now use one namespace per owner:
- web-app-matrix internal Jitsi: jitsi.matrix.<domain> (was jitsi.<domain>)
- web-app-jitsi: jitsi.call.<domain> (was meet.<domain>)
- web-app-bigbluebutton: bbb.call.<domain> (was bbb.talk.<domain>)

This changes the public URL of BigBlueButton and of the shared Jitsi on
every host that relies on the defaults.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 21:56:35 +02:00
kevinveenbirkenbachandClaude Opus 5.5 b83b361ea9 fix(web-app-bigbluebutton): run BBB v3.0.39 with the shared-notes server
The version-source update in #1146 moved the bigbluebutton/docker ref from
v3.0.23 to v3.0.39. That tag changes three things the role did not
handle, and it exposed a fourth defect in compose_file_args.

1. generate-compose renders etherpad and bbb-pads only behind
   USE_INSECURE_ETHERPAD and otherwise emits bbb-shared-notes-server with
   a `build:` block and no `image:` (upstream adds the image only after
   the tag, in 9a6a9a9a4c). compose_mods drops `build` only where an image
   is set, so the guard "Fail if any service still builds locally" stopped
   every BigBlueButton deploy (main run 39749899667, Nextcloud#11:
   `rc: 0`, `stdout: '    build:'`, bootstrap_compose_project.yml:35).
   meta/services.yml now pins alangecker/bbb-shared-notes-server:v3.0.39,
   compose_mods takes `pinned` (BBB_PINNED_SERVICES, every service the
   role config pins an image for) and drops their `build`, and the
   etherpad leftovers go (ep_redis_publisher patch, etherpad postgres
   entrypoint and initdb hook, etherpad database tasks, ETHERPAD_* env,
   etherpad_api_key secret).

2. The notes server connects to postgres once at start and exits with 2
   when it is not up yet; upstream gives it no restart policy. The
   override sets the role's restart policy, so it retries until postgres
   accepts connections.

3. Greenlight moves from v3.8.0 to v3.9.0.1, which sets
   `config.hosts = URL_HOST`, and the template passes
   `URL_HOST: https://${DOMAIN}`. Rails compares hostnames, so every
   request was blocked (`Blocked hosts: bbb.talk.infinito.test`, 403).
   Greenlight documents URL_HOST as the bare hostname; the override sets
   it to the role domain.

4. compose_file_args decided whether a role ships a compose override by
   templating "{{ application_id | abs_role_path_by_application_id }}"
   through the templar. ansible-core 2.21 does not render an untrusted
   string, so the check saw the literal and always returned False: the CA
   override generation and the pull handler never read BBB's
   compose.override.yml. That was harmless while compose.yml alone was
   valid; with the notes-server image living in the override, `compose
   config` failed ("has neither an image nor a build context"). The check
   now resolves the role path for the application id it is given. BBB is
   the only role that ships an override, so no other role's file list
   changes.

Existing installations drop the etherpad and bbb-pads containers on the
next `compose up --remove-orphans`; the old `etherpad` database stays in
PostgreSQL untouched.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 16:56:48 +02:00
kevinveenbirkenbachandClaude Opus 5.5 c65c5ce48c fix(update): pin only image tags the registry carries
7e82ba6d71 moved web-app-bigbluebutton's bbb-pads to v1.5.11, the tag
https://raw.githubusercontent.com/bigbluebutton/docker/develop/repos/tags
names for the bbb-pads source. alangecker/bbb-docker-pads has no such
image (Docker Hub: newest v1.5.8), so every BigBlueButton deploy pulls a
missing tag and test-external fails "These pinned Docker images do not
exist in their registry (HTTP 404): web-app-bigbluebutton/bbb-pads".

bbb-pads goes back to v1.5.8. The version-source updater now knows the
image a `version` pin tags (pulled directly or as the base of a custom
build) and withholds a bump when the registry answers 404 for the new
tag; an indeterminate answer does not block it, the same line
test-external draws. Run against main, the updater now reports no
update for bbb-pads instead of v1.5.11.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 10:30:10 +02:00
infinito-nexus-updater[bot] 7e82ba6d71 update(versions): update pins with a declared source (web-app-bigbluebutton) 2026-10-22 04:44:07 +00:00
infinito-nexus-updater[bot] 148274cc12 update(images): update Docker image versions (web-app-bigbluebutton) 2026-10-21 23:27:05 +00:00
evangelostsak 12590505e0 feat(web-app-bigbluebutton): let a deployment put its hosts into meshes 2026-10-16 03:40:17 +02:00
infinito-nexus-updater[bot] 8637ba92d4 update(images): update Docker image versions (web-app-bigbluebutton) 2026-10-12 07:34:01 +00:00
kevinveenbirkenbachandClaude Opus 5.5 c715b02b31 chore(web-app-bigbluebutton): apply the quoting autoformat writes for the release regexes
Every make quality run rewrites the three http_regex patterns in
meta/services.yml from single to double quotes and leaves the tree dirty.
Both forms load to the same strings; committing the autoformat output ends
the drift.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 10:06:01 +02:00
kevinveenbirkenbachandClaude Opus 5.5 73c7d862fb fix(web-app-bigbluebutton): take the nginx base and Etherpad version from upstream
The sources of nginx and etherpad carried 1.29 and 2.4.2 as literals.
Upstream keeps both in docker-compose.tmpl.yml, not in repos/tags, so a
change there would have left the proposed tag on the old value while the
image updater no longer watches these pins.

Both sources now read repos/tags and the compose template and take the
value from the image line.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 11:26:13 +02:00
kevinveenbirkenbachandClaude Opus 5.5 7d104ec139 fix(web-app-bigbluebutton): move the release set through one version source
The ref and the fifteen image pins are one upstream release, but the image
updater moved the Docker tags alone while the ref never moved, because
bigbluebutton/docker publishes its releases as branches and the repository
updater reads tags. 759a1aa4d0 stopped the split with fifteen
nocheck: docker-version markers, which also stopped every bump.

The ref and every pin of the set now name upstream's release manifest on
the default branch as their source: repos/tags for the versions,
docker-compose.tmpl.yml for periodic. freeswitch, nginx and etherpad
assemble their compound tags from it. One resolver run proposes the whole
set, and the markers are gone.

The pinned versions do not change. Upstream's v3.0.39 is still an open
pull request (bigbluebutton/docker#397).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 10:32:14 +02:00
kevinveenbirkenbachandClaude Opus 5.5 759a1aa4d0 fix(web-app-bigbluebutton): keep the images on the release the ref names
The image updater raised nine BigBlueButton images to v3.0.39 and
webrtc-sfu to v2.24.0, while the role still generates its compose file
from bigbluebutton/docker at ref v3.0.23. compose.override.yml.j2 runs
every sub-service on the pinned version, so bbb-web v3.0.39 started
against the v3.0.23 template, found no USE_INSECURE_ETHERPAD and
restarted until hlth-container failed web-app-nextcloud#11 in run
37705983036.

The versions are back on the set that repos/tags of v3.0.23 names. All
fifteen components of the upstream compose now carry
nocheck: docker-version, because upstream publishes its releases as
branches: the ref updater reads tags and never moves the ref, so the
image updater alone would split the set again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 09:16:14 +02:00
infinito-nexus-updater[bot] fa7bcdf0e4 update(images): update Docker image versions (web-app-bigbluebutton) 2026-10-07 23:31:49 +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 bde617af95 fix(bigbluebutton): take dockerize from the registry that publishes both arches
da665ab699 pinned the helper to jwilder/dockerize:v0.11.0 on Docker Hub, which
is the one tag in that repository's history published for arm64 alone. The
runners are amd64, so generate-compose died on 'exec /bin/dockerize: exec
format error' at bootstrap_compose_project.yml:13 and took the deploy with it.
Not only BigBlueButton's own: Nextcloud#11 failed on the same line, because BBB
sits in its closure.

The mirror is not at fault. Both the mirrored copy and the upstream tag are
single manifests, so the copy is faithful; Docker Hub simply has no amd64 for
that tag. Measured twice by different means, the registry API reading the
config blob and a pull reading the daemon's view, agreeing on all eighteen
rows: v0.11.0 is linux/arm64, v0.10.0 is linux/amd64, and the fifteen
alangecker/bbb-docker-* images are linux/amd64 without exception.

GHCR publishes what Docker Hub does not: ghcr.io/jwilder/dockerize carries a
manifest list with linux/amd64 and linux/arm64 for v0.11.0 and for v0.15.1, and
its tags run four minor versions further than Docker Hub's, which upstream's own
issue #320 describes as six years stale. ghcr.io is already in
_MIRRORABLE_REGISTRIES and eight other roles pin through it, so nothing in the
mirror chain changes.

The jump from v0.11.0 to v0.15.1 is covered by what the script actually uses:
generate-compose invokes dockerize with -template and nothing else, and
v0.15.1 documents that flag unchanged, single-argument form included.

Verified by deploying it. web-app-bigbluebutton variant 1 in compose, the
combination CI failed on, with make test green beforehand: the pre-pull takes
ghcr.io/jwilder/dockerize:v0.15.1, the task that used to die finishes in 0.60s,
'exec format error' appears zero times in 104k lines of log, and the play ends
ok=1178 changed=88 failed=0.

This does not make BigBlueButton run on arm64 and does not claim to. Fifteen of
its sixteen images are amd64-only; dockerize was the single arm64 one in the
set, which is why it broke an otherwise uniformly amd64 stack. Variant 0 and the
swarm mode are unexercised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 09:23:07 +02:00
kevinveenbirkenbachandClaude Opus 5 da665ab699 feat(services): give fifteen moving pins a name the registry can order
A pin like 'alpine', 'stable', 'lts' or 'buildx-stable-1' names no version,
so nothing can compare it to anything: the bump job skips it and the pin
drifts with whatever upstream last pushed under that name. Each of these
fifteen now names its release, which is the precondition for the freshness
pipeline to see it at all.

Every conversion was placed against the tag it replaces, and none of them
is a downgrade. Six resolve to a byte-identical digest and are pure
relabelling (memcached, redis, the two debian bases, jenkins, xwiki); four
are genuinely newer than what the moving tag carried, so those roles will
deploy a later version than before (nginx moves past the 1.31.5 behind
'alpine', buildkit past the v0.32.2 behind 'buildx-stable-1', dockerize
past a 'latest' older than v0.10.0); openresty and pretix keep their
version and differ only by build.

fider is the one that loses something: 'stable' carries a rebuild pushed
twelve days after v0.36.0, which is still the newest versioned tag
upstream publishes. An unnamed rebuild cannot be tracked, so the pin takes
the named release and the updater can raise it when v0.37.0 lands.

The flavor invariant keeps the families apart: jenkins stays on '-lts' and
xwiki inside '-postgres-tomcat', so no bump can cross into a different
runtime.

svc-runner loses a 'nocheck: docker-version' that has become dead. The
marker suppresses two checks at once, so while it stood buildkit was held
out of the daily freshness warning as well; removing it puts the role back
in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 16:58:40 +02: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
kevinveenbirkenbachandClaude Opus 5 30e43cce88 refactor(services): state a refused pairing as off instead of gating it
83c1c729de gave every provider a group_names-gated services entry per MCP
client, including the clients it refuses with mcp_consumer: false. The gate
is what makes the inventory resolver plan provider and client into one round,
so 19 of the repo's 82 co-deploy pairs started a client for a pairing the
provider had already declined. flowise is a live MCP client of qdrant,
homeassistant and prometheus; the other 17 providers were bringing up its
node stack for nothing.

The entries now read enabled: false, shared: false, mcp_consumer: false, and
the variants that pinned them to true are gone. Setting those variants to
false instead is not an option: test_variants_no_static_override forbids any
variant override of a literal base value in either direction.

bond leaves with them, and a new lint holds that line: collect_role_resources
skips a literally disabled entry before it reads any resource key, so a bond
there describes a cost nobody pays. Thirteen entries unrelated to MCP carried
one and are cleared too.

test_mcp_consumer_services_entry demanded the group_names shape for every
client entry, refused ones included, and now demands the opposite there.
dynamic-flag keeps its rule and takes its own documented per-key exemption.

make docs redraws twenty graphs: the edges go, the nodes stay as
'openwebui ❌' and 'flowise ❌', so the refusal is visible rather than read as
an integration.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 17:20:24 +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
kevinveenbirkenbachandClaude Opus 5 596b10c403 test(lint/csp): demand reason for unsafe-* grants
unsafe-inline re-admits the inline execution an XSS needs and unsafe-eval re-admits string-to-code, so a CSP relaxation must be verified against the running app rather than copied from another role. The new lint requires a nocheck marker plus a Reason on the flag's own line, so a marker cannot drift to the neighbouring directive.

The 197 pre-existing grants across 70 roles are checked out with a TODO: they were never verified and still have to be.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 23:28:14 +02:00
Alejandro Roman 7332bd66ae Merge upstream/main into feature/role-stalwart-mail-swarm 2026-08-18 01:42:23 +04:00
kevinveenbirkenbachandClaude Opus 5 246558a13e fix(swarm): ask the stack for the gate's service instead of guessing from metadata
The converge gate polls STACK_NAME_PRIMARY_SERVICE_KEY. Four roles never
create that name and each was given a static `workload: node-local` to make
the gate skip itself:

  21fe9e984  web-svc-cdn      ships only cdn_build, never cdn_cdn
  2c6c8b763  web-app-matrix   names its services matrix_synapse and friends
  76508c946  svc-runner       deploys node-local compose projects per node
  cb52a72f2  bigbluebutton    phantom service, and the DR drill must still run

Four hand-written claims for one observable fact. The claim is also static
where the truth is not: web-app-discourse renders `services: {}` only while
its sidecars are shared, and job 94965456921 of run 31864448027 polled
discourse_discourse ninety times for a stack that had none. Declaring the
role node-local would have blinded the gate in the variants where its
sidecars are local and a stack does exist.

has_swarm_service asks the deployed stack for the exact name instead. Every
caller runs behind `if rc == 0`, so an absent service is the role's shape and
not a broken deploy. Exact-line matching, not docker's name filter, which
matches substrings.

No coverage is lost: 03_wait_converge cannot pass without that service, and
05/06/07 plus the backup drill only run once it has. The four roles keep the
skip they were given; discourse and every future case get it without a
declaration. The teardown branch in the drill keeps its meaning - stack rm
where the service exists, compose stop per node where it does not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 03:38:46 +02:00
kevinveenbirkenbachandClaude Opus 5 ab72cff929 test(lint): require the onion in every variant, not just SSO ones
7a70d27b7 made a variant that switches SSO on also switch the onion on, because
that pairing is where the platform breaks in ways clearnet never shows. The same
argument reaches further than SSO: a Secure cookie is dropped, an https identity
provider refuses to embed, and crypto.subtle is undefined for any feature on a
non-secure origin, not only for a login. A variant that never runs the onion is
not testing the deployment users get.

So the rule now covers every variant of a role that declares a tor block.
Variant 1 stays exempt - it is the minimal, partner-free round every role
declares and the one place the stack is exercised without the onion at all - and
a role whose meta/services.yml pins tor.enabled to a literal false is skipped
outright, because that declaration already says the role has no onion and needs
no comment repeating it.

That found 137 variants. 101 were variant 1. Of the rest, 28 are the LDAP-only
auth-isolation rounds, which gain real coverage from running LDAP over the onion
and are flipped on here. Four cannot: svc-net-tor is the provider itself, and
nextcloud's variant 8 pairs the clearnet-only PeerTube. Both carry a marker
naming the reason; opentalk and coturn need none, since their services.yml pins
the literal false the rule now reads.

Flipping them exposed a contract that contradicted itself. test_auth_isolation
and test_non_baseline_explicit_disables demanded `shared: false` beside
`enabled: false`, while variants-static-override flagged that same line as a
static override - which is why all 133 of them carried a nocheck marker. The
demand only makes sense where `shared` is dynamic: a literal base value cannot
leak through deep-merge, so restating it protects nothing. Both contracts now
ask for it only in that case, and the 133 lines are gone.

The result is a stricter rule with fewer lines: 189 insertions against 193
deletions.

Verified: tests/lint 336 passed / 307 subtests, tests/integration/roles 107
passed / 610 subtests. The rule is checked in both directions - it reported 137
offenders before the resolution and none after.

Not measured: 28 additional onion rounds cost matrix wall-clock, against swarm
jobs already 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 750476b26a feat(lint): give every mount target one spot in meta/volumes.yml
A volume that reaches several services repeated its container path once per
mount. Written out each time it has no single point of truth: a rename that
misses one line still parses, still deploys, and mounts one service somewhere
nobody intended. Nothing fails until that service looks for content which is
not there.

YAML already solves this, and 62722a6ab used it here first -- web-app-openproject
anchors await_migrations_target and aliases it for the second service. Nineteen
roles now follow, and tests/lint/repository/spot/test_volumes_target_single_spot.py
fails on any target spelled out twice. An alias carries no path, so the rule
cannot see it, which is the point.

The conversion was mechanical and is provable rather than reviewed: every file
was rewritten and then reloaded, and any file whose yaml.safe_load result
differed from the original was discarded instead of kept. Nineteen written,
none discarded. Anchors and aliases are a serialisation form; the loader
resolves them before any consumer sees a document.

validate_volumes_meta over all 81 meta/volumes.yml reports no violation.
make test-lint: 328 passed.

web-app-prometheus carries two unrelated deletions: comments that predate this
change and fell under the comment lint once the file was touched. They explained
a 0444 mode and a compose-only bind; both facts are visible in the entries
themselves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 02:02:29 +02:00
kevinveenbirkenbachandClaude Opus 5 7a70d27b7c test(lint): require the onion in every variant that switches SSO on
SSO over a plaintext onion is where this platform actually breaks, and none of
it shows on clearnet: a framework's Secure session-cookie default makes Chromium
drop the cookie, an https identity provider cannot be embedded in an http page,
and crypto.subtle is undefined because an onion is no secure context. Pixelfed,
Taiga, Joomla and WordPress each cost a red job over exactly that. The
combination is only exercised if a variant deploys both, so the lint requires
it: services.sso.enabled true implies services.tor.enabled true.

69 variants enable SSO; 59 already complied. Of the ten that did not, seven are
flipped on because the mechanism is proven there - matrix and nextcloud both run
SSO behind the onion in variant 0 already (20ddda8c1), and their later variants
were pinned false only by the blanket rule from 1d9d355bc. Nextcloud variant 1
stays false: SSO is off there, and it is what gives the coverage test its
false-pinning variant.

Three roles opt out, each for a structural reason named in the marker. jitsi and
bigbluebutton pin tor.enabled false in meta/services.yml because the conference
needs getUserMedia, which no plaintext onion can offer. seaweedfs gates its own
sso flag on 'svc-net-tor not in group_names', so on a tor node there is no login
flow to test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 12:24:37 +02:00
kevinveenbirkenbachandClaude Opus 5 ef5ea7cb55 refactor(roles): name the credential file meta/secrets.yml
meta/schema.yml held credential definitions and their runtime values, but the
name said 'schema' and read like structural metadata next to services.yml and
volumes.yml. Nothing in the file is a schema in the sense the other meta files
use the word, and a contributor had to open it to learn it holds secrets.

62 role files renamed. ROLE_FILE_META_SCHEMA becomes ROLE_FILE_META_SECRETS and
keeps its role as the SPOT the path is built from.

Two readers bypassed that constant and spelled the name themselves:
utils/cache/applications.py built meta_dir / 'schema.yml' twice, and
plugins/lookup/application_gid.py carries the filename in the marker list it
detects application roles by. Left behind, the rename would not have raised —
credentials would have resolved empty and the role would have stopped counting
as an application.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 12:08:06 +02:00
kevinveenbirkenbachandClaude Opus 5 c7ddf6eb24 Merge branch 'feature/swarm-nfs' into feature/svc-net-tor
Three conflicts, each resolved by combining both sides rather than picking one.

compose.yml passes both env vars through to the container: swarm-nfs adds
INFINITO_SWARM_MGR_IP, this branch adds INFINITO_TOR_SOCKS_PORT, and neither
substitutes for the other.

daemon.json.j2 takes the data-root block from swarm-nfs and keeps HEAD's dns
key. The incoming side writes networks.internet.dns directly; lookup(
'container_dns') already returns that value as its clearnet entry and prepends
the docker0 bridge on a Tor node in compose mode, so the incoming form is a
strict subset and dropping it loses nothing.

The WordPress CA-trust addon spec gains skipUnlessServiceEnabled("sso") from
swarm-nfs alongside HEAD's gotoOnion and resolveTimeout, and asserts on the
Plugin Name the mu-plugin header declares - the mustuse screen renders that,
not the filename - with resolveTimeout applied to the literal the incoming
side left bare.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 00:47:10 +02:00
kevinveenbirkenbachandClaude Opus 5 33e8c82414 fix(web-app-bigbluebutton): create the etherpad database during initdb
ueberDB's pg driver opens the named database but never creates it, and the role created it from ansible after the stack was already running. etherpad therefore crash-looped on every cold deploy -- seven FATAL: database "etherpad" does not exist between 14:07:47 and 14:08:48 in run 30699990912. Hook it into /docker-entrypoint-initdb.d instead, where upstream already creates bbb_graphql, hasura_app and greenlight, eight seconds before etherpad first connects.

Mounted 0555 on purpose: the postgres entrypoint sources a non-executable initdb hook into its own shell rather than running it, which would leak set -eu into the entrypoint. A separate hook rather than an entry in POSTGRES_MULTIPLE_DATABASES, so a database upstream adds later is not silently dropped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 18:32:20 +02:00
kevinveenbirkenbach bb1b4f7f96 Merge branch 'feature/swarm-nfs' into feature/svc-net-tor 2026-07-27 23:55:03 +02:00
Alejandro Roman 871b00670a Merge kevinveenbirkenbach/feature/swarm-nfs into stalwart integration branch 2026-07-27 11:25:42 +02:00
kevinveenbirkenbachandClaude Fable 5 8916ee0ac3 fix(web-app-bigbluebutton): mirror the dockerize helper
Upstream generate-compose runs docker run jwilder/dockerize with no
tag pin, no pre-pull and no retry: the only direct Docker Hub pull in
the whole deploy, and a 15s Hub timeout killed a 62-minute swarm run
(30161503176 job bbb-0). Declare dockerize as a disabled pseudo
service so images-mirror-missing and mirrors.yml cover it, pre-pull
the resolved ref with retries and retag it to the exact name the
script requests, so the local image is used and Hub is never
contacted in CI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 20:43:07 +02:00
kevinveenbirkenbachandClaude Fable 5 62a46e472f Merge branch 'feature/swarm-nfs' into feature/svc-net-tor
Resolves the runner Dockerfile to the lint-standard Acquire::Retries=3
and keeps the collabora TODO wording; hardens the merged tree for the
stricter lints (apt retries in the tor Dockerfile, shell-or-true
markers, comment cleanup).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 16:27:25 +02:00
Alejandro Roman 1915ec115b Merge kevinveenbirkenbach/feature/swarm-nfs into stalwart integration branch 2026-07-22 12:16:40 +02:00
Alejandro Roman ac101d2efa Merge kevinveenbirkenbach/feature/swarm-nfs into stalwart integration branch 2026-07-21 10:00:17 +02:00
kevinveenbirkenbachandClaude Opus 4.8 68666d72b4 fix(web-app-bigbluebutton): lint-clean the etherpad postgres DB wiring
Quote ETHERPAD_DB_PASSWORD with dotenv_quote so the generated env file is
mode-safe (the passwords lint requires it; compose strips the outer quotes,
swarm passes the value verbatim). Switch the etherpad entrypoint wrapper
mount from a single-file bind to a docker config, which swarm can place on
any node; the meta-volume lint rejects single-file binds as swarm-fragile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 03:01:35 +02:00
kevinveenbirkenbachandClaude Opus 4.8 9c25f00cc3 fix(web-app-bigbluebutton): move etherpad DB off churning redis to postgres
Etherpad's ueberdb backend was pinned to the bbb redis service. During a
deploy that redis churns (RestartCount=2), taking etherpad's DB with it, so
etherpad crashes (RestartCount=1) and the health-container job fails. bbb's
own postgres stays stable (RestartCount=0) through the same deploy.

A boot wrapper rewrites settings.json dbType redis to postgres before the
upstream entrypoint runs; env carries the connection values; the etherpad
database is provisioned in the postgres password-sync task; compose adds a
postgres dependency. The ep_redis_publisher pub/sub client stays on redis
(its failures are non-fatal).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 03:01:35 +02:00
kevinveenbirkenbachandClaude Opus 4.8 a21e1e23d6 fix(web-app-bigbluebutton): patch ep_redis_publisher so a redis reconnect can't crash etherpad
The p88f3f6b etherpad image ships ep_redis_publisher@0.0.3 whose retry() calls logger.error() with no logger in scope, so any redis reconnect throws ReferenceError and kills etherpad, which fails the shared hlth-container gate (observed on bbb-0 swarm and, collaterally, the nextcloud bundle that pulls the same image). Vendor the plugin index.js verbatim from the image plus a console-backed logger shim, and bind it read-only over the plugin file via meta/volumes.yml + stack_host_copy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 05:45:26 +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 194671d90f feat(readme): uniform description schema and clearer role titles
Rewrite all 254 role descriptions to a uniform noun-phrase schema
(function-first, no leading verb, no trailing period, <=120 chars) and
enforce it via the extended description lint. Rename 12 role README H1
titles for clarity (e.g. Backup Container to Local -> Backup Docker
Volumes, CSS Service -> Corporate Design, NGINX Legal -> Imprint) and
regenerate their Cosmos and Quick Setup sections. Overview table
regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 01:11:48 +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 Opus 4.8 cb52a72f2e fix(dr-drill): drill node-local workloads instead of skipping them
The drill entry gate treated 'deploys no swarm service' as 'has nothing
to restore', but the backup chain operates on NFS volumes and deployed
backup units, not on scheduler objects; bigbluebutton's seven
NFS-backed volumes went entirely unproven. Gate the drill solely on the
NFS-flagged volume and make only the full-disaster step
workload-aware: stack roles keep docker stack rm, node-local roles get
their compose project (label com.docker.compose.project=<entity>, the
compose handler's --project convention) stopped on every node so no
writer races the export wipe and restore. Declare
workload: node-local on bigbluebutton so the phantom-service
converge/chaos gates skip while play, e2e and now the drill keep
running; svc-runner stays skipped via its missing NFS volume. Document
the nine drill steps host-by-host in the README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:07:39 +02:00
kevinveenbirkenbachandClaude Opus 4.8 9319d7ff62 fix(tor): annotate static tor pins in the WebRTC variants
The variants-static-override integration test flags variant overrides that repeat a static meta/services.yml literal. The tor enabled:false pins across the WebRTC roles' variants are intentional hard-invariant documentation, so annotate them with nocheck: variants-static-override like their shared:false siblings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 13:58:17 +02:00
kevinveenbirkenbachandClaude Opus 4.8 c596ad0e20 fix(tor): WebRTC roles never ride the node onion
coturn, bigbluebutton, jitsi and opentalk move WebRTC media over UDP (STUN/TURN, SRTP, JVB, LiveKit SFU), which Tor cannot carry. Pin their tor service block to enabled/shared false in meta/services.yml and in every variant, so no deploy scope or variant ever injects onion domains for them and every consumer lookup renders their clearnet URLs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 13:27:20 +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 3875eb2cf9 feat(variants): pin container_backup per matrix variant
Variant 0 pins the backup consumer flag enabled so the matrix baseline exercises the in-group branch; every other variant pins it disabled (shared: false carries the static-override suppression since the base value stays literally true). The matrix and wordpress variant entries move their services mapping to the list-item head so the static-override line parser resolves the override paths; yaml semantics are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 23:04:40 +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 Fable 5 d8f30a0c19 chore(bigbluebutton): drop explanatory comments
The ref pin and the build-strip condition carry their intent in code;
the guard task already fails loudly when a service still builds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 02:37:27 +02:00
kevinveenbirkenbachandClaude Fable 5 aedb26eacc fix(bigbluebutton): pull prebuilt images and align with house patterns
The freeswitch build fetched libopusenc from launchpad.net, whose
librarian backend rate-limits CI (429s across 11 attempts); three more
build-time fetches carried the same risk. The maintainer publishes an
alangecker/* image for every buildable service, so compose_mods drops
build: wherever the service carries an image and compose pulls instead;
a guard task fails loudly if a future upstream service still builds.

Pin ref to the v3.0.23 release branch (the packaging repo has no tags)
and clone it without submodules via the new compose_repository_recursive
toggle: submodules only feed build contexts and DEV_MODE mounts, and
generate-compose falls back to the committed repos/tags file.

Declare the pulled images in meta/services.yml (updater-managed SPOT,
mirror pre-pull) and the named data volumes in meta/volumes.yml; the
override renders both through the standard container_image /
container_volumes / compose_volumes lookups with the service_name
pattern. Compose merges volumes by target, so the override's named
volumes verifiably replace the clone-local bind mounts (docker compose
config proven). This also heals a split-brain mount: apps-akka,
periodic and prometheus-exporter previously kept bind mounts while
their peers used the named volumes.

Teach container_image and compose_volumes to honour compose_mode_force
(the SPOT the other mode-aware lookups already read): bbb deploys as a
node-local compose stack in swarm, so it must get hub image refs and
plain local volumes there, not registry-prefixed refs and NFS
driver_opts. The unpinned-engine-volume lint learns the same two safe
conditions. Read the embedded Collabora port from web-svc-collabora's
services SPOT instead of hardcoding 9980.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 02:37:27 +02:00