From 088022d63b8f0b09b9492a73515c461d7b411a89 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 24 Sep 2026 15:24:07 +0200 Subject: [PATCH 1/2] postgres: the provisioner connects to mesh-store's actual port, not a hardcoded 5432 MESH_PROVISION_POSTGRES was a literal connection string naming port 5432 -- correct only when mesh-store happens to run on the mesh's own default. On novox, mesh-store was adopted in place at HAL's original port (6852), and the provisioner has been retrying-and-failing against 127.0.0.1:5432 ever since, for every consumer including ones that already exist (gitea, umami, mesh-catalog), not just a new grant. Fixed with the same ${seat:mesh-store:5432} template mesh-controller's own manifest already uses for the identical connection. No other module needed this fix checked -- lavinmq's MESH_PROVISION_LAVINMQ already used 127.0.0.1:15672 unconditionally, but the broker's management port is fixed by the module itself (127.0.0.1:15672 in lavinmq/module.json's own ports), not by adoption, so it isn't the same bug. --- modules/postgres/module.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/postgres/module.json b/modules/postgres/module.json index 56dc81b..fcc2c2c 100644 --- a/modules/postgres/module.json +++ b/modules/postgres/module.json @@ -97,7 +97,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:${seat:mesh-store:5432}/postgres?sslmode=disable", "MESH_PROVISION_PASSWORD_FILE": "/run/secrets/superuser", "MESH_BROKER_FILE": "/run/secrets/broker", "MESH_RECEIVES": "/var/lib/postgres/grants/mesh.json" From 135101ce6e7b1906a2ac08d316e6f4b8a9000460 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 24 Sep 2026 15:27:16 +0200 Subject: [PATCH 2/2] =?UTF-8?q?postgres:=20fix=20the=20port=20override=20p?= =?UTF-8?q?roperly=20=E2=80=94=20a=20twin=20variable,=20not=20a=20raw=20su?= =?UTF-8?q?bstitution?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first commit on this branch embedded ${seat:mesh-store:5432} directly inside MESH_PROVISION_POSTGRES's URL. That placeholder resolves to an empty string whenever mesh-store is on its own default port (novox/hq internal/catalogue/seat_into.go: 'the mesh raised on the catalogue's own ports never gives them a setting at all') -- which produces a malformed connection string (host:/postgres) on exactly the common case, a fresh, non-adopted mesh. It only worked here because novox's mesh-store happens to be adopted at a non-default port. mesh-controller's own manifest already has the right shape for this -- internal/envfile/port.go's NAME / NAME_PORT twin, composed by mesh-controller's Placed(): the base value keeps its own port; a separate _PORT variable carries the override, spliced in only when it says something, and left alone -- not a fault -- when it's an unfilled placeholder (a manifest ahead of the running controller). Reverted the manifest to its original base value, added MESH_PROVISION_POSTGRES_PORT as the twin, and taught client.ts (the only consumer -- both tools/ and provisioner/ import it) the same precedence envfile.Placed uses. Checked: no other file in the module reads MESH_PROVISION_POSTGRES directly. --- modules/postgres/client.ts | 11 ++++++++++- modules/postgres/module.json | 3 ++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/postgres/client.ts b/modules/postgres/client.ts index 7582494..fa60b1b 100644 --- a/modules/postgres/client.ts +++ b/modules/postgres/client.ts @@ -39,7 +39,16 @@ export class PostgresClient { static fromEnv(env: NodeJS.ProcessEnv = process.env): PostgresClient { const url = env.MESH_PROVISION_POSTGRES ? safeUrl(env.MESH_PROVISION_POSTGRES) : undefined; const host = env.MESH_POSTGRES_HOST ?? url?.hostname; - const port = Number(env.MESH_POSTGRES_PORT ?? url?.port ?? "5432") || 5432; + // MESH_PROVISION_POSTGRES_PORT is the seat's twin (mesh-controller's own + // internal/envfile.Placed pattern): which port this machine actually put mesh-store at, when + // that differs from what the connection string above already says — e.g. adopted in place at + // a predecessor's port. Empty means the mesh has nothing to add and the string's own port + // stands; a placeholder the mesh never filled (a manifest ahead of the running controller) + // is treated the same way, not as a fault. + const seatPort = (env.MESH_PROVISION_POSTGRES_PORT ?? "").trim(); + const filledSeatPort = seatPort && !/^\$\{[^}]*\}$/.test(seatPort) ? seatPort : undefined; + const portSource = env.MESH_POSTGRES_PORT ?? filledSeatPort ?? url?.port ?? "5432"; + const port = Number(portSource) || 5432; const user = env.MESH_POSTGRES_USER ?? url?.username ?? "postgres"; const password = env.MESH_POSTGRES_PASSWORD ?? readSecretFile(env.MESH_PROVISION_PASSWORD_FILE); if (!host || !password) { diff --git a/modules/postgres/module.json b/modules/postgres/module.json index fcc2c2c..2b65929 100644 --- a/modules/postgres/module.json +++ b/modules/postgres/module.json @@ -97,7 +97,8 @@ "/var/lib/postgres/superuser.secret:/run/secrets/superuser:ro" ], "env": { - "MESH_PROVISION_POSTGRES": "postgres://postgres@127.0.0.1:${seat:mesh-store:5432}/postgres?sslmode=disable", + "MESH_PROVISION_POSTGRES": "postgres://postgres@127.0.0.1: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", "MESH_RECEIVES": "/var/lib/postgres/grants/mesh.json"