943ba03b76
- self-restart: a server in our unit's cgroup (/agenthub.service, system or user) restarts via `systemctl restart --no-block` (sudo -n for the system unit), our launchd job by exiting into KeepAlive. The detached restart-helper replacement held the port while Restart=always started the unit every 3 s (v2.7.2 on the S3 box: 240+ restarts, MainPID 0). - run-as.mayHaveTmux: only ENOENT means "no tmux". A dev server in server mode without CAP_DAC_READ_SEARCH got EACCES on a member's 0700 .agenthub/tmux, dropped the member from the list and ended their runs within seconds — the real cause of `401 not a live pane's token`. - tmux: a member's list is re-asked every 5 s, not 1 s (one sudo + three auth.log lines each). - POST /api/lanes/:id/start: 409 ENOCWD for a folder that no longer exists (the pane started in the account's home otherwise). - Settings: Remote sources only for staff (a developer got a 403). - scripts/test-server.js drops every inherited AGENTHUB_* (pane env). Tests: self-restart.test.js, server-s3-panes (mayHaveTmux), lanes-api (ENOCWD); touched suites 771 + client pages 191, tsc -b.
92 lines
3.1 KiB
JavaScript
92 lines
3.1 KiB
JavaScript
/**
|
|
* @file Triggers the one self-restart path this dashboard has: after
|
|
* `POST /api/updates/apply` fast-forwards and rebuilds the checkout, the
|
|
* running process needs to relaunch itself on the new code. Reuses the
|
|
* existing SIGTERM graceful-shutdown path in `server/index.js` (closes
|
|
* websockets, drains the HTTP server, closes the DB) instead of duplicating
|
|
* it, and hands the "wait for the old PID to actually die, then spawn the
|
|
* replacement" job to a detached helper script — the dying process can't do
|
|
* that itself without racing its own replacement for the port.
|
|
*
|
|
* A server our service unit runs (systemd `agenthub.service`, system or user;
|
|
* the launchd job) is restarted BY that manager instead: a detached
|
|
* replacement would hold the port while `Restart=always` starts the unit
|
|
* again every few seconds, each start exiting "already running" — forever.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const { spawn } = require("child_process");
|
|
|
|
const UNIT = "agenthub";
|
|
|
|
/**
|
|
* "systemd" when this process lives in our unit's cgroup, "launchd" when it
|
|
* is our LaunchAgent job, else null. Pure reads: no process is spawned.
|
|
*/
|
|
function owningUnit({
|
|
platform = process.platform,
|
|
cgroup = () => fs.readFileSync("/proc/self/cgroup", "utf8"),
|
|
env = process.env,
|
|
} = {}) {
|
|
if (platform === "darwin") {
|
|
return env.XPC_SERVICE_NAME === `io.smartgift.${UNIT}` ? "launchd" : null;
|
|
}
|
|
try {
|
|
return cgroup()
|
|
.split("\n")
|
|
.some((l) => l.trim().endsWith(`/${UNIT}.service`))
|
|
? "systemd"
|
|
: null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} [root] repo root the helper should run from.
|
|
* @returns {"service"|"exit"|"helper"} how the restart was handed over.
|
|
*/
|
|
function scheduleRestart(root = path.join(__dirname, "..", ".."), deps = {}) {
|
|
const {
|
|
unit = owningUnit(),
|
|
svc = () => require("../../scripts/service-manager"),
|
|
kill = () => process.kill(process.pid, "SIGTERM"),
|
|
spawnHelper = spawn,
|
|
} = deps;
|
|
// systemd: one non-blocking restart job (sudo -n for the system unit). If
|
|
// that cannot be asked, a plain exit still comes back — the unit is
|
|
// Restart=always — but never with a second server beside it.
|
|
if (unit === "systemd") {
|
|
let restarted = false;
|
|
try {
|
|
restarted = svc().restartUserService(UNIT) === "restarted";
|
|
} catch {
|
|
/* fall through to the exit */
|
|
}
|
|
if (restarted) return "service";
|
|
kill();
|
|
return "exit";
|
|
}
|
|
// launchd: KeepAlive restarts the job on exit; a `bootout` from inside the
|
|
// job would wait for this very process.
|
|
if (unit === "launchd") {
|
|
kill();
|
|
return "exit";
|
|
}
|
|
const helperPath = path.join(root, "scripts", "restart-helper.js");
|
|
const entry = process.argv[1] || path.join(root, "server", "index.js");
|
|
const helper = spawnHelper(process.execPath, [helperPath, String(process.pid), entry], {
|
|
cwd: root,
|
|
detached: true,
|
|
stdio: "ignore",
|
|
env: process.env,
|
|
});
|
|
helper.unref();
|
|
kill();
|
|
return "helper";
|
|
}
|
|
|
|
module.exports = { scheduleRestart, owningUnit };
|