@envsync-cloud/deploy-cli 0.8.12 → 0.8.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/index.js +304 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ var __export = (target, all) => {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// src/index.ts
|
|
9
|
-
import { createHash, randomBytes } from "crypto";
|
|
9
|
+
import { createHash, randomBytes, randomInt } from "crypto";
|
|
10
10
|
import { spawnSync } from "child_process";
|
|
11
11
|
import fs3 from "fs";
|
|
12
12
|
import path3 from "path";
|
|
@@ -5156,6 +5156,7 @@ var BACKUPS_ROOT = path3.join(HOST_ROOT, "backups");
|
|
|
5156
5156
|
var REPO_ROOT = process.env.ENVSYNC_REPO_ROOT ?? path3.join(HOST_ROOT, "repo");
|
|
5157
5157
|
var DEPLOY_ENV = path3.join(ETC_ROOT, "deploy.env");
|
|
5158
5158
|
var DEPLOY_YAML = path3.join(ETC_ROOT, "deploy.yaml");
|
|
5159
|
+
var WIREGUARD_ROOT = path3.join(ETC_ROOT, "wireguard");
|
|
5159
5160
|
var LICENSE_ROOT = path3.join(ETC_ROOT, "license");
|
|
5160
5161
|
var LICENSE_BUNDLE_FILE = path3.join(LICENSE_ROOT, "enterprise-license-bundle.json");
|
|
5161
5162
|
var LICENSE_CERT_FILE = path3.join(LICENSE_ROOT, "enterprise-cert.pem");
|
|
@@ -5176,6 +5177,10 @@ var OTEL_AGENT_CONF = path3.join(DEPLOY_ROOT, "otel-agent.yaml");
|
|
|
5176
5177
|
var CLICKSTACK_CLICKHOUSE_CONF = path3.join(DEPLOY_ROOT, "clickhouse-listen.xml");
|
|
5177
5178
|
var INTERNAL_CONFIG_JSON = path3.join(DEPLOY_ROOT, "config.json");
|
|
5178
5179
|
var UPGRADE_BACKUPS_ROOT = path3.join(BACKUPS_ROOT, "upgrade");
|
|
5180
|
+
var WIREGUARD_STATE_FILE = path3.join(WIREGUARD_ROOT, "state.json");
|
|
5181
|
+
var WIREGUARD_PRIVATE_KEY_FILE = path3.join(WIREGUARD_ROOT, "server-private.key");
|
|
5182
|
+
var WIREGUARD_PUBLIC_KEY_FILE = path3.join(WIREGUARD_ROOT, "server-public.key");
|
|
5183
|
+
var WIREGUARD_CONFIG_FILE = path3.join(WIREGUARD_ROOT, "envsync-wg.conf");
|
|
5179
5184
|
var DEPLOY_RENDER_PATHS = {
|
|
5180
5185
|
traefikStateRoot: TRAEFIK_STATE_ROOT,
|
|
5181
5186
|
deployRoot: DEPLOY_ROOT,
|
|
@@ -5202,6 +5207,11 @@ var REMOVE_TARGETS = [
|
|
|
5202
5207
|
LICENSE_ROOT_CA_FILE,
|
|
5203
5208
|
INTERNAL_CONFIG_JSON,
|
|
5204
5209
|
VERSIONS_LOCK,
|
|
5210
|
+
WIREGUARD_ROOT,
|
|
5211
|
+
WIREGUARD_STATE_FILE,
|
|
5212
|
+
WIREGUARD_PRIVATE_KEY_FILE,
|
|
5213
|
+
WIREGUARD_PUBLIC_KEY_FILE,
|
|
5214
|
+
WIREGUARD_CONFIG_FILE,
|
|
5205
5215
|
STACK_FILE,
|
|
5206
5216
|
BOOTSTRAP_BASE_STACK_FILE,
|
|
5207
5217
|
BOOTSTRAP_STACK_FILE,
|
|
@@ -5327,6 +5337,28 @@ ${stderr}` : ""}`);
|
|
|
5327
5337
|
}
|
|
5328
5338
|
return result.stdout?.toString() ?? "";
|
|
5329
5339
|
}
|
|
5340
|
+
function runCapture(cmd, args, opts = {}) {
|
|
5341
|
+
const result = spawnSync(cmd, args, {
|
|
5342
|
+
cwd: opts.cwd,
|
|
5343
|
+
env: { ...process.env, ...opts.env },
|
|
5344
|
+
input: opts.input,
|
|
5345
|
+
stdio: "pipe",
|
|
5346
|
+
encoding: "utf8"
|
|
5347
|
+
});
|
|
5348
|
+
if (result.status !== 0) {
|
|
5349
|
+
const stderr = typeof result.stderr === "string" ? result.stderr : "";
|
|
5350
|
+
throw new Error(`Command failed: ${cmd} ${args.join(" ")}${stderr ? `
|
|
5351
|
+
${stderr}` : ""}`);
|
|
5352
|
+
}
|
|
5353
|
+
return (result.stdout ?? "").toString().trim();
|
|
5354
|
+
}
|
|
5355
|
+
function parseJsonOrDefault(value, fallback) {
|
|
5356
|
+
try {
|
|
5357
|
+
return JSON.parse(value);
|
|
5358
|
+
} catch {
|
|
5359
|
+
return fallback;
|
|
5360
|
+
}
|
|
5361
|
+
}
|
|
5330
5362
|
function tryRun(cmd, args, opts = {}) {
|
|
5331
5363
|
try {
|
|
5332
5364
|
return run(cmd, args, opts);
|
|
@@ -5413,6 +5445,93 @@ function deterministicInstallFingerprint(rootDomain, stackName) {
|
|
|
5413
5445
|
function randomStrongPassword() {
|
|
5414
5446
|
return `EnvSync!${randomBytes(8).toString("hex")}Aa1`;
|
|
5415
5447
|
}
|
|
5448
|
+
function randomWireguardPort() {
|
|
5449
|
+
return randomInt(5e4, 65535);
|
|
5450
|
+
}
|
|
5451
|
+
function loadWireguardState() {
|
|
5452
|
+
if (!exists2(WIREGUARD_STATE_FILE)) return null;
|
|
5453
|
+
const raw = JSON.parse(fs3.readFileSync(WIREGUARD_STATE_FILE, "utf8"));
|
|
5454
|
+
return {
|
|
5455
|
+
private_key: raw.private_key,
|
|
5456
|
+
public_key: raw.public_key,
|
|
5457
|
+
listen_port: raw.listen_port,
|
|
5458
|
+
network: raw.network,
|
|
5459
|
+
server_ip: raw.server_ip,
|
|
5460
|
+
peers: Array.isArray(raw.peers) ? raw.peers : []
|
|
5461
|
+
};
|
|
5462
|
+
}
|
|
5463
|
+
function saveWireguardState(state) {
|
|
5464
|
+
if (currentOptions.dryRun) {
|
|
5465
|
+
logDryRun(`Would save WireGuard state to ${WIREGUARD_STATE_FILE}`);
|
|
5466
|
+
return;
|
|
5467
|
+
}
|
|
5468
|
+
writeFile(WIREGUARD_STATE_FILE, JSON.stringify(state, null, 2), 384);
|
|
5469
|
+
}
|
|
5470
|
+
function ensureWireguardState() {
|
|
5471
|
+
ensureDir(WIREGUARD_ROOT);
|
|
5472
|
+
let state = loadWireguardState();
|
|
5473
|
+
if (state) {
|
|
5474
|
+
state.private_key = state.private_key?.trim();
|
|
5475
|
+
state.public_key = state.public_key?.trim();
|
|
5476
|
+
state.network = state.network || "10.66.0.0/24";
|
|
5477
|
+
state.server_ip = state.server_ip || "10.66.0.1";
|
|
5478
|
+
state.listen_port = state.listen_port || randomWireguardPort();
|
|
5479
|
+
state.peers = Array.isArray(state.peers) ? state.peers : [];
|
|
5480
|
+
if (state.private_key && !state.public_key) {
|
|
5481
|
+
state.public_key = runCapture("wg", ["pubkey"], { input: `${state.private_key}
|
|
5482
|
+
` });
|
|
5483
|
+
}
|
|
5484
|
+
if (!state.private_key || !state.public_key) {
|
|
5485
|
+
state = null;
|
|
5486
|
+
}
|
|
5487
|
+
}
|
|
5488
|
+
if (!state) {
|
|
5489
|
+
const privateKey = runCapture("wg", ["genkey"]);
|
|
5490
|
+
const publicKey = runCapture("wg", ["pubkey"], { input: `${privateKey}
|
|
5491
|
+
` });
|
|
5492
|
+
state = {
|
|
5493
|
+
private_key: privateKey,
|
|
5494
|
+
public_key: publicKey,
|
|
5495
|
+
listen_port: randomWireguardPort(),
|
|
5496
|
+
network: "10.66.0.0/24",
|
|
5497
|
+
server_ip: "10.66.0.1",
|
|
5498
|
+
peers: []
|
|
5499
|
+
};
|
|
5500
|
+
saveWireguardState(state);
|
|
5501
|
+
}
|
|
5502
|
+
writeFileMaybe(WIREGUARD_PRIVATE_KEY_FILE, `${state.private_key}
|
|
5503
|
+
`, 384);
|
|
5504
|
+
writeFileMaybe(WIREGUARD_PUBLIC_KEY_FILE, `${state.public_key}
|
|
5505
|
+
`);
|
|
5506
|
+
return state;
|
|
5507
|
+
}
|
|
5508
|
+
function wireguardPeerAllowedIp(state) {
|
|
5509
|
+
const usedIps = new Set(state.peers.map((peer) => peer.allowed_ip));
|
|
5510
|
+
for (let i = 2; i <= 254; i++) {
|
|
5511
|
+
const candidate = `${state.server_ip.split(".").slice(0, 3).join(".")}.${i}/32`;
|
|
5512
|
+
if (!usedIps.has(candidate)) {
|
|
5513
|
+
return candidate;
|
|
5514
|
+
}
|
|
5515
|
+
}
|
|
5516
|
+
throw new Error("WireGuard client IP pool exhausted in subnet 10.66.0.0/24.");
|
|
5517
|
+
}
|
|
5518
|
+
function writeWireguardConfig(state) {
|
|
5519
|
+
const peersSection = state.peers.map((peer) => `
|
|
5520
|
+
[Peer]
|
|
5521
|
+
PublicKey = ${peer.public_key}
|
|
5522
|
+
AllowedIPs = ${peer.allowed_ip}`).join("\n");
|
|
5523
|
+
const content = `[Interface]
|
|
5524
|
+
Address = ${state.server_ip}/24
|
|
5525
|
+
ListenPort = ${state.listen_port}
|
|
5526
|
+
PrivateKey = ${state.private_key}
|
|
5527
|
+
PostUp = iptables -A FORWARD -i envsync-wg -j ACCEPT
|
|
5528
|
+
PostDown = iptables -D FORWARD -i envsync-wg -j ACCEPT
|
|
5529
|
+
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
|
|
5530
|
+
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
|
|
5531
|
+
${peersSection}
|
|
5532
|
+
`;
|
|
5533
|
+
writeFileMaybe(WIREGUARD_CONFIG_FILE, content);
|
|
5534
|
+
}
|
|
5416
5535
|
function emptyApiSlotState() {
|
|
5417
5536
|
return {
|
|
5418
5537
|
api_image: "",
|
|
@@ -5548,6 +5667,9 @@ function renderHelpBlock() {
|
|
|
5548
5667
|
" validate-topology Validate Enterprise edition topology rules",
|
|
5549
5668
|
" upgrade [version] Pin a target release and deploy it",
|
|
5550
5669
|
" upgrade-deps Refresh dependency images and redeploy",
|
|
5670
|
+
" vpn pubkey Generate or print WireGuard server public key",
|
|
5671
|
+
" vpn whitelist <key> Add a client public key to allowed peers",
|
|
5672
|
+
" vpn status Show WireGuard server/peer status",
|
|
5551
5673
|
" license issue-cert Issue and install an Enterprise certificate bundle",
|
|
5552
5674
|
" license renew-cert Renew and install the Enterprise certificate bundle",
|
|
5553
5675
|
" license validate-cert Validate the installed Enterprise certificate bundle files",
|
|
@@ -7179,7 +7301,19 @@ async function cmdPreinstall() {
|
|
|
7179
7301
|
ensureDir(TRAEFIK_STATE_ROOT);
|
|
7180
7302
|
run("bash", ["-lc", "command -v apt-get >/dev/null"]);
|
|
7181
7303
|
run("sudo", ["apt-get", "update"]);
|
|
7182
|
-
run("sudo", [
|
|
7304
|
+
run("sudo", [
|
|
7305
|
+
"apt-get",
|
|
7306
|
+
"install",
|
|
7307
|
+
"-y",
|
|
7308
|
+
"docker.io",
|
|
7309
|
+
"docker-compose-v2",
|
|
7310
|
+
"git",
|
|
7311
|
+
"curl",
|
|
7312
|
+
"jq",
|
|
7313
|
+
"openssl",
|
|
7314
|
+
"tar",
|
|
7315
|
+
"wireguard-tools"
|
|
7316
|
+
]);
|
|
7183
7317
|
run("sudo", ["systemctl", "enable", "--now", "docker"]);
|
|
7184
7318
|
try {
|
|
7185
7319
|
run("docker", ["swarm", "init"]);
|
|
@@ -8165,6 +8299,171 @@ async function cmdRestore(archivePath, autoDeploy = false) {
|
|
|
8165
8299
|
}
|
|
8166
8300
|
logInfo("Next: envsync-deploy deploy");
|
|
8167
8301
|
}
|
|
8302
|
+
async function cmdVpn(args) {
|
|
8303
|
+
const action = args[0];
|
|
8304
|
+
switch (action) {
|
|
8305
|
+
case "pubkey":
|
|
8306
|
+
await cmdVpnPubkey();
|
|
8307
|
+
break;
|
|
8308
|
+
case "whitelist":
|
|
8309
|
+
await cmdVpnWhitelist(args[1]);
|
|
8310
|
+
break;
|
|
8311
|
+
case "status":
|
|
8312
|
+
await cmdVpnStatus();
|
|
8313
|
+
break;
|
|
8314
|
+
default:
|
|
8315
|
+
throw new Error("Usage: envsync-deploy vpn <pubkey|whitelist|status>");
|
|
8316
|
+
}
|
|
8317
|
+
}
|
|
8318
|
+
async function cmdVpnPubkey() {
|
|
8319
|
+
logSection("WireGuard VPN");
|
|
8320
|
+
const state = ensureWireguardState();
|
|
8321
|
+
writeWireguardConfig(state);
|
|
8322
|
+
logInfo(`WireGuard interface: envsync-wg`);
|
|
8323
|
+
logInfo(`Server config: ${WIREGUARD_CONFIG_FILE}`);
|
|
8324
|
+
logInfo(`Private key: ${WIREGUARD_PRIVATE_KEY_FILE}`);
|
|
8325
|
+
logInfo(`Public key: ${WIREGUARD_PUBLIC_KEY_FILE}`);
|
|
8326
|
+
logInfo(`Server listen port: ${state.listen_port}`);
|
|
8327
|
+
logInfo(`Network: ${state.network}`);
|
|
8328
|
+
console.log("");
|
|
8329
|
+
logSuccess(`Server public key: ${state.public_key}`);
|
|
8330
|
+
}
|
|
8331
|
+
async function cmdVpnWhitelist(clientPublicKeyArg) {
|
|
8332
|
+
if (!clientPublicKeyArg) {
|
|
8333
|
+
throw new Error("usage: envsync-deploy vpn whitelist <client_public_key>");
|
|
8334
|
+
}
|
|
8335
|
+
const clientPublicKey = clientPublicKeyArg.trim();
|
|
8336
|
+
if (!clientPublicKey) {
|
|
8337
|
+
throw new Error("client public key cannot be empty");
|
|
8338
|
+
}
|
|
8339
|
+
const state = ensureWireguardState();
|
|
8340
|
+
const existing = state.peers.find((peer) => peer.public_key === clientPublicKey);
|
|
8341
|
+
if (existing) {
|
|
8342
|
+
logWarn(`Client key already whitelisted for ${existing.allowed_ip}`);
|
|
8343
|
+
return;
|
|
8344
|
+
}
|
|
8345
|
+
const allowedIp = wireguardPeerAllowedIp(state);
|
|
8346
|
+
state.peers.push({
|
|
8347
|
+
public_key: clientPublicKey,
|
|
8348
|
+
allowed_ip: allowedIp,
|
|
8349
|
+
added_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
8350
|
+
});
|
|
8351
|
+
state.peers.sort((a, b) => a.public_key.localeCompare(b.public_key));
|
|
8352
|
+
saveWireguardState(state);
|
|
8353
|
+
writeWireguardConfig(state);
|
|
8354
|
+
logSuccess(`Whitelisted client ${clientPublicKey}`);
|
|
8355
|
+
console.log(`AllowedIP: ${allowedIp}`);
|
|
8356
|
+
console.log(`Append to client config:`);
|
|
8357
|
+
console.log(`[Peer]`);
|
|
8358
|
+
console.log(`PublicKey = ${clientPublicKey}`);
|
|
8359
|
+
console.log(`AllowedIPs = ${allowedIp}`);
|
|
8360
|
+
}
|
|
8361
|
+
function getVpnStackName() {
|
|
8362
|
+
try {
|
|
8363
|
+
return loadConfig().services.stack_name;
|
|
8364
|
+
} catch {
|
|
8365
|
+
return "envsync";
|
|
8366
|
+
}
|
|
8367
|
+
}
|
|
8368
|
+
function listStackServiceNames(stackName) {
|
|
8369
|
+
const raw = tryRun("docker", ["service", "ls", "--filter", `label=com.docker.stack.namespace=${stackName}`, "--format", "{{.Name}}"], {
|
|
8370
|
+
quiet: true
|
|
8371
|
+
});
|
|
8372
|
+
if (!raw.trim()) return [];
|
|
8373
|
+
return raw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
8374
|
+
}
|
|
8375
|
+
function listServicePublishedPorts(serviceName) {
|
|
8376
|
+
const raw = tryRun("docker", ["service", "inspect", "--format", "{{json .Endpoint.Ports}}", serviceName], { quiet: true });
|
|
8377
|
+
const ports = parseJsonOrDefault(raw, []);
|
|
8378
|
+
const list = Array.isArray(ports) ? ports : [];
|
|
8379
|
+
if (list.length === 0) return [];
|
|
8380
|
+
return list.map((port) => {
|
|
8381
|
+
const typed = port;
|
|
8382
|
+
if (!typed || typeof typed !== "object") return "";
|
|
8383
|
+
if (!typed.PublishedPort || !typed.TargetPort || !typed.Protocol) return "";
|
|
8384
|
+
const mode = typed.PublishMode || "ingress";
|
|
8385
|
+
return `${typed.PublishedPort}/${typed.Protocol} -> ${typed.TargetPort}/${typed.Protocol} (${mode})`;
|
|
8386
|
+
}).filter(Boolean);
|
|
8387
|
+
}
|
|
8388
|
+
function listRunningContainerEndpoints(serviceName) {
|
|
8389
|
+
const raw = tryRun(
|
|
8390
|
+
"docker",
|
|
8391
|
+
["service", "ps", serviceName, "--filter", "desired-state=running", "--no-trunc", "--format", "{{.ID}}"],
|
|
8392
|
+
{ quiet: true }
|
|
8393
|
+
);
|
|
8394
|
+
const taskIds = raw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
8395
|
+
if (taskIds.length === 0) return [];
|
|
8396
|
+
const results = [];
|
|
8397
|
+
for (const taskId of taskIds) {
|
|
8398
|
+
const taskRaw = tryRun("docker", ["inspect", taskId, "--format", "{{json .}}"], { quiet: true });
|
|
8399
|
+
if (!taskRaw) continue;
|
|
8400
|
+
const inspections = parseJsonOrDefault(taskRaw, []);
|
|
8401
|
+
if (!Array.isArray(inspections) || inspections.length === 0) continue;
|
|
8402
|
+
const inspect = inspections[0];
|
|
8403
|
+
const containerId = inspect.Status?.ContainerStatus?.ContainerID ?? "";
|
|
8404
|
+
const addresses = Array.isArray(inspect.NetworksAttachments) ? inspect.NetworksAttachments.flatMap((attachment) => {
|
|
8405
|
+
const networkName = attachment.Network?.Spec?.Name ?? "overlay";
|
|
8406
|
+
const ips = Array.isArray(attachment.Addresses) ? attachment.Addresses : [];
|
|
8407
|
+
return ips.map((ip) => {
|
|
8408
|
+
const trimmed = typeof ip === "string" ? ip.trim() : "";
|
|
8409
|
+
return trimmed ? `${networkName}:${trimmed}` : "";
|
|
8410
|
+
}).filter(Boolean);
|
|
8411
|
+
}) : [];
|
|
8412
|
+
const line = `task ${taskId.slice(0, 12)} container=${containerId.slice(0, 12) || "unknown"} ${addresses.length ? `ips=${addresses.join(", ")}` : "ips=none"}`;
|
|
8413
|
+
results.push(line);
|
|
8414
|
+
}
|
|
8415
|
+
return results;
|
|
8416
|
+
}
|
|
8417
|
+
async function cmdVpnStatus() {
|
|
8418
|
+
logSection("WireGuard VPN");
|
|
8419
|
+
const state = loadWireguardState();
|
|
8420
|
+
if (!state) {
|
|
8421
|
+
logWarn("WireGuard state not initialized.");
|
|
8422
|
+
logInfo("Run: envsync-deploy vpn pubkey");
|
|
8423
|
+
return;
|
|
8424
|
+
}
|
|
8425
|
+
logInfo(`Config file: ${WIREGUARD_CONFIG_FILE}`);
|
|
8426
|
+
logInfo(`State file: ${WIREGUARD_STATE_FILE}`);
|
|
8427
|
+
logInfo(`Server endpoint: 0.0.0.0:${state.listen_port}`);
|
|
8428
|
+
logInfo(`Network: ${state.network}`);
|
|
8429
|
+
logInfo(`Server IP: ${state.server_ip}`);
|
|
8430
|
+
if (state.peers.length === 0) {
|
|
8431
|
+
logInfo("Peers: none");
|
|
8432
|
+
} else {
|
|
8433
|
+
logInfo("Peers:");
|
|
8434
|
+
for (const peer of state.peers) {
|
|
8435
|
+
console.log(` ${peer.public_key} -> ${peer.allowed_ip}`);
|
|
8436
|
+
}
|
|
8437
|
+
}
|
|
8438
|
+
console.log("");
|
|
8439
|
+
logInfo("Container endpoints:");
|
|
8440
|
+
const stackName = getVpnStackName();
|
|
8441
|
+
const services = listStackServiceNames(stackName);
|
|
8442
|
+
if (services.length === 0) {
|
|
8443
|
+
logWarn(`No running services found for stack ${stackName}`);
|
|
8444
|
+
} else {
|
|
8445
|
+
for (const service of services) {
|
|
8446
|
+
const ports = listServicePublishedPorts(service);
|
|
8447
|
+
const endpoints = listRunningContainerEndpoints(service);
|
|
8448
|
+
console.log(` ${service}`);
|
|
8449
|
+
console.log(` Published ports: ${ports.length ? ports.join(", ") : "none"}`);
|
|
8450
|
+
if (endpoints.length === 0) {
|
|
8451
|
+
console.log(" Containers: none running");
|
|
8452
|
+
} else {
|
|
8453
|
+
for (const endpoint of endpoints) {
|
|
8454
|
+
console.log(` ${endpoint}`);
|
|
8455
|
+
}
|
|
8456
|
+
}
|
|
8457
|
+
}
|
|
8458
|
+
}
|
|
8459
|
+
try {
|
|
8460
|
+
const ifaceStatus = runCapture("wg", ["show", "envsync-wg"]);
|
|
8461
|
+
console.log("Runtime:");
|
|
8462
|
+
console.log(ifaceStatus);
|
|
8463
|
+
} catch {
|
|
8464
|
+
logWarn("WireGuard interface is not running in kernel yet. Run wg-quick up on the generated config to activate.");
|
|
8465
|
+
}
|
|
8466
|
+
}
|
|
8168
8467
|
async function main() {
|
|
8169
8468
|
const argv = process.argv.slice(2);
|
|
8170
8469
|
const command = argv[0];
|
|
@@ -8212,6 +8511,9 @@ async function main() {
|
|
|
8212
8511
|
case "upgrade-deps":
|
|
8213
8512
|
await cmdUpgradeDeps();
|
|
8214
8513
|
break;
|
|
8514
|
+
case "vpn":
|
|
8515
|
+
await cmdVpn(positionals);
|
|
8516
|
+
break;
|
|
8215
8517
|
case "license":
|
|
8216
8518
|
await cmdLicense(positionals[0]);
|
|
8217
8519
|
break;
|