用户实测:服务器没有 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
128 lines
5.6 KiB
Markdown
128 lines
5.6 KiB
Markdown
# 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 端口
|
||
|
||
## 验收
|
||
|
||
```bash
|
||
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` 实机验收。
|
||
|
||
## 发布产物
|
||
|
||
```bash
|
||
./scripts/package-release.sh
|
||
# → dist-release/opendance-web-v0.1.0.tar.gz
|
||
```
|
||
|
||
内容:`web-server/`(源码 + dist)、`admin/`(构建产物)、`deploy/`、
|
||
`contracts/` 快照、`THIRD_PARTY.md`、`README.txt`。
|