f460fff406
- Sagi: clicking the pet opens its chat window directly (PetChat + SagiInfo status strip, Needs you, Mute / Clear alerts); SagiPanel removed. - Chat view answers Claude's unnumbered folder-trust prompt (parseBareMenu). - pty-attach: resize/write after a Windows attach exited, and node-pty's pipe errors, no longer throw out of the WebSocket handler. - restart-helper: the server started by an update logs to data/agenthub-server.log instead of discarding its output.
95 lines
3.3 KiB
JavaScript
95 lines
3.3 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");
|
|
// The same log `agenthub start` writes: without it every server started by
|
|
// an update ran with no stdout/stderr at all, and a crash left no trace.
|
|
const logFile = path.join(root, "data", "agenthub-server.log");
|
|
const helper = spawnHelper(process.execPath, [helperPath, String(process.pid), entry, logFile], {
|
|
cwd: root,
|
|
detached: true,
|
|
stdio: "ignore",
|
|
env: process.env,
|
|
});
|
|
helper.unref();
|
|
kill();
|
|
return "helper";
|
|
}
|
|
|
|
module.exports = { scheduleRestart, owningUnit };
|