Commit Graph
183 Commits
Author SHA1 Message Date
jschoubben 93634041db baserow: its texts no longer name the shared cache it stopped taking 2026-09-22 12:34:58 +02:00
jschoubben 24cbac816d n8n and baserow no longer take the shared cache: neither can keep its keys and channels under the login it is granted — baserow runs its own, n8n in its shipped mode needs none (novox/hq issue 081) 2026-09-22 12:26:33 +02:00
jschoubben b2a48558ea Merge pull request 'redis: a consumer's ACL loses the dangerous category (issue 080)' (#36) from multiple-fixes into main 2026-09-22 02:19:14 +02:00
jschoubben c71bbd497f redis: INFO is allowed back — client libraries ask it at connect, and it reads nothing a consumer keeps 2026-09-22 02:03:23 +02:00
jschoubben 421f4d8577 redis: a consumer's ACL loses the dangerous category — a key pattern does not confine FLUSHALL (novox/hq issue 080) 2026-09-22 01:39:29 +02:00
jschoubben 54af5e8536 Merge pull request 'route-proxy: the trust gate names the binding it reads; the server names the gate (ADR 0099)' (#35) from multiple-fixes into main 2026-09-21 23:58:48 +02:00
jschoubben 6fb2003b8d route-proxy: the trust gate names the binding it reads and runs again when the authority moved; the server follows it (ADR 0099) 2026-09-21 23:25:06 +02:00
jschoubben 64d62978f6 Merge pull request 'Multiple fixes: five modules keep their secrets from the vault (ADR 0094), the authority makes its own root (076), the builder on the host network, route-proxy declares its bases' (#34) from multiple-fixes into main 2026-09-21 22:58:24 +02:00
jschoubben ac651c7ac2 route-proxy: the trust container names its artifact 2026-09-21 22:55:35 +02:00
jschoubben 1c964cd571 route-proxy: the trust gate retries with a timeout and checks for a certificate; its images are declared artifacts (ADRs 0096, 0097, 0098) 2026-09-21 22:53:29 +02:00
jschoubben 28b8feebfc The authority makes its own root at first start, and the proxy fetches it through a gate
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).
2026-09-21 22:26:28 +02:00
jschoubben 82256fcdb0 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.
2026-09-21 22:24:43 +02:00
jschoubben facd41806d 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).
2026-09-21 22:16:10 +02:00
jschoubben 126969a829 Merge pull request 'The controller's manifest lives in the controller's repository, not here (hq issue 072)' (#33) from feat/one-controller-manifest into main 2026-09-21 19:23:18 +02:00
jschoubben a514d9827c The controller's manifest lives in the controller's repository, not here
ADR 0069 put it there; genesis now takes it from the build it runs (mesh-host,
novox/hq 04-ISSUES/072). This copy was read by nothing else and had already drifted.
2026-09-21 15:17:47 +02:00
jschoubben e0d34723f7 Merge pull request 'Six modules take their secrets from files; the rest say precisely why not (issue 041)' (#32) from feat/migration-blockers into main 2026-09-21 13:48:46 +02:00
jschoubben 1289510538 mongodb names its secrets' owner: the image drops to its own user before it reads the password file
The official entrypoint re-executes itself as mongodb (uid 999) and only then reads
MONGO_INITDB_ROOT_PASSWORD_FILE, so a root-owned 0600 file is 'Permission denied' at line 83 and
the server never starts. secrets-owner is the mechanism ADR 0086 gives for exactly this.
2026-09-21 13:43:41 +02:00
jschoubben 50b639ff52 The env-file references to files the conversion removed go with them 2026-09-21 12:43:41 +02:00
jschoubben 0b2f3bfbd3 grafana stays a declared exception until a bed exercises its admin password 2026-09-21 12:31:59 +02:00
jschoubben 1a28e5aec6 Six modules take their secrets from files; the rest say precisely why not
From the survey of every env-file secret (ADR 0086, issue 041): amqp-ping,
minio, mongodb and grafana use the _FILE twin their software honours;
mesh-catalog and model-usage read DATABASE_URL_FILE (a file the mesh
templates, mounted where only the runtime reads it); grafana's secret files
belong to its own account. Two dead deliveries removed: a line nothing read
in amqp-email-forwarder, and mailu's secret.env on four containers that
never read it. The 25 exceptions that remain carry the surveyed reason —
convertible and awaiting a bed, convertible through a generated config file,
the application's own code, or not convertible.
2026-09-21 12:29:25 +02:00
jschoubben db597bcb71 Merge pull request 'The controller reads its credentials from files; every other env-file secret says why' (#31) from feat/secret-not-in-environment into main 2026-09-21 11:59:08 +02:00
jschoubben 8647ea6671 The control plane's secret files belong to its own account (secrets-owner) 2026-09-21 10:19:00 +02:00
jschoubben 32dec5f0c2 The controller reads its credentials from files; every other env-file secret says why
ADR 0086. mesh-controller mounts its six own secrets and names them with
_FILE twins, so no credential of its own reaches its environment. The 35
containers that still read a secret through an env-file carry
secrets-in-environment with the reason; converting each where its software
accepts a path is the per-module work of issue 041.
2026-09-21 10:10:33 +02:00
jschoubben d03520f4ed Merge pull request 'Add mesh-vault; redis, postgres and lavinmq take their passwords from files' (#30) from feat/secrets-vault into main 2026-09-21 10:03:12 +02:00
jschoubben 82e513a360 Add the mesh-vault module; redis takes its password from it
mesh-vault provides `secret` (novox/hq ADR 0085, design 24). The value is
the pair credential the controller mints — the vault holds no copy, only a
ledger of who holds one, its fingerprint and every rotation, and two tools that
answer by fingerprint and never by value. Rotation is `rotate secret`,
unchanged machinery pointed at a secret with an owner (design 13). Named in the
mesh's own namespace, beside mesh-controller and mesh-catalog, because it is
the mesh's own code rather than wrapped software.

redis is the first consumer: its own password stops being an own-secret nothing
could rotate and becomes a `secret` it requires, read from the same file into
the same hole. The server now restarts on its config, or it would keep the
password it started with through every rotation (playbook 06).
2026-09-21 00:48:13 +02:00
jschoubben 8105ab6141 Merge pull request 'minio: pull image from quay.io (docker.io denies anonymous pulls)' (#29) from fix/minio-pull-from-quay into main 2026-09-20 22:03:50 +02:00
jschoubben 2ef7eb2a27 minio: pull image from quay.io (docker.io denies anonymous pulls)
Same digest, a registry that serves anonymous pulls. Unblocks raising minio in
the lab and the object-store cutover.
2026-09-20 22:01:20 +02:00
jschoubben 4ce9de6f3a Merge pull request 'Correct lavinmq Dockerfile's stale MESH_TOOL_MODULES comment (061 review)' (#28) from fix/lavinmq-dockerfile-comment into main 2026-09-20 13:42:03 +02:00
jschoubben 17243b72df Correct lavinmq Dockerfile's stale MESH_TOOL_MODULES comment (061 review)
The comment still said the provisioner is not listed and runs via a
container's args — but the 061 fix put it in MESH_TOOL_MODULES (serve
mode) and dropped the args. A future editor trusting the comment could
strip it again and silently reintroduce 061. Comment now matches the
code; only the run-once bootstrap runs via args.
2026-09-20 13:32:47 +02:00
jschoubben 43c9c973e2 Merge pull request 'The mesh builds its own catalogue (issue 060)' (#27) from feat/the-mesh-builds-its-catalogue into main 2026-09-20 12:53:04 +02:00
jschoubben 965c58fe44 Defer minio from the buildable set too (issue 060)
minio's runtime copies the `mc` client from minio/mc:latest — a
Docker Hub pull the mesh build environment cannot make (its docker
reaches the mesh registry, not public Hub), the same isolation that
blocks npm deps. apt-based installs (mongodb's mongosh, mosquitto)
build fine because the build has real internet for apt; only npm and
Docker Hub are redirected. Delivering an external binary or image layer
into a mesh build is the same open question as the npm deps — deferred
with them.
2026-09-18 02:51:09 +02:00
jschoubben b8d390cbae Defer model-usage and anthropic-manager from the buildable set (issue 060)
Both carry third-party runtime deps (pg; tweetnacl + sealedbox) that
their Dockerfile installs with npm — which 404s in a mesh build, whose
npm points at the mesh's own registry, not public npm. The workstation
build script got away with it by installing on a host with public npm.
Delivering a module's third-party deps into a mesh build is an open
question (how: publish to the mesh registry, or proxy); until it is
answered these two stay on the placeholder path they were already on.
The other 37 modules build from their own directory with no external
fetch.
2026-09-18 02:48:09 +02:00
jschoubben e362fb951c The rest of the catalogue becomes mesh-buildable (issue 060, batch 2)
The 21 media/home modules, the SaaS tool modules (cloudflare-dns,
confluence, gitlab, jira), model-usage, and the model-access trio get
the same Dockerfile + build section as batch 1. Scheduled-only modules
(anthropic-consumer, anthropic-manager, openai-consumer) deliberately
declare no MESH_TOOL_MODULES — every container of theirs names its
command. mosquitto's run-once bootstrap container builds from the same
artifact. Modules with third-party deps (model-usage: pg;
anthropic-manager: tweetnacl) install them beside their compiled code.

Also fixes cloudflare-dns's package.json, unparseable since its
description lost a closing quote.

Deliberately still without build sections: builder and mesh-controller
(the foundation builds them by its own path), distribution (provides
the artifact store — building it through itself is refused by design),
route-proxy (cross-repo build context, deferred), and the
upstream-image-only modules, which have no code to build.
2026-09-18 02:14:09 +02:00
jschoubben 591b26f712 Ten migration-critical modules become mesh-buildable (issue 060)
keycloak, mailu, minio, mongodb, mssql, nextcloud, portainer, redis,
umami and verdaccio get the Dockerfile + build section the eight
buildable modules already had; their runtime containers name the
artifact instead of a placeholder digest.

One convention, settled (060's open question, informed by 061): the
runtime container runs serve mode with every serve-time entrypoint in
MESH_TOOL_MODULES — tools serve, events flow, and a provider's
provisioner reconciles in the same process with the broker connected.
postgres, gitea and lavinmq are retrofitted from args-run provisioners,
which served no tools and emitted lifecycle events nowhere.

route-proxy is deferred: its build context is the mesh-controller
repository, a cross-repo shape the build section cannot yet express.
2026-09-18 02:02:21 +02:00
jschoubben 83a78b4fb3 Merge pull request 'Refs name the registry; lavinmq's runtime runs its provisioner (042/048/061)' (#26) from feat/refs-name-the-registry into main 2026-09-18 01:00:43 +02:00
jschoubben 1891b09c65 lavinmq's runtime container runs its provisioner
The runtime container named no command, so it ran the image default —
the tool host — and the provisioner entrypoint compiled beside it never
ran anywhere: no vhost was ever minted, while the grants sat applied in
its mounted directory. postgres already names its provisioner in args;
lavinmq now does the same. Surfaced by the built-store-cross-node bed,
run 9 — the first bed to reach the vhost assertion honestly.
2026-09-17 23:54:56 +02:00
jschoubben 2dab3069d2 The builder names the registry by the binding again (ADR 0082)
The one-line change e0c9219 parked "until there is a certificate" returns — with the
overlay recorded as the registry's transport security and every node's runtime told the
store speaks plain HTTP, a reference under the provider's internal name is one every
machine can pull. References minted at genesis stay loopback and are valid where they
matter, on the machine that made them.

https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-17 23:05:25 +02:00
jschoubben 728922a016 Merge pull request 'Foundation modules claim a mesh-scoped seat named after their server' (#25) from multi-node/foundation-seats into main 2026-09-17 02:15:59 +02:00
jschoubben fccc1e6552 The foundation modules claim a mesh-scoped seat named after their server
postgres claims mesh-store, lavinmq claims mesh-broker, and the controller's seat is
renamed the-controller -> mesh-controller so all three follow one convention. The resolver
refuses a second holder mesh-wide, so an adopted foundation module assigned to a second node
is refused rather than silently raising a second server. Closes hq issue 056 (ADR 0079).

https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-17 02:13:26 +02:00
jschoubben a24362b74b Merge pull request 'The adopted broker declares its amqps bus port (5671) in listens' (#24) from fix/broker-declares-amqps-port into main 2026-09-17 01:44:47 +02:00
jschoubben 06fd436eca The adopted broker declares its amqps bus port (5671) in listens
mesh-broker serves the amqps bus on 5671 (the control plane and every module's
events) but the lavinmq module declared only 5672, so the firewall's forward
chain — where the broker's published ports are matched — never opened 5671, and
a consumer on another node could not reach the bus over the overlay. Part of
issue 055.

https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-17 01:01:54 +02:00
jschoubben f155493926 Merge pull request 'Foundation modules adopted, and the mesh-controller/foundation rename' (#23) from feat/foundation-and-rename into main 2026-09-16 23:22:47 +02:00
jschoubben 5e4dc3748e Phase 3.2: the lavinmq module adopts mesh-broker instead of raising its own
server is now the container the foundation raised — name mesh-broker, the same
pinned upstream lavinmq image, the same TLS args, ports and volumes — so the
applier adopts it in place. The second server, the lavinmq.ini bootstrap and the
module's own network are gone. The provisioner is host-networked to the broker's
loopback management (127.0.0.1:15672) and authenticates as lavinmq's default
guest, which the foundation broker runs with; the mesh bus stays on the / vhost,
a vhost-per-consumer beside it. One lavinmq now.

Issue 051 (WBS 3.2).

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-16 21:24:13 +02:00
jschoubben a63ef3d954 Phase 3.1: the postgres module adopts mesh-store instead of raising its own
server is now the container the foundation raised — same name (mesh-store),
same env (POSTGRES_PASSWORD/PGDATA), ports (127.0.0.1:5432:5432), volume
(mesh-store-data) and the same pinned upstream postgres image the foundation
runs — so the applier adopts it in place rather than raising a second postgres.
The provisioner is host-networked to reach the loopback store at 127.0.0.1:5432.
The module's own network and bind-mounted data dir are gone; there is one
postgres now, holding the controller's contexts and every module's database.

Issue 051 (WBS 3.1).

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-16 21:04:06 +02:00
jschoubben 41637befff Rename mesh-control -> mesh-controller, substrate -> foundation
One name per thing, per the HQ glossary: the module/container/image/binary/repo
becomes mesh-controller, the seat the-controller, and the store+broker pair the
foundation (embedded base bundles, default template and example lock renamed with
their go:embed directives). No behaviour change — a pure vocabulary rename.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-16 18:40:40 +02:00
jschoubben b520bd1825 The builder's workspace is a same-path bind, so sibling builds see the clone
A docker run -v from inside the builder resolves the path on the host: with the
workspace mounted at a different path inside than out, the SDK publish and any
bundle compile mounted an empty directory. Bind it at the same path both sides.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-16 16:02:26 +02:00
jschoubben 065ddd6d69 gitea provides the package registry; the builder gets its npm credential
gitea gains the package-registry provision: serves/receives/grants, an admin
own-secret, a postgres-shaped build, and a provisioner that creates a gitea user
per consumer with the mesh-minted password and seals nothing (hq ADR 0048). The
builder takes its registry credential as an own-secret rather than a resolved
provision, because gitea-as-module needs the base to build its provisioner and so
cannot resolve before the base — a cycle the own-secret avoids.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-16 10:27:26 +02:00
jschoubben abcba14edd Review: four manifests said something stale or nothing at all
dnsmasq still required resolver-data, a provision that died with the
mesh-resolver module — assigning it would refuse with "nothing provides
resolver-data". It asks for the node-zones fact now, at the same path its
config already reads, restarting on the fact's own id.

gitea and verdaccio both provide package-registry now — ADR 0075's provision,
which neither declared, so ADR 0014's "consumes from the private registry" had
no provider anywhere in the catalogue. Two providers, mesh-scoped: the resolver
refuses until one is assigned, and choosing is assigning, which is the designed
shape.

audit-logger runs a container and declared no capability, alone among the
containerised modules. A machine without a runtime would have been assigned it
and failed at apply rather than at assignment.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-15 22:03:33 +02:00
jschoubben 1cb33732f2 Move amqp-ping's source, so the mesh has something to notice 2026-09-15 21:03:43 +02:00
jschoubben 71bbc7dab0 Name the two modules after their software: nftables and distribution
A module's identity is the software it is (ADR 0040). Two were named after the
job instead, and the job already had a name.

firewall installs the nftables package and runs nftables.service. The seat it
claims is the-packet-filter, which is correctly named for the role. Calling the
module firewall named neither the software nor the provision, and promised that
any firewall could sit there — the false genericity the naming rule forbids.

registry runs Distribution, the OCI reference implementation, and provides
artifact-store. So registry was a third name for a thing that already had two,
which is how one word ended up meaning the module, the software and the concept
in the same paragraph.

The capability stays firewall, and correctly: a capability IS a functionality, so
a node having one and fail2ban requiring one are both right. Only the module
moves.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-15 20:51:00 +02:00