@kvbox/cli 0.5.9 → 0.5.11
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 +209 -69
- 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.11" : 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";
|
|
@@ -417,7 +417,9 @@ var MachineSchema = z2.object({
|
|
|
417
417
|
*/
|
|
418
418
|
ssh: z2.object({ host: z2.string(), port: z2.number().int(), user: z2.string() }).optional(),
|
|
419
419
|
/** Whether the owner slug is a user or an org. Only the control plane knows; it fills it in on `GET /machines`. */
|
|
420
|
-
ownerKind: z2.enum(["user", "org"]).optional()
|
|
420
|
+
ownerKind: z2.enum(["user", "org"]).optional(),
|
|
421
|
+
/** Seconds without network traffic before the host hibernates the box (#47). Absent: never. */
|
|
422
|
+
autoHibernateSec: z2.number().int().positive().optional()
|
|
421
423
|
});
|
|
422
424
|
var CreateMachineRequest = z2.object({
|
|
423
425
|
name: z2.string().optional(),
|
|
@@ -431,7 +433,9 @@ var CreateMachineRequest = z2.object({
|
|
|
431
433
|
* its SSH CA as one `cert-authority …` line (#51). Written into the box's authorized_keys at every
|
|
432
434
|
* boot (#49); a host older than that ignores them.
|
|
433
435
|
*/
|
|
434
|
-
sshKeys: z2.array(z2.string()).optional()
|
|
436
|
+
sshKeys: z2.array(z2.string()).optional(),
|
|
437
|
+
/** Seconds without network traffic before the box is hibernated (#47); 0: never. Absent: the host's default. */
|
|
438
|
+
autoHibernateSec: z2.number().int().min(0).optional()
|
|
435
439
|
});
|
|
436
440
|
var SetSshKeysRequest = z2.object({
|
|
437
441
|
sshKeys: z2.array(z2.string())
|
|
@@ -442,6 +446,10 @@ var RescaleMachineRequest = z2.object({
|
|
|
442
446
|
memMib: z2.number().int().min(256).optional(),
|
|
443
447
|
diskGib: z2.number().int().min(1).optional()
|
|
444
448
|
});
|
|
449
|
+
var SetMachineRequest = z2.object({
|
|
450
|
+
/** Seconds without network traffic before the box is hibernated (#47); 0: never. */
|
|
451
|
+
autoHibernateSec: z2.number().int().min(0).optional()
|
|
452
|
+
});
|
|
445
453
|
var SnapshotSchema = z2.object({
|
|
446
454
|
name: z2.string(),
|
|
447
455
|
owner: z2.string(),
|
|
@@ -494,7 +502,10 @@ var HealthSchema = z2.object({
|
|
|
494
502
|
/** The public IP the relay owns for this host. */
|
|
495
503
|
publicIp: z2.string(),
|
|
496
504
|
/** The relay's WireGuard endpoint the tunnel dials. */
|
|
497
|
-
endpoint: z2.string()
|
|
505
|
+
endpoint: z2.string(),
|
|
506
|
+
/** Times the server redialed the tunnel since it started, because the relay stopped answering. Older servers omit them. */
|
|
507
|
+
restarts: z2.number().int().optional(),
|
|
508
|
+
lastRestartAt: z2.string().nullable().optional()
|
|
498
509
|
}).nullable().optional(),
|
|
499
510
|
/** Boxes brought back at the server's last start; null when there were none or it is still at it. Older servers omit it. */
|
|
500
511
|
recovery: RecoveryReportSchema.nullable().optional()
|
|
@@ -518,6 +529,23 @@ var MachineUsageSchema = z2.object({
|
|
|
518
529
|
estimatedStops: z2.number().int(),
|
|
519
530
|
historyFrom: z2.string().nullable()
|
|
520
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
|
+
});
|
|
521
549
|
var UsageStatusSchema = z2.enum([...BoxStatusSchema.options, "removed"]);
|
|
522
550
|
var BoxUsageSchema = MachineUsageSchema.omit({ window: true, status: true }).extend({
|
|
523
551
|
/** The host's box id; a name removed and recreated appears twice, with two ids. */
|
|
@@ -889,6 +917,13 @@ var ServerClient = class {
|
|
|
889
917
|
rescaleMachine(name, req) {
|
|
890
918
|
return this.call("POST", `/machines/${encodeURIComponent(name)}/rescale`, MachineSchema, req);
|
|
891
919
|
}
|
|
920
|
+
setMachine(name, req) {
|
|
921
|
+
return this.call("POST", `/machines/${encodeURIComponent(name)}/set`, MachineSchema, req);
|
|
922
|
+
}
|
|
923
|
+
/** A running box's own load, memory and disk, asked of its guest (#67). */
|
|
924
|
+
machineStats(name) {
|
|
925
|
+
return this.call("GET", `/machines/${encodeURIComponent(name)}/stats`, MachineStatsSchema);
|
|
926
|
+
}
|
|
892
927
|
/** `since`: an ISO date, already validated; the server counts from the box's creation without it. */
|
|
893
928
|
machineUsage(name, since) {
|
|
894
929
|
const query = since === void 0 ? "" : `?since=${encodeURIComponent(since)}`;
|
|
@@ -1015,6 +1050,9 @@ function sshArgs(ep, p = paths()) {
|
|
|
1015
1050
|
...hostKey,
|
|
1016
1051
|
"-o",
|
|
1017
1052
|
"LogLevel=ERROR",
|
|
1053
|
+
// A quiet open session still moves a few bytes a minute, so the host's auto-hibernate (#47) sees it.
|
|
1054
|
+
"-o",
|
|
1055
|
+
"ServerAliveInterval=60",
|
|
1018
1056
|
"-o",
|
|
1019
1057
|
"SetEnv=LC_ALL=C.UTF-8",
|
|
1020
1058
|
`${ep.user}@${ep.host}`
|
|
@@ -1106,7 +1144,8 @@ async function serving(p, timeoutMs, check) {
|
|
|
1106
1144
|
|
|
1107
1145
|
// src/commands/vm/new.ts
|
|
1108
1146
|
import { Command, Option } from "commander";
|
|
1109
|
-
var newCommand = new Command("new").description("boot a new box from the base image or a snapshot").argument("[name]", "box name (box1, box2, \u2026 when omitted)").addOption(new Option("-s, --size <size>", "box size (default: server config, s)").choices(SIZE_NAMES)).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(async (name, opts, cmd) => {
|
|
1147
|
+
var newCommand = new Command("new").description("boot a new box from the base image or a snapshot").argument("[name]", "box name (box1, box2, \u2026 when omitted)").addOption(new Option("-s, --size <size>", "box size (default: server config, s)").choices(SIZE_NAMES)).option("-f, --from <snapshot>", "start from a snapshot instead of the base image").option("--auto-hibernate <duration>", "hibernate the box after this long without network traffic (30m, 4h, 1d; off: never; default: server config, off)").option("--force", "skip the capacity guard").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
|
|
1148
|
+
const autoHibernateSec = opts.autoHibernate === void 0 ? void 0 : parseIdleTimeout(opts.autoHibernate);
|
|
1110
1149
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1111
1150
|
try {
|
|
1112
1151
|
const what = opts.from !== void 0 ? `restoring ${name ?? "a new box"} from snapshot ${opts.from}\u2026` : `booting ${name ?? "a new box"}\u2026`;
|
|
@@ -1114,8 +1153,10 @@ var newCommand = new Command("new").description("boot a new box from the base im
|
|
|
1114
1153
|
...name !== void 0 ? { name } : {},
|
|
1115
1154
|
...opts.size !== void 0 ? { size: opts.size } : {},
|
|
1116
1155
|
...opts.from !== void 0 ? { from: opts.from } : {},
|
|
1156
|
+
...autoHibernateSec !== void 0 ? { autoHibernateSec } : {},
|
|
1117
1157
|
...opts.force ? { force: true } : {}
|
|
1118
1158
|
}));
|
|
1159
|
+
if (autoHibernateSec && !box.autoHibernateSec) warn(`box ${name ?? box.name}: its host runs a kvbox-server too old for auto-hibernate; it will not hibernate on its own`);
|
|
1119
1160
|
if (opts.json) {
|
|
1120
1161
|
json(box);
|
|
1121
1162
|
return;
|
|
@@ -1126,6 +1167,7 @@ var newCommand = new Command("new").description("boot a new box from the base im
|
|
|
1126
1167
|
info(`${color.green("\u2713")} ${describe(box, user)}`);
|
|
1127
1168
|
if (opts.from !== void 0) info(` connect with: kvbox connect ${displayName(box, user)} (already up: everything that ran in the snapshot is running)`);
|
|
1128
1169
|
else info(` connect with: kvbox connect ${displayName(box, user)} (SSH is up a couple of seconds after boot)`);
|
|
1170
|
+
if (box.autoHibernateSec) info(` it ${autoHibernateText(box.autoHibernateSec)}`);
|
|
1129
1171
|
} finally {
|
|
1130
1172
|
await client.close();
|
|
1131
1173
|
}
|
|
@@ -1137,6 +1179,18 @@ function describe(box, user) {
|
|
|
1137
1179
|
const from = box.source.startsWith("snapshot:") ? ` from snapshot ${shortRef(box.source.slice("snapshot:".length), box.owner)}` : "";
|
|
1138
1180
|
return `box ${color.bold(displayName(box, user))} is ${box.status} at ${box.ip}${from}: ${box.vcpu} vCPU, ${gb(box.memMib * 1024 ** 2)} RAM, ${gb(box.diskBytes)} disk`;
|
|
1139
1181
|
}
|
|
1182
|
+
function autoHibernateText(sec) {
|
|
1183
|
+
return sec ? `hibernates after ${duration(sec)} without network traffic (kvbox connect wakes it)` : "never hibernates on its own";
|
|
1184
|
+
}
|
|
1185
|
+
var UNIT_SEC = { m: 60, h: 3600, d: 86400 };
|
|
1186
|
+
function parseIdleTimeout(text) {
|
|
1187
|
+
const t = text.trim().toLowerCase();
|
|
1188
|
+
if (t === "off" || t === "never" || t === "0") return 0;
|
|
1189
|
+
if (!/^(\d+[mhd])+$/.test(t)) throw new KvboxError(`--auto-hibernate ${text}: give a duration like 30m, 4h or 1d, or off`);
|
|
1190
|
+
let sec = 0;
|
|
1191
|
+
for (const [, n, unit] of t.matchAll(/(\d+)([mhd])/g)) sec += Number(n) * UNIT_SEC[unit];
|
|
1192
|
+
return sec;
|
|
1193
|
+
}
|
|
1140
1194
|
|
|
1141
1195
|
// src/commands/vm/connect.ts
|
|
1142
1196
|
var SSH_FAILED = 255;
|
|
@@ -1412,9 +1466,29 @@ var rmCommand = new Command9("rm").description("destroy a box (its disk is delet
|
|
|
1412
1466
|
}
|
|
1413
1467
|
});
|
|
1414
1468
|
|
|
1415
|
-
// src/commands/vm/
|
|
1469
|
+
// src/commands/vm/set.ts
|
|
1416
1470
|
import { Command as Command10 } from "commander";
|
|
1417
|
-
var
|
|
1471
|
+
var setCommand = new Command10("set").description("change a box's settings, e.g. --auto-hibernate 30m (off: never)").argument("<name>", "box name").option("--auto-hibernate <duration>", "hibernate the box after this long without network traffic (30m, 4h, 1d; off: never)").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
|
|
1472
|
+
if (opts.autoHibernate === void 0) throw new KvboxError("nothing to change: give --auto-hibernate");
|
|
1473
|
+
const autoHibernateSec = parseIdleTimeout(opts.autoHibernate);
|
|
1474
|
+
const client = await clientFor(cmd.optsWithGlobals());
|
|
1475
|
+
try {
|
|
1476
|
+
const found = pickRef(await client.listMachines(), name, me(client));
|
|
1477
|
+
if (!found) throw new KvboxError(`box ${name}: not found (kvbox ls)`);
|
|
1478
|
+
const box = await spin(`updating ${name}\u2026`, client.setMachine(displayName(found, me(client)), { autoHibernateSec }));
|
|
1479
|
+
if (opts.json) {
|
|
1480
|
+
json(box);
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
info(`${color.green("\u2713")} box ${color.bold(name)}: ${autoHibernateText(box.autoHibernateSec)}`);
|
|
1484
|
+
} finally {
|
|
1485
|
+
await client.close();
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
// src/commands/vm/start.ts
|
|
1490
|
+
import { Command as Command11 } from "commander";
|
|
1491
|
+
var startCommand = new Command11("start").description("boot a stopped box again (same name, IP and disk)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
|
|
1418
1492
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1419
1493
|
try {
|
|
1420
1494
|
const box = await spin(`booting ${name}\u2026`, client.startMachine(name));
|
|
@@ -1426,8 +1500,8 @@ var startCommand = new Command10("start").description("boot a stopped box again
|
|
|
1426
1500
|
});
|
|
1427
1501
|
|
|
1428
1502
|
// src/commands/vm/stop.ts
|
|
1429
|
-
import { Command as
|
|
1430
|
-
var stopCommand = new
|
|
1503
|
+
import { Command as Command12 } from "commander";
|
|
1504
|
+
var stopCommand = new Command12("stop").description("shut a box down (its disk is kept; start it again with kvbox start)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
|
|
1431
1505
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1432
1506
|
try {
|
|
1433
1507
|
const box = await spin(`shutting ${name} down\u2026`, client.stopMachine(name));
|
|
@@ -1438,9 +1512,70 @@ var stopCommand = new Command11("stop").description("shut a box down (its disk i
|
|
|
1438
1512
|
}
|
|
1439
1513
|
});
|
|
1440
1514
|
|
|
1515
|
+
// src/commands/vm/top.ts
|
|
1516
|
+
import { Command as Command13 } from "commander";
|
|
1517
|
+
var topCommand = new Command13("top").description("load, memory and disk of each running box, as the box itself sees them").option("--json", "print as JSON").action(async (opts, cmd) => {
|
|
1518
|
+
const client = await clientFor(cmd.optsWithGlobals());
|
|
1519
|
+
try {
|
|
1520
|
+
const running = (await client.listMachines()).filter((b) => b.status === "running");
|
|
1521
|
+
const entries = await mapLimit(running, ASK_AT_ONCE, async (b) => {
|
|
1522
|
+
const who = { name: b.name, owner: b.owner, ...b.ownerKind === void 0 ? {} : { ownerKind: b.ownerKind } };
|
|
1523
|
+
try {
|
|
1524
|
+
return { ...who, stats: (await client.machineStats(`${slugOf(b.owner)}/${b.name}`)).stats };
|
|
1525
|
+
} catch (err) {
|
|
1526
|
+
return { ...who, stats: null, error: err.message };
|
|
1527
|
+
}
|
|
1528
|
+
});
|
|
1529
|
+
if (entries.length > 0 && entries.every((e) => e.error !== void 0)) process.exitCode = 1;
|
|
1530
|
+
if (opts.json) {
|
|
1531
|
+
json(entries);
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
if (entries.length === 0) {
|
|
1535
|
+
info("no running boxes");
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
table(topRows(entries, client.target.kind === "cloud" ? { me: client.target.user } : void 0));
|
|
1539
|
+
for (const e of entries) if (e.error !== void 0) info(color.dim(`${e.name}: ${e.error}`));
|
|
1540
|
+
if (entries.some((e) => e.stats === null && e.error === void 0)) {
|
|
1541
|
+
info(color.dim("-: the box's image is older than base v0.7.0; kvbox vm update <box> moves it onto the current one"));
|
|
1542
|
+
}
|
|
1543
|
+
} finally {
|
|
1544
|
+
await client.close();
|
|
1545
|
+
}
|
|
1546
|
+
});
|
|
1547
|
+
var ASK_AT_ONCE = 8;
|
|
1548
|
+
async function mapLimit(items, limit, fn) {
|
|
1549
|
+
const out = new Array(items.length);
|
|
1550
|
+
let next = 0;
|
|
1551
|
+
const worker = async () => {
|
|
1552
|
+
while (next < items.length) {
|
|
1553
|
+
const i = next++;
|
|
1554
|
+
out[i] = await fn(items[i]);
|
|
1555
|
+
}
|
|
1556
|
+
};
|
|
1557
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
1558
|
+
return out;
|
|
1559
|
+
}
|
|
1560
|
+
var pct = (used, total) => total > 0 ? color.dim(` ${Math.round(used / total * 100)}%`) : "";
|
|
1561
|
+
function topRows(entries, cloud) {
|
|
1562
|
+
return entries.map((e) => {
|
|
1563
|
+
const s = e.stats;
|
|
1564
|
+
return {
|
|
1565
|
+
...cloud ? { owner: ownerCell(e, cloud.me) } : {},
|
|
1566
|
+
name: boxName(e.name),
|
|
1567
|
+
load: s ? `${s.load[0].toFixed(2)} ${s.load[1].toFixed(2)} ${color.dim(`/${s.cpus}`)}` : "-",
|
|
1568
|
+
mem: s ? `${gbShort(s.memUsedMib * 1024 ** 2)}/${gbShort(s.memTotalMib * 1024 ** 2)}${pct(s.memUsedMib, s.memTotalMib)}` : "-",
|
|
1569
|
+
disk: s ? `${gbShort(s.diskUsedBytes)}/${gbShort(s.diskTotalBytes)}${pct(s.diskUsedBytes, s.diskTotalBytes)}` : "-",
|
|
1570
|
+
// duration() writes "-" for 0, which here would read as "no numbers".
|
|
1571
|
+
uptime: s ? s.uptimeSec >= 1 ? duration(s.uptimeSec) : "0s" : "-"
|
|
1572
|
+
};
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1441
1576
|
// src/commands/vm/update.ts
|
|
1442
|
-
import { Command as
|
|
1443
|
-
var updateCommand = new
|
|
1577
|
+
import { Command as Command14 } from "commander";
|
|
1578
|
+
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) => {
|
|
1444
1579
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1445
1580
|
try {
|
|
1446
1581
|
const before = pickRef(await client.listMachines(), name, me(client));
|
|
@@ -1462,8 +1597,8 @@ var updateCommand = new Command12("update").description("move a box onto the cur
|
|
|
1462
1597
|
});
|
|
1463
1598
|
|
|
1464
1599
|
// src/commands/vm/usage.ts
|
|
1465
|
-
import { Command as
|
|
1466
|
-
var usageCommand = new
|
|
1600
|
+
import { Command as Command15 } from "commander";
|
|
1601
|
+
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) => {
|
|
1467
1602
|
let since;
|
|
1468
1603
|
if (opts.since !== void 0) {
|
|
1469
1604
|
const t = Date.parse(opts.since);
|
|
@@ -1510,8 +1645,8 @@ function block(u) {
|
|
|
1510
1645
|
}
|
|
1511
1646
|
|
|
1512
1647
|
// src/commands/vm/wake.ts
|
|
1513
|
-
import { Command as
|
|
1514
|
-
var wakeCommand = new
|
|
1648
|
+
import { Command as Command16 } from "commander";
|
|
1649
|
+
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) => {
|
|
1515
1650
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1516
1651
|
try {
|
|
1517
1652
|
const box = await spin(`waking ${name} (loading its memory from disk)\u2026`, client.wakeMachine(name));
|
|
@@ -1523,15 +1658,15 @@ var wakeCommand = new Command14("wake").description("continue a hibernated box w
|
|
|
1523
1658
|
});
|
|
1524
1659
|
|
|
1525
1660
|
// src/commands/vm/index.ts
|
|
1526
|
-
var vmCommand = new
|
|
1661
|
+
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);
|
|
1527
1662
|
for (const cmd of vmCommand.commands) cmd.option("--org <slug>", "act in this org (- for your personal boxes)");
|
|
1528
1663
|
var vmAliases = [newCommand, lsCommand, connectCommand, execCommand, stopCommand, startCommand, pauseCommand, resumeCommand, hibernateCommand, wakeCommand, rmCommand];
|
|
1529
1664
|
|
|
1530
1665
|
// src/commands/server/index.ts
|
|
1531
|
-
import { Command as
|
|
1666
|
+
import { Command as Command25 } from "commander";
|
|
1532
1667
|
|
|
1533
1668
|
// src/commands/server/join.ts
|
|
1534
|
-
import { Command as
|
|
1669
|
+
import { Command as Command18 } from "commander";
|
|
1535
1670
|
|
|
1536
1671
|
// src/commands/server/daemon.ts
|
|
1537
1672
|
import { spawn as spawn3 } from "child_process";
|
|
@@ -1585,7 +1720,7 @@ function runKvboxServer(args) {
|
|
|
1585
1720
|
}
|
|
1586
1721
|
|
|
1587
1722
|
// src/commands/server/join.ts
|
|
1588
|
-
var joinCommand = new
|
|
1723
|
+
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) => {
|
|
1589
1724
|
const args = ["join", url, "--token", opts.token];
|
|
1590
1725
|
if (opts.advertise !== void 0) args.push("--advertise", opts.advertise);
|
|
1591
1726
|
if (opts.name !== void 0) args.push("--name", opts.name);
|
|
@@ -1595,8 +1730,8 @@ var joinCommand = new Command16("join").description("put this machine behind a c
|
|
|
1595
1730
|
});
|
|
1596
1731
|
|
|
1597
1732
|
// src/commands/server/leave.ts
|
|
1598
|
-
import { Command as
|
|
1599
|
-
var leaveCommand = new
|
|
1733
|
+
import { Command as Command19 } from "commander";
|
|
1734
|
+
var leaveCommand = new Command19("leave").description("take this machine out from behind its control plane and restart its server").action(async () => {
|
|
1600
1735
|
const p = paths();
|
|
1601
1736
|
for (const line of (await runKvboxServer(["leave"])).split("\n")) info(line);
|
|
1602
1737
|
if (await runningPid(p) === null) return;
|
|
@@ -1605,14 +1740,14 @@ var leaveCommand = new Command17("leave").description("take this machine out fro
|
|
|
1605
1740
|
});
|
|
1606
1741
|
|
|
1607
1742
|
// src/commands/server/relay.ts
|
|
1608
|
-
import { Command as
|
|
1609
|
-
var relayCommand = new
|
|
1743
|
+
import { Command as Command20 } from "commander";
|
|
1744
|
+
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) => {
|
|
1610
1745
|
for (const line of (await runKvboxServer(["relay", joinString])).split("\n")) info(line);
|
|
1611
1746
|
});
|
|
1612
1747
|
|
|
1613
1748
|
// src/commands/server/start.ts
|
|
1614
|
-
import { Command as
|
|
1615
|
-
var startCommand2 = new
|
|
1749
|
+
import { Command as Command21 } from "commander";
|
|
1750
|
+
var startCommand2 = new Command21("start").description("start the local kvbox-server in the background").action(async (_opts, cmd) => {
|
|
1616
1751
|
const client = await clientFor({ ...cmd.optsWithGlobals(), local: true }, { autoStart: false });
|
|
1617
1752
|
try {
|
|
1618
1753
|
const health = await client.health();
|
|
@@ -1628,8 +1763,8 @@ var startCommand2 = new Command19("start").description("start the local kvbox-se
|
|
|
1628
1763
|
});
|
|
1629
1764
|
|
|
1630
1765
|
// src/commands/server/status.ts
|
|
1631
|
-
import { Command as
|
|
1632
|
-
var statusCommand = new
|
|
1766
|
+
import { Command as Command22 } from "commander";
|
|
1767
|
+
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) => {
|
|
1633
1768
|
const client = await clientFor({ ...cmd.optsWithGlobals(), local: true });
|
|
1634
1769
|
try {
|
|
1635
1770
|
const health = await client.health();
|
|
@@ -1646,7 +1781,12 @@ var statusCommand = new Command20("status").description("show whether this machi
|
|
|
1646
1781
|
}
|
|
1647
1782
|
if (health.controlPlane) info(`control plane: ${health.controlPlane.name} at ${health.controlPlane.url}, this host answers it on ${health.controlPlane.hostUrl}`);
|
|
1648
1783
|
else info("control plane: none (kvbox server join <url> --token <token> puts this machine behind one)");
|
|
1649
|
-
if (health.relay)
|
|
1784
|
+
if (health.relay) {
|
|
1785
|
+
info(`relay: ${health.relay.name} lends this machine ${health.relay.publicIp} (tunnel to ${health.relay.endpoint})`);
|
|
1786
|
+
if (health.relay.restarts && health.relay.lastRestartAt) {
|
|
1787
|
+
info(`the tunnel stopped carrying traffic and was redialed ${health.relay.restarts} time(s) since the server started, last at ${health.relay.lastRestartAt}`);
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1650
1790
|
if (health.base === null) info("base image: none usable yet (run install-server.sh)");
|
|
1651
1791
|
else info(`base image: ${health.base} (what kvbox new uses; kvbox vm update <box> moves an older box onto it)`);
|
|
1652
1792
|
}
|
|
@@ -1656,8 +1796,8 @@ var statusCommand = new Command20("status").description("show whether this machi
|
|
|
1656
1796
|
});
|
|
1657
1797
|
|
|
1658
1798
|
// src/commands/server/stop.ts
|
|
1659
|
-
import { Command as
|
|
1660
|
-
var stopCommand2 = new
|
|
1799
|
+
import { Command as Command23 } from "commander";
|
|
1800
|
+
var stopCommand2 = new Command23("stop").description("stop the local server (running boxes are not affected)").action(async () => {
|
|
1661
1801
|
const pid = await runningPid(paths());
|
|
1662
1802
|
if (pid === null) throw new KvboxError("kvbox-server is not running");
|
|
1663
1803
|
await stopLocalServer(pid);
|
|
@@ -1665,8 +1805,8 @@ var stopCommand2 = new Command21("stop").description("stop the local server (run
|
|
|
1665
1805
|
});
|
|
1666
1806
|
|
|
1667
1807
|
// src/commands/server/trust-ca.ts
|
|
1668
|
-
import { Command as
|
|
1669
|
-
var trustCaCommand = new
|
|
1808
|
+
import { Command as Command24 } from "commander";
|
|
1809
|
+
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) => {
|
|
1670
1810
|
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");
|
|
1671
1811
|
if (publicKey !== void 0 && opts.fromCloud) throw new KvboxError("either a public key or --from-cloud, not both");
|
|
1672
1812
|
const client = await clientFor({ ...cmd.optsWithGlobals(), local: true });
|
|
@@ -1704,7 +1844,7 @@ function describeReport(report) {
|
|
|
1704
1844
|
}
|
|
1705
1845
|
|
|
1706
1846
|
// src/commands/server/index.ts
|
|
1707
|
-
var serverCommand = new
|
|
1847
|
+
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);
|
|
1708
1848
|
|
|
1709
1849
|
// src/commands/login.ts
|
|
1710
1850
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
@@ -1712,8 +1852,8 @@ import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from
|
|
|
1712
1852
|
import { hostname as osHostname } from "os";
|
|
1713
1853
|
import { createInterface } from "readline/promises";
|
|
1714
1854
|
import { Writable } from "stream";
|
|
1715
|
-
import { Command as
|
|
1716
|
-
var loginCommand = new
|
|
1855
|
+
import { Command as Command26 } from "commander";
|
|
1856
|
+
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) => {
|
|
1717
1857
|
const url = normalizeUrl(urlArg ?? DEFAULT_CLOUD_URL);
|
|
1718
1858
|
const creds = await readCredentials(opts);
|
|
1719
1859
|
const session = await runLogin(url, creds, paths());
|
|
@@ -1811,8 +1951,8 @@ async function firstLine(stream) {
|
|
|
1811
1951
|
}
|
|
1812
1952
|
|
|
1813
1953
|
// src/commands/logout.ts
|
|
1814
|
-
import { Command as
|
|
1815
|
-
var logoutCommand = new
|
|
1954
|
+
import { Command as Command27 } from "commander";
|
|
1955
|
+
var logoutCommand = new Command27("logout").description("log out of the control plane; commands target this machine's kvbox-server again").action(async () => {
|
|
1816
1956
|
const p = paths();
|
|
1817
1957
|
const session = await readSession(p).catch(() => null);
|
|
1818
1958
|
switch (await runLogout(p)) {
|
|
@@ -1849,8 +1989,8 @@ async function runLogout(p = paths()) {
|
|
|
1849
1989
|
}
|
|
1850
1990
|
|
|
1851
1991
|
// src/commands/whoami.ts
|
|
1852
|
-
import { Command as
|
|
1853
|
-
var whoamiCommand = new
|
|
1992
|
+
import { Command as Command28 } from "commander";
|
|
1993
|
+
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) => {
|
|
1854
1994
|
const session = await readSession();
|
|
1855
1995
|
if (opts.json) {
|
|
1856
1996
|
json(session ? { user: session.user, url: session.url, sshCertExpires: session.sshCertExpires ?? null } : null);
|
|
@@ -1866,11 +2006,11 @@ function describeSession(session) {
|
|
|
1866
2006
|
}
|
|
1867
2007
|
|
|
1868
2008
|
// src/commands/snapshots/index.ts
|
|
1869
|
-
import { Command as
|
|
2009
|
+
import { Command as Command32 } from "commander";
|
|
1870
2010
|
|
|
1871
2011
|
// src/commands/snapshots/ls.ts
|
|
1872
|
-
import { Command as
|
|
1873
|
-
var lsCommand2 = new
|
|
2012
|
+
import { Command as Command29, Option as Option4 } from "commander";
|
|
2013
|
+
var lsCommand2 = new Command29("ls").description("list snapshots (yours and your orgs'; --org <slug> for one org)").addOption(new Option4("-a, --all", "kept for older scripts: the listing covers everything you can see anyway").hideHelp()).option("--json", "print as JSON").action(async (opts, cmd) => {
|
|
1874
2014
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1875
2015
|
try {
|
|
1876
2016
|
const cloud = client.target.kind === "cloud";
|
|
@@ -1901,8 +2041,8 @@ var lsCommand2 = new Command27("ls").description("list snapshots (yours and your
|
|
|
1901
2041
|
});
|
|
1902
2042
|
|
|
1903
2043
|
// src/commands/snapshots/new.ts
|
|
1904
|
-
import { Command as
|
|
1905
|
-
var newCommand2 = new
|
|
2044
|
+
import { Command as Command30 } from "commander";
|
|
2045
|
+
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) => {
|
|
1906
2046
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1907
2047
|
try {
|
|
1908
2048
|
const snap = await spin(`saving snapshot ${name} from box ${box} (memory + disk)\u2026`, client.createSnapshot({ box, name }));
|
|
@@ -1914,8 +2054,8 @@ var newCommand2 = new Command28("new").description("freeze a running box (memory
|
|
|
1914
2054
|
});
|
|
1915
2055
|
|
|
1916
2056
|
// src/commands/snapshots/rm.ts
|
|
1917
|
-
import { Command as
|
|
1918
|
-
var rmCommand2 = new
|
|
2057
|
+
import { Command as Command31 } from "commander";
|
|
2058
|
+
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) => {
|
|
1919
2059
|
if (!await confirm(`Delete snapshot ${name}?`, opts.yes ?? false)) {
|
|
1920
2060
|
info("cancelled");
|
|
1921
2061
|
process.exitCode = 1;
|
|
@@ -1931,14 +2071,14 @@ var rmCommand2 = new Command29("rm").description("delete a snapshot (boxes made
|
|
|
1931
2071
|
});
|
|
1932
2072
|
|
|
1933
2073
|
// src/commands/snapshots/index.ts
|
|
1934
|
-
var snapshotsCommand = new
|
|
2074
|
+
var snapshotsCommand = new Command32("snapshots").aliases(["snapshot", "snap"]).description("freeze running boxes into snapshots and manage them").addCommand(newCommand2).addCommand(lsCommand2).addCommand(rmCommand2);
|
|
1935
2075
|
for (const cmd of snapshotsCommand.commands) cmd.option("--org <slug>", "act in this org (- for your personal boxes)");
|
|
1936
2076
|
|
|
1937
2077
|
// src/commands/org/index.ts
|
|
1938
|
-
import { Command as
|
|
2078
|
+
import { Command as Command38 } from "commander";
|
|
1939
2079
|
|
|
1940
2080
|
// src/commands/org/ls.ts
|
|
1941
|
-
import { Command as
|
|
2081
|
+
import { Command as Command33 } from "commander";
|
|
1942
2082
|
|
|
1943
2083
|
// src/commands/org/context.ts
|
|
1944
2084
|
var NOT_LOGGED_IN = "orgs live on the control plane: kvbox login first";
|
|
@@ -1958,7 +2098,7 @@ function orgFor(opts, target) {
|
|
|
1958
2098
|
}
|
|
1959
2099
|
|
|
1960
2100
|
// src/commands/org/ls.ts
|
|
1961
|
-
var lsCommand3 = new
|
|
2101
|
+
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) => {
|
|
1962
2102
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
1963
2103
|
try {
|
|
1964
2104
|
const orgs = await client.listOrgs();
|
|
@@ -1980,8 +2120,8 @@ function orgRows(orgs) {
|
|
|
1980
2120
|
}
|
|
1981
2121
|
|
|
1982
2122
|
// src/commands/org/members.ts
|
|
1983
|
-
import { Command as
|
|
1984
|
-
var lsCommand4 = new
|
|
2123
|
+
import { Command as Command34, Option as Option5 } from "commander";
|
|
2124
|
+
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) => {
|
|
1985
2125
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
1986
2126
|
try {
|
|
1987
2127
|
const org = await client.getOrg(orgFor(opts, client.target));
|
|
@@ -1995,7 +2135,7 @@ var lsCommand4 = new Command32("ls").description("list an org's members and thei
|
|
|
1995
2135
|
await client.close();
|
|
1996
2136
|
}
|
|
1997
2137
|
});
|
|
1998
|
-
var addCommand = new
|
|
2138
|
+
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 Option5("-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) => {
|
|
1999
2139
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
2000
2140
|
try {
|
|
2001
2141
|
const slug = orgFor(opts, client.target);
|
|
@@ -2005,7 +2145,7 @@ var addCommand = new Command32("add").description("add a user to an org (admins
|
|
|
2005
2145
|
await client.close();
|
|
2006
2146
|
}
|
|
2007
2147
|
});
|
|
2008
|
-
var rmCommand3 = new
|
|
2148
|
+
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) => {
|
|
2009
2149
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
2010
2150
|
try {
|
|
2011
2151
|
const slug = orgFor(opts, client.target);
|
|
@@ -2021,11 +2161,11 @@ var rmCommand3 = new Command32("rm").description("remove a member (or leave: rem
|
|
|
2021
2161
|
await client.close();
|
|
2022
2162
|
}
|
|
2023
2163
|
});
|
|
2024
|
-
var membersCommand = new
|
|
2164
|
+
var membersCommand = new Command34("members").description("who is in an org and what they may do").addCommand(lsCommand4).addCommand(addCommand).addCommand(rmCommand3);
|
|
2025
2165
|
|
|
2026
2166
|
// src/commands/org/new.ts
|
|
2027
|
-
import { Command as
|
|
2028
|
-
var newCommand3 = new
|
|
2167
|
+
import { Command as Command35 } from "commander";
|
|
2168
|
+
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) => {
|
|
2029
2169
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
2030
2170
|
try {
|
|
2031
2171
|
const org = await client.createOrg({ slug, name: opts.name ?? slug });
|
|
@@ -2041,8 +2181,8 @@ var newCommand3 = new Command33("new").description("create an org; you become it
|
|
|
2041
2181
|
});
|
|
2042
2182
|
|
|
2043
2183
|
// src/commands/org/rm.ts
|
|
2044
|
-
import { Command as
|
|
2045
|
-
var rmCommand4 = new
|
|
2184
|
+
import { Command as Command36 } from "commander";
|
|
2185
|
+
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) => {
|
|
2046
2186
|
if (!await confirm(`Delete org ${slug}? Its members lose access; its slug stays taken.`, opts.yes ?? false)) {
|
|
2047
2187
|
info("cancelled");
|
|
2048
2188
|
process.exitCode = 1;
|
|
@@ -2058,8 +2198,8 @@ var rmCommand4 = new Command34("rm").description("delete an org (owners only; re
|
|
|
2058
2198
|
});
|
|
2059
2199
|
|
|
2060
2200
|
// src/commands/org/role.ts
|
|
2061
|
-
import { Argument, Command as
|
|
2062
|
-
var roleCommand = new
|
|
2201
|
+
import { Argument, Command as Command37 } from "commander";
|
|
2202
|
+
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) => {
|
|
2063
2203
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
2064
2204
|
try {
|
|
2065
2205
|
const slug = orgFor(opts, client.target);
|
|
@@ -2071,11 +2211,11 @@ var roleCommand = new Command35("role").description("change a member's role (own
|
|
|
2071
2211
|
});
|
|
2072
2212
|
|
|
2073
2213
|
// src/commands/org/index.ts
|
|
2074
|
-
var orgCommand = new
|
|
2214
|
+
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);
|
|
2075
2215
|
|
|
2076
2216
|
// src/commands/usage.ts
|
|
2077
|
-
import { Command as
|
|
2078
|
-
var usageCommand2 = new
|
|
2217
|
+
import { Command as Command39 } from "commander";
|
|
2218
|
+
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("--json", "print as JSON").action(async (opts, cmd) => {
|
|
2079
2219
|
for (const [name, value] of [
|
|
2080
2220
|
["since", opts.since],
|
|
2081
2221
|
["until", opts.until]
|
|
@@ -2148,8 +2288,8 @@ function usageRows(report, cloud) {
|
|
|
2148
2288
|
}
|
|
2149
2289
|
|
|
2150
2290
|
// src/commands/credit.ts
|
|
2151
|
-
import { Command as
|
|
2152
|
-
var creditCommand = new
|
|
2291
|
+
import { Command as Command40 } from "commander";
|
|
2292
|
+
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) => {
|
|
2153
2293
|
const client = await clientFor(cmd.optsWithGlobals(), { autoStart: false });
|
|
2154
2294
|
try {
|
|
2155
2295
|
if (client.target.kind === "local") throw new KvboxError("credit lives on the control plane: kvbox login first");
|
|
@@ -2178,7 +2318,7 @@ function creditRow(b, me2) {
|
|
|
2178
2318
|
|
|
2179
2319
|
// src/program.ts
|
|
2180
2320
|
function buildProgram() {
|
|
2181
|
-
const program = new
|
|
2321
|
+
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();
|
|
2182
2322
|
program.addCommand(vmCommand);
|
|
2183
2323
|
program.addCommand(snapshotsCommand);
|
|
2184
2324
|
program.addCommand(serverCommand);
|