@action-llama/action-llama 0.27.5 → 0.28.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/dist/agents/credential-setup.d.ts +0 -1
- package/dist/agents/credential-setup.d.ts.map +1 -1
- package/dist/agents/credential-setup.js +2 -23
- package/dist/agents/credential-setup.js.map +1 -1
- package/dist/agents/prompt.js +1 -1
- package/dist/agents/prompt.js.map +1 -1
- package/dist/agents/scheduler-tools.d.ts +40 -0
- package/dist/agents/scheduler-tools.d.ts.map +1 -0
- package/dist/agents/scheduler-tools.js +178 -0
- package/dist/agents/scheduler-tools.js.map +1 -0
- package/dist/agents/transport-runner.d.ts +95 -0
- package/dist/agents/transport-runner.d.ts.map +1 -0
- package/dist/agents/transport-runner.js +653 -0
- package/dist/agents/transport-runner.js.map +1 -0
- package/dist/build-info.json +1 -1
- package/dist/cli/commands/add.d.ts +1 -0
- package/dist/cli/commands/add.d.ts.map +1 -1
- package/dist/cli/commands/add.js +24 -9
- package/dist/cli/commands/add.js.map +1 -1
- package/dist/cli/commands/agent.d.ts +0 -3
- package/dist/cli/commands/agent.d.ts.map +1 -1
- package/dist/cli/commands/agent.js +3 -67
- package/dist/cli/commands/agent.js.map +1 -1
- package/dist/cli/main.js +1 -30
- package/dist/cli/main.js.map +1 -1
- package/dist/control/routes/dashboard-api.js +1 -1
- package/dist/control/routes/dashboard-api.js.map +1 -1
- package/dist/control/routes/log-helpers.d.ts +4 -4
- package/dist/control/routes/log-helpers.d.ts.map +1 -1
- package/dist/control/routes/log-helpers.js +12 -7
- package/dist/control/routes/log-helpers.js.map +1 -1
- package/dist/control/routes/logs.d.ts.map +1 -1
- package/dist/control/routes/logs.js +10 -10
- package/dist/control/routes/logs.js.map +1 -1
- package/dist/docker/providers/index.d.ts +0 -4
- package/dist/docker/providers/index.d.ts.map +1 -1
- package/dist/docker/providers/index.js +0 -38
- package/dist/docker/providers/index.js.map +1 -1
- package/dist/execution/execution.d.ts +0 -1
- package/dist/execution/execution.d.ts.map +1 -1
- package/dist/execution/execution.js +5 -9
- package/dist/execution/execution.js.map +1 -1
- package/dist/execution/index.d.ts +1 -11
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +1 -8
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/runner-setup.d.ts +6 -11
- package/dist/execution/runner-setup.d.ts.map +1 -1
- package/dist/execution/runner-setup.js +18 -14
- package/dist/execution/runner-setup.js.map +1 -1
- package/dist/execution/runtime-factory.d.ts +1 -15
- package/dist/execution/runtime-factory.d.ts.map +1 -1
- package/dist/execution/runtime-factory.js +1 -18
- package/dist/execution/runtime-factory.js.map +1 -1
- package/dist/gateway/index.d.ts +1 -1
- package/dist/gateway/index.d.ts.map +1 -1
- package/dist/gateway/index.js +6 -47
- package/dist/gateway/index.js.map +1 -1
- package/dist/gateway/routes/system.d.ts +1 -4
- package/dist/gateway/routes/system.d.ts.map +1 -1
- package/dist/gateway/routes/system.js +3 -8
- package/dist/gateway/routes/system.js.map +1 -1
- package/dist/gateway/stores.d.ts +0 -4
- package/dist/gateway/stores.d.ts.map +1 -1
- package/dist/gateway/stores.js +2 -10
- package/dist/gateway/stores.js.map +1 -1
- package/dist/gateway/types.d.ts +0 -13
- package/dist/gateway/types.d.ts.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +11 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/scheduler/gateway-setup.d.ts +0 -2
- package/dist/scheduler/gateway-setup.d.ts.map +1 -1
- package/dist/scheduler/gateway-setup.js +2 -11
- package/dist/scheduler/gateway-setup.js.map +1 -1
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +95 -55
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/validation.js +1 -1
- package/dist/scheduler/validation.js.map +1 -1
- package/dist/scheduler/watcher.d.ts +2 -8
- package/dist/scheduler/watcher.d.ts.map +1 -1
- package/dist/scheduler/watcher.js +7 -104
- package/dist/scheduler/watcher.js.map +1 -1
- package/dist/shared/config/load-agent.js +2 -2
- package/dist/shared/config/load-agent.js.map +1 -1
- package/dist/shared/config/load-project.js +2 -2
- package/dist/shared/config/load-project.js.map +1 -1
- package/dist/shared/config/types.d.ts +10 -2
- package/dist/shared/config/types.d.ts.map +1 -1
- package/dist/shared/constants.d.ts +1 -1
- package/dist/shared/constants.d.ts.map +1 -1
- package/dist/shared/constants.js +2 -2
- package/dist/shared/constants.js.map +1 -1
- package/dist/shared/credential-refs.js +1 -1
- package/dist/shared/credential-refs.js.map +1 -1
- package/dist/shared/paths.d.ts.map +1 -1
- package/dist/shared/paths.js +2 -2
- package/dist/shared/paths.js.map +1 -1
- package/dist/shared/validation.js +1 -1
- package/dist/shared/validation.js.map +1 -1
- package/dist/transport/docker-exec.d.ts +41 -0
- package/dist/transport/docker-exec.d.ts.map +1 -0
- package/dist/transport/docker-exec.js +331 -0
- package/dist/transport/docker-exec.js.map +1 -0
- package/dist/transport/host-user.d.ts +37 -0
- package/dist/transport/host-user.d.ts.map +1 -0
- package/dist/transport/host-user.js +232 -0
- package/dist/transport/host-user.js.map +1 -0
- package/dist/transport/index.d.ts +8 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +7 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/memory.d.ts +35 -0
- package/dist/transport/memory.d.ts.map +1 -0
- package/dist/transport/memory.js +110 -0
- package/dist/transport/memory.js.map +1 -0
- package/dist/transport/operations.d.ts +68 -0
- package/dist/transport/operations.d.ts.map +1 -0
- package/dist/transport/operations.js +164 -0
- package/dist/transport/operations.js.map +1 -0
- package/dist/transport/ssh.d.ts +43 -0
- package/dist/transport/ssh.d.ts.map +1 -0
- package/dist/transport/ssh.js +225 -0
- package/dist/transport/ssh.js.map +1 -0
- package/dist/transport/transport.d.ts +59 -0
- package/dist/transport/transport.d.ts.map +1 -0
- package/dist/transport/transport.js +29 -0
- package/dist/transport/transport.js.map +1 -0
- package/package.json +1 -1
- package/dist/agents/container-entry.d.ts +0 -31
- package/dist/agents/container-entry.d.ts.map +0 -1
- package/dist/agents/container-entry.js +0 -302
- package/dist/agents/container-entry.js.map +0 -1
- package/dist/agents/container-runner.d.ts +0 -59
- package/dist/agents/container-runner.d.ts.map +0 -1
- package/dist/agents/container-runner.js +0 -472
- package/dist/agents/container-runner.js.map +0 -1
- package/dist/agents/harness/claude-cli-harness.d.ts +0 -15
- package/dist/agents/harness/claude-cli-harness.d.ts.map +0 -1
- package/dist/agents/harness/claude-cli-harness.js +0 -260
- package/dist/agents/harness/claude-cli-harness.js.map +0 -1
- package/dist/agents/harness/consumer.d.ts +0 -31
- package/dist/agents/harness/consumer.d.ts.map +0 -1
- package/dist/agents/harness/consumer.js +0 -165
- package/dist/agents/harness/consumer.js.map +0 -1
- package/dist/agents/harness/factory.d.ts +0 -9
- package/dist/agents/harness/factory.d.ts.map +0 -1
- package/dist/agents/harness/factory.js +0 -25
- package/dist/agents/harness/factory.js.map +0 -1
- package/dist/agents/harness/index.d.ts +0 -9
- package/dist/agents/harness/index.d.ts.map +0 -1
- package/dist/agents/harness/index.js +0 -5
- package/dist/agents/harness/index.js.map +0 -1
- package/dist/agents/harness/pi-harness.d.ts +0 -18
- package/dist/agents/harness/pi-harness.d.ts.map +0 -1
- package/dist/agents/harness/pi-harness.js +0 -278
- package/dist/agents/harness/pi-harness.js.map +0 -1
- package/dist/agents/harness/types.d.ts +0 -57
- package/dist/agents/harness/types.d.ts.map +0 -1
- package/dist/agents/harness/types.js +0 -2
- package/dist/agents/harness/types.js.map +0 -1
- package/dist/agents/session-loop.d.ts +0 -36
- package/dist/agents/session-loop.d.ts.map +0 -1
- package/dist/agents/session-loop.js +0 -216
- package/dist/agents/session-loop.js.map +0 -1
- package/dist/agents/signals.d.ts +0 -34
- package/dist/agents/signals.d.ts.map +0 -1
- package/dist/agents/signals.js +0 -122
- package/dist/agents/signals.js.map +0 -1
- package/dist/cli/commands/claude.d.ts +0 -4
- package/dist/cli/commands/claude.d.ts.map +0 -1
- package/dist/cli/commands/claude.js +0 -6
- package/dist/cli/commands/claude.js.map +0 -1
- package/dist/cli/commands/run-agent.d.ts +0 -14
- package/dist/cli/commands/run-agent.d.ts.map +0 -1
- package/dist/cli/commands/run-agent.js +0 -270
- package/dist/cli/commands/run-agent.js.map +0 -1
- package/dist/docker/cloud-run-runtime.d.ts +0 -48
- package/dist/docker/cloud-run-runtime.d.ts.map +0 -1
- package/dist/docker/cloud-run-runtime.js +0 -490
- package/dist/docker/cloud-run-runtime.js.map +0 -1
- package/dist/docker/image.d.ts +0 -19
- package/dist/docker/image.d.ts.map +0 -1
- package/dist/docker/image.js +0 -111
- package/dist/docker/image.js.map +0 -1
- package/dist/execution/call-dispatcher.d.ts +0 -11
- package/dist/execution/call-dispatcher.d.ts.map +0 -1
- package/dist/execution/call-dispatcher.js +0 -75
- package/dist/execution/call-dispatcher.js.map +0 -1
- package/dist/execution/container-registry.d.ts +0 -42
- package/dist/execution/container-registry.d.ts.map +0 -1
- package/dist/execution/container-registry.js +0 -76
- package/dist/execution/container-registry.js.map +0 -1
- package/dist/execution/image-builder.d.ts +0 -48
- package/dist/execution/image-builder.d.ts.map +0 -1
- package/dist/execution/image-builder.js +0 -155
- package/dist/execution/image-builder.js.map +0 -1
- package/dist/execution/routes/calls.d.ts +0 -18
- package/dist/execution/routes/calls.d.ts.map +0 -1
- package/dist/execution/routes/calls.js +0 -74
- package/dist/execution/routes/calls.js.map +0 -1
- package/dist/execution/routes/locks.d.ts +0 -10
- package/dist/execution/routes/locks.d.ts.map +0 -1
- package/dist/execution/routes/locks.js +0 -166
- package/dist/execution/routes/locks.js.map +0 -1
- package/dist/execution/routes/shutdown.d.ts +0 -5
- package/dist/execution/routes/shutdown.d.ts.map +0 -1
- package/dist/execution/routes/shutdown.js +0 -24
- package/dist/execution/routes/shutdown.js.map +0 -1
- package/dist/execution/routes/signals.d.ts +0 -12
- package/dist/execution/routes/signals.d.ts.map +0 -1
- package/dist/execution/routes/signals.js +0 -123
- package/dist/execution/routes/signals.js.map +0 -1
- package/dist/execution/types.d.ts +0 -23
- package/dist/execution/types.d.ts.map +0 -1
- package/dist/execution/types.js +0 -2
- package/dist/execution/types.js.map +0 -1
- package/dist/gateway/routes/execution.d.ts +0 -24
- package/dist/gateway/routes/execution.d.ts.map +0 -1
- package/dist/gateway/routes/execution.js +0 -13
- package/dist/gateway/routes/execution.js.map +0 -1
- package/dist/scheduler/orphan-recovery.d.ts +0 -25
- package/dist/scheduler/orphan-recovery.d.ts.map +0 -1
- package/dist/scheduler/orphan-recovery.js +0 -144
- package/dist/scheduler/orphan-recovery.js.map +0 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DockerExecTransport — executes commands inside a running Docker container
|
|
3
|
+
* via `docker exec -i`, maintaining a persistent shell session.
|
|
4
|
+
*
|
|
5
|
+
* Shell state (cwd, env vars) persists across exec() calls because all commands
|
|
6
|
+
* run in the same bash process. File I/O uses `docker cp` for efficient batch
|
|
7
|
+
* transfers without framing issues.
|
|
8
|
+
*/
|
|
9
|
+
import type { Transport, ExecResult, ExecOptions } from "./transport.js";
|
|
10
|
+
export interface DockerExecTransportOpts {
|
|
11
|
+
/** The Docker container name or ID to connect to. */
|
|
12
|
+
container: string;
|
|
13
|
+
/** User to run as inside the container. Default: undefined (container default). */
|
|
14
|
+
user?: string;
|
|
15
|
+
/** Initial working directory. Default: "/" */
|
|
16
|
+
cwd?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare class DockerExecTransport implements Transport {
|
|
19
|
+
private opts;
|
|
20
|
+
private container;
|
|
21
|
+
private shell;
|
|
22
|
+
private ready;
|
|
23
|
+
private buffer;
|
|
24
|
+
private user?;
|
|
25
|
+
private _closed;
|
|
26
|
+
constructor(opts: DockerExecTransportOpts);
|
|
27
|
+
/** Start the persistent shell session. Must be called before exec(). */
|
|
28
|
+
connect(): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Wait for a delimiter to appear in the stdout buffer.
|
|
31
|
+
* Returns the output that appeared before the delimiter.
|
|
32
|
+
* Also parses the exit code from the delimiter line.
|
|
33
|
+
*/
|
|
34
|
+
private waitForDelimiter;
|
|
35
|
+
exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
36
|
+
readFiles(paths: string[]): Promise<Map<string, Buffer>>;
|
|
37
|
+
private readSingleFile;
|
|
38
|
+
writeFiles(files: Map<string, Buffer>): Promise<void>;
|
|
39
|
+
close(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=docker-exec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-exec.d.ts","sourceRoot":"","sources":["../../src/transport/docker-exec.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAYzE,MAAM,WAAW,uBAAuB;IACtC,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,mBAAoB,YAAW,SAAS;IAQvC,OAAO,CAAC,IAAI;IAPxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,IAAI,CAAC,CAAS;IACtB,OAAO,CAAC,OAAO,CAAS;gBAEJ,IAAI,EAAE,uBAAuB;IAKjD,wEAAwE;IAClE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAmC9B;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAyClB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IA2EjE,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAmEhD,cAAc;IAiBtB,UAAU,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA+DrD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAc7B"}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DockerExecTransport — executes commands inside a running Docker container
|
|
3
|
+
* via `docker exec -i`, maintaining a persistent shell session.
|
|
4
|
+
*
|
|
5
|
+
* Shell state (cwd, env vars) persists across exec() calls because all commands
|
|
6
|
+
* run in the same bash process. File I/O uses `docker cp` for efficient batch
|
|
7
|
+
* transfers without framing issues.
|
|
8
|
+
*/
|
|
9
|
+
import { spawn, execFileSync } from "child_process";
|
|
10
|
+
import { randomBytes } from "crypto";
|
|
11
|
+
import { mkdtempSync, writeFileSync, readFileSync, readdirSync, rmSync, mkdirSync } from "fs";
|
|
12
|
+
import { join, dirname, basename } from "path";
|
|
13
|
+
import { tmpdir } from "os";
|
|
14
|
+
/** How long to wait for the shell to become ready after spawning (ms). */
|
|
15
|
+
const SHELL_READY_TIMEOUT_MS = 10_000;
|
|
16
|
+
/** Default command execution timeout (ms). */
|
|
17
|
+
const DEFAULT_EXEC_TIMEOUT_MS = 300_000;
|
|
18
|
+
/** Generate a unique delimiter that won't appear in command output. */
|
|
19
|
+
function makeDelimiter() {
|
|
20
|
+
return `__AL_DELIM_${randomBytes(8).toString("hex")}__`;
|
|
21
|
+
}
|
|
22
|
+
export class DockerExecTransport {
|
|
23
|
+
opts;
|
|
24
|
+
container;
|
|
25
|
+
shell = null;
|
|
26
|
+
ready = false;
|
|
27
|
+
buffer = "";
|
|
28
|
+
user;
|
|
29
|
+
_closed = false;
|
|
30
|
+
constructor(opts) {
|
|
31
|
+
this.opts = opts;
|
|
32
|
+
this.container = opts.container;
|
|
33
|
+
this.user = opts.user;
|
|
34
|
+
}
|
|
35
|
+
/** Start the persistent shell session. Must be called before exec(). */
|
|
36
|
+
async connect() {
|
|
37
|
+
const args = ["exec", "-i"];
|
|
38
|
+
if (this.user) {
|
|
39
|
+
args.push("-u", this.user);
|
|
40
|
+
}
|
|
41
|
+
args.push(this.container, "bash", "--norc", "--noprofile");
|
|
42
|
+
this.shell = spawn("docker", args, {
|
|
43
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
44
|
+
});
|
|
45
|
+
// Absorb EPIPE on stdin — the spawned process may exit before we write to it
|
|
46
|
+
// (e.g. container not found, Docker daemon unreachable, or mocked Docker).
|
|
47
|
+
this.shell.stdin.on("error", () => { });
|
|
48
|
+
this.shell.stdout.on("data", (chunk) => {
|
|
49
|
+
this.buffer += chunk.toString();
|
|
50
|
+
});
|
|
51
|
+
this.shell.on("exit", () => {
|
|
52
|
+
this.ready = false;
|
|
53
|
+
});
|
|
54
|
+
// Wait for shell to be ready by sending a probe command
|
|
55
|
+
const readyDelim = makeDelimiter();
|
|
56
|
+
this.shell.stdin.write(`echo "${readyDelim} $?"\n`);
|
|
57
|
+
await this.waitForDelimiter(readyDelim, SHELL_READY_TIMEOUT_MS);
|
|
58
|
+
this.ready = true;
|
|
59
|
+
// Set initial cwd if specified
|
|
60
|
+
if (this.opts.cwd) {
|
|
61
|
+
await this.exec(`cd ${shellQuote(this.opts.cwd)}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Wait for a delimiter to appear in the stdout buffer.
|
|
66
|
+
* Returns the output that appeared before the delimiter.
|
|
67
|
+
* Also parses the exit code from the delimiter line.
|
|
68
|
+
*/
|
|
69
|
+
waitForDelimiter(delimiter, timeoutMs) {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const timer = setTimeout(() => {
|
|
72
|
+
clearInterval(poll);
|
|
73
|
+
reject(new Error(`Timed out waiting for shell response (${timeoutMs}ms)`));
|
|
74
|
+
}, timeoutMs);
|
|
75
|
+
const check = () => {
|
|
76
|
+
if (this._closed) {
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
clearInterval(poll);
|
|
79
|
+
reject(new Error("Transport closed"));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const idx = this.buffer.indexOf(delimiter);
|
|
83
|
+
if (idx === -1)
|
|
84
|
+
return;
|
|
85
|
+
// Find the end of the delimiter line
|
|
86
|
+
const lineEnd = this.buffer.indexOf("\n", idx);
|
|
87
|
+
if (lineEnd === -1)
|
|
88
|
+
return;
|
|
89
|
+
const output = this.buffer.slice(0, idx);
|
|
90
|
+
const delimLine = this.buffer.slice(idx, lineEnd);
|
|
91
|
+
this.buffer = this.buffer.slice(lineEnd + 1);
|
|
92
|
+
clearTimeout(timer);
|
|
93
|
+
clearInterval(poll);
|
|
94
|
+
// Parse exit code from delimiter line: "__AL_DELIM_xxx__ 0"
|
|
95
|
+
const match = delimLine.match(/\s+(\d+)\s*$/);
|
|
96
|
+
const exitCode = match ? parseInt(match[1], 10) : 0;
|
|
97
|
+
resolve({ output, exitCode });
|
|
98
|
+
};
|
|
99
|
+
const poll = setInterval(check, 10);
|
|
100
|
+
// Check immediately in case data already arrived
|
|
101
|
+
check();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
async exec(command, options) {
|
|
105
|
+
if (!this.shell || !this.ready) {
|
|
106
|
+
throw new Error("Transport not connected. Call connect() first.");
|
|
107
|
+
}
|
|
108
|
+
const delimiter = makeDelimiter();
|
|
109
|
+
let stderr = "";
|
|
110
|
+
// Capture stderr for this command
|
|
111
|
+
const stderrHandler = (chunk) => {
|
|
112
|
+
stderr += chunk.toString();
|
|
113
|
+
};
|
|
114
|
+
this.shell.stderr.on("data", stderrHandler);
|
|
115
|
+
// Set up abort handling
|
|
116
|
+
let aborted = false;
|
|
117
|
+
const onAbort = () => {
|
|
118
|
+
aborted = true;
|
|
119
|
+
// Send Ctrl+C to the shell to interrupt the running command
|
|
120
|
+
this.shell?.stdin?.write("\x03\n");
|
|
121
|
+
};
|
|
122
|
+
if (options?.signal) {
|
|
123
|
+
if (options.signal.aborted) {
|
|
124
|
+
this.shell?.stderr?.removeListener("data", stderrHandler);
|
|
125
|
+
return { stdout: "", stderr: "Aborted", exitCode: 130 };
|
|
126
|
+
}
|
|
127
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
128
|
+
}
|
|
129
|
+
const timeoutMs = options?.timeout ?? DEFAULT_EXEC_TIMEOUT_MS;
|
|
130
|
+
// Set up streaming if onData callback provided
|
|
131
|
+
let streamingInterval;
|
|
132
|
+
let lastStreamedIdx = 0;
|
|
133
|
+
if (options?.onData) {
|
|
134
|
+
streamingInterval = setInterval(() => {
|
|
135
|
+
if (this.buffer.length > lastStreamedIdx) {
|
|
136
|
+
const newData = this.buffer.slice(lastStreamedIdx);
|
|
137
|
+
// Don't stream past a delimiter if one appeared
|
|
138
|
+
const delimIdx = newData.indexOf("__AL_DELIM_");
|
|
139
|
+
const safeData = delimIdx >= 0 ? newData.slice(0, delimIdx) : newData;
|
|
140
|
+
if (safeData.length > 0) {
|
|
141
|
+
options.onData(Buffer.from(safeData));
|
|
142
|
+
lastStreamedIdx += safeData.length;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}, 50);
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
// Send the command, then send the delimiter probe.
|
|
149
|
+
// The delimiter line echoes the exit code of the command ($?).
|
|
150
|
+
this.shell.stdin.write(`${command}\necho "${delimiter} $?"\n`);
|
|
151
|
+
const { output, exitCode } = await this.waitForDelimiter(delimiter, timeoutMs);
|
|
152
|
+
return {
|
|
153
|
+
stdout: output.trimEnd(),
|
|
154
|
+
stderr: stderr.trimEnd(),
|
|
155
|
+
exitCode: aborted ? 130 : exitCode,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
if (aborted) {
|
|
160
|
+
return { stdout: "", stderr: "Aborted", exitCode: 130 };
|
|
161
|
+
}
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
this.shell?.stderr?.removeListener("data", stderrHandler);
|
|
166
|
+
if (options?.signal) {
|
|
167
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
168
|
+
}
|
|
169
|
+
if (streamingInterval)
|
|
170
|
+
clearInterval(streamingInterval);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async readFiles(paths) {
|
|
174
|
+
const result = new Map();
|
|
175
|
+
if (paths.length === 0)
|
|
176
|
+
return result;
|
|
177
|
+
if (paths.length === 1) {
|
|
178
|
+
return this.readSingleFile(paths[0]);
|
|
179
|
+
}
|
|
180
|
+
// Batch read: tar on the container → docker cp the tar → extract locally
|
|
181
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "al-read-"));
|
|
182
|
+
try {
|
|
183
|
+
const tarName = `al-batch-${randomBytes(4).toString("hex")}.tar`;
|
|
184
|
+
const remoteTar = `/tmp/${tarName}`;
|
|
185
|
+
// Create tar on container (ignoring missing files, using absolute paths)
|
|
186
|
+
const pathArgs = paths.map(p => shellQuote(p)).join(" ");
|
|
187
|
+
execFileSync("docker", [
|
|
188
|
+
"exec", this.container,
|
|
189
|
+
"bash", "-c", `tar cf ${remoteTar} --ignore-failed-read -P ${pathArgs} 2>/dev/null; true`,
|
|
190
|
+
], {
|
|
191
|
+
timeout: 30_000,
|
|
192
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
193
|
+
});
|
|
194
|
+
// Copy tar to local
|
|
195
|
+
execFileSync("docker", ["cp", `${this.container}:${remoteTar}`, join(tmpDir, "batch.tar")], {
|
|
196
|
+
timeout: 30_000,
|
|
197
|
+
});
|
|
198
|
+
// Clean up tar on container
|
|
199
|
+
execFileSync("docker", ["exec", this.container, "rm", "-f", remoteTar], {
|
|
200
|
+
timeout: 5_000,
|
|
201
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
202
|
+
});
|
|
203
|
+
// Extract locally (with -P to preserve absolute paths)
|
|
204
|
+
execFileSync("tar", ["xf", join(tmpDir, "batch.tar"), "-P", "-C", tmpDir], {
|
|
205
|
+
timeout: 30_000,
|
|
206
|
+
});
|
|
207
|
+
// Read extracted files — tar with -P preserves the full path under tmpDir
|
|
208
|
+
for (const path of paths) {
|
|
209
|
+
const localPath = join(tmpDir, path);
|
|
210
|
+
try {
|
|
211
|
+
result.set(path, readFileSync(localPath));
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// File wasn't in the tar (didn't exist on container)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Fallback: read files one by one
|
|
220
|
+
for (const path of paths) {
|
|
221
|
+
try {
|
|
222
|
+
const single = await this.readSingleFile(path);
|
|
223
|
+
const content = single.get(path);
|
|
224
|
+
if (content)
|
|
225
|
+
result.set(path, content);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
// Skip missing files
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
234
|
+
}
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
async readSingleFile(path) {
|
|
238
|
+
const result = new Map();
|
|
239
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "al-read-"));
|
|
240
|
+
try {
|
|
241
|
+
execFileSync("docker", ["cp", `${this.container}:${path}`, join(tmpDir, "file")], {
|
|
242
|
+
timeout: 30_000,
|
|
243
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
244
|
+
});
|
|
245
|
+
result.set(path, readFileSync(join(tmpDir, "file")));
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
// File not found — omit from result
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
252
|
+
}
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
async writeFiles(files) {
|
|
256
|
+
if (files.size === 0)
|
|
257
|
+
return;
|
|
258
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "al-write-"));
|
|
259
|
+
try {
|
|
260
|
+
if (files.size === 1) {
|
|
261
|
+
const [[path, content]] = [...files.entries()];
|
|
262
|
+
// Ensure parent directory exists on container
|
|
263
|
+
const dir = dirname(path);
|
|
264
|
+
if (dir !== "/" && dir !== ".") {
|
|
265
|
+
execFileSync("docker", ["exec", this.container, "mkdir", "-p", dir], {
|
|
266
|
+
timeout: 10_000,
|
|
267
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
const localPath = join(tmpDir, basename(path));
|
|
271
|
+
writeFileSync(localPath, content);
|
|
272
|
+
execFileSync("docker", ["cp", localPath, `${this.container}:${path}`], {
|
|
273
|
+
timeout: 30_000,
|
|
274
|
+
});
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
// Batch write: create a tar locally → docker cp to container → extract
|
|
278
|
+
for (const [path, content] of files) {
|
|
279
|
+
const localPath = join(tmpDir, "payload", path);
|
|
280
|
+
mkdirSync(dirname(localPath), { recursive: true });
|
|
281
|
+
writeFileSync(localPath, content);
|
|
282
|
+
}
|
|
283
|
+
// Create tar preserving absolute paths.
|
|
284
|
+
// List top-level entries explicitly (not ".") to avoid a "." entry in the
|
|
285
|
+
// tar that maps to "/" on extract and fails with permission errors when
|
|
286
|
+
// the container user is non-root.
|
|
287
|
+
const tarPath = join(tmpDir, "batch.tar");
|
|
288
|
+
const payloadDir = join(tmpDir, "payload");
|
|
289
|
+
const topEntries = readdirSync(payloadDir);
|
|
290
|
+
execFileSync("tar", ["cf", tarPath, "-P", "-C", payloadDir, ...topEntries], {
|
|
291
|
+
timeout: 30_000,
|
|
292
|
+
});
|
|
293
|
+
// Copy tar to container
|
|
294
|
+
const remoteTar = `/tmp/al-batch-${randomBytes(4).toString("hex")}.tar`;
|
|
295
|
+
execFileSync("docker", ["cp", tarPath, `${this.container}:${remoteTar}`], {
|
|
296
|
+
timeout: 30_000,
|
|
297
|
+
});
|
|
298
|
+
// Extract on container and clean up
|
|
299
|
+
execFileSync("docker", [
|
|
300
|
+
"exec", this.container,
|
|
301
|
+
"bash", "-c", `tar xf ${remoteTar} -P -C / && rm -f ${remoteTar}`,
|
|
302
|
+
], {
|
|
303
|
+
timeout: 30_000,
|
|
304
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
finally {
|
|
308
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
async close() {
|
|
312
|
+
this._closed = true;
|
|
313
|
+
if (this.shell) {
|
|
314
|
+
this.ready = false;
|
|
315
|
+
try {
|
|
316
|
+
this.shell.stdin?.write("exit\n");
|
|
317
|
+
this.shell.stdin?.end();
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
// stdin may already be closed
|
|
321
|
+
}
|
|
322
|
+
this.shell.kill();
|
|
323
|
+
this.shell = null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/** Escape a string for use in a shell command. */
|
|
328
|
+
function shellQuote(s) {
|
|
329
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
330
|
+
}
|
|
331
|
+
//# sourceMappingURL=docker-exec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-exec.js","sourceRoot":"","sources":["../../src/transport/docker-exec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HostUserTransport — executes commands as a different OS user via `sudo -u`,
|
|
3
|
+
* maintaining a persistent shell session.
|
|
4
|
+
*
|
|
5
|
+
* File I/O uses direct filesystem access since we're on the same machine —
|
|
6
|
+
* files are read/written as root (the scheduler process) then chowned to
|
|
7
|
+
* the target user.
|
|
8
|
+
*/
|
|
9
|
+
import type { Transport, ExecResult, ExecOptions } from "./transport.js";
|
|
10
|
+
export interface HostUserTransportOpts {
|
|
11
|
+
/** OS user to run commands as. */
|
|
12
|
+
user: string;
|
|
13
|
+
/** Additional OS groups. */
|
|
14
|
+
groups?: string[];
|
|
15
|
+
/** Initial working directory. Default: user's home dir. */
|
|
16
|
+
cwd?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare class HostUserTransport implements Transport {
|
|
19
|
+
private opts;
|
|
20
|
+
private shell;
|
|
21
|
+
private ready;
|
|
22
|
+
private buffer;
|
|
23
|
+
private uid?;
|
|
24
|
+
private gid?;
|
|
25
|
+
constructor(opts: HostUserTransportOpts);
|
|
26
|
+
/** Start the persistent shell as the target user. Must be called before exec(). */
|
|
27
|
+
connect(): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Wait for a delimiter to appear in the stdout buffer.
|
|
30
|
+
*/
|
|
31
|
+
private waitForDelimiter;
|
|
32
|
+
exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
33
|
+
readFiles(paths: string[]): Promise<Map<string, Buffer>>;
|
|
34
|
+
writeFiles(files: Map<string, Buffer>): Promise<void>;
|
|
35
|
+
close(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=host-user.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-user.d.ts","sourceRoot":"","sources":["../../src/transport/host-user.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAYzE,MAAM,WAAW,qBAAqB;IACpC,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,2DAA2D;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,iBAAkB,YAAW,SAAS;IAOrC,OAAO,CAAC,IAAI;IANxB,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,GAAG,CAAC,CAAS;IACrB,OAAO,CAAC,GAAG,CAAC,CAAS;gBAED,IAAI,EAAE,qBAAqB;IAE/C,mFAAmF;IAC7E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAwC9B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAgClB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAqEjE,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAgBxD,UAAU,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBrD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAa7B"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HostUserTransport — executes commands as a different OS user via `sudo -u`,
|
|
3
|
+
* maintaining a persistent shell session.
|
|
4
|
+
*
|
|
5
|
+
* File I/O uses direct filesystem access since we're on the same machine —
|
|
6
|
+
* files are read/written as root (the scheduler process) then chowned to
|
|
7
|
+
* the target user.
|
|
8
|
+
*/
|
|
9
|
+
import { spawn } from "child_process";
|
|
10
|
+
import { randomBytes } from "crypto";
|
|
11
|
+
import { readFileSync, writeFileSync, mkdirSync, chownSync, statSync, } from "fs";
|
|
12
|
+
import { dirname } from "path";
|
|
13
|
+
/** How long to wait for the shell to become ready after spawning (ms). */
|
|
14
|
+
const SHELL_READY_TIMEOUT_MS = 10_000;
|
|
15
|
+
/** Default command execution timeout (ms). */
|
|
16
|
+
const DEFAULT_EXEC_TIMEOUT_MS = 300_000;
|
|
17
|
+
/** Generate a unique delimiter that won't appear in command output. */
|
|
18
|
+
function makeDelimiter() {
|
|
19
|
+
return `__AL_DELIM_${randomBytes(8).toString("hex")}__`;
|
|
20
|
+
}
|
|
21
|
+
export class HostUserTransport {
|
|
22
|
+
opts;
|
|
23
|
+
shell = null;
|
|
24
|
+
ready = false;
|
|
25
|
+
buffer = "";
|
|
26
|
+
uid;
|
|
27
|
+
gid;
|
|
28
|
+
constructor(opts) {
|
|
29
|
+
this.opts = opts;
|
|
30
|
+
}
|
|
31
|
+
/** Start the persistent shell as the target user. Must be called before exec(). */
|
|
32
|
+
async connect() {
|
|
33
|
+
const args = ["-u", this.opts.user, "--", "bash", "--norc", "--noprofile"];
|
|
34
|
+
// If groups are specified, use sg or newgrp — but sudo -u is simpler
|
|
35
|
+
// Groups are set via the user's OS group membership, not per-command
|
|
36
|
+
this.shell = spawn("sudo", args, {
|
|
37
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
38
|
+
});
|
|
39
|
+
this.shell.stdout.on("data", (chunk) => {
|
|
40
|
+
this.buffer += chunk.toString();
|
|
41
|
+
});
|
|
42
|
+
this.shell.on("exit", () => {
|
|
43
|
+
this.ready = false;
|
|
44
|
+
});
|
|
45
|
+
// Wait for shell to be ready
|
|
46
|
+
const readyDelim = makeDelimiter();
|
|
47
|
+
this.shell.stdin.write(`echo "${readyDelim} $?"\n`);
|
|
48
|
+
await this.waitForDelimiter(readyDelim, SHELL_READY_TIMEOUT_MS);
|
|
49
|
+
this.ready = true;
|
|
50
|
+
// Resolve the user's uid/gid for file ownership
|
|
51
|
+
try {
|
|
52
|
+
const { stdout } = await this.exec("id -u");
|
|
53
|
+
this.uid = parseInt(stdout.trim(), 10);
|
|
54
|
+
const { stdout: gidStr } = await this.exec("id -g");
|
|
55
|
+
this.gid = parseInt(gidStr.trim(), 10);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Non-critical — files won't be chowned if we can't resolve uid/gid
|
|
59
|
+
}
|
|
60
|
+
// Set initial cwd if specified
|
|
61
|
+
if (this.opts.cwd) {
|
|
62
|
+
await this.exec(`cd ${shellQuote(this.opts.cwd)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Wait for a delimiter to appear in the stdout buffer.
|
|
67
|
+
*/
|
|
68
|
+
waitForDelimiter(delimiter, timeoutMs) {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const timer = setTimeout(() => {
|
|
71
|
+
clearInterval(poll);
|
|
72
|
+
reject(new Error(`Timed out waiting for shell response (${timeoutMs}ms)`));
|
|
73
|
+
}, timeoutMs);
|
|
74
|
+
const check = () => {
|
|
75
|
+
const idx = this.buffer.indexOf(delimiter);
|
|
76
|
+
if (idx === -1)
|
|
77
|
+
return;
|
|
78
|
+
const lineEnd = this.buffer.indexOf("\n", idx);
|
|
79
|
+
if (lineEnd === -1)
|
|
80
|
+
return;
|
|
81
|
+
const output = this.buffer.slice(0, idx);
|
|
82
|
+
const delimLine = this.buffer.slice(idx, lineEnd);
|
|
83
|
+
this.buffer = this.buffer.slice(lineEnd + 1);
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
clearInterval(poll);
|
|
86
|
+
const match = delimLine.match(/\s+(\d+)\s*$/);
|
|
87
|
+
const exitCode = match ? parseInt(match[1], 10) : 0;
|
|
88
|
+
resolve({ output, exitCode });
|
|
89
|
+
};
|
|
90
|
+
const poll = setInterval(check, 10);
|
|
91
|
+
check();
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async exec(command, options) {
|
|
95
|
+
if (!this.shell || !this.ready) {
|
|
96
|
+
throw new Error("Transport not connected. Call connect() first.");
|
|
97
|
+
}
|
|
98
|
+
const delimiter = makeDelimiter();
|
|
99
|
+
let stderr = "";
|
|
100
|
+
const stderrHandler = (chunk) => {
|
|
101
|
+
stderr += chunk.toString();
|
|
102
|
+
};
|
|
103
|
+
this.shell.stderr.on("data", stderrHandler);
|
|
104
|
+
// Abort handling
|
|
105
|
+
let aborted = false;
|
|
106
|
+
const onAbort = () => {
|
|
107
|
+
aborted = true;
|
|
108
|
+
this.shell?.stdin?.write("\x03\n");
|
|
109
|
+
};
|
|
110
|
+
if (options?.signal) {
|
|
111
|
+
if (options.signal.aborted) {
|
|
112
|
+
this.shell.stderr.removeListener("data", stderrHandler);
|
|
113
|
+
return { stdout: "", stderr: "Aborted", exitCode: 130 };
|
|
114
|
+
}
|
|
115
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
116
|
+
}
|
|
117
|
+
const timeoutMs = options?.timeout ?? DEFAULT_EXEC_TIMEOUT_MS;
|
|
118
|
+
// Streaming
|
|
119
|
+
let streamingInterval;
|
|
120
|
+
let lastStreamedIdx = 0;
|
|
121
|
+
if (options?.onData) {
|
|
122
|
+
streamingInterval = setInterval(() => {
|
|
123
|
+
if (this.buffer.length > lastStreamedIdx) {
|
|
124
|
+
const newData = this.buffer.slice(lastStreamedIdx);
|
|
125
|
+
const delimIdx = newData.indexOf("__AL_DELIM_");
|
|
126
|
+
const safeData = delimIdx >= 0 ? newData.slice(0, delimIdx) : newData;
|
|
127
|
+
if (safeData.length > 0) {
|
|
128
|
+
options.onData(Buffer.from(safeData));
|
|
129
|
+
lastStreamedIdx += safeData.length;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}, 50);
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
this.shell.stdin.write(`${command}\necho "${delimiter} $?"\n`);
|
|
136
|
+
const { output, exitCode } = await this.waitForDelimiter(delimiter, timeoutMs);
|
|
137
|
+
return {
|
|
138
|
+
stdout: output.trimEnd(),
|
|
139
|
+
stderr: stderr.trimEnd(),
|
|
140
|
+
exitCode: aborted ? 130 : exitCode,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
if (aborted) {
|
|
145
|
+
return { stdout: "", stderr: "Aborted", exitCode: 130 };
|
|
146
|
+
}
|
|
147
|
+
throw err;
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
this.shell.stderr.removeListener("data", stderrHandler);
|
|
151
|
+
if (options?.signal) {
|
|
152
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
153
|
+
}
|
|
154
|
+
if (streamingInterval)
|
|
155
|
+
clearInterval(streamingInterval);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async readFiles(paths) {
|
|
159
|
+
const result = new Map();
|
|
160
|
+
if (paths.length === 0)
|
|
161
|
+
return result;
|
|
162
|
+
// Direct filesystem read — the scheduler process can read any file
|
|
163
|
+
for (const path of paths) {
|
|
164
|
+
try {
|
|
165
|
+
result.set(path, readFileSync(path));
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Skip missing files
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
async writeFiles(files) {
|
|
174
|
+
if (files.size === 0)
|
|
175
|
+
return;
|
|
176
|
+
for (const [path, content] of files) {
|
|
177
|
+
const dir = dirname(path);
|
|
178
|
+
if (dir !== "/" && dir !== ".") {
|
|
179
|
+
mkdirSync(dir, { recursive: true });
|
|
180
|
+
// Chown directory to target user
|
|
181
|
+
if (this.uid != null && this.gid != null) {
|
|
182
|
+
try {
|
|
183
|
+
chownRecursiveNew(dir, this.uid, this.gid);
|
|
184
|
+
}
|
|
185
|
+
catch { /* best effort */ }
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
writeFileSync(path, content);
|
|
189
|
+
// Chown file to target user so they can read/write it
|
|
190
|
+
if (this.uid != null && this.gid != null) {
|
|
191
|
+
try {
|
|
192
|
+
chownSync(path, this.uid, this.gid);
|
|
193
|
+
}
|
|
194
|
+
catch { /* best effort */ }
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async close() {
|
|
199
|
+
if (this.shell) {
|
|
200
|
+
this.ready = false;
|
|
201
|
+
try {
|
|
202
|
+
this.shell.stdin?.write("exit\n");
|
|
203
|
+
this.shell.stdin?.end();
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// stdin may already be closed
|
|
207
|
+
}
|
|
208
|
+
this.shell.kill();
|
|
209
|
+
this.shell = null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/** Escape a string for use in a shell command. */
|
|
214
|
+
function shellQuote(s) {
|
|
215
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Recursively chown newly created directories up to an existing parent.
|
|
219
|
+
* Only chowns directories that were created by mkdirSync (newly created).
|
|
220
|
+
*/
|
|
221
|
+
function chownRecursiveNew(dir, uid, gid) {
|
|
222
|
+
try {
|
|
223
|
+
const stat = statSync(dir);
|
|
224
|
+
if (stat.uid !== uid) {
|
|
225
|
+
chownSync(dir, uid, gid);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// Directory doesn't exist or inaccessible
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=host-user.js.map
|