mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-25 10:42:45 +00:00
97 lines
3.2 KiB
TypeScript
97 lines
3.2 KiB
TypeScript
#!/usr/bin/env npx tsx
|
|
|
|
/**
|
|
* Regression: a reboot reassigns the supervisor's PID to an unrelated process. The lock
|
|
* left behind must not make the daemon look like it is running, and must never be treated
|
|
* as a handle on whatever now holds that PID.
|
|
*/
|
|
|
|
import assert from "node:assert";
|
|
import { spawn } from "node:child_process";
|
|
import { existsSync } from "node:fs";
|
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir, uptime } from "node:os";
|
|
import { join } from "node:path";
|
|
import { runLocalPaseo } from "./helpers/local-cli.ts";
|
|
|
|
console.log("=== Daemon lock left behind by a reboot ===\n");
|
|
|
|
const paseoHome = await mkdtemp(join(tmpdir(), "paseo-stale-boot-lock-"));
|
|
const env = {
|
|
PASEO_HOME: paseoHome,
|
|
PASEO_LOCAL_SPEECH_AUTO_DOWNLOAD: "0",
|
|
PASEO_DICTATION_ENABLED: "0",
|
|
PASEO_VOICE_MODE_ENABLED: "0",
|
|
};
|
|
|
|
// Stands in for the process that inherits the supervisor's PID after a reboot. It records a
|
|
// signal rather than dying of it, so a late delivery cannot slip past the assertion.
|
|
const signalMarker = join(paseoHome, "bystander-signalled");
|
|
const bystander = spawn(
|
|
process.execPath,
|
|
[
|
|
"-e",
|
|
`process.on("SIGTERM", () => require("node:fs").writeFileSync(${JSON.stringify(signalMarker)}, "SIGTERM"));` +
|
|
`setTimeout(() => {}, 120_000);`,
|
|
],
|
|
{ stdio: "ignore" },
|
|
);
|
|
let bystanderExited = false;
|
|
bystander.once("exit", () => {
|
|
bystanderExited = true;
|
|
});
|
|
assert(bystander.pid, "bystander process should have a pid");
|
|
|
|
try {
|
|
const beforeThisBoot = new Date(Date.now() - uptime() * 1000 - 60 * 60_000);
|
|
await writeFile(
|
|
join(paseoHome, "paseo.pid"),
|
|
JSON.stringify({
|
|
pid: bystander.pid,
|
|
startedAt: beforeThisBoot.toISOString(),
|
|
hostname: "before-reboot",
|
|
uid: process.getuid?.() ?? 0,
|
|
listen: "127.0.0.1:6767",
|
|
desktopManaged: true,
|
|
heartbeat: true,
|
|
}),
|
|
);
|
|
|
|
const statusResult = await runLocalPaseo(
|
|
["daemon", "status", "--home", paseoHome, "--json"],
|
|
env,
|
|
);
|
|
assert.strictEqual(
|
|
statusResult.exitCode,
|
|
0,
|
|
`daemon status should succeed:\nstdout:\n${statusResult.stdout}\nstderr:\n${statusResult.stderr}`,
|
|
);
|
|
const status = JSON.parse(statusResult.stdout) as { localDaemon?: string; pid?: number | null };
|
|
assert.strictEqual(
|
|
status.localDaemon,
|
|
"stopped",
|
|
`daemon left behind by a reboot should read as stopped, got: ${statusResult.stdout}`,
|
|
);
|
|
assert.strictEqual(status.pid, null, "status should not report the reused pid as the daemon");
|
|
console.log("✓ daemon status reports stopped\n");
|
|
|
|
const stopResult = await runLocalPaseo(["daemon", "stop", "--home", paseoHome], env);
|
|
assert.strictEqual(
|
|
stopResult.exitCode,
|
|
0,
|
|
`daemon stop should succeed:\nstdout:\n${stopResult.stdout}\nstderr:\n${stopResult.stderr}`,
|
|
);
|
|
assert.strictEqual(
|
|
existsSync(signalMarker),
|
|
false,
|
|
"daemon stop must not signal the process that inherited the pid",
|
|
);
|
|
assert.strictEqual(bystanderExited, false, "the process that inherited the pid should survive");
|
|
console.log("✓ daemon stop leaves the process holding that pid alone\n");
|
|
} finally {
|
|
bystander.kill("SIGKILL");
|
|
await rm(paseoHome, { recursive: true, force: true });
|
|
}
|
|
|
|
console.log("=== Daemon lock left behind by a reboot passed ===");
|