Path: blob/master/src/packages/file-server/zfs/nfs.ts
1447 views
import { get, set, touch } from "./db";1import { exec } from "./util";2import { filesystemDataset, filesystemMountpoint } from "./names";3import { primaryKey, type PrimaryKey } from "./types";45// Ensure that this filesystem is mounted and setup so that export to the6// given client is allowed.7// Returns the remote that the client should use for NFS mounting, i.e.,8// this return s, then type "mount s /mnt/..." to mount the filesystem.9// If client is not given, just sets the share at NFS level10// to what's specified in the database.11export async function shareNFS({12client,13...fs14}: PrimaryKey & { client?: string }): Promise<string> {15client = client?.trim();16const pk = primaryKey(fs);17const { pool, nfs } = get(pk);18let hostname;19if (client) {20hostname = await hostnameFor(client);21if (!nfs.includes(client)) {22nfs.push(client);23// update database which tracks what the share should be.24set({ ...pk, nfs: ({ nfs }) => [...nfs, client] });25}26}27// actually ensure share is configured.28const name = filesystemDataset({ pool, ...pk });29const sharenfs =30nfs.length > 031? `${nfs.map((client) => `rw=${client}`).join(",")},no_root_squash,crossmnt,no_subtree_check`32: "off";33await exec({34verbose: true,35command: "sudo",36args: ["zfs", "set", `sharenfs=${sharenfs}`, name],37});38if (nfs.length > 0) {39touch(pk);40}41if (client) {42return `${hostname}:${filesystemMountpoint(pk)}`;43} else {44return "";45}46}4748// remove given client from nfs sharing49export async function unshareNFS({50client,51...fs52}: PrimaryKey & { client: string }) {53const pk = primaryKey(fs);54let { nfs } = get(pk);55if (!nfs.includes(client)) {56// nothing to do57return;58}59nfs = nfs.filter((x) => x != client);60// update database which tracks what the share should be.61set({ ...pk, nfs });62// update zfs/nfs to no longer share to this client63await shareNFS(pk);64}6566let serverIps: null | string[] = null;67async function hostnameFor(client: string) {68if (serverIps == null) {69const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/;70const { stdout } = await exec({71verbose: false,72command: "ifconfig",73});74let i = stdout.indexOf("inet ");75const v: string[] = [];76while (i != -1) {77let j = stdout.indexOf("\n", i);78if (j == -1) {79break;80}81const x = stdout.slice(i, j).split(" ");82const ip = x[1];83if (ipRegex.test(ip)) {84v.push(ip);85}86i = stdout.indexOf("inet ", j);87}88if (v.length == 0) {89throw Error("unable to determine server ip address");90}91serverIps = v;92}93for (const ip of serverIps) {94if (subnetMatch(ip, client)) {95return ip;96}97}98throw Error("found no matching subdomain");99}100101// a and b are ip addresses. Return true102// if the are on the same subnet, by which103// we mean that the first *TWO* segments match,104// since that's the size of our subnets usually.105// TODO: make configurable (?).106function subnetMatch(a, b) {107const v = a.split(".");108const w = b.split(".");109return v[0] == w[0] && v[1] == w[1];110}111112113