Files
AgentHub/server/__tests__/server-s3-system.test.js
T
nntrivi2001 c83ddf62a0 feat(server-s3): a member's tmux follows tmux upgrades — tmux-member + Users → Members' tmux
A member's tmux server keeps the version it started with until their last
pane closes, but the dashboard asked it with TMUX_BIN, the newest tmux: a
3.8 client on a 3.6 server spun it at 100% CPU (three members needed a
manual kill on 2026-10-15).

- /opt/agenthub/libexec/tmux-member (install-system, in sudoers): runs a
  member's tmux call with the binary of THEIR running server
  (/proc/<pid>/exe, found from the socket in /proc/net/unix), else
  TMUX_BIN. It runs as the member and execs only a process they own.
  sudoArgv uses it once installed (linux-accounts readConf), TMUX_BIN
  until then.
- Users → Members' tmux (GET /api/users/tmux): each member server's version
  beside the installed one, sessions, agents working. An admin restarts one
  (POST /api/users/:id/tmux-restart) or every idle outdated one
  (POST /api/users/tmux-restart-idle), with their password again, audited
  per member; a server that will not stop answers 409 ETMUXSTUCK naming
  its pid for root.
- server-s3-system.test.js: `delete` is a provision action since Delete
  user, so the "unknown action" case now uses `purge`.

Operators: run `sudo agenthub server install-system` once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:44:47 +07:00

284 lines
11 KiB
JavaScript

/**
* @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ĩ <vinnt@smartgift.vn>
*/
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-<name> under USERS_ROOT", () => {
assert.deepEqual(linux.serverConf(), {
hostUser: "host",
hostUid: 4242,
usersRoot: "/srv/agenthub/users",
port: 4820,
tmuxInstalled: null,
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);
}
// `delete` is an action now (Delete user); anything else is still refused.
assert.throws(() => linux.provision("purge", "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);
});
});