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