feat(web): 部署方式由 Docker 改为 Linux 原生站点(nginx + systemd)
需求变更:Web 服务端不再使用 Docker,改为直接运行在 Linux 上的网站。 - 移除 deploy/docker-compose.yml、web-server/Dockerfile、 web-server/admin/Dockerfile、web-server/.dockerignore - 新增 deploy/install.sh:建服务用户与目录 → 装运行期依赖 → 迁移 → 注册 systemd 服务 → 配置 nginx 站点 → 健康检查,一键完成 - 新增 deploy/systemd/opendance-api.service:ProtectSystem=strict 等 安全加固,ReadWritePaths 限定为上传目录 - 新增 deploy/nginx/opendance.conf:同站点托管管理端静态资源 + 反代 /api /health /static,client_max_body_size 512m,SPA 回退 - 新增 deploy/uninstall.sh:默认保留数据,--purge 才删除 - 新增配置项 HOST(AppConfig.bindHost):原生部署下 Node 只监听 127.0.0.1,不直接对外暴露,实测外部 IP 不可达 - 同步更新 contracts/rest-api.yaml、DSH.md、project.yaml、README.txt、 tasks/008、tasks/010 与发布打包脚本
This commit is contained in:
+23
-10
@@ -1,22 +1,35 @@
|
||||
# ============================================================
|
||||
# OpenDance Studio Web 服务端 —— 部署环境变量
|
||||
# 复制为 .env 后填写,.env 已被 .gitignore 忽略,禁止提交。
|
||||
# OpenDance Studio Web 服务端 —— Linux 原生部署环境变量
|
||||
#
|
||||
# 复制为 deploy/.env 后填写;.env 已被 .gitignore 忽略,禁止提交。
|
||||
# 该文件由 systemd 通过 EnvironmentFile 注入 Node 进程。
|
||||
# ============================================================
|
||||
|
||||
# ---- 现有 MySQL / MariaDB 实例(本项目不部署、不管理数据库)----
|
||||
# ---- 服务 ----
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
# 仅监听本机,由 nginx 反向代理对外提供服务(不要直接暴露 Node 进程)
|
||||
HOST=127.0.0.1
|
||||
|
||||
# ---- 现有 MySQL / MariaDB 实例(本项目不安装、不管理数据库)----
|
||||
DB_HOST=<existing-mysql-host>
|
||||
DB_PORT=3306
|
||||
DB_USER=opendance_app
|
||||
DB_PASS=<部署方提供>
|
||||
DB_NAME=opendance
|
||||
# 生产环境必须为 false,表结构一律走 TypeORM migration
|
||||
DB_SYNCHRONIZE=false
|
||||
DB_LOGGING=false
|
||||
|
||||
# ---- 端口映射(宿主机端口)----
|
||||
API_PORT=3000
|
||||
ADMIN_PORT=8080
|
||||
|
||||
# ---- 上传与预处理 ----
|
||||
# 单文件上限(字节),默认 512 MiB
|
||||
# ---- 文件存储 ----
|
||||
# 需与 install.sh 的 STORAGE_DIR 一致,且服务用户可写
|
||||
STORAGE_DIR=/var/lib/opendance/storage
|
||||
# 单文件上传上限(字节),默认 512 MiB;nginx 的 client_max_body_size 需不小于此值
|
||||
UPLOAD_MAX_BYTES=536870912
|
||||
# 可选:Assimp 可执行文件(FBX/glTF 动作预处理需要)
|
||||
|
||||
# ---- 可选的 Assimp(FBX/glTF 动作预处理)----
|
||||
ASSIMP_BIN=assimp
|
||||
|
||||
# ---- 契约目录 ----
|
||||
# 指向安装目录下的 contracts/,使 /health 与 /api/catalog/version 反映实时契约版本
|
||||
CONTRACTS_DIR=/opt/opendance/contracts
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
# OpenDance Studio Web 服务端 —— Docker Compose 部署
|
||||
#
|
||||
# 重要:本文件**不包含 MySQL 服务**。
|
||||
# 数据库为部署方提供的现有 MySQL/MariaDB 实例,连接信息由 .env 注入
|
||||
# (见 contracts/database.md)。
|
||||
#
|
||||
# 用法:
|
||||
# cp .env.example .env # 填写数据库连接信息
|
||||
# ./install.sh
|
||||
|
||||
services:
|
||||
api:
|
||||
build:
|
||||
context: ../web-server
|
||||
dockerfile: Dockerfile
|
||||
image: opendance-web-server:0.1.0
|
||||
container_name: opendance-api
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: "3000"
|
||||
DB_HOST: ${DB_HOST}
|
||||
DB_PORT: ${DB_PORT:-3306}
|
||||
DB_USER: ${DB_USER}
|
||||
DB_PASS: ${DB_PASS}
|
||||
DB_NAME: ${DB_NAME}
|
||||
# 契约要求:生产环境禁止 synchronize
|
||||
DB_SYNCHRONIZE: "false"
|
||||
DB_LOGGING: ${DB_LOGGING:-false}
|
||||
STORAGE_DIR: /data/storage
|
||||
UPLOAD_MAX_BYTES: ${UPLOAD_MAX_BYTES:-536870912}
|
||||
ASSIMP_BIN: ${ASSIMP_BIN:-assimp}
|
||||
# 挂载的契约快照目录,/health 与 /api/catalog/version 会读取其实时版本
|
||||
CONTRACTS_DIR: /contracts
|
||||
ports:
|
||||
- "${API_PORT:-3000}:3000"
|
||||
volumes:
|
||||
- storage:/data/storage
|
||||
- ../contracts:/contracts:ro
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- node
|
||||
- -e
|
||||
- "fetch('http://127.0.0.1:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
start_period: 20s
|
||||
retries: 3
|
||||
|
||||
admin:
|
||||
build:
|
||||
context: ../web-server/admin
|
||||
dockerfile: Dockerfile
|
||||
image: opendance-admin:0.1.0
|
||||
container_name: opendance-admin
|
||||
ports:
|
||||
- "${ADMIN_PORT:-8080}:80"
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
storage:
|
||||
name: opendance-storage
|
||||
+173
-69
@@ -1,104 +1,208 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# OpenDance Studio Web 服务端 —— 一键部署脚本
|
||||
# OpenDance Studio Web 服务端 —— Linux 原生站点部署脚本
|
||||
#
|
||||
# 部署内容:API(NestJS)+ 管理端(静态站点)
|
||||
# 不部署内容:MySQL —— 数据库为部署方提供的现有实例(见 contracts/database.md)
|
||||
# 部署形态(不使用 Docker):
|
||||
# nginx ── 静态托管管理端 + 反向代理 API
|
||||
# └──▶ systemd 托管的 Node 进程(NestJS,仅监听 127.0.0.1)
|
||||
#
|
||||
# 用法:
|
||||
# cp .env.example .env && vi .env
|
||||
# ./install.sh
|
||||
# 数据库仍是部署方提供的**现有 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
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
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; }
|
||||
|
||||
# ---------- 1. 前置检查 ----------
|
||||
if [ ! -f .env ]; then
|
||||
die "缺少 .env 文件,请从 .env.example 复制并填写数据库连接信息:
|
||||
cp .env.example .env && vi .env"
|
||||
fi
|
||||
# ---------- 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
|
||||
set -a; . ./.env; set +a
|
||||
. ./deploy/.env
|
||||
set +a
|
||||
API_PORT="${PORT:-3000}"
|
||||
|
||||
[ -n "${DB_HOST:-}" ] || die ".env 中 DB_HOST 未设置"
|
||||
[ -n "${DB_USER:-}" ] || die ".env 中 DB_USER 未设置"
|
||||
[ -n "${DB_NAME:-}" ] || die ".env 中 DB_NAME 未设置"
|
||||
[ -n "${DB_PASS:-}" ] || die ".env 中 DB_PASS 未设置"
|
||||
[ -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 未设置"
|
||||
|
||||
if docker compose version >/dev/null 2>&1; then
|
||||
DC="docker compose"
|
||||
elif command -v docker-compose >/dev/null 2>&1; then
|
||||
DC="docker-compose"
|
||||
else
|
||||
die "未找到 docker compose 或 docker-compose,请先安装 Docker"
|
||||
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
|
||||
|
||||
command -v docker >/dev/null 2>&1 || die "未找到 docker"
|
||||
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
|
||||
|
||||
log "目标数据库:${DB_HOST}:${DB_PORT:-3306}/${DB_NAME}(现有实例,本脚本不会创建或管理 MySQL)"
|
||||
mkdir -p "$STORAGE_DIR"
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$APP_DIR" "$STORAGE_DIR"
|
||||
chmod 750 "$STORAGE_DIR"
|
||||
|
||||
# ---------- 2. 构建镜像 ----------
|
||||
log "构建镜像(API + Admin)..."
|
||||
$DC build
|
||||
# ---------- 2. 安装运行期依赖(必要时构建) ----------
|
||||
cd "$APP_DIR/web-server"
|
||||
|
||||
# ---------- 3. 执行数据库迁移 ----------
|
||||
# 表结构由 TypeORM migration 创建;DB_SYNCHRONIZE 恒为 false
|
||||
log "执行 TypeORM 迁移..."
|
||||
$DC run --rm api npm run migration:run
|
||||
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
|
||||
|
||||
# ---------- 4. 启动服务 ----------
|
||||
log "启动服务..."
|
||||
$DC up -d
|
||||
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"
|
||||
|
||||
# ---------- 5. 健康检查 ----------
|
||||
API_PORT="${API_PORT:-3000}"
|
||||
ADMIN_PORT="${ADMIN_PORT:-8080}"
|
||||
HEALTH_URL="http://127.0.0.1:${API_PORT}/health"
|
||||
# ---------- 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
|
||||
|
||||
log "等待 API 健康检查:${HEALTH_URL}"
|
||||
ok=0
|
||||
for i in $(seq 1 30); do
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
if curl -fsS -m 3 "$HEALTH_URL" >/dev/null 2>&1; then ok=1; break; fi
|
||||
# ---------- 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
|
||||
if $DC exec -T api node -e "fetch('http://127.0.0.1:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" >/dev/null 2>&1; then
|
||||
ok=1; break
|
||||
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
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ "$ok" -ne 1 ]; then
|
||||
warn "健康检查未在 60 秒内通过,最近日志:"
|
||||
$DC logs --tail=50 api || true
|
||||
die "部署未通过健康检查"
|
||||
fi
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
log "健康检查响应:$(curl -fsS -m 5 "$HEALTH_URL")"
|
||||
fi
|
||||
|
||||
HOST_HINT="${DEPLOY_HOST:-<host>}"
|
||||
# ---------- 6. 完成 ----------
|
||||
HOST_HINT="${DEPLOY_HOST:-<服务器IP>}"
|
||||
cat <<EOF
|
||||
|
||||
============================================================
|
||||
OpenDance Studio Web 服务端已启动
|
||||
API: http://${HOST_HINT}:${API_PORT}
|
||||
Admin: http://${HOST_HINT}:${ADMIN_PORT}
|
||||
健康检查:http://${HOST_HINT}:${API_PORT}/health
|
||||
数据库:使用现有实例 ${DB_HOST}:${DB_PORT:-3306}/${DB_NAME}
|
||||
上传存储:Docker 卷 opendance-storage → /data/storage
|
||||
契约快照:./contracts → 容器内 /contracts(只读)
|
||||
============================================================
|
||||
OpenDance Studio Web 服务端已部署(Linux 原生站点,无 Docker)
|
||||
管理端首页: http://${HOST_HINT}/
|
||||
API 前缀: http://${HOST_HINT}/api
|
||||
健康检查: http://${HOST_HINT}/health
|
||||
契约快照: http://${HOST_HINT}/contracts/
|
||||
数据库: 使用现有实例 ${DB_HOST}:${DB_PORT:-3306}/${DB_NAME}
|
||||
上传目录: ${STORAGE_DIR}
|
||||
程序目录: ${APP_DIR}
|
||||
Node 进程: 仅监听 ${API_HOST}:${API_PORT}(systemd 托管)
|
||||
|
||||
常用命令:
|
||||
$DC logs -f api # 查看 API 日志
|
||||
$DC ps # 查看服务状态
|
||||
$DC down # 停止服务(保留数据卷)
|
||||
systemctl status opendance-api # 服务状态
|
||||
journalctl -u opendance-api -f # 实时日志
|
||||
systemctl restart opendance-api # 重启
|
||||
nginx -t && systemctl reload nginx # 校验并重载 nginx
|
||||
============================================================
|
||||
EOF
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# ============================================================
|
||||
# OpenDance Studio —— nginx 站点配置(Linux 原生部署)
|
||||
#
|
||||
# 一个站点同时提供:
|
||||
# / 管理端静态站点(React 构建产物)
|
||||
# /api/ NestJS API 反向代理
|
||||
# /health 健康检查反向代理
|
||||
# /static/ 上传文件(模型 / 音频 / 关键点),由 API 提供
|
||||
# /contracts/ 契约快照(只读,方便另两端查阅)
|
||||
#
|
||||
# 安装位置:/etc/nginx/sites-available/opendance.conf
|
||||
# (并软链到 /etc/nginx/sites-enabled/)
|
||||
# ============================================================
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name __SERVER_NAME__;
|
||||
|
||||
# 上传上限:单个 VRM / FBX 可达 512 MiB,必须与后端 UPLOAD_MAX_BYTES 一致
|
||||
client_max_body_size 512m;
|
||||
client_body_timeout 300s;
|
||||
|
||||
# 管理端静态站点
|
||||
root __APP_DIR__/admin;
|
||||
index index.html;
|
||||
|
||||
charset utf-8;
|
||||
|
||||
# ---- 管理端 SPA ----
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Vite 产物带内容哈希,可长缓存
|
||||
location /assets/ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# ---- API 反向代理 ----
|
||||
location /api/ {
|
||||
proxy_pass http://__API_HOST__:__API_PORT__;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# 大文件上传与关键点序列下载都可能较慢
|
||||
proxy_connect_timeout 30s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_request_buffering off;
|
||||
}
|
||||
|
||||
location = /health {
|
||||
proxy_pass http://__API_HOST__:__API_PORT__/health;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# ---- 上传文件 ----
|
||||
location /static/ {
|
||||
proxy_pass http://__API_HOST__:__API_PORT__;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
expires 7d;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
|
||||
# ---- 契约快照(只读)----
|
||||
location /contracts/ {
|
||||
alias __APP_DIR__/contracts/;
|
||||
autoindex on;
|
||||
default_type text/plain;
|
||||
}
|
||||
|
||||
# 隐藏文件一律拒绝
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/json application/javascript
|
||||
application/x-ndjson text/xml application/xml image/svg+xml;
|
||||
gzip_min_length 1024;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
[Unit]
|
||||
Description=OpenDance Studio Web 管理服务端(NestJS)
|
||||
Documentation=https://gitea.kasugano.cn/zijunchiang/opendance-web
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=__SERVICE_USER__
|
||||
Group=__SERVICE_USER__
|
||||
WorkingDirectory=__APP_DIR__/web-server
|
||||
EnvironmentFile=__APP_DIR__/deploy/.env
|
||||
ExecStart=__NODE_BIN__ dist/main.js
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=opendance-api
|
||||
|
||||
# ---- 安全加固(进程只应写自己的存储目录)----
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
RestrictRealtime=true
|
||||
LockPersonality=true
|
||||
ReadWritePaths=__STORAGE_PARENT__
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
+47
-9
@@ -1,14 +1,52 @@
|
||||
#!/bin/bash
|
||||
# 停止服务(默认保留数据卷)
|
||||
# ============================================================
|
||||
# OpenDance Studio Web 服务端 —— 卸载(Linux 原生站点)
|
||||
#
|
||||
# 默认**保留**上传文件与数据库数据;只有显式加 --purge 才删除程序目录与上传目录。
|
||||
# 数据库从来不由本脚本管理,永不删除。
|
||||
#
|
||||
# 用法:
|
||||
# sudo bash deploy/uninstall.sh # 停服务、禁用站点,保留数据
|
||||
# sudo bash deploy/uninstall.sh --purge # 同时删除程序目录与上传文件
|
||||
# ============================================================
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
if docker compose version >/dev/null 2>&1; then
|
||||
DC="docker compose"
|
||||
else
|
||||
DC="docker-compose"
|
||||
APP_DIR="${APP_DIR:-/opt/opendance}"
|
||||
STORAGE_DIR="${STORAGE_DIR:-/var/lib/opendance/storage}"
|
||||
SERVICE_USER="${SERVICE_USER:-opendance}"
|
||||
PURGE=0
|
||||
[ "${1:-}" = "--purge" ] && PURGE=1
|
||||
|
||||
log() { printf '\033[1;34m[uninstall]\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; }
|
||||
|
||||
[ "$(id -u)" -eq 0 ] || die "需要 root 权限:sudo bash deploy/uninstall.sh"
|
||||
|
||||
log "停止并禁用 opendance-api 服务"
|
||||
systemctl stop opendance-api 2>/dev/null || true
|
||||
systemctl disable opendance-api 2>/dev/null || true
|
||||
rm -f /etc/systemd/system/opendance-api.service
|
||||
systemctl daemon-reload
|
||||
|
||||
if command -v nginx >/dev/null 2>&1; then
|
||||
log "移除 nginx 站点"
|
||||
rm -f /etc/nginx/sites-enabled/opendance.conf /etc/nginx/sites-available/opendance.conf /etc/nginx/conf.d/opendance.conf
|
||||
nginx -t >/dev/null 2>&1 && (systemctl reload nginx 2>/dev/null || nginx -s reload) || warn "nginx 重载跳过"
|
||||
fi
|
||||
|
||||
$DC down "$@"
|
||||
echo "服务已停止。数据卷 opendance-storage 未被删除。"
|
||||
echo "如需一并删除上传数据:$DC down -v"
|
||||
if [ "$PURGE" -eq 1 ]; then
|
||||
warn "删除程序目录 $APP_DIR 与上传目录 $STORAGE_DIR"
|
||||
rm -rf "$APP_DIR" "$STORAGE_DIR"
|
||||
if id -u "$SERVICE_USER" >/dev/null 2>&1; then
|
||||
userdel "$SERVICE_USER" 2>/dev/null || true
|
||||
log "已删除系统用户 $SERVICE_USER"
|
||||
fi
|
||||
log "完成(数据库未做任何改动)"
|
||||
else
|
||||
log "完成。已保留:"
|
||||
echo " 程序目录:$APP_DIR"
|
||||
echo " 上传目录:$STORAGE_DIR"
|
||||
echo " 如需一并删除:sudo bash deploy/uninstall.sh --purge"
|
||||
fi
|
||||
echo " 数据库:始终由部署方自行管理,本脚本不做任何改动"
|
||||
|
||||
Reference in New Issue
Block a user