A freshly built dev container installs ansible-lint unpinned (26.10.1,
yamllint 1.38.0), and its --fix pass in make autoformat now drops the
trailing blank line of meta/packages.yml and
roles/sys-svc-container/meta/packages.yml and adds the missing document
start to roles/svc-net-wireguard/files/test/compose.yml (from #323).
Parsed YAML is unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A ping proved the tunnels answer, not that a session lands on the right host
or that full-size TCP segments cross them. Each pair sharing a mesh now also
opens an SSH session to the peer's mesh address and must land on that peer,
then pulls 8 MiB through it and compares the checksum with the one taken at
the source. Each pair that shares no mesh must fail the SSH connection itself,
never on authentication, since every host trusts every other host's key.
WireGuard must refuse a packet forced into a mesh that holds no peer for it
(Required key not available), so the isolation is shown to come from the
cryptokey table rather than from a missing route. The two hosts must still
reach each other on the underlay, so the isolation is the mesh's doing.
The SSH options are the platform's own, mesh_ssh_args over the repository's
ssh_args, the ones the swarm transport switches Ansible onto; the addresses
come from mesh_address. Every probe runs through one check helper, so a probe
is a predicate rather than another copy of the pass and fail bookkeeping.
Verified in the dev stack: all 20 checks passed, and a mutant that inverted
every expectation (unreachable pairs, a wrong identity, a different file, a
denied key, the peer's own address forced through the cryptokey check) failed
all 9 of its checks.
Starts three bare Debian hosts from one inventory, puts host II into both
meshes through a role's networks.meshes, writes the mesh with the CLI, deploys
playbook-mesh.yml onto them, which installs WireGuard there, and asserts that
I and II reach each other, that II and III reach each other, and that I and
III do not.
Verified in a local compose deploy of svc-db-redis with svc-net-wireguard:
all six reachability assertions passed and every throwaway host ended with
failed=0.
The consolidated svc-net-wireguard role, its requirement document and the three
references that only existed to support it go away together: the lint ignore for
a load failure inside its tasks, the requirements index entry, and the tunnel
subnet variable nothing else consumed.
Left in place deliberately: the WireGuard mention in the svc-net category
description, which is upstream prose about networking roles in general rather
than anything this role owned.
Tighten verbose comments to one line each (no-long-comments) and remove
leftovers from the old mesh design: unused NODE_OCTET / WG_PORT and the
dead WIREGUARD_IMAGE/VERSION env asserts.
Reuse the proven client tunnel mechanics but restructure the e2e into 3
server/client pairs: the 3 debian nodes deploy in [server] flavor
(linuxserver generates each peer config), and each workstation joins via
that config in [client] flavor (wsdebian as [client, nat]). Verify each
client<->server tunnel, the NAT masquerade rule on the behind-NAT node,
and a sane auto-derived wg0 MTU.
Endpoints used the outer node name (wg-e2e-<m>), which the wireguard
container cannot resolve from inside the peer node's inner dockerd, so no
handshakes formed (all 30 pairs down). Resolve each node's WGNET IP and
use it as the peer Endpoint, reached via the published relay port. Also
dump `wg show` per node before the connectivity check for debugging.
With the default server flavor, env.j2 sets PEERS/SERVERURL so the
linuxserver image runs in server mode and regenerates wg0.conf on
restart, wiping the rendered full-mesh peer set -> no handshakes (all 30
pairs unreachable). Deploy the mesh with WIREGUARD_FLAVOR=[client] so the
container honors the provided wg0.conf.
The DinD daemon.json hardcoded fuse-overlayfs, which fails copy-up on
images shipping an /etc/mtab symlink (e.g. linuxserver/wireguard) under
deeply-nested CI: "symlink /proc/mounts .../etc/mtab: file exists".
Expose SYS_SVC_CONTAINER_STORAGE_DRIVER (default fuse-overlayfs,
unchanged) and let the wireguard e2e deploy override it to vfs.
make install only sets up the venv + collections; the systemd dependency
comes from the distro package (built at image-build, not run here), so
/sbin/init never existed and the systemd re-create failed. Install
systemd (+ systemd-sysv / systemd-sysvcompat for /sbin/init) as a
bootstrap prerequisite on every node.
The pkgmgr bases have no systemd (no /sbin/init), so booting init failed;
systemd only arrives as an infinito package dependency during make
install. Two-phase: boot the bare base, run the per-node make install
(fresh environment), mask container-hostile first-boot units + seed a
machine-id, then commit and re-create the node with --entrypoint=/sbin/init
so systemd is PID 1 and the deploy can start docker.service like a host.
The pkgmgr base image's ENTRYPOINT runs the pkgmgr CLI, so passing
/sbin/init as a command arg never made systemd PID 1 (systemd never
became ready). Override with --entrypoint=/sbin/init so systemd (already
present in the base) boots as PID 1, and dump container logs on a
readiness timeout.
The role starts docker via systemd (docker.service), which raw debian/
manjaro/centos containers (PID1=sleep) cannot do. Boot the nodes from
Infinito's own fresh pkgmgr base images (systemd + pkgmgr, no infinito
baked) as systemd containers (cgroupns=host, cgroupfs rw, tmpfs /run),
so each node still runs its own make install + inventory + deploy and
the deploy can install/start docker exactly like a real host. Images are
pinned to 1.15.2 (never a moving tag); manjaro workstation -> arch base.
sys-svc-container configures the docker-ce apt repo via apt_repository,
which needs a gpg binary to handle the signing key; the minimal images
lack it (Either apt-key or gpg binary is required). Add gnupg/gnupg2 to
the per-distro prerequisites.
The -e container=docker env did not flip DOCKER_IN_CONTAINER: fact
gathering runs under become/sudo, which env_resets and drops `container`
before the setup module reads it. Force DOCKER_IN_CONTAINER=true as an
ansible extra-var (highest precedence) so the deploy takes the
container/DinD path and skips the host-only timezone task.
Infinito's DOCKER_IN_CONTAINER derives from the `container` env var,
which plain Docker (unlike systemd/podman) does not set, so the nodes
misdetected as bare metal: the constructor ran the host-only timezone
task (needs hwclock) and sys-svc-container would take the non-DinD path.
Boot nodes with -e container=docker so autodetect is correct.
The deploy runs on the target node itself, but ansible defaulted to SSH
and failed UNREACHABLE (Connection refused, no sshd). Force
ansible_connection=local via extra-var so the role applies locally.
provision auto-creates a random vault password at <inv>/.password and
encrypts ansible_become_password with it. deploy dedicated does not
default to it, so rendering become_pass failed. Pass --password-file
<inv>/.password (node-local, written by provision) to the deploy.
The shared bind-mount let make install state (.env, venv stamps) leak
across nodes, so only the first nodes ran the real install and the rest
short-circuited with no venv (no infinito venv python). Mount the repo
read-only as -src and tar-copy it (excluding .git) into a node-local
/opt/src/infinito per node, so every node runs a full, independent
install. Aligns with the per-node-inventory design.
server1 resolved /opt/venvs/infinito/bin/python but a later node did
not, so the venv lands at a different path/name there. Glob the common
venv parents (/opt/venvs/*, /root/.venvs/*, $HOME/.venvs/*) instead of
two fixed paths, and on a miss dump the existing venv dirs + .env venv
lines so the real location is visible in CI.
load.sh's PYTHON resolver silently falls back to the system python3
(missing install deps -> ModuleNotFoundError: ruamel) when it cannot
confirm the venv, which happened on later nodes once server1 generated
the shared .env. Pin /opt/venvs/infinito/bin/python (or the HOME venv)
explicitly and fail loudly if absent, for both provision and deploy.
The `infinito` console script was not reliably on PATH across distros
(found on debian, missing on centos). Call the venv "$PYTHON" -m cli
from the repo root instead — deterministic, no console-script/PATH
dependency, and the venv python carries the install deps.
Bare `python3 -m cli` used the system interpreter, missing make install's
venv deps (ModuleNotFoundError: ruamel). Source scripts/meta/env/load.sh
to put /opt/venvs/infinito/bin on PATH and call the `infinito` console
script for provision + deploy.
make install ships only the docker CLI, not the engine, so the manual
`dockerd` start in 01_bootstrap failed with `dockerd: not found`. The
deploy installs + starts the engine itself via sys-svc-container
(DinD-aware), so bootstrap now just boots nodes + make install.
make install now succeeds; the inner dockerd would not come up. Start it with --storage-driver=vfs (nested-friendly) and dump /tmp/dockerd.log on failure so the real cause is visible in CI.
Raw debian/manjaro/centos base images have no make/git/python, so 'make install' failed with 'make: not found'. Install per-distro prerequisites first (apt/pacman/dnf).
Wrap make install / inventory provision / deploy dedicated in timeouts with stdin from /dev/null and non-interactive env, and cap the connectivity check at 180s. A stuck step now fails the harness fast instead of hanging the CI job.
Replaces the linuxserver-image harness with a deploy-driven one: 01_bootstrap (6 empty containers + make install + DinD), 02_registration (per-host dedicated inventory via 'inventory provision' + full-mesh wg0.conf), 03_handshake ('deploy dedicated --id svc-net-wireguard' per host + all-pairs check). Adds the role's inventory-driven config path (mounts a per-host wg0.conf). NAT-flavor coverage follows once the mesh is green.
Pinning the gateway to 10.30.30.1 collided with the docker-reserved subnet gateway address (Address already in use). The source-based masquerade does not need a fixed IP, so let docker assign it.
nat.sh brings up a WireGuard client (MTU 1400) behind a masquerading gateway and verifies it reaches an upstream host only via the masquerade, plus rule present/absent assertions. Wired into test.sh after mesh.sh.
On the linuxserver Alpine image /etc/wireguard is a dangling symlink when the s6 init is bypassed (--entrypoint sleep), so key generation failed. Use a plain /tmp/wg workdir and 'wg-quick up /tmp/wg/wg0.conf' on every node.
The server-generated peer config has AllowedIPs=0.0.0.0/0, which routes the endpoint through the tunnel and deadlocks the handshake, and WireGuard never initiates without traffic. external.sh now narrows AllowedIPs to the server tunnel subnet, adds PersistentKeepalive, and pings the server tunnel IP to trigger the handshake.
tests/ -> files/test/ (test.sh/local.sh/external.sh/mesh.sh) + templates/test.env.j2 (the test-e2e-cli discovery marker). README and requirement 026 updated to the adopted contract.