@ghalex/kvbox 0.0.0-stage → 0.0.1
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/README.md +75 -2
- package/dist/kvbox.js +515 -0
- package/dist/kvbox.js.map +1 -0
- package/package.json +55 -4
package/README.md
CHANGED
|
@@ -1,3 +1,76 @@
|
|
|
1
|
-
#
|
|
1
|
+
# kvbox
|
|
2
2
|
|
|
3
|
-
|
|
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
|
package/dist/kvbox.js
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/output.ts
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
var color = pc.createColors(process.stdout.isTTY === true && !process.env["NO_COLOR"]);
|
|
6
|
+
function json(value) {
|
|
7
|
+
process.stdout.write(JSON.stringify(value, null, 2) + "\n");
|
|
8
|
+
}
|
|
9
|
+
function info(msg) {
|
|
10
|
+
process.stderr.write(msg + "\n");
|
|
11
|
+
}
|
|
12
|
+
function notImplemented(command) {
|
|
13
|
+
process.stderr.write(color.yellow(`kvbox ${command}: not implemented yet
|
|
14
|
+
`));
|
|
15
|
+
process.exit(2);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/cli/program.ts
|
|
19
|
+
import { Command as Command14 } from "commander";
|
|
20
|
+
|
|
21
|
+
// src/shared/version.ts
|
|
22
|
+
import { createRequire } from "module";
|
|
23
|
+
var VERSION = true ? "0.0.1" : fromPackageJson();
|
|
24
|
+
|
|
25
|
+
// src/cli/commands/machine/index.ts
|
|
26
|
+
import { Command as Command5 } from "commander";
|
|
27
|
+
|
|
28
|
+
// src/cli/commands/machine/connect.ts
|
|
29
|
+
import { Command } from "commander";
|
|
30
|
+
var connectCommand = new Command("connect").description("open an interactive shell in a box over SSH").argument("<name>", "box name").action(() => notImplemented("machine connect"));
|
|
31
|
+
|
|
32
|
+
// src/cli/commands/machine/ls.ts
|
|
33
|
+
import { Command as Command2 } from "commander";
|
|
34
|
+
var lsCommand = new Command2("ls").description("list boxes with status and IP").option("--json", "print as JSON").action(() => notImplemented("machine ls"));
|
|
35
|
+
|
|
36
|
+
// src/cli/commands/machine/new.ts
|
|
37
|
+
import { Command as Command3, Option } from "commander";
|
|
38
|
+
|
|
39
|
+
// src/shared/sizes.ts
|
|
40
|
+
var SIZES = {
|
|
41
|
+
s: { vcpu: 2, memMib: 4096 },
|
|
42
|
+
m: { vcpu: 4, memMib: 8192 },
|
|
43
|
+
l: { vcpu: 8, memMib: 16384 }
|
|
44
|
+
};
|
|
45
|
+
var SIZE_NAMES = Object.keys(SIZES);
|
|
46
|
+
var DEFAULT_SIZE = "s";
|
|
47
|
+
|
|
48
|
+
// src/cli/commands/machine/new.ts
|
|
49
|
+
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
|
+
|
|
51
|
+
// src/cli/commands/machine/rm.ts
|
|
52
|
+
import { Command as Command4 } from "commander";
|
|
53
|
+
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
|
+
|
|
55
|
+
// src/cli/commands/machine/index.ts
|
|
56
|
+
var machineCommand = new Command5("machine").alias("m").description("manage boxes").addCommand(newCommand).addCommand(lsCommand).addCommand(connectCommand).addCommand(rmCommand);
|
|
57
|
+
var machineAliases = [newCommand, lsCommand, connectCommand, rmCommand];
|
|
58
|
+
|
|
59
|
+
// src/cli/commands/server/index.ts
|
|
60
|
+
import { Command as Command9 } from "commander";
|
|
61
|
+
|
|
62
|
+
// src/cli/commands/server/start.ts
|
|
63
|
+
import { Command as Command6 } from "commander";
|
|
64
|
+
|
|
65
|
+
// src/server/listen.ts
|
|
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
|
|
74
|
+
import { homedir } from "os";
|
|
75
|
+
import { join } from "path";
|
|
76
|
+
function kvboxHome() {
|
|
77
|
+
return process.env["KVBOX_HOME"] ?? join(homedir(), ".kvbox");
|
|
78
|
+
}
|
|
79
|
+
function paths(home = kvboxHome()) {
|
|
80
|
+
return {
|
|
81
|
+
home,
|
|
82
|
+
config: join(home, "config.json"),
|
|
83
|
+
serverSock: join(home, "server.sock"),
|
|
84
|
+
serverPid: join(home, "server.pid"),
|
|
85
|
+
hosts: join(home, "hosts.json"),
|
|
86
|
+
ssh: join(home, "ssh"),
|
|
87
|
+
sshKey: join(home, "ssh", "id_ed25519"),
|
|
88
|
+
bin: join(home, "bin"),
|
|
89
|
+
firecracker: join(home, "bin", "firecracker"),
|
|
90
|
+
assets: join(home, "assets"),
|
|
91
|
+
kernel: join(home, "assets", "vmlinux"),
|
|
92
|
+
rootfs: join(home, "assets", "rootfs.ext4"),
|
|
93
|
+
machines: join(home, "machines"),
|
|
94
|
+
snapshots: join(home, "snapshots"),
|
|
95
|
+
machine: (name) => ({
|
|
96
|
+
dir: join(home, "machines", name),
|
|
97
|
+
meta: join(home, "machines", name, "meta.json"),
|
|
98
|
+
disk: join(home, "machines", name, "disk.ext4"),
|
|
99
|
+
sock: join(home, "machines", name, "fc.sock"),
|
|
100
|
+
log: join(home, "machines", name, "fc.log")
|
|
101
|
+
}),
|
|
102
|
+
snapshot: (name) => ({
|
|
103
|
+
dir: join(home, "snapshots", name),
|
|
104
|
+
meta: join(home, "snapshots", name, "meta.json"),
|
|
105
|
+
vmstate: join(home, "snapshots", name, "vmstate"),
|
|
106
|
+
mem: join(home, "snapshots", name, "mem"),
|
|
107
|
+
disk: join(home, "snapshots", name, "disk.ext4")
|
|
108
|
+
})
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/core/state.ts
|
|
113
|
+
var MachineMetaSchema = z.object({
|
|
114
|
+
name: z.string(),
|
|
115
|
+
ip: z.string(),
|
|
116
|
+
mac: z.string(),
|
|
117
|
+
pid: z.number().int(),
|
|
118
|
+
vcpu: z.number().int(),
|
|
119
|
+
memMib: z.number().int(),
|
|
120
|
+
createdAt: z.string(),
|
|
121
|
+
source: z.string()
|
|
122
|
+
});
|
|
123
|
+
var SnapshotMetaSchema = z.object({
|
|
124
|
+
name: z.string(),
|
|
125
|
+
source: z.string(),
|
|
126
|
+
vcpu: z.number().int(),
|
|
127
|
+
memMib: z.number().int(),
|
|
128
|
+
createdAt: z.string()
|
|
129
|
+
});
|
|
130
|
+
async function ensureLayout(p = paths()) {
|
|
131
|
+
for (const dir of [p.home, p.ssh, p.bin, p.assets, p.machines, p.snapshots]) {
|
|
132
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
133
|
+
}
|
|
134
|
+
return p;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/core/config.ts
|
|
138
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
139
|
+
import { z as z2 } from "zod";
|
|
140
|
+
var ConfigSchema = z2.object({
|
|
141
|
+
kernel: z2.string(),
|
|
142
|
+
rootfs: z2.string(),
|
|
143
|
+
firecracker: z2.string(),
|
|
144
|
+
bridge: z2.string().default("zbr0"),
|
|
145
|
+
subnet: z2.string().default("172.30.0.0/24"),
|
|
146
|
+
gateway: z2.string().default("172.30.0.1"),
|
|
147
|
+
defaultSize: z2.enum(SIZE_NAMES).default(DEFAULT_SIZE)
|
|
148
|
+
});
|
|
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
|
+
|
|
170
|
+
// src/shared/errors.ts
|
|
171
|
+
var KvboxError = class extends Error {
|
|
172
|
+
constructor(message, exitCode = 1) {
|
|
173
|
+
super(message);
|
|
174
|
+
this.exitCode = exitCode;
|
|
175
|
+
this.name = "KvboxError";
|
|
176
|
+
}
|
|
177
|
+
exitCode;
|
|
178
|
+
};
|
|
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
|
+
|
|
192
|
+
// src/core/capacity.ts
|
|
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";
|
|
367
|
+
function resolveTarget(hostFlag) {
|
|
368
|
+
if (hostFlag) {
|
|
369
|
+
throw new KvboxError(`--host ${hostFlag}: remote hosts arrive in M2`);
|
|
370
|
+
}
|
|
371
|
+
return { kind: "local", socketPath: paths().serverSock };
|
|
372
|
+
}
|
|
373
|
+
var ServerClient = class {
|
|
374
|
+
constructor(target) {
|
|
375
|
+
this.target = target;
|
|
376
|
+
if (target.kind === "local") {
|
|
377
|
+
this.dispatcher = new Agent({ connect: { socketPath: target.socketPath } });
|
|
378
|
+
this.base = "http://kvbox";
|
|
379
|
+
} else {
|
|
380
|
+
this.dispatcher = new Agent();
|
|
381
|
+
this.base = target.url;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
target;
|
|
385
|
+
dispatcher;
|
|
386
|
+
base;
|
|
387
|
+
async call(method, path, schema, body) {
|
|
388
|
+
let res;
|
|
389
|
+
try {
|
|
390
|
+
res = await request(this.base + path, {
|
|
391
|
+
dispatcher: this.dispatcher,
|
|
392
|
+
method,
|
|
393
|
+
headers: body === void 0 ? {} : { "content-type": "application/json" },
|
|
394
|
+
body: body === void 0 ? null : JSON.stringify(body)
|
|
395
|
+
});
|
|
396
|
+
} catch (err) {
|
|
397
|
+
const code = err.code ?? err.cause;
|
|
398
|
+
if (this.target.kind === "local") {
|
|
399
|
+
throw new KvboxError(`kvbox server is not running (${String(code)}); start it with: kvbox server start`);
|
|
400
|
+
}
|
|
401
|
+
throw new KvboxError(`cannot reach ${this.target.name}: ${String(code)}`);
|
|
402
|
+
}
|
|
403
|
+
const text = await res.body.text();
|
|
404
|
+
if (res.statusCode >= 300) {
|
|
405
|
+
const parsed = ApiErrorSchema.safeParse(text ? JSON.parse(text) : {});
|
|
406
|
+
const msg = parsed.success ? parsed.data.error : `${res.statusCode} ${text}`;
|
|
407
|
+
throw new KvboxError(msg, res.statusCode === 501 ? 2 : 1);
|
|
408
|
+
}
|
|
409
|
+
return schema.parse(text ? JSON.parse(text) : void 0);
|
|
410
|
+
}
|
|
411
|
+
health() {
|
|
412
|
+
return this.call("GET", "/health", HealthSchema);
|
|
413
|
+
}
|
|
414
|
+
capacity() {
|
|
415
|
+
return this.call("GET", "/capacity", CapacitySchema);
|
|
416
|
+
}
|
|
417
|
+
listMachines() {
|
|
418
|
+
return this.call("GET", "/machines", z4.array(MachineSchema));
|
|
419
|
+
}
|
|
420
|
+
createMachine(req) {
|
|
421
|
+
return this.call("POST", "/machines", MachineSchema, req);
|
|
422
|
+
}
|
|
423
|
+
deleteMachine(name) {
|
|
424
|
+
return this.call("DELETE", `/machines/${encodeURIComponent(name)}`, z4.undefined());
|
|
425
|
+
}
|
|
426
|
+
listSnapshots() {
|
|
427
|
+
return this.call("GET", "/snapshots", z4.array(SnapshotSchema));
|
|
428
|
+
}
|
|
429
|
+
createSnapshot(req) {
|
|
430
|
+
return this.call("POST", "/snapshots", SnapshotSchema, req);
|
|
431
|
+
}
|
|
432
|
+
deleteSnapshot(name) {
|
|
433
|
+
return this.call("DELETE", `/snapshots/${encodeURIComponent(name)}`, z4.undefined());
|
|
434
|
+
}
|
|
435
|
+
async close() {
|
|
436
|
+
await this.dispatcher.close();
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
function clientFor(opts) {
|
|
440
|
+
return new ServerClient(resolveTarget(opts.host));
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/cli/commands/server/status.ts
|
|
444
|
+
var statusCommand = new Command7("status").description("show whether the server is running").option("--json", "print as JSON").action(async (opts, cmd) => {
|
|
445
|
+
const client = clientFor(cmd.optsWithGlobals());
|
|
446
|
+
try {
|
|
447
|
+
const health = await client.health();
|
|
448
|
+
if (opts.json) json(health);
|
|
449
|
+
else info(`kvbox server ${health.version} running, pid ${health.pid}`);
|
|
450
|
+
} finally {
|
|
451
|
+
await client.close();
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// src/cli/commands/server/stop.ts
|
|
456
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
457
|
+
import { Command as Command8 } from "commander";
|
|
458
|
+
var stopCommand = new Command8("stop").description("stop the local server (running boxes are not affected)").action(async () => {
|
|
459
|
+
const p = paths();
|
|
460
|
+
let pid;
|
|
461
|
+
try {
|
|
462
|
+
pid = Number.parseInt(await readFile3(p.serverPid, "utf8"), 10);
|
|
463
|
+
} catch {
|
|
464
|
+
throw new KvboxError("kvbox server is not running (no pid file)");
|
|
465
|
+
}
|
|
466
|
+
try {
|
|
467
|
+
process.kill(pid, "SIGTERM");
|
|
468
|
+
} catch {
|
|
469
|
+
throw new KvboxError(`kvbox server pid ${pid} is not running; remove ${p.serverPid}`);
|
|
470
|
+
}
|
|
471
|
+
info(`sent SIGTERM to kvbox server (pid ${pid})`);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// src/cli/commands/server/index.ts
|
|
475
|
+
var serverCommand = new Command9("server").description("run or manage the kvbox server").addCommand(startCommand).addCommand(statusCommand).addCommand(stopCommand);
|
|
476
|
+
|
|
477
|
+
// src/cli/commands/snapshot/index.ts
|
|
478
|
+
import { Command as Command13 } from "commander";
|
|
479
|
+
|
|
480
|
+
// src/cli/commands/snapshot/ls.ts
|
|
481
|
+
import { Command as Command10 } from "commander";
|
|
482
|
+
var lsCommand2 = new Command10("ls").description("list snapshots").option("--json", "print as JSON").action(() => notImplemented("snapshot ls"));
|
|
483
|
+
|
|
484
|
+
// src/cli/commands/snapshot/rm.ts
|
|
485
|
+
import { Command as Command11 } from "commander";
|
|
486
|
+
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
|
+
|
|
488
|
+
// src/cli/commands/snapshot/save.ts
|
|
489
|
+
import { Command as Command12 } from "commander";
|
|
490
|
+
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
|
+
|
|
492
|
+
// src/cli/commands/snapshot/index.ts
|
|
493
|
+
var snapshotCommand = new Command13("snapshot").alias("snap").description("manage snapshots").addCommand(saveCommand).addCommand(lsCommand2).addCommand(rmCommand2);
|
|
494
|
+
|
|
495
|
+
// src/cli/program.ts
|
|
496
|
+
function buildProgram() {
|
|
497
|
+
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
|
+
program.addCommand(machineCommand);
|
|
499
|
+
program.addCommand(snapshotCommand);
|
|
500
|
+
program.addCommand(serverCommand);
|
|
501
|
+
for (const cmd of machineAliases) program.addCommand(cmd);
|
|
502
|
+
return program;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// src/index.ts
|
|
506
|
+
try {
|
|
507
|
+
await buildProgram().parseAsync(process.argv);
|
|
508
|
+
} catch (err) {
|
|
509
|
+
if (err instanceof KvboxError) {
|
|
510
|
+
process.stderr.write(color.red(`error: ${err.message}`) + "\n");
|
|
511
|
+
process.exit(err.exitCode);
|
|
512
|
+
}
|
|
513
|
+
throw err;
|
|
514
|
+
}
|
|
515
|
+
//# sourceMappingURL=kvbox.js.map
|
|
@@ -0,0 +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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghalex/kvbox",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Self-hosted Firecracker microVM manager with Docker-like ergonomics",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"bin": {
|
|
8
|
+
"kvbox": "./dist/kvbox.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/ghalex/kvbox"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://kvbox.sh",
|
|
21
|
+
"keywords": [
|
|
22
|
+
"firecracker",
|
|
23
|
+
"microvm",
|
|
24
|
+
"sandbox",
|
|
25
|
+
"vm",
|
|
26
|
+
"cli"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22"
|
|
30
|
+
},
|
|
31
|
+
"packageManager": "pnpm@11.5.2",
|
|
32
|
+
"scripts": {
|
|
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"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@hono/node-server": "^2.1.5",
|
|
43
|
+
"commander": "^15.0.0",
|
|
44
|
+
"hono": "^4.13.13",
|
|
45
|
+
"picocolors": "^1.1.1",
|
|
46
|
+
"undici": "^8.12.3",
|
|
47
|
+
"zod": "^4.6.7"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^26.6.5",
|
|
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"
|
|
56
|
+
}
|
|
57
|
+
}
|