Commit Graph
357 Commits
Author SHA1 Message Date
jschoubben 833d8ec3ec The artifact store's seat is one per mesh
Review of the registry work found it node-scoped: a second `distribution` on another
machine resolved cleanly there, and only afterwards did the mesh notice `artifact-store`
offered by two nodes, with every consumer elsewhere refusing to choose. Worse, a node-scoped
requirement with one candidate installs that candidate, so anything that wanted the store
beside it would have raised a fresh, empty store on the wrong machine first.

There is one store in a mesh, which was already the effective rule; the claim now says it
where a second one is assigned, by name, instead of leaving consumers to discover it.
2026-09-23 23:40:54 +02:00
jschoubben 9f2c678355 Merge pull request 'Convert mssql from the module it replaces, not from a blank page' (#46) from convert/mssql-from-hal into main 2026-09-23 18:22:51 +00:00
jschoubben cae0d00a7d Convert mssql from the module it replaces, not from a blank page
Read against hal/modules/mssql: the predecessor publishes 4848:1433 and its connections
block hands every consumer localhost:4848; its data has lived in /services/mssql/data
since it was installed — 4.6 GB of it. The nox module had 1433, db-data, and a pin three
builds behind, so taking it would have moved the port every consumer was told about,
started on an empty directory, and downgraded the engine at the same time.

db-data was not a convention: only this module and mongodb used it, and both predecessors
say data. Now nothing moves at the cutover.
2026-09-23 20:22:34 +02:00
jschoubben c001e054fd Merge pull request 'Pin the analytics module to the version the machine it replaces is running' (#45) from fix/umami-matches-production into main 2026-09-23 00:48:13 +00:00
jschoubben b69edbb16f Pin the analytics module to the version the machine it replaces is running
The pin was a month old — 2026-08-20 against the 2026-09-17 image the container runs.
Third module in a row whose pin had aged into a downgrade; the pattern is hq issue 099.
2026-09-23 02:47:23 +02:00
jschoubben b0d48d5c54 Merge pull request 'Pin the package registry to the version the machine it replaces is running' (#44) from fix/verdaccio-matches-production into main 2026-09-23 00:37:31 +00:00
jschoubben a3eca8f4e6 Pin the package registry to the version the machine it replaces is running: 6.10.3, not 6.10.1
The rule the forge's cutover taught: a module that takes over a running service must not
carry an older image than the one running, or the cutover is a downgrade nobody asked for.
6.10.4 exists; moving to it is an upgrade and its own act.
2026-09-23 02:24:07 +02:00
jschoubben f11534102d Merge pull request 'Pin the forge to the version the machine it replaces is running' (#43) from fix/gitea-matches-production into main 2026-09-23 00:28:26 +02:00
jschoubben a29475617a Pin the forge to the version the machine it replaces is running: 1.27.3, not 1.22.6 2026-09-23 00:28:11 +02:00
jschoubben 176bbd6085 Merge pull request 'route-adapter: provide route by writing into the predecessor's proxy (hq ADR 0104)' (#42) from feat/route-adapter into main 2026-09-23 00:12:48 +02:00
jschoubben bd5b349a0d route-adapter: provide route by writing into the predecessor's proxy (hq ADR 0104)
A node being adopted cannot take a web module: every module reachable by
name requires route, the mesh's only provider of it binds the two public
ports, and the predecessor's proxy holds them and serves every public
name there. Stopping the predecessor to break the circle darkens every
name at once, with every certificate to re-obtain in the same window.

So this answers the same provision without binding anything. It provides
route and receives the same contributions file, and writes each
contribution as one route file where the predecessor's file provider
reads, naming the predecessor's own certificate resolver so no
certificate is asked for. It removes a file it wrote when its
contribution goes and never touches a file it did not write — the name
and a marker inside both have to say it is the mesh's.

A step, not a daemon: run-once, re-run by restart-on over the received
file and the settings. The predecessor's dynamic directory is a node
setting, because it is a fact about one machine.

Migration scaffolding with a stated end: assigned only on an adopted
node, deleted when the predecessor's proxy retires.
2026-09-23 00:11:21 +02:00
jschoubben 4f4a0750de Merge pull request 'The store keeps the vector extension the predecessor's database had' (#41) from fix/store-keeps-pgvector into main 2026-09-22 23:30:34 +02:00
jschoubben 5eb1baf5fe Keep the vector extension the predecessor's database had: the store runs the pgvector build of the same major 2026-09-22 23:30:21 +02:00
jschoubben d0fed5b982 Merge pull request 'The forge's own address follows the port the node gave it (hq issue 088)' (#40) from feat/forge-address into main 2026-09-22 23:29:47 +02:00
jschoubben cf30d4b3d0 The forge's sidecar dials the port the machine put the forge on (hq issue 088)
MESH_GITEA_URL named 3000 outright. The forge's container publishes 3000 without
fixing the machine side, so the mesh assigns it — and on a node given that port
as a setting it is the operator's number. The mapping that lets the forge go on
binding 3000 does nothing for a caller dialling the machine's loopback, so the
sidecar dialled a port nothing was listening on wherever the two differed.

${port:3000} is the mesh's answer to exactly that question, and it now resolves
in a container's environment as it always has in a file. The forge declares it
listens on 3000, so it may ask.

Still names 3000: the route contribution (hq issue 089) and the `listens` and
`ports` entries, which are the software's own number and belong there.
2026-09-22 22:36:34 +02:00
jschoubben 5706c00566 Merge pull request 'The builder's package binding is settable per node (hq issue 085)' (#39) from feat/packages-port into main 2026-09-22 21:59:57 +02:00
jschoubben b2e29871fd Protect the address the builder sends its registry password to
`at` was settable. A node setting could point the builder's package binding at
any host, and the builder sends its registry credential there as basic auth — so
a setting meant for a port was a way to hand the password to somebody else.

novox/hq 04-ISSUES/085
2026-09-22 21:56:51 +02:00
jschoubben d63f006cb8 The builder's package binding takes its port from the node, not from the manifest
The forge is raised by hand at genesis, before any module provides
`package-registry`, so the builder carries a binding instead of resolving one —
and the port in it was rewritten, as text, by the installer. A later
registration of this manifest from the catalogue put 3000 back, silently, and
pointed the builder and its registry credential at whatever holds that port.

Made settable instead: the port is a per-node setting the controller holds, and
the catalogue's number is only its default. `provision`, `from` and `as` are
protected — a setting here is about where the forge answers, never about who the
binding is with.

novox/hq 04-ISSUES/085, ADR 0100
2026-09-22 21:39:54 +02:00
jschoubben 6e88982498 Merge pull request 'Adoption mode: guards, and a filter unit that never flushes the ruleset (hq ADR 0100, 0103)' (#38) from feat/adoption-mode into main 2026-09-22 21:01:56 +02:00
jschoubben 90104d0818 Reload the filter on a rule change rather than restart it, so the node is never unfiltered in between (hq ADR 0102) 2026-09-22 19:47:43 +02:00
jschoubben 84012fab2e Make the stock nftables unit's stop delete only the mesh's table on nodes that still have it enabled (hq ADR 0100) 2026-09-22 18:06:15 +02:00
jschoubben 0c37d7389d Guard the store and management ports on adopted nodes, and load the filter through a unit that never flushes the ruleset (hq ADR 0100) 2026-09-22 17:32:32 +02:00
jschoubben f4097b3c57 Merge pull request 'n8n and baserow no longer take the shared cache (issue 081)' (#37) from multiple-fixes into main 2026-09-22 13:53:14 +02:00
jschoubben 431627c510 baserow: its data directory is 0755, as the image ships it — the cache it runs for itself does so as another user, who must reach its own directory beneath (issue 081) 2026-09-22 13:46:01 +02:00
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