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:
kind: private
cidr: [192.168.1.0/24]
cidr: [10.99.1.0/24]
gateway:
to: hosting
address: [192.0.2.50] # what the world sees the household as
@@ -25,7 +25,7 @@ machines:
inbound: allow
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:
- { port: 8080, on: home }
inbound: allow
+2 -2
View File
@@ -15,7 +15,7 @@ segments:
home:
kind: private
cidr: [192.168.1.0/24]
cidr: [10.99.1.0/24]
gateway:
to: hosting
address: [192.0.2.50]
@@ -53,7 +53,7 @@ machines:
inbound: allow
home-server:
at: { segment: home, address: [192.168.1.135] }
at: { segment: home, address: [10.99.1.135] }
inbound: allow
thermostat:
+4 -4
View File
@@ -21,7 +21,7 @@ segments:
home:
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
gateway:
to: isp-home
@@ -61,17 +61,17 @@ machines:
inbound: allow
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:
- { port: 443, on: home }
inbound: allow
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
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
# 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.
#
# 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
# substrate + novox set shanks 192.168.1.20 workstation (light)
# overlay hub, ingress g14 192.168.1.30 workstation (light)
# novox 192.0.2.20 ── anchor ace 10.99.1.10 home server, media/IoT set
# substrate + novox set shanks 10.99.1.20 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).
# 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.
home:
kind: private
cidr: [192.168.1.0/24]
cidr: [10.99.1.0/24]
gateway:
to: hosting
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
# (Plex, Home Assistant, Letta, Baserow, the UniFi JVM, mssql). Behind the gateway.
ace:
at: { segment: home, address: [192.168.1.10] }
at: { segment: home, address: [10.99.1.10] }
egress: true
inbound: allow
memory: 18GiB
@@ -140,7 +140,7 @@ machines:
# nodes with no overlay endpoint of their own hairpin the hub rather than peering directly, which
# is the normal case and is fine.
shanks:
at: { segment: home, address: [192.168.1.20] }
at: { segment: home, address: [10.99.1.20] }
egress: true
inbound: allow
memory: 3GiB
@@ -151,7 +151,7 @@ machines:
# everywhere" was never a description of anything real.
images: [mesh-runtime-portainer:development]
g14:
at: { segment: home, address: [192.168.1.30] }
at: { segment: home, address: [10.99.1.30] }
egress: true
inbound: allow
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", () => {
assert.match(xml, /192\.168\.1\.135/);
assert.match(xml, /10\.99\.1\.135/);
assert.match(xml, /198\.51\.100\.7/);
});
+7 -7
View File
@@ -30,7 +30,7 @@ segments:
cidr: [192.0.2.0/24]
home:
kind: private
cidr: [192.168.1.0/24]
cidr: [10.99.1.0/24]
gateway:
to: hosting
address: [192.0.2.50]
@@ -41,10 +41,10 @@ machines:
at: { segment: hosting, address: [192.0.2.20] }
egress: true
ace:
at: { segment: home, address: [192.168.1.10] }
at: { segment: home, address: [10.99.1.10] }
egress: true
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", () => {
@@ -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
// testing a flat network with extra steps.
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", () => {
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.
*
* 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", () => {
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", () => {
+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 () => {
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 () => {
@@ -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 () => {
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");
});
@@ -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.equal(server.kind, "machine");
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)}`,
);
});
+3 -3
View File
@@ -4,9 +4,9 @@
* anchor, everything on one public segment) into what production actually is:
*
* 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
* substrate (store/broker/ shanks 192.168.1.20 workstation (light: portainer only)
* control) + the whole novox g14 192.168.1.30 workstation (light: portainer only)
* novox 192.0.2.20 — the ANCHOR: ace 10.99.1.10 the home server, media/IoT set
* substrate (store/broker/ shanks 10.99.1.20 workstation (light: portainer only)
* control) + the whole novox g14 10.99.1.30 workstation (light: portainer only)
* set + overlay hub + ingress
*
* 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.
assert.deepEqual(
duplicateAddresses([
{ machine: "gw-a", segment: "home", address: "192.168.1.1/24" },
{ machine: "gw-b", segment: "cafe", address: "192.168.1.1/24" },
{ machine: "gw-a", segment: "home", address: "10.99.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
segments:
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] } }
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");
assert.equal(plans.length, 1, "one gateway declaration, one router");
assert.deepEqual(plans[0]?.inside.sort(), ["home", "iot"]);
@@ -21,9 +21,9 @@ test("different external addresses mean different routers", () => {
const scenario = parseScenario(`scenario: x
segments:
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] } }
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);
});
+4 -4
View File
@@ -13,8 +13,8 @@ import { assertSupported, UnsupportedError } from "../src/lifecycle/supported.ts
const withGateway = `scenario: x
segments:
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] } }
machines: { a: { at: { segment: home, address: [192.168.1.9] } } }`;
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: [10.99.1.9] } } }`;
test("a plain scenario is raisable", () => {
const scenario = parseScenario(`scenario: x
@@ -31,12 +31,12 @@ test("published ports and policy are implemented", () => {
const scenario = parseScenario(`scenario: x
segments:
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] } }
policy: [{ from: iot, to: home, allow: false }]
machines:
a:
at: { segment: home, address: [192.168.1.9] }
at: { segment: home, address: [10.99.1.9] }
published: [{ port: 443, on: home }]`);
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", () => {
refuses(
`scenario: x
segments: { net: { kind: public, cidr: [192.168.1.0/24] } }
machines: { a: { at: { segment: net, address: [192.168.1.1] } } }`,
segments: { net: { kind: public, cidr: [10.99.1.0/24] } }
machines: { a: { at: { segment: net, address: [10.99.1.1] } } }`,
/not documentation space/,
);
});
@@ -71,8 +71,8 @@ test("a gateway address must be on the PARENT segment, not the one behind it", (
`scenario: x
segments:
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] } }
machines: { a: { at: { segment: home, address: [192.168.1.9] } } }`,
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: [10.99.1.9] } } }`,
/is not within 'pub'/,
);
});
@@ -120,7 +120,7 @@ test("publishing on a segment the machine is not attached to is refused", () =>
`scenario: x
segments:
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: pub, address: [192.0.2.10] }
@@ -176,12 +176,12 @@ test("a multi-homed machine is valid", () => {
parseScenario(`scenario: x
segments:
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:
border:
at:
- { 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"]
home:
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 }
devices:
kind: private
@@ -236,7 +236,7 @@ segments:
cidr: [198.51.100.0/24]
home:
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 }
devices:
kind: private