container_name is illegal in swarm mode when replicas > 1 (docker rejects
with "services.deploy.replicas: can't set container_name and X as container
name must be unique"). All compose.yml.j2 templates now route through a
single lookup that suppresses the key in swarm mode and emits it in compose
mode.
SPOT: plugins/lookup/compose_container_name.py
Reads DEPLOYMENT_MODE from the templating context, returns
'container_name: "<name>"' under compose and an empty string under swarm.
Call sites read:
{{ lookup('compose_container_name', MY_CONTAINER) }}
No explicit DEPLOYMENT_MODE pass-through needed.
Sweep: 82 templates rewritten across roles/web-app-*, roles/web-svc-*,
roles/svc-*, plus 4 partial includes (sso_proxy, redis, mariadb, postgres).
For container names assembled from multiple Jinja fragments the args use
tilde concat (X ~ '-' ~ Y) so the lookup receives a single resolved string
and the call stays a single well-formed Jinja expression.
Lint: tests/lint/ansible/jinja/test_no_raw_container_name.py blocks
(a) raw 'container_name:' YAML keys in any roles/*/templates/**/*.j2,
(b) split-Jinja regression where the outer braces close mid-call:
{{ lookup('compose_container_name', A }}-{{ B) }},
(c) embedded-Jinja regression inside a string argument:
lookup(..., "{{ X }}_suffix") (removed in ansible-core 2.23).
The (b) and (c) regexes accept either single- or double-quoted lookup
name and scan the full file so multi-line shapes also surface. Suppress
per-site with '# nocheck: container-name' on the same or preceding line.
Tests: tests/unit/plugins/lookup/test_compose_container_name.py pins the
lookup contract: compose vs swarm output, whitespace tolerance, default-to
compose when DEPLOYMENT_MODE is absent, AnsibleError on missing or excess
terms, non-string name coercion.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drop the required_by declaration on svc-cache-registry so the
post-deploy validator no longer enforces its execution for every
svc-docker / web-app deploy. The pull-through cache is a performance
optimization, not a correctness requirement; operators that omit it
from inventory should not see a deploy fail.
Compose-mode auto-place now scopes svc-prx-openresty to plays that
actually carry a web-* role (any group whose name matches ^web-),
so svc-only deploys do not get an orphan front-proxy. The match is
intentionally on the web-* prefix family so new web-<sub>-<entity>
categories pick up the auto-place rule without an allowlist edit.
The swarm-NFS test still wants the cache for the pull-through speedup,
so svc-cache-registry is listed explicitly in 07_provision_inventory.sh
alongside the swarm bootstrap roles.
Fixes test-deploy-server / Server web-app-mediawiki failing with
"required role(s) did not execute for deploy: svc-cache-registry".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The api service had no command, so the CA-trust entrypoint wrapper ran the image entrypoint with no argument and it crash-looped (parameter not set). Pass command: gunicorn and set FUNKWHALE_API_PORT so the entrypoint binds 0.0.0.0:5000, matching the compose port mapping.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lookup('tls', ...).url.base keeps a trailing slash, so Friendica generated double-slash profile URLs (host//profile/<nick>) that broke local-follow contact resolution (no confirmation form rendered). Strip it via a shared FRIENDICA_BASE_URL var reused by env.j2 and the local.config.php patch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Frappe v16 gates the desk setup wizard on Installed Application.is_setup_complete per app and on the desktop:home_page default, not System Settings.setup_complete alone. Set the per-app flag, reset the setup-wizard home page, and clear cache so the native administrator lands on the desk.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ERPNext v16 bakes built assets into /home/frappe/frappe-bench/assets and symlinks sites/assets to them on every container start via an entrypoint that runs 'rm -rf sites/assets' under 'set -e'. Mounting assets as a separate named volume nested inside the sites volume made that rm hit a mountpoint (Device or resource busy), crashing the backend and all dependent containers before gunicorn opened its TCP port.
Persist only sites and logs, matching upstream frappe_docker; assets are relinked from the image on each start.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add tests/lint/ansible/jinja/test_no_ansible_facts_in_templates.py: under
swarm-mode the rendered template lands once on the primary manager and is
slurp-distributed to every other node, so any per-host fact reference
(ansible_hostname, ansible_default_ipv4, ansible_fqdn, ansible_nodename,
ansible_machine_id, ansible_eth0, ansible_all_ipv*_addresses) would
silently produce wrong output on the other nodes. The lint blocks them
at PR time.
scripts/tests/deploy/act/workflow.sh forwards --action-offline-mode +
--action-cache-path so repeated local act runs do not refetch the actions
catalog.
roles/sys-ctl-cln-anon-volumes/tasks/01_core.yml quoting cleanup applied
by autoformat (single to double quotes, no semantic change).
.claude/settings.json adds Workflow to permissions.allow so the
multi-agent Workflow tool no longer prompts per invocation.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Both deploy workflows now ship the rendered inventory for the matrix
entry as a zip artifact so failed runs can be inspected without
re-running the provisioner. test-deploy-swarm.yml zips /tmp/inv directly;
test-deploy-server.yml docker-cps /root/inventories from every running
infinito_nexus_* container into a staging dir first because the
inventory lives inside the compose container, not on the runner.
Both steps are if: always() to capture artifacts on failure too, but
gated on \!env.ACT because act-local does not provide
ACTIONS_RUNTIME_TOKEN and would otherwise fail the upload step.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DinD DNS chain reworked so the Playwright container run --rm npm install
no longer EAI_AGAINs on registry.npmjs.org:
- 03_start_swarm_nodes.sh runs each DinD with --dns 1.1.1.1/8.8.8.8 and
pre-seeds /etc/docker/daemon.json. systemd-resolved still clobbers the
outer resolv.conf at boot, so the daemon-level DNS is the only thing
that survives for containers the inner daemon spawns.
- daemon.json.j2 omits the "dns" key when networks.internet.dns is empty
(default), so the rendered config no longer overwrites the pre-seeded
daemon.json with "dns": [""] and break inner-container resolution.
- 09_write_extras.sh sets networks.internet.dns to a public resolver for
the test rig and bakes RUNTIME via cli.meta.runtime (so e2e-playwright
actually runs in act/github instead of being skipped under "host").
07_provision_inventory.sh generates mirrors.yml via
cli.contributing.mirror.resolver and passes --mirror to the inventory
provisioner only when cli.meta.runtime == "github". act/local keeps
upstream pulls (no mirror infrastructure to point at).
12_wait_converge.sh and 15_assert_state.sh accept any N/N replica state
instead of hardcoded 1/1, so multi-host-replica deploys converge cleanly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
sys-svc-rdbms now decides central-DB vs dedicated-DB mode from the provider
group's presence in the inventory (groups[provider_id] | length > 0)
instead of the per-host lookup('database', _, 'shared'). The old form
returned different values on different hosts (e.g. mariadb.shared =
'svc-db-mariadb' in group_names), so workers would silently disagree with
the manager about which mode is active and trip the central-init handlers
on the wrong host. The central-init block now also gates on the host
actually being in the provider group.
sys-stk-full guards the sys-stk-front-proxy include behind
"inventory_hostname in groups['svc-prx-openresty']", so under swarm only
the openresty host renders the per-app vhost. Combined with the
compose-mode auto-place from the previous commit, single-host compose and
swarm both pick exactly one host.
tasks/utils/load_app.yml skips loading a role for hosts that are not in
its target group when a group exists, matching the new gating semantic
upstream.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Allow more than one host in svc-docker-swarm-manager. The 01_core
assertion drops the exactly-one check and now requires >= 1 manager; the
init phase still bootstraps the first manager as leader, additional
managers join via the manager-join token (new 02_manager_join.yml), and
workers join via the worker token as before. With three or more managers
in the group, swarm gains raft quorum and survives the loss of a single
manager.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Roles that declare default_placement: manager in meta/services.yml are now
auto-added to their own inventory group when the operator did not list them
explicitly. Both the runtime constructor add_host loop and the CLI
pre-flight inventory extender use the new roles_with_default_placement
lookup so swarm and CLI-validation stay consistent.
In compose-mode (no svc-docker-swarm-manager group), svc-prx-openresty
auto-joins the first play host. run_once + ansible_play_hosts | first
keeps that deterministic in multi-host compose deploys so the openresty
vhost view does not fragment.
The svc-cache-registry / svc-docker-registry entries previously hardcoded
in scripts/tests/deploy/swarm/08_extend_inventory.sh fall out of the new
dynamic loop; net inventory is unchanged.
ansible_play_hosts joins the magic-var allowlist in
test_variable_definitions so the constructor add_host passes integration
lint.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Collapse two repeated swarm-vs-compose expressions into single-source-of-truth
group vars and apply across sys-svc-compose, web-app-mediawiki:
- SWARM_MANAGER_ACTIVE: true when DEPLOYMENT_MODE \!= 'swarm' or this host is
in svc-docker-swarm-manager. Used as the gate for "execute manager-only
deploy work here" (dir creation, file render, compose up, handler tasks).
- SWARM_PRIMARY_MANAGER: first svc-docker-swarm-manager or self under
compose-mode fallback. Used as delegate_to: for cluster-wide ops
(compose pull/build/up, swarm stack deploy, post-deploy DB health probe,
network create, MediaWiki one-shot install/admin).
Pure refactor — no semantic change. Subsequent edits to either expression
now live in one file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drop the "web-opt-rdr-domains in deployed" gate in the
current_play_redirect_domains lookup. CURRENT_PLAY_REDIRECT_DOMAINS now
always carries DOMAIN_PRIMARY to DOMAIN_HOMEPAGE when both are set and
differ. This triggers sys-stk-front-base to auto-load web-opt-rdr-domains
so openresty actually renders a vhost matching its declared canonical, and
the bare apex stops hitting the default-server's ssl_reject_handshake.
CSP probing skips those redirect-source domains: pure 301 vhosts have no
CSP header to validate.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The install + update + admin maintenance commands all run as throwaway
container instances against the custom image, with the live service's
bind mounts attached. The previous container exec-against-static-name
pattern stops working in swarm where the container name is a
per-replica swarm task id and the task itself can be reaped by the
healthcheck mid-update.
container_name is gated under swarm because docker-compose's strict
validation rejects it once replicas > 1. Cross-host config replication
in 01_configuration.yml is removed entirely: the play now runs natively
on every host in the role's group, so the LocalSettings.php / oidc.php
/ debug.php templates render in place per host.
Test inventory adds the pilot role to all three simulated swarm hosts,
which is what flips DEPLOYMENT_MODE to swarm under the new
inventory-driven model.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DEPLOYMENT_MODE is now derived per role from group membership:
swarm iff len(groups[application_id]) > 1, else compose. The cluster
infrastructure flag SWARM_CLUSTER_AVAILABLE is true whenever any host
sits in svc-docker-swarm, so DB and proxy services render attachable
overlay networks even when their own group lists only one host.
All service_is_stateful flags are removed across svc-db-mariadb,
svc-db-postgres, svc-db-openldap, svc-prx-openresty, svc-docker-registry
and svc-db-mariadb's compose template; the inventory shape now expresses
the same intent. Cluster-wide handlers (swarm build/push/validate/deploy,
external network creation, DB healthcheck) gain run_once + delegate_to
the manager so they execute exactly once regardless of how many hosts
the role's play targets. CA-injection and compose validate are gated to
compose-mode only; their compose-config strictness breaks when the
deploy block coexists with container_name under swarm.
Dead per-host swarm-resolve infrastructure is removed: swarm_resolve_
container.sh, resolve_container.yml, resolve_self.yml, the MariaDB-
specific swarm_mariadb_cid.sh, the cross-host template replication
step, and the _swarm_retries / _swarm_delay defaults. With run_once +
delegate_to: manager replacing the old "render every template on every
swarm peer" pattern, every consumer becomes a noop on workers and
collapses to the manager's one-shot path.
The web-app-nextcloud memcache override mapping flips from the legacy
"single" sentinel to "compose" (semantically equivalent under the new
model). Requirement 023 is updated to reflect the new model and drops
the now-irrelevant per-role opt-out / placement-constraint clauses.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New role that runs registry:2 in pull-through proxy mode on the swarm
manager. Worker nodes' daemon.json gets a registry-mirrors entry plus
the corresponding insecure-registries entry so plain-HTTP traffic to
the cache is permitted. The role's inventory group lists exactly one
host (manager) so the new inventory-driven topology model resolves
DEPLOYMENT_MODE to compose for the cache itself.
Within a single deploy run, the three swarm workers all pull the same
base images via the cache instead of hitting registry-1.docker.io
three separate times. Measured speedup on iter26 vs the no-cache iter21
baseline: pkgmgr/apt-install steps 2.4-2.8x faster, Docker image pulls
1.8-2.8x faster.
Cross-iter cache persistence is intentionally out of scope: act
recycles the DinD nodes between runs so the cache volume is wiped each
time. Cross-run reuse would require either bind-mounting the cache
from the host or pointing daemon.json at the dev compose stack's
registry-cache; both are tracked separately.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add tests/lint/docker/dockerfile/test_from_parameterized.py to fail any
roles/*/{files,templates}/**/Dockerfile* whose FROM does not reference an
ARG (${VAR}) or Jinja variable ({{ var }}). FROM scratch and a
`# nocheck: from-parameterized` per-line marker are exempt.
Refactor web-app-bluesky login-broker Dockerfile to satisfy the rule:
surface the base image and version via services.broker.{base_image,base_version}
-> vars/main.yml -> docker-compose build args -> ARGs in the Dockerfile.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New requirement for adding a second deployment flavor to web-app-matrix
that delegates to the upstream matrix-docker-ansible-deploy (MDAD)
playbook. The existing role-internal compose stack stays selectable as
the `compose` flavor for one deprecation window.
Captures 12 proposed design decisions for operator review before
implementation: flavor dispatch in services.matrix.flavor (ansible
default; compose deprecated), MDAD pulled in as a pinned git checkout at
deploy time (not vendored), MDAD inventory templated from
lookup('config', ...), every supported bridge + Jitsi + Element Call
enabled by default when no operator-issued third-party token is needed,
central svc-db-postgres + svc-db-redis + Mailu reuse, sys-svc-proxy
fronts MDAD's services (MDAD's bundled traefik disabled), four variants
(V1 ansible all-on, V2 ansible all-off, V3 ansible ldap-only, Vcompose-1
regression), two new Playwright specs (element-call, bridge-roster),
lifecycle: alpha on the new flavor.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`service_is_stateful` is set unconditionally to `false` in
group_vars/all/18_swarm.yml; per-role overrides in vars/main.yml win by
Ansible precedence. The `| default(false)` fallback is no longer reachable.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the host-scoped set_fact resolvers in sys-svc-compose/tasks/01_core.yml
and tasks/stages/01_constructor.yml with a single group_vars definition driven
by group_names. Uppercase the variable to match the SPOT convention and drop
every `| default('compose')` fallback now that the resolution is unconditional.
Role-var opt-out on a swarm host still works via Ansible's role-vars > group_vars
precedence.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The new meta/volumes.yml schema (enforced by
test_every_volumes_yml_entry_has_mandatory_name) requires each entry
to be a mapping with a mandatory `name:` string. erpnext was still
shipping the legacy plain-string form, which broke the lookup-schema
test in turn — consumer code referencing `volumes.sites` (no
sub-attribute) fails test_every_volumes_lookup_resolves_to_declared_attr.
Promote each volume entry to a mapping with `name:` and append `.name`
to the three matching `lookup('config', application_id, 'volumes.…')`
calls in vars/main.yml. The autoformat also normalized
roles/web-app-mediawiki/meta/volumes.yml's mode quoting from single to
double — included in the same commit because it landed in the same
autoformat sweep that preceded the test run.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On a fresh Ubuntu (the swarm pilot's jrei/systemd-ubuntu base) neither
nixbld1 nor nixbld2 exist, so `getent passwd nixbld1` returns exit code
2. `set -euo pipefail` makes the whole script abort silently — ansible
reported `rc: 2 / stderr: '' / stdout: ''` with no useful output, and
iter13 spent ages tracing it to dev-nix being pulled in transitively
via pkgmgr → dev-git → dev-make → dev-nix on every compose-deploying
role.
Wrap both `getent` calls with `2>/dev/null | ... || true`. The
existing `[[ -z "$uid1" || -z "$uid2" ]]` guard then catches the empty
values and the script exits 0 with `[UNCHANGED] nixbld1/nixbld2 not
both present, nothing to purge` — the documented idempotent path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three coupled fixes for the Controller≠Target topology the swarm pilot
exposed in iter12, iter15, and iter17/iter18:
1. `swarm_resolve_container.sh` now locates task containers via the
swarm state API (`docker service ps`, then `docker inspect TASK
--format '{{.Status.ContainerStatus.ContainerID}}'`) instead of a
local `docker ps --filter label=...` on the manager. A replica that
landed on a worker stayed invisible to the manager-side filter and
the role-level wait spun out after 60 retries (iter17 / iter18).
The state check accepts both `running` and `starting` so the
resolve unblocks before the app's healthcheck has had to pass —
first-deploy apps like mediawiki need their install_or_update.sh
step BEFORE they can become healthy.
2. `resolve_container.yml` invokes the script via an absolute
`{{ playbook_dir }}/roles/sys-svc-compose/files/...` path. When the
include_tasks was pulled in from another role (e.g. svc-db-mariadb's
02_init.yml), ansible's `ansible.builtin.script` file-lookup
searched the calling role's `files/` directory and aborted with
"Could not find or access 'swarm_resolve_container.sh'" (iter15).
3. `post_deploy.yml` gates the swarm task resolve on
`not service_is_stateful`. Stateful services (svc-docker-registry,
svc-db-mariadb) deploy as plain compose containers on the manager,
not as replicated swarm services — there is no `STACK_SERVICE` task
to resolve, so the role spun out for 60 retries waiting for one
that would never exist (iter12).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
iter11/iter12/iter14 each hung nfs-server's `apt-get update` in TCP
CLOSE_WAIT against apt-cache:3142 for 5-60 minutes, even with
Pipeline-Depth=0 + No-Keep-Alive + Retries=3 + Timeout=30. Pulling once
into the cache and fanning out to four nodes saves ~4 minutes of
bootstrap when it works, but every run that hit the hang lost 20+
minutes — net negative.
Remove the workflow cache restore step, the 02b_start_apt_cache.sh
launcher, the apt.conf.d/01-cacher write in 05_bootstrap_nodes.sh, and
the apt-cache cleanup in 17_cleanup.sh. Each node now talks directly
to the upstream mirrors. Slower-but-reliable beats faster-but-stuck.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 192.168.105.160/28 slot is also claimed by the open web-app-jitsi PR
(#287). CI's network-uniqueness lint catches the overlap once both branches
are checked together. Move to .176/28 (next free slot in the 105.x band).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New requirement for a `web-app-erpnext` role that integrates ERPNext
(Frappe Framework) into Infinito.Nexus with OIDC-direct SSO against
Keycloak, reusing central `svc-db-mariadb` and `svc-db-redis` (three
Redis logical roles split by DB number 0/1/2 for cache/queue/socketio).
Captures 13 proposed design decisions for operator review before
implementation: upstream `frappe/erpnext` image pinning, central-service
reuse (Decision #7), wizard-bypass `bench` bootstrap (Decision #9),
Keycloak-group → Frappe-role reconciliation (Decision #5), Mailu IMAP
inbound + SMTP outbound (Decision #8), and the standard
V1/V2/V3 variant matrix (Decision #11) mirroring web-app-kix /
web-app-zammad. Out of scope for v1: multi-site / multi-tenant Frappe
(Decision #12).
Modeled on the layout precedents from web-app-odoo (ERP shape,
dual HTTP + WebSocket vhost) and web-app-zammad (central-service
reuse pattern, three-variant matrix).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
iter11 hung nfs-server's `apt-get update` for 16+ minutes in CLOSE_WAIT
against apt-cache:3142 even with Pipeline-Depth=0 — the cache closed
each TCP slot but apt's HTTP method held onto them via keep-alive and
never noticed the half-close.
Set `Acquire::http::No-Keep-Alive "true"` so every apt request opens a
fresh socket and tears it down on completion (no shared state to leak),
plus a 30 s `Acquire::http::Timeout` so any future stuck fetch errors
out fast enough for the Retries=3 backoff to actually kick in.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
iter9 aborted at the registry deploy with
`failed to create network registry: ... This node is not a swarm manager`.
sys-svc-compose auto-sets `deployment_mode='swarm'` when the host is in
the svc-docker-swarm group, which makes networks.yml.j2 render the
registry's default network as `driver: overlay`. compose-up then tries
to create that overlay before `docker swarm init` has run, because the
generated tasks/groups/svc-docker-roles.yml orders roles alphabetically
(registry < swarm < swarm-manager).
Declare the dependency explicitly via run_after on svc-docker-swarm and
the matching `services.swarm` flag the registry's compose chain
genuinely consumes (it lives in a swarm overlay; the dependency is
hard, not cosmetic — hence the `# nocheck: dynamic-flag` literal `true`
in place of the usual `'<role>' in group_names` Jinja). The group-file
regenerator now emits registry after svc-docker-swarm, so overlay
creation hits an already-initialized cluster.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`lookup('command_path', 'dockreap')` runs on the Ansible controller and
calls `shutil.which('dockreap')` against the controller's PATH. That
works for single-host deploys (controller == target), but in the
swarm-NFS pilot the controller is the GHA runner / act-runner and
dockreap is only ever installed on the target swarm nodes. Result:
`command not found in PATH: 'dockreap'` aborted every compose deploy
once iter6/iter7/iter8 got past the daemon.json fix and reached the
first sys-ctl-cln-anon-volumes invocation.
Write the systemd unit with a bare command name + explicit PATH= in
[Service]. systemd resolves dockreap at unit-start time from the
target's own filesystem (where sys-pip-install put it), so the
controller no longer needs the binary at all.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In CI the workflow runs as the unprivileged `runner` user, so
`scripts/install/package.sh`'s `apt-get update && apt-get install
build-essential debhelper dpkg-dev rsync` aborted with `E: Could not open
lock file /var/lib/apt/lists/lock - open (13: Permission denied)` before
the .deb was ever produced. Local act runs as root inside
catthehacker/ubuntu so we never hit it.
Detect non-root in 05_bootstrap_nodes.sh and prepend `sudo -E` to the
build invocation. Inside the swarm-node containers package.sh still
runs as root and never goes through sudo (those exec calls are
unchanged).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sys-stk-backend pulls the full backup chain (sys-svc-rdbms +
sys-bkp-container-2-local), and the latter's systemd unit renders via
`lookup('command_path', 'dockreap')` — which fails at registry-deploy
time because the `sys-pip-install` step that installs `dockreap` runs
later in the play. iter6 hit `command_path: command not found in PATH:
'dockreap'` here and the play aborted on swarm-mgr-01.
The registry is a stateless image cache for swarm workers — it has no
database, no backup target, and no SSO proxy. sys-svc-compose covers
exactly what it needs: compose render + handler flush.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Leaving the apt-cacher-ng proxy enabled past the .deb-deps install made the
later ansible-driven apt tasks (sys-svc-ssh, update, ...) hang on TCP
CLOSE_WAIT against the cache even with Pipeline-Depth=0: the connections
the proxy closed upstream were never picked up by ansible's apt method on
the next invocation. The cache only pays for itself during the parallel
4-node .deb-deps fetch anyway — afterwards the proxy is pure friction.
Remove /etc/apt/apt.conf.d/01-cacher at the end of provision_node so
every ansible apt task hits the upstream mirrors directly, undisturbed
by the cache's keep-alive semantics.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jrei/systemd-ubuntu:24.04 has been exiting with code 255 in ~0.4s on
ubuntu-24.04 GHA runners since image build 20260525.161, producing zero
stdout/stderr (the docker-logs fallback from d7e859041 captured nothing).
`--privileged` alone no longer disables AppArmor + seccomp on the host
runner image, so systemd can't take ownership of its cgroup / signal mask
and PID 1 aborts before it logs anything.
Add the three flags that, in combination, restore the same isolation
posture systemd-in-docker has historically had:
--cgroupns=host let systemd see the host cgroup tree
(otherwise the unified hierarchy is
remapped under /sys/fs/cgroup/system
and pid 1 refuses to manage it)
--security-opt seccomp=unconfined drop the default seccomp filter that
now blocks io_uring/clone3 calls
systemd-255 uses during init
--security-opt apparmor=unconfined explicit override; --privileged is
supposed to imply this but the new
runner image's docker.service-level
profile re-applies it on top
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
iter4 hung swarm-wrk-02's `apt-get update` for 5+ minutes in TCP CLOSE_WAIT
against the apt-cacher-ng container: 3 sockets to apt-cache:3142 were closed
upstream, but apt's http method kept reusing pipelined slots and never
detected EOF.
`Acquire::http::Pipeline-Depth "0"` forces apt to one request per connection,
which is the documented workaround for apt-cacher-ng (see acng manual
section 4 "Common Apt Configuration Problems"). Bundled defaults:
* `No-Cache "false"` lets validated cache hits short-circuit IMS checks
* `Retries "3"` makes transient proxy errors recoverable
Switched the inline apt.conf write from a fragile single-line `printf
> file` to a heredoc piped through `docker exec -i`, since the new content
spans multiple lines.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pilot's /tmp/swarm-nfs-extras.yml is passed to ansible via -e @file
which makes its top-level keys (including swarm:) fully *replace* the
group_vars/all/18_swarm.yml swarm dict — silently dropping swarm.registry
and breaking the daemon.json.j2 + handler chain with
"object of type 'dict' has no attribute 'registry'" once iter3 hit the
Configure docker daemon storage driver task on the workers.
09_write_extras.sh now emits swarm.registry.host=${MGR} (swarm-mgr-01,
docker-DNS-resolvable from the workers) and port=5000, matching the
group_vars defaults.
Bundled: the lint-python EXE001 violation on the new
roles/sys-svc-compose/files/swarm_registry_push.py — the shebang was
present but the file was not chmod +x.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>