@cotal-ai/seat 0.57.0 → 0.59.0
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/README.md +26 -1
- package/dist/artifacts.d.ts +14 -0
- package/dist/artifacts.d.ts.map +1 -0
- package/dist/artifacts.js +40 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/client.d.ts +6 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +14 -3
- package/dist/client.js.map +1 -1
- package/dist/custodian.d.ts +5 -0
- package/dist/custodian.d.ts.map +1 -1
- package/dist/custodian.js +134 -28
- package/dist/custodian.js.map +1 -1
- package/dist/diagnostic.d.ts +13 -0
- package/dist/diagnostic.d.ts.map +1 -0
- package/dist/diagnostic.js +43 -0
- package/dist/diagnostic.js.map +1 -0
- package/dist/handle.d.ts +2 -4
- package/dist/handle.d.ts.map +1 -1
- package/dist/handle.js +1 -1
- package/dist/handle.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/launcher.d.ts +2 -0
- package/dist/launcher.d.ts.map +1 -1
- package/dist/launcher.js +55 -33
- package/dist/launcher.js.map +1 -1
- package/dist/oom.d.ts +18 -0
- package/dist/oom.d.ts.map +1 -0
- package/dist/oom.js +28 -0
- package/dist/oom.js.map +1 -0
- package/dist/protocol.d.ts +12 -8
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/dist/reap.d.ts +35 -2
- package/dist/reap.d.ts.map +1 -1
- package/dist/reap.js +142 -14
- package/dist/reap.js.map +1 -1
- package/dist/record.d.ts +12 -0
- package/dist/record.d.ts.map +1 -1
- package/dist/record.js +15 -1
- package/dist/record.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,6 +12,16 @@ named `custody transport unsupported on <platform>` error on darwin and win32. T
|
|
|
12
12
|
in-process node-pty fallback here. The manager's `pty` runtime still spawns in-process off
|
|
13
13
|
Linux and only `adopt` throws that named error.
|
|
14
14
|
|
|
15
|
+
`peerCredentials(socket)` and its `PeerCredentials` type are exported from the package root.
|
|
16
|
+
For a connected Linux Unix socket, the function returns the kernel's peer `pid`, `uid` and
|
|
17
|
+
`gid` using the same `SO_PEERCRED` helper as custody. Callers must compare that identity
|
|
18
|
+
against their own authorization policy; a PID is not a lifecycle or ownership fence. It
|
|
19
|
+
throws for an unsupported transport or missing native helper, and for a socket without a
|
|
20
|
+
file descriptor. No PTY or custodian is started by reading the peer identity.
|
|
21
|
+
|
|
22
|
+
The custodian raises its child's `oom_score_adj` to 500 and writes one line to `custodian.log`
|
|
23
|
+
when the kernel refuses.
|
|
24
|
+
|
|
15
25
|
The Linux `SO_PEERCRED` helper is compiled for the host arch by `pnpm build`. That is a
|
|
16
26
|
developer tree, not a publishable one: it prints a host-dev-build banner and writes
|
|
17
27
|
`build/Release/linux-<arch>/peercred.node`. Pack and publish require both `linux-x64`
|
|
@@ -38,11 +48,18 @@ observation. A manager worker connects to that custodian over a 0600 filesystem
|
|
|
38
48
|
authenticated by `SO_PEERCRED` uid match plus a per-seat capability token. Path possession is
|
|
39
49
|
not enough. Child exit is pushed to every authenticated controller socket. After the child
|
|
40
50
|
exits and the last authenticated client disconnects, the custodian closes the Unix server, unlinks the
|
|
41
|
-
socket
|
|
51
|
+
socket, and exits. The custody record stays until `reapSeat` verifies process departure. If the
|
|
52
|
+
recorded group leader is absent but its numeric process group still has members, ownership is
|
|
53
|
+
unproved: reaping refuses without signalling those members or deleting the record. An active child, or a still-connected observer of an exited
|
|
42
54
|
child, keeps the process. A connected socket that never authenticated does not: it owns no
|
|
43
55
|
session, no output subscription and no wait, so a settle owes it nothing. A seat whose child has
|
|
44
56
|
already exited at listen stays up briefly so the launcher can adopt it.
|
|
45
57
|
|
|
58
|
+
A reap signals the custodian and the child only while each carries its recorded start identity.
|
|
59
|
+
The child's descendants are not in the record, so the reap kills the child's process group by
|
|
60
|
+
membership. The record carries no authentication, so a same-uid process that rewrites it chooses
|
|
61
|
+
what the next reap signals. `docs/security.md` lists both as accepted residuals.
|
|
62
|
+
|
|
46
63
|
A custodian with no authenticated controller stops its child and exits after `UNATTENDED_MS`
|
|
47
64
|
(ten minutes, overridable at launch with `COTAL_SEAT_UNATTENDED_MS`). The window restarts at each
|
|
48
65
|
disconnect, so a manager that detaches and re-adopts keeps its seats; one that crashes, or a suite
|
|
@@ -55,5 +72,13 @@ Every custodian carries `--cotal-run <marker>` on its argv and `COTAL_RUN` in it
|
|
|
55
72
|
`censusCustodians(run?)` reads that marker back out of `/proc/<pid>/cmdline`, which is
|
|
56
73
|
world-readable, so a reaper can find and attribute orphans without walking `/proc/*/cwd`.
|
|
57
74
|
|
|
75
|
+
`drainSeats(root, { drain })` lists the custody records under a root. A record that cannot be
|
|
76
|
+
read, or whose start and boot identity do not tie its pids to this boot, is reported as `refused`
|
|
77
|
+
and left on disk, with or without `drain`. A seat whose child still holds its recorded start
|
|
78
|
+
identity is reported as `live-child` and never signalled. With `drain`, every other seat goes
|
|
79
|
+
through `reapSeat`. One it cannot prove gone is reported as `refused` and left on disk, and
|
|
80
|
+
`reapSeat` may already have sent `SIGKILL` to its custodian.
|
|
81
|
+
`cotal seats [--drain]` is the operator command over it.
|
|
82
|
+
|
|
58
83
|
Generation CAS, the crash journal, N/N-1 protocol compatibility, and manager-worker activation
|
|
59
84
|
are later milestones. This package currently speaks a single implicit controller.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove a seat's launch artifacts (the record's `artifacts`). Called only where this package
|
|
3
|
+
* can prove the seat's child gone or never started: the custodian when it observes its child's exit
|
|
4
|
+
* or fails to start one, the launcher when it refuses before any process exists, and the reap once it
|
|
5
|
+
* has proved the seat gone. Losing a connection to the custodian proves none of these, so nothing on
|
|
6
|
+
* the client side calls this.
|
|
7
|
+
*
|
|
8
|
+
* `root` is the temp dir of the process that wrote them (the record's `artifactRoot`). Refuses,
|
|
9
|
+
* before removing anything, a path that is not a `cotal-` directory directly under it. Otherwise it
|
|
10
|
+
* tries every directory and then throws naming each one it could not remove, so the caller can keep
|
|
11
|
+
* those owned and try again.
|
|
12
|
+
*/
|
|
13
|
+
export declare function discardSeatArtifacts(artifacts: readonly string[] | undefined, root: string | undefined): void;
|
|
14
|
+
//# sourceMappingURL=artifacts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAiB7G"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { rmSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, resolve } from "node:path";
|
|
3
|
+
/** Every launch artifact directory name starts with this (core launch-artifacts). Repeated here
|
|
4
|
+
* because this package depends on nothing else in the repo, and it guards a recursive removal. */
|
|
5
|
+
const DIR_PREFIX = "cotal-";
|
|
6
|
+
/**
|
|
7
|
+
* Remove a seat's launch artifacts (the record's `artifacts`). Called only where this package
|
|
8
|
+
* can prove the seat's child gone or never started: the custodian when it observes its child's exit
|
|
9
|
+
* or fails to start one, the launcher when it refuses before any process exists, and the reap once it
|
|
10
|
+
* has proved the seat gone. Losing a connection to the custodian proves none of these, so nothing on
|
|
11
|
+
* the client side calls this.
|
|
12
|
+
*
|
|
13
|
+
* `root` is the temp dir of the process that wrote them (the record's `artifactRoot`). Refuses,
|
|
14
|
+
* before removing anything, a path that is not a `cotal-` directory directly under it. Otherwise it
|
|
15
|
+
* tries every directory and then throws naming each one it could not remove, so the caller can keep
|
|
16
|
+
* those owned and try again.
|
|
17
|
+
*/
|
|
18
|
+
export function discardSeatArtifacts(artifacts, root) {
|
|
19
|
+
if (!artifacts?.length)
|
|
20
|
+
return;
|
|
21
|
+
if (root === undefined)
|
|
22
|
+
throw new Error(`refusing to remove launch artifacts ${artifacts.join(", ")}: no temp dir recorded for them`);
|
|
23
|
+
const base = resolve(root);
|
|
24
|
+
for (const dir of artifacts) {
|
|
25
|
+
if (dirname(resolve(dir)) !== base || !basename(dir).startsWith(DIR_PREFIX))
|
|
26
|
+
throw new Error(`refusing to remove launch artifact ${dir}: not a ${DIR_PREFIX}* directory directly under ${base}`);
|
|
27
|
+
}
|
|
28
|
+
const failed = [];
|
|
29
|
+
for (const dir of artifacts) {
|
|
30
|
+
try {
|
|
31
|
+
rmSync(dir, { recursive: true, force: true });
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
failed.push(`${dir} (${e.message})`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (failed.length)
|
|
38
|
+
throw new Error(`could not remove launch artifact ${failed.join(", ")}`);
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=artifacts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEvD;mGACmG;AACnG,MAAM,UAAU,GAAG,QAAQ,CAAC;AAE5B;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAwC,EAAE,IAAwB;IACrG,IAAI,CAAC,SAAS,EAAE,MAAM;QAAE,OAAO;IAC/B,IAAI,IAAI,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;IACtI,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC5B,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;YACzE,MAAM,IAAI,KAAK,CAAC,sCAAsC,GAAG,WAAW,UAAU,8BAA8B,IAAI,EAAE,CAAC,CAAC;IACxH,CAAC;IACD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC5B,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,KAAM,CAAW,CAAC,OAAO,GAAG,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IACD,IAAI,MAAM,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC9F,CAAC"}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type StopMode } from "./protocol.js";
|
|
1
|
+
import { type SeatExit, type StopMode } from "./protocol.js";
|
|
2
2
|
import type { SeatRecord } from "./record.js";
|
|
3
3
|
export interface HelloInfo {
|
|
4
4
|
name: string;
|
|
@@ -6,10 +6,7 @@ export interface HelloInfo {
|
|
|
6
6
|
cols: number;
|
|
7
7
|
rows: number;
|
|
8
8
|
status: "running" | "exited";
|
|
9
|
-
exit?:
|
|
10
|
-
code?: number;
|
|
11
|
-
signal?: number;
|
|
12
|
-
};
|
|
9
|
+
exit?: SeatExit;
|
|
13
10
|
}
|
|
14
11
|
export declare class SeatClient {
|
|
15
12
|
private readonly record;
|
|
@@ -23,6 +20,7 @@ export declare class SeatClient {
|
|
|
23
20
|
private exits;
|
|
24
21
|
private helloInfo;
|
|
25
22
|
private pendingExit;
|
|
23
|
+
private pendingExitInfo;
|
|
26
24
|
private closed;
|
|
27
25
|
constructor(record: SeatRecord);
|
|
28
26
|
connect(): Promise<HelloInfo>;
|
|
@@ -33,15 +31,14 @@ export declare class SeatClient {
|
|
|
33
31
|
rows: number;
|
|
34
32
|
}>;
|
|
35
33
|
subscribeOutput(fn: (data: Buffer) => void): Promise<() => void>;
|
|
34
|
+
/** How the child ended, once the custodian has said so (hello, exit event, or wait-exit). */
|
|
35
|
+
exitInfo(): SeatExit | undefined;
|
|
36
36
|
onExit(fn: () => void): () => void;
|
|
37
37
|
write(data: string): Promise<number>;
|
|
38
38
|
resize(cols: number, rows: number): Promise<void>;
|
|
39
39
|
interrupt(): Promise<void>;
|
|
40
40
|
stop(mode: StopMode): Promise<void>;
|
|
41
|
-
waitExit(): Promise<
|
|
42
|
-
code?: number;
|
|
43
|
-
signal?: number;
|
|
44
|
-
} | undefined>;
|
|
41
|
+
waitExit(): Promise<SeatExit | undefined>;
|
|
45
42
|
health(): Promise<{
|
|
46
43
|
pid: number;
|
|
47
44
|
status: "running" | "exited";
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,QAAQ,EAGb,KAAK,QAAQ,EACd,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC7B,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB;AAQD,qBAAa,UAAU;IAcT,OAAO,CAAC,QAAQ,CAAC,MAAM;IAbnC,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,MAAM,CAA6C;IAC3D,OAAO,CAAC,gBAAgB,CAAuC;IAC/D,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,SAAS,CAAwB;IACzC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,MAAM,CAAS;gBAEM,MAAM,EAAE,UAAU;IAEzC,OAAO,IAAI,OAAO,CAAC,SAAS,CAAC;IAyCnC,IAAI,IAAI,SAAS;IAKX,QAAQ,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAMjE,eAAe,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC;IAgBtE,6FAA6F;IAC7F,QAAQ,IAAI,QAAQ,GAAG,SAAS;IAIhC,MAAM,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAW5B,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAMpC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASjD,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAK1B,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnC,QAAQ,IAAI,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAOzC,MAAM,IAAI,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAMxF,KAAK,IAAI,IAAI;IASb,OAAO,CAAC,OAAO;IAUf,OAAO,CAAC,QAAQ;IA2BhB,OAAO,CAAC,OAAO;CAchB"}
|
package/dist/client.js
CHANGED
|
@@ -12,6 +12,7 @@ export class SeatClient {
|
|
|
12
12
|
exits = new Set();
|
|
13
13
|
helloInfo;
|
|
14
14
|
pendingExit = false;
|
|
15
|
+
pendingExitInfo;
|
|
15
16
|
closed = false;
|
|
16
17
|
constructor(record) {
|
|
17
18
|
this.record = record;
|
|
@@ -57,7 +58,7 @@ export class SeatClient {
|
|
|
57
58
|
cols: reply.cols,
|
|
58
59
|
rows: reply.rows,
|
|
59
60
|
status: this.pendingExit ? "exited" : reply.status,
|
|
60
|
-
...(reply.exit ? { exit: reply.exit } : {}),
|
|
61
|
+
...((reply.exit ?? this.pendingExitInfo) ? { exit: reply.exit ?? this.pendingExitInfo } : {}),
|
|
61
62
|
};
|
|
62
63
|
return this.helloInfo;
|
|
63
64
|
}
|
|
@@ -90,6 +91,10 @@ export class SeatClient {
|
|
|
90
91
|
this.pendingSubscribe = undefined;
|
|
91
92
|
}
|
|
92
93
|
}
|
|
94
|
+
/** How the child ended, once the custodian has said so (hello, exit event, or wait-exit). */
|
|
95
|
+
exitInfo() {
|
|
96
|
+
return this.helloInfo?.exit ?? this.pendingExitInfo;
|
|
97
|
+
}
|
|
93
98
|
onExit(fn) {
|
|
94
99
|
if (this.helloInfo?.status === "exited" || this.pendingExit) {
|
|
95
100
|
queueMicrotask(fn);
|
|
@@ -167,10 +172,16 @@ export class SeatClient {
|
|
|
167
172
|
return;
|
|
168
173
|
}
|
|
169
174
|
if (msg.event === "exit") {
|
|
170
|
-
if (this.helloInfo)
|
|
175
|
+
if (this.helloInfo) {
|
|
171
176
|
this.helloInfo.status = "exited";
|
|
172
|
-
|
|
177
|
+
if (msg.exit)
|
|
178
|
+
this.helloInfo.exit = msg.exit;
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
173
181
|
this.pendingExit = true;
|
|
182
|
+
if (msg.exit)
|
|
183
|
+
this.pendingExitInfo = msg.exit;
|
|
184
|
+
}
|
|
174
185
|
for (const fn of this.exits)
|
|
175
186
|
fn();
|
|
176
187
|
this.exits.clear();
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAe,MAAM,UAAU,CAAC;AAChD,OAAO,EACL,WAAW,EACX,WAAW,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/custodian.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ export interface CustodianLaunch {
|
|
|
17
17
|
* Resolved by the LAUNCHER, because the launcher scrubs the environment it hands this process
|
|
18
18
|
* (the child must not inherit the caller's), so an env override read here would never see one. */
|
|
19
19
|
unattendedMs?: number;
|
|
20
|
+
/** The launch's private temporary directories, copied onto the seat record. This custodian removes
|
|
21
|
+
* them once it sees its child exit, the one exit it observes directly. */
|
|
22
|
+
artifacts?: string[];
|
|
23
|
+
/** The launcher's temp dir, which every entry of `artifacts` must sit directly under. */
|
|
24
|
+
artifactRoot?: string;
|
|
20
25
|
}
|
|
21
26
|
export declare function runCustodian(launch: CustodianLaunch): Promise<void>;
|
|
22
27
|
//# sourceMappingURL=custodian.d.ts.map
|
package/dist/custodian.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custodian.d.ts","sourceRoot":"","sources":["../src/custodian.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"custodian.d.ts","sourceRoot":"","sources":["../src/custodian.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;oEACgE;IAChE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;uGAEmG;IACnG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;+EAC2E;IAC3E,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,yFAAyF;IACzF,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAOD,wBAAsB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAsmBzE"}
|
package/dist/custodian.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { appendFileSync, chmodSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
1
|
+
import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { createServer } from "node:net";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
import * as pty from "@lydell/node-pty";
|
|
5
5
|
import Headless from "@xterm/headless";
|
|
6
6
|
import { SerializeAddon } from "@xterm/addon-serialize";
|
|
7
|
+
import { discardSeatArtifacts } from "./artifacts.js";
|
|
7
8
|
import { peerCredentials } from "./peercred.js";
|
|
9
|
+
import { preferSeatForOomKill } from "./oom.js";
|
|
8
10
|
import { CONFIRM_TIMEOUT_MS, DEFAULT_COLS, DEFAULT_ROWS, FrameReader, GRACE_MS, MAX_FRAME_SIZE, PROTOCOL_VERSION, SCROLLBACK_ROWS, UNATTENDED_MS as UNATTENDED_DEFAULT_MS, encodeFrame, } from "./protocol.js";
|
|
9
|
-
import {
|
|
11
|
+
import { ConnectorDiagnosticReader } from "./diagnostic.js";
|
|
12
|
+
import { RECORD_VERSION, bootToken, exitPath, processStartToken, writeRecord } from "./record.js";
|
|
10
13
|
import { StartupConfirmMatcher, unmatchedConfirmMessage } from "./startup-confirm.js";
|
|
11
14
|
function send(sock, msg) {
|
|
12
15
|
if (!sock.writable)
|
|
@@ -25,13 +28,50 @@ export async function runCustodian(launch) {
|
|
|
25
28
|
if (e.code !== "ENOENT")
|
|
26
29
|
throw e;
|
|
27
30
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
// Removes the launch's files, logging rather than throwing: a refused path must not take down the
|
|
32
|
+
// custodian of a live seat.
|
|
33
|
+
const discardArtifacts = (artifacts) => {
|
|
34
|
+
try {
|
|
35
|
+
discardSeatArtifacts(artifacts, launch.artifactRoot);
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
note(`${e.message}\n`);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const note = (line) => {
|
|
42
|
+
try {
|
|
43
|
+
if (launch.logPath)
|
|
44
|
+
appendFileSync(launch.logPath, line, { mode: 0o600 });
|
|
45
|
+
else
|
|
46
|
+
process.stderr.write(line);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
/* the seat directory may already be gone */
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
let proc;
|
|
53
|
+
try {
|
|
54
|
+
proc = pty.spawn(launch.command, launch.args, {
|
|
55
|
+
name: "xterm-256color",
|
|
56
|
+
cols: DEFAULT_COLS,
|
|
57
|
+
rows: DEFAULT_ROWS,
|
|
58
|
+
cwd: launch.cwd,
|
|
59
|
+
env: launch.env,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
// No child was started, so none will read the files.
|
|
64
|
+
discardArtifacts(launch.artifacts);
|
|
65
|
+
throw e;
|
|
66
|
+
}
|
|
67
|
+
const oomPref = preferSeatForOomKill(proc.pid);
|
|
68
|
+
if (!oomPref.applied) {
|
|
69
|
+
const line = `oom preference not applied to child ${proc.pid}: ${oomPref.reason}\n`;
|
|
70
|
+
if (launch.logPath)
|
|
71
|
+
appendFileSync(launch.logPath, line, { mode: 0o600 });
|
|
72
|
+
else
|
|
73
|
+
process.stderr.write(line);
|
|
74
|
+
}
|
|
35
75
|
// Pin both start identities NOW, before the child can exit and be reaped: a successor that finds
|
|
36
76
|
// these pids later must be able to tell this custodian and this child from an unrelated process
|
|
37
77
|
// that inherited the pid. A zombie still reports its start token, a reaped pid does not: a child
|
|
@@ -57,6 +97,7 @@ export async function runCustodian(launch) {
|
|
|
57
97
|
let cols = DEFAULT_COLS;
|
|
58
98
|
let rows = DEFAULT_ROWS;
|
|
59
99
|
let exit;
|
|
100
|
+
const diagnostic = new ConnectorDiagnosticReader();
|
|
60
101
|
const dataSubs = new Map();
|
|
61
102
|
const waiters = new Map();
|
|
62
103
|
const controllers = new Set();
|
|
@@ -72,6 +113,8 @@ export async function runCustodian(launch) {
|
|
|
72
113
|
let server;
|
|
73
114
|
/** Wait for the first adopter when the child has already exited at listen. */
|
|
74
115
|
const LAUNCH_HANDOFF_MS = 5_000;
|
|
116
|
+
/** How long a child already gone from /proc may wait for node-pty to report how it ended. */
|
|
117
|
+
const EXIT_STATUS_GRACE_MS = 1_000;
|
|
75
118
|
const UNATTENDED_MS = launch.unattendedMs ?? UNATTENDED_DEFAULT_MS;
|
|
76
119
|
if (confirmMatcher) {
|
|
77
120
|
confirmTimer = setTimeout(() => {
|
|
@@ -107,6 +150,20 @@ export async function runCustodian(launch) {
|
|
|
107
150
|
return true;
|
|
108
151
|
}
|
|
109
152
|
};
|
|
153
|
+
/**
|
|
154
|
+
* Mark a child that /proc shows gone as exited WITHOUT a status, but only once node-pty has had
|
|
155
|
+
* time to report the status itself. node-pty reports an exit only when the pty closes, and when a
|
|
156
|
+
* descendant still holds the pty open it forces that close 200 ms after reaping the child. The
|
|
157
|
+
* exit event carries whatever is known when it is sent, so marking earlier sent no status.
|
|
158
|
+
*/
|
|
159
|
+
let goneSince;
|
|
160
|
+
const markGone = () => {
|
|
161
|
+
if (!alive || !childGone())
|
|
162
|
+
return;
|
|
163
|
+
goneSince ??= Date.now();
|
|
164
|
+
if (Date.now() - goneSince >= EXIT_STATUS_GRACE_MS)
|
|
165
|
+
markExited({});
|
|
166
|
+
};
|
|
110
167
|
const settleTerminal = () => {
|
|
111
168
|
if (alive || settling || !ready)
|
|
112
169
|
return;
|
|
@@ -173,13 +230,8 @@ export async function runCustodian(launch) {
|
|
|
173
230
|
if (e.code !== "ENOENT")
|
|
174
231
|
throw e;
|
|
175
232
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
catch (e) {
|
|
180
|
-
if (e.code !== "ENOENT")
|
|
181
|
-
throw e;
|
|
182
|
-
}
|
|
233
|
+
// The custody record is retained on disk until verified reaping can consume it;
|
|
234
|
+
// reapSeat proves kernel process identities and removes the record directory.
|
|
183
235
|
process.exit(0);
|
|
184
236
|
};
|
|
185
237
|
/**
|
|
@@ -249,19 +301,76 @@ export async function runCustodian(launch) {
|
|
|
249
301
|
return;
|
|
250
302
|
}
|
|
251
303
|
alive = false;
|
|
252
|
-
|
|
304
|
+
const last = diagnostic.read();
|
|
305
|
+
exit = { ...(info ?? exit ?? {}), ...(last ? { diagnostic: last } : {}) };
|
|
306
|
+
// Kept beside the custody record so a reap that runs after every controller is gone, even in a
|
|
307
|
+
// successor manager, can still say how the child ended. Renamed into place, so a reader never
|
|
308
|
+
// sees half a record.
|
|
309
|
+
const exitFile = exitPath(launch.recordPath);
|
|
310
|
+
try {
|
|
311
|
+
writeFileSync(`${exitFile}.tmp`, `${JSON.stringify(exit)}\n`, { mode: 0o600 });
|
|
312
|
+
renameSync(`${exitFile}.tmp`, exitFile);
|
|
313
|
+
}
|
|
314
|
+
catch (e) {
|
|
315
|
+
// A seat directory that is already gone has nobody left to read the record. Any other failure
|
|
316
|
+
// loses the only account a later reap has, so it is said where an operator can find it, and
|
|
317
|
+
// the reap reports the record missing or unreadable.
|
|
318
|
+
if (e.code !== "ENOENT") {
|
|
319
|
+
const line = `exit record not written: ${e.message}\n`;
|
|
320
|
+
try {
|
|
321
|
+
unlinkSync(`${exitFile}.tmp`);
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
/* never created */
|
|
325
|
+
}
|
|
326
|
+
try {
|
|
327
|
+
if (launch.logPath)
|
|
328
|
+
appendFileSync(launch.logPath, line, { mode: 0o600 });
|
|
329
|
+
else
|
|
330
|
+
process.stderr.write(line);
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
/* the log can sit on the same failed storage */
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// The child is gone, so it has made every read it will make. Remove its files before anyone is
|
|
338
|
+
// told of the exit, and drop the removed ones from the record so a later reap does not remove the
|
|
339
|
+
// same names again. One that could not be removed stays listed, so the reap that proves this seat
|
|
340
|
+
// gone tries it again. Losing a controller's connection is not this: only the child's exit is.
|
|
341
|
+
if (record.artifacts) {
|
|
342
|
+
discardArtifacts(record.artifacts);
|
|
343
|
+
const left = record.artifacts.filter((dir) => existsSync(dir));
|
|
344
|
+
if (left.length)
|
|
345
|
+
record.artifacts = left;
|
|
346
|
+
else {
|
|
347
|
+
delete record.artifacts;
|
|
348
|
+
delete record.artifactRoot;
|
|
349
|
+
}
|
|
350
|
+
if (ready) {
|
|
351
|
+
try {
|
|
352
|
+
const tmp = `${launch.recordPath}.tmp`;
|
|
353
|
+
writeRecord(tmp, record);
|
|
354
|
+
renameSync(tmp, launch.recordPath);
|
|
355
|
+
}
|
|
356
|
+
catch (e) {
|
|
357
|
+
note(`${e.message}\n`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
253
361
|
if (confirmTimer) {
|
|
254
362
|
clearTimeout(confirmTimer);
|
|
255
363
|
confirmTimer = undefined;
|
|
256
364
|
}
|
|
257
365
|
for (const sock of controllers)
|
|
258
|
-
send(sock, { event: "exit" });
|
|
366
|
+
send(sock, { event: "exit", exit });
|
|
259
367
|
resolveWaiters();
|
|
260
368
|
settleTerminal();
|
|
261
369
|
armUnobservedHandoff();
|
|
262
370
|
};
|
|
263
371
|
proc.onData((d) => {
|
|
264
372
|
term.write(d);
|
|
373
|
+
diagnostic.push(d);
|
|
265
374
|
if (early.length < MAX_FRAME_SIZE) {
|
|
266
375
|
early += early.length + d.length > MAX_FRAME_SIZE ? d.slice(0, MAX_FRAME_SIZE - early.length) : d;
|
|
267
376
|
}
|
|
@@ -287,8 +396,7 @@ export async function runCustodian(launch) {
|
|
|
287
396
|
reap = undefined;
|
|
288
397
|
return;
|
|
289
398
|
}
|
|
290
|
-
|
|
291
|
-
markExited({});
|
|
399
|
+
markGone();
|
|
292
400
|
}, 50);
|
|
293
401
|
reap.unref();
|
|
294
402
|
const stopChild = (mode) => {
|
|
@@ -316,6 +424,7 @@ export async function runCustodian(launch) {
|
|
|
316
424
|
// Bind the pids to THIS boot: their start tokens are ticks since boot and the record outlives a
|
|
317
425
|
// reboot on disk, so without this a survivor could match an innocent process on the next boot.
|
|
318
426
|
...(bootId === undefined ? {} : { bootId }),
|
|
427
|
+
...(launch.artifacts?.length ? { artifacts: launch.artifacts, artifactRoot: launch.artifactRoot } : {}),
|
|
319
428
|
};
|
|
320
429
|
server = createServer((sock) => {
|
|
321
430
|
const reader = new FrameReader();
|
|
@@ -407,8 +516,7 @@ export async function runCustodian(launch) {
|
|
|
407
516
|
clearTimeout(handoffTimer);
|
|
408
517
|
handoffTimer = undefined;
|
|
409
518
|
}
|
|
410
|
-
|
|
411
|
-
markExited({});
|
|
519
|
+
markGone();
|
|
412
520
|
send(sock, {
|
|
413
521
|
id: req.id,
|
|
414
522
|
ok: true,
|
|
@@ -421,7 +529,7 @@ export async function runCustodian(launch) {
|
|
|
421
529
|
...(exit ? { exit } : {}),
|
|
422
530
|
});
|
|
423
531
|
if (!alive)
|
|
424
|
-
send(sock, { event: "exit" });
|
|
532
|
+
send(sock, { event: "exit", ...(exit ? { exit } : {}) });
|
|
425
533
|
return;
|
|
426
534
|
}
|
|
427
535
|
case "snapshot": {
|
|
@@ -439,7 +547,7 @@ export async function runCustodian(launch) {
|
|
|
439
547
|
if (early)
|
|
440
548
|
send(sock, { event: "output", sub, data: Buffer.from(early, "utf8").toString("base64") });
|
|
441
549
|
if (!alive)
|
|
442
|
-
send(sock, { event: "exit", sub });
|
|
550
|
+
send(sock, { event: "exit", sub, ...(exit ? { exit } : {}) });
|
|
443
551
|
return;
|
|
444
552
|
}
|
|
445
553
|
case "unsubscribe-output": {
|
|
@@ -483,8 +591,7 @@ export async function runCustodian(launch) {
|
|
|
483
591
|
return;
|
|
484
592
|
}
|
|
485
593
|
case "wait-exit": {
|
|
486
|
-
|
|
487
|
-
markExited({});
|
|
594
|
+
markGone();
|
|
488
595
|
if (!alive) {
|
|
489
596
|
send(sock, { id: req.id, ok: true, op: "wait-exit", exit });
|
|
490
597
|
return;
|
|
@@ -495,8 +602,7 @@ export async function runCustodian(launch) {
|
|
|
495
602
|
return;
|
|
496
603
|
}
|
|
497
604
|
case "health": {
|
|
498
|
-
|
|
499
|
-
markExited({});
|
|
605
|
+
markGone();
|
|
500
606
|
send(sock, {
|
|
501
607
|
id: req.id,
|
|
502
608
|
ok: true,
|