@envsync-cloud/deploy 0.20.12 → 0.21.2
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/cli.js +118 -94
- package/dist/index.js +118 -94
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { createHash as
|
|
4
|
+
import { createHash as createHash3, generateKeyPairSync, randomBytes as randomBytes2, randomInt } from "crypto";
|
|
5
5
|
import { spawnSync } from "child_process";
|
|
6
|
-
import
|
|
6
|
+
import fs5 from "fs";
|
|
7
7
|
import path4 from "path";
|
|
8
8
|
import readline2 from "readline";
|
|
9
9
|
import chalk from "chalk";
|
|
@@ -37,13 +37,11 @@ var deployConfigSchema = z.object({
|
|
|
37
37
|
services: z.object({
|
|
38
38
|
stack_name: z.string().default("envsync"),
|
|
39
39
|
api_port: z.number().int().positive().default(4e3),
|
|
40
|
-
management_api_port: z.number().int().positive().default(4001),
|
|
41
40
|
public_http_port: z.number().int().positive().default(80),
|
|
42
41
|
public_https_port: z.number().int().positive().default(443)
|
|
43
42
|
}).default({}),
|
|
44
43
|
images: z.object({
|
|
45
44
|
api: z.string().default("ghcr.io/envsync-cloud/envsync-api:stable"),
|
|
46
|
-
management_api: z.string().default("ghcr.io/envsync-cloud/envsync-management-api:stable"),
|
|
47
45
|
web: z.string().default("ghcr.io/envsync-cloud/envsync-web-oss-static:stable"),
|
|
48
46
|
enterprise_web: z.string().default("ghcr.io/envsync-cloud/envsync-web-static:stable"),
|
|
49
47
|
landing: z.string().default("ghcr.io/envsync-cloud/envsync-landing-static:stable"),
|
|
@@ -286,6 +284,16 @@ function formatDeploymentPlan(plan, format = "yaml") {
|
|
|
286
284
|
}
|
|
287
285
|
|
|
288
286
|
// src/render.ts
|
|
287
|
+
import { createHash } from "crypto";
|
|
288
|
+
import fs2 from "fs";
|
|
289
|
+
function swarmConfigObjectName(stackName, logical, filePath, fallbackVersion) {
|
|
290
|
+
let token = fallbackVersion.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_|_$/g, "") || "v";
|
|
291
|
+
try {
|
|
292
|
+
token = createHash("sha256").update(fs2.readFileSync(filePath)).digest("hex").slice(0, 12);
|
|
293
|
+
} catch {
|
|
294
|
+
}
|
|
295
|
+
return `${stackName}_${logical}_${token}`;
|
|
296
|
+
}
|
|
289
297
|
function domainMap(rootDomain) {
|
|
290
298
|
return {
|
|
291
299
|
landing: rootDomain,
|
|
@@ -383,7 +391,6 @@ function buildRuntimeEnv2(config, generated, options) {
|
|
|
383
391
|
ENVSYNC_STACK_NAME: config.services.stack_name,
|
|
384
392
|
DB_AUTO_MIGRATE: "false",
|
|
385
393
|
PORT: `${config.services.api_port}`,
|
|
386
|
-
MANAGEMENT_API_PORT: `${config.services.management_api_port}`,
|
|
387
394
|
DATABASE_HOST: "postgres",
|
|
388
395
|
DATABASE_PORT: "5432",
|
|
389
396
|
DATABASE_USER: "postgres",
|
|
@@ -427,7 +434,13 @@ function buildRuntimeEnv2(config, generated, options) {
|
|
|
427
434
|
OPENFGA_MODEL_ID: generated.openfga.model_id,
|
|
428
435
|
OPENFGA_DB_PASSWORD: generated.secrets.openfga_db_password,
|
|
429
436
|
API_URL: publicHttpsUrl(config, hosts.api),
|
|
437
|
+
SAML_SESSION_SECRET: generated.secrets.saml_session_secret,
|
|
430
438
|
MANAGEMENT_API_URL: oss ? "" : publicHttpsUrl(config, hosts.api, "/api/v1/manage"),
|
|
439
|
+
KEYCLOAK_ACCESS_TOKEN_LIFESPAN_SECONDS: "3600",
|
|
440
|
+
KEYCLOAK_SSO_SESSION_IDLE_TIMEOUT_SECONDS: "604800",
|
|
441
|
+
KEYCLOAK_SSO_SESSION_MAX_LIFESPAN_SECONDS: "604800",
|
|
442
|
+
KEYCLOAK_CLIENT_SESSION_IDLE_TIMEOUT_SECONDS: "604800",
|
|
443
|
+
KEYCLOAK_CLIENT_SESSION_MAX_LIFESPAN_SECONDS: "604800",
|
|
431
444
|
CLICKSTACK_OPERATOR_EMAIL: generated.clickstack.operator_email,
|
|
432
445
|
CLICKSTACK_OPERATOR_PASSWORD: generated.clickstack.operator_password,
|
|
433
446
|
CLICKSTACK_ACCESS_KEY: generated.clickstack.access_key,
|
|
@@ -466,12 +479,22 @@ function renderKeycloakRealm(config, runtimeEnv) {
|
|
|
466
479
|
const webOrigins = publicHttpsOriginVariants(config, hosts.app);
|
|
467
480
|
const apiRedirectUris = publicHttpsUrlVariants(config, hosts.api, "/api/access/api/callback");
|
|
468
481
|
const apiOrigins = publicHttpsOriginVariants(config, hosts.api);
|
|
482
|
+
const accessTokenLifespan = Number(runtimeEnv.KEYCLOAK_ACCESS_TOKEN_LIFESPAN_SECONDS || "3600");
|
|
483
|
+
const ssoSessionIdleTimeout = Number(runtimeEnv.KEYCLOAK_SSO_SESSION_IDLE_TIMEOUT_SECONDS || "604800");
|
|
484
|
+
const ssoSessionMaxLifespan = Number(runtimeEnv.KEYCLOAK_SSO_SESSION_MAX_LIFESPAN_SECONDS || "604800");
|
|
485
|
+
const clientSessionIdleTimeout = Number(runtimeEnv.KEYCLOAK_CLIENT_SESSION_IDLE_TIMEOUT_SECONDS || "604800");
|
|
486
|
+
const clientSessionMaxLifespan = Number(runtimeEnv.KEYCLOAK_CLIENT_SESSION_MAX_LIFESPAN_SECONDS || "604800");
|
|
469
487
|
return JSON.stringify(
|
|
470
488
|
{
|
|
471
489
|
realm: config.auth.keycloak_realm,
|
|
472
490
|
enabled: true,
|
|
473
491
|
loginTheme: "envsync",
|
|
474
492
|
emailTheme: "envsync",
|
|
493
|
+
accessTokenLifespan,
|
|
494
|
+
ssoSessionIdleTimeout,
|
|
495
|
+
ssoSessionMaxLifespan,
|
|
496
|
+
clientSessionIdleTimeout,
|
|
497
|
+
clientSessionMaxLifespan,
|
|
475
498
|
clients: [
|
|
476
499
|
{
|
|
477
500
|
clientId: config.auth.web_client_id,
|
|
@@ -524,7 +547,7 @@ function renderTraefikDynamicConfig(config, generated) {
|
|
|
524
547
|
const landingEnabled = false;
|
|
525
548
|
const managementEnabled = !isOssConfig(config);
|
|
526
549
|
const otelAllowedOrigins = [
|
|
527
|
-
...
|
|
550
|
+
...publicHttpsOriginVariants(config, hosts.landing),
|
|
528
551
|
...publicHttpsOriginVariants(config, hosts.app)
|
|
529
552
|
];
|
|
530
553
|
return [
|
|
@@ -912,6 +935,7 @@ function renderStack(config, runtimeEnv, generated, mode, paths) {
|
|
|
912
935
|
const apiLicenseVolume = managementEnabled ? "\n volumes:\n - /etc/envsync/license:/etc/envsync/license:ro" : "";
|
|
913
936
|
const deployment = createSteadyApiDeploymentState(config, generated);
|
|
914
937
|
const stackName = config.services.stack_name;
|
|
938
|
+
const configName = (logical, filePath) => swarmConfigObjectName(stackName, logical, filePath, config.release.version);
|
|
915
939
|
const s3RouterName = `${stackName}-s3-router`;
|
|
916
940
|
const s3ServiceName = `${stackName}-s3-service`;
|
|
917
941
|
const s3ConsoleRouterName = `${stackName}-s3-console-router`;
|
|
@@ -1021,7 +1045,7 @@ ${includeRuntimeInfra ? `
|
|
|
1021
1045
|
image: ${config.images.keycloak}
|
|
1022
1046
|
entrypoint: ["/bin/sh", "-lc"]
|
|
1023
1047
|
command:
|
|
1024
|
-
- /opt/keycloak/bin/kc.sh import --dir /opt/keycloak/data/import --override
|
|
1048
|
+
- /opt/keycloak/bin/kc.sh import --dir /opt/keycloak/data/import --override false && exec /opt/keycloak/bin/kc.sh start --optimized
|
|
1025
1049
|
environment:
|
|
1026
1050
|
${renderEnvList({
|
|
1027
1051
|
KC_DB: "postgres",
|
|
@@ -1203,56 +1227,62 @@ volumes:
|
|
|
1203
1227
|
|
|
1204
1228
|
configs:
|
|
1205
1229
|
keycloak_realm:
|
|
1230
|
+
name: ${configName("keycloak_realm", paths.keycloakRealmFile)}
|
|
1206
1231
|
file: ${paths.keycloakRealmFile}
|
|
1207
1232
|
clickstack_clickhouse_conf:
|
|
1233
|
+
name: ${configName("clickstack_clickhouse_conf", paths.clickstackClickhouseConf)}
|
|
1208
1234
|
file: ${paths.clickstackClickhouseConf}
|
|
1209
1235
|
otel_agent_conf:
|
|
1236
|
+
name: ${configName("otel_agent_conf", paths.otelAgentConf)}
|
|
1210
1237
|
file: ${paths.otelAgentConf}
|
|
1211
1238
|
${landingEnabled ? ` nginx_landing_conf:
|
|
1239
|
+
name: ${configName("nginx_landing_conf", paths.nginxLandingConf)}
|
|
1212
1240
|
file: ${paths.nginxLandingConf}
|
|
1213
1241
|
` : ""}
|
|
1214
1242
|
nginx_web_conf:
|
|
1243
|
+
name: ${configName("nginx_web_conf", paths.nginxWebConf)}
|
|
1215
1244
|
file: ${paths.nginxWebConf}
|
|
1216
1245
|
nginx_api_maintenance_conf:
|
|
1246
|
+
name: ${configName("nginx_api_maintenance_conf", paths.nginxApiMaintenanceConf)}
|
|
1217
1247
|
file: ${paths.nginxApiMaintenanceConf}
|
|
1218
1248
|
`.trimStart();
|
|
1219
1249
|
}
|
|
1220
1250
|
|
|
1221
1251
|
// src/static-bundle.ts
|
|
1222
|
-
import
|
|
1252
|
+
import fs3 from "fs";
|
|
1223
1253
|
import path2 from "path";
|
|
1224
1254
|
function exists(target) {
|
|
1225
|
-
return
|
|
1255
|
+
return fs3.existsSync(target);
|
|
1226
1256
|
}
|
|
1227
1257
|
function normalizeExtractedStaticBundle(kind, targetDir) {
|
|
1228
1258
|
const directIndex = path2.join(targetDir, "index.html");
|
|
1229
1259
|
if (exists(directIndex)) {
|
|
1230
1260
|
return;
|
|
1231
1261
|
}
|
|
1232
|
-
const childDirs =
|
|
1262
|
+
const childDirs = fs3.readdirSync(targetDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path2.join(targetDir, entry.name));
|
|
1233
1263
|
const candidateRoots = childDirs.filter((dir) => exists(path2.join(dir, "index.html")));
|
|
1234
1264
|
if (candidateRoots.length !== 1) {
|
|
1235
1265
|
return;
|
|
1236
1266
|
}
|
|
1237
1267
|
const nestedRoot = candidateRoots[0];
|
|
1238
|
-
for (const entry of
|
|
1239
|
-
|
|
1268
|
+
for (const entry of fs3.readdirSync(nestedRoot)) {
|
|
1269
|
+
fs3.cpSync(path2.join(nestedRoot, entry), path2.join(targetDir, entry), { recursive: true });
|
|
1240
1270
|
}
|
|
1241
|
-
|
|
1271
|
+
fs3.rmSync(nestedRoot, { recursive: true, force: true });
|
|
1242
1272
|
}
|
|
1243
1273
|
function validateStaticBundle(kind, targetDir) {
|
|
1244
1274
|
if (exists(path2.join(targetDir, "index.html"))) {
|
|
1245
1275
|
return;
|
|
1246
1276
|
}
|
|
1247
|
-
const entries = exists(targetDir) ?
|
|
1277
|
+
const entries = exists(targetDir) ? fs3.readdirSync(targetDir).slice(0, 20).join(", ") : "";
|
|
1248
1278
|
throw new Error(
|
|
1249
1279
|
`Invalid ${kind} static bundle at ${targetDir}: missing index.html${entries ? `. Found: ${entries}` : ""}`
|
|
1250
1280
|
);
|
|
1251
1281
|
}
|
|
1252
1282
|
|
|
1253
1283
|
// src/org-setup.ts
|
|
1254
|
-
import { createHash, randomBytes } from "crypto";
|
|
1255
|
-
import
|
|
1284
|
+
import { createHash as createHash2, randomBytes } from "crypto";
|
|
1285
|
+
import fs4 from "fs";
|
|
1256
1286
|
import path3 from "path";
|
|
1257
1287
|
import readline from "readline";
|
|
1258
1288
|
var SETUP_TOKEN_HEADER = "X-EnvSync-Setup-Token";
|
|
@@ -1262,33 +1292,33 @@ function generateSetupToken() {
|
|
|
1262
1292
|
function ensureSetupTokenFile(tokenFile, existingToken) {
|
|
1263
1293
|
if (existingToken?.trim()) {
|
|
1264
1294
|
const dir2 = path3.dirname(tokenFile);
|
|
1265
|
-
|
|
1266
|
-
|
|
1295
|
+
fs4.mkdirSync(dir2, { recursive: true, mode: 448 });
|
|
1296
|
+
fs4.writeFileSync(tokenFile, `${existingToken.trim()}
|
|
1267
1297
|
`, { mode: 384 });
|
|
1268
1298
|
return existingToken.trim();
|
|
1269
1299
|
}
|
|
1270
|
-
if (
|
|
1271
|
-
const fromFile =
|
|
1300
|
+
if (fs4.existsSync(tokenFile)) {
|
|
1301
|
+
const fromFile = fs4.readFileSync(tokenFile, "utf8").trim();
|
|
1272
1302
|
if (fromFile.length >= 16) {
|
|
1273
1303
|
return fromFile;
|
|
1274
1304
|
}
|
|
1275
1305
|
}
|
|
1276
1306
|
const token = generateSetupToken();
|
|
1277
1307
|
const dir = path3.dirname(tokenFile);
|
|
1278
|
-
|
|
1279
|
-
|
|
1308
|
+
fs4.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
1309
|
+
fs4.writeFileSync(tokenFile, `${token}
|
|
1280
1310
|
`, { mode: 384 });
|
|
1281
1311
|
return token;
|
|
1282
1312
|
}
|
|
1283
1313
|
function readSetupTokenFile(tokenFile) {
|
|
1284
|
-
if (!
|
|
1314
|
+
if (!fs4.existsSync(tokenFile)) {
|
|
1285
1315
|
return null;
|
|
1286
1316
|
}
|
|
1287
|
-
const token =
|
|
1317
|
+
const token = fs4.readFileSync(tokenFile, "utf8").trim();
|
|
1288
1318
|
return token.length >= 16 ? token : null;
|
|
1289
1319
|
}
|
|
1290
1320
|
function setupTokenFingerprint(token) {
|
|
1291
|
-
return
|
|
1321
|
+
return createHash2("sha256").update(token).digest("hex").slice(0, 12);
|
|
1292
1322
|
}
|
|
1293
1323
|
async function fetchSetupStatus(apiBaseUrl, setupToken) {
|
|
1294
1324
|
const base = apiBaseUrl.replace(/\/$/, "");
|
|
@@ -1581,8 +1611,6 @@ var DEFAULT_ENTERPRISE_LICENSE_SERVER_URL = "https://license.envsync.cloud";
|
|
|
1581
1611
|
var MANAGED_VERSIONED_IMAGE_PREFIXES = {
|
|
1582
1612
|
api: "ghcr.io/envsync-cloud/envsync-api:",
|
|
1583
1613
|
api_enterprise: "ghcr.io/envsync-cloud/envsync-api-enterprise:",
|
|
1584
|
-
// Retired second process — kept so old deploy.yaml image strings still parse as "managed".
|
|
1585
|
-
management_api: "ghcr.io/envsync-cloud/envsync-management-api:",
|
|
1586
1614
|
keycloak: "envsync-keycloak:",
|
|
1587
1615
|
web: "ghcr.io/envsync-cloud/envsync-web-static:",
|
|
1588
1616
|
web_oss: "ghcr.io/envsync-cloud/envsync-web-oss-static:",
|
|
@@ -1777,12 +1805,12 @@ ${typeof result.stderr === "string" ? result.stderr : ""}`.toLowerCase();
|
|
|
1777
1805
|
${stderr}` : ""}`);
|
|
1778
1806
|
}
|
|
1779
1807
|
function ensureDir(dir) {
|
|
1780
|
-
|
|
1808
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
1781
1809
|
}
|
|
1782
1810
|
function writeFile(target, content, mode) {
|
|
1783
1811
|
ensureDir(path4.dirname(target));
|
|
1784
|
-
|
|
1785
|
-
if (mode != null)
|
|
1812
|
+
fs5.writeFileSync(target, content, "utf8");
|
|
1813
|
+
if (mode != null) fs5.chmodSync(target, mode);
|
|
1786
1814
|
}
|
|
1787
1815
|
function writeFileMaybe(target, content, mode) {
|
|
1788
1816
|
if (currentOptions.dryRun) {
|
|
@@ -1794,7 +1822,7 @@ function writeFileMaybe(target, content, mode) {
|
|
|
1794
1822
|
function ensureEnterpriseLicenseFilesReadable() {
|
|
1795
1823
|
if (exists2(LICENSE_ROOT)) {
|
|
1796
1824
|
try {
|
|
1797
|
-
|
|
1825
|
+
fs5.chmodSync(LICENSE_ROOT, LICENSE_DIR_MODE);
|
|
1798
1826
|
} catch {
|
|
1799
1827
|
throw new Error(
|
|
1800
1828
|
`Cannot update directory mode for ${LICENSE_ROOT}. Fix by running: chmod ${LICENSE_DIR_MODE.toString(8)} ${LICENSE_ROOT}`
|
|
@@ -1804,7 +1832,7 @@ function ensureEnterpriseLicenseFilesReadable() {
|
|
|
1804
1832
|
for (const filePath of [LICENSE_BUNDLE_FILE, LICENSE_CERT_FILE, LICENSE_KEY_FILE, LICENSE_ROOT_CA_FILE]) {
|
|
1805
1833
|
if (!exists2(filePath)) continue;
|
|
1806
1834
|
try {
|
|
1807
|
-
|
|
1835
|
+
fs5.chmodSync(filePath, LICENSE_FILE_MODE);
|
|
1808
1836
|
} catch {
|
|
1809
1837
|
throw new Error(
|
|
1810
1838
|
`Cannot update file mode for ${filePath}. Fix by running: chmod ${LICENSE_FILE_MODE.toString(8)} ${filePath}`
|
|
@@ -1813,7 +1841,7 @@ function ensureEnterpriseLicenseFilesReadable() {
|
|
|
1813
1841
|
}
|
|
1814
1842
|
}
|
|
1815
1843
|
function exists2(target) {
|
|
1816
|
-
return
|
|
1844
|
+
return fs5.existsSync(target);
|
|
1817
1845
|
}
|
|
1818
1846
|
function randomSecret(bytes = 24) {
|
|
1819
1847
|
return randomBytes2(bytes).toString("hex");
|
|
@@ -1823,7 +1851,7 @@ function generateMinikmsSessionSigningKey() {
|
|
|
1823
1851
|
return privateKey.export({ type: "pkcs8", format: "pem" }).toString();
|
|
1824
1852
|
}
|
|
1825
1853
|
function deterministicInstallFingerprint(rootDomain, stackName) {
|
|
1826
|
-
return `envsync-${
|
|
1854
|
+
return `envsync-${createHash3("sha256").update(`${rootDomain}:${stackName}`).digest("hex").slice(0, 32)}`;
|
|
1827
1855
|
}
|
|
1828
1856
|
function randomStrongPassword() {
|
|
1829
1857
|
return `EnvSync!${randomBytes2(8).toString("hex")}Aa1`;
|
|
@@ -1927,7 +1955,7 @@ function normalizeNetutilsForwards(raw, stackName, enabled) {
|
|
|
1927
1955
|
}
|
|
1928
1956
|
function loadWireguardState() {
|
|
1929
1957
|
if (!exists2(WIREGUARD_STATE_FILE)) return null;
|
|
1930
|
-
const raw = JSON.parse(
|
|
1958
|
+
const raw = JSON.parse(fs5.readFileSync(WIREGUARD_STATE_FILE, "utf8"));
|
|
1931
1959
|
return {
|
|
1932
1960
|
private_key: raw.private_key,
|
|
1933
1961
|
public_key: raw.public_key,
|
|
@@ -2111,7 +2139,7 @@ function publicHttpsUrl2(config, host, path5 = "") {
|
|
|
2111
2139
|
function getDeployCliVersion() {
|
|
2112
2140
|
try {
|
|
2113
2141
|
const packageJsonPath = new URL("../package.json", import.meta.url);
|
|
2114
|
-
const raw =
|
|
2142
|
+
const raw = fs5.readFileSync(packageJsonPath, "utf8");
|
|
2115
2143
|
return JSON.parse(raw).version ?? "0.0.0";
|
|
2116
2144
|
} catch {
|
|
2117
2145
|
return process.env.npm_package_version ?? "0.0.0";
|
|
@@ -2203,7 +2231,6 @@ function buildOperatorOverview() {
|
|
|
2203
2231
|
const bootstrapComplete = hasCompleteBootstrapState(generated) && generated.bootstrap.completed_at.length > 0;
|
|
2204
2232
|
const api = apiHealth(services, config.services.stack_name);
|
|
2205
2233
|
const web = serviceHealth(services, `${config.services.stack_name}_web_nginx`);
|
|
2206
|
-
const landing = serviceHealth(services, `${config.services.stack_name}_landing_nginx`);
|
|
2207
2234
|
statusLines.push(`Configured: ${chalk.green("yes")}`);
|
|
2208
2235
|
statusLines.push(`Pinned release: ${chalk.cyan(config.release.version)}`);
|
|
2209
2236
|
statusLines.push(`Stack: ${stackRunning ? chalk.green(config.services.stack_name) : chalk.yellow("not running")}`);
|
|
@@ -2211,7 +2238,7 @@ function buildOperatorOverview() {
|
|
|
2211
2238
|
statusLines.push(`Active API slot: ${chalk.cyan(generated.deployment.active_slot)}`);
|
|
2212
2239
|
statusLines.push(`API: ${api === "healthy" ? chalk.green(api) : api === "missing" ? chalk.red(api) : chalk.yellow(api)}`);
|
|
2213
2240
|
statusLines.push(`Web: ${web === "healthy" ? chalk.green(web) : web === "missing" ? chalk.red(web) : chalk.yellow(web)}`);
|
|
2214
|
-
statusLines.push(`Landing: ${
|
|
2241
|
+
statusLines.push(`Landing: ${chalk.dim("omitted")}`);
|
|
2215
2242
|
if (!bootstrapComplete) {
|
|
2216
2243
|
return {
|
|
2217
2244
|
statusLines,
|
|
@@ -2221,7 +2248,7 @@ function buildOperatorOverview() {
|
|
|
2221
2248
|
]
|
|
2222
2249
|
};
|
|
2223
2250
|
}
|
|
2224
|
-
if (api !== "healthy" || web !== "healthy"
|
|
2251
|
+
if (api !== "healthy" || web !== "healthy") {
|
|
2225
2252
|
return {
|
|
2226
2253
|
statusLines,
|
|
2227
2254
|
nextSteps: [
|
|
@@ -2290,8 +2317,6 @@ function versionedImages(version, edition = "enterprise") {
|
|
|
2290
2317
|
const api = edition === "oss" ? `ghcr.io/envsync-cloud/envsync-api:${version}` : `ghcr.io/envsync-cloud/envsync-api-enterprise:${version}`;
|
|
2291
2318
|
return {
|
|
2292
2319
|
api,
|
|
2293
|
-
// Deprecated field: kept for older deploy.yaml keys; unused by stack render.
|
|
2294
|
-
management_api: api,
|
|
2295
2320
|
keycloak: `envsync-keycloak:${version}`,
|
|
2296
2321
|
web,
|
|
2297
2322
|
landing: `ghcr.io/envsync-cloud/envsync-landing-static:${version}`
|
|
@@ -2377,7 +2402,6 @@ function normalizeConfig(raw) {
|
|
|
2377
2402
|
},
|
|
2378
2403
|
images: {
|
|
2379
2404
|
api: !raw.images?.api || isManagedVersionedImage(raw.images.api, "api") ? derivedImages.api : raw.images.api,
|
|
2380
|
-
management_api: !raw.images?.management_api || isManagedVersionedImage(raw.images.management_api, "management_api") ? derivedImages.management_api : raw.images.management_api,
|
|
2381
2405
|
keycloak: !raw.images?.keycloak || isManagedVersionedImage(raw.images.keycloak, "keycloak") ? derivedImages.keycloak : raw.images.keycloak,
|
|
2382
2406
|
web: !raw.images?.web || isManagedVersionedImage(raw.images.web, "web") ? derivedImages.web : raw.images.web,
|
|
2383
2407
|
landing: !raw.images?.landing || isManagedVersionedImage(raw.images.landing, "landing") ? derivedImages.landing : raw.images.landing,
|
|
@@ -2388,7 +2412,6 @@ function normalizeConfig(raw) {
|
|
|
2388
2412
|
services: {
|
|
2389
2413
|
stack_name: stackName,
|
|
2390
2414
|
api_port: requireDefined(raw.services?.api_port, "services.api_port"),
|
|
2391
|
-
management_api_port: raw.services?.management_api_port ?? 4001,
|
|
2392
2415
|
public_http_port: raw.services?.public_http_port ?? 80,
|
|
2393
2416
|
public_https_port: raw.services?.public_https_port ?? 443,
|
|
2394
2417
|
clickstack_ui_port: requireDefined(raw.services?.clickstack_ui_port, "services.clickstack_ui_port"),
|
|
@@ -2489,7 +2512,8 @@ function emptyGeneratedState() {
|
|
|
2489
2512
|
openfga_db_password: "",
|
|
2490
2513
|
minikms_root_key: "",
|
|
2491
2514
|
minikms_session_signing_key: "",
|
|
2492
|
-
minikms_db_password: ""
|
|
2515
|
+
minikms_db_password: "",
|
|
2516
|
+
saml_session_secret: ""
|
|
2493
2517
|
},
|
|
2494
2518
|
bootstrap: {
|
|
2495
2519
|
completed_at: ""
|
|
@@ -2526,7 +2550,8 @@ function normalizeGeneratedState(raw) {
|
|
|
2526
2550
|
openfga_db_password: raw?.secrets?.openfga_db_password ?? defaults.secrets.openfga_db_password,
|
|
2527
2551
|
minikms_root_key: raw?.secrets?.minikms_root_key ?? defaults.secrets.minikms_root_key,
|
|
2528
2552
|
minikms_session_signing_key: raw?.secrets?.minikms_session_signing_key ?? defaults.secrets.minikms_session_signing_key,
|
|
2529
|
-
minikms_db_password: raw?.secrets?.minikms_db_password ?? defaults.secrets.minikms_db_password
|
|
2553
|
+
minikms_db_password: raw?.secrets?.minikms_db_password ?? defaults.secrets.minikms_db_password,
|
|
2554
|
+
saml_session_secret: raw?.secrets?.saml_session_secret ?? defaults.secrets.saml_session_secret
|
|
2530
2555
|
},
|
|
2531
2556
|
bootstrap: {
|
|
2532
2557
|
completed_at: raw?.bootstrap?.completed_at ?? defaults.bootstrap.completed_at
|
|
@@ -2535,7 +2560,7 @@ function normalizeGeneratedState(raw) {
|
|
|
2535
2560
|
}
|
|
2536
2561
|
function readInternalState() {
|
|
2537
2562
|
if (!exists2(INTERNAL_CONFIG_JSON)) return null;
|
|
2538
|
-
const raw = JSON.parse(
|
|
2563
|
+
const raw = JSON.parse(fs5.readFileSync(INTERNAL_CONFIG_JSON, "utf8"));
|
|
2539
2564
|
return {
|
|
2540
2565
|
config: raw.config ? normalizeConfig(raw.config) : void 0,
|
|
2541
2566
|
generated: normalizeGeneratedState(raw.generated)
|
|
@@ -2545,7 +2570,7 @@ function loadConfig() {
|
|
|
2545
2570
|
if (!exists2(DEPLOY_YAML)) {
|
|
2546
2571
|
throw new Error(`Missing deploy config at ${DEPLOY_YAML}. Run setup first.`);
|
|
2547
2572
|
}
|
|
2548
|
-
const raw =
|
|
2573
|
+
const raw = fs5.readFileSync(DEPLOY_YAML, "utf8");
|
|
2549
2574
|
if (raw.trimStart().startsWith("{")) {
|
|
2550
2575
|
return normalizeConfig(JSON.parse(raw));
|
|
2551
2576
|
}
|
|
@@ -2553,7 +2578,7 @@ function loadConfig() {
|
|
|
2553
2578
|
}
|
|
2554
2579
|
function loadGeneratedEnv() {
|
|
2555
2580
|
if (!exists2(DEPLOY_ENV)) return {};
|
|
2556
|
-
return parseEnvFile(
|
|
2581
|
+
return parseEnvFile(fs5.readFileSync(DEPLOY_ENV, "utf8"));
|
|
2557
2582
|
}
|
|
2558
2583
|
function mergeGeneratedState(env, generated) {
|
|
2559
2584
|
const normalized = normalizeGeneratedState(generated);
|
|
@@ -2577,7 +2602,8 @@ function mergeGeneratedState(env, generated) {
|
|
|
2577
2602
|
openfga_db_password: env.OPENFGA_DB_PASSWORD ?? normalized.secrets.openfga_db_password,
|
|
2578
2603
|
minikms_root_key: env.MINIKMS_ROOT_KEY ?? normalized.secrets.minikms_root_key,
|
|
2579
2604
|
minikms_session_signing_key: env.MINIKMS_SESSION_SIGNING_KEY ?? normalized.secrets.minikms_session_signing_key,
|
|
2580
|
-
minikms_db_password: env.MINIKMS_DB_PASSWORD ?? normalized.secrets.minikms_db_password
|
|
2605
|
+
minikms_db_password: env.MINIKMS_DB_PASSWORD ?? normalized.secrets.minikms_db_password,
|
|
2606
|
+
saml_session_secret: env.SAML_SESSION_SECRET ?? normalized.secrets.saml_session_secret
|
|
2581
2607
|
},
|
|
2582
2608
|
bootstrap: normalized.bootstrap
|
|
2583
2609
|
});
|
|
@@ -2606,7 +2632,8 @@ function ensureGeneratedRuntimeState(config, generated) {
|
|
|
2606
2632
|
openfga_db_password: generated.secrets.openfga_db_password || randomSecret(),
|
|
2607
2633
|
minikms_root_key: generated.secrets.minikms_root_key || randomBytes2(32).toString("hex"),
|
|
2608
2634
|
minikms_session_signing_key: generated.secrets.minikms_session_signing_key || generateMinikmsSessionSigningKey(),
|
|
2609
|
-
minikms_db_password: generated.secrets.minikms_db_password || randomSecret()
|
|
2635
|
+
minikms_db_password: generated.secrets.minikms_db_password || randomSecret(),
|
|
2636
|
+
saml_session_secret: generated.secrets.saml_session_secret || randomBytes2(32).toString("hex")
|
|
2610
2637
|
},
|
|
2611
2638
|
bootstrap: generated.bootstrap
|
|
2612
2639
|
});
|
|
@@ -2839,7 +2866,7 @@ function extractStaticBundle(kind, image, targetDir) {
|
|
|
2839
2866
|
logDryRun(`Would extract ${kind} bundle from ${image} into ${targetDir}`);
|
|
2840
2867
|
return;
|
|
2841
2868
|
}
|
|
2842
|
-
|
|
2869
|
+
fs5.rmSync(targetDir, { recursive: true, force: true });
|
|
2843
2870
|
ensureDir(targetDir);
|
|
2844
2871
|
const containerId = run("docker", ["create", image], { quiet: true }).trim();
|
|
2845
2872
|
try {
|
|
@@ -2867,23 +2894,23 @@ function writeFrontendRuntimeConfig(targetDir, runtimeConfig) {
|
|
|
2867
2894
|
function syncFrontendReleaseContents(sourceDir, targetDir) {
|
|
2868
2895
|
ensureDir(targetDir);
|
|
2869
2896
|
const deferredEntries = /* @__PURE__ */ new Set(["index.html", "runtime-config.js"]);
|
|
2870
|
-
const entries =
|
|
2897
|
+
const entries = fs5.readdirSync(sourceDir, { withFileTypes: true });
|
|
2871
2898
|
for (const entry of entries) {
|
|
2872
2899
|
if (deferredEntries.has(entry.name)) {
|
|
2873
2900
|
continue;
|
|
2874
2901
|
}
|
|
2875
|
-
|
|
2902
|
+
fs5.cpSync(path4.join(sourceDir, entry.name), path4.join(targetDir, entry.name), {
|
|
2876
2903
|
recursive: true,
|
|
2877
2904
|
force: true
|
|
2878
2905
|
});
|
|
2879
2906
|
}
|
|
2880
2907
|
const stagedRuntimeConfig = path4.join(sourceDir, "runtime-config.js");
|
|
2881
2908
|
if (exists2(stagedRuntimeConfig)) {
|
|
2882
|
-
|
|
2909
|
+
fs5.cpSync(stagedRuntimeConfig, path4.join(targetDir, "runtime-config.js"), { force: true });
|
|
2883
2910
|
}
|
|
2884
2911
|
const stagedIndex = path4.join(sourceDir, "index.html");
|
|
2885
2912
|
if (exists2(stagedIndex)) {
|
|
2886
|
-
|
|
2913
|
+
fs5.cpSync(stagedIndex, path4.join(targetDir, "index.html"), { force: true });
|
|
2887
2914
|
}
|
|
2888
2915
|
}
|
|
2889
2916
|
function activateFrontendRelease(kind, version, runtimeConfig) {
|
|
@@ -3281,7 +3308,7 @@ ${trimmed}`
|
|
|
3281
3308
|
}
|
|
3282
3309
|
function readRenderedRuntimeConfig(filePath) {
|
|
3283
3310
|
if (!exists2(filePath)) return null;
|
|
3284
|
-
const raw =
|
|
3311
|
+
const raw = fs5.readFileSync(filePath, "utf8").trim();
|
|
3285
3312
|
const prefix = "window.__ENVSYNC_RUNTIME_CONFIG__ = ";
|
|
3286
3313
|
if (!raw.startsWith(prefix) || !raw.endsWith(";")) return null;
|
|
3287
3314
|
try {
|
|
@@ -3578,7 +3605,7 @@ function removeTarget(target) {
|
|
|
3578
3605
|
logDryRun(`Would remove ${target}`);
|
|
3579
3606
|
return;
|
|
3580
3607
|
}
|
|
3581
|
-
|
|
3608
|
+
fs5.rmSync(target, { recursive: true, force: true });
|
|
3582
3609
|
logInfo(`Removed ${target}`);
|
|
3583
3610
|
}
|
|
3584
3611
|
async function cmdRemove() {
|
|
@@ -3977,7 +4004,6 @@ async function cmdSetup() {
|
|
|
3977
4004
|
domain: { root_domain: rootDomain, acme_email: acmeEmail },
|
|
3978
4005
|
images: {
|
|
3979
4006
|
api: releaseImages.api,
|
|
3980
|
-
management_api: releaseImages.management_api,
|
|
3981
4007
|
keycloak: releaseImages.keycloak,
|
|
3982
4008
|
web: releaseImages.web,
|
|
3983
4009
|
landing: releaseImages.landing,
|
|
@@ -3988,7 +4014,6 @@ async function cmdSetup() {
|
|
|
3988
4014
|
services: {
|
|
3989
4015
|
stack_name: "envsync",
|
|
3990
4016
|
api_port: 4e3,
|
|
3991
|
-
management_api_port: 4001,
|
|
3992
4017
|
public_http_port: 80,
|
|
3993
4018
|
public_https_port: 443,
|
|
3994
4019
|
clickstack_ui_port: 8080,
|
|
@@ -4327,7 +4352,7 @@ async function cmdDeploy() {
|
|
|
4327
4352
|
sleepSeconds(3);
|
|
4328
4353
|
promoted = true;
|
|
4329
4354
|
if (snapshotPath && !currentOptions.dryRun) {
|
|
4330
|
-
|
|
4355
|
+
fs5.rmSync(snapshotPath, { force: true });
|
|
4331
4356
|
}
|
|
4332
4357
|
} catch (error) {
|
|
4333
4358
|
const rollbackTarget = preUpgradeHead ?? "zero";
|
|
@@ -4361,7 +4386,7 @@ ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
|
4361
4386
|
deployRenderedStack(config, "rollback recovery");
|
|
4362
4387
|
waitForApiSlotHealthy(config, recoveredState.deployment.active_slot);
|
|
4363
4388
|
if (snapshotPath && !config.upgrade.keep_failed_upgrade_db_snapshot && !currentOptions.dryRun) {
|
|
4364
|
-
|
|
4389
|
+
fs5.rmSync(snapshotPath, { force: true });
|
|
4365
4390
|
}
|
|
4366
4391
|
throw new Error(
|
|
4367
4392
|
usedSnapshotRestore ? `Deploy failed after DB migration. Previous release was restored using DB snapshot recovery. ${error instanceof Error ? error.message : String(error)}` : `Deploy failed after DB migration. Previous release was restored. ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -4387,7 +4412,6 @@ ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
|
4387
4412
|
{ label: "openfga", getHealth: (services) => serviceHealth(services, `${config.services.stack_name}_openfga`) },
|
|
4388
4413
|
{ label: "minikms", getHealth: (services) => serviceHealth(services, `${config.services.stack_name}_minikms`) },
|
|
4389
4414
|
{ label: "clickstack", getHealth: (services) => serviceHealth(services, `${config.services.stack_name}_clickstack`) },
|
|
4390
|
-
...isOssConfig2(config) ? [] : [{ label: "landing", getHealth: (services) => serviceHealth(services, `${config.services.stack_name}_landing_nginx`) }],
|
|
4391
4415
|
{ label: "web", getHealth: (services) => serviceHealth(services, `${config.services.stack_name}_web_nginx`) },
|
|
4392
4416
|
{ label: "api", getHealth: (services) => apiHealth(services, config.services.stack_name) }
|
|
4393
4417
|
]);
|
|
@@ -4430,7 +4454,7 @@ async function cmdPromote(target) {
|
|
|
4430
4454
|
}
|
|
4431
4455
|
});
|
|
4432
4456
|
writeDeployArtifacts(config, promotedState);
|
|
4433
|
-
if (exists2(releaseAssetDir("web", promotedState.deployment.slots[targetSlot].release_version))
|
|
4457
|
+
if (exists2(releaseAssetDir("web", promotedState.deployment.slots[targetSlot].release_version))) {
|
|
4434
4458
|
activateFrontendReleaseForState(config, promotedState);
|
|
4435
4459
|
} else {
|
|
4436
4460
|
logWarn(`Missing staged frontend assets for release ${promotedState.deployment.slots[targetSlot].release_version}; leaving current frontend assets unchanged.`);
|
|
@@ -4461,7 +4485,7 @@ async function cmdRollback() {
|
|
|
4461
4485
|
}
|
|
4462
4486
|
});
|
|
4463
4487
|
writeDeployArtifacts(config, rollbackState);
|
|
4464
|
-
if (exists2(releaseAssetDir("web", rollbackState.deployment.slots[rollbackState.deployment.active_slot].release_version))
|
|
4488
|
+
if (exists2(releaseAssetDir("web", rollbackState.deployment.slots[rollbackState.deployment.active_slot].release_version))) {
|
|
4465
4489
|
activateFrontendReleaseForState(config, rollbackState);
|
|
4466
4490
|
} else {
|
|
4467
4491
|
logWarn(`Missing staged frontend assets for release ${rollbackState.deployment.slots[rollbackState.deployment.active_slot].release_version}; leaving current frontend assets unchanged.`);
|
|
@@ -4483,7 +4507,7 @@ async function cmdHealth(asJson) {
|
|
|
4483
4507
|
const hosts = domainMap2(config.domain.root_domain);
|
|
4484
4508
|
const services = listStackServices(config);
|
|
4485
4509
|
const stackName = config.services.stack_name;
|
|
4486
|
-
const traefikDynamic = exists2(TRAEFIK_DYNAMIC_FILE) ?
|
|
4510
|
+
const traefikDynamic = exists2(TRAEFIK_DYNAMIC_FILE) ? fs5.readFileSync(TRAEFIK_DYNAMIC_FILE, "utf8") : "";
|
|
4487
4511
|
const webRuntimeConfig = readRenderedRuntimeConfig(path4.join(RELEASES_ROOT, "web", "current", "runtime-config.js"));
|
|
4488
4512
|
const landingRuntimeConfig = readRenderedRuntimeConfig(path4.join(RELEASES_ROOT, "landing", "current", "runtime-config.js"));
|
|
4489
4513
|
const clickstackSearchState = getClickstackSearchState(config);
|
|
@@ -4557,7 +4581,7 @@ async function cmdHealth(asJson) {
|
|
|
4557
4581
|
}
|
|
4558
4582
|
},
|
|
4559
4583
|
web: serviceHealth(services, `${stackName}_web_nginx`),
|
|
4560
|
-
landing:
|
|
4584
|
+
landing: "omitted"
|
|
4561
4585
|
},
|
|
4562
4586
|
database: {
|
|
4563
4587
|
api: databaseHealth
|
|
@@ -4675,8 +4699,8 @@ async function cmdUpgradeDeps() {
|
|
|
4675
4699
|
await cmdDeploy();
|
|
4676
4700
|
}
|
|
4677
4701
|
function sha256File(filePath) {
|
|
4678
|
-
const hash =
|
|
4679
|
-
hash.update(
|
|
4702
|
+
const hash = createHash3("sha256");
|
|
4703
|
+
hash.update(fs5.readFileSync(filePath));
|
|
4680
4704
|
return hash.digest("hex");
|
|
4681
4705
|
}
|
|
4682
4706
|
function parseEnterpriseLicenseBundle(raw) {
|
|
@@ -4705,7 +4729,7 @@ function parseEnterpriseLicenseBundle(raw) {
|
|
|
4705
4729
|
return candidate;
|
|
4706
4730
|
}
|
|
4707
4731
|
function readEnterpriseLicenseBundle(bundlePath) {
|
|
4708
|
-
return parseEnterpriseLicenseBundle(JSON.parse(
|
|
4732
|
+
return parseEnterpriseLicenseBundle(JSON.parse(fs5.readFileSync(bundlePath, "utf8")));
|
|
4709
4733
|
}
|
|
4710
4734
|
function normalizeLicenseServerUrl(serverUrl, route) {
|
|
4711
4735
|
const trimmed = serverUrl.replace(/\/+$/, "");
|
|
@@ -4967,19 +4991,19 @@ async function cmdBackup() {
|
|
|
4967
4991
|
logWarn("Backup will temporarily stop the EnvSync stack to capture consistent volume data.");
|
|
4968
4992
|
stopManagedRuntime(config, "Stopping existing EnvSync services for consistent backup");
|
|
4969
4993
|
}
|
|
4970
|
-
writeFile(path4.join(staged, "deploy.env"),
|
|
4971
|
-
writeFile(path4.join(staged, "deploy.yaml"),
|
|
4972
|
-
writeFile(path4.join(staged, "config.json"),
|
|
4973
|
-
writeFile(path4.join(staged, "versions.lock.json"),
|
|
4974
|
-
writeFile(path4.join(staged, "docker-stack.bootstrap.base.yaml"),
|
|
4975
|
-
writeFile(path4.join(staged, "docker-stack.bootstrap.yaml"),
|
|
4976
|
-
writeFile(path4.join(staged, "docker-stack.yaml"),
|
|
4977
|
-
writeFile(path4.join(staged, "traefik-dynamic.yaml"),
|
|
4978
|
-
writeFile(path4.join(staged, "keycloak-realm.envsync.json"),
|
|
4979
|
-
writeFile(path4.join(staged, "otel-agent.yaml"),
|
|
4980
|
-
writeFile(path4.join(staged, "clickhouse-listen.xml"),
|
|
4994
|
+
writeFile(path4.join(staged, "deploy.env"), fs5.readFileSync(DEPLOY_ENV, "utf8"));
|
|
4995
|
+
writeFile(path4.join(staged, "deploy.yaml"), fs5.readFileSync(DEPLOY_YAML, "utf8"));
|
|
4996
|
+
writeFile(path4.join(staged, "config.json"), fs5.readFileSync(INTERNAL_CONFIG_JSON, "utf8"));
|
|
4997
|
+
writeFile(path4.join(staged, "versions.lock.json"), fs5.readFileSync(VERSIONS_LOCK, "utf8"));
|
|
4998
|
+
writeFile(path4.join(staged, "docker-stack.bootstrap.base.yaml"), fs5.readFileSync(BOOTSTRAP_BASE_STACK_FILE, "utf8"));
|
|
4999
|
+
writeFile(path4.join(staged, "docker-stack.bootstrap.yaml"), fs5.readFileSync(BOOTSTRAP_STACK_FILE, "utf8"));
|
|
5000
|
+
writeFile(path4.join(staged, "docker-stack.yaml"), fs5.readFileSync(STACK_FILE, "utf8"));
|
|
5001
|
+
writeFile(path4.join(staged, "traefik-dynamic.yaml"), fs5.readFileSync(TRAEFIK_DYNAMIC_FILE, "utf8"));
|
|
5002
|
+
writeFile(path4.join(staged, "keycloak-realm.envsync.json"), fs5.readFileSync(KEYCLOAK_REALM_FILE, "utf8"));
|
|
5003
|
+
writeFile(path4.join(staged, "otel-agent.yaml"), fs5.readFileSync(OTEL_AGENT_CONF, "utf8"));
|
|
5004
|
+
writeFile(path4.join(staged, "clickhouse-listen.xml"), fs5.readFileSync(CLICKSTACK_CLICKHOUSE_CONF, "utf8"));
|
|
4981
5005
|
if (!isOssConfig2(config) && exists2(LICENSE_ROOT)) {
|
|
4982
|
-
|
|
5006
|
+
fs5.cpSync(LICENSE_ROOT, path4.join(staged, "license"), { recursive: true });
|
|
4983
5007
|
}
|
|
4984
5008
|
const volumesDir = path4.join(staged, "volumes");
|
|
4985
5009
|
for (const volume of STACK_VOLUMES) {
|
|
@@ -5046,21 +5070,21 @@ async function cmdRestore(archivePath, autoDeploy = false) {
|
|
|
5046
5070
|
}
|
|
5047
5071
|
ensureDir(restoreRoot);
|
|
5048
5072
|
run("bash", ["-lc", `tar -xzf ${JSON.stringify(archivePath)} -C ${JSON.stringify(restoreRoot)}`]);
|
|
5049
|
-
writeFile(DEPLOY_ENV,
|
|
5050
|
-
writeFile(DEPLOY_YAML,
|
|
5051
|
-
writeFile(INTERNAL_CONFIG_JSON,
|
|
5052
|
-
writeFile(VERSIONS_LOCK,
|
|
5053
|
-
writeFile(BOOTSTRAP_BASE_STACK_FILE,
|
|
5054
|
-
writeFile(BOOTSTRAP_STACK_FILE,
|
|
5055
|
-
writeFile(STACK_FILE,
|
|
5056
|
-
writeFile(TRAEFIK_DYNAMIC_FILE,
|
|
5057
|
-
writeFile(KEYCLOAK_REALM_FILE,
|
|
5058
|
-
writeFile(OTEL_AGENT_CONF,
|
|
5059
|
-
writeFile(CLICKSTACK_CLICKHOUSE_CONF,
|
|
5073
|
+
writeFile(DEPLOY_ENV, fs5.readFileSync(path4.join(restoreRoot, "deploy.env"), "utf8"), 384);
|
|
5074
|
+
writeFile(DEPLOY_YAML, fs5.readFileSync(path4.join(restoreRoot, "deploy.yaml"), "utf8"));
|
|
5075
|
+
writeFile(INTERNAL_CONFIG_JSON, fs5.readFileSync(path4.join(restoreRoot, "config.json"), "utf8"));
|
|
5076
|
+
writeFile(VERSIONS_LOCK, fs5.readFileSync(path4.join(restoreRoot, "versions.lock.json"), "utf8"));
|
|
5077
|
+
writeFile(BOOTSTRAP_BASE_STACK_FILE, fs5.readFileSync(path4.join(restoreRoot, "docker-stack.bootstrap.base.yaml"), "utf8"));
|
|
5078
|
+
writeFile(BOOTSTRAP_STACK_FILE, fs5.readFileSync(path4.join(restoreRoot, "docker-stack.bootstrap.yaml"), "utf8"));
|
|
5079
|
+
writeFile(STACK_FILE, fs5.readFileSync(path4.join(restoreRoot, "docker-stack.yaml"), "utf8"));
|
|
5080
|
+
writeFile(TRAEFIK_DYNAMIC_FILE, fs5.readFileSync(path4.join(restoreRoot, "traefik-dynamic.yaml"), "utf8"));
|
|
5081
|
+
writeFile(KEYCLOAK_REALM_FILE, fs5.readFileSync(path4.join(restoreRoot, "keycloak-realm.envsync.json"), "utf8"));
|
|
5082
|
+
writeFile(OTEL_AGENT_CONF, fs5.readFileSync(path4.join(restoreRoot, "otel-agent.yaml"), "utf8"));
|
|
5083
|
+
writeFile(CLICKSTACK_CLICKHOUSE_CONF, fs5.readFileSync(path4.join(restoreRoot, "clickhouse-listen.xml"), "utf8"));
|
|
5060
5084
|
const restoredLicenseRoot = path4.join(restoreRoot, "license");
|
|
5061
5085
|
if (exists2(restoredLicenseRoot)) {
|
|
5062
|
-
|
|
5063
|
-
|
|
5086
|
+
fs5.rmSync(LICENSE_ROOT, { recursive: true, force: true });
|
|
5087
|
+
fs5.cpSync(restoredLicenseRoot, LICENSE_ROOT, { recursive: true });
|
|
5064
5088
|
ensureEnterpriseLicenseFilesReadable();
|
|
5065
5089
|
}
|
|
5066
5090
|
const config = loadConfig();
|
package/dist/index.js
CHANGED
|
@@ -243,13 +243,11 @@ var init_src = __esm({
|
|
|
243
243
|
services: z.object({
|
|
244
244
|
stack_name: z.string().default("envsync"),
|
|
245
245
|
api_port: z.number().int().positive().default(4e3),
|
|
246
|
-
management_api_port: z.number().int().positive().default(4001),
|
|
247
246
|
public_http_port: z.number().int().positive().default(80),
|
|
248
247
|
public_https_port: z.number().int().positive().default(443)
|
|
249
248
|
}).default({}),
|
|
250
249
|
images: z.object({
|
|
251
250
|
api: z.string().default("ghcr.io/envsync-cloud/envsync-api:stable"),
|
|
252
|
-
management_api: z.string().default("ghcr.io/envsync-cloud/envsync-management-api:stable"),
|
|
253
251
|
web: z.string().default("ghcr.io/envsync-cloud/envsync-web-oss-static:stable"),
|
|
254
252
|
enterprise_web: z.string().default("ghcr.io/envsync-cloud/envsync-web-static:stable"),
|
|
255
253
|
landing: z.string().default("ghcr.io/envsync-cloud/envsync-landing-static:stable"),
|
|
@@ -287,6 +285,16 @@ var init_src = __esm({
|
|
|
287
285
|
});
|
|
288
286
|
|
|
289
287
|
// src/render.ts
|
|
288
|
+
import { createHash } from "crypto";
|
|
289
|
+
import fs2 from "fs";
|
|
290
|
+
function swarmConfigObjectName(stackName, logical, filePath, fallbackVersion) {
|
|
291
|
+
let token = fallbackVersion.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_|_$/g, "") || "v";
|
|
292
|
+
try {
|
|
293
|
+
token = createHash("sha256").update(fs2.readFileSync(filePath)).digest("hex").slice(0, 12);
|
|
294
|
+
} catch {
|
|
295
|
+
}
|
|
296
|
+
return `${stackName}_${logical}_${token}`;
|
|
297
|
+
}
|
|
290
298
|
function domainMap(rootDomain) {
|
|
291
299
|
return {
|
|
292
300
|
landing: rootDomain,
|
|
@@ -384,7 +392,6 @@ function buildRuntimeEnv2(config, generated, options) {
|
|
|
384
392
|
ENVSYNC_STACK_NAME: config.services.stack_name,
|
|
385
393
|
DB_AUTO_MIGRATE: "false",
|
|
386
394
|
PORT: `${config.services.api_port}`,
|
|
387
|
-
MANAGEMENT_API_PORT: `${config.services.management_api_port}`,
|
|
388
395
|
DATABASE_HOST: "postgres",
|
|
389
396
|
DATABASE_PORT: "5432",
|
|
390
397
|
DATABASE_USER: "postgres",
|
|
@@ -428,7 +435,13 @@ function buildRuntimeEnv2(config, generated, options) {
|
|
|
428
435
|
OPENFGA_MODEL_ID: generated.openfga.model_id,
|
|
429
436
|
OPENFGA_DB_PASSWORD: generated.secrets.openfga_db_password,
|
|
430
437
|
API_URL: publicHttpsUrl(config, hosts.api),
|
|
438
|
+
SAML_SESSION_SECRET: generated.secrets.saml_session_secret,
|
|
431
439
|
MANAGEMENT_API_URL: oss ? "" : publicHttpsUrl(config, hosts.api, "/api/v1/manage"),
|
|
440
|
+
KEYCLOAK_ACCESS_TOKEN_LIFESPAN_SECONDS: "3600",
|
|
441
|
+
KEYCLOAK_SSO_SESSION_IDLE_TIMEOUT_SECONDS: "604800",
|
|
442
|
+
KEYCLOAK_SSO_SESSION_MAX_LIFESPAN_SECONDS: "604800",
|
|
443
|
+
KEYCLOAK_CLIENT_SESSION_IDLE_TIMEOUT_SECONDS: "604800",
|
|
444
|
+
KEYCLOAK_CLIENT_SESSION_MAX_LIFESPAN_SECONDS: "604800",
|
|
432
445
|
CLICKSTACK_OPERATOR_EMAIL: generated.clickstack.operator_email,
|
|
433
446
|
CLICKSTACK_OPERATOR_PASSWORD: generated.clickstack.operator_password,
|
|
434
447
|
CLICKSTACK_ACCESS_KEY: generated.clickstack.access_key,
|
|
@@ -467,12 +480,22 @@ function renderKeycloakRealm(config, runtimeEnv) {
|
|
|
467
480
|
const webOrigins = publicHttpsOriginVariants(config, hosts.app);
|
|
468
481
|
const apiRedirectUris = publicHttpsUrlVariants(config, hosts.api, "/api/access/api/callback");
|
|
469
482
|
const apiOrigins = publicHttpsOriginVariants(config, hosts.api);
|
|
483
|
+
const accessTokenLifespan = Number(runtimeEnv.KEYCLOAK_ACCESS_TOKEN_LIFESPAN_SECONDS || "3600");
|
|
484
|
+
const ssoSessionIdleTimeout = Number(runtimeEnv.KEYCLOAK_SSO_SESSION_IDLE_TIMEOUT_SECONDS || "604800");
|
|
485
|
+
const ssoSessionMaxLifespan = Number(runtimeEnv.KEYCLOAK_SSO_SESSION_MAX_LIFESPAN_SECONDS || "604800");
|
|
486
|
+
const clientSessionIdleTimeout = Number(runtimeEnv.KEYCLOAK_CLIENT_SESSION_IDLE_TIMEOUT_SECONDS || "604800");
|
|
487
|
+
const clientSessionMaxLifespan = Number(runtimeEnv.KEYCLOAK_CLIENT_SESSION_MAX_LIFESPAN_SECONDS || "604800");
|
|
470
488
|
return JSON.stringify(
|
|
471
489
|
{
|
|
472
490
|
realm: config.auth.keycloak_realm,
|
|
473
491
|
enabled: true,
|
|
474
492
|
loginTheme: "envsync",
|
|
475
493
|
emailTheme: "envsync",
|
|
494
|
+
accessTokenLifespan,
|
|
495
|
+
ssoSessionIdleTimeout,
|
|
496
|
+
ssoSessionMaxLifespan,
|
|
497
|
+
clientSessionIdleTimeout,
|
|
498
|
+
clientSessionMaxLifespan,
|
|
476
499
|
clients: [
|
|
477
500
|
{
|
|
478
501
|
clientId: config.auth.web_client_id,
|
|
@@ -525,7 +548,7 @@ function renderTraefikDynamicConfig(config, generated) {
|
|
|
525
548
|
const landingEnabled = false;
|
|
526
549
|
const managementEnabled = !isOssConfig(config);
|
|
527
550
|
const otelAllowedOrigins = [
|
|
528
|
-
...
|
|
551
|
+
...publicHttpsOriginVariants(config, hosts.landing),
|
|
529
552
|
...publicHttpsOriginVariants(config, hosts.app)
|
|
530
553
|
];
|
|
531
554
|
return [
|
|
@@ -909,6 +932,7 @@ function renderStack(config, runtimeEnv, generated, mode, paths) {
|
|
|
909
932
|
const apiLicenseVolume = managementEnabled ? "\n volumes:\n - /etc/envsync/license:/etc/envsync/license:ro" : "";
|
|
910
933
|
const deployment = createSteadyApiDeploymentState(config, generated);
|
|
911
934
|
const stackName = config.services.stack_name;
|
|
935
|
+
const configName = (logical, filePath) => swarmConfigObjectName(stackName, logical, filePath, config.release.version);
|
|
912
936
|
const s3RouterName = `${stackName}-s3-router`;
|
|
913
937
|
const s3ServiceName = `${stackName}-s3-service`;
|
|
914
938
|
const s3ConsoleRouterName = `${stackName}-s3-console-router`;
|
|
@@ -1018,7 +1042,7 @@ ${includeRuntimeInfra ? `
|
|
|
1018
1042
|
image: ${config.images.keycloak}
|
|
1019
1043
|
entrypoint: ["/bin/sh", "-lc"]
|
|
1020
1044
|
command:
|
|
1021
|
-
- /opt/keycloak/bin/kc.sh import --dir /opt/keycloak/data/import --override
|
|
1045
|
+
- /opt/keycloak/bin/kc.sh import --dir /opt/keycloak/data/import --override false && exec /opt/keycloak/bin/kc.sh start --optimized
|
|
1022
1046
|
environment:
|
|
1023
1047
|
${renderEnvList({
|
|
1024
1048
|
KC_DB: "postgres",
|
|
@@ -1200,17 +1224,23 @@ volumes:
|
|
|
1200
1224
|
|
|
1201
1225
|
configs:
|
|
1202
1226
|
keycloak_realm:
|
|
1227
|
+
name: ${configName("keycloak_realm", paths.keycloakRealmFile)}
|
|
1203
1228
|
file: ${paths.keycloakRealmFile}
|
|
1204
1229
|
clickstack_clickhouse_conf:
|
|
1230
|
+
name: ${configName("clickstack_clickhouse_conf", paths.clickstackClickhouseConf)}
|
|
1205
1231
|
file: ${paths.clickstackClickhouseConf}
|
|
1206
1232
|
otel_agent_conf:
|
|
1233
|
+
name: ${configName("otel_agent_conf", paths.otelAgentConf)}
|
|
1207
1234
|
file: ${paths.otelAgentConf}
|
|
1208
1235
|
${landingEnabled ? ` nginx_landing_conf:
|
|
1236
|
+
name: ${configName("nginx_landing_conf", paths.nginxLandingConf)}
|
|
1209
1237
|
file: ${paths.nginxLandingConf}
|
|
1210
1238
|
` : ""}
|
|
1211
1239
|
nginx_web_conf:
|
|
1240
|
+
name: ${configName("nginx_web_conf", paths.nginxWebConf)}
|
|
1212
1241
|
file: ${paths.nginxWebConf}
|
|
1213
1242
|
nginx_api_maintenance_conf:
|
|
1243
|
+
name: ${configName("nginx_api_maintenance_conf", paths.nginxApiMaintenanceConf)}
|
|
1214
1244
|
file: ${paths.nginxApiMaintenanceConf}
|
|
1215
1245
|
`.trimStart();
|
|
1216
1246
|
}
|
|
@@ -1226,32 +1256,32 @@ var init_render = __esm({
|
|
|
1226
1256
|
});
|
|
1227
1257
|
|
|
1228
1258
|
// src/static-bundle.ts
|
|
1229
|
-
import
|
|
1259
|
+
import fs3 from "fs";
|
|
1230
1260
|
import path2 from "path";
|
|
1231
1261
|
function exists(target) {
|
|
1232
|
-
return
|
|
1262
|
+
return fs3.existsSync(target);
|
|
1233
1263
|
}
|
|
1234
1264
|
function normalizeExtractedStaticBundle(kind, targetDir) {
|
|
1235
1265
|
const directIndex = path2.join(targetDir, "index.html");
|
|
1236
1266
|
if (exists(directIndex)) {
|
|
1237
1267
|
return;
|
|
1238
1268
|
}
|
|
1239
|
-
const childDirs =
|
|
1269
|
+
const childDirs = fs3.readdirSync(targetDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path2.join(targetDir, entry.name));
|
|
1240
1270
|
const candidateRoots = childDirs.filter((dir) => exists(path2.join(dir, "index.html")));
|
|
1241
1271
|
if (candidateRoots.length !== 1) {
|
|
1242
1272
|
return;
|
|
1243
1273
|
}
|
|
1244
1274
|
const nestedRoot = candidateRoots[0];
|
|
1245
|
-
for (const entry of
|
|
1246
|
-
|
|
1275
|
+
for (const entry of fs3.readdirSync(nestedRoot)) {
|
|
1276
|
+
fs3.cpSync(path2.join(nestedRoot, entry), path2.join(targetDir, entry), { recursive: true });
|
|
1247
1277
|
}
|
|
1248
|
-
|
|
1278
|
+
fs3.rmSync(nestedRoot, { recursive: true, force: true });
|
|
1249
1279
|
}
|
|
1250
1280
|
function validateStaticBundle(kind, targetDir) {
|
|
1251
1281
|
if (exists(path2.join(targetDir, "index.html"))) {
|
|
1252
1282
|
return;
|
|
1253
1283
|
}
|
|
1254
|
-
const entries = exists(targetDir) ?
|
|
1284
|
+
const entries = exists(targetDir) ? fs3.readdirSync(targetDir).slice(0, 20).join(", ") : "";
|
|
1255
1285
|
throw new Error(
|
|
1256
1286
|
`Invalid ${kind} static bundle at ${targetDir}: missing index.html${entries ? `. Found: ${entries}` : ""}`
|
|
1257
1287
|
);
|
|
@@ -1263,8 +1293,8 @@ var init_static_bundle = __esm({
|
|
|
1263
1293
|
});
|
|
1264
1294
|
|
|
1265
1295
|
// src/org-setup.ts
|
|
1266
|
-
import { createHash, randomBytes } from "crypto";
|
|
1267
|
-
import
|
|
1296
|
+
import { createHash as createHash2, randomBytes } from "crypto";
|
|
1297
|
+
import fs4 from "fs";
|
|
1268
1298
|
import path3 from "path";
|
|
1269
1299
|
import readline from "readline";
|
|
1270
1300
|
function generateSetupToken() {
|
|
@@ -1273,33 +1303,33 @@ function generateSetupToken() {
|
|
|
1273
1303
|
function ensureSetupTokenFile(tokenFile, existingToken) {
|
|
1274
1304
|
if (existingToken?.trim()) {
|
|
1275
1305
|
const dir2 = path3.dirname(tokenFile);
|
|
1276
|
-
|
|
1277
|
-
|
|
1306
|
+
fs4.mkdirSync(dir2, { recursive: true, mode: 448 });
|
|
1307
|
+
fs4.writeFileSync(tokenFile, `${existingToken.trim()}
|
|
1278
1308
|
`, { mode: 384 });
|
|
1279
1309
|
return existingToken.trim();
|
|
1280
1310
|
}
|
|
1281
|
-
if (
|
|
1282
|
-
const fromFile =
|
|
1311
|
+
if (fs4.existsSync(tokenFile)) {
|
|
1312
|
+
const fromFile = fs4.readFileSync(tokenFile, "utf8").trim();
|
|
1283
1313
|
if (fromFile.length >= 16) {
|
|
1284
1314
|
return fromFile;
|
|
1285
1315
|
}
|
|
1286
1316
|
}
|
|
1287
1317
|
const token = generateSetupToken();
|
|
1288
1318
|
const dir = path3.dirname(tokenFile);
|
|
1289
|
-
|
|
1290
|
-
|
|
1319
|
+
fs4.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
1320
|
+
fs4.writeFileSync(tokenFile, `${token}
|
|
1291
1321
|
`, { mode: 384 });
|
|
1292
1322
|
return token;
|
|
1293
1323
|
}
|
|
1294
1324
|
function readSetupTokenFile(tokenFile) {
|
|
1295
|
-
if (!
|
|
1325
|
+
if (!fs4.existsSync(tokenFile)) {
|
|
1296
1326
|
return null;
|
|
1297
1327
|
}
|
|
1298
|
-
const token =
|
|
1328
|
+
const token = fs4.readFileSync(tokenFile, "utf8").trim();
|
|
1299
1329
|
return token.length >= 16 ? token : null;
|
|
1300
1330
|
}
|
|
1301
1331
|
function setupTokenFingerprint(token) {
|
|
1302
|
-
return
|
|
1332
|
+
return createHash2("sha256").update(token).digest("hex").slice(0, 12);
|
|
1303
1333
|
}
|
|
1304
1334
|
async function fetchSetupStatus(apiBaseUrl, setupToken) {
|
|
1305
1335
|
const base = apiBaseUrl.replace(/\/$/, "");
|
|
@@ -1442,9 +1472,9 @@ var init_org_setup = __esm({
|
|
|
1442
1472
|
|
|
1443
1473
|
// src/cli.ts
|
|
1444
1474
|
var cli_exports = {};
|
|
1445
|
-
import { createHash as
|
|
1475
|
+
import { createHash as createHash3, generateKeyPairSync, randomBytes as randomBytes2, randomInt } from "crypto";
|
|
1446
1476
|
import { spawnSync } from "child_process";
|
|
1447
|
-
import
|
|
1477
|
+
import fs5 from "fs";
|
|
1448
1478
|
import path4 from "path";
|
|
1449
1479
|
import readline2 from "readline";
|
|
1450
1480
|
import chalk from "chalk";
|
|
@@ -1638,12 +1668,12 @@ ${typeof result.stderr === "string" ? result.stderr : ""}`.toLowerCase();
|
|
|
1638
1668
|
${stderr}` : ""}`);
|
|
1639
1669
|
}
|
|
1640
1670
|
function ensureDir(dir) {
|
|
1641
|
-
|
|
1671
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
1642
1672
|
}
|
|
1643
1673
|
function writeFile(target, content, mode) {
|
|
1644
1674
|
ensureDir(path4.dirname(target));
|
|
1645
|
-
|
|
1646
|
-
if (mode != null)
|
|
1675
|
+
fs5.writeFileSync(target, content, "utf8");
|
|
1676
|
+
if (mode != null) fs5.chmodSync(target, mode);
|
|
1647
1677
|
}
|
|
1648
1678
|
function writeFileMaybe(target, content, mode) {
|
|
1649
1679
|
if (currentOptions.dryRun) {
|
|
@@ -1655,7 +1685,7 @@ function writeFileMaybe(target, content, mode) {
|
|
|
1655
1685
|
function ensureEnterpriseLicenseFilesReadable() {
|
|
1656
1686
|
if (exists2(LICENSE_ROOT)) {
|
|
1657
1687
|
try {
|
|
1658
|
-
|
|
1688
|
+
fs5.chmodSync(LICENSE_ROOT, LICENSE_DIR_MODE);
|
|
1659
1689
|
} catch {
|
|
1660
1690
|
throw new Error(
|
|
1661
1691
|
`Cannot update directory mode for ${LICENSE_ROOT}. Fix by running: chmod ${LICENSE_DIR_MODE.toString(8)} ${LICENSE_ROOT}`
|
|
@@ -1665,7 +1695,7 @@ function ensureEnterpriseLicenseFilesReadable() {
|
|
|
1665
1695
|
for (const filePath of [LICENSE_BUNDLE_FILE, LICENSE_CERT_FILE, LICENSE_KEY_FILE, LICENSE_ROOT_CA_FILE]) {
|
|
1666
1696
|
if (!exists2(filePath)) continue;
|
|
1667
1697
|
try {
|
|
1668
|
-
|
|
1698
|
+
fs5.chmodSync(filePath, LICENSE_FILE_MODE);
|
|
1669
1699
|
} catch {
|
|
1670
1700
|
throw new Error(
|
|
1671
1701
|
`Cannot update file mode for ${filePath}. Fix by running: chmod ${LICENSE_FILE_MODE.toString(8)} ${filePath}`
|
|
@@ -1674,7 +1704,7 @@ function ensureEnterpriseLicenseFilesReadable() {
|
|
|
1674
1704
|
}
|
|
1675
1705
|
}
|
|
1676
1706
|
function exists2(target) {
|
|
1677
|
-
return
|
|
1707
|
+
return fs5.existsSync(target);
|
|
1678
1708
|
}
|
|
1679
1709
|
function randomSecret(bytes = 24) {
|
|
1680
1710
|
return randomBytes2(bytes).toString("hex");
|
|
@@ -1684,7 +1714,7 @@ function generateMinikmsSessionSigningKey() {
|
|
|
1684
1714
|
return privateKey.export({ type: "pkcs8", format: "pem" }).toString();
|
|
1685
1715
|
}
|
|
1686
1716
|
function deterministicInstallFingerprint(rootDomain, stackName) {
|
|
1687
|
-
return `envsync-${
|
|
1717
|
+
return `envsync-${createHash3("sha256").update(`${rootDomain}:${stackName}`).digest("hex").slice(0, 32)}`;
|
|
1688
1718
|
}
|
|
1689
1719
|
function randomStrongPassword() {
|
|
1690
1720
|
return `EnvSync!${randomBytes2(8).toString("hex")}Aa1`;
|
|
@@ -1788,7 +1818,7 @@ function normalizeNetutilsForwards(raw, stackName, enabled) {
|
|
|
1788
1818
|
}
|
|
1789
1819
|
function loadWireguardState() {
|
|
1790
1820
|
if (!exists2(WIREGUARD_STATE_FILE)) return null;
|
|
1791
|
-
const raw = JSON.parse(
|
|
1821
|
+
const raw = JSON.parse(fs5.readFileSync(WIREGUARD_STATE_FILE, "utf8"));
|
|
1792
1822
|
return {
|
|
1793
1823
|
private_key: raw.private_key,
|
|
1794
1824
|
public_key: raw.public_key,
|
|
@@ -1972,7 +2002,7 @@ function publicHttpsUrl2(config, host, path5 = "") {
|
|
|
1972
2002
|
function getDeployCliVersion() {
|
|
1973
2003
|
try {
|
|
1974
2004
|
const packageJsonPath = new URL("../package.json", import.meta.url);
|
|
1975
|
-
const raw =
|
|
2005
|
+
const raw = fs5.readFileSync(packageJsonPath, "utf8");
|
|
1976
2006
|
return JSON.parse(raw).version ?? "0.0.0";
|
|
1977
2007
|
} catch {
|
|
1978
2008
|
return process.env.npm_package_version ?? "0.0.0";
|
|
@@ -2064,7 +2094,6 @@ function buildOperatorOverview() {
|
|
|
2064
2094
|
const bootstrapComplete = hasCompleteBootstrapState(generated) && generated.bootstrap.completed_at.length > 0;
|
|
2065
2095
|
const api = apiHealth(services, config.services.stack_name);
|
|
2066
2096
|
const web = serviceHealth(services, `${config.services.stack_name}_web_nginx`);
|
|
2067
|
-
const landing = serviceHealth(services, `${config.services.stack_name}_landing_nginx`);
|
|
2068
2097
|
statusLines.push(`Configured: ${chalk.green("yes")}`);
|
|
2069
2098
|
statusLines.push(`Pinned release: ${chalk.cyan(config.release.version)}`);
|
|
2070
2099
|
statusLines.push(`Stack: ${stackRunning ? chalk.green(config.services.stack_name) : chalk.yellow("not running")}`);
|
|
@@ -2072,7 +2101,7 @@ function buildOperatorOverview() {
|
|
|
2072
2101
|
statusLines.push(`Active API slot: ${chalk.cyan(generated.deployment.active_slot)}`);
|
|
2073
2102
|
statusLines.push(`API: ${api === "healthy" ? chalk.green(api) : api === "missing" ? chalk.red(api) : chalk.yellow(api)}`);
|
|
2074
2103
|
statusLines.push(`Web: ${web === "healthy" ? chalk.green(web) : web === "missing" ? chalk.red(web) : chalk.yellow(web)}`);
|
|
2075
|
-
statusLines.push(`Landing: ${
|
|
2104
|
+
statusLines.push(`Landing: ${chalk.dim("omitted")}`);
|
|
2076
2105
|
if (!bootstrapComplete) {
|
|
2077
2106
|
return {
|
|
2078
2107
|
statusLines,
|
|
@@ -2082,7 +2111,7 @@ function buildOperatorOverview() {
|
|
|
2082
2111
|
]
|
|
2083
2112
|
};
|
|
2084
2113
|
}
|
|
2085
|
-
if (api !== "healthy" || web !== "healthy"
|
|
2114
|
+
if (api !== "healthy" || web !== "healthy") {
|
|
2086
2115
|
return {
|
|
2087
2116
|
statusLines,
|
|
2088
2117
|
nextSteps: [
|
|
@@ -2151,8 +2180,6 @@ function versionedImages(version, edition = "enterprise") {
|
|
|
2151
2180
|
const api = edition === "oss" ? `ghcr.io/envsync-cloud/envsync-api:${version}` : `ghcr.io/envsync-cloud/envsync-api-enterprise:${version}`;
|
|
2152
2181
|
return {
|
|
2153
2182
|
api,
|
|
2154
|
-
// Deprecated field: kept for older deploy.yaml keys; unused by stack render.
|
|
2155
|
-
management_api: api,
|
|
2156
2183
|
keycloak: `envsync-keycloak:${version}`,
|
|
2157
2184
|
web,
|
|
2158
2185
|
landing: `ghcr.io/envsync-cloud/envsync-landing-static:${version}`
|
|
@@ -2238,7 +2265,6 @@ function normalizeConfig(raw) {
|
|
|
2238
2265
|
},
|
|
2239
2266
|
images: {
|
|
2240
2267
|
api: !raw.images?.api || isManagedVersionedImage(raw.images.api, "api") ? derivedImages.api : raw.images.api,
|
|
2241
|
-
management_api: !raw.images?.management_api || isManagedVersionedImage(raw.images.management_api, "management_api") ? derivedImages.management_api : raw.images.management_api,
|
|
2242
2268
|
keycloak: !raw.images?.keycloak || isManagedVersionedImage(raw.images.keycloak, "keycloak") ? derivedImages.keycloak : raw.images.keycloak,
|
|
2243
2269
|
web: !raw.images?.web || isManagedVersionedImage(raw.images.web, "web") ? derivedImages.web : raw.images.web,
|
|
2244
2270
|
landing: !raw.images?.landing || isManagedVersionedImage(raw.images.landing, "landing") ? derivedImages.landing : raw.images.landing,
|
|
@@ -2249,7 +2275,6 @@ function normalizeConfig(raw) {
|
|
|
2249
2275
|
services: {
|
|
2250
2276
|
stack_name: stackName,
|
|
2251
2277
|
api_port: requireDefined(raw.services?.api_port, "services.api_port"),
|
|
2252
|
-
management_api_port: raw.services?.management_api_port ?? 4001,
|
|
2253
2278
|
public_http_port: raw.services?.public_http_port ?? 80,
|
|
2254
2279
|
public_https_port: raw.services?.public_https_port ?? 443,
|
|
2255
2280
|
clickstack_ui_port: requireDefined(raw.services?.clickstack_ui_port, "services.clickstack_ui_port"),
|
|
@@ -2350,7 +2375,8 @@ function emptyGeneratedState() {
|
|
|
2350
2375
|
openfga_db_password: "",
|
|
2351
2376
|
minikms_root_key: "",
|
|
2352
2377
|
minikms_session_signing_key: "",
|
|
2353
|
-
minikms_db_password: ""
|
|
2378
|
+
minikms_db_password: "",
|
|
2379
|
+
saml_session_secret: ""
|
|
2354
2380
|
},
|
|
2355
2381
|
bootstrap: {
|
|
2356
2382
|
completed_at: ""
|
|
@@ -2387,7 +2413,8 @@ function normalizeGeneratedState(raw) {
|
|
|
2387
2413
|
openfga_db_password: raw?.secrets?.openfga_db_password ?? defaults.secrets.openfga_db_password,
|
|
2388
2414
|
minikms_root_key: raw?.secrets?.minikms_root_key ?? defaults.secrets.minikms_root_key,
|
|
2389
2415
|
minikms_session_signing_key: raw?.secrets?.minikms_session_signing_key ?? defaults.secrets.minikms_session_signing_key,
|
|
2390
|
-
minikms_db_password: raw?.secrets?.minikms_db_password ?? defaults.secrets.minikms_db_password
|
|
2416
|
+
minikms_db_password: raw?.secrets?.minikms_db_password ?? defaults.secrets.minikms_db_password,
|
|
2417
|
+
saml_session_secret: raw?.secrets?.saml_session_secret ?? defaults.secrets.saml_session_secret
|
|
2391
2418
|
},
|
|
2392
2419
|
bootstrap: {
|
|
2393
2420
|
completed_at: raw?.bootstrap?.completed_at ?? defaults.bootstrap.completed_at
|
|
@@ -2396,7 +2423,7 @@ function normalizeGeneratedState(raw) {
|
|
|
2396
2423
|
}
|
|
2397
2424
|
function readInternalState() {
|
|
2398
2425
|
if (!exists2(INTERNAL_CONFIG_JSON)) return null;
|
|
2399
|
-
const raw = JSON.parse(
|
|
2426
|
+
const raw = JSON.parse(fs5.readFileSync(INTERNAL_CONFIG_JSON, "utf8"));
|
|
2400
2427
|
return {
|
|
2401
2428
|
config: raw.config ? normalizeConfig(raw.config) : void 0,
|
|
2402
2429
|
generated: normalizeGeneratedState(raw.generated)
|
|
@@ -2406,7 +2433,7 @@ function loadConfig() {
|
|
|
2406
2433
|
if (!exists2(DEPLOY_YAML)) {
|
|
2407
2434
|
throw new Error(`Missing deploy config at ${DEPLOY_YAML}. Run setup first.`);
|
|
2408
2435
|
}
|
|
2409
|
-
const raw =
|
|
2436
|
+
const raw = fs5.readFileSync(DEPLOY_YAML, "utf8");
|
|
2410
2437
|
if (raw.trimStart().startsWith("{")) {
|
|
2411
2438
|
return normalizeConfig(JSON.parse(raw));
|
|
2412
2439
|
}
|
|
@@ -2414,7 +2441,7 @@ function loadConfig() {
|
|
|
2414
2441
|
}
|
|
2415
2442
|
function loadGeneratedEnv() {
|
|
2416
2443
|
if (!exists2(DEPLOY_ENV)) return {};
|
|
2417
|
-
return parseEnvFile(
|
|
2444
|
+
return parseEnvFile(fs5.readFileSync(DEPLOY_ENV, "utf8"));
|
|
2418
2445
|
}
|
|
2419
2446
|
function mergeGeneratedState(env, generated) {
|
|
2420
2447
|
const normalized = normalizeGeneratedState(generated);
|
|
@@ -2438,7 +2465,8 @@ function mergeGeneratedState(env, generated) {
|
|
|
2438
2465
|
openfga_db_password: env.OPENFGA_DB_PASSWORD ?? normalized.secrets.openfga_db_password,
|
|
2439
2466
|
minikms_root_key: env.MINIKMS_ROOT_KEY ?? normalized.secrets.minikms_root_key,
|
|
2440
2467
|
minikms_session_signing_key: env.MINIKMS_SESSION_SIGNING_KEY ?? normalized.secrets.minikms_session_signing_key,
|
|
2441
|
-
minikms_db_password: env.MINIKMS_DB_PASSWORD ?? normalized.secrets.minikms_db_password
|
|
2468
|
+
minikms_db_password: env.MINIKMS_DB_PASSWORD ?? normalized.secrets.minikms_db_password,
|
|
2469
|
+
saml_session_secret: env.SAML_SESSION_SECRET ?? normalized.secrets.saml_session_secret
|
|
2442
2470
|
},
|
|
2443
2471
|
bootstrap: normalized.bootstrap
|
|
2444
2472
|
});
|
|
@@ -2467,7 +2495,8 @@ function ensureGeneratedRuntimeState(config, generated) {
|
|
|
2467
2495
|
openfga_db_password: generated.secrets.openfga_db_password || randomSecret(),
|
|
2468
2496
|
minikms_root_key: generated.secrets.minikms_root_key || randomBytes2(32).toString("hex"),
|
|
2469
2497
|
minikms_session_signing_key: generated.secrets.minikms_session_signing_key || generateMinikmsSessionSigningKey(),
|
|
2470
|
-
minikms_db_password: generated.secrets.minikms_db_password || randomSecret()
|
|
2498
|
+
minikms_db_password: generated.secrets.minikms_db_password || randomSecret(),
|
|
2499
|
+
saml_session_secret: generated.secrets.saml_session_secret || randomBytes2(32).toString("hex")
|
|
2471
2500
|
},
|
|
2472
2501
|
bootstrap: generated.bootstrap
|
|
2473
2502
|
});
|
|
@@ -2700,7 +2729,7 @@ function extractStaticBundle(kind, image, targetDir) {
|
|
|
2700
2729
|
logDryRun(`Would extract ${kind} bundle from ${image} into ${targetDir}`);
|
|
2701
2730
|
return;
|
|
2702
2731
|
}
|
|
2703
|
-
|
|
2732
|
+
fs5.rmSync(targetDir, { recursive: true, force: true });
|
|
2704
2733
|
ensureDir(targetDir);
|
|
2705
2734
|
const containerId = run("docker", ["create", image], { quiet: true }).trim();
|
|
2706
2735
|
try {
|
|
@@ -2728,23 +2757,23 @@ function writeFrontendRuntimeConfig(targetDir, runtimeConfig) {
|
|
|
2728
2757
|
function syncFrontendReleaseContents(sourceDir, targetDir) {
|
|
2729
2758
|
ensureDir(targetDir);
|
|
2730
2759
|
const deferredEntries = /* @__PURE__ */ new Set(["index.html", "runtime-config.js"]);
|
|
2731
|
-
const entries =
|
|
2760
|
+
const entries = fs5.readdirSync(sourceDir, { withFileTypes: true });
|
|
2732
2761
|
for (const entry of entries) {
|
|
2733
2762
|
if (deferredEntries.has(entry.name)) {
|
|
2734
2763
|
continue;
|
|
2735
2764
|
}
|
|
2736
|
-
|
|
2765
|
+
fs5.cpSync(path4.join(sourceDir, entry.name), path4.join(targetDir, entry.name), {
|
|
2737
2766
|
recursive: true,
|
|
2738
2767
|
force: true
|
|
2739
2768
|
});
|
|
2740
2769
|
}
|
|
2741
2770
|
const stagedRuntimeConfig = path4.join(sourceDir, "runtime-config.js");
|
|
2742
2771
|
if (exists2(stagedRuntimeConfig)) {
|
|
2743
|
-
|
|
2772
|
+
fs5.cpSync(stagedRuntimeConfig, path4.join(targetDir, "runtime-config.js"), { force: true });
|
|
2744
2773
|
}
|
|
2745
2774
|
const stagedIndex = path4.join(sourceDir, "index.html");
|
|
2746
2775
|
if (exists2(stagedIndex)) {
|
|
2747
|
-
|
|
2776
|
+
fs5.cpSync(stagedIndex, path4.join(targetDir, "index.html"), { force: true });
|
|
2748
2777
|
}
|
|
2749
2778
|
}
|
|
2750
2779
|
function activateFrontendRelease(kind, version, runtimeConfig) {
|
|
@@ -3137,7 +3166,7 @@ ${trimmed}`
|
|
|
3137
3166
|
}
|
|
3138
3167
|
function readRenderedRuntimeConfig(filePath) {
|
|
3139
3168
|
if (!exists2(filePath)) return null;
|
|
3140
|
-
const raw =
|
|
3169
|
+
const raw = fs5.readFileSync(filePath, "utf8").trim();
|
|
3141
3170
|
const prefix = "window.__ENVSYNC_RUNTIME_CONFIG__ = ";
|
|
3142
3171
|
if (!raw.startsWith(prefix) || !raw.endsWith(";")) return null;
|
|
3143
3172
|
try {
|
|
@@ -3434,7 +3463,7 @@ function removeTarget(target) {
|
|
|
3434
3463
|
logDryRun(`Would remove ${target}`);
|
|
3435
3464
|
return;
|
|
3436
3465
|
}
|
|
3437
|
-
|
|
3466
|
+
fs5.rmSync(target, { recursive: true, force: true });
|
|
3438
3467
|
logInfo(`Removed ${target}`);
|
|
3439
3468
|
}
|
|
3440
3469
|
async function cmdRemove() {
|
|
@@ -3833,7 +3862,6 @@ async function cmdSetup() {
|
|
|
3833
3862
|
domain: { root_domain: rootDomain, acme_email: acmeEmail },
|
|
3834
3863
|
images: {
|
|
3835
3864
|
api: releaseImages.api,
|
|
3836
|
-
management_api: releaseImages.management_api,
|
|
3837
3865
|
keycloak: releaseImages.keycloak,
|
|
3838
3866
|
web: releaseImages.web,
|
|
3839
3867
|
landing: releaseImages.landing,
|
|
@@ -3844,7 +3872,6 @@ async function cmdSetup() {
|
|
|
3844
3872
|
services: {
|
|
3845
3873
|
stack_name: "envsync",
|
|
3846
3874
|
api_port: 4e3,
|
|
3847
|
-
management_api_port: 4001,
|
|
3848
3875
|
public_http_port: 80,
|
|
3849
3876
|
public_https_port: 443,
|
|
3850
3877
|
clickstack_ui_port: 8080,
|
|
@@ -4183,7 +4210,7 @@ async function cmdDeploy() {
|
|
|
4183
4210
|
sleepSeconds(3);
|
|
4184
4211
|
promoted = true;
|
|
4185
4212
|
if (snapshotPath && !currentOptions.dryRun) {
|
|
4186
|
-
|
|
4213
|
+
fs5.rmSync(snapshotPath, { force: true });
|
|
4187
4214
|
}
|
|
4188
4215
|
} catch (error) {
|
|
4189
4216
|
const rollbackTarget = preUpgradeHead ?? "zero";
|
|
@@ -4217,7 +4244,7 @@ ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
|
4217
4244
|
deployRenderedStack(config, "rollback recovery");
|
|
4218
4245
|
waitForApiSlotHealthy(config, recoveredState.deployment.active_slot);
|
|
4219
4246
|
if (snapshotPath && !config.upgrade.keep_failed_upgrade_db_snapshot && !currentOptions.dryRun) {
|
|
4220
|
-
|
|
4247
|
+
fs5.rmSync(snapshotPath, { force: true });
|
|
4221
4248
|
}
|
|
4222
4249
|
throw new Error(
|
|
4223
4250
|
usedSnapshotRestore ? `Deploy failed after DB migration. Previous release was restored using DB snapshot recovery. ${error instanceof Error ? error.message : String(error)}` : `Deploy failed after DB migration. Previous release was restored. ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -4243,7 +4270,6 @@ ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
|
4243
4270
|
{ label: "openfga", getHealth: (services) => serviceHealth(services, `${config.services.stack_name}_openfga`) },
|
|
4244
4271
|
{ label: "minikms", getHealth: (services) => serviceHealth(services, `${config.services.stack_name}_minikms`) },
|
|
4245
4272
|
{ label: "clickstack", getHealth: (services) => serviceHealth(services, `${config.services.stack_name}_clickstack`) },
|
|
4246
|
-
...isOssConfig2(config) ? [] : [{ label: "landing", getHealth: (services) => serviceHealth(services, `${config.services.stack_name}_landing_nginx`) }],
|
|
4247
4273
|
{ label: "web", getHealth: (services) => serviceHealth(services, `${config.services.stack_name}_web_nginx`) },
|
|
4248
4274
|
{ label: "api", getHealth: (services) => apiHealth(services, config.services.stack_name) }
|
|
4249
4275
|
]);
|
|
@@ -4286,7 +4312,7 @@ async function cmdPromote(target) {
|
|
|
4286
4312
|
}
|
|
4287
4313
|
});
|
|
4288
4314
|
writeDeployArtifacts(config, promotedState);
|
|
4289
|
-
if (exists2(releaseAssetDir("web", promotedState.deployment.slots[targetSlot].release_version))
|
|
4315
|
+
if (exists2(releaseAssetDir("web", promotedState.deployment.slots[targetSlot].release_version))) {
|
|
4290
4316
|
activateFrontendReleaseForState(config, promotedState);
|
|
4291
4317
|
} else {
|
|
4292
4318
|
logWarn(`Missing staged frontend assets for release ${promotedState.deployment.slots[targetSlot].release_version}; leaving current frontend assets unchanged.`);
|
|
@@ -4317,7 +4343,7 @@ async function cmdRollback() {
|
|
|
4317
4343
|
}
|
|
4318
4344
|
});
|
|
4319
4345
|
writeDeployArtifacts(config, rollbackState);
|
|
4320
|
-
if (exists2(releaseAssetDir("web", rollbackState.deployment.slots[rollbackState.deployment.active_slot].release_version))
|
|
4346
|
+
if (exists2(releaseAssetDir("web", rollbackState.deployment.slots[rollbackState.deployment.active_slot].release_version))) {
|
|
4321
4347
|
activateFrontendReleaseForState(config, rollbackState);
|
|
4322
4348
|
} else {
|
|
4323
4349
|
logWarn(`Missing staged frontend assets for release ${rollbackState.deployment.slots[rollbackState.deployment.active_slot].release_version}; leaving current frontend assets unchanged.`);
|
|
@@ -4339,7 +4365,7 @@ async function cmdHealth(asJson) {
|
|
|
4339
4365
|
const hosts = domainMap2(config.domain.root_domain);
|
|
4340
4366
|
const services = listStackServices(config);
|
|
4341
4367
|
const stackName = config.services.stack_name;
|
|
4342
|
-
const traefikDynamic = exists2(TRAEFIK_DYNAMIC_FILE) ?
|
|
4368
|
+
const traefikDynamic = exists2(TRAEFIK_DYNAMIC_FILE) ? fs5.readFileSync(TRAEFIK_DYNAMIC_FILE, "utf8") : "";
|
|
4343
4369
|
const webRuntimeConfig = readRenderedRuntimeConfig(path4.join(RELEASES_ROOT, "web", "current", "runtime-config.js"));
|
|
4344
4370
|
const landingRuntimeConfig = readRenderedRuntimeConfig(path4.join(RELEASES_ROOT, "landing", "current", "runtime-config.js"));
|
|
4345
4371
|
const clickstackSearchState = getClickstackSearchState(config);
|
|
@@ -4413,7 +4439,7 @@ async function cmdHealth(asJson) {
|
|
|
4413
4439
|
}
|
|
4414
4440
|
},
|
|
4415
4441
|
web: serviceHealth(services, `${stackName}_web_nginx`),
|
|
4416
|
-
landing:
|
|
4442
|
+
landing: "omitted"
|
|
4417
4443
|
},
|
|
4418
4444
|
database: {
|
|
4419
4445
|
api: databaseHealth
|
|
@@ -4531,8 +4557,8 @@ async function cmdUpgradeDeps() {
|
|
|
4531
4557
|
await cmdDeploy();
|
|
4532
4558
|
}
|
|
4533
4559
|
function sha256File(filePath) {
|
|
4534
|
-
const hash =
|
|
4535
|
-
hash.update(
|
|
4560
|
+
const hash = createHash3("sha256");
|
|
4561
|
+
hash.update(fs5.readFileSync(filePath));
|
|
4536
4562
|
return hash.digest("hex");
|
|
4537
4563
|
}
|
|
4538
4564
|
function parseEnterpriseLicenseBundle(raw) {
|
|
@@ -4561,7 +4587,7 @@ function parseEnterpriseLicenseBundle(raw) {
|
|
|
4561
4587
|
return candidate;
|
|
4562
4588
|
}
|
|
4563
4589
|
function readEnterpriseLicenseBundle(bundlePath) {
|
|
4564
|
-
return parseEnterpriseLicenseBundle(JSON.parse(
|
|
4590
|
+
return parseEnterpriseLicenseBundle(JSON.parse(fs5.readFileSync(bundlePath, "utf8")));
|
|
4565
4591
|
}
|
|
4566
4592
|
function normalizeLicenseServerUrl(serverUrl, route) {
|
|
4567
4593
|
const trimmed = serverUrl.replace(/\/+$/, "");
|
|
@@ -4823,19 +4849,19 @@ async function cmdBackup() {
|
|
|
4823
4849
|
logWarn("Backup will temporarily stop the EnvSync stack to capture consistent volume data.");
|
|
4824
4850
|
stopManagedRuntime(config, "Stopping existing EnvSync services for consistent backup");
|
|
4825
4851
|
}
|
|
4826
|
-
writeFile(path4.join(staged, "deploy.env"),
|
|
4827
|
-
writeFile(path4.join(staged, "deploy.yaml"),
|
|
4828
|
-
writeFile(path4.join(staged, "config.json"),
|
|
4829
|
-
writeFile(path4.join(staged, "versions.lock.json"),
|
|
4830
|
-
writeFile(path4.join(staged, "docker-stack.bootstrap.base.yaml"),
|
|
4831
|
-
writeFile(path4.join(staged, "docker-stack.bootstrap.yaml"),
|
|
4832
|
-
writeFile(path4.join(staged, "docker-stack.yaml"),
|
|
4833
|
-
writeFile(path4.join(staged, "traefik-dynamic.yaml"),
|
|
4834
|
-
writeFile(path4.join(staged, "keycloak-realm.envsync.json"),
|
|
4835
|
-
writeFile(path4.join(staged, "otel-agent.yaml"),
|
|
4836
|
-
writeFile(path4.join(staged, "clickhouse-listen.xml"),
|
|
4852
|
+
writeFile(path4.join(staged, "deploy.env"), fs5.readFileSync(DEPLOY_ENV, "utf8"));
|
|
4853
|
+
writeFile(path4.join(staged, "deploy.yaml"), fs5.readFileSync(DEPLOY_YAML, "utf8"));
|
|
4854
|
+
writeFile(path4.join(staged, "config.json"), fs5.readFileSync(INTERNAL_CONFIG_JSON, "utf8"));
|
|
4855
|
+
writeFile(path4.join(staged, "versions.lock.json"), fs5.readFileSync(VERSIONS_LOCK, "utf8"));
|
|
4856
|
+
writeFile(path4.join(staged, "docker-stack.bootstrap.base.yaml"), fs5.readFileSync(BOOTSTRAP_BASE_STACK_FILE, "utf8"));
|
|
4857
|
+
writeFile(path4.join(staged, "docker-stack.bootstrap.yaml"), fs5.readFileSync(BOOTSTRAP_STACK_FILE, "utf8"));
|
|
4858
|
+
writeFile(path4.join(staged, "docker-stack.yaml"), fs5.readFileSync(STACK_FILE, "utf8"));
|
|
4859
|
+
writeFile(path4.join(staged, "traefik-dynamic.yaml"), fs5.readFileSync(TRAEFIK_DYNAMIC_FILE, "utf8"));
|
|
4860
|
+
writeFile(path4.join(staged, "keycloak-realm.envsync.json"), fs5.readFileSync(KEYCLOAK_REALM_FILE, "utf8"));
|
|
4861
|
+
writeFile(path4.join(staged, "otel-agent.yaml"), fs5.readFileSync(OTEL_AGENT_CONF, "utf8"));
|
|
4862
|
+
writeFile(path4.join(staged, "clickhouse-listen.xml"), fs5.readFileSync(CLICKSTACK_CLICKHOUSE_CONF, "utf8"));
|
|
4837
4863
|
if (!isOssConfig2(config) && exists2(LICENSE_ROOT)) {
|
|
4838
|
-
|
|
4864
|
+
fs5.cpSync(LICENSE_ROOT, path4.join(staged, "license"), { recursive: true });
|
|
4839
4865
|
}
|
|
4840
4866
|
const volumesDir = path4.join(staged, "volumes");
|
|
4841
4867
|
for (const volume of STACK_VOLUMES) {
|
|
@@ -4902,21 +4928,21 @@ async function cmdRestore(archivePath, autoDeploy = false) {
|
|
|
4902
4928
|
}
|
|
4903
4929
|
ensureDir(restoreRoot);
|
|
4904
4930
|
run("bash", ["-lc", `tar -xzf ${JSON.stringify(archivePath)} -C ${JSON.stringify(restoreRoot)}`]);
|
|
4905
|
-
writeFile(DEPLOY_ENV,
|
|
4906
|
-
writeFile(DEPLOY_YAML,
|
|
4907
|
-
writeFile(INTERNAL_CONFIG_JSON,
|
|
4908
|
-
writeFile(VERSIONS_LOCK,
|
|
4909
|
-
writeFile(BOOTSTRAP_BASE_STACK_FILE,
|
|
4910
|
-
writeFile(BOOTSTRAP_STACK_FILE,
|
|
4911
|
-
writeFile(STACK_FILE,
|
|
4912
|
-
writeFile(TRAEFIK_DYNAMIC_FILE,
|
|
4913
|
-
writeFile(KEYCLOAK_REALM_FILE,
|
|
4914
|
-
writeFile(OTEL_AGENT_CONF,
|
|
4915
|
-
writeFile(CLICKSTACK_CLICKHOUSE_CONF,
|
|
4931
|
+
writeFile(DEPLOY_ENV, fs5.readFileSync(path4.join(restoreRoot, "deploy.env"), "utf8"), 384);
|
|
4932
|
+
writeFile(DEPLOY_YAML, fs5.readFileSync(path4.join(restoreRoot, "deploy.yaml"), "utf8"));
|
|
4933
|
+
writeFile(INTERNAL_CONFIG_JSON, fs5.readFileSync(path4.join(restoreRoot, "config.json"), "utf8"));
|
|
4934
|
+
writeFile(VERSIONS_LOCK, fs5.readFileSync(path4.join(restoreRoot, "versions.lock.json"), "utf8"));
|
|
4935
|
+
writeFile(BOOTSTRAP_BASE_STACK_FILE, fs5.readFileSync(path4.join(restoreRoot, "docker-stack.bootstrap.base.yaml"), "utf8"));
|
|
4936
|
+
writeFile(BOOTSTRAP_STACK_FILE, fs5.readFileSync(path4.join(restoreRoot, "docker-stack.bootstrap.yaml"), "utf8"));
|
|
4937
|
+
writeFile(STACK_FILE, fs5.readFileSync(path4.join(restoreRoot, "docker-stack.yaml"), "utf8"));
|
|
4938
|
+
writeFile(TRAEFIK_DYNAMIC_FILE, fs5.readFileSync(path4.join(restoreRoot, "traefik-dynamic.yaml"), "utf8"));
|
|
4939
|
+
writeFile(KEYCLOAK_REALM_FILE, fs5.readFileSync(path4.join(restoreRoot, "keycloak-realm.envsync.json"), "utf8"));
|
|
4940
|
+
writeFile(OTEL_AGENT_CONF, fs5.readFileSync(path4.join(restoreRoot, "otel-agent.yaml"), "utf8"));
|
|
4941
|
+
writeFile(CLICKSTACK_CLICKHOUSE_CONF, fs5.readFileSync(path4.join(restoreRoot, "clickhouse-listen.xml"), "utf8"));
|
|
4916
4942
|
const restoredLicenseRoot = path4.join(restoreRoot, "license");
|
|
4917
4943
|
if (exists2(restoredLicenseRoot)) {
|
|
4918
|
-
|
|
4919
|
-
|
|
4944
|
+
fs5.rmSync(LICENSE_ROOT, { recursive: true, force: true });
|
|
4945
|
+
fs5.cpSync(restoredLicenseRoot, LICENSE_ROOT, { recursive: true });
|
|
4920
4946
|
ensureEnterpriseLicenseFilesReadable();
|
|
4921
4947
|
}
|
|
4922
4948
|
const config = loadConfig();
|
|
@@ -5338,8 +5364,6 @@ var init_cli = __esm({
|
|
|
5338
5364
|
MANAGED_VERSIONED_IMAGE_PREFIXES = {
|
|
5339
5365
|
api: "ghcr.io/envsync-cloud/envsync-api:",
|
|
5340
5366
|
api_enterprise: "ghcr.io/envsync-cloud/envsync-api-enterprise:",
|
|
5341
|
-
// Retired second process — kept so old deploy.yaml image strings still parse as "managed".
|
|
5342
|
-
management_api: "ghcr.io/envsync-cloud/envsync-management-api:",
|
|
5343
5367
|
keycloak: "envsync-keycloak:",
|
|
5344
5368
|
web: "ghcr.io/envsync-cloud/envsync-web-static:",
|
|
5345
5369
|
web_oss: "ghcr.io/envsync-cloud/envsync-web-oss-static:",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@envsync-cloud/deploy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.2",
|
|
4
4
|
"description": "OSS CLI for self-hosted EnvSync deployment on Docker Swarm",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"zod": "^3.25.76"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@envsync-cloud/deploy-core": "0.
|
|
49
|
+
"@envsync-cloud/deploy-core": "0.21.2",
|
|
50
50
|
"tsup": "^8.5.0",
|
|
51
51
|
"typescript": "^5.9.2"
|
|
52
52
|
},
|