The Dynamic Security plugin refuses to start the broker unless dynamic-security.json already holds an admin client, and no reconcile loop seeds it (novox/hq ADR 0052). Add a run-once init container, declared before the server container, that runs mosquitto's own bootstrap entrypoint in the runtime image: it seeds the store offline via mosquitto_ctrl and exits, and the host gates the broker on its completion. The bootstrap hands the seeded file to the broker's user (uid 1883, chown + 0600): the broker must read the seed at startup AND persist to it as clients come and go, but the init container runs as root and would otherwise leave a file the broker can neither read nor rewrite. This is the ownership question ADR 0052 left for the lab to settle. It seeds only when the file is absent, so what the running plugin grows is never clobbered (issue 035). Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
48 lines
3.1 KiB
TypeScript
48 lines
3.1 KiB
TypeScript
// mosquitto's run-once bootstrap — mosquitto's own code (novox/hq ADR 0039), run once before the
|
|
// broker first starts (ADR 0052). The Dynamic Security plugin refuses to bring the broker up unless
|
|
// `dynamic-security.json` already holds an admin client, and nothing in a reconcile loop ever seeds
|
|
// that file — a state declaration describes what should exist, not a step that runs. This is that
|
|
// step: it writes the seed offline, exactly once, and exits. The host runs it to completion and only
|
|
// then starts the broker container the manifest places after it.
|
|
//
|
|
// It runs in the module's own runtime image, under the module's own account, as `mesh-tools run`
|
|
// imports it — no broker connection, because seeding is an offline file operation and there is no
|
|
// broker to reach yet. `mosquitto_ctrl dynsec init` writes the file; nothing here talks to a server.
|
|
//
|
|
// **Two disciplines make the seed safe to live beside a file the plugin then grows:**
|
|
//
|
|
// - It writes only when the file is absent, and never reconciles it. Once the broker is up, the
|
|
// dynsec plugin owns that file and rewrites it on every client it creates; re-seeding would wipe
|
|
// every provisioned client (novox/hq 04-ISSUES/035). The host's completion marker keeps the step
|
|
// from re-running; this absent-check keeps the one pass it does run from clobbering.
|
|
// - It hands the file to the broker's user. The broker runs as uid 1883 and must both READ the
|
|
// seed at startup and PERSIST to it as clients come and go; this container runs as root and would
|
|
// otherwise leave a root-owned file the broker at 1883 can neither read (if 0600) nor rewrite.
|
|
// So after writing, it chowns the file to 1883:1883 and sets 0600 — the broker's to read and to
|
|
// grow, and no one else's. This is the ownership question ADR 0052 left for the lab to settle.
|
|
|
|
import { chownSync, chmodSync, existsSync } from "node:fs";
|
|
import { MosquittoClient } from "../client.js";
|
|
|
|
// The broker (eclipse-mosquitto) runs as this uid/gid; the seeded store must be its to read and
|
|
// rewrite. Overridable for a broker image that runs as a different user.
|
|
const BROKER_UID = Number(process.env.MESH_MQTT_BROKER_UID ?? "1883") || 1883;
|
|
const BROKER_GID = Number(process.env.MESH_MQTT_BROKER_GID ?? "1883") || 1883;
|
|
|
|
// The same path the broker's `plugin_opt_config_file` names, reached through the shared data volume.
|
|
const configFile = process.env.MESH_DYNSEC_FILE ?? "/mosquitto/data/dynamic-security.json";
|
|
|
|
if (existsSync(configFile)) {
|
|
// Already seeded — and possibly grown by the running plugin since. Leave it exactly as it is.
|
|
console.log(`[mosquitto:bootstrap] ${configFile} already exists; leaving it untouched`);
|
|
} else {
|
|
const mosquitto = MosquittoClient.fromEnv();
|
|
await mosquitto.initBootstrapFile(configFile);
|
|
// Hand the store to the broker's user so it can read the seed and persist to it (see the header).
|
|
chownSync(configFile, BROKER_UID, BROKER_GID);
|
|
chmodSync(configFile, 0o600);
|
|
console.log(
|
|
`[mosquitto:bootstrap] seeded ${configFile} with the dynsec admin client, owned by ${BROKER_UID}:${BROKER_GID}`,
|
|
);
|
|
}
|