Delete the lab's registry, and bootstrap the anchor through the installer #19

Merged
jschoubben merged 14 commits from feat/bed-bootstraps-through-the-installer into main 2026-09-11 19:57:05 +00:00
12 changed files with 48 additions and 48 deletions
Showing only changes of commit 94e617915c - Show all commits
+2 -2
View File
@@ -11,7 +11,7 @@ segments:
home: home:
kind: private kind: private
cidr: [192.168.1.0/24] cidr: [10.99.1.0/24]
gateway: gateway:
to: hosting to: hosting
address: [192.0.2.50] # what the world sees the household as address: [192.0.2.50] # what the world sees the household as
@@ -25,7 +25,7 @@ machines:
inbound: allow inbound: allow
home-server: # a dash in the name, on purpose home-server: # a dash in the name, on purpose
at: { segment: home, address: [192.168.1.135] } at: { segment: home, address: [10.99.1.135] }
published: published:
- { port: 8080, on: home } - { port: 8080, on: home }
inbound: allow inbound: allow
+2 -2
View File
@@ -15,7 +15,7 @@ segments:
home: home:
kind: private kind: private
cidr: [192.168.1.0/24] cidr: [10.99.1.0/24]
gateway: gateway:
to: hosting to: hosting
address: [192.0.2.50] address: [192.0.2.50]
@@ -53,7 +53,7 @@ machines:
inbound: allow inbound: allow
home-server: home-server:
at: { segment: home, address: [192.168.1.135] } at: { segment: home, address: [10.99.1.135] }
inbound: allow inbound: allow
thermostat: thermostat:
+4 -4
View File
@@ -21,7 +21,7 @@ segments:
home: home:
kind: private kind: private
cidr: [192.168.1.0/24, "2001:db8:b:1::/64"] cidr: [10.99.1.0/24, "2001:db8:b:1::/64"]
mtu: 1492 mtu: 1492
gateway: gateway:
to: isp-home to: isp-home
@@ -61,17 +61,17 @@ machines:
inbound: allow inbound: allow
home-server: home-server:
at: { segment: home, address: [192.168.1.135, "2001:db8:b:1::135"] } at: { segment: home, address: [10.99.1.135, "2001:db8:b:1::135"] }
published: published:
- { port: 443, on: home } - { port: 443, on: home }
inbound: allow inbound: allow
workstation: workstation:
at: { segment: home, address: [192.168.1.250, "2001:db8:b:1::250"] } at: { segment: home, address: [10.99.1.250, "2001:db8:b:1::250"] }
inbound: deny inbound: deny
laptop: laptop:
at: { segment: home, address: [192.168.1.98, "2001:db8:b:1::98"] } at: { segment: home, address: [10.99.1.98, "2001:db8:b:1::98"] }
inbound: deny inbound: deny
# No `place:` yet. The node host it would place does not exist — this lab is being built to # No `place:` yet. The node host it would place does not exist — this lab is being built to
+7 -7
View File
@@ -8,9 +8,9 @@
# — the access point — reachable from the outside only through what they dial out to. # — the access point — reachable from the outside only through what they dial out to.
# #
# hosting (public, routable) home (private, behind the access point) # hosting (public, routable) home (private, behind the access point)
# novox 192.0.2.20 ── anchor ace 192.168.1.10 home server, media/IoT set # novox 192.0.2.20 ── anchor ace 10.99.1.10 home server, media/IoT set
# substrate + novox set shanks 192.168.1.20 workstation (light) # substrate + novox set shanks 10.99.1.20 workstation (light)
# overlay hub, ingress g14 192.168.1.30 workstation (light) # overlay hub, ingress g14 10.99.1.30 workstation (light)
# #
# The `home` gateway masquerades v4 outbound and forwards inbound (an ordinary household router). # The `home` gateway masquerades v4 outbound and forwards inbound (an ordinary household router).
# Home nodes reach novox's public 192.0.2.20 by dialling OUT through it: the substrate broker (5671), # Home nodes reach novox's public 192.0.2.20 by dialling OUT through it: the substrate broker (5671),
@@ -58,7 +58,7 @@ segments:
# is exactly the NAT hole a WireGuard keepalive has to hold open. # is exactly the NAT hole a WireGuard keepalive has to hold open.
home: home:
kind: private kind: private
cidr: [192.168.1.0/24] cidr: [10.99.1.0/24]
gateway: gateway:
to: hosting to: hosting
address: [192.0.2.50] # what the world sees the household as address: [192.0.2.50] # what the world sees the household as
@@ -100,7 +100,7 @@ machines:
# The home server: the whole ace media/home set — 24 modules, ~50 containers, several heavy # The home server: the whole ace media/home set — 24 modules, ~50 containers, several heavy
# (Plex, Home Assistant, Letta, Baserow, the UniFi JVM, mssql). Behind the gateway. # (Plex, Home Assistant, Letta, Baserow, the UniFi JVM, mssql). Behind the gateway.
ace: ace:
at: { segment: home, address: [192.168.1.10] } at: { segment: home, address: [10.99.1.10] }
egress: true egress: true
inbound: allow inbound: allow
memory: 18GiB memory: 18GiB
@@ -140,7 +140,7 @@ machines:
# nodes with no overlay endpoint of their own hairpin the hub rather than peering directly, which # nodes with no overlay endpoint of their own hairpin the hub rather than peering directly, which
# is the normal case and is fine. # is the normal case and is fine.
shanks: shanks:
at: { segment: home, address: [192.168.1.20] } at: { segment: home, address: [10.99.1.20] }
egress: true egress: true
inbound: allow inbound: allow
memory: 3GiB memory: 3GiB
@@ -151,7 +151,7 @@ machines:
# everywhere" was never a description of anything real. # everywhere" was never a description of anything real.
images: [mesh-runtime-portainer:development] images: [mesh-runtime-portainer:development]
g14: g14:
at: { segment: home, address: [192.168.1.30] } at: { segment: home, address: [10.99.1.30] }
egress: true egress: true
inbound: allow inbound: allow
memory: 3GiB memory: 3GiB
+1 -1
View File
@@ -109,7 +109,7 @@ test("segments are ordered public first, then by depth behind them", () => {
}); });
test("a declared address appears on the machine that holds it", () => { test("a declared address appears on the machine that holds it", () => {
assert.match(xml, /192\.168\.1\.135/); assert.match(xml, /10\.99\.1\.135/);
assert.match(xml, /198\.51\.100\.7/); assert.match(xml, /198\.51\.100\.7/);
}); });
+7 -7
View File
@@ -30,7 +30,7 @@ segments:
cidr: [192.0.2.0/24] cidr: [192.0.2.0/24]
home: home:
kind: private kind: private
cidr: [192.168.1.0/24] cidr: [10.99.1.0/24]
gateway: gateway:
to: hosting to: hosting
address: [192.0.2.50] address: [192.0.2.50]
@@ -41,10 +41,10 @@ machines:
at: { segment: hosting, address: [192.0.2.20] } at: { segment: hosting, address: [192.0.2.20] }
egress: true egress: true
ace: ace:
at: { segment: home, address: [192.168.1.10] } at: { segment: home, address: [10.99.1.10] }
egress: true egress: true
sealed: sealed:
at: { segment: home, address: [192.168.1.99] } at: { segment: home, address: [10.99.1.99] }
`; `;
test("a machine behind a gateway still reaches the scenario through that gateway", () => { test("a machine behind a gateway still reaches the scenario through that gateway", () => {
@@ -52,16 +52,16 @@ test("a machine behind a gateway still reaches the scenario through that gateway
// handshake has to survive it. An egress machine that stopped using its gateway would be // handshake has to survive it. An egress machine that stopped using its gateway would be
// testing a flat network with extra steps. // testing a flat network with extra steps.
const routes = scenarioRoutesFor(parseScenario(HOUSEHOLD), "ace"); const routes = scenarioRoutesFor(parseScenario(HOUSEHOLD), "ace");
assert.deepEqual(routes, [{ cidr: "192.0.2.0/24", via: "192.168.1.1" }]); assert.deepEqual(routes, [{ cidr: "192.0.2.0/24", via: "10.99.1.1" }]);
}); });
test("a machine's own segment gets no route — it is already on-link", () => { test("a machine's own segment gets no route — it is already on-link", () => {
const routes = scenarioRoutesFor(parseScenario(HOUSEHOLD), "ace"); const routes = scenarioRoutesFor(parseScenario(HOUSEHOLD), "ace");
assert.ok(!routes.some((r) => r.cidr === "192.168.1.0/24"), JSON.stringify(routes)); assert.ok(!routes.some((r) => r.cidr === "10.99.1.0/24"), JSON.stringify(routes));
}); });
/** /**
* **The dangerous one.** `home` is 192.168.1.0/24 — a documentation range in spirit, an ordinary * **The dangerous one.** `home` is 10.99.1.0/24 — a documentation range in spirit, an ordinary
* private one in fact, and very possibly the network the workstation itself is on. * private one in fact, and very possibly the network the workstation itself is on.
* *
* With one public segment there is no transit router, so novox has no path to `home` at all. Left * With one public segment there is no transit router, so novox has no path to `home` at all. Left
@@ -71,7 +71,7 @@ test("a machine's own segment gets no route — it is already on-link", () => {
*/ */
test("a range with no path inside the scenario is unreachable, not leaked to the uplink", () => { test("a range with no path inside the scenario is unreachable, not leaked to the uplink", () => {
const routes = scenarioRoutesFor(parseScenario(HOUSEHOLD), "novox"); const routes = scenarioRoutesFor(parseScenario(HOUSEHOLD), "novox");
assert.deepEqual(routes, [{ cidr: "192.168.1.0/24", via: null }]); assert.deepEqual(routes, [{ cidr: "10.99.1.0/24", via: null }]);
}); });
test("a machine without egress is left to its default route, and states nothing", () => { test("a machine without egress is left to its default route, and states nothing", () => {
+3 -3
View File
@@ -45,7 +45,7 @@ test("ADR 0016 — the lab provides the underlay and NOTHING of the overlay", {
test("ADR 0016 — the declared address IS what the machine holds", { skip }, async () => { test("ADR 0016 — the declared address IS what the machine holds", { skip }, async () => {
const { stdout } = await exec(instanceId, "home-server", ["ip", "-o", "-4", "addr", "show"]); const { stdout } = await exec(instanceId, "home-server", ["ip", "-o", "-4", "addr", "show"]);
assert.match(stdout, /192\.168\.1\.135\/24/); assert.match(stdout, /10\.99\.1\.135\/24/);
}); });
test("design — raise waits for USABLE, not for the call to return", { skip, timeout: 120_000 }, async () => { test("design — raise waits for USABLE, not for the call to return", { skip, timeout: 120_000 }, async () => {
@@ -75,7 +75,7 @@ test("ADR 0016 — a router is scenery: containers, while machines are virtual m
test("design — NAT: a private address is not reachable from outside", { skip, timeout: 120_000 }, async () => { test("design — NAT: a private address is not reachable from outside", { skip, timeout: 120_000 }, async () => {
const { stdout } = await exec(instanceId, "anchor", [ const { stdout } = await exec(instanceId, "anchor", [
"sh", "-c", "ping -c1 -W2 192.168.1.135 >/dev/null 2>&1 && echo reachable || echo unreachable", "sh", "-c", "ping -c1 -W2 10.99.1.135 >/dev/null 2>&1 && echo reachable || echo unreachable",
]); ]);
assert.equal(stdout.trim(), "unreachable"); assert.equal(stdout.trim(), "unreachable");
}); });
@@ -139,7 +139,7 @@ test("the live diagram reads the hypervisor, and a VM's addresses are not lost",
assert.ok(server, "home-server missing from the live picture"); assert.ok(server, "home-server missing from the live picture");
assert.equal(server.kind, "machine"); assert.equal(server.kind, "machine");
assert.ok( assert.ok(
server.attachments.some((a) => a.addresses.some((address) => address.startsWith("192.168.1.135"))), server.attachments.some((a) => a.addresses.some((address) => address.startsWith("10.99.1.135"))),
`a virtual machine's addresses were not read back: ${JSON.stringify(server.attachments)}`, `a virtual machine's addresses were not read back: ${JSON.stringify(server.attachments)}`,
); );
}); });
+3 -3
View File
@@ -4,9 +4,9 @@
* anchor, everything on one public segment) into what production actually is: * anchor, everything on one public segment) into what production actually is:
* *
* hosting (public) home (private, behind a NAT access point) * hosting (public) home (private, behind a NAT access point)
* novox 192.0.2.20 — the ANCHOR: ace 192.168.1.10 the home server, media/IoT set * novox 192.0.2.20 — the ANCHOR: ace 10.99.1.10 the home server, media/IoT set
* substrate (store/broker/ shanks 192.168.1.20 workstation (light: portainer only) * substrate (store/broker/ shanks 10.99.1.20 workstation (light: portainer only)
* control) + the whole novox g14 192.168.1.30 workstation (light: portainer only) * control) + the whole novox g14 10.99.1.30 workstation (light: portainer only)
* set + overlay hub + ingress * set + overlay hub + ingress
* *
* There is NO separate anchor: novox IS the anchor. The substrate runs on novox, and novox also * There is NO separate anchor: novox IS the anchor. The substrate runs on novox, and novox also
+2 -2
View File
@@ -23,8 +23,8 @@ test("the same address on different segments is NOT a conflict", () => {
// Every private network has its own `.1`. Reporting that would make the check useless. // Every private network has its own `.1`. Reporting that would make the check useless.
assert.deepEqual( assert.deepEqual(
duplicateAddresses([ duplicateAddresses([
{ machine: "gw-a", segment: "home", address: "192.168.1.1/24" }, { machine: "gw-a", segment: "home", address: "10.99.1.1/24" },
{ machine: "gw-b", segment: "cafe", address: "192.168.1.1/24" }, { machine: "gw-b", segment: "cafe", address: "10.99.1.1/24" },
]), ]),
[], [],
); );
+4 -4
View File
@@ -9,9 +9,9 @@ test("segments sharing a gateway declaration share ONE router", () => {
const scenario = parseScenario(`scenario: x const scenario = parseScenario(`scenario: x
segments: segments:
pub: { kind: public, cidr: [192.0.2.0/24] } pub: { kind: public, cidr: [192.0.2.0/24] }
home: { kind: private, cidr: [192.168.1.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } } home: { kind: private, cidr: [10.99.1.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } }
iot: { kind: private, cidr: [192.168.30.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } } iot: { kind: private, cidr: [192.168.30.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } }
machines: { a: { at: { segment: home, address: [192.168.1.9] } } }`); machines: { a: { at: { segment: home, address: [10.99.1.9] } } }`);
const plans = planRouters(scenario, "inst"); const plans = planRouters(scenario, "inst");
assert.equal(plans.length, 1, "one gateway declaration, one router"); assert.equal(plans.length, 1, "one gateway declaration, one router");
assert.deepEqual(plans[0]?.inside.sort(), ["home", "iot"]); assert.deepEqual(plans[0]?.inside.sort(), ["home", "iot"]);
@@ -21,9 +21,9 @@ test("different external addresses mean different routers", () => {
const scenario = parseScenario(`scenario: x const scenario = parseScenario(`scenario: x
segments: segments:
pub: { kind: public, cidr: [192.0.2.0/24] } pub: { kind: public, cidr: [192.0.2.0/24] }
home: { kind: private, cidr: [192.168.1.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } } home: { kind: private, cidr: [10.99.1.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } }
other: { kind: private, cidr: [192.168.30.0/24], gateway: { to: pub, address: [192.0.2.6], nat: [v4] } } other: { kind: private, cidr: [192.168.30.0/24], gateway: { to: pub, address: [192.0.2.6], nat: [v4] } }
machines: { a: { at: { segment: home, address: [192.168.1.9] } } }`); machines: { a: { at: { segment: home, address: [10.99.1.9] } } }`);
assert.equal(planRouters(scenario, "inst").length, 2); assert.equal(planRouters(scenario, "inst").length, 2);
}); });
+4 -4
View File
@@ -13,8 +13,8 @@ import { assertSupported, UnsupportedError } from "../src/lifecycle/supported.ts
const withGateway = `scenario: x const withGateway = `scenario: x
segments: segments:
pub: { kind: public, cidr: [192.0.2.0/24] } pub: { kind: public, cidr: [192.0.2.0/24] }
home: { kind: private, cidr: [192.168.1.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } } home: { kind: private, cidr: [10.99.1.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } }
machines: { a: { at: { segment: home, address: [192.168.1.9] } } }`; machines: { a: { at: { segment: home, address: [10.99.1.9] } } }`;
test("a plain scenario is raisable", () => { test("a plain scenario is raisable", () => {
const scenario = parseScenario(`scenario: x const scenario = parseScenario(`scenario: x
@@ -31,12 +31,12 @@ test("published ports and policy are implemented", () => {
const scenario = parseScenario(`scenario: x const scenario = parseScenario(`scenario: x
segments: segments:
pub: { kind: public, cidr: [192.0.2.0/24] } pub: { kind: public, cidr: [192.0.2.0/24] }
home: { kind: private, cidr: [192.168.1.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } } home: { kind: private, cidr: [10.99.1.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } }
iot: { kind: private, cidr: [192.168.30.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } } iot: { kind: private, cidr: [192.168.30.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } }
policy: [{ from: iot, to: home, allow: false }] policy: [{ from: iot, to: home, allow: false }]
machines: machines:
a: a:
at: { segment: home, address: [192.168.1.9] } at: { segment: home, address: [10.99.1.9] }
published: [{ port: 443, on: home }]`); published: [{ port: 443, on: home }]`);
assert.doesNotThrow(() => assertSupported(scenario)); assert.doesNotThrow(() => assertSupported(scenario));
}); });
+9 -9
View File
@@ -27,8 +27,8 @@ test("the shipped scenarios are valid", () => {
test("a public segment on a private range is refused — the mesh would silently never form", () => { test("a public segment on a private range is refused — the mesh would silently never form", () => {
refuses( refuses(
`scenario: x `scenario: x
segments: { net: { kind: public, cidr: [192.168.1.0/24] } } segments: { net: { kind: public, cidr: [10.99.1.0/24] } }
machines: { a: { at: { segment: net, address: [192.168.1.1] } } }`, machines: { a: { at: { segment: net, address: [10.99.1.1] } } }`,
/not documentation space/, /not documentation space/,
); );
}); });
@@ -71,8 +71,8 @@ test("a gateway address must be on the PARENT segment, not the one behind it", (
`scenario: x `scenario: x
segments: segments:
pub: { kind: public, cidr: [192.0.2.0/24] } pub: { kind: public, cidr: [192.0.2.0/24] }
home: { kind: private, cidr: [192.168.1.0/24], gateway: { to: pub, address: [192.168.1.1], nat: [v4] } } home: { kind: private, cidr: [10.99.1.0/24], gateway: { to: pub, address: [10.99.1.1], nat: [v4] } }
machines: { a: { at: { segment: home, address: [192.168.1.9] } } }`, machines: { a: { at: { segment: home, address: [10.99.1.9] } } }`,
/is not within 'pub'/, /is not within 'pub'/,
); );
}); });
@@ -120,7 +120,7 @@ test("publishing on a segment the machine is not attached to is refused", () =>
`scenario: x `scenario: x
segments: segments:
pub: { kind: public, cidr: [192.0.2.0/24] } pub: { kind: public, cidr: [192.0.2.0/24] }
home: { kind: private, cidr: [192.168.1.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } } home: { kind: private, cidr: [10.99.1.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } }
machines: machines:
a: a:
at: { segment: pub, address: [192.0.2.10] } at: { segment: pub, address: [192.0.2.10] }
@@ -176,12 +176,12 @@ test("a multi-homed machine is valid", () => {
parseScenario(`scenario: x parseScenario(`scenario: x
segments: segments:
pub: { kind: public, cidr: [192.0.2.0/24] } pub: { kind: public, cidr: [192.0.2.0/24] }
home: { kind: private, cidr: [192.168.1.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } } home: { kind: private, cidr: [10.99.1.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } }
machines: machines:
border: border:
at: at:
- { segment: pub, address: [192.0.2.60] } - { segment: pub, address: [192.0.2.60] }
- { segment: home, address: [192.168.1.2] }`), - { segment: home, address: [10.99.1.2] }`),
); );
}); });
@@ -206,7 +206,7 @@ segments:
cidr: [198.51.100.0/24, "2001:db8:b::/48"] cidr: [198.51.100.0/24, "2001:db8:b::/48"]
home: home:
kind: private kind: private
cidr: [192.168.1.0/24] cidr: [10.99.1.0/24]
gateway: { to: isp, address: [198.51.100.7, "2001:db8:b::7"], nat: [v4], forwardable: true, mapping_ttl: 120s } gateway: { to: isp, address: [198.51.100.7, "2001:db8:b::7"], nat: [v4], forwardable: true, mapping_ttl: 120s }
devices: devices:
kind: private kind: private
@@ -236,7 +236,7 @@ segments:
cidr: [198.51.100.0/24] cidr: [198.51.100.0/24]
home: home:
kind: private kind: private
cidr: [192.168.1.0/24] cidr: [10.99.1.0/24]
gateway: { to: isp, address: [198.51.100.7], nat: [v4], forwardable: true } gateway: { to: isp, address: [198.51.100.7], nat: [v4], forwardable: true }
devices: devices:
kind: private kind: private