fix(deploy): fail closed on postgres password mismatch

This commit is contained in:
root committed 2026-09-05 14:57:10 +00:00
1 parent d140a47f23
commit aea8cd7d60
8 files changed
+318 -21

No files matched your search

+9
View File
@@ -26,3 +26,12 @@ S node deployment.
The installer must stop on missing prerequisites, invalid public address, The installer must stop on missing prerequisites, invalid public address,
occupied ports, failed image verification, failed schema migration, or failed occupied ports, failed image verification, failed schema migration, or failed
health checks. It must never continue with a partially initialized database. health checks. It must never continue with a partially initialized database.
PostgreSQL initialization and retries use the fixed application roles
`product_e` and `product_s`. Credentials are generated once and then read back
from `production.env`; a password mismatch is fail-closed. The installer never
logs in as `postgres`, changes a role password, removes volumes by default, or
silently continues after a database error. E's failed volume can only be
removed with `OVERCOOKED_RESET_FAILED_INSTALL=1` plus
`OVERCOOKED_CONFIRM_NO_BUSINESS_DATA=YES`; S database mismatches always require
manual recovery.
+10
View File
@@ -83,6 +83,16 @@ 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 If either migration fails, the installer exits non-zero and does not start the
corresponding application service. corresponding application service.
The fixed PostgreSQL roles are `product_e` and `product_s`; the installer never
uses a `postgres` superuser login. A retry reuses the passwords in the existing
mode-600 `production.env` and keeps named volumes. If a role password cannot be
authenticated, it stops with
`DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY` and does not delete data.
Only a failed E database volume may be reset, and only when both
`OVERCOOKED_RESET_FAILED_INSTALL=1` and
`OVERCOOKED_CONFIRM_NO_BUSINESS_DATA=YES` are explicitly supplied. No
`down -v` or broad volume cleanup is performed.
The installer also exports the approved Media Node image into The installer also exports the approved Media Node image into
`/opt/overcooked/node-images/media-node.tar.gz`; S uses that archive when a `/opt/overcooked/node-images/media-node.tar.gz`; S uses that archive when a
customer adds a node without a source checkout. customer adds a node without a source checkout.
+5 -1
View File
@@ -1,6 +1,10 @@
3776581f5b8755afe0fbc3ea9121416504f117da83f2baa6214c315d2d473ef8 install.sh e895daeae728dfc2a54bda1484847ee90465879c981b17b1ab2cfdebcacf2439 install.sh
e8cabc5a989d171206361e6ae8b4e0baf9bb9a611ac41106bbe42de0782e8975 compose/s.yml e8cabc5a989d171206361e6ae8b4e0baf9bb9a611ac41106bbe42de0782e8975 compose/s.yml
203b6da918dee21dbfabbcf5466111bd92c7a8c9e754dad61e7e75e20513b880 compose/e.yml 203b6da918dee21dbfabbcf5466111bd92c7a8c9e754dad61e7e75e20513b880 compose/e.yml
c226c5e1feb5cd6948ee9b32897e77240e21793090d8f42234ca999450ec90b0 config/e-server.production.yaml c226c5e1feb5cd6948ee9b32897e77240e21793090d8f42234ca999450ec90b0 config/e-server.production.yaml
3e3dc775017577fad3b36b6c21036dfcb6e530793de4c18c825dda888f62e4e3 s-migrate.sh 3e3dc775017577fad3b36b6c21036dfcb6e530793de4c18c825dda888f62e4e3 s-migrate.sh
07f432c281ba90565cd376b42967909b6ff2d19d04454a3ba4e3b184117d45af s-migrations.tar.gz 07f432c281ba90565cd376b42967909b6ff2d19d04454a3ba4e3b184117d45af s-migrations.tar.gz
7845f54d517588953d91f7fbef8be26faccc4dc51b81b6d5fb2d699cd5e86bda test/static-check.sh
7fadbf1f62d2f972a5ae697daf75ff711e6d6bae257f45d0ed9928c9e79e4948 test/database-credentials.sh
e20cc9a6210525b6335b70a1567e35cae01054b1558d17f7593598e672574bda test/fake-docker.sh
306c6ca7407560340797866e077e053627ad409277d1b9da58106fce4cf717cb test/fake-curl.sh
+106 -19
View File
@@ -31,6 +31,8 @@ E_PLAYBACK_TICKET_SECRET=not-generated
E_PLAYBACK_LEASE_KEY=not-generated E_PLAYBACK_LEASE_KEY=not-generated
E_STORAGE_CREDENTIAL_KEY=not-generated E_STORAGE_CREDENTIAL_KEY=not-generated
E_PLUGIN_SECRET_KEY=not-generated E_PLUGIN_SECRET_KEY=not-generated
RESET_FAILED_INSTALL=${OVERCOOKED_RESET_FAILED_INSTALL:-0}
CONFIRM_NO_BUSINESS_DATA=${OVERCOOKED_CONFIRM_NO_BUSINESS_DATA:-}
die() { echo "overcooked-install: $*" >&2; exit 1; } die() { echo "overcooked-install: $*" >&2; exit 1; }
@@ -232,16 +234,46 @@ chown 1000:10001 "$CONTROL_PLANE_DIR/client.key"
chmod 755 "$CONTROL_PLANE_DIR" chmod 755 "$CONTROL_PLANE_DIR"
secret() { openssl rand -hex 32; } secret() { openssl rand -hex 32; }
S_POSTGRES_PASSWORD=$(secret); S_VALKEY_PASSWORD=$(secret) read_existing_secret() {
S_INTERNAL_SHARED_SECRET=$(secret); S_ACCESS_TOKEN_SECRET=$(secret); S_SETUP_TOKEN=$(secret) secret_name=$1
E_POSTGRES_PASSWORD=$(secret); E_PLAYBACK_TICKET_SECRET=$(secret); E_PLAYBACK_LEASE_KEY=$(secret) secret_count=$(awk -v key="$secret_name" 'index($0, key "=") == 1 { count++ } END { print count + 0 }' "$ENV_FILE")
E_STORAGE_CREDENTIAL_KEY=$(secret); E_PLUGIN_SECRET_KEY=$(secret) [ "$secret_count" = 1 ] || { echo "production.env 中缺少或重复的 $secret_name" >&2; return 1; }
# Re-runs must reuse credentials that initialized existing database volumes. secret_value=$(awk -v key="$secret_name" 'index($0, key "=") == 1 { print substr($0, length(key) + 2); exit }' "$ENV_FILE")
case "$secret_value" in ''|*[!0-9A-Fa-f]*) echo "production.env 中的 $secret_name 不是安全的 hex 值" >&2; return 1 ;; esac
[ "${#secret_value}" = 64 ] || { echo "production.env 中的 $secret_name 长度无效" >&2; return 1; }
printf '%s\n' "$secret_value"
}
read_existing_secret_alias() {
primary_name=$1; legacy_name=$2
primary_count=$(awk -v key="$primary_name" 'index($0, key "=") == 1 { count++ } END { print count + 0 }' "$ENV_FILE")
legacy_count=$(awk -v key="$legacy_name" 'index($0, key "=") == 1 { count++ } END { print count + 0 }' "$ENV_FILE")
if [ "$primary_count" = 1 ] && [ "$legacy_count" = 0 ]; then
read_existing_secret "$primary_name"
elif [ "$primary_count" = 0 ] && [ "$legacy_count" = 1 ]; then
read_existing_secret "$legacy_name"
else
echo "production.env 中的 $primary_name/$legacy_name 缺少、重复或同时存在" >&2
return 1
fi
}
if [ -f "$ENV_FILE" ]; then if [ -f "$ENV_FILE" ]; then
for name in S_POSTGRES_PASSWORD S_VALKEY_PASSWORD S_INTERNAL_SHARED_SECRET S_ACCESS_TOKEN_SECRET S_SETUP_TOKEN E_POSTGRES_PASSWORD E_PLAYBACK_TICKET_SECRET E_PLAYBACK_LEASE_KEY E_STORAGE_CREDENTIAL_KEY E_PLUGIN_SECRET_KEY; do # Re-runs must reuse every generated credential that initialized existing
value=$(sed -n "s/^${name}=//p" "$ENV_FILE" | head -n 1) # volumes. Do not execute arbitrary production.env content.
[ -n "$value" ] && eval "$name=\"$value\"" S_POSTGRES_PASSWORD=$(read_existing_secret S_POSTGRES_PASSWORD) || die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: 无法安全读取 S_POSTGRES_PASSWORD"
done S_VALKEY_PASSWORD=$(read_existing_secret S_VALKEY_PASSWORD) || die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: 无法安全读取 S_VALKEY_PASSWORD"
S_INTERNAL_SHARED_SECRET=$(read_existing_secret S_INTERNAL_SHARED_SECRET) || die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: 无法安全读取 S_INTERNAL_SHARED_SECRET"
S_ACCESS_TOKEN_SECRET=$(read_existing_secret S_ACCESS_TOKEN_SECRET) || die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: 无法安全读取 S_ACCESS_TOKEN_SECRET"
S_SETUP_TOKEN=$(read_existing_secret S_SETUP_TOKEN) || die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: 无法安全读取 S_SETUP_TOKEN"
E_POSTGRES_PASSWORD=$(read_existing_secret_alias PRODUCT_E_POSTGRES_PASSWORD E_POSTGRES_PASSWORD) || die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: 无法安全读取 Product E 数据库密码"
E_PLAYBACK_TICKET_SECRET=$(read_existing_secret_alias PRODUCT_E_PLAYBACK_TICKET_SECRET E_PLAYBACK_TICKET_SECRET) || die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: 无法安全读取 Product E 播放票据密钥"
E_PLAYBACK_LEASE_KEY=$(read_existing_secret_alias PRODUCT_E_PLAYBACK_LEASE_KEY E_PLAYBACK_LEASE_KEY) || die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: 无法安全读取 Product E 租约密钥"
E_STORAGE_CREDENTIAL_KEY=$(read_existing_secret_alias PRODUCT_E_STORAGE_CREDENTIAL_KEY E_STORAGE_CREDENTIAL_KEY) || die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: 无法安全读取 Product E 存储凭据密钥"
E_PLUGIN_SECRET_KEY=$(read_existing_secret_alias PRODUCT_E_PLUGIN_SECRET_KEY E_PLUGIN_SECRET_KEY) || die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: 无法安全读取 Product E 插件密钥"
else
S_POSTGRES_PASSWORD=$(secret); S_VALKEY_PASSWORD=$(secret)
S_INTERNAL_SHARED_SECRET=$(secret); S_ACCESS_TOKEN_SECRET=$(secret); S_SETUP_TOKEN=$(secret)
E_POSTGRES_PASSWORD=$(secret); E_PLAYBACK_TICKET_SECRET=$(secret); E_PLAYBACK_LEASE_KEY=$(secret)
E_STORAGE_CREDENTIAL_KEY=$(secret); E_PLUGIN_SECRET_KEY=$(secret)
fi fi
INSTANCE_SUFFIX=$(hostname 2>/dev/null | tr -cd '[:alnum:]' | cut -c1-24); [ -n "$INSTANCE_SUFFIX" ] || INSTANCE_SUFFIX=customer INSTANCE_SUFFIX=$(hostname 2>/dev/null | tr -cd '[:alnum:]' | cut -c1-24); [ -n "$INSTANCE_SUFFIX" ] || INSTANCE_SUFFIX=customer
@@ -363,10 +395,14 @@ chmod 644 "$ROOT/node-images/media-node.tar.gz"
wait_for_compose_health() { wait_for_compose_health() {
compose_file=$1; project=$2; service=$3; attempt=1 compose_file=$1; project=$2; service=$3; attempt=1
while [ "$attempt" -le 60 ]; do while [ "$attempt" -le 60 ]; do
container=$(docker compose --env-file "$ENV_FILE" -f "$compose_file" -p "$project" ps -q "$service" 2>/dev/null || true) if ! container=$(docker compose --env-file "$ENV_FILE" -f "$compose_file" -p "$project" ps -q "$service" 2>/dev/null); then
die "$service 容器状态查询失败"
fi
status='' status=''
if [ -n "$container" ]; then if [ -n "$container" ]; then
status=$(docker inspect -f '{{.State.Health.Status}}' "$container" 2>/dev/null || true) if ! status=$(docker inspect -f '{{.State.Health.Status}}' "$container" 2>/dev/null); then
die "$service 容器健康状态查询失败"
fi
fi fi
[ "$status" = healthy ] && return 0 [ "$status" = healthy ] && return 0
[ "$status" = unhealthy ] && die "$service 容器健康检查失败" [ "$status" = unhealthy ] && die "$service 容器健康检查失败"
@@ -376,15 +412,66 @@ wait_for_compose_health() {
die "$service 容器健康检查超时" die "$service 容器健康检查超时"
} }
docker compose --env-file "$ENV_FILE" -f "$E_COMPOSE" -p "$E_PROJECT" up -d postgres valkey reset_e_postgres_volume() {
# A previous interrupted install may have initialized the volume with a compose_file=$1; project=$2
# different generated role password. Reconcile it through the local Postgres database_label=E; expected_volume=e-postgres
# socket before running migrations; no data is dropped. [ "$RESET_FAILED_INSTALL" = 1 ] && [ "$CONFIRM_NO_BUSINESS_DATA" = YES ] || die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: $database_label 数据库密码不匹配;如确认卷中无业务数据,请同时设置 OVERCOOKED_RESET_FAILED_INSTALL=1 OVERCOOKED_CONFIRM_NO_BUSINESS_DATA=YES"
docker compose --env-file "$ENV_FILE" -f "$E_COMPOSE" -p "$E_PROJECT" exec -T postgres \ container=$(docker compose --env-file "$ENV_FILE" -f "$compose_file" -p "$project" ps -q postgres 2>/dev/null || true)
psql -U postgres -d postgres -v ON_ERROR_STOP=1 \ [ -n "$container" ] || die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: 找不到 $database_label postgres 容器"
-c "ALTER ROLE product_e WITH PASSWORD '$E_POSTGRES_PASSWORD';" >/dev/null volume=$(docker inspect -f '{{range .Mounts}}{{if eq .Destination "/var/lib/postgresql/data"}}{{.Name}}{{end}}{{end}}' "$container" 2>/dev/null || true)
case "$volume" in ''|*[!A-Za-z0-9_.-]*) die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: 无法确认 $database_label 数据卷" ;; esac
volume_project=$(docker volume inspect -f '{{index .Labels "com.docker.compose.project"}}' "$volume" 2>/dev/null || true)
volume_service=$(docker volume inspect -f '{{index .Labels "com.docker.compose.volume"}}' "$volume" 2>/dev/null || true)
[ "$volume_project" = "$project" ] && [ "$volume_service" = "$expected_volume" ] || die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: $database_label 数据卷标签不匹配,拒绝删除"
echo "已收到显式无业务数据确认,将删除失败安装的 $database_label PostgreSQL 卷:$volume" >&2
docker compose --env-file "$ENV_FILE" -f "$compose_file" -p "$project" rm -sf postgres
docker volume rm "$volume" >/dev/null || die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: 无法删除 $database_label 数据卷 $volume"
}
check_e_database_role() {
database_output=''
if database_output=$(printf 'postgres:5432:product_e:product_e:%s\n' "$E_POSTGRES_PASSWORD" | docker compose --env-file "$ENV_FILE" -f "$E_COMPOSE" -p "$E_PROJECT" exec -T postgres sh -ceu 'umask 077; cat > /tmp/.overcooked-pgpass; PGPASSFILE=/tmp/.overcooked-pgpass psql -U product_e -d product_e -v ON_ERROR_STOP=1 -Atqc "SELECT current_user"; status=$?; rm -f /tmp/.overcooked-pgpass; exit "$status"' 2>&1); then
[ "$database_output" = product_e ] || { echo "$database_output" | redact_logs >&2; return 1; }
return 0
fi
echo "$database_output" | redact_logs >&2
return 1
}
check_s_database_role() {
database_output=''
if database_output=$(printf 'postgres:5432:product_s:product_s:%s\n' "$S_POSTGRES_PASSWORD" | docker compose --env-file "$ENV_FILE" -f "$S_COMPOSE" -p "$S_PROJECT" exec -T postgres sh -ceu 'umask 077; cat > /tmp/.overcooked-pgpass; PGPASSFILE=/tmp/.overcooked-pgpass psql -U product_s -d product_s -v ON_ERROR_STOP=1 -Atqc "SELECT current_user"; status=$?; rm -f /tmp/.overcooked-pgpass; exit "$status"' 2>&1); then
[ "$database_output" = product_s ] || { echo "$database_output" | redact_logs >&2; return 1; }
return 0
fi
echo "$database_output" | redact_logs >&2
return 1
}
prepare_e_database() {
docker compose --env-file "$ENV_FILE" -f "$E_COMPOSE" -p "$E_PROJECT" up -d postgres valkey
wait_for_compose_health "$E_COMPOSE" "$E_PROJECT" postgres
if check_e_database_role; then return 0; fi
if [ "$RESET_FAILED_INSTALL" = 1 ] && [ "$CONFIRM_NO_BUSINESS_DATA" = YES ]; then
reset_e_postgres_volume "$E_COMPOSE" "$E_PROJECT"
docker compose --env-file "$ENV_FILE" -f "$E_COMPOSE" -p "$E_PROJECT" up -d postgres
wait_for_compose_health "$E_COMPOSE" "$E_PROJECT" postgres
check_e_database_role || die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: 重置后仍无法使用固定 product_e 角色认证"
return 0
fi
die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: product_e 角色密码不匹配;未删除数据库卷"
}
prepare_s_database() {
docker compose --env-file "$ENV_FILE" -f "$S_COMPOSE" -p "$S_PROJECT" up -d postgres valkey
wait_for_compose_health "$S_COMPOSE" "$S_PROJECT" postgres
if check_s_database_role; then return 0; fi
die "DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY: product_s 角色密码不匹配;未删除数据库卷"
}
prepare_e_database
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 "$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 prepare_s_database
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" 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 "$S_COMPOSE" -p "$S_PROJECT" up -d s-control control-proxy webui
wait_for_compose_health "$S_COMPOSE" "$S_PROJECT" control-proxy wait_for_compose_health "$S_COMPOSE" "$S_PROJECT" control-proxy
+100
View File
@@ -0,0 +1,100 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
fake_bin=$(mktemp -d /tmp/overcooked-installer-fakes.XXXXXX)
test_root=$(mktemp -d /tmp/overcooked-installer-db-test.XXXXXX)
trap 'rm -rf "$fake_bin" "$test_root"' EXIT
cp "$root/test/fake-docker.sh" "$fake_bin/docker"
cp "$root/test/fake-curl.sh" "$fake_bin/curl"
chmod 755 "$fake_bin/docker" "$fake_bin/curl"
run_install() {
mode=$1
output=$2
printf '%s\n' "$mode" > "$test_root/db-state"
: > "$test_root/docker.log"
set +e
PATH="$fake_bin:$PATH" \
FAKE_DOCKER_LOG="$test_root/docker.log" \
FAKE_DB_STATE="$test_root/db-state" \
OVERCOOKED_ROOT="$test_root/root" \
OVERCOOKED_SERVER_IP=203.0.113.10 \
OVERCOOKED_API_PORT=29880 \
OVERCOOKED_WEB_PORT=29883 \
OVERCOOKED_E_PORT=29896 \
OVERCOOKED_AUTH_PUBLIC_KEYS='{"auth-1":"PUBLIC_KEY"}' \
OVERCOOKED_ACTIVATION_CODE=TEST-CODE \
"$root/install.sh" > "$output" 2>&1
status=$?
set -e
if [ "$status" -ne 0 ]; then
cat "$output" >&2
fi
return "$status"
}
first_output=$test_root/first.out
run_install ok "$first_output"
first_e_password=$(sed -n 's/^PRODUCT_E_POSTGRES_PASSWORD=//p' "$test_root/root/production.env")
first_s_password=$(sed -n 's/^S_POSTGRES_PASSWORD=//p' "$test_root/root/production.env")
[ "${#first_e_password}" = 64 ]
[ "${#first_s_password}" = 64 ]
[ "$(stat -c '%a' "$test_root/root/production.env")" = 600 ]
! rg -n 'volume rm|down .*-[^ ]*v' "$test_root/docker.log"
second_output=$test_root/second.out
run_install ok "$second_output"
second_e_password=$(sed -n 's/^PRODUCT_E_POSTGRES_PASSWORD=//p' "$test_root/root/production.env")
second_s_password=$(sed -n 's/^S_POSTGRES_PASSWORD=//p' "$test_root/root/production.env")
[ "$first_e_password" = "$second_e_password" ]
[ "$first_s_password" = "$second_s_password" ]
! rg -n 'volume rm|down .*-[^ ]*v' "$test_root/docker.log"
echo "MALICIOUS=\$(touch $test_root/marker)" >> "$test_root/root/production.env"
third_output=$test_root/third.out
run_install ok "$third_output"
[ ! -e "$test_root/marker" ]
mismatch_output=$test_root/mismatch.out
if run_install mismatch "$mismatch_output"; then
echo 'password mismatch unexpectedly succeeded' >&2
exit 1
fi
rg -q 'DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY' "$mismatch_output"
! rg -n '^volume rm|down .*-[^ ]*v' "$test_root/docker.log"
[ "$first_e_password" = "$(sed -n 's/^PRODUCT_E_POSTGRES_PASSWORD=//p' "$test_root/root/production.env")" ]
wrong_role_output=$test_root/wrong-role.out
if run_install wrong-role "$wrong_role_output"; then
echo 'wrong database role unexpectedly succeeded' >&2
exit 1
fi
rg -q 'DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY' "$wrong_role_output"
! rg -n '^volume rm|down .*-[^ ]*v' "$test_root/docker.log"
s_mismatch_output=$test_root/s-mismatch.out
if run_install s-mismatch "$s_mismatch_output"; then
echo 'S password mismatch unexpectedly succeeded' >&2
exit 1
fi
rg -q 'DATABASE_PASSWORD_MISMATCH_NEEDS_MANUAL_RECOVERY' "$s_mismatch_output"
! rg -n '^volume rm|down .*-[^ ]*v' "$test_root/docker.log"
reset_output=$test_root/reset.out
printf 'reset-once\n' > "$test_root/db-state"
PATH="$fake_bin:$PATH" \
FAKE_DOCKER_LOG="$test_root/docker.log" \
FAKE_DB_STATE="$test_root/db-state" \
OVERCOOKED_ROOT="$test_root/root" \
OVERCOOKED_SERVER_IP=203.0.113.10 \
OVERCOOKED_API_PORT=29880 \
OVERCOOKED_WEB_PORT=29883 \
OVERCOOKED_E_PORT=29896 \
OVERCOOKED_AUTH_PUBLIC_KEYS='{"auth-1":"PUBLIC_KEY"}' \
OVERCOOKED_ACTIVATION_CODE=TEST-CODE \
OVERCOOKED_RESET_FAILED_INSTALL=1 \
OVERCOOKED_CONFIRM_NO_BUSINESS_DATA=YES \
"$root/install.sh" > "$reset_output" 2>&1
rg -q '已收到显式无业务数据确认' "$reset_output"
rg -q '^volume rm fake-e-postgres$' "$test_root/docker.log"
echo 'database credential retry/reset tests passed'
+2
View File
@@ -0,0 +1,2 @@
#!/bin/sh
exit 0
+81
View File
@@ -0,0 +1,81 @@
#!/bin/sh
set -eu
: "${FAKE_DOCKER_LOG:?}"
: "${FAKE_DB_STATE:?}"
printf '%s\n' "$*" >> "$FAKE_DOCKER_LOG"
if [ "${1:-}" = compose ]; then
shift
args=$*
case "$args" in
*' version'*) exit 0 ;;
*' ps -q postgres'*) printf 'fake-postgres\n'; exit 0 ;;
*' ps -q control-proxy'*) printf 'fake-control-proxy\n'; exit 0 ;;
*' exec '*product_e*)
if [ "$(cat "$FAKE_DB_STATE")" = mismatch ]; then
printf 'FATAL: password authentication failed for user product_e\n' >&2
exit 2
fi
if [ "$(cat "$FAKE_DB_STATE")" = reset-once ]; then
printf 'FATAL: password authentication failed for user product_e\n' >&2
exit 2
fi
if [ "$(cat "$FAKE_DB_STATE")" = wrong-role ]; then
printf 'product_s\n'
exit 0
fi
printf 'product_e\n'; exit 0 ;;
*' exec '*product_s*)
if [ "$(cat "$FAKE_DB_STATE")" = wrong-role ]; then
printf 'product_e\n'
exit 0
fi
if [ "$(cat "$FAKE_DB_STATE")" = s-mismatch ]; then
printf 'FATAL: password authentication failed for user product_s\n' >&2
exit 2
fi
printf 'product_s\n'; exit 0 ;;
*) exit 0 ;;
esac
fi
case "${1:-}" in
network|pull|image) exit 0 ;;
save)
output=''
previous=''
for argument do
if [ "$previous" = -o ]; then output=$argument; fi
previous=$argument
done
[ -n "$output" ] && : > "$output"
exit 0
;;
inspect)
format=${3:-}
case "$format" in
*State.Health.Status*) printf 'healthy\n' ;;
*var/lib/postgresql/data*) printf 'fake-e-postgres\n' ;;
*) printf '\n' ;;
esac
exit 0
;;
volume)
case "${2:-}" in
inspect)
case "${4:-}" in
*com.docker.compose.project*) printf 'overcooked-e\n' ;;
*com.docker.compose.volume*) printf 'e-postgres\n' ;;
esac
exit 0
;;
rm)
printf 'ok\n' > "$FAKE_DB_STATE"
exit 0
;;
esac
;;
esac
exit 0
+5 -1
View File
@@ -16,11 +16,15 @@ test -f compose/s.yml
test -f compose/e.yml test -f compose/e.yml
test -f config/e-server.production.yaml test -f config/e-server.production.yaml
test -f s-migrations.tar.gz test -f s-migrations.tar.gz
test "$(find s-migrations -type f -name '*.up.sql' | wc -l)" -gt 0 test "$(tar -tzf s-migrations.tar.gz | awk '/\.up\.sql$/ { count++ } END { print count + 0 }')" -gt 0
test -x test/database-credentials.sh
sha256sum -c SHA256SUMS >/dev/null sha256sum -c SHA256SUMS >/dev/null
! rg -n '^([[:space:]]*)build:' compose ! rg -n '^([[:space:]]*)build:' compose
! rg -n 'overcooked-auth|overcooked-auth-webui' compose install.sh ! rg -n 'overcooked-auth|overcooked-auth-webui' compose install.sh
! rg -n 'S_TURNSTILE_(SITE|SECRET)_KEY: .*\\?required' compose ! 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 'S_INTERNAL_TLS_(ADDR|CERT_FILE|KEY_FILE|CLIENT_CA_FILE)' compose install.sh
! rg -n 'psql[[:space:]]+-U[[:space:]]+postgres|(^|[^[:alnum:]_])eval([[:space:]]|$)' install.sh
rg -n 'psql[[:space:]]+-U[[:space:]]+product_e|psql[[:space:]]+-U[[:space:]]+product_s' install.sh >/dev/null
rg -n 'control-proxy|PRODUCT_E_CONTROL_PLANE_TLS_' compose install.sh >/dev/null rg -n 'control-proxy|PRODUCT_E_CONTROL_PLANE_TLS_' compose install.sh >/dev/null
./test/database-credentials.sh
echo 'static customer installer checks passed' echo 'static customer installer checks passed'