Files
mesh-catalog/modules/nfs-server

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.