fix(deploy): add optional Turnstile and customer mTLS proxy
This commit is contained in:
1 parent
e06a43c76f
commit
35068319e5
7 files changed
+186
-28
No files matched your search
+10
-1
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
+3
-3
@@ -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
|
||||
+4
-1
@@ -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:
|
||||
|
||||
+33
-3
@@ -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
|
||||
|
||||
+113
-14
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in new issue
Block a user