diff --git a/.dockerignore b/.dockerignore index 09165d44..26c5bd74 100644 --- a/.dockerignore +++ b/.dockerignore @@ -16,6 +16,7 @@ apps/*/dist apps/*/.vite # backend runtime data +apps/backend/data/ apps/backend/data/*.db apps/backend/data/*.db-shm apps/backend/data/*.db-wal @@ -24,5 +25,5 @@ apps/backend/data/redeem-screenshots apps/backend/data/cache apps/backend/.npm-cache -# deploy data (server only) +# legacy deploy data (server only) deploy/data/ diff --git a/README.md b/README.md index 6fcab299..e631068a 100644 --- a/README.md +++ b/README.md @@ -115,10 +115,12 @@ cp .env.server.example .env 生产部署数据: -- 后端数据挂载到 `deploy/data/backend` +- 后端数据挂载到 `apps/backend/data`,与本地开发路径保持一致 - PostgreSQL 数据保存在 Docker volume `postgres_data` - Caddy 数据保存在 Docker volume `caddy_data` +如果旧部署曾使用 `deploy/data/backend`,升级前请先把其中的 JSON 配置迁移到 `apps/backend/data`。 + 注意:`apps/backend/data/*.json` 中可能包含账号、Cookie、token、平台配置等敏感信息,默认不进入仓库;仓库只保留 `*.example.json` 模板。 ## 生产部署 @@ -127,10 +129,19 @@ cp .env.server.example .env ```bash cp .env.server.example .env -mkdir -p deploy/data/backend +mkdir -p apps/backend/data +sudo chown -R 1001:1001 apps/backend/data docker compose up -d --build ``` +Ubuntu 服务器也可以使用一键部署脚本: + +```bash +bash deploy/ubuntu-deploy.sh +``` + +第一次执行如果没有 `.env`,脚本会从 `.env.server.example` 生成模板并停止;编辑真实配置后再次执行即可。脚本会自动检查 Docker、检查 `.env` 占位值、修正后端数据目录权限、构建启动服务并等待健康检查通过。 + 生产入口: - 前端与领取页:`https://你的域名/` diff --git a/deploy/ubuntu-deploy.sh b/deploy/ubuntu-deploy.sh new file mode 100755 index 00000000..c055a7d5 --- /dev/null +++ b/deploy/ubuntu-deploy.sh @@ -0,0 +1,125 @@ +#!/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 "$@" diff --git a/docker-compose.yml b/docker-compose.yml index d2c62b91..a3c0f5ab 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,7 +47,7 @@ services: ADMIN_SESSION_SECRET: ${ADMIN_SESSION_SECRET} ADMIN_DEFAULT_USERS_JSON: ${ADMIN_DEFAULT_USERS_JSON} volumes: - - ./deploy/data/backend:/app/data + - ./apps/backend/data:/app/data web: build: