diff --git a/scenarios/growing-mesh.yml b/scenarios/growing-mesh.yml new file mode 100644 index 0000000..41fcd55 --- /dev/null +++ b/scenarios/growing-mesh.yml @@ -0,0 +1,33 @@ +# Three machines, joined one at a time. +# +# The point is not the third machine. It is that adding one changes **every other node's** peer +# list: each existing node has to be told again, or the newcomer is on a network nobody else can +# see. A mesh that only configures the arriving node looks like it worked and is half a network. +# +# So this scenario exists to be raised, then grown — enrol two, push, check; enrol the third, +# push, and check that the first two changed. +scenario: growing-mesh + +segments: + hosting: + kind: public + cidr: [192.0.2.0/24] + +machines: + anchor: + at: { segment: hosting, address: [192.0.2.10] } + inbound: allow + laptop: + at: { segment: hosting, address: [192.0.2.20] } + inbound: allow + workstation: + at: { segment: hosting, address: [192.0.2.30] } + inbound: allow + +images: + - postgres:17-alpine + - cloudamqp/lavinmq:latest + - mesh-control:development + +place: + all: [host, runtime] diff --git a/src/lifecycle/base.ts b/src/lifecycle/base.ts index 7f215a2..5eaa204 100644 --- a/src/lifecycle/base.ts +++ b/src/lifecycle/base.ts @@ -55,6 +55,16 @@ export async function buildBaseImage( log(" installing a container runtime"); await incus(["exec", BUILDER, "--", "pacman", "-Sy", "--noconfirm", "docker"], 600_000); + // And the tools for the private network, for the same reason as the runtime: a sealed + // scenario cannot install them, so a lab that omits them cannot test connectivity at all — + // which is most of what the mesh does between machines. + // + // Installed here and NOT started. What a node runs is the mesh's decision, delivered as a + // declaration; a lab that brought the interface up itself would be testing its own setup + // rather than the mesh's. + log(" installing the tools for the private network"); + await incus(["exec", BUILDER, "--", "pacman", "-S", "--noconfirm", "wireguard-tools"], 600_000); + // Trust the documentation ranges as plain-HTTP registries. // // A scenario's registry is scenery inside the scenario, serving over HTTP, and a runtime @@ -70,6 +80,16 @@ export async function buildBaseImage( await incus(["exec", BUILDER, "--", "systemctl", "enable", "docker"], 60_000); await incus(["exec", BUILDER, "--", "systemctl", "start", "docker"], 120_000); + // Read back from the tool, not from the package manager (novox/hq 04-ISSUES/007). + const wg = await incusOk(["exec", BUILDER, "--", "wg", "--version"], 60_000); + if (!wg?.trim()) { + throw new BaseImageError( + `wireguard-tools was installed in ${BUILDER} and \`wg\` does not answer. Publishing ` + + `this would give every scenario a machine that cannot join a private network.`, + ); + } + log(` ${wg.trim()}`); + // Read back from the runtime, not from the package manager. An installed package is not a // capability (novox/hq 04-ISSUES/007), and this is the one place to catch that — after // publishing, every scenario pays for it instead.