CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/project/named-servers/control.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import getPort from "get-port";
7
import { exec } from "node:child_process";
8
import { mkdir, readFile, writeFile } from "node:fs/promises";
9
import { join } from "node:path";
10
11
import basePath from "@cocalc/backend/base-path";
12
import { data } from "@cocalc/backend/data";
13
import { project_id } from "@cocalc/project/data";
14
import { INFO } from "@cocalc/project/info-json";
15
import { getLogger } from "@cocalc/project/logger";
16
import { NamedServerName } from "@cocalc/util/types/servers";
17
import getSpec from "./list";
18
19
const winston = getLogger("named-servers:control");
20
21
// Returns the port or throws an exception.
22
export async function start(name: NamedServerName): Promise<number> {
23
winston.debug(`start ${name}`);
24
const s = await status(name);
25
if (s.status === "running") {
26
winston.debug(`${name} is already running`);
27
return s.port;
28
}
29
const port = await getPort({ port: preferredPort(name) });
30
// For servers that need a basePath, they will use this one.
31
// Other servers (e.g., Pluto, code-server) that don't need
32
// a basePath because they use only relative URL's are accessed
33
// via .../project_id/server/${port}.
34
let ip = INFO.location.host ?? "127.0.0.1";
35
if (ip === "localhost") {
36
ip = "127.0.0.1";
37
}
38
// TODO that baseType should come from named-server-panel:SPEC[name].usesBasePath
39
const baseType = name === "rserver" ? "server" : "port";
40
const base = join(basePath, `/${project_id}/${baseType}/${name}`);
41
const cmd = await getCommand(name, ip, port, base);
42
winston.debug(`will start ${name} by running "${cmd}"`);
43
44
const p = await paths(name);
45
await writeFile(p.port, `${port}`);
46
await writeFile(p.command, `#!/bin/sh\n${cmd}\n`);
47
48
const child = exec(cmd, { cwd: process.env.HOME });
49
await writeFile(p.pid, `${child.pid}`);
50
return port;
51
}
52
53
async function getCommand(
54
name: NamedServerName,
55
ip: string,
56
port: number,
57
base: string,
58
): Promise<string> {
59
const { stdout, stderr } = await paths(name);
60
const spec = getSpec(name);
61
const cmd: string = await spec(ip, port, base);
62
return `${cmd} 1>${stdout} 2>${stderr}`;
63
}
64
65
// Returns the status and port (if defined).
66
export async function status(
67
name: NamedServerName,
68
): Promise<{ status: "running"; port: number } | { status: "stopped" }> {
69
const { pid, port } = await paths(name);
70
try {
71
const pidValue = parseInt((await readFile(pid)).toString());
72
// it might be running
73
process.kill(pidValue, 0); // throws error if NOT running
74
// it is running
75
const portValue = parseInt((await readFile(port)).toString());
76
// and there is a port file,
77
// and the port is a number.
78
if (!Number.isInteger(portValue)) {
79
throw Error("invalid port");
80
}
81
return { status: "running", port: portValue };
82
} catch (_err) {
83
// it's not running or the port isn't valid
84
return { status: "stopped" };
85
}
86
}
87
88
async function paths(name: NamedServerName): Promise<{
89
pid: string;
90
stderr: string;
91
stdout: string;
92
port: string;
93
command: string;
94
}> {
95
const path = join(data, "named_servers", name);
96
try {
97
await mkdir(path, { recursive: true });
98
} catch (_err) {
99
// probably already exists
100
}
101
return {
102
pid: join(path, "pid"),
103
stderr: join(path, "stderr"),
104
stdout: join(path, "stdout"),
105
port: join(path, "port"),
106
command: join(path, "command.sh"),
107
};
108
}
109
110
function preferredPort(name: NamedServerName): number | undefined {
111
const p = process.env[`COCALC_${name.toUpperCase()}_PORT`];
112
if (p == null) {
113
return p;
114
}
115
return parseInt(p);
116
}
117
118