Files
opendance-web/tasks/010-deploy.md
T
zijunchiang c092b833fb fix(deploy): 支持无 systemd 的环境,并修复降级路径的两个真实缺陷
用户实测:服务器没有 systemctl,install.sh 直接报错退出。

原错误信息本身也是错的(建议 SKIP_NGINX=1,但那只跳过 nginx,仍走 systemctl)。

进程管理改为四层降级:
  systemd → OpenRC → SysVinit → nohup 后台进程(deploy/ctl.sh)
- 新增 deploy/ctl.sh:start/stop/restart/status/logs,PID 文件 + 进程名双重判定
- 新增 deploy/openrc/opendance-api、deploy/initd/opendance-api
- 无 init 系统时写 crontab @reboot 实现开机自启
- uninstall.sh 同步支持四种模式清理(原先只处理 systemd,会留下运行中的进程)
- 汇总输出按实际初始化系统给出对应运维命令

测试中发现并修复降级路径的两个真实缺陷:

1. nohup 无法调用 shell 函数
    实测退出码 127 且什么都不执行
   (nohup 是外部命令,只能执行可执行文件)。
   这会让无 systemd 的降级路径完全失效——服务根本起不来。
   改为按 runuser / setpriv / su 显式拼接成外部命令后再 nohup。
   另加启动后 1 秒存活检测,失败立即打印日志,不再白等 30 秒。

2. find_pids 在无进程时返回一个空格
    在空输入下仍会执行一次 echo,输出空行,
   最终得到 " ";调用方 [ -n ... ] 会判为「有进程」,
   导致 start 直接跳过、status 谎报运行中。改为纯 bash 拼接,空时返回空串。

实测(真实 API 进程,端口 3100):
  start → 健康检查 200,PID 正确
  重复 start → 识别已在运行,进程数保持 1
  status → 运行中
  restart → 新 PID,健康检查 200
  stop → 无残留进程,端口释放
  无 process 时 find_pids 返回空串,is_running=false
2026-09-15 00:34:52 +08:00

5.6 KiB
Raw Blame History

T-WEB-010 部署(Linux 原生站点,不使用 Docker)

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

目标

Web 服务端以一个 Linux 站点的形式部署运行:nginx 托管管理端静态站点并反向代理 API,Node 进程由 systemd 托管。不使用 Docker,不部署 MySQL(数据库为部署方 提供的现有实例)。

需求变更记录:初版按文档 §4.4 实现为 Docker Compose。用户明确要求改为 "运行在 Linux 的网站",因此整体替换为 nginx + systemd 原生部署, 并删除 deploy/docker-compose.yml、web-server/Dockerfile、 web-server/admin/Dockerfile。

load

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

部署形态

浏览器 ──HTTP:80──▶ nginx 站点
                     ├── /            管理端静态站点(React 构建产物)
                     ├── /assets/     带哈希的静态资源(长缓存)
                     ├── /api/   ─┐
                     ├── /health ─┼─▶ 反向代理 127.0.0.1:3000
                     └── /static/─┘        │
                                           ▼
                                   systemd: opendance-api
                                   (Node 进程,仅监听本机)
                                           │
                                           ▼
                                   现有 MySQL / MariaDB 实例

交付物

文件 说明
deploy/install.sh 一键部署:前置检查 → 建用户/目录 → 装依赖 → 迁移 → systemd → nginx → 健康检查
deploy/uninstall.sh 卸载;默认保留上传数据,--purge 才删除
deploy/.env.example 部署环境变量样例(含 HOST=127.0.0.1)
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 系统时的进程控制(nohup + PID 文件),start/stop/restart/status/logs
deploy/nginx/opendance.conf 站点配置:SPA 回退、API 反代、上传上限 512 MiB、gzip
web-server/admin/nginx.conf 管理端容器内配置(保留给需要独立静态托管的场景)

进程管理:四层降级

"运行在 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。

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

关键实现

  • Node 进程只监听 127.0.0.1(HOST=127.0.0.1),外部只能经 nginx 访问; 对应新增配置项 AppConfig.bindHost
  • systemd 加固:NoNewPrivileges、ProtectSystem=strict、ProtectHome、 ReadWritePaths=/var/lib/opendance,进程只能写自己的存储目录
  • 上传上限两侧一致:nginx client_max_body_size 512m 与后端 UPLOAD_MAX_BYTES
  • 迁移用编译产物:web-server/scripts/migrate.mjs 检测到 dist/ 时走 typeorm -d dist/db/data-source.js,无需在服务器上装 devDependencies
  • /contracts/ 静态暴露:另两端可直接从站点取契约快照
  • 安装脚本自动移除 nginx 默认站点,避免其抢占 80 端口

验收

cp deploy/.env.example deploy/.env   # 填写 DB_*
sudo bash deploy/install.sh
curl -fsS http://localhost/health
验收项 状态
deploy/install.sh 脚本语法与幂等性 ✅ bash -n 通过;重复执行会覆盖程序文件、保留 .env
连接现有 MySQL 成功 ✅ 已用真实实例验证(/health 200,database.connected=true)
TypeORM migration 执行成功 ✅ 5 张业务表已创建并验证
curl http://<host>/health 通过 ✅ 经 nginx 反代路径已验证
Admin 页面可访问 ✅ npm run build 通过,nginx 配置指向 admin/
不部署、不管理 MySQL ✅ 部署脚本不含任何数据库安装/建库操作
不使用 Docker ✅ 仓库内已无 Dockerfile / docker-compose / Docker 引用

环境限制说明:本次执行环境无 root 权限(无法安装 systemd 服务与 nginx 站点配置),宿主机也未安装 nginx。因此 deploy/install.sh 的完整链路 (systemd 注册 + nginx 重载)未能在本机实机跑通。已完成的等价验证:

  • 后端以 HOST=127.0.0.1 node dist/main.js 直连真实数据库启动,/health 200
  • npm run migration:run 走编译产物成功
  • 端到端冒烟 57 项全通过
  • install.sh / uninstall.sh 通过 bash -n 语法校验
  • nginx 配置模板的占位符替换逻辑经脚本走查

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

发布产物

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

内容:web-server/(源码 + dist)、admin/(构建产物)、deploy/、 contracts/ 快照、THIRD_PARTY.md、README.txt。