Commit Graph
11 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5.5 0c748f74ed fix(web-app-moodle): swap the code tree only after a complete copy
The entrypoint removed the old tree before it copied the new one. A
copy that failed left the volume without a working Moodle, and every
restart removed again instead of recovering. It now copies into a
staging directory inside the volume and swaps the trees once the copy
is complete, so a failed copy leaves the old tree in place.

An image that ships an older release than the volume records no longer
replaces it and exits with a message instead. Two instances on
different releases would otherwise overwrite each other's tree.

upgrade.php reports "No upgrade needed" on stderr, so the task counted
every run on a current site as a change. It reads stderr now.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 10:45:29 +02:00
kevinveenbirkenbachandClaude Opus 5.5 d1eee3e26f fix(web-app-moodle): run Moodle 5.3 and upgrade existing sites to it
The pin moved from 4.5.11 to 5.3.0, and the role fitted neither a new
install nor an existing one.

Moodle 5 serves from public/ and routes every other path through
r.php. nginx now uses public/ as its root, hands unknown paths to the
router and passes PATH_INFO for scripts with slash arguments, and
config.php declares the router as configured. The paths the role checks
and writes (version.php, auth, webservice, the OIDC plugin) moved below
public/, the URL of the OIDC plugin stays auth/oidc. The source archive
no longer contains vendor/, so the image installs the Composer
dependencies it declares. The OIDC plugin is pinned to v5.2.1, the
newest release.

An existing site kept its old code: the entrypoint filled the code
volume once and never again. It now records the release it copied and
replaces the tree when the image carries another one, and a new task
runs admin/cli/upgrade.php. Plugins that were installed through the web
interface are not carried over, the README says how to ship them.

A site installed with 5.3 forces login by default, which 4.5 did not,
so its start page answers 303 to the login page and the webserver
health check refused it. The role declares 200 and 303 as the codes
that check accepts: 200 is what an upgraded site keeps answering.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 02:10:13 +02:00
kevinveenbirkenbachandClaude Opus 5 0e98fd4259 fix(moodle): let the entrypoint fail on a config it could not write
Four unmarked `|| true` remained in this file after c505ce7466 grandfathered
the corpus; they entered later and left the file in violation of shell-or-true.
Handling them beats marking them.

cp -f writing config.php is the one that mattered: swallowing its failure let
Moodle boot against a missing or stale configuration instead of stopping. It
now fails.

The chown and chmod calls can only fail for lack of privilege, so the root
check states that condition instead of discarding every error. The image
declares no USER over php, and the compose template sets no user:, so this
runs as root today; the check guards a future USER rather than an observed
failure.

moodle_refresh_config inverts its guard to an early return so the copy sits one
level shallower.

The five grandfathered `|| true` keep their nocheck and their TODO, including
the chown three lines above the one hardened here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 13:22:26 +02:00
kevinveenbirkenbachandClaude Opus 5 7162b7bf4c Merge fork/feature/svc-net-tor into feature/ai-agent-employees
834 commits from the Tor onion-service branch against 299 of ours, from merge
base 7b646bdece: 127 conflicted files, 3506 files changed.

The two sides are largely orthogonal. Theirs adds svc-net-tor and a `tor` service
across the role tree, plus two sweeping refactors: shipped scripts sorted by
language (files/ruby/, files/python/, files/shell/, scripts/lint/*.sh) and the
unit suite moved under tests/unit/python/. Ours is requirement 036, the native AI
gateway, plus the MCP role work. Most conflicts were both sides appending to the
same service map, resolved as unions.

Resolutions that were not unions:

Credentials moved namespace. Theirs renamed meta/schema.yml to meta/secrets.yml
and re-addressed credentials as secrets.credentials.<name>. Git carried our
litellm_api_key blocks through the rename, but every consumer we added still read
'credentials.<name>', which no longer resolves. All 45 call sites are migrated.

plugins/filter/container_volumes.py is deleted by theirs and relocated to
plugins/lookup/container_volumes.py, where it carries the same tmpfs defect our
f85996b43 had just fixed: a hard-coded {"size": ...} that silently drops a per
mount `mode`. The fix is re-applied in the relocated file, as that commit's merge
note anticipated.

compose.yml keeps only INFINITO_PLAYWRIGHT_REPORTS_BASE_DIR. Theirs also forwards
INFINITO_PLAYWRIGHT_STAGE_BASE_DIR; that line is deliberately dropped, per the
analysis recorded in 45804e9ad -- STAGE has a drift-proof role-vars fallback,
while forwarding it promotes a setdefault-pinnable .env value above that SPOT.

tasks/stages/01_constructor.yml takes their include, ./tasks/utils/setup/group.yml,
because their refactor removed tasks/groups/*-roles.yml, leaving our include
target nonexistent. Our task name is kept, since svc-virt is in the loop.

Makefile swarm-zombie is a union: their validate plus teardown, then our
lab_subnet cleanup, which their side dropped while leaving lab_subnet.sh as the
only orphaned reference in the file.

01_bootstrap.sh keeps our deterministic --ip pinning, which distinguishes an
already-attached node from a real failure, over their `|| true`.

Their language-sorted layout orphaned references our side still held: zammad's
two scripts/apply/*.rb, jenkins' java-with-ca-trust.sh, and the notify topics
their handlers renamed to hyphenated form. All corrected.

roles/sys-svc-compose/files/shell/swarm/stack_ready.sh gains our latched-update
block: paused, rollback_paused, rollback_started and rollback_completed exit 2
rather than polling a state that cannot resolve without another deploy.

config_from_run is unioned to (title, logged, jobs); the production caller in
trigger/__main__.py now passes jobs, without which our suite suppression would
have been inert.

READMEs are regenerated rather than hand-merged: 15 LiteLLM rows and 91 Tor rows
now derive from the merged metadata, which is itself evidence the meta merge is
coherent.

Verified: 5161 unit tests pass. The single failure, test_query.py TestSortSpec,
also fails on fork/feature/svc-net-tor at b51d2f9919, measured in a baseline
worktree -- so this merge introduces no new unit-test failure. Two modules are
excluded from that run because `baudolo`, a dependency their branch introduces,
is not installed on this host.
Not verified: no deploy has run against the merged tree, and make quality has not
been run end to end here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 16:32:30 +02:00
kevinveenbirkenbachandClaude Opus 5 5e810c4a5a fix(moodle): accept MCP requests under the internal service name
Moodle 303-redirects every request whose Host is not $CFG->wwwroot, and an MCP
client dialling the internal service name cannot follow that redirect from the
container network. The previous accommodation was on the wrong side: the probe
sent -H 'Host: <public domain>' and -H 'X-Forwarded-Proto: https' and pinned
mcp_probe_host to MOODLE_DOMAIN, so it asserted the endpoint works when spoofed
and said nothing about how a real client reaches it.

Both headers and the probe host are gone. Acceptance moves into config.php,
which reads MOODLE_INTERNAL_HOST -- rendered from MOODLE_MCP_SERVICE_KEY -- and,
when the incoming HTTP_HOST matches it, rewrites HTTP_HOST and SERVER_NAME to
the wwwroot host before Moodle's redirect logic runs. The comparison splits on
':' so a port cannot defeat the match, and a request from any other host is
untouched, leaving the canonical-host protection intact for real traffic.

None of that could have taken effect on a deployed instance. The bootstrap seeds
the code directory with `cp -an`, which never clobbers, so an existing volume
keeps its first-run config.php forever and every later config change is silently
discarded. moodle_refresh_config now copies the deployment-managed config.php on
every start, which is what makes the rewrite above reachable at all.

Not verified: no MCP call has been observed succeeding against the internal name
since the change. This is also the same failure class that ended the last
deploy on another role, where the client already shared the provider's network
and had the right alias registered, yet the DNS search suffix still resolved the
single-label name to the public ingress. If that applies here too, the Host fix
is correct and the request still never arrives -- and removing the spoofing
headers will surface it as a failure where the probe previously passed.

Two further consequences worth stating: config.php is now overwritten on every
start, so an operator edit inside the container is discarded; and the HTTP_HOST
rewrite runs in the hot path of every page load, narrow though its condition is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 14:15:01 +02:00
kevinveenbirkenbachandClaude Opus 5 0be527abb0 fix(web-app-moodle): give the local cache a node-local home
Moodle kept everything under dataroot, which is the shared NFS export in swarm
mode - including localcachedir. Upstream is explicit that this is the one
directory that must not be shared: config-dist.php marks tempdir and cachedir
as 'MUST BE SHARED by all cluster nodes' and localcachedir as 'Intended for
local node caching'.

The path is declared once, as the mount target in meta/volumes.yml, and every
other place derives from it: vars/main.yml reads it back through
lookup('volume', ...), compose passes it as a build argument, the Dockerfile
exports it, the entrypoint requires it with :? and creates it with the runtime
owner, env.j2 hands it to config.php as MOODLE_LOCALCACHEDIR. The two mounts of
the same volume share one YAML anchor rather than repeating the literal.

The cron service mounts it too, because it runs the same code and would
otherwise write its cache somewhere else than the web service reads it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 22:05:43 +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 c505ce7466 test(lint): forbid unmarked '|| true' in shell scripts; grandfather the corpus
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 20:39:16 +02:00
kevinveenbirkenbachandClaude Opus 4.8 7bc5822421 fix(moodle): survive flock EIO on the NFS bootstrap lock and widen the converge budget
Six replicas race the bootstrap lock across three NFS clients; the truncating open invalidated the holder's lock state, so flock died with EIO and the task crash-looped with exit 65 until the winner finished the ~6min code copy. The lock file is now opened append-only and flock failures fall back to a sentinel recheck loop instead of killing PID 1. The converge budget gets the same 600-retry override the other slow-bootstrap roles already carry; the default 150 missed this run's recovery by six seconds.

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 00:50:53 +02:00
kevinveenbirkenbach 63ddd24aaf feat(req-015): self-built Moodle image, OIDC+LDAP hybrid + LDAP-only variants 2026-05-04 18:37:03 +02:00