From 7563569c8a118c5efee876adc3f7ab257f9dcab6 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 23:33:50 +0200 Subject: [PATCH 01/13] postgres: its handlers, tools and provisioner run in the node's runtime (hq ADR 0198) The mesh-postgres container goes with its Dockerfile, build bases and bus credential: its three entrypoints are loads of one bundle, given their words as host paths, and psql comes from postgresql-libs instead of the image's apt layer. The seat word is dropped, since a bundle's words cannot carry one and the client treats it as optional. --- modules/postgres/Dockerfile | 41 ----------------------- modules/postgres/module.json | 63 +++++++++++++----------------------- 2 files changed, 22 insertions(+), 82 deletions(-) delete mode 100644 modules/postgres/Dockerfile diff --git a/modules/postgres/Dockerfile b/modules/postgres/Dockerfile deleted file mode 100644 index 818ada5..0000000 --- a/modules/postgres/Dockerfile +++ /dev/null @@ -1,41 +0,0 @@ -# postgres's runtime: the tool runtime, carrying this module's compiled provisioner, tools and -# event consumer. -# -# **Built from this module's own directory and nothing else.** The sdk is in the base image, so -# nothing is copied out of a neighbouring checkout — which is what lets the mesh build this from a -# repository and a path (novox/hq ADR 0069) rather than only on a workstation that happens to have -# the siblings. -# -# Two bases, named rather than pinned: the image this is COMPILED in, and the image it RUNS in. -# They are different images on purpose — the first carries a compiler and the second must not, or -# every running container would carry one it never invokes. The mesh answers both with the copies it -# holds, because a fingerprint written here would name one particular copy and no other mesh has it -# (novox/hq issue 044). Declared in module.json's `build.on`; deliberately no defaults, so a build -# nobody told stops here and says which module to build first. -ARG BUILD_BASE -ARG RUNTIME_BASE - -FROM ${BUILD_BASE} AS build -# Compiled under /app/modules so `@novox/mesh-sdk` resolves upward into the base's own -# node_modules — the module is compiled against exactly the sdk it will run against. -WORKDIR /app/modules/postgres -COPY . . -# The compiler is invoked by its real path rather than through node_modules/.bin, whose entries are -# symlinks to a launcher that requires its library relatively — resolved away when the base image -# was assembled. -RUN node /app/node_modules/typescript/bin/tsc client.ts index.ts provisioner/index.ts tools/index.ts \ - --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist - -FROM ${RUNTIME_BASE} -# **This module talks to its database through psql, so psql has to be here.** The client is how -# postgres's provisioner runs DDL — it does not carry a driver — and the runtime base holds only -# what every module needs. -RUN apt-get update \ - && apt-get install -y --no-install-recommends postgresql-client \ - && rm -rf /var/lib/apt/lists/* -COPY --from=build /app/modules/postgres/dist /app/modules/postgres/dist -# What a tool host should load from this module: its event consumer and its tools, which are -# separate entrypoints because they are loaded by different things. The provisioner is the third, -# and is not listed here — the declaration names it in the container's `args`, because it is what -# this module's own container runs. One image, because they are one module and share a client. -ENV MESH_TOOL_MODULES=/app/modules/postgres/dist/index.js,/app/modules/postgres/dist/tools/index.js,/app/modules/postgres/dist/provisioner/index.js diff --git a/modules/postgres/module.json b/modules/postgres/module.json index 98da700..f25d84d 100644 --- a/modules/postgres/module.json +++ b/modules/postgres/module.json @@ -53,16 +53,9 @@ }, "own-secrets": { "superuser": "${dir:state}/superuser.secret", - "broker": "${dir:mesh-state}/broker", "reader": "${dir:state}/reader.secret" }, "resources": [ - { - "id": "mesh-state", - "type": "directory", - "mode": "0700", - "place": "mesh" - }, { "id": "state", "type": "directory", @@ -99,45 +92,33 @@ ] }, { - "id": "runtime", - "type": "container", - "name": "mesh-postgres", - "network": "host", - "volumes": [ - "${dir:mesh-state}/broker:/run/secrets/broker:ro", - "${dir:grants}:${dir:grants}:ro", - "${dir:state}/superuser.secret:/run/secrets/superuser:ro", - "${dir:state}/reader.secret:/run/secrets/reader:ro" - ], - "env": { - "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", - "MESH_RECEIVES": "${dir:grants}/mesh.json", - "MESH_POSTGRES_READER_PASSWORD_FILE": "/run/secrets/reader" - }, - "artifact": "runtime" + "id": "client", + "type": "package", + "package": "postgresql-libs" } ], "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": [ + "index.js", + "tools/index.js", + "provisioner/index.js" + ], + "loads": [ + "index.js", + "tools/index.js", + "provisioner/index.js" + ], + "env": { + "MESH_PROVISION_POSTGRES": "postgres://postgres@127.0.0.1:${port:5432}/postgres?sslmode=disable", + "MESH_PROVISION_PASSWORD_FILE": "${dir:state}/superuser.secret", + "MESH_RECEIVES": "${dir:grants}/mesh.json", + "MESH_POSTGRES_READER_PASSWORD_FILE": "${dir:state}/reader.secret" + } } ] } From 1b27ce319a3099648485cc3eda11734475ad068f Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 23:33:50 +0200 Subject: [PATCH 02/13] redis: its handlers, tools and provisioner run in the node's runtime (hq ADR 0198) The mesh-redis container goes with its Dockerfile, build bases, bus credential and the state directory only that credential lived in. The bundle reaches redis on the port this machine published rather than the container network's name. --- modules/redis/Dockerfile | 30 -------------------- modules/redis/module.json | 60 ++++++++++++--------------------------- 2 files changed, 18 insertions(+), 72 deletions(-) delete mode 100644 modules/redis/Dockerfile diff --git a/modules/redis/Dockerfile b/modules/redis/Dockerfile deleted file mode 100644 index 2338a69..0000000 --- a/modules/redis/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -# redis's runtime: the tool runtime, carrying this module's compiled code. -# -# **Built from this module's own directory and nothing else.** The sdk and the tool runtime are in -# the base images, published like any other artifact — which is what makes this buildable by the -# mesh from a repository and a path (novox/hq ADR 0069) rather than only on a workstation that -# happens to have the siblings. -# -# Two bases, named rather than pinned (novox/hq issue 044): the image this is COMPILED in and the -# image it RUNS in — the second must not carry a compiler. Declared in module.json's `build.on`. -ARG BUILD_BASE -ARG RUNTIME_BASE - -FROM ${BUILD_BASE} AS build -# Compiled under /app/modules so `@novox/mesh-sdk` resolves upward into the base's own -# node_modules — the module is compiled against exactly the sdk it will run against. The compiler -# is invoked by its real path: node_modules/.bin entries are launcher symlinks the base image -# resolved away. -WORKDIR /app/modules/redis -COPY . . -RUN node /app/node_modules/typescript/bin/tsc client.ts index.ts tools/index.ts provisioner/index.ts \ - --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist - -FROM ${RUNTIME_BASE} -COPY --from=build /app/modules/redis/dist /app/modules/redis/dist -# Every serve-time entrypoint, loaded by the runtime in serve mode: tools and events serve, and a -# provider's provisioner runs its reconcile loop in the same process, with the broker connected — -# the convention novox/hq issues 060/061 settled. A container that instead ran only its -# provisioner (`run`) served no tools and emitted no events; a container that named no command -# ran no provisioner at all. -ENV MESH_TOOL_MODULES=/app/modules/redis/dist/index.js,/app/modules/redis/dist/tools/index.js,/app/modules/redis/dist/provisioner/index.js diff --git a/modules/redis/module.json b/modules/redis/module.json index 646a8df..0736848 100644 --- a/modules/redis/module.json +++ b/modules/redis/module.json @@ -35,9 +35,6 @@ "secrets": { "secret": "${dir:state}/default.secret" }, - "own-secrets": { - "broker": "${dir:mesh-state}/broker" - }, "listens": [ { "name": "cache", @@ -48,12 +45,6 @@ } ], "resources": [ - { - "id": "mesh-state", - "type": "directory", - "mode": "0700", - "place": "mesh" - }, { "id": "state", "type": "directory", @@ -103,44 +94,29 @@ "restart-on": [ "server-conf" ] - }, - { - "id": "runtime", - "type": "container", - "name": "mesh-redis", - "network": "redis", - "volumes": [ - "${dir:mesh-state}/broker:/run/secrets/broker:ro", - "${dir:grants}:/var/lib/redis-module/grants:ro", - "${dir:state}/default.secret:/run/secrets/default:ro" - ], - "env": { - "MESH_BROKER_FILE": "/run/secrets/broker", - "MESH_RECEIVES": "/var/lib/redis-module/grants/mesh.json", - "MESH_PROVISION_REDIS": "redis:6379", - "MESH_PROVISION_PASSWORD_FILE": "/run/secrets/default" - }, - "artifact": "runtime" } ], "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": [ + "index.js", + "tools/index.js", + "provisioner/index.js" + ], + "loads": [ + "index.js", + "tools/index.js", + "provisioner/index.js" + ], + "env": { + "MESH_RECEIVES": "${dir:grants}/mesh.json", + "MESH_PROVISION_REDIS": "127.0.0.1:${port:6379}", + "MESH_PROVISION_PASSWORD_FILE": "${dir:state}/default.secret" + } } ] } From 038a0a25ce845ec1934aedd06c0c1062220eae9f Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 23:33:50 +0200 Subject: [PATCH 03/13] mosquitto: the runtime serves its code, and its bootstrap is a run-once process (hq ADR 0198) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mesh-mosquitto container goes with its Dockerfile, build bases and bus credential. mosquitto_ctrl comes from the mosquitto package, and the bootstrap step runs node on the bundle, reading the broker's published port from an env-file the mesh fills, because a process's env is not given ${port:…}. --- modules/mosquitto/Dockerfile | 28 ----------- modules/mosquitto/module.json | 91 ++++++++++++++++------------------- 2 files changed, 42 insertions(+), 77 deletions(-) delete mode 100644 modules/mosquitto/Dockerfile diff --git a/modules/mosquitto/Dockerfile b/modules/mosquitto/Dockerfile deleted file mode 100644 index fddaa44..0000000 --- a/modules/mosquitto/Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -# mosquitto's runtime: the tool runtime, carrying this module's compiled code. -# -# **Built from this module's own directory and nothing else.** The sdk and the tool runtime are in -# the base images, published like any other artifact — which is what makes this buildable by the -# mesh from a repository and a path (novox/hq ADR 0069) rather than only on a workstation that -# happens to have the siblings. -# -# Two bases, named rather than pinned (novox/hq issue 044): the image this is COMPILED in and the -# image it RUNS in — the second must not carry a compiler. Declared in module.json's `build.on`. -ARG BUILD_BASE -ARG RUNTIME_BASE - -FROM ${BUILD_BASE} AS build -WORKDIR /app/modules/mosquitto -COPY . . -RUN node /app/node_modules/typescript/bin/tsc topics.ts client.ts index.ts tools/index.ts provisioner/index.ts bootstrap/index.ts \ - --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist - -FROM ${RUNTIME_BASE} -# mosquitto's client and bootstrap drive `mosquitto_ctrl`; the apt package carries it with its -# shared libraries — the musl binary from the eclipse image would not load on this glibc base. -RUN apt-get update && apt-get install -y --no-install-recommends mosquitto \ - && rm -rf /var/lib/apt/lists/* -COPY --from=build /app/modules/mosquitto/dist /app/modules/mosquitto/dist -# Every serve-time entrypoint, loaded by the runtime in serve mode: tools and events serve, and a -# provider's provisioner runs its reconcile loop in the same process, with the broker connected — -# the convention novox/hq issues 060/061 settled. -ENV MESH_TOOL_MODULES=/app/modules/mosquitto/dist/index.js,/app/modules/mosquitto/dist/tools/index.js,/app/modules/mosquitto/dist/provisioner/index.js diff --git a/modules/mosquitto/module.json b/modules/mosquitto/module.json index ed5390e..88be665 100644 --- a/modules/mosquitto/module.json +++ b/modules/mosquitto/module.json @@ -32,8 +32,7 @@ "mqtt-topic": "${dir:grants}" }, "own-secrets": { - "admin": "${dir:mesh-state}/admin", - "broker": "${dir:mesh-state}/broker" + "admin": "${dir:mesh-state}/admin" }, "listens": [ { @@ -88,26 +87,29 @@ "type": "network", "name": "mosquitto" }, + { + "id": "bootstrap-env", + "type": "file", + "path": "${dir:state}/bootstrap.env", + "mode": "0600", + "content": "MESH_PROVISION_MQTT=127.0.0.1:${port:1883}\nMESH_PROVISION_ADMIN_USER=mesh-admin\nMESH_PROVISION_PASSWORD_FILE=${dir:mesh-state}/admin\nMESH_DYNSEC_FILE=${dir:data}/dynamic-security.json\n" + }, { "id": "bootstrap", - "type": "container", + "type": "process", "name": "mosquitto-bootstrap", + "artifact": "code", + "run": [ + "node", + "bootstrap/index.js" + ], "run-once": true, - "volumes": [ - "${dir:data}:/mosquitto/data", - "${dir:mesh-state}/admin:/run/secrets/admin:ro" + "env-file": [ + "${dir:state}/bootstrap.env" ], - "env": { - "MESH_PROVISION_MQTT": "mosquitto:1883", - "MESH_PROVISION_ADMIN_USER": "mesh-admin", - "MESH_PROVISION_PASSWORD_FILE": "/run/secrets/admin", - "MESH_DYNSEC_FILE": "/mosquitto/data/dynamic-security.json" - }, - "args": [ - "run", - "/app/modules/mosquitto/dist/bootstrap/index.js" - ], - "artifact": "runtime" + "restart-on": [ + "bootstrap-env" + ] }, { "id": "server", @@ -125,43 +127,34 @@ ] }, { - "id": "runtime", - "type": "container", - "name": "mesh-mosquitto", - "network": "mosquitto", - "volumes": [ - "${dir:mesh-state}/broker:/run/secrets/broker:ro", - "${dir:grants}:${dir:grants}:ro", - "${dir:mesh-state}/admin:/run/secrets/admin:ro" - ], - "env": { - "MESH_BROKER_FILE": "/run/secrets/broker", - "MESH_RECEIVES": "${dir:grants}/mesh.json", - "MESH_PROVISION_MQTT": "mosquitto:1883", - "MESH_PROVISION_ADMIN_USER": "mesh-admin", - "MESH_PROVISION_PASSWORD_FILE": "/run/secrets/admin" - }, - "artifact": "runtime" + "id": "client", + "type": "package", + "package": "mosquitto" } ], "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": [ + "index.js", + "tools/index.js", + "provisioner/index.js", + "bootstrap/index.js" + ], + "loads": [ + "index.js", + "tools/index.js", + "provisioner/index.js" + ], + "env": { + "MESH_RECEIVES": "${dir:grants}/mesh.json", + "MESH_PROVISION_MQTT": "127.0.0.1:${port:1883}", + "MESH_PROVISION_ADMIN_USER": "mesh-admin", + "MESH_PROVISION_PASSWORD_FILE": "${dir:mesh-state}/admin" + } } ] } From 0cb67e856f70a5453369d892c4d3849b90583248 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 23:33:51 +0200 Subject: [PATCH 04/13] influxdb: its tools and provisioner run in the node's runtime (hq ADR 0198) The mesh-influxdb container goes with its Dockerfile, build bases and bus credential; its env becomes the bundle's words with mount targets folded back to host paths. --- modules/influxdb/Dockerfile | 24 ---------------- modules/influxdb/module.json | 56 +++++++++++------------------------- 2 files changed, 17 insertions(+), 63 deletions(-) delete mode 100644 modules/influxdb/Dockerfile diff --git a/modules/influxdb/Dockerfile b/modules/influxdb/Dockerfile deleted file mode 100644 index fb4b123..0000000 --- a/modules/influxdb/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -# influxdb's runtime: the tool runtime, carrying this module's compiled code. -# -# **Built from this module's own directory and nothing else.** The sdk and the tool runtime are in -# the base images, published like any other artifact — which is what makes this buildable by the -# mesh from a repository and a path (novox/hq ADR 0069) rather than only on a workstation that -# happens to have the siblings. -# -# Two bases, named rather than pinned (novox/hq issue 044): the image this is COMPILED in and the -# image it RUNS in — the second must not carry a compiler. Declared in module.json's `build.on`. -ARG BUILD_BASE -ARG RUNTIME_BASE - -FROM ${BUILD_BASE} AS build -WORKDIR /app/modules/influxdb -COPY . . -RUN node /app/node_modules/typescript/bin/tsc client.ts grants.ts provisioner/index.ts tools/index.ts \ - --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist - -FROM ${RUNTIME_BASE} -COPY --from=build /app/modules/influxdb/dist /app/modules/influxdb/dist -# Every serve-time entrypoint, loaded by the runtime in serve mode: tools and events serve, and a -# provider's provisioner runs its reconcile loop in the same process, with the broker connected — -# the convention novox/hq issues 060/061 settled. -ENV MESH_TOOL_MODULES=/app/modules/influxdb/dist/tools/index.js,/app/modules/influxdb/dist/provisioner/index.js diff --git a/modules/influxdb/module.json b/modules/influxdb/module.json index 3b8976c..0232510 100644 --- a/modules/influxdb/module.json +++ b/modules/influxdb/module.json @@ -11,7 +11,6 @@ "container-runtime" ], "own-secrets": { - "broker": "${dir:mesh-state}/broker", "admin": "${dir:state}/admin.secret", "admin-token": "${dir:state}/admin-token.secret" }, @@ -100,29 +99,6 @@ "mode": "0600", "content": "{}\n", "merge": "json" - }, - { - "id": "runtime", - "type": "container", - "name": "mesh-influxdb", - "network": "host", - "volumes": [ - "${dir:mesh-state}/broker:/run/secrets/broker:ro", - "${dir:mesh-state}/config.json:/run/config/config.json:ro", - "${dir:state}/admin-token.secret:/run/secrets/admin-token:ro", - "${dir:grants}:${dir:grants}:ro" - ], - "env": { - "MESH_BROKER_FILE": "/run/secrets/broker", - "MESH_INFLUXDB_URL": "http://127.0.0.1:${port:8086}", - "MESH_INFLUXDB_CONFIG_FILE": "/run/config/config.json", - "MESH_INFLUXDB_TOKEN_FILE": "/run/secrets/admin-token", - "MESH_RECEIVES": "${dir:grants}/mesh.json" - }, - "restart-on": [ - "runtime-config" - ], - "artifact": "runtime" } ], "requires": [ @@ -135,23 +111,25 @@ } }, "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", + "provisioner/index.js" + ], + "loads": [ + "tools/index.js", + "provisioner/index.js" + ], + "env": { + "MESH_INFLUXDB_URL": "http://127.0.0.1:${port:8086}", + "MESH_INFLUXDB_CONFIG_FILE": "${dir:mesh-state}/config.json", + "MESH_INFLUXDB_TOKEN_FILE": "${dir:state}/admin-token.secret", + "MESH_RECEIVES": "${dir:grants}/mesh.json" + } } ] } From 7440b8d0098d9b89a254f9d547815f5a2ffcf313 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 23:33:51 +0200 Subject: [PATCH 05/13] keycloak: its handlers, tools and provisioner run in the node's runtime (hq ADR 0198) The mesh-keycloak container goes with its Dockerfile, build bases and bus credential; its env becomes the bundle's words with mount targets folded back to host paths. --- modules/keycloak/Dockerfile | 30 ------------------ modules/keycloak/module.json | 60 ++++++++++++------------------------ 2 files changed, 20 insertions(+), 70 deletions(-) delete mode 100644 modules/keycloak/Dockerfile diff --git a/modules/keycloak/Dockerfile b/modules/keycloak/Dockerfile deleted file mode 100644 index 7a024a3..0000000 --- a/modules/keycloak/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -# keycloak's runtime: the tool runtime, carrying this module's compiled code. -# -# **Built from this module's own directory and nothing else.** The sdk and the tool runtime are in -# the base images, published like any other artifact — which is what makes this buildable by the -# mesh from a repository and a path (novox/hq ADR 0069) rather than only on a workstation that -# happens to have the siblings. -# -# Two bases, named rather than pinned (novox/hq issue 044): the image this is COMPILED in and the -# image it RUNS in — the second must not carry a compiler. Declared in module.json's `build.on`. -ARG BUILD_BASE -ARG RUNTIME_BASE - -FROM ${BUILD_BASE} AS build -# Compiled under /app/modules so `@novox/mesh-sdk` resolves upward into the base's own -# node_modules — the module is compiled against exactly the sdk it will run against. The compiler -# is invoked by its real path: node_modules/.bin entries are launcher symlinks the base image -# resolved away. -WORKDIR /app/modules/keycloak -COPY . . -RUN node /app/node_modules/typescript/bin/tsc client.ts oidc.ts index.ts provisioner/index.ts tools/index.ts \ - --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist - -FROM ${RUNTIME_BASE} -COPY --from=build /app/modules/keycloak/dist /app/modules/keycloak/dist -# Every serve-time entrypoint, loaded by the runtime in serve mode: tools and events serve, and a -# provider's provisioner runs its reconcile loop in the same process, with the broker connected — -# the convention novox/hq issues 060/061 settled. A container that instead ran only its -# provisioner (`run`) served no tools and emitted no events; a container that named no command -# ran no provisioner at all. -ENV MESH_TOOL_MODULES=/app/modules/keycloak/dist/index.js,/app/modules/keycloak/dist/tools/index.js,/app/modules/keycloak/dist/provisioner/index.js diff --git a/modules/keycloak/module.json b/modules/keycloak/module.json index 551996d..7c66957 100644 --- a/modules/keycloak/module.json +++ b/modules/keycloak/module.json @@ -62,8 +62,7 @@ "oidc-client": "${dir:grants}" }, "own-secrets": { - "admin": "${dir:state}/admin.secret", - "broker": "${dir:mesh-state}/broker" + "admin": "${dir:state}/admin.secret" }, "resources": [ { @@ -144,49 +143,30 @@ "mode": "0600", "content": "{}\n", "merge": "json" - }, - { - "id": "runtime", - "type": "container", - "name": "mesh-keycloak", - "network": "host", - "volumes": [ - "${dir:mesh-state}/broker:/run/secrets/broker:ro", - "${dir:mesh-state}/config.json:/run/config/config.json:ro", - "${dir:state}/admin.secret:/run/secrets/admin:ro", - "${dir:grants}:${dir:grants}:ro" - ], - "env": { - "MESH_BROKER_FILE": "/run/secrets/broker", - "MESH_KEYCLOAK_URL": "http://127.0.0.1:${port:8080}", - "MESH_KEYCLOAK_CONFIG_FILE": "/run/config/config.json", - "MESH_KEYCLOAK_PASSWORD_FILE": "/run/secrets/admin", - "MESH_RECEIVES": "${dir:grants}/mesh.json" - }, - "restart-on": [ - "runtime-config" - ], - "artifact": "runtime" } ], "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": [ + "index.js", + "tools/index.js", + "provisioner/index.js" + ], + "loads": [ + "index.js", + "tools/index.js", + "provisioner/index.js" + ], + "env": { + "MESH_KEYCLOAK_URL": "http://127.0.0.1:${port:8080}", + "MESH_KEYCLOAK_CONFIG_FILE": "${dir:mesh-state}/config.json", + "MESH_KEYCLOAK_PASSWORD_FILE": "${dir:state}/admin.secret", + "MESH_RECEIVES": "${dir:grants}/mesh.json" + } } ] } From 26021865c1b4d2f03ce585b47e685ad41cfcb37f Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 23:33:51 +0200 Subject: [PATCH 06/13] umami: its tools and provisioner run in the node's runtime (hq ADR 0198) The mesh-umami container goes with its Dockerfile, build bases, bus credential and state directory. The provisioner's env-file only told it umami's container-network address, so it becomes a word on the published port and the file goes. --- modules/umami/Dockerfile | 30 ------------------ modules/umami/module.json | 67 ++++++++++----------------------------- 2 files changed, 16 insertions(+), 81 deletions(-) delete mode 100644 modules/umami/Dockerfile diff --git a/modules/umami/Dockerfile b/modules/umami/Dockerfile deleted file mode 100644 index 2cf6a83..0000000 --- a/modules/umami/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -# umami's runtime: the tool runtime, carrying this module's compiled code. -# -# **Built from this module's own directory and nothing else.** The sdk and the tool runtime are in -# the base images, published like any other artifact — which is what makes this buildable by the -# mesh from a repository and a path (novox/hq ADR 0069) rather than only on a workstation that -# happens to have the siblings. -# -# Two bases, named rather than pinned (novox/hq issue 044): the image this is COMPILED in and the -# image it RUNS in — the second must not carry a compiler. Declared in module.json's `build.on`. -ARG BUILD_BASE -ARG RUNTIME_BASE - -FROM ${BUILD_BASE} AS build -# Compiled under /app/modules so `@novox/mesh-sdk` resolves upward into the base's own -# node_modules — the module is compiled against exactly the sdk it will run against. The compiler -# is invoked by its real path: node_modules/.bin entries are launcher symlinks the base image -# resolved away. -WORKDIR /app/modules/umami -COPY . . -RUN node /app/node_modules/typescript/bin/tsc client.ts tools/index.ts provisioner/index.ts \ - --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist - -FROM ${RUNTIME_BASE} -COPY --from=build /app/modules/umami/dist /app/modules/umami/dist -# Every serve-time entrypoint, loaded by the runtime in serve mode: tools and events serve, and a -# provider's provisioner runs its reconcile loop in the same process, with the broker connected — -# the convention novox/hq issues 060/061 settled. A container that instead ran only its -# provisioner (`run`) served no tools and emitted no events; a container that named no command -# ran no provisioner at all. -ENV MESH_TOOL_MODULES=/app/modules/umami/dist/tools/index.js,/app/modules/umami/dist/provisioner/index.js diff --git a/modules/umami/module.json b/modules/umami/module.json index de2b3d6..7452f9f 100644 --- a/modules/umami/module.json +++ b/modules/umami/module.json @@ -44,9 +44,6 @@ "grants": { "analytics": "${dir:grants}" }, - "own-secrets": { - "broker": "${dir:mesh-state}/broker" - }, "listens": [ { "name": "web", @@ -57,12 +54,6 @@ } ], "resources": [ - { - "id": "mesh-state", - "type": "directory", - "mode": "0700", - "place": "mesh" - }, { "id": "state", "type": "directory", @@ -81,13 +72,6 @@ "mode": "0600", "content": "DATABASE_URL=postgresql://${bound:postgres-database:as}:${secret:postgres-database}@${bound:postgres-database:at}:${bound:postgres-database:port}/${bound:postgres-database:as}\nDATABASE_TYPE=postgresql\nAPP_SECRET=${secret:app-secret}\n" }, - { - "id": "provisioner-env", - "type": "file", - "path": "${dir:state}/provisioner.env", - "mode": "0600", - "content": "MESH_PROVISION_UMAMI_URL=http://umami:3000\nGRANTS=${dir:grants}\n" - }, { "id": "net", "type": "network", @@ -106,46 +90,27 @@ "3000" ], "secrets-in-environment": "a Next.js/Prisma application: DATABASE_URL and APP_SECRET are read from the environment only; not convertible" - }, - { - "id": "runtime", - "type": "container", - "name": "mesh-umami", - "network": "umami", - "volumes": [ - "${dir:mesh-state}/broker:/run/secrets/broker:ro", - "${dir:grants}:${dir:grants}", - "${dir:state}/admin.secret:/run/secrets/admin:ro" - ], - "env": { - "MESH_BROKER_FILE": "/run/secrets/broker", - "MESH_RECEIVES": "${dir:grants}/mesh.json", - "MESH_UMAMI_ADMIN_PASSWORD_FILE": "/run/secrets/admin" - }, - "env-file": [ - "${dir:state}/provisioner.env" - ], - "artifact": "runtime" } ], "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", + "provisioner/index.js" + ], + "loads": [ + "tools/index.js", + "provisioner/index.js" + ], + "env": { + "MESH_RECEIVES": "${dir:grants}/mesh.json", + "MESH_UMAMI_ADMIN_PASSWORD_FILE": "${dir:state}/admin.secret", + "MESH_PROVISION_UMAMI_URL": "http://127.0.0.1:${port:3000}" + } } ] } From 7b0cfceb684ea95718c8fc5448e004ee9ac81201 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 23:33:51 +0200 Subject: [PATCH 07/13] cloudflare-dns: its tools and provisioner run in the node's runtime (hq ADR 0198) The mesh-cloudflare-dns container goes with its Dockerfile, build bases, bus credential and state directory. MESH_RECEIVES now names the grants directory itself; the container's value pointed at a path nothing was mounted on. --- modules/cloudflare-dns/Dockerfile | 24 ------------ modules/cloudflare-dns/module.json | 59 +++++++++--------------------- 2 files changed, 17 insertions(+), 66 deletions(-) delete mode 100644 modules/cloudflare-dns/Dockerfile diff --git a/modules/cloudflare-dns/Dockerfile b/modules/cloudflare-dns/Dockerfile deleted file mode 100644 index c838705..0000000 --- a/modules/cloudflare-dns/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -# cloudflare-dns's runtime: the tool runtime, carrying this module's compiled code. -# -# **Built from this module's own directory and nothing else.** The sdk and the tool runtime are in -# the base images, published like any other artifact — which is what makes this buildable by the -# mesh from a repository and a path (novox/hq ADR 0069) rather than only on a workstation that -# happens to have the siblings. -# -# Two bases, named rather than pinned (novox/hq issue 044): the image this is COMPILED in and the -# image it RUNS in — the second must not carry a compiler. Declared in module.json's `build.on`. -ARG BUILD_BASE -ARG RUNTIME_BASE - -FROM ${BUILD_BASE} AS build -WORKDIR /app/modules/cloudflare-dns -COPY . . -RUN node /app/node_modules/typescript/bin/tsc client.ts tools/index.ts provisioner/index.ts \ - --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist - -FROM ${RUNTIME_BASE} -COPY --from=build /app/modules/cloudflare-dns/dist /app/modules/cloudflare-dns/dist -# Every serve-time entrypoint, loaded by the runtime in serve mode: tools and events serve, and a -# provider's provisioner runs its reconcile loop in the same process, with the broker connected — -# the convention novox/hq issues 060/061 settled. -ENV MESH_TOOL_MODULES=/app/modules/cloudflare-dns/dist/tools/index.js,/app/modules/cloudflare-dns/dist/provisioner/index.js diff --git a/modules/cloudflare-dns/module.json b/modules/cloudflare-dns/module.json index 3dd2267..69b0237 100644 --- a/modules/cloudflare-dns/module.json +++ b/modules/cloudflare-dns/module.json @@ -18,20 +18,13 @@ "public-dns": "${dir:grants}/mesh.json" }, "own-secrets": { - "token": "${dir:state}/token", - "broker": "${dir:mesh-state}/broker" + "token": "${dir:state}/token" }, "emits": [ "record.created", "record.removed" ], "resources": [ - { - "id": "mesh-state", - "type": "directory", - "mode": "0700", - "place": "mesh" - }, { "id": "state", "type": "directory", @@ -50,48 +43,30 @@ "merge": "json", "content": "{}", "mode": "0600" - }, - { - "id": "runtime", - "type": "container", - "name": "mesh-cloudflare-dns", - "network": "host", - "volumes": [ - "${dir:state}/config.json:/run/config/config.json:ro", - "${dir:grants}:/grants", - "${dir:state}/token:/run/secrets/token:ro", - "${dir:mesh-state}/broker:/run/secrets/broker:ro" - ], - "env": { - "MESH_CLOUDFLARE_TOKEN_FILE": "/run/secrets/token", - "MESH_BROKER_FILE": "/run/secrets/broker", - "MESH_CLOUDFLARE_CONFIG_FILE": "/run/config/config.json", - "MESH_RECEIVES": "/var/lib/cloudflare-dns/grants/mesh.json" - }, - "artifact": "runtime" } ], "capabilities": [ "container-runtime" ], "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", + "provisioner/index.js" + ], + "loads": [ + "tools/index.js", + "provisioner/index.js" + ], + "env": { + "MESH_CLOUDFLARE_TOKEN_FILE": "${dir:state}/token", + "MESH_CLOUDFLARE_CONFIG_FILE": "${dir:state}/config.json", + "MESH_RECEIVES": "${dir:grants}/mesh.json" + } } ] } From af346f60665da3a23934c2dfd95f0d76476d86ca Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 23:33:51 +0200 Subject: [PATCH 08/13] grafana: its handlers and tools run in the node's runtime (hq ADR 0198) The mesh-grafana container goes with its Dockerfile, build bases and bus credential; its env becomes the bundle's words with mount targets folded back to host paths. --- modules/grafana/Dockerfile | 24 ----------------- modules/grafana/module.json | 52 ++++++++++++------------------------- 2 files changed, 16 insertions(+), 60 deletions(-) delete mode 100644 modules/grafana/Dockerfile diff --git a/modules/grafana/Dockerfile b/modules/grafana/Dockerfile deleted file mode 100644 index 9ff34c1..0000000 --- a/modules/grafana/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -# grafana's runtime: the tool runtime, carrying this module's compiled code. -# -# **Built from this module's own directory and nothing else.** The sdk and the tool runtime are in -# the base images, published like any other artifact — which is what makes this buildable by the -# mesh from a repository and a path (novox/hq ADR 0069) rather than only on a workstation that -# happens to have the siblings. -# -# Two bases, named rather than pinned (novox/hq issue 044): the image this is COMPILED in and the -# image it RUNS in — the second must not carry a compiler. Declared in module.json's `build.on`. -ARG BUILD_BASE -ARG RUNTIME_BASE - -FROM ${BUILD_BASE} AS build -WORKDIR /app/modules/grafana -COPY . . -RUN node /app/node_modules/typescript/bin/tsc client.ts index.ts tools/index.ts \ - --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist - -FROM ${RUNTIME_BASE} -COPY --from=build /app/modules/grafana/dist /app/modules/grafana/dist -# Every serve-time entrypoint, loaded by the runtime in serve mode: tools and events serve, and a -# provider's provisioner runs its reconcile loop in the same process, with the broker connected — -# the convention novox/hq issues 060/061 settled. -ENV MESH_TOOL_MODULES=/app/modules/grafana/dist/index.js,/app/modules/grafana/dist/tools/index.js diff --git a/modules/grafana/module.json b/modules/grafana/module.json index 023e5dd..7645f6b 100644 --- a/modules/grafana/module.json +++ b/modules/grafana/module.json @@ -5,8 +5,7 @@ "alert.firing" ], "own-secrets": { - "admin": "${dir:mesh-state}/admin", - "broker": "${dir:mesh-state}/broker" + "admin": "${dir:mesh-state}/admin" }, "capabilities": [ "container-runtime" @@ -112,25 +111,6 @@ "mode": "0600", "content": "{\n \"user\": \"admin\",\n \"password\": \"${secret:admin}\"\n}\n", "merge": "json" - }, - { - "id": "runtime", - "type": "container", - "name": "mesh-grafana", - "network": "host", - "volumes": [ - "${dir:mesh-state}/broker:/run/secrets/broker:ro", - "${dir:mesh-state}/config.json:/run/config/config.json:ro" - ], - "env": { - "MESH_BROKER_FILE": "/run/secrets/broker", - "MESH_GRAFANA_URL": "http://127.0.0.1:${port:3000}", - "MESH_GRAFANA_CONFIG_FILE": "/run/config/config.json" - }, - "restart-on": [ - "runtime-config" - ], - "artifact": "runtime" } ], "requires": [ @@ -162,23 +142,23 @@ "influxdb-api": "${dir:mesh-state}/influxdb-api" }, "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": [ + "index.js", + "tools/index.js" + ], + "loads": [ + "index.js", + "tools/index.js" + ], + "env": { + "MESH_GRAFANA_URL": "http://127.0.0.1:${port:3000}", + "MESH_GRAFANA_CONFIG_FILE": "${dir:mesh-state}/config.json" + } } ] } From a934e2a69f59c216e0eb10809b3e73c8270beb53 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 23:33:51 +0200 Subject: [PATCH 09/13] icecast: its handlers and tools run in the node's runtime (hq ADR 0198) The mesh-icecast container goes with its Dockerfile, build bases and bus credential. The bundle reaches icecast on the port this machine published rather than the container network's name. --- modules/icecast/Dockerfile | 24 ----------------- modules/icecast/module.json | 52 +++++++++++-------------------------- 2 files changed, 15 insertions(+), 61 deletions(-) delete mode 100644 modules/icecast/Dockerfile diff --git a/modules/icecast/Dockerfile b/modules/icecast/Dockerfile deleted file mode 100644 index cdc8b00..0000000 --- a/modules/icecast/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -# icecast's runtime: the tool runtime, carrying this module's compiled code. -# -# **Built from this module's own directory and nothing else.** The sdk and the tool runtime are in -# the base images, published like any other artifact — which is what makes this buildable by the -# mesh from a repository and a path (novox/hq ADR 0069) rather than only on a workstation that -# happens to have the siblings. -# -# Two bases, named rather than pinned (novox/hq issue 044): the image this is COMPILED in and the -# image it RUNS in — the second must not carry a compiler. Declared in module.json's `build.on`. -ARG BUILD_BASE -ARG RUNTIME_BASE - -FROM ${BUILD_BASE} AS build -WORKDIR /app/modules/icecast -COPY . . -RUN node /app/node_modules/typescript/bin/tsc client.ts index.ts tools/index.ts \ - --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist - -FROM ${RUNTIME_BASE} -COPY --from=build /app/modules/icecast/dist /app/modules/icecast/dist -# Every serve-time entrypoint, loaded by the runtime in serve mode: tools and events serve, and a -# provider's provisioner runs its reconcile loop in the same process, with the broker connected — -# the convention novox/hq issues 060/061 settled. -ENV MESH_TOOL_MODULES=/app/modules/icecast/dist/index.js,/app/modules/icecast/dist/tools/index.js diff --git a/modules/icecast/module.json b/modules/icecast/module.json index 327eae1..f3098e7 100644 --- a/modules/icecast/module.json +++ b/modules/icecast/module.json @@ -28,9 +28,6 @@ "stream.started", "stream.stopped" ], - "own-secrets": { - "broker": "${dir:mesh-state}/broker" - }, "listens": [ { "name": "stream", @@ -95,45 +92,26 @@ "mode": "0600", "content": "{}\n", "merge": "json" - }, - { - "id": "runtime", - "type": "container", - "name": "mesh-icecast", - "network": "icecast", - "volumes": [ - "${dir:mesh-state}/broker:/run/secrets/broker:ro", - "${dir:mesh-state}/config.json:/run/config/config.json:ro" - ], - "env": { - "MESH_BROKER_FILE": "/run/secrets/broker", - "MESH_ICECAST_URL": "http://icecast:8000", - "MESH_ICECAST_CONFIG_FILE": "/run/config/config.json" - }, - "restart-on": [ - "runtime-config" - ], - "artifact": "runtime" } ], "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": [ + "index.js", + "tools/index.js" + ], + "loads": [ + "index.js", + "tools/index.js" + ], + "env": { + "MESH_ICECAST_URL": "http://127.0.0.1:${port:8000}", + "MESH_ICECAST_CONFIG_FILE": "${dir:mesh-state}/config.json" + } } ] } From b19c4a259300874668ee3d90623437e9299efaab Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 23:33:51 +0200 Subject: [PATCH 10/13] home-assistant: the runtime serves its code, and its provisions step is a run-once process (hq ADR 0198) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mesh-home-assistant container goes with its Dockerfile, build bases and bus credential. The provisions step runs node on the bundle and reads the binding files where the mesh writes them, from an env-file the mesh fills because a process's env is not given ${port:…}. It still runs again when a binding it reads changes. --- modules/home-assistant/Dockerfile | 27 --------- modules/home-assistant/module.json | 94 +++++++++++------------------- 2 files changed, 33 insertions(+), 88 deletions(-) delete mode 100644 modules/home-assistant/Dockerfile diff --git a/modules/home-assistant/Dockerfile b/modules/home-assistant/Dockerfile deleted file mode 100644 index b8c8352..0000000 --- a/modules/home-assistant/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# home-assistant's runtime: the tool runtime, carrying this module's compiled code. -# -# **Built from this module's own directory and nothing else.** The sdk and the tool runtime are in -# the base images, published like any other artifact — which is what makes this buildable by the -# mesh from a repository and a path (novox/hq ADR 0069) rather than only on a workstation that -# happens to have the siblings. -# -# Two bases, named rather than pinned (novox/hq issue 044): the image this is COMPILED in and the -# image it RUNS in — the second must not carry a compiler. Declared in module.json's `build.on`. -ARG BUILD_BASE -ARG RUNTIME_BASE - -FROM ${BUILD_BASE} AS build -WORKDIR /app/modules/home-assistant -COPY . . -RUN node /app/node_modules/typescript/bin/tsc client.ts index.ts tools/index.ts provisions/hass.ts provisions/probe.ts provisions/connections.ts provisions/mesh.ts provisions/index.ts \ - --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist - -FROM ${RUNTIME_BASE} -COPY --from=build /app/modules/home-assistant/dist /app/modules/home-assistant/dist -# Every serve-time entrypoint, loaded by the runtime in serve mode: tools and events serve, and a -# provider's provisioner runs its reconcile loop in the same process, with the broker connected — -# the convention novox/hq issues 060/061 settled. -ENV MESH_TOOL_MODULES=/app/modules/home-assistant/dist/index.js,/app/modules/home-assistant/dist/tools/index.js -# NOT dist/provisions/index.js: that is a step the host runs to completion, named by the -# `provisions` container's args as `mesh-tools run …` (novox/hq ADR 0052). Listed here it would run -# inside the serving sidecar too, and exit it. diff --git a/modules/home-assistant/module.json b/modules/home-assistant/module.json index 08ebd92..ada4c9b 100644 --- a/modules/home-assistant/module.json +++ b/modules/home-assistant/module.json @@ -9,7 +9,6 @@ "state.changed" ], "own-secrets": { - "broker": "${dir:mesh-state}/broker", "token": "${dir:mesh-state}/token" }, "listens": [ @@ -80,55 +79,27 @@ "merge": "json" }, { - "id": "runtime", - "type": "container", - "name": "mesh-home-assistant", - "network": "host", - "volumes": [ - "${dir:mesh-state}/broker:/run/secrets/broker:ro", - "${dir:mesh-state}/token:/run/secrets/token:ro", - "${dir:mesh-state}/config.json:/run/config/config.json:ro" - ], - "env": { - "MESH_BROKER_FILE": "/run/secrets/broker", - "MESH_HOMEASSISTANT_URL": "http://127.0.0.1:${port:8123}", - "MESH_HOMEASSISTANT_TOKEN_FILE": "/run/secrets/token", - "MESH_HOMEASSISTANT_CONFIG_FILE": "/run/config/config.json" - }, - "restart-on": [ - "runtime-config" - ], - "artifact": "runtime" + "id": "provisions-env", + "type": "file", + "path": "${dir:state}/provisions.env", + "mode": "0600", + "content": "MESH_HOMEASSISTANT_URL=http://127.0.0.1:${port:8123}\nMESH_HOMEASSISTANT_TOKEN_FILE=${dir:mesh-state}/token\nMESH_PROVISIONS_DIR=${dir:state}\nMESH_WRITTEN_DIR=${dir:written}\n" }, { "id": "provisions", - "type": "container", - "name": "mesh-home-assistant-provisions", - "network": "host", - "run-once": true, - "volumes": [ - "${dir:mesh-state}/token:/run/secrets/token:ro", - "${dir:written}:/var/lib/home-assistant-provisions", - "${dir:state}/mqtt-topic.json:/run/provisions/mqtt-topic.json:ro", - "${dir:state}/mqtt-topic.secret:/run/provisions/mqtt-topic.secret:ro", - "${dir:state}/sonarr-api.json:/run/provisions/sonarr-api.json:ro", - "${dir:state}/sonarr-api.secret:/run/provisions/sonarr-api.secret:ro", - "${dir:state}/radarr-api.json:/run/provisions/radarr-api.json:ro", - "${dir:state}/radarr-api.secret:/run/provisions/radarr-api.secret:ro", - "${dir:state}/lidarr-api.json:/run/provisions/lidarr-api.json:ro", - "${dir:state}/lidarr-api.secret:/run/provisions/lidarr-api.secret:ro" + "type": "process", + "name": "home-assistant-provisions", + "artifact": "code", + "run": [ + "node", + "provisions/index.js" ], - "env": { - "MESH_HOMEASSISTANT_URL": "http://127.0.0.1:${port:8123}", - "MESH_HOMEASSISTANT_TOKEN_FILE": "/run/secrets/token", - "MESH_PROVISIONS_DIR": "/run/provisions", - "MESH_WRITTEN_DIR": "/var/lib/home-assistant-provisions" - }, - "args": [ - "run", - "/app/modules/home-assistant/dist/provisions/index.js" + "run-once": true, + "env-file": [ + "${dir:state}/provisions.env" ], "restart-on": [ + "provisions-env", "bound-mqtt-topic", "secret-mqtt-topic", "bound-sonarr-api", @@ -137,8 +108,7 @@ "secret-radarr-api", "bound-lidarr-api", "secret-lidarr-api" - ], - "artifact": "runtime" + ] } ], "requires": [ @@ -173,23 +143,25 @@ "lidarr-api": "${dir:state}/lidarr-api.secret" }, "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": [ + "index.js", + "tools/index.js", + "provisions/index.js" + ], + "loads": [ + "index.js", + "tools/index.js" + ], + "env": { + "MESH_HOMEASSISTANT_URL": "http://127.0.0.1:${port:8123}", + "MESH_HOMEASSISTANT_TOKEN_FILE": "${dir:mesh-state}/token", + "MESH_HOMEASSISTANT_CONFIG_FILE": "${dir:mesh-state}/config.json" + } } ] } From 6a6d5747a38a806585b83dea4d752b4a3de5e161 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 23:33:51 +0200 Subject: [PATCH 11/13] nodered: the runtime serves its tools, and its mqtt step is a run-once process (hq ADR 0198) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mesh-nodered container goes with its Dockerfile, build bases and bus credential. The mqtt step runs node on the bundle and reads the binding and settings files where the mesh writes them, from an env-file the mesh fills because a process's env is not given ${port:…}. --- modules/nodered/Dockerfile | 27 ------------ modules/nodered/module.json | 86 +++++++++++++------------------------ 2 files changed, 31 insertions(+), 82 deletions(-) delete mode 100644 modules/nodered/Dockerfile diff --git a/modules/nodered/Dockerfile b/modules/nodered/Dockerfile deleted file mode 100644 index 3031ce6..0000000 --- a/modules/nodered/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# nodered's runtime: the tool runtime, carrying this module's compiled code. -# -# **Built from this module's own directory and nothing else.** The sdk and the tool runtime are in -# the base images, published like any other artifact — which is what makes this buildable by the -# mesh from a repository and a path (novox/hq ADR 0069) rather than only on a workstation that -# happens to have the siblings. -# -# Two bases, named rather than pinned (novox/hq issue 044): the image this is COMPILED in and the -# image it RUNS in — the second must not carry a compiler. Declared in module.json's `build.on`. -ARG BUILD_BASE -ARG RUNTIME_BASE - -FROM ${BUILD_BASE} AS build -WORKDIR /app/modules/nodered -COPY . . -RUN node /app/node_modules/typescript/bin/tsc client.ts tools/index.ts mqtt/probe.ts mqtt/connection.ts mqtt/index.ts \ - --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist - -FROM ${RUNTIME_BASE} -COPY --from=build /app/modules/nodered/dist /app/modules/nodered/dist -# Every serve-time entrypoint, loaded by the runtime in serve mode: tools and events serve, and a -# provider's provisioner runs its reconcile loop in the same process, with the broker connected — -# the convention novox/hq issues 060/061 settled. -ENV MESH_TOOL_MODULES=/app/modules/nodered/dist/tools/index.js -# NOT dist/mqtt/index.js: that is a step the host runs to completion, named by the `mqtt` -# container's args as `mesh-tools run …` (novox/hq ADR 0052). Listed here it would run inside the -# serving sidecar too, and exit it. diff --git a/modules/nodered/module.json b/modules/nodered/module.json index 0ce746a..131de90 100644 --- a/modules/nodered/module.json +++ b/modules/nodered/module.json @@ -12,8 +12,7 @@ "api-token": { "path": "${dir:mesh-state}/api-token", "taken": "at-start" - }, - "broker": "${dir:mesh-state}/broker" + } }, "capabilities": [ "container-runtime" @@ -101,53 +100,31 @@ "content": "{\n \"token\": \"${secret:api-token}\"\n}\n" }, { - "id": "runtime", - "type": "container", - "name": "mesh-nodered", - "network": "host", - "volumes": [ - "${dir:mesh-state}/broker:/run/secrets/broker:ro", - "${dir:mesh-state}/config.json:/run/config/config.json:ro" - ], - "env": { - "MESH_BROKER_FILE": "/run/secrets/broker", - "MESH_NODERED_URL": "http://127.0.0.1:${port:1880}", - "MESH_NODERED_CONFIG_FILE": "/run/config/config.json" - }, - "restart-on": [ - "runtime-config" - ], - "artifact": "runtime" + "id": "mqtt-env", + "type": "file", + "path": "${dir:state}/mqtt.env", + "mode": "0600", + "content": "MESH_NODERED_URL=http://127.0.0.1:${port:1880}\nMESH_NODERED_CONFIG_FILE=${dir:mesh-state}/config.json\nMESH_PROVISIONS_DIR=${dir:state}\nMESH_WRITTEN_DIR=${dir:written}\n" }, { "id": "mqtt", - "type": "container", - "name": "mesh-nodered-mqtt", - "network": "host", - "run-once": true, - "volumes": [ - "${dir:mesh-state}/config.json:/run/config/config.json:ro", - "${dir:written}:/var/lib/nodered-provisions", - "${dir:state}/mqtt-topic.json:/run/provisions/mqtt-topic.json:ro", - "${dir:state}/mqtt-topic.secret:/run/provisions/mqtt-topic.secret:ro", - "${dir:state}/settings.json:/run/provisions/settings.json:ro" + "type": "process", + "name": "nodered-mqtt", + "artifact": "code", + "run": [ + "node", + "mqtt/index.js" ], - "env": { - "MESH_NODERED_URL": "http://127.0.0.1:${port:1880}", - "MESH_NODERED_CONFIG_FILE": "/run/config/config.json", - "MESH_PROVISIONS_DIR": "/run/provisions", - "MESH_WRITTEN_DIR": "/var/lib/nodered-provisions" - }, - "args": [ - "run", - "/app/modules/nodered/dist/mqtt/index.js" + "run-once": true, + "env-file": [ + "${dir:state}/mqtt.env" ], "restart-on": [ + "mqtt-env", "bound-mqtt-topic", "secret-mqtt-topic", "settings" - ], - "artifact": "runtime" + ] } ], "requires": [ @@ -173,23 +150,22 @@ "mqtt-topic": "${dir:state}/mqtt-topic.secret" }, "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", + "mqtt/index.js" + ], + "loads": [ + "tools/index.js" + ], + "env": { + "MESH_NODERED_URL": "http://127.0.0.1:${port:1880}", + "MESH_NODERED_CONFIG_FILE": "${dir:mesh-state}/config.json" + } } ] } From b9d0884335324c797035458abc72c35d00bde3d9 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 23:33:51 +0200 Subject: [PATCH 12/13] nextcloud: its handlers and tools run in the node's runtime (hq ADR 0198) The mesh-nextcloud container goes with its Dockerfile, build bases and bus credential. occ still runs through docker exec, now with the host's own docker CLI and socket. --- modules/nextcloud/Dockerfile | 40 ---------------------- modules/nextcloud/module.json | 62 ++++++++++------------------------- 2 files changed, 18 insertions(+), 84 deletions(-) delete mode 100644 modules/nextcloud/Dockerfile diff --git a/modules/nextcloud/Dockerfile b/modules/nextcloud/Dockerfile deleted file mode 100644 index d8a00e9..0000000 --- a/modules/nextcloud/Dockerfile +++ /dev/null @@ -1,40 +0,0 @@ -# nextcloud's runtime: the tool runtime, carrying this module's compiled code. -# -# **Built from this module's own directory and nothing else.** The sdk and the tool runtime are in -# the base images, published like any other artifact — which is what makes this buildable by the -# mesh from a repository and a path (novox/hq ADR 0069) rather than only on a workstation that -# happens to have the siblings. -# -# Two bases, named rather than pinned (novox/hq issue 044): the image this is COMPILED in and the -# image it RUNS in — the second must not carry a compiler. Declared in module.json's `build.on`. -ARG BUILD_BASE -ARG RUNTIME_BASE -ARG DOCKER_CLI - -# Named so the final stage's COPY --from can reference a stage, not an ARG — the legacy builder -# this host still runs doesn't expand ARGs inside COPY --from, only inside FROM. -FROM ${DOCKER_CLI} AS dockercli - -FROM ${BUILD_BASE} AS build -# Compiled under /app/modules so `@novox/mesh-sdk` resolves upward into the base's own -# node_modules — the module is compiled against exactly the sdk it will run against. The compiler -# is invoked by its real path: node_modules/.bin entries are launcher symlinks the base image -# resolved away. -WORKDIR /app/modules/nextcloud -COPY . . -RUN node /app/node_modules/typescript/bin/tsc client.ts index.ts tools/index.ts \ - --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist - -FROM ${RUNTIME_BASE} -COPY --from=build /app/modules/nextcloud/dist /app/modules/nextcloud/dist -# occ runs inside nextcloud's own container, reached over the mounted docker socket — which needs -# the docker CLI itself present here, not only the socket. Copied from Docker's own official client -# image rather than apt-installed, so this stays the one binary and nothing else (no daemon, no -# systemd unit, no package manager tree pulled in for it). -COPY --from=dockercli /usr/local/bin/docker /usr/local/bin/docker -# Every serve-time entrypoint, loaded by the runtime in serve mode: tools and events serve, and a -# provider's provisioner runs its reconcile loop in the same process, with the broker connected — -# the convention novox/hq issues 060/061 settled. A container that instead ran only its -# provisioner (`run`) served no tools and emitted no events; a container that named no command -# ran no provisioner at all. -ENV MESH_TOOL_MODULES=/app/modules/nextcloud/dist/index.js,/app/modules/nextcloud/dist/tools/index.js diff --git a/modules/nextcloud/module.json b/modules/nextcloud/module.json index 60260d0..fbcadd2 100644 --- a/modules/nextcloud/module.json +++ b/modules/nextcloud/module.json @@ -30,8 +30,7 @@ "share.created" ], "own-secrets": { - "admin": "${dir:state}/admin.secret", - "broker": "${dir:mesh-state}/broker" + "admin": "${dir:state}/admin.secret" }, "capabilities": [ "container-runtime" @@ -94,53 +93,28 @@ "mode": "0600", "content": "{}\n", "merge": "json" - }, - { - "id": "runtime", - "type": "container", - "name": "mesh-nextcloud", - "network": "host", - "volumes": [ - "${dir:mesh-state}/broker:/run/secrets/broker:ro", - "${dir:mesh-state}/config.json:/run/config/config.json:ro", - "${dir:state}/admin.secret:/run/secrets/admin:ro", - "/var/run/docker.sock:/var/run/docker.sock" - ], - "env": { - "MESH_BROKER_FILE": "/run/secrets/broker", - "MESH_NEXTCLOUD_URL": "http://127.0.0.1:${port:80}", - "MESH_NEXTCLOUD_CONFIG_FILE": "/run/config/config.json", - "MESH_NEXTCLOUD_ADMIN_USER": "mesh-admin", - "MESH_NEXTCLOUD_ADMIN_PASSWORD_FILE": "/run/secrets/admin" - }, - "restart-on": [ - "runtime-config" - ], - "artifact": "runtime" } ], "build": { - "on": [ - { - "arg": "BUILD_BASE", - "module": "mesh-tools", - "artifact": "build" - }, - { - "arg": "RUNTIME_BASE", - "module": "mesh-tools", - "artifact": "runtime" - }, - { - "arg": "DOCKER_CLI", - "image": "docker@sha256:018edbc908e08fcc9dbf029c812c34251e9b4719e6f71ca0e5eae2a987d014ca" - } - ], "artifacts": [ { - "name": "runtime", - "kind": "image", - "from": "Dockerfile" + "name": "code", + "kind": "bundle", + "language": "typescript", + "entrypoints": [ + "index.js", + "tools/index.js" + ], + "loads": [ + "index.js", + "tools/index.js" + ], + "env": { + "MESH_NEXTCLOUD_URL": "http://127.0.0.1:${port:80}", + "MESH_NEXTCLOUD_CONFIG_FILE": "${dir:mesh-state}/config.json", + "MESH_NEXTCLOUD_ADMIN_USER": "mesh-admin", + "MESH_NEXTCLOUD_ADMIN_PASSWORD_FILE": "${dir:state}/admin.secret" + } } ] } From f79199777d39480bc2ef57ecaa1b36a16097c925 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 23:33:51 +0200 Subject: [PATCH 13/13] minio: its tools and provisioner run in the node's runtime (hq ADR 0198) The mesh-minio container goes with its Dockerfile, build bases, bus credential and state directory. The client reaches minio on the published port, runs the minio-client package's mcli instead of the image's mc, and keeps mc's config, which holds the root alias, in the module's own state directory rather than a shared /tmp. --- modules/minio/Dockerfile | 40 ----------------------- modules/minio/module.json | 69 ++++++++++++++------------------------- 2 files changed, 24 insertions(+), 85 deletions(-) delete mode 100644 modules/minio/Dockerfile diff --git a/modules/minio/Dockerfile b/modules/minio/Dockerfile deleted file mode 100644 index fc0e121..0000000 --- a/modules/minio/Dockerfile +++ /dev/null @@ -1,40 +0,0 @@ -# minio's runtime: the tool runtime, carrying this module's compiled code. -# -# **Built from this module's own directory and nothing else.** The sdk and the tool runtime are in -# the base images, published like any other artifact — which is what makes this buildable by the -# mesh from a repository and a path (novox/hq ADR 0069) rather than only on a workstation that -# happens to have the siblings. -# -# Two bases, named rather than pinned (novox/hq issue 044): the image this is COMPILED in and the -# image it RUNS in — the second must not carry a compiler. Declared in module.json's `build.on`. -ARG BUILD_BASE -ARG RUNTIME_BASE -ARG MC_CLI - -# Named so the final stage's COPY --from can reference a stage, not an ARG — the legacy builder -# this host still runs doesn't expand ARGs inside COPY --from, only inside FROM. -FROM ${MC_CLI} AS mccli - -FROM ${BUILD_BASE} AS build -# Compiled under /app/modules so `@novox/mesh-sdk` resolves upward into the base's own -# node_modules — the module is compiled against exactly the sdk it will run against. The compiler -# is invoked by its real path: node_modules/.bin entries are launcher symlinks the base image -# resolved away. -WORKDIR /app/modules/minio -COPY . . -RUN node /app/node_modules/typescript/bin/tsc client.ts tools/index.ts provisioner/index.ts \ - --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist - -FROM ${RUNTIME_BASE} -COPY --from=build /app/modules/minio/dist /app/modules/minio/dist -# The provisioner shells out to mc to actually create buckets and service accounts on the running -# minio server — mc itself was never in this runtime image, only in minio's own. Silently retried -# "spawn mc ENOENT" forever: a requirement was granted at the control-plane level without ever -# materializing the credential on minio. /usr/bin/mc there is a symlink to the real binary, mcli — -# both copied so the symlink resolves. -COPY --from=mccli /usr/bin/mcli /usr/bin/mcli -COPY --from=mccli /usr/bin/mc /usr/bin/mc -# Every serve-time entrypoint, loaded by the runtime in serve mode: tools and events serve, and a -# provider's provisioner runs its reconcile loop in the same process, with the broker connected — -# the convention novox/hq issues 060/061 settled. -ENV MESH_TOOL_MODULES=/app/modules/minio/dist/tools/index.js,/app/modules/minio/dist/provisioner/index.js diff --git a/modules/minio/module.json b/modules/minio/module.json index eb304b4..1d1f045 100644 --- a/modules/minio/module.json +++ b/modules/minio/module.json @@ -59,16 +59,9 @@ "s3-bucket": "${dir:grants}" }, "own-secrets": { - "root": "${dir:state}/root.secret", - "broker": "${dir:mesh-state}/broker" + "root": "${dir:state}/root.secret" }, "resources": [ - { - "id": "mesh-state", - "type": "directory", - "mode": "0700", - "place": "mesh" - }, { "id": "state", "type": "directory", @@ -127,48 +120,34 @@ } }, { - "id": "runtime", - "type": "container", - "name": "mesh-minio", - "network": "minio-net", - "volumes": [ - "${dir:mesh-state}/broker:/run/secrets/broker:ro", - "${dir:grants}:${dir:grants}:ro", - "${dir:state}/root.secret:/run/secrets/root:ro" - ], - "env": { - "MESH_MINIO_ENDPOINT": "http://minio:9000", - "MESH_MINIO_ROOT_USER": "meshroot", - "MESH_MINIO_ROOT_PASSWORD_FILE": "/run/secrets/root", - "MESH_MINIO_REGION": "eu-west", - "MESH_BROKER_FILE": "/run/secrets/broker", - "MESH_RECEIVES": "${dir:grants}/mesh.json" - }, - "artifact": "runtime" + "id": "client", + "type": "package", + "package": "minio-client" } ], "build": { - "on": [ - { - "arg": "BUILD_BASE", - "module": "mesh-tools", - "artifact": "build" - }, - { - "arg": "RUNTIME_BASE", - "module": "mesh-tools", - "artifact": "runtime" - }, - { - "arg": "MC_CLI", - "image": "docker.io/pgsty/minio@sha256:b6bfe7239bfc83fb90d31612d9704d86039dd714f7904b3f1ad68f211e602372" - } - ], "artifacts": [ { - "name": "runtime", - "kind": "image", - "from": "Dockerfile" + "name": "code", + "kind": "bundle", + "language": "typescript", + "entrypoints": [ + "tools/index.js", + "provisioner/index.js" + ], + "loads": [ + "tools/index.js", + "provisioner/index.js" + ], + "env": { + "MESH_MINIO_ENDPOINT": "http://127.0.0.1:${port:9000}", + "MESH_MINIO_ROOT_USER": "meshroot", + "MESH_MINIO_ROOT_PASSWORD_FILE": "${dir:state}/root.secret", + "MESH_MINIO_REGION": "eu-west", + "MESH_MINIO_MC_BIN": "mcli", + "MESH_MINIO_MC_CONFIG": "${dir:state}/mc", + "MESH_RECEIVES": "${dir:grants}/mesh.json" + } } ] }