vmsan 0.1.0-alpha.15 → 0.1.0-alpha.17
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/_chunks/agent.mjs +231 -4
- package/dist/_chunks/connect.mjs +1 -3
- package/dist/_chunks/context.mjs +3 -33
- package/dist/_chunks/create.mjs +1 -0
- package/dist/_chunks/exec.mjs +191 -0
- package/dist/_chunks/list.mjs +1 -0
- package/dist/_chunks/network.mjs +1 -0
- package/dist/_chunks/remove.mjs +1 -0
- package/dist/_chunks/shell.mjs +1 -0
- package/dist/_chunks/start.mjs +1 -0
- package/dist/_chunks/stop.mjs +1 -0
- package/dist/_chunks/summary.mjs +1 -1
- package/dist/_chunks/timeout-extender.mjs +66 -0
- package/dist/_chunks/timeout-killer.mjs +33 -0
- package/dist/bin/cli.mjs +1 -0
- package/dist/index.d.mts +91 -2
- package/dist/index.mjs +3 -57
- package/package.json +1 -1
package/dist/_chunks/agent.mjs
CHANGED
|
@@ -1,6 +1,196 @@
|
|
|
1
1
|
import { createGzip } from "node:zlib";
|
|
2
2
|
import { Readable } from "node:stream";
|
|
3
3
|
import { pack } from "tar-stream";
|
|
4
|
+
/**
|
|
5
|
+
* Lightweight async iterable queue for pushing events and yielding them to consumers.
|
|
6
|
+
*/
|
|
7
|
+
var AsyncQueue = class {
|
|
8
|
+
_buffer = [];
|
|
9
|
+
_waiting = [];
|
|
10
|
+
_closed = false;
|
|
11
|
+
push(item) {
|
|
12
|
+
if (this._closed) return;
|
|
13
|
+
if (this._waiting.length > 0) this._waiting.shift()({
|
|
14
|
+
value: item,
|
|
15
|
+
done: false
|
|
16
|
+
});
|
|
17
|
+
else this._buffer.push(item);
|
|
18
|
+
}
|
|
19
|
+
close() {
|
|
20
|
+
this._closed = true;
|
|
21
|
+
for (const resolve of this._waiting) resolve({
|
|
22
|
+
value: void 0,
|
|
23
|
+
done: true
|
|
24
|
+
});
|
|
25
|
+
this._waiting.length = 0;
|
|
26
|
+
}
|
|
27
|
+
async *[Symbol.asyncIterator]() {
|
|
28
|
+
while (true) if (this._buffer.length > 0) yield this._buffer.shift();
|
|
29
|
+
else if (this._closed) return;
|
|
30
|
+
else {
|
|
31
|
+
const item = await new Promise((resolve) => {
|
|
32
|
+
this._waiting.push(resolve);
|
|
33
|
+
});
|
|
34
|
+
if (item.done) return;
|
|
35
|
+
yield item.value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var CommandFinished = class {
|
|
40
|
+
cmdId;
|
|
41
|
+
exitCode;
|
|
42
|
+
stdout;
|
|
43
|
+
stderr;
|
|
44
|
+
output;
|
|
45
|
+
timedOut;
|
|
46
|
+
startedAt;
|
|
47
|
+
constructor(opts) {
|
|
48
|
+
this.cmdId = opts.cmdId;
|
|
49
|
+
this.exitCode = opts.exitCode;
|
|
50
|
+
this.stdout = opts.stdout;
|
|
51
|
+
this.stderr = opts.stderr;
|
|
52
|
+
this.output = opts.output;
|
|
53
|
+
this.timedOut = opts.timedOut;
|
|
54
|
+
this.startedAt = opts.startedAt;
|
|
55
|
+
}
|
|
56
|
+
get ok() {
|
|
57
|
+
return this.exitCode === 0;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const MAX_LOG_ENTRIES = 1e5;
|
|
61
|
+
var Command = class {
|
|
62
|
+
cmdId;
|
|
63
|
+
_startedAt;
|
|
64
|
+
_exitCode = null;
|
|
65
|
+
_timedOut = false;
|
|
66
|
+
_logEntries = [];
|
|
67
|
+
_logTruncated = false;
|
|
68
|
+
_eventQueue = new AsyncQueue();
|
|
69
|
+
_completion;
|
|
70
|
+
_stdoutPromise = null;
|
|
71
|
+
_stderrPromise = null;
|
|
72
|
+
_outputPromise = null;
|
|
73
|
+
_agent;
|
|
74
|
+
constructor(init) {
|
|
75
|
+
const { agent, cmdId, startedAt, stream, signal, onStdout, onStderr } = init;
|
|
76
|
+
this._agent = agent;
|
|
77
|
+
this.cmdId = cmdId;
|
|
78
|
+
this._startedAt = startedAt;
|
|
79
|
+
let resolveCompletion;
|
|
80
|
+
let rejectCompletion;
|
|
81
|
+
this._completion = new Promise((resolve, reject) => {
|
|
82
|
+
resolveCompletion = resolve;
|
|
83
|
+
rejectCompletion = reject;
|
|
84
|
+
});
|
|
85
|
+
let onAbort;
|
|
86
|
+
if (signal) {
|
|
87
|
+
onAbort = () => {
|
|
88
|
+
this.kill().catch(() => {});
|
|
89
|
+
};
|
|
90
|
+
if (signal.aborted) this.kill().catch(() => {});
|
|
91
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
92
|
+
}
|
|
93
|
+
(async () => {
|
|
94
|
+
try {
|
|
95
|
+
for await (const event of stream) {
|
|
96
|
+
this._eventQueue.push(event);
|
|
97
|
+
switch (event.type) {
|
|
98
|
+
case "stdout":
|
|
99
|
+
case "stderr":
|
|
100
|
+
if (event.data !== void 0) {
|
|
101
|
+
if (this._logEntries.length < MAX_LOG_ENTRIES) this._logEntries.push({
|
|
102
|
+
stream: event.type,
|
|
103
|
+
data: event.data
|
|
104
|
+
});
|
|
105
|
+
else this._logTruncated = true;
|
|
106
|
+
(event.type === "stdout" ? onStdout : onStderr)?.(event.data);
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
case "exit":
|
|
110
|
+
this._exitCode = event.exitCode ?? 1;
|
|
111
|
+
break;
|
|
112
|
+
case "timeout":
|
|
113
|
+
this._timedOut = true;
|
|
114
|
+
this._exitCode = 124;
|
|
115
|
+
break;
|
|
116
|
+
case "error":
|
|
117
|
+
this._exitCode = 1;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
this._eventQueue.close();
|
|
122
|
+
resolveCompletion(new CommandFinished({
|
|
123
|
+
cmdId,
|
|
124
|
+
exitCode: this._exitCode ?? 1,
|
|
125
|
+
stdout: this._logEntries.filter((e) => e.stream === "stdout").map((e) => e.data).join("\n"),
|
|
126
|
+
stderr: this._logEntries.filter((e) => e.stream === "stderr").map((e) => e.data).join("\n"),
|
|
127
|
+
output: this._logEntries.map((e) => e.data).join("\n"),
|
|
128
|
+
timedOut: this._timedOut,
|
|
129
|
+
startedAt
|
|
130
|
+
}));
|
|
131
|
+
this._logEntries.length = 0;
|
|
132
|
+
} catch (err) {
|
|
133
|
+
this._eventQueue.close();
|
|
134
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
135
|
+
rejectCompletion(error);
|
|
136
|
+
} finally {
|
|
137
|
+
if (signal && onAbort) signal.removeEventListener("abort", onAbort);
|
|
138
|
+
}
|
|
139
|
+
})();
|
|
140
|
+
}
|
|
141
|
+
get startedAt() {
|
|
142
|
+
return this._startedAt;
|
|
143
|
+
}
|
|
144
|
+
get exitCode() {
|
|
145
|
+
return this._exitCode;
|
|
146
|
+
}
|
|
147
|
+
async *logs(opts) {
|
|
148
|
+
const signal = opts?.signal;
|
|
149
|
+
if (signal?.aborted) return;
|
|
150
|
+
for await (const event of this._eventQueue) {
|
|
151
|
+
if (signal?.aborted) return;
|
|
152
|
+
if (event.type === "stdout" || event.type === "stderr") yield {
|
|
153
|
+
stream: event.type,
|
|
154
|
+
data: event.data ?? ""
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
stdout(opts) {
|
|
159
|
+
if (!this._stdoutPromise) this._stdoutPromise = this._completion.then((r) => r.stdout);
|
|
160
|
+
return this._withSignal(this._stdoutPromise, opts?.signal);
|
|
161
|
+
}
|
|
162
|
+
stderr(opts) {
|
|
163
|
+
if (!this._stderrPromise) this._stderrPromise = this._completion.then((r) => r.stderr);
|
|
164
|
+
return this._withSignal(this._stderrPromise, opts?.signal);
|
|
165
|
+
}
|
|
166
|
+
output(stream, opts) {
|
|
167
|
+
if (stream === "stdout") return this.stdout(opts);
|
|
168
|
+
if (stream === "stderr") return this.stderr(opts);
|
|
169
|
+
if (!this._outputPromise) this._outputPromise = this._completion.then((r) => r.output);
|
|
170
|
+
return this._withSignal(this._outputPromise, opts?.signal);
|
|
171
|
+
}
|
|
172
|
+
wait(opts) {
|
|
173
|
+
return this._withSignal(this._completion, opts?.signal);
|
|
174
|
+
}
|
|
175
|
+
async kill(signal, opts) {
|
|
176
|
+
await this._agent.killCommand(this.cmdId, signal, opts?.abortSignal);
|
|
177
|
+
}
|
|
178
|
+
_withSignal(promise, signal) {
|
|
179
|
+
if (!signal) return promise;
|
|
180
|
+
if (signal.aborted) return Promise.reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
const onAbort = () => reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
183
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
184
|
+
promise.then((value) => {
|
|
185
|
+
signal.removeEventListener("abort", onAbort);
|
|
186
|
+
resolve(value);
|
|
187
|
+
}, (err) => {
|
|
188
|
+
signal.removeEventListener("abort", onAbort);
|
|
189
|
+
reject(err);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
};
|
|
4
194
|
var AgentClient = class {
|
|
5
195
|
constructor(baseUrl, token) {
|
|
6
196
|
this.baseUrl = baseUrl;
|
|
@@ -11,14 +201,15 @@ var AgentClient = class {
|
|
|
11
201
|
if (!res.ok) throw new Error(`Agent health check failed: ${res.status}`);
|
|
12
202
|
return res.json();
|
|
13
203
|
}
|
|
14
|
-
async *run(params) {
|
|
204
|
+
async *run(params, signal) {
|
|
15
205
|
const res = await fetch(`${this.baseUrl}/exec`, {
|
|
16
206
|
method: "POST",
|
|
17
207
|
headers: {
|
|
18
208
|
"Content-Type": "application/json",
|
|
19
209
|
Authorization: `Bearer ${this.token}`
|
|
20
210
|
},
|
|
21
|
-
body: JSON.stringify(params)
|
|
211
|
+
body: JSON.stringify(params),
|
|
212
|
+
signal
|
|
22
213
|
});
|
|
23
214
|
if (!res.ok) {
|
|
24
215
|
const text = await res.text();
|
|
@@ -41,7 +232,7 @@ var AgentClient = class {
|
|
|
41
232
|
}
|
|
42
233
|
if (buffer.trim()) yield JSON.parse(buffer.trim());
|
|
43
234
|
}
|
|
44
|
-
async killCommand(cmdId, signal) {
|
|
235
|
+
async killCommand(cmdId, signal, abortSignal) {
|
|
45
236
|
const url = `${this.baseUrl}/exec/${cmdId}/kill`;
|
|
46
237
|
const res = await fetch(url, {
|
|
47
238
|
method: "POST",
|
|
@@ -49,7 +240,8 @@ var AgentClient = class {
|
|
|
49
240
|
"Content-Type": "application/json",
|
|
50
241
|
Authorization: `Bearer ${this.token}`
|
|
51
242
|
},
|
|
52
|
-
body: signal ? JSON.stringify({ signal }) : void 0
|
|
243
|
+
body: signal ? JSON.stringify({ signal }) : void 0,
|
|
244
|
+
signal: abortSignal
|
|
53
245
|
});
|
|
54
246
|
if (!res.ok) {
|
|
55
247
|
const text = await res.text();
|
|
@@ -94,6 +286,41 @@ var AgentClient = class {
|
|
|
94
286
|
throw new Error(`Agent killShellSession failed (${res.status}): ${text}`);
|
|
95
287
|
}
|
|
96
288
|
}
|
|
289
|
+
async exec(params, opts) {
|
|
290
|
+
const stream = this.run(params, opts?.signal);
|
|
291
|
+
const first = await stream.next();
|
|
292
|
+
if (first.done) throw new Error("Stream ended without 'started' event");
|
|
293
|
+
if (first.value.type !== "started") throw new Error(`Expected 'started' event, got '${first.value.type}'`);
|
|
294
|
+
const cmdId = first.value.id;
|
|
295
|
+
if (!cmdId) throw new Error("'started' event missing 'id' field");
|
|
296
|
+
const startedAt = first.value.ts ? new Date(first.value.ts) : /* @__PURE__ */ new Date();
|
|
297
|
+
return new Command({
|
|
298
|
+
agent: this,
|
|
299
|
+
cmdId,
|
|
300
|
+
startedAt,
|
|
301
|
+
stream,
|
|
302
|
+
signal: opts?.signal,
|
|
303
|
+
onStdout: opts?.onStdout,
|
|
304
|
+
onStderr: opts?.onStderr
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
async runCommand(cmdOrParams, args, opts) {
|
|
308
|
+
let params;
|
|
309
|
+
if (typeof cmdOrParams === "string") params = {
|
|
310
|
+
cmd: cmdOrParams,
|
|
311
|
+
args,
|
|
312
|
+
signal: opts?.signal
|
|
313
|
+
};
|
|
314
|
+
else params = cmdOrParams;
|
|
315
|
+
const { signal, onStdout, onStderr, ...runParams } = params;
|
|
316
|
+
const command = await this.exec(runParams, {
|
|
317
|
+
signal,
|
|
318
|
+
onStdout,
|
|
319
|
+
onStderr
|
|
320
|
+
});
|
|
321
|
+
if (params.detached) return command;
|
|
322
|
+
return command.wait({ signal });
|
|
323
|
+
}
|
|
97
324
|
async readFile(path) {
|
|
98
325
|
const res = await fetch(`${this.baseUrl}/files/read`, {
|
|
99
326
|
method: "POST",
|
package/dist/_chunks/connect.mjs
CHANGED
|
@@ -29,11 +29,9 @@ const connectCommand = defineCommand({
|
|
|
29
29
|
const paths = vmsanPaths();
|
|
30
30
|
try {
|
|
31
31
|
const { state, guestIp, port } = resolveVmState(args.vmId, paths);
|
|
32
|
-
const log = consola.withTag(args.vmId);
|
|
33
32
|
consola.debug(`Agent endpoint: ${guestIp}:${port}`);
|
|
34
|
-
|
|
33
|
+
consola.debug("Waiting for agent to become ready...");
|
|
35
34
|
await waitForAgent(guestIp, port);
|
|
36
|
-
log.success("Agent is ready. Connecting via PTY shell...");
|
|
37
35
|
const shell = new ShellSession({
|
|
38
36
|
host: guestIp,
|
|
39
37
|
port,
|
package/dist/_chunks/context.mjs
CHANGED
|
@@ -2,9 +2,10 @@ import { n as vmsanPaths } from "./paths.mjs";
|
|
|
2
2
|
import { B as mutuallyExclusiveFlagsError, C as vmNotStoppedError, S as vmNotRunningError, U as VmsanError, _ as chrootNotFoundError, a as noKernelDirError, d as socketTimeoutError, i as noExt4RootfsError, o as noKernelError, p as defaultInterfaceNotFoundError, r as missingBinaryError, s as noRootfsDirError, u as lockTimeoutError, x as vmNotFoundError, y as snapshotNotFoundError } from "./errors.mjs";
|
|
3
3
|
import { c as safeKill, f as toError, i as generateVmId, t as FileVmStateStore } from "./vm-state.mjs";
|
|
4
4
|
import { t as FirecrackerClient } from "./firecracker.mjs";
|
|
5
|
+
import { t as spawnTimeoutKiller } from "./timeout-killer.mjs";
|
|
5
6
|
import { createHooks } from "hookable";
|
|
6
7
|
import { dirname, join } from "node:path";
|
|
7
|
-
import { execFileSync, execSync
|
|
8
|
+
import { execFileSync, execSync } from "node:child_process";
|
|
8
9
|
import { copyFileSync, existsSync, linkSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
9
10
|
import { consola } from "consola";
|
|
10
11
|
import { randomBytes } from "node:crypto";
|
|
@@ -1825,37 +1826,6 @@ function ensureSeccompFilter(paths) {
|
|
|
1825
1826
|
return null;
|
|
1826
1827
|
}
|
|
1827
1828
|
}
|
|
1828
|
-
/**
|
|
1829
|
-
* Spawn a detached bash process that kills the VM after timeout.
|
|
1830
|
-
* The process sleeps for the timeout duration, then verifies the VM
|
|
1831
|
-
* is still running with the expected PID before sending SIGTERM.
|
|
1832
|
-
*/
|
|
1833
|
-
function spawnTimeoutKiller(opts) {
|
|
1834
|
-
const { vmId, pid, timeoutMs, stateFile } = opts;
|
|
1835
|
-
const timeoutSec = String(Math.ceil(timeoutMs / 1e3));
|
|
1836
|
-
const killer = spawn("bash", [
|
|
1837
|
-
"-c",
|
|
1838
|
-
[
|
|
1839
|
-
"sleep \"$1\"",
|
|
1840
|
-
"STATE=$(cat -- \"$2\" 2>/dev/null) || exit 0",
|
|
1841
|
-
"echo \"$STATE\" | grep -q '\"status\":\"running\"' || exit 0",
|
|
1842
|
-
"echo \"$STATE\" | grep -q \"\"pid\":$3\" || exit 0",
|
|
1843
|
-
"[ -d \"/proc/$3\" ] || exit 0",
|
|
1844
|
-
"grep -aq -- \"$4\" \"/proc/$3/cmdline\" 2>/dev/null || exit 0",
|
|
1845
|
-
"kill -- \"$3\" 2>/dev/null"
|
|
1846
|
-
].join(" && "),
|
|
1847
|
-
"bash",
|
|
1848
|
-
timeoutSec,
|
|
1849
|
-
stateFile,
|
|
1850
|
-
String(pid),
|
|
1851
|
-
vmId
|
|
1852
|
-
], {
|
|
1853
|
-
detached: true,
|
|
1854
|
-
stdio: "ignore"
|
|
1855
|
-
});
|
|
1856
|
-
killer.unref();
|
|
1857
|
-
return killer;
|
|
1858
|
-
}
|
|
1859
1829
|
var VMService = class {
|
|
1860
1830
|
paths;
|
|
1861
1831
|
store;
|
|
@@ -2394,4 +2364,4 @@ async function createVmsan(options) {
|
|
|
2394
2364
|
if (options?.plugins) for (const plugin of options.plugins) await plugin.setup(ctx);
|
|
2395
2365
|
return vmsan;
|
|
2396
2366
|
}
|
|
2397
|
-
export {
|
|
2367
|
+
export { createSilentLogger as C, createDefaultLogger as S, waitForSocket as _, resolveImageRootfs as a, FileLock as b, cleanupNetwork as c, assertSnapshotExists as d, findKernel as f, validateEnvironment as g, getVmPid as h, ensureSeccompFilter as i, killOrphanVmProcess as l, getVmJailerPid as m, VMService as n, buildInitialVmState as o, findRootfs as p, compileSeccompFilter as r, cleanupChroot as s, createVmsan as t, markVmAsError as u, Jailer as v, NetworkManager as x, detectCgroupVersion as y };
|
package/dist/_chunks/create.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { i as initVmsanLogger, n as createScopedLogger, t as createCommandLogger
|
|
|
4
4
|
import "./vm-state.mjs";
|
|
5
5
|
import { t as createVmsan } from "./context.mjs";
|
|
6
6
|
import "./firecracker.mjs";
|
|
7
|
+
import "./timeout-killer.mjs";
|
|
7
8
|
import { n as waitForAgent } from "./vm-context.mjs";
|
|
8
9
|
import { t as ShellSession } from "./shell.mjs";
|
|
9
10
|
import { a as parseImageReference, t as parseBandwidth } from "./validation.mjs";
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { n as vmsanPaths } from "./paths.mjs";
|
|
2
|
+
import { t as handleCommandError } from "./errors.mjs";
|
|
3
|
+
import { t as createCommandLogger } from "./logger.mjs";
|
|
4
|
+
import "./vm-state.mjs";
|
|
5
|
+
import "./timeout-killer.mjs";
|
|
6
|
+
import { t as AgentClient } from "./agent.mjs";
|
|
7
|
+
import { t as TimeoutExtender } from "./timeout-extender.mjs";
|
|
8
|
+
import { n as waitForAgent, t as resolveVmState } from "./vm-context.mjs";
|
|
9
|
+
import { t as ShellSession } from "./shell.mjs";
|
|
10
|
+
import { consola } from "consola";
|
|
11
|
+
import { defineCommand } from "citty";
|
|
12
|
+
import { isatty } from "node:tty";
|
|
13
|
+
function shellEscape(s) {
|
|
14
|
+
if (/^[a-zA-Z0-9._\-/=:@]+$/.test(s)) return s;
|
|
15
|
+
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
16
|
+
}
|
|
17
|
+
function parseEnvFlags(targetCommand) {
|
|
18
|
+
const env = {};
|
|
19
|
+
const argv = process.argv;
|
|
20
|
+
let positionalCount = 0;
|
|
21
|
+
for (let i = 2; i < argv.length; i++) {
|
|
22
|
+
const arg = argv[i];
|
|
23
|
+
if (arg === "--") break;
|
|
24
|
+
if (!arg.startsWith("-")) {
|
|
25
|
+
positionalCount++;
|
|
26
|
+
if (positionalCount >= 3) break;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
let value;
|
|
30
|
+
if (arg === "--env" || arg === "-e") {
|
|
31
|
+
value = argv[i + 1];
|
|
32
|
+
if (value !== void 0) i++;
|
|
33
|
+
} else if (arg.startsWith("--env=")) value = arg.slice(6);
|
|
34
|
+
else if (arg.startsWith("-e=")) value = arg.slice(3);
|
|
35
|
+
if (value !== void 0) {
|
|
36
|
+
const eqIdx = value.indexOf("=");
|
|
37
|
+
if (eqIdx > 0) env[value.slice(0, eqIdx)] = value.slice(eqIdx + 1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return env;
|
|
41
|
+
}
|
|
42
|
+
function buildVmsanPrompt(vmId) {
|
|
43
|
+
return `\\[\\033[1;32m\\]vmsan:${vmId.slice(0, 8)}\\[\\033[0m\\]:\\[\\033[1;34m\\]\\w\\[\\033[0m\\]\\$ `;
|
|
44
|
+
}
|
|
45
|
+
const execCommand = defineCommand({
|
|
46
|
+
meta: {
|
|
47
|
+
name: "exec",
|
|
48
|
+
description: "Execute a command inside a running VM"
|
|
49
|
+
},
|
|
50
|
+
args: {
|
|
51
|
+
vmId: {
|
|
52
|
+
type: "positional",
|
|
53
|
+
description: "VM ID, followed by command and arguments",
|
|
54
|
+
required: true
|
|
55
|
+
},
|
|
56
|
+
sudo: {
|
|
57
|
+
type: "boolean",
|
|
58
|
+
default: false,
|
|
59
|
+
description: "Run with extended privileges (sudo)"
|
|
60
|
+
},
|
|
61
|
+
interactive: {
|
|
62
|
+
type: "boolean",
|
|
63
|
+
alias: "i",
|
|
64
|
+
default: false,
|
|
65
|
+
description: "Interactive shell mode (PTY)"
|
|
66
|
+
},
|
|
67
|
+
"no-extend-timeout": {
|
|
68
|
+
type: "boolean",
|
|
69
|
+
default: false,
|
|
70
|
+
description: "Skip timeout extension (interactive only)"
|
|
71
|
+
},
|
|
72
|
+
tty: {
|
|
73
|
+
type: "boolean",
|
|
74
|
+
alias: "t",
|
|
75
|
+
default: false,
|
|
76
|
+
description: "Allocate a pseudo-TTY (accepted for compatibility)"
|
|
77
|
+
},
|
|
78
|
+
workdir: {
|
|
79
|
+
type: "string",
|
|
80
|
+
alias: "w",
|
|
81
|
+
description: "Working directory inside the VM"
|
|
82
|
+
},
|
|
83
|
+
env: {
|
|
84
|
+
type: "string",
|
|
85
|
+
alias: "e",
|
|
86
|
+
description: "Environment variable (KEY=VAL), repeatable"
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
async run({ args }) {
|
|
90
|
+
const cmdLog = createCommandLogger("exec");
|
|
91
|
+
const paths = vmsanPaths();
|
|
92
|
+
try {
|
|
93
|
+
const command = args._[1];
|
|
94
|
+
const commandArgs = args._.slice(2);
|
|
95
|
+
if (!command) {
|
|
96
|
+
consola.error("No command provided. Usage: vmsan exec <vm_id> <command> [...args]");
|
|
97
|
+
cmdLog.emit();
|
|
98
|
+
process.exitCode = 1;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (args.interactive && !isatty(1)) {
|
|
102
|
+
consola.error("--interactive requires a terminal (TTY).");
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const envVars = parseEnvFlags(command);
|
|
107
|
+
const { state, guestIp, port, store } = resolveVmState(args.vmId, paths);
|
|
108
|
+
consola.debug(`Agent endpoint: ${guestIp}:${port}`);
|
|
109
|
+
consola.debug(`Waiting for agent on ${guestIp}:${port}...`);
|
|
110
|
+
await waitForAgent(guestIp, port);
|
|
111
|
+
if (args.interactive) {
|
|
112
|
+
const parts = [];
|
|
113
|
+
parts.push(`export PS1=${shellEscape(buildVmsanPrompt(args.vmId))} TERM=xterm-256color &&`);
|
|
114
|
+
if (args.workdir) parts.push(`cd ${shellEscape(args.workdir)} &&`);
|
|
115
|
+
for (const [key, val] of Object.entries(envVars)) parts.push(`${key}=${shellEscape(val)}`);
|
|
116
|
+
if (args.sudo) parts.push("/usr/bin/sudo");
|
|
117
|
+
parts.push(shellEscape(command));
|
|
118
|
+
for (const a of commandArgs) parts.push(shellEscape(a));
|
|
119
|
+
const injectedCmd = "clear; " + parts.join(" ") + "; exit $?\n";
|
|
120
|
+
let extender = null;
|
|
121
|
+
if (!args["no-extend-timeout"] && state.timeoutMs) {
|
|
122
|
+
extender = new TimeoutExtender({
|
|
123
|
+
vmId: args.vmId,
|
|
124
|
+
store,
|
|
125
|
+
paths
|
|
126
|
+
});
|
|
127
|
+
extender.start();
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
await new ShellSession({
|
|
131
|
+
host: guestIp,
|
|
132
|
+
port,
|
|
133
|
+
token: state.agentToken,
|
|
134
|
+
initialCommand: injectedCmd
|
|
135
|
+
}).connect();
|
|
136
|
+
} finally {
|
|
137
|
+
extender?.stop();
|
|
138
|
+
}
|
|
139
|
+
cmdLog.set({
|
|
140
|
+
vmId: args.vmId,
|
|
141
|
+
mode: "interactive",
|
|
142
|
+
command,
|
|
143
|
+
args: commandArgs,
|
|
144
|
+
...args["no-extend-timeout"] && { noExtendTimeout: true }
|
|
145
|
+
});
|
|
146
|
+
cmdLog.emit();
|
|
147
|
+
} else {
|
|
148
|
+
consola.debug(`exec: ${command} ${commandArgs.join(" ")}`);
|
|
149
|
+
const agent = new AgentClient(`http://${guestIp}:${port}`, state.agentToken);
|
|
150
|
+
const cmd = args.sudo ? "/usr/bin/sudo" : command;
|
|
151
|
+
const runArgs = args.sudo ? [command, ...commandArgs] : commandArgs;
|
|
152
|
+
const params = {
|
|
153
|
+
cmd,
|
|
154
|
+
args: runArgs.length > 0 ? runArgs : void 0,
|
|
155
|
+
cwd: args.workdir || void 0,
|
|
156
|
+
env: Object.keys(envVars).length > 0 ? envVars : void 0
|
|
157
|
+
};
|
|
158
|
+
const ac = new AbortController();
|
|
159
|
+
const onSignal = () => ac.abort();
|
|
160
|
+
process.on("SIGINT", onSignal);
|
|
161
|
+
process.on("SIGTERM", onSignal);
|
|
162
|
+
try {
|
|
163
|
+
const result = await (await agent.exec(params, {
|
|
164
|
+
signal: ac.signal,
|
|
165
|
+
onStdout: (line) => process.stdout.write(line + "\n"),
|
|
166
|
+
onStderr: (line) => process.stderr.write(line + "\n")
|
|
167
|
+
})).wait();
|
|
168
|
+
process.exitCode = result.exitCode;
|
|
169
|
+
if (result.timedOut) consola.error("Command timed out.");
|
|
170
|
+
} catch (err) {
|
|
171
|
+
if (!ac.signal.aborted) throw err;
|
|
172
|
+
process.exitCode = 130;
|
|
173
|
+
} finally {
|
|
174
|
+
process.removeListener("SIGINT", onSignal);
|
|
175
|
+
process.removeListener("SIGTERM", onSignal);
|
|
176
|
+
}
|
|
177
|
+
cmdLog.set({
|
|
178
|
+
vmId: args.vmId,
|
|
179
|
+
mode: "non-interactive",
|
|
180
|
+
command,
|
|
181
|
+
args: commandArgs
|
|
182
|
+
});
|
|
183
|
+
cmdLog.emit();
|
|
184
|
+
}
|
|
185
|
+
} catch (error) {
|
|
186
|
+
handleCommandError(error, cmdLog);
|
|
187
|
+
process.exitCode = 1;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
export { execCommand as default };
|
package/dist/_chunks/list.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { r as getOutputMode, t as createCommandLogger } from "./logger.mjs";
|
|
|
4
4
|
import { d as timeRemaining, l as table, u as timeAgo } from "./vm-state.mjs";
|
|
5
5
|
import { t as createVmsan } from "./context.mjs";
|
|
6
6
|
import "./firecracker.mjs";
|
|
7
|
+
import "./timeout-killer.mjs";
|
|
7
8
|
import { consola } from "consola";
|
|
8
9
|
import { defineCommand } from "citty";
|
|
9
10
|
const STATUS_COLORS = {
|
package/dist/_chunks/network.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { r as getOutputMode, t as createCommandLogger } from "./logger.mjs";
|
|
|
4
4
|
import "./vm-state.mjs";
|
|
5
5
|
import { t as createVmsan } from "./context.mjs";
|
|
6
6
|
import "./firecracker.mjs";
|
|
7
|
+
import "./timeout-killer.mjs";
|
|
7
8
|
import { d as validateCidr, i as parseDomains, n as parseCidrList, s as parseNetworkPolicy } from "./validation.mjs";
|
|
8
9
|
import { consola } from "consola";
|
|
9
10
|
import { defineCommand } from "citty";
|
package/dist/_chunks/remove.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { t as createCommandLogger } from "./logger.mjs";
|
|
|
4
4
|
import "./vm-state.mjs";
|
|
5
5
|
import { t as createVmsan } from "./context.mjs";
|
|
6
6
|
import "./firecracker.mjs";
|
|
7
|
+
import "./timeout-killer.mjs";
|
|
7
8
|
import { consola } from "consola";
|
|
8
9
|
import { defineCommand } from "citty";
|
|
9
10
|
const removeCommand = defineCommand({
|
package/dist/_chunks/shell.mjs
CHANGED
|
@@ -100,6 +100,7 @@ var ShellSession = class {
|
|
|
100
100
|
process.stdin.resume();
|
|
101
101
|
this.ws.send(serializeReady());
|
|
102
102
|
if (process.stdout.columns && process.stdout.rows) this.ws.send(serializeResize(process.stdout.columns, process.stdout.rows));
|
|
103
|
+
if (this.opts.initialCommand) this.ws.send(serializeData(Buffer.from(this.opts.initialCommand)));
|
|
103
104
|
process.stdin.on("data", onStdinData);
|
|
104
105
|
process.stdout.on("resize", onResize);
|
|
105
106
|
});
|
package/dist/_chunks/start.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { t as createCommandLogger } from "./logger.mjs";
|
|
|
4
4
|
import "./vm-state.mjs";
|
|
5
5
|
import { t as createVmsan } from "./context.mjs";
|
|
6
6
|
import "./firecracker.mjs";
|
|
7
|
+
import "./timeout-killer.mjs";
|
|
7
8
|
import { defineCommand } from "citty";
|
|
8
9
|
const startCommand = defineCommand({
|
|
9
10
|
meta: {
|
package/dist/_chunks/stop.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { t as createCommandLogger } from "./logger.mjs";
|
|
|
4
4
|
import "./vm-state.mjs";
|
|
5
5
|
import { t as createVmsan } from "./context.mjs";
|
|
6
6
|
import "./firecracker.mjs";
|
|
7
|
+
import "./timeout-killer.mjs";
|
|
7
8
|
import { consola } from "consola";
|
|
8
9
|
import { defineCommand } from "citty";
|
|
9
10
|
const stopCommand = defineCommand({
|
package/dist/_chunks/summary.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { V as policyConflictError } from "./errors.mjs";
|
|
2
2
|
import { s as parseDuration } from "./vm-state.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { d as assertSnapshotExists } from "./context.mjs";
|
|
4
4
|
import { c as parsePublishedPorts, d as validateCidr, f as validatePublishedPortsAvailable, i as parseDomains, l as parseRuntime, n as parseCidrList, o as parseMemoryMib, r as parseDiskSizeGb, s as parseNetworkPolicy, u as parseVcpuCount } from "./validation.mjs";
|
|
5
5
|
function parseCreateInput(args, paths) {
|
|
6
6
|
const vcpus = parseVcpuCount(args.vcpus);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { c as safeKill } from "./vm-state.mjs";
|
|
2
|
+
import { t as spawnTimeoutKiller } from "./timeout-killer.mjs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const DEFAULT_INTERVAL_MS = 300 * 1e3;
|
|
5
|
+
var TimeoutExtender = class {
|
|
6
|
+
_timer = null;
|
|
7
|
+
_previousKillerPid = null;
|
|
8
|
+
_vmId;
|
|
9
|
+
_store;
|
|
10
|
+
_paths;
|
|
11
|
+
_intervalMs;
|
|
12
|
+
_signal;
|
|
13
|
+
constructor(opts) {
|
|
14
|
+
this._vmId = opts.vmId;
|
|
15
|
+
this._store = opts.store;
|
|
16
|
+
this._paths = opts.paths;
|
|
17
|
+
this._intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
18
|
+
this._signal = opts.signal;
|
|
19
|
+
}
|
|
20
|
+
start() {
|
|
21
|
+
if (this._timer) return;
|
|
22
|
+
if (this._signal) {
|
|
23
|
+
this._signal.addEventListener("abort", () => this.stop(), { once: true });
|
|
24
|
+
if (this._signal.aborted) {
|
|
25
|
+
this.stop();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
this._extendSafe();
|
|
30
|
+
this._timer = setInterval(() => this._extendSafe(), this._intervalMs);
|
|
31
|
+
}
|
|
32
|
+
stop() {
|
|
33
|
+
if (this._timer) {
|
|
34
|
+
clearInterval(this._timer);
|
|
35
|
+
this._timer = null;
|
|
36
|
+
}
|
|
37
|
+
if (this._previousKillerPid !== null) {
|
|
38
|
+
safeKill(this._previousKillerPid);
|
|
39
|
+
this._previousKillerPid = null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
_extendSafe() {
|
|
43
|
+
try {
|
|
44
|
+
this._extend();
|
|
45
|
+
} catch {
|
|
46
|
+
this.stop();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
_extend() {
|
|
50
|
+
const state = this._store.load(this._vmId);
|
|
51
|
+
if (!state || state.status !== "running" || !state.timeoutMs) return;
|
|
52
|
+
const timeoutAt = new Date(Date.now() + state.timeoutMs).toISOString();
|
|
53
|
+
this._store.update(this._vmId, { timeoutAt });
|
|
54
|
+
if (this._previousKillerPid !== null) {
|
|
55
|
+
safeKill(this._previousKillerPid);
|
|
56
|
+
this._previousKillerPid = null;
|
|
57
|
+
}
|
|
58
|
+
if (state.pid) this._previousKillerPid = spawnTimeoutKiller({
|
|
59
|
+
vmId: this._vmId,
|
|
60
|
+
pid: state.pid,
|
|
61
|
+
timeoutMs: state.timeoutMs,
|
|
62
|
+
stateFile: join(this._paths.vmsDir, `${this._vmId}.json`)
|
|
63
|
+
}).pid ?? null;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
export { TimeoutExtender as t };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
/**
|
|
3
|
+
* Spawn a detached bash process that kills the VM after timeout.
|
|
4
|
+
* The process sleeps for the timeout duration, then verifies the VM
|
|
5
|
+
* is still running with the expected PID before sending SIGTERM.
|
|
6
|
+
*/
|
|
7
|
+
function spawnTimeoutKiller(opts) {
|
|
8
|
+
const { vmId, pid, timeoutMs, stateFile } = opts;
|
|
9
|
+
const timeoutSec = String(Math.ceil(timeoutMs / 1e3));
|
|
10
|
+
const killer = spawn("bash", [
|
|
11
|
+
"-c",
|
|
12
|
+
[
|
|
13
|
+
"sleep \"$1\"",
|
|
14
|
+
"STATE=$(cat -- \"$2\" 2>/dev/null) || exit 0",
|
|
15
|
+
"echo \"$STATE\" | grep -q '\"status\":\"running\"' || exit 0",
|
|
16
|
+
"echo \"$STATE\" | grep -q '\"pid\":'\"$3\" || exit 0",
|
|
17
|
+
"[ -d \"/proc/$3\" ] || exit 0",
|
|
18
|
+
"grep -aq -- \"$4\" \"/proc/$3/cmdline\" 2>/dev/null || exit 0",
|
|
19
|
+
"kill -- \"$3\" 2>/dev/null"
|
|
20
|
+
].join(" && "),
|
|
21
|
+
"bash",
|
|
22
|
+
timeoutSec,
|
|
23
|
+
stateFile,
|
|
24
|
+
String(pid),
|
|
25
|
+
vmId
|
|
26
|
+
], {
|
|
27
|
+
detached: true,
|
|
28
|
+
stdio: "ignore"
|
|
29
|
+
});
|
|
30
|
+
killer.unref();
|
|
31
|
+
return killer;
|
|
32
|
+
}
|
|
33
|
+
export { spawnTimeoutKiller as t };
|
package/dist/bin/cli.mjs
CHANGED
|
@@ -59,6 +59,7 @@ runMain(defineCommand({
|
|
|
59
59
|
connect: () => import("../_chunks/connect.mjs").then((m) => m.default),
|
|
60
60
|
upload: () => import("../_chunks/upload.mjs").then((m) => m.default),
|
|
61
61
|
download: () => import("../_chunks/download.mjs").then((m) => m.default),
|
|
62
|
+
exec: () => import("../_chunks/exec.mjs").then((m) => m.default),
|
|
62
63
|
network: () => import("../_chunks/network.mjs").then((m) => m.default)
|
|
63
64
|
}
|
|
64
65
|
}));
|
package/dist/index.d.mts
CHANGED
|
@@ -2779,6 +2779,76 @@ declare class FirecrackerClient {
|
|
|
2779
2779
|
static getVersion(baseDir: string): Promise<string | undefined>;
|
|
2780
2780
|
}
|
|
2781
2781
|
//#endregion
|
|
2782
|
+
//#region src/lib/command.d.ts
|
|
2783
|
+
interface LogEntry {
|
|
2784
|
+
stream: "stdout" | "stderr";
|
|
2785
|
+
data: string;
|
|
2786
|
+
}
|
|
2787
|
+
interface CommandInit {
|
|
2788
|
+
agent: AgentClient;
|
|
2789
|
+
cmdId: string;
|
|
2790
|
+
startedAt: Date;
|
|
2791
|
+
stream: AsyncIterable<RunEvent>;
|
|
2792
|
+
signal?: AbortSignal;
|
|
2793
|
+
onStdout?: (line: string) => void;
|
|
2794
|
+
onStderr?: (line: string) => void;
|
|
2795
|
+
}
|
|
2796
|
+
declare class CommandFinished {
|
|
2797
|
+
readonly cmdId: string;
|
|
2798
|
+
readonly exitCode: number;
|
|
2799
|
+
readonly stdout: string;
|
|
2800
|
+
readonly stderr: string;
|
|
2801
|
+
readonly output: string;
|
|
2802
|
+
readonly timedOut: boolean;
|
|
2803
|
+
readonly startedAt: Date;
|
|
2804
|
+
constructor(opts: {
|
|
2805
|
+
cmdId: string;
|
|
2806
|
+
exitCode: number;
|
|
2807
|
+
stdout: string;
|
|
2808
|
+
stderr: string;
|
|
2809
|
+
output: string;
|
|
2810
|
+
timedOut: boolean;
|
|
2811
|
+
startedAt: Date;
|
|
2812
|
+
});
|
|
2813
|
+
get ok(): boolean;
|
|
2814
|
+
}
|
|
2815
|
+
declare class Command {
|
|
2816
|
+
readonly cmdId: string;
|
|
2817
|
+
private _startedAt;
|
|
2818
|
+
private _exitCode;
|
|
2819
|
+
private _timedOut;
|
|
2820
|
+
private _logEntries;
|
|
2821
|
+
private _logTruncated;
|
|
2822
|
+
private _eventQueue;
|
|
2823
|
+
private _completion;
|
|
2824
|
+
private _stdoutPromise;
|
|
2825
|
+
private _stderrPromise;
|
|
2826
|
+
private _outputPromise;
|
|
2827
|
+
private _agent;
|
|
2828
|
+
constructor(init: CommandInit);
|
|
2829
|
+
get startedAt(): Date;
|
|
2830
|
+
get exitCode(): number | null;
|
|
2831
|
+
logs(opts?: {
|
|
2832
|
+
signal?: AbortSignal;
|
|
2833
|
+
}): AsyncGenerator<LogEntry>;
|
|
2834
|
+
stdout(opts?: {
|
|
2835
|
+
signal?: AbortSignal;
|
|
2836
|
+
}): Promise<string>;
|
|
2837
|
+
stderr(opts?: {
|
|
2838
|
+
signal?: AbortSignal;
|
|
2839
|
+
}): Promise<string>;
|
|
2840
|
+
output(stream?: "stdout" | "stderr" | "both", opts?: {
|
|
2841
|
+
signal?: AbortSignal;
|
|
2842
|
+
}): Promise<string>;
|
|
2843
|
+
wait(opts?: {
|
|
2844
|
+
signal?: AbortSignal;
|
|
2845
|
+
}): Promise<CommandFinished>;
|
|
2846
|
+
kill(signal?: string, opts?: {
|
|
2847
|
+
abortSignal?: AbortSignal;
|
|
2848
|
+
}): Promise<void>;
|
|
2849
|
+
private _withSignal;
|
|
2850
|
+
}
|
|
2851
|
+
//#endregion
|
|
2782
2852
|
//#region src/services/agent.d.ts
|
|
2783
2853
|
interface RunParams {
|
|
2784
2854
|
cmd: string;
|
|
@@ -2808,6 +2878,11 @@ interface SessionInfo {
|
|
|
2808
2878
|
createdAt: string;
|
|
2809
2879
|
subscriberCount: number;
|
|
2810
2880
|
}
|
|
2881
|
+
interface RunCommandParams extends RunParams {
|
|
2882
|
+
signal?: AbortSignal;
|
|
2883
|
+
onStdout?: (line: string) => void;
|
|
2884
|
+
onStderr?: (line: string) => void;
|
|
2885
|
+
}
|
|
2811
2886
|
declare class AgentClient {
|
|
2812
2887
|
private baseUrl;
|
|
2813
2888
|
private token;
|
|
@@ -2816,11 +2891,23 @@ declare class AgentClient {
|
|
|
2816
2891
|
status: string;
|
|
2817
2892
|
version: string;
|
|
2818
2893
|
}>;
|
|
2819
|
-
run(params: RunParams): AsyncGenerator<RunEvent>;
|
|
2820
|
-
killCommand(cmdId: string, signal?: string): Promise<void>;
|
|
2894
|
+
run(params: RunParams, signal?: AbortSignal): AsyncGenerator<RunEvent>;
|
|
2895
|
+
killCommand(cmdId: string, signal?: string, abortSignal?: AbortSignal): Promise<void>;
|
|
2821
2896
|
writeFiles(files: WriteFileEntry[], extractDir?: string): Promise<void>;
|
|
2822
2897
|
listShellSessions(): Promise<SessionInfo[]>;
|
|
2823
2898
|
killShellSession(sessionId: string): Promise<void>;
|
|
2899
|
+
exec(params: RunParams, opts?: {
|
|
2900
|
+
signal?: AbortSignal;
|
|
2901
|
+
onStdout?: (line: string) => void;
|
|
2902
|
+
onStderr?: (line: string) => void;
|
|
2903
|
+
}): Promise<Command>;
|
|
2904
|
+
runCommand(cmd: string, args?: string[], opts?: {
|
|
2905
|
+
signal?: AbortSignal;
|
|
2906
|
+
}): Promise<CommandFinished>;
|
|
2907
|
+
runCommand(params: RunCommandParams & {
|
|
2908
|
+
detached: true;
|
|
2909
|
+
}): Promise<Command>;
|
|
2910
|
+
runCommand(params: RunCommandParams): Promise<CommandFinished>;
|
|
2824
2911
|
readFile(path: string): Promise<Buffer | null>;
|
|
2825
2912
|
}
|
|
2826
2913
|
//#endregion
|
|
@@ -2843,6 +2930,7 @@ declare class TimeoutExtender {
|
|
|
2843
2930
|
constructor(opts: TimeoutExtenderOptions);
|
|
2844
2931
|
start(): void;
|
|
2845
2932
|
stop(): void;
|
|
2933
|
+
private _extendSafe;
|
|
2846
2934
|
private _extend;
|
|
2847
2935
|
}
|
|
2848
2936
|
//#endregion
|
|
@@ -2954,6 +3042,7 @@ interface ShellSessionOptions {
|
|
|
2954
3042
|
token: string;
|
|
2955
3043
|
shell?: string;
|
|
2956
3044
|
sessionId?: string;
|
|
3045
|
+
initialCommand?: string;
|
|
2957
3046
|
}
|
|
2958
3047
|
interface ShellCloseInfo {
|
|
2959
3048
|
/** true when the shell process exited (e.g. user typed `exit`) */
|
package/dist/index.mjs
CHANGED
|
@@ -2,73 +2,19 @@ import { n as vmsanPaths } from "./_chunks/paths.mjs";
|
|
|
2
2
|
import { A as invalidDiskSizeRangeError, B as mutuallyExclusiveFlagsError, C as vmNotStoppedError, D as invalidCidrOctetError, E as invalidCidrFormatError, F as invalidImageRefTagError, H as portConflictError, I as invalidIntegerFlagError, L as invalidNetworkPolicyError, M as invalidDomainPatternError, N as invalidDurationError, O as invalidCidrPrefixError, P as invalidImageRefEmptyError, R as invalidPortError, S as vmNotRunningError, T as ValidationError, U as VmsanError, V as policyConflictError, _ as chrootNotFoundError, a as noKernelDirError, b as vmNoAgentTokenError, c as TimeoutError, d as socketTimeoutError, f as NetworkError, g as VmError, h as firecrackerApiError, i as noExt4RootfsError, j as invalidDomainError, k as invalidDiskSizeFormatError, l as agentTimeoutError, m as FirecrackerApiError, n as SetupError, o as noKernelError, p as defaultInterfaceNotFoundError, r as missingBinaryError, s as noRootfsDirError, t as handleCommandError, u as lockTimeoutError, v as networkSlotsExhaustedError, w as vmStateNotFoundError, x as vmNotFoundError, y as snapshotNotFoundError, z as invalidRuntimeError } from "./_chunks/errors.mjs";
|
|
3
3
|
import { i as initVmsanLogger, n as createScopedLogger, r as getOutputMode, t as createCommandLogger } from "./_chunks/logger.mjs";
|
|
4
4
|
import { a as isProcessAlive, c as safeKill, d as timeRemaining, f as toError, i as generateVmId, l as table, n as findFreeNetworkSlot, o as mkdirSecure, p as writeSecure, r as getActiveTapSlots, s as parseDuration, t as FileVmStateStore, u as timeAgo } from "./_chunks/vm-state.mjs";
|
|
5
|
-
import { C as
|
|
5
|
+
import { C as createSilentLogger, S as createDefaultLogger, _ as waitForSocket, a as resolveImageRootfs, b as FileLock, c as cleanupNetwork, f as findKernel, g as validateEnvironment, h as getVmPid, i as ensureSeccompFilter, l as killOrphanVmProcess, m as getVmJailerPid, n as VMService, o as buildInitialVmState, p as findRootfs, r as compileSeccompFilter, s as cleanupChroot, t as createVmsan, u as markVmAsError, v as Jailer, x as NetworkManager, y as detectCgroupVersion } from "./_chunks/context.mjs";
|
|
6
6
|
import { n as firecrackerFetch, t as FirecrackerClient } from "./_chunks/firecracker.mjs";
|
|
7
|
+
import { t as spawnTimeoutKiller } from "./_chunks/timeout-killer.mjs";
|
|
7
8
|
import { t as AgentClient } from "./_chunks/agent.mjs";
|
|
9
|
+
import { t as TimeoutExtender } from "./_chunks/timeout-extender.mjs";
|
|
8
10
|
import { n as waitForAgent, t as resolveVmState } from "./_chunks/vm-context.mjs";
|
|
9
11
|
import { n as connectShell, t as ShellSession } from "./_chunks/shell.mjs";
|
|
10
12
|
import { a as parseImageReference, c as parsePublishedPorts, d as validateCidr, f as validatePublishedPortsAvailable, i as parseDomains, l as parseRuntime, n as parseCidrList, o as parseMemoryMib, r as parseDiskSizeGb, s as parseNetworkPolicy, t as parseBandwidth, u as parseVcpuCount } from "./_chunks/validation.mjs";
|
|
11
13
|
import { n as parseCreateInput, t as buildCreateSummaryLines } from "./_chunks/summary.mjs";
|
|
12
|
-
import { join } from "node:path";
|
|
13
14
|
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
14
15
|
function definePlugin(plugin) {
|
|
15
16
|
return plugin;
|
|
16
17
|
}
|
|
17
|
-
const DEFAULT_INTERVAL_MS = 300 * 1e3;
|
|
18
|
-
var TimeoutExtender = class {
|
|
19
|
-
_timer = null;
|
|
20
|
-
_previousKillerPid = null;
|
|
21
|
-
_vmId;
|
|
22
|
-
_store;
|
|
23
|
-
_paths;
|
|
24
|
-
_intervalMs;
|
|
25
|
-
_signal;
|
|
26
|
-
constructor(opts) {
|
|
27
|
-
this._vmId = opts.vmId;
|
|
28
|
-
this._store = opts.store;
|
|
29
|
-
this._paths = opts.paths;
|
|
30
|
-
this._intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
31
|
-
this._signal = opts.signal;
|
|
32
|
-
}
|
|
33
|
-
start() {
|
|
34
|
-
if (this._timer) return;
|
|
35
|
-
if (this._signal) {
|
|
36
|
-
this._signal.addEventListener("abort", () => this.stop(), { once: true });
|
|
37
|
-
if (this._signal.aborted) {
|
|
38
|
-
this.stop();
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
this._extend();
|
|
43
|
-
this._timer = setInterval(() => this._extend(), this._intervalMs);
|
|
44
|
-
}
|
|
45
|
-
stop() {
|
|
46
|
-
if (this._timer) {
|
|
47
|
-
clearInterval(this._timer);
|
|
48
|
-
this._timer = null;
|
|
49
|
-
}
|
|
50
|
-
if (this._previousKillerPid !== null) {
|
|
51
|
-
safeKill(this._previousKillerPid);
|
|
52
|
-
this._previousKillerPid = null;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
_extend() {
|
|
56
|
-
const state = this._store.load(this._vmId);
|
|
57
|
-
if (!state || state.status !== "running" || !state.timeoutMs) return;
|
|
58
|
-
const timeoutAt = new Date(Date.now() + state.timeoutMs).toISOString();
|
|
59
|
-
this._store.update(this._vmId, { timeoutAt });
|
|
60
|
-
if (this._previousKillerPid !== null) {
|
|
61
|
-
safeKill(this._previousKillerPid);
|
|
62
|
-
this._previousKillerPid = null;
|
|
63
|
-
}
|
|
64
|
-
if (state.pid) this._previousKillerPid = spawnTimeoutKiller({
|
|
65
|
-
vmId: this._vmId,
|
|
66
|
-
pid: state.pid,
|
|
67
|
-
timeoutMs: state.timeoutMs,
|
|
68
|
-
stateFile: join(this._paths.vmsDir, `${this._vmId}.json`)
|
|
69
|
-
}).pid ?? null;
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
18
|
var MemoryVmStateStore = class {
|
|
73
19
|
states = /* @__PURE__ */ new Map();
|
|
74
20
|
save(state) {
|
package/package.json
CHANGED