Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/project/named-servers/control.ts
Views: 687
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import getPort from "get-port";6import { exec } from "node:child_process";7import { mkdir, readFile, writeFile } from "node:fs/promises";8import { join } from "node:path";910import basePath from "@cocalc/backend/base-path";11import { data } from "@cocalc/backend/data";12import { project_id } from "@cocalc/project/data";13import { INFO } from "@cocalc/project/info-json";14import { getLogger } from "@cocalc/project/logger";15import { NamedServerName } from "@cocalc/util/types/servers";16import getSpec from "./list";1718const winston = getLogger("named-servers:control");1920// Returns the port or throws an exception.21export async function start(name: NamedServerName): Promise<number> {22winston.debug(`start ${name}`);23const s = await status(name);24if (s.status === "running") {25winston.debug(`${name} is already running`);26return s.port;27}28const port = await getPort({ port: preferredPort(name) });29// For servers that need a basePath, they will use this one.30// Other servers (e.g., Pluto, code-server) that don't need31// a basePath because they use only relative URL's are accessed32// via .../project_id/server/${port}.33let ip = INFO.location.host ?? "127.0.0.1";34if (ip === "localhost") {35ip = "127.0.0.1";36}37// TODO that baseType should come from named-server-panel:SPEC[name].usesBasePath38const baseType = name === "rserver" ? "server" : "port";39const base = join(basePath, `/${project_id}/${baseType}/${name}`);40const cmd = await getCommand(name, ip, port, base);41winston.debug(`will start ${name} by running "${cmd}"`);4243const p = await paths(name);44await writeFile(p.port, `${port}`);45await writeFile(p.command, `#!/bin/sh\n${cmd}\n`);4647const child = exec(cmd, { cwd: process.env.HOME });48await writeFile(p.pid, `${child.pid}`);49return port;50}5152async function getCommand(53name: NamedServerName,54ip: string,55port: number,56base: string,57): Promise<string> {58const { stdout, stderr } = await paths(name);59const spec = getSpec(name);60const cmd: string = await spec(ip, port, base);61return `${cmd} 1>${stdout} 2>${stderr}`;62}6364// Returns the status and port (if defined).65export async function status(66name: NamedServerName,67): Promise<{ status: "running"; port: number } | { status: "stopped" }> {68const { pid, port } = await paths(name);69try {70const pidValue = parseInt((await readFile(pid)).toString());71// it might be running72process.kill(pidValue, 0); // throws error if NOT running73// it is running74const portValue = parseInt((await readFile(port)).toString());75// and there is a port file,76// and the port is a number.77if (!Number.isInteger(portValue)) {78throw Error("invalid port");79}80return { status: "running", port: portValue };81} catch (_err) {82// it's not running or the port isn't valid83return { status: "stopped" };84}85}8687async function paths(name: NamedServerName): Promise<{88pid: string;89stderr: string;90stdout: string;91port: string;92command: string;93}> {94const path = join(data, "named_servers", name);95try {96await mkdir(path, { recursive: true });97} catch (_err) {98// probably already exists99}100return {101pid: join(path, "pid"),102stderr: join(path, "stderr"),103stdout: join(path, "stdout"),104port: join(path, "port"),105command: join(path, "command.sh"),106};107}108109function preferredPort(name: NamedServerName): number | undefined {110const p = process.env[`COCALC_${name.toUpperCase()}_PORT`];111if (p == null) {112return p;113}114return parseInt(p);115}116117118