@ghalex/kvbox 0.0.1 → 0.0.2
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/kvbox.js +141 -274
- package/dist/kvbox.js.map +1 -1
- package/package.json +6 -18
- package/README.md +0 -76
package/dist/kvbox.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/
|
|
3
|
+
// src/output.ts
|
|
4
4
|
import pc from "picocolors";
|
|
5
5
|
var color = pc.createColors(process.stdout.isTTY === true && !process.env["NO_COLOR"]);
|
|
6
6
|
function json(value) {
|
|
@@ -15,28 +15,39 @@ function notImplemented(command) {
|
|
|
15
15
|
process.exit(2);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
// src/
|
|
18
|
+
// src/program.ts
|
|
19
19
|
import { Command as Command14 } from "commander";
|
|
20
20
|
|
|
21
|
-
// src/
|
|
21
|
+
// ../shared/src/version.ts
|
|
22
22
|
import { createRequire } from "module";
|
|
23
|
-
|
|
23
|
+
function versionOf(srcFileUrl, injected) {
|
|
24
|
+
if (typeof injected === "string") return injected;
|
|
25
|
+
try {
|
|
26
|
+
const require2 = createRequire(srcFileUrl);
|
|
27
|
+
return require2("../package.json").version;
|
|
28
|
+
} catch {
|
|
29
|
+
return "0.0.0-dev";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/version.ts
|
|
34
|
+
var VERSION = versionOf(import.meta.url, true ? "0.0.2" : void 0);
|
|
24
35
|
|
|
25
|
-
// src/
|
|
36
|
+
// src/commands/machine/index.ts
|
|
26
37
|
import { Command as Command5 } from "commander";
|
|
27
38
|
|
|
28
|
-
// src/
|
|
39
|
+
// src/commands/machine/connect.ts
|
|
29
40
|
import { Command } from "commander";
|
|
30
41
|
var connectCommand = new Command("connect").description("open an interactive shell in a box over SSH").argument("<name>", "box name").action(() => notImplemented("machine connect"));
|
|
31
42
|
|
|
32
|
-
// src/
|
|
43
|
+
// src/commands/machine/ls.ts
|
|
33
44
|
import { Command as Command2 } from "commander";
|
|
34
45
|
var lsCommand = new Command2("ls").description("list boxes with status and IP").option("--json", "print as JSON").action(() => notImplemented("machine ls"));
|
|
35
46
|
|
|
36
|
-
// src/
|
|
47
|
+
// src/commands/machine/new.ts
|
|
37
48
|
import { Command as Command3, Option } from "commander";
|
|
38
49
|
|
|
39
|
-
// src/
|
|
50
|
+
// ../shared/src/sizes.ts
|
|
40
51
|
var SIZES = {
|
|
41
52
|
s: { vcpu: 2, memMib: 4096 },
|
|
42
53
|
m: { vcpu: 4, memMib: 8192 },
|
|
@@ -45,32 +56,24 @@ var SIZES = {
|
|
|
45
56
|
var SIZE_NAMES = Object.keys(SIZES);
|
|
46
57
|
var DEFAULT_SIZE = "s";
|
|
47
58
|
|
|
48
|
-
// src/
|
|
59
|
+
// src/commands/machine/new.ts
|
|
49
60
|
var newCommand = new Command3("new").description("boot a new box from the base image or a snapshot").argument("[name]", "box name (generated when omitted)").addOption(new Option("-s, --size <size>", "box size").choices(SIZE_NAMES).default(DEFAULT_SIZE)).option("-f, --from <snapshot>", "start from a snapshot instead of the base image").option("--force", "skip the capacity guard").option("--json", "print the box as JSON").action(() => notImplemented("machine new"));
|
|
50
61
|
|
|
51
|
-
// src/
|
|
62
|
+
// src/commands/machine/rm.ts
|
|
52
63
|
import { Command as Command4 } from "commander";
|
|
53
64
|
var rmCommand = new Command4("rm").description("destroy a box").argument("<name>", "box name").option("-y, --yes", "do not ask for confirmation").action(() => notImplemented("machine rm"));
|
|
54
65
|
|
|
55
|
-
// src/
|
|
66
|
+
// src/commands/machine/index.ts
|
|
56
67
|
var machineCommand = new Command5("machine").alias("m").description("manage boxes").addCommand(newCommand).addCommand(lsCommand).addCommand(connectCommand).addCommand(rmCommand);
|
|
57
68
|
var machineAliases = [newCommand, lsCommand, connectCommand, rmCommand];
|
|
58
69
|
|
|
59
|
-
// src/
|
|
70
|
+
// src/commands/server/index.ts
|
|
60
71
|
import { Command as Command9 } from "commander";
|
|
61
72
|
|
|
62
|
-
// src/
|
|
73
|
+
// src/commands/server/start.ts
|
|
63
74
|
import { Command as Command6 } from "commander";
|
|
64
75
|
|
|
65
|
-
// src/
|
|
66
|
-
import { rm, writeFile as writeFile3 } from "fs/promises";
|
|
67
|
-
import { createAdaptorServer } from "@hono/node-server";
|
|
68
|
-
|
|
69
|
-
// src/core/state.ts
|
|
70
|
-
import { mkdir, readFile, readdir, writeFile } from "fs/promises";
|
|
71
|
-
import { z } from "zod";
|
|
72
|
-
|
|
73
|
-
// src/shared/paths.ts
|
|
76
|
+
// ../shared/src/paths.ts
|
|
74
77
|
import { homedir } from "os";
|
|
75
78
|
import { join } from "path";
|
|
76
79
|
function kvboxHome() {
|
|
@@ -82,6 +85,7 @@ function paths(home = kvboxHome()) {
|
|
|
82
85
|
config: join(home, "config.json"),
|
|
83
86
|
serverSock: join(home, "server.sock"),
|
|
84
87
|
serverPid: join(home, "server.pid"),
|
|
88
|
+
serverLog: join(home, "server.log"),
|
|
85
89
|
hosts: join(home, "hosts.json"),
|
|
86
90
|
ssh: join(home, "ssh"),
|
|
87
91
|
sshKey: join(home, "ssh", "id_ed25519"),
|
|
@@ -109,65 +113,63 @@ function paths(home = kvboxHome()) {
|
|
|
109
113
|
};
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
// src/
|
|
113
|
-
|
|
116
|
+
// src/client.ts
|
|
117
|
+
import { spawn } from "child_process";
|
|
118
|
+
import { openSync } from "fs";
|
|
119
|
+
import { mkdir, stat } from "fs/promises";
|
|
120
|
+
import { setTimeout as sleep } from "timers/promises";
|
|
121
|
+
import { Agent, request } from "undici";
|
|
122
|
+
import { z as z2 } from "zod";
|
|
123
|
+
|
|
124
|
+
// ../shared/src/api.ts
|
|
125
|
+
import { z } from "zod";
|
|
126
|
+
var SizeSchema = z.enum(SIZE_NAMES);
|
|
127
|
+
var MachineSchema = z.object({
|
|
114
128
|
name: z.string(),
|
|
115
129
|
ip: z.string(),
|
|
116
130
|
mac: z.string(),
|
|
117
|
-
pid: z.number().int(),
|
|
131
|
+
pid: z.number().int().nullable(),
|
|
118
132
|
vcpu: z.number().int(),
|
|
119
133
|
memMib: z.number().int(),
|
|
120
134
|
createdAt: z.string(),
|
|
121
|
-
source: z.string()
|
|
135
|
+
source: z.string(),
|
|
136
|
+
// "base" or "snapshot:<name>"
|
|
137
|
+
status: z.enum(["running", "stopped"])
|
|
138
|
+
});
|
|
139
|
+
var CreateMachineRequest = z.object({
|
|
140
|
+
name: z.string().optional(),
|
|
141
|
+
size: SizeSchema.optional(),
|
|
142
|
+
from: z.string().optional(),
|
|
143
|
+
force: z.boolean().optional()
|
|
122
144
|
});
|
|
123
|
-
var
|
|
145
|
+
var SnapshotSchema = z.object({
|
|
124
146
|
name: z.string(),
|
|
125
147
|
source: z.string(),
|
|
126
148
|
vcpu: z.number().int(),
|
|
127
149
|
memMib: z.number().int(),
|
|
128
150
|
createdAt: z.string()
|
|
129
151
|
});
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
var CreateSnapshotRequest = z.object({
|
|
153
|
+
box: z.string(),
|
|
154
|
+
name: z.string()
|
|
155
|
+
});
|
|
156
|
+
var CapacitySchema = z.object({
|
|
157
|
+
hostMemMib: z.number().int(),
|
|
158
|
+
hostCores: z.number().int(),
|
|
159
|
+
reservedMemMib: z.number().int(),
|
|
160
|
+
reservedVcpu: z.number().int(),
|
|
161
|
+
running: z.number().int()
|
|
162
|
+
});
|
|
163
|
+
var HealthSchema = z.object({
|
|
164
|
+
ok: z.literal(true),
|
|
165
|
+
version: z.string(),
|
|
166
|
+
pid: z.number().int()
|
|
167
|
+
});
|
|
168
|
+
var ApiErrorSchema = z.object({
|
|
169
|
+
error: z.string()
|
|
148
170
|
});
|
|
149
|
-
function defaultConfig(p = paths()) {
|
|
150
|
-
return ConfigSchema.parse({
|
|
151
|
-
kernel: p.kernel,
|
|
152
|
-
rootfs: p.rootfs,
|
|
153
|
-
firecracker: p.firecracker
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
async function loadConfig(p = paths()) {
|
|
157
|
-
try {
|
|
158
|
-
const raw = await readFile2(p.config, "utf8");
|
|
159
|
-
return ConfigSchema.parse(JSON.parse(raw));
|
|
160
|
-
} catch (err) {
|
|
161
|
-
if (err.code === "ENOENT") {
|
|
162
|
-
const cfg = defaultConfig(p);
|
|
163
|
-
await writeFile2(p.config, JSON.stringify(cfg, null, 2) + "\n");
|
|
164
|
-
return cfg;
|
|
165
|
-
}
|
|
166
|
-
throw err;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
171
|
|
|
170
|
-
// src/
|
|
172
|
+
// ../shared/src/errors.ts
|
|
171
173
|
var KvboxError = class extends Error {
|
|
172
174
|
constructor(message, exitCode = 1) {
|
|
173
175
|
super(message);
|
|
@@ -176,194 +178,8 @@ var KvboxError = class extends Error {
|
|
|
176
178
|
}
|
|
177
179
|
exitCode;
|
|
178
180
|
};
|
|
179
|
-
var NotImplementedError = class extends KvboxError {
|
|
180
|
-
constructor(what) {
|
|
181
|
-
super(`${what}: not implemented yet`, 2);
|
|
182
|
-
this.name = "NotImplementedError";
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
// src/server/app.ts
|
|
187
|
-
import { Hono as Hono4 } from "hono";
|
|
188
|
-
|
|
189
|
-
// src/server/routes/capacity.ts
|
|
190
|
-
import { Hono } from "hono";
|
|
191
181
|
|
|
192
|
-
// src/
|
|
193
|
-
async function capacity() {
|
|
194
|
-
throw new NotImplementedError("capacity.capacity");
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// src/server/routes/capacity.ts
|
|
198
|
-
function capacityRoutes() {
|
|
199
|
-
const r = new Hono();
|
|
200
|
-
r.get("/", async (c) => c.json(await capacity()));
|
|
201
|
-
return r;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// src/server/routes/machines.ts
|
|
205
|
-
import { Hono as Hono2 } from "hono";
|
|
206
|
-
|
|
207
|
-
// src/shared/api.ts
|
|
208
|
-
import { z as z3 } from "zod";
|
|
209
|
-
var SizeSchema = z3.enum(SIZE_NAMES);
|
|
210
|
-
var MachineSchema = z3.object({
|
|
211
|
-
name: z3.string(),
|
|
212
|
-
ip: z3.string(),
|
|
213
|
-
mac: z3.string(),
|
|
214
|
-
pid: z3.number().int().nullable(),
|
|
215
|
-
vcpu: z3.number().int(),
|
|
216
|
-
memMib: z3.number().int(),
|
|
217
|
-
createdAt: z3.string(),
|
|
218
|
-
source: z3.string(),
|
|
219
|
-
// "base" or "snapshot:<name>"
|
|
220
|
-
status: z3.enum(["running", "stopped"])
|
|
221
|
-
});
|
|
222
|
-
var CreateMachineRequest = z3.object({
|
|
223
|
-
name: z3.string().optional(),
|
|
224
|
-
size: SizeSchema.optional(),
|
|
225
|
-
from: z3.string().optional(),
|
|
226
|
-
force: z3.boolean().optional()
|
|
227
|
-
});
|
|
228
|
-
var SnapshotSchema = z3.object({
|
|
229
|
-
name: z3.string(),
|
|
230
|
-
source: z3.string(),
|
|
231
|
-
vcpu: z3.number().int(),
|
|
232
|
-
memMib: z3.number().int(),
|
|
233
|
-
createdAt: z3.string()
|
|
234
|
-
});
|
|
235
|
-
var CreateSnapshotRequest = z3.object({
|
|
236
|
-
box: z3.string(),
|
|
237
|
-
name: z3.string()
|
|
238
|
-
});
|
|
239
|
-
var CapacitySchema = z3.object({
|
|
240
|
-
hostMemMib: z3.number().int(),
|
|
241
|
-
hostCores: z3.number().int(),
|
|
242
|
-
reservedMemMib: z3.number().int(),
|
|
243
|
-
reservedVcpu: z3.number().int(),
|
|
244
|
-
running: z3.number().int()
|
|
245
|
-
});
|
|
246
|
-
var HealthSchema = z3.object({
|
|
247
|
-
ok: z3.literal(true),
|
|
248
|
-
version: z3.string(),
|
|
249
|
-
pid: z3.number().int()
|
|
250
|
-
});
|
|
251
|
-
var ApiErrorSchema = z3.object({
|
|
252
|
-
error: z3.string()
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// src/core/machines.ts
|
|
256
|
-
async function listMachines() {
|
|
257
|
-
throw new NotImplementedError("machines.listMachines");
|
|
258
|
-
}
|
|
259
|
-
async function createMachine(_req) {
|
|
260
|
-
throw new NotImplementedError("machines.createMachine");
|
|
261
|
-
}
|
|
262
|
-
async function destroyMachine(_name) {
|
|
263
|
-
throw new NotImplementedError("machines.destroyMachine");
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// src/server/routes/machines.ts
|
|
267
|
-
function machineRoutes() {
|
|
268
|
-
const r = new Hono2();
|
|
269
|
-
r.get("/", async (c) => c.json(await listMachines()));
|
|
270
|
-
r.post("/", async (c) => {
|
|
271
|
-
const body = CreateMachineRequest.parse(await c.req.json().catch(() => ({})));
|
|
272
|
-
return c.json(await createMachine(body), 201);
|
|
273
|
-
});
|
|
274
|
-
r.delete("/:name", async (c) => {
|
|
275
|
-
await destroyMachine(c.req.param("name"));
|
|
276
|
-
return c.body(null, 204);
|
|
277
|
-
});
|
|
278
|
-
return r;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// src/server/routes/snapshots.ts
|
|
282
|
-
import { Hono as Hono3 } from "hono";
|
|
283
|
-
|
|
284
|
-
// src/core/snapshots.ts
|
|
285
|
-
async function listSnapshots() {
|
|
286
|
-
throw new NotImplementedError("snapshots.listSnapshots");
|
|
287
|
-
}
|
|
288
|
-
async function saveSnapshot(_req) {
|
|
289
|
-
throw new NotImplementedError("snapshots.saveSnapshot");
|
|
290
|
-
}
|
|
291
|
-
async function deleteSnapshot(_name) {
|
|
292
|
-
throw new NotImplementedError("snapshots.deleteSnapshot");
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// src/server/routes/snapshots.ts
|
|
296
|
-
function snapshotRoutes() {
|
|
297
|
-
const r = new Hono3();
|
|
298
|
-
r.get("/", async (c) => c.json(await listSnapshots()));
|
|
299
|
-
r.post("/", async (c) => {
|
|
300
|
-
const body = CreateSnapshotRequest.parse(await c.req.json());
|
|
301
|
-
return c.json(await saveSnapshot(body), 201);
|
|
302
|
-
});
|
|
303
|
-
r.delete("/:name", async (c) => {
|
|
304
|
-
await deleteSnapshot(c.req.param("name"));
|
|
305
|
-
return c.body(null, 204);
|
|
306
|
-
});
|
|
307
|
-
return r;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// src/server/app.ts
|
|
311
|
-
function createApp() {
|
|
312
|
-
const app = new Hono4();
|
|
313
|
-
app.get("/health", (c) => c.json({ ok: true, version: VERSION, pid: process.pid }));
|
|
314
|
-
app.route("/machines", machineRoutes());
|
|
315
|
-
app.route("/snapshots", snapshotRoutes());
|
|
316
|
-
app.route("/capacity", capacityRoutes());
|
|
317
|
-
app.notFound((c) => c.json({ error: `no route: ${c.req.method} ${c.req.path}` }, 404));
|
|
318
|
-
app.onError((err, c) => {
|
|
319
|
-
if (err instanceof KvboxError) {
|
|
320
|
-
return c.json({ error: err.message }, err.exitCode === 2 ? 501 : 400);
|
|
321
|
-
}
|
|
322
|
-
console.error(err);
|
|
323
|
-
return c.json({ error: err.message }, 500);
|
|
324
|
-
});
|
|
325
|
-
return app;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// src/server/listen.ts
|
|
329
|
-
async function listen(opts = {}) {
|
|
330
|
-
const p = await ensureLayout(paths());
|
|
331
|
-
await loadConfig(p);
|
|
332
|
-
const socketPath = opts.socketPath ?? p.serverSock;
|
|
333
|
-
if (Buffer.byteLength(socketPath) > 100) {
|
|
334
|
-
throw new KvboxError(`socket path too long for a Unix socket (${socketPath}); set KVBOX_HOME to a shorter path`);
|
|
335
|
-
}
|
|
336
|
-
await rm(socketPath, { force: true });
|
|
337
|
-
const server = createAdaptorServer({ fetch: createApp().fetch });
|
|
338
|
-
await new Promise((resolve, reject) => {
|
|
339
|
-
server.once("error", reject);
|
|
340
|
-
server.listen(socketPath, () => resolve());
|
|
341
|
-
});
|
|
342
|
-
await writeFile3(p.serverPid, `${process.pid}
|
|
343
|
-
`);
|
|
344
|
-
const shutdown = async () => {
|
|
345
|
-
server.close();
|
|
346
|
-
await rm(socketPath, { force: true });
|
|
347
|
-
await rm(p.serverPid, { force: true });
|
|
348
|
-
process.exit(0);
|
|
349
|
-
};
|
|
350
|
-
process.once("SIGINT", () => void shutdown());
|
|
351
|
-
process.once("SIGTERM", () => void shutdown());
|
|
352
|
-
return { socketPath, server };
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// src/cli/commands/server/start.ts
|
|
356
|
-
var startCommand = new Command6("start").description("run the kvbox server (owns Firecracker and ~/.kvbox)").option("--foreground", "stay attached to the terminal (default for now)").action(async () => {
|
|
357
|
-
const { socketPath } = await listen();
|
|
358
|
-
info(`kvbox server listening on ${socketPath} (pid ${process.pid})`);
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
// src/cli/commands/server/status.ts
|
|
362
|
-
import { Command as Command7 } from "commander";
|
|
363
|
-
|
|
364
|
-
// src/cli/client.ts
|
|
365
|
-
import { Agent, request } from "undici";
|
|
366
|
-
import { z as z4 } from "zod";
|
|
182
|
+
// src/client.ts
|
|
367
183
|
function resolveTarget(hostFlag) {
|
|
368
184
|
if (hostFlag) {
|
|
369
185
|
throw new KvboxError(`--host ${hostFlag}: remote hosts arrive in M2`);
|
|
@@ -396,7 +212,7 @@ var ServerClient = class {
|
|
|
396
212
|
} catch (err) {
|
|
397
213
|
const code = err.code ?? err.cause;
|
|
398
214
|
if (this.target.kind === "local") {
|
|
399
|
-
throw new KvboxError(`kvbox
|
|
215
|
+
throw new KvboxError(`kvbox-server is not running (${String(code)}); start it with: kvbox server start`);
|
|
400
216
|
}
|
|
401
217
|
throw new KvboxError(`cannot reach ${this.target.name}: ${String(code)}`);
|
|
402
218
|
}
|
|
@@ -415,22 +231,22 @@ var ServerClient = class {
|
|
|
415
231
|
return this.call("GET", "/capacity", CapacitySchema);
|
|
416
232
|
}
|
|
417
233
|
listMachines() {
|
|
418
|
-
return this.call("GET", "/machines",
|
|
234
|
+
return this.call("GET", "/machines", z2.array(MachineSchema));
|
|
419
235
|
}
|
|
420
236
|
createMachine(req) {
|
|
421
237
|
return this.call("POST", "/machines", MachineSchema, req);
|
|
422
238
|
}
|
|
423
239
|
deleteMachine(name) {
|
|
424
|
-
return this.call("DELETE", `/machines/${encodeURIComponent(name)}`,
|
|
240
|
+
return this.call("DELETE", `/machines/${encodeURIComponent(name)}`, z2.undefined());
|
|
425
241
|
}
|
|
426
242
|
listSnapshots() {
|
|
427
|
-
return this.call("GET", "/snapshots",
|
|
243
|
+
return this.call("GET", "/snapshots", z2.array(SnapshotSchema));
|
|
428
244
|
}
|
|
429
245
|
createSnapshot(req) {
|
|
430
246
|
return this.call("POST", "/snapshots", SnapshotSchema, req);
|
|
431
247
|
}
|
|
432
248
|
deleteSnapshot(name) {
|
|
433
|
-
return this.call("DELETE", `/snapshots/${encodeURIComponent(name)}`,
|
|
249
|
+
return this.call("DELETE", `/snapshots/${encodeURIComponent(name)}`, z2.undefined());
|
|
434
250
|
}
|
|
435
251
|
async close() {
|
|
436
252
|
await this.dispatcher.close();
|
|
@@ -439,60 +255,111 @@ var ServerClient = class {
|
|
|
439
255
|
function clientFor(opts) {
|
|
440
256
|
return new ServerClient(resolveTarget(opts.host));
|
|
441
257
|
}
|
|
258
|
+
var NOT_A_HOST_HINT = "kvbox-server is not installed, so this machine is not a kvbox host. Set it up with install-server.sh, or point the CLI at a remote host (M2: kvbox host add).";
|
|
259
|
+
async function startLocalServer(p = paths(), timeoutMs = 5e3) {
|
|
260
|
+
await mkdir(p.home, { recursive: true });
|
|
261
|
+
const log = openSync(p.serverLog, "a");
|
|
262
|
+
const child = spawn("kvbox-server", ["start"], { detached: true, stdio: ["ignore", log, log] });
|
|
263
|
+
const failed = new Promise((_, reject) => {
|
|
264
|
+
child.once("error", (err) => {
|
|
265
|
+
reject(new KvboxError(err.code === "ENOENT" ? NOT_A_HOST_HINT : `could not start kvbox-server: ${err.message}`));
|
|
266
|
+
});
|
|
267
|
+
child.once("exit", (code) => {
|
|
268
|
+
reject(new KvboxError(`kvbox-server exited with code ${String(code)} before listening; see ${p.serverLog}`));
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
const ready = (async () => {
|
|
272
|
+
const deadline = Date.now() + timeoutMs;
|
|
273
|
+
while (Date.now() < deadline) {
|
|
274
|
+
if (await stat(p.serverSock).then(() => true, () => false)) {
|
|
275
|
+
const probe = new ServerClient({ kind: "local", socketPath: p.serverSock });
|
|
276
|
+
try {
|
|
277
|
+
await probe.health();
|
|
278
|
+
return;
|
|
279
|
+
} catch {
|
|
280
|
+
} finally {
|
|
281
|
+
await probe.close();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
await sleep(100);
|
|
285
|
+
}
|
|
286
|
+
throw new KvboxError(`kvbox-server did not start within ${timeoutMs / 1e3}s; see ${p.serverLog}`);
|
|
287
|
+
})();
|
|
288
|
+
await Promise.race([ready, failed]);
|
|
289
|
+
child.removeAllListeners("exit");
|
|
290
|
+
child.unref();
|
|
291
|
+
return { pid: child.pid ?? -1, socketPath: p.serverSock };
|
|
292
|
+
}
|
|
442
293
|
|
|
443
|
-
// src/
|
|
294
|
+
// src/commands/server/start.ts
|
|
295
|
+
var startCommand = new Command6("start").description("start the local kvbox-server in the background").action(async (_opts, cmd) => {
|
|
296
|
+
const client = clientFor(cmd.optsWithGlobals());
|
|
297
|
+
try {
|
|
298
|
+
const health = await client.health();
|
|
299
|
+
info(`kvbox-server ${health.version} is already running (pid ${health.pid})`);
|
|
300
|
+
return;
|
|
301
|
+
} catch {
|
|
302
|
+
} finally {
|
|
303
|
+
await client.close();
|
|
304
|
+
}
|
|
305
|
+
const { pid, socketPath } = await startLocalServer(paths());
|
|
306
|
+
info(`kvbox-server started (pid ${pid}), listening on ${socketPath}`);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// src/commands/server/status.ts
|
|
310
|
+
import { Command as Command7 } from "commander";
|
|
444
311
|
var statusCommand = new Command7("status").description("show whether the server is running").option("--json", "print as JSON").action(async (opts, cmd) => {
|
|
445
312
|
const client = clientFor(cmd.optsWithGlobals());
|
|
446
313
|
try {
|
|
447
314
|
const health = await client.health();
|
|
448
315
|
if (opts.json) json(health);
|
|
449
|
-
else info(`kvbox
|
|
316
|
+
else info(`kvbox-server ${health.version} running, pid ${health.pid}`);
|
|
450
317
|
} finally {
|
|
451
318
|
await client.close();
|
|
452
319
|
}
|
|
453
320
|
});
|
|
454
321
|
|
|
455
|
-
// src/
|
|
456
|
-
import { readFile
|
|
322
|
+
// src/commands/server/stop.ts
|
|
323
|
+
import { readFile } from "fs/promises";
|
|
457
324
|
import { Command as Command8 } from "commander";
|
|
458
325
|
var stopCommand = new Command8("stop").description("stop the local server (running boxes are not affected)").action(async () => {
|
|
459
326
|
const p = paths();
|
|
460
327
|
let pid;
|
|
461
328
|
try {
|
|
462
|
-
pid = Number.parseInt(await
|
|
329
|
+
pid = Number.parseInt(await readFile(p.serverPid, "utf8"), 10);
|
|
463
330
|
} catch {
|
|
464
|
-
throw new KvboxError("kvbox
|
|
331
|
+
throw new KvboxError("kvbox-server is not running (no pid file)");
|
|
465
332
|
}
|
|
466
333
|
try {
|
|
467
334
|
process.kill(pid, "SIGTERM");
|
|
468
335
|
} catch {
|
|
469
|
-
throw new KvboxError(`kvbox
|
|
336
|
+
throw new KvboxError(`kvbox-server pid ${pid} is not running; remove ${p.serverPid}`);
|
|
470
337
|
}
|
|
471
|
-
info(`sent SIGTERM to kvbox
|
|
338
|
+
info(`sent SIGTERM to kvbox-server (pid ${pid})`);
|
|
472
339
|
});
|
|
473
340
|
|
|
474
|
-
// src/
|
|
341
|
+
// src/commands/server/index.ts
|
|
475
342
|
var serverCommand = new Command9("server").description("run or manage the kvbox server").addCommand(startCommand).addCommand(statusCommand).addCommand(stopCommand);
|
|
476
343
|
|
|
477
|
-
// src/
|
|
344
|
+
// src/commands/snapshot/index.ts
|
|
478
345
|
import { Command as Command13 } from "commander";
|
|
479
346
|
|
|
480
|
-
// src/
|
|
347
|
+
// src/commands/snapshot/ls.ts
|
|
481
348
|
import { Command as Command10 } from "commander";
|
|
482
349
|
var lsCommand2 = new Command10("ls").description("list snapshots").option("--json", "print as JSON").action(() => notImplemented("snapshot ls"));
|
|
483
350
|
|
|
484
|
-
// src/
|
|
351
|
+
// src/commands/snapshot/rm.ts
|
|
485
352
|
import { Command as Command11 } from "commander";
|
|
486
353
|
var rmCommand2 = new Command11("rm").description("delete a snapshot").argument("<name>", "snapshot name").option("-y, --yes", "do not ask for confirmation").action(() => notImplemented("snapshot rm"));
|
|
487
354
|
|
|
488
|
-
// src/
|
|
355
|
+
// src/commands/snapshot/save.ts
|
|
489
356
|
import { Command as Command12 } from "commander";
|
|
490
357
|
var saveCommand = new Command12("save").description("freeze a running box (memory + disk) into a snapshot").argument("<box>", "box name").argument("<name>", "snapshot name").option("--json", "print the snapshot as JSON").action(() => notImplemented("snapshot save"));
|
|
491
358
|
|
|
492
|
-
// src/
|
|
359
|
+
// src/commands/snapshot/index.ts
|
|
493
360
|
var snapshotCommand = new Command13("snapshot").alias("snap").description("manage snapshots").addCommand(saveCommand).addCommand(lsCommand2).addCommand(rmCommand2);
|
|
494
361
|
|
|
495
|
-
// src/
|
|
362
|
+
// src/program.ts
|
|
496
363
|
function buildProgram() {
|
|
497
364
|
const program = new Command14("kvbox").description("self-hosted Firecracker microVMs with Docker-like ergonomics").version(VERSION, "-v, --version").option("--host <name>", "target a configured remote host (M2)").showHelpAfterError().enablePositionalOptions();
|
|
498
365
|
program.addCommand(machineCommand);
|
package/dist/kvbox.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/output.ts","../src/cli/program.ts","../src/shared/version.ts","../src/cli/commands/machine/index.ts","../src/cli/commands/machine/connect.ts","../src/cli/commands/machine/ls.ts","../src/cli/commands/machine/new.ts","../src/shared/sizes.ts","../src/cli/commands/machine/rm.ts","../src/cli/commands/server/index.ts","../src/cli/commands/server/start.ts","../src/server/listen.ts","../src/core/state.ts","../src/shared/paths.ts","../src/core/config.ts","../src/shared/errors.ts","../src/server/app.ts","../src/server/routes/capacity.ts","../src/core/capacity.ts","../src/server/routes/machines.ts","../src/shared/api.ts","../src/core/machines.ts","../src/server/routes/snapshots.ts","../src/core/snapshots.ts","../src/cli/commands/server/status.ts","../src/cli/client.ts","../src/cli/commands/server/stop.ts","../src/cli/commands/snapshot/index.ts","../src/cli/commands/snapshot/ls.ts","../src/cli/commands/snapshot/rm.ts","../src/cli/commands/snapshot/save.ts","../src/index.ts"],"sourcesContent":["/** Printing: tables for humans, `--json` for agents, colors only on a TTY. */\nimport pc from \"picocolors\";\n\nexport const color = pc.createColors(process.stdout.isTTY === true && !process.env[\"NO_COLOR\"]);\n\nexport function json(value: unknown): void {\n process.stdout.write(JSON.stringify(value, null, 2) + \"\\n\");\n}\n\nexport function table(rows: Record<string, string | number | null | undefined>[], columns?: string[]): void {\n if (rows.length === 0) return;\n const cols = columns ?? Object.keys(rows[0]!);\n const cells = rows.map((r) => cols.map((c) => String(r[c] ?? \"\")));\n const widths = cols.map((c, i) => Math.max(c.length, ...cells.map((r) => r[i]!.length)));\n const line = (parts: string[]) => parts.map((p, i) => p.padEnd(widths[i]!)).join(\" \").trimEnd();\n process.stdout.write(color.dim(line(cols.map((c) => c.toUpperCase()))) + \"\\n\");\n for (const row of cells) process.stdout.write(line(row) + \"\\n\");\n}\n\nexport function info(msg: string): void {\n process.stderr.write(msg + \"\\n\");\n}\n\nexport function warn(msg: string): void {\n process.stderr.write(color.yellow(msg) + \"\\n\");\n}\n\nexport function notImplemented(command: string): never {\n process.stderr.write(color.yellow(`kvbox ${command}: not implemented yet\\n`));\n process.exit(2);\n}\n\n/** y/N prompt on a TTY; requires `-y` otherwise (boxd's rule for destructive commands). */\nexport async function confirm(question: string, yes: boolean): Promise<boolean> {\n if (yes) return true;\n if (!process.stdin.isTTY) {\n warn(\"refusing without -y when stdin is not a terminal\");\n return false;\n }\n const { createInterface } = await import(\"node:readline/promises\");\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n try {\n const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();\n return answer === \"y\" || answer === \"yes\";\n } finally {\n rl.close();\n }\n}\n","/** Builds the commander tree. Importable without side effects (tests); src/index.ts runs it. */\nimport { Command } from \"commander\";\nimport { VERSION } from \"../shared/version.js\";\nimport { machineAliases, machineCommand } from \"./commands/machine/index.js\";\nimport { serverCommand } from \"./commands/server/index.js\";\nimport { snapshotCommand } from \"./commands/snapshot/index.js\";\n\nexport function buildProgram(): Command {\n const program = new Command(\"kvbox\")\n .description(\"self-hosted Firecracker microVMs with Docker-like ergonomics\")\n .version(VERSION, \"-v, --version\")\n .option(\"--host <name>\", \"target a configured remote host (M2)\")\n .showHelpAfterError()\n .enablePositionalOptions();\n\n program.addCommand(machineCommand);\n program.addCommand(snapshotCommand);\n program.addCommand(serverCommand);\n // Top-level shortcuts: the same Command objects as under `machine`.\n for (const cmd of machineAliases) program.addCommand(cmd);\n\n return program;\n}\n","/**\n * The version comes from package.json: tsup injects it at build time, and the\n * dev path (tsx) reads the file. `pnpm version` is the only place it changes.\n */\nimport { createRequire } from \"node:module\";\n\ndeclare const __KVBOX_VERSION__: string | undefined;\n\nfunction fromPackageJson(): string {\n try {\n const require = createRequire(import.meta.url);\n return (require(\"../../package.json\") as { version: string }).version;\n } catch {\n return \"0.0.0-dev\";\n }\n}\n\nexport const VERSION: string = typeof __KVBOX_VERSION__ === \"string\" ? __KVBOX_VERSION__ : fromPackageJson();\n","import { Command } from \"commander\";\nimport { connectCommand } from \"./connect.js\";\nimport { lsCommand } from \"./ls.js\";\nimport { newCommand } from \"./new.js\";\nimport { rmCommand } from \"./rm.js\";\n\nexport const machineCommand = new Command(\"machine\")\n .alias(\"m\")\n .description(\"manage boxes\")\n .addCommand(newCommand)\n .addCommand(lsCommand)\n .addCommand(connectCommand)\n .addCommand(rmCommand);\n\n/** The same Command objects, added again at the top level so they cannot drift. */\nexport const machineAliases = [newCommand, lsCommand, connectCommand, rmCommand];\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox machine connect <name> — exec ssh kvbox@<ip> with the kvbox key */\nexport const connectCommand = new Command(\"connect\")\n .description(\"open an interactive shell in a box over SSH\")\n .argument(\"<name>\", \"box name\")\n .action(() => notImplemented(\"machine connect\"));\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox machine ls [--json] — --json output: Machine[] */\nexport const lsCommand = new Command(\"ls\")\n .description(\"list boxes with status and IP\")\n .option(\"--json\", \"print as JSON\")\n .action(() => notImplemented(\"machine ls\"));\n","import { Command, Option } from \"commander\";\nimport { DEFAULT_SIZE, SIZE_NAMES } from \"../../../shared/sizes.js\";\nimport { notImplemented } from \"../../output.js\";\n\n/**\n * kvbox machine new [name] [--size s|m|l] [--from <snapshot>] [--force] [--json]\n * --json output: the Machine object (see src/shared/api.ts).\n */\nexport const newCommand = new Command(\"new\")\n .description(\"boot a new box from the base image or a snapshot\")\n .argument(\"[name]\", \"box name (generated when omitted)\")\n .addOption(new Option(\"-s, --size <size>\", \"box size\").choices(SIZE_NAMES).default(DEFAULT_SIZE))\n .option(\"-f, --from <snapshot>\", \"start from a snapshot instead of the base image\")\n .option(\"--force\", \"skip the capacity guard\")\n .option(\"--json\", \"print the box as JSON\")\n .action(() => notImplemented(\"machine new\"));\n","/** Fixed box sizes. Firecracker backs guest memory lazily, so a box only costs what the guest touches. */\nexport const SIZES = {\n s: { vcpu: 2, memMib: 4096 },\n m: { vcpu: 4, memMib: 8192 },\n l: { vcpu: 8, memMib: 16384 },\n} as const;\n\nexport type SizeName = keyof typeof SIZES;\nexport const SIZE_NAMES = Object.keys(SIZES) as SizeName[];\nexport const DEFAULT_SIZE: SizeName = \"s\";\n\nexport function isSizeName(value: string): value is SizeName {\n return value in SIZES;\n}\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox machine rm <name> [-y] — destructive: asks y/N on a TTY, needs -y otherwise */\nexport const rmCommand = new Command(\"rm\")\n .description(\"destroy a box\")\n .argument(\"<name>\", \"box name\")\n .option(\"-y, --yes\", \"do not ask for confirmation\")\n .action(() => notImplemented(\"machine rm\"));\n","import { Command } from \"commander\";\nimport { startCommand } from \"./start.js\";\nimport { statusCommand } from \"./status.js\";\nimport { stopCommand } from \"./stop.js\";\n\nexport const serverCommand = new Command(\"server\")\n .description(\"run or manage the kvbox server\")\n .addCommand(startCommand)\n .addCommand(statusCommand)\n .addCommand(stopCommand);\n","/**\n * kvbox server start [--foreground]\n * The daemon mode of the executable and the ONLY command that imports src/server.\n */\nimport { Command } from \"commander\";\nimport { listen } from \"../../../server/listen.js\";\nimport { info } from \"../../output.js\";\n\nexport const startCommand = new Command(\"start\")\n .description(\"run the kvbox server (owns Firecracker and ~/.kvbox)\")\n .option(\"--foreground\", \"stay attached to the terminal (default for now)\")\n .action(async () => {\n const { socketPath } = await listen();\n info(`kvbox server listening on ${socketPath} (pid ${process.pid})`);\n });\n","/** Bind the app to the local Unix socket and write the pid file. */\nimport { rm, writeFile } from \"node:fs/promises\";\nimport { createAdaptorServer } from \"@hono/node-server\";\nimport { ensureLayout } from \"../core/state.js\";\nimport { loadConfig } from \"../core/config.js\";\nimport { KvboxError } from \"../shared/errors.js\";\nimport { paths } from \"../shared/paths.js\";\nimport { createApp } from \"./app.js\";\n\nexport interface ListenOptions {\n socketPath?: string;\n}\n\nexport async function listen(opts: ListenOptions = {}) {\n const p = await ensureLayout(paths());\n await loadConfig(p);\n const socketPath = opts.socketPath ?? p.serverSock;\n // Linux limits a Unix socket path (sun_path) to 108 bytes; bind() fails with EINVAL past it.\n if (Buffer.byteLength(socketPath) > 100) {\n throw new KvboxError(`socket path too long for a Unix socket (${socketPath}); set KVBOX_HOME to a shorter path`);\n }\n\n // A stale socket file from a crashed server blocks bind(); the pid file tells\n // `server status` whether anything is actually alive.\n await rm(socketPath, { force: true });\n\n const server = createAdaptorServer({ fetch: createApp().fetch });\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(socketPath, () => resolve());\n });\n await writeFile(p.serverPid, `${process.pid}\\n`);\n\n const shutdown = async () => {\n server.close();\n await rm(socketPath, { force: true });\n await rm(p.serverPid, { force: true });\n process.exit(0);\n };\n process.once(\"SIGINT\", () => void shutdown());\n process.once(\"SIGTERM\", () => void shutdown());\n\n return { socketPath, server };\n}\n","/**\n * The `~/.kvbox` layout and the per-box / per-snapshot `meta.json` files.\n * The server is the only writer; every read is validated with zod.\n */\nimport { mkdir, readFile, readdir, writeFile } from \"node:fs/promises\";\nimport { z } from \"zod\";\nimport { paths, type Paths } from \"../shared/paths.js\";\n\nexport const MachineMetaSchema = z.object({\n name: z.string(),\n ip: z.string(),\n mac: z.string(),\n pid: z.number().int(),\n vcpu: z.number().int(),\n memMib: z.number().int(),\n createdAt: z.string(),\n source: z.string(),\n});\nexport type MachineMeta = z.infer<typeof MachineMetaSchema>;\n\nexport const SnapshotMetaSchema = z.object({\n name: z.string(),\n source: z.string(),\n vcpu: z.number().int(),\n memMib: z.number().int(),\n createdAt: z.string(),\n});\nexport type SnapshotMeta = z.infer<typeof SnapshotMetaSchema>;\n\n/** Create every directory of the layout. Idempotent; called by `kvbox server start`. */\nexport async function ensureLayout(p: Paths = paths()): Promise<Paths> {\n for (const dir of [p.home, p.ssh, p.bin, p.assets, p.machines, p.snapshots]) {\n await mkdir(dir, { recursive: true, mode: 0o700 });\n }\n return p;\n}\n\nasync function readJson<T>(file: string, schema: z.ZodType<T>): Promise<T> {\n return schema.parse(JSON.parse(await readFile(file, \"utf8\")));\n}\n\nasync function listDirs(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isDirectory()).map((e) => e.name);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw err;\n }\n}\n\nexport async function readMachineMeta(name: string, p: Paths = paths()) {\n return readJson(p.machine(name).meta, MachineMetaSchema);\n}\n\nexport async function writeMachineMeta(meta: MachineMeta, p: Paths = paths()) {\n await mkdir(p.machine(meta.name).dir, { recursive: true });\n await writeFile(p.machine(meta.name).meta, JSON.stringify(meta, null, 2) + \"\\n\");\n}\n\nexport async function listMachineNames(p: Paths = paths()) {\n return listDirs(p.machines);\n}\n\nexport async function readSnapshotMeta(name: string, p: Paths = paths()) {\n return readJson(p.snapshot(name).meta, SnapshotMetaSchema);\n}\n\nexport async function writeSnapshotMeta(meta: SnapshotMeta, p: Paths = paths()) {\n await mkdir(p.snapshot(meta.name).dir, { recursive: true });\n await writeFile(p.snapshot(meta.name).meta, JSON.stringify(meta, null, 2) + \"\\n\");\n}\n\nexport async function listSnapshotNames(p: Paths = paths()) {\n return listDirs(p.snapshots);\n}\n\n/** True when a pid is alive (signal 0). */\nexport function pidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n","/**\n * Host state layout. The filesystem is the database and `~/.kvbox` is its root.\n * This module is dependency-free so both the CLI (socket path, hosts.json)\n * and core (everything else) can use it without crossing the layer boundary.\n *\n * `KVBOX_HOME` overrides the root, mainly for tests.\n */\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport function kvboxHome(): string {\n return process.env[\"KVBOX_HOME\"] ?? join(homedir(), \".kvbox\");\n}\n\nexport function paths(home: string = kvboxHome()) {\n return {\n home,\n config: join(home, \"config.json\"),\n serverSock: join(home, \"server.sock\"),\n serverPid: join(home, \"server.pid\"),\n hosts: join(home, \"hosts.json\"),\n ssh: join(home, \"ssh\"),\n sshKey: join(home, \"ssh\", \"id_ed25519\"),\n bin: join(home, \"bin\"),\n firecracker: join(home, \"bin\", \"firecracker\"),\n assets: join(home, \"assets\"),\n kernel: join(home, \"assets\", \"vmlinux\"),\n rootfs: join(home, \"assets\", \"rootfs.ext4\"),\n machines: join(home, \"machines\"),\n snapshots: join(home, \"snapshots\"),\n machine: (name: string) => ({\n dir: join(home, \"machines\", name),\n meta: join(home, \"machines\", name, \"meta.json\"),\n disk: join(home, \"machines\", name, \"disk.ext4\"),\n sock: join(home, \"machines\", name, \"fc.sock\"),\n log: join(home, \"machines\", name, \"fc.log\"),\n }),\n snapshot: (name: string) => ({\n dir: join(home, \"snapshots\", name),\n meta: join(home, \"snapshots\", name, \"meta.json\"),\n vmstate: join(home, \"snapshots\", name, \"vmstate\"),\n mem: join(home, \"snapshots\", name, \"mem\"),\n disk: join(home, \"snapshots\", name, \"disk.ext4\"),\n }),\n } as const;\n}\n\nexport type Paths = ReturnType<typeof paths>;\n","/** `~/.kvbox/config.json`: asset paths, subnet, default size. Written with defaults on first run. */\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { z } from \"zod\";\nimport { paths, type Paths } from \"../shared/paths.js\";\nimport { DEFAULT_SIZE, SIZE_NAMES } from \"../shared/sizes.js\";\n\nexport const ConfigSchema = z.object({\n kernel: z.string(),\n rootfs: z.string(),\n firecracker: z.string(),\n bridge: z.string().default(\"zbr0\"),\n subnet: z.string().default(\"172.30.0.0/24\"),\n gateway: z.string().default(\"172.30.0.1\"),\n defaultSize: z.enum(SIZE_NAMES as [string, ...string[]]).default(DEFAULT_SIZE),\n});\nexport type Config = z.infer<typeof ConfigSchema>;\n\nexport function defaultConfig(p: Paths = paths()): Config {\n return ConfigSchema.parse({\n kernel: p.kernel,\n rootfs: p.rootfs,\n firecracker: p.firecracker,\n });\n}\n\nexport async function loadConfig(p: Paths = paths()): Promise<Config> {\n try {\n const raw = await readFile(p.config, \"utf8\");\n return ConfigSchema.parse(JSON.parse(raw));\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n const cfg = defaultConfig(p);\n await writeFile(p.config, JSON.stringify(cfg, null, 2) + \"\\n\");\n return cfg;\n }\n throw err;\n }\n}\n","/** Errors with an exit code; the entry point prints `message` and exits with `exitCode`. */\nexport class KvboxError extends Error {\n constructor(\n message: string,\n public readonly exitCode: number = 1,\n ) {\n super(message);\n this.name = \"KvboxError\";\n }\n}\n\nexport class NotImplementedError extends KvboxError {\n constructor(what: string) {\n super(`${what}: not implemented yet`, 2);\n this.name = \"NotImplementedError\";\n }\n}\n","/**\n * The hono app behind `kvbox server start`. Served over `~/.kvbox/server.sock`\n * in M1; the same app goes behind HTTPS in M2.\n */\nimport { Hono } from \"hono\";\nimport { KvboxError } from \"../shared/errors.js\";\nimport { VERSION } from \"../shared/version.js\";\nimport { capacityRoutes } from \"./routes/capacity.js\";\nimport { machineRoutes } from \"./routes/machines.js\";\nimport { snapshotRoutes } from \"./routes/snapshots.js\";\n\nexport function createApp() {\n const app = new Hono();\n\n app.get(\"/health\", (c) => c.json({ ok: true as const, version: VERSION, pid: process.pid }));\n app.route(\"/machines\", machineRoutes());\n app.route(\"/snapshots\", snapshotRoutes());\n app.route(\"/capacity\", capacityRoutes());\n\n app.notFound((c) => c.json({ error: `no route: ${c.req.method} ${c.req.path}` }, 404));\n app.onError((err, c) => {\n if (err instanceof KvboxError) {\n return c.json({ error: err.message }, err.exitCode === 2 ? 501 : 400);\n }\n console.error(err);\n return c.json({ error: err.message }, 500);\n });\n\n return app;\n}\n\nexport type App = ReturnType<typeof createApp>;\n","import { Hono } from \"hono\";\nimport { capacity } from \"../../core/capacity.js\";\n\nexport function capacityRoutes() {\n const r = new Hono();\n r.get(\"/\", async (c) => c.json(await capacity()));\n return r;\n}\n","/**\n * The capacity guard: `kvbox new` refuses when reserved RAM of running boxes\n * plus the new one exceeds host RAM minus 2 GB (hard), or reserved vCPUs exceed\n * 2x host cores (soft). `--force` skips it.\n */\nimport { NotImplementedError } from \"../shared/errors.js\";\nimport type { Capacity } from \"../shared/api.js\";\n\nexport const MEM_HEADROOM_MIB = 2048;\nexport const VCPU_OVERSUBSCRIPTION = 2;\n\nexport async function capacity(): Promise<Capacity> {\n throw new NotImplementedError(\"capacity.capacity\");\n}\n\nexport async function assertRoomFor(_vcpu: number, _memMib: number, _force: boolean): Promise<void> {\n throw new NotImplementedError(\"capacity.assertRoomFor\");\n}\n","import { Hono } from \"hono\";\nimport { CreateMachineRequest } from \"../../shared/api.js\";\nimport { createMachine, destroyMachine, listMachines } from \"../../core/machines.js\";\n\nexport function machineRoutes() {\n const r = new Hono();\n r.get(\"/\", async (c) => c.json(await listMachines()));\n r.post(\"/\", async (c) => {\n const body = CreateMachineRequest.parse(await c.req.json().catch(() => ({})));\n return c.json(await createMachine(body), 201);\n });\n r.delete(\"/:name\", async (c) => {\n await destroyMachine(c.req.param(\"name\"));\n return c.body(null, 204);\n });\n return r;\n}\n","/**\n * Wire shapes of the kvbox HTTP API, shared by the client (src/cli) and the\n * server (src/server). Validated with zod on both sides because the client\n * may be talking to an older or newer server.\n */\nimport { z } from \"zod\";\nimport { SIZE_NAMES } from \"./sizes.js\";\n\nexport const SizeSchema = z.enum(SIZE_NAMES as [string, ...string[]]);\n\nexport const MachineSchema = z.object({\n name: z.string(),\n ip: z.string(),\n mac: z.string(),\n pid: z.number().int().nullable(),\n vcpu: z.number().int(),\n memMib: z.number().int(),\n createdAt: z.string(),\n source: z.string(), // \"base\" or \"snapshot:<name>\"\n status: z.enum([\"running\", \"stopped\"]),\n});\nexport type Machine = z.infer<typeof MachineSchema>;\n\nexport const CreateMachineRequest = z.object({\n name: z.string().optional(),\n size: SizeSchema.optional(),\n from: z.string().optional(),\n force: z.boolean().optional(),\n});\nexport type CreateMachineRequest = z.infer<typeof CreateMachineRequest>;\n\nexport const SnapshotSchema = z.object({\n name: z.string(),\n source: z.string(),\n vcpu: z.number().int(),\n memMib: z.number().int(),\n createdAt: z.string(),\n});\nexport type Snapshot = z.infer<typeof SnapshotSchema>;\n\nexport const CreateSnapshotRequest = z.object({\n box: z.string(),\n name: z.string(),\n});\nexport type CreateSnapshotRequest = z.infer<typeof CreateSnapshotRequest>;\n\nexport const CapacitySchema = z.object({\n hostMemMib: z.number().int(),\n hostCores: z.number().int(),\n reservedMemMib: z.number().int(),\n reservedVcpu: z.number().int(),\n running: z.number().int(),\n});\nexport type Capacity = z.infer<typeof CapacitySchema>;\n\nexport const HealthSchema = z.object({\n ok: z.literal(true),\n version: z.string(),\n pid: z.number().int(),\n});\nexport type Health = z.infer<typeof HealthSchema>;\n\nexport const ApiErrorSchema = z.object({\n error: z.string(),\n});\n","/** Create, list and destroy boxes. See architecture \"Process model\". */\nimport { NotImplementedError } from \"../shared/errors.js\";\nimport type { CreateMachineRequest, Machine } from \"../shared/api.js\";\n\nexport async function listMachines(): Promise<Machine[]> {\n throw new NotImplementedError(\"machines.listMachines\");\n}\n\nexport async function createMachine(_req: CreateMachineRequest): Promise<Machine> {\n throw new NotImplementedError(\"machines.createMachine\");\n}\n\nexport async function destroyMachine(_name: string): Promise<void> {\n throw new NotImplementedError(\"machines.destroyMachine\");\n}\n","import { Hono } from \"hono\";\nimport { CreateSnapshotRequest } from \"../../shared/api.js\";\nimport { deleteSnapshot, listSnapshots, saveSnapshot } from \"../../core/snapshots.js\";\n\nexport function snapshotRoutes() {\n const r = new Hono();\n r.get(\"/\", async (c) => c.json(await listSnapshots()));\n r.post(\"/\", async (c) => {\n const body = CreateSnapshotRequest.parse(await c.req.json());\n return c.json(await saveSnapshot(body), 201);\n });\n r.delete(\"/:name\", async (c) => {\n await deleteSnapshot(c.req.param(\"name\"));\n return c.body(null, 204);\n });\n return r;\n}\n","/** Full snapshots (vmstate + mem + disk copy) and their listing. */\nimport { NotImplementedError } from \"../shared/errors.js\";\nimport type { CreateSnapshotRequest, Snapshot } from \"../shared/api.js\";\n\nexport async function listSnapshots(): Promise<Snapshot[]> {\n throw new NotImplementedError(\"snapshots.listSnapshots\");\n}\n\nexport async function saveSnapshot(_req: CreateSnapshotRequest): Promise<Snapshot> {\n throw new NotImplementedError(\"snapshots.saveSnapshot\");\n}\n\nexport async function deleteSnapshot(_name: string): Promise<void> {\n throw new NotImplementedError(\"snapshots.deleteSnapshot\");\n}\n","import { Command } from \"commander\";\nimport { clientFor } from \"../../client.js\";\nimport { info, json } from \"../../output.js\";\n\n/** kvbox server status [--json] — --json output: Health */\nexport const statusCommand = new Command(\"status\")\n .description(\"show whether the server is running\")\n .option(\"--json\", \"print as JSON\")\n .action(async (opts: { json?: boolean }, cmd: Command) => {\n const client = clientFor(cmd.optsWithGlobals());\n try {\n const health = await client.health();\n if (opts.json) json(health);\n else info(`kvbox server ${health.version} running, pid ${health.pid}`);\n } finally {\n await client.close();\n }\n });\n","/**\n * ServerClient: one method per route of the kvbox API, over undici.\n * Target resolution: `--host` -> configured hosts (M2) -> local Unix socket.\n * Auto-starting the local server when nothing listens is #17.\n */\nimport { Agent, request, type Dispatcher } from \"undici\";\nimport { z } from \"zod\";\nimport {\n ApiErrorSchema,\n CapacitySchema,\n HealthSchema,\n MachineSchema,\n SnapshotSchema,\n type CreateMachineRequest,\n type CreateSnapshotRequest,\n} from \"../shared/api.js\";\nimport { KvboxError } from \"../shared/errors.js\";\nimport { paths } from \"../shared/paths.js\";\n\nexport type Target = { kind: \"local\"; socketPath: string } | { kind: \"remote\"; name: string; url: string };\n\nexport function resolveTarget(hostFlag?: string): Target {\n if (hostFlag) {\n throw new KvboxError(`--host ${hostFlag}: remote hosts arrive in M2`);\n }\n return { kind: \"local\", socketPath: paths().serverSock };\n}\n\nexport class ServerClient {\n private readonly dispatcher: Dispatcher;\n private readonly base: string;\n\n constructor(readonly target: Target) {\n if (target.kind === \"local\") {\n this.dispatcher = new Agent({ connect: { socketPath: target.socketPath } });\n this.base = \"http://kvbox\";\n } else {\n this.dispatcher = new Agent();\n this.base = target.url;\n }\n }\n\n private async call<T>(\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n schema: z.ZodType<T>,\n body?: unknown,\n ): Promise<T> {\n let res;\n try {\n res = await request(this.base + path, {\n dispatcher: this.dispatcher,\n method,\n headers: body === undefined ? {} : { \"content-type\": \"application/json\" },\n body: body === undefined ? null : JSON.stringify(body),\n });\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code ?? (err as Error).cause;\n if (this.target.kind === \"local\") {\n throw new KvboxError(`kvbox server is not running (${String(code)}); start it with: kvbox server start`);\n }\n throw new KvboxError(`cannot reach ${this.target.name}: ${String(code)}`);\n }\n const text = await res.body.text();\n if (res.statusCode >= 300) {\n const parsed = ApiErrorSchema.safeParse(text ? JSON.parse(text) : {});\n const msg = parsed.success ? parsed.data.error : `${res.statusCode} ${text}`;\n throw new KvboxError(msg, res.statusCode === 501 ? 2 : 1);\n }\n return schema.parse(text ? JSON.parse(text) : undefined);\n }\n\n health() {\n return this.call(\"GET\", \"/health\", HealthSchema);\n }\n\n capacity() {\n return this.call(\"GET\", \"/capacity\", CapacitySchema);\n }\n\n listMachines() {\n return this.call(\"GET\", \"/machines\", z.array(MachineSchema));\n }\n\n createMachine(req: CreateMachineRequest) {\n return this.call(\"POST\", \"/machines\", MachineSchema, req);\n }\n\n deleteMachine(name: string) {\n return this.call(\"DELETE\", `/machines/${encodeURIComponent(name)}`, z.undefined());\n }\n\n listSnapshots() {\n return this.call(\"GET\", \"/snapshots\", z.array(SnapshotSchema));\n }\n\n createSnapshot(req: CreateSnapshotRequest) {\n return this.call(\"POST\", \"/snapshots\", SnapshotSchema, req);\n }\n\n deleteSnapshot(name: string) {\n return this.call(\"DELETE\", `/snapshots/${encodeURIComponent(name)}`, z.undefined());\n }\n\n async close() {\n await this.dispatcher.close();\n }\n}\n\n/** Build a client from the global `--host` option of the root command. */\nexport function clientFor(opts: { host?: string }): ServerClient {\n return new ServerClient(resolveTarget(opts.host));\n}\n","import { readFile } from \"node:fs/promises\";\nimport { Command } from \"commander\";\nimport { KvboxError } from \"../../../shared/errors.js\";\nimport { paths } from \"../../../shared/paths.js\";\nimport { info } from \"../../output.js\";\n\n/** kvbox server stop — SIGTERM the pid in server.pid. Boxes keep running. */\nexport const stopCommand = new Command(\"stop\")\n .description(\"stop the local server (running boxes are not affected)\")\n .action(async () => {\n const p = paths();\n let pid: number;\n try {\n pid = Number.parseInt(await readFile(p.serverPid, \"utf8\"), 10);\n } catch {\n throw new KvboxError(\"kvbox server is not running (no pid file)\");\n }\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n throw new KvboxError(`kvbox server pid ${pid} is not running; remove ${p.serverPid}`);\n }\n info(`sent SIGTERM to kvbox server (pid ${pid})`);\n });\n","import { Command } from \"commander\";\nimport { lsCommand } from \"./ls.js\";\nimport { rmCommand } from \"./rm.js\";\nimport { saveCommand } from \"./save.js\";\n\nexport const snapshotCommand = new Command(\"snapshot\")\n .alias(\"snap\")\n .description(\"manage snapshots\")\n .addCommand(saveCommand)\n .addCommand(lsCommand)\n .addCommand(rmCommand);\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox snapshot ls [--json] — --json output: Snapshot[] */\nexport const lsCommand = new Command(\"ls\")\n .description(\"list snapshots\")\n .option(\"--json\", \"print as JSON\")\n .action(() => notImplemented(\"snapshot ls\"));\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox snapshot rm <name> [-y] — destructive: asks y/N on a TTY, needs -y otherwise */\nexport const rmCommand = new Command(\"rm\")\n .description(\"delete a snapshot\")\n .argument(\"<name>\", \"snapshot name\")\n .option(\"-y, --yes\", \"do not ask for confirmation\")\n .action(() => notImplemented(\"snapshot rm\"));\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox snapshot save <box> <name> [--json] — --json output: Snapshot */\nexport const saveCommand = new Command(\"save\")\n .description(\"freeze a running box (memory + disk) into a snapshot\")\n .argument(\"<box>\", \"box name\")\n .argument(\"<name>\", \"snapshot name\")\n .option(\"--json\", \"print the snapshot as JSON\")\n .action(() => notImplemented(\"snapshot save\"));\n","/** Entry point: parse argv, map KvboxError to an exit code. */\nimport { color } from \"./cli/output.js\";\nimport { buildProgram } from \"./cli/program.js\";\nimport { KvboxError } from \"./shared/errors.js\";\n\ntry {\n await buildProgram().parseAsync(process.argv);\n} catch (err) {\n if (err instanceof KvboxError) {\n process.stderr.write(color.red(`error: ${err.message}`) + \"\\n\");\n process.exit(err.exitCode);\n }\n throw err;\n}\n"],"mappings":"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","names":["Command","Command","Command","Command","Command","Command","Command","Command","Command","Command","Command","writeFile","readFile","writeFile","z","z","readFile","writeFile","Hono","Hono","z","z","Hono","Hono","Hono","Hono","writeFile","Command","Command","z","z","Command","readFile","Command","Command","readFile","Command","Command","Command","lsCommand","Command","Command","rmCommand","Command","Command","Command","Command","lsCommand","rmCommand","Command"]}
|
|
1
|
+
{"version":3,"sources":["../src/output.ts","../src/program.ts","../../shared/src/version.ts","../src/version.ts","../src/commands/machine/index.ts","../src/commands/machine/connect.ts","../src/commands/machine/ls.ts","../src/commands/machine/new.ts","../../shared/src/sizes.ts","../src/commands/machine/rm.ts","../src/commands/server/index.ts","../src/commands/server/start.ts","../../shared/src/paths.ts","../src/client.ts","../../shared/src/api.ts","../../shared/src/errors.ts","../src/commands/server/status.ts","../src/commands/server/stop.ts","../src/commands/snapshot/index.ts","../src/commands/snapshot/ls.ts","../src/commands/snapshot/rm.ts","../src/commands/snapshot/save.ts","../src/index.ts"],"sourcesContent":["/** Printing: tables for humans, `--json` for agents, colors only on a TTY. */\nimport pc from \"picocolors\";\n\nexport const color = pc.createColors(process.stdout.isTTY === true && !process.env[\"NO_COLOR\"]);\n\nexport function json(value: unknown): void {\n process.stdout.write(JSON.stringify(value, null, 2) + \"\\n\");\n}\n\nexport function table(rows: Record<string, string | number | null | undefined>[], columns?: string[]): void {\n if (rows.length === 0) return;\n const cols = columns ?? Object.keys(rows[0]!);\n const cells = rows.map((r) => cols.map((c) => String(r[c] ?? \"\")));\n const widths = cols.map((c, i) => Math.max(c.length, ...cells.map((r) => r[i]!.length)));\n const line = (parts: string[]) => parts.map((p, i) => p.padEnd(widths[i]!)).join(\" \").trimEnd();\n process.stdout.write(color.dim(line(cols.map((c) => c.toUpperCase()))) + \"\\n\");\n for (const row of cells) process.stdout.write(line(row) + \"\\n\");\n}\n\nexport function info(msg: string): void {\n process.stderr.write(msg + \"\\n\");\n}\n\nexport function warn(msg: string): void {\n process.stderr.write(color.yellow(msg) + \"\\n\");\n}\n\nexport function notImplemented(command: string): never {\n process.stderr.write(color.yellow(`kvbox ${command}: not implemented yet\\n`));\n process.exit(2);\n}\n\n/** y/N prompt on a TTY; requires `-y` otherwise (boxd's rule for destructive commands). */\nexport async function confirm(question: string, yes: boolean): Promise<boolean> {\n if (yes) return true;\n if (!process.stdin.isTTY) {\n warn(\"refusing without -y when stdin is not a terminal\");\n return false;\n }\n const { createInterface } = await import(\"node:readline/promises\");\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n try {\n const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();\n return answer === \"y\" || answer === \"yes\";\n } finally {\n rl.close();\n }\n}\n","/** Builds the commander tree. Importable without side effects (tests); src/index.ts runs it. */\nimport { Command } from \"commander\";\nimport { VERSION } from \"./version.js\";\nimport { machineAliases, machineCommand } from \"./commands/machine/index.js\";\nimport { serverCommand } from \"./commands/server/index.js\";\nimport { snapshotCommand } from \"./commands/snapshot/index.js\";\n\nexport function buildProgram(): Command {\n const program = new Command(\"kvbox\")\n .description(\"self-hosted Firecracker microVMs with Docker-like ergonomics\")\n .version(VERSION, \"-v, --version\")\n .option(\"--host <name>\", \"target a configured remote host (M2)\")\n .showHelpAfterError()\n .enablePositionalOptions();\n\n program.addCommand(machineCommand);\n program.addCommand(snapshotCommand);\n program.addCommand(serverCommand);\n // Top-level shortcuts: the same Command objects as under `machine`.\n for (const cmd of machineAliases) program.addCommand(cmd);\n\n return program;\n}\n","/**\n * Each binary's version comes from its own package.json: tsup injects\n * `__KVBOX_VERSION__` at build time; the dev path (tsx) reads the file.\n * Usage in a package: `versionOf(import.meta.url)` from its `src/version.ts`.\n */\nimport { createRequire } from \"node:module\";\n\nexport function versionOf(srcFileUrl: string, injected?: string): string {\n if (typeof injected === \"string\") return injected;\n try {\n const require = createRequire(srcFileUrl);\n return (require(\"../package.json\") as { version: string }).version;\n } catch {\n return \"0.0.0-dev\";\n }\n}\n","import { versionOf } from \"@kvbox/shared/version\";\n\ndeclare const __KVBOX_VERSION__: string | undefined;\n\nexport const VERSION: string = versionOf(import.meta.url, typeof __KVBOX_VERSION__ === \"string\" ? __KVBOX_VERSION__ : undefined);\n","import { Command } from \"commander\";\nimport { connectCommand } from \"./connect.js\";\nimport { lsCommand } from \"./ls.js\";\nimport { newCommand } from \"./new.js\";\nimport { rmCommand } from \"./rm.js\";\n\nexport const machineCommand = new Command(\"machine\")\n .alias(\"m\")\n .description(\"manage boxes\")\n .addCommand(newCommand)\n .addCommand(lsCommand)\n .addCommand(connectCommand)\n .addCommand(rmCommand);\n\n/** The same Command objects, added again at the top level so they cannot drift. */\nexport const machineAliases = [newCommand, lsCommand, connectCommand, rmCommand];\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox machine connect <name> — exec ssh kvbox@<ip> with the kvbox key */\nexport const connectCommand = new Command(\"connect\")\n .description(\"open an interactive shell in a box over SSH\")\n .argument(\"<name>\", \"box name\")\n .action(() => notImplemented(\"machine connect\"));\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox machine ls [--json] — --json output: Machine[] */\nexport const lsCommand = new Command(\"ls\")\n .description(\"list boxes with status and IP\")\n .option(\"--json\", \"print as JSON\")\n .action(() => notImplemented(\"machine ls\"));\n","import { Command, Option } from \"commander\";\nimport { DEFAULT_SIZE, SIZE_NAMES } from \"@kvbox/shared/sizes\";\nimport { notImplemented } from \"../../output.js\";\n\n/**\n * kvbox machine new [name] [--size s|m|l] [--from <snapshot>] [--force] [--json]\n * --json output: the Machine object (see packages/shared/src/api.ts).\n */\nexport const newCommand = new Command(\"new\")\n .description(\"boot a new box from the base image or a snapshot\")\n .argument(\"[name]\", \"box name (generated when omitted)\")\n .addOption(new Option(\"-s, --size <size>\", \"box size\").choices(SIZE_NAMES).default(DEFAULT_SIZE))\n .option(\"-f, --from <snapshot>\", \"start from a snapshot instead of the base image\")\n .option(\"--force\", \"skip the capacity guard\")\n .option(\"--json\", \"print the box as JSON\")\n .action(() => notImplemented(\"machine new\"));\n","/** Fixed box sizes. Firecracker backs guest memory lazily, so a box only costs what the guest touches. */\nexport const SIZES = {\n s: { vcpu: 2, memMib: 4096 },\n m: { vcpu: 4, memMib: 8192 },\n l: { vcpu: 8, memMib: 16384 },\n} as const;\n\nexport type SizeName = keyof typeof SIZES;\nexport const SIZE_NAMES = Object.keys(SIZES) as SizeName[];\nexport const DEFAULT_SIZE: SizeName = \"s\";\n\nexport function isSizeName(value: string): value is SizeName {\n return value in SIZES;\n}\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox machine rm <name> [-y] — destructive: asks y/N on a TTY, needs -y otherwise */\nexport const rmCommand = new Command(\"rm\")\n .description(\"destroy a box\")\n .argument(\"<name>\", \"box name\")\n .option(\"-y, --yes\", \"do not ask for confirmation\")\n .action(() => notImplemented(\"machine rm\"));\n","import { Command } from \"commander\";\nimport { startCommand } from \"./start.js\";\nimport { statusCommand } from \"./status.js\";\nimport { stopCommand } from \"./stop.js\";\n\nexport const serverCommand = new Command(\"server\")\n .description(\"run or manage the kvbox server\")\n .addCommand(startCommand)\n .addCommand(statusCommand)\n .addCommand(stopCommand);\n","/**\n * kvbox server start\n * Starts the local `kvbox-server` (a separate package) detached, logging to\n * ~/.kvbox/server.log, and waits for its socket. The CLI never imports server code.\n */\nimport { Command } from \"commander\";\nimport { paths } from \"@kvbox/shared/paths\";\nimport { clientFor, startLocalServer } from \"../../client.js\";\nimport { info } from \"../../output.js\";\n\nexport const startCommand = new Command(\"start\")\n .description(\"start the local kvbox-server in the background\")\n .action(async (_opts: Record<string, never>, cmd: Command) => {\n const client = clientFor(cmd.optsWithGlobals());\n try {\n const health = await client.health();\n info(`kvbox-server ${health.version} is already running (pid ${health.pid})`);\n return;\n } catch {\n // not running: start it\n } finally {\n await client.close();\n }\n const { pid, socketPath } = await startLocalServer(paths());\n info(`kvbox-server started (pid ${pid}), listening on ${socketPath}`);\n });\n","/**\n * Host state layout. The filesystem is the database and `~/.kvbox` is its root.\n * This module is dependency-free so both the CLI (socket path, hosts.json)\n * and core (everything else) can use it without crossing the layer boundary.\n *\n * `KVBOX_HOME` overrides the root, mainly for tests.\n */\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport function kvboxHome(): string {\n return process.env[\"KVBOX_HOME\"] ?? join(homedir(), \".kvbox\");\n}\n\nexport function paths(home: string = kvboxHome()) {\n return {\n home,\n config: join(home, \"config.json\"),\n serverSock: join(home, \"server.sock\"),\n serverPid: join(home, \"server.pid\"),\n serverLog: join(home, \"server.log\"),\n hosts: join(home, \"hosts.json\"),\n ssh: join(home, \"ssh\"),\n sshKey: join(home, \"ssh\", \"id_ed25519\"),\n bin: join(home, \"bin\"),\n firecracker: join(home, \"bin\", \"firecracker\"),\n assets: join(home, \"assets\"),\n kernel: join(home, \"assets\", \"vmlinux\"),\n rootfs: join(home, \"assets\", \"rootfs.ext4\"),\n machines: join(home, \"machines\"),\n snapshots: join(home, \"snapshots\"),\n machine: (name: string) => ({\n dir: join(home, \"machines\", name),\n meta: join(home, \"machines\", name, \"meta.json\"),\n disk: join(home, \"machines\", name, \"disk.ext4\"),\n sock: join(home, \"machines\", name, \"fc.sock\"),\n log: join(home, \"machines\", name, \"fc.log\"),\n }),\n snapshot: (name: string) => ({\n dir: join(home, \"snapshots\", name),\n meta: join(home, \"snapshots\", name, \"meta.json\"),\n vmstate: join(home, \"snapshots\", name, \"vmstate\"),\n mem: join(home, \"snapshots\", name, \"mem\"),\n disk: join(home, \"snapshots\", name, \"disk.ext4\"),\n }),\n } as const;\n}\n\nexport type Paths = ReturnType<typeof paths>;\n","/**\n * ServerClient: one method per route of the kvbox API, over undici.\n * Target resolution: `--host` -> configured hosts (M2) -> local Unix socket.\n * Auto-starting the local server from every command when nothing listens is #17;\n * `startLocalServer` below is the piece it will reuse.\n */\nimport { spawn } from \"node:child_process\";\nimport { openSync } from \"node:fs\";\nimport { mkdir, stat } from \"node:fs/promises\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\nimport { Agent, request, type Dispatcher } from \"undici\";\nimport { z } from \"zod\";\nimport {\n ApiErrorSchema,\n CapacitySchema,\n HealthSchema,\n MachineSchema,\n SnapshotSchema,\n type CreateMachineRequest,\n type CreateSnapshotRequest,\n} from \"@kvbox/shared/api\";\nimport { KvboxError } from \"@kvbox/shared/errors\";\nimport { paths, type Paths } from \"@kvbox/shared/paths\";\n\nexport type Target = { kind: \"local\"; socketPath: string } | { kind: \"remote\"; name: string; url: string };\n\nexport function resolveTarget(hostFlag?: string): Target {\n if (hostFlag) {\n throw new KvboxError(`--host ${hostFlag}: remote hosts arrive in M2`);\n }\n return { kind: \"local\", socketPath: paths().serverSock };\n}\n\nexport class ServerClient {\n private readonly dispatcher: Dispatcher;\n private readonly base: string;\n\n constructor(readonly target: Target) {\n if (target.kind === \"local\") {\n this.dispatcher = new Agent({ connect: { socketPath: target.socketPath } });\n this.base = \"http://kvbox\";\n } else {\n this.dispatcher = new Agent();\n this.base = target.url;\n }\n }\n\n private async call<T>(\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n schema: z.ZodType<T>,\n body?: unknown,\n ): Promise<T> {\n let res;\n try {\n res = await request(this.base + path, {\n dispatcher: this.dispatcher,\n method,\n headers: body === undefined ? {} : { \"content-type\": \"application/json\" },\n body: body === undefined ? null : JSON.stringify(body),\n });\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code ?? (err as Error).cause;\n if (this.target.kind === \"local\") {\n throw new KvboxError(`kvbox-server is not running (${String(code)}); start it with: kvbox server start`);\n }\n throw new KvboxError(`cannot reach ${this.target.name}: ${String(code)}`);\n }\n const text = await res.body.text();\n if (res.statusCode >= 300) {\n const parsed = ApiErrorSchema.safeParse(text ? JSON.parse(text) : {});\n const msg = parsed.success ? parsed.data.error : `${res.statusCode} ${text}`;\n throw new KvboxError(msg, res.statusCode === 501 ? 2 : 1);\n }\n return schema.parse(text ? JSON.parse(text) : undefined);\n }\n\n health() {\n return this.call(\"GET\", \"/health\", HealthSchema);\n }\n\n capacity() {\n return this.call(\"GET\", \"/capacity\", CapacitySchema);\n }\n\n listMachines() {\n return this.call(\"GET\", \"/machines\", z.array(MachineSchema));\n }\n\n createMachine(req: CreateMachineRequest) {\n return this.call(\"POST\", \"/machines\", MachineSchema, req);\n }\n\n deleteMachine(name: string) {\n return this.call(\"DELETE\", `/machines/${encodeURIComponent(name)}`, z.undefined());\n }\n\n listSnapshots() {\n return this.call(\"GET\", \"/snapshots\", z.array(SnapshotSchema));\n }\n\n createSnapshot(req: CreateSnapshotRequest) {\n return this.call(\"POST\", \"/snapshots\", SnapshotSchema, req);\n }\n\n deleteSnapshot(name: string) {\n return this.call(\"DELETE\", `/snapshots/${encodeURIComponent(name)}`, z.undefined());\n }\n\n async close() {\n await this.dispatcher.close();\n }\n}\n\n/** Build a client from the global `--host` option of the root command. */\nexport function clientFor(opts: { host?: string }): ServerClient {\n return new ServerClient(resolveTarget(opts.host));\n}\n\nexport const NOT_A_HOST_HINT =\n \"kvbox-server is not installed, so this machine is not a kvbox host. \" +\n \"Set it up with install-server.sh, or point the CLI at a remote host (M2: kvbox host add).\";\n\n/**\n * Spawn `kvbox-server start` from PATH, detached, stdio to ~/.kvbox/server.log,\n * and resolve once its socket accepts a /health call (or reject after `timeoutMs`).\n */\nexport async function startLocalServer(p: Paths = paths(), timeoutMs = 5000): Promise<{ pid: number; socketPath: string }> {\n await mkdir(p.home, { recursive: true });\n const log = openSync(p.serverLog, \"a\");\n const child = spawn(\"kvbox-server\", [\"start\"], { detached: true, stdio: [\"ignore\", log, log] });\n\n const failed = new Promise<never>((_, reject) => {\n child.once(\"error\", (err: NodeJS.ErrnoException) => {\n reject(new KvboxError(err.code === \"ENOENT\" ? NOT_A_HOST_HINT : `could not start kvbox-server: ${err.message}`));\n });\n child.once(\"exit\", (code) => {\n reject(new KvboxError(`kvbox-server exited with code ${String(code)} before listening; see ${p.serverLog}`));\n });\n });\n\n const ready = (async () => {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await stat(p.serverSock).then(() => true, () => false)) {\n const probe = new ServerClient({ kind: \"local\", socketPath: p.serverSock });\n try {\n await probe.health();\n return;\n } catch {\n // socket exists but not serving yet\n } finally {\n await probe.close();\n }\n }\n await sleep(100);\n }\n throw new KvboxError(`kvbox-server did not start within ${timeoutMs / 1000}s; see ${p.serverLog}`);\n })();\n\n await Promise.race([ready, failed]);\n child.removeAllListeners(\"exit\");\n child.unref();\n return { pid: child.pid ?? -1, socketPath: p.serverSock };\n}\n","/**\n * Wire shapes of the kvbox HTTP API, shared by the client (src/cli) and the\n * server (src/server). Validated with zod on both sides because the client\n * may be talking to an older or newer server.\n */\nimport { z } from \"zod\";\nimport { SIZE_NAMES } from \"./sizes.js\";\n\nexport const SizeSchema = z.enum(SIZE_NAMES as [string, ...string[]]);\n\nexport const MachineSchema = z.object({\n name: z.string(),\n ip: z.string(),\n mac: z.string(),\n pid: z.number().int().nullable(),\n vcpu: z.number().int(),\n memMib: z.number().int(),\n createdAt: z.string(),\n source: z.string(), // \"base\" or \"snapshot:<name>\"\n status: z.enum([\"running\", \"stopped\"]),\n});\nexport type Machine = z.infer<typeof MachineSchema>;\n\nexport const CreateMachineRequest = z.object({\n name: z.string().optional(),\n size: SizeSchema.optional(),\n from: z.string().optional(),\n force: z.boolean().optional(),\n});\nexport type CreateMachineRequest = z.infer<typeof CreateMachineRequest>;\n\nexport const SnapshotSchema = z.object({\n name: z.string(),\n source: z.string(),\n vcpu: z.number().int(),\n memMib: z.number().int(),\n createdAt: z.string(),\n});\nexport type Snapshot = z.infer<typeof SnapshotSchema>;\n\nexport const CreateSnapshotRequest = z.object({\n box: z.string(),\n name: z.string(),\n});\nexport type CreateSnapshotRequest = z.infer<typeof CreateSnapshotRequest>;\n\nexport const CapacitySchema = z.object({\n hostMemMib: z.number().int(),\n hostCores: z.number().int(),\n reservedMemMib: z.number().int(),\n reservedVcpu: z.number().int(),\n running: z.number().int(),\n});\nexport type Capacity = z.infer<typeof CapacitySchema>;\n\nexport const HealthSchema = z.object({\n ok: z.literal(true),\n version: z.string(),\n pid: z.number().int(),\n});\nexport type Health = z.infer<typeof HealthSchema>;\n\nexport const ApiErrorSchema = z.object({\n error: z.string(),\n});\n","/** Errors with an exit code; the entry point prints `message` and exits with `exitCode`. */\nexport class KvboxError extends Error {\n constructor(\n message: string,\n public readonly exitCode: number = 1,\n ) {\n super(message);\n this.name = \"KvboxError\";\n }\n}\n\nexport class NotImplementedError extends KvboxError {\n constructor(what: string) {\n super(`${what}: not implemented yet`, 2);\n this.name = \"NotImplementedError\";\n }\n}\n","import { Command } from \"commander\";\nimport { clientFor } from \"../../client.js\";\nimport { info, json } from \"../../output.js\";\n\n/** kvbox server status [--json] — --json output: Health */\nexport const statusCommand = new Command(\"status\")\n .description(\"show whether the server is running\")\n .option(\"--json\", \"print as JSON\")\n .action(async (opts: { json?: boolean }, cmd: Command) => {\n const client = clientFor(cmd.optsWithGlobals());\n try {\n const health = await client.health();\n if (opts.json) json(health);\n else info(`kvbox-server ${health.version} running, pid ${health.pid}`);\n } finally {\n await client.close();\n }\n });\n","import { readFile } from \"node:fs/promises\";\nimport { Command } from \"commander\";\nimport { KvboxError } from \"@kvbox/shared/errors\";\nimport { paths } from \"@kvbox/shared/paths\";\nimport { info } from \"../../output.js\";\n\n/** kvbox server stop — SIGTERM the pid in server.pid. Boxes keep running. */\nexport const stopCommand = new Command(\"stop\")\n .description(\"stop the local server (running boxes are not affected)\")\n .action(async () => {\n const p = paths();\n let pid: number;\n try {\n pid = Number.parseInt(await readFile(p.serverPid, \"utf8\"), 10);\n } catch {\n throw new KvboxError(\"kvbox-server is not running (no pid file)\");\n }\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n throw new KvboxError(`kvbox-server pid ${pid} is not running; remove ${p.serverPid}`);\n }\n info(`sent SIGTERM to kvbox-server (pid ${pid})`);\n });\n","import { Command } from \"commander\";\nimport { lsCommand } from \"./ls.js\";\nimport { rmCommand } from \"./rm.js\";\nimport { saveCommand } from \"./save.js\";\n\nexport const snapshotCommand = new Command(\"snapshot\")\n .alias(\"snap\")\n .description(\"manage snapshots\")\n .addCommand(saveCommand)\n .addCommand(lsCommand)\n .addCommand(rmCommand);\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox snapshot ls [--json] — --json output: Snapshot[] */\nexport const lsCommand = new Command(\"ls\")\n .description(\"list snapshots\")\n .option(\"--json\", \"print as JSON\")\n .action(() => notImplemented(\"snapshot ls\"));\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox snapshot rm <name> [-y] — destructive: asks y/N on a TTY, needs -y otherwise */\nexport const rmCommand = new Command(\"rm\")\n .description(\"delete a snapshot\")\n .argument(\"<name>\", \"snapshot name\")\n .option(\"-y, --yes\", \"do not ask for confirmation\")\n .action(() => notImplemented(\"snapshot rm\"));\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox snapshot save <box> <name> [--json] — --json output: Snapshot */\nexport const saveCommand = new Command(\"save\")\n .description(\"freeze a running box (memory + disk) into a snapshot\")\n .argument(\"<box>\", \"box name\")\n .argument(\"<name>\", \"snapshot name\")\n .option(\"--json\", \"print the snapshot as JSON\")\n .action(() => notImplemented(\"snapshot save\"));\n","/** Entry point: parse argv, map KvboxError to an exit code. */\nimport { color } from \"./output.js\";\nimport { buildProgram } from \"./program.js\";\nimport { KvboxError } from \"@kvbox/shared/errors\";\n\ntry {\n await buildProgram().parseAsync(process.argv);\n} catch (err) {\n if (err instanceof KvboxError) {\n process.stderr.write(color.red(`error: ${err.message}`) + \"\\n\");\n process.exit(err.exitCode);\n }\n throw err;\n}\n"],"mappings":";;;AACA,OAAO,QAAQ;AAER,IAAM,QAAQ,GAAG,aAAa,QAAQ,OAAO,UAAU,QAAQ,CAAC,QAAQ,IAAI,UAAU,CAAC;AAEvF,SAAS,KAAK,OAAsB;AACzC,UAAQ,OAAO,MAAM,KAAK,UAAU,OAAO,MAAM,CAAC,IAAI,IAAI;AAC5D;AAYO,SAAS,KAAK,KAAmB;AACtC,UAAQ,OAAO,MAAM,MAAM,IAAI;AACjC;AAMO,SAAS,eAAe,SAAwB;AACrD,UAAQ,OAAO,MAAM,MAAM,OAAO,SAAS,OAAO;AAAA,CAAyB,CAAC;AAC5E,UAAQ,KAAK,CAAC;AAChB;;;AC7BA,SAAS,WAAAA,iBAAe;;;ACIxB,SAAS,qBAAqB;AAEvB,SAAS,UAAU,YAAoB,UAA2B;AACvE,MAAI,OAAO,aAAa,SAAU,QAAO;AACzC,MAAI;AACF,UAAMC,WAAU,cAAc,UAAU;AACxC,WAAQA,SAAQ,iBAAiB,EAA0B;AAAA,EAC7D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACXO,IAAM,UAAkB,UAAU,YAAY,KAAK,OAAwC,UAAoB,MAAS;;;ACJ/H,SAAS,WAAAC,gBAAe;;;ACAxB,SAAS,eAAe;AAIjB,IAAM,iBAAiB,IAAI,QAAQ,SAAS,EAChD,YAAY,6CAA6C,EACzD,SAAS,UAAU,UAAU,EAC7B,OAAO,MAAM,eAAe,iBAAiB,CAAC;;;ACPjD,SAAS,WAAAC,gBAAe;AAIjB,IAAM,YAAY,IAAIC,SAAQ,IAAI,EACtC,YAAY,+BAA+B,EAC3C,OAAO,UAAU,eAAe,EAChC,OAAO,MAAM,eAAe,YAAY,CAAC;;;ACP5C,SAAS,WAAAC,UAAS,cAAc;;;ACCzB,IAAM,QAAQ;AAAA,EACnB,GAAG,EAAE,MAAM,GAAG,QAAQ,KAAK;AAAA,EAC3B,GAAG,EAAE,MAAM,GAAG,QAAQ,KAAK;AAAA,EAC3B,GAAG,EAAE,MAAM,GAAG,QAAQ,MAAM;AAC9B;AAGO,IAAM,aAAa,OAAO,KAAK,KAAK;AACpC,IAAM,eAAyB;;;ADD/B,IAAM,aAAa,IAAIC,SAAQ,KAAK,EACxC,YAAY,kDAAkD,EAC9D,SAAS,UAAU,mCAAmC,EACtD,UAAU,IAAI,OAAO,qBAAqB,UAAU,EAAE,QAAQ,UAAU,EAAE,QAAQ,YAAY,CAAC,EAC/F,OAAO,yBAAyB,iDAAiD,EACjF,OAAO,WAAW,yBAAyB,EAC3C,OAAO,UAAU,uBAAuB,EACxC,OAAO,MAAM,eAAe,aAAa,CAAC;;;AEf7C,SAAS,WAAAC,gBAAe;AAIjB,IAAM,YAAY,IAAIC,SAAQ,IAAI,EACtC,YAAY,eAAe,EAC3B,SAAS,UAAU,UAAU,EAC7B,OAAO,aAAa,6BAA6B,EACjD,OAAO,MAAM,eAAe,YAAY,CAAC;;;ALFrC,IAAM,iBAAiB,IAAIC,SAAQ,SAAS,EAChD,MAAM,GAAG,EACT,YAAY,cAAc,EAC1B,WAAW,UAAU,EACrB,WAAW,SAAS,EACpB,WAAW,cAAc,EACzB,WAAW,SAAS;AAGhB,IAAM,iBAAiB,CAAC,YAAY,WAAW,gBAAgB,SAAS;;;AMf/E,SAAS,WAAAC,gBAAe;;;ACKxB,SAAS,WAAAC,gBAAe;;;ACExB,SAAS,eAAe;AACxB,SAAS,YAAY;AAEd,SAAS,YAAoB;AAClC,SAAO,QAAQ,IAAI,YAAY,KAAK,KAAK,QAAQ,GAAG,QAAQ;AAC9D;AAEO,SAAS,MAAM,OAAe,UAAU,GAAG;AAChD,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,KAAK,MAAM,aAAa;AAAA,IAChC,YAAY,KAAK,MAAM,aAAa;AAAA,IACpC,WAAW,KAAK,MAAM,YAAY;AAAA,IAClC,WAAW,KAAK,MAAM,YAAY;AAAA,IAClC,OAAO,KAAK,MAAM,YAAY;AAAA,IAC9B,KAAK,KAAK,MAAM,KAAK;AAAA,IACrB,QAAQ,KAAK,MAAM,OAAO,YAAY;AAAA,IACtC,KAAK,KAAK,MAAM,KAAK;AAAA,IACrB,aAAa,KAAK,MAAM,OAAO,aAAa;AAAA,IAC5C,QAAQ,KAAK,MAAM,QAAQ;AAAA,IAC3B,QAAQ,KAAK,MAAM,UAAU,SAAS;AAAA,IACtC,QAAQ,KAAK,MAAM,UAAU,aAAa;AAAA,IAC1C,UAAU,KAAK,MAAM,UAAU;AAAA,IAC/B,WAAW,KAAK,MAAM,WAAW;AAAA,IACjC,SAAS,CAAC,UAAkB;AAAA,MAC1B,KAAK,KAAK,MAAM,YAAY,IAAI;AAAA,MAChC,MAAM,KAAK,MAAM,YAAY,MAAM,WAAW;AAAA,MAC9C,MAAM,KAAK,MAAM,YAAY,MAAM,WAAW;AAAA,MAC9C,MAAM,KAAK,MAAM,YAAY,MAAM,SAAS;AAAA,MAC5C,KAAK,KAAK,MAAM,YAAY,MAAM,QAAQ;AAAA,IAC5C;AAAA,IACA,UAAU,CAAC,UAAkB;AAAA,MAC3B,KAAK,KAAK,MAAM,aAAa,IAAI;AAAA,MACjC,MAAM,KAAK,MAAM,aAAa,MAAM,WAAW;AAAA,MAC/C,SAAS,KAAK,MAAM,aAAa,MAAM,SAAS;AAAA,MAChD,KAAK,KAAK,MAAM,aAAa,MAAM,KAAK;AAAA,MACxC,MAAM,KAAK,MAAM,aAAa,MAAM,WAAW;AAAA,IACjD;AAAA,EACF;AACF;;;ACxCA,SAAS,aAAa;AACtB,SAAS,gBAAgB;AACzB,SAAS,OAAO,YAAY;AAC5B,SAAS,cAAc,aAAa;AACpC,SAAS,OAAO,eAAgC;AAChD,SAAS,KAAAC,UAAS;;;ACNlB,SAAS,SAAS;AAGX,IAAM,aAAa,EAAE,KAAK,UAAmC;AAE7D,IAAM,gBAAgB,EAAE,OAAO;AAAA,EACpC,MAAM,EAAE,OAAO;AAAA,EACf,IAAI,EAAE,OAAO;AAAA,EACb,KAAK,EAAE,OAAO;AAAA,EACd,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EAC/B,MAAM,EAAE,OAAO,EAAE,IAAI;AAAA,EACrB,QAAQ,EAAE,OAAO,EAAE,IAAI;AAAA,EACvB,WAAW,EAAE,OAAO;AAAA,EACpB,QAAQ,EAAE,OAAO;AAAA;AAAA,EACjB,QAAQ,EAAE,KAAK,CAAC,WAAW,SAAS,CAAC;AACvC,CAAC;AAGM,IAAM,uBAAuB,EAAE,OAAO;AAAA,EAC3C,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,MAAM,WAAW,SAAS;AAAA,EAC1B,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,OAAO,EAAE,QAAQ,EAAE,SAAS;AAC9B,CAAC;AAGM,IAAM,iBAAiB,EAAE,OAAO;AAAA,EACrC,MAAM,EAAE,OAAO;AAAA,EACf,QAAQ,EAAE,OAAO;AAAA,EACjB,MAAM,EAAE,OAAO,EAAE,IAAI;AAAA,EACrB,QAAQ,EAAE,OAAO,EAAE,IAAI;AAAA,EACvB,WAAW,EAAE,OAAO;AACtB,CAAC;AAGM,IAAM,wBAAwB,EAAE,OAAO;AAAA,EAC5C,KAAK,EAAE,OAAO;AAAA,EACd,MAAM,EAAE,OAAO;AACjB,CAAC;AAGM,IAAM,iBAAiB,EAAE,OAAO;AAAA,EACrC,YAAY,EAAE,OAAO,EAAE,IAAI;AAAA,EAC3B,WAAW,EAAE,OAAO,EAAE,IAAI;AAAA,EAC1B,gBAAgB,EAAE,OAAO,EAAE,IAAI;AAAA,EAC/B,cAAc,EAAE,OAAO,EAAE,IAAI;AAAA,EAC7B,SAAS,EAAE,OAAO,EAAE,IAAI;AAC1B,CAAC;AAGM,IAAM,eAAe,EAAE,OAAO;AAAA,EACnC,IAAI,EAAE,QAAQ,IAAI;AAAA,EAClB,SAAS,EAAE,OAAO;AAAA,EAClB,KAAK,EAAE,OAAO,EAAE,IAAI;AACtB,CAAC;AAGM,IAAM,iBAAiB,EAAE,OAAO;AAAA,EACrC,OAAO,EAAE,OAAO;AAClB,CAAC;;;AC/DM,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YACE,SACgB,WAAmB,GACnC;AACA,UAAM,OAAO;AAFG;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EAJkB;AAKpB;;;AFiBO,SAAS,cAAc,UAA2B;AACvD,MAAI,UAAU;AACZ,UAAM,IAAI,WAAW,UAAU,QAAQ,6BAA6B;AAAA,EACtE;AACA,SAAO,EAAE,MAAM,SAAS,YAAY,MAAM,EAAE,WAAW;AACzD;AAEO,IAAM,eAAN,MAAmB;AAAA,EAIxB,YAAqB,QAAgB;AAAhB;AACnB,QAAI,OAAO,SAAS,SAAS;AAC3B,WAAK,aAAa,IAAI,MAAM,EAAE,SAAS,EAAE,YAAY,OAAO,WAAW,EAAE,CAAC;AAC1E,WAAK,OAAO;AAAA,IACd,OAAO;AACL,WAAK,aAAa,IAAI,MAAM;AAC5B,WAAK,OAAO,OAAO;AAAA,IACrB;AAAA,EACF;AAAA,EARqB;AAAA,EAHJ;AAAA,EACA;AAAA,EAYjB,MAAc,KACZ,QACA,MACA,QACA,MACY;AACZ,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,QAAQ,KAAK,OAAO,MAAM;AAAA,QACpC,YAAY,KAAK;AAAA,QACjB;AAAA,QACA,SAAS,SAAS,SAAY,CAAC,IAAI,EAAE,gBAAgB,mBAAmB;AAAA,QACxE,MAAM,SAAS,SAAY,OAAO,KAAK,UAAU,IAAI;AAAA,MACvD,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,OAAQ,IAA8B,QAAS,IAAc;AACnE,UAAI,KAAK,OAAO,SAAS,SAAS;AAChC,cAAM,IAAI,WAAW,gCAAgC,OAAO,IAAI,CAAC,sCAAsC;AAAA,MACzG;AACA,YAAM,IAAI,WAAW,gBAAgB,KAAK,OAAO,IAAI,KAAK,OAAO,IAAI,CAAC,EAAE;AAAA,IAC1E;AACA,UAAM,OAAO,MAAM,IAAI,KAAK,KAAK;AACjC,QAAI,IAAI,cAAc,KAAK;AACzB,YAAM,SAAS,eAAe,UAAU,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC;AACpE,YAAM,MAAM,OAAO,UAAU,OAAO,KAAK,QAAQ,GAAG,IAAI,UAAU,IAAI,IAAI;AAC1E,YAAM,IAAI,WAAW,KAAK,IAAI,eAAe,MAAM,IAAI,CAAC;AAAA,IAC1D;AACA,WAAO,OAAO,MAAM,OAAO,KAAK,MAAM,IAAI,IAAI,MAAS;AAAA,EACzD;AAAA,EAEA,SAAS;AACP,WAAO,KAAK,KAAK,OAAO,WAAW,YAAY;AAAA,EACjD;AAAA,EAEA,WAAW;AACT,WAAO,KAAK,KAAK,OAAO,aAAa,cAAc;AAAA,EACrD;AAAA,EAEA,eAAe;AACb,WAAO,KAAK,KAAK,OAAO,aAAaC,GAAE,MAAM,aAAa,CAAC;AAAA,EAC7D;AAAA,EAEA,cAAc,KAA2B;AACvC,WAAO,KAAK,KAAK,QAAQ,aAAa,eAAe,GAAG;AAAA,EAC1D;AAAA,EAEA,cAAc,MAAc;AAC1B,WAAO,KAAK,KAAK,UAAU,aAAa,mBAAmB,IAAI,CAAC,IAAIA,GAAE,UAAU,CAAC;AAAA,EACnF;AAAA,EAEA,gBAAgB;AACd,WAAO,KAAK,KAAK,OAAO,cAAcA,GAAE,MAAM,cAAc,CAAC;AAAA,EAC/D;AAAA,EAEA,eAAe,KAA4B;AACzC,WAAO,KAAK,KAAK,QAAQ,cAAc,gBAAgB,GAAG;AAAA,EAC5D;AAAA,EAEA,eAAe,MAAc;AAC3B,WAAO,KAAK,KAAK,UAAU,cAAc,mBAAmB,IAAI,CAAC,IAAIA,GAAE,UAAU,CAAC;AAAA,EACpF;AAAA,EAEA,MAAM,QAAQ;AACZ,UAAM,KAAK,WAAW,MAAM;AAAA,EAC9B;AACF;AAGO,SAAS,UAAU,MAAuC;AAC/D,SAAO,IAAI,aAAa,cAAc,KAAK,IAAI,CAAC;AAClD;AAEO,IAAM,kBACX;AAOF,eAAsB,iBAAiB,IAAW,MAAM,GAAG,YAAY,KAAoD;AACzH,QAAM,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,CAAC;AACvC,QAAM,MAAM,SAAS,EAAE,WAAW,GAAG;AACrC,QAAM,QAAQ,MAAM,gBAAgB,CAAC,OAAO,GAAG,EAAE,UAAU,MAAM,OAAO,CAAC,UAAU,KAAK,GAAG,EAAE,CAAC;AAE9F,QAAM,SAAS,IAAI,QAAe,CAAC,GAAG,WAAW;AAC/C,UAAM,KAAK,SAAS,CAAC,QAA+B;AAClD,aAAO,IAAI,WAAW,IAAI,SAAS,WAAW,kBAAkB,iCAAiC,IAAI,OAAO,EAAE,CAAC;AAAA,IACjH,CAAC;AACD,UAAM,KAAK,QAAQ,CAAC,SAAS;AAC3B,aAAO,IAAI,WAAW,iCAAiC,OAAO,IAAI,CAAC,0BAA0B,EAAE,SAAS,EAAE,CAAC;AAAA,IAC7G,CAAC;AAAA,EACH,CAAC;AAED,QAAM,SAAS,YAAY;AACzB,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,WAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,UAAI,MAAM,KAAK,EAAE,UAAU,EAAE,KAAK,MAAM,MAAM,MAAM,KAAK,GAAG;AAC1D,cAAM,QAAQ,IAAI,aAAa,EAAE,MAAM,SAAS,YAAY,EAAE,WAAW,CAAC;AAC1E,YAAI;AACF,gBAAM,MAAM,OAAO;AACnB;AAAA,QACF,QAAQ;AAAA,QAER,UAAE;AACA,gBAAM,MAAM,MAAM;AAAA,QACpB;AAAA,MACF;AACA,YAAM,MAAM,GAAG;AAAA,IACjB;AACA,UAAM,IAAI,WAAW,qCAAqC,YAAY,GAAI,UAAU,EAAE,SAAS,EAAE;AAAA,EACnG,GAAG;AAEH,QAAM,QAAQ,KAAK,CAAC,OAAO,MAAM,CAAC;AAClC,QAAM,mBAAmB,MAAM;AAC/B,QAAM,MAAM;AACZ,SAAO,EAAE,KAAK,MAAM,OAAO,IAAI,YAAY,EAAE,WAAW;AAC1D;;;AF1JO,IAAM,eAAe,IAAIC,SAAQ,OAAO,EAC5C,YAAY,gDAAgD,EAC5D,OAAO,OAAO,OAA8B,QAAiB;AAC5D,QAAM,SAAS,UAAU,IAAI,gBAAgB,CAAC;AAC9C,MAAI;AACF,UAAM,SAAS,MAAM,OAAO,OAAO;AACnC,SAAK,gBAAgB,OAAO,OAAO,4BAA4B,OAAO,GAAG,GAAG;AAC5E;AAAA,EACF,QAAQ;AAAA,EAER,UAAE;AACA,UAAM,OAAO,MAAM;AAAA,EACrB;AACA,QAAM,EAAE,KAAK,WAAW,IAAI,MAAM,iBAAiB,MAAM,CAAC;AAC1D,OAAK,6BAA6B,GAAG,mBAAmB,UAAU,EAAE;AACtE,CAAC;;;AKzBH,SAAS,WAAAC,gBAAe;AAKjB,IAAM,gBAAgB,IAAIC,SAAQ,QAAQ,EAC9C,YAAY,oCAAoC,EAChD,OAAO,UAAU,eAAe,EAChC,OAAO,OAAO,MAA0B,QAAiB;AACxD,QAAM,SAAS,UAAU,IAAI,gBAAgB,CAAC;AAC9C,MAAI;AACF,UAAM,SAAS,MAAM,OAAO,OAAO;AACnC,QAAI,KAAK,KAAM,MAAK,MAAM;AAAA,QACrB,MAAK,gBAAgB,OAAO,OAAO,iBAAiB,OAAO,GAAG,EAAE;AAAA,EACvE,UAAE;AACA,UAAM,OAAO,MAAM;AAAA,EACrB;AACF,CAAC;;;ACjBH,SAAS,gBAAgB;AACzB,SAAS,WAAAC,gBAAe;AAMjB,IAAM,cAAc,IAAIC,SAAQ,MAAM,EAC1C,YAAY,wDAAwD,EACpE,OAAO,YAAY;AAClB,QAAM,IAAI,MAAM;AAChB,MAAI;AACJ,MAAI;AACF,UAAM,OAAO,SAAS,MAAM,SAAS,EAAE,WAAW,MAAM,GAAG,EAAE;AAAA,EAC/D,QAAQ;AACN,UAAM,IAAI,WAAW,2CAA2C;AAAA,EAClE;AACA,MAAI;AACF,YAAQ,KAAK,KAAK,SAAS;AAAA,EAC7B,QAAQ;AACN,UAAM,IAAI,WAAW,oBAAoB,GAAG,2BAA2B,EAAE,SAAS,EAAE;AAAA,EACtF;AACA,OAAK,qCAAqC,GAAG,GAAG;AAClD,CAAC;;;APlBI,IAAM,gBAAgB,IAAIC,SAAQ,QAAQ,EAC9C,YAAY,gCAAgC,EAC5C,WAAW,YAAY,EACvB,WAAW,aAAa,EACxB,WAAW,WAAW;;;AQTzB,SAAS,WAAAC,iBAAe;;;ACAxB,SAAS,WAAAC,iBAAe;AAIjB,IAAMC,aAAY,IAAIC,UAAQ,IAAI,EACtC,YAAY,gBAAgB,EAC5B,OAAO,UAAU,eAAe,EAChC,OAAO,MAAM,eAAe,aAAa,CAAC;;;ACP7C,SAAS,WAAAC,iBAAe;AAIjB,IAAMC,aAAY,IAAIC,UAAQ,IAAI,EACtC,YAAY,mBAAmB,EAC/B,SAAS,UAAU,eAAe,EAClC,OAAO,aAAa,6BAA6B,EACjD,OAAO,MAAM,eAAe,aAAa,CAAC;;;ACR7C,SAAS,WAAAC,iBAAe;AAIjB,IAAM,cAAc,IAAIC,UAAQ,MAAM,EAC1C,YAAY,sDAAsD,EAClE,SAAS,SAAS,UAAU,EAC5B,SAAS,UAAU,eAAe,EAClC,OAAO,UAAU,4BAA4B,EAC7C,OAAO,MAAM,eAAe,eAAe,CAAC;;;AHJxC,IAAM,kBAAkB,IAAIC,UAAQ,UAAU,EAClD,MAAM,MAAM,EACZ,YAAY,kBAAkB,EAC9B,WAAW,WAAW,EACtB,WAAWC,UAAS,EACpB,WAAWC,UAAS;;;AjBHhB,SAAS,eAAwB;AACtC,QAAM,UAAU,IAAIC,UAAQ,OAAO,EAChC,YAAY,8DAA8D,EAC1E,QAAQ,SAAS,eAAe,EAChC,OAAO,iBAAiB,sCAAsC,EAC9D,mBAAmB,EACnB,wBAAwB;AAE3B,UAAQ,WAAW,cAAc;AACjC,UAAQ,WAAW,eAAe;AAClC,UAAQ,WAAW,aAAa;AAEhC,aAAW,OAAO,eAAgB,SAAQ,WAAW,GAAG;AAExD,SAAO;AACT;;;AqBjBA,IAAI;AACF,QAAM,aAAa,EAAE,WAAW,QAAQ,IAAI;AAC9C,SAAS,KAAK;AACZ,MAAI,eAAe,YAAY;AAC7B,YAAQ,OAAO,MAAM,MAAM,IAAI,UAAU,IAAI,OAAO,EAAE,IAAI,IAAI;AAC9D,YAAQ,KAAK,IAAI,QAAQ;AAAA,EAC3B;AACA,QAAM;AACR;","names":["Command","require","Command","Command","Command","Command","Command","Command","Command","Command","Command","Command","z","z","Command","Command","Command","Command","Command","Command","Command","Command","lsCommand","Command","Command","rmCommand","Command","Command","Command","Command","lsCommand","rmCommand","Command"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghalex/kvbox",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "kvbox CLI: manage Firecracker microVM hosts with Docker-like commands. Thin client; the host runs @ghalex/kvbox-server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
},
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
18
|
-
"url": "https://github.com/ghalex/kvbox"
|
|
18
|
+
"url": "https://github.com/ghalex/kvbox",
|
|
19
|
+
"directory": "packages/cli"
|
|
19
20
|
},
|
|
20
21
|
"homepage": "https://kvbox.sh",
|
|
21
22
|
"keywords": [
|
|
@@ -28,30 +29,17 @@
|
|
|
28
29
|
"engines": {
|
|
29
30
|
"node": ">=22"
|
|
30
31
|
},
|
|
31
|
-
"packageManager": "pnpm@11.5.2",
|
|
32
32
|
"scripts": {
|
|
33
33
|
"dev": "tsx src/index.ts",
|
|
34
|
-
"build": "tsup"
|
|
35
|
-
"typecheck": "tsc --noEmit",
|
|
36
|
-
"test": "vitest run",
|
|
37
|
-
"test:watch": "vitest",
|
|
38
|
-
"lint:deps": "depcruise src --config .dependency-cruiser.cjs",
|
|
39
|
-
"check": "pnpm typecheck && pnpm lint:deps && pnpm test"
|
|
34
|
+
"build": "tsup"
|
|
40
35
|
},
|
|
41
36
|
"dependencies": {
|
|
42
|
-
"@hono/node-server": "^2.1.5",
|
|
43
37
|
"commander": "^15.0.0",
|
|
44
|
-
"hono": "^4.13.13",
|
|
45
38
|
"picocolors": "^1.1.1",
|
|
46
39
|
"undici": "^8.12.3",
|
|
47
40
|
"zod": "^4.6.7"
|
|
48
41
|
},
|
|
49
42
|
"devDependencies": {
|
|
50
|
-
"@
|
|
51
|
-
"dependency-cruiser": "^18.6.0",
|
|
52
|
-
"tsup": "^9.0.0",
|
|
53
|
-
"tsx": "^4.23.15",
|
|
54
|
-
"typescript": "^6.0.3",
|
|
55
|
-
"vitest": "^5.0.5"
|
|
43
|
+
"@kvbox/shared": "workspace:*"
|
|
56
44
|
}
|
|
57
45
|
}
|
package/README.md
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# kvbox
|
|
2
|
-
|
|
3
|
-
Self-hosted Firecracker microVMs with Docker-like ergonomics. Every box is a
|
|
4
|
-
real VM with its own kernel, boots in seconds, and a running box can be
|
|
5
|
-
snapshotted (memory + disk) and cloned from that snapshot already warm.
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
kvbox new # boot a fresh box (2 vCPU / 4 GB)
|
|
9
|
-
kvbox connect <name> # shell into it over SSH
|
|
10
|
-
kvbox snapshot save <box> <n> # freeze memory + disk
|
|
11
|
-
kvbox new --from <n> # start a warm clone
|
|
12
|
-
kvbox ls / kvbox rm <name>
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Linux host with KVM only. Status: early development, nothing boots yet.
|
|
16
|
-
|
|
17
|
-
## Install
|
|
18
|
-
|
|
19
|
-
One line on a fresh Linux host with Node 22+ (it asks for sudo only where
|
|
20
|
-
root is needed):
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
curl -fsSL https://kvbox.sh/install.sh | bash
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Until that domain is live, use the raw GitHub URL of `install.sh` instead, or
|
|
27
|
-
run `./install.sh` from a clone. The installer checks the host, installs a pinned
|
|
28
|
-
Firecracker, installs the `kvbox` CLI from npm (`@ghalex/kvbox`), the base
|
|
29
|
-
image and the SSH key `kvbox connect` uses, and adds you to the `kvm` group.
|
|
30
|
-
Rerun it any time; it only changes what is missing. `KVBOX_YES=1` takes every
|
|
31
|
-
default without asking.
|
|
32
|
-
|
|
33
|
-
## Layout
|
|
34
|
-
|
|
35
|
-
One executable, two roles. `kvbox server start` is the daemon that owns
|
|
36
|
-
Firecracker and `~/.kvbox`; every other command is a thin client talking to it
|
|
37
|
-
over a Unix socket (HTTPS to remote hosts later).
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
src/
|
|
41
|
-
index.ts entry point; cli/program.ts builds the command tree
|
|
42
|
-
cli/ commands (one file each), HTTP client, output
|
|
43
|
-
server/ hono app + routes, started only by `server start`
|
|
44
|
-
core/ Firecracker client, state dir, networking, capacity guard
|
|
45
|
-
shared/ dependency-free helpers: paths, sizes, API schemas
|
|
46
|
-
install.sh one-shot host setup
|
|
47
|
-
scripts/ host networking (bridge + NAT)
|
|
48
|
-
spikes/ throwaway experiments
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Dependency direction `cli -> (HTTP) -> server -> core` is enforced by
|
|
52
|
-
dependency-cruiser.
|
|
53
|
-
|
|
54
|
-
## Develop
|
|
55
|
-
|
|
56
|
-
Requires Node >= 22 and pnpm.
|
|
57
|
-
|
|
58
|
-
```
|
|
59
|
-
pnpm install
|
|
60
|
-
pnpm dev -- --help # run from source
|
|
61
|
-
pnpm check # typecheck + dependency rules + tests
|
|
62
|
-
pnpm build # dist/kvbox.js, the single file published to npm
|
|
63
|
-
npm link # put this checkout's `kvbox` on PATH for development
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Set `KVBOX_HOME` to use a state dir other than `~/.kvbox`.
|
|
67
|
-
|
|
68
|
-
The base image comes from [kvbox-images](https://github.com/ghalex/kvbox-images)
|
|
69
|
-
as a release asset; `install.sh` pins it with `ROOTFS_VERSION`. To use a local
|
|
70
|
-
build instead, point `KVBOX_IMAGES_DIR` at a checkout of that repo. Per-box
|
|
71
|
-
settings (hostname, SSH key, IP) arrive on the kernel command line at boot, so
|
|
72
|
-
one image serves every host and user.
|
|
73
|
-
|
|
74
|
-
## License
|
|
75
|
-
|
|
76
|
-
MIT
|