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/LICENSE +190 -21
- package/README.md +91 -47
- package/dist/_chunks/agent.mjs +231 -4
- package/dist/_chunks/connect.mjs +53 -11
- package/dist/_chunks/context.mjs +2380 -0
- package/dist/_chunks/create.mjs +48 -180
- package/dist/_chunks/download.mjs +14 -22
- package/dist/_chunks/errors.mjs +11 -5
- package/dist/_chunks/exec.mjs +190 -0
- package/dist/_chunks/list.mjs +60 -54
- package/dist/_chunks/network.mjs +6 -5
- package/dist/_chunks/remove.mjs +9 -8
- package/dist/_chunks/shell.mjs +2 -0
- package/dist/_chunks/start.mjs +16 -165
- package/dist/_chunks/stop.mjs +8 -7
- package/dist/_chunks/summary.mjs +69 -0
- package/dist/_chunks/timeout-extender.mjs +66 -0
- package/dist/_chunks/timeout-killer.mjs +33 -0
- package/dist/_chunks/upload.mjs +5 -20
- package/dist/_chunks/validation.mjs +1 -1
- package/dist/_chunks/vm-context.mjs +34 -0
- package/dist/_chunks/vm-state.mjs +56 -24
- package/dist/bin/cli.mjs +16 -2
- package/dist/index.d.mts +660 -366
- package/dist/index.mjs +35 -8
- package/package.json +7 -6
- package/dist/_chunks/cleanup.mjs +0 -328
- package/dist/_chunks/connect2.mjs +0 -72
- package/dist/_chunks/environment.mjs +0 -1064
- package/dist/_chunks/image-rootfs.mjs +0 -329
- package/dist/_chunks/vm.mjs +0 -208
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
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,10 +1,36 @@
|
|
|
1
|
-
import {
|
|
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
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
|
+
}
|
|
30
|
+
function toError(err) {
|
|
31
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
8
34
|
* Send a signal to a process. Returns true if delivered, false if
|
|
9
35
|
* the process is already dead (ESRCH). Falls back to sudo for
|
|
10
36
|
* root-owned processes (EPERM). Re-throws unexpected errors.
|
|
@@ -80,6 +106,7 @@ function mkdirSecure(path) {
|
|
|
80
106
|
} catch (error) {
|
|
81
107
|
if (error.code !== "ENOENT") throw error;
|
|
82
108
|
}
|
|
109
|
+
chownToSudoUser(path);
|
|
83
110
|
}
|
|
84
111
|
function writeSecure(path, contents) {
|
|
85
112
|
writeFileSync(path, contents, { mode: 384 });
|
|
@@ -88,6 +115,7 @@ function writeSecure(path, contents) {
|
|
|
88
115
|
} catch (error) {
|
|
89
116
|
if (error.code !== "ENOENT") throw error;
|
|
90
117
|
}
|
|
118
|
+
chownToSudoUser(path);
|
|
91
119
|
}
|
|
92
120
|
const TIME_UNITS = [
|
|
93
121
|
[
|
|
@@ -150,7 +178,16 @@ function table(opts) {
|
|
|
150
178
|
const sep = " ";
|
|
151
179
|
return [`\x1b[1m${titles.map(padded).join(sep)}\x1b[0m`, ...data.map((row) => row.map(padded).join(sep))].join("\n");
|
|
152
180
|
}
|
|
153
|
-
|
|
181
|
+
function findFreeNetworkSlot(states) {
|
|
182
|
+
const usedSlots = new Set(states.filter((s) => s.status === "running" || s.status === "creating").map((s) => {
|
|
183
|
+
const parts = s.network.hostIp.split(".");
|
|
184
|
+
return Number(parts[2]);
|
|
185
|
+
}));
|
|
186
|
+
for (const slot of getActiveTapSlots()) usedSlots.add(slot);
|
|
187
|
+
for (let slot = 0; slot <= 254; slot++) if (!usedSlots.has(slot)) return slot;
|
|
188
|
+
throw networkSlotsExhaustedError();
|
|
189
|
+
}
|
|
190
|
+
var FileVmStateStore = class {
|
|
154
191
|
constructor(dir) {
|
|
155
192
|
this.dir = dir;
|
|
156
193
|
}
|
|
@@ -181,26 +218,21 @@ var FileVmStateStore = class FileVmStateStore {
|
|
|
181
218
|
if (existsSync(filePath)) unlinkSync(filePath);
|
|
182
219
|
}
|
|
183
220
|
allocateNetworkSlot() {
|
|
184
|
-
|
|
185
|
-
const usedSlots = new Set(states.filter((s) => s.status === "running" || s.status === "creating").map((s) => {
|
|
186
|
-
const parts = s.network.hostIp.split(".");
|
|
187
|
-
return Number(parts[2]);
|
|
188
|
-
}));
|
|
189
|
-
for (const slot of FileVmStateStore.getActiveTapSlots()) usedSlots.add(slot);
|
|
190
|
-
for (let slot = 0; slot <= 254; slot++) if (!usedSlots.has(slot)) return slot;
|
|
191
|
-
throw networkSlotsExhaustedError();
|
|
192
|
-
}
|
|
193
|
-
static getActiveTapSlots() {
|
|
194
|
-
const slots = /* @__PURE__ */ new Set();
|
|
195
|
-
try {
|
|
196
|
-
for (const iface of readdirSync("/sys/class/net")) {
|
|
197
|
-
const match = /^fhvm(\d+)$/.exec(iface);
|
|
198
|
-
if (!match) continue;
|
|
199
|
-
const slot = Number(match[1]);
|
|
200
|
-
if (Number.isInteger(slot) && slot >= 0 && slot <= 254) slots.add(slot);
|
|
201
|
-
}
|
|
202
|
-
} catch {}
|
|
203
|
-
return slots;
|
|
221
|
+
return findFreeNetworkSlot(this.list());
|
|
204
222
|
}
|
|
205
223
|
};
|
|
206
|
-
|
|
224
|
+
function getActiveTapSlots() {
|
|
225
|
+
const slots = /* @__PURE__ */ new Set();
|
|
226
|
+
try {
|
|
227
|
+
for (const iface of readdirSync("/sys/class/net")) {
|
|
228
|
+
const tapMatch = /^fhvm(\d+)$/.exec(iface);
|
|
229
|
+
const vethMatch = /^veth-h-(\d+)$/.exec(iface);
|
|
230
|
+
const match = tapMatch || vethMatch;
|
|
231
|
+
if (!match) continue;
|
|
232
|
+
const slot = Number(match[1]);
|
|
233
|
+
if (Number.isInteger(slot) && slot >= 0 && slot <= 254) slots.add(slot);
|
|
234
|
+
}
|
|
235
|
+
} catch {}
|
|
236
|
+
return slots;
|
|
237
|
+
}
|
|
238
|
+
export { isProcessAlive as a, safeKill as c, timeRemaining as d, toError as f, generateVmId as i, table as l, findFreeNetworkSlot as n, mkdirSecure as o, writeSecure as p, getActiveTapSlots as r, parseDuration as s, FileVmStateStore as t, timeAgo as u };
|
package/dist/bin/cli.mjs
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { i as initVmsanLogger } from "../_chunks/logger.mjs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
5
|
import { consola } from "consola";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
4
7
|
import { defineCommand, runMain } from "citty";
|
|
8
|
+
function findPackageVersion() {
|
|
9
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
while (dir !== dirname(dir)) {
|
|
11
|
+
const pkgPath = join(dir, "package.json");
|
|
12
|
+
if (existsSync(pkgPath)) return JSON.parse(readFileSync(pkgPath, "utf8")).version;
|
|
13
|
+
dir = dirname(dir);
|
|
14
|
+
}
|
|
15
|
+
return "0.0.0";
|
|
16
|
+
}
|
|
17
|
+
const version = findPackageVersion();
|
|
5
18
|
const SUDO_COMMANDS = new Set([
|
|
6
19
|
"create",
|
|
7
20
|
"start",
|
|
@@ -17,7 +30,7 @@ if (subCommand && SUDO_COMMANDS.has(subCommand) && process.getuid?.() !== 0) {
|
|
|
17
30
|
runMain(defineCommand({
|
|
18
31
|
meta: {
|
|
19
32
|
name: "vmsan",
|
|
20
|
-
version
|
|
33
|
+
version,
|
|
21
34
|
description: "Firecracker microVM sandbox toolkit"
|
|
22
35
|
},
|
|
23
36
|
args: {
|
|
@@ -43,9 +56,10 @@ runMain(defineCommand({
|
|
|
43
56
|
stop: () => import("../_chunks/stop.mjs").then((m) => m.default),
|
|
44
57
|
remove: () => import("../_chunks/remove.mjs").then((m) => m.default),
|
|
45
58
|
rm: () => import("../_chunks/remove.mjs").then((m) => m.default),
|
|
46
|
-
connect: () => import("../_chunks/
|
|
59
|
+
connect: () => import("../_chunks/connect.mjs").then((m) => m.default),
|
|
47
60
|
upload: () => import("../_chunks/upload.mjs").then((m) => m.default),
|
|
48
61
|
download: () => import("../_chunks/download.mjs").then((m) => m.default),
|
|
62
|
+
exec: () => import("../_chunks/exec.mjs").then((m) => m.default),
|
|
49
63
|
network: () => import("../_chunks/network.mjs").then((m) => m.default)
|
|
50
64
|
}
|
|
51
65
|
}));
|