vmsan 0.1.0-alpha.14 → 0.1.0-alpha.15

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.
@@ -1,13 +1,57 @@
1
- import { l as agentTimeoutError } from "./errors.mjs";
2
- async function waitForAgent(guestIp, port, timeoutMs = 6e4) {
3
- const start = Date.now();
4
- const url = `http://${guestIp}:${port}/health`;
5
- while (Date.now() - start < timeoutMs) {
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 { n as waitForAgent, t as resolveVmState } from "./vm-context.mjs";
6
+ import { t as ShellSession } from "./shell.mjs";
7
+ import { consola } from "consola";
8
+ import { defineCommand } from "citty";
9
+ const connectCommand = defineCommand({
10
+ meta: {
11
+ name: "connect",
12
+ description: "Connect to a running VM"
13
+ },
14
+ args: {
15
+ vmId: {
16
+ type: "positional",
17
+ description: "VM ID to connect to",
18
+ required: true
19
+ },
20
+ session: {
21
+ type: "string",
22
+ alias: "s",
23
+ description: "Attach to an existing shell session ID",
24
+ required: false
25
+ }
26
+ },
27
+ async run({ args }) {
28
+ const cmdLog = createCommandLogger("connect");
29
+ const paths = vmsanPaths();
6
30
  try {
7
- if ((await fetch(url, { signal: AbortSignal.timeout(2e3) })).ok) return;
8
- } catch {}
9
- await new Promise((r) => setTimeout(r, 500));
31
+ const { state, guestIp, port } = resolveVmState(args.vmId, paths);
32
+ const log = consola.withTag(args.vmId);
33
+ consola.debug(`Agent endpoint: ${guestIp}:${port}`);
34
+ log.start("Waiting for agent to become ready...");
35
+ await waitForAgent(guestIp, port);
36
+ log.success("Agent is ready. Connecting via PTY shell...");
37
+ const shell = new ShellSession({
38
+ host: guestIp,
39
+ port,
40
+ token: state.agentToken,
41
+ sessionId: args.session
42
+ });
43
+ const closeInfo = await shell.connect();
44
+ cmdLog.set({
45
+ vmId: args.vmId,
46
+ method: "pty"
47
+ });
48
+ cmdLog.emit();
49
+ if (!closeInfo.sessionDestroyed && shell.sessionId) process.stderr.write(`\nResume this session with:\n vmsan connect ${args.vmId} --session ${shell.sessionId}\n`);
50
+ process.exit(0);
51
+ } catch (error) {
52
+ handleCommandError(error, cmdLog);
53
+ process.exit(1);
54
+ }
10
55
  }
11
- throw agentTimeoutError(guestIp, timeoutMs);
12
- }
13
- export { waitForAgent as t };
56
+ });
57
+ export { connectCommand as default };
@@ -1,5 +1,5 @@
1
1
  import { n as vmsanPaths } from "./paths.mjs";
2
- import { H as VmsanError, S as vmNotStoppedError, _ as chrootNotFoundError, a as noKernelDirError, b as vmNotFoundError, d as socketTimeoutError, i as noExt4RootfsError, o as noKernelError, p as defaultInterfaceNotFoundError, r as missingBinaryError, s as noRootfsDirError, u as lockTimeoutError, x as vmNotRunningError, y as snapshotNotFoundError, z as mutuallyExclusiveFlagsError } from "./errors.mjs";
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
5
  import { createHooks } from "hookable";
@@ -1825,6 +1825,37 @@ function ensureSeccompFilter(paths) {
1825
1825
  return null;
1826
1826
  }
1827
1827
  }
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
+ }
1828
1859
  var VMService = class {
1829
1860
  paths;
1830
1861
  store;
@@ -1988,21 +2019,12 @@ var VMService = class {
1988
2019
  pid
1989
2020
  });
1990
2021
  log.success(`VM ${vmId} is running (PID: ${pid || "unknown"})`);
1991
- if (timeoutMs && pid) {
1992
- const stateFile = join(paths.vmsDir, `${vmId}.json`);
1993
- spawn("bash", ["-c", [
1994
- `sleep ${Math.ceil(timeoutMs / 1e3)}`,
1995
- `STATE=$(cat "${stateFile}" 2>/dev/null) || exit 0`,
1996
- `echo "$STATE" | grep -q '"status":"running"' || exit 0`,
1997
- `echo "$STATE" | grep -q '"pid":${pid}' || exit 0`,
1998
- `[ -d /proc/${pid} ] || exit 0`,
1999
- `grep -q "${vmId}" /proc/${pid}/cmdline 2>/dev/null || exit 0`,
2000
- `kill ${pid} 2>/dev/null`
2001
- ].join(" && ")], {
2002
- detached: true,
2003
- stdio: "ignore"
2004
- }).unref();
2005
- }
2022
+ if (timeoutMs && pid) spawnTimeoutKiller({
2023
+ vmId,
2024
+ pid,
2025
+ timeoutMs,
2026
+ stateFile: join(paths.vmsDir, `${vmId}.json`)
2027
+ });
2006
2028
  const finalState = this.store.load(vmId);
2007
2029
  await hooks.callHook("vm:afterCreate", finalState);
2008
2030
  return {
@@ -2372,4 +2394,4 @@ async function createVmsan(options) {
2372
2394
  if (options?.plugins) for (const plugin of options.plugins) await plugin.setup(ctx);
2373
2395
  return vmsan;
2374
2396
  }
2375
- 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 };
2397
+ export { createDefaultLogger as C, NetworkManager as S, validateEnvironment as _, ensureSeccompFilter as a, detectCgroupVersion as b, cleanupChroot as c, markVmAsError as d, assertSnapshotExists as f, getVmPid as g, getVmJailerPid as h, compileSeccompFilter as i, cleanupNetwork as l, findRootfs as m, VMService as n, resolveImageRootfs as o, findKernel as p, spawnTimeoutKiller as r, buildInitialVmState as s, createVmsan as t, killOrphanVmProcess as u, waitForSocket as v, createSilentLogger as w, FileLock as x, Jailer as y };
@@ -4,10 +4,10 @@ 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 { n as waitForAgent } from "./vm-context.mjs";
7
8
  import { t as ShellSession } from "./shell.mjs";
8
9
  import { a as parseImageReference, t as parseBandwidth } from "./validation.mjs";
9
10
  import { n as parseCreateInput, t as buildCreateSummaryLines } from "./summary.mjs";
10
- import { t as waitForAgent } from "./connect.mjs";
11
11
  import { join } from "node:path";
12
12
  import { consola } from "consola";
13
13
  import { defineCommand } from "citty";
@@ -1,9 +1,9 @@
1
1
  import { n as vmsanPaths } from "./paths.mjs";
2
- import { b as vmNotFoundError, t as handleCommandError } from "./errors.mjs";
2
+ import { t as handleCommandError } from "./errors.mjs";
3
3
  import { t as createCommandLogger } from "./logger.mjs";
4
- import { t as FileVmStateStore } from "./vm-state.mjs";
4
+ import "./vm-state.mjs";
5
5
  import { t as AgentClient } from "./agent.mjs";
6
- import { t as waitForAgent } from "./connect.mjs";
6
+ import { n as waitForAgent, t as resolveVmState } from "./vm-context.mjs";
7
7
  import { basename, join, resolve } from "node:path";
8
8
  import { existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
9
9
  import { consola } from "consola";
@@ -29,23 +29,8 @@ const downloadCommand = defineCommand({
29
29
  const cmdLog = createCommandLogger("download");
30
30
  const paths = vmsanPaths();
31
31
  try {
32
- const state = new FileVmStateStore(paths.vmsDir).load(args.vmId);
33
- if (!state) throw vmNotFoundError(args.vmId);
34
- if (state.status !== "running") {
35
- consola.error(`VM ${args.vmId} is not running (status: ${state.status})`);
36
- cmdLog.emit();
37
- process.exitCode = 1;
38
- return;
39
- }
40
- if (!state.agentToken) {
41
- consola.error("VM has no agent token. Cannot download files without the agent.");
42
- cmdLog.emit();
43
- process.exitCode = 1;
44
- return;
45
- }
32
+ const { state, guestIp, port } = resolveVmState(args.vmId, paths);
46
33
  const log = consola.withTag(args.vmId);
47
- const guestIp = state.network.guestIp;
48
- const port = state.agentPort || paths.agentPort;
49
34
  consola.debug(`Agent endpoint: ${guestIp}:${port}`);
50
35
  log.start("Waiting for agent...");
51
36
  await waitForAgent(guestIp, port);
@@ -122,10 +122,16 @@ const chrootNotFoundError = (vmId) => new VmError("ERR_VM_CHROOT_NOT_FOUND", {
122
122
  fix: "The VM must be recreated with 'vmsan create'."
123
123
  });
124
124
  const networkSlotsExhaustedError = () => new VmError("ERR_VM_NETWORK_SLOTS_EXHAUSTED", { message: "No available network slots (max 255 VMs)" });
125
- const vmNotRunningError = (vmId) => new VmError("ERR_VM_NOT_RUNNING", {
125
+ const vmNotRunningError = (vmId, currentStatus) => new VmError("ERR_VM_NOT_RUNNING", {
126
126
  vmId,
127
- message: `VM ${vmId} is not running`,
128
- fix: "The VM must be running to update its network policy. Start it with 'vmsan start <vm-id>'."
127
+ message: currentStatus ? `VM ${vmId} is not running (current status: ${currentStatus})` : `VM ${vmId} is not running`,
128
+ fix: "The VM must be running. Start it with 'vmsan start <vm-id>'."
129
+ });
130
+ const vmNoAgentTokenError = (vmId) => new VmError("ERR_VM_NO_AGENT_TOKEN", {
131
+ vmId,
132
+ message: `VM ${vmId} has no agent token`,
133
+ why: "The vmsan-agent binary was not found at ~/.vmsan/bin/vmsan-agent when this VM was created.",
134
+ fix: "Install the agent binary into ~/.vmsan/bin/vmsan-agent and recreate the VM with 'vmsan create'."
129
135
  });
130
136
  const snapshotNotFoundError = (snapshotId) => new VmError("ERR_VM_SNAPSHOT_NOT_FOUND", { message: `Snapshot not found: ${snapshotId}` });
131
137
  var FirecrackerApiError = class extends VmsanError {
@@ -229,4 +235,4 @@ function handleCommandError(error, cmdLog) {
229
235
  if (error.link) consola.log(` More: ${error.link}`);
230
236
  } else consola.error(error instanceof Error ? error.message : String(error));
231
237
  }
232
- export { invalidDomainError as A, policyConflictError as B, vmStateNotFoundError as C, invalidCidrPrefixError as D, invalidCidrOctetError as E, invalidIntegerFlagError as F, VmsanError as H, invalidNetworkPolicyError as I, invalidPortError as L, invalidDurationError as M, invalidImageRefEmptyError as N, invalidDiskSizeFormatError as O, invalidImageRefTagError as P, invalidRuntimeError as R, vmNotStoppedError as S, invalidCidrFormatError as T, portConflictError as V, chrootNotFoundError as _, noKernelDirError as a, vmNotFoundError as b, TimeoutError as c, socketTimeoutError as d, NetworkError as f, VmError as g, firecrackerApiError as h, noExt4RootfsError as i, invalidDomainPatternError as j, invalidDiskSizeRangeError as k, agentTimeoutError as l, FirecrackerApiError as m, SetupError as n, noKernelError as o, defaultInterfaceNotFoundError as p, missingBinaryError as r, noRootfsDirError as s, handleCommandError as t, lockTimeoutError as u, networkSlotsExhaustedError as v, ValidationError as w, vmNotRunningError as x, snapshotNotFoundError as y, mutuallyExclusiveFlagsError as z };
238
+ export { invalidDiskSizeRangeError as A, mutuallyExclusiveFlagsError as B, vmNotStoppedError as C, invalidCidrOctetError as D, invalidCidrFormatError as E, invalidImageRefTagError as F, portConflictError as H, invalidIntegerFlagError as I, invalidNetworkPolicyError as L, invalidDomainPatternError as M, invalidDurationError as N, invalidCidrPrefixError as O, invalidImageRefEmptyError as P, invalidPortError as R, vmNotRunningError as S, ValidationError as T, VmsanError as U, policyConflictError as V, chrootNotFoundError as _, noKernelDirError as a, vmNoAgentTokenError as b, TimeoutError as c, socketTimeoutError as d, NetworkError as f, VmError as g, firecrackerApiError as h, noExt4RootfsError as i, invalidDomainError as j, invalidDiskSizeFormatError as k, agentTimeoutError as l, FirecrackerApiError as m, SetupError as n, noKernelError as o, defaultInterfaceNotFoundError as p, missingBinaryError as r, noRootfsDirError as s, handleCommandError as t, lockTimeoutError as u, networkSlotsExhaustedError as v, vmStateNotFoundError as w, vmNotFoundError as x, snapshotNotFoundError as y, invalidRuntimeError as z };
@@ -1,5 +1,5 @@
1
1
  import "./paths.mjs";
2
- import { B as policyConflictError, t as handleCommandError } from "./errors.mjs";
2
+ import { V as policyConflictError, t as handleCommandError } from "./errors.mjs";
3
3
  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";
@@ -1,6 +1,6 @@
1
- import { B as policyConflictError } from "./errors.mjs";
1
+ import { V as policyConflictError } from "./errors.mjs";
2
2
  import { s as parseDuration } from "./vm-state.mjs";
3
- import { d as assertSnapshotExists } from "./context.mjs";
3
+ import { f 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);
@@ -1,9 +1,9 @@
1
1
  import { n as vmsanPaths } from "./paths.mjs";
2
- import { b as vmNotFoundError, t as handleCommandError } from "./errors.mjs";
2
+ import { t as handleCommandError } from "./errors.mjs";
3
3
  import { t as createCommandLogger } from "./logger.mjs";
4
- import { t as FileVmStateStore } from "./vm-state.mjs";
4
+ import "./vm-state.mjs";
5
5
  import { t as AgentClient } from "./agent.mjs";
6
- import { t as waitForAgent } from "./connect.mjs";
6
+ import { n as waitForAgent, t as resolveVmState } from "./vm-context.mjs";
7
7
  import { basename } from "node:path";
8
8
  import { readFileSync } from "node:fs";
9
9
  import { consola } from "consola";
@@ -30,23 +30,8 @@ const uploadCommand = defineCommand({
30
30
  const cmdLog = createCommandLogger("upload");
31
31
  const paths = vmsanPaths();
32
32
  try {
33
- const state = new FileVmStateStore(paths.vmsDir).load(args.vmId);
34
- if (!state) throw vmNotFoundError(args.vmId);
35
- if (state.status !== "running") {
36
- consola.error(`VM ${args.vmId} is not running (status: ${state.status})`);
37
- cmdLog.emit();
38
- process.exitCode = 1;
39
- return;
40
- }
41
- if (!state.agentToken) {
42
- consola.error("VM has no agent token. Cannot upload files without the agent.");
43
- cmdLog.emit();
44
- process.exitCode = 1;
45
- return;
46
- }
33
+ const { state, guestIp, port } = resolveVmState(args.vmId, paths);
47
34
  const log = consola.withTag(args.vmId);
48
- const guestIp = state.network.guestIp;
49
- const port = state.agentPort || paths.agentPort;
50
35
  consola.debug(`Agent endpoint: ${guestIp}:${port}`);
51
36
  log.start("Waiting for agent...");
52
37
  await waitForAgent(guestIp, port);
@@ -1,4 +1,4 @@
1
- import { A as invalidDomainError, D as invalidCidrPrefixError, E as invalidCidrOctetError, F as invalidIntegerFlagError, I as invalidNetworkPolicyError, L as invalidPortError, N as invalidImageRefEmptyError, O as invalidDiskSizeFormatError, P as invalidImageRefTagError, R as invalidRuntimeError, T as invalidCidrFormatError, V as portConflictError, j as invalidDomainPatternError, k as invalidDiskSizeRangeError } from "./errors.mjs";
1
+ import { A as invalidDiskSizeRangeError, D as invalidCidrOctetError, E as invalidCidrFormatError, F as invalidImageRefTagError, H as portConflictError, I as invalidIntegerFlagError, L as invalidNetworkPolicyError, M as invalidDomainPatternError, O as invalidCidrPrefixError, P as invalidImageRefEmptyError, R as invalidPortError, j as invalidDomainError, k as invalidDiskSizeFormatError, z as invalidRuntimeError } from "./errors.mjs";
2
2
  import { t as FileVmStateStore } from "./vm-state.mjs";
3
3
  const VALID_RUNTIMES = [
4
4
  "base",
@@ -0,0 +1,34 @@
1
+ import { S as vmNotRunningError, b as vmNoAgentTokenError, l as agentTimeoutError, x as vmNotFoundError } from "./errors.mjs";
2
+ import { t as FileVmStateStore } from "./vm-state.mjs";
3
+ /**
4
+ * Load VM state and validate it's running with an agent token.
5
+ * Throws VmError on failure (handled by handleCommandError in the caller).
6
+ */
7
+ function resolveVmState(vmId, paths) {
8
+ const store = new FileVmStateStore(paths.vmsDir);
9
+ const state = store.load(vmId);
10
+ if (!state) throw vmNotFoundError(vmId);
11
+ if (state.status !== "running") throw vmNotRunningError(vmId, state.status);
12
+ if (!state.agentToken) throw vmNoAgentTokenError(vmId);
13
+ return {
14
+ state,
15
+ guestIp: state.network.guestIp,
16
+ port: state.agentPort || paths.agentPort,
17
+ store
18
+ };
19
+ }
20
+ /**
21
+ * Poll the agent health endpoint until it responds OK.
22
+ */
23
+ async function waitForAgent(guestIp, port, timeoutMs = 6e4) {
24
+ const start = Date.now();
25
+ const url = `http://${guestIp}:${port}/health`;
26
+ while (Date.now() - start < timeoutMs) {
27
+ try {
28
+ if ((await fetch(url, { signal: AbortSignal.timeout(2e3) })).ok) return;
29
+ } catch {}
30
+ await new Promise((r) => setTimeout(r, 500));
31
+ }
32
+ throw agentTimeoutError(guestIp, timeoutMs);
33
+ }
34
+ export { waitForAgent as n, resolveVmState as t };
@@ -1,9 +1,32 @@
1
- import { C as vmStateNotFoundError, M as invalidDurationError, v as networkSlotsExhaustedError } from "./errors.mjs";
1
+ import { N as invalidDurationError, v as networkSlotsExhaustedError, w as vmStateNotFoundError } from "./errors.mjs";
2
2
  import { join } from "node:path";
3
3
  import { execSync } from "node:child_process";
4
- import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
4
+ import { chmodSync, chownSync, existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
5
5
  import { randomBytes } from "node:crypto";
6
6
  import { stripAnsi } from "consola/utils";
7
+ /**
8
+ * Resolve the UID/GID of the real (non-root) user when running under sudo.
9
+ * Returns null when not running under sudo or when env vars are missing.
10
+ */
11
+ function getSudoOwner() {
12
+ const uid = process.env.SUDO_UID;
13
+ const gid = process.env.SUDO_GID;
14
+ if (!uid || !gid) return null;
15
+ const numUid = Number(uid);
16
+ const numGid = Number(gid);
17
+ if (!Number.isInteger(numUid) || !Number.isInteger(numGid)) return null;
18
+ return {
19
+ uid: numUid,
20
+ gid: numGid
21
+ };
22
+ }
23
+ function chownToSudoUser(path) {
24
+ const owner = getSudoOwner();
25
+ if (!owner) return;
26
+ try {
27
+ chownSync(path, owner.uid, owner.gid);
28
+ } catch {}
29
+ }
7
30
  function toError(err) {
8
31
  return err instanceof Error ? err : new Error(String(err));
9
32
  }
@@ -83,6 +106,7 @@ function mkdirSecure(path) {
83
106
  } catch (error) {
84
107
  if (error.code !== "ENOENT") throw error;
85
108
  }
109
+ chownToSudoUser(path);
86
110
  }
87
111
  function writeSecure(path, contents) {
88
112
  writeFileSync(path, contents, { mode: 384 });
@@ -91,6 +115,7 @@ function writeSecure(path, contents) {
91
115
  } catch (error) {
92
116
  if (error.code !== "ENOENT") throw error;
93
117
  }
118
+ chownToSudoUser(path);
94
119
  }
95
120
  const TIME_UNITS = [
96
121
  [
package/dist/bin/cli.mjs CHANGED
@@ -56,7 +56,7 @@ runMain(defineCommand({
56
56
  stop: () => import("../_chunks/stop.mjs").then((m) => m.default),
57
57
  remove: () => import("../_chunks/remove.mjs").then((m) => m.default),
58
58
  rm: () => import("../_chunks/remove.mjs").then((m) => m.default),
59
- connect: () => import("../_chunks/connect2.mjs").then((m) => m.default),
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
62
  network: () => import("../_chunks/network.mjs").then((m) => m.default)
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Hookable } from "hookable";
2
+ import { ChildProcess } from "node:child_process";
2
3
  import { ErrorOptions, EvlogError, RequestLogger } from "evlog";
3
4
  import { ConsolaInstance } from "consola";
4
5
 
@@ -213,7 +214,7 @@ declare function parseDiskSizeGb(value: string | undefined): number;
213
214
  //#endregion
214
215
  //#region src/errors/codes.d.ts
215
216
  type ValidationErrorCode = "ERR_VALIDATION_INTEGER" | "ERR_VALIDATION_RUNTIME" | "ERR_VALIDATION_NETWORK_POLICY" | "ERR_VALIDATION_PORT" | "ERR_VALIDATION_PORT_CONFLICT" | "ERR_VALIDATION_DOMAIN" | "ERR_VALIDATION_CIDR" | "ERR_VALIDATION_IMAGE_REF" | "ERR_VALIDATION_DISK_SIZE" | "ERR_VALIDATION_DURATION" | "ERR_VALIDATION_FLAGS" | "ERR_VALIDATION_POLICY_CONFLICT";
216
- type VmErrorCode = "ERR_VM_NOT_FOUND" | "ERR_VM_STATE_NOT_FOUND" | "ERR_VM_NOT_STOPPED" | "ERR_VM_NOT_RUNNING" | "ERR_VM_CHROOT_NOT_FOUND" | "ERR_VM_NETWORK_SLOTS_EXHAUSTED" | "ERR_VM_SNAPSHOT_NOT_FOUND";
217
+ type VmErrorCode = "ERR_VM_NOT_FOUND" | "ERR_VM_STATE_NOT_FOUND" | "ERR_VM_NOT_STOPPED" | "ERR_VM_NOT_RUNNING" | "ERR_VM_NO_AGENT_TOKEN" | "ERR_VM_CHROOT_NOT_FOUND" | "ERR_VM_NETWORK_SLOTS_EXHAUSTED" | "ERR_VM_SNAPSHOT_NOT_FOUND";
217
218
  type FirecrackerErrorCode = "ERR_FIRECRACKER_API";
218
219
  type NetworkErrorCode = "ERR_NETWORK_DEFAULT_INTERFACE";
219
220
  type TimeoutErrorCode = "ERR_TIMEOUT_SOCKET" | "ERR_TIMEOUT_LOCK" | "ERR_TIMEOUT_AGENT";
@@ -266,7 +267,8 @@ declare const vmStateNotFoundError: (vmId: string) => VmError;
266
267
  declare const vmNotStoppedError: (vmId: string, currentStatus: string) => VmError;
267
268
  declare const chrootNotFoundError: (vmId: string) => VmError;
268
269
  declare const networkSlotsExhaustedError: () => VmError;
269
- declare const vmNotRunningError: (vmId: string) => VmError;
270
+ declare const vmNotRunningError: (vmId: string, currentStatus?: string) => VmError;
271
+ declare const vmNoAgentTokenError: (vmId: string) => VmError;
270
272
  declare const snapshotNotFoundError: (snapshotId: string) => VmError;
271
273
  //#endregion
272
274
  //#region src/errors/firecracker.d.ts
@@ -2822,6 +2824,61 @@ declare class AgentClient {
2822
2824
  readFile(path: string): Promise<Buffer | null>;
2823
2825
  }
2824
2826
  //#endregion
2827
+ //#region src/lib/timeout-extender.d.ts
2828
+ interface TimeoutExtenderOptions {
2829
+ vmId: string;
2830
+ store: VmStateStore;
2831
+ paths: VmsanPaths;
2832
+ intervalMs?: number;
2833
+ signal?: AbortSignal;
2834
+ }
2835
+ declare class TimeoutExtender {
2836
+ private _timer;
2837
+ private _previousKillerPid;
2838
+ private readonly _vmId;
2839
+ private readonly _store;
2840
+ private readonly _paths;
2841
+ private readonly _intervalMs;
2842
+ private readonly _signal?;
2843
+ constructor(opts: TimeoutExtenderOptions);
2844
+ start(): void;
2845
+ stop(): void;
2846
+ private _extend;
2847
+ }
2848
+ //#endregion
2849
+ //#region src/lib/timeout-killer.d.ts
2850
+ interface SpawnTimeoutKillerOpts {
2851
+ vmId: string;
2852
+ pid: number;
2853
+ timeoutMs: number;
2854
+ stateFile: string;
2855
+ }
2856
+ /**
2857
+ * Spawn a detached bash process that kills the VM after timeout.
2858
+ * The process sleeps for the timeout duration, then verifies the VM
2859
+ * is still running with the expected PID before sending SIGTERM.
2860
+ */
2861
+ declare function spawnTimeoutKiller(opts: SpawnTimeoutKillerOpts): ChildProcess;
2862
+ //#endregion
2863
+ //#region src/lib/vm-context.d.ts
2864
+ interface RunningVmContext {
2865
+ state: VmState & {
2866
+ agentToken: string;
2867
+ };
2868
+ guestIp: string;
2869
+ port: number;
2870
+ store: FileVmStateStore;
2871
+ }
2872
+ /**
2873
+ * Load VM state and validate it's running with an agent token.
2874
+ * Throws VmError on failure (handled by handleCommandError in the caller).
2875
+ */
2876
+ declare function resolveVmState(vmId: string, paths: VmsanPaths): RunningVmContext;
2877
+ /**
2878
+ * Poll the agent health endpoint until it responds OK.
2879
+ */
2880
+ declare function waitForAgent(guestIp: string, port: number, timeoutMs?: number): Promise<void>;
2881
+ //#endregion
2825
2882
  //#region src/stores/memory.d.ts
2826
2883
  declare class MemoryVmStateStore implements VmStateStore {
2827
2884
  private states;
@@ -3006,9 +3063,6 @@ declare function waitForSocket(socketPath: string, timeoutMs?: number): Promise<
3006
3063
  declare function getVmPid(vmId: string): number | null;
3007
3064
  declare function getVmJailerPid(vmId: string): number | null;
3008
3065
  //#endregion
3009
- //#region src/commands/create/connect.d.ts
3010
- declare function waitForAgent(guestIp: string, port: number, timeoutMs?: number): Promise<void>;
3011
- //#endregion
3012
3066
  //#region src/commands/create/cleanup.d.ts
3013
3067
  declare function killOrphanVmProcess(vmId: string): void;
3014
3068
  declare function markVmAsError(vmId: string, error: unknown, paths: VmsanPaths): void;
@@ -3038,4 +3092,4 @@ declare function resolveImageRootfs(imageRef: ImageReference, registryDir: strin
3038
3092
  //#region src/index.d.ts
3039
3093
  declare function getFirecrackerVersion(dir?: string): Promise<string | undefined>;
3040
3094
  //#endregion
3041
- export { AgentClient, type CgroupConfig, type CommandLogger, type CreateCommandRuntimeArgs, type CreateLifecycleState, type CreateSummaryInput, type CreateVmOptions, type CreateVmResult, FileLock, FileVmStateStore, FirecrackerApiError, FirecrackerClient, type components as FirecrackerComponents, type FirecrackerErrorCode, type paths as FirecrackerPaths, type ImageReference, type InitialVmStateInput, Jailer, type JailerPaths, MemoryVmStateStore, type NetworkConfig, NetworkError, type NetworkErrorCode, NetworkManager, type NetworkPolicy, type OutputMode, type ParsedCreateInput, PidFile, type PrepareChrootConfig, type RunEvent, type RunParams, type Runtime, type SessionInfo, SetupError, type SetupErrorCode, ShellSession, type ShellSessionOptions, type SpawnJailerConfig, type StartVmResult, type StopResult, TimeoutError, type TimeoutErrorCode, type UpdatePolicyResult, type VALID_NETWORK_POLICIES, type VALID_RUNTIMES, VMService, ValidationError, type ValidationErrorCode, VmError, type VmErrorCode, type VmNetwork, type VmPhase, type VmState, type VmStateStore, type VmsanContext, VmsanError, type VmsanErrorCode, type VmsanHooks, type VmsanLogger, type VmsanOptions, type VmsanPaths, type VmsanPlugin, type WriteFileEntry, agentTimeoutError, buildCreateSummaryLines, buildInitialVmState, chrootNotFoundError, cleanupChroot, cleanupNetwork, compileSeccompFilter, connectShell, createCommandLogger, createDefaultLogger, createScopedLogger, createSilentLogger, createVmsan, defaultInterfaceNotFoundError, definePlugin, detectCgroupVersion, ensureSeccompFilter, findFreeNetworkSlot, findKernel, findRootfs, firecrackerApiError, firecrackerFetch, generateVmId, getActiveTapSlots, getFirecrackerVersion, getOutputMode, getVmJailerPid, getVmPid, handleCommandError, initVmsanLogger, invalidCidrFormatError, invalidCidrOctetError, invalidCidrPrefixError, invalidDiskSizeFormatError, invalidDiskSizeRangeError, invalidDomainError, invalidDomainPatternError, invalidDurationError, invalidImageRefEmptyError, invalidImageRefTagError, invalidIntegerFlagError, invalidNetworkPolicyError, invalidPortError, invalidRuntimeError, isProcessAlive, killOrphanVmProcess, lockTimeoutError, markVmAsError, missingBinaryError, mkdirSecure, mutuallyExclusiveFlagsError, networkSlotsExhaustedError, noExt4RootfsError, noKernelDirError, noKernelError, noRootfsDirError, parseBandwidth, parseCidrList, parseCreateInput, parseDiskSizeGb, parseDomains, parseDuration, parseImageReference, parseMemoryMib, parseNetworkPolicy, parsePublishedPorts, parseRuntime, parseVcpuCount, policyConflictError, portConflictError, resolveImageRootfs, safeKill, snapshotNotFoundError, socketTimeoutError, table, timeAgo, timeRemaining, toError, validateCidr, validateEnvironment, validatePublishedPortsAvailable, vmNotFoundError, vmNotRunningError, vmNotStoppedError, vmStateNotFoundError, vmsanPaths, waitForAgent, waitForSocket, writeSecure };
3095
+ export { AgentClient, type CgroupConfig, type CommandLogger, type CreateCommandRuntimeArgs, type CreateLifecycleState, type CreateSummaryInput, type CreateVmOptions, type CreateVmResult, FileLock, FileVmStateStore, FirecrackerApiError, FirecrackerClient, type components as FirecrackerComponents, type FirecrackerErrorCode, type paths as FirecrackerPaths, type ImageReference, type InitialVmStateInput, Jailer, type JailerPaths, MemoryVmStateStore, type NetworkConfig, NetworkError, type NetworkErrorCode, NetworkManager, type NetworkPolicy, type OutputMode, type ParsedCreateInput, PidFile, type PrepareChrootConfig, type RunEvent, type RunParams, type RunningVmContext, type Runtime, type SessionInfo, SetupError, type SetupErrorCode, ShellSession, type ShellSessionOptions, type SpawnJailerConfig, type SpawnTimeoutKillerOpts, type StartVmResult, type StopResult, TimeoutError, type TimeoutErrorCode, TimeoutExtender, type TimeoutExtenderOptions, type UpdatePolicyResult, type VALID_NETWORK_POLICIES, type VALID_RUNTIMES, VMService, ValidationError, type ValidationErrorCode, VmError, type VmErrorCode, type VmNetwork, type VmPhase, type VmState, type VmStateStore, type VmsanContext, VmsanError, type VmsanErrorCode, type VmsanHooks, type VmsanLogger, type VmsanOptions, type VmsanPaths, type VmsanPlugin, type WriteFileEntry, agentTimeoutError, buildCreateSummaryLines, buildInitialVmState, chrootNotFoundError, cleanupChroot, cleanupNetwork, compileSeccompFilter, connectShell, createCommandLogger, createDefaultLogger, createScopedLogger, createSilentLogger, createVmsan, defaultInterfaceNotFoundError, definePlugin, detectCgroupVersion, ensureSeccompFilter, findFreeNetworkSlot, findKernel, findRootfs, firecrackerApiError, firecrackerFetch, generateVmId, getActiveTapSlots, getFirecrackerVersion, getOutputMode, getVmJailerPid, getVmPid, handleCommandError, initVmsanLogger, invalidCidrFormatError, invalidCidrOctetError, invalidCidrPrefixError, invalidDiskSizeFormatError, invalidDiskSizeRangeError, invalidDomainError, invalidDomainPatternError, invalidDurationError, invalidImageRefEmptyError, invalidImageRefTagError, invalidIntegerFlagError, invalidNetworkPolicyError, invalidPortError, invalidRuntimeError, isProcessAlive, killOrphanVmProcess, lockTimeoutError, markVmAsError, missingBinaryError, mkdirSecure, mutuallyExclusiveFlagsError, networkSlotsExhaustedError, noExt4RootfsError, noKernelDirError, noKernelError, noRootfsDirError, parseBandwidth, parseCidrList, parseCreateInput, parseDiskSizeGb, parseDomains, parseDuration, parseImageReference, parseMemoryMib, parseNetworkPolicy, parsePublishedPorts, parseRuntime, parseVcpuCount, policyConflictError, portConflictError, resolveImageRootfs, resolveVmState, safeKill, snapshotNotFoundError, socketTimeoutError, spawnTimeoutKiller, table, timeAgo, timeRemaining, toError, validateCidr, validateEnvironment, validatePublishedPortsAvailable, vmNoAgentTokenError, vmNotFoundError, vmNotRunningError, vmNotStoppedError, vmStateNotFoundError, vmsanPaths, waitForAgent, waitForSocket, writeSecure };
package/dist/index.mjs CHANGED
@@ -1,18 +1,74 @@
1
1
  import { n as vmsanPaths } from "./_chunks/paths.mjs";
2
- import { A as invalidDomainError, B as policyConflictError, C as vmStateNotFoundError, D as invalidCidrPrefixError, E as invalidCidrOctetError, F as invalidIntegerFlagError, H as VmsanError, I as invalidNetworkPolicyError, L as invalidPortError, M as invalidDurationError, N as invalidImageRefEmptyError, O as invalidDiskSizeFormatError, P as invalidImageRefTagError, R as invalidRuntimeError, S as vmNotStoppedError, T as invalidCidrFormatError, V as portConflictError, _ as chrootNotFoundError, a as noKernelDirError, b as vmNotFoundError, c as TimeoutError, d as socketTimeoutError, f as NetworkError, g as VmError, h as firecrackerApiError, i as noExt4RootfsError, j as invalidDomainPatternError, k as invalidDiskSizeRangeError, 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 ValidationError, x as vmNotRunningError, y as snapshotNotFoundError, z as mutuallyExclusiveFlagsError } from "./_chunks/errors.mjs";
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 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";
5
+ import { C as createDefaultLogger, S as NetworkManager, _ as validateEnvironment, a as ensureSeccompFilter, b as detectCgroupVersion, c as cleanupChroot, d as markVmAsError, g as getVmPid, h as getVmJailerPid, i as compileSeccompFilter, l as cleanupNetwork, m as findRootfs, n as VMService, o as resolveImageRootfs, p as findKernel, r as spawnTimeoutKiller, s as buildInitialVmState, t as createVmsan, u as killOrphanVmProcess, v as waitForSocket, w as createSilentLogger, x as FileLock, y as Jailer } from "./_chunks/context.mjs";
6
6
  import { n as firecrackerFetch, t as FirecrackerClient } from "./_chunks/firecracker.mjs";
7
7
  import { t as AgentClient } from "./_chunks/agent.mjs";
8
+ import { n as waitForAgent, t as resolveVmState } from "./_chunks/vm-context.mjs";
8
9
  import { n as connectShell, t as ShellSession } from "./_chunks/shell.mjs";
9
10
  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";
10
11
  import { n as parseCreateInput, t as buildCreateSummaryLines } from "./_chunks/summary.mjs";
11
- import { t as waitForAgent } from "./_chunks/connect.mjs";
12
+ import { join } from "node:path";
12
13
  import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
13
14
  function definePlugin(plugin) {
14
15
  return plugin;
15
16
  }
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
+ };
16
72
  var MemoryVmStateStore = class {
17
73
  states = /* @__PURE__ */ new Map();
18
74
  save(state) {
@@ -64,4 +120,4 @@ async function getFirecrackerVersion(dir) {
64
120
  const { FirecrackerClient: FC } = await import("./_chunks/firecracker.mjs").then((n) => n.r);
65
121
  return FC.getVersion(dir || paths().baseDir);
66
122
  }
67
- export { AgentClient, FileLock, FileVmStateStore, FirecrackerApiError, FirecrackerClient, Jailer, MemoryVmStateStore, NetworkError, NetworkManager, PidFile, SetupError, ShellSession, TimeoutError, VMService, ValidationError, VmError, VmsanError, agentTimeoutError, buildCreateSummaryLines, buildInitialVmState, chrootNotFoundError, cleanupChroot, cleanupNetwork, compileSeccompFilter, connectShell, createCommandLogger, createDefaultLogger, createScopedLogger, createSilentLogger, createVmsan, defaultInterfaceNotFoundError, definePlugin, detectCgroupVersion, ensureSeccompFilter, findFreeNetworkSlot, findKernel, findRootfs, firecrackerApiError, firecrackerFetch, generateVmId, getActiveTapSlots, getFirecrackerVersion, getOutputMode, getVmJailerPid, getVmPid, handleCommandError, initVmsanLogger, invalidCidrFormatError, invalidCidrOctetError, invalidCidrPrefixError, invalidDiskSizeFormatError, invalidDiskSizeRangeError, invalidDomainError, invalidDomainPatternError, invalidDurationError, invalidImageRefEmptyError, invalidImageRefTagError, invalidIntegerFlagError, invalidNetworkPolicyError, invalidPortError, invalidRuntimeError, isProcessAlive, killOrphanVmProcess, lockTimeoutError, markVmAsError, missingBinaryError, mkdirSecure, mutuallyExclusiveFlagsError, networkSlotsExhaustedError, noExt4RootfsError, noKernelDirError, noKernelError, noRootfsDirError, parseBandwidth, parseCidrList, parseCreateInput, parseDiskSizeGb, parseDomains, parseDuration, parseImageReference, parseMemoryMib, parseNetworkPolicy, parsePublishedPorts, parseRuntime, parseVcpuCount, policyConflictError, portConflictError, resolveImageRootfs, safeKill, snapshotNotFoundError, socketTimeoutError, table, timeAgo, timeRemaining, toError, validateCidr, validateEnvironment, validatePublishedPortsAvailable, vmNotFoundError, vmNotRunningError, vmNotStoppedError, vmStateNotFoundError, vmsanPaths, waitForAgent, waitForSocket, writeSecure };
123
+ export { AgentClient, FileLock, FileVmStateStore, FirecrackerApiError, FirecrackerClient, Jailer, MemoryVmStateStore, NetworkError, NetworkManager, PidFile, SetupError, ShellSession, TimeoutError, TimeoutExtender, VMService, ValidationError, VmError, VmsanError, agentTimeoutError, buildCreateSummaryLines, buildInitialVmState, chrootNotFoundError, cleanupChroot, cleanupNetwork, compileSeccompFilter, connectShell, createCommandLogger, createDefaultLogger, createScopedLogger, createSilentLogger, createVmsan, defaultInterfaceNotFoundError, definePlugin, detectCgroupVersion, ensureSeccompFilter, findFreeNetworkSlot, findKernel, findRootfs, firecrackerApiError, firecrackerFetch, generateVmId, getActiveTapSlots, getFirecrackerVersion, getOutputMode, getVmJailerPid, getVmPid, handleCommandError, initVmsanLogger, invalidCidrFormatError, invalidCidrOctetError, invalidCidrPrefixError, invalidDiskSizeFormatError, invalidDiskSizeRangeError, invalidDomainError, invalidDomainPatternError, invalidDurationError, invalidImageRefEmptyError, invalidImageRefTagError, invalidIntegerFlagError, invalidNetworkPolicyError, invalidPortError, invalidRuntimeError, isProcessAlive, killOrphanVmProcess, lockTimeoutError, markVmAsError, missingBinaryError, mkdirSecure, mutuallyExclusiveFlagsError, networkSlotsExhaustedError, noExt4RootfsError, noKernelDirError, noKernelError, noRootfsDirError, parseBandwidth, parseCidrList, parseCreateInput, parseDiskSizeGb, parseDomains, parseDuration, parseImageReference, parseMemoryMib, parseNetworkPolicy, parsePublishedPorts, parseRuntime, parseVcpuCount, policyConflictError, portConflictError, resolveImageRootfs, resolveVmState, safeKill, snapshotNotFoundError, socketTimeoutError, spawnTimeoutKiller, table, timeAgo, timeRemaining, toError, validateCidr, validateEnvironment, validatePublishedPortsAvailable, vmNoAgentTokenError, vmNotFoundError, vmNotRunningError, vmNotStoppedError, vmStateNotFoundError, vmsanPaths, waitForAgent, waitForSocket, writeSecure };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vmsan",
3
- "version": "0.1.0-alpha.14",
3
+ "version": "0.1.0-alpha.15",
4
4
  "description": "Firecracker microVM sandbox toolkit",
5
5
  "homepage": "https://github.com/angelorc/vmsan",
6
6
  "bugs": "https://github.com/angelorc/vmsan/issues",
@@ -1,72 +0,0 @@
1
- import { n as vmsanPaths } from "./paths.mjs";
2
- import { b as vmNotFoundError, t as handleCommandError } from "./errors.mjs";
3
- import { t as createCommandLogger } from "./logger.mjs";
4
- import { t as FileVmStateStore } from "./vm-state.mjs";
5
- import { t as ShellSession } from "./shell.mjs";
6
- import { t as waitForAgent } from "./connect.mjs";
7
- import { consola } from "consola";
8
- import { defineCommand } from "citty";
9
- const connectCommand = defineCommand({
10
- meta: {
11
- name: "connect",
12
- description: "Connect to a running VM"
13
- },
14
- args: {
15
- vmId: {
16
- type: "positional",
17
- description: "VM ID to connect to",
18
- required: true
19
- },
20
- session: {
21
- type: "string",
22
- alias: "s",
23
- description: "Attach to an existing shell session ID",
24
- required: false
25
- }
26
- },
27
- async run({ args }) {
28
- const cmdLog = createCommandLogger("connect");
29
- const paths = vmsanPaths();
30
- try {
31
- const state = new FileVmStateStore(paths.vmsDir).load(args.vmId);
32
- if (!state) throw vmNotFoundError(args.vmId);
33
- if (state.status !== "running") {
34
- consola.error(`VM ${args.vmId} is not running (status: ${state.status})`);
35
- cmdLog.emit();
36
- process.exitCode = 1;
37
- return;
38
- }
39
- const log = consola.withTag(args.vmId);
40
- const guestIp = state.network.guestIp;
41
- const port = state.agentPort || paths.agentPort;
42
- consola.debug(`Agent endpoint: ${guestIp}:${port}`);
43
- if (!state.agentToken) {
44
- consola.error(`VM ${args.vmId} has no agent token. The agent is required for shell access.`);
45
- cmdLog.emit();
46
- process.exitCode = 1;
47
- return;
48
- }
49
- log.start("Waiting for agent to become ready...");
50
- await waitForAgent(guestIp, port);
51
- log.success("Agent is ready. Connecting via PTY shell...");
52
- const shell = new ShellSession({
53
- host: guestIp,
54
- port,
55
- token: state.agentToken,
56
- sessionId: args.session
57
- });
58
- const closeInfo = await shell.connect();
59
- cmdLog.set({
60
- vmId: args.vmId,
61
- method: "pty"
62
- });
63
- cmdLog.emit();
64
- if (!closeInfo.sessionDestroyed && shell.sessionId) process.stderr.write(`\nResume this session with:\n vmsan connect ${args.vmId} --session ${shell.sessionId}\n`);
65
- process.exit(0);
66
- } catch (error) {
67
- handleCommandError(error, cmdLog);
68
- process.exit(1);
69
- }
70
- }
71
- });
72
- export { connectCommand as default };