/** * @file server-s3-system.test.js * @description S3 plan, P2 (one Linux user per account), with every sudo and * systemctl call faked: the system-unit service manager (host-owned env file, * sudo for start/stop/restart, pure-fs staleness), service-manager picking it, * Quit stopping it through sudo, the Linux-account layer (server.conf, name * rule, the exact provision argv), user create provisioning FIRST, disable * ending the logins first (and locking only an account that exists), and a * member's bootstrap doing nothing. * @author Nguyễn Ngọc Trí Vĩ */ const { describe, it, before, after, beforeEach } = require("node:test"); const assert = require("node:assert/strict"); const path = require("node:path"); const fs = require("node:fs"); const os = require("node:os"); const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "server-s3-system-")); process.env.DASHBOARD_DB_PATH = path.join(TMP, "dashboard.db"); process.env.DASHBOARD_DATA_DIR = path.join(TMP, "data"); process.env.AGENTHUB_SYSTEM_UNIT_DIR = path.join(TMP, "units"); process.env.AGENTHUB_SERVER_CONF = path.join(TMP, "server.conf"); // `lin` has a Linux account; `pre` (made before S3) and `Old.Name` do not. process.env.AGENTHUB_PASSWD = path.join(TMP, "passwd"); fs.writeFileSync(process.env.AGENTHUB_PASSWD, "ah-lin:x:5101:5101::/:/bin/sh\n"); const sys = require("../../scripts/systemd-system-service"); const linux = require("../lib/linux-accounts"); const auth = require("../lib/auth"); const { db } = require("../db"); const UNIT = path.join(process.env.AGENTHUB_SYSTEM_UNIT_DIR, "agenthub.service"); const writeUnit = () => { fs.mkdirSync(path.dirname(UNIT), { recursive: true }); fs.writeFileSync(UNIT, `[Unit]\n${sys.MARKER}\n`); }; const writeConf = (uid = 4242) => { linux.__resetConfCache(); fs.writeFileSync( process.env.AGENTHUB_SERVER_CONF, `HOST_USER=host\nHOST_UID=${uid}\nUSERS_ROOT=/srv/agenthub/users\nPORT=4820\n` ); }; after(() => { sys.__reset(); delete process.env.AGENTHUB_MODE; fs.rmSync(TMP, { recursive: true, force: true }); }); describe("the system-unit service manager", () => { let calls; beforeEach(() => { calls = []; fs.rmSync(path.dirname(UNIT), { recursive: true, force: true }); fs.rmSync(sys.envPath(), { force: true }); sys.__setExecImpl( (args) => { calls.push(["systemctl", ...args]); return { status: 0, stdout: args[0] === "is-active" ? "active\n" : "enabled\n" }; }, (args) => { calls.push(["sudo", "-n", "systemctl", ...args]); return { status: 0 }; } ); }); it("is unavailable, and changes nothing, without its unit", () => { assert.equal(sys.unitIsOurs("agenthub"), false); assert.equal(sys.systemctlAvailable(), false); assert.equal(sys.writeUserService({ argv: ["/n", "/r/server/index.js"] }), "unavailable"); assert.equal(sys.restartUserService("agenthub"), "unavailable"); assert.deepEqual(calls, []); }); it("repoints by rewriting the host-owned env file, never the unit", () => { writeUnit(); const unitBefore = fs.readFileSync(UNIT, "utf8"); const unit = { argv: ["/usr/bin/node", "/plugins/v2/server/index.js"], env: { NODE_ENV: "production" }, }; assert.equal(sys.writeUserService(unit), "written"); assert.equal(sys.writeUserService(unit), "unchanged"); const env = fs.readFileSync(sys.envPath(), "utf8"); assert.match(env, /^AGENTHUB_NODE=\/usr\/bin\/node$/m); assert.match(env, /^AGENTHUB_ROOT=\/plugins\/v2$/m); assert.match(env, /^NODE_ENV=production$/m); assert.equal(fs.statSync(sys.envPath()).mode & 0o777, 0o600); assert.equal(fs.readFileSync(UNIT, "utf8"), unitBefore); assert.equal(sys.unitRoot("agenthub"), "/plugins/v2"); assert.equal(sys.unitIsStale("agenthub", "/plugins/v2"), false); assert.equal(sys.unitIsStale("agenthub", "/plugins/v3"), true); assert.ok(!calls.some((c) => c[0] === "sudo"), "no root needed to repoint"); }); it("restarts, starts and enables only through sudo, restart without blocking", () => { writeUnit(); assert.equal(sys.restartUserService("agenthub"), "restarted"); assert.equal(sys.setUnitEnabled("agenthub", false), "ok"); sys.installUserService({ name: "agenthub", argv: ["/n", "/r/server/index.js"] }); const sudo = calls.filter((c) => c[0] === "sudo").map((c) => c.slice(3).join(" ")); assert.deepEqual(sudo, [ "restart --no-block agenthub.service", "disable agenthub.service", "start --no-block agenthub.service", ]); assert.deepEqual(sys.stopArgv("agenthub"), [ "sudo", ["-n", "systemctl", "stop", "--no-block", "agenthub.service"], ]); }); it("is what service-manager picks while the unit exists", () => { const load = () => { delete require.cache[require.resolve("../../scripts/service-manager")]; return require("../../scripts/service-manager"); }; if (process.platform !== "linux") return; fs.rmSync(UNIT, { force: true }); assert.notEqual(load().kind, "systemd-system"); writeUnit(); assert.equal(load().kind, "systemd-system"); }); it("lets Quit stop it through sudo, never by SIGTERM", () => { const { stopServer } = require("../lib/quit"); const spawned = []; let killed = false; stopServer({ svc: { ...sys, systemctlAvailable: () => true, unitIsOurs: () => true, unitIsActive: () => true, }, spawn: (cmd, args) => { spawned.push([cmd, ...args]); return { unref() {} }; }, kill: () => (killed = true), }); assert.deepEqual(spawned, [ ["sudo", "-n", "systemctl", "stop", "--no-block", "agenthub.service"], ]); assert.equal(killed, false); }); }); describe("Linux accounts", () => { let argvs; let failNext; before(() => { linux.__setRunImpl((argv) => { argvs.push(argv); if (failNext) { failNext = false; throw Object.assign(new Error("exit 4"), { stderr: "agenthub-provision: ah-x exists\n" }); } return `ok ${argv[1]} ${argv[2]}\n`; }); }); beforeEach(() => { argvs = []; failNext = false; writeConf(); process.env.AGENTHUB_MODE = "server"; }); it("is off outside server mode or without server.conf", () => { delete process.env.AGENTHUB_MODE; assert.equal(linux.s3Enabled(), false); assert.equal(linux.provision("create", "anyone"), null); process.env.AGENTHUB_MODE = "server"; fs.rmSync(process.env.AGENTHUB_SERVER_CONF); linux.__resetConfCache(); assert.equal(linux.s3Enabled(), false); assert.equal(linux.accountFor("mia"), null); assert.deepEqual(argvs, []); }); it("reads server.conf and maps a user to ah- under USERS_ROOT", () => { assert.deepEqual(linux.serverConf(), { hostUser: "host", hostUid: 4242, usersRoot: "/srv/agenthub/users", port: 4820, bins: {}, }); assert.deepEqual(linux.accountFor("mia"), { login: "ah-mia", home: "/srv/agenthub/users/mia", }); }); it("refuses a name Linux cannot take, before any sudo", () => { for (const bad of ["Mia", "1mia", "mi.a", "-x", "a".repeat(30), "root;id", "../x"]) { assert.throws(() => linux.provision("create", bad), { code: "EBADUSER" }, bad); } assert.throws(() => linux.provision("delete", "mia"), /unknown provision action/); assert.deepEqual(argvs, []); }); it("calls exactly the provision helper with an action and a name", () => { linux.provision("create", "mia"); linux.provision("disable", "mia"); assert.deepEqual(argvs, [ ["/opt/agenthub/sbin/agenthub-provision", "create", "mia"], ["/opt/agenthub/sbin/agenthub-provision", "disable", "mia"], ]); }); it("creates the Linux account BEFORE the user, and no user when that fails", async () => { const made = await auth.createUser("lin", "member-pass-1", "developer"); assert.equal(made.username, "lin"); assert.deepEqual(argvs[0], ["/opt/agenthub/sbin/agenthub-provision", "create", "lin"]); failNext = true; await assert.rejects(auth.createUser("lux", "member-pass-1", "developer"), { code: "EPROVISION", message: /ah-x exists/, }); assert.equal(db.prepare("SELECT 1 FROM users WHERE username = 'lux'").get(), undefined); await assert.rejects(auth.createUser("Lux", "member-pass-1", "developer"), { code: "EBADUSER", }); }); it("ends the logins first on disable, then locks the Linux account", () => { argvs = []; failNext = true; // A failed lock is still reported — but the dashboard access is gone. assert.throws(() => auth.setDisabled("lin", true), { code: "EPROVISION" }); assert.equal(db.prepare("SELECT disabled FROM users WHERE username = 'lin'").get().disabled, 1); auth.setDisabled("lin", false); assert.equal(db.prepare("SELECT disabled FROM users WHERE username = 'lin'").get().disabled, 0); auth.setDisabled("lin", true); assert.equal(db.prepare("SELECT disabled FROM users WHERE username = 'lin'").get().disabled, 1); assert.deepEqual( argvs.map((a) => a[1]), ["disable", "enable", "disable"] ); }); it("disables and enables a user who has no Linux account, with no sudo", () => { argvs = []; for (const n of ["pre", "Old.Name"]) { db.prepare("INSERT INTO users (username, password_hash) VALUES (?, 'x')").run(n); const id = db.prepare("SELECT id FROM users WHERE username = ?").get(n).id; db.prepare( "INSERT INTO auth_sessions (token_hash, user_id, expires_at) VALUES (?, ?, '2999-01-01')" ).run(`h-${n}`, id); auth.setDisabled(n, true); const row = db.prepare("SELECT disabled FROM users WHERE id = ?").get(id); assert.equal(row.disabled, 1, n); assert.equal( db.prepare("SELECT 1 FROM auth_sessions WHERE user_id = ?").get(id), undefined, `${n}'s logins end` ); auth.setDisabled(n, false); assert.equal(db.prepare("SELECT disabled FROM users WHERE id = ?").get(id).disabled, 0, n); } assert.deepEqual(argvs, []); }); }); describe("a member's plugin bootstrap", () => { it("does nothing under another uid than the host's", () => { const { notTheHost, bootstrap } = require("../../scripts/plugin-bootstrap"); writeConf(process.getuid() + 1); assert.equal(notTheHost(), true); assert.equal(bootstrap(path.join(TMP, "member-rt")), "not-host"); assert.equal(fs.existsSync(path.join(TMP, "member-rt")), false); writeConf(process.getuid()); assert.equal(notTheHost(), false); fs.rmSync(process.env.AGENTHUB_SERVER_CONF); assert.equal(notTheHost(), false); }); });