Files
opendance-web/tasks/010-deploy.md
T
zijunchiang 321ab1f584 docs(web): 清理 Docker/nginx 残留引用,反代职责边界写进任务书与契约
- tasks/010-deploy.md: 按"仅业务服务、不部署反代、服务自托管管理端"重写,
  补充四层进程降级、无 sudo 降权、上传上限两侧一致等实现说明与验收表
- deploy/uninstall.sh: 删除 nginx 站点清理逻辑(本项目从未安装过反代,
  也不应改动运维侧的 nginx / IIS 配置)
- scripts/package-release.sh: 修正产物结构注释,去掉已删除的
  web-server/admin/nginx.conf 拷贝与 deploy/nginx 备份清理
- web-server/.env.example: 修正"容器内路径 / docker-compose 挂载"过时注释
- contracts/rest-api.yaml: 服务地址描述改为业务服务部署语义
2026-09-15 01:27:47 +08:00

8.2 KiB
Raw Blame History

T-WEB-010 部署(Linux 业务服务,不使用 Docker,不部署反代)

  • 状态:done(两次需求变更后重做)
  • 依赖:T-WEB-001 ~ 009

目标

Web 服务端以一个 Linux 业务服务的形式部署运行:Node 进程由系统服务 (systemd / OpenRC / SysVinit / 后台进程)托管,同时提供 REST API 与管理端页面。 不使用 Docker,不部署 MySQL(数据库为部署方提供的现有实例), 也不部署反向代理(由另外的服务/团队负责)。

需求变更记录

  1. 初版按文档 §4.4 实现为 Docker Compose;用户要求改为"运行在 Linux 的网站", 于是替换为 nginx + systemd 原生站点部署,并删除 deploy/docker-compose.yml、 web-server/Dockerfile、web-server/admin/Dockerfile。
  2. 随后用户明确:"web 端仅需要业务需求,反代等工作交由另外的服务处理。 只需要给出 iis 和 nginx 反代的说明文档,不用部署反代服务。" 于是 install.sh 彻底移除 nginx 的安装与站点配置,管理端改由 Node 服务自身 托管(ADMIN_DIR + SPA 深链接回退),并新增 deploy/proxy/ 只交付说明文档。

load

DSH.md
contracts/database.md
deploy/install.sh
deploy/uninstall.sh
deploy/.env.example
deploy/systemd/opendance-api.service
deploy/proxy/README.md

部署形态

职责边界:本端只提供业务服务;反向代理由另外的服务/团队负责。

浏览器 ──▶ 反向代理(外部提供,IIS / nginx / 网关…)
              │  整体转发,不需要托管静态文件
              ▼
        Node 业务服务 :3000
        ├── /            管理端页面(本服务自带,含 SPA 深链接回退)
        ├── /assets/     前端静态资源
        ├── /api/        REST API
        ├── /health      健康检查(数据库不可用返回 503)
        ├── /static/     上传文件
        └── /contracts/  契约快照
              │
              ▼
        现有 MySQL / MariaDB 实例

本服务同时提供 API 与管理端页面(ADMIN_DIR),因此反代只需一条全匹配转发规则, 既不用在代理层托管静态文件,也不用配置 SPA 回退(try_files)。

deploy/install.sh 不安装、不配置任何反向代理软件,收尾时只打印反代交接指引。

交付物

文件 说明
deploy/install.sh 一键部署:前置检查 → 建用户/目录 → 复制并校验文件 → 装依赖 → 迁移 → 注册系统服务 → 健康检查
deploy/uninstall.sh 卸载;默认保留上传数据,--purge 才删除
deploy/.env.example 部署环境变量样例(含 HOST 说明与上传上限告警)
deploy/systemd/opendance-api.service systemd 单元,含 ProtectSystem=strict 等安全加固
deploy/openrc/opendance-api OpenRC 服务脚本
deploy/initd/opendance-api SysVinit / LSB 启动脚本(转调 ctl.sh)
deploy/ctl.sh 无 init 系统时的进程控制(后台进程 + PID 文件),start/stop/restart/status/logs
deploy/proxy/README.md 反代说明文档:转发目标、绑定地址、自检清单、故障对照
deploy/proxy/nginx.conf.example nginx 配置样例(上传上限、超时、keepalive、可选 HTTPS)
deploy/proxy/web.config.example IIS 配置样例(需 ARR + URL Rewrite 模块)

进程管理:四层降级

"运行在 Linux 上"不应等于"必须有 systemd"。安装时按以下顺序探测并选择:

1. systemd    /run/systemd/system 存在且 systemctl 可用
2. OpenRC     rc-service + rc-update 可用
3. SysVinit   /etc/init.d 存在且 update-rc.d 或 chkconfig 可用
4. 都没有     nohup 后台进程 + PID 文件(deploy/ctl.sh)
              并写 crontab @reboot 实现开机自启

三种 init 方式最终都调用同一个 Node 进程,只是守护方式不同;第 4 种由 deploy/ctl.sh 提供 start/stop/restart/status/logs。 (注意:nohup 不能调用 shell 函数,故 ctl.sh 用 spawn_service_process() 拼装外部命令,而非包装函数。)

不使用 sudo:脚本已要求 root,降权到服务用户用 runuser → setpriv → su 依次回退(部分服务器根本没装 sudo)。

关键实现

  • 服务自身托管管理端:app.useStaticAssets(adminDir, …) + SPA 回退中间件; 回退会跳过 /api/、/health、/static/、/contracts/ 以及带扩展名的路径, 保证未知 API 仍返回 404 而不是 HTML
  • HOST 绑定地址:同机反代建议 127.0.0.1,异机反代用 0.0.0.0 + 防火墙; 默认值取 0.0.0.0 以保证开箱可用
  • systemd 加固:NoNewPrivileges、ProtectSystem=strict、ProtectHome、 ReadWritePaths=/var/lib/opendance,进程只能写自己的存储目录
  • 上传上限两侧一致:后端 UPLOAD_MAX_BYTES=536870912(512 MiB), 反代侧 client_max_body_size / maxAllowedContentLength 必须不小于此值
  • 迁移用编译产物:web-server/scripts/migrate.mjs 检测到 dist/ 时走 typeorm -d dist/db/data-source.js,无需在服务器上装 devDependencies
  • 依赖安装显式带 dev:安装脚本先 set -a; . deploy/.env 导出了 NODE_ENV=production,会让 npm 跳过 devDependencies 导致 tsc: not found; 故用 env -u NODE_ENV npm ci --include=dev
  • 复制文件时锚定排除:storage/ 这类模式会误伤 src/storage/ 与 dist/storage/ (曾导致 MODULE_NOT_FOUND …storage.module),排除规则一律用 /storage/ 锚定
  • /contracts/ 静态暴露:另两端可直接从站点取契约快照

验收

cp deploy/.env.example deploy/.env   # 填写 DB_*
bash deploy/install.sh               # 需 root(不需要 sudo)
curl -fsS http://localhost:3000/health
验收项 状态
deploy/install.sh 脚本语法与幂等性 ✅ bash -n 通过;重复执行会覆盖程序文件、保留 .env
连接现有 MySQL 成功 ✅ 已用真实实例验证(/health 200,database.connected=true)
TypeORM migration 执行成功 ✅ 5 张业务表已创建并验证
curl http://<host>:3000/health 通过 ✅ 直连后端端口已验证
管理端页面可访问 ✅ /、/songs、/models 均返回同一份 HTML(200)
前端深链接回退 ✅ 未知静态路径回退到 index.html,未知 API 仍 404
不部署、不管理 MySQL ✅ 部署脚本不含任何数据库安装/建库操作
不使用 Docker ✅ 仓库内已无 Dockerfile / docker-compose / Docker 引用
不部署反向代理 ✅ 仅交付 deploy/proxy/ 文档,install.sh 中无任何反代安装动作

环境限制说明:本次执行环境无 root 权限,且未安装 nginx(按需求也不应安装)。 因此 deploy/install.sh 的服务注册链路(systemd / OpenRC / SysVinit / crontab) 未能在本机实机跑通。已完成的等价验证:

  • 后端以 node dist/main.js 直连真实数据库启动,/health 200
  • 管理端托管与 SPA 回退逐路径实测(/、/songs、/models、/api/*、未知路径)
  • ADMIN_DIR 环境变量生效,index.html 缺失时打印告警而非崩溃
  • npm run migration:run 走编译产物成功
  • 端到端冒烟 57 项全通过
  • install.sh / uninstall.sh / ctl.sh 通过 bash -n 语法校验
  • 反代文档中的占位符与后端实际行为逐条对照(端口、路径、上传上限、健康检查)

需在一台有 root 的 Linux 服务器上补做一次 install.sh 实机验收。

发布产物

./scripts/package-release.sh
# → dist-release/opendance-web-v0.1.0.tar.gz

内容:web-server/(源码 + dist)、admin/(构建产物)、deploy/ (含 proxy/ 反代文档)、contracts/ 快照、THIRD_PARTY.md、README.txt。 打包脚本会对产物做硬校验:src/**/*.ts 必须都有对应 dist/*.js, 关键文件(dist/main.js、dist/storage/storage.module.js、admin/index.html、 deploy/proxy/*)必须齐全,且不得含真实 .env 与 Docker/nginx 安装配置。