@kvbox/cli 0.5.8 → 0.5.10
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 +225 -106
- 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 Command40 } from "commander";
|
|
151
151
|
|
|
152
152
|
// ../shared/src/version.ts
|
|
153
153
|
import { createRequire } from "module";
|
|
@@ -162,13 +162,13 @@ 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.10" : void 0);
|
|
166
166
|
|
|
167
167
|
// src/commands/vm/index.ts
|
|
168
|
-
import { Command as
|
|
168
|
+
import { Command as Command16 } from "commander";
|
|
169
169
|
|
|
170
170
|
// src/commands/vm/connect.ts
|
|
171
|
-
import { spawnSync } from "child_process";
|
|
171
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
172
172
|
import { Command as Command2 } from "commander";
|
|
173
173
|
|
|
174
174
|
// src/cert.ts
|
|
@@ -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(),
|
|
@@ -466,6 +474,12 @@ var CapacitySchema = z2.object({
|
|
|
466
474
|
reservedVcpu: z2.number().int(),
|
|
467
475
|
running: z2.number().int()
|
|
468
476
|
});
|
|
477
|
+
var RecoveryReportSchema = z2.object({
|
|
478
|
+
at: z2.string(),
|
|
479
|
+
booted: z2.array(z2.string()),
|
|
480
|
+
leftStopped: z2.array(z2.string()),
|
|
481
|
+
failed: z2.array(z2.object({ box: z2.string(), error: z2.string() }))
|
|
482
|
+
});
|
|
469
483
|
var HealthSchema = z2.object({
|
|
470
484
|
ok: z2.literal(true),
|
|
471
485
|
version: z2.string(),
|
|
@@ -488,8 +502,13 @@ var HealthSchema = z2.object({
|
|
|
488
502
|
/** The public IP the relay owns for this host. */
|
|
489
503
|
publicIp: z2.string(),
|
|
490
504
|
/** The relay's WireGuard endpoint the tunnel dials. */
|
|
491
|
-
endpoint: z2.string()
|
|
492
|
-
|
|
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()
|
|
509
|
+
}).nullable().optional(),
|
|
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. */
|
|
511
|
+
recovery: RecoveryReportSchema.nullable().optional()
|
|
493
512
|
});
|
|
494
513
|
var MachineUsageSchema = z2.object({
|
|
495
514
|
name: z2.string(),
|
|
@@ -739,6 +758,34 @@ function groupWithMember(gid, user, groupFile) {
|
|
|
739
758
|
return void 0;
|
|
740
759
|
}
|
|
741
760
|
|
|
761
|
+
// src/systemd.ts
|
|
762
|
+
import { spawnSync } from "child_process";
|
|
763
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
764
|
+
import { resolve } from "path";
|
|
765
|
+
var UNIT = "kvbox-server";
|
|
766
|
+
var UNIT_FILE = `/etc/systemd/system/${UNIT}.service`;
|
|
767
|
+
function hasUnitFor(p, file = UNIT_FILE) {
|
|
768
|
+
let text;
|
|
769
|
+
try {
|
|
770
|
+
text = readFileSync2(file, "utf8");
|
|
771
|
+
} catch {
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
const home = /^Environment="?KVBOX_HOME=([^"\n]+)"?$/m.exec(text)?.[1];
|
|
775
|
+
return home !== void 0 && resolve(home) === resolve(p.home);
|
|
776
|
+
}
|
|
777
|
+
function startUnitProcess() {
|
|
778
|
+
const res = spawnSync("sudo", ["-n", "systemctl", "start", UNIT], { encoding: "utf8" });
|
|
779
|
+
return { ok: res.status === 0, out: `${res.stdout ?? ""}${res.stderr ?? ""}`.trim() };
|
|
780
|
+
}
|
|
781
|
+
function unitActive() {
|
|
782
|
+
const res = spawnSync("systemctl", ["is-active", UNIT], { encoding: "utf8" });
|
|
783
|
+
return (res.stdout ?? "").trim() === "active";
|
|
784
|
+
}
|
|
785
|
+
function startsAtBoot(p) {
|
|
786
|
+
return hasUnitFor(p) && spawnSync("systemctl", ["is-enabled", "--quiet", UNIT]).status === 0;
|
|
787
|
+
}
|
|
788
|
+
|
|
742
789
|
// src/client.ts
|
|
743
790
|
var ORG_SLUG_RE = /^[a-z0-9][a-z0-9._-]{0,31}$/;
|
|
744
791
|
function orgOption(opts) {
|
|
@@ -853,6 +900,9 @@ var ServerClient = class {
|
|
|
853
900
|
rescaleMachine(name, req) {
|
|
854
901
|
return this.call("POST", `/machines/${encodeURIComponent(name)}/rescale`, MachineSchema, req);
|
|
855
902
|
}
|
|
903
|
+
setMachine(name, req) {
|
|
904
|
+
return this.call("POST", `/machines/${encodeURIComponent(name)}/set`, MachineSchema, req);
|
|
905
|
+
}
|
|
856
906
|
/** `since`: an ISO date, already validated; the server counts from the box's creation without it. */
|
|
857
907
|
machineUsage(name, since) {
|
|
858
908
|
const query = since === void 0 ? "" : `?since=${encodeURIComponent(since)}`;
|
|
@@ -979,6 +1029,9 @@ function sshArgs(ep, p = paths()) {
|
|
|
979
1029
|
...hostKey,
|
|
980
1030
|
"-o",
|
|
981
1031
|
"LogLevel=ERROR",
|
|
1032
|
+
// A quiet open session still moves a few bytes a minute, so the host's auto-hibernate (#47) sees it.
|
|
1033
|
+
"-o",
|
|
1034
|
+
"ServerAliveInterval=60",
|
|
982
1035
|
"-o",
|
|
983
1036
|
"SetEnv=LC_ALL=C.UTF-8",
|
|
984
1037
|
`${ep.user}@${ep.host}`
|
|
@@ -987,17 +1040,17 @@ function sshArgs(ep, p = paths()) {
|
|
|
987
1040
|
async function waitForSsh(ep, timeoutMs = 3e4) {
|
|
988
1041
|
const { connect } = await import("net");
|
|
989
1042
|
const deadline = Date.now() + timeoutMs;
|
|
990
|
-
const attempt = () => new Promise((
|
|
1043
|
+
const attempt = () => new Promise((resolve2) => {
|
|
991
1044
|
const sock = connect({ host: ep.host, port: ep.port, timeout: 1e3 });
|
|
992
1045
|
sock.once("connect", () => {
|
|
993
1046
|
sock.destroy();
|
|
994
|
-
|
|
1047
|
+
resolve2(true);
|
|
995
1048
|
});
|
|
996
1049
|
sock.once("timeout", () => {
|
|
997
1050
|
sock.destroy();
|
|
998
|
-
|
|
1051
|
+
resolve2(false);
|
|
999
1052
|
});
|
|
1000
|
-
sock.once("error", () =>
|
|
1053
|
+
sock.once("error", () => resolve2(false));
|
|
1001
1054
|
});
|
|
1002
1055
|
while (Date.now() < deadline) {
|
|
1003
1056
|
if (await attempt()) return;
|
|
@@ -1006,14 +1059,15 @@ async function waitForSsh(ep, timeoutMs = 3e4) {
|
|
|
1006
1059
|
throw new KvboxError(`box at ${ep.host}:${ep.port} did not answer within ${Math.round(timeoutMs / 1e3)}s`);
|
|
1007
1060
|
}
|
|
1008
1061
|
var NOT_A_HOST_HINT = "kvbox-server is not installed, so this machine is not a kvbox host. Set it up with install-server.sh, or log in to the cloud with: kvbox login";
|
|
1009
|
-
async function lastErrorLine(logPath) {
|
|
1062
|
+
async function lastErrorLine(logPath, from = 0) {
|
|
1010
1063
|
const { readFile: readFile6 } = await import("fs/promises");
|
|
1011
|
-
const text = await readFile6(logPath
|
|
1064
|
+
const text = (await readFile6(logPath).catch(() => Buffer.alloc(0))).subarray(from).toString("utf8");
|
|
1012
1065
|
const lines = text.trimEnd().split("\n").slice(-20).reverse();
|
|
1013
1066
|
return lines.find((l) => l.startsWith("error: "))?.slice("error: ".length);
|
|
1014
1067
|
}
|
|
1015
1068
|
async function startLocalServer(p = paths(), timeoutMs = 5e3) {
|
|
1016
1069
|
await mkdir2(p.home, { recursive: true });
|
|
1070
|
+
if (hasUnitFor(p)) return startUnit(p, timeoutMs);
|
|
1017
1071
|
const log = openSync(p.serverLog, "a");
|
|
1018
1072
|
const launcher = serverLauncher();
|
|
1019
1073
|
const child = spawn(launcher.cmd, launcher.args, { detached: true, stdio: ["ignore", log, log] });
|
|
@@ -1027,33 +1081,50 @@ async function startLocalServer(p = paths(), timeoutMs = 5e3) {
|
|
|
1027
1081
|
});
|
|
1028
1082
|
});
|
|
1029
1083
|
});
|
|
1030
|
-
|
|
1031
|
-
const deadline = Date.now() + timeoutMs;
|
|
1032
|
-
while (Date.now() < deadline) {
|
|
1033
|
-
if (await stat(p.serverSock).then(() => true, () => false)) {
|
|
1034
|
-
const probe = new ServerClient({ kind: "local", socketPath: p.serverSock }, { autoStart: false });
|
|
1035
|
-
try {
|
|
1036
|
-
await probe.health();
|
|
1037
|
-
return;
|
|
1038
|
-
} catch {
|
|
1039
|
-
} finally {
|
|
1040
|
-
await probe.close();
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
await sleep(100);
|
|
1044
|
-
}
|
|
1045
|
-
throw new KvboxError(`kvbox-server did not start within ${timeoutMs / 1e3}s; see ${p.serverLog}`);
|
|
1046
|
-
})();
|
|
1047
|
-
await Promise.race([ready, failed]);
|
|
1084
|
+
await Promise.race([serving(p, timeoutMs), failed]);
|
|
1048
1085
|
child.removeAllListeners("exit");
|
|
1049
1086
|
child.unref();
|
|
1050
1087
|
const pid = Number.parseInt(await readFile3(p.serverPid, "utf8").catch(() => ""), 10);
|
|
1051
1088
|
return { pid: Number.isNaN(pid) ? child.pid ?? -1 : pid, socketPath: p.serverSock, viaGroup: launcher.viaGroup };
|
|
1052
1089
|
}
|
|
1090
|
+
async function startUnit(p, timeoutMs) {
|
|
1091
|
+
const logFrom = await stat(p.serverLog).then((s) => s.size, () => 0);
|
|
1092
|
+
const res = startUnitProcess();
|
|
1093
|
+
if (!res.ok) {
|
|
1094
|
+
const why = res.out || await lastErrorLine(p.serverLog, logFrom) || "systemctl start failed";
|
|
1095
|
+
throw new KvboxError(`kvbox-server could not start: ${why} (journalctl -u ${UNIT}; rerun install-server.sh if sudo asked for a password)`);
|
|
1096
|
+
}
|
|
1097
|
+
await serving(p, timeoutMs, async () => {
|
|
1098
|
+
if (unitActive()) return;
|
|
1099
|
+
const why = await lastErrorLine(p.serverLog, logFrom) ?? "it exited right after starting";
|
|
1100
|
+
throw new KvboxError(`kvbox-server could not start: ${why} (journalctl -u ${UNIT})`);
|
|
1101
|
+
});
|
|
1102
|
+
const pid = Number.parseInt(await readFile3(p.serverPid, "utf8").catch(() => ""), 10);
|
|
1103
|
+
return { pid: Number.isNaN(pid) ? -1 : pid, socketPath: p.serverSock, viaGroup: void 0 };
|
|
1104
|
+
}
|
|
1105
|
+
async function serving(p, timeoutMs, check) {
|
|
1106
|
+
const deadline = Date.now() + timeoutMs;
|
|
1107
|
+
while (Date.now() < deadline) {
|
|
1108
|
+
await check?.();
|
|
1109
|
+
if (await stat(p.serverSock).then(() => true, () => false)) {
|
|
1110
|
+
const probe = new ServerClient({ kind: "local", socketPath: p.serverSock }, { autoStart: false });
|
|
1111
|
+
try {
|
|
1112
|
+
await probe.health();
|
|
1113
|
+
return;
|
|
1114
|
+
} catch {
|
|
1115
|
+
} finally {
|
|
1116
|
+
await probe.close();
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
await sleep(100);
|
|
1120
|
+
}
|
|
1121
|
+
throw new KvboxError(`kvbox-server did not start within ${timeoutMs / 1e3}s; see ${p.serverLog}`);
|
|
1122
|
+
}
|
|
1053
1123
|
|
|
1054
1124
|
// src/commands/vm/new.ts
|
|
1055
1125
|
import { Command, Option } from "commander";
|
|
1056
|
-
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) => {
|
|
1126
|
+
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) => {
|
|
1127
|
+
const autoHibernateSec = opts.autoHibernate === void 0 ? void 0 : parseIdleTimeout(opts.autoHibernate);
|
|
1057
1128
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1058
1129
|
try {
|
|
1059
1130
|
const what = opts.from !== void 0 ? `restoring ${name ?? "a new box"} from snapshot ${opts.from}\u2026` : `booting ${name ?? "a new box"}\u2026`;
|
|
@@ -1061,8 +1132,10 @@ var newCommand = new Command("new").description("boot a new box from the base im
|
|
|
1061
1132
|
...name !== void 0 ? { name } : {},
|
|
1062
1133
|
...opts.size !== void 0 ? { size: opts.size } : {},
|
|
1063
1134
|
...opts.from !== void 0 ? { from: opts.from } : {},
|
|
1135
|
+
...autoHibernateSec !== void 0 ? { autoHibernateSec } : {},
|
|
1064
1136
|
...opts.force ? { force: true } : {}
|
|
1065
1137
|
}));
|
|
1138
|
+
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`);
|
|
1066
1139
|
if (opts.json) {
|
|
1067
1140
|
json(box);
|
|
1068
1141
|
return;
|
|
@@ -1073,6 +1146,7 @@ var newCommand = new Command("new").description("boot a new box from the base im
|
|
|
1073
1146
|
info(`${color.green("\u2713")} ${describe(box, user)}`);
|
|
1074
1147
|
if (opts.from !== void 0) info(` connect with: kvbox connect ${displayName(box, user)} (already up: everything that ran in the snapshot is running)`);
|
|
1075
1148
|
else info(` connect with: kvbox connect ${displayName(box, user)} (SSH is up a couple of seconds after boot)`);
|
|
1149
|
+
if (box.autoHibernateSec) info(` it ${autoHibernateText(box.autoHibernateSec)}`);
|
|
1076
1150
|
} finally {
|
|
1077
1151
|
await client.close();
|
|
1078
1152
|
}
|
|
@@ -1084,6 +1158,18 @@ function describe(box, user) {
|
|
|
1084
1158
|
const from = box.source.startsWith("snapshot:") ? ` from snapshot ${shortRef(box.source.slice("snapshot:".length), box.owner)}` : "";
|
|
1085
1159
|
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`;
|
|
1086
1160
|
}
|
|
1161
|
+
function autoHibernateText(sec) {
|
|
1162
|
+
return sec ? `hibernates after ${duration(sec)} without network traffic (kvbox connect wakes it)` : "never hibernates on its own";
|
|
1163
|
+
}
|
|
1164
|
+
var UNIT_SEC = { m: 60, h: 3600, d: 86400 };
|
|
1165
|
+
function parseIdleTimeout(text) {
|
|
1166
|
+
const t = text.trim().toLowerCase();
|
|
1167
|
+
if (t === "off" || t === "never" || t === "0") return 0;
|
|
1168
|
+
if (!/^(\d+[mhd])+$/.test(t)) throw new KvboxError(`--auto-hibernate ${text}: give a duration like 30m, 4h or 1d, or off`);
|
|
1169
|
+
let sec = 0;
|
|
1170
|
+
for (const [, n, unit] of t.matchAll(/(\d+)([mhd])/g)) sec += Number(n) * UNIT_SEC[unit];
|
|
1171
|
+
return sec;
|
|
1172
|
+
}
|
|
1087
1173
|
|
|
1088
1174
|
// src/commands/vm/connect.ts
|
|
1089
1175
|
var SSH_FAILED = 255;
|
|
@@ -1104,14 +1190,14 @@ var connectCommand = new Command2("connect").alias("cn").description("open a she
|
|
|
1104
1190
|
} finally {
|
|
1105
1191
|
await client.close();
|
|
1106
1192
|
}
|
|
1107
|
-
let res =
|
|
1193
|
+
let res = spawnSync2("ssh", [...sshArgs(ep), ...cmdArgs], { stdio: "inherit" });
|
|
1108
1194
|
if (res.error) throw new KvboxError(`could not run ssh: ${res.error.message}`);
|
|
1109
1195
|
if (res.status === SSH_FAILED) {
|
|
1110
1196
|
const again = await clientFor(cmd.optsWithGlobals());
|
|
1111
1197
|
const retry = await renewAfterRefusal(again, cert, owner).finally(() => again.close());
|
|
1112
1198
|
if (retry) {
|
|
1113
1199
|
info("renewed your SSH certificate, trying again\u2026");
|
|
1114
|
-
res =
|
|
1200
|
+
res = spawnSync2("ssh", [...sshArgs(ep), ...cmdArgs], { stdio: "inherit" });
|
|
1115
1201
|
if (res.error) throw new KvboxError(`could not run ssh: ${res.error.message}`);
|
|
1116
1202
|
}
|
|
1117
1203
|
}
|
|
@@ -1155,12 +1241,12 @@ async function runSsh(ssh, opts) {
|
|
|
1155
1241
|
timedOut = true;
|
|
1156
1242
|
child.kill("SIGTERM");
|
|
1157
1243
|
}, opts.timeout * 1e3);
|
|
1158
|
-
const status2 = await new Promise((
|
|
1244
|
+
const status2 = await new Promise((resolve2, reject) => {
|
|
1159
1245
|
child.once("error", (err) => reject(new KvboxError(`could not run ssh: ${err.message}`)));
|
|
1160
1246
|
child.once("close", (code, signal) => {
|
|
1161
|
-
if (timedOut)
|
|
1162
|
-
else if (code !== null)
|
|
1163
|
-
else
|
|
1247
|
+
if (timedOut) resolve2(TIMEOUT_EXIT);
|
|
1248
|
+
else if (code !== null) resolve2(code);
|
|
1249
|
+
else resolve2(signal === "SIGINT" ? 130 : 1);
|
|
1164
1250
|
});
|
|
1165
1251
|
}).finally(() => clearTimeout(timer));
|
|
1166
1252
|
return { status: status2, chunks, timedOut };
|
|
@@ -1359,9 +1445,29 @@ var rmCommand = new Command9("rm").description("destroy a box (its disk is delet
|
|
|
1359
1445
|
}
|
|
1360
1446
|
});
|
|
1361
1447
|
|
|
1362
|
-
// src/commands/vm/
|
|
1448
|
+
// src/commands/vm/set.ts
|
|
1363
1449
|
import { Command as Command10 } from "commander";
|
|
1364
|
-
var
|
|
1450
|
+
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) => {
|
|
1451
|
+
if (opts.autoHibernate === void 0) throw new KvboxError("nothing to change: give --auto-hibernate");
|
|
1452
|
+
const autoHibernateSec = parseIdleTimeout(opts.autoHibernate);
|
|
1453
|
+
const client = await clientFor(cmd.optsWithGlobals());
|
|
1454
|
+
try {
|
|
1455
|
+
const found = pickRef(await client.listMachines(), name, me(client));
|
|
1456
|
+
if (!found) throw new KvboxError(`box ${name}: not found (kvbox ls)`);
|
|
1457
|
+
const box = await spin(`updating ${name}\u2026`, client.setMachine(displayName(found, me(client)), { autoHibernateSec }));
|
|
1458
|
+
if (opts.json) {
|
|
1459
|
+
json(box);
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
info(`${color.green("\u2713")} box ${color.bold(name)}: ${autoHibernateText(box.autoHibernateSec)}`);
|
|
1463
|
+
} finally {
|
|
1464
|
+
await client.close();
|
|
1465
|
+
}
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
// src/commands/vm/start.ts
|
|
1469
|
+
import { Command as Command11 } from "commander";
|
|
1470
|
+
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) => {
|
|
1365
1471
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1366
1472
|
try {
|
|
1367
1473
|
const box = await spin(`booting ${name}\u2026`, client.startMachine(name));
|
|
@@ -1373,8 +1479,8 @@ var startCommand = new Command10("start").description("boot a stopped box again
|
|
|
1373
1479
|
});
|
|
1374
1480
|
|
|
1375
1481
|
// src/commands/vm/stop.ts
|
|
1376
|
-
import { Command as
|
|
1377
|
-
var stopCommand = new
|
|
1482
|
+
import { Command as Command12 } from "commander";
|
|
1483
|
+
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) => {
|
|
1378
1484
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1379
1485
|
try {
|
|
1380
1486
|
const box = await spin(`shutting ${name} down\u2026`, client.stopMachine(name));
|
|
@@ -1386,8 +1492,8 @@ var stopCommand = new Command11("stop").description("shut a box down (its disk i
|
|
|
1386
1492
|
});
|
|
1387
1493
|
|
|
1388
1494
|
// src/commands/vm/update.ts
|
|
1389
|
-
import { Command as
|
|
1390
|
-
var updateCommand = new
|
|
1495
|
+
import { Command as Command13 } from "commander";
|
|
1496
|
+
var updateCommand = new Command13("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) => {
|
|
1391
1497
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1392
1498
|
try {
|
|
1393
1499
|
const before = pickRef(await client.listMachines(), name, me(client));
|
|
@@ -1409,8 +1515,8 @@ var updateCommand = new Command12("update").description("move a box onto the cur
|
|
|
1409
1515
|
});
|
|
1410
1516
|
|
|
1411
1517
|
// src/commands/vm/usage.ts
|
|
1412
|
-
import { Command as
|
|
1413
|
-
var usageCommand = new
|
|
1518
|
+
import { Command as Command14 } from "commander";
|
|
1519
|
+
var usageCommand = new Command14("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) => {
|
|
1414
1520
|
let since;
|
|
1415
1521
|
if (opts.since !== void 0) {
|
|
1416
1522
|
const t = Date.parse(opts.since);
|
|
@@ -1457,8 +1563,8 @@ function block(u) {
|
|
|
1457
1563
|
}
|
|
1458
1564
|
|
|
1459
1565
|
// src/commands/vm/wake.ts
|
|
1460
|
-
import { Command as
|
|
1461
|
-
var wakeCommand = new
|
|
1566
|
+
import { Command as Command15 } from "commander";
|
|
1567
|
+
var wakeCommand = new Command15("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) => {
|
|
1462
1568
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1463
1569
|
try {
|
|
1464
1570
|
const box = await spin(`waking ${name} (loading its memory from disk)\u2026`, client.wakeMachine(name));
|
|
@@ -1470,15 +1576,15 @@ var wakeCommand = new Command14("wake").description("continue a hibernated box w
|
|
|
1470
1576
|
});
|
|
1471
1577
|
|
|
1472
1578
|
// src/commands/vm/index.ts
|
|
1473
|
-
var vmCommand = new
|
|
1579
|
+
var vmCommand = new Command16("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(rmCommand);
|
|
1474
1580
|
for (const cmd of vmCommand.commands) cmd.option("--org <slug>", "act in this org (- for your personal boxes)");
|
|
1475
1581
|
var vmAliases = [newCommand, lsCommand, connectCommand, execCommand, stopCommand, startCommand, pauseCommand, resumeCommand, hibernateCommand, wakeCommand, rmCommand];
|
|
1476
1582
|
|
|
1477
1583
|
// src/commands/server/index.ts
|
|
1478
|
-
import { Command as
|
|
1584
|
+
import { Command as Command24 } from "commander";
|
|
1479
1585
|
|
|
1480
1586
|
// src/commands/server/join.ts
|
|
1481
|
-
import { Command as
|
|
1587
|
+
import { Command as Command17 } from "commander";
|
|
1482
1588
|
|
|
1483
1589
|
// src/commands/server/daemon.ts
|
|
1484
1590
|
import { spawn as spawn3 } from "child_process";
|
|
@@ -1515,7 +1621,7 @@ async function restartLocalServer(p = paths()) {
|
|
|
1515
1621
|
return (await startLocalServer(p)).pid;
|
|
1516
1622
|
}
|
|
1517
1623
|
function runKvboxServer(args) {
|
|
1518
|
-
return new Promise((
|
|
1624
|
+
return new Promise((resolve2, reject) => {
|
|
1519
1625
|
const child = spawn3("kvbox-server", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
1520
1626
|
let out = "";
|
|
1521
1627
|
let err = "";
|
|
@@ -1525,14 +1631,14 @@ function runKvboxServer(args) {
|
|
|
1525
1631
|
reject(new KvboxError(e.code === "ENOENT" ? NOT_A_HOST_HINT : `could not run kvbox-server: ${e.message}`));
|
|
1526
1632
|
});
|
|
1527
1633
|
child.once("close", (code) => {
|
|
1528
|
-
if (code === 0)
|
|
1634
|
+
if (code === 0) resolve2(out.trimEnd());
|
|
1529
1635
|
else reject(new KvboxError(err.trim().replace(/^error: /, "") || `kvbox-server ${args[0] ?? ""} exited with code ${String(code)}`, code === 2 ? 2 : 1));
|
|
1530
1636
|
});
|
|
1531
1637
|
});
|
|
1532
1638
|
}
|
|
1533
1639
|
|
|
1534
1640
|
// src/commands/server/join.ts
|
|
1535
|
-
var joinCommand = new
|
|
1641
|
+
var joinCommand = new Command17("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) => {
|
|
1536
1642
|
const args = ["join", url, "--token", opts.token];
|
|
1537
1643
|
if (opts.advertise !== void 0) args.push("--advertise", opts.advertise);
|
|
1538
1644
|
if (opts.name !== void 0) args.push("--name", opts.name);
|
|
@@ -1542,8 +1648,8 @@ var joinCommand = new Command16("join").description("put this machine behind a c
|
|
|
1542
1648
|
});
|
|
1543
1649
|
|
|
1544
1650
|
// src/commands/server/leave.ts
|
|
1545
|
-
import { Command as
|
|
1546
|
-
var leaveCommand = new
|
|
1651
|
+
import { Command as Command18 } from "commander";
|
|
1652
|
+
var leaveCommand = new Command18("leave").description("take this machine out from behind its control plane and restart its server").action(async () => {
|
|
1547
1653
|
const p = paths();
|
|
1548
1654
|
for (const line of (await runKvboxServer(["leave"])).split("\n")) info(line);
|
|
1549
1655
|
if (await runningPid(p) === null) return;
|
|
@@ -1552,14 +1658,14 @@ var leaveCommand = new Command17("leave").description("take this machine out fro
|
|
|
1552
1658
|
});
|
|
1553
1659
|
|
|
1554
1660
|
// src/commands/server/relay.ts
|
|
1555
|
-
import { Command as
|
|
1556
|
-
var relayCommand = new
|
|
1661
|
+
import { Command as Command19 } from "commander";
|
|
1662
|
+
var relayCommand = new Command19("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) => {
|
|
1557
1663
|
for (const line of (await runKvboxServer(["relay", joinString])).split("\n")) info(line);
|
|
1558
1664
|
});
|
|
1559
1665
|
|
|
1560
1666
|
// src/commands/server/start.ts
|
|
1561
|
-
import { Command as
|
|
1562
|
-
var startCommand2 = new
|
|
1667
|
+
import { Command as Command20 } from "commander";
|
|
1668
|
+
var startCommand2 = new Command20("start").description("start the local kvbox-server in the background").action(async (_opts, cmd) => {
|
|
1563
1669
|
const client = await clientFor({ ...cmd.optsWithGlobals(), local: true }, { autoStart: false });
|
|
1564
1670
|
try {
|
|
1565
1671
|
const health = await client.health();
|
|
@@ -1575,17 +1681,30 @@ var startCommand2 = new Command19("start").description("start the local kvbox-se
|
|
|
1575
1681
|
});
|
|
1576
1682
|
|
|
1577
1683
|
// src/commands/server/status.ts
|
|
1578
|
-
import { Command as
|
|
1579
|
-
var statusCommand = new
|
|
1684
|
+
import { Command as Command21 } from "commander";
|
|
1685
|
+
var statusCommand = new Command21("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) => {
|
|
1580
1686
|
const client = await clientFor({ ...cmd.optsWithGlobals(), local: true });
|
|
1581
1687
|
try {
|
|
1582
1688
|
const health = await client.health();
|
|
1583
|
-
|
|
1689
|
+
const atBoot = startsAtBoot(paths());
|
|
1690
|
+
if (opts.json) json({ ...health, startsAtBoot: atBoot });
|
|
1584
1691
|
else {
|
|
1585
1692
|
info(`kvbox-server ${health.version} running, pid ${health.pid}`);
|
|
1693
|
+
info(atBoot ? "starts at boot: yes, and brings back the boxes that were running" : "starts at boot: no (install-server.sh sets that up)");
|
|
1694
|
+
const r = health.recovery;
|
|
1695
|
+
if (r) {
|
|
1696
|
+
if (r.booted.length > 0) info(`after the last restart it booted again: ${r.booted.join(", ")}`);
|
|
1697
|
+
if (r.leftStopped.length > 0) info(`left stopped (were in standby, their memory was lost): ${r.leftStopped.join(", ")}`);
|
|
1698
|
+
for (const f of r.failed) info(`did not come back: ${f.box} (${f.error})`);
|
|
1699
|
+
}
|
|
1586
1700
|
if (health.controlPlane) info(`control plane: ${health.controlPlane.name} at ${health.controlPlane.url}, this host answers it on ${health.controlPlane.hostUrl}`);
|
|
1587
1701
|
else info("control plane: none (kvbox server join <url> --token <token> puts this machine behind one)");
|
|
1588
|
-
if (health.relay)
|
|
1702
|
+
if (health.relay) {
|
|
1703
|
+
info(`relay: ${health.relay.name} lends this machine ${health.relay.publicIp} (tunnel to ${health.relay.endpoint})`);
|
|
1704
|
+
if (health.relay.restarts && health.relay.lastRestartAt) {
|
|
1705
|
+
info(`the tunnel stopped carrying traffic and was redialed ${health.relay.restarts} time(s) since the server started, last at ${health.relay.lastRestartAt}`);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1589
1708
|
if (health.base === null) info("base image: none usable yet (run install-server.sh)");
|
|
1590
1709
|
else info(`base image: ${health.base} (what kvbox new uses; kvbox vm update <box> moves an older box onto it)`);
|
|
1591
1710
|
}
|
|
@@ -1595,8 +1714,8 @@ var statusCommand = new Command20("status").description("show whether this machi
|
|
|
1595
1714
|
});
|
|
1596
1715
|
|
|
1597
1716
|
// src/commands/server/stop.ts
|
|
1598
|
-
import { Command as
|
|
1599
|
-
var stopCommand2 = new
|
|
1717
|
+
import { Command as Command22 } from "commander";
|
|
1718
|
+
var stopCommand2 = new Command22("stop").description("stop the local server (running boxes are not affected)").action(async () => {
|
|
1600
1719
|
const pid = await runningPid(paths());
|
|
1601
1720
|
if (pid === null) throw new KvboxError("kvbox-server is not running");
|
|
1602
1721
|
await stopLocalServer(pid);
|
|
@@ -1604,8 +1723,8 @@ var stopCommand2 = new Command21("stop").description("stop the local server (run
|
|
|
1604
1723
|
});
|
|
1605
1724
|
|
|
1606
1725
|
// src/commands/server/trust-ca.ts
|
|
1607
|
-
import { Command as
|
|
1608
|
-
var trustCaCommand = new
|
|
1726
|
+
import { Command as Command23 } from "commander";
|
|
1727
|
+
var trustCaCommand = new Command23("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) => {
|
|
1609
1728
|
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");
|
|
1610
1729
|
if (publicKey !== void 0 && opts.fromCloud) throw new KvboxError("either a public key or --from-cloud, not both");
|
|
1611
1730
|
const client = await clientFor({ ...cmd.optsWithGlobals(), local: true });
|
|
@@ -1643,16 +1762,16 @@ function describeReport(report) {
|
|
|
1643
1762
|
}
|
|
1644
1763
|
|
|
1645
1764
|
// src/commands/server/index.ts
|
|
1646
|
-
var serverCommand = new
|
|
1765
|
+
var serverCommand = new Command24("server").description("run or manage the kvbox server").addCommand(startCommand2).addCommand(statusCommand).addCommand(stopCommand2).addCommand(joinCommand).addCommand(leaveCommand).addCommand(relayCommand).addCommand(trustCaCommand);
|
|
1647
1766
|
|
|
1648
1767
|
// src/commands/login.ts
|
|
1649
|
-
import { spawnSync as
|
|
1768
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
1650
1769
|
import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
|
|
1651
1770
|
import { hostname as osHostname } from "os";
|
|
1652
1771
|
import { createInterface } from "readline/promises";
|
|
1653
1772
|
import { Writable } from "stream";
|
|
1654
|
-
import { Command as
|
|
1655
|
-
var loginCommand = new
|
|
1773
|
+
import { Command as Command25 } from "commander";
|
|
1774
|
+
var loginCommand = new Command25("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) => {
|
|
1656
1775
|
const url = normalizeUrl(urlArg ?? DEFAULT_CLOUD_URL);
|
|
1657
1776
|
const creds = await readCredentials(opts);
|
|
1658
1777
|
const session = await runLogin(url, creds, paths());
|
|
@@ -1698,7 +1817,7 @@ async function ensureSshKey(p = paths(), hostname = osHostname()) {
|
|
|
1698
1817
|
await mkdir3(p.ssh, { recursive: true, mode: 448 });
|
|
1699
1818
|
const havePrivate = await readFile5(p.sshKey).then(() => true, () => false);
|
|
1700
1819
|
const args = havePrivate ? ["-y", "-f", p.sshKey] : ["-q", "-t", "ed25519", "-N", "", "-C", `kvbox@${hostname}`, "-f", p.sshKey];
|
|
1701
|
-
const res =
|
|
1820
|
+
const res = spawnSync3("ssh-keygen", args, { stdio: ["ignore", "pipe", "pipe"], encoding: "utf8" });
|
|
1702
1821
|
if (res.error) {
|
|
1703
1822
|
if (res.error.code === "ENOENT") throw new KvboxError("ssh-keygen not found; install OpenSSH");
|
|
1704
1823
|
throw new KvboxError(`could not run ssh-keygen: ${res.error.message}`);
|
|
@@ -1750,8 +1869,8 @@ async function firstLine(stream) {
|
|
|
1750
1869
|
}
|
|
1751
1870
|
|
|
1752
1871
|
// src/commands/logout.ts
|
|
1753
|
-
import { Command as
|
|
1754
|
-
var logoutCommand = new
|
|
1872
|
+
import { Command as Command26 } from "commander";
|
|
1873
|
+
var logoutCommand = new Command26("logout").description("log out of the control plane; commands target this machine's kvbox-server again").action(async () => {
|
|
1755
1874
|
const p = paths();
|
|
1756
1875
|
const session = await readSession(p).catch(() => null);
|
|
1757
1876
|
switch (await runLogout(p)) {
|
|
@@ -1788,8 +1907,8 @@ async function runLogout(p = paths()) {
|
|
|
1788
1907
|
}
|
|
1789
1908
|
|
|
1790
1909
|
// src/commands/whoami.ts
|
|
1791
|
-
import { Command as
|
|
1792
|
-
var whoamiCommand = new
|
|
1910
|
+
import { Command as Command27 } from "commander";
|
|
1911
|
+
var whoamiCommand = new Command27("whoami").description("show which account and control plane commands target (or local mode)").option("--json", "print as JSON").action(async (opts) => {
|
|
1793
1912
|
const session = await readSession();
|
|
1794
1913
|
if (opts.json) {
|
|
1795
1914
|
json(session ? { user: session.user, url: session.url, sshCertExpires: session.sshCertExpires ?? null } : null);
|
|
@@ -1805,11 +1924,11 @@ function describeSession(session) {
|
|
|
1805
1924
|
}
|
|
1806
1925
|
|
|
1807
1926
|
// src/commands/snapshots/index.ts
|
|
1808
|
-
import { Command as
|
|
1927
|
+
import { Command as Command31 } from "commander";
|
|
1809
1928
|
|
|
1810
1929
|
// src/commands/snapshots/ls.ts
|
|
1811
|
-
import { Command as
|
|
1812
|
-
var lsCommand2 = new
|
|
1930
|
+
import { Command as Command28, Option as Option4 } from "commander";
|
|
1931
|
+
var lsCommand2 = new Command28("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) => {
|
|
1813
1932
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1814
1933
|
try {
|
|
1815
1934
|
const cloud = client.target.kind === "cloud";
|
|
@@ -1840,8 +1959,8 @@ var lsCommand2 = new Command27("ls").description("list snapshots (yours and your
|
|
|
1840
1959
|
});
|
|
1841
1960
|
|
|
1842
1961
|
// src/commands/snapshots/new.ts
|
|
1843
|
-
import { Command as
|
|
1844
|
-
var newCommand2 = new
|
|
1962
|
+
import { Command as Command29 } from "commander";
|
|
1963
|
+
var newCommand2 = new Command29("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) => {
|
|
1845
1964
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
1846
1965
|
try {
|
|
1847
1966
|
const snap = await spin(`saving snapshot ${name} from box ${box} (memory + disk)\u2026`, client.createSnapshot({ box, name }));
|
|
@@ -1853,8 +1972,8 @@ var newCommand2 = new Command28("new").description("freeze a running box (memory
|
|
|
1853
1972
|
});
|
|
1854
1973
|
|
|
1855
1974
|
// src/commands/snapshots/rm.ts
|
|
1856
|
-
import { Command as
|
|
1857
|
-
var rmCommand2 = new
|
|
1975
|
+
import { Command as Command30 } from "commander";
|
|
1976
|
+
var rmCommand2 = new Command30("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) => {
|
|
1858
1977
|
if (!await confirm(`Delete snapshot ${name}?`, opts.yes ?? false)) {
|
|
1859
1978
|
info("cancelled");
|
|
1860
1979
|
process.exitCode = 1;
|
|
@@ -1870,14 +1989,14 @@ var rmCommand2 = new Command29("rm").description("delete a snapshot (boxes made
|
|
|
1870
1989
|
});
|
|
1871
1990
|
|
|
1872
1991
|
// src/commands/snapshots/index.ts
|
|
1873
|
-
var snapshotsCommand = new
|
|
1992
|
+
var snapshotsCommand = new Command31("snapshots").aliases(["snapshot", "snap"]).description("freeze running boxes into snapshots and manage them").addCommand(newCommand2).addCommand(lsCommand2).addCommand(rmCommand2);
|
|
1874
1993
|
for (const cmd of snapshotsCommand.commands) cmd.option("--org <slug>", "act in this org (- for your personal boxes)");
|
|
1875
1994
|
|
|
1876
1995
|
// src/commands/org/index.ts
|
|
1877
|
-
import { Command as
|
|
1996
|
+
import { Command as Command37 } from "commander";
|
|
1878
1997
|
|
|
1879
1998
|
// src/commands/org/ls.ts
|
|
1880
|
-
import { Command as
|
|
1999
|
+
import { Command as Command32 } from "commander";
|
|
1881
2000
|
|
|
1882
2001
|
// src/commands/org/context.ts
|
|
1883
2002
|
var NOT_LOGGED_IN = "orgs live on the control plane: kvbox login first";
|
|
@@ -1897,7 +2016,7 @@ function orgFor(opts, target) {
|
|
|
1897
2016
|
}
|
|
1898
2017
|
|
|
1899
2018
|
// src/commands/org/ls.ts
|
|
1900
|
-
var lsCommand3 = new
|
|
2019
|
+
var lsCommand3 = new Command32("ls").description("list the orgs you belong to and your role in each").option("--json", "print as JSON").action(async (opts, cmd) => {
|
|
1901
2020
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
1902
2021
|
try {
|
|
1903
2022
|
const orgs = await client.listOrgs();
|
|
@@ -1919,8 +2038,8 @@ function orgRows(orgs) {
|
|
|
1919
2038
|
}
|
|
1920
2039
|
|
|
1921
2040
|
// src/commands/org/members.ts
|
|
1922
|
-
import { Command as
|
|
1923
|
-
var lsCommand4 = new
|
|
2041
|
+
import { Command as Command33, Option as Option5 } from "commander";
|
|
2042
|
+
var lsCommand4 = new Command33("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) => {
|
|
1924
2043
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
1925
2044
|
try {
|
|
1926
2045
|
const org = await client.getOrg(orgFor(opts, client.target));
|
|
@@ -1934,7 +2053,7 @@ var lsCommand4 = new Command32("ls").description("list an org's members and thei
|
|
|
1934
2053
|
await client.close();
|
|
1935
2054
|
}
|
|
1936
2055
|
});
|
|
1937
|
-
var addCommand = new
|
|
2056
|
+
var addCommand = new Command33("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) => {
|
|
1938
2057
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
1939
2058
|
try {
|
|
1940
2059
|
const slug = orgFor(opts, client.target);
|
|
@@ -1944,7 +2063,7 @@ var addCommand = new Command32("add").description("add a user to an org (admins
|
|
|
1944
2063
|
await client.close();
|
|
1945
2064
|
}
|
|
1946
2065
|
});
|
|
1947
|
-
var rmCommand3 = new
|
|
2066
|
+
var rmCommand3 = new Command33("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) => {
|
|
1948
2067
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
1949
2068
|
try {
|
|
1950
2069
|
const slug = orgFor(opts, client.target);
|
|
@@ -1960,11 +2079,11 @@ var rmCommand3 = new Command32("rm").description("remove a member (or leave: rem
|
|
|
1960
2079
|
await client.close();
|
|
1961
2080
|
}
|
|
1962
2081
|
});
|
|
1963
|
-
var membersCommand = new
|
|
2082
|
+
var membersCommand = new Command33("members").description("who is in an org and what they may do").addCommand(lsCommand4).addCommand(addCommand).addCommand(rmCommand3);
|
|
1964
2083
|
|
|
1965
2084
|
// src/commands/org/new.ts
|
|
1966
|
-
import { Command as
|
|
1967
|
-
var newCommand3 = new
|
|
2085
|
+
import { Command as Command34 } from "commander";
|
|
2086
|
+
var newCommand3 = new Command34("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) => {
|
|
1968
2087
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
1969
2088
|
try {
|
|
1970
2089
|
const org = await client.createOrg({ slug, name: opts.name ?? slug });
|
|
@@ -1980,8 +2099,8 @@ var newCommand3 = new Command33("new").description("create an org; you become it
|
|
|
1980
2099
|
});
|
|
1981
2100
|
|
|
1982
2101
|
// src/commands/org/rm.ts
|
|
1983
|
-
import { Command as
|
|
1984
|
-
var rmCommand4 = new
|
|
2102
|
+
import { Command as Command35 } from "commander";
|
|
2103
|
+
var rmCommand4 = new Command35("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) => {
|
|
1985
2104
|
if (!await confirm(`Delete org ${slug}? Its members lose access; its slug stays taken.`, opts.yes ?? false)) {
|
|
1986
2105
|
info("cancelled");
|
|
1987
2106
|
process.exitCode = 1;
|
|
@@ -1997,8 +2116,8 @@ var rmCommand4 = new Command34("rm").description("delete an org (owners only; re
|
|
|
1997
2116
|
});
|
|
1998
2117
|
|
|
1999
2118
|
// src/commands/org/role.ts
|
|
2000
|
-
import { Argument, Command as
|
|
2001
|
-
var roleCommand = new
|
|
2119
|
+
import { Argument, Command as Command36 } from "commander";
|
|
2120
|
+
var roleCommand = new Command36("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) => {
|
|
2002
2121
|
const client = await cloudClientFor(cmd.optsWithGlobals());
|
|
2003
2122
|
try {
|
|
2004
2123
|
const slug = orgFor(opts, client.target);
|
|
@@ -2010,11 +2129,11 @@ var roleCommand = new Command35("role").description("change a member's role (own
|
|
|
2010
2129
|
});
|
|
2011
2130
|
|
|
2012
2131
|
// src/commands/org/index.ts
|
|
2013
|
-
var orgCommand = new
|
|
2132
|
+
var orgCommand = new Command37("org").description("orgs: shared slugs that own boxes, with owners, admins and members").addCommand(lsCommand3).addCommand(newCommand3).addCommand(membersCommand).addCommand(roleCommand).addCommand(rmCommand4);
|
|
2014
2133
|
|
|
2015
2134
|
// src/commands/usage.ts
|
|
2016
|
-
import { Command as
|
|
2017
|
-
var usageCommand2 = new
|
|
2135
|
+
import { Command as Command38 } from "commander";
|
|
2136
|
+
var usageCommand2 = new Command38("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) => {
|
|
2018
2137
|
for (const [name, value] of [
|
|
2019
2138
|
["since", opts.since],
|
|
2020
2139
|
["until", opts.until]
|
|
@@ -2087,8 +2206,8 @@ function usageRows(report, cloud) {
|
|
|
2087
2206
|
}
|
|
2088
2207
|
|
|
2089
2208
|
// src/commands/credit.ts
|
|
2090
|
-
import { Command as
|
|
2091
|
-
var creditCommand = new
|
|
2209
|
+
import { Command as Command39 } from "commander";
|
|
2210
|
+
var creditCommand = new Command39("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) => {
|
|
2092
2211
|
const client = await clientFor(cmd.optsWithGlobals(), { autoStart: false });
|
|
2093
2212
|
try {
|
|
2094
2213
|
if (client.target.kind === "local") throw new KvboxError("credit lives on the control plane: kvbox login first");
|
|
@@ -2117,7 +2236,7 @@ function creditRow(b, me2) {
|
|
|
2117
2236
|
|
|
2118
2237
|
// src/program.ts
|
|
2119
2238
|
function buildProgram() {
|
|
2120
|
-
const program = new
|
|
2239
|
+
const program = new Command40("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();
|
|
2121
2240
|
program.addCommand(vmCommand);
|
|
2122
2241
|
program.addCommand(snapshotsCommand);
|
|
2123
2242
|
program.addCommand(serverCommand);
|