Preliminary containerization work
This commit is contained in:
		
							parent
							
								
									83208355d4
								
							
						
					
					
						commit
						b99d17bcd3
					
				
							
								
								
									
										4
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										4
									
								
								Makefile
								
								
								
								
							| 
						 | 
					@ -72,11 +72,13 @@ ifneq (,$(filter $(I),admin ci))
 | 
				
			||||||
	docker run -it --rm --hostname $(I) -v $(VOLUME_MOUNT):/src -v /var/run/docker.sock:/var/run/docker.sock -v $(HOME)/.aws:/var/riju/.aws -v $(HOME)/.docker:/var/riju/.docker -v $(HOME)/.ssh:/var/riju/.ssh -v $(HOME)/.terraform.d:/var/riju/.terraform.d -e AWS_REGION -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e DOCKER_USERNAME -e DOCKER_PASSWORD -e DEPLOY_SSH_PRIVATE_KEY -e DOCKER_REPO -e S3_BUCKET -e DOMAIN -e VOLUME_MOUNT=$(VOLUME_MOUNT) $(SHELL_PORTS) $(SHELL_ENV) --network host riju:$(I) $(BASH_CMD)
 | 
						docker run -it --rm --hostname $(I) -v $(VOLUME_MOUNT):/src -v /var/run/docker.sock:/var/run/docker.sock -v $(HOME)/.aws:/var/riju/.aws -v $(HOME)/.docker:/var/riju/.docker -v $(HOME)/.ssh:/var/riju/.ssh -v $(HOME)/.terraform.d:/var/riju/.terraform.d -e AWS_REGION -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e DOCKER_USERNAME -e DOCKER_PASSWORD -e DEPLOY_SSH_PRIVATE_KEY -e DOCKER_REPO -e S3_BUCKET -e DOMAIN -e VOLUME_MOUNT=$(VOLUME_MOUNT) $(SHELL_PORTS) $(SHELL_ENV) --network host riju:$(I) $(BASH_CMD)
 | 
				
			||||||
else ifeq ($(I),app)
 | 
					else ifeq ($(I),app)
 | 
				
			||||||
	docker run -it --rm --hostname $(I) $(SHELL_PORTS) $(SHELL_ENV) riju:$(I) $(BASH_CMD)
 | 
						docker run -it --rm --hostname $(I) $(SHELL_PORTS) $(SHELL_ENV) riju:$(I) $(BASH_CMD)
 | 
				
			||||||
else ifneq (,$(filter $(I),runtime lang))
 | 
					else ifneq (,$(filter $(I),base lang))
 | 
				
			||||||
ifeq ($(I),lang)
 | 
					ifeq ($(I),lang)
 | 
				
			||||||
	@: $${L}
 | 
						@: $${L}
 | 
				
			||||||
endif
 | 
					endif
 | 
				
			||||||
	docker run -it --rm --hostname $(I) -v $(VOLUME_MOUNT):/src --label riju-install-target=yes $(SHELL_PORTS) $(SHELL_ENV) riju:$(LANG_TAG) $(BASH_CMD)
 | 
						docker run -it --rm --hostname $(I) -v $(VOLUME_MOUNT):/src --label riju-install-target=yes $(SHELL_PORTS) $(SHELL_ENV) riju:$(LANG_TAG) $(BASH_CMD)
 | 
				
			||||||
 | 
					else ifeq ($(I),runtime)
 | 
				
			||||||
 | 
						docker run -it --rm --hostname $(I) -v $(VOLUME_MOUNT):/src -v /var/run/docker.sock:/var/run/docker.sock $(SHELL_PORTS) $(SHELL_ENV) riju:$(I) $(BASH_CMD)
 | 
				
			||||||
else
 | 
					else
 | 
				
			||||||
	docker run -it --rm --hostname $(I) -v $(VOLUME_MOUNT):/src $(SHELL_PORTS) $(SHELL_ENV) riju:$(I) $(BASH_CMD)
 | 
						docker run -it --rm --hostname $(I) -v $(VOLUME_MOUNT):/src $(SHELL_PORTS) $(SHELL_ENV) riju:$(I) $(BASH_CMD)
 | 
				
			||||||
endif
 | 
					endif
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,12 +6,10 @@ import pty from "node-pty";
 | 
				
			||||||
import pQueue from "p-queue";
 | 
					import pQueue from "p-queue";
 | 
				
			||||||
const PQueue = pQueue.default;
 | 
					const PQueue = pQueue.default;
 | 
				
			||||||
import rpc from "vscode-jsonrpc";
 | 
					import rpc from "vscode-jsonrpc";
 | 
				
			||||||
import { v4 as getUUID } from "uuid";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { langs } from "./langs.js";
 | 
					import { langs } from "./langs.js";
 | 
				
			||||||
import { borrowUser } from "./users.js";
 | 
					 | 
				
			||||||
import * as util from "./util.js";
 | 
					import * as util from "./util.js";
 | 
				
			||||||
import { bash } from "./util.js";
 | 
					import { bash, getUUID } from "./util.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const allSessions = new Set();
 | 
					const allSessions = new Set();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,16 +22,8 @@ export class Session {
 | 
				
			||||||
    return langs[this.lang];
 | 
					    return langs[this.lang];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  get uid() {
 | 
					 | 
				
			||||||
    return this.uidInfo.uid;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  returnUser = async () => {
 | 
					 | 
				
			||||||
    this.uidInfo && (await this.uidInfo.returnUser());
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  get context() {
 | 
					  get context() {
 | 
				
			||||||
    return { uid: this.uid, uuid: this.uuid };
 | 
					    return { uuid: this.uuid, lang: this.lang };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  log = (msg) => this.logPrimitive(`[${this.uuid}] ${msg}`);
 | 
					  log = (msg) => this.logPrimitive(`[${this.uuid}] ${msg}`);
 | 
				
			||||||
| 
						 | 
					@ -43,7 +33,7 @@ export class Session {
 | 
				
			||||||
    this.uuid = getUUID();
 | 
					    this.uuid = getUUID();
 | 
				
			||||||
    this.lang = lang;
 | 
					    this.lang = lang;
 | 
				
			||||||
    this.tearingDown = false;
 | 
					    this.tearingDown = false;
 | 
				
			||||||
    this.uidInfo = null;
 | 
					    this.container = null;
 | 
				
			||||||
    this.term = null;
 | 
					    this.term = null;
 | 
				
			||||||
    this.lsp = null;
 | 
					    this.lsp = null;
 | 
				
			||||||
    this.daemon = null;
 | 
					    this.daemon = null;
 | 
				
			||||||
| 
						 | 
					@ -57,24 +47,48 @@ export class Session {
 | 
				
			||||||
    return await util.run(args, this.log, options);
 | 
					    return await util.run(args, this.log, options);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  privilegedSetup = () => util.privilegedSetup(this.context);
 | 
					  privilegedSession = () => util.privilegedSession(this.context);
 | 
				
			||||||
  privilegedSpawn = (args) => util.privilegedSpawn(this.context, args);
 | 
					  privilegedWait = () => util.privilegedWait(this.context);
 | 
				
			||||||
  privilegedUseradd = () => util.privilegedUseradd(this.uid);
 | 
					  privilegedExec = (args) => util.privilegedExec(this.context, args);
 | 
				
			||||||
  privilegedTeardown = () => util.privilegedTeardown(this.context);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setup = async () => {
 | 
					  setup = async () => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      allSessions.add(this);
 | 
					      allSessions.add(this);
 | 
				
			||||||
      const { uid, returnUser } = await borrowUser();
 | 
					      const containerArgs = this.privilegedSession();
 | 
				
			||||||
      this.uidInfo = { uid, returnUser };
 | 
					      const containerProc = spawn(containerArgs[0], containerArgs.slice(1));
 | 
				
			||||||
      this.log(`Borrowed uid ${this.uid}`);
 | 
					      this.container = {
 | 
				
			||||||
      await this.run(this.privilegedSetup());
 | 
					        proc: containerProc,
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      for (const stream of [containerProc.stdout, containerProc.stderr]) {
 | 
				
			||||||
 | 
					        stream.on("data", (data) =>
 | 
				
			||||||
 | 
					          this.send({
 | 
				
			||||||
 | 
					            event: "serviceLog",
 | 
				
			||||||
 | 
					            service: "container",
 | 
				
			||||||
 | 
					            output: data.toString("utf8"),
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        containerProc.on("close", (code, signal) =>
 | 
				
			||||||
 | 
					          this.send({
 | 
				
			||||||
 | 
					            event: "serviceFailed",
 | 
				
			||||||
 | 
					            service: "container",
 | 
				
			||||||
 | 
					            error: `Exited with status ${signal || code}`,
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        containerProc.on("error", (err) =>
 | 
				
			||||||
 | 
					          this.send({
 | 
				
			||||||
 | 
					            event: "serviceFailed",
 | 
				
			||||||
 | 
					            service: "container",
 | 
				
			||||||
 | 
					            error: `${err}`,
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      await this.run(this.privilegedWait(this.context));
 | 
				
			||||||
      if (this.config.setup) {
 | 
					      if (this.config.setup) {
 | 
				
			||||||
        await this.run(this.privilegedSpawn(bash(this.config.setup)));
 | 
					        await this.run(this.privilegedExec(bash(this.config.setup)));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      await this.runCode();
 | 
					      await this.runCode();
 | 
				
			||||||
      if (this.config.daemon) {
 | 
					      if (this.config.daemon) {
 | 
				
			||||||
        const daemonArgs = this.privilegedSpawn(bash(this.config.daemon));
 | 
					        const daemonArgs = this.privilegedExec(bash(this.config.daemon));
 | 
				
			||||||
        const daemonProc = spawn(daemonArgs[0], daemonArgs.slice(1));
 | 
					        const daemonProc = spawn(daemonArgs[0], daemonArgs.slice(1));
 | 
				
			||||||
        this.daemon = {
 | 
					        this.daemon = {
 | 
				
			||||||
          proc: daemonProc,
 | 
					          proc: daemonProc,
 | 
				
			||||||
| 
						 | 
					@ -105,9 +119,9 @@ export class Session {
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (this.config.lsp) {
 | 
					      if (this.config.lsp) {
 | 
				
			||||||
        if (this.config.lsp.setup) {
 | 
					        if (this.config.lsp.setup) {
 | 
				
			||||||
          await this.run(this.privilegedSpawn(bash(this.config.lsp.setup)));
 | 
					          await this.run(this.privilegedExec(bash(this.config.lsp.setup)));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const lspArgs = this.privilegedSpawn(bash(this.config.lsp.start));
 | 
					        const lspArgs = this.privilegedExec(bash(this.config.lsp.start));
 | 
				
			||||||
        const lspProc = spawn(lspArgs[0], lspArgs.slice(1));
 | 
					        const lspProc = spawn(lspArgs[0], lspArgs.slice(1));
 | 
				
			||||||
        this.lsp = {
 | 
					        this.lsp = {
 | 
				
			||||||
          proc: lspProc,
 | 
					          proc: lspProc,
 | 
				
			||||||
| 
						 | 
					@ -252,7 +266,7 @@ export class Session {
 | 
				
			||||||
  writeCode = async (code) => {
 | 
					  writeCode = async (code) => {
 | 
				
			||||||
    if (this.config.main.includes("/")) {
 | 
					    if (this.config.main.includes("/")) {
 | 
				
			||||||
      await this.run(
 | 
					      await this.run(
 | 
				
			||||||
        this.privilegedSpawn([
 | 
					        this.privilegedExec([
 | 
				
			||||||
          "mkdir",
 | 
					          "mkdir",
 | 
				
			||||||
          "-p",
 | 
					          "-p",
 | 
				
			||||||
          path.dirname(`${this.homedir}/${this.config.main}`),
 | 
					          path.dirname(`${this.homedir}/${this.config.main}`),
 | 
				
			||||||
| 
						 | 
					@ -260,7 +274,7 @@ export class Session {
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await this.run(
 | 
					    await this.run(
 | 
				
			||||||
      this.privilegedSpawn([
 | 
					      this.privilegedExec([
 | 
				
			||||||
        "sh",
 | 
					        "sh",
 | 
				
			||||||
        "-c",
 | 
					        "-c",
 | 
				
			||||||
        `cat > ${path.resolve(this.homedir, this.config.main)}`,
 | 
					        `cat > ${path.resolve(this.homedir, this.config.main)}`,
 | 
				
			||||||
| 
						 | 
					@ -283,7 +297,7 @@ export class Session {
 | 
				
			||||||
      } = this.config;
 | 
					      } = this.config;
 | 
				
			||||||
      if (this.term) {
 | 
					      if (this.term) {
 | 
				
			||||||
        const pid = this.term.pty.pid;
 | 
					        const pid = this.term.pty.pid;
 | 
				
			||||||
        const args = this.privilegedSpawn(
 | 
					        const args = this.privilegedExec(
 | 
				
			||||||
          bash(`kill -SIGTERM ${pid}; sleep 1; kill -SIGKILL ${pid}`)
 | 
					          bash(`kill -SIGTERM ${pid}; sleep 1; kill -SIGKILL ${pid}`)
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        spawn(args[0], args.slice(1));
 | 
					        spawn(args[0], args.slice(1));
 | 
				
			||||||
| 
						 | 
					@ -310,7 +324,7 @@ export class Session {
 | 
				
			||||||
        code += suffix + "\n";
 | 
					        code += suffix + "\n";
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      await this.writeCode(code);
 | 
					      await this.writeCode(code);
 | 
				
			||||||
      const termArgs = this.privilegedSpawn(bash(cmdline));
 | 
					      const termArgs = this.privilegedExec(bash(cmdline));
 | 
				
			||||||
      const term = {
 | 
					      const term = {
 | 
				
			||||||
        pty: pty.spawn(termArgs[0], termArgs.slice(1), {
 | 
					        pty: pty.spawn(termArgs[0], termArgs.slice(1), {
 | 
				
			||||||
          name: "xterm-color",
 | 
					          name: "xterm-color",
 | 
				
			||||||
| 
						 | 
					@ -349,14 +363,14 @@ export class Session {
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (this.formatter) {
 | 
					      if (this.formatter) {
 | 
				
			||||||
        const pid = this.formatter.proc.pid;
 | 
					        const pid = this.formatter.proc.pid;
 | 
				
			||||||
        const args = this.privilegedSpawn(
 | 
					        const args = this.privilegedExec(
 | 
				
			||||||
          bash(`kill -SIGTERM ${pid}; sleep 1; kill -SIGKILL ${pid}`)
 | 
					          bash(`kill -SIGTERM ${pid}; sleep 1; kill -SIGKILL ${pid}`)
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        spawn(args[0], args.slice(1));
 | 
					        spawn(args[0], args.slice(1));
 | 
				
			||||||
        this.formatter.live = false;
 | 
					        this.formatter.live = false;
 | 
				
			||||||
        this.formatter = null;
 | 
					        this.formatter = null;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      const args = this.privilegedSpawn(bash(this.config.format.run));
 | 
					      const args = this.privilegedExec(bash(this.config.format.run));
 | 
				
			||||||
      const formatter = {
 | 
					      const formatter = {
 | 
				
			||||||
        proc: spawn(args[0], args.slice(1)),
 | 
					        proc: spawn(args[0], args.slice(1)),
 | 
				
			||||||
        live: true,
 | 
					        live: true,
 | 
				
			||||||
| 
						 | 
					@ -409,7 +423,7 @@ export class Session {
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ensure = async (cmd) => {
 | 
					  ensure = async (cmd) => {
 | 
				
			||||||
    const code = await this.run(this.privilegedSpawn(bash(cmd)), {
 | 
					    const code = await this.run(this.privilegedExec(bash(cmd)), {
 | 
				
			||||||
      check: false,
 | 
					      check: false,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    this.send({ event: "ensured", code });
 | 
					    this.send({ event: "ensured", code });
 | 
				
			||||||
| 
						 | 
					@ -422,11 +436,15 @@ export class Session {
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.log(`Tearing down session`);
 | 
					      this.log(`Tearing down session`);
 | 
				
			||||||
      this.tearingDown = true;
 | 
					      this.tearingDown = true;
 | 
				
			||||||
      allSessions.delete(this);
 | 
					      if (this.container) {
 | 
				
			||||||
      if (this.uidInfo) {
 | 
					        // SIGTERM should be sufficient as the command running in the
 | 
				
			||||||
        await this.run(this.privilegedTeardown());
 | 
					        // foreground is just 'tail -f /dev/null' which won't try to
 | 
				
			||||||
        await this.returnUser();
 | 
					        // block signals. Killing the foreground process (i.e. pid1)
 | 
				
			||||||
 | 
					        // should cause the Docker runtime to bring everything else
 | 
				
			||||||
 | 
					        // down in flames.
 | 
				
			||||||
 | 
					        this.container.proc.kill();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      allSessions.delete(this);
 | 
				
			||||||
      this.ws.terminate();
 | 
					      this.ws.terminate();
 | 
				
			||||||
    } catch (err) {
 | 
					    } catch (err) {
 | 
				
			||||||
      this.log(`Error during teardown`);
 | 
					      this.log(`Error during teardown`);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,9 +3,9 @@ import { promises as fs } from "fs";
 | 
				
			||||||
import process from "process";
 | 
					import process from "process";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { quote } from "shell-quote";
 | 
					import { quote } from "shell-quote";
 | 
				
			||||||
import { v4 as getUUID } from "uuid";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { borrowUser } from "./users.js";
 | 
					import { getUUID } from "./util.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  privilegedSetup,
 | 
					  privilegedSetup,
 | 
				
			||||||
  privilegedSpawn,
 | 
					  privilegedSpawn,
 | 
				
			||||||
| 
						 | 
					@ -29,9 +29,8 @@ async function main() {
 | 
				
			||||||
    die("environment variable unset: $L");
 | 
					    die("environment variable unset: $L");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  const uuid = getUUID();
 | 
					  const uuid = getUUID();
 | 
				
			||||||
  const { uid, returnUser } = await borrowUser(log);
 | 
					  await run(privilegedSetup({ uuid }), log);
 | 
				
			||||||
  await run(privilegedSetup({ uid, uuid }), log);
 | 
					  const args = privilegedSpawn({ uuid }, [
 | 
				
			||||||
  const args = privilegedSpawn({ uid, uuid }, [
 | 
					 | 
				
			||||||
    "bash",
 | 
					    "bash",
 | 
				
			||||||
    "-c",
 | 
					    "-c",
 | 
				
			||||||
    `exec env L='${lang}' bash --rcfile <(cat <<< ${quote([sandboxScript])})`,
 | 
					    `exec env L='${lang}' bash --rcfile <(cat <<< ${quote([sandboxScript])})`,
 | 
				
			||||||
| 
						 | 
					@ -43,7 +42,7 @@ async function main() {
 | 
				
			||||||
    proc.on("error", reject);
 | 
					    proc.on("error", reject);
 | 
				
			||||||
    proc.on("close", resolve);
 | 
					    proc.on("close", resolve);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  await run(privilegedTeardown({ uid, uuid }), log);
 | 
					  await run(privilegedTeardown({ uuid }), log);
 | 
				
			||||||
  await returnUser();
 | 
					  await returnUser();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,10 +5,10 @@ import _ from "lodash";
 | 
				
			||||||
import pQueue from "p-queue";
 | 
					import pQueue from "p-queue";
 | 
				
			||||||
const PQueue = pQueue.default;
 | 
					const PQueue = pQueue.default;
 | 
				
			||||||
import stripAnsi from "strip-ansi";
 | 
					import stripAnsi from "strip-ansi";
 | 
				
			||||||
import { v4 as getUUID } from "uuid";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as api from "./api.js";
 | 
					import * as api from "./api.js";
 | 
				
			||||||
import { langsPromise } from "./langs.js";
 | 
					import { langsPromise } from "./langs.js";
 | 
				
			||||||
 | 
					import { getUUID } from "./util.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let langs = {};
 | 
					let langs = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										115
									
								
								backend/users.js
								
								
								
								
							
							
						
						
									
										115
									
								
								backend/users.js
								
								
								
								
							| 
						 | 
					@ -1,115 +0,0 @@
 | 
				
			||||||
import { spawn } from "child_process";
 | 
					 | 
				
			||||||
import { promises as fs } from "fs";
 | 
					 | 
				
			||||||
import os from "os";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import AsyncLock from "async-lock";
 | 
					 | 
				
			||||||
import _ from "lodash";
 | 
					 | 
				
			||||||
import parsePasswd from "parse-passwd";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { asBool, privilegedUseradd, run, uuidRegexp } from "./util.js";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Keep in sync with system/src/riju-system-privileged.c
 | 
					 | 
				
			||||||
export const MIN_UID = 2000;
 | 
					 | 
				
			||||||
export const MAX_UID = 65000;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function validUID(uid) {
 | 
					 | 
				
			||||||
  return uid >= MIN_UID && uid < MAX_UID;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const CUR_UID = os.userInfo().uid;
 | 
					 | 
				
			||||||
const ASSUME_SINGLE_PROCESS = asBool(
 | 
					 | 
				
			||||||
  process.env.RIJU_ASSUME_SINGLE_PROCESS,
 | 
					 | 
				
			||||||
  false
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let initialized = false;
 | 
					 | 
				
			||||||
let nextUserToCreate = null;
 | 
					 | 
				
			||||||
let locallyBorrowedUsers = new Set();
 | 
					 | 
				
			||||||
let availableUsers = new Set();
 | 
					 | 
				
			||||||
let lock = new AsyncLock();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function getCreatedUsers() {
 | 
					 | 
				
			||||||
  return new Set(
 | 
					 | 
				
			||||||
    parsePasswd(await fs.readFile("/etc/passwd", "utf-8"))
 | 
					 | 
				
			||||||
      .map(({ uid }) => parseInt(uid))
 | 
					 | 
				
			||||||
      .filter((uid) => !isNaN(uid) && validUID(uid))
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function getActiveUsers() {
 | 
					 | 
				
			||||||
  let dirents;
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    dirents = await fs.readdir("/tmp/riju");
 | 
					 | 
				
			||||||
  } catch (err) {
 | 
					 | 
				
			||||||
    if (err.code === "ENOENT") {
 | 
					 | 
				
			||||||
      return new Set();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    throw err;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return new Set(
 | 
					 | 
				
			||||||
    (
 | 
					 | 
				
			||||||
      await Promise.all(
 | 
					 | 
				
			||||||
        dirents
 | 
					 | 
				
			||||||
          .filter((name) => name.match(uuidRegexp))
 | 
					 | 
				
			||||||
          .map((name) => fs.stat(`/tmp/riju/${name}`))
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
      .map(({ uid }) => uid)
 | 
					 | 
				
			||||||
      .filter(validUID)
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function createUser(log) {
 | 
					 | 
				
			||||||
  if (nextUserToCreate >= MAX_UID) {
 | 
					 | 
				
			||||||
    throw new Error("too many users");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  const uid = nextUserToCreate;
 | 
					 | 
				
			||||||
  await run(privilegedUseradd(uid), log);
 | 
					 | 
				
			||||||
  nextUserToCreate += 1;
 | 
					 | 
				
			||||||
  return uid;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function borrowUser(log) {
 | 
					 | 
				
			||||||
  return await lock.acquire("key", async () => {
 | 
					 | 
				
			||||||
    if (!initialized || !ASSUME_SINGLE_PROCESS) {
 | 
					 | 
				
			||||||
      const createdUsers = await getCreatedUsers();
 | 
					 | 
				
			||||||
      const activeUsers = await getActiveUsers();
 | 
					 | 
				
			||||||
      if (createdUsers.size > 0) {
 | 
					 | 
				
			||||||
        nextUserToCreate = _.max([...createdUsers]) + 1;
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        nextUserToCreate = MIN_UID;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      // If there are new users created, we want to make them
 | 
					 | 
				
			||||||
      // available (unless they are already active). Similarly, if
 | 
					 | 
				
			||||||
      // there are users that have become inactive, we want to make
 | 
					 | 
				
			||||||
      // them available (unless they are already borrowed locally).
 | 
					 | 
				
			||||||
      for (const user of createdUsers) {
 | 
					 | 
				
			||||||
        if (!activeUsers.has(user) && !locallyBorrowedUsers.has(user)) {
 | 
					 | 
				
			||||||
          availableUsers.add(user);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      // If there are users that have become active, we want to make
 | 
					 | 
				
			||||||
      // them unavailable.
 | 
					 | 
				
			||||||
      for (const user of activeUsers) {
 | 
					 | 
				
			||||||
        availableUsers.delete(user);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      initialized = true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (availableUsers.size === 0) {
 | 
					 | 
				
			||||||
      availableUsers.add(await createUser(log));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // https://stackoverflow.com/a/32539929/3538165
 | 
					 | 
				
			||||||
    const user = availableUsers.values().next().value;
 | 
					 | 
				
			||||||
    locallyBorrowedUsers.add(user);
 | 
					 | 
				
			||||||
    availableUsers.delete(user);
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      uid: user,
 | 
					 | 
				
			||||||
      returnUser: async () => {
 | 
					 | 
				
			||||||
        await lock.acquire("key", () => {
 | 
					 | 
				
			||||||
          locallyBorrowedUsers.delete(user);
 | 
					 | 
				
			||||||
          availableUsers.add(user);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -3,55 +3,12 @@ import os from "os";
 | 
				
			||||||
import process from "process";
 | 
					import process from "process";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { quote } from "shell-quote";
 | 
					import { quote } from "shell-quote";
 | 
				
			||||||
 | 
					import { v4 as getUUIDOrig } from "uuid";
 | 
				
			||||||
import { MIN_UID, MAX_UID } from "./users.js";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const rijuSystemPrivileged = "system/out/riju-system-privileged";
 | 
					export const rijuSystemPrivileged = "system/out/riju-system-privileged";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const rubyVersion = (() => {
 | 
					export function getUUID() {
 | 
				
			||||||
  try {
 | 
					  return getUUIDOrig().replace(/-/g, "");
 | 
				
			||||||
    return spawnSync("ruby", ["-e", "puts RUBY_VERSION"])
 | 
					 | 
				
			||||||
      .stdout.toString()
 | 
					 | 
				
			||||||
      .trim();
 | 
					 | 
				
			||||||
  } catch (err) {
 | 
					 | 
				
			||||||
    return null;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
})();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getEnv({ uid, uuid }) {
 | 
					 | 
				
			||||||
  const cwd = `/tmp/riju/${uuid}`;
 | 
					 | 
				
			||||||
  const path = [
 | 
					 | 
				
			||||||
    rubyVersion && `${cwd}/.gem/ruby/${rubyVersion}/bin`,
 | 
					 | 
				
			||||||
    `${cwd}/.local/bin`,
 | 
					 | 
				
			||||||
    `${cwd}/node_modules/.bin`,
 | 
					 | 
				
			||||||
    `/usr/local/sbin`,
 | 
					 | 
				
			||||||
    `/usr/local/bin`,
 | 
					 | 
				
			||||||
    `/usr/sbin`,
 | 
					 | 
				
			||||||
    `/usr/bin`,
 | 
					 | 
				
			||||||
    `/bin`,
 | 
					 | 
				
			||||||
  ].filter((x) => x);
 | 
					 | 
				
			||||||
  const username =
 | 
					 | 
				
			||||||
    uid >= MIN_UID && uid < MAX_UID ? `riju${uid}` : os.userInfo().username;
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    HOME: cwd,
 | 
					 | 
				
			||||||
    HOSTNAME: "riju",
 | 
					 | 
				
			||||||
    LANG: "C.UTF-8",
 | 
					 | 
				
			||||||
    LC_ALL: "C.UTF-8",
 | 
					 | 
				
			||||||
    LOGNAME: username,
 | 
					 | 
				
			||||||
    PATH: path.join(":"),
 | 
					 | 
				
			||||||
    PWD: cwd,
 | 
					 | 
				
			||||||
    SHELL: "/usr/bin/bash",
 | 
					 | 
				
			||||||
    TERM: "xterm-256color",
 | 
					 | 
				
			||||||
    TMPDIR: `${cwd}`,
 | 
					 | 
				
			||||||
    USER: username,
 | 
					 | 
				
			||||||
    USERNAME: username,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getEnvString(ctx) {
 | 
					 | 
				
			||||||
  return Object.entries(getEnv(ctx))
 | 
					 | 
				
			||||||
    .map(([key, val]) => `${key}=${quote([val])}`)
 | 
					 | 
				
			||||||
    .join(" ");
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function run(args, log, options) {
 | 
					export async function run(args, log, options) {
 | 
				
			||||||
| 
						 | 
					@ -87,30 +44,16 @@ export async function run(args, log, options) {
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function privilegedUseradd(uid) {
 | 
					export function privilegedSession({ uuid, lang }) {
 | 
				
			||||||
  return [rijuSystemPrivileged, "useradd", `${uid}`];
 | 
					  return [rijuSystemPrivileged, "session", uuid, lang];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function privilegedSetup({ uid, uuid }) {
 | 
					export function privilegedWait({ uuid }) {
 | 
				
			||||||
  return [rijuSystemPrivileged, "setup", `${uid}`, uuid];
 | 
					  return [rijuSystemPrivileged, "wait", uuid];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function privilegedSpawn(ctx, args) {
 | 
					export function privilegedExec({ uuid }, args) {
 | 
				
			||||||
  const { uid, uuid } = ctx;
 | 
					  return [rijuSystemPrivileged, "exec", uuid].concat(args);
 | 
				
			||||||
  return [
 | 
					 | 
				
			||||||
    rijuSystemPrivileged,
 | 
					 | 
				
			||||||
    "spawn",
 | 
					 | 
				
			||||||
    `${uid}`,
 | 
					 | 
				
			||||||
    uuid,
 | 
					 | 
				
			||||||
    "sh",
 | 
					 | 
				
			||||||
    "-c",
 | 
					 | 
				
			||||||
    `exec env -i ${getEnvString(ctx)} "$@"`,
 | 
					 | 
				
			||||||
    "--",
 | 
					 | 
				
			||||||
  ].concat(args);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function privilegedTeardown({ uid, uuid }) {
 | 
					 | 
				
			||||||
  return [rijuSystemPrivileged, "teardown", `${uid}`, uuid];
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function bash(cmdline) {
 | 
					export function bash(cmdline) {
 | 
				
			||||||
| 
						 | 
					@ -130,9 +73,6 @@ export const log = {
 | 
				
			||||||
  error: console.error,
 | 
					  error: console.error,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// https://gist.github.com/bugventure/f71337e3927c34132b9a
 | 
					 | 
				
			||||||
export const uuidRegexp = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function asBool(value, def) {
 | 
					export function asBool(value, def) {
 | 
				
			||||||
  if (def === undefined) {
 | 
					  if (def === undefined) {
 | 
				
			||||||
    throw new Error("asBool needs an explicit default value");
 | 
					    throw new Error("asBool needs an explicit default value");
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,17 +22,10 @@ COPY lib ./lib/
 | 
				
			||||||
COPY backend ./backend/
 | 
					COPY backend ./backend/
 | 
				
			||||||
COPY langs ./langs/
 | 
					COPY langs ./langs/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FROM ubuntu:rolling
 | 
					FROM riju:runtime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY docker/app/install.bash /tmp/
 | 
					 | 
				
			||||||
RUN /tmp/install.bash
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY docker/shared/my_init /usr/local/sbin/
 | 
					 | 
				
			||||||
ENTRYPOINT ["/usr/local/sbin/my_init", "--quiet", "--"]
 | 
					ENTRYPOINT ["/usr/local/sbin/my_init", "--quiet", "--"]
 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN useradd -p '!' -m -l -s /usr/bin/bash riju
 | 
					RUN useradd -p '!' -m -l -s /usr/bin/bash riju
 | 
				
			||||||
 | 
					 | 
				
			||||||
WORKDIR /src
 | 
					 | 
				
			||||||
COPY --chown=riju:riju --from=build /src ./
 | 
					COPY --chown=riju:riju --from=build /src ./
 | 
				
			||||||
RUN chown root:riju system/out/*-privileged && chmod a=,g=rx,u=rwxs system/out/*-privileged
 | 
					RUN chown root:riju system/out/*-privileged && chmod a=,g=rx,u=rwxs system/out/*-privileged
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,30 +0,0 @@
 | 
				
			||||||
#!/usr/bin/env bash
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
set -euxo pipefail
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export DEBIAN_FRONTEND=noninteractive
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
apt-get update
 | 
					 | 
				
			||||||
apt-get dist-upgrade -y
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
apt-get install -y curl gnupg lsb-release
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -
 | 
					 | 
				
			||||||
curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ubuntu_ver="$(lsb_release -rs)"
 | 
					 | 
				
			||||||
ubuntu_name="$(lsb_release -cs)"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
node_repo="$(curl -sS https://deb.nodesource.com/setup_current.x | grep NODEREPO= | grep -Eo 'node_[0-9]+\.x' | head -n1)"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
tee -a /etc/apt/sources.list.d/custom.list >/dev/null <<EOF
 | 
					 | 
				
			||||||
deb [arch=amd64] https://deb.nodesource.com/${node_repo} ${ubuntu_name} main
 | 
					 | 
				
			||||||
deb [arch=amd64] https://dl.yarnpkg.com/debian/ stable main
 | 
					 | 
				
			||||||
EOF
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
apt-get update
 | 
					 | 
				
			||||||
apt-get install -y make nodejs yarn
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
rm -rf /var/lib/apt/lists/*
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
rm "$0"
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					FROM ubuntu:rolling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY docker/base/install.bash /tmp/
 | 
				
			||||||
 | 
					RUN /tmp/install.bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WORKDIR /src
 | 
				
			||||||
 | 
					COPY docker/shared/my_init /usr/local/sbin/
 | 
				
			||||||
 | 
					ENTRYPOINT ["/usr/local/sbin/my_init", "--quiet", "--"]
 | 
				
			||||||
 | 
					CMD ["bash"]
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,164 @@
 | 
				
			||||||
 | 
					#!/usr/bin/env bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set -euxo pipefail
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					latest_release() {
 | 
				
			||||||
 | 
					    curl -sSL "https://api.github.com/repos/$1/releases/latest" | jq -r .tag_name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mkdir /tmp/riju-work
 | 
				
			||||||
 | 
					pushd /tmp/riju-work
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export DEBIAN_FRONTEND=noninteractive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dpkg --add-architecture i386
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					apt-get update
 | 
				
			||||||
 | 
					(yes || true) | unminimize
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					apt-get install -y curl gnupg lsb-release wget
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Ceylon
 | 
				
			||||||
 | 
					wget https://cacerts.digicert.com/DigiCertTLSRSASHA2562020CA1.crt.pem -O /usr/local/share/ca-certificates/DigiCertTLSRSASHA2562020CA1.crt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# D
 | 
				
			||||||
 | 
					wget https://letsencrypt.org/certs/lets-encrypt-r3.pem -O /usr/local/share/ca-certificates/lets-encrypt-r3.crt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					update-ca-certificates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ubuntu_ver="$(lsb_release -rs)"
 | 
				
			||||||
 | 
					ubuntu_name="$(lsb_release -cs)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cran_repo="$(curl -fsSL https://cran.r-project.org/bin/linux/ubuntu/ | grep -Eo 'cran[0-9]+' | head -n1)"
 | 
				
			||||||
 | 
					node_repo="$(curl -fsSL https://deb.nodesource.com/setup_current.x | grep NODEREPO= | grep -Eo 'node_[0-9]+\.x' | head -n1)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# .NET
 | 
				
			||||||
 | 
					wget "https://packages.microsoft.com/config/ubuntu/${ubuntu_ver}/packages-microsoft-prod.deb"
 | 
				
			||||||
 | 
					apt-get install ./packages-microsoft-prod.deb
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Ceylon
 | 
				
			||||||
 | 
					curl -fsSL https://downloads.ceylon-lang.org/apt/ceylon-debian-repo.gpg.key | apt-key add -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Crystal
 | 
				
			||||||
 | 
					curl -fsSL https://keybase.io/crystal/pgp_keys.asc | apt-key add -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Dart
 | 
				
			||||||
 | 
					curl -fsSL https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Hack
 | 
				
			||||||
 | 
					apt-key adv --keyserver keyserver.ubuntu.com --recv-keys B4112585D386EB94
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# MongoDB
 | 
				
			||||||
 | 
					curl -fsSL https://www.mongodb.org/static/pgp/server-4.4.asc | apt-key add -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Node.js
 | 
				
			||||||
 | 
					curl -fsSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# R
 | 
				
			||||||
 | 
					apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Yarn
 | 
				
			||||||
 | 
					curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tee -a /etc/apt/sources.list.d/custom.list >/dev/null <<EOF
 | 
				
			||||||
 | 
					# Ceylon
 | 
				
			||||||
 | 
					deb [arch=amd64] https://downloads.ceylon-lang.org/apt/ unstable main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Crystal
 | 
				
			||||||
 | 
					deb [arch=amd64] https://dist.crystal-lang.org/apt crystal main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Dart
 | 
				
			||||||
 | 
					deb [arch=amd64] https://storage.googleapis.com/download.dartlang.org/linux/debian stable main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Hack
 | 
				
			||||||
 | 
					deb [arch=amd64] https://dl.hhvm.com/ubuntu ${ubuntu_name} main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# MongoDB
 | 
				
			||||||
 | 
					deb [arch=amd64] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Node.js
 | 
				
			||||||
 | 
					deb [arch=amd64] https://deb.nodesource.com/${node_repo} ${ubuntu_name} main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# R
 | 
				
			||||||
 | 
					deb [arch=amd64] https://cloud.r-project.org/bin/linux/ubuntu ${ubuntu_name}-${cran_repo}/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Yarn
 | 
				
			||||||
 | 
					deb [arch=amd64] https://dl.yarnpkg.com/debian/ stable main
 | 
				
			||||||
 | 
					EOF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Work around brutal packaging error courtesy of Microsoft.
 | 
				
			||||||
 | 
					# Unfortunately, the Microsoft repo includes a duplicate version of
 | 
				
			||||||
 | 
					# the libodbc1 package whose version is not in sync with the one
 | 
				
			||||||
 | 
					# shipped by the corresponding release of Ubuntu. If this one happens
 | 
				
			||||||
 | 
					# to be newer, then it can cause horrifyingly difficult to diagnose
 | 
				
			||||||
 | 
					# errors later on because there's a conflict between the
 | 
				
			||||||
 | 
					# default-available versions of libodbc1 and libodbc1:i386, which has
 | 
				
			||||||
 | 
					# in the past surfaced as an inability to install dependencies for
 | 
				
			||||||
 | 
					# Erlang. Thanks Microsoft. Please don't. Anyway, solution is to pin
 | 
				
			||||||
 | 
					# this repository at a lower priority than the Ubuntu standard
 | 
				
			||||||
 | 
					# packages, so the correct version of libodbc1 gets installed by
 | 
				
			||||||
 | 
					# default.
 | 
				
			||||||
 | 
					tee -a /etc/apt/preferences.d/riju >/dev/null <<EOF
 | 
				
			||||||
 | 
					Package: *
 | 
				
			||||||
 | 
					Pin: origin packages.microsoft.com
 | 
				
			||||||
 | 
					Pin-Priority: 1
 | 
				
			||||||
 | 
					EOF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					apt-get update
 | 
				
			||||||
 | 
					apt-get install -y dctrl-tools
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					libicu="$(grep-aptavail -wF Package 'libicu[0-9]+' -s Package -n | head -n1)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					packages="
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# compilation tools
 | 
				
			||||||
 | 
					clang
 | 
				
			||||||
 | 
					g++
 | 
				
			||||||
 | 
					gcc
 | 
				
			||||||
 | 
					make
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# base languages
 | 
				
			||||||
 | 
					nodejs
 | 
				
			||||||
 | 
					ocaml
 | 
				
			||||||
 | 
					perl
 | 
				
			||||||
 | 
					python3
 | 
				
			||||||
 | 
					ruby
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# packaging tools
 | 
				
			||||||
 | 
					apt-file
 | 
				
			||||||
 | 
					dctrl-tools
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# basic utilities
 | 
				
			||||||
 | 
					bind9-dnsutils
 | 
				
			||||||
 | 
					less
 | 
				
			||||||
 | 
					git
 | 
				
			||||||
 | 
					htop
 | 
				
			||||||
 | 
					jq
 | 
				
			||||||
 | 
					make
 | 
				
			||||||
 | 
					man
 | 
				
			||||||
 | 
					moreutils
 | 
				
			||||||
 | 
					psmisc
 | 
				
			||||||
 | 
					ripgrep
 | 
				
			||||||
 | 
					strace
 | 
				
			||||||
 | 
					sudo
 | 
				
			||||||
 | 
					tmux
 | 
				
			||||||
 | 
					tree
 | 
				
			||||||
 | 
					vim
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# shared dependencies
 | 
				
			||||||
 | 
					${libicu}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					apt-get install -y $(sed 's/#.*//' <<< "${packages}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rm -rf /var/lib/apt/lists/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tee /etc/sudoers.d/90-riju >/dev/null <<"EOF"
 | 
				
			||||||
 | 
					%sudo ALL=(ALL:ALL) NOPASSWD: ALL
 | 
				
			||||||
 | 
					EOF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					popd
 | 
				
			||||||
 | 
					rm -rf /tmp/riju-work
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rm "$0"
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
FROM riju:runtime
 | 
					FROM riju:base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ARG LANG
 | 
					ARG LANG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,8 +2,8 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set -euxo pipefail
 | 
					set -euxo pipefail
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# See install.bash for the runtime image for much of the same, but
 | 
					# See install.bash for the base image for much of the same, but with
 | 
				
			||||||
# with more comments.
 | 
					# more comments.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mkdir /tmp/riju-work
 | 
					mkdir /tmp/riju-work
 | 
				
			||||||
pushd /tmp/riju-work
 | 
					pushd /tmp/riju-work
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,9 +3,10 @@ FROM ubuntu:rolling
 | 
				
			||||||
COPY docker/runtime/install.bash /tmp/
 | 
					COPY docker/runtime/install.bash /tmp/
 | 
				
			||||||
RUN /tmp/install.bash
 | 
					RUN /tmp/install.bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WORKDIR /src
 | 
					 | 
				
			||||||
COPY docker/shared/my_init docker/runtime/pid1.bash /usr/local/sbin/
 | 
					COPY docker/shared/my_init docker/runtime/pid1.bash /usr/local/sbin/
 | 
				
			||||||
ENTRYPOINT ["/usr/local/sbin/my_init", "--quiet", "--", "/usr/local/sbin/pid1.bash"]
 | 
					ENTRYPOINT ["/usr/local/sbin/my_init", "--quiet", "--", "/usr/local/sbin/pid1.bash"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WORKDIR /src
 | 
				
			||||||
CMD ["bash"]
 | 
					CMD ["bash"]
 | 
				
			||||||
EXPOSE 6119
 | 
					EXPOSE 6119
 | 
				
			||||||
EXPOSE 6120
 | 
					EXPOSE 6120
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,120 +11,32 @@ pushd /tmp/riju-work
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export DEBIAN_FRONTEND=noninteractive
 | 
					export DEBIAN_FRONTEND=noninteractive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dpkg --add-architecture i386
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
apt-get update
 | 
					apt-get update
 | 
				
			||||||
(yes || true) | unminimize
 | 
					(yes || true) | unminimize
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apt-get install -y curl gnupg lsb-release wget
 | 
					apt-get install -y curl gnupg lsb-release wget
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Ceylon
 | 
					curl -fsSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -
 | 
				
			||||||
wget https://cacerts.digicert.com/DigiCertTLSRSASHA2562020CA1.crt.pem -O /usr/local/share/ca-certificates/DigiCertTLSRSASHA2562020CA1.crt
 | 
					curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
 | 
				
			||||||
 | 
					curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# D
 | 
					 | 
				
			||||||
wget https://letsencrypt.org/certs/lets-encrypt-r3.pem -O /usr/local/share/ca-certificates/lets-encrypt-r3.crt
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
update-ca-certificates
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ubuntu_ver="$(lsb_release -rs)"
 | 
					 | 
				
			||||||
ubuntu_name="$(lsb_release -cs)"
 | 
					ubuntu_name="$(lsb_release -cs)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cran_repo="$(curl -fsSL https://cran.r-project.org/bin/linux/ubuntu/ | grep -Eo 'cran[0-9]+' | head -n1)"
 | 
					node_repo="$(curl -sS https://deb.nodesource.com/setup_current.x | grep NODEREPO= | grep -Eo 'node_[0-9]+\.x' | head -n1)"
 | 
				
			||||||
node_repo="$(curl -fsSL https://deb.nodesource.com/setup_current.x | grep NODEREPO= | grep -Eo 'node_[0-9]+\.x' | head -n1)"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# .NET
 | 
					 | 
				
			||||||
wget "https://packages.microsoft.com/config/ubuntu/${ubuntu_ver}/packages-microsoft-prod.deb"
 | 
					 | 
				
			||||||
apt-get install ./packages-microsoft-prod.deb
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Ceylon
 | 
					 | 
				
			||||||
curl -fsSL https://downloads.ceylon-lang.org/apt/ceylon-debian-repo.gpg.key | apt-key add -
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Crystal
 | 
					 | 
				
			||||||
curl -fsSL https://keybase.io/crystal/pgp_keys.asc | apt-key add -
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Dart
 | 
					 | 
				
			||||||
curl -fsSL https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Hack
 | 
					 | 
				
			||||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys B4112585D386EB94
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# MongoDB
 | 
					 | 
				
			||||||
curl -fsSL https://www.mongodb.org/static/pgp/server-4.4.asc | apt-key add -
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Node.js
 | 
					 | 
				
			||||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# R
 | 
					 | 
				
			||||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Yarn
 | 
					 | 
				
			||||||
curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
tee -a /etc/apt/sources.list.d/custom.list >/dev/null <<EOF
 | 
					tee -a /etc/apt/sources.list.d/custom.list >/dev/null <<EOF
 | 
				
			||||||
# Ceylon
 | 
					 | 
				
			||||||
deb [arch=amd64] https://downloads.ceylon-lang.org/apt/ unstable main
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Crystal
 | 
					 | 
				
			||||||
deb [arch=amd64] https://dist.crystal-lang.org/apt crystal main
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Dart
 | 
					 | 
				
			||||||
deb [arch=amd64] https://storage.googleapis.com/download.dartlang.org/linux/debian stable main
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Hack
 | 
					 | 
				
			||||||
deb [arch=amd64] https://dl.hhvm.com/ubuntu ${ubuntu_name} main
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# MongoDB
 | 
					 | 
				
			||||||
deb [arch=amd64] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Node.js
 | 
					 | 
				
			||||||
deb [arch=amd64] https://deb.nodesource.com/${node_repo} ${ubuntu_name} main
 | 
					deb [arch=amd64] https://deb.nodesource.com/${node_repo} ${ubuntu_name} main
 | 
				
			||||||
 | 
					 | 
				
			||||||
# R
 | 
					 | 
				
			||||||
deb [arch=amd64] https://cloud.r-project.org/bin/linux/ubuntu ${ubuntu_name}-${cran_repo}/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Yarn
 | 
					 | 
				
			||||||
deb [arch=amd64] https://dl.yarnpkg.com/debian/ stable main
 | 
					deb [arch=amd64] https://dl.yarnpkg.com/debian/ stable main
 | 
				
			||||||
 | 
					deb [arch=amd64] https://download.docker.com/linux/ubuntu ${ubuntu_name} stable
 | 
				
			||||||
EOF
 | 
					EOF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Work around brutal packaging error courtesy of Microsoft.
 | 
					 | 
				
			||||||
# Unfortunately, the Microsoft repo includes a duplicate version of
 | 
					 | 
				
			||||||
# the libodbc1 package whose version is not in sync with the one
 | 
					 | 
				
			||||||
# shipped by the corresponding release of Ubuntu. If this one happens
 | 
					 | 
				
			||||||
# to be newer, then it can cause horrifyingly difficult to diagnose
 | 
					 | 
				
			||||||
# errors later on because there's a conflict between the
 | 
					 | 
				
			||||||
# default-available versions of libodbc1 and libodbc1:i386, which has
 | 
					 | 
				
			||||||
# in the past surfaced as an inability to install dependencies for
 | 
					 | 
				
			||||||
# Erlang. Thanks Microsoft. Please don't. Anyway, solution is to pin
 | 
					 | 
				
			||||||
# this repository at a lower priority than the Ubuntu standard
 | 
					 | 
				
			||||||
# packages, so the correct version of libodbc1 gets installed by
 | 
					 | 
				
			||||||
# default.
 | 
					 | 
				
			||||||
tee -a /etc/apt/preferences.d/riju >/dev/null <<EOF
 | 
					 | 
				
			||||||
Package: *
 | 
					 | 
				
			||||||
Pin: origin packages.microsoft.com
 | 
					 | 
				
			||||||
Pin-Priority: 1
 | 
					 | 
				
			||||||
EOF
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
apt-get update
 | 
					 | 
				
			||||||
apt-get install -y dctrl-tools
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
libicu="$(grep-aptavail -wF Package 'libicu[0-9]+' -s Package -n | head -n1)"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
packages="
 | 
					packages="
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# compilation tools
 | 
					 | 
				
			||||||
clang
 | 
					 | 
				
			||||||
g++
 | 
					 | 
				
			||||||
gcc
 | 
					 | 
				
			||||||
make
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# base languages
 | 
					 | 
				
			||||||
nodejs
 | 
					 | 
				
			||||||
ocaml
 | 
					 | 
				
			||||||
perl
 | 
					 | 
				
			||||||
python3
 | 
					 | 
				
			||||||
ruby
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# project tools
 | 
					# project tools
 | 
				
			||||||
 | 
					clang
 | 
				
			||||||
 | 
					docker-ce-cli
 | 
				
			||||||
 | 
					make
 | 
				
			||||||
 | 
					nodejs
 | 
				
			||||||
yarn
 | 
					yarn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# packaging tools
 | 
					# packaging tools
 | 
				
			||||||
| 
						 | 
					@ -148,11 +60,9 @@ tmux
 | 
				
			||||||
tree
 | 
					tree
 | 
				
			||||||
vim
 | 
					vim
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# shared dependencies
 | 
					 | 
				
			||||||
${libicu}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"
 | 
					"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					apt-get update
 | 
				
			||||||
apt-get install -y $(sed 's/#.*//' <<< "${packages}")
 | 
					apt-get install -y $(sed 's/#.*//' <<< "${packages}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ver="$(latest_release watchexec/watchexec)"
 | 
					ver="$(latest_release watchexec/watchexec)"
 | 
				
			||||||
| 
						 | 
					@ -166,9 +76,6 @@ tee /etc/sudoers.d/90-riju >/dev/null <<"EOF"
 | 
				
			||||||
%sudo ALL=(ALL:ALL) NOPASSWD: ALL
 | 
					%sudo ALL=(ALL:ALL) NOPASSWD: ALL
 | 
				
			||||||
EOF
 | 
					EOF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mkdir -p /opt/riju/langs
 | 
					 | 
				
			||||||
touch /opt/riju/langs/.keep
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
popd
 | 
					popd
 | 
				
			||||||
rm -rf /tmp/riju-work
 | 
					rm -rf /tmp/riju-work
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,9 +19,7 @@ for src in system/src/*.c; do
 | 
				
			||||||
    out="${out/.c}"
 | 
					    out="${out/.c}"
 | 
				
			||||||
    verbosely clang -Wall -Wextra -Werror -std=c11 "${src}" -o "${out}"
 | 
					    verbosely clang -Wall -Wextra -Werror -std=c11 "${src}" -o "${out}"
 | 
				
			||||||
    if [[ "${out}" == *-privileged ]]; then
 | 
					    if [[ "${out}" == *-privileged ]]; then
 | 
				
			||||||
        if getent group riju >/dev/null; then
 | 
					        verbosely sudo chown root:riju "${out}"
 | 
				
			||||||
            sudo chown root:riju "${out}"
 | 
					        verbosely sudo chmod a=,g=rx,u=rwxs "${out}"
 | 
				
			||||||
        fi
 | 
					 | 
				
			||||||
        sudo chmod a=,g=rx,u=rwxs "${out}"
 | 
					 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
done
 | 
					done
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,19 +1,15 @@
 | 
				
			||||||
#define _GNU_SOURCE
 | 
					#define _GNU_SOURCE
 | 
				
			||||||
#include <errno.h>
 | 
					#include <errno.h>
 | 
				
			||||||
#include <grp.h>
 | 
					#include <grp.h>
 | 
				
			||||||
 | 
					#include <signal.h>
 | 
				
			||||||
#include <stdio.h>
 | 
					#include <stdio.h>
 | 
				
			||||||
#include <stdlib.h>
 | 
					#include <stdlib.h>
 | 
				
			||||||
#include <string.h>
 | 
					#include <string.h>
 | 
				
			||||||
#include <sys/stat.h>
 | 
					#include <sys/stat.h>
 | 
				
			||||||
#include <sys/types.h>
 | 
					#include <sys/types.h>
 | 
				
			||||||
 | 
					#include <time.h>
 | 
				
			||||||
#include <unistd.h>
 | 
					#include <unistd.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Keep in sync with backend/src/users.ts
 | 
					 | 
				
			||||||
const int MIN_UID = 2000;
 | 
					 | 
				
			||||||
const int MAX_UID = 65000;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int privileged;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void __attribute__ ((noreturn)) die(char *msg)
 | 
					void __attribute__ ((noreturn)) die(char *msg)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  fprintf(stderr, "%s\n", msg);
 | 
					  fprintf(stderr, "%s\n", msg);
 | 
				
			||||||
| 
						 | 
					@ -23,155 +19,136 @@ void __attribute__ ((noreturn)) die(char *msg)
 | 
				
			||||||
void die_with_usage()
 | 
					void die_with_usage()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  die("usage:\n"
 | 
					  die("usage:\n"
 | 
				
			||||||
      "  riju-system-privileged useradd UID\n"
 | 
					      "  riju-system-privileged session UUID LANG\n"
 | 
				
			||||||
      "  riju-system-privileged setup UID UUID\n"
 | 
					      "  riju-system-privileged wait UUID\n"
 | 
				
			||||||
      "  riju-system-privileged spawn UID UUID CMDLINE...\n"
 | 
					      "  riju-system-privileged exec UUID CMDLINE...");
 | 
				
			||||||
      "  riju-system-privileged teardown UID UUID");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int parseUID(char *str)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  if (!privileged)
 | 
					 | 
				
			||||||
    return -1;
 | 
					 | 
				
			||||||
  char *endptr;
 | 
					 | 
				
			||||||
  long uid = strtol(str, &endptr, 10);
 | 
					 | 
				
			||||||
  if (!*str || *endptr)
 | 
					 | 
				
			||||||
    die("uid must be an integer");
 | 
					 | 
				
			||||||
  if (uid < MIN_UID || uid >= MAX_UID)
 | 
					 | 
				
			||||||
    die("uid is out of range");
 | 
					 | 
				
			||||||
  return uid;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
char *parseUUID(char *uuid)
 | 
					char *parseUUID(char *uuid)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  if (!*uuid)
 | 
					  if (strnlen(uuid, 33) != 32)
 | 
				
			||||||
    die("illegal uuid");
 | 
					    die("illegal uuid");
 | 
				
			||||||
  for (char *ptr = uuid; *ptr; ++ptr)
 | 
					  for (char *ptr = uuid; *ptr; ++ptr)
 | 
				
			||||||
    if (!((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= '0' && *ptr <= '9') || *ptr == '-'))
 | 
					    if (!((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= '0' && *ptr <= '9')))
 | 
				
			||||||
      die("illegal uuid");
 | 
					      die("illegal uuid");
 | 
				
			||||||
  return uuid;
 | 
					  return uuid;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void useradd(int uid)
 | 
					char *parseLang(char *lang) {
 | 
				
			||||||
{
 | 
					  size_t len = strnlen(lang, 65);
 | 
				
			||||||
  if (!privileged)
 | 
					  if (len == 0 || len > 64)
 | 
				
			||||||
    die("useradd not allowed without root privileges");
 | 
					    die("illegal language name");
 | 
				
			||||||
  char *cmdline;
 | 
					  return lang;
 | 
				
			||||||
  if (asprintf(&cmdline, "groupadd -g %1$d riju%1$d", uid) < 0)
 | 
					 | 
				
			||||||
    die("asprintf failed");
 | 
					 | 
				
			||||||
  int status = system(cmdline);
 | 
					 | 
				
			||||||
  if (status != 0)
 | 
					 | 
				
			||||||
    die("groupadd failed");
 | 
					 | 
				
			||||||
  if (asprintf(&cmdline, "useradd -M -N -l -r -u %1$d -g %1$d -p '!' -s /usr/bin/bash riju%1$d", uid) < 0)
 | 
					 | 
				
			||||||
    die("asprintf failed");
 | 
					 | 
				
			||||||
  status = system(cmdline);
 | 
					 | 
				
			||||||
  if (status != 0)
 | 
					 | 
				
			||||||
    die("useradd failed");
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void spawn(int uid, char *uuid, char **cmdline)
 | 
					void session(char *uuid, char *lang)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  char *cwd;
 | 
					  char *image, *container;
 | 
				
			||||||
  if (asprintf(&cwd, "/tmp/riju/%s", uuid) < 0)
 | 
					  if (asprintf(&image, "riju:lang-%s", lang) < 0)
 | 
				
			||||||
    die("asprintf failed");
 | 
					    die("asprintf failed");
 | 
				
			||||||
  if (chdir(cwd) < 0)
 | 
					  if (asprintf(&container, "riju-session-%s", uuid) < 0)
 | 
				
			||||||
    die("chdir failed");
 | 
					    die("asprintf failed");
 | 
				
			||||||
  if (privileged) {
 | 
					  char *argv[] = {
 | 
				
			||||||
    if (setgid(uid) < 0)
 | 
					    "docker",
 | 
				
			||||||
      die("setgid failed");
 | 
					    "run",
 | 
				
			||||||
    if (setgroups(0, NULL) < 0)
 | 
					    "--rm",
 | 
				
			||||||
      die("setgroups failed");
 | 
					    "-e", "HOME=/home/riju",
 | 
				
			||||||
    if (setuid(uid) < 0)
 | 
					    "-e", "HOSTNAME=riju",
 | 
				
			||||||
      die("setuid failed");
 | 
					    "-e", "LANG=C.UTF-8",
 | 
				
			||||||
  }
 | 
					    "-e", "LC_ALL=C.UTF-8",
 | 
				
			||||||
  umask(077);
 | 
					    "-e", "LOGNAME=riju",
 | 
				
			||||||
  execvp(cmdline[0], cmdline);
 | 
					    "-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/bin",
 | 
				
			||||||
 | 
					    "-e", "PWD=/home/riju/src",
 | 
				
			||||||
 | 
					    "-e", "SHELL=/usr/bin/bash",
 | 
				
			||||||
 | 
					    "-e", "TERM=xterm-256color",
 | 
				
			||||||
 | 
					    "-e", "TMPDIR=/tmp",
 | 
				
			||||||
 | 
					    "-e", "USER=riju",
 | 
				
			||||||
 | 
					    "-e", "USERNAME=riju",
 | 
				
			||||||
 | 
					    "--hostname", "riju",
 | 
				
			||||||
 | 
					    "--name", container,
 | 
				
			||||||
 | 
					    image, "tail", "-f", "/dev/null", NULL,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  execvp(argv[0], argv);
 | 
				
			||||||
  die("execvp failed");
 | 
					  die("execvp failed");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void setup(int uid, char *uuid)
 | 
					void wait_alarm(int signum)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  char *cmdline;
 | 
					  (void)signum;
 | 
				
			||||||
  if (asprintf(&cmdline, privileged
 | 
					  die("container did not come up within 1 second");
 | 
				
			||||||
               ? "install -d -o riju%1$d -g riju%1$d -m 700 /tmp/riju/%2$s"
 | 
					 | 
				
			||||||
               : "install -d -m 700 /tmp/riju/%2$s", uid, uuid) < 0)
 | 
					 | 
				
			||||||
    die("asprintf failed");
 | 
					 | 
				
			||||||
  int status = system(cmdline);
 | 
					 | 
				
			||||||
  if (status != 0)
 | 
					 | 
				
			||||||
    die("install failed");
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void teardown(int uid, char *uuid)
 | 
					void wait(char *uuid)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  char *cmdline;
 | 
					  char *cmdline;
 | 
				
			||||||
  int status;
 | 
					  if (asprintf(&cmdline, "docker inspect riju-session-%s", uuid) < 0)
 | 
				
			||||||
  char *users;
 | 
					 | 
				
			||||||
  if (uid >= MIN_UID && uid < MAX_UID) {
 | 
					 | 
				
			||||||
    if (asprintf(&users, "%d", uid) < 0)
 | 
					 | 
				
			||||||
      die("asprintf failed");
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    cmdline = "getent passwd | grep -Eo '^riju[0-9]{4}' | paste -s -d, - | tr -d '\n'";
 | 
					 | 
				
			||||||
    FILE *fp = popen(cmdline, "r");
 | 
					 | 
				
			||||||
    if (fp == NULL)
 | 
					 | 
				
			||||||
      die("popen failed");
 | 
					 | 
				
			||||||
    static char buf[(MAX_UID - MIN_UID) * 9];
 | 
					 | 
				
			||||||
    if (fgets(buf, sizeof(buf), fp) == NULL) {
 | 
					 | 
				
			||||||
      if (feof(fp))
 | 
					 | 
				
			||||||
        users = NULL;
 | 
					 | 
				
			||||||
      else {
 | 
					 | 
				
			||||||
        die("fgets failed");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else
 | 
					 | 
				
			||||||
      users = buf;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (users != NULL) {
 | 
					 | 
				
			||||||
    if (asprintf(&cmdline, "while pkill -9 --uid %1$s; do sleep 0.01; done", users) < 0)
 | 
					 | 
				
			||||||
      die("asprintf failed");
 | 
					 | 
				
			||||||
    status = system(cmdline);
 | 
					 | 
				
			||||||
    if (status != 0 && status != 256)
 | 
					 | 
				
			||||||
      die("pkill failed");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (asprintf(&cmdline, "rm -rf /tmp/riju/%s", uuid) < 0)
 | 
					 | 
				
			||||||
    die("asprintf failed");
 | 
					    die("asprintf failed");
 | 
				
			||||||
  status = system(cmdline);
 | 
					  struct timespec ts;
 | 
				
			||||||
  if (status != 0)
 | 
					  ts.tv_sec = 0;
 | 
				
			||||||
    die("rm failed");
 | 
					  ts.tv_nsec = 1000 * 1000 * 10;
 | 
				
			||||||
 | 
					  signal(SIGALRM, wait_alarm);
 | 
				
			||||||
 | 
					  alarm(1);
 | 
				
			||||||
 | 
					  while (1) {
 | 
				
			||||||
 | 
					    FILE *proc = popen(cmdline, "r");
 | 
				
			||||||
 | 
					    if (proc == NULL)
 | 
				
			||||||
 | 
					      die("popen failed");
 | 
				
			||||||
 | 
					    int status = pclose(proc);
 | 
				
			||||||
 | 
					    if (status < 0)
 | 
				
			||||||
 | 
					      die("pclose failed");
 | 
				
			||||||
 | 
					    if (WEXITSTATUS(status) == 0)
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    int rv = nanosleep(&ts, NULL);
 | 
				
			||||||
 | 
					    if (rv != 0 && rv != EINTR)
 | 
				
			||||||
 | 
					      die("nanosleep failed");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void exec(char *uuid, int argc, char **cmdline)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  char *container;
 | 
				
			||||||
 | 
					  if (asprintf(&container, "riju-session-%s", uuid) < 0)
 | 
				
			||||||
 | 
					    die("asprintf failed");
 | 
				
			||||||
 | 
					  char *argvPrefix[] = {
 | 
				
			||||||
 | 
					    "docker",
 | 
				
			||||||
 | 
					    "exec",
 | 
				
			||||||
 | 
					    "-it",
 | 
				
			||||||
 | 
					    container,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  char **argv = malloc(sizeof(argvPrefix) + (argc + 1) * sizeof(char *));
 | 
				
			||||||
 | 
					  if (argv == NULL)
 | 
				
			||||||
 | 
					    die("malloc failed");
 | 
				
			||||||
 | 
					  memcpy(argv, argvPrefix, sizeof(argvPrefix));
 | 
				
			||||||
 | 
					  memcpy((void *)argv + sizeof(argvPrefix), cmdline, argc * sizeof(char *));
 | 
				
			||||||
 | 
					  argv[sizeof(argvPrefix) + argc * sizeof(char *)] = NULL;
 | 
				
			||||||
 | 
					  execvp(argv[0], argv);
 | 
				
			||||||
 | 
					  die("execvp failed");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int main(int argc, char **argv)
 | 
					int main(int argc, char **argv)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  int code = setuid(0);
 | 
					  if (setuid(0) != 0)
 | 
				
			||||||
  if (code != 0 && code != -EPERM)
 | 
					 | 
				
			||||||
    die("setuid failed");
 | 
					    die("setuid failed");
 | 
				
			||||||
  privileged = code == 0;
 | 
					 | 
				
			||||||
  if (argc < 2)
 | 
					  if (argc < 2)
 | 
				
			||||||
    die_with_usage();
 | 
					    die_with_usage();
 | 
				
			||||||
  if (!strcmp(argv[1], "useradd")) {
 | 
					  if (!strcmp(argv[1], "session")) {
 | 
				
			||||||
 | 
					    if (argc != 4)
 | 
				
			||||||
 | 
					      die_with_usage();
 | 
				
			||||||
 | 
					    char *uuid = parseUUID(argv[2]);
 | 
				
			||||||
 | 
					    char *lang = parseLang(argv[3]);
 | 
				
			||||||
 | 
					    session(uuid, lang);
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (!strcmp(argv[1], "wait")) {
 | 
				
			||||||
    if (argc != 3)
 | 
					    if (argc != 3)
 | 
				
			||||||
      die_with_usage();
 | 
					      die_with_usage();
 | 
				
			||||||
    useradd(parseUID(argv[2]));
 | 
					    char *uuid = parseUUID(argv[2]);
 | 
				
			||||||
 | 
					    wait(uuid);
 | 
				
			||||||
    return 0;
 | 
					    return 0;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (!strcmp(argv[1], "spawn")) {
 | 
					  if (!strcmp(argv[1], "exec")) {
 | 
				
			||||||
    if (argc < 5)
 | 
					    if (argc < 4)
 | 
				
			||||||
      die_with_usage();
 | 
					      die_with_usage();
 | 
				
			||||||
    spawn(parseUID(argv[2]), parseUUID(argv[3]), &argv[4]);
 | 
					    exec(parseUUID(argv[2]), argc, &argv[3]);
 | 
				
			||||||
    return 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (!strcmp(argv[1], "setup")) {
 | 
					 | 
				
			||||||
    if (argc != 4)
 | 
					 | 
				
			||||||
      die_with_usage();
 | 
					 | 
				
			||||||
    int uid = parseUID(argv[2]);
 | 
					 | 
				
			||||||
    char *uuid = parseUUID(argv[3]);
 | 
					 | 
				
			||||||
    setup(uid, uuid);
 | 
					 | 
				
			||||||
    return 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (!strcmp(argv[1], "teardown")) {
 | 
					 | 
				
			||||||
    if (argc != 4)
 | 
					 | 
				
			||||||
      die_with_usage();
 | 
					 | 
				
			||||||
    int uid = strcmp(argv[2], "*") ? parseUID(argv[2]) : -1;
 | 
					 | 
				
			||||||
    char *uuid = strcmp(argv[3], "*") ? parseUUID(argv[3]) : "*";
 | 
					 | 
				
			||||||
    teardown(uid, uuid);
 | 
					 | 
				
			||||||
    return 0;
 | 
					    return 0;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  die_with_usage();
 | 
					  die_with_usage();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,10 +28,7 @@ async function main() {
 | 
				
			||||||
  const hash = await hashDockerfile(
 | 
					  const hash = await hashDockerfile(
 | 
				
			||||||
    "lang",
 | 
					    "lang",
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "riju:runtime": await getLocalImageLabel(
 | 
					      "riju:base": await getLocalImageLabel("riju:base", "riju.image-hash"),
 | 
				
			||||||
        "riju:runtime",
 | 
					 | 
				
			||||||
        "riju.image-hash"
 | 
					 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      salt: {
 | 
					      salt: {
 | 
				
			||||||
| 
						 | 
					@ -52,7 +49,7 @@ async function main() {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    if (debug) {
 | 
					    if (debug) {
 | 
				
			||||||
      await runCommand(
 | 
					      await runCommand(
 | 
				
			||||||
        `docker run -it --rm -e LANG=${lang} -w /tmp/riju-work --network host riju:runtime`
 | 
					        `docker run -it --rm -e LANG=${lang} -w /tmp/riju-work --network host base:runtime`
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      await runCommand(
 | 
					      await runCommand(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@ import _ from "lodash";
 | 
				
			||||||
import { getLocalImageDigest, getLocalImageLabel } from "./docker-util.js";
 | 
					import { getLocalImageDigest, getLocalImageLabel } from "./docker-util.js";
 | 
				
			||||||
import { runCommand } from "./util.js";
 | 
					import { runCommand } from "./util.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Given a string like "runtime" that identifies the relevant
 | 
					// Given a string like "base" that identifies the relevant
 | 
				
			||||||
// Dockerfile, read it from disk and parse it into a list of commands.
 | 
					// Dockerfile, read it from disk and parse it into a list of commands.
 | 
				
			||||||
async function parseDockerfile(name) {
 | 
					async function parseDockerfile(name) {
 | 
				
			||||||
  const contents = await fs.readFile(`docker/${name}/Dockerfile`, "utf-8");
 | 
					  const contents = await fs.readFile(`docker/${name}/Dockerfile`, "utf-8");
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -149,9 +149,7 @@ async function planDebianPackages(opts) {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            clauses.push(`make installs L=${lang}`);
 | 
					            clauses.push(`make installs L=${lang}`);
 | 
				
			||||||
            clauses.push("make test");
 | 
					            clauses.push("make test");
 | 
				
			||||||
            await runCommand(
 | 
					            await runCommand(`make shell I=base CMD="${clauses.join(" && ")}"`);
 | 
				
			||||||
              `make shell I=runtime CMD="${clauses.join(" && ")}"`
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          await runCommand(`make upload L=${lang} T=${type}`);
 | 
					          await runCommand(`make upload L=${lang} T=${type}`);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
| 
						 | 
					@ -186,12 +184,12 @@ async function computePlan() {
 | 
				
			||||||
    "ubuntu:rolling": await getLocalImageDigest("ubuntu:rolling"),
 | 
					    "ubuntu:rolling": await getLocalImageDigest("ubuntu:rolling"),
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  const packaging = await planDockerImage("packaging", dependentHashes);
 | 
					  const packaging = await planDockerImage("packaging", dependentHashes);
 | 
				
			||||||
  const runtime = await planDockerImage("runtime", dependentHashes);
 | 
					  const base = await planDockerImage("base", dependentHashes);
 | 
				
			||||||
  const packages = await planDebianPackages({
 | 
					  const packages = await planDebianPackages({
 | 
				
			||||||
    deps: [packaging.id, runtime.id],
 | 
					    deps: [packaging.id, base.id],
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  const composite = await planDockerImage("composite", dependentHashes, {
 | 
					  const composite = await planDockerImage("composite", dependentHashes, {
 | 
				
			||||||
    deps: [runtime.id, ...packages.map(({ id }) => id)],
 | 
					    deps: [base.id, ...packages.map(({ id }) => id)],
 | 
				
			||||||
    hashOpts: {
 | 
					    hashOpts: {
 | 
				
			||||||
      salt: {
 | 
					      salt: {
 | 
				
			||||||
        packageHashes: packages.map(({ desired }) => desired).sort(),
 | 
					        packageHashes: packages.map(({ desired }) => desired).sort(),
 | 
				
			||||||
| 
						 | 
					@ -202,7 +200,7 @@ async function computePlan() {
 | 
				
			||||||
  const app = await planDockerImage("app", dependentHashes, {
 | 
					  const app = await planDockerImage("app", dependentHashes, {
 | 
				
			||||||
    deps: [composite.id, compile.id],
 | 
					    deps: [composite.id, compile.id],
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  return [packaging, runtime, ...packages, composite, compile, app];
 | 
					  return [packaging, base, ...packages, composite, compile, app];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function printTable(data, headers) {
 | 
					function printTable(data, headers) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue