116 lines
6.6 KiB
Markdown
116 lines
6.6 KiB
Markdown
---
|
|
status: open
|
|
opened: 2026-09-30
|
|
located-in:
|
|
- 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)
|
|
fixed-by:
|
|
amended-design:
|
|
---
|
|
|
|
# 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](../153-an-adopted-machines-data-cannot-be-placed-where-it-is/00-report.md)
|
|
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](../154-a-machines-own-network-is-not-a-reach/00-report.md)
|
|
(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.
|