fix: v2.7.3 — Update now restarts through our own service unit, member panes stay live on a dev server, a start refuses a missing folder
- 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.
This commit is contained in:
@@ -7,26 +7,85 @@
|
||||
* 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, "..", "..")) {
|
||||
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 = spawn(process.execPath, [helperPath, String(process.pid), entry], {
|
||||
const helper = spawnHelper(process.execPath, [helperPath, String(process.pid), entry], {
|
||||
cwd: root,
|
||||
detached: true,
|
||||
stdio: "ignore",
|
||||
env: process.env,
|
||||
});
|
||||
helper.unref();
|
||||
process.kill(process.pid, "SIGTERM");
|
||||
kill();
|
||||
return "helper";
|
||||
}
|
||||
|
||||
module.exports = { scheduleRestart };
|
||||
module.exports = { scheduleRestart, owningUnit };
|
||||
|
||||
Reference in New Issue
Block a user