feat(peer-cli): run harness containers on a macvlan network

The previous `peer-cli-run` recipe attached containers with `--network host`.
That makes every peer share the host network namespace, which means two
harness containers on the same machine cannot independently advertise mDNS
or look like distinct LAN devices: they all reuse the host's interface, the
mDNS daemon is single-instance per namespace, and any port the peer binds
is a host-wide port. That defeats the whole point of running multiple
peers side-by-side for end-to-end testing.

Switch the recipe to a Docker macvlan network. Each container gets its own
MAC and IP carved out of the real LAN subnet, sends and receives multicast
on the parent NIC, and appears to the rest of the home network as a fresh
device. mDNS discovery then works between peers exactly as it would for
two laptops on the same LAN, with no relay, reflector, or special routing.

Add a `peer-cli-net` recipe that creates the network idempotently (the
`docker network inspect` short-circuits when it already exists), make
`peer-cli-run` depend on it, and parameterise the parent interface,
subnet, and gateway as justfile variables so they can be overridden from
the command line for machines whose LAN does not match the defaults:

    just LANSPREAD_PARENT_IFACE=enp4s0 \
         LANSPREAD_SUBNET=10.0.0.0/24 \
         LANSPREAD_GATEWAY=10.0.0.1 \
         peer-cli-run alpha

The well-known macvlan limitation that the host cannot reach its own
macvlan children over the network is intentionally not worked around:
agents drive each peer through `docker run -i` stdin/stdout, which is the
docker control socket, not the LAN. Host-to-peer connectivity is not part
of the mental model and is not needed for any current test scenario.

Test Plan:
- `just peer-cli-image`
- `docker network create -d macvlan ... lanspread` succeeds on a host with
  the default `eth0` interface (or with overridden variables on others).
- `just peer-cli-run alpha` and `just peer-cli-run beta` in two terminals;
  both containers come up on the LAN with distinct IPs and discover each
  other via mDNS without any `connect` command.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 18:51:28 +02:00
parent dc9e13e6a1
commit 93c6133ea9
+18 -2
View File
@@ -31,9 +31,25 @@ peer-cli-build:
peer-cli-image: peer-cli-image:
docker build -f crates/lanspread-peer-cli/Dockerfile -t lanspread-peer-cli:dev . docker build -f crates/lanspread-peer-cli/Dockerfile -t lanspread-peer-cli:dev .
peer-cli-run NAME: # macvlan: each peer container gets its own MAC/IP on the real LAN.
# Override on the command line if your LAN differs, e.g.
# just LANSPREAD_PARENT_IFACE=enp4s0 LANSPREAD_SUBNET=10.0.0.0/24 LANSPREAD_GATEWAY=10.0.0.1 peer-cli-net
LANSPREAD_NET := "lanspread"
LANSPREAD_PARENT_IFACE := "eth0"
LANSPREAD_SUBNET := "192.168.1.0/24"
LANSPREAD_GATEWAY := "192.168.1.1"
peer-cli-net:
docker network inspect {{LANSPREAD_NET}} >/dev/null 2>&1 || \
docker network create -d macvlan \
--subnet={{LANSPREAD_SUBNET}} \
--gateway={{LANSPREAD_GATEWAY}} \
-o parent={{LANSPREAD_PARENT_IFACE}} \
{{LANSPREAD_NET}}
peer-cli-run NAME: peer-cli-net
mkdir -p "target/peer-cli/{{NAME}}/state" "target/peer-cli/{{NAME}}/games" mkdir -p "target/peer-cli/{{NAME}}/state" "target/peer-cli/{{NAME}}/games"
docker run --rm --init --network host --name "lanspread-peer-cli-{{NAME}}" -i \ docker run --rm --init --network {{LANSPREAD_NET}} --name "lanspread-peer-cli-{{NAME}}" -i \
-v "$PWD/target/peer-cli/{{NAME}}/state:/state" \ -v "$PWD/target/peer-cli/{{NAME}}/state:/state" \
-v "$PWD/target/peer-cli/{{NAME}}/games:/games" \ -v "$PWD/target/peer-cli/{{NAME}}/games:/games" \
lanspread-peer-cli:dev \ lanspread-peer-cli:dev \