From 59c42b20860662ffa0a6be0ca804a929dc3551f1 Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 4 Oct 2026 00:36:17 +0200 Subject: [PATCH] lab: its tools run in the node's runtime (hq ADR 0198) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mesh-lab container goes with its Dockerfile, build bases, bus credential and state directory. What the image installed — git, make, python, file, iproute2, sudo, npm, go and the incus client — are packages of the machine, and docker and incus are reached through their sockets as the runtime's account. The forge is an operator's setting, which reaches a file and never a bundle's words, so the tools read it from the env-file the mesh already fills, at each call; that is the one code change. --- modules/lab/Dockerfile | 31 ------------ modules/lab/module.json | 101 ++++++++++++++++++++----------------- modules/lab/tools/index.ts | 24 ++++++++- 3 files changed, 79 insertions(+), 77 deletions(-) delete mode 100644 modules/lab/Dockerfile diff --git a/modules/lab/Dockerfile b/modules/lab/Dockerfile deleted file mode 100644 index d43ec04..0000000 --- a/modules/lab/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -# lab's runtime: the tool runtime, carrying this module's code, and the toolchain the lab's suite -# builds the mesh with (novox/hq ADR 0172). It reaches the machine's virtualisation and container -# runtime through their sockets, so what it raises is what a hand run on this machine raises. -# -# Every download is pinned by its checksum: an image that builds the mesh is the last place to take -# whatever an upstream serves today. -ARG BUILD_BASE -ARG RUNTIME_BASE - -FROM ${BUILD_BASE} AS build -WORKDIR /app/modules/lab -COPY . . -RUN node /app/node_modules/typescript/bin/tsc tools/index.ts tools/runs.ts --rootDir . \ - --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist - -FROM ${RUNTIME_BASE} -RUN apt-get update \ - && apt-get install -y --no-install-recommends git make ca-certificates curl python3 file iproute2 sudo \ - && rm -rf /var/lib/apt/lists/* -RUN curl -fsSL -o /tmp/go.tgz https://go.dev/dl/go1.26.8.linux-amd64.tar.gz \ - && echo "d0f743b33e8d8945e6b1f432edd15785c70507121d6e2a723b21285eddf8b57b /tmp/go.tgz" | sha256sum -c - \ - && tar -C /usr/local -xzf /tmp/go.tgz && rm /tmp/go.tgz -RUN curl -fsSL -o /usr/local/bin/incus https://github.com/lxc/incus/releases/download/v7.5.1/bin.linux.incus.x86_64 \ - && echo "7bd6223b369f4d693fcde695bd8549a73b5b3d403735329212483702aa22c179 /usr/local/bin/incus" | sha256sum -c - \ - && chmod 0755 /usr/local/bin/incus -RUN curl -fsSL -o /tmp/docker.tgz https://download.docker.com/linux/static/stable/x86_64/docker-28.5.2.tgz \ - && echo "ea90cfd12e1eeb12aa1c971741adb8bd4ed88e2a574eaac13f5029a1dbc6300d /tmp/docker.tgz" | sha256sum -c - \ - && tar -C /tmp -xzf /tmp/docker.tgz docker/docker && mv /tmp/docker/docker /usr/local/bin/docker && rm -rf /tmp/docker /tmp/docker.tgz -ENV PATH=/usr/local/go/bin:$PATH -COPY --from=build /app/modules/lab/dist /app/modules/lab/dist -ENV MESH_TOOL_MODULES=/app/modules/lab/dist/tools/index.js diff --git a/modules/lab/module.json b/modules/lab/module.json index df85bc9..5cdc40b 100644 --- a/modules/lab/module.json +++ b/modules/lab/module.json @@ -5,16 +5,7 @@ "container-runtime", "virtualisation" ], - "own-secrets": { - "broker": "${dir:mesh-state}/broker" - }, "resources": [ - { - "id": "mesh-state", - "type": "directory", - "mode": "0700", - "place": "mesh" - }, { "id": "state", "type": "directory", @@ -35,47 +26,67 @@ "content": "MESH_LAB_FORGE=${setting:forge}\n" }, { - "id": "runtime", - "type": "container", - "name": "mesh-lab", - "network": "host", - "env-file": [ - "${dir:state}/lab.env" - ], - "volumes": [ - "${dir:mesh-state}/broker:/run/secrets/broker:ro", - "${dir:work}:${dir:work}", - "/var/run/docker.sock:/var/run/docker.sock", - "/var/lib/incus/unix.socket:/var/lib/incus/unix.socket" - ], - "env": { - "MESH_BROKER_FILE": "/run/secrets/broker", - "MESH_LAB_WORK": "${dir:work}" - }, - "restart-on": [ - "runtime-env" - ], - "artifact": "runtime" + "id": "git", + "type": "package", + "package": "git" + }, + { + "id": "make", + "type": "package", + "package": "make" + }, + { + "id": "python", + "type": "package", + "package": "python" + }, + { + "id": "file", + "type": "package", + "package": "file" + }, + { + "id": "iproute2", + "type": "package", + "package": "iproute2" + }, + { + "id": "sudo", + "type": "package", + "package": "sudo" + }, + { + "id": "npm", + "type": "package", + "package": "npm" + }, + { + "id": "go", + "type": "package", + "package": "go" + }, + { + "id": "incus", + "type": "package", + "package": "incus" } ], "build": { - "on": [ - { - "arg": "BUILD_BASE", - "module": "mesh-tools", - "artifact": "build" - }, - { - "arg": "RUNTIME_BASE", - "module": "mesh-tools", - "artifact": "runtime" - } - ], "artifacts": [ { - "name": "runtime", - "kind": "image", - "from": "Dockerfile" + "name": "code", + "kind": "bundle", + "language": "typescript", + "entrypoints": [ + "tools/index.js" + ], + "loads": [ + "tools/index.js" + ], + "env": { + "MESH_LAB_WORK": "${dir:work}", + "MESH_LAB_ENV_FILE": "${dir:state}/lab.env" + } } ] } diff --git a/modules/lab/tools/index.ts b/modules/lab/tools/index.ts index 097b022..53dbd75 100644 --- a/modules/lab/tools/index.ts +++ b/modules/lab/tools/index.ts @@ -2,18 +2,23 @@ // lab is assigned to, and only there: a bed raises virtual machines on that machine's virtualisation. import { spawnSync } from "node:child_process"; +import { readFileSync } from "node:fs"; import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools"; import { listRuns, readStatus, REPOSITORIES, running, start, stop, tail } from "./runs.js"; export function getLabTools(env: NodeJS.ProcessEnv): ToolDefinition[] { const work = env.MESH_LAB_WORK ?? "/var/lib/mesh-lab-runs"; - const forge = (env.MESH_LAB_FORGE ?? "").replace(/\/+$/, ""); + // The forge is an operator's setting, which reaches a file and never a bundle's words (novox/hq + // ADR 0192): read from the env-file the mesh fills, at each call, so a changed setting is used + // without restarting the runtime. MESH_LAB_FORGE itself still wins, for a hand-run instance. + const forgeOf = (): string => (env.MESH_LAB_FORGE ?? wordIn(env.MESH_LAB_ENV_FILE, "MESH_LAB_FORGE")).replace(/\/+$/, ""); return [ { name: "lab_check", description: "Whether this machine can run the lab's beds: the lab's own check, against the forge's main branch.", input: {}, run: async () => { + const forge = forgeOf(); if (!forge) return { ok: false, output: "the lab's forge is not set: settings for lab, {\"forge\": \"\"}" }; const dir = `${work}/check`; spawnSync("rm", ["-rf", dir]); @@ -38,6 +43,7 @@ export function getLabTools(env: NodeJS.ProcessEnv): ToolDefinition[] { }, }, run: async (args) => { + const forge = forgeOf(); if (!forge) return { started: false, reason: "the lab's forge is not set: settings for lab, {\"forge\": \"\"}" }; const tests = String(args.tests ?? "").split(",").map((s) => s.trim()).filter(Boolean); if (tests.length === 0) return { started: false, reason: "name at least one bed test file" }; @@ -83,4 +89,20 @@ export function getLabTools(env: NodeJS.ProcessEnv): ToolDefinition[] { ]; } +/** One word from an env-file (`KEY=value` lines), or "" when the file or the word is absent. */ +export function wordIn(file: string | undefined, word: string): string { + if (!file) return ""; + let text: string; + try { + text = readFileSync(file, "utf8"); + } catch { + return ""; + } + for (const line of text.split("\n")) { + const at = line.indexOf("="); + if (at > 0 && line.slice(0, at).trim() === word) return line.slice(at + 1).trim(); + } + return ""; +} + registerModuleTools("lab", (env) => getLabTools(env));