@envsync-cloud/deploy-cli 0.6.4 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/index.js +118 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,6 +71,8 @@ Bootstrap infra, migrations, RustFS, and OpenFGA:
|
|
|
71
71
|
npx @envsync-cloud/deploy-cli bootstrap
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
`bootstrap` is destructive. It removes the existing EnvSync stack, matching containers, network, and managed volumes before rebuilding, and requires typing `ARE YOU SURE?` to continue.
|
|
75
|
+
|
|
74
76
|
Deploy the pending API and frontend services:
|
|
75
77
|
|
|
76
78
|
```bash
|
|
@@ -79,7 +81,7 @@ npx @envsync-cloud/deploy-cli deploy
|
|
|
79
81
|
|
|
80
82
|
The staged flow is:
|
|
81
83
|
- `setup` writes desired config
|
|
82
|
-
- `bootstrap` starts base infra, runs OpenFGA and miniKMS migrations, starts runtime infra, and persists generated runtime env state
|
|
84
|
+
- `bootstrap` resets the existing EnvSync deployment, then starts base infra, runs OpenFGA and miniKMS migrations, starts runtime infra, and persists generated runtime env state
|
|
83
85
|
- `deploy` starts the pending API and frontend services
|
|
84
86
|
|
|
85
87
|
Check service health:
|
package/dist/index.js
CHANGED
|
@@ -69,6 +69,9 @@ function logInfo(message) {
|
|
|
69
69
|
function logSuccess(message) {
|
|
70
70
|
console.log(`${chalk.green("[ok]")} ${message}`);
|
|
71
71
|
}
|
|
72
|
+
function logWarn(message) {
|
|
73
|
+
console.log(`${chalk.yellow("[warn]")} ${message}`);
|
|
74
|
+
}
|
|
72
75
|
function logDryRun(message) {
|
|
73
76
|
console.log(`${chalk.magenta("[dry-run]")} ${message}`);
|
|
74
77
|
}
|
|
@@ -216,6 +219,18 @@ async function ask(question, fallback = "") {
|
|
|
216
219
|
});
|
|
217
220
|
});
|
|
218
221
|
}
|
|
222
|
+
async function askRequired(question) {
|
|
223
|
+
if (!process.stdin.isTTY) {
|
|
224
|
+
throw new Error(`${question} confirmation requires an interactive terminal.`);
|
|
225
|
+
}
|
|
226
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
227
|
+
return await new Promise((resolve) => {
|
|
228
|
+
rl.question(`${question} `, (answer) => {
|
|
229
|
+
rl.close();
|
|
230
|
+
resolve(answer.trim());
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
}
|
|
219
234
|
function sleepSeconds(seconds) {
|
|
220
235
|
spawnSync("sleep", [`${seconds}`], { stdio: "ignore" });
|
|
221
236
|
}
|
|
@@ -364,6 +379,7 @@ function emptyGeneratedState() {
|
|
|
364
379
|
},
|
|
365
380
|
secrets: {
|
|
366
381
|
s3_secret_key: "",
|
|
382
|
+
keycloak_db_password: "",
|
|
367
383
|
keycloak_web_client_secret: "",
|
|
368
384
|
keycloak_api_client_secret: "",
|
|
369
385
|
openfga_db_password: "",
|
|
@@ -384,6 +400,7 @@ function normalizeGeneratedState(raw) {
|
|
|
384
400
|
},
|
|
385
401
|
secrets: {
|
|
386
402
|
s3_secret_key: raw?.secrets?.s3_secret_key ?? defaults.secrets.s3_secret_key,
|
|
403
|
+
keycloak_db_password: raw?.secrets?.keycloak_db_password ?? defaults.secrets.keycloak_db_password,
|
|
387
404
|
keycloak_web_client_secret: raw?.secrets?.keycloak_web_client_secret ?? defaults.secrets.keycloak_web_client_secret,
|
|
388
405
|
keycloak_api_client_secret: raw?.secrets?.keycloak_api_client_secret ?? defaults.secrets.keycloak_api_client_secret,
|
|
389
406
|
openfga_db_password: raw?.secrets?.openfga_db_password ?? defaults.secrets.openfga_db_password,
|
|
@@ -426,6 +443,7 @@ function mergeGeneratedState(env, generated) {
|
|
|
426
443
|
},
|
|
427
444
|
secrets: {
|
|
428
445
|
s3_secret_key: env.S3_SECRET_KEY ?? normalized.secrets.s3_secret_key,
|
|
446
|
+
keycloak_db_password: env.KEYCLOAK_DB_PASSWORD ?? normalized.secrets.keycloak_db_password,
|
|
429
447
|
keycloak_web_client_secret: env.KEYCLOAK_WEB_CLIENT_SECRET ?? normalized.secrets.keycloak_web_client_secret,
|
|
430
448
|
keycloak_api_client_secret: env.KEYCLOAK_API_CLIENT_SECRET ?? normalized.secrets.keycloak_api_client_secret,
|
|
431
449
|
openfga_db_password: env.OPENFGA_DB_PASSWORD ?? normalized.secrets.openfga_db_password,
|
|
@@ -446,6 +464,7 @@ function ensureGeneratedRuntimeState(generated) {
|
|
|
446
464
|
openfga: generated.openfga,
|
|
447
465
|
secrets: {
|
|
448
466
|
s3_secret_key: generated.secrets.s3_secret_key || randomSecret(16),
|
|
467
|
+
keycloak_db_password: generated.secrets.keycloak_db_password || "",
|
|
449
468
|
keycloak_web_client_secret: generated.secrets.keycloak_web_client_secret || randomSecret(),
|
|
450
469
|
keycloak_api_client_secret: generated.secrets.keycloak_api_client_secret || randomSecret(),
|
|
451
470
|
openfga_db_password: generated.secrets.openfga_db_password || randomSecret(),
|
|
@@ -488,6 +507,7 @@ function buildRuntimeEnv(config, generated) {
|
|
|
488
507
|
KEYCLOAK_REALM: config.auth.keycloak_realm,
|
|
489
508
|
KEYCLOAK_ADMIN_USER: config.auth.admin_user,
|
|
490
509
|
KEYCLOAK_ADMIN_PASSWORD: config.auth.admin_password,
|
|
510
|
+
KEYCLOAK_DB_PASSWORD: generated.secrets.keycloak_db_password || config.auth.admin_password,
|
|
491
511
|
KEYCLOAK_WEB_CLIENT_ID: config.auth.web_client_id,
|
|
492
512
|
KEYCLOAK_WEB_CLIENT_SECRET: generated.secrets.keycloak_web_client_secret,
|
|
493
513
|
KEYCLOAK_CLI_CLIENT_ID: config.auth.cli_client_id,
|
|
@@ -781,7 +801,7 @@ ${renderEnvList({
|
|
|
781
801
|
environment:
|
|
782
802
|
${renderEnvList({
|
|
783
803
|
POSTGRES_USER: "keycloak",
|
|
784
|
-
POSTGRES_PASSWORD: runtimeEnv.
|
|
804
|
+
POSTGRES_PASSWORD: runtimeEnv.KEYCLOAK_DB_PASSWORD,
|
|
785
805
|
POSTGRES_DB: "keycloak"
|
|
786
806
|
})}
|
|
787
807
|
volumes:
|
|
@@ -798,7 +818,7 @@ ${renderEnvList({
|
|
|
798
818
|
KC_DB: "postgres",
|
|
799
819
|
KC_DB_URL: "jdbc:postgresql://keycloak_db:5432/keycloak",
|
|
800
820
|
KC_DB_USERNAME: "keycloak",
|
|
801
|
-
KC_DB_PASSWORD: runtimeEnv.
|
|
821
|
+
KC_DB_PASSWORD: runtimeEnv.KEYCLOAK_DB_PASSWORD,
|
|
802
822
|
KC_BOOTSTRAP_ADMIN_USERNAME: config.auth.admin_user,
|
|
803
823
|
KC_BOOTSTRAP_ADMIN_PASSWORD: config.auth.admin_password,
|
|
804
824
|
KC_HTTP_ENABLED: "true",
|
|
@@ -1251,6 +1271,95 @@ function listStackServices(config) {
|
|
|
1251
1271
|
}
|
|
1252
1272
|
return services;
|
|
1253
1273
|
}
|
|
1274
|
+
function listManagedContainers(config) {
|
|
1275
|
+
const output = tryRun("docker", ["ps", "-aq", "--filter", `name=^/${config.services.stack_name}_`], { quiet: true });
|
|
1276
|
+
return output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1277
|
+
}
|
|
1278
|
+
function stackExists(config) {
|
|
1279
|
+
const output = tryRun("docker", ["stack", "ls", "--format", "{{.Name}}"], { quiet: true });
|
|
1280
|
+
return output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).includes(config.services.stack_name);
|
|
1281
|
+
}
|
|
1282
|
+
function waitForStackRemoval(config, timeoutSeconds = 60) {
|
|
1283
|
+
if (currentOptions.dryRun) {
|
|
1284
|
+
logDryRun(`Would wait for stack ${config.services.stack_name} to be removed`);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
const deadline = Date.now() + timeoutSeconds * 1e3;
|
|
1288
|
+
while (Date.now() < deadline) {
|
|
1289
|
+
if (!stackExists(config)) {
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
sleepSeconds(2);
|
|
1293
|
+
}
|
|
1294
|
+
throw new Error(`Timed out waiting for stack ${config.services.stack_name} to be removed`);
|
|
1295
|
+
}
|
|
1296
|
+
async function confirmBootstrapReset(config) {
|
|
1297
|
+
const volumeNames = STACK_VOLUMES.map((volume) => stackVolumeName(config, volume));
|
|
1298
|
+
const containerIds = listManagedContainers(config);
|
|
1299
|
+
const networkName = stackNetworkName(config);
|
|
1300
|
+
logWarn("Bootstrap will delete existing EnvSync Docker resources before rebuilding infra.");
|
|
1301
|
+
logWarn(`Stack: ${config.services.stack_name}`);
|
|
1302
|
+
logWarn(`Network: ${networkName}`);
|
|
1303
|
+
logWarn(`Volumes: ${volumeNames.join(", ")}`);
|
|
1304
|
+
if (containerIds.length > 0) {
|
|
1305
|
+
logWarn(`Containers: ${containerIds.join(", ")}`);
|
|
1306
|
+
} else {
|
|
1307
|
+
logWarn("Containers: none currently matched");
|
|
1308
|
+
}
|
|
1309
|
+
logWarn("This removes existing deployment data for the managed EnvSync services.");
|
|
1310
|
+
const response = await askRequired(chalk.bold.red('Type "ARE YOU SURE?" to continue:'));
|
|
1311
|
+
if (response !== "ARE YOU SURE?") {
|
|
1312
|
+
throw new Error("Bootstrap aborted. Confirmation did not match 'ARE YOU SURE?'.");
|
|
1313
|
+
}
|
|
1314
|
+
logSuccess("Destructive bootstrap reset confirmed");
|
|
1315
|
+
}
|
|
1316
|
+
function cleanupBootstrapState(config) {
|
|
1317
|
+
const volumeNames = STACK_VOLUMES.map((volume) => stackVolumeName(config, volume));
|
|
1318
|
+
const containerIds = listManagedContainers(config);
|
|
1319
|
+
const networkName = stackNetworkName(config);
|
|
1320
|
+
logStep("Removing existing EnvSync deployment resources");
|
|
1321
|
+
if (currentOptions.dryRun) {
|
|
1322
|
+
if (stackExists(config)) {
|
|
1323
|
+
logDryRun(`Would remove stack ${config.services.stack_name}`);
|
|
1324
|
+
logCommand("docker", ["stack", "rm", config.services.stack_name]);
|
|
1325
|
+
} else {
|
|
1326
|
+
logDryRun(`No existing stack named ${config.services.stack_name} found`);
|
|
1327
|
+
}
|
|
1328
|
+
if (containerIds.length > 0) {
|
|
1329
|
+
logDryRun(`Would remove containers: ${containerIds.join(", ")}`);
|
|
1330
|
+
logCommand("docker", ["rm", "-f", ...containerIds]);
|
|
1331
|
+
}
|
|
1332
|
+
logDryRun(`Would remove network ${networkName} if present`);
|
|
1333
|
+
logCommand("docker", ["network", "rm", networkName]);
|
|
1334
|
+
for (const volumeName of volumeNames) {
|
|
1335
|
+
logDryRun(`Would remove volume ${volumeName}`);
|
|
1336
|
+
logCommand("docker", ["volume", "rm", "-f", volumeName]);
|
|
1337
|
+
}
|
|
1338
|
+
logSuccess("Bootstrap cleanup preview completed");
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
if (stackExists(config)) {
|
|
1342
|
+
logCommand("docker", ["stack", "rm", config.services.stack_name]);
|
|
1343
|
+
run("docker", ["stack", "rm", config.services.stack_name]);
|
|
1344
|
+
waitForStackRemoval(config);
|
|
1345
|
+
}
|
|
1346
|
+
const refreshedContainers = listManagedContainers(config);
|
|
1347
|
+
if (refreshedContainers.length > 0) {
|
|
1348
|
+
logCommand("docker", ["rm", "-f", ...refreshedContainers]);
|
|
1349
|
+
run("docker", ["rm", "-f", ...refreshedContainers]);
|
|
1350
|
+
}
|
|
1351
|
+
if (commandSucceeds("docker", ["network", "inspect", networkName])) {
|
|
1352
|
+
logCommand("docker", ["network", "rm", networkName]);
|
|
1353
|
+
run("docker", ["network", "rm", networkName]);
|
|
1354
|
+
}
|
|
1355
|
+
for (const volumeName of volumeNames) {
|
|
1356
|
+
if (commandSucceeds("docker", ["volume", "inspect", volumeName])) {
|
|
1357
|
+
logCommand("docker", ["volume", "rm", "-f", volumeName]);
|
|
1358
|
+
run("docker", ["volume", "rm", "-f", volumeName]);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
logSuccess("Existing EnvSync deployment resources removed");
|
|
1362
|
+
}
|
|
1254
1363
|
function serviceHealth(services, name) {
|
|
1255
1364
|
return services.get(`${name}`) ?? "missing";
|
|
1256
1365
|
}
|
|
@@ -1369,6 +1478,12 @@ async function cmdBootstrap() {
|
|
|
1369
1478
|
const runtimeEnv = buildRuntimeEnv(config, nextGenerated);
|
|
1370
1479
|
logInfo(`Release version: ${config.release.version}`);
|
|
1371
1480
|
assertSwarmManager();
|
|
1481
|
+
if (currentOptions.dryRun) {
|
|
1482
|
+
logWarn("Dry-run mode: bootstrap reset will be previewed but not executed.");
|
|
1483
|
+
} else {
|
|
1484
|
+
await confirmBootstrapReset(config);
|
|
1485
|
+
}
|
|
1486
|
+
cleanupBootstrapState(config);
|
|
1372
1487
|
ensureRepoCheckout(config);
|
|
1373
1488
|
writeDeployArtifacts(config, nextGenerated);
|
|
1374
1489
|
buildKeycloakImage(config.images.keycloak);
|
|
@@ -1384,7 +1499,7 @@ async function cmdBootstrap() {
|
|
|
1384
1499
|
waitForPostgresService(config, "postgres", "postgres", "postgres", "envsync-postgres");
|
|
1385
1500
|
waitForRedisService(config);
|
|
1386
1501
|
waitForTcpService(config, "rustfs", "rustfs", 9e3);
|
|
1387
|
-
waitForPostgresService(config, "keycloak", "keycloak_db", "keycloak", runtimeEnv.
|
|
1502
|
+
waitForPostgresService(config, "keycloak", "keycloak_db", "keycloak", runtimeEnv.KEYCLOAK_DB_PASSWORD);
|
|
1388
1503
|
waitForPostgresService(config, "openfga", "openfga_db", "openfga", runtimeEnv.OPENFGA_DB_PASSWORD);
|
|
1389
1504
|
waitForPostgresService(config, "minikms", "minikms_db", "postgres", runtimeEnv.MINIKMS_DB_PASSWORD);
|
|
1390
1505
|
runOpenFgaMigrate(config, runtimeEnv);
|