#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BACKEND_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" JAR_PATH="${JAR_PATH:-$BACKEND_DIR/target/petstore-backend-1.0.0.jar}" JAVA_BIN="${JAVA_BIN:-java}" BUILD_FIRST="${BUILD_FIRST:-1}" fail() { printf 'PRECHECK FAIL: %s\n' "$1" >&2 exit 1 } require_env() { local name="$1" [[ -n "${!name:-}" ]] || fail "missing environment variable: $name" } for name in MYSQL_URL MYSQL_USERNAME MYSQL_PASSWORD WECHAT_APPID WECHAT_APPSECRET \ WECHAT_REDIRECT_URI PETSTORE_SESSION_SECRET APP_BASE_URL UPLOAD_PATH CORS_ALLOWED_ORIGINS; do require_env "$name" done [[ "$APP_BASE_URL" == https://* ]] || fail "APP_BASE_URL must use https" [[ "$APP_BASE_URL" != *localhost* ]] || fail "APP_BASE_URL must not use localhost" [[ "$APP_BASE_URL" != *.invalid* ]] || fail "APP_BASE_URL must not use a placeholder domain" [[ "$APP_BASE_URL" != *.example* && "$APP_BASE_URL" != *.test* ]] \ || fail "APP_BASE_URL must not use a reserved example/test domain" [[ "$WECHAT_REDIRECT_URI" == https://* ]] || fail "WECHAT_REDIRECT_URI must use https" [[ "$WECHAT_REDIRECT_URI" != *localhost* && "$WECHAT_REDIRECT_URI" != *.invalid* ]] \ || fail "WECHAT_REDIRECT_URI must use a production domain" [[ "$WECHAT_REDIRECT_URI" != *.example* && "$WECHAT_REDIRECT_URI" != *.test* ]] \ || fail "WECHAT_REDIRECT_URI must not use a reserved example/test domain" [[ ${#PETSTORE_SESSION_SECRET} -ge 32 ]] || fail "PETSTORE_SESSION_SECRET must be at least 32 characters" [[ "$PETSTORE_SESSION_SECRET" != "dev-change-me" ]] || fail "PETSTORE_SESSION_SECRET must not use the default" [[ -z "${SMS_UNIVERSAL_CODE:-}" ]] || fail "SMS_UNIVERSAL_CODE must be empty in production" [[ "${SPRING_JPA_HIBERNATE_DDL_AUTO:-validate}" == "validate" ]] || fail "production ddl-auto must be validate" [[ "${SPRING_JPA_SHOW_SQL:-false}" == "false" ]] || fail "production show-sql must be false" [[ "$UPLOAD_PATH" == /* ]] || fail "UPLOAD_PATH must be absolute" [[ -d "$UPLOAD_PATH" && -r "$UPLOAD_PATH" && -w "$UPLOAD_PATH" ]] \ || fail "UPLOAD_PATH must exist and be readable/writable by the service user" IFS=',' read -r -a cors_origins <<< "$CORS_ALLOWED_ORIGINS" [[ ${#cors_origins[@]} -gt 0 ]] || fail "CORS_ALLOWED_ORIGINS must not be empty" for origin in "${cors_origins[@]}"; do origin="${origin#${origin%%[![:space:]]*}}" origin="${origin%${origin##*[![:space:]]}}" [[ "$origin" == https://* && "$origin" != *'*'* \ && "$origin" != *localhost* && "$origin" != *127.0.0.1* && "$origin" != *.invalid* ]] \ || fail "every CORS origin must be an explicit https origin" [[ "$origin" != *.example* && "$origin" != *.test* ]] \ || fail "CORS origins must not use reserved example/test domains" done command_ready() { local binary="$1" if [[ "$binary" == */* ]]; then [[ -x "$binary" ]] else command -v "$binary" >/dev/null 2>&1 fi } FFMPEG_BINARY="${HIGHLIGHT_FFMPEG:-ffmpeg}" FFPROBE_BINARY="${HIGHLIGHT_FFPROBE:-ffprobe}" command_ready "$FFMPEG_BINARY" || fail "HIGHLIGHT_FFMPEG is not executable" command_ready "$FFPROBE_BINARY" || fail "HIGHLIGHT_FFPROBE is not executable" command -v "$JAVA_BIN" >/dev/null 2>&1 || fail "JAVA_BIN is not executable" if [[ "$BUILD_FIRST" == "1" ]]; then (cd "$BACKEND_DIR" && mvn -q -DskipTests package) fi [[ -r "$JAR_PATH" ]] || fail "backend jar not found: $JAR_PATH" printf 'Static production precheck PASS\n' printf 'Running read-only Spring/JPA/database invariant preflight...\n' exec "$JAVA_BIN" -jar "$JAR_PATH" \ --spring.profiles.active=production \ --spring.main.web-application-type=none \ --app.preflight-only=true