From 7914fd74c6afe2f3f1785b0b9982194f735935ef Mon Sep 17 00:00:00 2001 From: jochens Date: Fri, 2 Oct 2026 16:41:00 +0200 Subject: [PATCH] The two-node bed joins its second machine through the tunnel A token carries the bus's address, and at genesis that is the anchor's loopback, which no other machine reaches. The anchor joins locally and becomes the hub; the laptop makes its tunnel key, is issued a token for it and joins over the tunnel (novox/hq ADR 0169, issue 146). --- test/integration/mesh.test.ts | 42 ++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/test/integration/mesh.test.ts b/test/integration/mesh.test.ts index 73210ad..c6dccbf 100644 --- a/test/integration/mesh.test.ts +++ b/test/integration/mesh.test.ts @@ -322,17 +322,37 @@ test("a bare machine becomes a mesh", { skip, timeout: 600_000 }, async () => { }); test("both machines join it, and the token is all they need", { skip, timeout: 900_000 }, async () => { - for (const [machine, node] of [["anchor", "anchor"], ["laptop", "laptop"]] as const) { - await mesh(`node add ${node}`); - const token = tokenFrom(await mesh(`token issue --node ${node}`)); - await composeTheBusUsers(); - // No --name. The token says what the mesh calls the machine, which is the fault this walk - // found the first time it was run. - const said = await must(machine, `${HOST_PATH} enrol --token ${quote(token)}`); - await composeTheBusUsers(); - assert.match(said, new RegExp(`enrolled as ${node}`), said); - assert.match(said, /sealing key/, "no sealing key was generated"); - } + // **The anchor runs the bus, so it joins over its own loopback.** Every other machine joins + // through the tunnel (novox/hq ADR 0169): the anchor is the hub, and its tunnel comes up first. + await mesh(`node add anchor`); + const first = tokenFrom(await mesh(`token issue --node anchor`)); + await composeTheBusUsers(); + // No --name. The token says what the mesh calls the machine, which is the fault this walk + // found the first time it was run. + const anchorSaid = await must("anchor", `${HOST_PATH} enrol --token ${quote(first)}`); + await composeTheBusUsers(); + assert.match(anchorSaid, /enrolled as anchor/, anchorSaid); + assert.match(anchorSaid, /sealing key/, "no sealing key was generated"); + + await mesh("overlay place anchor --hub --endpoint 192.0.2.10:51820 --site lab"); + await mesh("assign anchor networking"); + await must("anchor", `pgrep -x mesh-host >/dev/null || (nohup ${HOST_PATH} run > /var/log/mesh-host.log 2>&1 & sleep 3)`); + await mesh("push anchor"); + await new Promise((r) => setTimeout(r, 8000)); + await deriveTheFilterOn({ machine: "anchor", node: "anchor", hubPort: 51820, must, mesh, on }); + + // **The laptop makes its tunnel key, and the token is issued for it.** The hub is told the key + // before the token is shown, so the tunnel answers the first time the laptop knocks. + await must("laptop", `pacman -Sy --noconfirm --needed wireguard-tools >/dev/null 2>&1`); + const key = (await must("laptop", `${HOST_PATH} key 2>/dev/null`)).trim(); + await mesh(`node add laptop`); + const second = tokenFrom(await mesh(`token issue --node laptop --overlay-key ${key}`)); + await composeTheBusUsers(); + const laptopSaid = await must("laptop", `${HOST_PATH} enrol --token ${quote(second)}`); + await composeTheBusUsers(); + assert.match(laptopSaid, /the tunnel to the hub is up/, laptopSaid); + assert.match(laptopSaid, /enrolled as laptop/, laptopSaid); + assert.match(laptopSaid, /sealing key/, "no sealing key was generated"); const recorded = await must("anchor", `docker exec mesh-store psql -U postgres -d inventory -qAt ` +