@envsync-cloud/deploy-cli 0.6.2 → 0.6.3

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.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/dist/index.js +223 -27
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -63,6 +63,8 @@ Write the desired self-hosted config:
63
63
  npx @envsync-cloud/deploy-cli setup
64
64
  ```
65
65
 
66
+ `setup` requires an exact release version such as `0.6.2`. Channel names like `stable` and `latest` are not accepted for self-hosted installs.
67
+
66
68
  Bootstrap infra, migrations, RustFS, and OpenFGA:
67
69
 
68
70
  ```bash
@@ -77,7 +79,7 @@ npx @envsync-cloud/deploy-cli deploy
77
79
 
78
80
  The staged flow is:
79
81
  - `setup` writes desired config
80
- - `bootstrap` starts infra and persists generated runtime env state
82
+ - `bootstrap` starts base infra, runs OpenFGA and miniKMS migrations, starts runtime infra, and persists generated runtime env state
81
83
  - `deploy` starts the pending API and frontend services
82
84
 
83
85
  Check service health:
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ var DEPLOY_ENV = "/etc/envsync/deploy.env";
17
17
  var DEPLOY_YAML = "/etc/envsync/deploy.yaml";
18
18
  var VERSIONS_LOCK = "/opt/envsync/deploy/versions.lock.json";
19
19
  var STACK_FILE = "/opt/envsync/deploy/docker-stack.yaml";
20
+ var BOOTSTRAP_BASE_STACK_FILE = "/opt/envsync/deploy/docker-stack.bootstrap.base.yaml";
20
21
  var BOOTSTRAP_STACK_FILE = "/opt/envsync/deploy/docker-stack.bootstrap.yaml";
21
22
  var TRAEFIK_DYNAMIC_FILE = "/opt/envsync/deploy/traefik-dynamic.yaml";
22
23
  var KEYCLOAK_REALM_FILE = "/opt/envsync/deploy/keycloak-realm.envsync.json";
@@ -45,6 +46,7 @@ var REQUIRED_BOOTSTRAP_ENV_KEYS = [
45
46
  "OPENFGA_STORE_ID",
46
47
  "OPENFGA_MODEL_ID"
47
48
  ];
49
+ var SEMVER_VERSION_RE = /^\d+\.\d+\.\d+$/;
48
50
  function run(cmd, args, opts = {}) {
49
51
  const result = spawnSync(cmd, args, {
50
52
  cwd: opts.cwd,
@@ -203,16 +205,120 @@ function getDeployCliVersion() {
203
205
  return process.env.npm_package_version ?? "0.0.0";
204
206
  }
205
207
  }
206
- function defaultSourceConfig() {
208
+ function assertSemverVersion(version, label = "release version") {
209
+ if (!SEMVER_VERSION_RE.test(version)) {
210
+ throw new Error(`Invalid ${label} '${version}'. Expected an exact semver like 0.6.2.`);
211
+ }
212
+ }
213
+ function versionedImages(version) {
214
+ assertSemverVersion(version);
215
+ return {
216
+ api: `ghcr.io/envsync-cloud/envsync-api:${version}`,
217
+ keycloak: `envsync-keycloak:${version}`,
218
+ web: `ghcr.io/envsync-cloud/envsync-web-static:${version}`,
219
+ landing: `ghcr.io/envsync-cloud/envsync-landing-static:${version}`
220
+ };
221
+ }
222
+ function defaultSourceConfig(version) {
207
223
  return {
208
224
  repo_url: "https://github.com/EnvSync-Cloud/envsync.git",
209
- ref: `v${getDeployCliVersion()}`
225
+ ref: `v${version}`
210
226
  };
211
227
  }
228
+ function resolveReleaseVersion(raw) {
229
+ const releaseVersion = raw.release?.version;
230
+ if (releaseVersion) {
231
+ assertSemverVersion(releaseVersion);
232
+ return releaseVersion;
233
+ }
234
+ if (typeof raw.release_channel === "string" && raw.release_channel.length > 0) {
235
+ if (SEMVER_VERSION_RE.test(raw.release_channel)) {
236
+ return raw.release_channel;
237
+ }
238
+ if (raw.release_channel === "stable" || raw.release_channel === "latest") {
239
+ throw new Error(
240
+ "Legacy release channel config is no longer supported for self-hosted installs. Set an exact release version in /etc/envsync/deploy.yaml."
241
+ );
242
+ }
243
+ throw new Error(`Invalid legacy release channel '${raw.release_channel}'. Set an exact release version in /etc/envsync/deploy.yaml.`);
244
+ }
245
+ return getDeployCliVersion();
246
+ }
247
+ function requireDefined(value, label) {
248
+ if (value === void 0) {
249
+ throw new Error(`Missing ${label} in ${DEPLOY_YAML}. Run setup again.`);
250
+ }
251
+ return value;
252
+ }
212
253
  function normalizeConfig(raw) {
254
+ const version = resolveReleaseVersion(raw);
255
+ const derivedImages = versionedImages(version);
256
+ const { release_channel: _legacyReleaseChannel, ...rest } = raw;
257
+ const rootDomain = requireDefined(raw.domain?.root_domain, "domain.root_domain");
258
+ const acmeEmail = requireDefined(raw.domain?.acme_email, "domain.acme_email");
213
259
  return {
214
- ...raw,
215
- source: raw.source ?? defaultSourceConfig()
260
+ ...rest,
261
+ source: {
262
+ repo_url: raw.source?.repo_url ?? "https://github.com/EnvSync-Cloud/envsync.git",
263
+ ref: `v${version}`
264
+ },
265
+ release: {
266
+ version
267
+ },
268
+ domain: {
269
+ root_domain: rootDomain,
270
+ acme_email: acmeEmail
271
+ },
272
+ images: {
273
+ api: derivedImages.api,
274
+ keycloak: derivedImages.keycloak,
275
+ web: derivedImages.web,
276
+ landing: derivedImages.landing,
277
+ clickstack: raw.images?.clickstack ?? "clickhouse/clickstack-all-in-one:latest",
278
+ traefik: raw.images?.traefik ?? "traefik:v3.1",
279
+ otel_agent: raw.images?.otel_agent ?? "otel/opentelemetry-collector-contrib:0.111.0"
280
+ },
281
+ services: {
282
+ stack_name: requireDefined(raw.services?.stack_name, "services.stack_name"),
283
+ api_port: requireDefined(raw.services?.api_port, "services.api_port"),
284
+ clickstack_ui_port: requireDefined(raw.services?.clickstack_ui_port, "services.clickstack_ui_port"),
285
+ clickstack_otlp_http_port: requireDefined(raw.services?.clickstack_otlp_http_port, "services.clickstack_otlp_http_port"),
286
+ clickstack_otlp_grpc_port: requireDefined(raw.services?.clickstack_otlp_grpc_port, "services.clickstack_otlp_grpc_port"),
287
+ keycloak_port: requireDefined(raw.services?.keycloak_port, "services.keycloak_port"),
288
+ rustfs_port: requireDefined(raw.services?.rustfs_port, "services.rustfs_port"),
289
+ rustfs_console_port: requireDefined(raw.services?.rustfs_console_port, "services.rustfs_console_port")
290
+ },
291
+ auth: {
292
+ keycloak_realm: requireDefined(raw.auth?.keycloak_realm, "auth.keycloak_realm"),
293
+ admin_user: requireDefined(raw.auth?.admin_user, "auth.admin_user"),
294
+ admin_password: requireDefined(raw.auth?.admin_password, "auth.admin_password"),
295
+ web_client_id: requireDefined(raw.auth?.web_client_id, "auth.web_client_id"),
296
+ api_client_id: requireDefined(raw.auth?.api_client_id, "auth.api_client_id"),
297
+ cli_client_id: requireDefined(raw.auth?.cli_client_id, "auth.cli_client_id")
298
+ },
299
+ observability: {
300
+ retention_days: requireDefined(raw.observability?.retention_days, "observability.retention_days"),
301
+ public_obs: requireDefined(raw.observability?.public_obs, "observability.public_obs")
302
+ },
303
+ backup: {
304
+ output_dir: requireDefined(raw.backup?.output_dir, "backup.output_dir"),
305
+ encrypted: requireDefined(raw.backup?.encrypted, "backup.encrypted")
306
+ },
307
+ smtp: {
308
+ host: requireDefined(raw.smtp?.host, "smtp.host"),
309
+ port: requireDefined(raw.smtp?.port, "smtp.port"),
310
+ secure: requireDefined(raw.smtp?.secure, "smtp.secure"),
311
+ user: requireDefined(raw.smtp?.user, "smtp.user"),
312
+ pass: requireDefined(raw.smtp?.pass, "smtp.pass"),
313
+ from: requireDefined(raw.smtp?.from, "smtp.from")
314
+ },
315
+ exposure: {
316
+ public_auth: requireDefined(raw.exposure?.public_auth, "exposure.public_auth"),
317
+ public_obs: requireDefined(raw.exposure?.public_obs, "exposure.public_obs"),
318
+ mailpit_enabled: requireDefined(raw.exposure?.mailpit_enabled, "exposure.mailpit_enabled"),
319
+ s3_public: requireDefined(raw.exposure?.s3_public, "exposure.s3_public"),
320
+ s3_console_public: requireDefined(raw.exposure?.s3_console_public, "exposure.s3_console_public")
321
+ }
216
322
  };
217
323
  }
218
324
  function emptyGeneratedState() {
@@ -550,8 +656,10 @@ function renderOtelAgentConfig(config) {
550
656
  " exporters: [otlphttp/clickstack]"
551
657
  ].join("\n") + "\n";
552
658
  }
553
- function renderStack(config, runtimeEnv, includeAppServices) {
659
+ function renderStack(config, runtimeEnv, mode) {
554
660
  const hosts = domainMap(config.domain.root_domain);
661
+ const includeRuntimeInfra = mode !== "base";
662
+ const includeAppServices = mode === "full";
555
663
  const apiEnvironment = {
556
664
  ...runtimeEnv,
557
665
  OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-agent:4318",
@@ -644,7 +752,7 @@ ${renderEnvList({
644
752
  volumes:
645
753
  - keycloak_db_data:/var/lib/postgresql/data
646
754
  networks: [envsync]
647
-
755
+ ${includeRuntimeInfra ? `
648
756
  keycloak:
649
757
  image: ${config.images.keycloak}
650
758
  entrypoint: ["/bin/sh", "-lc"]
@@ -671,7 +779,7 @@ ${renderEnvList({
671
779
  - traefik.http.routers.keycloak.rule=Host(\`${hosts.auth}\`)
672
780
  - traefik.http.routers.keycloak.entrypoints=websecure
673
781
  - traefik.http.routers.keycloak.tls.certresolver=letsencrypt
674
- - traefik.http.services.keycloak.loadbalancer.server.port=8080
782
+ - traefik.http.services.keycloak.loadbalancer.server.port=8080` : ""}
675
783
 
676
784
  openfga_db:
677
785
  image: postgres:17
@@ -684,7 +792,7 @@ ${renderEnvList({
684
792
  volumes:
685
793
  - openfga_db_data:/var/lib/postgresql/data
686
794
  networks: [envsync]
687
-
795
+ ${includeRuntimeInfra ? `
688
796
  openfga:
689
797
  image: openfga/openfga:v1.12.0
690
798
  command: run
@@ -695,7 +803,7 @@ ${renderEnvList({
695
803
  OPENFGA_HTTP_ADDR: "0.0.0.0:8090",
696
804
  OPENFGA_GRPC_ADDR: "0.0.0.0:8091"
697
805
  })}
698
- networks: [envsync]
806
+ networks: [envsync]` : ""}
699
807
 
700
808
  minikms_db:
701
809
  image: postgres:17
@@ -708,7 +816,7 @@ ${renderEnvList({
708
816
  volumes:
709
817
  - minikms_db_data:/var/lib/postgresql/data
710
818
  networks: [envsync]
711
-
819
+ ${includeRuntimeInfra ? `
712
820
  minikms:
713
821
  image: ghcr.io/envsync-cloud/minikms:sha-735dfe8
714
822
  environment:
@@ -719,7 +827,7 @@ ${renderEnvList({
719
827
  MINIKMS_GRPC_ADDR: "0.0.0.0:50051",
720
828
  MINIKMS_TLS_ENABLED: "false"
721
829
  })}
722
- networks: [envsync]
830
+ networks: [envsync]` : ""}
723
831
 
724
832
  clickstack:
725
833
  image: ${config.images.clickstack}
@@ -797,8 +905,9 @@ function writeDeployArtifacts(config, generated) {
797
905
  writeFile(VERSIONS_LOCK, JSON.stringify(config.images, null, 2) + "\n");
798
906
  writeFile(KEYCLOAK_REALM_FILE, renderKeycloakRealm(config, runtimeEnv));
799
907
  writeFile(TRAEFIK_DYNAMIC_FILE, renderTraefikDynamicConfig(config));
800
- writeFile(STACK_FILE, renderStack(config, runtimeEnv, true));
801
- writeFile(BOOTSTRAP_STACK_FILE, renderStack(config, runtimeEnv, false));
908
+ writeFile(BOOTSTRAP_BASE_STACK_FILE, renderStack(config, runtimeEnv, "base"));
909
+ writeFile(BOOTSTRAP_STACK_FILE, renderStack(config, runtimeEnv, "bootstrap"));
910
+ writeFile(STACK_FILE, renderStack(config, runtimeEnv, "full"));
802
911
  writeFile(NGINX_WEB_CONF, renderNginxConf("web"));
803
912
  writeFile(NGINX_LANDING_CONF, renderNginxConf("landing"));
804
913
  writeFile(OTEL_AGENT_CONF, renderOtelAgentConfig(config));
@@ -840,6 +949,38 @@ function buildKeycloakImage(imageTag, repoRoot = REPO_ROOT) {
840
949
  function stackNetworkName(config) {
841
950
  return `${config.services.stack_name}_envsync`;
842
951
  }
952
+ function assertSwarmManager() {
953
+ const state = tryRun("docker", ["info", "--format", "{{.Swarm.LocalNodeState}}|{{.Swarm.ControlAvailable}}"], { quiet: true }).trim();
954
+ if (state !== "active|true") {
955
+ throw new Error("Docker Swarm is not initialized on this node. Run 'docker swarm init' or 'envsync-deploy preinstall' first.");
956
+ }
957
+ }
958
+ function waitForCommand(config, label, image, command, timeoutSeconds = 120, env = {}, volumes = []) {
959
+ const deadline = Date.now() + timeoutSeconds * 1e3;
960
+ while (Date.now() < deadline) {
961
+ const args = ["run", "--rm", "--network", stackNetworkName(config)];
962
+ for (const volume of volumes) {
963
+ args.push("-v", volume);
964
+ }
965
+ for (const [key, value] of Object.entries(env)) {
966
+ args.push("-e", `${key}=${value}`);
967
+ }
968
+ args.push(image, "sh", "-lc", command);
969
+ if (commandSucceeds("docker", args)) {
970
+ return;
971
+ }
972
+ sleepSeconds(2);
973
+ }
974
+ throw new Error(`Timed out waiting for ${label}`);
975
+ }
976
+ function waitForPostgresService(config, label, host, user, password) {
977
+ waitForCommand(config, `${label} database readiness`, "postgres:17", `pg_isready -h ${host} -U ${user}`, 120, {
978
+ PGPASSWORD: password
979
+ });
980
+ }
981
+ function waitForRedisService(config) {
982
+ waitForCommand(config, "redis readiness", "redis:7", "redis-cli -h redis ping | grep PONG");
983
+ }
843
984
  function waitForTcpService(config, label, host, port, timeoutSeconds = 120) {
844
985
  const deadline = Date.now() + timeoutSeconds * 1e3;
845
986
  while (Date.now() < deadline) {
@@ -853,6 +994,39 @@ function waitForTcpService(config, label, host, port, timeoutSeconds = 120) {
853
994
  }
854
995
  throw new Error(`Timed out waiting for ${label} at ${host}:${port}`);
855
996
  }
997
+ function waitForHttpService(config, label, url, timeoutSeconds = 120) {
998
+ waitForCommand(config, `${label} HTTP readiness`, "alpine:3.20", `wget -q -O /dev/null ${JSON.stringify(url)}`, timeoutSeconds);
999
+ }
1000
+ function runOpenFgaMigrate(config, runtimeEnv) {
1001
+ run("docker", [
1002
+ "run",
1003
+ "--rm",
1004
+ "--network",
1005
+ stackNetworkName(config),
1006
+ "-e",
1007
+ "OPENFGA_DATASTORE_ENGINE=postgres",
1008
+ "-e",
1009
+ `OPENFGA_DATASTORE_URI=postgres://openfga:${runtimeEnv.OPENFGA_DB_PASSWORD}@openfga_db:5432/openfga?sslmode=disable`,
1010
+ "openfga/openfga:v1.12.0",
1011
+ "migrate"
1012
+ ]);
1013
+ }
1014
+ function runMiniKmsMigrate(config, runtimeEnv) {
1015
+ run("docker", [
1016
+ "run",
1017
+ "--rm",
1018
+ "--network",
1019
+ stackNetworkName(config),
1020
+ "-e",
1021
+ `PGPASSWORD=${runtimeEnv.MINIKMS_DB_PASSWORD}`,
1022
+ "-v",
1023
+ `${path.join(REPO_ROOT, "docker/minikms/migrations")}:/migrations:ro`,
1024
+ "postgres:17",
1025
+ "sh",
1026
+ "-lc",
1027
+ "psql -h minikms_db -U postgres -d minikms -f /migrations/001_initial_schema.sql && psql -h minikms_db -U postgres -d minikms -f /migrations/002_vault_storage.sql"
1028
+ ]);
1029
+ }
856
1030
  function runBootstrapInit(config) {
857
1031
  const output = run(
858
1032
  "docker",
@@ -969,7 +1143,9 @@ async function cmdPreinstall() {
969
1143
  async function cmdSetup() {
970
1144
  const rootDomain = await ask("Root domain", "example.com");
971
1145
  const acmeEmail = await ask("ACME email", `admin@${rootDomain}`);
972
- const channel = await ask("Release channel", "stable");
1146
+ const releaseVersion = await ask("Release version", getDeployCliVersion());
1147
+ assertSemverVersion(releaseVersion, "release version");
1148
+ const releaseImages = versionedImages(releaseVersion);
973
1149
  const adminUser = await ask("Keycloak admin user", "admin");
974
1150
  const adminPassword = await ask("Keycloak admin password", randomSecret(12));
975
1151
  const smtpHost = await ask("SMTP host", "smtp.example.com");
@@ -983,13 +1159,16 @@ async function cmdSetup() {
983
1159
  const publicObs = await ask("Expose obs.<domain> publicly (true/false)", "true") === "true";
984
1160
  const mailpitEnabled = await ask("Enable mailpit (true/false)", "false") === "true";
985
1161
  const config = {
986
- source: defaultSourceConfig(),
1162
+ source: defaultSourceConfig(releaseVersion),
1163
+ release: {
1164
+ version: releaseVersion
1165
+ },
987
1166
  domain: { root_domain: rootDomain, acme_email: acmeEmail },
988
1167
  images: {
989
- api: `ghcr.io/envsync-cloud/envsync-api:${channel}`,
990
- keycloak: `envsync-keycloak:${channel}`,
991
- web: `ghcr.io/envsync-cloud/envsync-web-static:${channel}`,
992
- landing: `ghcr.io/envsync-cloud/envsync-landing-static:${channel}`,
1168
+ api: releaseImages.api,
1169
+ keycloak: releaseImages.keycloak,
1170
+ web: releaseImages.web,
1171
+ landing: releaseImages.landing,
993
1172
  clickstack: "clickhouse/clickstack-all-in-one:latest",
994
1173
  traefik: "traefik:v3.1",
995
1174
  otel_agent: "otel/opentelemetry-collector-contrib:0.111.0"
@@ -1034,8 +1213,7 @@ async function cmdSetup() {
1034
1213
  mailpit_enabled: mailpitEnabled,
1035
1214
  s3_public: true,
1036
1215
  s3_console_public: true
1037
- },
1038
- release_channel: channel
1216
+ }
1039
1217
  };
1040
1218
  saveDesiredConfig(config);
1041
1219
  console.log(`Config written to ${DEPLOY_YAML}`);
@@ -1046,15 +1224,23 @@ async function cmdSetup() {
1046
1224
  async function cmdBootstrap() {
1047
1225
  const { config, generated } = loadState();
1048
1226
  const nextGenerated = ensureGeneratedRuntimeState(generated);
1227
+ const runtimeEnv = buildRuntimeEnv(config, nextGenerated);
1228
+ assertSwarmManager();
1049
1229
  ensureRepoCheckout(config);
1050
1230
  writeDeployArtifacts(config, nextGenerated);
1051
1231
  buildKeycloakImage(config.images.keycloak);
1052
- run("docker", ["stack", "deploy", "-c", BOOTSTRAP_STACK_FILE, config.services.stack_name]);
1053
- waitForTcpService(config, "postgres", "postgres", 5432);
1054
- waitForTcpService(config, "redis", "redis", 6379);
1232
+ run("docker", ["stack", "deploy", "-c", BOOTSTRAP_BASE_STACK_FILE, config.services.stack_name]);
1233
+ waitForPostgresService(config, "postgres", "postgres", "postgres", "envsync-postgres");
1234
+ waitForRedisService(config);
1055
1235
  waitForTcpService(config, "rustfs", "rustfs", 9e3);
1056
- waitForTcpService(config, "keycloak", "keycloak", 8080);
1057
- waitForTcpService(config, "openfga", "openfga", 8090);
1236
+ waitForPostgresService(config, "keycloak", "keycloak_db", "keycloak", runtimeEnv.KEYCLOAK_ADMIN_PASSWORD);
1237
+ waitForPostgresService(config, "openfga", "openfga_db", "openfga", runtimeEnv.OPENFGA_DB_PASSWORD);
1238
+ waitForPostgresService(config, "minikms", "minikms_db", "postgres", runtimeEnv.MINIKMS_DB_PASSWORD);
1239
+ runOpenFgaMigrate(config, runtimeEnv);
1240
+ runMiniKmsMigrate(config, runtimeEnv);
1241
+ run("docker", ["stack", "deploy", "-c", BOOTSTRAP_STACK_FILE, config.services.stack_name]);
1242
+ waitForHttpService(config, "keycloak", "http://keycloak:8080/realms/master");
1243
+ waitForHttpService(config, "openfga", "http://openfga:8090/stores");
1058
1244
  waitForTcpService(config, "minikms", "minikms", 50051);
1059
1245
  const initResult = runBootstrapInit(config);
1060
1246
  const bootstrappedGenerated = normalizeGeneratedState({
@@ -1072,7 +1258,12 @@ async function cmdBootstrap() {
1072
1258
  }
1073
1259
  async function cmdDeploy() {
1074
1260
  const { config, generated } = loadState();
1261
+ assertSwarmManager();
1075
1262
  assertBootstrapState(generated);
1263
+ const services = listStackServices(config);
1264
+ if (serviceHealth(services, `${config.services.stack_name}_keycloak`) === "missing" || serviceHealth(services, `${config.services.stack_name}_openfga`) === "missing" || serviceHealth(services, `${config.services.stack_name}_minikms`) === "missing") {
1265
+ throw new Error("Bootstrap has not completed successfully. Run 'envsync-deploy bootstrap' again.");
1266
+ }
1076
1267
  ensureRepoCheckout(config);
1077
1268
  writeDeployArtifacts(config, generated);
1078
1269
  buildKeycloakImage(config.images.keycloak);
@@ -1124,7 +1315,10 @@ async function cmdHealth(asJson) {
1124
1315
  }
1125
1316
  async function cmdUpgrade() {
1126
1317
  const { config } = loadState();
1127
- config.images.api = `ghcr.io/envsync-cloud/envsync-api:${config.release_channel}`;
1318
+ config.images = {
1319
+ ...config.images,
1320
+ ...versionedImages(config.release.version)
1321
+ };
1128
1322
  saveDesiredConfig(config);
1129
1323
  await cmdDeploy();
1130
1324
  }
@@ -1187,6 +1381,7 @@ async function cmdBackup() {
1187
1381
  writeFile(path.join(staged, "deploy.yaml"), fs.readFileSync(DEPLOY_YAML, "utf8"));
1188
1382
  writeFile(path.join(staged, "config.json"), fs.readFileSync(INTERNAL_CONFIG_JSON, "utf8"));
1189
1383
  writeFile(path.join(staged, "versions.lock.json"), fs.readFileSync(VERSIONS_LOCK, "utf8"));
1384
+ writeFile(path.join(staged, "docker-stack.bootstrap.base.yaml"), fs.readFileSync(BOOTSTRAP_BASE_STACK_FILE, "utf8"));
1190
1385
  writeFile(path.join(staged, "docker-stack.bootstrap.yaml"), fs.readFileSync(BOOTSTRAP_STACK_FILE, "utf8"));
1191
1386
  writeFile(path.join(staged, "docker-stack.yaml"), fs.readFileSync(STACK_FILE, "utf8"));
1192
1387
  writeFile(path.join(staged, "traefik-dynamic.yaml"), fs.readFileSync(TRAEFIK_DYNAMIC_FILE, "utf8"));
@@ -1216,6 +1411,7 @@ async function cmdRestore(archivePath) {
1216
1411
  writeFile(DEPLOY_YAML, fs.readFileSync(path.join(restoreRoot, "deploy.yaml"), "utf8"));
1217
1412
  writeFile(INTERNAL_CONFIG_JSON, fs.readFileSync(path.join(restoreRoot, "config.json"), "utf8"));
1218
1413
  writeFile(VERSIONS_LOCK, fs.readFileSync(path.join(restoreRoot, "versions.lock.json"), "utf8"));
1414
+ writeFile(BOOTSTRAP_BASE_STACK_FILE, fs.readFileSync(path.join(restoreRoot, "docker-stack.bootstrap.base.yaml"), "utf8"));
1219
1415
  writeFile(BOOTSTRAP_STACK_FILE, fs.readFileSync(path.join(restoreRoot, "docker-stack.bootstrap.yaml"), "utf8"));
1220
1416
  writeFile(STACK_FILE, fs.readFileSync(path.join(restoreRoot, "docker-stack.yaml"), "utf8"));
1221
1417
  writeFile(TRAEFIK_DYNAMIC_FILE, fs.readFileSync(path.join(restoreRoot, "traefik-dynamic.yaml"), "utf8"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envsync-cloud/deploy-cli",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "description": "CLI for self-hosted EnvSync deployment on Docker Swarm",
5
5
  "type": "module",
6
6
  "bin": {