smc-hub
Version:
CoCalc: Backend webserver component
284 lines (280 loc) • 14.1 kB
JavaScript
"use strict";
/*
multi-user: a multi-user Linux system where the hub runs as root,
so can create and delete user accounts, etc.
There is some security and isolation between projects, coming from
different operating system users.
This is mainly used for cocalc-docker, which is a deployment of
CoCalc running in a single docker container, with one hub running
as root.
This **executes some basic shell commands** (e.g., useradd, rsync)
to start and stop the project, copy files between projects, etc.
This code is very similar to single-user.ts, except with some
small modifications due to having to create and delete Linux users.
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
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 __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var util_1 = require("./util");
var base_1 = require("./base");
var logger_1 = __importDefault(require("smc-hub/logger"));
var misc_1 = require("smc-util-node/misc");
var winston = logger_1.default("project-control:single-user");
var MAX_START_TIME_MS = 30000;
var MAX_STOP_TIME_MS = 20000;
var Project = /** @class */ (function (_super) {
__extends(Project, _super);
function Project(project_id) {
var _this = _super.call(this, project_id) || this;
_this.HOME = util_1.homePath(_this.project_id);
_this.uid = misc_1.getUid(_this.project_id);
return _this;
}
Project.prototype.state = function () {
return __awaiter(this, void 0, void 0, function () {
var state;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (this.stateChanging != null) {
return [2 /*return*/, this.stateChanging];
}
return [4 /*yield*/, util_1.getState(this.HOME)];
case 1:
state = _a.sent();
winston.debug("got state of " + this.project_id + " = " + JSON.stringify(state));
this.saveStateToDatabase(state);
return [2 /*return*/, state];
}
});
});
};
Project.prototype.status = function () {
return __awaiter(this, void 0, void 0, function () {
var status;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, util_1.getStatus(this.HOME)];
case 1:
status = _a.sent();
// TODO: don't include secret token in log message.
winston.debug("got status of " + this.project_id + " = " + JSON.stringify(status));
this.saveStatusToDatabase(status);
return [2 /*return*/, status];
}
});
});
};
Project.prototype.start = function () {
return __awaiter(this, void 0, void 0, function () {
var HOME, env;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (this.stateChanging != null)
return [2 /*return*/];
winston.info("start " + this.project_id);
HOME = this.HOME;
return [4 /*yield*/, util_1.isProjectRunning(HOME)];
case 1:
if (!_a.sent()) return [3 /*break*/, 3];
winston.debug("start -- already running");
return [4 /*yield*/, this.saveStateToDatabase({ state: "running" })];
case 2:
_a.sent();
return [2 /*return*/];
case 3:
_a.trys.push([3, , 14, 16]);
this.stateChanging = { state: "starting" };
return [4 /*yield*/, this.saveStateToDatabase(this.stateChanging)];
case 4:
_a.sent();
return [4 /*yield*/, this.siteLicenseHook()];
case 5:
_a.sent();
return [4 /*yield*/, util_1.mkdir(HOME, { recursive: true })];
case 6:
_a.sent();
return [4 /*yield*/, util_1.createUser(this.project_id)];
case 7:
_a.sent();
return [4 /*yield*/, util_1.chown(HOME, this.uid)];
case 8:
_a.sent();
return [4 /*yield*/, util_1.ensureConfFilesExists(HOME, this.uid)];
case 9:
_a.sent();
return [4 /*yield*/, util_1.getEnvironment(this.project_id)];
case 10:
env = _a.sent();
winston.debug("start " + this.project_id + ": env = " + JSON.stringify(env));
// Setup files
return [4 /*yield*/, util_1.setupDataPath(HOME, this.uid)];
case 11:
// Setup files
_a.sent();
// Fork and launch project server daemon
return [4 /*yield*/, util_1.launchProjectDaemon(env, this.uid)];
case 12:
// Fork and launch project server daemon
_a.sent();
return [4 /*yield*/, this.wait({
until: function () { return __awaiter(_this, void 0, void 0, function () {
var status;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, util_1.isProjectRunning(this.HOME)];
case 1:
if (!(_a.sent())) {
return [2 /*return*/, false];
}
return [4 /*yield*/, this.status()];
case 2:
status = _a.sent();
return [2 /*return*/, !!status.secret_token && !!status["hub-server.port"]];
}
});
}); },
maxTime: MAX_START_TIME_MS,
})];
case 13:
_a.sent();
return [3 /*break*/, 16];
case 14:
this.stateChanging = undefined;
// ensure state valid
return [4 /*yield*/, this.state()];
case 15:
// ensure state valid
_a.sent();
return [7 /*endfinally*/];
case 16: return [2 /*return*/];
}
});
});
};
Project.prototype.stop = function () {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (this.stateChanging != null)
return [2 /*return*/];
winston.info("stop ", this.project_id);
return [4 /*yield*/, util_1.isProjectRunning(this.HOME)];
case 1:
if (!!(_a.sent())) return [3 /*break*/, 3];
return [4 /*yield*/, this.saveStateToDatabase({ state: "opened" })];
case 2:
_a.sent();
return [2 /*return*/];
case 3:
_a.trys.push([3, , 7, 9]);
this.stateChanging = { state: "stopping" };
return [4 /*yield*/, this.saveStateToDatabase(this.stateChanging)];
case 4:
_a.sent();
return [4 /*yield*/, util_1.deleteUser(this.project_id)];
case 5:
_a.sent();
return [4 /*yield*/, this.wait({
until: function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, util_1.isProjectRunning(this.HOME)];
case 1: return [2 /*return*/, !(_a.sent())];
}
}); }); },
maxTime: MAX_STOP_TIME_MS,
})];
case 6:
_a.sent();
return [3 /*break*/, 9];
case 7:
this.stateChanging = undefined;
// ensure state valid in database
return [4 /*yield*/, this.state()];
case 8:
// ensure state valid in database
_a.sent();
return [7 /*endfinally*/];
case 9: return [2 /*return*/];
}
});
});
};
Project.prototype.copyPath = function (opts) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
winston.debug("copyPath ", this.project_id, opts);
return [4 /*yield*/, util_1.copyPath(opts, this.project_id, opts.target_project_id ? misc_1.getUid(opts.target_project_id) : undefined)];
case 1:
_a.sent();
return [2 /*return*/, ""];
}
});
});
};
return Project;
}(base_1.BaseProject));
function get(project_id) {
var _a;
return (_a = base_1.getProject(project_id)) !== null && _a !== void 0 ? _a : new Project(project_id);
}
exports.default = get;
//# sourceMappingURL=multi-user.js.map