Commit Graph
1392 Commits
Author SHA1 Message Date
kevinveenbirkenbach dedecefcbd Merge branch 'feature/web-app-wordpress/options-simplify' into feature/store-i18n
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	roles/web-app-wordpress/vars/main.yml
2026-11-04 12:58:32 +01:00
kevinveenbirkenbachandClaude Opus 5.5 268b2a46f9 feat(web-app-wordpress): set admin_email, blogname and the FreedomTranslate options through the options map
WORDPRESS_OPTIONS now merges the role's own contributions (admin_email from the platform administrator, blogname from the design title while the design service is on, the freedomtranslate-wp config.options while that addon is on) under services.wordpress.options, so the inventory still wins and wordpress_options_checked guards the merged map. design.php no longer writes blogname, the addon task no longer writes its options, and WORDPRESS_OPTIONS_OWNED_ELSEWHERE drops the three. Every deploy now converges admin_email, and blogname while the design is on; a write WP-CLI reports as unchanged no longer counts as a change, and the FreedomTranslate readback runs after the options write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:53:11 +01:00
kevinveenbirkenbachandClaude Opus 5.5 50ee46ed22 refactor(provision): group the services disabler modules in a services package
test_prefix_grouped_files flagged cli/administration/inventory/provision/services_disabler.py and services_disabled_conflicts.py, which share the 'services' prefix since the conflict check moved into its own module. Both now live in cli/administration/inventory/provision/services/ as disabler.py and disabled_conflicts.py; every importer, the purge script, the BLE001 per-file ignore and the requirement 037 link follow the new path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:23:07 +01:00
kevinveenbirkenbachandClaude Opus 5.5 c3e2114e91 Merge branch 'fix/web-app-wordpress/permalink-options' into feature/store-i18n
Conflicts resolved: the inventory validator keeps store-i18n's custom-role
declaration skip and gains the open-subtree check; the suppression catalogue
keeps both new rows; WORDPRESS_PERMALINK_STRUCTURE gives way to the options
map. WORDPRESS_OPTIONS_OWNED_ELSEWHERE also lists the store-i18n writers the
merge brought along: the design logos (WORDPRESS_DESIGN_LOGOS keys) and the
freedomtranslate options; two custom-role validator tests pass open_paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:35:18 +01:00
kevinveenbirkenbachandClaude Opus 5.5 46e0bd1367 feat(web-app-wordpress): manage WordPress options from services.yml and pin the old permalink structure
Deploys since 197d74416d rewrote the blog's permalink structure to
/%postname%/, so links of the form /blog/YYYY/MM/DD/<slug>/ answer 404
(docs/security/user.md:24 fails the URL check).

services.wordpress.options maps WordPress option names to values; the role
reads each option, writes only the ones that drift and flushes the rewrite
rules after a write. permalink_structure is pinned to
/blog/%year%/%monthnum%/%day%/%postname%/, the value WordPress itself sets
for a subdirectory network. A guard refuses options the role writes
elsewhere, secret-named keys and values WordPress would not read back
unchanged.

The inventory validator accepts any child key under a services.yml key that
carries '# nocheck: inventory-open-subtree <reason>'; services.wordpress.options
carries it, everything else stays strict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:30:31 +01:00
kevinveenbirkenbachandClaude Opus 5.5 e9779c4c7b refactor(provision): move the disable/inventory conflict check into its own module
services_disabler.py grew past the 500-line limit with the dependents step
(gate pass 16, test_python_file_size). The conflict check moves unchanged to
services_disabled_conflicts.py; the YAML helper it shares becomes public
(load_yaml_mapping_tolerant) and the two importers follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:24:38 +01:00
kevinveenbirkenbachandClaude Opus 5.5 c5a69ee951 test(web-app-discourse): build the vars path from ROLE_FILE_VARS_MAIN
test_file_spot flagged the hand-written vars/main.yml path in the chat
integration test (gate pass 16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:20:58 +01:00
kevinveenbirkenbach 3d7c9da23e Merge branch 'fix/front-inj-design/css-per-network' into feature/store-i18n 2026-11-04 10:53:49 +01:00
kevinveenbirkenbach 28bbf6dc21 Merge branch 'fix/web-app-discourse/chat-integration-enable' into feature/store-i18n 2026-11-04 10:53:49 +01:00
kevinveenbirkenbachandClaude Opus 5.5 01e27bfddb fix(sys-front-inj-design): store and link the role stylesheet per network
The design inject renders a role's style.css once per vhost but copied every
render to one shared CDN path, so the last render won. xwiki's stylesheet
embeds its own origin; on a multi-network node the onion render landed last
and every clearnet page loaded onion URLs, failing network-leak.spec.js
whenever the pass picked clearnet.

The first network keeps style.css, every further network gets
<label>-style.css (onion-style.css), keyed by the same rule the tls lookup
uses for the render (serving_network), and each vhost links its own file.
Single-network roles keep the exact old path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:46:58 +01:00
kevinveenbirkenbachandClaude Opus 5.5 09e855ae1b fix(web-app-discourse): enable the chat integration and the bridged partners' providers
The chat-integration plugin ships with core and defaults to
chat_integration_enabled: false, while the addon task was empty, so the
spec's first assertion could never pass and topic notifications never left
Discourse for Matrix or Mattermost.

The addon now turns on chat_integration_enabled plus the provider of every
partner the round deploys, idempotently, reads the settings back and fails
when one does not stick. The spec asks the plugin's real admin route
(/admin/plugins/discourse-chat-integration/providers.json; the old path is a
404 on v2026.10.0) and requires an enabled partner provider, since the
catalogue now lists disabled providers too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:44:19 +01:00
kevinveenbirkenbachandClaude Opus 5.5 1ed3fd11da fix(provision): disable the services gated on a provider the row disables
A tor-only CI row disables web-svc-coturn, which no onion can serve, but the
disabler switched off only service entries keyed coturn. Nextcloud's talk
entry is gated on 'web-svc-coturn' in group_names and variant 0 pins it true,
so Talk stayed on without a TURN server and 01_fullstack.yml's assert stopped
every Nextcloud#0 tor row.

When disable resolves to a provider role P, every entry whose base enabled is
exactly the presence test of P is written false as well, and the reconcile
releases those entries once P is no longer disabled. Negated, compound and
variable gates are left alone. Across all 755 planner row states this changes
one value an app sees: talk on Nextcloud#0 tor rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:41:09 +01:00
kevinveenbirkenbach 9f4be23164 Merge remote-tracking branch 'origin/main' into feature/store-i18n 2026-11-03 22:56:04 +01:00
kevinveenbirkenbachandClaude Opus 5.5 c3c0e9db77 fix(swarm): test service membership without a pipe into grep -q
_context.sh runs under set -euo pipefail and tested membership with
`printf '%s\n' "${_names}" | grep -qxF <name>`. grep -q exits on the first
match; when printf still has lines to write it dies of SIGPIPE (141), and
pipefail turns the matched test into false. resolve_db_service then fell
through to the central <dep>_<dep> name or printed nothing, and
has_swarm_service reported a deployed service as missing, so
skip_if_no_swarm_service let the converge and chaos gates exit 0 untested.
The race grows with the stack's service count and with CPU load (store-i18n
gate pass 11: 'mariadb_mariadb' != 'magento_database').

The three tests read the listing from a here-string, which is no pipeline.
Two regression tests put the match first in a 10000-name listing and fail
on every run against the old helper. Root cause found by i18n 2.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 22:49:39 +01:00
kevinveenbirkenbach 8690590e40 Merge remote-tracking branch 'origin/main' into feature/store-i18n
# Conflicts:
#	cli/build/docs/integration_matrix/__main__.py
#	cli/build/docs/integration_matrix/data.py
#	docs/requirements/027-integration-matrix.md
2026-11-03 22:42:56 +01:00
kevinveenbirkenbach 715820d6c1 Merge remote-tracking branch 'origin/main' into fix/ci/pin-dev-runtime-images 2026-11-03 22:07:52 +01:00
kevinveenbirkenbachandClaude Opus 5.5 7ac795c0cb test(web-app-gitlab): build the runner-stdin environment with select_autoescape
ruff S701 flagged the jinja2 Environment without autoescape in the single-boot rails test; select_autoescape(default_for_string=False) keeps the rendered Ruby stdin unescaped, the repository's pattern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:56:06 +01:00
kevinveenbirkenbachandClaude Opus 5.5 81ef94e111 style(dev-python): fold the env filter onto one line after the rename
DEV_PYTHON_REQUIRES is shorter than the old name, so ruff format joins the
dict comprehension in test_install.py onto one line, and the CI format
check would report the drift.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:47:46 +01:00
kevinveenbirkenbach a70802eb1b Merge branch 'perf/web-app-gitlab/one-rails-boot' into feature/store-i18n 2026-11-03 21:43:54 +01:00
kevinveenbirkenbachandClaude Opus 5.5 a6d15a5ed4 test(web-app-moodle): mark the tempdir walk of the auth_oidc sync test
test_no_raw_project_walk flagged tree.rglob in files_in; the walk scans the test's own temporary directory, the exception the lint documents, so it carries the project-walk nocheck instead of the cached project helpers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:43:54 +01:00
kevinveenbirkenbachandClaude Opus 5.5 6e2166d825 perf(web-app-gitlab): converge root, platform admin and appearance in one Rails boot
Three separate 'rails runner -' calls each paid a full Rails boot (47 s, 42 s and 39 s on the echoserver production deploy). platform_admin.yml now runs one runner whose stdin is a small step driver (run_steps.rb) followed by the unchanged root-password, platform-admin and appearance scripts, each wrapped in its own InfinitoSteps.run block: every step keeps its own [CHANGED]/[FAIL] verdict prefixed with its name, exit(0) ends only that step, and the run fails naming every failed step. Rails 7.2.3.1 evals runner stdin at TOPLEVEL_BINDING inside an executor that passes SystemExit through; puts writes to the swapped $stdout while the logger writes to files. The design render and slurp move to tasks/utils/design.yml ahead of it, keeping the numbered includes ascending, and the file layout leaves feature/iam-profile-rbac's hunks on platform_admin.yml and 00_core.yml mergeable without conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:33:37 +01:00
kevinveenbirkenbachandClaude Opus 5.5 f7923b9f94 test(web-app-moodle): build the sync-body environment with select_autoescape
ruff S701 flagged the jinja2 Environment without autoescape in the auth_oidc sync test; select_autoescape(default_for_string=False) keeps the from_string shell body unescaped, as other tests do, and autoformat wrapped one long filecmp call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:31:24 +01:00
kevinveenbirkenbach 089ca505b6 Merge branch 'fix/web-app-moodle/oidc-plugin-missing' into feature/store-i18n 2026-11-03 21:16:59 +01:00
kevinveenbirkenbachandClaude Opus 5.5 9985fb6d08 style(dev-python): apply ruff format to the install test after the DEV_PYTHON_REQUIRES rename
The shorter variable name from 0bdb14cc1d (#1358) lets the dict comprehension fit on one line; make autoformat in gate pass 6 collapsed it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:16:47 +01:00
kevinveenbirkenbachandClaude Opus 5.5 279a449566 fix(web-app-moodle): sync the auth_oidc plugin from the image into the code volume
The entrypoint refills the shared code volume only when the release changes, so a volume bootstrapped at 5.3.0 by an image without auth_oidc never received the plugin from a later same-release image; galaxyserver's production deploy failed at the presence check although the image ships it (probe: /opt/moodle-source/public/auth/oidc exists). 04_oidc.yml now asserts the image ships the plugin, naming the image when it does not, and syncs it into the volume as the runtime user through a staging directory, only when diff -rq finds a difference, before the existing presence check. The OIDC upgrade.php run no longer hides a failed plugin install behind failed_when: false, matching the MCP and objectfs plugin tasks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:42:17 +01:00
kevinveenbirkenbach 42315cb065 Merge commit '0bdb14cc1d' (fix/dev-python/python-requires-env-name, #1358) into feature/store-i18n 2026-11-03 20:25:21 +01:00
kevinveenbirkenbach 9642ff13e4 Merge remote-tracking branch 'origin/main' into feature/store-i18n 2026-11-03 20:22:44 +01:00
kevinveenbirkenbach b73af793aa Merge branch 'perf/web-app-stalwart/jmap-batch' into feature/store-i18n 2026-11-03 20:22:33 +01:00
kevinveenbirkenbachandClaude Opus 5.5 be60486fc4 perf(stack_host_copy): settle trees with empty leaf directories in the one-call check
Development worktrees carry empty .claude/.cc-writes directories in role source trees; the one-call unchanged check gave up on any directory without files, so the docs tooling copy fell back to per-file stat and copy round trips (about 56 s on the echoserver production deploy). file_checksums now also reports directories (isdir, mode), and the check asks for every leaf directory in the same remote call: it settles only when each leaf exists as a real directory and, when directory_mode is set, carries exactly that mode. With directory_mode unset the stock copy would not touch an existing directory either (file module with mode None). A missing directory, a mode mismatch, a file or symlink in its place, or a symbolic directory_mode with leaves still runs the regular copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:04:23 +01:00
kevinveenbirkenbachandClaude Opus 5.5 0ed50ae0a5 feat(sys-os): declare the workspace runtime images where the updater finds them
The runtime image pins lived in meta/distros.yml, which neither the docker
updater, the image mirror nor the vulnerability scan reads, so a pin would
only ever move by hand. The new role sys-os carries them in its
meta/services.yml under the distro id; utils/distros.py renders the
workspace matrix from there with the stdlib block reader, and
meta/distros.yml keeps the distro attributes only.

A new release now arrives as an updater pull request. follow: latest holds
debian, ubuntu and fedora to the release their latest tag points at, by
Docker Hub digest: Ubuntu moves between LTS releases only (26.10 shares the
digest of rolling, not latest) and Fedora skips rawhide (46 shares the
digest of rawhide, latest is 45). Arch stays rolling; CentOS stream tags
carry no number the updater can order.

dockerhub_repo and is_dockerhub move to utils/docker/image/ref.py beside
split_registry_and_name, so utils/update/follow.py can use them without an
import cycle and utils/update/docker.py stays under the size cap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 19:55:32 +01:00
kevinveenbirkenbachandClaude Opus 5.5 0bdb14cc1d fix(dev-python): name the requires-python hand-over outside the .env key space
#1347 passed the supported Python range to the remote install.sh as
INFINITO_PYTHON_REQUIRES. Every INFINITO_* name is a key of the generated
.env, so test_every_referenced_infinito_key_is_registered fails on main
for this one, which no registry entry can carry: its value is derived
from pyproject.toml's requires-python, the single source, and a default
in default.env would be a second copy of the range. The task environment
and the script now call it DEV_PYTHON_REQUIRES; behaviour is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 19:55:27 +01:00
kevinveenbirkenbachandClaude Opus 5.5 7ee231201d perf(web-app-stalwart): batch independent management JMAP reads into one request each
Every jmap_call.yml call pays about 1.5 s of fixed cost (container exec, curl, include, parse and assert tasks) before Stalwart does any work; the echoserver production deploy spent 98 s in 33 calls. Independent reads now share one request: Security, AllowedIp and Http in security.yml, Domain and MailingList in 15_aliases.yml, Application and OAuthClient in 02_webadmin.yml, Directory and Role in 06_oidc.yml. The rendered method calls and arguments are identical to the old requests, no JMAP call runs between a batched read and the facts it feeds, and the User-role assert still follows the directory writes. Writes stay in their own requests, because JMAP keeps processing later calls after a refused set. A refused read is now named by its object instead of c0. Projected saving: about 19 s per echoserver deploy, about 10 s on galaxyserver.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 19:54:58 +01:00
kevinveenbirkenbachandClaude Opus 5.5 e3ffa80c11 perf(stack_host_copy): leave Python bytecode caches out of directory copies
Source trees in a development worktree carry __pycache__ next to their sources (web-app-docs python/: 76 .pyc beside 42 .py). They defeated the one-call unchanged check and were copied file by file over SSH; on the echoserver production deploy the docs tooling staging took 155.9 s at about 1.27 s per file. The plugin now walks the tree without __pycache__ for both the unchanged check and the stock copy, which reads _walk_dirs as a module global; the swap is restored in finally and each task runs in its own forked worker. Sourceless .pyc outside a cache directory stay in the copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 19:04:11 +01:00
kevinveenbirkenbachandClaude Opus 5.5 0c54388951 fix(constructor): accept the legacy owners of networks and volumes in the pre-flight
Production hosts carry objects from before the compose project was named after the entity: galaxyserver's mariadb, openldap and postgres networks have no project label, and echoserver's openldap_data volume is labelled svc-db-openldap, the role id older deploys passed as -p. The pre-flight stopped both deploys although compose reuses such networks and volumes with a warning. A network named after its entity now passes without a label or with a declaring role id, still checked against its subnet; a volume passes with a declaring role id as well. A container does not, because compose cannot see a container of another project and fails on its name. A label naming another project still stops the deploy, and the volume message now says compose would hand the role another project's data instead of claiming it refuses the volume.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 18:07:15 +01:00
kevinveenbirkenbachandClaude Opus 5.5 77cab6e40b perf(svc-swarm-node): read the ingress ports and the node addresses in one shell
The ingress guard read ran two remote shells per swarm deploy, each asking container info again (WordPress#0 run 41303726270: 66 s + 61 s over 64 calls each). One shell now prints 'port <json>' and 'node <address>' lines into swarm_ingress_read; the new swarm_ingress_sections filter splits them by tag and raises on any untagged line, and SWARM_INGRESS_GUARDED and SWARM_INGRESS_PEERS read their section, so the guarded set, the peers, the firewall digest and the ruleset stay the same. A test ties the --format tags in read.yml to the sections the filter knows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:38:05 +01:00
kevinveenbirkenbachandClaude Opus 5.5 eb36a54c44 perf(sys-svc-compose-ca): stop probing images on the swarm no-wrapper path
render_override gathered the metadata of every image (docker image inspect, a pull when missing, a docker run probe) before it checked wrap, and the swarm handler's --no-wrapper path threw the result away, because image_meta is read only behind 'if not wrap: continue'. The gather now runs only when wrap is true, so the swarm override is unchanged byte for byte. It pulled again the externals that registry_sync had just removed (WordPress#0 run 41303726270: 275 s over 64 calls).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:14:01 +01:00
kevinveenbirkenbachandClaude Opus 5.5 a3cb439a68 fix(constructor): stop the disk pre-flight only when a full filesystem also lacks absolute room
The pre-flight stopped every deploy on a filesystem above SIZE_PERCENT_MAXIMUM_DISK_USAGE, which blocked a 1.8 TB workstation at 99 % with 30 GB free while a 145 GB runner was fine. A filesystem now fails only when it is above the percentage AND offers less than SIZE_GB_MINIMUM_DISK_FREE (20 GB): disk_usage_errors reads df's available column, the preflight_disk_usage filter passes the floor, and disk.yml runs df -P -k so the block unit stays KiB even under POSIXLY_CORRECT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:10:34 +01:00
kevinveenbirkenbachandClaude Opus 5.5 81e2be2204 fix(dev-python): hand requires-python to the remote script with a valid backreference
76adaeb9e8 wrote the regex_search group reference as '\1' inside the folded
task scalar. Jinja unescapes string literals, so the filter received \x01,
rejected it as an unknown argument and failed the dev-python task on every
remote host. The literal is now '\\1', and the unit test renders the task's
environment expression the way Ansible does instead of only checking that
the variable is named.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:55:22 +01:00
kevinveenbirkenbachandClaude Opus 5.5 76adaeb9e8 fix(dev-python): take the supported Python range from requires-python
bf9c5be173 capped the bootstrap at 3.14 with constants inside install.sh, a
second copy of the range pyproject.toml already declares. requires-python is
now the single source (>=3.11,<3.15): install.sh derives its floor and cap
from it, in the repository or, on a remote host where the script runs alone,
from INFINITO_PYTHON_REQUIRES that the dev-python role reads from the same
file; any other form fails loudly. The Dockerfile copies pyproject.toml
before the bootstrap, and pip now refuses 3.15 for the package as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:42:34 +01:00
kevinveenbirkenbachandClaude Opus 5.5 bf9c5be173 fix(dev-python): cap the bootstrap Python at the newest minor ansible-core supports
Fedora 45 defaults to Python 3.15. ansible-core 2.21, which the unpinned
`ansible` extra still resolves to (ansible 14.5.0 pins ~=2.21.5), calls
dataclasses._is_type, which 3.15 removed, so the Workspace (fedora:latest)
job died before the first playbook (run 41491308729).

install.sh now picks and installs the highest Python between
PYTHON_MIN_MINOR and PYTHON_MAX_MINOR (3.11 to 3.14) on apt, rpm and
pick_python_bin, and fails loudly when only newer interpreters exist. On
Fedora 45 it installs python3.14. Raise PYTHON_MAX_MINOR once ansible ships
ansible-core 2.22.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:06:41 +01:00
kevinveenbirkenbachandClaude Opus 5.5 1ec96181b1 Merge remote-tracking branch 'origin/main' into feature/store-i18n
Conflicts resolved by combining both sides: utils/tests/swarm/run.py keeps store-i18n's re-export of run_watched from watchdog.py, and the #1330 storage roots (lowest_free_mb over / and /var/lib/containerd, the fullest root in the abort banner and the sample line, df over every root) move into watchdog.py; test_run_storage_roots.py now targets watchdog and expects the process-group kill. The seaweedfs 4.52 bump lands in roles/web-svc-seaweedfs, where store-i18n moved the engine; roles/web-app-seaweedfs keeps only the console.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 13:53:44 +01:00
kevinveenbirkenbachandClaude Opus 5.5 a67dd9b372 feat(constructor): stop a deploy in a pre-flight when the host cannot carry it
The constructor now runs tasks/utils/constructor/preflight.yml before the package install and fails with a named remedy when: the running kernel has no module index on disk or the clock is not synchronised (chrony leap status, else timedatectl, outside containers); / or the docker root is fuller than SIZE_PERCENT_MAXIMUM_DISK_USAGE (90); a docker network contradicts the entity that declares it (name, compose project label, subnet outside its block, overlap with another declared subnet); a declared container lacks or a declared volume carries a foreign entity label; a docker default address pool overlaps the mesh, Tor or a declared subnet; a host process outside docker holds a public port of the deploy; or, in assert mode, a clearnet domain of the deploy does not resolve.

A volume without any label passes, since compose adopts it and installs before this change created the Synapse volume with docker run. web-app-discourse declares its launcher-started containers through networks.local.launcher, and web-app-matrix labels a new Synapse volume with its compose project.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 13:38:15 +01:00
kevinveenbirkenbachandClaude Opus 5.5 e66c635fb6 perf(sys-svc-container): roll a single-replica swarm service without a stagger delay
swarmkit sleeps update_config.delay after the last slot too and then monitors for delay + 1s, so a one-replica service paid about 21 s per rollout for nothing to stagger (database stacks converged in 32 s instead of 8 s in WordPress#0's update pass of run 41303726270, about 400 s per row). A service whose replicas line is 'replicas: 1' now rolls with delay 0s; several replicas keep service_update_delay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:45:15 +01:00
kevinveenbirkenbachandClaude Opus 5.5 d6151aa559 fix(web-app-bigbluebutton): publish every container port once, on the proxy's upstream host
BBB's upstream compose template publishes nginx's 48081/48082/48087 on 127.0.0.1 and on 10.7.7.1. On Arch, Docker's nftables backend with the userland proxy off writes one identical 'MASQ TO OWN PORT' rule per binding and the second fails with 'rule exists' (moby 60109b06fb, PR #54031, first released in 29.10.0), so compose up failed in Moodle#3 of run 41303726270. compose_mods keeps only bindings on services.bigbluebutton.upstream_host or without a host address; nothing in the platform uses the loopback ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:34:22 +01:00
kevinveenbirkenbachandClaude Opus 5.5 486d85c29f perf(web-app-docs): claim a build only for a free slot and skip current translations
The builder handed every queued marker straight to its worker pool, whose own FIFO let a background latest build run ahead of the deployed:de build a visitor waited on (40-81 min in docs#1 of run 41303726270). A marker is now claimed only while a slot is free. build_language dequeues a translation that is already current instead of rebuilding it for 2-3 h on a duplicate request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:31:03 +01:00
kevinveenbirkenbachandClaude Opus 5.5 4f20a57a7e fix(web-app-docs): look at the shared volume again before answering 'Building'
On the swarm NFS volume a docs replica keeps a failed lookup until the parent's cached attributes expire, so a site another replica just renamed into place stayed invisible to it and it served the 202 'Building' page (lang en) after a sibling had answered 200 (docs#1 of run 41303726270). recheck_servable() lists the parent to force close-to-open revalidation and is called only on the path that would answer 202; ordinary page requests pay nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:27:05 +01:00
kevinveenbirkenbachandClaude Opus 5.5 cfc8cbb8fe fix(web-app-wazuh): give the manager room for the vulnerability feed and name failing API calls
At 2g the vulnerability-detection feed update thrashed the manager cgroup until the kernel OOM-killed wazuh-modulesd; Wazuh restarts no daemon, so the API answered every call with HTTP 500 (error 1017) and the API administrator grant failed in the update pass of run 41303726270 (Wazuh#1, swarm). The manager gets mem_limit 4g, and api_administrators.py raises '<METHOD> <path> answered HTTP <code>: <body>' instead of a bare 500 so a dead daemon shows up by name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:23:53 +01:00
kevinveenbirkenbachandClaude Opus 5.5 23c3da94cd Merge the ERPNext standard price lists into staging/alphaserver
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:28:07 +01:00
kevinveenbirkenbachandClaude Opus 5.5 b23574fe78 Merge the stack_host_copy batched checksum into staging/alphaserver
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:00:20 +01:00
kevinveenbirkenbachandClaude Opus 5.5 a394d4bf6b fix(svc-net-wireguard): dial every endpoint at the IPv4 address it resolves to
The mesh is IPv4 only (60-byte header, IPv4 pools), yet wg.conf handed
wg the endpoint name, and wg dials the first getaddrinfo answer, an AAAA
on a dual-stack name. A host with broken IPv6 then never handshook
(alphaserver, 2026-11-01). Resolve every dialled endpoint with getent
ahostsv4 on the dialling host, render that literal, measure the path MTU
against it, and fail before rendering when a name has no IPv4 address.
Also set the tunnel MTU on a live interface, since wg syncconf drops it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:53:01 +01:00