#!/usr/bin/env bash set -Eeuo pipefail # Ubuntu 服务器一键部署脚本。 # 用法:bash deploy/ubuntu-deploy.sh PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" ENV_FILE="${PROJECT_ROOT}/.env" ENV_EXAMPLE="${PROJECT_ROOT}/.env.server.example" BACKEND_DATA_DIR="${PROJECT_ROOT}/apps/backend/data" BACKEND_CONTAINER_UID=1001 BACKEND_CONTAINER_GID=1001 HEALTH_URL="" log() { printf '\033[1;34m[deploy]\033[0m %s\n' "$*" } warn() { printf '\033[1;33m[deploy]\033[0m %s\n' "$*" } fail() { printf '\033[1;31m[deploy]\033[0m %s\n' "$*" >&2 exit 1 } read_env_value() { local key="$1" awk -F= -v key="${key}" ' $0 !~ /^[[:space:]]*#/ && $1 == key { value = substr($0, index($0, "=") + 1) gsub(/^[[:space:]]+|[[:space:]]+$/, "", value) gsub(/^"|"$/, "", value) gsub(/^'\''|'\''$/, "", value) print value exit } ' "${ENV_FILE}" } ensure_docker() { command -v docker >/dev/null 2>&1 || fail "未找到 docker,请先在 Ubuntu 上安装 Docker Engine。" docker compose version >/dev/null 2>&1 || fail "未找到 docker compose 插件,请安装 Docker Compose plugin。" command -v curl >/dev/null 2>&1 || fail "未找到 curl,请先安装:sudo apt-get update && sudo apt-get install -y curl" } ensure_env_file() { if [[ -f "${ENV_FILE}" ]]; then return fi cp "${ENV_EXAMPLE}" "${ENV_FILE}" fail "已生成 .env,请先编辑真实域名、数据库密码、后台账号和密钥后再重新执行。" } validate_env_file() { if grep -nE 'change-me|your-domain|example\.com' "${ENV_FILE}" >/tmp/order-site-env-placeholders.txt; then cat /tmp/order-site-env-placeholders.txt >&2 fail ".env 仍包含示例占位值,请替换后再部署。" fi local site_addr site_addr="$(read_env_value CADDY_SITE_ADDR)" [[ -n "${site_addr}" ]] || fail ".env 缺少 CADDY_SITE_ADDR。" HEALTH_URL="${site_addr%/}/health/ready" } ensure_data_dir() { mkdir -p "${BACKEND_DATA_DIR}" if [[ "$(id -u)" == "0" ]]; then chown -R "${BACKEND_CONTAINER_UID}:${BACKEND_CONTAINER_GID}" "${BACKEND_DATA_DIR}" return fi if command -v sudo >/dev/null 2>&1; then sudo chown -R "${BACKEND_CONTAINER_UID}:${BACKEND_CONTAINER_GID}" "${BACKEND_DATA_DIR}" return fi warn "未找到 sudo,跳过目录属主调整。若后端无法写入日志或配置,请执行:" warn "sudo chown -R ${BACKEND_CONTAINER_UID}:${BACKEND_CONTAINER_GID} ${BACKEND_DATA_DIR}" } deploy_stack() { log "拉取基础镜像" docker compose pull --ignore-buildable log "构建并启动服务" docker compose up -d --build } wait_for_health() { log "等待服务健康检查:${HEALTH_URL}" for _ in $(seq 1 60); do if curl -fsS "${HEALTH_URL}" >/dev/null 2>&1; then log "服务已就绪" return fi sleep 2 done docker compose ps warn "健康检查暂未通过,最近后端日志如下:" docker compose logs --tail=80 backend fail "部署已启动但健康检查未通过,请根据日志排查。" } main() { cd "${PROJECT_ROOT}" ensure_docker ensure_env_file validate_env_file ensure_data_dir deploy_stack wait_for_health log "部署完成" docker compose ps } main "$@"