@envsync-cloud/deploy-cli 0.8.5 → 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 +209 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -86,6 +86,11 @@ function buildRuntimeEnv(config, generated) {
|
|
|
86
86
|
ENVSYNC_LANDING_ENABLED: oss ? "false" : "true",
|
|
87
87
|
ENVSYNC_SINGLE_ORG_MODE: oss ? "true" : "false",
|
|
88
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",
|
|
89
94
|
DB_AUTO_MIGRATE: "false",
|
|
90
95
|
PORT: `${config.services.api_port}`,
|
|
91
96
|
DATABASE_HOST: "postgres",
|
|
@@ -449,6 +454,7 @@ function renderStack(config, runtimeEnv, generated, mode, paths) {
|
|
|
449
454
|
const includeRuntimeInfra = mode !== "base";
|
|
450
455
|
const includeAppServices = mode === "full";
|
|
451
456
|
const landingEnabled = !isOssConfig(config);
|
|
457
|
+
const apiLicenseVolume = landingEnabled ? "\n volumes:\n - /etc/envsync/license:/etc/envsync/license:ro" : "";
|
|
452
458
|
const deployment = createSteadyApiDeploymentState(config, generated);
|
|
453
459
|
const stackName = config.services.stack_name;
|
|
454
460
|
const s3RouterName = `${stackName}-s3-router`;
|
|
@@ -695,6 +701,7 @@ ${renderEnvList({
|
|
|
695
701
|
ENVSYNC_DEPLOY_SLOT: "blue",
|
|
696
702
|
ENVSYNC_DEPLOY_RELEASE_VERSION: deployment.slots.blue.release_version || config.release.version
|
|
697
703
|
})}
|
|
704
|
+
${apiLicenseVolume}
|
|
698
705
|
networks: [envsync]
|
|
699
706
|
deploy:
|
|
700
707
|
replicas: ${slotHasApiDeployment(deployment.slots.blue) ? 1 : 0}
|
|
@@ -707,6 +714,7 @@ ${renderEnvList({
|
|
|
707
714
|
ENVSYNC_DEPLOY_SLOT: "green",
|
|
708
715
|
ENVSYNC_DEPLOY_RELEASE_VERSION: deployment.slots.green.release_version || config.release.version
|
|
709
716
|
})}
|
|
717
|
+
${apiLicenseVolume}
|
|
710
718
|
networks: [envsync]
|
|
711
719
|
deploy:
|
|
712
720
|
replicas: ${slotHasApiDeployment(deployment.slots.green) ? 1 : 0}` : ""}
|
|
@@ -786,6 +794,11 @@ var BACKUPS_ROOT = path2.join(HOST_ROOT, "backups");
|
|
|
786
794
|
var REPO_ROOT = process.env.ENVSYNC_REPO_ROOT ?? path2.join(HOST_ROOT, "repo");
|
|
787
795
|
var DEPLOY_ENV = path2.join(ETC_ROOT, "deploy.env");
|
|
788
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");
|
|
789
802
|
var VERSIONS_LOCK = path2.join(DEPLOY_ROOT, "versions.lock.json");
|
|
790
803
|
var STACK_FILE = path2.join(DEPLOY_ROOT, "docker-stack.yaml");
|
|
791
804
|
var BOOTSTRAP_BASE_STACK_FILE = path2.join(DEPLOY_ROOT, "docker-stack.bootstrap.base.yaml");
|
|
@@ -857,6 +870,7 @@ var CLICKSTACK_SELFHOST_TEAM_NAME = "EnvSync Self-Hosted Team";
|
|
|
857
870
|
var SEMVER_VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
858
871
|
var currentOptions = { dryRun: false, force: false };
|
|
859
872
|
var DEFAULT_SOURCE_REPO_URL = "https://github.com/EnvSync-Cloud/envsync.git";
|
|
873
|
+
var DEFAULT_ENTERPRISE_LICENSE_SERVER_URL = "https://license.envsync.cloud";
|
|
860
874
|
var MANAGED_VERSIONED_IMAGE_PREFIXES = {
|
|
861
875
|
api: "ghcr.io/envsync-cloud/envsync-api:",
|
|
862
876
|
keycloak: "envsync-keycloak:",
|
|
@@ -980,6 +994,9 @@ function exists2(target) {
|
|
|
980
994
|
function randomSecret(bytes = 24) {
|
|
981
995
|
return randomBytes(bytes).toString("hex");
|
|
982
996
|
}
|
|
997
|
+
function deterministicInstallFingerprint(rootDomain, stackName) {
|
|
998
|
+
return `envsync-${createHash("sha256").update(`${rootDomain}:${stackName}`).digest("hex").slice(0, 32)}`;
|
|
999
|
+
}
|
|
983
1000
|
function randomStrongPassword() {
|
|
984
1001
|
return `EnvSync!${randomBytes(8).toString("hex")}Aa1`;
|
|
985
1002
|
}
|
|
@@ -1116,6 +1133,9 @@ function renderHelpBlock() {
|
|
|
1116
1133
|
" validate-topology Validate Enterprise edition topology rules",
|
|
1117
1134
|
" upgrade [version] Pin a target release and deploy it",
|
|
1118
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",
|
|
1119
1139
|
" backup Create a managed self-host backup archive",
|
|
1120
1140
|
" restore <archive> Restore a backup archive into the managed self-host roots",
|
|
1121
1141
|
"",
|
|
@@ -1191,6 +1211,7 @@ function printOperatorOverview() {
|
|
|
1191
1211
|
"`envsync-deploy bootstrap --force`",
|
|
1192
1212
|
"`envsync-deploy deploy`",
|
|
1193
1213
|
"`envsync-deploy upgrade`",
|
|
1214
|
+
"`envsync-deploy license issue-cert`",
|
|
1194
1215
|
"`envsync-deploy health --json`",
|
|
1195
1216
|
"`envsync-deploy plan-topology --json`",
|
|
1196
1217
|
"`envsync-deploy backup`",
|
|
@@ -1279,6 +1300,7 @@ function normalizeConfig(raw) {
|
|
|
1279
1300
|
const { release_channel: _legacyReleaseChannel, ...rest } = raw;
|
|
1280
1301
|
const rootDomain = requireDefined(raw.domain?.root_domain, "domain.root_domain");
|
|
1281
1302
|
const acmeEmail = requireDefined(raw.domain?.acme_email, "domain.acme_email");
|
|
1303
|
+
const stackName = requireDefined(raw.services?.stack_name, "services.stack_name");
|
|
1282
1304
|
return {
|
|
1283
1305
|
...rest,
|
|
1284
1306
|
edition: raw.edition ?? "enterprise",
|
|
@@ -1303,7 +1325,7 @@ function normalizeConfig(raw) {
|
|
|
1303
1325
|
otel_agent: raw.images?.otel_agent ?? "otel/opentelemetry-collector-contrib:0.111.0"
|
|
1304
1326
|
},
|
|
1305
1327
|
services: {
|
|
1306
|
-
stack_name:
|
|
1328
|
+
stack_name: stackName,
|
|
1307
1329
|
api_port: requireDefined(raw.services?.api_port, "services.api_port"),
|
|
1308
1330
|
public_http_port: raw.services?.public_http_port ?? 80,
|
|
1309
1331
|
public_https_port: raw.services?.public_https_port ?? 443,
|
|
@@ -1351,6 +1373,16 @@ function normalizeConfig(raw) {
|
|
|
1351
1373
|
maintenance_mode_enabled: raw.upgrade?.maintenance_mode_enabled ?? true,
|
|
1352
1374
|
db_snapshot_on_api_upgrade: raw.upgrade?.db_snapshot_on_api_upgrade ?? true,
|
|
1353
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
|
|
1354
1386
|
}
|
|
1355
1387
|
};
|
|
1356
1388
|
}
|
|
@@ -2668,6 +2700,7 @@ async function cmdPreinstall() {
|
|
|
2668
2700
|
ensureDir(RELEASES_ROOT);
|
|
2669
2701
|
ensureDir(BACKUPS_ROOT);
|
|
2670
2702
|
ensureDir(ETC_ROOT);
|
|
2703
|
+
ensureDir(LICENSE_ROOT);
|
|
2671
2704
|
ensureDir(TRAEFIK_STATE_ROOT);
|
|
2672
2705
|
run("bash", ["-lc", "command -v apt-get >/dev/null"]);
|
|
2673
2706
|
run("sudo", ["apt-get", "update"]);
|
|
@@ -2700,7 +2733,12 @@ async function cmdSetup() {
|
|
|
2700
2733
|
const publicAuth = await ask("Expose auth.<domain> publicly (true/false)", "true") === "true";
|
|
2701
2734
|
const publicObs = await ask("Expose obs.<domain> publicly (true/false)", "true") === "true";
|
|
2702
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");
|
|
2703
2740
|
const config = {
|
|
2741
|
+
edition: "enterprise",
|
|
2704
2742
|
source: defaultSourceConfig(releaseVersion),
|
|
2705
2743
|
release: {
|
|
2706
2744
|
version: releaseVersion
|
|
@@ -2762,6 +2800,13 @@ async function cmdSetup() {
|
|
|
2762
2800
|
maintenance_mode_enabled: true,
|
|
2763
2801
|
db_snapshot_on_api_upgrade: true,
|
|
2764
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
|
|
2765
2810
|
}
|
|
2766
2811
|
};
|
|
2767
2812
|
saveDesiredConfig(config);
|
|
@@ -2777,6 +2822,7 @@ async function cmdBootstrap() {
|
|
|
2777
2822
|
const runtimeEnv = buildRuntimeEnv(config, nextGenerated);
|
|
2778
2823
|
logReleaseContext(config);
|
|
2779
2824
|
assertSwarmManager();
|
|
2825
|
+
await ensureEnterpriseCertificateBundle(config);
|
|
2780
2826
|
if (currentOptions.dryRun) {
|
|
2781
2827
|
logWarn("Dry-run mode: bootstrap reset will be previewed but not executed.");
|
|
2782
2828
|
}
|
|
@@ -3238,6 +3284,154 @@ function sha256File(filePath) {
|
|
|
3238
3284
|
hash.update(fs2.readFileSync(filePath));
|
|
3239
3285
|
return hash.digest("hex");
|
|
3240
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
|
+
}
|
|
3241
3435
|
function stackVolumeName(config, name) {
|
|
3242
3436
|
return `${config.services.stack_name}_${name}`;
|
|
3243
3437
|
}
|
|
@@ -3349,6 +3543,9 @@ async function cmdBackup() {
|
|
|
3349
3543
|
for (const volume of STACK_VOLUMES) {
|
|
3350
3544
|
backupDockerVolume(stackVolumeName(config, volume), path2.join(staged, "volumes", volume));
|
|
3351
3545
|
}
|
|
3546
|
+
if (!isOssConfig2(config)) {
|
|
3547
|
+
logDryRun(`Would include Enterprise license material from ${LICENSE_ROOT}`);
|
|
3548
|
+
}
|
|
3352
3549
|
logDryRun(`Would write manifest ${manifestPath}`);
|
|
3353
3550
|
logDryRun(`Would create archive ${tarPath}`);
|
|
3354
3551
|
logSuccess("Backup dry-run completed");
|
|
@@ -3376,6 +3573,9 @@ async function cmdBackup() {
|
|
|
3376
3573
|
writeFile(path2.join(staged, "keycloak-realm.envsync.json"), fs2.readFileSync(KEYCLOAK_REALM_FILE, "utf8"));
|
|
3377
3574
|
writeFile(path2.join(staged, "otel-agent.yaml"), fs2.readFileSync(OTEL_AGENT_CONF, "utf8"));
|
|
3378
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
|
+
}
|
|
3379
3579
|
const volumesDir = path2.join(staged, "volumes");
|
|
3380
3580
|
for (const volume of STACK_VOLUMES) {
|
|
3381
3581
|
backupDockerVolume(stackVolumeName(config, volume), path2.join(volumesDir, volume));
|
|
@@ -3452,6 +3652,11 @@ async function cmdRestore(archivePath, autoDeploy = false) {
|
|
|
3452
3652
|
writeFile(KEYCLOAK_REALM_FILE, fs2.readFileSync(path2.join(restoreRoot, "keycloak-realm.envsync.json"), "utf8"));
|
|
3453
3653
|
writeFile(OTEL_AGENT_CONF, fs2.readFileSync(path2.join(restoreRoot, "otel-agent.yaml"), "utf8"));
|
|
3454
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
|
+
}
|
|
3455
3660
|
const config = loadConfig();
|
|
3456
3661
|
for (const volume of STACK_VOLUMES) {
|
|
3457
3662
|
restoreDockerVolume(stackVolumeName(config, volume), path2.join(restoreRoot, "volumes", volume));
|
|
@@ -3513,6 +3718,9 @@ async function main() {
|
|
|
3513
3718
|
case "upgrade-deps":
|
|
3514
3719
|
await cmdUpgradeDeps();
|
|
3515
3720
|
break;
|
|
3721
|
+
case "license":
|
|
3722
|
+
await cmdLicense(positionals[0]);
|
|
3723
|
+
break;
|
|
3516
3724
|
case "backup":
|
|
3517
3725
|
await cmdBackup();
|
|
3518
3726
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@envsync-cloud/deploy-cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.7",
|
|
4
4
|
"description": "CLI for self-hosted EnvSync deployment on Docker Swarm",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"secrets"
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@envsync-cloud/deploy-core": "
|
|
44
|
+
"@envsync-cloud/deploy-core": "workspace:*",
|
|
45
45
|
"chalk": "^5.6.2",
|
|
46
46
|
"yaml": "^1.10.2"
|
|
47
47
|
},
|