@kvbox/cli 0.5.10 → 0.5.12
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 +172 -79
- package/dist/kvbox.js.map +1 -1
- package/package.json +1 -1
package/dist/kvbox.js
CHANGED
|
@@ -147,7 +147,7 @@ async function spin(message, work, opts = {}) {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
// src/program.ts
|
|
150
|
-
import { Command as
|
|
150
|
+
import { Command as Command41 } from "commander";
|
|
151
151
|
|
|
152
152
|
// ../shared/src/version.ts
|
|
153
153
|
import { createRequire } from "module";
|
|
@@ -162,10 +162,10 @@ function versionOf(srcFileUrl, injected) {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
// src/version.ts
|
|
165
|
-
var VERSION = versionOf(import.meta.url, true ? "0.5.
|
|
165
|
+
var VERSION = versionOf(import.meta.url, true ? "0.5.12" : void 0);
|
|
166
166
|
|
|
167
167
|
// src/commands/vm/index.ts
|
|
168
|
-
import { Command as
|
|
168
|
+
import { Command as Command17 } from "commander";
|
|
169
169
|
|
|
170
170
|
// src/commands/vm/connect.ts
|
|
171
171
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
@@ -529,6 +529,23 @@ var MachineUsageSchema = z2.object({
|
|
|
529
529
|
estimatedStops: z2.number().int(),
|
|
530
530
|
historyFrom: z2.string().nullable()
|
|
531
531
|
});
|
|
532
|
+
var GuestStatsSchema = z2.object({
|
|
533
|
+
/** Load average over 1, 5 and 15 minutes. */
|
|
534
|
+
load: z2.tuple([z2.number(), z2.number(), z2.number()]),
|
|
535
|
+
cpus: z2.number().int(),
|
|
536
|
+
memTotalMib: z2.number().int(),
|
|
537
|
+
/** Total minus available: page cache counts as free. */
|
|
538
|
+
memUsedMib: z2.number().int(),
|
|
539
|
+
/** The box's root, which is its overlay disk: the box's own writes, not the shared base. */
|
|
540
|
+
diskTotalBytes: z2.number().int(),
|
|
541
|
+
diskUsedBytes: z2.number().int(),
|
|
542
|
+
uptimeSec: z2.number().int()
|
|
543
|
+
});
|
|
544
|
+
var MachineStatsSchema = z2.object({
|
|
545
|
+
name: z2.string(),
|
|
546
|
+
owner: z2.string(),
|
|
547
|
+
stats: GuestStatsSchema.nullable()
|
|
548
|
+
});
|
|
532
549
|
var UsageStatusSchema = z2.enum([...BoxStatusSchema.options, "removed"]);
|
|
533
550
|
var BoxUsageSchema = MachineUsageSchema.omit({ window: true, status: true }).extend({
|
|
534
551
|
/** The host's box id; a name removed and recreated appears twice, with two ids. */
|
|
@@ -803,6 +820,7 @@ async function resolveTarget(opts, p = paths()) {
|
|
|
803
820
|
}
|
|
804
821
|
return { kind: "local", socketPath: p.serverSock };
|
|
805
822
|
}
|
|
823
|
+
var allQuery = (opts) => opts.all ? "?all=1" : "";
|
|
806
824
|
var ServerClient = class {
|
|
807
825
|
constructor(target, opts = {}) {
|
|
808
826
|
this.target = target;
|
|
@@ -869,9 +887,12 @@ var ServerClient = class {
|
|
|
869
887
|
capacity() {
|
|
870
888
|
return this.call("GET", "/capacity", CapacitySchema);
|
|
871
889
|
}
|
|
872
|
-
/**
|
|
873
|
-
|
|
874
|
-
|
|
890
|
+
/**
|
|
891
|
+
* Through a control plane: the user's boxes and their orgs', the `--org` org's, or `org`'s;
|
|
892
|
+
* `all`: every box on the platform (platform admins only, #78).
|
|
893
|
+
*/
|
|
894
|
+
listMachines(opts = {}) {
|
|
895
|
+
return this.call("GET", `/machines${allQuery(opts)}`, z3.array(MachineSchema), void 0, opts.org);
|
|
875
896
|
}
|
|
876
897
|
createMachine(req) {
|
|
877
898
|
return this.call("POST", "/machines", MachineSchema, req);
|
|
@@ -903,14 +924,19 @@ var ServerClient = class {
|
|
|
903
924
|
setMachine(name, req) {
|
|
904
925
|
return this.call("POST", `/machines/${encodeURIComponent(name)}/set`, MachineSchema, req);
|
|
905
926
|
}
|
|
927
|
+
/** A running box's own load, memory and disk, asked of its guest (#67). */
|
|
928
|
+
machineStats(name) {
|
|
929
|
+
return this.call("GET", `/machines/${encodeURIComponent(name)}/stats`, MachineStatsSchema);
|
|
930
|
+
}
|
|
906
931
|
/** `since`: an ISO date, already validated; the server counts from the box's creation without it. */
|
|
907
932
|
machineUsage(name, since) {
|
|
908
933
|
const query = since === void 0 ? "" : `?since=${encodeURIComponent(since)}`;
|
|
909
934
|
return this.call("GET", `/machines/${encodeURIComponent(name)}/usage${query}`, MachineUsageSchema);
|
|
910
935
|
}
|
|
911
936
|
/** Every box of the caller over the window (#41 on a host, #42 through a control plane); dates as the user wrote them. */
|
|
912
|
-
usageReport(since, until) {
|
|
937
|
+
usageReport(since, until, all = false) {
|
|
913
938
|
const q = new URLSearchParams();
|
|
939
|
+
if (all) q.set("all", "1");
|
|
914
940
|
if (since !== void 0) q.set("since", since);
|
|
915
941
|
if (until !== void 0) q.set("until", until);
|
|
916
942
|
const query = q.size === 0 ? "" : `?${q.toString()}`;
|
|
@@ -923,8 +949,9 @@ var ServerClient = class {
|
|
|
923
949
|
deleteMachine(name) {
|
|
924
950
|
return this.call("DELETE", `/machines/${encodeURIComponent(name)}`, z3.undefined());
|
|
925
951
|
}
|
|
926
|
-
|
|
927
|
-
|
|
952
|
+
/** Like `listMachines`, for snapshots. */
|
|
953
|
+
listSnapshots(opts = {}) {
|
|
954
|
+
return this.call("GET", `/snapshots${allQuery(opts)}`, z3.array(SnapshotSchema), void 0, opts.org);
|
|
928
955
|
}
|
|
929
956
|
createSnapshot(req) {
|
|
930
957
|
return this.call("POST", "/snapshots", SnapshotSchema, req);
|
|
@@ -1010,6 +1037,11 @@ async function wakeUp(client, box, name) {
|
|
|
1010
1037
|
return box;
|
|
1011
1038
|
}
|
|
1012
1039
|
}
|
|
1040
|
+
function boxesFor(client, text) {
|
|
1041
|
+
const slash = text.indexOf("/");
|
|
1042
|
+
if (slash === -1 || client.target.kind !== "cloud") return client.listMachines();
|
|
1043
|
+
return client.listMachines({ org: text.slice(0, slash).split("@")[0] });
|
|
1044
|
+
}
|
|
1013
1045
|
var me = (client) => client.target.kind === "cloud" ? client.target.user : void 0;
|
|
1014
1046
|
async function clientFor(opts, client = {}) {
|
|
1015
1047
|
return new ServerClient(await resolveTarget(opts), client);
|
|
@@ -1179,7 +1211,7 @@ var connectCommand = new Command2("connect").alias("cn").description("open a she
|
|
|
1179
1211
|
let cert;
|
|
1180
1212
|
let owner;
|
|
1181
1213
|
try {
|
|
1182
|
-
let box = pickRef(await client
|
|
1214
|
+
let box = pickRef(await boxesFor(client, name), name, me(client));
|
|
1183
1215
|
if (!box) throw new KvboxError(`box ${name}: not found (kvbox ls)`);
|
|
1184
1216
|
cert = await ensureCert(client, void 0, void 0, { owner: box.owner });
|
|
1185
1217
|
owner = box.owner;
|
|
@@ -1257,7 +1289,7 @@ var execCommand = new Command3("exec").description("run a command in a box and e
|
|
|
1257
1289
|
let cert;
|
|
1258
1290
|
let owner;
|
|
1259
1291
|
try {
|
|
1260
|
-
let box = pickRef(await client
|
|
1292
|
+
let box = pickRef(await boxesFor(client, name), name, me(client));
|
|
1261
1293
|
if (!box) throw new KvboxError(`box ${name}: not found (kvbox ls)`);
|
|
1262
1294
|
if (box.status === "stopped") throw new KvboxError(`box ${name} is stopped (kvbox start ${name})`);
|
|
1263
1295
|
cert = await ensureCert(client, void 0, void 0, { owner: box.owner });
|
|
@@ -1313,7 +1345,7 @@ var hibernateCommand = new Command4("hibernate").description("save a box's memor
|
|
|
1313
1345
|
});
|
|
1314
1346
|
|
|
1315
1347
|
// src/commands/vm/ls.ts
|
|
1316
|
-
import { Command as Command5
|
|
1348
|
+
import { Command as Command5 } from "commander";
|
|
1317
1349
|
function ownerCell(b, me2) {
|
|
1318
1350
|
const slug = slugOf(b.owner);
|
|
1319
1351
|
if (slug === me2) return "private";
|
|
@@ -1323,10 +1355,10 @@ function addressOf(b) {
|
|
|
1323
1355
|
if (b.ssh !== void 0) return `${b.ssh.host}:${b.ssh.port}`;
|
|
1324
1356
|
return b.ip === "" ? "-" : b.ip;
|
|
1325
1357
|
}
|
|
1326
|
-
var lsCommand = new Command5("ls").description("list boxes with status and address (yours and your orgs'; --org <slug> for one org)").
|
|
1358
|
+
var lsCommand = new Command5("ls").description("list boxes with status and address (yours and your orgs'; --org <slug> for one org)").option("-a, --all", "every box on the platform (platform admins only)").option("--json", "print as JSON").action(async (opts, cmd) => {
|
|
1327
1359
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1328
1360
|
try {
|
|
1329
|
-
const boxes = await client.listMachines();
|
|
1361
|
+
const boxes = await client.listMachines({ all: opts.all });
|
|
1330
1362
|
if (opts.json) {
|
|
1331
1363
|
json(boxes);
|
|
1332
1364
|
return;
|
|
@@ -1367,8 +1399,8 @@ var pauseCommand = new Command6("pause").description("freeze a box in place (mem
|
|
|
1367
1399
|
});
|
|
1368
1400
|
|
|
1369
1401
|
// src/commands/vm/rescale.ts
|
|
1370
|
-
import { Command as Command7, Option as
|
|
1371
|
-
var rescaleCommand = new Command7("rescale").description("change a box's vCPUs, memory or disk (e.g. --vcpu 4 --mem 16GB); a running box restarts").argument("<name>", "box name").addOption(new
|
|
1402
|
+
import { Command as Command7, Option as Option2 } from "commander";
|
|
1403
|
+
var rescaleCommand = new Command7("rescale").description("change a box's vCPUs, memory or disk (e.g. --vcpu 4 --mem 16GB); a running box restarts").argument("<name>", "box name").addOption(new Option2("-s, --size <size>", "a preset to start from").choices(SIZE_NAMES)).option("--vcpu <n>", "number of vCPUs").option("--mem <size>", "memory, e.g. 16GB or 4096MB").option("--disk <size>", "disk size, e.g. 32GB (grow only)").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
|
|
1372
1404
|
const req = {
|
|
1373
1405
|
...opts.size !== void 0 ? { size: opts.size } : {},
|
|
1374
1406
|
...opts.vcpu !== void 0 ? { vcpu: parseCount(opts.vcpu) } : {},
|
|
@@ -1378,7 +1410,7 @@ var rescaleCommand = new Command7("rescale").description("change a box's vCPUs,
|
|
|
1378
1410
|
if (Object.keys(req).length === 0) throw new KvboxError("nothing to change: give --size, --vcpu, --mem or --disk");
|
|
1379
1411
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1380
1412
|
try {
|
|
1381
|
-
const before = pickRef(await client
|
|
1413
|
+
const before = pickRef(await boxesFor(client, name), name, me(client));
|
|
1382
1414
|
if (!before) throw new KvboxError(`box ${name}: not found (kvbox ls)`);
|
|
1383
1415
|
const box = await spin(`rescaling ${name}\u2026`, client.rescaleMachine(displayName(before, me(client)), req));
|
|
1384
1416
|
if (opts.json) {
|
|
@@ -1430,7 +1462,7 @@ import { Command as Command9 } from "commander";
|
|
|
1430
1462
|
var rmCommand = new Command9("rm").description("destroy a box (its disk is deleted; snapshots are kept)").argument("<name>", "box name").option("-y, --yes", "do not ask for confirmation").action(async (name, opts, cmd) => {
|
|
1431
1463
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1432
1464
|
try {
|
|
1433
|
-
const box = pickRef(await client
|
|
1465
|
+
const box = pickRef(await boxesFor(client, name), name, me(client));
|
|
1434
1466
|
if (!box) throw new KvboxError(`box ${name}: not found (kvbox ls)`);
|
|
1435
1467
|
const ref = displayName(box, me(client));
|
|
1436
1468
|
if (!await confirm(`Destroy box ${ref} and its disk?`, opts.yes ?? false)) {
|
|
@@ -1452,7 +1484,7 @@ var setCommand = new Command10("set").description("change a box's settings, e.g.
|
|
|
1452
1484
|
const autoHibernateSec = parseIdleTimeout(opts.autoHibernate);
|
|
1453
1485
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1454
1486
|
try {
|
|
1455
|
-
const found = pickRef(await client
|
|
1487
|
+
const found = pickRef(await boxesFor(client, name), name, me(client));
|
|
1456
1488
|
if (!found) throw new KvboxError(`box ${name}: not found (kvbox ls)`);
|
|
1457
1489
|
const box = await spin(`updating ${name}\u2026`, client.setMachine(displayName(found, me(client)), { autoHibernateSec }));
|
|
1458
1490
|
if (opts.json) {
|
|
@@ -1491,12 +1523,73 @@ var stopCommand = new Command12("stop").description("shut a box down (its disk i
|
|
|
1491
1523
|
}
|
|
1492
1524
|
});
|
|
1493
1525
|
|
|
1494
|
-
// src/commands/vm/
|
|
1526
|
+
// src/commands/vm/top.ts
|
|
1495
1527
|
import { Command as Command13 } from "commander";
|
|
1496
|
-
var
|
|
1528
|
+
var topCommand = new Command13("top").description("load, memory and disk of each running box, as the box itself sees them").option("-a, --all", "every running box on the platform (platform admins only)").option("--json", "print as JSON").action(async (opts, cmd) => {
|
|
1529
|
+
const client = await clientFor(cmd.optsWithGlobals());
|
|
1530
|
+
try {
|
|
1531
|
+
const running = (await client.listMachines({ all: opts.all })).filter((b) => b.status === "running");
|
|
1532
|
+
const entries = await mapLimit(running, ASK_AT_ONCE, async (b) => {
|
|
1533
|
+
const who = { name: b.name, owner: b.owner, ...b.ownerKind === void 0 ? {} : { ownerKind: b.ownerKind } };
|
|
1534
|
+
try {
|
|
1535
|
+
return { ...who, stats: (await client.machineStats(`${slugOf(b.owner)}/${b.name}`)).stats };
|
|
1536
|
+
} catch (err) {
|
|
1537
|
+
return { ...who, stats: null, error: err.message };
|
|
1538
|
+
}
|
|
1539
|
+
});
|
|
1540
|
+
if (entries.length > 0 && entries.every((e) => e.error !== void 0)) process.exitCode = 1;
|
|
1541
|
+
if (opts.json) {
|
|
1542
|
+
json(entries);
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
if (entries.length === 0) {
|
|
1546
|
+
info("no running boxes");
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
table(topRows(entries, client.target.kind === "cloud" ? { me: client.target.user } : void 0));
|
|
1550
|
+
for (const e of entries) if (e.error !== void 0) info(color.dim(`${e.name}: ${e.error}`));
|
|
1551
|
+
if (entries.some((e) => e.stats === null && e.error === void 0)) {
|
|
1552
|
+
info(color.dim("-: the box's image is older than base v0.7.0; kvbox vm update <box> moves it onto the current one"));
|
|
1553
|
+
}
|
|
1554
|
+
} finally {
|
|
1555
|
+
await client.close();
|
|
1556
|
+
}
|
|
1557
|
+
});
|
|
1558
|
+
var ASK_AT_ONCE = 8;
|
|
1559
|
+
async function mapLimit(items, limit, fn) {
|
|
1560
|
+
const out = new Array(items.length);
|
|
1561
|
+
let next = 0;
|
|
1562
|
+
const worker = async () => {
|
|
1563
|
+
while (next < items.length) {
|
|
1564
|
+
const i = next++;
|
|
1565
|
+
out[i] = await fn(items[i]);
|
|
1566
|
+
}
|
|
1567
|
+
};
|
|
1568
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
1569
|
+
return out;
|
|
1570
|
+
}
|
|
1571
|
+
var pct = (used, total) => total > 0 ? color.dim(` ${Math.round(used / total * 100)}%`) : "";
|
|
1572
|
+
function topRows(entries, cloud) {
|
|
1573
|
+
return entries.map((e) => {
|
|
1574
|
+
const s = e.stats;
|
|
1575
|
+
return {
|
|
1576
|
+
...cloud ? { owner: ownerCell(e, cloud.me) } : {},
|
|
1577
|
+
name: boxName(e.name),
|
|
1578
|
+
load: s ? `${s.load[0].toFixed(2)} ${s.load[1].toFixed(2)} ${color.dim(`/${s.cpus}`)}` : "-",
|
|
1579
|
+
mem: s ? `${gbShort(s.memUsedMib * 1024 ** 2)}/${gbShort(s.memTotalMib * 1024 ** 2)}${pct(s.memUsedMib, s.memTotalMib)}` : "-",
|
|
1580
|
+
disk: s ? `${gbShort(s.diskUsedBytes)}/${gbShort(s.diskTotalBytes)}${pct(s.diskUsedBytes, s.diskTotalBytes)}` : "-",
|
|
1581
|
+
// duration() writes "-" for 0, which here would read as "no numbers".
|
|
1582
|
+
uptime: s ? s.uptimeSec >= 1 ? duration(s.uptimeSec) : "0s" : "-"
|
|
1583
|
+
};
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// src/commands/vm/update.ts
|
|
1588
|
+
import { Command as Command14 } from "commander";
|
|
1589
|
+
var updateCommand = new Command14("update").description("move a box onto the current base image (its files stay; a running box restarts)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
|
|
1497
1590
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1498
1591
|
try {
|
|
1499
|
-
const before = pickRef(await client
|
|
1592
|
+
const before = pickRef(await boxesFor(client, name), name, me(client));
|
|
1500
1593
|
if (!before) throw new KvboxError(`box ${name}: not found (kvbox ls)`);
|
|
1501
1594
|
const box = await spin(`updating ${name}\u2026`, client.updateMachine(displayName(before, me(client))));
|
|
1502
1595
|
if (opts.json) {
|
|
@@ -1515,8 +1608,8 @@ var updateCommand = new Command13("update").description("move a box onto the cur
|
|
|
1515
1608
|
});
|
|
1516
1609
|
|
|
1517
1610
|
// src/commands/vm/usage.ts
|
|
1518
|
-
import { Command as
|
|
1519
|
-
var usageCommand = new
|
|
1611
|
+
import { Command as Command15 } from "commander";
|
|
1612
|
+
var usageCommand = new Command15("usage").description("how long a box ran, slept and sat stopped, as vCPU-hours, RAM and disk GiB-hours (and cost, when the host prices them)").argument("<name>", "box name").option("--since <date>", "count from this date instead of the box's creation, e.g. 2026-10-01").option("--json", "print as JSON").action(async (name, opts, cmd) => {
|
|
1520
1613
|
let since;
|
|
1521
1614
|
if (opts.since !== void 0) {
|
|
1522
1615
|
const t = Date.parse(opts.since);
|
|
@@ -1563,8 +1656,8 @@ function block(u) {
|
|
|
1563
1656
|
}
|
|
1564
1657
|
|
|
1565
1658
|
// src/commands/vm/wake.ts
|
|
1566
|
-
import { Command as
|
|
1567
|
-
var wakeCommand = new
|
|
1659
|
+
import { Command as Command16 } from "commander";
|
|
1660
|
+
var wakeCommand = new Command16("wake").description("continue a hibernated box where it left off (same name, IP and disk)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
|
|
1568
1661
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1569
1662
|
try {
|
|
1570
1663
|
const box = await spin(`waking ${name} (loading its memory from disk)\u2026`, client.wakeMachine(name));
|
|
@@ -1576,15 +1669,15 @@ var wakeCommand = new Command15("wake").description("continue a hibernated box w
|
|
|
1576
1669
|
});
|
|
1577
1670
|
|
|
1578
1671
|
// src/commands/vm/index.ts
|
|
1579
|
-
var vmCommand = new
|
|
1672
|
+
var vmCommand = new Command17("vm").enablePositionalOptions().description("manage boxes (each one is a Firecracker microVM)").addCommand(newCommand).addCommand(lsCommand).addCommand(connectCommand).addCommand(execCommand).addCommand(stopCommand).addCommand(startCommand).addCommand(pauseCommand).addCommand(resumeCommand).addCommand(hibernateCommand).addCommand(wakeCommand).addCommand(updateCommand).addCommand(rescaleCommand).addCommand(setCommand).addCommand(usageCommand).addCommand(topCommand).addCommand(rmCommand);
|
|
1580
1673
|
for (const cmd of vmCommand.commands) cmd.option("--org <slug>", "act in this org (- for your personal boxes)");
|
|
1581
1674
|
var vmAliases = [newCommand, lsCommand, connectCommand, execCommand, stopCommand, startCommand, pauseCommand, resumeCommand, hibernateCommand, wakeCommand, rmCommand];
|
|
1582
1675
|
|
|
1583
1676
|
// src/commands/server/index.ts
|
|
1584
|
-
import { Command as
|
|
1677
|
+
import { Command as Command25 } from "commander";
|
|
1585
1678
|
|
|
1586
1679
|
// src/commands/server/join.ts
|
|
1587
|
-
import { Command as
|
|
1680
|
+
import { Command as Command18 } from "commander";
|
|
1588
1681
|
|
|
1589
1682
|
// src/commands/server/daemon.ts
|
|
1590
1683
|
import { spawn as spawn3 } from "child_process";
|
|
@@ -1638,7 +1731,7 @@ function runKvboxServer(args) {
|
|
|
1638
1731
|
}
|
|
1639
1732
|
|
|
1640
1733
|
// src/commands/server/join.ts
|
|
1641
|
-
var joinCommand = new
|
|
1734
|
+
var joinCommand = new Command18("join").description("put this machine behind a control plane (needs a one-time token minted there) and restart its server").argument("<control-plane-url>", "the control plane, e.g. https://api.kvbox.sh").requiredOption("--token <token>", "one-time join token from the control plane").option("--advertise <host-or-ip>", "address the control plane reaches this machine at (default: its default-route address)").option("--name <name>", "how the control plane lists this machine (default: hostname)").action(async (url, opts) => {
|
|
1642
1735
|
const args = ["join", url, "--token", opts.token];
|
|
1643
1736
|
if (opts.advertise !== void 0) args.push("--advertise", opts.advertise);
|
|
1644
1737
|
if (opts.name !== void 0) args.push("--name", opts.name);
|
|
@@ -1648,8 +1741,8 @@ var joinCommand = new Command17("join").description("put this machine behind a c
|
|
|
1648
1741
|
});
|
|
1649
1742
|
|
|
1650
1743
|
// src/commands/server/leave.ts
|
|
1651
|
-
import { Command as
|
|
1652
|
-
var leaveCommand = new
|
|
1744
|
+
import { Command as Command19 } from "commander";
|
|
1745
|
+
var leaveCommand = new Command19("leave").description("take this machine out from behind its control plane and restart its server").action(async () => {
|
|
1653
1746
|
const p = paths();
|
|
1654
1747
|
for (const line of (await runKvboxServer(["leave"])).split("\n")) info(line);
|
|
1655
1748
|
if (await runningPid(p) === null) return;
|
|
@@ -1658,14 +1751,14 @@ var leaveCommand = new Command18("leave").description("take this machine out fro
|
|
|
1658
1751
|
});
|
|
1659
1752
|
|
|
1660
1753
|
// src/commands/server/relay.ts
|
|
1661
|
-
import { Command as
|
|
1662
|
-
var relayCommand = new
|
|
1754
|
+
import { Command as Command20 } from "commander";
|
|
1755
|
+
var relayCommand = new Command20("relay").description("reach the internet through a relay (paste what `kvbox-relay add` printed on the relay VM); `off` takes the tunnel down").argument("<join-string>", "the line `kvbox-relay add` printed, or `off`").action(async (joinString) => {
|
|
1663
1756
|
for (const line of (await runKvboxServer(["relay", joinString])).split("\n")) info(line);
|
|
1664
1757
|
});
|
|
1665
1758
|
|
|
1666
1759
|
// src/commands/server/start.ts
|
|
1667
|
-
import { Command as
|
|
1668
|
-
var startCommand2 = new
|
|
1760
|
+
import { Command as Command21 } from "commander";
|
|
1761
|
+
var startCommand2 = new Command21("start").description("start the local kvbox-server in the background").action(async (_opts, cmd) => {
|
|
1669
1762
|
const client = await clientFor({ ...cmd.optsWithGlobals(), local: true }, { autoStart: false });
|
|
1670
1763
|
try {
|
|
1671
1764
|
const health = await client.health();
|
|
@@ -1681,8 +1774,8 @@ var startCommand2 = new Command20("start").description("start the local kvbox-se
|
|
|
1681
1774
|
});
|
|
1682
1775
|
|
|
1683
1776
|
// src/commands/server/status.ts
|
|
1684
|
-
import { Command as
|
|
1685
|
-
var statusCommand = new
|
|
1777
|
+
import { Command as Command22 } from "commander";
|
|
1778
|
+
var statusCommand = new Command22("status").description("show whether this machine's server is running, its control plane and which base image new boxes get").option("--json", "print as JSON").action(async (opts, cmd) => {
|
|
1686
1779
|
const client = await clientFor({ ...cmd.optsWithGlobals(), local: true });
|
|
1687
1780
|
try {
|
|
1688
1781
|
const health = await client.health();
|
|
@@ -1714,8 +1807,8 @@ var statusCommand = new Command21("status").description("show whether this machi
|
|
|
1714
1807
|
});
|
|
1715
1808
|
|
|
1716
1809
|
// src/commands/server/stop.ts
|
|
1717
|
-
import { Command as
|
|
1718
|
-
var stopCommand2 = new
|
|
1810
|
+
import { Command as Command23 } from "commander";
|
|
1811
|
+
var stopCommand2 = new Command23("stop").description("stop the local server (running boxes are not affected)").action(async () => {
|
|
1719
1812
|
const pid = await runningPid(paths());
|
|
1720
1813
|
if (pid === null) throw new KvboxError("kvbox-server is not running");
|
|
1721
1814
|
await stopLocalServer(pid);
|
|
@@ -1723,8 +1816,8 @@ var stopCommand2 = new Command22("stop").description("stop the local server (run
|
|
|
1723
1816
|
});
|
|
1724
1817
|
|
|
1725
1818
|
// src/commands/server/trust-ca.ts
|
|
1726
|
-
import { Command as
|
|
1727
|
-
var trustCaCommand = new
|
|
1819
|
+
import { Command as Command24 } from "commander";
|
|
1820
|
+
var trustCaCommand = new Command24("trust-ca").description("make every cloud box on this machine trust a new SSH CA (after `ca rotate` on the control plane)").argument("[public-key]", "the CA public key (or its cert-authority line); or --from-cloud").option("--from-cloud", "ask the control plane this host joined for its current CA, show the fingerprint, then ask").option("-y, --yes", "do not ask for confirmation").option("--json", "print the report as JSON").action(async (publicKey, opts, cmd) => {
|
|
1728
1821
|
if (publicKey === void 0 && !opts.fromCloud) throw new KvboxError("give the CA public key, or --from-cloud to fetch it from the control plane this host joined");
|
|
1729
1822
|
if (publicKey !== void 0 && opts.fromCloud) throw new KvboxError("either a public key or --from-cloud, not both");
|
|
1730
1823
|
const client = await clientFor({ ...cmd.optsWithGlobals(), local: true });
|
|
@@ -1762,7 +1855,7 @@ function describeReport(report) {
|
|
|
1762
1855
|
}
|
|
1763
1856
|
|
|
1764
1857
|
// src/commands/server/index.ts
|
|
1765
|
-
var serverCommand = new
|
|
1858
|
+
var serverCommand = new Command25("server").description("run or manage the kvbox server").addCommand(startCommand2).addCommand(statusCommand).addCommand(stopCommand2).addCommand(joinCommand).addCommand(leaveCommand).addCommand(relayCommand).addCommand(trustCaCommand);
|
|
1766
1859
|
|
|
1767
1860
|
// src/commands/login.ts
|
|
1768
1861
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
@@ -1770,8 +1863,8 @@ import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from
|
|
|
1770
1863
|
import { hostname as osHostname } from "os";
|
|
1771
1864
|
import { createInterface } from "readline/promises";
|
|
1772
1865
|
import { Writable } from "stream";
|
|
1773
|
-
import { Command as
|
|
1774
|
-
var loginCommand = new
|
|
1866
|
+
import { Command as Command26 } from "commander";
|
|
1867
|
+
var loginCommand = new Command26("login").description("log in to the kvbox cloud (or your own control plane) and target it from now on").argument("[url]", `control plane URL (default: ${DEFAULT_CLOUD_URL})`).option("-e, --email <email>", "account email (prompted otherwise)").option("--password-stdin", "read the password from the first line of stdin (for scripts)").action(async (urlArg, opts) => {
|
|
1775
1868
|
const url = normalizeUrl(urlArg ?? DEFAULT_CLOUD_URL);
|
|
1776
1869
|
const creds = await readCredentials(opts);
|
|
1777
1870
|
const session = await runLogin(url, creds, paths());
|
|
@@ -1869,8 +1962,8 @@ async function firstLine(stream) {
|
|
|
1869
1962
|
}
|
|
1870
1963
|
|
|
1871
1964
|
// src/commands/logout.ts
|
|
1872
|
-
import { Command as
|
|
1873
|
-
var logoutCommand = new
|
|
1965
|
+
import { Command as Command27 } from "commander";
|
|
1966
|
+
var logoutCommand = new Command27("logout").description("log out of the control plane; commands target this machine's kvbox-server again").action(async () => {
|
|
1874
1967
|
const p = paths();
|
|
1875
1968
|
const session = await readSession(p).catch(() => null);
|
|
1876
1969
|
switch (await runLogout(p)) {
|
|
@@ -1907,8 +2000,8 @@ async function runLogout(p = paths()) {
|
|
|
1907
2000
|
}
|
|
1908
2001
|
|
|
1909
2002
|
// src/commands/whoami.ts
|
|
1910
|
-
import { Command as
|
|
1911
|
-
var whoamiCommand = new
|
|
2003
|
+
import { Command as Command28 } from "commander";
|
|
2004
|
+
var whoamiCommand = new Command28("whoami").description("show which account and control plane commands target (or local mode)").option("--json", "print as JSON").action(async (opts) => {
|
|
1912
2005
|
const session = await readSession();
|
|
1913
2006
|
if (opts.json) {
|
|
1914
2007
|
json(session ? { user: session.user, url: session.url, sshCertExpires: session.sshCertExpires ?? null } : null);
|
|
@@ -1924,15 +2017,15 @@ function describeSession(session) {
|
|
|
1924
2017
|
}
|
|
1925
2018
|
|
|
1926
2019
|
// src/commands/snapshots/index.ts
|
|
1927
|
-
import { Command as
|
|
2020
|
+
import { Command as Command32 } from "commander";
|
|
1928
2021
|
|
|
1929
2022
|
// src/commands/snapshots/ls.ts
|
|
1930
|
-
import { Command as
|
|
1931
|
-
var lsCommand2 = new
|
|
2023
|
+
import { Command as Command29 } from "commander";
|
|
2024
|
+
var lsCommand2 = new Command29("ls").description("list snapshots (yours and your orgs'; --org <slug> for one org)").option("-a, --all", "every snapshot on the platform (platform admins only)").option("--json", "print as JSON").action(async (opts, cmd) => {
|
|
1932
2025
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1933
2026
|
try {
|
|
1934
2027
|
const cloud = client.target.kind === "cloud";
|
|
1935
|
-
const snaps = await client.listSnapshots();
|
|
2028
|
+
const snaps = await client.listSnapshots({ all: opts.all });
|
|
1936
2029
|
if (opts.json) {
|
|
1937
2030
|
json(snaps);
|
|
1938
2031
|
return;
|
|
@@ -1959,8 +2052,8 @@ var lsCommand2 = new Command28("ls").description("list snapshots (yours and your
|
|
|
1959
2052
|
});
|
|
1960
2053
|
|
|
1961
2054
|
// src/commands/snapshots/new.ts
|
|
1962
|
-
import { Command as
|
|
1963
|
-
var newCommand2 = new
|
|
2055
|
+
import { Command as Command30 } from "commander";
|
|
2056
|
+
var newCommand2 = new Command30("new").description("freeze a running box (memory + disk) into a snapshot; the box keeps running").argument("<box>", "box name").argument("<name>", "snapshot name").option("--json", "print the snapshot as JSON").action(async (box, name, opts, cmd) => {
|
|
1964
2057
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1965
2058
|
try {
|
|
1966
2059
|
const snap = await spin(`saving snapshot ${name} from box ${box} (memory + disk)\u2026`, client.createSnapshot({ box, name }));
|
|
@@ -1972,8 +2065,8 @@ var newCommand2 = new Command29("new").description("freeze a running box (memory
|
|
|
1972
2065
|
});
|
|
1973
2066
|
|
|
1974
2067
|
// src/commands/snapshots/rm.ts
|
|
1975
|
-
import { Command as
|
|
1976
|
-
var rmCommand2 = new
|
|
2068
|
+
import { Command as Command31 } from "commander";
|
|
2069
|
+
var rmCommand2 = new Command31("rm").description("delete a snapshot (boxes made from it are not affected)").argument("<name>", "snapshot name").option("-y, --yes", "do not ask for confirmation").action(async (name, opts, cmd) => {
|
|
1977
2070
|
if (!await confirm(`Delete snapshot ${name}?`, opts.yes ?? false)) {
|
|
1978
2071
|
info("cancelled");
|
|
1979
2072
|
process.exitCode = 1;
|
|
@@ -1989,14 +2082,14 @@ var rmCommand2 = new Command30("rm").description("delete a snapshot (boxes made
|
|
|
1989
2082
|
});
|
|
1990
2083
|
|
|
1991
2084
|
// src/commands/snapshots/index.ts
|
|
1992
|
-
var snapshotsCommand = new
|
|
2085
|
+
var snapshotsCommand = new Command32("snapshots").aliases(["snapshot", "snap"]).description("freeze running boxes into snapshots and manage them").addCommand(newCommand2).addCommand(lsCommand2).addCommand(rmCommand2);
|
|
1993
2086
|
for (const cmd of snapshotsCommand.commands) cmd.option("--org <slug>", "act in this org (- for your personal boxes)");
|
|
1994
2087
|
|
|
1995
2088
|
// src/commands/org/index.ts
|
|
1996
|
-
import { Command as
|
|
2089
|
+
import { Command as Command38 } from "commander";
|
|
1997
2090
|
|
|
1998
2091
|
// src/commands/org/ls.ts
|
|
1999
|
-
import { Command as
|
|
2092
|
+
import { Command as Command33 } from "commander";
|
|
2000
2093
|
|
|
2001
2094
|
// src/commands/org/context.ts
|
|
2002
2095
|
var NOT_LOGGED_IN = "orgs live on the control plane: kvbox login first";
|
|
@@ -2016,7 +2109,7 @@ function orgFor(opts, target) {
|
|
|
2016
2109
|
}
|
|
2017
2110
|
|
|
2018
2111
|
// src/commands/org/ls.ts
|
|
2019
|
-
var lsCommand3 = new
|
|
2112
|
+
var lsCommand3 = new Command33("ls").description("list the orgs you belong to and your role in each").option("--json", "print as JSON").action(async (opts, cmd) => {
|
|
2020
2113
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
2021
2114
|
try {
|
|
2022
2115
|
const orgs = await client.listOrgs();
|
|
@@ -2038,8 +2131,8 @@ function orgRows(orgs) {
|
|
|
2038
2131
|
}
|
|
2039
2132
|
|
|
2040
2133
|
// src/commands/org/members.ts
|
|
2041
|
-
import { Command as
|
|
2042
|
-
var lsCommand4 = new
|
|
2134
|
+
import { Command as Command34, Option as Option3 } from "commander";
|
|
2135
|
+
var lsCommand4 = new Command34("ls").description("list an org's members and their roles").option("-o, --org <slug>", "the org (or --org before the command)").option("--json", "print as JSON").action(async (opts, cmd) => {
|
|
2043
2136
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
2044
2137
|
try {
|
|
2045
2138
|
const org = await client.getOrg(orgFor(opts, client.target));
|
|
@@ -2053,7 +2146,7 @@ var lsCommand4 = new Command33("ls").description("list an org's members and thei
|
|
|
2053
2146
|
await client.close();
|
|
2054
2147
|
}
|
|
2055
2148
|
});
|
|
2056
|
-
var addCommand = new
|
|
2149
|
+
var addCommand = new Command34("add").description("add a user to an org (admins and owners; only an owner can add an owner)").argument("<user>", "their username").addOption(new Option3("-r, --role <role>", "their role").choices(ORG_ROLES).default("member")).option("-o, --org <slug>", "the org (or --org before the command)").action(async (user, opts, cmd) => {
|
|
2057
2150
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
2058
2151
|
try {
|
|
2059
2152
|
const slug = orgFor(opts, client.target);
|
|
@@ -2063,7 +2156,7 @@ var addCommand = new Command33("add").description("add a user to an org (admins
|
|
|
2063
2156
|
await client.close();
|
|
2064
2157
|
}
|
|
2065
2158
|
});
|
|
2066
|
-
var rmCommand3 = new
|
|
2159
|
+
var rmCommand3 = new Command34("rm").description("remove a member (or leave: remove yourself)").argument("<user>", "their username").option("-o, --org <slug>", "the org (or --org before the command)").option("-y, --yes", "do not ask for confirmation").action(async (user, opts, cmd) => {
|
|
2067
2160
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
2068
2161
|
try {
|
|
2069
2162
|
const slug = orgFor(opts, client.target);
|
|
@@ -2079,11 +2172,11 @@ var rmCommand3 = new Command33("rm").description("remove a member (or leave: rem
|
|
|
2079
2172
|
await client.close();
|
|
2080
2173
|
}
|
|
2081
2174
|
});
|
|
2082
|
-
var membersCommand = new
|
|
2175
|
+
var membersCommand = new Command34("members").description("who is in an org and what they may do").addCommand(lsCommand4).addCommand(addCommand).addCommand(rmCommand3);
|
|
2083
2176
|
|
|
2084
2177
|
// src/commands/org/new.ts
|
|
2085
|
-
import { Command as
|
|
2086
|
-
var newCommand3 = new
|
|
2178
|
+
import { Command as Command35 } from "commander";
|
|
2179
|
+
var newCommand3 = new Command35("new").description("create an org; you become its owner (the slug is permanent, the name is not)").argument("<slug>", "the org's slug: lowercase, like a username (acme)").option("-n, --name <name>", "display name (default: the slug)").option("--json", "print the org as JSON").action(async (slug, opts, cmd) => {
|
|
2087
2180
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
2088
2181
|
try {
|
|
2089
2182
|
const org = await client.createOrg({ slug, name: opts.name ?? slug });
|
|
@@ -2099,8 +2192,8 @@ var newCommand3 = new Command34("new").description("create an org; you become it
|
|
|
2099
2192
|
});
|
|
2100
2193
|
|
|
2101
2194
|
// src/commands/org/rm.ts
|
|
2102
|
-
import { Command as
|
|
2103
|
-
var rmCommand4 = new
|
|
2195
|
+
import { Command as Command36 } from "commander";
|
|
2196
|
+
var rmCommand4 = new Command36("rm").description("delete an org (owners only; remove its boxes and snapshots first)").argument("<slug>", "the org").option("-y, --yes", "do not ask for confirmation").action(async (slug, opts, cmd) => {
|
|
2104
2197
|
if (!await confirm(`Delete org ${slug}? Its members lose access; its slug stays taken.`, opts.yes ?? false)) {
|
|
2105
2198
|
info("cancelled");
|
|
2106
2199
|
process.exitCode = 1;
|
|
@@ -2116,8 +2209,8 @@ var rmCommand4 = new Command35("rm").description("delete an org (owners only; re
|
|
|
2116
2209
|
});
|
|
2117
2210
|
|
|
2118
2211
|
// src/commands/org/role.ts
|
|
2119
|
-
import { Argument, Command as
|
|
2120
|
-
var roleCommand = new
|
|
2212
|
+
import { Argument, Command as Command37 } from "commander";
|
|
2213
|
+
var roleCommand = new Command37("role").description("change a member's role (owners only)").argument("<user>", "their username").addArgument(new Argument("<role>", "owner, admin or member").choices(ORG_ROLES)).option("-o, --org <slug>", "the org (default: the current context)").action(async (user, role, opts, cmd) => {
|
|
2121
2214
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
2122
2215
|
try {
|
|
2123
2216
|
const slug = orgFor(opts, client.target);
|
|
@@ -2129,11 +2222,11 @@ var roleCommand = new Command36("role").description("change a member's role (own
|
|
|
2129
2222
|
});
|
|
2130
2223
|
|
|
2131
2224
|
// src/commands/org/index.ts
|
|
2132
|
-
var orgCommand = new
|
|
2225
|
+
var orgCommand = new Command38("org").description("orgs: shared slugs that own boxes, with owners, admins and members").addCommand(lsCommand3).addCommand(newCommand3).addCommand(membersCommand).addCommand(roleCommand).addCommand(rmCommand4);
|
|
2133
2226
|
|
|
2134
2227
|
// src/commands/usage.ts
|
|
2135
|
-
import { Command as
|
|
2136
|
-
var usageCommand2 = new
|
|
2228
|
+
import { Command as Command39 } from "commander";
|
|
2229
|
+
var usageCommand2 = new Command39("usage").description("what your boxes used this month (or --since/--until): running time, vCPU-h, RAM and disk GiB-h, cost").option("--since <date>", "count from this date, e.g. 2026-10-01 (default: the 1st of this month)").option("--until <date>", "count up to this date (default: now)").option("--org <slug>", "one org's boxes only").option("-a, --all", "every box on the platform (platform admins only)").option("--json", "print as JSON").action(async (opts, cmd) => {
|
|
2137
2230
|
for (const [name, value] of [
|
|
2138
2231
|
["since", opts.since],
|
|
2139
2232
|
["until", opts.until]
|
|
@@ -2143,7 +2236,7 @@ var usageCommand2 = new Command38("usage").description("what your boxes used thi
|
|
|
2143
2236
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
2144
2237
|
try {
|
|
2145
2238
|
if (client.target.kind === "local" && cmd.optsWithGlobals().org) throw new KvboxError("--org needs a control plane: kvbox login, or drop --org");
|
|
2146
|
-
const report = await client.usageReport(opts.since, opts.until);
|
|
2239
|
+
const report = await client.usageReport(opts.since, opts.until, opts.all);
|
|
2147
2240
|
if (opts.json) {
|
|
2148
2241
|
json(report);
|
|
2149
2242
|
return;
|
|
@@ -2206,8 +2299,8 @@ function usageRows(report, cloud) {
|
|
|
2206
2299
|
}
|
|
2207
2300
|
|
|
2208
2301
|
// src/commands/credit.ts
|
|
2209
|
-
import { Command as
|
|
2210
|
-
var creditCommand = new
|
|
2302
|
+
import { Command as Command40 } from "commander";
|
|
2303
|
+
var creditCommand = new Command40("credit").description("the prepaid credit left on your account and on each of your orgs").option("--org <slug>", "one org only").option("--json", "print as JSON").action(async (opts, cmd) => {
|
|
2211
2304
|
const client = await clientFor(cmd.optsWithGlobals(), { autoStart: false });
|
|
2212
2305
|
try {
|
|
2213
2306
|
if (client.target.kind === "local") throw new KvboxError("credit lives on the control plane: kvbox login first");
|
|
@@ -2236,7 +2329,7 @@ function creditRow(b, me2) {
|
|
|
2236
2329
|
|
|
2237
2330
|
// src/program.ts
|
|
2238
2331
|
function buildProgram() {
|
|
2239
|
-
const program = new
|
|
2332
|
+
const program = new Command41("kvbox").description("self-hosted Firecracker microVMs with Docker-like ergonomics").version(VERSION, "-v, --version").option("--local", "use this machine's kvbox-server over its socket, ignoring kvbox login").option("--org <slug>", "the org: where kvbox new creates, and which box a name means when it is not one of yours; before the command here, or after it").showHelpAfterError().enablePositionalOptions();
|
|
2240
2333
|
program.addCommand(vmCommand);
|
|
2241
2334
|
program.addCommand(snapshotsCommand);
|
|
2242
2335
|
program.addCommand(serverCommand);
|