@kvbox/cli 0.5.12 → 0.5.14
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 +396 -76
- 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 Command48 } 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.14" : void 0);
|
|
166
166
|
|
|
167
167
|
// src/commands/vm/index.ts
|
|
168
|
-
import { Command as
|
|
168
|
+
import { Command as Command24 } from "commander";
|
|
169
169
|
|
|
170
170
|
// src/commands/vm/connect.ts
|
|
171
171
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
@@ -384,6 +384,30 @@ function parseMib(text) {
|
|
|
384
384
|
var SizeSchema = z2.enum(SIZE_NAMES);
|
|
385
385
|
var BOX_SSH_USER = "kvbox";
|
|
386
386
|
var BoxStatusSchema = z2.enum(["running", "standby", "hibernated", "stopped", "unknown"]);
|
|
387
|
+
var PortProtoSchema = z2.enum(["tcp", "udp"]);
|
|
388
|
+
var MAX_BOX_PORTS = 16;
|
|
389
|
+
var PortForwardSchema = z2.object({
|
|
390
|
+
guestPort: z2.number().int().min(1).max(65535),
|
|
391
|
+
hostPort: z2.number().int(),
|
|
392
|
+
proto: PortProtoSchema,
|
|
393
|
+
/** The host's public address; only the control plane fills it in, as for `ssh`. */
|
|
394
|
+
address: z2.string().optional()
|
|
395
|
+
});
|
|
396
|
+
var PortsRequest = z2.object({
|
|
397
|
+
ports: z2.array(z2.object({ guestPort: z2.number().int().min(1).max(65535), proto: PortProtoSchema.optional() })).min(1).max(MAX_BOX_PORTS)
|
|
398
|
+
});
|
|
399
|
+
var BoxPortSchema = z2.number().int().min(1).max(65535);
|
|
400
|
+
var ClaimSubdomainRequest = z2.object({ name: z2.string(), port: BoxPortSchema });
|
|
401
|
+
var SubdomainPortRequest = z2.object({ port: BoxPortSchema });
|
|
402
|
+
var SubdomainRouteRequest = z2.object({ name: z2.string(), port: BoxPortSchema });
|
|
403
|
+
var SubdomainSchema = z2.object({
|
|
404
|
+
owner: z2.string(),
|
|
405
|
+
box: z2.string(),
|
|
406
|
+
subdomain: z2.string(),
|
|
407
|
+
port: BoxPortSchema,
|
|
408
|
+
url: z2.string(),
|
|
409
|
+
routes: z2.array(z2.object({ name: z2.string(), port: BoxPortSchema, url: z2.string() }))
|
|
410
|
+
});
|
|
387
411
|
var MachineSchema = z2.object({
|
|
388
412
|
/** Host-unique short id (the tap is `kv-<id>`). Missing only on a box made before ids that has not restarted since. */
|
|
389
413
|
id: z2.string(),
|
|
@@ -416,6 +440,8 @@ var MachineSchema = z2.object({
|
|
|
416
440
|
* whose host reported no port or did not answer.
|
|
417
441
|
*/
|
|
418
442
|
ssh: z2.object({ host: z2.string(), port: z2.number().int(), user: z2.string() }).optional(),
|
|
443
|
+
/** Box ports published on the host (#30). Absent: none. */
|
|
444
|
+
ports: z2.array(PortForwardSchema).optional(),
|
|
419
445
|
/** Whether the owner slug is a user or an org. Only the control plane knows; it fills it in on `GET /machines`. */
|
|
420
446
|
ownerKind: z2.enum(["user", "org"]).optional(),
|
|
421
447
|
/** Seconds without network traffic before the host hibernates the box (#47). Absent: never. */
|
|
@@ -924,6 +950,35 @@ var ServerClient = class {
|
|
|
924
950
|
setMachine(name, req) {
|
|
925
951
|
return this.call("POST", `/machines/${encodeURIComponent(name)}/set`, MachineSchema, req);
|
|
926
952
|
}
|
|
953
|
+
/** Publish box ports on the host (#30); the answer is the box with every port it publishes. */
|
|
954
|
+
addPorts(name, req) {
|
|
955
|
+
return this.call("POST", `/machines/${encodeURIComponent(name)}/ports`, MachineSchema, req);
|
|
956
|
+
}
|
|
957
|
+
/** Stop publishing them (#30). */
|
|
958
|
+
removePorts(name, req) {
|
|
959
|
+
return this.call("DELETE", `/machines/${encodeURIComponent(name)}/ports`, MachineSchema, req);
|
|
960
|
+
}
|
|
961
|
+
/** HTTPS names (#31), the control plane's only: the box's subdomain, claimed for its default port. */
|
|
962
|
+
claimSubdomain(name, req) {
|
|
963
|
+
return this.call("PUT", `/machines/${encodeURIComponent(name)}/subdomain`, SubdomainSchema, req);
|
|
964
|
+
}
|
|
965
|
+
setSubdomainPort(name, req) {
|
|
966
|
+
return this.call("POST", `/machines/${encodeURIComponent(name)}/subdomain/port`, SubdomainSchema, req);
|
|
967
|
+
}
|
|
968
|
+
releaseSubdomain(name) {
|
|
969
|
+
return this.call("DELETE", `/machines/${encodeURIComponent(name)}/subdomain`, SubdomainSchema);
|
|
970
|
+
}
|
|
971
|
+
/** `<route>.<sub>`: a named port under the box's subdomain. */
|
|
972
|
+
addSubdomainRoute(name, req) {
|
|
973
|
+
return this.call("POST", `/machines/${encodeURIComponent(name)}/subdomain/routes`, SubdomainSchema, req);
|
|
974
|
+
}
|
|
975
|
+
removeSubdomainRoute(name, route) {
|
|
976
|
+
return this.call("DELETE", `/machines/${encodeURIComponent(name)}/subdomain/routes/${encodeURIComponent(route)}`, SubdomainSchema);
|
|
977
|
+
}
|
|
978
|
+
/** Every readable box's subdomain. */
|
|
979
|
+
listSubdomains() {
|
|
980
|
+
return this.call("GET", "/subdomains", z3.array(SubdomainSchema));
|
|
981
|
+
}
|
|
927
982
|
/** A running box's own load, memory and disk, asked of its guest (#67). */
|
|
928
983
|
machineStats(name) {
|
|
929
984
|
return this.call("GET", `/machines/${encodeURIComponent(name)}/stats`, MachineStatsSchema);
|
|
@@ -1398,9 +1453,274 @@ var pauseCommand = new Command6("pause").description("freeze a box in place (mem
|
|
|
1398
1453
|
}
|
|
1399
1454
|
});
|
|
1400
1455
|
|
|
1456
|
+
// src/commands/vm/proxy/index.ts
|
|
1457
|
+
import { Command as Command13 } from "commander";
|
|
1458
|
+
|
|
1459
|
+
// src/commands/vm/proxy/add.ts
|
|
1460
|
+
import { Command as Command7 } from "commander";
|
|
1461
|
+
|
|
1462
|
+
// ../shared/src/names.ts
|
|
1463
|
+
import { createHmac } from "crypto";
|
|
1464
|
+
var LABEL_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
1465
|
+
var RESERVED_SUBDOMAINS = /* @__PURE__ */ new Set([
|
|
1466
|
+
"admin",
|
|
1467
|
+
"api",
|
|
1468
|
+
"app",
|
|
1469
|
+
"assets",
|
|
1470
|
+
"auth",
|
|
1471
|
+
"billing",
|
|
1472
|
+
"blog",
|
|
1473
|
+
"cdn",
|
|
1474
|
+
"dashboard",
|
|
1475
|
+
"dev",
|
|
1476
|
+
"docs",
|
|
1477
|
+
"edge",
|
|
1478
|
+
"help",
|
|
1479
|
+
"install",
|
|
1480
|
+
"kvbox",
|
|
1481
|
+
"login",
|
|
1482
|
+
"mail",
|
|
1483
|
+
"relay",
|
|
1484
|
+
"root",
|
|
1485
|
+
"smtp",
|
|
1486
|
+
"static",
|
|
1487
|
+
"status",
|
|
1488
|
+
"support",
|
|
1489
|
+
"www"
|
|
1490
|
+
]);
|
|
1491
|
+
function subdomainProblem(text) {
|
|
1492
|
+
if (text.length < 3 || text.length > 63) return `"${text}" must be 3 to 63 characters`;
|
|
1493
|
+
if (!LABEL_RE.test(text)) return `"${text}" may hold only lowercase letters, digits and '-', not at either end`;
|
|
1494
|
+
if (RESERVED_SUBDOMAINS.has(text)) return `"${text}" is reserved; pick another name`;
|
|
1495
|
+
return null;
|
|
1496
|
+
}
|
|
1497
|
+
function routeNameProblem(text) {
|
|
1498
|
+
if (text.length < 1 || text.length > 63) return `"${text}" must be 1 to 63 characters`;
|
|
1499
|
+
if (!LABEL_RE.test(text)) return `"${text}" may hold only lowercase letters, digits and '-', not at either end`;
|
|
1500
|
+
return null;
|
|
1501
|
+
}
|
|
1502
|
+
function assertSubdomain(text) {
|
|
1503
|
+
const problem = subdomainProblem(text);
|
|
1504
|
+
if (problem) throw new KvboxError(`subdomain ${problem}`);
|
|
1505
|
+
return text;
|
|
1506
|
+
}
|
|
1507
|
+
function assertRouteName(text) {
|
|
1508
|
+
const problem = routeNameProblem(text);
|
|
1509
|
+
if (problem) throw new KvboxError(`name ${problem}`);
|
|
1510
|
+
return text;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
// src/commands/vm/proxy/shared.ts
|
|
1514
|
+
var boxOf = (sub, user) => user !== void 0 && sub.owner !== user ? `${sub.owner}/${sub.box}` : sub.box;
|
|
1515
|
+
function subdomainJson(sub, user) {
|
|
1516
|
+
return { box: boxOf(sub, user), subdomain: sub.subdomain, port: sub.port, url: sub.url, routes: sub.routes };
|
|
1517
|
+
}
|
|
1518
|
+
function httpsRowsOf(sub, user) {
|
|
1519
|
+
const box = boxOf(sub, user);
|
|
1520
|
+
return [{ box, guestPort: sub.port, proto: "https", url: sub.url }, ...sub.routes.map((r) => ({ box, guestPort: r.port, proto: "https", url: r.url, name: r.name }))];
|
|
1521
|
+
}
|
|
1522
|
+
function requireCloud(client) {
|
|
1523
|
+
if (client.target.kind !== "cloud") throw new KvboxError("HTTPS names need the kvbox cloud: kvbox login (this host's own socket has none)");
|
|
1524
|
+
}
|
|
1525
|
+
function rowsOf(box, user, only) {
|
|
1526
|
+
return (box.ports ?? []).filter(only ?? (() => true)).map((f) => ({ box: displayName(box, user), ...f }));
|
|
1527
|
+
}
|
|
1528
|
+
function reaches(row, short = false) {
|
|
1529
|
+
if (row.address !== void 0) return `${row.address}:${row.hostPort}`;
|
|
1530
|
+
return short ? `:${row.hostPort}` : `port ${row.hostPort} of this host's public address`;
|
|
1531
|
+
}
|
|
1532
|
+
function parsePorts(texts) {
|
|
1533
|
+
return texts.map((t) => {
|
|
1534
|
+
const n = Number(t);
|
|
1535
|
+
if (!/^\d+$/.test(t) || n < 1 || n > 65535) throw new KvboxError(`"${t}" is not a port (1-65535)`);
|
|
1536
|
+
return n;
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
async function findBox(client, text) {
|
|
1540
|
+
const found = pickRef(await boxesFor(client, text), text, me(client));
|
|
1541
|
+
if (!found) throw new KvboxError(`box ${text}: not found (kvbox ls)`);
|
|
1542
|
+
return found;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// src/commands/vm/proxy/add.ts
|
|
1546
|
+
var addCommand = new Command7("add").description("publish box ports on the internet; each gets a random public port, or an HTTPS name with --name").argument("<box>", "box name").argument("<port...>", "ports inside the box, e.g. 3000 8080").option("--udp", "UDP instead of TCP").option("--name <name>", "serve the port at https://<name>.<subdomain>.kvbox.app (one port; needs kvbox vm proxy claim first)").option("--json", "print the new forwards as JSON").action(async (name, portTexts, opts, cmd) => {
|
|
1547
|
+
const guestPorts = parsePorts(portTexts);
|
|
1548
|
+
if (opts.name !== void 0) {
|
|
1549
|
+
assertRouteName(opts.name);
|
|
1550
|
+
if (guestPorts.length !== 1) throw new KvboxError("--name publishes one port: kvbox vm proxy add <box> <port> --name <name>");
|
|
1551
|
+
if (opts.udp) throw new KvboxError("--name serves HTTPS, which is TCP: drop --udp");
|
|
1552
|
+
}
|
|
1553
|
+
const proto = opts.udp ? "udp" : "tcp";
|
|
1554
|
+
const client = await clientFor(cmd.optsWithGlobals());
|
|
1555
|
+
try {
|
|
1556
|
+
const user = me(client);
|
|
1557
|
+
if (opts.name !== void 0) requireCloud(client);
|
|
1558
|
+
const found = await findBox(client, name);
|
|
1559
|
+
const box = displayName(found, user);
|
|
1560
|
+
if (opts.name !== void 0) {
|
|
1561
|
+
const route = opts.name;
|
|
1562
|
+
const sub = await spin(`publishing ${guestPorts[0]} on ${name} as ${route}\u2026`, client.addSubdomainRoute(box, { name: route, port: guestPorts[0] }));
|
|
1563
|
+
const url = sub.routes.find((r) => r.name === route).url;
|
|
1564
|
+
if (opts.json) {
|
|
1565
|
+
json({ box, guestPort: guestPorts[0], proto: "https", url, name: route });
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
info(`${color.green("\u2713")} ${color.bold(url)} \u2192 box ${color.bold(box)} port ${guestPorts[0]}`);
|
|
1569
|
+
info(" the first name under a subdomain waits for its certificate, about a minute");
|
|
1570
|
+
if (found.status !== "running") info(` box ${box} is ${found.status}: the address answers once it runs`);
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
const updated = await spin(`publishing ${guestPorts.join(", ")} on ${name}\u2026`, client.addPorts(box, { ports: guestPorts.map((guestPort) => ({ guestPort, proto })) }));
|
|
1574
|
+
const rows = rowsOf(updated, user, (f) => f.proto === proto && guestPorts.includes(f.guestPort));
|
|
1575
|
+
if (opts.json) {
|
|
1576
|
+
json(rows);
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
for (const row of rows) info(`${color.green("\u2713")} box ${color.bold(row.box)} port ${row.guestPort} is reachable at ${reaches(row)} (${row.proto})`);
|
|
1580
|
+
if (updated.status !== "running") info(` box ${name} is ${updated.status}: the ports open when it runs`);
|
|
1581
|
+
} finally {
|
|
1582
|
+
await client.close();
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
// src/commands/vm/proxy/claim.ts
|
|
1587
|
+
import { Command as Command8 } from "commander";
|
|
1588
|
+
var claimCommand = new Command8("claim").description("give a box its HTTPS address, https://<subdomain>.kvbox.app, serving one of its ports").argument("<box>", "box name").argument("<subdomain>", "3-63 lowercase letters, digits and '-'; must be free").requiredOption("--port <port>", "the box port the address serves").option("--json", "print the subdomain as JSON").action(async (name, subdomain, opts, cmd) => {
|
|
1589
|
+
assertSubdomain(subdomain);
|
|
1590
|
+
const [port] = parsePorts([opts.port]);
|
|
1591
|
+
const client = await clientFor(cmd.optsWithGlobals());
|
|
1592
|
+
try {
|
|
1593
|
+
requireCloud(client);
|
|
1594
|
+
const user = me(client);
|
|
1595
|
+
const found = await findBox(client, name);
|
|
1596
|
+
const box = displayName(found, user);
|
|
1597
|
+
const sub = await spin(`claiming ${subdomain}\u2026`, client.claimSubdomain(box, { name: subdomain, port }));
|
|
1598
|
+
if (opts.json) {
|
|
1599
|
+
json(subdomainJson(sub, me(client)));
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
info(`${color.green("\u2713")} ${color.bold(sub.url)} \u2192 box ${color.bold(box)} port ${sub.port}`);
|
|
1603
|
+
info(` more ports under it: kvbox vm proxy add ${box} <port> --name <name> \u2192 https://<name>.${sub.url.replace(/^https:\/\//, "")}`);
|
|
1604
|
+
if (found.status !== "running") info(` box ${box} is ${found.status}: the address answers once it runs`);
|
|
1605
|
+
} finally {
|
|
1606
|
+
await client.close();
|
|
1607
|
+
}
|
|
1608
|
+
});
|
|
1609
|
+
|
|
1610
|
+
// src/commands/vm/proxy/ls.ts
|
|
1611
|
+
import { Command as Command9 } from "commander";
|
|
1612
|
+
async function subdomainsOf(client) {
|
|
1613
|
+
try {
|
|
1614
|
+
return await client.listSubdomains();
|
|
1615
|
+
} catch (err) {
|
|
1616
|
+
warn(`HTTPS names not listed: ${err.message}`);
|
|
1617
|
+
return [];
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
var lsCommand2 = new Command9("ls").description("list the box ports published on the internet").argument("[box]", "only this box").option("--json", "print as JSON").action(async (name, opts, cmd) => {
|
|
1621
|
+
const client = await clientFor(cmd.optsWithGlobals());
|
|
1622
|
+
try {
|
|
1623
|
+
const user = me(client);
|
|
1624
|
+
const [boxes, subs] = await Promise.all([
|
|
1625
|
+
name === void 0 ? client.listMachines() : findBox(client, name).then((b) => [b]),
|
|
1626
|
+
client.target.kind === "cloud" ? subdomainsOf(client) : []
|
|
1627
|
+
]);
|
|
1628
|
+
const shown = new Set(boxes.map((b) => displayName(b, user)));
|
|
1629
|
+
const https = subs.flatMap((s) => httpsRowsOf(s, user)).filter((r) => shown.has(r.box));
|
|
1630
|
+
const rows = [...boxes.flatMap((b) => rowsOf(b, user)), ...https].sort((a, b) => a.box.localeCompare(b.box) || a.guestPort - b.guestPort);
|
|
1631
|
+
if (opts.json) {
|
|
1632
|
+
json(rows);
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
if (rows.length === 0) {
|
|
1636
|
+
info(name === void 0 ? "no published ports (kvbox vm proxy add <box> <port>)" : `box ${name} publishes no ports (kvbox vm proxy add ${name} <port>)`);
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
table(rows.map((r) => ({ box: r.box, port: r.guestPort, reaches: r.proto === "https" ? r.url : reaches(r, true), proto: r.proto })));
|
|
1640
|
+
} finally {
|
|
1641
|
+
await client.close();
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1644
|
+
|
|
1645
|
+
// src/commands/vm/proxy/release.ts
|
|
1646
|
+
import { Command as Command10 } from "commander";
|
|
1647
|
+
var releaseCommand = new Command10("release").description("give up a box's HTTPS address and every name under it").argument("<box>", "box name").option("-y, --yes", "do not ask for confirmation").action(async (name, opts, cmd) => {
|
|
1648
|
+
const client = await clientFor(cmd.optsWithGlobals());
|
|
1649
|
+
try {
|
|
1650
|
+
requireCloud(client);
|
|
1651
|
+
const box = displayName(await findBox(client, name), me(client));
|
|
1652
|
+
if (!await confirm(`Give up box ${box}'s HTTPS address and every name under it? Anyone may claim it after.`, opts.yes ?? false)) {
|
|
1653
|
+
info("cancelled");
|
|
1654
|
+
process.exitCode = 1;
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
const sub = await spin(`releasing ${name}'s address\u2026`, client.releaseSubdomain(box));
|
|
1658
|
+
const names = sub.routes.length === 0 ? "" : ` and ${sub.routes.length} name${sub.routes.length > 1 ? "s" : ""} under it`;
|
|
1659
|
+
info(`${color.green("\u2713")} ${sub.url.replace(/^https:\/\//, "")}${names} no longer reach box ${color.bold(box)}`);
|
|
1660
|
+
} finally {
|
|
1661
|
+
await client.close();
|
|
1662
|
+
}
|
|
1663
|
+
});
|
|
1664
|
+
|
|
1665
|
+
// src/commands/vm/proxy/rm.ts
|
|
1666
|
+
import { Command as Command11 } from "commander";
|
|
1667
|
+
var rmCommand = new Command11("rm").description("stop publishing box ports, or an HTTPS name with --name").argument("<box>", "box name").argument("[port...]", "ports inside the box, as given to add").option("--udp", "the UDP forwards of those ports").option("--name <name>", "the HTTPS name to free (kvbox vm proxy release frees the whole subdomain)").option("-y, --yes", "do not ask for confirmation").action(async (name, portTexts, opts, cmd) => {
|
|
1668
|
+
if (opts.name !== void 0) {
|
|
1669
|
+
assertRouteName(opts.name);
|
|
1670
|
+
if (portTexts.length > 0) throw new KvboxError("--name frees a name, not ports: kvbox vm proxy rm <box> --name <name>");
|
|
1671
|
+
} else if (portTexts.length === 0) {
|
|
1672
|
+
throw new KvboxError("which ports? kvbox vm proxy rm <box> <port...>, or --name <name> for an HTTPS name");
|
|
1673
|
+
}
|
|
1674
|
+
const guestPorts = parsePorts(portTexts);
|
|
1675
|
+
const proto = opts.udp ? "udp" : "tcp";
|
|
1676
|
+
const client = await clientFor(cmd.optsWithGlobals());
|
|
1677
|
+
try {
|
|
1678
|
+
if (opts.name !== void 0) requireCloud(client);
|
|
1679
|
+
const box = displayName(await findBox(client, name), me(client));
|
|
1680
|
+
const question = opts.name !== void 0 ? `Stop serving ${opts.name} under box ${box}'s subdomain?` : `Stop publishing port${guestPorts.length > 1 ? "s" : ""} ${guestPorts.join(", ")} of box ${box}?`;
|
|
1681
|
+
if (!await confirm(question, opts.yes ?? false)) {
|
|
1682
|
+
info("cancelled");
|
|
1683
|
+
process.exitCode = 1;
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
if (opts.name !== void 0) {
|
|
1687
|
+
const sub = await spin(`freeing ${opts.name} on ${name}\u2026`, client.removeSubdomainRoute(box, opts.name));
|
|
1688
|
+
info(`${color.green("\u2713")} ${opts.name}.${sub.url.replace(/^https:\/\//, "")} no longer reaches box ${color.bold(box)}`);
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
await spin(`closing ${guestPorts.join(", ")} on ${name}\u2026`, client.removePorts(box, { ports: guestPorts.map((guestPort) => ({ guestPort, proto })) }));
|
|
1692
|
+
info(`${color.green("\u2713")} box ${color.bold(name)} no longer publishes ${guestPorts.join(", ")} (${proto})`);
|
|
1693
|
+
} finally {
|
|
1694
|
+
await client.close();
|
|
1695
|
+
}
|
|
1696
|
+
});
|
|
1697
|
+
|
|
1698
|
+
// src/commands/vm/proxy/set-port.ts
|
|
1699
|
+
import { Command as Command12 } from "commander";
|
|
1700
|
+
var setPortCommand = new Command12("set-port").description("point a box's HTTPS address at another of its ports").argument("<box>", "box name").argument("<port>", "the box port https://<subdomain>.kvbox.app serves from now on").option("--json", "print the subdomain as JSON").action(async (name, portText, opts, cmd) => {
|
|
1701
|
+
const [port] = parsePorts([portText]);
|
|
1702
|
+
const client = await clientFor(cmd.optsWithGlobals());
|
|
1703
|
+
try {
|
|
1704
|
+
requireCloud(client);
|
|
1705
|
+
const box = displayName(await findBox(client, name), me(client));
|
|
1706
|
+
const sub = await spin(`pointing ${name} at port ${port}\u2026`, client.setSubdomainPort(box, { port }));
|
|
1707
|
+
if (opts.json) {
|
|
1708
|
+
json(subdomainJson(sub, me(client)));
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
info(`${color.green("\u2713")} ${color.bold(sub.url)} \u2192 box ${color.bold(box)} port ${sub.port}`);
|
|
1712
|
+
} finally {
|
|
1713
|
+
await client.close();
|
|
1714
|
+
}
|
|
1715
|
+
});
|
|
1716
|
+
|
|
1717
|
+
// src/commands/vm/proxy/index.ts
|
|
1718
|
+
var proxyCommand = new Command13("proxy").description("publish box ports on the internet").addCommand(addCommand).addCommand(lsCommand2).addCommand(rmCommand).addCommand(claimCommand).addCommand(setPortCommand).addCommand(releaseCommand);
|
|
1719
|
+
for (const cmd of proxyCommand.commands) cmd.option("--org <slug>", "act in this org (- for your personal boxes)");
|
|
1720
|
+
|
|
1401
1721
|
// src/commands/vm/rescale.ts
|
|
1402
|
-
import { Command as
|
|
1403
|
-
var rescaleCommand = new
|
|
1722
|
+
import { Command as Command14, Option as Option2 } from "commander";
|
|
1723
|
+
var rescaleCommand = new Command14("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) => {
|
|
1404
1724
|
const req = {
|
|
1405
1725
|
...opts.size !== void 0 ? { size: opts.size } : {},
|
|
1406
1726
|
...opts.vcpu !== void 0 ? { vcpu: parseCount(opts.vcpu) } : {},
|
|
@@ -1445,8 +1765,8 @@ function parseGib(flag, text) {
|
|
|
1445
1765
|
}
|
|
1446
1766
|
|
|
1447
1767
|
// src/commands/vm/resume.ts
|
|
1448
|
-
import { Command as
|
|
1449
|
-
var resumeCommand = new
|
|
1768
|
+
import { Command as Command15 } from "commander";
|
|
1769
|
+
var resumeCommand = new Command15("resume").description("continue a box that is in standby (kvbox pause)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
|
|
1450
1770
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1451
1771
|
try {
|
|
1452
1772
|
const box = await spin(`resuming ${name}\u2026`, client.resumeMachine(name));
|
|
@@ -1458,8 +1778,8 @@ var resumeCommand = new Command8("resume").description("continue a box that is i
|
|
|
1458
1778
|
});
|
|
1459
1779
|
|
|
1460
1780
|
// src/commands/vm/rm.ts
|
|
1461
|
-
import { Command as
|
|
1462
|
-
var
|
|
1781
|
+
import { Command as Command16 } from "commander";
|
|
1782
|
+
var rmCommand2 = new Command16("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) => {
|
|
1463
1783
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1464
1784
|
try {
|
|
1465
1785
|
const box = pickRef(await boxesFor(client, name), name, me(client));
|
|
@@ -1478,8 +1798,8 @@ var rmCommand = new Command9("rm").description("destroy a box (its disk is delet
|
|
|
1478
1798
|
});
|
|
1479
1799
|
|
|
1480
1800
|
// src/commands/vm/set.ts
|
|
1481
|
-
import { Command as
|
|
1482
|
-
var setCommand = new
|
|
1801
|
+
import { Command as Command17 } from "commander";
|
|
1802
|
+
var setCommand = new Command17("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) => {
|
|
1483
1803
|
if (opts.autoHibernate === void 0) throw new KvboxError("nothing to change: give --auto-hibernate");
|
|
1484
1804
|
const autoHibernateSec = parseIdleTimeout(opts.autoHibernate);
|
|
1485
1805
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
@@ -1498,8 +1818,8 @@ var setCommand = new Command10("set").description("change a box's settings, e.g.
|
|
|
1498
1818
|
});
|
|
1499
1819
|
|
|
1500
1820
|
// src/commands/vm/start.ts
|
|
1501
|
-
import { Command as
|
|
1502
|
-
var startCommand = new
|
|
1821
|
+
import { Command as Command18 } from "commander";
|
|
1822
|
+
var startCommand = new Command18("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) => {
|
|
1503
1823
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1504
1824
|
try {
|
|
1505
1825
|
const box = await spin(`booting ${name}\u2026`, client.startMachine(name));
|
|
@@ -1511,8 +1831,8 @@ var startCommand = new Command11("start").description("boot a stopped box again
|
|
|
1511
1831
|
});
|
|
1512
1832
|
|
|
1513
1833
|
// src/commands/vm/stop.ts
|
|
1514
|
-
import { Command as
|
|
1515
|
-
var stopCommand = new
|
|
1834
|
+
import { Command as Command19 } from "commander";
|
|
1835
|
+
var stopCommand = new Command19("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) => {
|
|
1516
1836
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1517
1837
|
try {
|
|
1518
1838
|
const box = await spin(`shutting ${name} down\u2026`, client.stopMachine(name));
|
|
@@ -1524,8 +1844,8 @@ var stopCommand = new Command12("stop").description("shut a box down (its disk i
|
|
|
1524
1844
|
});
|
|
1525
1845
|
|
|
1526
1846
|
// src/commands/vm/top.ts
|
|
1527
|
-
import { Command as
|
|
1528
|
-
var topCommand = new
|
|
1847
|
+
import { Command as Command20 } from "commander";
|
|
1848
|
+
var topCommand = new Command20("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
1849
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1530
1850
|
try {
|
|
1531
1851
|
const running = (await client.listMachines({ all: opts.all })).filter((b) => b.status === "running");
|
|
@@ -1585,8 +1905,8 @@ function topRows(entries, cloud) {
|
|
|
1585
1905
|
}
|
|
1586
1906
|
|
|
1587
1907
|
// src/commands/vm/update.ts
|
|
1588
|
-
import { Command as
|
|
1589
|
-
var updateCommand = new
|
|
1908
|
+
import { Command as Command21 } from "commander";
|
|
1909
|
+
var updateCommand = new Command21("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) => {
|
|
1590
1910
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1591
1911
|
try {
|
|
1592
1912
|
const before = pickRef(await boxesFor(client, name), name, me(client));
|
|
@@ -1608,8 +1928,8 @@ var updateCommand = new Command14("update").description("move a box onto the cur
|
|
|
1608
1928
|
});
|
|
1609
1929
|
|
|
1610
1930
|
// src/commands/vm/usage.ts
|
|
1611
|
-
import { Command as
|
|
1612
|
-
var usageCommand = new
|
|
1931
|
+
import { Command as Command22 } from "commander";
|
|
1932
|
+
var usageCommand = new Command22("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) => {
|
|
1613
1933
|
let since;
|
|
1614
1934
|
if (opts.since !== void 0) {
|
|
1615
1935
|
const t = Date.parse(opts.since);
|
|
@@ -1656,8 +1976,8 @@ function block(u) {
|
|
|
1656
1976
|
}
|
|
1657
1977
|
|
|
1658
1978
|
// src/commands/vm/wake.ts
|
|
1659
|
-
import { Command as
|
|
1660
|
-
var wakeCommand = new
|
|
1979
|
+
import { Command as Command23 } from "commander";
|
|
1980
|
+
var wakeCommand = new Command23("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) => {
|
|
1661
1981
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1662
1982
|
try {
|
|
1663
1983
|
const box = await spin(`waking ${name} (loading its memory from disk)\u2026`, client.wakeMachine(name));
|
|
@@ -1669,15 +1989,15 @@ var wakeCommand = new Command16("wake").description("continue a hibernated box w
|
|
|
1669
1989
|
});
|
|
1670
1990
|
|
|
1671
1991
|
// src/commands/vm/index.ts
|
|
1672
|
-
var vmCommand = new
|
|
1673
|
-
for (const cmd of vmCommand.commands) cmd.option("--org <slug>", "act in this org (- for your personal boxes)");
|
|
1674
|
-
var vmAliases = [newCommand, lsCommand, connectCommand, execCommand, stopCommand, startCommand, pauseCommand, resumeCommand, hibernateCommand, wakeCommand,
|
|
1992
|
+
var vmCommand = new Command24("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(proxyCommand).addCommand(rmCommand2);
|
|
1993
|
+
for (const cmd of vmCommand.commands) if (cmd !== proxyCommand) cmd.option("--org <slug>", "act in this org (- for your personal boxes)");
|
|
1994
|
+
var vmAliases = [newCommand, lsCommand, connectCommand, execCommand, stopCommand, startCommand, pauseCommand, resumeCommand, hibernateCommand, wakeCommand, rmCommand2];
|
|
1675
1995
|
|
|
1676
1996
|
// src/commands/server/index.ts
|
|
1677
|
-
import { Command as
|
|
1997
|
+
import { Command as Command32 } from "commander";
|
|
1678
1998
|
|
|
1679
1999
|
// src/commands/server/join.ts
|
|
1680
|
-
import { Command as
|
|
2000
|
+
import { Command as Command25 } from "commander";
|
|
1681
2001
|
|
|
1682
2002
|
// src/commands/server/daemon.ts
|
|
1683
2003
|
import { spawn as spawn3 } from "child_process";
|
|
@@ -1731,7 +2051,7 @@ function runKvboxServer(args) {
|
|
|
1731
2051
|
}
|
|
1732
2052
|
|
|
1733
2053
|
// src/commands/server/join.ts
|
|
1734
|
-
var joinCommand = new
|
|
2054
|
+
var joinCommand = new Command25("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) => {
|
|
1735
2055
|
const args = ["join", url, "--token", opts.token];
|
|
1736
2056
|
if (opts.advertise !== void 0) args.push("--advertise", opts.advertise);
|
|
1737
2057
|
if (opts.name !== void 0) args.push("--name", opts.name);
|
|
@@ -1741,8 +2061,8 @@ var joinCommand = new Command18("join").description("put this machine behind a c
|
|
|
1741
2061
|
});
|
|
1742
2062
|
|
|
1743
2063
|
// src/commands/server/leave.ts
|
|
1744
|
-
import { Command as
|
|
1745
|
-
var leaveCommand = new
|
|
2064
|
+
import { Command as Command26 } from "commander";
|
|
2065
|
+
var leaveCommand = new Command26("leave").description("take this machine out from behind its control plane and restart its server").action(async () => {
|
|
1746
2066
|
const p = paths();
|
|
1747
2067
|
for (const line of (await runKvboxServer(["leave"])).split("\n")) info(line);
|
|
1748
2068
|
if (await runningPid(p) === null) return;
|
|
@@ -1751,14 +2071,14 @@ var leaveCommand = new Command19("leave").description("take this machine out fro
|
|
|
1751
2071
|
});
|
|
1752
2072
|
|
|
1753
2073
|
// src/commands/server/relay.ts
|
|
1754
|
-
import { Command as
|
|
1755
|
-
var relayCommand = new
|
|
2074
|
+
import { Command as Command27 } from "commander";
|
|
2075
|
+
var relayCommand = new Command27("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) => {
|
|
1756
2076
|
for (const line of (await runKvboxServer(["relay", joinString])).split("\n")) info(line);
|
|
1757
2077
|
});
|
|
1758
2078
|
|
|
1759
2079
|
// src/commands/server/start.ts
|
|
1760
|
-
import { Command as
|
|
1761
|
-
var startCommand2 = new
|
|
2080
|
+
import { Command as Command28 } from "commander";
|
|
2081
|
+
var startCommand2 = new Command28("start").description("start the local kvbox-server in the background").action(async (_opts, cmd) => {
|
|
1762
2082
|
const client = await clientFor({ ...cmd.optsWithGlobals(), local: true }, { autoStart: false });
|
|
1763
2083
|
try {
|
|
1764
2084
|
const health = await client.health();
|
|
@@ -1774,8 +2094,8 @@ var startCommand2 = new Command21("start").description("start the local kvbox-se
|
|
|
1774
2094
|
});
|
|
1775
2095
|
|
|
1776
2096
|
// src/commands/server/status.ts
|
|
1777
|
-
import { Command as
|
|
1778
|
-
var statusCommand = new
|
|
2097
|
+
import { Command as Command29 } from "commander";
|
|
2098
|
+
var statusCommand = new Command29("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) => {
|
|
1779
2099
|
const client = await clientFor({ ...cmd.optsWithGlobals(), local: true });
|
|
1780
2100
|
try {
|
|
1781
2101
|
const health = await client.health();
|
|
@@ -1807,8 +2127,8 @@ var statusCommand = new Command22("status").description("show whether this machi
|
|
|
1807
2127
|
});
|
|
1808
2128
|
|
|
1809
2129
|
// src/commands/server/stop.ts
|
|
1810
|
-
import { Command as
|
|
1811
|
-
var stopCommand2 = new
|
|
2130
|
+
import { Command as Command30 } from "commander";
|
|
2131
|
+
var stopCommand2 = new Command30("stop").description("stop the local server (running boxes are not affected)").action(async () => {
|
|
1812
2132
|
const pid = await runningPid(paths());
|
|
1813
2133
|
if (pid === null) throw new KvboxError("kvbox-server is not running");
|
|
1814
2134
|
await stopLocalServer(pid);
|
|
@@ -1816,8 +2136,8 @@ var stopCommand2 = new Command23("stop").description("stop the local server (run
|
|
|
1816
2136
|
});
|
|
1817
2137
|
|
|
1818
2138
|
// src/commands/server/trust-ca.ts
|
|
1819
|
-
import { Command as
|
|
1820
|
-
var trustCaCommand = new
|
|
2139
|
+
import { Command as Command31 } from "commander";
|
|
2140
|
+
var trustCaCommand = new Command31("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) => {
|
|
1821
2141
|
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");
|
|
1822
2142
|
if (publicKey !== void 0 && opts.fromCloud) throw new KvboxError("either a public key or --from-cloud, not both");
|
|
1823
2143
|
const client = await clientFor({ ...cmd.optsWithGlobals(), local: true });
|
|
@@ -1855,7 +2175,7 @@ function describeReport(report) {
|
|
|
1855
2175
|
}
|
|
1856
2176
|
|
|
1857
2177
|
// src/commands/server/index.ts
|
|
1858
|
-
var serverCommand = new
|
|
2178
|
+
var serverCommand = new Command32("server").description("run or manage the kvbox server").addCommand(startCommand2).addCommand(statusCommand).addCommand(stopCommand2).addCommand(joinCommand).addCommand(leaveCommand).addCommand(relayCommand).addCommand(trustCaCommand);
|
|
1859
2179
|
|
|
1860
2180
|
// src/commands/login.ts
|
|
1861
2181
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
@@ -1863,8 +2183,8 @@ import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from
|
|
|
1863
2183
|
import { hostname as osHostname } from "os";
|
|
1864
2184
|
import { createInterface } from "readline/promises";
|
|
1865
2185
|
import { Writable } from "stream";
|
|
1866
|
-
import { Command as
|
|
1867
|
-
var loginCommand = new
|
|
2186
|
+
import { Command as Command33 } from "commander";
|
|
2187
|
+
var loginCommand = new Command33("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) => {
|
|
1868
2188
|
const url = normalizeUrl(urlArg ?? DEFAULT_CLOUD_URL);
|
|
1869
2189
|
const creds = await readCredentials(opts);
|
|
1870
2190
|
const session = await runLogin(url, creds, paths());
|
|
@@ -1962,8 +2282,8 @@ async function firstLine(stream) {
|
|
|
1962
2282
|
}
|
|
1963
2283
|
|
|
1964
2284
|
// src/commands/logout.ts
|
|
1965
|
-
import { Command as
|
|
1966
|
-
var logoutCommand = new
|
|
2285
|
+
import { Command as Command34 } from "commander";
|
|
2286
|
+
var logoutCommand = new Command34("logout").description("log out of the control plane; commands target this machine's kvbox-server again").action(async () => {
|
|
1967
2287
|
const p = paths();
|
|
1968
2288
|
const session = await readSession(p).catch(() => null);
|
|
1969
2289
|
switch (await runLogout(p)) {
|
|
@@ -2000,8 +2320,8 @@ async function runLogout(p = paths()) {
|
|
|
2000
2320
|
}
|
|
2001
2321
|
|
|
2002
2322
|
// src/commands/whoami.ts
|
|
2003
|
-
import { Command as
|
|
2004
|
-
var whoamiCommand = new
|
|
2323
|
+
import { Command as Command35 } from "commander";
|
|
2324
|
+
var whoamiCommand = new Command35("whoami").description("show which account and control plane commands target (or local mode)").option("--json", "print as JSON").action(async (opts) => {
|
|
2005
2325
|
const session = await readSession();
|
|
2006
2326
|
if (opts.json) {
|
|
2007
2327
|
json(session ? { user: session.user, url: session.url, sshCertExpires: session.sshCertExpires ?? null } : null);
|
|
@@ -2017,11 +2337,11 @@ function describeSession(session) {
|
|
|
2017
2337
|
}
|
|
2018
2338
|
|
|
2019
2339
|
// src/commands/snapshots/index.ts
|
|
2020
|
-
import { Command as
|
|
2340
|
+
import { Command as Command39 } from "commander";
|
|
2021
2341
|
|
|
2022
2342
|
// src/commands/snapshots/ls.ts
|
|
2023
|
-
import { Command as
|
|
2024
|
-
var
|
|
2343
|
+
import { Command as Command36 } from "commander";
|
|
2344
|
+
var lsCommand3 = new Command36("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) => {
|
|
2025
2345
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
2026
2346
|
try {
|
|
2027
2347
|
const cloud = client.target.kind === "cloud";
|
|
@@ -2052,8 +2372,8 @@ var lsCommand2 = new Command29("ls").description("list snapshots (yours and your
|
|
|
2052
2372
|
});
|
|
2053
2373
|
|
|
2054
2374
|
// src/commands/snapshots/new.ts
|
|
2055
|
-
import { Command as
|
|
2056
|
-
var newCommand2 = new
|
|
2375
|
+
import { Command as Command37 } from "commander";
|
|
2376
|
+
var newCommand2 = new Command37("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) => {
|
|
2057
2377
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
2058
2378
|
try {
|
|
2059
2379
|
const snap = await spin(`saving snapshot ${name} from box ${box} (memory + disk)\u2026`, client.createSnapshot({ box, name }));
|
|
@@ -2065,8 +2385,8 @@ var newCommand2 = new Command30("new").description("freeze a running box (memory
|
|
|
2065
2385
|
});
|
|
2066
2386
|
|
|
2067
2387
|
// src/commands/snapshots/rm.ts
|
|
2068
|
-
import { Command as
|
|
2069
|
-
var
|
|
2388
|
+
import { Command as Command38 } from "commander";
|
|
2389
|
+
var rmCommand3 = new Command38("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) => {
|
|
2070
2390
|
if (!await confirm(`Delete snapshot ${name}?`, opts.yes ?? false)) {
|
|
2071
2391
|
info("cancelled");
|
|
2072
2392
|
process.exitCode = 1;
|
|
@@ -2082,14 +2402,14 @@ var rmCommand2 = new Command31("rm").description("delete a snapshot (boxes made
|
|
|
2082
2402
|
});
|
|
2083
2403
|
|
|
2084
2404
|
// src/commands/snapshots/index.ts
|
|
2085
|
-
var snapshotsCommand = new
|
|
2405
|
+
var snapshotsCommand = new Command39("snapshots").aliases(["snapshot", "snap"]).description("freeze running boxes into snapshots and manage them").addCommand(newCommand2).addCommand(lsCommand3).addCommand(rmCommand3);
|
|
2086
2406
|
for (const cmd of snapshotsCommand.commands) cmd.option("--org <slug>", "act in this org (- for your personal boxes)");
|
|
2087
2407
|
|
|
2088
2408
|
// src/commands/org/index.ts
|
|
2089
|
-
import { Command as
|
|
2409
|
+
import { Command as Command45 } from "commander";
|
|
2090
2410
|
|
|
2091
2411
|
// src/commands/org/ls.ts
|
|
2092
|
-
import { Command as
|
|
2412
|
+
import { Command as Command40 } from "commander";
|
|
2093
2413
|
|
|
2094
2414
|
// src/commands/org/context.ts
|
|
2095
2415
|
var NOT_LOGGED_IN = "orgs live on the control plane: kvbox login first";
|
|
@@ -2109,7 +2429,7 @@ function orgFor(opts, target) {
|
|
|
2109
2429
|
}
|
|
2110
2430
|
|
|
2111
2431
|
// src/commands/org/ls.ts
|
|
2112
|
-
var
|
|
2432
|
+
var lsCommand4 = new Command40("ls").description("list the orgs you belong to and your role in each").option("--json", "print as JSON").action(async (opts, cmd) => {
|
|
2113
2433
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
2114
2434
|
try {
|
|
2115
2435
|
const orgs = await client.listOrgs();
|
|
@@ -2131,8 +2451,8 @@ function orgRows(orgs) {
|
|
|
2131
2451
|
}
|
|
2132
2452
|
|
|
2133
2453
|
// src/commands/org/members.ts
|
|
2134
|
-
import { Command as
|
|
2135
|
-
var
|
|
2454
|
+
import { Command as Command41, Option as Option3 } from "commander";
|
|
2455
|
+
var lsCommand5 = new Command41("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) => {
|
|
2136
2456
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
2137
2457
|
try {
|
|
2138
2458
|
const org = await client.getOrg(orgFor(opts, client.target));
|
|
@@ -2146,7 +2466,7 @@ var lsCommand4 = new Command34("ls").description("list an org's members and thei
|
|
|
2146
2466
|
await client.close();
|
|
2147
2467
|
}
|
|
2148
2468
|
});
|
|
2149
|
-
var
|
|
2469
|
+
var addCommand2 = new Command41("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) => {
|
|
2150
2470
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
2151
2471
|
try {
|
|
2152
2472
|
const slug = orgFor(opts, client.target);
|
|
@@ -2156,7 +2476,7 @@ var addCommand = new Command34("add").description("add a user to an org (admins
|
|
|
2156
2476
|
await client.close();
|
|
2157
2477
|
}
|
|
2158
2478
|
});
|
|
2159
|
-
var
|
|
2479
|
+
var rmCommand4 = new Command41("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) => {
|
|
2160
2480
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
2161
2481
|
try {
|
|
2162
2482
|
const slug = orgFor(opts, client.target);
|
|
@@ -2172,11 +2492,11 @@ var rmCommand3 = new Command34("rm").description("remove a member (or leave: rem
|
|
|
2172
2492
|
await client.close();
|
|
2173
2493
|
}
|
|
2174
2494
|
});
|
|
2175
|
-
var membersCommand = new
|
|
2495
|
+
var membersCommand = new Command41("members").description("who is in an org and what they may do").addCommand(lsCommand5).addCommand(addCommand2).addCommand(rmCommand4);
|
|
2176
2496
|
|
|
2177
2497
|
// src/commands/org/new.ts
|
|
2178
|
-
import { Command as
|
|
2179
|
-
var newCommand3 = new
|
|
2498
|
+
import { Command as Command42 } from "commander";
|
|
2499
|
+
var newCommand3 = new Command42("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) => {
|
|
2180
2500
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
2181
2501
|
try {
|
|
2182
2502
|
const org = await client.createOrg({ slug, name: opts.name ?? slug });
|
|
@@ -2192,8 +2512,8 @@ var newCommand3 = new Command35("new").description("create an org; you become it
|
|
|
2192
2512
|
});
|
|
2193
2513
|
|
|
2194
2514
|
// src/commands/org/rm.ts
|
|
2195
|
-
import { Command as
|
|
2196
|
-
var
|
|
2515
|
+
import { Command as Command43 } from "commander";
|
|
2516
|
+
var rmCommand5 = new Command43("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) => {
|
|
2197
2517
|
if (!await confirm(`Delete org ${slug}? Its members lose access; its slug stays taken.`, opts.yes ?? false)) {
|
|
2198
2518
|
info("cancelled");
|
|
2199
2519
|
process.exitCode = 1;
|
|
@@ -2209,8 +2529,8 @@ var rmCommand4 = new Command36("rm").description("delete an org (owners only; re
|
|
|
2209
2529
|
});
|
|
2210
2530
|
|
|
2211
2531
|
// src/commands/org/role.ts
|
|
2212
|
-
import { Argument, Command as
|
|
2213
|
-
var roleCommand = new
|
|
2532
|
+
import { Argument, Command as Command44 } from "commander";
|
|
2533
|
+
var roleCommand = new Command44("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) => {
|
|
2214
2534
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
2215
2535
|
try {
|
|
2216
2536
|
const slug = orgFor(opts, client.target);
|
|
@@ -2222,11 +2542,11 @@ var roleCommand = new Command37("role").description("change a member's role (own
|
|
|
2222
2542
|
});
|
|
2223
2543
|
|
|
2224
2544
|
// src/commands/org/index.ts
|
|
2225
|
-
var orgCommand = new
|
|
2545
|
+
var orgCommand = new Command45("org").description("orgs: shared slugs that own boxes, with owners, admins and members").addCommand(lsCommand4).addCommand(newCommand3).addCommand(membersCommand).addCommand(roleCommand).addCommand(rmCommand5);
|
|
2226
2546
|
|
|
2227
2547
|
// src/commands/usage.ts
|
|
2228
|
-
import { Command as
|
|
2229
|
-
var usageCommand2 = new
|
|
2548
|
+
import { Command as Command46 } from "commander";
|
|
2549
|
+
var usageCommand2 = new Command46("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) => {
|
|
2230
2550
|
for (const [name, value] of [
|
|
2231
2551
|
["since", opts.since],
|
|
2232
2552
|
["until", opts.until]
|
|
@@ -2299,8 +2619,8 @@ function usageRows(report, cloud) {
|
|
|
2299
2619
|
}
|
|
2300
2620
|
|
|
2301
2621
|
// src/commands/credit.ts
|
|
2302
|
-
import { Command as
|
|
2303
|
-
var creditCommand = new
|
|
2622
|
+
import { Command as Command47 } from "commander";
|
|
2623
|
+
var creditCommand = new Command47("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) => {
|
|
2304
2624
|
const client = await clientFor(cmd.optsWithGlobals(), { autoStart: false });
|
|
2305
2625
|
try {
|
|
2306
2626
|
if (client.target.kind === "local") throw new KvboxError("credit lives on the control plane: kvbox login first");
|
|
@@ -2329,7 +2649,7 @@ function creditRow(b, me2) {
|
|
|
2329
2649
|
|
|
2330
2650
|
// src/program.ts
|
|
2331
2651
|
function buildProgram() {
|
|
2332
|
-
const program = new
|
|
2652
|
+
const program = new Command48("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();
|
|
2333
2653
|
program.addCommand(vmCommand);
|
|
2334
2654
|
program.addCommand(snapshotsCommand);
|
|
2335
2655
|
program.addCommand(serverCommand);
|