文件
hyb-oa/deploy/trialrun/production-identity-bootstrap-remote.sh
T
wangsli bf2631c0d4
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s
修正(08-08-production-deployment-execution): 先验生产公网身份再冻结目录
2026-08-08 19:36:32 +08:00

106 行
3.6 KiB
Bash
可执行文件

#!/usr/bin/env bash
# Read-only identity bootstrap. The caller captures the database identity in
# memory and must never forward this script's stdout to an ordinary log.
set -euo pipefail
fail() {
printf '[production-identity-bootstrap][FAIL] code=%s\n' "$1" >&2
exit 1
}
for key in IDENTITY_BOOTSTRAP_MODE REMOTE_DIR REMOTE_ENV_FILE COMPOSE_FILE \
COMPOSE_PROJECT_NAME EXPECTED_PRODUCTION_ORIGIN_SHA256; do
[[ -n "${!key:-}" ]] || fail "${key}_MISSING"
done
case "$IDENTITY_BOOTSTRAP_MODE" in
origin|database) ;;
*) fail "IDENTITY_BOOTSTRAP_MODE_INVALID" ;;
esac
[[ "$EXPECTED_PRODUCTION_ORIGIN_SHA256" =~ ^[0-9a-f]{64}$ ]] \
|| fail "PROD_TARGET_ORIGIN_HASH_INVALID"
cd "$REMOTE_DIR"
[[ -f "$REMOTE_ENV_FILE" && ! -L "$REMOTE_ENV_FILE" && -r "$REMOTE_ENV_FILE" ]] \
|| fail "PROD_TARGET_ENV_UNREADABLE"
dotenv_value() {
local key="$1"
local value
value="$(awk -v wanted="$key" '
$0 !~ /^[[:space:]]*#/ && $0 ~ "^[[:space:]]*" wanted "=" {
sub("^[[:space:]]*" wanted "=", "")
print
exit
}
' "$REMOTE_ENV_FILE")" || return 1
value="${value%$'\r'}"
case "$value" in
\"*\") value="${value:1:${#value}-2}" ;;
\'*\') value="${value:1:${#value}-2}" ;;
esac
[[ -n "$value" ]] || return 1
printf '%s' "$value"
}
hash_text() {
if command -v sha256sum >/dev/null 2>&1; then
printf '%s' "$1" | sha256sum | awk '{print $1}'
return 0
fi
if command -v shasum >/dev/null 2>&1; then
printf '%s' "$1" | shasum -a 256 | awk '{print $1}'
return 0
fi
return 1
}
reject_non_production_identity() {
local value="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')"
[[ "$value" != *"<"* && "$value" != *">"* ]] || return 1
[[ "$value" != *"localhost"* && "$value" != *"127.0.0.1"* \
&& "$value" != *"::1"* && "$value" != *"hyb_oa_regression"* \
&& "$value" != *"staging"* && "$value" != *"stage"* \
&& "$value" != *"regression"* && "$value" != *"shadow"* \
&& "$value" != *"/test"* ]]
}
actual_origin="$(dotenv_value APP_ORIGIN)" || fail "PROD_TARGET_ORIGIN_MISSING"
[[ "$(hash_text "$actual_origin")" == "$EXPECTED_PRODUCTION_ORIGIN_SHA256" ]] \
|| fail "PROD_TARGET_ORIGIN_MISMATCH"
if docker compose version >/dev/null 2>&1; then
compose_cmd=(docker compose -p "$COMPOSE_PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$REMOTE_ENV_FILE")
elif command -v docker-compose >/dev/null 2>&1; then
compose_cmd=(docker-compose -p "$COMPOSE_PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$REMOTE_ENV_FILE")
else
fail "PROD_TARGET_COMPOSE_UNAVAILABLE"
fi
mapfile -t server_ids < <(
docker ps \
--filter "label=com.docker.compose.project=$COMPOSE_PROJECT_NAME" \
--filter "label=com.docker.compose.service=server" \
--format '{{.ID}}' 2>/dev/null || true
)
[[ "${#server_ids[@]}" -eq 1 ]] || fail "PROD_TARGET_RUNTIME_NOT_UNIQUE"
runtime_origin="$(
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' "${server_ids[0]}" \
| sed -n 's/^APP_ORIGIN=//p' | tail -n 1
)"
[[ "$(hash_text "$runtime_origin")" == "$EXPECTED_PRODUCTION_ORIGIN_SHA256" ]] \
|| fail "PROD_TARGET_RUNTIME_ORIGIN_MISMATCH"
if [[ "$IDENTITY_BOOTSTRAP_MODE" == "origin" ]]; then
printf '[production-identity-bootstrap][PASS] origin_identity\n'
exit 0
fi
# Only the comparison baseline crosses the SSH channel. The database name
# itself remains remote and cannot be reconstructed from task evidence.
actual_db_name="$(dotenv_value POSTGRES_DB)" || fail "PROD_TARGET_DB_NAME_MISSING"
reject_non_production_identity "$actual_db_name" \
|| fail "PROD_TARGET_DB_NON_PRODUCTION_IDENTITY"
printf '[production-identity-bootstrap][DB_SHA256] %s\n' "$(hash_text "$actual_db_name")"