From 35068319e53e43d19cf4b85ac43d258b32903903 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 4 Sep 2026 14:17:18 +0000 Subject: [PATCH] fix(deploy): add optional Turnstile and customer mTLS proxy --- DEPLOYMENT.md | 11 +++- README.md | 24 ++++++-- SHA256SUMS | 6 +- compose/e.yml | 5 +- compose/s.yml | 36 +++++++++++- install.sh | 127 ++++++++++++++++++++++++++++++++++++++----- test/static-check.sh | 5 +- 7 files changed, 186 insertions(+), 28 deletions(-) diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 5054d7f..2f314a0 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -13,7 +13,16 @@ Generated keys are written only to `/opt/overcooked/production.env` with mode 600. The installer does not log generated values, activation codes, or Auth configuration. Failure logs are redacted before display. +Turnstile is optional. Empty site and secret keys disable only that abuse +prevention challenge; Auth, license validation, and login policy remain on. + +E production control-plane traffic uses mTLS. The installer creates a local +CA and service/client certificates, then generates a minimal HTTPS proxy in the +installation directory and runs it from the approved S WebUI image. The proxy +has no public port and only forwards authenticated `/internal/v1/` calls to +S. The approved Media Node image is exported to the node-image archive used by +S node deployment. + The installer must stop on missing prerequisites, invalid public address, occupied ports, failed image verification, failed schema migration, or failed health checks. It must never continue with a partially initialized database. - diff --git a/README.md b/README.md index fff10dc..aefb030 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,13 @@ only these public images: It does not download or start an Auth image, and it does not use Docker build contexts. +Because E production mode requires an encrypted control-plane connection, the +installer generates a minimal allowlisted HTTPS reverse proxy at install time +and runs it from the already-listed S WebUI image. The proxy only accepts +mTLS-authenticated `/internal/v1/` requests and `/health/live`, and forwards +only to the S control service; it is not a public endpoint and does not expose +Shell, SQL, Docker, or arbitrary file access. + ## Install ```sh @@ -23,8 +30,6 @@ sudo env \\ OVERCOOKED_SERVER_IP=203.0.113.10 \\ OVERCOOKED_AUTH_PUBLIC_KEYS='{"auth-1":"PUBLIC_KEY"}' \\ OVERCOOKED_ACTIVATION_CODE='ACTIVATION_CODE' \\ - OVERCOOKED_TURNSTILE_SITE_KEY='TURNSTILE_SITE_KEY' \\ - OVERCOOKED_TURNSTILE_SECRET_KEY='TURNSTILE_SECRET_KEY' \\ ./install.sh ``` @@ -33,9 +38,10 @@ with `OVERCOOKED_AUTH_URL`. Auth private keys, administrator credentials, license databases, provider credentials, and production secrets are never included in this repository. -The current S production image requires the Turnstile key pair shown above. -The installer fails early if the pair is missing or incomplete; it never uses -placeholder credentials. +Turnstile is optional. Set `OVERCOOKED_TURNSTILE_SITE_KEY` and +`OVERCOOKED_TURNSTILE_SECRET_KEY` together to enable it; leaving both empty +disables only the Turnstile challenge and does not disable Auth, license, or +login policy checks. A partial pair is rejected by S configuration validation. ## Endpoints @@ -48,6 +54,10 @@ The installer checks these host ports before changing service state and does not bind ports 80 or 443. Configure the firewall to allow the chosen public ports and restrict database/Valkey traffic to the Docker networks. +S's E client uses the private backend Docker network (`http://e-server:8096`) +with its explicit private-network opt-in. E's control-plane client is a +separate production path and always uses the generated HTTPS/mTLS proxy. + The server address is selected from `OVERCOOKED_SERVER_IP`, or from the interface selected by `ip -4 route get 1.1.1.1`. A detected RFC1918, loopback, link-local, or carrier-grade NAT address is rejected unless explicitly @@ -73,6 +83,10 @@ image. S uses only the schema migration assets published in this repository. If either migration fails, the installer exits non-zero and does not start the corresponding application service. +The installer also exports the approved Media Node image into +`/opt/overcooked/node-images/media-node.tar.gz`; S uses that archive when a +customer adds a node without a source checkout. + ## Common 404/401 download failures The customer command must fetch the raw script from the public Installer diff --git a/SHA256SUMS b/SHA256SUMS index 24e0fc3..f28d317 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -1,6 +1,6 @@ -3395c27ad4158e70d47e7d002c141b1261f502875f2e7ac5b26ade51b7689138 install.sh -028d415f0c0aef0ed9a807573239079f9e7e8cb3ed796d9dec90f4be8cafdd05 compose/s.yml -fab213abb69e0141139c0d4234a7b1636588206f6588942a55c3380114069d92 compose/e.yml +80d42d9797b20d0b8fd53ac9529cd579fae9fe294267ac8e8ab990ed961e88d1 install.sh +e8cabc5a989d171206361e6ae8b4e0baf9bb9a611ac41106bbe42de0782e8975 compose/s.yml +203b6da918dee21dbfabbcf5466111bd92c7a8c9e754dad61e7e75e20513b880 compose/e.yml c226c5e1feb5cd6948ee9b32897e77240e21793090d8f42234ca999450ec90b0 config/e-server.production.yaml 3e3dc775017577fad3b36b6c21036dfcb6e530793de4c18c825dda888f62e4e3 s-migrate.sh 07f432c281ba90565cd376b42967909b6ff2d19d04454a3ba4e3b184117d45af s-migrations.tar.gz diff --git a/compose/e.yml b/compose/e.yml index 22602c3..834c47b 100644 --- a/compose/e.yml +++ b/compose/e.yml @@ -62,6 +62,7 @@ services: - e-data:/data - e-plugin-data:/data/plugins - ${OVERCOOKED_ROOT:-/opt/overcooked}/config/e-server.production.yaml:/etc/overcooked-e/config.yaml:ro + - ${OVERCOOKED_ROOT:-/opt/overcooked}/control-plane:/run/overcooked-control-plane:ro environment: PRODUCT_E_CONFIG: /etc/overcooked-e/config.yaml PRODUCT_E_ENVIRONMENT: production @@ -72,6 +73,9 @@ services: PRODUCT_E_CONTROL_PLANE_URL: ${PRODUCT_E_CONTROL_PLANE_URL:?required} PRODUCT_E_CONTROL_PLANE_KEY_ID: ${PRODUCT_E_CONTROL_PLANE_KEY_ID:?required} PRODUCT_E_CONTROL_PLANE_SHARED_SECRET: ${PRODUCT_E_CONTROL_PLANE_SHARED_SECRET:?required} + PRODUCT_E_CONTROL_PLANE_TLS_CERT_FILE: ${PRODUCT_E_CONTROL_PLANE_TLS_CERT_FILE:?required} + PRODUCT_E_CONTROL_PLANE_TLS_KEY_FILE: ${PRODUCT_E_CONTROL_PLANE_TLS_KEY_FILE:?required} + PRODUCT_E_CONTROL_PLANE_TLS_CA_FILE: ${PRODUCT_E_CONTROL_PLANE_TLS_CA_FILE:?required} PRODUCT_E_LICENSE_ENABLED: ${PRODUCT_E_LICENSE_ENABLED:-true} PRODUCT_E_AUTH_URL: ${PRODUCT_E_AUTH_URL:?required} PRODUCT_E_AUTH_ALLOW_INSECURE_HTTP: ${PRODUCT_E_AUTH_ALLOW_INSECURE_HTTP:-false} @@ -123,4 +127,3 @@ volumes: e-data: e-plugin-data: e-backup-staging: - diff --git a/compose/s.yml b/compose/s.yml index fd085a0..d713277 100644 --- a/compose/s.yml +++ b/compose/s.yml @@ -102,8 +102,8 @@ services: S_VERSION: ${S_VERSION:-customer} S_BACKUP_DIRECTORY: /var/lib/overcooked-s/backups S_UPDATE_DIRECTORY: /var/lib/overcooked-s/updates - S_TURNSTILE_SITE_KEY: ${S_TURNSTILE_SITE_KEY:?required} - S_TURNSTILE_SECRET_KEY: ${S_TURNSTILE_SECRET_KEY:?required} + S_TURNSTILE_SITE_KEY: ${S_TURNSTILE_SITE_KEY:-} + S_TURNSTILE_SECRET_KEY: ${S_TURNSTILE_SECRET_KEY:-} S_TURNSTILE_HOSTNAME: ${S_TURNSTILE_HOSTNAME:-} S_TELEGRAM_MTPROTO_URL: ${S_TELEGRAM_MTPROTO_URL:-http://telegram-disabled.invalid:8098} S_PORTAL_LOCATION: ${S_PORTAL_LOCATION:-Private Node} @@ -112,7 +112,7 @@ services: S_PORTAL_ANNOUNCEMENT: ${S_PORTAL_ANNOUNCEMENT:-} S_STORAGE_WEBHOOK_SECRET: ${S_STORAGE_WEBHOOK_SECRET:-} S_NODE_IMAGE_ARCHIVE: /var/lib/overcooked-s/node-images/media-node.tar.gz - S_NODE_IMAGE_NAME: ${S_NODE_IMAGE_NAME:-overcooked-media-node:latest} + S_NODE_IMAGE_NAME: ${S_NODE_IMAGE_NAME:-git.leee.xin/overcooked/overcooked-media-node:latest} S_NODE_CONTROLLER_HOST: ${S_NODE_CONTROLLER_HOST:-} volumes: - s-backups:/var/lib/overcooked-s/backups @@ -133,6 +133,36 @@ services: logging: *logging mem_limit: 384m + control-proxy: + image: ${S_WEBUI_IMAGE:?required} + restart: unless-stopped + init: true + depends_on: + s-control: {condition: service_healthy} + command: ["node", "/run/overcooked-control-plane/control-proxy.mjs"] + volumes: + - ${OVERCOOKED_ROOT:-/opt/overcooked}/control-plane:/run/overcooked-control-plane:ro + healthcheck: + test: + - CMD + - node + - -e + - | + const fs = require("node:fs"); + const https = require("node:https"); + const request = https.get("https://control-proxy:8443/health/live", {ca: fs.readFileSync("/run/overcooked-control-plane/ca.crt"), cert: fs.readFileSync("/run/overcooked-control-plane/client.crt"), key: fs.readFileSync("/run/overcooked-control-plane/client.key"), servername: "control-proxy", rejectUnauthorized: true}, (response) => { response.resume(); process.exit(response.statusCode === 200 ? 0 : 1); }); + request.setTimeout(2000, () => { request.destroy(); process.exit(1); }); + request.on("error", () => process.exit(1)); + interval: 10s + timeout: 3s + start_period: 10s + retries: 6 + networks: [control-plane] + security_opt: [no-new-privileges:true] + cap_drop: [ALL] + logging: *logging + mem_limit: 128m + webui: image: ${S_WEBUI_IMAGE:?required} restart: unless-stopped diff --git a/install.sh b/install.sh index 3d42eda..8456881 100755 --- a/install.sh +++ b/install.sh @@ -12,8 +12,6 @@ ACTIVATION_CODE=${OVERCOOKED_ACTIVATION_CODE:-} AUTH_URL=${OVERCOOKED_AUTH_URL:-http://148.135.97.10:18091} AUTH_PUBLIC_KEYS=${OVERCOOKED_AUTH_PUBLIC_KEYS:-} INSTALLER_BASE_URL=${OVERCOOKED_INSTALLER_BASE_URL:-https://git.leee.xin/OverCooked/OverCooked-Installer/raw/branch/main} -TURNSTILE_SITE_KEY=${OVERCOOKED_TURNSTILE_SITE_KEY:-} -TURNSTILE_SECRET_KEY=${OVERCOOKED_TURNSTILE_SECRET_KEY:-} S_COMPOSE=$ROOT/compose/s.yml E_COMPOSE=$ROOT/compose/e.yml ENV_FILE=$ROOT/production.env @@ -51,7 +49,8 @@ is_private_ipv4() { } check_port() { port=$1 - if command -v ss >/dev/null 2>&1 && ss -H -ltn 2>/dev/null | awk -v p=":$port" '$4 ~ p "$" || $4 ~ p "\\]$" { found=1 } END { exit !found }'; then + command -v ss >/dev/null 2>&1 || die "未找到 ss,无法安全检测端口 $port;请安装 iproute2 后重试" + if ss -H -ltn 2>/dev/null | awk -v p=":$port" '$4 ~ p "$" || $4 ~ p "\\]$" { found=1 } END { exit !found }'; then die "端口 $port 已被占用,请停止占用进程或调整部署端口" fi } @@ -116,12 +115,9 @@ if [ -z "$ACTIVATION_CODE" ]; then printf '请输入授权码: ' >&2; IFS= read -r ACTIVATION_CODE fi [ -n "$ACTIVATION_CODE" ] || die "授权码不能为空" -if [ -z "$TURNSTILE_SITE_KEY" ] || [ -z "$TURNSTILE_SECRET_KEY" ]; then - die "当前 S 生产镜像需要 OVERCOOKED_TURNSTILE_SITE_KEY 和 OVERCOOKED_TURNSTILE_SECRET_KEY;请提供成对配置" -fi check_port 18080; check_port 18083; check_port 8096 -install -d -m 700 "$ROOT" "$ROOT/config" "$ROOT/compose" "$ROOT/migrations" "$ROOT/node-images" +install -d -m 700 "$ROOT" "$ROOT/config" "$ROOT/compose" "$ROOT/migrations" "$ROOT/node-images" "$ROOT/control-plane" SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) fetch_asset() { relative=$1; destination=$2; source="$SCRIPT_DIR/$relative" @@ -134,11 +130,100 @@ fetch_asset() { fetch_asset compose/s.yml "$S_COMPOSE" fetch_asset compose/e.yml "$E_COMPOSE" fetch_asset s-migrate.sh "$ROOT/s-migrate.sh" +chmod 644 "$ROOT/s-migrate.sh" fetch_asset config/e-server.production.yaml "$ROOT/config/e-server.production.yaml" +chmod 644 "$ROOT/config/e-server.production.yaml" fetch_asset s-migrations.tar.gz "$ROOT/s-migrations.tar.gz" tar -xzf "$ROOT/s-migrations.tar.gz" -C "$ROOT/migrations"; rm -f "$ROOT/s-migrations.tar.gz" find "$ROOT/migrations" -type f ! -name '*.up.sql' -delete -find "$ROOT/migrations" -type f -name '*.up.sql' -exec chmod 600 {} \; +find "$ROOT/migrations" -type f -name '*.up.sql' -exec chmod 644 {} \; +chmod 755 "$ROOT/migrations" "$ROOT/node-images" + +# E requires an HTTPS control-plane origin in production. Generate a local +# CA and service/client certificates for the restricted proxy sidecar. The +# proxy is generated locally and runs from the already-approved S WebUI image; +# no additional image or source checkout is needed. +CONTROL_PLANE_DIR="$ROOT/control-plane" +cat > "$CONTROL_PLANE_DIR/control-proxy.mjs" <<'NODE_PROXY' +import fs from "node:fs"; +import http from "node:http"; +import https from "node:https"; + +const listenPort = 8443; +const options = { + key: fs.readFileSync("/run/overcooked-control-plane/server.key"), + cert: fs.readFileSync("/run/overcooked-control-plane/server.crt"), + ca: fs.readFileSync("/run/overcooked-control-plane/ca.crt"), + requestCert: true, + rejectUnauthorized: true, + minVersion: "TLSv1.2", +}; + +function reject(response, status, message) { + response.writeHead(status, {"content-type": "text/plain; charset=utf-8"}); + response.end(message); +} + +const server = https.createServer(options, (request, response) => { + let target; + try { + target = new URL(request.url ?? "/", "https://control-proxy"); + } catch { + reject(response, 400, "invalid request target"); + return; + } + if (target.origin !== "https://control-proxy" || target.username || target.password || + target.pathname.includes("..") || target.pathname !== "/health/live" && !target.pathname.startsWith("/internal/v1/")) { + reject(response, 404, "not found"); + return; + } + if (target.pathname === "/health/live") { + response.writeHead(200, {"content-type": "application/json"}); + response.end('{"status":"live"}'); + return; + } + + const headers = {...request.headers}; + delete headers.host; + delete headers.connection; + delete headers["x-forwarded-for"]; + delete headers["x-forwarded-host"]; + delete headers["x-forwarded-proto"]; + headers.host = "s-control:8080"; + const upstream = http.request({hostname: "s-control", port: 8080, method: request.method, path: `${target.pathname}${target.search}`, headers, timeout: 10000}, (upstreamResponse) => { + response.writeHead(upstreamResponse.statusCode ?? 502, upstreamResponse.headers); + upstreamResponse.pipe(response); + }); + upstream.on("timeout", () => upstream.destroy(new Error("upstream timeout"))); + upstream.on("error", () => { + if (!response.headersSent) reject(response, 502, "control plane unavailable"); + else response.destroy(); + }); + request.pipe(upstream); +}); + +server.listen(listenPort, "0.0.0.0"); +NODE_PROXY +chmod 644 "$CONTROL_PLANE_DIR/control-proxy.mjs" +chown 1000:1000 "$CONTROL_PLANE_DIR/control-proxy.mjs" +openssl genrsa -out "$CONTROL_PLANE_DIR/ca.key" 3072 +openssl req -x509 -new -nodes -key "$CONTROL_PLANE_DIR/ca.key" -sha256 -days 3650 -subj "/CN=OverCooked customer control plane CA" -out "$CONTROL_PLANE_DIR/ca.crt" +openssl genrsa -out "$CONTROL_PLANE_DIR/server.key" 2048 +openssl req -new -key "$CONTROL_PLANE_DIR/server.key" -subj "/CN=control-proxy" -out "$CONTROL_PLANE_DIR/server.csr" +printf '%s\n' 'subjectAltName=DNS:control-proxy,DNS:product-e' 'extendedKeyUsage=serverAuth' > "$CONTROL_PLANE_DIR/server.ext" +openssl x509 -req -in "$CONTROL_PLANE_DIR/server.csr" -CA "$CONTROL_PLANE_DIR/ca.crt" -CAkey "$CONTROL_PLANE_DIR/ca.key" -CAcreateserial -out "$CONTROL_PLANE_DIR/server.crt" -days 825 -sha256 -extfile "$CONTROL_PLANE_DIR/server.ext" +openssl genrsa -out "$CONTROL_PLANE_DIR/client.key" 2048 +openssl req -new -key "$CONTROL_PLANE_DIR/client.key" -subj "/CN=overcooked-e" -out "$CONTROL_PLANE_DIR/client.csr" +printf '%s\n' 'extendedKeyUsage=clientAuth' > "$CONTROL_PLANE_DIR/client.ext" +openssl x509 -req -in "$CONTROL_PLANE_DIR/client.csr" -CA "$CONTROL_PLANE_DIR/ca.crt" -CAkey "$CONTROL_PLANE_DIR/ca.key" -CAcreateserial -out "$CONTROL_PLANE_DIR/client.crt" -days 825 -sha256 -extfile "$CONTROL_PLANE_DIR/client.ext" +rm -f "$CONTROL_PLANE_DIR/ca.key" "$CONTROL_PLANE_DIR/server.csr" "$CONTROL_PLANE_DIR/server.ext" "$CONTROL_PLANE_DIR/client.csr" "$CONTROL_PLANE_DIR/client.ext" "$CONTROL_PLANE_DIR/ca.srl" +chmod 644 "$CONTROL_PLANE_DIR/ca.crt" "$CONTROL_PLANE_DIR/server.crt" "$CONTROL_PLANE_DIR/client.crt" +chmod 640 "$CONTROL_PLANE_DIR/server.key" "$CONTROL_PLANE_DIR/client.key" +chown 1000:1000 "$CONTROL_PLANE_DIR/server.key" +# The proxy healthcheck (uid 1000) and E (uid 10001) both need the client key; +# a shared group keeps it unreadable to other container users. +chown 1000:10001 "$CONTROL_PLANE_DIR/client.key" +chmod 755 "$CONTROL_PLANE_DIR" secret() { openssl rand -hex 32; } S_POSTGRES_PASSWORD=$(secret); S_VALKEY_PASSWORD=$(secret) @@ -161,6 +246,8 @@ S_E_BASE_URL=http://e-server:8096 S_E_PUBLIC_URL=http://$SERVER_IP:8096 S_E_KEY_ID=customer-e S_E_INTERNAL_SECRET=$S_INTERNAL_SHARED_SECRET +# S's private backend client talks to E on the internal Docker network. This +# does not weaken E's production mTLS control-plane connection below. S_E_ALLOW_INSECURE_HTTP=true S_AUTH_BASE_URL=$AUTH_URL S_AUTH_ALLOW_INSECURE_HTTP=$(case "$AUTH_URL" in http://*) echo true ;; *) echo false ;; esac) @@ -173,16 +260,20 @@ S_PORTAL_SERVER_URL=http://$SERVER_IP:18080 S_PORTAL_WEB_CLIENT_URL=http://$SERVER_IP:18083/login S_API_PORT_MAPPING=0.0.0.0:18080:8080 S_WEB_PORT_MAPPING=0.0.0.0:18083:3000 -S_TURNSTILE_SITE_KEY=$TURNSTILE_SITE_KEY -S_TURNSTILE_SECRET_KEY=$TURNSTILE_SECRET_KEY +S_TURNSTILE_SITE_KEY=${OVERCOOKED_TURNSTILE_SITE_KEY:-} +S_TURNSTILE_SECRET_KEY=${OVERCOOKED_TURNSTILE_SECRET_KEY:-} +S_NODE_IMAGE_NAME=$REGISTRY/overcooked-media-node:$VERSION PRODUCT_E_IMAGE=$REGISTRY/overcooked-e:$VERSION PRODUCT_E_INSTANCE_ID=e-$INSTANCE_SUFFIX PRODUCT_E_DATABASE_URL=postgres://product_e:$E_POSTGRES_PASSWORD@postgres:5432/product_e?sslmode=disable PRODUCT_E_POSTGRES_PASSWORD=$E_POSTGRES_PASSWORD PRODUCT_E_VALKEY_PASSWORD=$S_VALKEY_PASSWORD -PRODUCT_E_CONTROL_PLANE_URL=http://s-control:8080 +PRODUCT_E_CONTROL_PLANE_URL=https://control-proxy:8443 PRODUCT_E_CONTROL_PLANE_KEY_ID=customer-s PRODUCT_E_CONTROL_PLANE_SHARED_SECRET=$S_INTERNAL_SHARED_SECRET +PRODUCT_E_CONTROL_PLANE_TLS_CERT_FILE=/run/overcooked-control-plane/client.crt +PRODUCT_E_CONTROL_PLANE_TLS_KEY_FILE=/run/overcooked-control-plane/client.key +PRODUCT_E_CONTROL_PLANE_TLS_CA_FILE=/run/overcooked-control-plane/ca.crt PRODUCT_E_LICENSE_ENABLED=true PRODUCT_E_AUTH_URL=$AUTH_URL PRODUCT_E_AUTH_ALLOW_INSECURE_HTTP=$(case "$AUTH_URL" in http://*) echo true ;; *) echo false ;; esac) @@ -236,23 +327,31 @@ plugins: network_allowed_domains: [] network_allowed_cidrs: [] EOF -chmod 600 "$ROOT/config/e-server.production.yaml" +chmod 644 "$ROOT/config/e-server.production.yaml" docker network inspect overcooked-control >/dev/null 2>&1 || docker network create overcooked-control >/dev/null docker compose --env-file "$ENV_FILE" -f "$E_COMPOSE" -p "$E_PROJECT" config >/dev/null docker compose --env-file "$ENV_FILE" -f "$S_COMPOSE" -p "$S_PROJECT" config >/dev/null -for image in "$REGISTRY/overcooked-e:$VERSION" "$REGISTRY/overcooked-s-control:$VERSION" "$REGISTRY/overcooked-s-webui:$VERSION" "$REGISTRY/overcooked-media-node:$VERSION"; do +MEDIA_NODE_IMAGE="$REGISTRY/overcooked-media-node:$VERSION" +for image in "$REGISTRY/overcooked-e:$VERSION" "$REGISTRY/overcooked-s-control:$VERSION" "$REGISTRY/overcooked-s-webui:$VERSION" "$MEDIA_NODE_IMAGE"; do docker pull "$image" docker image inspect "$image" >/dev/null 2>&1 || die "镜像校验失败:$image" done +image_tar=$(mktemp "$ROOT/node-images/media-node.tar.XXXXXX") +image_archive=$(mktemp "$ROOT/node-images/media-node.tar.gz.XXXXXX") +docker save "$MEDIA_NODE_IMAGE" -o "$image_tar" || die "无法导出 Media Node 镜像归档" +gzip -c "$image_tar" > "$image_archive" || die "无法压缩 Media Node 镜像归档" +rm -f "$image_tar" +mv "$image_archive" "$ROOT/node-images/media-node.tar.gz" +chmod 644 "$ROOT/node-images/media-node.tar.gz" docker compose --env-file "$ENV_FILE" -f "$E_COMPOSE" -p "$E_PROJECT" up -d postgres valkey docker compose --env-file "$ENV_FILE" -f "$E_COMPOSE" -p "$E_PROJECT" run --rm --no-deps e-server migrate -config /etc/overcooked-e/config.yaml docker compose --env-file "$ENV_FILE" -f "$S_COMPOSE" -p "$S_PROJECT" up -d postgres valkey docker compose --env-file "$ENV_FILE" -f "$S_COMPOSE" -p "$S_PROJECT" run --rm --no-deps s-migrate +docker compose --env-file "$ENV_FILE" -f "$S_COMPOSE" -p "$S_PROJECT" up -d s-control control-proxy webui docker compose --env-file "$ENV_FILE" -f "$E_COMPOSE" -p "$E_PROJECT" up -d e-server -docker compose --env-file "$ENV_FILE" -f "$S_COMPOSE" -p "$S_PROJECT" up -d s-control webui wait_for() { label=$1; url=$2; attempt=1 diff --git a/test/static-check.sh b/test/static-check.sh index 3d3db98..56c4ad7 100755 --- a/test/static-check.sh +++ b/test/static-check.sh @@ -17,7 +17,10 @@ test -f compose/e.yml test -f config/e-server.production.yaml test -f s-migrations.tar.gz test "$(find s-migrations -type f -name '*.up.sql' | wc -l)" -gt 0 +sha256sum -c SHA256SUMS >/dev/null ! rg -n '^([[:space:]]*)build:' compose ! rg -n 'overcooked-auth|overcooked-auth-webui' compose install.sh +! rg -n 'S_TURNSTILE_(SITE|SECRET)_KEY: .*\\?required' compose +! rg -n 'S_INTERNAL_TLS_(ADDR|CERT_FILE|KEY_FILE|CLIENT_CA_FILE)' compose install.sh +rg -n 'control-proxy|PRODUCT_E_CONTROL_PLANE_TLS_' compose install.sh >/dev/null echo 'static customer installer checks passed' -