# nfs-server A node shares a folder with the mesh's machines, as a module (novox/hq ADR 0263, to-be 52). It holds the node seat `node-nfs-server` and provides the provision `nfs-share`, which the `mounts` module on another node requires. ## The rules it keeps - **One share per folder.** The shares are the assignment's `shares` setting: `name=folder`, or `name=folder:ro`, separated by spaces. The setting has no default: which folders a machine shares is the operator's value (ADR 0112, ADR 0262). - **To the mesh's machines only.** Every export line names the private network's range (`${machine:mesh-range}`) and nothing else, and the module opens port 2049 to the mesh only. The home network is never named. - **Every client is the folder's owner.** NFS with `sec=sys` believes the user id a client sends, and one id names different accounts on different machines. Every line is `all_squash` with `anonuid` and `anongid` read from the folder's owner on this machine, so a file written from any node lands owned by the folder's owner, and root is squashed with everyone else. - **Never the backups.** A folder that is the machine's root, is not there, is not a folder, or holds a backup (restic) repository at its path or one level below is refused, said by the tools, and makes the module unhealthy. - **The folders are the operator's** (ADR 0051). The module reads a folder's owner and exports it. It never creates, chowns, chmods or removes anything under a shared path; its code writes two files of its own and nothing else, and a test holds it to that. - **NFS version 4 only**, so 2049 is the only port a client needs: rpcbind and mountd stay closed. ## What it owns - the `nfs-utils` package and `nfs-server.service`, running and enabled; - `/etc/nfs.conf.d/50-mesh.conf`: versions 2, 3 and 4.0 off, 4.1 and 4.2 on; - `/etc/nfs-server/shares.conf`: the setting and the range, rendered by the mesh; - `/etc/exports.d/mesh.exports`: written by its process, with a marker header; - `/run/nfs-server/status.json`: the process's last look, for the tools; - the state `exports` on the bus, one key per machine: what `exports` answers, for a consumer's module and for a view (ADR 0201). ## How it runs - **Its process** (`nfs-server exports`, as root) looks every 30 seconds. It reads the shares, judges each folder, writes the export file when it changed, and runs `exportfs -ra` when the file changed or a share is missing from the kernel's table. Then it writes its status. - **Its tools** run as the operator account. They read the status and the kernel's table, and run what only root may do (`exportfs -ra`, `zfs set`) through `sudo -n`, like the packet filter's tools. - **Health** is the tool `nfs_health`, asked by the node-engine every minute: unhealthy when the NFS server is not active, the process has not looked for five minutes, a share is refused or missing from the kernel's table, or the kernel could not read the files again. ## Tools | tool | | answers | |---|---|---| | `node-nfs-server.exports` | r | every share: name, folder, rw or ro, the owner every client is mapped to, the clients, whether it is live; refused shares and why; every export that is not the mesh's (`/etc/exports`, other files in `/etc/exports.d`, ZFS `sharenfs`) | | `node-nfs-server.clients` | r | the NFS clients the kernel knows (address, name), or the connections to 2049 | | `node-nfs-server.test` | r | whether one share is exported to the range now, the server is active, and an address is inside the range | | `node-nfs-server.reload` | a | `exportfs -ra`, then the shares as the kernel holds them | | `node-nfs-server.adopt` | a | take over a ZFS dataset's `sharenfs` export of a folder the mesh exports: `sharenfs=off` on that one dataset, then `exportfs -ra`; only when the mesh's export of the folder is live; a dry run without `confirm`; a person's act, with why | | `nfs_health` | r | the module's health, one sentence per finding | `exports` answers, for the `mounts` module: ``` {"shares": [{"name", "path", "access", "uid", "gid", "clients", "line", "live"}], "refused": [{"name", "path", "why"}], "foreign": [{"path", "client", "options", "from"}], "service": "active", "range", "looked", "said", "notes"} ``` ## What it leaves - **The export file after unassignment.** The process writes `/etc/exports.d/mesh.exports`, not the node-engine, because each line carries the folder's owner as this machine reads it. When the module is unassigned, the process stops and the file stays until removed. `exports` on any later holder, and the `mounts` module's `mounts_exports`, show it. - **A subfolder export shares its filesystem.** A share that is a folder inside a larger filesystem (not a filesystem of its own) is exported with `no_subtree_check`, so a client that forges file handles could reach other folders of that filesystem. Every client is a node and is squashed to the share's owner, so a folder that owner cannot read stays closed. A share that must be strictly fenced is better given its own dataset. - **Exports that are not the mesh's** (`/etc/exports`, a ZFS dataset's `sharenfs`) are left as found and shown, until `adopt` takes one over on a person's word.