@envsync-cloud/deploy-cli 0.8.6 → 0.8.7
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 +211 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
1
3
|
// src/index.ts
|
|
2
4
|
import { createHash, randomBytes } from "crypto";
|
|
3
5
|
import { spawnSync } from "child_process";
|
|
@@ -84,6 +86,11 @@ function buildRuntimeEnv(config, generated) {
|
|
|
84
86
|
ENVSYNC_LANDING_ENABLED: oss ? "false" : "true",
|
|
85
87
|
ENVSYNC_SINGLE_ORG_MODE: oss ? "true" : "false",
|
|
86
88
|
ENVSYNC_LICENSE_ENFORCEMENT: oss ? "false" : "true",
|
|
89
|
+
ENVSYNC_LICENSE_MODE: oss ? "none" : "certificate",
|
|
90
|
+
ENVSYNC_LICENSE_BUNDLE_PATH: oss ? "" : "/etc/envsync/license/enterprise-license-bundle.json",
|
|
91
|
+
ENVSYNC_LICENSE_CERT_PATH: oss ? "" : "/etc/envsync/license/enterprise-cert.pem",
|
|
92
|
+
ENVSYNC_LICENSE_KEY_PATH: oss ? "" : "/etc/envsync/license/enterprise-key.pem",
|
|
93
|
+
ENVSYNC_LICENSE_ROOT_CA_CERT_PATH: oss ? "" : "/etc/envsync/license/root-ca.pem",
|
|
87
94
|
DB_AUTO_MIGRATE: "false",
|
|
88
95
|
PORT: `${config.services.api_port}`,
|
|
89
96
|
DATABASE_HOST: "postgres",
|
|
@@ -447,6 +454,7 @@ function renderStack(config, runtimeEnv, generated, mode, paths) {
|
|
|
447
454
|
const includeRuntimeInfra = mode !== "base";
|
|
448
455
|
const includeAppServices = mode === "full";
|
|
449
456
|
const landingEnabled = !isOssConfig(config);
|
|
457
|
+
const apiLicenseVolume = landingEnabled ? "\n volumes:\n - /etc/envsync/license:/etc/envsync/license:ro" : "";
|
|
450
458
|
const deployment = createSteadyApiDeploymentState(config, generated);
|
|
451
459
|
const stackName = config.services.stack_name;
|
|
452
460
|
const s3RouterName = `${stackName}-s3-router`;
|
|
@@ -693,6 +701,7 @@ ${renderEnvList({
|
|
|
693
701
|
ENVSYNC_DEPLOY_SLOT: "blue",
|
|
694
702
|
ENVSYNC_DEPLOY_RELEASE_VERSION: deployment.slots.blue.release_version || config.release.version
|
|
695
703
|
})}
|
|
704
|
+
${apiLicenseVolume}
|
|
696
705
|
networks: [envsync]
|
|
697
706
|
deploy:
|
|
698
707
|
replicas: ${slotHasApiDeployment(deployment.slots.blue) ? 1 : 0}
|
|
@@ -705,6 +714,7 @@ ${renderEnvList({
|
|
|
705
714
|
ENVSYNC_DEPLOY_SLOT: "green",
|
|
706
715
|
ENVSYNC_DEPLOY_RELEASE_VERSION: deployment.slots.green.release_version || config.release.version
|
|
707
716
|
})}
|
|
717
|
+
${apiLicenseVolume}
|
|
708
718
|
networks: [envsync]
|
|
709
719
|
deploy:
|
|
710
720
|
replicas: ${slotHasApiDeployment(deployment.slots.green) ? 1 : 0}` : ""}
|
|
@@ -784,6 +794,11 @@ var BACKUPS_ROOT = path2.join(HOST_ROOT, "backups");
|
|
|
784
794
|
var REPO_ROOT = process.env.ENVSYNC_REPO_ROOT ?? path2.join(HOST_ROOT, "repo");
|
|
785
795
|
var DEPLOY_ENV = path2.join(ETC_ROOT, "deploy.env");
|
|
786
796
|
var DEPLOY_YAML = path2.join(ETC_ROOT, "deploy.yaml");
|
|
797
|
+
var LICENSE_ROOT = path2.join(ETC_ROOT, "license");
|
|
798
|
+
var LICENSE_BUNDLE_FILE = path2.join(LICENSE_ROOT, "enterprise-license-bundle.json");
|
|
799
|
+
var LICENSE_CERT_FILE = path2.join(LICENSE_ROOT, "enterprise-cert.pem");
|
|
800
|
+
var LICENSE_KEY_FILE = path2.join(LICENSE_ROOT, "enterprise-key.pem");
|
|
801
|
+
var LICENSE_ROOT_CA_FILE = path2.join(LICENSE_ROOT, "root-ca.pem");
|
|
787
802
|
var VERSIONS_LOCK = path2.join(DEPLOY_ROOT, "versions.lock.json");
|
|
788
803
|
var STACK_FILE = path2.join(DEPLOY_ROOT, "docker-stack.yaml");
|
|
789
804
|
var BOOTSTRAP_BASE_STACK_FILE = path2.join(DEPLOY_ROOT, "docker-stack.bootstrap.base.yaml");
|
|
@@ -855,6 +870,7 @@ var CLICKSTACK_SELFHOST_TEAM_NAME = "EnvSync Self-Hosted Team";
|
|
|
855
870
|
var SEMVER_VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
856
871
|
var currentOptions = { dryRun: false, force: false };
|
|
857
872
|
var DEFAULT_SOURCE_REPO_URL = "https://github.com/EnvSync-Cloud/envsync.git";
|
|
873
|
+
var DEFAULT_ENTERPRISE_LICENSE_SERVER_URL = "https://license.envsync.cloud";
|
|
858
874
|
var MANAGED_VERSIONED_IMAGE_PREFIXES = {
|
|
859
875
|
api: "ghcr.io/envsync-cloud/envsync-api:",
|
|
860
876
|
keycloak: "envsync-keycloak:",
|
|
@@ -978,6 +994,9 @@ function exists2(target) {
|
|
|
978
994
|
function randomSecret(bytes = 24) {
|
|
979
995
|
return randomBytes(bytes).toString("hex");
|
|
980
996
|
}
|
|
997
|
+
function deterministicInstallFingerprint(rootDomain, stackName) {
|
|
998
|
+
return `envsync-${createHash("sha256").update(`${rootDomain}:${stackName}`).digest("hex").slice(0, 32)}`;
|
|
999
|
+
}
|
|
981
1000
|
function randomStrongPassword() {
|
|
982
1001
|
return `EnvSync!${randomBytes(8).toString("hex")}Aa1`;
|
|
983
1002
|
}
|
|
@@ -1114,6 +1133,9 @@ function renderHelpBlock() {
|
|
|
1114
1133
|
" validate-topology Validate Enterprise edition topology rules",
|
|
1115
1134
|
" upgrade [version] Pin a target release and deploy it",
|
|
1116
1135
|
" upgrade-deps Refresh dependency images and redeploy",
|
|
1136
|
+
" license issue-cert Issue and install an Enterprise certificate bundle",
|
|
1137
|
+
" license renew-cert Renew and install the Enterprise certificate bundle",
|
|
1138
|
+
" license validate-cert Validate the installed Enterprise certificate bundle files",
|
|
1117
1139
|
" backup Create a managed self-host backup archive",
|
|
1118
1140
|
" restore <archive> Restore a backup archive into the managed self-host roots",
|
|
1119
1141
|
"",
|
|
@@ -1189,6 +1211,7 @@ function printOperatorOverview() {
|
|
|
1189
1211
|
"`envsync-deploy bootstrap --force`",
|
|
1190
1212
|
"`envsync-deploy deploy`",
|
|
1191
1213
|
"`envsync-deploy upgrade`",
|
|
1214
|
+
"`envsync-deploy license issue-cert`",
|
|
1192
1215
|
"`envsync-deploy health --json`",
|
|
1193
1216
|
"`envsync-deploy plan-topology --json`",
|
|
1194
1217
|
"`envsync-deploy backup`",
|
|
@@ -1277,6 +1300,7 @@ function normalizeConfig(raw) {
|
|
|
1277
1300
|
const { release_channel: _legacyReleaseChannel, ...rest } = raw;
|
|
1278
1301
|
const rootDomain = requireDefined(raw.domain?.root_domain, "domain.root_domain");
|
|
1279
1302
|
const acmeEmail = requireDefined(raw.domain?.acme_email, "domain.acme_email");
|
|
1303
|
+
const stackName = requireDefined(raw.services?.stack_name, "services.stack_name");
|
|
1280
1304
|
return {
|
|
1281
1305
|
...rest,
|
|
1282
1306
|
edition: raw.edition ?? "enterprise",
|
|
@@ -1301,7 +1325,7 @@ function normalizeConfig(raw) {
|
|
|
1301
1325
|
otel_agent: raw.images?.otel_agent ?? "otel/opentelemetry-collector-contrib:0.111.0"
|
|
1302
1326
|
},
|
|
1303
1327
|
services: {
|
|
1304
|
-
stack_name:
|
|
1328
|
+
stack_name: stackName,
|
|
1305
1329
|
api_port: requireDefined(raw.services?.api_port, "services.api_port"),
|
|
1306
1330
|
public_http_port: raw.services?.public_http_port ?? 80,
|
|
1307
1331
|
public_https_port: raw.services?.public_https_port ?? 443,
|
|
@@ -1349,6 +1373,16 @@ function normalizeConfig(raw) {
|
|
|
1349
1373
|
maintenance_mode_enabled: raw.upgrade?.maintenance_mode_enabled ?? true,
|
|
1350
1374
|
db_snapshot_on_api_upgrade: raw.upgrade?.db_snapshot_on_api_upgrade ?? true,
|
|
1351
1375
|
keep_failed_upgrade_db_snapshot: raw.upgrade?.keep_failed_upgrade_db_snapshot ?? true
|
|
1376
|
+
},
|
|
1377
|
+
license: raw.license ? {
|
|
1378
|
+
server_url: raw.license.server_url,
|
|
1379
|
+
key: raw.license.key,
|
|
1380
|
+
install_fingerprint: raw.license.install_fingerprint ?? deterministicInstallFingerprint(rootDomain, stackName),
|
|
1381
|
+
certificate_bundle_file: raw.license.certificate_bundle_file,
|
|
1382
|
+
certificate_validity_days: raw.license.certificate_validity_days
|
|
1383
|
+
} : raw.edition === "oss" ? void 0 : {
|
|
1384
|
+
install_fingerprint: deterministicInstallFingerprint(rootDomain, stackName),
|
|
1385
|
+
certificate_validity_days: 1095
|
|
1352
1386
|
}
|
|
1353
1387
|
};
|
|
1354
1388
|
}
|
|
@@ -2666,6 +2700,7 @@ async function cmdPreinstall() {
|
|
|
2666
2700
|
ensureDir(RELEASES_ROOT);
|
|
2667
2701
|
ensureDir(BACKUPS_ROOT);
|
|
2668
2702
|
ensureDir(ETC_ROOT);
|
|
2703
|
+
ensureDir(LICENSE_ROOT);
|
|
2669
2704
|
ensureDir(TRAEFIK_STATE_ROOT);
|
|
2670
2705
|
run("bash", ["-lc", "command -v apt-get >/dev/null"]);
|
|
2671
2706
|
run("sudo", ["apt-get", "update"]);
|
|
@@ -2698,7 +2733,12 @@ async function cmdSetup() {
|
|
|
2698
2733
|
const publicAuth = await ask("Expose auth.<domain> publicly (true/false)", "true") === "true";
|
|
2699
2734
|
const publicObs = await ask("Expose obs.<domain> publicly (true/false)", "true") === "true";
|
|
2700
2735
|
const mailpitEnabled = await ask("Enable mailpit (true/false)", "false") === "true";
|
|
2736
|
+
const licenseServerUrl = await ask("Enterprise license server URL", DEFAULT_ENTERPRISE_LICENSE_SERVER_URL);
|
|
2737
|
+
const licenseKey = licenseServerUrl ? await ask("Enterprise license key", "") : "";
|
|
2738
|
+
const certificateBundleFile = licenseServerUrl ? "" : await ask("Enterprise certificate bundle file (optional)", "");
|
|
2739
|
+
const installFingerprint = deterministicInstallFingerprint(rootDomain, "envsync");
|
|
2701
2740
|
const config = {
|
|
2741
|
+
edition: "enterprise",
|
|
2702
2742
|
source: defaultSourceConfig(releaseVersion),
|
|
2703
2743
|
release: {
|
|
2704
2744
|
version: releaseVersion
|
|
@@ -2760,6 +2800,13 @@ async function cmdSetup() {
|
|
|
2760
2800
|
maintenance_mode_enabled: true,
|
|
2761
2801
|
db_snapshot_on_api_upgrade: true,
|
|
2762
2802
|
keep_failed_upgrade_db_snapshot: true
|
|
2803
|
+
},
|
|
2804
|
+
license: {
|
|
2805
|
+
server_url: licenseServerUrl || void 0,
|
|
2806
|
+
key: licenseKey || void 0,
|
|
2807
|
+
install_fingerprint: installFingerprint,
|
|
2808
|
+
certificate_bundle_file: certificateBundleFile || void 0,
|
|
2809
|
+
certificate_validity_days: 1095
|
|
2763
2810
|
}
|
|
2764
2811
|
};
|
|
2765
2812
|
saveDesiredConfig(config);
|
|
@@ -2775,6 +2822,7 @@ async function cmdBootstrap() {
|
|
|
2775
2822
|
const runtimeEnv = buildRuntimeEnv(config, nextGenerated);
|
|
2776
2823
|
logReleaseContext(config);
|
|
2777
2824
|
assertSwarmManager();
|
|
2825
|
+
await ensureEnterpriseCertificateBundle(config);
|
|
2778
2826
|
if (currentOptions.dryRun) {
|
|
2779
2827
|
logWarn("Dry-run mode: bootstrap reset will be previewed but not executed.");
|
|
2780
2828
|
}
|
|
@@ -3236,6 +3284,154 @@ function sha256File(filePath) {
|
|
|
3236
3284
|
hash.update(fs2.readFileSync(filePath));
|
|
3237
3285
|
return hash.digest("hex");
|
|
3238
3286
|
}
|
|
3287
|
+
function parseEnterpriseLicenseBundle(raw) {
|
|
3288
|
+
if (!raw || typeof raw !== "object") {
|
|
3289
|
+
throw new Error("Invalid Enterprise license certificate bundle: expected an object.");
|
|
3290
|
+
}
|
|
3291
|
+
const candidate = raw;
|
|
3292
|
+
const requiredStrings = [
|
|
3293
|
+
"certificate_pem",
|
|
3294
|
+
"private_key_pem",
|
|
3295
|
+
"root_ca_pem",
|
|
3296
|
+
"serial_hex",
|
|
3297
|
+
"certificate_fingerprint_sha256",
|
|
3298
|
+
"root_ca_fingerprint_sha256",
|
|
3299
|
+
"issued_at",
|
|
3300
|
+
"expires_at"
|
|
3301
|
+
];
|
|
3302
|
+
for (const key of requiredStrings) {
|
|
3303
|
+
if (typeof candidate[key] !== "string" || candidate[key].length === 0) {
|
|
3304
|
+
throw new Error(`Invalid Enterprise license certificate bundle: missing ${String(key)}.`);
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
if (!candidate.metadata || candidate.metadata.edition !== "enterprise") {
|
|
3308
|
+
throw new Error("Invalid Enterprise license certificate bundle: missing enterprise metadata.");
|
|
3309
|
+
}
|
|
3310
|
+
return candidate;
|
|
3311
|
+
}
|
|
3312
|
+
function readEnterpriseLicenseBundle(bundlePath) {
|
|
3313
|
+
return parseEnterpriseLicenseBundle(JSON.parse(fs2.readFileSync(bundlePath, "utf8")));
|
|
3314
|
+
}
|
|
3315
|
+
function normalizeLicenseServerUrl(serverUrl, route) {
|
|
3316
|
+
const trimmed = serverUrl.replace(/\/+$/, "");
|
|
3317
|
+
return `${trimmed}/v1/certificates/${route}`;
|
|
3318
|
+
}
|
|
3319
|
+
async function requestEnterpriseLicenseCertificate(config, route) {
|
|
3320
|
+
const license = config.license;
|
|
3321
|
+
if (!license?.server_url) {
|
|
3322
|
+
throw new Error(`Missing license.server_url in ${DEPLOY_YAML}. Set it or use license.certificate_bundle_file.`);
|
|
3323
|
+
}
|
|
3324
|
+
if (!license.key) {
|
|
3325
|
+
throw new Error(`Missing license.key in ${DEPLOY_YAML}.`);
|
|
3326
|
+
}
|
|
3327
|
+
const installFingerprint = license.install_fingerprint || deterministicInstallFingerprint(config.domain.root_domain, config.services.stack_name);
|
|
3328
|
+
const response = await fetch(normalizeLicenseServerUrl(license.server_url, route), {
|
|
3329
|
+
method: "POST",
|
|
3330
|
+
headers: {
|
|
3331
|
+
"content-type": "application/json",
|
|
3332
|
+
"x-license-server-access-key": license.key
|
|
3333
|
+
},
|
|
3334
|
+
body: JSON.stringify({
|
|
3335
|
+
license_key: license.key,
|
|
3336
|
+
install_fingerprint: installFingerprint,
|
|
3337
|
+
root_domain: config.domain.root_domain,
|
|
3338
|
+
stack_name: config.services.stack_name,
|
|
3339
|
+
edition: "enterprise",
|
|
3340
|
+
requested_validity_days: license.certificate_validity_days ?? 1095
|
|
3341
|
+
})
|
|
3342
|
+
});
|
|
3343
|
+
const body = await response.json().catch(() => ({}));
|
|
3344
|
+
if (!response.ok || !body.bundle) {
|
|
3345
|
+
throw new Error(body.error || body.message || `License certificate ${route} failed with HTTP ${response.status}`);
|
|
3346
|
+
}
|
|
3347
|
+
return parseEnterpriseLicenseBundle(body.bundle);
|
|
3348
|
+
}
|
|
3349
|
+
function writeEnterpriseLicenseBundle(bundle) {
|
|
3350
|
+
writeFileMaybe(LICENSE_BUNDLE_FILE, JSON.stringify(bundle, null, 2) + "\n", 384);
|
|
3351
|
+
writeFileMaybe(LICENSE_CERT_FILE, bundle.certificate_pem.endsWith("\n") ? bundle.certificate_pem : `${bundle.certificate_pem}
|
|
3352
|
+
`, 384);
|
|
3353
|
+
writeFileMaybe(LICENSE_KEY_FILE, bundle.private_key_pem.endsWith("\n") ? bundle.private_key_pem : `${bundle.private_key_pem}
|
|
3354
|
+
`, 384);
|
|
3355
|
+
writeFileMaybe(LICENSE_ROOT_CA_FILE, bundle.root_ca_pem.endsWith("\n") ? bundle.root_ca_pem : `${bundle.root_ca_pem}
|
|
3356
|
+
`, 420);
|
|
3357
|
+
}
|
|
3358
|
+
async function ensureEnterpriseCertificateBundle(config) {
|
|
3359
|
+
if (isOssConfig2(config)) return;
|
|
3360
|
+
if (exists2(LICENSE_BUNDLE_FILE) && exists2(LICENSE_CERT_FILE) && exists2(LICENSE_KEY_FILE) && exists2(LICENSE_ROOT_CA_FILE)) {
|
|
3361
|
+
validateEnterpriseLicenseBundleFiles(config, false);
|
|
3362
|
+
return;
|
|
3363
|
+
}
|
|
3364
|
+
if (currentOptions.dryRun) {
|
|
3365
|
+
logDryRun(`Would install or issue Enterprise certificate bundle under ${LICENSE_ROOT}`);
|
|
3366
|
+
return;
|
|
3367
|
+
}
|
|
3368
|
+
if (config.license?.certificate_bundle_file) {
|
|
3369
|
+
logStep("Installing Enterprise certificate bundle from configured file");
|
|
3370
|
+
const bundle = readEnterpriseLicenseBundle(config.license.certificate_bundle_file);
|
|
3371
|
+
writeEnterpriseLicenseBundle(bundle);
|
|
3372
|
+
validateEnterpriseLicenseBundleFiles(config, false);
|
|
3373
|
+
return;
|
|
3374
|
+
}
|
|
3375
|
+
if (config.license?.server_url && config.license.key) {
|
|
3376
|
+
logStep("Issuing Enterprise certificate bundle from license server");
|
|
3377
|
+
const bundle = await requestEnterpriseLicenseCertificate(config, "issue");
|
|
3378
|
+
writeEnterpriseLicenseBundle(bundle);
|
|
3379
|
+
validateEnterpriseLicenseBundleFiles(config, false);
|
|
3380
|
+
return;
|
|
3381
|
+
}
|
|
3382
|
+
throw new Error(
|
|
3383
|
+
`Enterprise deployments require a certificate bundle. Configure license.server_url and license.key, set license.certificate_bundle_file, or run 'envsync-deploy license issue-cert'.`
|
|
3384
|
+
);
|
|
3385
|
+
}
|
|
3386
|
+
function validateEnterpriseLicenseBundleFiles(config, verbose = true) {
|
|
3387
|
+
if (isOssConfig2(config)) {
|
|
3388
|
+
if (verbose) logInfo("OSS deployments do not use Enterprise license certificates.");
|
|
3389
|
+
return null;
|
|
3390
|
+
}
|
|
3391
|
+
const bundle = readEnterpriseLicenseBundle(LICENSE_BUNDLE_FILE);
|
|
3392
|
+
if (bundle.metadata.install_fingerprint !== config.license?.install_fingerprint) {
|
|
3393
|
+
throw new Error("Installed Enterprise certificate bundle does not match deploy.yaml license.install_fingerprint.");
|
|
3394
|
+
}
|
|
3395
|
+
if (bundle.metadata.stack_name !== config.services.stack_name) {
|
|
3396
|
+
throw new Error("Installed Enterprise certificate bundle does not match deploy.yaml services.stack_name.");
|
|
3397
|
+
}
|
|
3398
|
+
if (bundle.metadata.root_domain !== config.domain.root_domain) {
|
|
3399
|
+
throw new Error("Installed Enterprise certificate bundle does not match deploy.yaml domain.root_domain.");
|
|
3400
|
+
}
|
|
3401
|
+
for (const filePath of [LICENSE_CERT_FILE, LICENSE_KEY_FILE, LICENSE_ROOT_CA_FILE]) {
|
|
3402
|
+
if (!exists2(filePath)) throw new Error(`Missing Enterprise license file: ${filePath}`);
|
|
3403
|
+
}
|
|
3404
|
+
if (verbose) {
|
|
3405
|
+
logSuccess(`Enterprise certificate bundle is installed at ${LICENSE_ROOT}`);
|
|
3406
|
+
logInfo(`Certificate serial: ${bundle.serial_hex}`);
|
|
3407
|
+
logInfo(`Certificate expires: ${bundle.expires_at}`);
|
|
3408
|
+
}
|
|
3409
|
+
return bundle;
|
|
3410
|
+
}
|
|
3411
|
+
async function cmdLicense(action = "validate-cert") {
|
|
3412
|
+
logSection("Enterprise License");
|
|
3413
|
+
const config = loadConfig();
|
|
3414
|
+
if (isOssConfig2(config)) {
|
|
3415
|
+
logInfo("OSS deployments do not require license verification.");
|
|
3416
|
+
return;
|
|
3417
|
+
}
|
|
3418
|
+
if (action === "issue-cert" || action === "renew-cert") {
|
|
3419
|
+
const route = action === "renew-cert" ? "renew" : "issue";
|
|
3420
|
+
if (currentOptions.dryRun) {
|
|
3421
|
+
logDryRun(`Would ${route} Enterprise certificate bundle and install it under ${LICENSE_ROOT}`);
|
|
3422
|
+
return;
|
|
3423
|
+
}
|
|
3424
|
+
const bundle = config.license?.certificate_bundle_file ? readEnterpriseLicenseBundle(config.license.certificate_bundle_file) : await requestEnterpriseLicenseCertificate(config, route);
|
|
3425
|
+
writeEnterpriseLicenseBundle(bundle);
|
|
3426
|
+
validateEnterpriseLicenseBundleFiles(config);
|
|
3427
|
+
return;
|
|
3428
|
+
}
|
|
3429
|
+
if (action === "validate-cert") {
|
|
3430
|
+
validateEnterpriseLicenseBundleFiles(config);
|
|
3431
|
+
return;
|
|
3432
|
+
}
|
|
3433
|
+
throw new Error("Unknown license action. Expected issue-cert, renew-cert, or validate-cert.");
|
|
3434
|
+
}
|
|
3239
3435
|
function stackVolumeName(config, name) {
|
|
3240
3436
|
return `${config.services.stack_name}_${name}`;
|
|
3241
3437
|
}
|
|
@@ -3347,6 +3543,9 @@ async function cmdBackup() {
|
|
|
3347
3543
|
for (const volume of STACK_VOLUMES) {
|
|
3348
3544
|
backupDockerVolume(stackVolumeName(config, volume), path2.join(staged, "volumes", volume));
|
|
3349
3545
|
}
|
|
3546
|
+
if (!isOssConfig2(config)) {
|
|
3547
|
+
logDryRun(`Would include Enterprise license material from ${LICENSE_ROOT}`);
|
|
3548
|
+
}
|
|
3350
3549
|
logDryRun(`Would write manifest ${manifestPath}`);
|
|
3351
3550
|
logDryRun(`Would create archive ${tarPath}`);
|
|
3352
3551
|
logSuccess("Backup dry-run completed");
|
|
@@ -3374,6 +3573,9 @@ async function cmdBackup() {
|
|
|
3374
3573
|
writeFile(path2.join(staged, "keycloak-realm.envsync.json"), fs2.readFileSync(KEYCLOAK_REALM_FILE, "utf8"));
|
|
3375
3574
|
writeFile(path2.join(staged, "otel-agent.yaml"), fs2.readFileSync(OTEL_AGENT_CONF, "utf8"));
|
|
3376
3575
|
writeFile(path2.join(staged, "clickhouse-listen.xml"), fs2.readFileSync(CLICKSTACK_CLICKHOUSE_CONF, "utf8"));
|
|
3576
|
+
if (!isOssConfig2(config) && exists2(LICENSE_ROOT)) {
|
|
3577
|
+
fs2.cpSync(LICENSE_ROOT, path2.join(staged, "license"), { recursive: true });
|
|
3578
|
+
}
|
|
3377
3579
|
const volumesDir = path2.join(staged, "volumes");
|
|
3378
3580
|
for (const volume of STACK_VOLUMES) {
|
|
3379
3581
|
backupDockerVolume(stackVolumeName(config, volume), path2.join(volumesDir, volume));
|
|
@@ -3450,6 +3652,11 @@ async function cmdRestore(archivePath, autoDeploy = false) {
|
|
|
3450
3652
|
writeFile(KEYCLOAK_REALM_FILE, fs2.readFileSync(path2.join(restoreRoot, "keycloak-realm.envsync.json"), "utf8"));
|
|
3451
3653
|
writeFile(OTEL_AGENT_CONF, fs2.readFileSync(path2.join(restoreRoot, "otel-agent.yaml"), "utf8"));
|
|
3452
3654
|
writeFile(CLICKSTACK_CLICKHOUSE_CONF, fs2.readFileSync(path2.join(restoreRoot, "clickhouse-listen.xml"), "utf8"));
|
|
3655
|
+
const restoredLicenseRoot = path2.join(restoreRoot, "license");
|
|
3656
|
+
if (exists2(restoredLicenseRoot)) {
|
|
3657
|
+
fs2.rmSync(LICENSE_ROOT, { recursive: true, force: true });
|
|
3658
|
+
fs2.cpSync(restoredLicenseRoot, LICENSE_ROOT, { recursive: true });
|
|
3659
|
+
}
|
|
3453
3660
|
const config = loadConfig();
|
|
3454
3661
|
for (const volume of STACK_VOLUMES) {
|
|
3455
3662
|
restoreDockerVolume(stackVolumeName(config, volume), path2.join(restoreRoot, "volumes", volume));
|
|
@@ -3511,6 +3718,9 @@ async function main() {
|
|
|
3511
3718
|
case "upgrade-deps":
|
|
3512
3719
|
await cmdUpgradeDeps();
|
|
3513
3720
|
break;
|
|
3721
|
+
case "license":
|
|
3722
|
+
await cmdLicense(positionals[0]);
|
|
3723
|
+
break;
|
|
3514
3724
|
case "backup":
|
|
3515
3725
|
await cmdBackup();
|
|
3516
3726
|
break;
|