Files
hq/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md
T

6.6 KiB

status, opened, located-in, fixed-by, amended-design
status opened located-in fixed-by amended-design
open 2026-09-30
mesh-catalog (no module shares a path over the network)
hq 02-DECISIONS (a file-share seat, per ADR 0126, is a module's own to define)

169 — A machine shares its files, and the mesh does not know

What was observed

ace serves the operator's media library to the home network with two host services no module declares and HAL never managed either:

/etc/exports:   /storage/media  192.168.1.0/24(rw,sync,root_squash,…)     nfs-server active, :2049
/etc/samba/smb.conf: [media] path = /storage/media/  valid users = media   smb active, :139/:445

Two LAN clients were connected at survey (2026-09-30). The library itself is operator data (ADR 0051: ~40 TB on ZFS, the mesh owns nothing about it — issue 153 is about modules reaching it in place).

Under the mesh as it stands, this arrangement has no expression and one failure mode:

  • Nothing declares the listens. At converge ace the filter is the sum of what modules listen on (ADR 0045); 2049 and 445 are nobody's, so the shares close — silently, for the two clients that mount them.
  • Nothing owns the configuration. /etc/exports and smb.conf are hand-written files on one machine; a second machine sharing a directory would be written by hand again.
  • Nothing can consume it. A module on another node that wanted the library (a player, an indexer, a backup) has no requires to state and no binding to read; it would mount by a hand-typed host and path.
  • The clients are LAN devices, so this also meets issue 154 (no reach for the machine's own network).

The proposal (the operator's, 2026-09-30, settled after two rounds)

Two module-defined seats, one per protocol, because NFS and SMB share an intent and not a contract. A seat in the mesh's sense is a contract — what it accepts, emits and serves, and the tools its holder must answer (ADR 0126, 0132) — and lined up, the two share almost none of it:

nfs-share smb-share
serves export path(s); the client ranges allowed (sec=sys authorises by address) share name(s), path
pair credential none a user and password per consumer
consumer's mount at:/path //at/share with credentials
holder's tools export / unexport a path for a range add / remove a share, create a user

One file-share seat would be the union with every field optional — a consumer could bind it and still not know how to mount what it got (the emptiness ADR 0129 warns against). "Export a path to the network" is a category, and the mesh needs no seat category: a consumer requires the one it can mount. If "give me the library, however" is ever needed, it is a provision an umbrella module serves, not a seat.

Both are node-scoped, one holder per node (ADR 0110), so ace holds both. nfs and samba are the first implementations; a second (Ganesha for nfs-share, ksmbd for smb-share) is what proves 0126's promise that "replacing the implementation changes nothing for any caller".

The holder module:

  • declares the exported paths as accesses (ADR 0051: it owns nothing about them — never creates, chowns or removes), and which paths as the assignment's settings (ADR 0046/0112);
  • writes the share configuration (/etc/exports, smb.conf) as mesh-managed files and drives the units, like dnsmasq/sshd do for theirs;
  • declares its endpoints (nfs 2049/tcp; smb 445/tcp, …) so the reach — internal, or the LAN once 154 has an answer — is the assignment's, and converge keeps them open;
  • provides the seat's provision, so a consumer on another node requires nfs-share (or smb-share) and reads ${bound:nfs-share:at} and the path from its binding instead of a hand-typed mount.

The design gap this exposes

A seat definition has no home outside the module that first declared it. Today a seat is declared inside a manifest (showcase declares the-showcase, ca-trust its own). If nfs declared nfs-share, Ganesha could hold it only by depending on nfs's manifest — the coupling 0126 removed for callers, reintroduced for implementations. The protocol needs a neutral place in the catalogue beside the modules (a seat definition registered like a manifest), with a module saying which seats it implements. This is the first role with an obvious second implementation, which is what makes it the exemplar for that mechanism.

The consumer's half: the machine mounts it (2026-09-30, third round)

A binding tells a consumer where the share is; it does not put the files on its machine. A consumer on another node needs the export mounted by the host, at a directory the consumer declares, before its container starts, and unmounted when the requirement goes. That is a host action, not something a container reads from a file.

Not a client module. A node-wide nfs-client with a list of mounts in its settings is fstab with a manifest around it: consumers would stop requiring nfs-share and couple on a path again, unassigning a consumer would leave its mount behind, and a person is back in the loop deciding which machine mounts what — the thing the binding removes.

A mount is a resource of the consuming module. The vocabulary (directory, file, package, container, service, process, network, archive, user, access) has no mount. It can be assembled today — a package (nfs-utils), a file writing a systemd .mount unit with What=${bound:nfs-share:at}:${bound:nfs-share:path} and Where=${dir:library}, a service enabling it after the overlay is up — but the honest form is a mount resource kind: the host mounts from the binding, refuses a mountpoint the module did not declare (ADR 0091's three ways a path can be), and unmounts on undeclare. The nfs and samba modules are then the server side only.

Identity crosses the wire. sec=sys NFS trusts the client's uid, so a consumer must run as the library's owner on the server (ace: media, 1001:2000) — hq 153's ${access:<id>:uid} proposal extended to a mount, ${mount:<id>:uid}, read from the mounted tree.

Open questions for the decision

  • Whether an NFS export over the overlay is an internal reach of the same endpoint or a second export line — NFS authorises by client address, so the mesh range and the LAN range are two entries in one file.
  • How a consumer's binding expresses a path to mount (today bindings carry at, port, as and whatever the provider serves), and whether one share can serve several paths.