@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.
- package/README.md +3 -1
- package/dist/index.js +223 -27
- 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
|
|
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${
|
|
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
|
-
...
|
|
215
|
-
source:
|
|
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,
|
|
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(
|
|
801
|
-
writeFile(BOOTSTRAP_STACK_FILE, renderStack(config, runtimeEnv,
|
|
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
|
|
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:
|
|
990
|
-
keycloak:
|
|
991
|
-
web:
|
|
992
|
-
landing:
|
|
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",
|
|
1053
|
-
|
|
1054
|
-
|
|
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
|
-
|
|
1057
|
-
|
|
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
|
|
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"));
|