A bed that raises the foundation from the bundle derives the anchor's filter before it relies on the hub

The base ruleset (ADR 0088) admits ssh, the bus and the registry and nothing else until the mesh
derives one, and the mesh derives one only where the filter module is assigned — which genesis
does and these beds did not. Without it the hub's WireGuard port stayed closed, no joined node's
tunnel formed, and every module dialling the anchor by its overlay name timed out fetching the
broker's certificate; the model-usage bed showed it as a login that failed for a role never made.
This commit is contained in:
2026-09-21 13:30:26 +02:00
parent af14f1c909
commit e085e31f95
4 changed files with 68 additions and 4 deletions
+56 -1
View File
@@ -9,7 +9,8 @@
*/
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { existsSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
import { isReachable, pools, supportedDrivers } from "../../src/incus/client.ts";
import { destroy, list } from "../../src/lifecycle/operate.ts";
import { diagramFromLive } from "../../src/diagram/from-live.ts";
@@ -230,3 +231,57 @@ export async function assertUniversalInvariants(
}
}
}
// --- the packet filter, where a bed raises the foundation without genesis ------------------------
/** The packet filter genesis installs on the control-node, by the name the catalogue gives it. */
export const FILTER_MODULE = "nftables";
/** The catalogue's manifest for a module, under either spelling of MESH_LAB_CATALOG: the modules
* directory, or the checkout that holds it. */
export function catalogueManifest(module: string): string {
const dir = process.env["MESH_LAB_CATALOG"] ?? "";
for (const candidate of [resolve(dir, module, "module.json"), resolve(dir, "modules", module, "module.json")]) {
if (existsSync(candidate)) return candidate;
}
throw new Error(`no manifest for ${module} under MESH_LAB_CATALOG=${dir || "(unset)"}`);
}
function shellQuote(s: string): string {
return `'${s.replaceAll("'", `'\\''`)}'`;
}
/**
* The foundation is raised behind a base ruleset that admits ssh, the bus and the registry and
* nothing else, "until the mesh derives one" (novox/hq ADR 0088) — and the mesh derives one only
* where the packet-filter module is assigned, which genesis does on the control-node. A bed that
* raises the foundation from the bundle skips genesis, so it must do the same before it relies on
* an overlay hub there: the hub's port is derived from its endpoint, and until the derived ruleset
* lands no joined node's tunnel forms, and every module that dials the anchor by its overlay name
* times out fetching the broker's certificate — the failure this helper was written after.
*
* Registered, assigned, pushed, and then WAITED FOR: what the machine loaded must admit the hub's
* port, which the base ruleset cannot. Call it after the hub is placed, so there is a port to derive.
*/
export async function deriveTheFilterOn(o: {
machine: string; node: string; hubPort: number;
must: (machine: string, command: string, timeoutMs?: number) => Promise<string>;
mesh: (command: string, timeoutMs?: number) => Promise<string>;
on: (machine: string, command: string, timeoutMs?: number) => Promise<{ out: string; ok: boolean }>;
}): Promise<string> {
const manifest = readFileSync(catalogueManifest(FILTER_MODULE), "utf8");
await o.must(o.machine,
`printf %s ${shellQuote(manifest)} > /tmp/${FILTER_MODULE}.json && docker cp /tmp/${FILTER_MODULE}.json mesh-controller:/${FILTER_MODULE}.json`);
await o.mesh(`module add /${FILTER_MODULE}.json`);
await o.mesh(`assign ${o.node} ${FILTER_MODULE}`);
await o.mesh(`push ${o.node}`, 600_000);
const admits = new RegExp(`udp dport ${o.hubPort} accept`);
const deadline = Date.now() + 180_000;
let ruleset = "";
while (Date.now() < deadline) {
ruleset = (await o.on(o.machine, `nft list table inet mesh 2>&1`)).out;
if (admits.test(ruleset)) return ruleset;
await new Promise((r) => setTimeout(r, 5_000));
}
assert.fail(`${FILTER_MODULE} is assigned to ${o.node} and the ruleset it loaded does not admit the hub's udp/${o.hubPort}:\n${ruleset}`);
}