Compare commits

..
Author SHA1 Message Date
jschoubben 76443ec06d postgres: its server container is not named after the seat
The module's server container was called mesh-store and its data directory
/var/lib/mesh-store — the seat's name reused for the module's own resources,
a leftover from the first migration. On a machine whose postgres holds no
seat (ace, as a database provider only) that produced a container called
mesh-store holding nothing of the kind. The container is now named
postgres. The data directory keeps its path: a path change recreates a
running container on an empty directory (hq 126), and novox's store lives
there.

Rolling this out recreates novox's store container once (a restart on its
bind mount, no data moves). The two catalogue-test failures on this branch
(resolver_manifests_test) fail identically on main today.
2026-09-30 14:56:31 +02:00
jschoubben 7ab522ba31 Merge pull request 'dnsmasq: answer a container's query, which arrives on the runtime's bridge' (#176) from fix/110-the-resolver-answers-a-container into main 2026-09-30 12:31:30 +00:00
jschoubben 204bfbbaf9 dnsmasq: answer a container's query, which arrives on the runtime's bridge
dnsmasq admits a query by the interface it arrives on when told interface=,
and by the address it is sent to when told listen-address=. A container's
query is sent to the machine's private address but arrives on docker0, so
interface=mesh0 dropped it silently on every machine (novox/hq issue 110).
Name the address, not the interface.
2026-09-30 14:31:26 +02:00
jschoubben e0c09f46b6 Merge pull request 'dnsmasq: the runtime is reloaded when its dns file changes, and may then be restarted safely' (#175) from fix/110-the-runtime-reads-its-dns into main 2026-09-30 12:22:10 +00:00
jschoubben e0faf012be dnsmasq: the runtime is reloaded when its dns file changes, and may then be restarted safely
novox/hq 04-ISSUES/110. The module writes the runtime's `dns` key and
deliberately ordered no restart, because a restart stops every container.
It also ordered no reload, and the key holds only for containers created
after the runtime next starts. On two of four machines the runtime
predated the file — one since August — so every container there was
handed a public resolver and no mesh name resolved, while everything read
as fine. The third machine works only because its runtime happened to
restart later.

The file now also sets live-restore, which the runtime reads on a reload,
and the module declares the runtime reloaded when the file changes (ADR
0102: reload, don't restart). A reload still does not make `dns` take
effect; what it does is make the one restart that key needs keep every
container running. That restart stays the operator's, once per machine,
and is harmless from the second time on.

The mesh restarts nothing here. Undeclared, the runtime's unit goes back
to the state it was found in (ADR 0118).
2026-09-30 14:22:03 +02:00
mesh-admin 1b19c79d63 Merge pull request 'postgres: the provisioner dials the port the mesh gave the store' (#174) from fix/postgres-provisioner-dials-the-port-it-was-given into main 2026-09-30 12:06:25 +00:00
jschoubben 4ec2ae1f7f postgres: the provisioner dials the port the mesh gave the store
MESH_PROVISION_POSTGRES named 127.0.0.1:5432 literally; the seat twin
(${seat:mesh-store:5432}) corrected it only on the machine holding the
mesh-store seat. On any other machine — ace, where the module provides
postgres-database without the seat — the twin is empty and the sidecar
would have dialled whatever else holds 5432 (HAL's postgres). ${port:5432}
is the machine port on every node; on novox the twin still says the same
6852, so nothing moves there.
2026-09-30 14:01:53 +02:00
4 changed files with 28 additions and 71 deletions
File diff suppressed because one or more lines are too long
+2 -20
View File
@@ -48,20 +48,6 @@ function meshConfig(file?: string): Record<string, string> {
catch { return {}; }
}
/** One key of nzbget's own nzbget.conf, from the config directory the mesh mounts read-only
* (MESH_NZBGET_CONFIG_DIR). The software's file is the truth about who may log in, so the tools
* ask it rather than a setting that could disagree. Absent, unreadable or unset yields undefined. */
function confValue(dir: string | undefined, key: string): string | undefined {
if (!dir) return undefined;
try {
const line = readFileSync(`${dir.replace(/\/$/, "")}/nzbget.conf`, "utf8")
.split("\n")
.find((l) => l.startsWith(`${key}=`));
const value = line?.slice(key.length + 1).trim();
return value ? value : undefined;
} catch { return undefined; }
}
/** Read a secret the mesh mounted at a file path (an own-secret delivered by `secret accept`);
* absent or unreadable yields undefined so callers fall back rather than crash. */
function readSecret(file?: string): string | undefined {
@@ -83,9 +69,7 @@ export class NzbgetClient {
* Build from the module's resolved environment. URL and password are read from MESH_NZBGET_URL
* and MESH_NZBGET_PASSWORD; both must be present — an unconfigured NZBGet throws rather than
* pretend to be reachable, so the tools/events simply do not load (the harness treats the throw
* as "exposes nothing"). The password file is the same own-secret the server container is started
* with (FILE__NZBGET_PASS), so the two cannot disagree. The control username is read from
* nzbget.conf, falling back to "nzbget", NZBGet's own default.
* as "exposes nothing"). The control username defaults to "nzbget", NZBGet's own default.
*/
static fromEnv(env: NodeJS.ProcessEnv = process.env): NzbgetClient {
const cfg = meshConfig(env.MESH_NZBGET_CONFIG_FILE);
@@ -94,9 +78,7 @@ export class NzbgetClient {
if (!url || !password) {
throw new Error("NZBGet not configured — set MESH_NZBGET_URL and MESH_NZBGET_PASSWORD");
}
// The control username: a setting or the environment if one says so, else whatever
// nzbget.conf holds (an adopted machine keeps its own, e.g. not "nzbget"), else NZBGet's default.
const user = cfg.user ?? env.MESH_NZBGET_USER ?? confValue(env.MESH_NZBGET_CONFIG_DIR, "ControlUsername") ?? "nzbget";
const user = cfg.user ?? env.MESH_NZBGET_USER ?? "nzbget";
return new NzbgetClient(url, user, password);
}
+11 -45
View File
@@ -19,7 +19,7 @@
"port": 6789,
"protocol": "tcp",
"from": "mesh",
"why": "the download client's pages, and the JSON-RPC API its consumers and its own tools call"
"why": "the download client's pages"
}
],
"accesses": [
@@ -35,15 +35,10 @@
"path": "/var/lib/mesh/nzbget",
"mode": "0700"
},
{
"id": "state",
"type": "directory",
"mode": "0700",
"place": "."
},
{
"id": "config",
"type": "directory",
"path": "/services/nzbget/config",
"mode": "0700",
"owner": "1000:1000"
},
@@ -51,29 +46,24 @@
"id": "server",
"type": "container",
"name": "nzbget",
"image": "lscr.io/linuxserver/nzbget@sha256:ec3ef0ae7dc410084086a7fdd447deda4747531154aa1cc5c7c48ac60794e277",
"image": "lscr.io/linuxserver/nzbget@sha256:5f3d3fa71029004156eff2cbf4ef4455ce4ce59517cf13fa7d1d7c8a4cd2c8a4",
"env": {
"PUID": "1000",
"PGID": "1000",
"TZ": "Etc/UTC",
"FILE__NZBGET_PASS": "/run/secrets/password"
"TZ": "Etc/UTC"
},
"ports": [
"6789"
],
"volumes": [
"${dir:config}:/config",
"/services/media/downloads:/downloads",
"/var/lib/mesh/nzbget/password:/run/secrets/password:ro"
],
"restart-on": [
"needs-password"
"/services/nzbget/config:/config",
"/services/media/downloads:/downloads"
]
},
{
"id": "runtime-config",
"type": "file",
"path": "${dir:state}/config.json",
"path": "/var/lib/mesh/nzbget/config.json",
"mode": "0600",
"content": "{}\n",
"merge": "json"
@@ -86,46 +76,22 @@
"volumes": [
"/var/lib/mesh/nzbget/broker:/run/secrets/broker:ro",
"/var/lib/mesh/nzbget/password:/run/secrets/password:ro",
"${dir:state}/config.json:/run/config/config.json:ro",
"${dir:config}:/var/lib/nzbget/config:ro"
"/var/lib/mesh/nzbget/config.json:/run/config/config.json:ro",
"/services/nzbget/config:/var/lib/nzbget/config:ro"
],
"env": {
"MESH_BROKER_FILE": "/run/secrets/broker",
"MESH_NZBGET_URL": "http://127.0.0.1:${port:6789}",
"MESH_NZBGET_URL": "http://127.0.0.1:6789",
"MESH_NZBGET_PASSWORD_FILE": "/run/secrets/password",
"MESH_NZBGET_CONFIG_FILE": "/run/config/config.json",
"MESH_NZBGET_CONFIG_DIR": "/var/lib/nzbget/config"
},
"restart-on": [
"runtime-config",
"needs-password"
"runtime-config"
],
"artifact": "runtime"
}
],
"provides": [
"nzbget-api"
],
"serves": {
"nzbget-api": {
"scheme": "http",
"port": 6789,
"url-base": "",
"username": "nzbget"
}
},
"requires": [
"route"
],
"contributes": {
"route": {
"label": "nzbget",
"endpoint": "web"
}
},
"binds": {
"route": "${dir:state}/route.json"
},
"build": {
"on": [
{
+2 -2
View File
@@ -80,7 +80,7 @@
{
"id": "server",
"type": "container",
"name": "mesh-store",
"name": "postgres",
"image": "pgvector/pgvector@sha256:cf134a767f474095eeba57e0117be8e568e011a63f33fbf252f14c9b760f8e6f",
"env": {
"POSTGRES_PASSWORD_FILE": "/run/secrets/superuser",
@@ -105,7 +105,7 @@
"/var/lib/postgres/superuser.secret:/run/secrets/superuser:ro"
],
"env": {
"MESH_PROVISION_POSTGRES": "postgres://postgres@127.0.0.1:5432/postgres?sslmode=disable",
"MESH_PROVISION_POSTGRES": "postgres://postgres@127.0.0.1:${port:5432}/postgres?sslmode=disable",
"MESH_PROVISION_POSTGRES_PORT": "${seat:mesh-store:5432}",
"MESH_PROVISION_PASSWORD_FILE": "/run/secrets/superuser",
"MESH_BROKER_FILE": "/run/secrets/broker",