From facd41806d0cfb85f22de5a526bcb4a8447a5cff Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 21 Sep 2026 22:16:10 +0200 Subject: [PATCH 1/5] Five modules keep their own secrets from the vault, under local names; route-proxy declares its bases gitea, umami, influxdb, icecast and mailu require a secret and keep each of theirs under a local name (novox/hq ADR 0094); the broker account stays their own. The route proxy's recipe starts FROM the bases its manifest declares (ADR 0097). --- modules/gitea/module.json | 11 +++++++---- modules/icecast/module.json | 13 ++++++++++--- modules/influxdb/module.json | 11 +++++++++-- modules/mailu/module.json | 13 ++++++++----- modules/route-proxy/Dockerfile | 6 ++++-- modules/route-proxy/module.json | 14 +++++++++++++- modules/umami/module.json | 11 +++++++---- 7 files changed, 58 insertions(+), 21 deletions(-) diff --git a/modules/gitea/module.json b/modules/gitea/module.json index cd11944..90f6eb2 100644 --- a/modules/gitea/module.json +++ b/modules/gitea/module.json @@ -3,7 +3,8 @@ "version": "1", "requires": [ "postgres-database", - "route" + "route", + "secret" ], "contributes": { "postgres-database": { @@ -19,7 +20,11 @@ "route": "/var/lib/gitea/route.json" }, "secrets": { - "postgres-database": "/var/lib/gitea/database.secret" + "postgres-database": "/var/lib/gitea/database.secret", + "secret": { + "internal-token": "/var/lib/gitea/internal-token.secret", + "admin": "/var/lib/gitea/admin.secret" + } }, "capabilities": [ "container-runtime" @@ -57,8 +62,6 @@ "package-registry": "/var/lib/gitea/grants" }, "own-secrets": { - "internal-token": "/var/lib/gitea/internal-token.secret", - "admin": "/var/lib/gitea/admin.secret", "broker": "/var/lib/mesh/gitea/broker" }, "resources": [ diff --git a/modules/icecast/module.json b/modules/icecast/module.json index 82f1ba3..cdbef64 100644 --- a/modules/icecast/module.json +++ b/modules/icecast/module.json @@ -9,9 +9,6 @@ "module.icecast.stream.stopped" ], "own-secrets": { - "source": "/var/lib/icecast-module/source.secret", - "admin": "/var/lib/icecast-module/admin.secret", - "relay": "/var/lib/icecast-module/relay.secret", "broker": "/var/lib/mesh/icecast/broker" }, "listens": [ @@ -103,5 +100,15 @@ "from": "Dockerfile" } ] + }, + "requires": [ + "secret" + ], + "secrets": { + "secret": { + "source": "/var/lib/icecast-module/source.secret", + "admin": "/var/lib/icecast-module/admin.secret", + "relay": "/var/lib/icecast-module/relay.secret" + } } } diff --git a/modules/influxdb/module.json b/modules/influxdb/module.json index 6102067..54d4a93 100644 --- a/modules/influxdb/module.json +++ b/modules/influxdb/module.json @@ -5,8 +5,6 @@ "container-runtime" ], "own-secrets": { - "admin": "/var/lib/influxdb-module/admin.secret", - "admin-token": "/var/lib/influxdb-module/admin-token.secret", "broker": "/var/lib/mesh/influxdb/broker" }, "listens": [ @@ -118,5 +116,14 @@ "from": "Dockerfile" } ] + }, + "requires": [ + "secret" + ], + "secrets": { + "secret": { + "admin": "/var/lib/influxdb-module/admin.secret", + "admin-token": "/var/lib/influxdb-module/admin-token.secret" + } } } diff --git a/modules/mailu/module.json b/modules/mailu/module.json index ee67fbb..9a7239c 100644 --- a/modules/mailu/module.json +++ b/modules/mailu/module.json @@ -6,7 +6,8 @@ ], "requires": [ "postgres-database", - "route" + "route", + "secret" ], "contributes": { "postgres-database": { @@ -22,7 +23,12 @@ "route": "/var/lib/mailu/route.json" }, "secrets": { - "postgres-database": "/var/lib/mailu/database.secret" + "postgres-database": "/var/lib/mailu/database.secret", + "secret": { + "secret-key": "/var/lib/mailu/secret-key.secret", + "admin": "/var/lib/mailu/admin.secret", + "api-token": "/var/lib/mailu/api-token.secret" + } }, "emits": [ "module.mailu.user.created", @@ -67,9 +73,6 @@ } ], "own-secrets": { - "secret-key": "/var/lib/mailu/secret-key.secret", - "admin": "/var/lib/mailu/admin.secret", - "api-token": "/var/lib/mailu/api-token.secret", "broker": "/var/lib/mesh/mailu/broker" }, "resources": [ diff --git a/modules/route-proxy/Dockerfile b/modules/route-proxy/Dockerfile index a22fa35..45593a7 100644 --- a/modules/route-proxy/Dockerfile +++ b/modules/route-proxy/Dockerfile @@ -1,3 +1,5 @@ +ARG ALPINE_BASE=alpine:3.20 +ARG GO_BASE=golang:1.25 # The route-proxy module's runtime image: the reference reverse proxy compiled into a container. # # **The proxy source is not vendored here.** The canonical proxy — the contract written as something @@ -10,7 +12,7 @@ # # The mesh pins the digest of what this produces; the committed module.json carries the placeholder # digest every mesh-built image does, replaced at publish. -FROM golang:1.25 AS build +FROM ${GO_BASE} AS build WORKDIR /src COPY go.mod go.sum ./ RUN go mod download @@ -19,7 +21,7 @@ RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -o /mesh-route-proxy ./examples/ # A small runtime with the public CA roots the ACME client needs to reach a real authority, and run # as root so it can bind :80 and :443 — the two privileged ports a public front door listens on. -FROM alpine:3.20 +FROM ${ALPINE_BASE} RUN apk add --no-cache ca-certificates COPY --from=build /mesh-route-proxy /usr/local/bin/mesh-route-proxy ENTRYPOINT ["/usr/local/bin/mesh-route-proxy"] diff --git a/modules/route-proxy/module.json b/modules/route-proxy/module.json index d0f73f5..7f944ee 100644 --- a/modules/route-proxy/module.json +++ b/modules/route-proxy/module.json @@ -98,5 +98,17 @@ "ACME_CA_BUNDLE": "/ca/root.crt" } } - ] + ], + "build": { + "on": [ + { + "arg": "GO_BASE", + "image": "golang@sha256:699337d620559a59b4a2bb298ad59611e535d2ee755a34cf2d2a98f37578dc80" + }, + { + "arg": "ALPINE_BASE", + "image": "alpine@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc" + } + ] + } } diff --git a/modules/umami/module.json b/modules/umami/module.json index 8e1a1f7..a6025d2 100644 --- a/modules/umami/module.json +++ b/modules/umami/module.json @@ -6,7 +6,8 @@ ], "requires": [ "postgres-database", - "route" + "route", + "secret" ], "contributes": { "postgres-database": { @@ -22,7 +23,11 @@ "route": "/var/lib/umami/route.json" }, "secrets": { - "postgres-database": "/var/lib/umami/database.secret" + "postgres-database": "/var/lib/umami/database.secret", + "secret": { + "app-secret": "/var/lib/umami/app.secret", + "admin": "/var/lib/umami/admin.secret" + } }, "provides": [ { @@ -40,8 +45,6 @@ "analytics": "/var/lib/umami/grants" }, "own-secrets": { - "app-secret": "/var/lib/umami/app.secret", - "admin": "/var/lib/umami/admin.secret", "broker": "/var/lib/mesh/umami/broker" }, "listens": [ -- 2.54.0 From 82256fcdb0e10debb27229b046f3f19342aa1384 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 21 Sep 2026 22:24:43 +0200 Subject: [PATCH 2/5] The builder runs on the machine's network: it copies images into the mesh's registry itself now The copy between registries (ADR 0096) reaches the mesh's registry over HTTP from inside the builder's container, where loopback on the default bridge is not the machine; docker push never noticed because it went through the machine's daemon. The builder already holds the runtime's socket, so the host network adds nothing it did not have. --- modules/builder/module.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/builder/module.json b/modules/builder/module.json index 18e8347..e7fa8dd 100644 --- a/modules/builder/module.json +++ b/modules/builder/module.json @@ -64,7 +64,8 @@ "builder-env", "package-binding", "needs-npm-password" - ] + ], + "network": "host" } ] } -- 2.54.0 From 28b8feebfca8a26a27827287d01a5f5b7cfd8858 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 21 Sep 2026 22:26:28 +0200 Subject: [PATCH 3/5] The authority makes its own root at first start, and the proxy fetches it through a gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit step-ca's root certificate, its key and that key's password were own secrets — random bytes the mesh minted, which no certificate is (novox/hq 04-ISSUES/076). The mesh mints only the CA password now; step-ca makes its root at first start and serves it at /roots.pem, which the manifest now names beside the ACME directory. The route proxy fetches that root over the mesh network in a run-once step before it starts, instead of being handed a served fact that could only be written before anything ran (ADR 0098). --- modules/route-proxy/module.json | 28 +++++++++++++++++-------- modules/step-ca/module.json | 36 +++------------------------------ 2 files changed, 23 insertions(+), 41 deletions(-) diff --git a/modules/route-proxy/module.json b/modules/route-proxy/module.json index 7f944ee..1e615e5 100644 --- a/modules/route-proxy/module.json +++ b/modules/route-proxy/module.json @@ -62,19 +62,31 @@ "path": "/var/lib/route-proxy/ca", "mode": "0755" }, - { - "id": "ca-bundle", - "type": "file", - "path": "/var/lib/route-proxy/ca/root.crt", - "mode": "0644", - "content": "${bound:acme-ca:root}\n" - }, { "id": "acme-env", "type": "file", "path": "/var/lib/route-proxy/acme.env", "mode": "0600", - "content": "ACME_DIRECTORY=https://${bound:acme-ca:at}:${bound:acme-ca:port}${bound:acme-ca:path}\n" + "content": "ACME_DIRECTORY=https://${bound:acme-ca:at}:${bound:acme-ca:port}${bound:acme-ca:path}\nACME_ROOTS=https://${bound:acme-ca:at}:${bound:acme-ca:port}${bound:acme-ca:roots}\n" + }, + { + "id": "trust", + "type": "container", + "name": "route-proxy-trust", + "run-once": true, + "image": "alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b", + "network": "host", + "env-file": [ + "/var/lib/route-proxy/acme.env" + ], + "volumes": [ + "/var/lib/route-proxy/ca:/ca" + ], + "args": [ + "sh", + "-c", + "wget -q --no-check-certificate -O /ca/root.crt \"$ACME_ROOTS\" && test -s /ca/root.crt" + ] }, { "id": "server", diff --git a/modules/step-ca/module.json b/modules/step-ca/module.json index b5fdffd..31cc53a 100644 --- a/modules/step-ca/module.json +++ b/modules/step-ca/module.json @@ -13,7 +13,7 @@ "serves": { "acme-ca": { "path": "/acme/acme/directory", - "root": "" + "roots": "/roots.pem" } }, "listens": [ @@ -25,10 +25,7 @@ } ], "own-secrets": { - "password": "/var/lib/mesh/step-ca/password", - "root-cert": "/var/lib/mesh/step-ca/root-cert", - "root-key": "/var/lib/mesh/step-ca/root-key", - "root-key-password": "/var/lib/mesh/step-ca/root-key-password" + "password": "/var/lib/mesh/step-ca/password" }, "resources": [ { @@ -59,30 +56,6 @@ "mode": "0600", "content": "DOCKER_STEPCA_INIT_PASSWORD=${secret:password}\nDOCKER_STEPCA_INIT_DNS_NAMES=${machine:at},${machine:name},localhost,127.0.0.1\n" }, - { - "id": "root-cert-file", - "type": "file", - "path": "/var/lib/mesh/step-ca/root-cert.pem", - "mode": "0600", - "owner": "1000:1000", - "content": "${secret:root-cert}" - }, - { - "id": "root-key-file", - "type": "file", - "path": "/var/lib/mesh/step-ca/root-key.pem", - "mode": "0600", - "owner": "1000:1000", - "content": "${secret:root-key}" - }, - { - "id": "root-key-password-file", - "type": "file", - "path": "/var/lib/mesh/step-ca/root-key-password.txt", - "mode": "0600", - "owner": "1000:1000", - "content": "${secret:root-key-password}" - }, { "id": "server", "type": "container", @@ -95,10 +68,7 @@ "env": { "DOCKER_STEPCA_INIT_NAME": "Mesh Internal CA", "DOCKER_STEPCA_INIT_ACME": "true", - "DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT": "false", - "DOCKER_STEPCA_INIT_ROOT_FILE": "/run/mesh/root-cert.pem", - "DOCKER_STEPCA_INIT_KEY_FILE": "/run/mesh/root-key.pem", - "DOCKER_STEPCA_INIT_KEY_PASSWORD_FILE": "/run/mesh/root-key-password.txt" + "DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT": "false" }, "volumes": [ "/var/lib/step-ca:/home/step", -- 2.54.0 From 1c964cd571ba733630ced5103c47351a49ce9b19 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 21 Sep 2026 22:53:29 +0200 Subject: [PATCH 4/5] route-proxy: the trust gate retries with a timeout and checks for a certificate; its images are declared artifacts (ADRs 0096, 0097, 0098) --- modules/route-proxy/module.json | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/modules/route-proxy/module.json b/modules/route-proxy/module.json index 1e615e5..93be06e 100644 --- a/modules/route-proxy/module.json +++ b/modules/route-proxy/module.json @@ -74,7 +74,6 @@ "type": "container", "name": "route-proxy-trust", "run-once": true, - "image": "alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b", "network": "host", "env-file": [ "/var/lib/route-proxy/acme.env" @@ -85,7 +84,7 @@ "args": [ "sh", "-c", - "wget -q --no-check-certificate -O /ca/root.crt \"$ACME_ROOTS\" && test -s /ca/root.crt" + "for i in $(seq 1 60); do wget -q -T 10 --no-check-certificate -O /ca/root.crt \"$ACME_ROOTS\" && grep -q 'BEGIN CERTIFICATE' /ca/root.crt && exit 0; sleep 2; done; echo \"the authority at $ACME_ROOTS did not serve its roots within two minutes\" >&2; exit 1" ] }, { @@ -112,6 +111,18 @@ } ], "build": { + "artifacts": [ + { + "name": "server", + "kind": "image", + "from": "Dockerfile" + }, + { + "name": "trust", + "kind": "upstream", + "from": "alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b" + } + ], "on": [ { "arg": "GO_BASE", -- 2.54.0 From ac651c7ac26d401a2cc32d27300872c2b505be59 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 21 Sep 2026 22:55:35 +0200 Subject: [PATCH 5/5] route-proxy: the trust container names its artifact --- modules/route-proxy/module.json | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/route-proxy/module.json b/modules/route-proxy/module.json index 93be06e..c04555d 100644 --- a/modules/route-proxy/module.json +++ b/modules/route-proxy/module.json @@ -73,6 +73,7 @@ "id": "trust", "type": "container", "name": "route-proxy-trust", + "artifact": "trust", "run-once": true, "network": "host", "env-file": [ -- 2.54.0