@kvbox/cli 0.5.23 → 0.5.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/dist/kvbox.js +230 -16
- package/dist/kvbox.js.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @kvbox/cli
|
|
2
|
+
|
|
3
|
+
The `kvbox` command: Firecracker microVMs with Docker-like ergonomics. Every box is a real VM with its own kernel, boots in seconds, and can be snapshotted warm and cloned.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
npm i -g @kvbox/cli
|
|
7
|
+
kvbox login # use kvbox.sh, or point it at your own host
|
|
8
|
+
kvbox new dev --size m # boot a box
|
|
9
|
+
kvbox connect dev # shell in over SSH
|
|
10
|
+
kvbox exec dev -- uname -a
|
|
11
|
+
kvbox snapshots new dev base # memory + disk, the box keeps running
|
|
12
|
+
kvbox new api --from base # a warm clone
|
|
13
|
+
kvbox volume create data --size 50GB --box dev # a disk that outlives the box, at /mnt/data
|
|
14
|
+
kvbox ls / kvbox rm dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This is a thin client, any OS. It talks to [kvbox.sh](https://kvbox.sh) or to a host of your own running [`@kvbox/server`](https://www.npmjs.com/package/@kvbox/server). Every listing command takes `--json`.
|
|
18
|
+
|
|
19
|
+
Docs: [kvbox.sh/docs](https://kvbox.sh/docs). Agent skill: [kvbox.sh/kvbox/SKILL.md](https://kvbox.sh/kvbox/SKILL.md).
|
package/dist/kvbox.js
CHANGED
|
@@ -149,7 +149,7 @@ async function spin(message, work, opts = {}) {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
// src/program.ts
|
|
152
|
-
import { Command as
|
|
152
|
+
import { Command as Command61 } from "commander";
|
|
153
153
|
|
|
154
154
|
// ../shared/src/version.ts
|
|
155
155
|
import { createRequire } from "module";
|
|
@@ -164,7 +164,7 @@ function versionOf(srcFileUrl, injected) {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
// src/version.ts
|
|
167
|
-
var VERSION = versionOf(import.meta.url, true ? "0.5.
|
|
167
|
+
var VERSION = versionOf(import.meta.url, true ? "0.5.25" : void 0);
|
|
168
168
|
|
|
169
169
|
// src/commands/vm/index.ts
|
|
170
170
|
import { Command as Command26 } from "commander";
|
|
@@ -262,6 +262,9 @@ function paths(home = kvboxHome()) {
|
|
|
262
262
|
disk: join(dir, "disk.ext4")
|
|
263
263
|
};
|
|
264
264
|
},
|
|
265
|
+
/** Each owner's environment variables as the control plane last sent them (#88): env/<owner>.json, mode 600. */
|
|
266
|
+
env: join(home, "env"),
|
|
267
|
+
ownerEnv: (owner) => join(home, "env", `${owner}.json`),
|
|
265
268
|
snapshot: (owner, name) => {
|
|
266
269
|
const dir = join(home, "snapshots", owner, name);
|
|
267
270
|
return {
|
|
@@ -496,15 +499,35 @@ var CreateVolumeRequest = z2.object({
|
|
|
496
499
|
name: z2.string(),
|
|
497
500
|
sizeGib: z2.number().int().min(1).max(MAX_VOLUME_GIB),
|
|
498
501
|
/**
|
|
499
|
-
* Through a control plane (#91): the box this volume is for, so it is made on that box's host
|
|
500
|
-
* volume attaches only to a box on its own host
|
|
501
|
-
* A host ignores it.
|
|
502
|
+
* Through a control plane (#91): the box this volume is for, so it is made on that box's host; the
|
|
503
|
+
* control plane requires it (a volume attaches only to a box on its own host, so one made for no box
|
|
504
|
+
* could never attach) and the CLI attaches right after. A host ignores it.
|
|
502
505
|
*/
|
|
503
506
|
box: z2.string().optional()
|
|
504
507
|
});
|
|
505
508
|
var AttachVolumeRequest = z2.object({
|
|
506
509
|
box: z2.string()
|
|
507
510
|
});
|
|
511
|
+
var EnvVarSchema = z2.object({
|
|
512
|
+
name: z2.string(),
|
|
513
|
+
value: z2.string().optional(),
|
|
514
|
+
secret: z2.boolean(),
|
|
515
|
+
updatedAt: z2.string()
|
|
516
|
+
});
|
|
517
|
+
var SetEnvRequest = z2.object({
|
|
518
|
+
value: z2.string(),
|
|
519
|
+
secret: z2.boolean().optional()
|
|
520
|
+
});
|
|
521
|
+
var EnvChangeSchema = z2.object({
|
|
522
|
+
owner: z2.string(),
|
|
523
|
+
name: z2.string(),
|
|
524
|
+
boxes: z2.number().int()
|
|
525
|
+
});
|
|
526
|
+
var HostEnvRequest = z2.object({
|
|
527
|
+
version: z2.number().int().min(0),
|
|
528
|
+
vars: z2.array(z2.object({ name: z2.string(), value: z2.string() }))
|
|
529
|
+
});
|
|
530
|
+
var HostEnvVersionsSchema = z2.array(z2.object({ owner: z2.string(), version: z2.number().int() }));
|
|
508
531
|
var CreateMachineRequest = z2.object({
|
|
509
532
|
name: z2.string().optional(),
|
|
510
533
|
/** Ignored over the local socket (the caller is the host owner); the control plane sets it over HTTPS. */
|
|
@@ -1137,6 +1160,16 @@ var ServerClient = class {
|
|
|
1137
1160
|
deleteSnapshot(name) {
|
|
1138
1161
|
return this.call("DELETE", `/snapshots/${encodeURIComponent(name)}`, z3.undefined());
|
|
1139
1162
|
}
|
|
1163
|
+
// ---- environment variables (#88), a control plane only: the caller's slug, or `--org`'s ----
|
|
1164
|
+
listEnv() {
|
|
1165
|
+
return this.call("GET", "/env", z3.array(EnvVarSchema));
|
|
1166
|
+
}
|
|
1167
|
+
setEnv(name, req) {
|
|
1168
|
+
return this.call("PUT", `/env/${encodeURIComponent(name)}`, EnvChangeSchema, req);
|
|
1169
|
+
}
|
|
1170
|
+
removeEnv(name) {
|
|
1171
|
+
return this.call("DELETE", `/env/${encodeURIComponent(name)}`, EnvChangeSchema);
|
|
1172
|
+
}
|
|
1140
1173
|
// ---- volumes (#52) ----
|
|
1141
1174
|
listVolumes(opts = {}) {
|
|
1142
1175
|
return this.call("GET", `/volumes${allQuery(opts)}`, z3.array(VolumeSchema), void 0, opts.org);
|
|
@@ -2419,7 +2452,13 @@ var cancelled = () => new KvboxError("cancelled", 130);
|
|
|
2419
2452
|
function keysIn(chunk) {
|
|
2420
2453
|
const keys = [];
|
|
2421
2454
|
for (let i = 0; i < chunk.length; ) {
|
|
2422
|
-
if (chunk[i] === ESC &&
|
|
2455
|
+
if (chunk[i] === ESC && chunk[i + 1] === "[" && i + 2 < chunk.length) {
|
|
2456
|
+
let j = i + 2;
|
|
2457
|
+
while (j < chunk.length && /[0-9;?]/.test(chunk[j])) j += 1;
|
|
2458
|
+
const end = j < chunk.length ? j + 1 : j;
|
|
2459
|
+
keys.push(chunk.slice(i, end));
|
|
2460
|
+
i = end;
|
|
2461
|
+
} else if (chunk[i] === ESC && chunk[i + 1] === "O" && i + 2 < chunk.length) {
|
|
2423
2462
|
keys.push(chunk.slice(i, i + 3));
|
|
2424
2463
|
i += 3;
|
|
2425
2464
|
} else {
|
|
@@ -2496,7 +2535,8 @@ async function text(message, opts = {}) {
|
|
|
2496
2535
|
const hint = opts.default !== void 0 ? ` ${color.dim(`(${opts.default})`)}` : "";
|
|
2497
2536
|
const question = `${color.green("?")} ${color.bold(message)}${hint} ${color.dim("\u203A")} `;
|
|
2498
2537
|
const rl = createInterface({ input: process.stdin, output: out });
|
|
2499
|
-
const
|
|
2538
|
+
const line = await askLine(rl, question);
|
|
2539
|
+
const raw = opts.trim === false ? line : line.trim();
|
|
2500
2540
|
const answer = raw === "" && opts.default !== void 0 ? opts.default : raw;
|
|
2501
2541
|
const problem = opts.validate?.(answer);
|
|
2502
2542
|
out.write(`${ESC}[${rows(question + raw)}A\r${ESC}[J`);
|
|
@@ -2509,6 +2549,44 @@ async function text(message, opts = {}) {
|
|
|
2509
2549
|
`);
|
|
2510
2550
|
}
|
|
2511
2551
|
}
|
|
2552
|
+
async function hiddenText(message) {
|
|
2553
|
+
const out = process.stderr;
|
|
2554
|
+
const inp = process.stdin;
|
|
2555
|
+
const question = `${color.green("?")} ${color.bold(message)} ${color.dim("(hidden) \u203A")} `;
|
|
2556
|
+
out.write(question);
|
|
2557
|
+
inp.setRawMode(true);
|
|
2558
|
+
inp.resume();
|
|
2559
|
+
inp.setEncoding("utf8");
|
|
2560
|
+
let chars = [];
|
|
2561
|
+
let done = false;
|
|
2562
|
+
try {
|
|
2563
|
+
return await new Promise((resolve3, reject) => {
|
|
2564
|
+
const onData = (chunk) => {
|
|
2565
|
+
for (const key of keysIn(chunk)) {
|
|
2566
|
+
if (key === ENTER || key === "\n") {
|
|
2567
|
+
inp.off("data", onData);
|
|
2568
|
+
done = true;
|
|
2569
|
+
resolve3(chars.join(""));
|
|
2570
|
+
return;
|
|
2571
|
+
}
|
|
2572
|
+
if (key === CTRL_C || key === CTRL_D) {
|
|
2573
|
+
inp.off("data", onData);
|
|
2574
|
+
reject(cancelled());
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
if (key === "\x7F" || key === "\b") chars = chars.slice(0, -1);
|
|
2578
|
+
else if (key.length === 1 && (key >= " " || key === " ")) chars.push(key);
|
|
2579
|
+
}
|
|
2580
|
+
};
|
|
2581
|
+
inp.on("data", onData);
|
|
2582
|
+
});
|
|
2583
|
+
} finally {
|
|
2584
|
+
inp.setRawMode(false);
|
|
2585
|
+
inp.pause();
|
|
2586
|
+
out.write(`\r${ESC}[J${done ? `${settled(message, "\u2022\u2022\u2022\u2022\u2022")}
|
|
2587
|
+
` : "\n"}`);
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2512
2590
|
async function askLine(rl, question) {
|
|
2513
2591
|
let onClose = () => {
|
|
2514
2592
|
};
|
|
@@ -3145,7 +3223,8 @@ var nextjsTemplate = {
|
|
|
3145
3223
|
* The source lands in a staging directory and is installed and built there while the old app
|
|
3146
3224
|
* keeps serving; only then is it swapped into place and the service restarted, so a failed
|
|
3147
3225
|
* install or build leaves the running app alone. The unit is rewritten every time, so a changed
|
|
3148
|
-
* package manager or start script takes effect. /srv/app is the kvbox user's (image v0.8.0+).
|
|
3226
|
+
* package manager or start script takes effect. /srv/app is the kvbox user's (image v0.8.0+). The
|
|
3227
|
+
* build runs in the deploy's SSH session, so it sees the owner's variables (`kvbox env`) too.
|
|
3149
3228
|
*/
|
|
3150
3229
|
remote(built) {
|
|
3151
3230
|
const c = commands(built.pm);
|
|
@@ -3157,8 +3236,12 @@ var nextjsTemplate = {
|
|
|
3157
3236
|
"[Service]",
|
|
3158
3237
|
"User=kvbox",
|
|
3159
3238
|
`WorkingDirectory=${APP_DIR}`,
|
|
3160
|
-
|
|
3161
|
-
|
|
3239
|
+
// PORT on the command line: the proxied address points at it, and nothing in the environment
|
|
3240
|
+
// file below may move the app off it (env's PORT, say, copied from a .env).
|
|
3241
|
+
`ExecStart=/usr/bin/env PORT=${PORT} ${c.start}`,
|
|
3242
|
+
"Environment=NODE_ENV=production",
|
|
3243
|
+
// The box owner's variables (#88, base v0.10.0+; the `-` makes an older box start without them).
|
|
3244
|
+
"EnvironmentFile=-/etc/kvbox/env",
|
|
3162
3245
|
"Restart=always",
|
|
3163
3246
|
"RestartSec=2",
|
|
3164
3247
|
"",
|
|
@@ -3556,6 +3639,10 @@ import { Command as Command56 } from "commander";
|
|
|
3556
3639
|
|
|
3557
3640
|
// src/commands/volume/attach.ts
|
|
3558
3641
|
import { Command as Command51 } from "commander";
|
|
3642
|
+
function describeMount(machine, volume, box) {
|
|
3643
|
+
const path = machine.volumes?.find((v) => volume === v.name || volume.endsWith(`/${v.name}`))?.mountPath ?? volumeMountPath(volume.slice(volume.lastIndexOf("/") + 1));
|
|
3644
|
+
return machine.status === "running" ? ` the box was restarted; the volume is at ${path} inside it` : ` box ${box} is ${machine.status}; the volume is at ${path} once it runs`;
|
|
3645
|
+
}
|
|
3559
3646
|
var attachCommand = new Command51("attach").description("attach a volume to a box at /mnt/<volume> (a running box is restarted to take it)").argument("<volume>", "volume name").argument("<box>", "box name").option("--json", "print the box as JSON").action(async (volume, box, opts, cmd) => {
|
|
3560
3647
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
3561
3648
|
try {
|
|
@@ -3564,9 +3651,8 @@ var attachCommand = new Command51("attach").description("attach a volume to a bo
|
|
|
3564
3651
|
json(machine);
|
|
3565
3652
|
return;
|
|
3566
3653
|
}
|
|
3567
|
-
const path = machine.volumes?.find((v) => volume === v.name || volume.endsWith(`/${v.name}`))?.mountPath ?? volumeMountPath(volume.slice(volume.lastIndexOf("/") + 1));
|
|
3568
3654
|
info(`${color.green("\u2713")} volume ${volume} attached to box ${box}`);
|
|
3569
|
-
info(machine
|
|
3655
|
+
info(describeMount(machine, volume, box));
|
|
3570
3656
|
} finally {
|
|
3571
3657
|
await client.close();
|
|
3572
3658
|
}
|
|
@@ -3581,13 +3667,25 @@ function parseGib2(text2) {
|
|
|
3581
3667
|
if (gib > MAX_VOLUME_GIB) throw new KvboxError(`--size ${text2}: a volume is at most ${MAX_VOLUME_GIB} GB`);
|
|
3582
3668
|
return gib;
|
|
3583
3669
|
}
|
|
3584
|
-
var createCommand = new Command52("create").alias("new").description("create a volume: an empty disk of its own
|
|
3670
|
+
var createCommand = new Command52("create").alias("new").description("create a volume for a box: an empty disk of its own at /mnt/<name> inside it (a running box is restarted)").argument("<name>", "volume name (1-12 chars; the box mounts it at /mnt/<name>)").requiredOption("--size <gb>", "size in GB, like 50 or 50GB (whole GB; it only occupies what is written)").option("--box <box>", "the box it is for: made on that box's host and attached to it (required on kvbox.sh; optional with --local)").option("--json", "print the volume as JSON").action(async (name, opts, cmd) => {
|
|
3585
3671
|
const sizeGib = parseGib2(opts.size);
|
|
3586
3672
|
const client = await clientFor(cmd.optsWithGlobals());
|
|
3587
3673
|
try {
|
|
3674
|
+
if (opts.box === void 0 && client.target.kind === "cloud") {
|
|
3675
|
+
throw new KvboxError(`a volume is made for a box, on that box's host: kvbox volume create ${name} --size ${sizeGib} --box <box>`);
|
|
3676
|
+
}
|
|
3588
3677
|
const vol = await spin(`creating volume ${name} (${sizeGib} GB)\u2026`, client.createVolume({ name, sizeGib, ...opts.box !== void 0 ? { box: opts.box } : {} }));
|
|
3589
|
-
if (opts.
|
|
3590
|
-
|
|
3678
|
+
if (opts.box === void 0) {
|
|
3679
|
+
if (opts.json) json(vol);
|
|
3680
|
+
else info(`${color.green("\u2713")} volume ${name} created (${vol.sizeGib} GB); attach it to a box with: kvbox volume attach ${name} <box>`);
|
|
3681
|
+
return;
|
|
3682
|
+
}
|
|
3683
|
+
const machine = await spin(`attaching volume ${name} to box ${opts.box} (a running box is restarted)\u2026`, client.attachVolume(name, { box: opts.box }));
|
|
3684
|
+
if (opts.json) json({ ...vol, attachedTo: machine.name });
|
|
3685
|
+
else {
|
|
3686
|
+
info(`${color.green("\u2713")} volume ${name} created (${vol.sizeGib} GB) and attached to box ${opts.box}`);
|
|
3687
|
+
info(describeMount(machine, name, opts.box));
|
|
3688
|
+
}
|
|
3591
3689
|
} finally {
|
|
3592
3690
|
await client.close();
|
|
3593
3691
|
}
|
|
@@ -3662,12 +3760,128 @@ var rmCommand6 = new Command55("rm").alias("remove").description("delete a volum
|
|
|
3662
3760
|
var volumeCommand = new Command56("volume").aliases(["volumes", "vol"]).description("disks of their own: create one, attach it to a box at /mnt/<name>, move it to another box later").addCommand(createCommand).addCommand(lsCommand6).addCommand(attachCommand).addCommand(detachCommand).addCommand(rmCommand6);
|
|
3663
3761
|
for (const cmd of volumeCommand.commands) cmd.option("--org <slug>", "act in this org (- for your personal volumes)");
|
|
3664
3762
|
|
|
3763
|
+
// src/commands/env/index.ts
|
|
3764
|
+
import { Command as Command60 } from "commander";
|
|
3765
|
+
|
|
3766
|
+
// src/commands/env/ls.ts
|
|
3767
|
+
import { Command as Command57 } from "commander";
|
|
3768
|
+
|
|
3769
|
+
// src/commands/env/shared.ts
|
|
3770
|
+
function requireCloud2(client) {
|
|
3771
|
+
if (client.target.kind === "local") throw new KvboxError("kvbox env needs kvbox.sh: kvbox login first");
|
|
3772
|
+
return client.target.org ?? client.target.user ?? "you";
|
|
3773
|
+
}
|
|
3774
|
+
function reach(boxes, verb) {
|
|
3775
|
+
if (boxes === 0) return verb === "get" ? "no boxes yet, every new one gets it" : "no boxes yet";
|
|
3776
|
+
return boxes === 1 ? `1 box ${verb}s it when it next starts` : `${boxes} boxes ${verb} it when they next start`;
|
|
3777
|
+
}
|
|
3778
|
+
|
|
3779
|
+
// src/commands/env/ls.ts
|
|
3780
|
+
var lsCommand7 = new Command57("ls").alias("list").description("list the variables (a secret shows as (sealed))").option("--json", "print as JSON").action(async (opts, cmd) => {
|
|
3781
|
+
const client = await clientFor(cmd.optsWithGlobals(), { autoStart: false });
|
|
3782
|
+
try {
|
|
3783
|
+
const owner = requireCloud2(client);
|
|
3784
|
+
const vars = await client.listEnv();
|
|
3785
|
+
if (opts.json) {
|
|
3786
|
+
json(vars);
|
|
3787
|
+
return;
|
|
3788
|
+
}
|
|
3789
|
+
if (vars.length === 0) {
|
|
3790
|
+
info(`no variables for ${owner} yet; set one with: kvbox env set NAME value`);
|
|
3791
|
+
return;
|
|
3792
|
+
}
|
|
3793
|
+
table(vars.map((v) => ({ name: v.name, value: v.secret ? color.dim("(sealed)") : v.value, updated: age(v.updatedAt) })));
|
|
3794
|
+
} finally {
|
|
3795
|
+
await client.close();
|
|
3796
|
+
}
|
|
3797
|
+
});
|
|
3798
|
+
|
|
3799
|
+
// src/commands/env/rm.ts
|
|
3800
|
+
import { Command as Command58 } from "commander";
|
|
3801
|
+
var rmCommand7 = new Command58("rm").alias("remove").description("remove variables; boxes lose them when they next start").argument("<names...>", "variable names").option("-y, --yes", "do not ask for confirmation").option("--json", "print as JSON").action(async (names, opts, cmd) => {
|
|
3802
|
+
const client = await clientFor(cmd.optsWithGlobals(), { autoStart: false });
|
|
3803
|
+
try {
|
|
3804
|
+
const owner = requireCloud2(client);
|
|
3805
|
+
if (!await confirm(`Remove ${names.join(", ")} from ${owner}'s variables?`, opts.yes ?? false)) {
|
|
3806
|
+
info("cancelled");
|
|
3807
|
+
process.exitCode = 1;
|
|
3808
|
+
return;
|
|
3809
|
+
}
|
|
3810
|
+
const changes = [];
|
|
3811
|
+
const failed = [];
|
|
3812
|
+
for (const name of names) {
|
|
3813
|
+
try {
|
|
3814
|
+
const c = await spin(`removing ${name}\u2026`, client.removeEnv(name));
|
|
3815
|
+
changes.push(c);
|
|
3816
|
+
if (!opts.json) info(`${color.green("\u2713")} removed ${c.name} from ${owner}: ${reach(c.boxes, "lose")}`);
|
|
3817
|
+
} catch (err) {
|
|
3818
|
+
failed.push({ name, err });
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
if (changes.length === 0 && failed.length > 0) throw failed[0].err;
|
|
3822
|
+
if (opts.json) json(changes);
|
|
3823
|
+
for (const f of failed) warn(`${color.red("\u2717")} ${f.name}: ${f.err.message}`);
|
|
3824
|
+
if (failed.length > 0) process.exitCode = 1;
|
|
3825
|
+
} finally {
|
|
3826
|
+
await client.close();
|
|
3827
|
+
}
|
|
3828
|
+
});
|
|
3829
|
+
|
|
3830
|
+
// src/commands/env/set.ts
|
|
3831
|
+
import { Command as Command59 } from "commander";
|
|
3832
|
+
|
|
3833
|
+
// ../shared/src/env.ts
|
|
3834
|
+
var ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
3835
|
+
var ENV_RESERVED = ["PATH", "HOME", "USER", "SHELL"];
|
|
3836
|
+
var ENV_MAX_VALUE_BYTES = 4096;
|
|
3837
|
+
function envNameProblem(name) {
|
|
3838
|
+
if (!ENV_NAME_RE.test(name)) return `${JSON.stringify(name)} is not a variable name: letters, digits and _, not starting with a digit`;
|
|
3839
|
+
if (ENV_RESERVED.includes(name)) return `${name} is the box's own and cannot be set`;
|
|
3840
|
+
if (name.startsWith("KVBOX_")) return `${name}: names starting with KVBOX_ are kept for kvbox`;
|
|
3841
|
+
return null;
|
|
3842
|
+
}
|
|
3843
|
+
function envValueProblem(value) {
|
|
3844
|
+
if (/[\n\r\0]/.test(value)) return "a value is one line: no line breaks (base64 a multi-line value such as a key file)";
|
|
3845
|
+
if (Buffer.byteLength(value, "utf8") > ENV_MAX_VALUE_BYTES) return `a value is at most ${ENV_MAX_VALUE_BYTES / 1024} KB`;
|
|
3846
|
+
return null;
|
|
3847
|
+
}
|
|
3848
|
+
|
|
3849
|
+
// src/commands/env/set.ts
|
|
3850
|
+
var setCommand2 = new Command59("set").description("add or change a variable; boxes get it when they next start").argument("[name]", "variable name, e.g. DATABASE_URL (asked for when left out)").argument("[value]", "its value (asked for when left out; hidden with --secret)").option("--secret", "write-only: the value is never shown again, by any command").option("--json", "print as JSON").action(async (nameArg, valueArg, opts, cmd) => {
|
|
3851
|
+
const client = await clientFor(cmd.optsWithGlobals(), { autoStart: false });
|
|
3852
|
+
try {
|
|
3853
|
+
const owner = requireCloud2(client);
|
|
3854
|
+
const ask = interactive();
|
|
3855
|
+
if ((nameArg === void 0 || valueArg === void 0) && !ask) throw new KvboxError("give the name and the value: kvbox env set NAME VALUE (or run it in a terminal to be asked)");
|
|
3856
|
+
const name = nameArg ?? await text("Variable name", { validate: (a) => envNameProblem(a) ?? void 0 });
|
|
3857
|
+
const nameProblem = envNameProblem(name);
|
|
3858
|
+
if (nameProblem !== null) throw new KvboxError(nameProblem);
|
|
3859
|
+
const value = valueArg ?? (opts.secret ? await hiddenText(`Value for ${name}`) : await text(`Value for ${name}`, { trim: false }));
|
|
3860
|
+
const valueProblem = envValueProblem(value);
|
|
3861
|
+
if (valueProblem !== null) throw new KvboxError(valueProblem);
|
|
3862
|
+
const change = await spin(`setting ${name}\u2026`, client.setEnv(name, { value, secret: opts.secret ?? false }));
|
|
3863
|
+
if (opts.json) {
|
|
3864
|
+
json(change);
|
|
3865
|
+
return;
|
|
3866
|
+
}
|
|
3867
|
+
const what = opts.secret ? `${name} (secret)` : name;
|
|
3868
|
+
info(`${color.green("\u2713")} set ${what} for ${owner}: ${reach(change.boxes, "get")}`);
|
|
3869
|
+
} finally {
|
|
3870
|
+
await client.close();
|
|
3871
|
+
}
|
|
3872
|
+
});
|
|
3873
|
+
|
|
3874
|
+
// src/commands/env/index.ts
|
|
3875
|
+
var envCommand = new Command60("env").description("environment variables every box of yours gets when it starts (secrets too)").addCommand(lsCommand7, { isDefault: true }).addCommand(setCommand2).addCommand(rmCommand7);
|
|
3876
|
+
for (const cmd of envCommand.commands) cmd.option("--org <slug>", "the org's variables instead of yours");
|
|
3877
|
+
|
|
3665
3878
|
// src/program.ts
|
|
3666
3879
|
function buildProgram() {
|
|
3667
|
-
const program = new
|
|
3880
|
+
const program = new Command61("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();
|
|
3668
3881
|
program.addCommand(vmCommand);
|
|
3669
3882
|
program.addCommand(snapshotsCommand);
|
|
3670
3883
|
program.addCommand(volumeCommand);
|
|
3884
|
+
program.addCommand(envCommand);
|
|
3671
3885
|
program.addCommand(serverCommand);
|
|
3672
3886
|
program.addCommand(loginCommand);
|
|
3673
3887
|
program.addCommand(logoutCommand);
|