Files
mesh-controller/internal/catalogue/shares.go
T
jochen 59fdffb979
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery-group group feat/mounts-module ready: every member ready, and composed together they pass
mesh/delivery superseded: a newer head of the same pull request
Add the node-nfs-server and node-mounts seats, so a share and a mount have a role the mesh defines (hq ADR 0263)
A machine sharing folders and a machine mounting them each need one holder
per machine, with verbs an agent calls instead of exportfs, fstab edits or
zfs set. Both seats deliver nothing: nfs-share is provided at the mesh's
scope. The two adopt verbs are dry runs unless confirmed.
2026-10-08 18:24:38 +02:00

95 lines
5.8 KiB
Go

package catalogue
// A machine's shares, and the shares a machine mounts (novox/hq ADR 0263).
//
// **Two roles, one per side of the wire.** A machine that shares a directory with the mesh does it
// through the holder of `node-nfs-server`: it writes the machine's export file, runs the NFS service,
// opens its port to the private network only, and provides each share as the provision `nfs-share`. A
// machine that wants the files gets them through the holder of `node-mounts`: it writes a mount unit and
// an automount unit per share, so nothing mounts at boot and nothing can fail a boot, and it says a
// device that comes and goes is absent rather than failed.
//
// **One holder per machine on each side.** Two modules writing one machine's export file, or two writing
// mount units for one mount point, is the conflict a seat exists to refuse. Both seats deliver nothing:
// `nfs-share` is provided at the mesh's scope, by the module holding `node-nfs-server` on the machine that
// shares, and a seat at a node's scope cannot be the answer for a provision at the mesh's.
//
// **The data is the operator's** (ADR 0051). Neither holder creates, chowns or removes anything under a
// shared path; the export maps every client to the path's owner, so no client acts as another account on
// the server. Their verbs read, and the ones that act take over what a person wrote by hand only on a
// person's word: a dataset's export property, an fstab line.
// NFSServerSeat is the role of the machine that shares directories over NFS (novox/hq ADR 0263).
const NFSServerSeat = "node-nfs-server"
// MountsSeat is the role that mounts a machine's shares and occasional sources (novox/hq ADR 0263).
const MountsSeat = "node-mounts"
// nfsServerVerbs is the contract every holder of node-nfs-server serves (novox/hq ADR 0263).
func nfsServerVerbs() []Verb {
return []Verb{
{Name: "exports", Description: "Every share this machine exports: its name, its path, read-write or " +
"read-only, the owner every client is mapped to (uid and gid), the clients it is exported to (the " +
"private network's range), and whether the kernel holds it now. Also the exports found that are " +
"not the mesh's: a dataset's sharenfs property, a line in /etc/exports.",
Input: schema(map[string]string{}, nil),
Replaces: []string{"exportfs -v", "cat /etc/exports", "zfs get sharenfs"}},
{Name: "clients", Description: "Which machines have mounted which share now, as the NFS server " +
"knows its clients.",
Input: schema(map[string]string{}, nil),
Replaces: []string{"ss -tn sport = :2049", "cat /proc/fs/nfsd/clients/*/info"}},
{Name: "test", Description: "Whether this machine exports one share to one node's address now, and " +
"whether its NFS service is up: what a machine mounting the share asks before it mounts.",
Input: schema(map[string]string{
"share": "the share, by its name",
"node": "the node that would mount it, by name (the asking node when unsaid)",
}, []string{"share"}),
Replaces: []string{"showmount -e"}},
{Name: "reload", Description: "Write this machine's export file again from the module's settings and " +
"have the kernel take it. Changes no shared path.",
Input: schema(map[string]string{}, nil),
Replaces: []string{"exportfs -ra"}},
{Name: "adopt", Description: "Take over an export a person made by hand: clear a dataset's sharenfs " +
"property for a path the mesh's own export now serves — only when that export is live. Without " +
"confirm, says what it would do and changes nothing.",
Input: schema(map[string]string{
"path": "the shared path whose hand-made export is taken over",
"confirm": "true to change it; anything else is a dry run",
"why": "why, for the record",
}, []string{"path"}, "confirm"),
Replaces: []string{"zfs set sharenfs=off"}},
}
}
// mountsVerbs is the contract every holder of node-mounts serves (novox/hq ADR 0263).
func mountsVerbs() []Verb {
return []Verb{
{Name: "list", Description: "Every mount point on this machine: its fstab line, the mesh's mount and " +
"automount units for it, its state (armed, mounted, absent, unreachable, failed) and since when, " +
"and which settings asked for it. A password in a mount's options is never shown.",
Input: schema(map[string]string{}, nil),
Replaces: []string{"cat /etc/fstab", "findmnt", "systemctl list-units --type=mount"}},
{Name: "test", Description: "Whether one share's or occasional source's server answers from this " +
"machine now, without touching its mount point.",
Input: schema(map[string]string{"name": "the share or source, by its name"}, []string{"name"}),
Replaces: []string{"showmount -e", "ping"}},
{Name: "mount", Description: "Mount one share or occasional source now, rather than at its first " +
"access.",
Input: schema(map[string]string{"name": "the share or source, by its name"}, []string{"name"}),
Replaces: []string{"mount"}},
{Name: "unmount", Description: "Release one share or occasional source now; its automount stays " +
"armed, so the next access mounts it again.",
Input: schema(map[string]string{"name": "the share or source, by its name"}, []string{"name"}),
Replaces: []string{"umount"}},
{Name: "adopt", Description: "Take over a mount a person wrote by hand: comment out the /etc/fstab " +
"line for a mount point the mesh's own units now serve, keeping a copy of the file — only when " +
"the mesh's automount for it is armed. Without confirm, says what it would do and changes nothing.",
Input: schema(map[string]string{
"mountpoint": "the mount point whose fstab line is taken over",
"confirm": "true to change it; anything else is a dry run",
"why": "why, for the record",
}, []string{"mountpoint"}, "confirm"),
Replaces: []string{"sed -i /etc/fstab"}},
}
}