@envsync-cloud/deploy-cli 0.6.10 → 0.6.12
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 +8 -1
- package/dist/index.js +138 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -81,9 +81,16 @@ npx @envsync-cloud/deploy-cli deploy
|
|
|
81
81
|
|
|
82
82
|
The staged flow is:
|
|
83
83
|
- `setup` writes desired config
|
|
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
|
|
84
|
+
- `bootstrap` resets the existing EnvSync deployment, then starts base infra, runs OpenFGA and miniKMS migrations, starts runtime infra, initializes ClickStack sources and dashboards, and persists generated runtime env state
|
|
85
85
|
- `deploy` starts the pending API and frontend services
|
|
86
86
|
|
|
87
|
+
Self-hosted observability routing is:
|
|
88
|
+
- `https://obs.<root-domain>/` for ClickStack UI
|
|
89
|
+
- `https://obs.<root-domain>/api/...` for ClickStack API
|
|
90
|
+
- `https://obs.<root-domain>/v1/{traces,logs,metrics}` for browser OTLP
|
|
91
|
+
|
|
92
|
+
Both frontends receive `otelEndpoint = https://obs.<root-domain>` in the generated `runtime-config.js`.
|
|
93
|
+
|
|
87
94
|
Check service health:
|
|
88
95
|
|
|
89
96
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ var KEYCLOAK_REALM_FILE = "/opt/envsync/deploy/keycloak-realm.envsync.json";
|
|
|
25
25
|
var NGINX_WEB_CONF = "/opt/envsync/deploy/nginx-web.conf";
|
|
26
26
|
var NGINX_LANDING_CONF = "/opt/envsync/deploy/nginx-landing.conf";
|
|
27
27
|
var OTEL_AGENT_CONF = "/opt/envsync/deploy/otel-agent.yaml";
|
|
28
|
+
var CLICKSTACK_CLICKHOUSE_CONF = "/opt/envsync/deploy/clickhouse-listen.xml";
|
|
28
29
|
var INTERNAL_CONFIG_JSON = "/opt/envsync/deploy/config.json";
|
|
29
30
|
var STACK_VOLUMES = [
|
|
30
31
|
"postgres_data",
|
|
@@ -611,6 +612,19 @@ function renderTraefikDynamicConfig(config) {
|
|
|
611
612
|
" stsSeconds: 31536000",
|
|
612
613
|
" gzip:",
|
|
613
614
|
" compress: {}",
|
|
615
|
+
" otel-cors:",
|
|
616
|
+
" headers:",
|
|
617
|
+
" accessControlAllowOriginList:",
|
|
618
|
+
` - https://${hosts.landing}`,
|
|
619
|
+
` - https://${hosts.app}`,
|
|
620
|
+
" accessControlAllowMethods:",
|
|
621
|
+
" - POST",
|
|
622
|
+
" - OPTIONS",
|
|
623
|
+
" accessControlAllowHeaders:",
|
|
624
|
+
" - Content-Type",
|
|
625
|
+
" - Authorization",
|
|
626
|
+
" accessControlAllowCredentials: false",
|
|
627
|
+
" addVaryHeader: true",
|
|
614
628
|
" services:",
|
|
615
629
|
" envsync-api:",
|
|
616
630
|
" weighted:",
|
|
@@ -635,7 +649,15 @@ function renderTraefikDynamicConfig(config) {
|
|
|
635
649
|
" loadBalancer:",
|
|
636
650
|
" servers:",
|
|
637
651
|
" - url: http://web_nginx:8080",
|
|
638
|
-
"
|
|
652
|
+
" clickstack-ui:",
|
|
653
|
+
" loadBalancer:",
|
|
654
|
+
" servers:",
|
|
655
|
+
" - url: http://clickstack:8080",
|
|
656
|
+
" clickstack-api:",
|
|
657
|
+
" loadBalancer:",
|
|
658
|
+
" servers:",
|
|
659
|
+
" - url: http://clickstack:8000",
|
|
660
|
+
" clickstack-otlp:",
|
|
639
661
|
" loadBalancer:",
|
|
640
662
|
" servers:",
|
|
641
663
|
` - url: http://clickstack:${config.services.clickstack_otlp_http_port}`,
|
|
@@ -650,16 +672,23 @@ function renderTraefikDynamicConfig(config) {
|
|
|
650
672
|
" service: web",
|
|
651
673
|
" entryPoints: [websecure]",
|
|
652
674
|
" tls: {}",
|
|
653
|
-
"
|
|
654
|
-
` rule: Host(\`${hosts.
|
|
655
|
-
" service:
|
|
675
|
+
" obs-otlp-router:",
|
|
676
|
+
` rule: Host(\`${hosts.obs}\`) && (PathPrefix(\`/v1/traces\`) || PathPrefix(\`/v1/logs\`) || PathPrefix(\`/v1/metrics\`))`,
|
|
677
|
+
" service: clickstack-otlp",
|
|
678
|
+
" middlewares: [otel-cors]",
|
|
656
679
|
" priority: 100",
|
|
657
680
|
" entryPoints: [websecure]",
|
|
658
681
|
" tls: {}",
|
|
659
|
-
"
|
|
660
|
-
` rule: Host(\`${hosts.
|
|
661
|
-
" service:
|
|
662
|
-
" priority:
|
|
682
|
+
" obs-api-router:",
|
|
683
|
+
` rule: Host(\`${hosts.obs}\`) && PathPrefix(\`/api\`)`,
|
|
684
|
+
" service: clickstack-api",
|
|
685
|
+
" priority: 90",
|
|
686
|
+
" entryPoints: [websecure]",
|
|
687
|
+
" tls: {}",
|
|
688
|
+
" obs-ui-router:",
|
|
689
|
+
` rule: Host(\`${hosts.obs}\`)`,
|
|
690
|
+
" service: clickstack-ui",
|
|
691
|
+
" priority: 10",
|
|
663
692
|
" entryPoints: [websecure]",
|
|
664
693
|
" tls: {}",
|
|
665
694
|
" api-router:",
|
|
@@ -684,7 +713,7 @@ function renderNginxConf(kind) {
|
|
|
684
713
|
}
|
|
685
714
|
function renderFrontendRuntimeConfig(config, kind) {
|
|
686
715
|
const hosts = domainMap(config.domain.root_domain);
|
|
687
|
-
const otelEndpoint =
|
|
716
|
+
const otelEndpoint = `https://${hosts.obs}`;
|
|
688
717
|
return `window.__ENVSYNC_RUNTIME_CONFIG__ = ${JSON.stringify({
|
|
689
718
|
apiBaseUrl: `https://${hosts.api}`,
|
|
690
719
|
appBaseUrl: `https://${hosts.app}`,
|
|
@@ -729,10 +758,23 @@ function renderOtelAgentConfig(config) {
|
|
|
729
758
|
" exporters: [otlphttp/clickstack]"
|
|
730
759
|
].join("\n") + "\n";
|
|
731
760
|
}
|
|
761
|
+
function renderClickstackClickHouseConfig() {
|
|
762
|
+
return [
|
|
763
|
+
"<clickhouse>",
|
|
764
|
+
" <listen_host>0.0.0.0</listen_host>",
|
|
765
|
+
" <listen_try>1</listen_try>",
|
|
766
|
+
"</clickhouse>"
|
|
767
|
+
].join("\n") + "\n";
|
|
768
|
+
}
|
|
732
769
|
function renderStack(config, runtimeEnv, mode) {
|
|
733
770
|
const hosts = domainMap(config.domain.root_domain);
|
|
734
771
|
const includeRuntimeInfra = mode !== "base";
|
|
735
772
|
const includeAppServices = mode === "full";
|
|
773
|
+
const stackName = config.services.stack_name;
|
|
774
|
+
const s3RouterName = `${stackName}-s3-router`;
|
|
775
|
+
const s3ServiceName = `${stackName}-s3-service`;
|
|
776
|
+
const s3ConsoleRouterName = `${stackName}-s3-console-router`;
|
|
777
|
+
const s3ConsoleServiceName = `${stackName}-s3-console-service`;
|
|
736
778
|
const apiEnvironment = {
|
|
737
779
|
...runtimeEnv,
|
|
738
780
|
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-agent:4318",
|
|
@@ -809,16 +851,16 @@ ${renderEnvList({
|
|
|
809
851
|
deploy:
|
|
810
852
|
labels:
|
|
811
853
|
- traefik.enable=true
|
|
812
|
-
- traefik.http.routers.
|
|
813
|
-
- traefik.http.routers.
|
|
814
|
-
- traefik.http.routers.
|
|
815
|
-
- traefik.http.routers.
|
|
816
|
-
- traefik.http.services.
|
|
817
|
-
- traefik.http.routers.
|
|
818
|
-
- traefik.http.routers.
|
|
819
|
-
- traefik.http.routers.
|
|
820
|
-
- traefik.http.routers.
|
|
821
|
-
- traefik.http.services.
|
|
854
|
+
- traefik.http.routers.${s3RouterName}.rule=Host(\`${hosts.s3}\`)
|
|
855
|
+
- traefik.http.routers.${s3RouterName}.entrypoints=websecure
|
|
856
|
+
- traefik.http.routers.${s3RouterName}.tls.certresolver=letsencrypt
|
|
857
|
+
- traefik.http.routers.${s3RouterName}.service=${s3ServiceName}
|
|
858
|
+
- traefik.http.services.${s3ServiceName}.loadbalancer.server.port=9000
|
|
859
|
+
- traefik.http.routers.${s3ConsoleRouterName}.rule=Host(\`${hosts.s3Console}\`)
|
|
860
|
+
- traefik.http.routers.${s3ConsoleRouterName}.entrypoints=websecure
|
|
861
|
+
- traefik.http.routers.${s3ConsoleRouterName}.tls.certresolver=letsencrypt
|
|
862
|
+
- traefik.http.routers.${s3ConsoleRouterName}.service=${s3ConsoleServiceName}
|
|
863
|
+
- traefik.http.services.${s3ConsoleServiceName}.loadbalancer.server.port=9001
|
|
822
864
|
|
|
823
865
|
keycloak_db:
|
|
824
866
|
image: postgres:17
|
|
@@ -911,18 +953,26 @@ ${renderEnvList({
|
|
|
911
953
|
|
|
912
954
|
clickstack:
|
|
913
955
|
image: ${config.images.clickstack}
|
|
956
|
+
environment:
|
|
957
|
+
${renderEnvList({
|
|
958
|
+
HYPERDX_APP_URL: `https://${hosts.obs}`,
|
|
959
|
+
HYPERDX_APP_PORT: "443",
|
|
960
|
+
HYPERDX_API_URL: `https://${hosts.obs}`,
|
|
961
|
+
HYPERDX_API_PORT: "443",
|
|
962
|
+
FRONTEND_URL: `https://${hosts.obs}`
|
|
963
|
+
})}
|
|
914
964
|
volumes:
|
|
915
965
|
- clickstack_data:/data/db
|
|
916
966
|
- clickstack_ch_data:/var/lib/clickhouse
|
|
917
967
|
- clickstack_ch_logs:/var/log/clickhouse-server
|
|
968
|
+
- ${CLICKSTACK_CLICKHOUSE_CONF}:/etc/clickhouse-server/config.d/envsync-listen-host.xml:ro
|
|
918
969
|
networks: [envsync]
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
- traefik.http.services.obs.loadbalancer.server.port=8080
|
|
970
|
+
healthcheck:
|
|
971
|
+
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:8080 || exit 1"]
|
|
972
|
+
interval: 30s
|
|
973
|
+
timeout: 10s
|
|
974
|
+
retries: 10
|
|
975
|
+
start_period: 180s
|
|
926
976
|
|
|
927
977
|
otel-agent:
|
|
928
978
|
image: ${config.images.otel_agent}
|
|
@@ -992,6 +1042,7 @@ function writeDeployArtifacts(config, generated) {
|
|
|
992
1042
|
writeFileMaybe(NGINX_WEB_CONF, renderNginxConf("web"));
|
|
993
1043
|
writeFileMaybe(NGINX_LANDING_CONF, renderNginxConf("landing"));
|
|
994
1044
|
writeFileMaybe(OTEL_AGENT_CONF, renderOtelAgentConfig(config));
|
|
1045
|
+
writeFileMaybe(CLICKSTACK_CLICKHOUSE_CONF, renderClickstackClickHouseConfig());
|
|
995
1046
|
logSuccess(currentOptions.dryRun ? "Deploy artifacts previewed" : "Deploy artifacts written");
|
|
996
1047
|
}
|
|
997
1048
|
function saveDesiredConfig(config) {
|
|
@@ -1265,6 +1316,32 @@ function runBootstrapInit(config) {
|
|
|
1265
1316
|
openfgaModelId: result.openfgaModelId
|
|
1266
1317
|
};
|
|
1267
1318
|
}
|
|
1319
|
+
function runClickstackBootstrap(config) {
|
|
1320
|
+
logStep("Running ClickStack self-host bootstrap");
|
|
1321
|
+
if (currentOptions.dryRun) {
|
|
1322
|
+
logDryRun("Would bootstrap ClickStack sources and dashboards");
|
|
1323
|
+
logCommand("node", [path.join(REPO_ROOT, "scripts/bootstrap-clickstack-selfhost.mjs")]);
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
const deadline = Date.now() + 180 * 1e3;
|
|
1327
|
+
let lastError = "unknown error";
|
|
1328
|
+
while (Date.now() < deadline) {
|
|
1329
|
+
try {
|
|
1330
|
+
run("node", [path.join(REPO_ROOT, "scripts/bootstrap-clickstack-selfhost.mjs")], {
|
|
1331
|
+
env: {
|
|
1332
|
+
ENVSYNC_STACK_NAME: config.services.stack_name,
|
|
1333
|
+
ENVSYNC_ROOT_DOMAIN: config.domain.root_domain
|
|
1334
|
+
}
|
|
1335
|
+
});
|
|
1336
|
+
logSuccess("ClickStack self-host bootstrap completed");
|
|
1337
|
+
return;
|
|
1338
|
+
} catch (error) {
|
|
1339
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
1340
|
+
sleepSeconds(3);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
throw new Error(`Timed out bootstrapping ClickStack: ${lastError}`);
|
|
1344
|
+
}
|
|
1268
1345
|
function parseBootstrapInitJson(output) {
|
|
1269
1346
|
const trimmed = output.trim();
|
|
1270
1347
|
if (!trimmed) {
|
|
@@ -1591,7 +1668,20 @@ async function cmdBootstrap() {
|
|
|
1591
1668
|
waitForHttpService(config, "keycloak management readiness", "http://keycloak:9000/health/ready", 180);
|
|
1592
1669
|
waitForHttpService(config, "openfga", "http://openfga:8090/stores");
|
|
1593
1670
|
waitForTcpService(config, "minikms", "minikms", 50051);
|
|
1671
|
+
waitForHttpService(config, "clickstack ui", "http://clickstack:8080", 180);
|
|
1594
1672
|
const initResult = runBootstrapInit(config);
|
|
1673
|
+
const persistedGenerated = normalizeGeneratedState({
|
|
1674
|
+
openfga: {
|
|
1675
|
+
store_id: initResult.openfgaStoreId,
|
|
1676
|
+
model_id: initResult.openfgaModelId
|
|
1677
|
+
},
|
|
1678
|
+
secrets: nextGenerated.secrets,
|
|
1679
|
+
bootstrap: nextGenerated.bootstrap
|
|
1680
|
+
});
|
|
1681
|
+
if (!currentOptions.dryRun) {
|
|
1682
|
+
writeDeployArtifacts(config, persistedGenerated);
|
|
1683
|
+
}
|
|
1684
|
+
runClickstackBootstrap(config);
|
|
1595
1685
|
if (currentOptions.dryRun) {
|
|
1596
1686
|
logDryRun("Skipping generated OpenFGA ID persistence in preview mode");
|
|
1597
1687
|
logSuccess("Bootstrap dry-run completed");
|
|
@@ -1654,6 +1744,7 @@ async function cmdHealth(asJson) {
|
|
|
1654
1744
|
const hosts = domainMap(config.domain.root_domain);
|
|
1655
1745
|
const services = listStackServices(config);
|
|
1656
1746
|
const stackName = config.services.stack_name;
|
|
1747
|
+
const traefikDynamic = exists(TRAEFIK_DYNAMIC_FILE) ? fs.readFileSync(TRAEFIK_DYNAMIC_FILE, "utf8") : "";
|
|
1657
1748
|
const bootstrapServices = {
|
|
1658
1749
|
postgres: serviceHealth(services, `${stackName}_postgres`),
|
|
1659
1750
|
redis: serviceHealth(services, `${stackName}_redis`),
|
|
@@ -1673,6 +1764,25 @@ async function cmdHealth(asJson) {
|
|
|
1673
1764
|
web: serviceHealth(services, `${stackName}_web_nginx`),
|
|
1674
1765
|
landing: serviceHealth(services, `${stackName}_landing_nginx`)
|
|
1675
1766
|
},
|
|
1767
|
+
observability: {
|
|
1768
|
+
service: serviceHealth(services, `${stackName}_clickstack`),
|
|
1769
|
+
obs_ui: {
|
|
1770
|
+
url: `https://${hosts.obs}`,
|
|
1771
|
+
configured: traefikDynamic.includes("obs-ui-router")
|
|
1772
|
+
},
|
|
1773
|
+
obs_api: {
|
|
1774
|
+
url: `https://${hosts.obs}/api`,
|
|
1775
|
+
configured: traefikDynamic.includes("obs-api-router")
|
|
1776
|
+
},
|
|
1777
|
+
obs_otlp: {
|
|
1778
|
+
url: `https://${hosts.obs}/v1/traces`,
|
|
1779
|
+
configured: traefikDynamic.includes("obs-otlp-router")
|
|
1780
|
+
},
|
|
1781
|
+
frontend_otel_endpoint: {
|
|
1782
|
+
web: `https://${hosts.obs}`,
|
|
1783
|
+
landing: `https://${hosts.obs}`
|
|
1784
|
+
}
|
|
1785
|
+
},
|
|
1676
1786
|
public: {
|
|
1677
1787
|
landing: `https://${hosts.landing}`,
|
|
1678
1788
|
app: `https://${hosts.app}`,
|
|
@@ -1794,6 +1904,7 @@ async function cmdBackup() {
|
|
|
1794
1904
|
writeFile(path.join(staged, "traefik-dynamic.yaml"), fs.readFileSync(TRAEFIK_DYNAMIC_FILE, "utf8"));
|
|
1795
1905
|
writeFile(path.join(staged, "keycloak-realm.envsync.json"), fs.readFileSync(KEYCLOAK_REALM_FILE, "utf8"));
|
|
1796
1906
|
writeFile(path.join(staged, "otel-agent.yaml"), fs.readFileSync(OTEL_AGENT_CONF, "utf8"));
|
|
1907
|
+
writeFile(path.join(staged, "clickhouse-listen.xml"), fs.readFileSync(CLICKSTACK_CLICKHOUSE_CONF, "utf8"));
|
|
1797
1908
|
const volumesDir = path.join(staged, "volumes");
|
|
1798
1909
|
for (const volume of STACK_VOLUMES) {
|
|
1799
1910
|
backupDockerVolume(stackVolumeName(config, volume), path.join(volumesDir, volume));
|
|
@@ -1834,6 +1945,7 @@ async function cmdRestore(archivePath) {
|
|
|
1834
1945
|
writeFile(TRAEFIK_DYNAMIC_FILE, fs.readFileSync(path.join(restoreRoot, "traefik-dynamic.yaml"), "utf8"));
|
|
1835
1946
|
writeFile(KEYCLOAK_REALM_FILE, fs.readFileSync(path.join(restoreRoot, "keycloak-realm.envsync.json"), "utf8"));
|
|
1836
1947
|
writeFile(OTEL_AGENT_CONF, fs.readFileSync(path.join(restoreRoot, "otel-agent.yaml"), "utf8"));
|
|
1948
|
+
writeFile(CLICKSTACK_CLICKHOUSE_CONF, fs.readFileSync(path.join(restoreRoot, "clickhouse-listen.xml"), "utf8"));
|
|
1837
1949
|
const config = loadConfig();
|
|
1838
1950
|
for (const volume of STACK_VOLUMES) {
|
|
1839
1951
|
restoreDockerVolume(stackVolumeName(config, volume), path.join(restoreRoot, "volumes", volume));
|