vmsan 0.1.0-alpha.2 → 0.1.0-alpha.21

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/index.mjs CHANGED
@@ -1,17 +1,44 @@
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
+ 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";
4
6
  import { n as firecrackerFetch, t as FirecrackerClient } from "./_chunks/firecracker.mjs";
7
+ import { t as spawnTimeoutKiller } from "./_chunks/timeout-killer.mjs";
5
8
  import { t as AgentClient } from "./_chunks/agent.mjs";
6
- import { a as parseDuration, c as timeAgo, i as mkdirSecure, l as timeRemaining, n as generateVmId, o as safeKill, r as isProcessAlive, s as table, t as FileVmStateStore, u as writeSecure } from "./_chunks/vm-state.mjs";
7
- import { a as getVmPid, c as NetworkManager, i as getVmJailerPid, n as findKernel, o as validateEnvironment, r as findRootfs, s as waitForSocket } from "./_chunks/environment.mjs";
8
- import { n as FileLock, t as VMService } from "./_chunks/vm.mjs";
9
- import { a as Jailer, i as markVmAsError, n as cleanupNetwork, o as detectCgroupVersion, r as killOrphanVmProcess, t as cleanupChroot } from "./_chunks/cleanup.mjs";
9
+ import { t as TimeoutExtender } from "./_chunks/timeout-extender.mjs";
10
+ import { n as waitForAgent, t as resolveVmState } from "./_chunks/vm-context.mjs";
10
11
  import { n as connectShell, t as ShellSession } from "./_chunks/shell.mjs";
11
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";
12
- import { a as buildInitialVmState, i as buildCreateSummaryLines, n as compileSeccompFilter, o as parseCreateInput, r as ensureSeccompFilter, t as resolveImageRootfs } from "./_chunks/image-rootfs.mjs";
13
- import { t as waitForAgent } from "./_chunks/connect.mjs";
13
+ import { n as parseCreateInput, t as buildCreateSummaryLines } from "./_chunks/summary.mjs";
14
14
  import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
15
+ function definePlugin(plugin) {
16
+ return plugin;
17
+ }
18
+ var MemoryVmStateStore = class {
19
+ states = /* @__PURE__ */ new Map();
20
+ save(state) {
21
+ this.states.set(state.id, structuredClone(state));
22
+ }
23
+ load(id) {
24
+ const state = this.states.get(id);
25
+ return state ? structuredClone(state) : null;
26
+ }
27
+ list() {
28
+ return [...this.states.values()].map((s) => structuredClone(s));
29
+ }
30
+ update(id, updates) {
31
+ const state = this.states.get(id);
32
+ if (!state) throw vmStateNotFoundError(id);
33
+ Object.assign(state, updates);
34
+ }
35
+ delete(id) {
36
+ this.states.delete(id);
37
+ }
38
+ allocateNetworkSlot() {
39
+ return findFreeNetworkSlot([...this.states.values()]);
40
+ }
41
+ };
15
42
  var PidFile = class {
16
43
  constructor(path) {
17
44
  this.path = path;
@@ -39,4 +66,4 @@ async function getFirecrackerVersion(dir) {
39
66
  const { FirecrackerClient: FC } = await import("./_chunks/firecracker.mjs").then((n) => n.r);
40
67
  return FC.getVersion(dir || paths().baseDir);
41
68
  }
42
- export { AgentClient, FileLock, FileVmStateStore, FirecrackerApiError, FirecrackerClient, Jailer, NetworkError, NetworkManager, PidFile, SetupError, ShellSession, TimeoutError, VMService, ValidationError, VmError, VmsanError, agentTimeoutError, buildCreateSummaryLines, buildInitialVmState, chrootNotFoundError, cleanupChroot, cleanupNetwork, compileSeccompFilter, connectShell, createCommandLogger, createScopedLogger, defaultInterfaceNotFoundError, detectCgroupVersion, ensureSeccompFilter, findKernel, findRootfs, firecrackerApiError, firecrackerFetch, generateVmId, 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, validateCidr, validateEnvironment, validatePublishedPortsAvailable, vmNotFoundError, vmNotRunningError, vmNotStoppedError, vmStateNotFoundError, vmsanPaths, waitForAgent, waitForSocket, writeSecure };
69
+ 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,10 +1,10 @@
1
1
  {
2
2
  "name": "vmsan",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.21",
4
4
  "description": "Firecracker microVM sandbox toolkit",
5
5
  "homepage": "https://github.com/angelorc/vmsan",
6
6
  "bugs": "https://github.com/angelorc/vmsan/issues",
7
- "license": "MIT",
7
+ "license": "Apache-2.0",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/angelorc/vmsan.git"
@@ -27,10 +27,10 @@
27
27
  "scripts": {
28
28
  "build": "obuild",
29
29
  "dev": "obuild --stub",
30
- "lint": "oxlint . && oxfmt --check .",
31
- "lint:fix": "oxlint . --fix && oxfmt .",
32
- "fmt": "oxfmt .",
33
- "fmt:check": "oxfmt --check .",
30
+ "lint": "oxlint . && oxfmt --check '!**/*.md' '!docs/**' .",
31
+ "lint:fix": "oxlint . --fix && oxfmt '!**/*.md' '!docs/**' .",
32
+ "fmt": "oxfmt '!**/*.md' '!docs/**' .",
33
+ "fmt:check": "oxfmt --check '!**/*.md' '!docs/**' .",
34
34
  "test": "vitest run",
35
35
  "typecheck": "tsc --noEmit",
36
36
  "prepack": "bun run build",
@@ -43,6 +43,7 @@
43
43
  "citty": "^0.2.1",
44
44
  "consola": "^3.4.2",
45
45
  "evlog": "^2.0.0",
46
+ "hookable": "^6.0.1",
46
47
  "proper-lockfile": "^4.1.2",
47
48
  "tar-stream": "^3.1.8",
48
49
  "ws": "^8.19.0"
@@ -1,328 +0,0 @@
1
- import { o as safeKill, t as FileVmStateStore } from "./vm-state.mjs";
2
- import { a as getVmPid, c as NetworkManager, i as getVmJailerPid } from "./environment.mjs";
3
- import { dirname, join } from "node:path";
4
- import { execFileSync, execSync } from "node:child_process";
5
- import { copyFileSync, existsSync, linkSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
6
- /**
7
- * Template generators for the node22-demo runtime welcome page.
8
- * All functions are pure and return string content ready to write to files.
9
- */
10
- function generateWelcomeHtml(vmId, ports) {
11
- ports.map((p) => `<li>${p}</li>`).join("\n ");
12
- return `<!DOCTYPE html>
13
- <html lang="en">
14
- <head>
15
- <meta charset="utf-8">
16
- <meta name="viewport" content="width=device-width, initial-scale=1">
17
- <title>vmsan VM ${vmId}</title>
18
- <style>
19
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
20
- body {
21
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
22
- background: #0f172a;
23
- color: #e2e8f0;
24
- min-height: 100vh;
25
- display: flex;
26
- align-items: center;
27
- justify-content: center;
28
- padding: 2rem;
29
- }
30
- .container { max-width: 640px; width: 100%; }
31
- .header { text-align: center; margin-bottom: 2rem; }
32
- .logo {
33
- font-size: 2.5rem;
34
- font-weight: 800;
35
- background: linear-gradient(135deg, #f97316, #ef4444);
36
- -webkit-background-clip: text;
37
- -webkit-text-fill-color: transparent;
38
- background-clip: text;
39
- }
40
- .subtitle { color: #94a3b8; margin-top: 0.5rem; font-size: 1.1rem; }
41
- .card {
42
- background: #1e293b;
43
- border: 1px solid #334155;
44
- border-radius: 12px;
45
- padding: 1.5rem;
46
- margin-bottom: 1.25rem;
47
- }
48
- .card h2 { font-size: 1rem; color: #f97316; margin-bottom: 0.75rem; }
49
- .info-row { display: flex; justify-content: space-between; padding: 0.35rem 0; }
50
- .info-label { color: #94a3b8; }
51
- .info-value { font-family: monospace; color: #e2e8f0; }
52
- ul { list-style: none; }
53
- ul li { padding: 0.25rem 0; }
54
- code {
55
- background: #0f172a;
56
- border: 1px solid #334155;
57
- border-radius: 6px;
58
- padding: 0.2rem 0.5rem;
59
- font-size: 0.875rem;
60
- color: #f97316;
61
- }
62
- .steps li { padding: 0.5rem 0; color: #cbd5e1; }
63
- .steps li strong { color: #e2e8f0; }
64
- .footer { text-align: center; color: #475569; font-size: 0.85rem; margin-top: 1.5rem; }
65
- </style>
66
- </head>
67
- <body>
68
- <div class="container">
69
- <div class="header">
70
- <div class="logo">vmsan</div>
71
- <div class="subtitle">Your microVM is running</div>
72
- </div>
73
- <div class="card">
74
- <h2>VM Info</h2>
75
- <div class="info-row">
76
- <span class="info-label">VM ID</span>
77
- <span class="info-value">${vmId}</span>
78
- </div>
79
- <div class="info-row">
80
- <span class="info-label">Runtime</span>
81
- <span class="info-value">node22-demo</span>
82
- </div>
83
- <div class="info-row">
84
- <span class="info-label">Published Ports</span>
85
- <span class="info-value">${ports.join(", ")}</span>
86
- </div>
87
- </div>
88
- <div class="card">
89
- <h2>Next Steps</h2>
90
- <ul class="steps">
91
- <li><strong>Connect to the VM:</strong> <code>vmsan connect ${vmId}</code></li>
92
- <li><strong>Deploy your app:</strong> Replace this page by stopping the welcome service and running your own server on the published port(s).</li>
93
- <li><strong>Stop this page:</strong> <code>systemctl stop vmsan-welcome</code></li>
94
- </ul>
95
- </div>
96
- <div class="footer">Powered by vmsan &middot; Firecracker microVMs</div>
97
- </div>
98
- </body>
99
- </html>`;
100
- }
101
- function generateWelcomeServer(ports) {
102
- return `"use strict";
103
- const http = require("node:http");
104
- const fs = require("node:fs");
105
- const path = require("node:path");
106
-
107
- const html = fs.readFileSync(path.join(__dirname, "index.html"), "utf-8");
108
-
109
- const server = http.createServer((req, res) => {
110
- res.writeHead(200, {
111
- "Content-Type": "text/html; charset=utf-8",
112
- "Cache-Control": "no-cache",
113
- });
114
- res.end(html);
115
- });
116
-
117
- ${ports.map((p) => `server.listen(${p}, "0.0.0.0", () => console.log("vmsan-welcome listening on 0.0.0.0:${p}"));`).join("\n")}
118
- `;
119
- }
120
- function generateWelcomeService(ports) {
121
- return `[Unit]
122
- Description=${`vmsan welcome page on port(s) ${ports.join(", ")}`}
123
- After=network.target
124
-
125
- [Service]
126
- Type=simple
127
- ExecStart=/usr/local/bin/node /opt/vmsan/welcome/server.js
128
- Restart=on-failure
129
- RestartSec=2
130
-
131
- [Install]
132
- WantedBy=multi-user.target
133
- `;
134
- }
135
- /**
136
- * Template generators for the vmsan-agent systemd service.
137
- * Follows the same pattern as welcome-page.ts.
138
- */
139
- function generateAgentService() {
140
- return `[Unit]
141
- Description=Vmsan VM Agent
142
- After=network.target
143
-
144
- [Service]
145
- Type=simple
146
- ExecStart=/usr/local/bin/vmsan-agent
147
- EnvironmentFile=/etc/vmsan/agent.env
148
- Restart=always
149
- RestartSec=2
150
-
151
- [Install]
152
- WantedBy=multi-user.target
153
- `;
154
- }
155
- function generateAgentEnv(token, port, vmId) {
156
- return `VMSAN_AGENT_TOKEN=${token}
157
- VMSAN_AGENT_PORT=${port}
158
- VMSAN_VM_ID=${vmId}
159
- `;
160
- }
161
- function detectCgroupVersion() {
162
- try {
163
- readFileSync("/sys/fs/cgroup/cgroup.controllers", "utf-8");
164
- return 2;
165
- } catch {
166
- return 1;
167
- }
168
- }
169
- var Jailer = class {
170
- paths;
171
- constructor(vmId, jailerBaseDir) {
172
- this.vmId = vmId;
173
- const chrootBase = jailerBaseDir;
174
- const chrootDir = join(chrootBase, "firecracker", vmId);
175
- const rootDir = join(chrootDir, "root");
176
- const kernelDir = join(rootDir, "kernel");
177
- const rootfsDir = join(rootDir, "rootfs");
178
- const socketDir = join(rootDir, "run");
179
- const snapshotDir = join(rootDir, "snapshot");
180
- this.paths = {
181
- chrootBase,
182
- chrootDir,
183
- rootDir,
184
- kernelDir,
185
- kernelPath: join(kernelDir, "vmlinux"),
186
- rootfsDir,
187
- rootfsPath: join(rootfsDir, "rootfs.ext4"),
188
- socketDir,
189
- socketPath: join(socketDir, "firecracker.socket"),
190
- snapshotDir
191
- };
192
- }
193
- prepare(config) {
194
- const paths = this.paths;
195
- mkdirSync(paths.kernelDir, { recursive: true });
196
- mkdirSync(paths.rootfsDir, { recursive: true });
197
- mkdirSync(paths.socketDir, { recursive: true });
198
- if (!existsSync(paths.kernelPath)) linkSync(config.kernelSrc, paths.kernelPath);
199
- copyFileSync(config.rootfsSrc, paths.rootfsPath);
200
- if (typeof config.diskSizeGb === "number" && Number.isFinite(config.diskSizeGb)) {
201
- const targetBytes = Math.trunc(config.diskSizeGb * 1024 * 1024 * 1024);
202
- if (targetBytes > statSync(paths.rootfsPath).size) {
203
- execSync(`truncate -s ${targetBytes} "${paths.rootfsPath}"`, { stdio: "pipe" });
204
- execSync(`sudo e2fsck -fy "${paths.rootfsPath}"; [ $? -lt 4 ]`, { stdio: "pipe" });
205
- execSync(`sudo resize2fs "${paths.rootfsPath}"`, { stdio: "pipe" });
206
- execSync(`sudo tune2fs -m 0 "${paths.rootfsPath}"`, { stdio: "pipe" });
207
- }
208
- }
209
- const tmpMount = join(paths.rootDir, "tmp-mount");
210
- mkdirSync(tmpMount, { recursive: true });
211
- try {
212
- execSync(`sudo mount -o loop "${paths.rootfsPath}" "${tmpMount}"`, { stdio: "pipe" });
213
- execSync(`rm -f "${tmpMount}/etc/resolv.conf" && ln -s /proc/net/pnp "${tmpMount}/etc/resolv.conf"`, { stdio: "pipe" });
214
- if (config.welcomePage) {
215
- const { vmId: welcomeVmId, ports: welcomePorts } = config.welcomePage;
216
- const welcomeDir = join(tmpMount, "opt", "vmsan", "welcome");
217
- mkdirSync(welcomeDir, { recursive: true });
218
- writeFileSync(join(welcomeDir, "index.html"), generateWelcomeHtml(welcomeVmId, welcomePorts));
219
- writeFileSync(join(welcomeDir, "server.js"), generateWelcomeServer(welcomePorts));
220
- const systemdDir = join(tmpMount, "etc", "systemd", "system");
221
- mkdirSync(systemdDir, { recursive: true });
222
- writeFileSync(join(systemdDir, "vmsan-welcome.service"), generateWelcomeService(welcomePorts));
223
- const wantsDir = join(systemdDir, "multi-user.target.wants");
224
- mkdirSync(wantsDir, { recursive: true });
225
- execSync(`ln -sf /etc/systemd/system/vmsan-welcome.service "${join(wantsDir, "vmsan-welcome.service")}"`, { stdio: "pipe" });
226
- }
227
- if (config.agent) {
228
- const agentDst = join(tmpMount, "usr", "local", "bin", "vmsan-agent");
229
- mkdirSync(join(tmpMount, "usr", "local", "bin"), { recursive: true });
230
- copyFileSync(config.agent.binaryPath, agentDst);
231
- execSync(`chmod 755 "${agentDst}"`, { stdio: "pipe" });
232
- const envDir = join(tmpMount, "etc", "vmsan");
233
- mkdirSync(envDir, { recursive: true });
234
- writeFileSync(join(envDir, "agent.env"), generateAgentEnv(config.agent.token, config.agent.port, config.agent.vmId));
235
- const systemdDir = join(tmpMount, "etc", "systemd", "system");
236
- mkdirSync(systemdDir, { recursive: true });
237
- writeFileSync(join(systemdDir, "vmsan-agent.service"), generateAgentService());
238
- const wantsDir = join(systemdDir, "multi-user.target.wants");
239
- mkdirSync(wantsDir, { recursive: true });
240
- execSync(`ln -sf /etc/systemd/system/vmsan-agent.service "${join(wantsDir, "vmsan-agent.service")}"`, { stdio: "pipe" });
241
- }
242
- execSync(`sudo umount "${tmpMount}"`, { stdio: "pipe" });
243
- } catch {
244
- try {
245
- execSync(`sudo umount "${tmpMount}" 2>/dev/null`, { stdio: "pipe" });
246
- } catch {}
247
- }
248
- try {
249
- execSync(`rm -rf "${tmpMount}"`, { stdio: "pipe" });
250
- } catch {}
251
- if (config.snapshot) {
252
- mkdirSync(paths.snapshotDir, { recursive: true });
253
- copyFileSync(config.snapshot.snapshotFile, join(paths.snapshotDir, "snapshot_file"));
254
- copyFileSync(config.snapshot.memFile, join(paths.snapshotDir, "mem_file"));
255
- }
256
- return paths;
257
- }
258
- spawn(config) {
259
- const uid = config.uid ?? 0;
260
- const gid = config.gid ?? 0;
261
- const args = [
262
- config.jailerBin,
263
- "--exec-file",
264
- config.firecrackerBin,
265
- "--id",
266
- this.vmId,
267
- "--uid",
268
- String(uid),
269
- "--gid",
270
- String(gid),
271
- "--chroot-base-dir",
272
- config.chrootBase,
273
- "--daemonize"
274
- ];
275
- if (config.newPidNs !== false) args.push("--new-pid-ns");
276
- if (config.netns) args.push("--netns", `/var/run/netns/${config.netns}`);
277
- if (config.cgroup) if (detectCgroupVersion() === 2) {
278
- args.push("--cgroup-version", "2");
279
- args.push("--cgroup", `cpu.max=${config.cgroup.cpuQuotaUs} ${config.cgroup.cpuPeriodUs}`);
280
- args.push("--cgroup", `memory.max=${config.cgroup.memoryBytes}`);
281
- } else {
282
- args.push("--cgroup", `cpu.cfs_quota_us=${config.cgroup.cpuQuotaUs}`);
283
- args.push("--cgroup", `cpu.cfs_period_us=${config.cgroup.cpuPeriodUs}`);
284
- args.push("--cgroup", `memory.limit_in_bytes=${config.cgroup.memoryBytes}`);
285
- }
286
- args.push("--", "--api-sock", "run/firecracker.socket");
287
- if (config.seccompFilter && existsSync(config.seccompFilter)) args.push("--seccomp-filter", config.seccompFilter);
288
- else args.push("--no-seccomp");
289
- execFileSync("sudo", args, { stdio: "pipe" });
290
- }
291
- };
292
- function killOrphanVmProcess(vmId) {
293
- const orphanPid = getVmPid(vmId);
294
- const orphanJailerPid = getVmJailerPid(vmId);
295
- if (orphanPid) safeKill(orphanPid, "SIGKILL");
296
- if (orphanJailerPid) safeKill(orphanJailerPid, "SIGKILL");
297
- }
298
- function markVmAsError(vmId, error, paths) {
299
- try {
300
- new FileVmStateStore(paths.vmsDir).update(vmId, {
301
- status: "error",
302
- error: error instanceof Error ? error.message : String(error)
303
- });
304
- } catch {}
305
- }
306
- function cleanupNetwork(networkConfig) {
307
- if (!networkConfig) return;
308
- try {
309
- NetworkManager.fromConfig(networkConfig).teardown();
310
- } catch {}
311
- }
312
- function cleanupChroot(chrootDir) {
313
- if (!chrootDir) return;
314
- const vmJailerDir = dirname(chrootDir);
315
- try {
316
- rmSync(chrootDir, {
317
- recursive: true,
318
- force: true
319
- });
320
- } catch {}
321
- try {
322
- rmSync(vmJailerDir, {
323
- recursive: true,
324
- force: true
325
- });
326
- } catch {}
327
- }
328
- export { Jailer as a, markVmAsError as i, cleanupNetwork as n, detectCgroupVersion as o, killOrphanVmProcess as r, cleanupChroot as t };
@@ -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 };