# 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/` 只交付说明文档。 > 3. 再随后用户要求**管理端口与软件 API 端口分离**:新增 `ADMIN_PORT` > (默认 `PORT + 1`)与可选的 `ADMIN_HOST`(默认跟随 `HOST`)。软件 API 端口 > 只给客户端调接口(`/api`、`/health`、`/static`、`/contracts`,`/` 等返回 404), > 管理端端口给浏览器(管理页面 + 全部接口)。`ADMIN_PORT === PORT` 保留为 > 单端口兼容模式,只 warn 不中止。install.sh 的汇总与反代说明改为两个端口。 > 4. 本次追加两项: > a) **首次启动的 .env 引导**:新增 `deploy/init-env.sh`(交互式逐项询问数据库 > 与端口,写盘前校验必填项/端口范围,可用 Node + mysql2 做连通性自检); > `install.sh` 在缺 `deploy/.env` 时先调用它,失败才 die(安装目录已有上次 > 安装留下的 .env 则直接复用,保持原语义)。 > b) **默认监听 0.0.0.0 固化**:确认 `configuration.ts` 的 > `bindHost = process.env.HOST ?? '0.0.0.0'`、`adminHost` 默认跟随 HOST, > 两个 `.env.example` 与 `init-env.sh` 的 HOST 默认值均为 `0.0.0.0`。 ## load ``` DSH.md contracts/database.md deploy/init-env.sh deploy/install.sh deploy/uninstall.sh deploy/.env.example deploy/systemd/opendance-api.service deploy/proxy/README.md ``` ## 部署形态 **职责边界**:本端只提供**业务服务**;反向代理由另外的服务/团队负责。 ``` Windows/Godot 客户端、Android App ─▶ 软件 API 端口 :3000(只提供接口,无管理页面) ├── /api/ REST API ├── /health 健康检查(数据库不可用返回 503) ├── /static/ 上传文件 └── /contracts/ 契约快照 (/、/songs 等返回 404,有意设计) 浏览器 ─▶ 反向代理(外部提供,IIS / nginx / 网关…)──▶ 管理端端口 :3001 │ 整端口转发,不需要托管静态文件 ├── / 管理端页面(含 SPA 深链接回退) │ ├── /assets/ 前端静态资源 │ └── 上面全部接口 ▼ 现有 MySQL / MariaDB 实例(两个端口共用同一 Node 进程) ``` 本服务一个 Node 进程**同时监听两个端口**(软件 API 端口 + 管理端端口), 管理页面只在管理端口提供;反代按用途分别转发,每个端口各一条全匹配规则即可, 既不用在代理层托管静态文件,也不用配置 SPA 回退(`try_files`)。 `deploy/install.sh` **不安装、不配置任何反向代理软件**,收尾时只打印反代交接指引。 ## 交付物 | 文件 | 说明 | |---|---| | `deploy/install.sh` | 一键部署:前置检查 → 建用户/目录 → 复制并校验文件 → 装依赖 → 迁移 → 注册系统服务 → 健康检查;汇总打印两个端口与防火墙放行提示;缺 `deploy/.env` 时先调用 `init-env.sh` 引导 | | `deploy/init-env.sh` | **首次启动的 `.env` 交互引导**:逐项询问 8 个键、写盘前校验必填项与端口范围、可选数据库连通性自检;只替换/追加这 8 个键并保留模板注释,写完 `chmod 600` 并回读;`--check` / `-y` 等非交互模式下绝不阻塞 | | `deploy/uninstall.sh` | 卸载;默认保留上传数据,`--purge` 才删除 | | `deploy/.env.example` | 部署环境变量样例(`PORT` / `ADMIN_PORT` / `ADMIN_HOST` / `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 模块) | ## deploy/.env 初始化引导(init-env.sh) 首次部署不再要求手工 `cp deploy/.env.example deploy/.env && vi deploy/.env`: ``` install.sh 检测到缺 deploy/.env ├── $APP_DIR/deploy/.env 存在 → 直接复用(上次安装留下的配置,原语义保留) └── 否则调用 deploy/init-env.sh ├── 缺 .env → 从 .env.example 复制,chmod 600 ├── 交互(TTY / 管道)→ 逐项询问并写回 └── 非交互(CI、:3000/health` 通过 | ✅ 直连后端端口已验证 | | 软件 API 端口 `/` 返回 404 | ✅ `PORT=3000` 上 `/`、`/songs` 均 404(有意设计,无管理页面) | | 管理端端口 `/` 返回 200 | ✅ `ADMIN_PORT=3001` 上 `/`、`/songs`、`/models` 均返回同一份 HTML(200) | | 前端深链接回退 | ✅ 未知静态路径回退到 `index.html`,未知 API 仍 404 | | `ADMIN_PORT === PORT` 单端口兼容模式 | ✅ 只建一个 server,管理页面回到 API 端口,日志提示「未分端口」 | | 端口非法在生产环境中止启动 | ✅ `validateEnv` 对非 1..65535 整数端口报 error | | 不部署、不管理 MySQL | ✅ 部署脚本不含任何数据库安装/建库操作 | | 不使用 Docker | ✅ 仓库内已无 Dockerfile / docker-compose / Docker 引用 | | 不部署反向代理 | ✅ 仅交付 `deploy/proxy/` 文档,install.sh 中无任何反代安装动作 | | 缺 `deploy/.env` 时由 `init-env.sh` 引导 | ✅ 交互逐项询问 8 个键;只改这 8 个键、保留模板注释、`chmod 600`、回读校验;安装目录已有 `.env` 时直接复用 | | `init-env.sh --check` / 非交互不阻塞 | ✅ `test/init-env.spec.ts`(16 项)覆盖 `--check`、`-y`、EOF/超时立即退出、口令不加引号写盘 | | `HOST` 默认 `0.0.0.0` | ✅ `configuration.ts`、`deploy/.env.example`、`web-server/.env.example`、`install.sh`、`init-env.sh` 五处一致 | > **环境限制说明**:本次执行环境**无 root 权限**,且**未安装 nginx**(按需求也不应安装)。 > 因此 `deploy/install.sh` 的服务注册链路(systemd / OpenRC / SysVinit / crontab) > 未能在本机实机跑通。已完成的等价验证: > - 后端以 `node dist/main.js` 直连真实数据库启动,两个端口 `/health` 均 200 > - 管理端托管与 SPA 回退逐路径实测(`/`、`/songs`、`/models`、`/api/*`、未知路径), > 并确认 3000 上返回 404、3001 上返回 200 > - `ADMIN_DIR` 环境变量生效,`index.html` 缺失时打印告警而非崩溃 > - `npm run migration:run` 走编译产物成功 > - 端到端冒烟 57 项全通过 > - `install.sh` / `uninstall.sh` / `ctl.sh` 通过 `bash -n` 语法校验 > - `init-env.sh` 通过 `bash -n`,并由 `test/init-env.spec.ts`(16 项)覆盖 > 交互写回 / `--check` / 非交互 / 超时 / 幂等 / 口令不加引号 > - 反代文档中的占位符与后端实际行为逐条对照(两个端口、路径、上传上限、健康检查) > > 需在一台有 root 的 Linux 服务器上补做一次 `install.sh` 实机验收。 ## 发布产物 ```bash ./scripts/package-release.sh # → dist-release/opendance-web-v0.2.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 安装配置。 v0.2.0 起校验清单追加模型新模块:`dist/models/model-inspector.js`、 `dist/models/model-thumbnail.js`、`dist/models/vrm-writer.js`、 `dist/models/gltf-packer.js`、`dist/migrations/1789460000000-AddModelStats.js`。 > **发布包名变更**:`opendance-web-v0.1.0.tar.gz` → **`opendance-web-v0.2.0.tar.gz`** > (`scripts/package-release.sh` 的 `VERSION` 默认值已改为 `0.2.0`, > 与 `project.yaml` 的 `version: 0.2.0`、`web-server/package.json` 一致)。