UNPKG

smc-hub

Version:

CoCalc: Backend webserver component

612 lines 26.2 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.copyPath = exports.ensureConfFilesExists = exports.getStatus = exports.getState = exports.getEnvironment = exports.sanitizedEnv = exports.deleteUser = exports.createUser = exports.launchProjectDaemon = exports.setupDataPath = exports.isProjectRunning = exports.getProjectPID = exports.getUsername = exports.homePath = exports.dataPath = exports.chown = exports.mkdir = void 0; var util_1 = require("util"); var path_1 = require("path"); var child_process_1 = require("child_process"); var fs = __importStar(require("fs")); var data_1 = require("smc-util-node/data"); var misc_1 = require("smc-util/misc"); var async_utils_1 = require("smc-util/async-utils"); var logger_1 = __importDefault(require("smc-hub/logger")); var misc_2 = require("smc-util-node/misc"); var base_path_1 = __importDefault(require("smc-util-node/base-path")); var database_1 = require("smc-hub/servers/database"); var winston = logger_1.default("project-control:util"); exports.mkdir = util_1.promisify(fs.mkdir); var readFile = util_1.promisify(fs.readFile); var stat = util_1.promisify(fs.stat); var copyFile = util_1.promisify(fs.copyFile); var rm = util_1.promisify(fs.rm); function chown(path, uid) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, util_1.promisify(fs.chown)(path, uid, uid)]; case 1: _a.sent(); return [2 /*return*/]; } }); }); } exports.chown = chown; function dataPath(HOME) { return path_1.join(HOME, ".smc"); } exports.dataPath = dataPath; function homePath(project_id) { return path_1.join(data_1.projects, project_id); } exports.homePath = homePath; function getUsername(project_id) { return project_id.split("-").join(""); } exports.getUsername = getUsername; function pidIsRunning(pid) { try { process.kill(pid, 0); return true; } catch (_err) { return false; } } function pidFile(HOME) { return path_1.join(dataPath(HOME), "project.pid"); } // throws error if no such file function getProjectPID(HOME) { return __awaiter(this, void 0, void 0, function () { var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: _a = parseInt; return [4 /*yield*/, readFile(pidFile(HOME))]; case 1: return [2 /*return*/, _a.apply(void 0, [(_b.sent()).toString()])]; } }); }); } exports.getProjectPID = getProjectPID; function isProjectRunning(HOME) { return __awaiter(this, void 0, void 0, function () { var pid, err_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); return [4 /*yield*/, getProjectPID(HOME)]; case 1: pid = _a.sent(); //winston.debug(`isProjectRunning(HOME="${HOME}") -- pid=${pid}`); return [2 /*return*/, pidIsRunning(pid)]; case 2: err_1 = _a.sent(); //winston.debug(`isProjectRunning(HOME="${HOME}") -- no pid ${err}`); // err would happen if file doesn't exist, which means nothing to do. return [2 /*return*/, false]; case 3: return [2 /*return*/]; } }); }); } exports.isProjectRunning = isProjectRunning; function setupDataPath(HOME, uid) { return __awaiter(this, void 0, void 0, function () { var data; return __generator(this, function (_a) { switch (_a.label) { case 0: data = dataPath(HOME); winston.debug("setup \"" + data + "\"..."); return [4 /*yield*/, rm(data, { recursive: true, force: true })]; case 1: _a.sent(); return [4 /*yield*/, exports.mkdir(data)]; case 2: _a.sent(); if (!(uid != null)) return [3 /*break*/, 4]; return [4 /*yield*/, chown(data, uid)]; case 3: _a.sent(); _a.label = 4; case 4: return [2 /*return*/]; } }); }); } exports.setupDataPath = setupDataPath; function launchProjectDaemon(env, uid) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: winston.debug("launching project daemon at \"" + env.HOME + "\"..."); return [4 /*yield*/, util_1.promisify(function (cb) { var child = child_process_1.spawn("npx", ["cocalc-project", "--daemon"], { env: env, cwd: path_1.join(data_1.root, "smc-project"), uid: uid, gid: uid, }); child.on("error", function (err) { cb(err); }); child.on("exit", function (code) { cb(code); }); })()]; case 1: _a.sent(); return [2 /*return*/]; } }); }); } exports.launchProjectDaemon = launchProjectDaemon; function exec(command, verbose) { return __awaiter(this, void 0, void 0, function () { var output; return __generator(this, function (_a) { switch (_a.label) { case 0: winston.debug("exec '" + command + "'"); return [4 /*yield*/, util_1.promisify(child_process_1.exec)(command)]; case 1: output = _a.sent(); if (verbose) { winston.debug("output: " + JSON.stringify(output)); } return [2 /*return*/, output]; } }); }); } function createUser(project_id) { return __awaiter(this, void 0, void 0, function () { var username, _1, uid; return __generator(this, function (_a) { switch (_a.label) { case 0: username = getUsername(project_id); _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, exec("/usr/sbin/userdel " + username)]; case 2: _a.sent(); // this also deletes the group return [3 /*break*/, 4]; case 3: _1 = _a.sent(); return [3 /*break*/, 4]; case 4: uid = "" + misc_2.getUid(project_id); winston.debug("createUser: adding group"); return [4 /*yield*/, exec("/usr/sbin/groupadd -g " + uid + " -o " + username, true)]; case 5: _a.sent(); winston.debug("createUser: adding user"); return [4 /*yield*/, exec("/usr/sbin/useradd -u " + uid + " -g " + uid + " -o " + username + " -d " + homePath(project_id) + " -s /bin/bash", true)]; case 6: _a.sent(); return [2 /*return*/]; } }); }); } exports.createUser = createUser; function deleteUser(project_id) { return __awaiter(this, void 0, void 0, function () { var username, uid, _2; return __generator(this, function (_a) { switch (_a.label) { case 0: username = getUsername(project_id); uid = "" + misc_2.getUid(project_id); return [4 /*yield*/, exec("pkill -9 -u " + uid)]; case 1: _a.sent(); _a.label = 2; case 2: _a.trys.push([2, 4, , 5]); return [4 /*yield*/, exec("/usr/sbin/userdel " + username)]; case 3: _a.sent(); // this also deletes the group return [3 /*break*/, 5]; case 4: _2 = _a.sent(); return [3 /*break*/, 5]; case 5: return [2 /*return*/]; } }); }); } exports.deleteUser = deleteUser; function sanitizedEnv(env) { var e_1, _a; var env2 = __assign({}, env); try { // Remove some potentially confusing env variables for (var _b = __values([ "PGDATA", "PGHOST", "NODE_ENV", "NODE_OPTIONS", "BASE_PATH", "PORT", "DATA", ]), _c = _b.next(); !_c.done; _c = _b.next()) { var key = _c.value; delete env2[key]; } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_1) throw e_1.error; } } for (var key in env2) { if (key.startsWith("COCALC_") || env2[key] == null) { delete env2[key]; } } return env2; } exports.sanitizedEnv = sanitizedEnv; function getEnvironment(project_id) { return __awaiter(this, void 0, void 0, function () { var extra, extra_env, USER, HOME; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, async_utils_1.callback2(database_1.database.get_project_extra_env, { project_id: project_id })]; case 1: extra = _a.sent(); extra_env = Buffer.from(JSON.stringify(extra !== null && extra !== void 0 ? extra : {})).toString("base64"); USER = getUsername(project_id); HOME = homePath(project_id); return [2 /*return*/, __assign(__assign({}, sanitizedEnv(process.env)), { HOME: HOME, BASE_PATH: base_path_1.default, DATA: dataPath(HOME), // important to reset the COCALC_ vars since server env has own in a project COCALC_PROJECT_ID: project_id, COCALC_USERNAME: USER, USER: USER, COCALC_EXTRA_ENV: extra_env, PATH: HOME + "/bin:" + HOME + "/.local/bin:" + process.env.PATH, })]; } }); }); } exports.getEnvironment = getEnvironment; function getState(HOME) { return __awaiter(this, void 0, void 0, function () { var err_2; var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: winston.debug("getState(\"" + HOME + "\")"); _b.label = 1; case 1: _b.trys.push([1, 3, , 4]); _a = { ip: "localhost" }; return [4 /*yield*/, isProjectRunning(HOME)]; case 2: return [2 /*return*/, (_a.state = (_b.sent()) ? "running" : "opened", _a.time = new Date(), _a)]; case 3: err_2 = _b.sent(); return [2 /*return*/, { error: "" + err_2, time: new Date(), state: "opened", }]; case 4: return [2 /*return*/]; } }); }); } exports.getState = getState; function getStatus(HOME) { return __awaiter(this, void 0, void 0, function () { var data, status, _a, _b, path, val, pid, _err_1, e_2_1; var e_2, _c; return __generator(this, function (_d) { switch (_d.label) { case 0: winston.debug("getStatus(\"" + HOME + "\")"); data = dataPath(HOME); status = {}; return [4 /*yield*/, isProjectRunning(HOME)]; case 1: if (!(_d.sent())) { return [2 /*return*/, status]; } _d.label = 2; case 2: _d.trys.push([2, 9, 10, 11]); _a = __values([ "project.pid", "hub-server.port", "browser-server.port", "sage_server.port", "sage_server.pid", "secret_token", ]), _b = _a.next(); _d.label = 3; case 3: if (!!_b.done) return [3 /*break*/, 8]; path = _b.value; _d.label = 4; case 4: _d.trys.push([4, 6, , 7]); return [4 /*yield*/, readFile(path_1.join(data, path))]; case 5: val = (_d.sent()).toString().trim(); if (path.endsWith(".pid")) { pid = parseInt(val); if (pidIsRunning(pid)) { status[path] = pid; } } else if (path.endsWith(".port")) { status[path] = parseInt(val); } else { status[path] = val; } return [3 /*break*/, 7]; case 6: _err_1 = _d.sent(); return [3 /*break*/, 7]; case 7: _b = _a.next(); return [3 /*break*/, 3]; case 8: return [3 /*break*/, 11]; case 9: e_2_1 = _d.sent(); e_2 = { error: e_2_1 }; return [3 /*break*/, 11]; case 10: try { if (_b && !_b.done && (_c = _a.return)) _c.call(_a); } finally { if (e_2) throw e_2.error; } return [7 /*endfinally*/]; case 11: return [2 /*return*/, status]; } }); }); } exports.getStatus = getStatus; function ensureConfFilesExists(HOME, uid) { return __awaiter(this, void 0, void 0, function () { var _a, _b, path, target, _3, source, err_3, e_3_1; var e_3, _c; return __generator(this, function (_d) { switch (_d.label) { case 0: _d.trys.push([0, 13, 14, 15]); _a = __values(["bashrc", "bash_profile"]), _b = _a.next(); _d.label = 1; case 1: if (!!_b.done) return [3 /*break*/, 12]; path = _b.value; target = path_1.join(HOME, "." + path); _d.label = 2; case 2: _d.trys.push([2, 4, , 11]); return [4 /*yield*/, stat(target)]; case 3: _d.sent(); return [3 /*break*/, 11]; case 4: _3 = _d.sent(); source = path_1.join(data_1.root, "smc_pyutil/smc_pyutil/templates", process.platform, path); _d.label = 5; case 5: _d.trys.push([5, 9, , 10]); return [4 /*yield*/, copyFile(source, target)]; case 6: _d.sent(); if (!(uid != null)) return [3 /*break*/, 8]; return [4 /*yield*/, chown(target, uid)]; case 7: _d.sent(); _d.label = 8; case 8: return [3 /*break*/, 10]; case 9: err_3 = _d.sent(); winston.error("ensureConfFilesExists -- " + err_3); return [3 /*break*/, 10]; case 10: return [3 /*break*/, 11]; case 11: _b = _a.next(); return [3 /*break*/, 1]; case 12: return [3 /*break*/, 15]; case 13: e_3_1 = _d.sent(); e_3 = { error: e_3_1 }; return [3 /*break*/, 15]; case 14: try { if (_b && !_b.done && (_c = _a.return)) _c.call(_a); } finally { if (e_3) throw e_3.error; } return [7 /*endfinally*/]; case 15: return [2 /*return*/]; } }); }); } exports.ensureConfFilesExists = ensureConfFilesExists; // Copy a path using rsync and the specified options // on the local filesystem. (TODO) When running as root, // we can also specify the user to change the target // ownership of the files to. // NOTE: the wait_until_done and scheduled CopyOptions // are not implemented at all here. function copyPath(opts, project_id, target_uid) { var _a, _b; return __awaiter(this, void 0, void 0, function () { var path, overwrite_newer, delete_missing, backup, timeout, bwlimit, target_project_id, target_path, sourceHome, source_abspath, targetHome, target_abspath, stats, isDir, args; return __generator(this, function (_c) { switch (_c.label) { case 0: winston.info("copyPath(target=\"" + project_id + "\"): opts=" + JSON.stringify(opts)); path = opts.path, overwrite_newer = opts.overwrite_newer, delete_missing = opts.delete_missing, backup = opts.backup, timeout = opts.timeout, bwlimit = opts.bwlimit; if (path == null) { // typescript already enforces this... throw Error("path must be specified"); } target_project_id = (_a = opts.target_project_id) !== null && _a !== void 0 ? _a : project_id; target_path = (_b = opts.target_path) !== null && _b !== void 0 ? _b : path; // check that both UUID's are valid if (!misc_1.is_valid_uuid_string(project_id)) { throw Error("project_id=" + project_id + " is invalid"); } if (!misc_1.is_valid_uuid_string(target_project_id)) { throw Error("target_project_id=" + target_project_id + " is invalid"); } sourceHome = homePath(project_id); source_abspath = path_1.resolve(path_1.join(sourceHome, path)); if (!source_abspath.startsWith(sourceHome)) { throw Error("source path must be contained in project home dir"); } targetHome = homePath(target_project_id); target_abspath = path_1.resolve(path_1.join(targetHome, target_path)); if (!target_abspath.startsWith(targetHome)) { throw Error("target path must be contained in target project home dir"); } // check for trivial special case. if (source_abspath == target_abspath) { return [2 /*return*/]; } return [4 /*yield*/, stat(source_abspath)]; case 1: stats = _c.sent(); isDir = stats.isDirectory(); args = ["-zaxs", "--omit-link-times"]; if (!overwrite_newer) { args.push("--update"); } if (backup) { args.push("--backup"); } if (delete_missing) { // IMPORTANT: newly created files will be deleted even if overwrite_newer is true args.push("--delete"); } if (bwlimit) { args.push("--bwlimit=" + bwlimit); } if (timeout) { args.push("--timeout=" + timeout); } if (target_uid && target_project_id != project_id) { // change target ownership on copy; only do this if explicitly requested and needed. args.push("--chown=" + target_uid + ":" + target_uid); } args.push("--ignore-errors"); args.push(source_abspath + (isDir ? "/" : "")); args.push(target_abspath + (isDir ? "/" : "")); // do the copy! winston.info("rsync " + args.join(" ")); return [4 /*yield*/, child_process_1.spawn("rsync", args)]; case 2: _c.sent(); return [2 /*return*/]; } }); }); } exports.copyPath = copyPath; //# sourceMappingURL=util.js.map