Commit Graph
363 Commits
Author SHA1 Message Date
jschoubben 048f1b8284 ssh-client module: the mesh owns ~/.ssh, config from the hub (to-be 29)
Requires openssh; creates ~/.ssh (0700, owned by the operator account via
${machine:account}); writes every other node's Host block (HostName + User
<account>) into a marked region of ~/.ssh/config (home-scoped, into:block), so
`ssh <node>` reaches each peer as the right account and the operator's own
config is kept. Universal-tier: assigned wherever a person logs in; a node with
no account gets no config.
2026-09-27 17:50:57 +02:00
jschoubben 278610c0c3 fail2ban never bans a tunnel peer: ignoreip names the mesh range
The jail.local [DEFAULT] gains ignoreip = 127.0.0.1/8 ::1 ${machine:mesh-range}
— localhost plus the mesh's own private range, named through the placeholder
rather than hardcoded (data is the mesh's, ADR 0112). Without it fail2ban could
ban the mesh's own nodes on 10.10.0.0/24; on novox that rule survived only in
memory from a now-deleted HAL file and would be lost on the next restart.
2026-09-27 16:55:34 +02:00
jschoubben 6bedcd3f21 Rename seat claims to the mesh-*/node-* convention; retire verdaccio (ADR 0121)
Claims renamed to match the controller's seat set: node-dns-resolver (dnsmasq),
node-intrusion-prevention (fail2ban), node-packet-filter (nftables),
node-resolver-config (resolv-conf, resolved-split-dns), node-uplink
(networkmanager, systemd-networkd, dhcpcd), mesh-build-machine (builder, +mesh
scope), mesh-catalog (mesh-catalog). showcase now declares its own seat and
claims it. verdaccio removed — the mesh keeps distribution as its registry and
gitea already serves npm, so a second npm registry is redundant.
2026-09-27 14:30:56 +02:00
jschoubben 968473219a dnsmasq owns its resolver format: node-zones is a template, not a controller formatter (ADR 0120)
The node-zones fact was a path; the local=/address= syntax lived in the
control plane. It is dnsmasq's configuration language, so it moves into
dnsmasq's manifest as a template over the roster. The mesh renders it; it
reads none of it. Output is unchanged.

Lands with mesh-controller's ADR 0120 change — the two are one schema step.
2026-09-27 01:33:28 +02:00
jschoubben ad219beee2 Merge pull request 'The uplink's managers are modules: networkmanager, systemd-networkd, dhcpcd (hq ADR 0117)' (#110) from feat/the-uplink-modules into main 2026-09-26 23:00:30 +00:00
jochen fd09b1a50e review: the uplink managers are the machine's — no state on their services, none for dhcpcd; comments corrected 2026-09-27 00:07:27 +02:00
jochen 7aea08d6c3 dhcpcd's mesh block goes at the start: lines after an interface line are that interface's 2026-09-26 23:48:20 +02:00
jochen 23d735a0bf The uplink's managers are modules (hq ADR 0117)
networkmanager, systemd-networkd and dhcpcd each claim the-uplink and
declare only what keeps the machine's own network manager from
contradicting the mesh: the resolver file left to resolv-conf, mesh0
left alone. Never a link, profile or credential — the link is the
mesh's only channel to the machine, so NetworkManager and networkd are
reloaded on a change, never restarted, and dhcpcd (no reload; a restart
drops the address) takes its block at its next start.
2026-09-26 23:47:31 +02:00
jschoubben 226eab4c6f Merge pull request 'dnsmasq: the operator's own names have a home the mesh never rewrites' (#109) from feat/dnsmasq-has-a-home-for-operator-names into main 2026-09-26 20:43:53 +00:00
jschoubben bb8f2e76a9 dnsmasq: the operator's own names have a home the mesh never rewrites
A workstation's job includes names that are neither a mesh machine nor
a routed name (novox/hq 122): shanks carries 13 Mediahuis entries in
/etc/hosts, and mesh-wireguard replaces /etc/hosts whole when taken —
so without this they vanish, and the take gates the node. Two homes,
neither the mesh's to own: conf-dir=/etc/dnsmasq.d/,*.conf (drop-in
directives, HAL's dnsmasq-app used exactly this) and
addn-hosts=/etc/hosts.local (plain host lines). The mesh creates and
rewrites neither; a machine with none loses nothing. The migration
moves such names here BEFORE the /etc/hosts take, closing the window.
2026-09-26 22:43:29 +02:00
jschoubben 372450851f Merge pull request 'mssql: its data is placed — the last /services placement retires' (#108) from feat/mssql-data-is-placed into main 2026-09-26 18:36:16 +00:00
jschoubben f4e4e12c99 mssql: its data is placed — the last /services placement retires
The stated path was the adopted-data exception; with the take done and
the placement vocabulary live, the exception has no reason left. The
landing window renames the directory and recreates the container, since
a changed volume path does not do that by itself (hq 126).
2026-09-26 20:36:03 +02:00
jschoubben fd9be011c0 Merge pull request 'sshd: the operator's door is a module' (#107) from feat/sshd-module into main 2026-09-26 18:15:08 +00:00
jschoubben a85b0ee346 sshd: the operator's door is a module
The spec is the working system: HAL's 99-hal.conf, restated as
10-mesh.conf so lexical include order makes the mesh's answer the one
that wins while the predecessor's file is still on disk. Subsystem
stays the stock config's — first-set wins and it sits before the
Include. Port 22 from anywhere, said in listens with its reason: the
machines that need the door are exactly the ones not on the mesh yet,
and locking the operator out is the one failure a firewall must never
arrange.
2026-09-26 20:14:54 +02:00
jschoubben 34243c9e34 Merge pull request 'dnsmasq: the runtime's DNS is written into daemon.json, never over it' (#106) from fix/dnsmasq-writes-into-daemon-json into main 2026-09-26 18:12:20 +00:00
jschoubben 870a541072 dnsmasq: the runtime's DNS is written into daemon.json, never over it
The file is shared — the operator's insecure-registries for the mesh's
own store live there — and replacing it whole would break every pull
from that store the moment the module is taken (the 098 class, caught
in the pre-take diff this time). ADR 0102's verb is merge.
2026-09-26 20:12:06 +02:00
jschoubben a59750fa28 Merge pull request 'mailu: the smtp provision serves the name its certificate answers to' (#105) from fix/smtp-serves-its-tls-name into main 2026-09-26 17:27:06 +00:00
jschoubben 81592a3b2c mailu: the smtp provision serves the name its certificate answers to
A consumer connecting by the binding's address meets a certificate for
mail.novox.be and refuses it — found live by the forwarder's cutover
proof, one send before production would have. The TLS name is mailu's
own fact (HOSTNAMES), so the binding carries it; consumers say
${bound:smtp:name} and verification holds.
2026-09-26 19:26:53 +02:00
jschoubben 705ceec1e7 Merge pull request 'Six modules name no /var/lib: the root is a place, the maps reference it' (#104) from feat/six-modules-name-no-var-lib into main 2026-09-26 16:21:00 +00:00
jschoubben afdd149ab7 Six modules name no /var/lib: the root is a place, the maps reference it
The state directories say place "." — the assignment's own root — and
every bind, secret, own-secret, receives and grants path references it
as ${dir:state}/…; grants directories that are their own resources are
placed by id. gitea's two coincidence strings from the first pass
(${dir:data}base.json — resolving correctly by pure concatenation) are
spelled honestly now. What still says /var/lib is inside containers —
the software's contract — or under /var/lib/mesh, the mesh's own
plumbing, which the requirements unification absorbs next. Every
resolved path is byte-identical to what runs; landing this is a no-op
on the node, and the converter checks its own boundaries this time.
2026-09-26 18:20:47 +02:00
jschoubben dde8b15483 Merge pull request 'mailu: seventeen data directories are placed, not stated' (#103) from feat/mailu-dirs-are-placed into main 2026-09-26 16:07:24 +00:00
jschoubben 8a046be198 mailu: seventeen data directories are placed, not stated
Each resolves to <root>/mailu/<id> — the maildir at
/var/lib/mailu/data-mail, certs at data-certs, and so on. Landing this
is a window, not an edit: seventeen renames on the node (the nested
data/ tree flattens to the ids), then the full stack recreated, because
a changed volume path does not recreate a container by itself (hq 126).
Ids are untouched on purpose — a renamed id orphans its held record,
and the mail spool is the wrong place to learn what a removal step does
with one.
2026-09-26 18:07:11 +02:00
jschoubben 668278bde4 Merge pull request 'nextcloud: it lives under its own name, and html is placed' (#102) from feat/nextcloud-lives-under-its-own-name into main 2026-09-26 16:05:41 +00:00
jschoubben 6761bb02a1 nextcloud: it lives under its own name, and html is placed
The module is nextcloud; its tree was /var/lib/nextcloud-module — a
historic spelling nothing depends on. The root moves to
/var/lib/nextcloud (a rename on the node, done in this change's
window), and html drops its path: the mesh resolves it to
<root>/nextcloud/html. Landing this requires the window: rename the
tree, push, recreate the container — a changed volume path does not
recreate one by itself (hq 126).
2026-09-26 18:05:28 +02:00
jschoubben f98c9859d2 Merge pull request 'gitea: its data and grants are placed, not stated' (#101) from feat/gitea-dirs-are-placed into main 2026-09-26 16:04:30 +00:00
jschoubben cac5eab7da gitea: its data and grants are placed, not stated
The mesh resolves both to <root>/gitea/<id> — where the 5.8G forge and
its grant files already sit, so the roll-out its upgrade policy makes
of this build changes no byte of the spec. The module root and the
mesh's plumbing stay stated.
2026-09-26 18:04:17 +02:00
jschoubben 770c9f6a78 Merge pull request 'mongodb: its data directory is placed, not stated' (#100) from feat/mongodb-dir-is-placed into main 2026-09-26 16:03:36 +00:00
jschoubben 5427118614 mongodb: its data directory is placed, not stated
The mesh resolves it to <root>/mongodb/data — where the granted
databases already sit. The provider's own state, grants and the mesh's
plumbing stay stated.
2026-09-26 18:03:25 +02:00
jschoubben 53765335cf Merge pull request 'portainer: its data directory is placed, not stated' (#99) from feat/portainer-dir-is-placed into main 2026-09-26 16:02:42 +00:00
jschoubben 5fd2ed9686 portainer: its data directory is placed, not stated
The mesh resolves it to <root>/portainer/data — where the 16M of
endpoints and users already sit. A textual no-op on this node.
2026-09-26 18:02:25 +02:00
jschoubben f7887d706d Merge pull request 'only-office: its directories are placed, not stated' (#98) from feat/only-office-dirs-are-placed into main 2026-09-26 16:01:43 +00:00
jschoubben d6dd21a091 only-office: its directories are placed, not stated
Seven data directories drop their paths; the mesh resolves each to
<root>/only-office/<id>, which is exactly where the data already sits —
a textual no-op on this node, and the first module speaking ADR 0112's
vocabulary. The module root and the mesh's own state stay stated.
2026-09-26 18:01:31 +02:00
jschoubben a844701577 Merge pull request 'Module data lives in /var/lib, now that nothing is mid-cutover' (#97) from feat/module-data-lives-in-var-lib into main 2026-09-26 14:47:37 +00:00
jschoubben 50a99f022c Module data lives in /var/lib, now that nothing is mid-cutover
The /services paths were the adopted-node pattern doing its job: take
replaced containers over the predecessor's data without moving a byte
(gitea set it — 'its data never moved'). With every cutover done the
exception has no reason left, and the operator called it: a nox
module's world is /var/lib/<module>, data included. Six modules
repathed; mssql keeps its /services path deliberately — it is still
held, HAL-run, and moves at its own take. Both trees are one
filesystem, so each move is a rename.
2026-09-26 16:47:24 +02:00
jschoubben 382a44621e Merge pull request 'A bucket is the one the mesh derives, and the photos module named another' (#96) from fix/a-bucket-is-the-one-the-mesh-derives into main 2026-09-26 14:46:30 +00:00
jochen ddb67fc095 A bucket is the one the mesh derives, and the photos module named another
The provisioner derives a consumer's bucket from the login the mesh minted — 'derived from the
login, so teardown recomputes it with nothing to persist' — and never reads the bucket a manifest
contributed. Three modules contributed one anyway, and the value was decorative in two and wrong in
the third: photos told its container MINIO_BUCKET=photos, the predecessor's bucket, while its minted
key is scoped to mesh-novox-photos. Deployed as it stood, it would have authenticated and then been
denied on every object.

photos now names the bucket the mesh actually provisions, and the contributed bucket is gone from
all three: a value nothing reads, that reads as though it decides.

Verified against the live store before changing anything: the derived names are the populated ones —
mesh-novox-ncloud (77,886 objects, 174.9 GiB), mesh-novox-photos and mesh-novox-invoice. Nothing has
to move.
2026-09-26 16:46:10 +02:00
jschoubben 78595e4db3 Merge pull request 'lavinmq: the broker TLS directory is the operator's, read by whoever needs it' (#95) from fix/the-broker-tls-directory-is-the-operators into main 2026-09-26 14:12:45 +00:00
jschoubben 37634de1e3 lavinmq: the broker TLS directory is the operator's, read by whoever needs it
The controller now accesses /var/lib/mesh-broker-tls (mesh-controller
#54) and the push refused whole: lavinmq declared the directory as an
owned resource, and shared data is the operator's, owned by no module
(ADR 0051). lavinmq only ever reads the certs — genesis laid them down
— so it declares a read access like the controller does, and the
directory belongs to nobody.
2026-09-26 16:12:29 +02:00
jschoubben 36c5f87130 Merge pull request 'route-adapter: write a body limit as the predecessor's buffering middleware' (#94) from feat/a-route-may-limit-the-body-it-carries into main 2026-09-26 14:02:03 +00:00
jochen b2e39eb2cd route-adapter: write a body limit as the predecessor's buffering middleware
The adapter skips what its one file shape cannot say. A body limit is the exception: the predecessor
has a buffering middleware and served its own registry name with exactly it, so this is written
rather than skipped, named after the router so the two halves cannot drift.

A limit that is not a whole positive number of bytes takes the route with it. Written without the
limit, the predecessor would carry what the module said not to carry and this module would report
success. Silence stays silence — no middleware, the predecessor's default.
2026-09-26 16:01:23 +02:00
jschoubben 3d73c9f54e Merge pull request 'nextcloud: real mesh module, MariaDB→PostgreSQL, S3 via _FILE secrets' (#59) from feat/nextcloud-module-postgres-migration into main 2026-09-26 13:06:32 +00:00
jschoubben fb95eb6e46 Merge main 2026-09-26 15:06:11 +02:00
jschoubben 4d715f8b73 Merge pull request 'minio: the real 4-node/8-drive erasure-coded cluster, both public routes, verified live on novox' (#58) from feat/minio-real-cluster-not-single-node into main 2026-09-26 13:05:57 +00:00
jochen afe8aae826 Merge main
# Conflicts:
#	modules/minio/module.json
2026-09-26 15:05:40 +02:00
jschoubben fa91be4941 Merge pull request 'postgres: declare the data directory's real owner; keycloak: use the port template' (#55) from fix/postgres-owner-and-keycloak-port-template into main 2026-09-26 13:05:10 +00:00
jschoubben 87f73dce6a Merge main 2026-09-26 15:04:47 +02:00
jschoubben fc5ccdfe2a Merge pull request 'mailu: one WEBMAIL_ADDRESS, the mesh's container name' (#93) from fix/one-webmail-address into main 2026-09-26 13:04:44 +00:00
jschoubben 4489e56935 mailu: one WEBMAIL_ADDRESS, the mesh's container name
The env block carried the key twice — mailu-webmail from #79's address
sweep, and a stray =webmail further down that survived it. Last write
wins in an env file, so the front resolved a name that answers nowhere
on the mesh's network and 502'd every logged-in webmail request. Latent
since the cutover: the SSO redirect the checks watched never touches
the upstream; the operator's first real login did.
2026-09-26 15:04:32 +02:00
jschoubben 08e947e4c8 Merge pull request 'The npm registry is a seat gitea holds, and gitea holds the git seat a build's source can live on' (#69) from feat/seats-are-a-closed-set into main 2026-09-26 12:31:10 +00:00
jschoubben 7fb9dd0254 Merge main 2026-09-26 14:29:28 +02:00