141 lines
3.3 KiB
TypeScript
141 lines
3.3 KiB
TypeScript
import { SpawnOptions, spawn, spawnSync } from "child_process";
|
|
import * as os from "os";
|
|
import * as process from "process";
|
|
|
|
import * as appRoot from "app-root-path";
|
|
import { quote } from "shell-quote";
|
|
|
|
import { MIN_UID, MAX_UID } from "./users";
|
|
|
|
export interface Options extends SpawnOptions {
|
|
input?: string;
|
|
check?: boolean;
|
|
}
|
|
|
|
export interface Context {
|
|
uid: number;
|
|
uuid: string;
|
|
}
|
|
|
|
export const rijuSystemPrivileged = appRoot.resolve(
|
|
"system/out/riju-system-privileged"
|
|
);
|
|
|
|
const rubyVersion = (() => {
|
|
try {
|
|
return spawnSync("ruby", ["-e", "puts RUBY_VERSION"])
|
|
.stdout.toString()
|
|
.trim();
|
|
} catch (err) {
|
|
return null;
|
|
}
|
|
})();
|
|
|
|
function getEnv({ uid, uuid }: Context) {
|
|
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: process.env.LANG || "",
|
|
LC_ALL: process.env.LC_ALL || "",
|
|
LOGNAME: username,
|
|
PATH: path.join(":"),
|
|
PWD: cwd,
|
|
SHELL: "/usr/bin/bash",
|
|
TERM: "xterm-256color",
|
|
TMPDIR: `${cwd}`,
|
|
USER: username,
|
|
USERNAME: username,
|
|
};
|
|
}
|
|
|
|
function getEnvString(ctx: Context) {
|
|
return Object.entries(getEnv(ctx))
|
|
.map(([key, val]) => `${key}=${quote([val])}`)
|
|
.join(" ");
|
|
}
|
|
|
|
export async function run(
|
|
args: string[],
|
|
log: (msg: string) => void,
|
|
options?: Options
|
|
) {
|
|
options = options || {};
|
|
const input = options.input;
|
|
const check = options.check === undefined ? true : options.check;
|
|
delete options.input;
|
|
delete options.check;
|
|
const proc = spawn(args[0], args.slice(1), options);
|
|
if (typeof input === "string") {
|
|
proc.stdin!.end(input);
|
|
}
|
|
let output = "";
|
|
proc.stdout!.on("data", (data: Buffer) => {
|
|
output += `${data}`;
|
|
});
|
|
proc.stderr!.on("data", (data: Buffer) => {
|
|
output += `${data}`;
|
|
});
|
|
return await new Promise((resolve, reject) => {
|
|
proc.on("error", reject);
|
|
proc.on("close", (code: number, signal: string) => {
|
|
output = output.trim();
|
|
if (output) {
|
|
log(`Output from ${args[0]}:\n` + output);
|
|
}
|
|
if (code === 0 || !check) {
|
|
resolve(code);
|
|
} else {
|
|
reject(`command ${args[0]} failed with error code ${signal || code}`);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
export function privilegedUseradd(uid: number) {
|
|
return [rijuSystemPrivileged, "useradd", `${uid}`];
|
|
}
|
|
|
|
export function privilegedSetup({ uid, uuid }: Context) {
|
|
return [rijuSystemPrivileged, "setup", `${uid}`, uuid];
|
|
}
|
|
|
|
export function privilegedSpawn(ctx: Context, args: string[]) {
|
|
const { uid, uuid } = ctx;
|
|
return [
|
|
rijuSystemPrivileged,
|
|
"spawn",
|
|
`${uid}`,
|
|
uuid,
|
|
"sh",
|
|
"-c",
|
|
`exec env -i ${getEnvString(ctx)} "$@"`,
|
|
"--",
|
|
].concat(args);
|
|
}
|
|
|
|
export function privilegedTeardown({ uid, uuid }: Context) {
|
|
return [rijuSystemPrivileged, "teardown", `${uid}`, uuid];
|
|
}
|
|
|
|
export function bash(cmdline: string) {
|
|
if (!cmdline.match(/[;|&(){}=]/)) {
|
|
// Reduce number of subshells we generate, if we're just running a
|
|
// single command (no shell logic).
|
|
cmdline = "exec " + cmdline;
|
|
}
|
|
return ["bash", "-c", cmdline];
|
|
}
|