UNPKG

@sellable/install

Version:

One-command installer for Sellable MCP in Claude Code, Codex, and Hermes

89 lines (81 loc) • 2.98 kB
#!/usr/bin/env node import { spawn } from "node:child_process"; import { closeSync, constants, fstatSync, openSync, unlinkSync } from "node:fs"; import { buildRuntimeIdentityLaunchArgs } from "../lib/sellable-agent/runtime-boundary.mjs"; const APP_PATH = "/run/bootstrap/slack-app"; const BOT_PATH = "/run/bootstrap/slack-bot"; const SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/; function required(name) { const value = process.env[name]; if (typeof value !== "string" || !value) throw new Error(`missing ${name}`); return value; } function identity(name) { const value = required(name); if (!/^[1-9][0-9]{0,9}$/.test(value)) throw new Error("runtime identity rejected"); return value; } function inheritedSecret(path) { const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW); const info = fstatSync(fd); if (!info.isFile() || info.uid !== 0 || info.gid !== 0 || (info.mode & 0o777) !== 0o600 || info.size < 6 || info.size > 4096) { closeSync(fd); throw new Error("runtime Slack descriptor source rejected"); } unlinkSync(path); return fd; } async function run() { if (process.argv.length !== 2 || process.getuid?.() !== 0 || process.getgid?.() !== 0) { throw new Error("sandbox init identity rejected"); } const uid = identity("SELLABLE_AGENT_RUNTIME_UID"); const gid = identity("SELLABLE_AGENT_RUNTIME_GID"); const profileId = required("SELLABLE_AGENT_PROFILE_ID"); if (!SAFE_ID.test(profileId)) throw new Error("sandbox profile rejected"); const hermes = required("SELLABLE_AGENT_HERMES_EXECUTABLE"); const setpriv = required("SELLABLE_AGENT_SETPRIV_EXECUTABLE"); if (!hermes.startsWith("/") || !setpriv.startsWith("/") || setpriv !== "/usr/bin/setpriv") { throw new Error("sandbox executable rejected"); } const appFd = inheritedSecret(APP_PATH); const botFd = inheritedSecret(BOT_PATH); try { const args = buildRuntimeIdentityLaunchArgs({ uid, gid, profileId, hermesExecutablePath: hermes, }); const stdio = Array(10).fill("ignore"); stdio[1] = "inherit"; stdio[2] = "inherit"; stdio[8] = appFd; stdio[9] = botFd; const child = spawn(setpriv, args, { env: process.env, shell: false, stdio, }); closeSync(appFd); closeSync(botFd); const forwardTerm = () => { if (!child.killed) child.kill("SIGTERM"); }; const forwardInt = () => { if (!child.killed) child.kill("SIGINT"); }; process.once("SIGTERM", forwardTerm); process.once("SIGINT", forwardInt); process.exitCode = await new Promise((resolveExit, rejectExit) => { child.once("error", rejectExit); child.once("exit", (code, signal) => resolveExit(code ?? (signal ? 1 : 0))); }); } catch (error) { try { closeSync(appFd); } catch {} try { closeSync(botFd); } catch {} throw error; } } try { await run(); } catch { process.stderr.write("sellable-agent sandbox init failed closed\n"); process.exitCode = 1; }