thread-master/scripts/prod-common.sh

201 lines
5.4 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# shellcheck disable=SC2034
# Shared helpers for production Docker scripts.
_PROD_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BACKEND_DIR="$(cd "$_PROD_SCRIPT_DIR/.." && pwd)"
DEPLOY_DIR="$BACKEND_DIR/deploy"
COMPOSE_FILE="$DEPLOY_DIR/docker-compose.prod.yml"
ENV_FILE="$DEPLOY_DIR/.env"
ENV_EXAMPLE="$DEPLOY_DIR/.env.example"
prod_common_init() {
:
}
prod_load_env() {
prod_common_init
if [[ ! -f "$ENV_FILE" ]]; then
if [[ -f "$ENV_EXAMPLE" ]]; then
cp "$ENV_EXAMPLE" "$ENV_FILE"
echo "[prod] created $ENV_FILE from .env.example — 請先修改密鑰與管理員密碼"
else
echo "[prod] missing $ENV_FILE" >&2
exit 1
fi
fi
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
GO_REPLICAS="${GO_WORKER_REPLICAS:-1}"
NODE_REPLICAS="${NODE_STYLE8D_WORKER_REPLICAS:-1}"
WEB_PORT="${HAIXUN_WEB_PORT:-8080}"
MONGO_DB="${HAIXUN_MONGO_DATABASE:-haixun}"
}
prod_require_docker() {
if ! command -v docker >/dev/null 2>&1; then
echo "[prod] docker is required" >&2
exit 1
fi
}
prod_compose() {
prod_common_init
docker compose -f "$COMPOSE_FILE" "$@"
}
prod_service_health() {
local service="$1"
prod_compose ps --format json "$service" 2>/dev/null \
| grep -o '"Health":"[^"]*"' \
| head -1 \
| cut -d'"' -f4 \
|| true
}
prod_named_container_health() {
local name="$1"
docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{if .State.Running}}running{{else}}stopped{{end}}{{end}}' \
"$name" 2>/dev/null || true
}
prod_named_container_running() {
local name="$1"
[[ "$(docker inspect --format '{{.State.Running}}' "$name" 2>/dev/null || echo false)" == "true" ]]
}
prod_deps_healthy() {
local mongo_ok redis_ok
mongo_ok="$(prod_service_health mongo)"
redis_ok="$(prod_service_health redis)"
if [[ "$mongo_ok" == "healthy" && "$redis_ok" == "healthy" ]]; then
return 0
fi
# compose ps 偶發失敗時,改查實際 container避免重複 create 撞名)
mongo_ok="$(prod_named_container_health haixun-prod-mongo-1)"
redis_ok="$(prod_named_container_health haixun-prod-redis-1)"
[[ "$mongo_ok" == "healthy" && "$redis_ok" == "healthy" ]]
}
prod_wait_deps_healthy() {
echo "[prod] waiting for mongo/redis..."
for _ in $(seq 1 90); do
if prod_deps_healthy; then
return 0
fi
sleep 1
done
echo "[prod] mongo/redis did not become healthy in time" >&2
exit 1
}
prod_ensure_deps() {
if prod_deps_healthy; then
echo "[prod] mongo + redis already healthy — 略過重啟(資料在 named volume"
return 0
fi
echo "[prod] starting mongo + redis..."
if prod_named_container_running haixun-prod-mongo-1 && prod_named_container_running haixun-prod-redis-1; then
prod_compose start mongo redis 2>/dev/null \
|| prod_compose up -d --no-recreate mongo redis
else
prod_compose up -d mongo redis
fi
prod_wait_deps_healthy
}
prod_mongo_has_members() {
prod_compose exec -T mongo mongosh --quiet "$MONGO_DB" --eval \
'db.members.countDocuments({})' 2>/dev/null \
| tr -d '\r' \
| grep -Eq '^[1-9][0-9]*$'
}
prod_should_skip_init() {
if [[ "${HAIXUN_SKIP_INIT:-0}" == "1" ]]; then
return 0
fi
if [[ "${PROD_FORCE_INIT:-0}" == "1" ]]; then
return 1
fi
if prod_mongo_has_members; then
return 0
fi
return 1
}
prod_run_init_if_needed() {
if prod_should_skip_init; then
if [[ "${HAIXUN_SKIP_INIT:-0}" == "1" ]]; then
echo "[prod] skip init (HAIXUN_SKIP_INIT=1)"
else
echo "[prod] skip init (Mongo 已有資料;若要強制重跑請設 PROD_FORCE_INIT=1)"
fi
return 0
fi
echo "[prod] running bootstrap init..."
prod_compose --profile init run --rm init
}
prod_build_web_if_static() {
prod_common_init
if [[ "${HAIXUN_WEB_BUILD_MODE:-static}" == "static" ]]; then
echo "[prod] building frontend static files (vite → web/dist)..."
(cd "$BACKEND_DIR" && make web-build)
else
echo "[prod] HAIXUN_WEB_BUILD_MODE=docker — web image will compile inside Docker"
fi
}
prod_start_app_services() {
local build_flag=()
if [[ "${PROD_SKIP_BUILD:-0}" != "1" ]]; then
build_flag=(--build)
fi
echo "[prod] starting api, web, workers (go=${GO_REPLICAS}, node-style-8d=${NODE_REPLICAS})..."
prod_compose up -d "${build_flag[@]}" \
--no-deps \
--scale "go-worker=${GO_REPLICAS}" \
--scale "node-worker-style-8d=${NODE_REPLICAS}" \
api web go-worker node-worker-style-8d
}
prod_wait_api_health() {
echo "[prod] waiting for API health..."
for _ in $(seq 1 60); do
if curl -fsS "http://127.0.0.1:${WEB_PORT}/api/v1/health" >/dev/null 2>&1; then
return 0
fi
sleep 1
done
echo "[prod] API health check timed out" >&2
exit 1
}
prod_print_volume_hint() {
echo " Data: Mongo/Redis 使用 named volume重啟 container 不會清資料)"
echo " Update app: make -C haixun-backend prod-update"
echo " Wipe data: make -C haixun-backend prod-wipe-data # 會刪除 volume"
}
prod_print_stack_summary() {
echo ""
echo "[prod] stack is up"
echo " Web: http://127.0.0.1:${WEB_PORT}"
echo " API: http://127.0.0.1:${WEB_PORT}/api/v1/health (via nginx)"
echo " Go worker: ${GO_REPLICAS} replica(s)"
echo " Node 8D: ${NODE_REPLICAS} replica(s)"
echo " Env: ${ENV_FILE}"
echo " Stop: make -C haixun-backend prod-down"
echo " Logs: make -C haixun-backend prod-logs"
prod_print_volume_hint
}