Commit Graph
5 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5 a46348b6cf refactor(handlers): bind notify to a listen topic, not to a name
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>
2026-08-16 03:38:42 +02:00
kevinveenbirkenbachandClaude Opus 5 1ff6999ee2 fix(nfs-server): make one script own "ganesha serves", and let it judge
Penpot 0 of run 31658241728 died on the restart handler while the node was
healing itself. Its journal, in order:

    20:46:47 nfs-ganesha.service: State 'stop-sigterm' timed out. Aborting.
    20:46:58 nfs-ganesha.service: State 'stop-watchdog' timed out. Killing.
    20:46:58 Main process exited, code=killed, status=9/KILL
    20:46:58 Failed to start nfs-ganesha.service
    20:47:00 [main] nfs_start :NFS STARTUP :EVENT : NFS SERVER INITIALIZED
    20:47:01 systemd[1]: Started nfs-ganesha.service - NFS-Ganesha file server.

Ansible failed at 20:46:58, three seconds before the replacement was serving.
The rescue bundle confirms the end state independently: 17 threads, RPC workers
in svc_rqst_epoll_loop's epoll_wait, fridge threads idle -- a healthy server.

9b52a966c built exactly this recovery (ExecStartPost turns a wedge into a unit
failure, Restart=on-failure replaces the process, a wait_for on 2049 absorbs one
cycle). The defect was that the recovery sat downstream of the handler that
died on the event it exists for: `systemd: state=restarted` reports the restart
job's result, and the play never reached the gate.

A shell script now owns the whole statement -- restart or start, then wait for
2049, exit 0 only once the port answers, exit 1 with the unit's status and
journal. systemctl's own result is deliberately not the verdict, because a
recovering unit and a broken one both read 'failed' at the moment it returns;
the difference only exists two seconds later, which is what the wait measures.
The handler is that SPOT's restart entry point and the task after it is the
start one; vars carries unit, port and budget once.

That removes the tolerance instead of relocating it. Gone with it: a
`failed_when: false`, a dead `register`, a handler that set a fact instead of
restarting, three `|| true` against this repo's own no-or-true rule (unnecessary
anyway -- the script sets -u, not -e), and the now-duplicated wait_for gate.
`changed_when` on both call sites reads the script's CHANGED marker, so a start
that had to act reports changed and one that found the unit already active does
not; previously the task claimed `changed_when: false` even when it had started
the service itself.

Measured, and in the image it will run in (infinito-swarm-node, the same one the
rescue bundle came from):

  - all four combinations: start+inactive -> CHANGED, start+active -> no change,
    restart -> CHANGED either way
  - systemctl failing while the port answers -> rc 0; port never opening -> rc 1
    with systemctl's own rc and message, status and journal on stderr
  - bash 5.2.37, /usr/bin/systemctl, /usr/bin/journalctl present; /dev/tcp works
  - the script run inside that image with no systemd at all still judges
    correctly, because its verdict is the port

TimeoutStopSec stays at 10. The first attempt at this raised it to 90, which
9b52a966c rules out in writing -- the wedged process blocks SIGTERM, so a longer
budget only burns the recovery cycle the 120s gate has to hold. The merge-fix
search caught that before it was committed.

Not verified: that ansible.builtin.script's temp directory is executable on
every node (it was exercised here through docker run, not through the module),
that 120s covers a real recovery beyond the single three-second data point, and
the three sibling node images.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 02:02:31 +02:00
kevinveenbirkenbachandClaude Opus 4.8 d95f8d8d22 fix(ansible): clear deploy deprecation warnings + gate against regressions
Remove the [DEPRECATION WARNING] entries the compose/swarm deploys emitted:

- apt_repository (removed in ansible-core 2.25): write docker.list via copy +
  an explicit apt update. deb822_repository is unusable here because
  apt_repo.sh's sanitize_sources deletes .sources every run.
- INJECT_FACTS_AS_VARS (default deprecated in 2.24): use ansible_facts['<name>']
  instead of the auto-injected top-level ansible_os_family / virtualization_* /
  default_ipv4 vars.
- community.mysql.* (removed in community.mysql 6.0.0): switch to ansible.mysql
  and pin the collection in requirements.galaxy.yml.

Add a compose deploy gate (assert_no_deprecation_warnings.sh + workflow step +
test) that fails on [DEPRECATION WARNING] in the deploy log, and a lint test
(test_no_deprecated_ansible_patterns) that forbids the three patterns above so
they cannot return.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 20:36:57 +02:00
kevinveenbirkenbachandClaude Opus 4.8 c76c6da6a8 feat(svc-storage-nfs-server): userspace nfs-ganesha flavor for the swarm-test + refactor agent iteration docs
svc-storage-nfs-server gains a backend flavor selected by services.nfs-server.flavor (default "{{ 'ganesha' if RUNTIME in ['dev','act'] else 'kernel' }}"). The ganesha flavor installs nfs-ganesha + FSAL_VFS and renders /etc/ganesha/ganesha.conf (NFSv4, Pseudo == Path so clients mount the real paths unchanged, Root_Squash on the export base and No_Root_Squash on the state subdir), reloaded via systemctl restart. It runs in the dev/act DinD swarm-test, where the host kernel nfsd is not network-namespaced and a privileged node driving exportfs would wedge it into a D-state. The kernel flavor keeps nfs-kernel-server + exportfs -ra for production and every non-dev/act runtime. 01_core dispatches on the flavor, 03_prepare holds the shared export preparation, flavor/{kernel,ganesha}/main.yml hold the per-backend install/render/service steps, and handlers gains the ganesha restart.

docs/agents/action/iteration: role.md renamed to compose.md (Compose Loop); new swarm.md for the swarm deploy loop split out of workflow.md; workflow.md trimmed to GitHub Actions workflow iteration; all cross-references updated; the duplicated README gotcha removed; the NFS gotcha rewritten to the ganesha design.

Also drops a retired-flag history comment in group_vars/all/01_modes.yml and a redundant comment in the init selection script, and allows WebFetch(repology.org) used during ganesha packaging research.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 07:30:34 +02:00
kevinveenbirkenbachandClaude Opus 4.7 3c07721303 feat(swarm-nfs): bootstrap Docker Swarm + NFS-backed shared volumes (#023)
Adds the svc-docker-swarm, svc-docker-swarm-manager, svc-storage-nfs-server
and svc-storage-nfs-client roles, the compose_volumes filter NFS support,
the test-deploy-swarm-nfs CI workflow with its swarm-nfs-pilot script set,
and the deploy_mode-aware compose/swarm wiring across mediawiki, mariadb
and sys-svc-* helpers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 18:57:34 +02:00