#!/bin/bash # ============================================================ # OpenDance Studio Web 服务端 —— Linux 原生站点部署脚本 # # 部署形态(不使用 Docker): # nginx ── 静态托管管理端 + 反向代理 API # └──▶ systemd 托管的 Node 进程(NestJS,仅监听 127.0.0.1) # # 数据库仍是部署方提供的**现有 MySQL / MariaDB 实例**,本脚本不安装、不管理数据库。 # # 用法(在解压后的发布目录内,需要 root): # cp deploy/.env.example deploy/.env && vi deploy/.env # sudo bash deploy/install.sh # # 可用环境变量覆盖: # APP_DIR 安装目录(默认 /opt/opendance) # SERVICE_USER 运行服务的系统用户(默认 opendance) # STORAGE_DIR 上传文件目录(默认 /var/lib/opendance/storage) # SERVER_NAME nginx server_name(默认 _) # SKIP_NGINX=1 只装 API 服务,不配置 nginx # ============================================================ set -euo pipefail SRC_DIR="$(cd "$(dirname "$0")/.." && pwd)" cd "$SRC_DIR" APP_DIR="${APP_DIR:-/opt/opendance}" SERVICE_USER="${SERVICE_USER:-opendance}" STORAGE_DIR="${STORAGE_DIR:-/var/lib/opendance/storage}" SERVER_NAME="${SERVER_NAME:-_}" API_HOST="127.0.0.1" API_PORT="${PORT:-3000}" log() { printf '\033[1;34m[install]\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*"; } die() { printf '\033[1;31m[error]\033[0m %s\n' "$*" >&2; exit 1; } # ---------- 0. 前置检查 ---------- [ "$(id -u)" -eq 0 ] || die "需要 root 权限(安装 systemd 服务与 nginx 站点):sudo bash deploy/install.sh" [ -f deploy/.env ] || die "缺少 deploy/.env,请先: cp deploy/.env.example deploy/.env && vi deploy/.env" set -a # shellcheck disable=SC1091 . ./deploy/.env set +a API_PORT="${PORT:-3000}" [ -n "${DB_HOST:-}" ] || die "deploy/.env 中 DB_HOST 未设置" [ -n "${DB_USER:-}" ] || die "deploy/.env 中 DB_USER 未设置" [ -n "${DB_PASS:-}" ] || die "deploy/.env 中 DB_PASS 未设置" [ -n "${DB_NAME:-}" ] || die "deploy/.env 中 DB_NAME 未设置" command -v node >/dev/null 2>&1 || die "未找到 node。请安装 Node.js 22 或更高版本。" NODE_BIN="$(command -v node)" NODE_MAJOR="$(node -p 'process.versions.node.split(".")[0]')" [ "$NODE_MAJOR" -ge 22 ] 2>/dev/null || die "Node.js 版本过低(当前 $(node -v)),需要 22+" command -v npm >/dev/null 2>&1 || die "未找到 npm" log "Node.js:$("$NODE_BIN" -v)($NODE_BIN)" log "安装目录:$APP_DIR" log "运行用户:$SERVICE_USER" log "上传目录:$STORAGE_DIR" log "目标数据库:${DB_HOST}:${DB_PORT:-3306}/${DB_NAME}(现有实例,本脚本不会创建或管理数据库)" # ---------- 1. 系统用户与目录 ---------- if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then log "创建系统用户 $SERVICE_USER" useradd --system --shell /usr/sbin/nologin --home-dir "$APP_DIR" --create-home "$SERVICE_USER" 2>/dev/null \ || useradd -r -s /sbin/nologin -d "$APP_DIR" -m "$SERVICE_USER" fi log "同步程序文件到 $APP_DIR" mkdir -p "$APP_DIR" for item in web-server admin contracts; do [ -e "$SRC_DIR/$item" ] || die "发布包缺少 $item/ 目录" rm -rf "${APP_DIR:?}/$item" cp -r "$SRC_DIR/$item" "$APP_DIR/$item" done mkdir -p "$APP_DIR/deploy" cp -r "$SRC_DIR/deploy/." "$APP_DIR/deploy/" if [ -f "$SRC_DIR/THIRD_PARTY.md" ]; then cp "$SRC_DIR/THIRD_PARTY.md" "$APP_DIR/"; fi if [ -f "$SRC_DIR/README.txt" ]; then cp "$SRC_DIR/README.txt" "$APP_DIR/"; fi mkdir -p "$STORAGE_DIR" chown -R "$SERVICE_USER:$SERVICE_USER" "$APP_DIR" "$STORAGE_DIR" chmod 750 "$STORAGE_DIR" # ---------- 2. 安装运行期依赖(必要时构建) ---------- cd "$APP_DIR/web-server" if [ -f package-lock.json ]; then log "安装运行期依赖(npm ci --omit=dev)" sudo -u "$SERVICE_USER" npm ci --omit=dev --no-audit --no-fund else log "安装运行期依赖(npm install --omit=dev)" sudo -u "$SERVICE_USER" npm install --omit=dev --no-audit --no-fund fi if [ ! -f dist/main.js ]; then warn "未找到构建产物 dist/main.js,改为在服务器上构建(需要 devDependencies)" sudo -u "$SERVICE_USER" npm install --no-audit --no-fund sudo -u "$SERVICE_USER" npm run build fi [ -f dist/main.js ] || die "构建产物缺失:$APP_DIR/web-server/dist/main.js" [ -f "$APP_DIR/admin/index.html" ] || die "管理端构建产物缺失:$APP_DIR/admin/index.html" # ---------- 3. 数据库迁移 ---------- log "执行 TypeORM 迁移(不使用 DB_SYNCHRONIZE)" set -a; . "$APP_DIR/deploy/.env"; set +a cd "$APP_DIR/web-server" sudo -u "$SERVICE_USER" env \ "PATH=$(dirname "$NODE_BIN"):/usr/local/bin:/usr/bin:/bin" \ "DB_HOST=${DB_HOST}" "DB_PORT=${DB_PORT:-3306}" "DB_USER=${DB_USER}" \ "DB_PASS=${DB_PASS}" "DB_NAME=${DB_NAME}" "DB_LOGGING=${DB_LOGGING:-false}" \ "$NODE_BIN" scripts/migrate.mjs run # ---------- 4. systemd 服务 ---------- log "安装 systemd 服务" SERVICE_FILE=/etc/systemd/system/opendance-api.service sed -e "s|__SERVICE_USER__|$SERVICE_USER|g" \ -e "s|__APP_DIR__|$APP_DIR|g" \ -e "s|__NODE_BIN__|$NODE_BIN|g" \ -e "s|__STORAGE_PARENT__|$(dirname "$STORAGE_DIR")|g" \ "$APP_DIR/deploy/systemd/opendance-api.service" > "$SERVICE_FILE" chmod 644 "$SERVICE_FILE" systemctl daemon-reload systemctl enable opendance-api >/dev/null systemctl restart opendance-api log "等待 API 就绪…" READY=0 for _ in $(seq 1 30); do if curl -fsS -m 3 "http://${API_HOST}:${API_PORT}/health" >/dev/null 2>&1; then READY=1; break; fi sleep 1 done if [ "$READY" -ne 1 ]; then warn "API 未在 30 秒内就绪,最近日志:" journalctl -u opendance-api -n 40 --no-pager || true die "API 启动失败" fi log "API 已就绪:$(curl -fsS -m 5 "http://${API_HOST}:${API_PORT}/health" | head -c 200)" # ---------- 5. nginx ---------- if [ "${SKIP_NGINX:-0}" = "1" ]; then warn "SKIP_NGINX=1,跳过 nginx 配置。API 仅在 ${API_HOST}:${API_PORT} 提供。" else if ! command -v nginx >/dev/null 2>&1; then warn "未安装 nginx,跳过站点配置。" warn " 安装后重新执行本脚本,或手动把 deploy/nginx/opendance.conf 放到 nginx 配置目录。" else log "配置 nginx 站点" NGINX_AVAILABLE=/etc/nginx/sites-available NGINX_ENABLED=/etc/nginx/sites-enabled # RHEL 系发行版没有 sites-available 约定 if [ ! -d "$NGINX_AVAILABLE" ]; then NGINX_AVAILABLE=/etc/nginx/conf.d NGINX_ENABLED=/etc/nginx/conf.d fi DEST="$NGINX_AVAILABLE/opendance.conf" sed -e "s|__APP_DIR__|$APP_DIR|g" \ -e "s|__SERVER_NAME__|$SERVER_NAME|g" \ -e "s|__API_HOST__|$API_HOST|g" \ -e "s|__API_PORT__|$API_PORT|g" \ "$APP_DIR/deploy/nginx/opendance.conf" > "$DEST" if [ "$NGINX_ENABLED" != "$NGINX_AVAILABLE" ]; then ln -sf "$DEST" "$NGINX_ENABLED/opendance.conf" if [ -e "$NGINX_ENABLED/default" ]; then rm -f "$NGINX_ENABLED/default" log "已移除 nginx 默认站点(避免抢占 80 端口)" fi fi if nginx -t; then systemctl reload nginx 2>/dev/null || systemctl restart nginx 2>/dev/null || nginx -s reload log "nginx 已重载" else die "nginx 配置校验失败,请检查 $DEST" fi fi fi # ---------- 6. 完成 ---------- HOST_HINT="${DEPLOY_HOST:-<服务器IP>}" cat <