@danypops/pi-packed 0.27.0 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -446,12 +446,38 @@ const SELF_RESTART_SCHEDULED_REASON = "self-restart scheduled via a detached arm
|
|
|
446
446
|
* detached child's `armada restart` stops this very process. */
|
|
447
447
|
const SELF_RESTART_DELAY_MS = 500;
|
|
448
448
|
|
|
449
|
-
/**
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
*
|
|
449
|
+
/**
|
|
450
|
+
* Builds the command to launch armada's own `restart pi-packed` out-of-process. Plain
|
|
451
|
+
* `spawn(..., { detached: true }).unref()` is NOT enough on Linux: systemd's default
|
|
452
|
+
* KillMode=control-group kills every process in a unit's cgroup on `stop`, including a
|
|
453
|
+
* detached+unref'd child -- setsid() escapes a Unix process group, not a cgroup. Confirmed
|
|
454
|
+
* live: a bare detached spawn was killed mid-restart the instant the child's own `armada
|
|
455
|
+
* restart`'s `stop` step fired, because the child was still inside armada-pi-packed's own
|
|
456
|
+
* cgroup. `systemd-run --user --collect --unit=<name>` launches it as an independent
|
|
457
|
+
* transient unit with its own cgroup instead, immune to this unit's stop (confirmed live:
|
|
458
|
+
* a probe unit's child outlived its own service being stopped). launchd/Windows don't tear
|
|
459
|
+
* down a detached child's process tree the same way, so they use a plain spawn.
|
|
460
|
+
*/
|
|
461
|
+
export function selfRestartCommand(
|
|
462
|
+
platform: NodeJS.Platform,
|
|
463
|
+
execPath: string,
|
|
464
|
+
cliPath: string,
|
|
465
|
+
unitName: string,
|
|
466
|
+
): { readonly command: string; readonly arguments: readonly string[] } {
|
|
467
|
+
if (platform === "linux") {
|
|
468
|
+
return {
|
|
469
|
+
command: "systemd-run",
|
|
470
|
+
arguments: ["--user", "--collect", `--unit=${unitName}`, "--", execPath, cliPath, "restart", PACKED_VEHICLE_NAME, "--json"],
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
return { command: execPath, arguments: [cliPath, "restart", PACKED_VEHICLE_NAME, "--json"] };
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/** Fire-and-forget: schedule Packed's own Vehicle to restart from OUTSIDE this process (see
|
|
477
|
+
* selfRestartCommand for why a plain detached spawn alone doesn't survive on Linux). Runs a
|
|
478
|
+
* beat later so the response reporting "scheduled" has time to flush first. Swallows a
|
|
479
|
+
* resolution/spawn failure silently: worst case is the pre-existing behavior (a human restarts
|
|
480
|
+
* the service by hand), never a crash. */
|
|
455
481
|
export type SelfRestartScheduler = (spec: ServiceSpec) => void;
|
|
456
482
|
|
|
457
483
|
export const scheduleSelfRestart: SelfRestartScheduler = (_spec) => {
|
|
@@ -463,10 +489,9 @@ export const scheduleSelfRestart: SelfRestartScheduler = (_spec) => {
|
|
|
463
489
|
}
|
|
464
490
|
setTimeout(() => {
|
|
465
491
|
try {
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
});
|
|
492
|
+
const unitName = `armada-selfrestart-${PACKED_VEHICLE_NAME}-${process.pid}-${Date.now()}`;
|
|
493
|
+
const { command, arguments: arguments_ } = selfRestartCommand(process.platform, process.execPath, cliPath, unitName);
|
|
494
|
+
const child = spawn(command, arguments_, { detached: true, stdio: "ignore" });
|
|
470
495
|
child.unref();
|
|
471
496
|
} catch {
|
|
472
497
|
/* best-effort -- a human can still restart the service by hand */
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
RealDaemonServiceInstaller,
|
|
14
14
|
resolveDaemonServiceSpec,
|
|
15
15
|
scheduleSelfRestart,
|
|
16
|
+
selfRestartCommand,
|
|
16
17
|
type SelfRestartScheduler,
|
|
17
18
|
} from "../src/daemon/daemon-service.ts";
|
|
18
19
|
|
|
@@ -499,6 +500,36 @@ describe("RealDaemonServiceInstaller -- Armada registration through the in-proce
|
|
|
499
500
|
expect(typeof scheduleSelfRestart).toBe("function");
|
|
500
501
|
});
|
|
501
502
|
|
|
503
|
+
it("selfRestartCommand routes through systemd-run's own transient unit on Linux, not a bare detached spawn", () => {
|
|
504
|
+
// A bare `spawn(..., {detached:true}).unref()` alone does NOT escape systemd's
|
|
505
|
+
// KillMode=control-group -- confirmed live: it was killed mid-restart the instant the
|
|
506
|
+
// unit it was spawned from was stopped, because cgroups (unlike a Unix process group)
|
|
507
|
+
// aren't escaped by setsid(). systemd-run's own transient unit has an independent cgroup.
|
|
508
|
+
const result = selfRestartCommand("linux", "/usr/bin/node", "/opt/armada/dist/cli.js", "armada-selfrestart-pi-packed-123-456");
|
|
509
|
+
expect(result.command).toBe("systemd-run");
|
|
510
|
+
expect(result.arguments).toEqual([
|
|
511
|
+
"--user",
|
|
512
|
+
"--collect",
|
|
513
|
+
"--unit=armada-selfrestart-pi-packed-123-456",
|
|
514
|
+
"--",
|
|
515
|
+
"/usr/bin/node",
|
|
516
|
+
"/opt/armada/dist/cli.js",
|
|
517
|
+
"restart",
|
|
518
|
+
PACKED_VEHICLE_NAME,
|
|
519
|
+
"--json",
|
|
520
|
+
]);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it("selfRestartCommand spawns the CLI directly on non-Linux platforms -- no systemd cgroup to escape", () => {
|
|
524
|
+
for (const platform of ["darwin", "win32"] as const) {
|
|
525
|
+
const result = selfRestartCommand(platform, "/usr/local/bin/node", "/opt/armada/dist/cli.js", "unused-on-this-platform");
|
|
526
|
+
expect(result).toEqual({
|
|
527
|
+
command: "/usr/local/bin/node",
|
|
528
|
+
arguments: ["/opt/armada/dist/cli.js", "restart", PACKED_VEHICLE_NAME, "--json"],
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
|
|
502
533
|
it("install() reports a non-daemon package without touching Armada", async () => {
|
|
503
534
|
const piHome = fakePiHome();
|
|
504
535
|
writePackage(piHome, "some-pkg", undefined);
|