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>
roles/sys-svc-mail-msmtp/tasks/ca_bundle.yml stat'ed candidate bundle paths on
the Ansible target and wrote the winner into msmtprc. Moodle and WordSpress then
copied that file into a container, where the named path is a different file - or
none. Only update-ca-certificates populates a container's system bundle, and
that runs solely from the with-ca-trust.sh entrypoint, which compose_ca.py sets
only when 'docker image inspect' succeeded. On a fresh host the image is built
minutes later, so it never ran: job 94965457602 of run 31864448027 shows
Config.Entrypoint still moodle-entrypoint.sh while the CA sits mounted beside it,
and msmtp reporting 'The certificate issuer is unknown'.
An explicit tls_trust_file also outranks SSL_CERT_FILE, so the env injection the
override always performs could not compensate.
Name CA_TRUST.inject_cert_container when the CA is actually injected, and emit
no line at all otherwise - msmtp's own 'system' default is the right answer for
a letsencrypt relay, and it is what the container's stock bundle verifies. This
also removes a latent defect on letsencrypt: a RedHat host would have written
/etc/pki/... into a Debian container.
The removal of the probing apparatus is taken from 9a72d01eb on
fork/feature/ai-agent-employees, which diagnosed the wrong-machine measurement
correctly. Its own conclusion - drop the line entirely and rely on the trust
store - does not hold where the wrapper never runs, and in swarm it never does:
handlers/swarm.yml passes wrapper=False by design.
The three-term predicate behind all of this was hand-copied into four places.
lookup('ca_injected', application_id) is now its single source; the swarm and
compose gates and sys-svc-compose-ca's own var read it too.
compose_ca.py finally says when it degrades to mount-plus-env, naming the service
and the image it could not inspect.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A handler name was doing two jobs at once: the line an operator reads in the
play output, and the identifier a `notify:` binds to. The two pull in opposite
directions -- the log wants "restart the SSH service, with diagnostics", the
binding wants a token that survives quoting, YAML flow style and a shell
round-trip -- and a name that changes for readability silently changes which
handler runs.
Every handler that a notify reached by name now carries a `listen:` with that
exact wording, so the binding is explicit before anything is renamed. The
topics themselves become identifiers: `compose up` reads `compose-up`,
`Restart NFS ganesha` reads `restart-nfs-ganesha`, 22 of them across both
sides of every notify. Where the topic is assembled at runtime, both branches
moved together: the conditional in sys-service carries two literals, and the
LDAP renderer interpolates `import-{{ folder }}-ldif-files`, which is what
makes those handlers reachable at all.
Freed from the binding, the names take the emoji prefix every other task line
already has.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One template serves two consumers with opposite needs. sys-svc-mail-msmtp
renders it to /root/.msmtprc, which the host itself reads, and web-app-wordpress
and web-app-moodle render the same file into their config directory, from where
it is mounted at /etc/msmtprc inside a container. resolve_smtp_host returned the
loopback for both, which is right for the first and meaningless for the second:
inside a container 127.0.0.1 is that container's own loopback and nothing is
listening on it.
WordPress 0 of run 31658270172 died on it. The deploy converged and then
"⏳ Wait for the delivery marker the healthcheck writes" retried sixteen times
before `assertion: wordpress_smoke_mail.rc == 0` evaluated to false. The reason
is in the rescue bundle rather than the log, identically in all three replicas
(containers/wordpress_wordpress.{1,2,3}.*.inspect.json):
msmtp: cannot connect to 127.0.0.1, port 587: Connection refused
msmtp: could not send mail (account default from /etc/msmtprc)
Those containers report Status: healthy with FailingStreak: 0, which is by
design since ebdadd91b decoupled the smoke mail from liveness -- the probe ends
on curl and the delivery assertion carries detection. The assertion was right;
the address it complained about was ours.
smtp_host gains in_container=, and the template passes it from a variable each
of the three call sites sets explicitly. There is deliberately no default: a
fourth consumer must fail loudly rather than silently inherit a loopback that
does not apply to it, which is the very defect being fixed. grep across roles,
utils and plugins finds exactly those three consumers today.
The alias needs pinning to resolve, and container_extra_hosts is the only
emitter of extra_hosts. It gained host_alias=, which appends
host.docker.internal at whatever reaches the host in the effective mode by
reusing _gateway_address -- host-gateway under compose, the stack host's routable
address under swarm. Hardcoding host-gateway at the call site would repeat the
mistake 7b1839681 already paid for: that alias resolves per node and misses the
one that publishes.
Measured, not assumed:
- all four resolver branches: host 127.0.0.1, container host.docker.internal,
bare infra node the manager, outside the rig the configured relay unchanged
- both branches again through a real ansible lookup call, because run() at
first did not forward the kwarg and Python-level testing would have missed
a silently ignored parameter
- the pin emits `host.docker.internal:host-gateway` under compose and
`host.docker.internal:<node address>` under swarm
- the destination exists under swarm: sockets-tcp.txt of all three nodes in
the same rescue bundle shows `LISTEN 0 4096 *:587 users:(("dockerd",…))`, so
the submission port is published on every interface of the node -- which is
why the container's own loopback missed it and the host alias will not
- and under compose by construction: mailu publishes submission on
MAILU_IP4_PUBLIC (templates/compose.yml.j2:12), which is
DOCKER_PUBLIC_BIND_HOST, which is 0.0.0.0 inside the rig
(group_vars/all/00_general.yml:46)
- 11 existing smtp_host unit tests stay green; make test-lint stays at 328
The moodle half fixes nothing that is currently red, and that is the point:
moodle renders the same broken file into two services but its healthcheck only
opens a TCP connection to the FPM port, so Moodle 0 of run 31658241728 passed
with mail that could never leave the container. It breaks when Moodle actually
sends -- a password reset, a forum subscription -- and no job would say so.
Not verified: that the relay accepts the message once reached. The port is
proven reachable, delivery is not.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
msmtp has defaulted tls_trust_file to the special value 'system' since 1.8.2,
and 'system' is exactly what the platform already curates: with-ca-trust.sh
installs the root anchor through update-ca-certificates, update-ca-trust or
trust, whichever the distribution has, and never needs a path. The deleted
apparatus reimplemented that by hand and measured the wrong machine - it
stat'ed candidate paths on the Ansible target while the rendered file is read
inside Debian containers, so a RedHat host would have named a path those images
do not have.
Measured on a live Debian node with no config file at all, i.e. the state this
commit produces: msmtp reports tls_trust_file = system with tls_certcheck = on
and verifies the relay leaf CN=INFINITO.EXAMPLE Global TLS against
CN=infinito.example Root CA; pointing it at an unrelated CA fails as expected.
Inside the deployed wordpress container the rendered msmtprc now carries no
trust line and msmtp 1.8.28 (GnuTLS) resolves it to system.
On p11-kit distributions this widens the anchor set from the serverAuth
extraction to the unfiltered store rather than being neutral; no anchor is lost
on any supported distribution.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Install the protocol plugin during the image build, because a Moodle
plugin must match the pinned release and installing it at runtime would
leave the two free to drift.
Provision the webservice and its token through PHP, and attach only
read-only functions, so the read-only tool policy is enforced by the
service definition rather than declared in metadata.
Address the endpoint as its canonical origin. Moodle compares every
request against wwwroot, so a probe over the internal HTTP origin was
answered with an HTML redirect instead of JSON and every retry hit the
same wall; the verify step now sends the canonical Host and the
forwarded-proto header.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add tests/lint/repository/test_task_file_numbering.py: numbered task
files use exactly two leading digits, a numbered *_core.yml must be
00_core.yml, per directory the numbers are unique/consecutive and start
at 01 (00 with a core file), numbered includes appear in ascending
order, and shared helpers live in tasks/utils/ instead of the numbered
chain.
Apply it across every role: rename NN_core.yml to 00_core.yml with a
consecutive down-shift of the siblings, move letter-suffix children into
a subdirectory named after their parent task, relocate helpers included
from more than one place into tasks/utils/, and update every reference
(include_tasks, path_absolute, .ansible-lint-ignore, requirement docs).
Legacy narration comments in the touched files are deleted; the upstream
xwiki.properties reference block opts out via nocheck. The unused
MAILU_IP6_PUBLIC var (only ever named in now-removed comments) is dropped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>