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).
This commit is contained in:
2026-10-02 16:41:00 +02:00
parent 91ba825975
commit 7914fd74c6
+31 -11
View File
@@ -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 ` +