import { shQuote } from "../platform-infra-public-service"; import type { ExternalActiveSecretMaterial, Sub2ApiConfig, Sub2ApiHostDockerConfig, Sub2ApiTargetConfig } from "./entry"; import { requiredSecretKeys } from "./entry"; import { imageRef, targetDatabase, targetDependencyImages, targetImage } from "./manifest"; export function requireHostDockerConfig(target: Sub2ApiTargetConfig): Sub2ApiHostDockerConfig { if (target.hostDocker === null) throw new Error(`target ${target.id} requires hostDocker config when runtimeMode=host-docker`); return target.hostDocker; } export function hostDockerPlanSummary(sub2api: Sub2ApiConfig, target: Sub2ApiTargetConfig): Record { const host = requireHostDockerConfig(target); const database = targetDatabase(sub2api, target); return { runtimeMode: "host-docker", route: target.route, projectName: host.projectName, workDir: host.workDir, composePath: host.composePath, envPath: host.envPath, appDataDir: host.appDataDir, appPort: host.appPort, redisPort: host.redisPort, databaseHost: database.host, databasePort: database.port, databaseSslMode: database.sslMode, noProxy: host.noProxy, valuesPrinted: false, }; } export function hostDockerComposeYaml(sub2api: Sub2ApiConfig, target: Sub2ApiTargetConfig): string { const host = requireHostDockerConfig(target); const dependencyImages = targetDependencyImages(sub2api, target); return `services: redis: image: ${yamlString(dependencyImages.redis)} container_name: ${yamlString(`${host.projectName}-redis`)} network_mode: host restart: unless-stopped command: - redis-server - --save - "" - --appendonly - "no" - --bind - "127.0.0.1" - --port - ${yamlString(String(host.redisPort))} app: image: ${yamlString(imageRef(sub2api, target))} container_name: ${yamlString(`${host.projectName}-app`)} network_mode: host restart: unless-stopped env_file: - ${yamlString(host.envPath)} volumes: - ${yamlString(`${host.appDataDir}:/app/data`)} depends_on: - redis `; } export function hostDockerEnvFile(sub2api: Sub2ApiConfig, target: Sub2ApiTargetConfig, secretMaterial: ExternalActiveSecretMaterial): string { const host = requireHostDockerConfig(target); const database = targetDatabase(sub2api, target); const urlAllowlist = sub2api.security.urlAllowlist; const values: Record = { AUTO_SETUP: "true", SERVER_HOST: "127.0.0.1", SERVER_PORT: String(host.appPort), SERVER_MODE: "release", RUN_MODE: "standard", DATABASE_HOST: database.host, DATABASE_PORT: String(database.port), DATABASE_USER: database.user, DATABASE_DBNAME: database.dbName, DATABASE_SSLMODE: database.sslMode, DATABASE_PASSWORD: secretMaterial.values.POSTGRES_PASSWORD, DATABASE_MAX_OPEN_CONNS: "10", DATABASE_MAX_IDLE_CONNS: "2", DATABASE_CONN_MAX_LIFETIME_MINUTES: "30", DATABASE_CONN_MAX_IDLE_TIME_MINUTES: "5", REDIS_HOST: "127.0.0.1", REDIS_PORT: String(host.redisPort), REDIS_PASSWORD: "", REDIS_DB: "0", REDIS_POOL_SIZE: "32", REDIS_MIN_IDLE_CONNS: "2", REDIS_ENABLE_TLS: "false", ADMIN_EMAIL: "admin@sub2api.platform-infra.local", ADMIN_PASSWORD: secretMaterial.values.ADMIN_PASSWORD, JWT_SECRET: secretMaterial.values.JWT_SECRET, TOTP_ENCRYPTION_KEY: secretMaterial.values.TOTP_ENCRYPTION_KEY, JWT_EXPIRE_HOUR: "24", TZ: "Asia/Shanghai", SECURITY_URL_ALLOWLIST_ENABLED: String(urlAllowlist.enabled), SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP: String(urlAllowlist.allowInsecureHttp), SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS: String(urlAllowlist.allowPrivateHosts), SECURITY_URL_ALLOWLIST_UPSTREAM_HOSTS: urlAllowlist.upstreamHosts.join(","), UPDATE_PROXY_URL: "", HTTP_PROXY: "", HTTPS_PROXY: "", ALL_PROXY: "", http_proxy: "", https_proxy: "", all_proxy: "", NO_PROXY: host.noProxy.join(","), no_proxy: host.noProxy.join(","), GATEWAY_OPENAI_RESPONSE_HEADER_TIMEOUT: "0", GATEWAY_OPENAI_HTTP2_ENABLED: "true", GATEWAY_OPENAI_HTTP2_ALLOW_PROXY_FALLBACK_TO_HTTP1: "true", GATEWAY_OPENAI_HTTP2_FALLBACK_ERROR_THRESHOLD: "2", GATEWAY_OPENAI_HTTP2_FALLBACK_WINDOW_SECONDS: "60", GATEWAY_OPENAI_HTTP2_FALLBACK_TTL_SECONDS: "600", GATEWAY_IMAGE_STREAM_DATA_INTERVAL_TIMEOUT: "900", GATEWAY_IMAGE_STREAM_KEEPALIVE_INTERVAL: "10", GATEWAY_IMAGE_CONCURRENCY_ENABLED: "false", GATEWAY_IMAGE_CONCURRENCY_MAX_CONCURRENT_REQUESTS: "0", GATEWAY_IMAGE_CONCURRENCY_OVERFLOW_MODE: "reject", GATEWAY_IMAGE_CONCURRENCY_WAIT_TIMEOUT_SECONDS: "30", GATEWAY_IMAGE_CONCURRENCY_MAX_WAITING_REQUESTS: "100", }; return `${Object.entries(values).map(([key, value]) => `${key}=${envFileValue(value)}`).join("\n")}\n`; } export function hostDockerDryRunScript(sub2api: Sub2ApiConfig, target: Sub2ApiTargetConfig): string { const host = requireHostDockerConfig(target); const compose = hostDockerComposeYaml(sub2api, target); const composeB64 = Buffer.from(compose, "utf8").toString("base64"); return ` set -u tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT compose="$tmp/docker-compose.yml" printf '%s' '${composeB64}' | base64 -d >"$compose" docker_out="$tmp/docker.out" docker_err="$tmp/docker.err" compose_out="$tmp/compose.out" compose_err="$tmp/compose.err" ports_out="$tmp/ports.out" ports_err="$tmp/ports.err" docker_bin="" if docker ps >/dev/null 2>&1; then docker_bin="docker" elif sudo docker ps >/dev/null 2>&1; then docker_bin="sudo docker" fi if [ -n "$docker_bin" ]; then $docker_bin ps --format '{{json .}}' >"$docker_out" 2>"$docker_err" docker_rc=$? if $docker_bin compose version >"$compose_out" 2>"$compose_err"; then compose_rc=0 elif command -v docker-compose >/dev/null 2>&1 && docker-compose version >"$compose_out" 2>"$compose_err"; then compose_rc=0 else compose_rc=0 printf '%s\\n' 'docker compose is absent; apply will use raw docker run fallback' >"$compose_out" fi else docker_rc=1 compose_rc=1 : >"$docker_out" printf '%s\\n' 'docker daemon is not accessible' >"$docker_err" : >"$compose_out" printf '%s\\n' 'docker compose is not accessible' >"$compose_err" fi { for port in ${host.appPort} ${host.redisPort}; do state="free" if command -v ss >/dev/null 2>&1 && ss -ltn | awk '{print $4}' | grep -E "(:|\\])$port$" >/dev/null 2>&1; then state="listening" fi printf 'port=%s state=%s\\n' "$port" "$state" done } >"$ports_out" 2>"$ports_err" ports_rc=$? python3 - "$docker_rc" "$compose_rc" "$ports_rc" "$docker_out" "$docker_err" "$compose_out" "$compose_err" "$ports_out" "$ports_err" <<'PY' import json import sys docker_rc, compose_rc, ports_rc = [int(value) for value in sys.argv[1:4]] paths = sys.argv[4:] def text(path, limit=4000): try: return open(path, encoding="utf-8", errors="replace").read()[-limit:] except FileNotFoundError: return "" payload = { "ok": docker_rc == 0 and compose_rc == 0 and ports_rc == 0, "target": "${target.id}", "runtimeMode": "host-docker", "route": "${target.route}", "projectName": "${host.projectName}", "composePath": "${host.composePath}", "envPath": "${host.envPath}", "appDataDir": "${host.appDataDir}", "image": "${imageRef(sub2api, target)}", "dependencyImages": ${JSON.stringify(targetDependencyImages(sub2api, target))}, "ports": { "app": ${host.appPort}, "redis": ${host.redisPort}, "observed": text(paths[4], 2000), }, "steps": { "docker": {"exitCode": docker_rc, "stdout": text(paths[0]), "stderr": text(paths[1])}, "dockerCompose": {"exitCode": compose_rc, "stdout": text(paths[2]), "stderr": text(paths[3])}, "portObservation": {"exitCode": ports_rc, "stdout": text(paths[4]), "stderr": text(paths[5])}, }, "valuesPrinted": False, } print(json.dumps(payload, ensure_ascii=False, indent=2)) sys.exit(0 if payload["ok"] else 1) PY `; } export function hostDockerApplyScript(sub2api: Sub2ApiConfig, target: Sub2ApiTargetConfig, secretMaterial: ExternalActiveSecretMaterial | null): string { if (secretMaterial === null) throw new Error("host-docker apply requires external-active secret material"); const host = requireHostDockerConfig(target); const compose = hostDockerComposeYaml(sub2api, target); const env = hostDockerEnvFile(sub2api, target, secretMaterial); const composeB64 = Buffer.from(compose, "utf8").toString("base64"); const envB64 = Buffer.from(env, "utf8").toString("base64"); const image = targetImage(sub2api, target); const dependencyImages = targetDependencyImages(sub2api, target); const pullCommand = image.pullPolicy === "Always" ? `$docker_bin pull ${shQuote(imageRef(sub2api, target))} >"$pull_out" 2>"$pull_err"\napp_pull_rc=$?\n$docker_bin pull ${shQuote(dependencyImages.redis)} >>"$pull_out" 2>>"$pull_err"\nredis_pull_rc=$?\nif [ "$app_pull_rc" -eq 0 ] && [ "$redis_pull_rc" -eq 0 ]; then pull_rc=0; else pull_rc=1; fi` : `: >"$pull_out"\n: >"$pull_err"\npull_rc=0`; const secretSourceSummary = { sourceRef: secretMaterial.sourceRef, sourcePath: secretMaterial.sourcePath, appSourceRef: secretMaterial.appSourceRef, appSourcePath: secretMaterial.appSourcePath, sourceAction: secretMaterial.action, fingerprint: secretMaterial.fingerprint, valuesPrinted: false, }; return ` set -u tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT compose_tmp="$tmp/docker-compose.yml" env_tmp="$tmp/sub2api.env" printf '%s' '${composeB64}' | base64 -d >"$compose_tmp" printf '%s' '${envB64}' | base64 -d >"$env_tmp" docker_out="$tmp/docker.out" docker_err="$tmp/docker.err" install_out="$tmp/install.out" install_err="$tmp/install.err" pull_out="$tmp/pull.out" pull_err="$tmp/pull.err" up_out="$tmp/up.out" up_err="$tmp/up.err" health_out="$tmp/health.out" health_err="$tmp/health.err" docker_bin="" if docker ps >/dev/null 2>&1; then docker_bin="docker" elif sudo docker ps >/dev/null 2>&1; then docker_bin="sudo docker" fi if [ -n "$docker_bin" ]; then if $docker_bin compose version >"$docker_out" 2>"$docker_err"; then compose_cmd="$docker_bin compose" compose_mode="plugin" docker_rc=0 elif command -v docker-compose >/dev/null 2>&1 && docker-compose version >"$docker_out" 2>"$docker_err"; then compose_cmd="docker-compose" compose_mode="standalone" docker_rc=0 else compose_cmd="" compose_mode="raw-docker" $docker_bin version >"$docker_out" 2>"$docker_err" docker_rc=$? fi else compose_cmd="" compose_mode="none" docker_rc=1 : >"$docker_out" printf '%s\\n' 'docker daemon or docker compose is not accessible' >"$docker_err" fi install_rc=1 if [ "$docker_rc" -eq 0 ]; then sudo mkdir -p ${shQuote(host.workDir)} ${shQuote(host.appDataDir)} "$(dirname ${shQuote(host.composePath)})" "$(dirname ${shQuote(host.envPath)})" >"$install_out" 2>"$install_err" install_rc=$? if [ "$install_rc" -eq 0 ]; then sudo install -m 0644 "$compose_tmp" ${shQuote(host.composePath)} >>"$install_out" 2>>"$install_err" install_rc=$? fi if [ "$install_rc" -eq 0 ]; then current_uid="$(id -u)" current_gid="$(id -g)" sudo install -m 0600 -o "$current_uid" -g "$current_gid" "$env_tmp" ${shQuote(host.envPath)} >>"$install_out" 2>>"$install_err" install_rc=$? fi else : >"$install_out" : >"$install_err" fi if [ "$install_rc" -eq 0 ]; then ${pullCommand} else : >"$pull_out" printf '%s\\n' 'skipped because install failed' >"$pull_err" pull_rc=1 fi if [ "$pull_rc" -eq 0 ]; then if [ -n "$compose_cmd" ]; then $compose_cmd -p ${shQuote(host.projectName)} -f ${shQuote(host.composePath)} up -d --remove-orphans >"$up_out" 2>"$up_err" up_rc=$? else $docker_bin rm -f ${shQuote(`${host.projectName}-app`)} ${shQuote(`${host.projectName}-redis`)} >"$up_out" 2>"$up_err" || true $docker_bin run -d --name ${shQuote(`${host.projectName}-redis`)} \\ --restart unless-stopped \\ --network host \\ ${shQuote(dependencyImages.redis)} \\ redis-server --save "" --appendonly no --bind 127.0.0.1 --port ${host.redisPort} >>"$up_out" 2>>"$up_err" redis_run_rc=$? if [ "$redis_run_rc" -eq 0 ]; then $docker_bin run -d --name ${shQuote(`${host.projectName}-app`)} \\ --restart unless-stopped \\ --network host \\ --env-file ${shQuote(host.envPath)} \\ -v ${shQuote(`${host.appDataDir}:/app/data`)} \\ ${shQuote(imageRef(sub2api, target))} >>"$up_out" 2>>"$up_err" app_run_rc=$? else app_run_rc=1 fi if [ "$redis_run_rc" -eq 0 ] && [ "$app_run_rc" -eq 0 ]; then up_rc=0; else up_rc=1; fi fi else : >"$up_out" printf '%s\\n' 'skipped because pull failed' >"$up_err" up_rc=1 fi health_rc=1 if [ "$up_rc" -eq 0 ]; then deadline=$(( $(date +%s) + 180 )) while [ "$(date +%s)" -lt "$deadline" ]; do if curl -fsS --max-time 5 http://127.0.0.1:${host.appPort}/health >"$health_out" 2>"$health_err"; then health_rc=0 break fi sleep 3 done else : >"$health_out" printf '%s\\n' 'skipped because compose up failed' >"$health_err" fi python3 - "$docker_rc" "$install_rc" "$pull_rc" "$up_rc" "$health_rc" "$compose_mode" "$docker_out" "$docker_err" "$install_out" "$install_err" "$pull_out" "$pull_err" "$up_out" "$up_err" "$health_out" "$health_err" <<'PY' import json import sys docker_rc, install_rc, pull_rc, up_rc, health_rc = [int(value) for value in sys.argv[1:6]] docker_mode = sys.argv[6] paths = sys.argv[7:] def text(path, limit=4000): try: return open(path, encoding="utf-8", errors="replace").read()[-limit:] except FileNotFoundError: return "" payload = { "ok": docker_rc == 0 and install_rc == 0 and pull_rc == 0 and up_rc == 0 and health_rc == 0, "target": "${target.id}", "runtimeMode": "host-docker", "route": "${target.route}", "projectName": "${host.projectName}", "dockerMode": docker_mode, "composePath": "${host.composePath}", "envPath": "${host.envPath}", "appDataDir": "${host.appDataDir}", "image": "${imageRef(sub2api, target)}", "secret": { "requiredKeys": ${JSON.stringify(requiredSecretKeys)}, "externalActiveSource": json.loads(${JSON.stringify(JSON.stringify(secretSourceSummary))}), "valuesPrinted": False, }, "steps": { "docker": {"exitCode": docker_rc, "stdout": text(paths[0]), "stderr": text(paths[1])}, "installFiles": {"exitCode": install_rc, "stdout": text(paths[2]), "stderr": text(paths[3])}, "pull": {"exitCode": pull_rc, "stdout": text(paths[4]), "stderr": text(paths[5])}, "composeUp": {"exitCode": up_rc, "stdout": text(paths[6]), "stderr": text(paths[7])}, "localHealth": {"exitCode": health_rc, "stdout": text(paths[8]), "stderr": text(paths[9])}, }, "valuesPrinted": False, } print(json.dumps(payload, ensure_ascii=False, indent=2)) sys.exit(0 if payload["ok"] else 1) PY `; } export function hostDockerStatusScript(sub2api: Sub2ApiConfig, target: Sub2ApiTargetConfig): string { const host = requireHostDockerConfig(target); return ` set -u tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT docker_out="$tmp/docker.out" docker_err="$tmp/docker.err" ps_out="$tmp/ps.out" ps_err="$tmp/ps.err" health_out="$tmp/health.out" health_err="$tmp/health.err" caddy_out="$tmp/caddy.out" caddy_err="$tmp/caddy.err" docker_bin="" if docker ps >/dev/null 2>&1; then docker_bin="docker" elif sudo docker ps >/dev/null 2>&1; then docker_bin="sudo docker" fi if [ -n "$docker_bin" ]; then $docker_bin version --format '{{json .}}' >"$docker_out" 2>"$docker_err" docker_rc=$? $docker_bin ps -a --filter label=com.docker.compose.project=${shQuote(host.projectName)} --format '{{json .}}' >"$ps_out" 2>"$ps_err" $docker_bin ps -a --filter name=${shQuote(`${host.projectName}-`)} --format '{{json .}}' >>"$ps_out" 2>>"$ps_err" ps_rc=$? else docker_rc=1 ps_rc=1 : >"$docker_out" printf '%s\\n' 'docker daemon is not accessible' >"$docker_err" : >"$ps_out" : >"$ps_err" fi curl -fsS --max-time 5 http://127.0.0.1:${host.appPort}/health >"$health_out" 2>"$health_err" health_rc=$? systemctl is-active caddy >"$caddy_out" 2>"$caddy_err" caddy_rc=$? python3 - "$docker_rc" "$ps_rc" "$health_rc" "$caddy_rc" "$docker_out" "$docker_err" "$ps_out" "$ps_err" "$health_out" "$health_err" "$caddy_out" "$caddy_err" <<'PY' import json import sys docker_rc, ps_rc, health_rc, caddy_rc = [int(value) for value in sys.argv[1:5]] paths = sys.argv[5:] def text(path, limit=4000): try: return open(path, encoding="utf-8", errors="replace").read()[-limit:] except FileNotFoundError: return "" containers = [] for line in text(paths[2], 20000).splitlines(): try: containers.append(json.loads(line)) except json.JSONDecodeError: pass payload = { "ok": docker_rc == 0 and ps_rc == 0 and health_rc == 0, "target": "${target.id}", "runtimeMode": "host-docker", "route": "${target.route}", "projectName": "${host.projectName}", "serviceDns": "host-docker:127.0.0.1:${host.appPort}", "containers": containers, "secret": {"managedByThisApply": True, "valuesPrinted": False}, "networkPolicy": {"ok": True, "mode": "not-applicable-host-docker"}, "egressProxy": {"enabled": False}, "sentinel": {"enabled": False, "mode": "not-deployed-host-docker"}, "checks": { "docker": {"exitCode": docker_rc, "stdout": text(paths[0]), "stderr": text(paths[1])}, "composeContainers": {"exitCode": ps_rc, "stdout": text(paths[2], 8000), "stderr": text(paths[3])}, "localHealth": {"exitCode": health_rc, "stdout": text(paths[4]), "stderr": text(paths[5])}, "caddyService": {"exitCode": caddy_rc, "stdout": text(paths[6]), "stderr": text(paths[7])}, }, "valuesPrinted": False, } print(json.dumps(payload, ensure_ascii=False, indent=2)) sys.exit(0 if payload["ok"] else 1) PY `; } export function hostDockerValidateScript(sub2api: Sub2ApiConfig, target: Sub2ApiTargetConfig): string { const host = requireHostDockerConfig(target); const database = targetDatabase(sub2api, target); const exposure = target.publicExposure?.enabled ? target.publicExposure : null; const publicProbe = exposure === null ? ` public_rc=0 : >"$tmp/public.out" : >"$tmp/public.err" ` : ` curl -kfsS --max-time 20 --resolve ${shQuote(`${exposure.dns.hostname}:443:127.0.0.1`)} ${shQuote(`${exposure.publicBaseUrl}/health`)} >"$tmp/public.out" 2>"$tmp/public.err" public_rc=$? `; return ` set -u tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT docker_out="$tmp/docker.out" docker_err="$tmp/docker.err" app_out="$tmp/app.out" app_err="$tmp/app.err" redis_out="$tmp/redis.out" redis_err="$tmp/redis.err" pg_out="$tmp/pg.out" pg_err="$tmp/pg.err" health_out="$tmp/health.out" health_err="$tmp/health.err" docker_bin="" if docker ps >/dev/null 2>&1; then docker_bin="docker" elif sudo docker ps >/dev/null 2>&1; then docker_bin="sudo docker" fi if [ -n "$docker_bin" ]; then $docker_bin inspect -f '{{.State.Running}}' ${shQuote(`${host.projectName}-app`)} >"$app_out" 2>"$app_err" app_rc=$? $docker_bin exec ${shQuote(`${host.projectName}-redis`)} redis-cli -h 127.0.0.1 -p ${host.redisPort} ping >"$redis_out" 2>"$redis_err" redis_rc=$? $docker_bin ps --filter label=com.docker.compose.project=${shQuote(host.projectName)} --format '{{json .}}' >"$docker_out" 2>"$docker_err" $docker_bin ps --filter name=${shQuote(`${host.projectName}-`)} --format '{{json .}}' >>"$docker_out" 2>>"$docker_err" docker_rc=$? else app_rc=1 redis_rc=1 docker_rc=1 : >"$app_out" : >"$redis_out" : >"$docker_out" printf '%s\\n' 'docker daemon is not accessible' >"$app_err" printf '%s\\n' 'docker daemon is not accessible' >"$redis_err" printf '%s\\n' 'docker daemon is not accessible' >"$docker_err" fi if command -v pg_isready >/dev/null 2>&1; then pg_isready -h ${shQuote(database.host)} -p ${database.port} -U ${shQuote(database.user)} -d ${shQuote(database.dbName)} >"$pg_out" 2>"$pg_err" pg_rc=$? else pg_rc=127 : >"$pg_out" printf '%s\\n' 'pg_isready is not installed on PK01 host' >"$pg_err" fi curl -fsS --max-time 10 http://127.0.0.1:${host.appPort}/health >"$health_out" 2>"$health_err" health_rc=$? ${publicProbe} python3 - "$docker_rc" "$app_rc" "$redis_rc" "$pg_rc" "$health_rc" "$public_rc" "$docker_out" "$docker_err" "$app_out" "$app_err" "$redis_out" "$redis_err" "$pg_out" "$pg_err" "$health_out" "$health_err" "$tmp/public.out" "$tmp/public.err" <<'PY' import json import sys docker_rc, app_rc, redis_rc, pg_rc, health_rc, public_rc = [int(value) for value in sys.argv[1:7]] paths = sys.argv[7:] def text(path, limit=4000): try: return open(path, encoding="utf-8", errors="replace").read()[-limit:] except FileNotFoundError: return "" payload = { "ok": docker_rc == 0 and app_rc == 0 and redis_rc == 0 and pg_rc == 0 and health_rc == 0 and public_rc == 0, "target": "${target.id}", "runtimeMode": "host-docker", "route": "${target.route}", "serviceDns": "host-docker:127.0.0.1:${host.appPort}", "checks": { "dockerComposeProject": {"exitCode": docker_rc, "stdout": text(paths[0], 8000), "stderr": text(paths[1])}, "appContainerRunning": {"exitCode": app_rc, "stdout": text(paths[2]), "stderr": text(paths[3])}, "redisPing": {"exitCode": redis_rc, "stdout": text(paths[4]), "stderr": text(paths[5])}, "postgresLocalPgIsReady": {"exitCode": pg_rc, "host": "${database.host}", "port": ${database.port}, "user": "${database.user}", "dbName": "${database.dbName}", "stdout": text(paths[6]), "stderr": text(paths[7])}, "sub2apiLocalHealth": {"exitCode": health_rc, "stdout": text(paths[8]), "stderr": text(paths[9])}, "sub2apiPublicHealthViaPk01Caddy": {"exitCode": public_rc, "stdout": text(paths[10]), "stderr": text(paths[11])}, "sentinel": {"ok": True, "mode": "not-deployed-host-docker"}, "egressProxy": {"ok": True, "mode": "not-deployed-host-docker"}, }, "valuesPrinted": False, } print(json.dumps(payload, ensure_ascii=False, indent=2)) sys.exit(0 if payload["ok"] else 1) PY `; } function yamlString(value: string): string { return JSON.stringify(value); } function envFileValue(value: string): string { if (/^[A-Za-z0-9_./:@,+-]*$/u.test(value)) return value; return JSON.stringify(value); }