docs(readme): 新增初次部署清单与常见问题;统一 root 执行写法;清除 markdown 粗体标记与残留真实库名

This commit is contained in:
2026-09-15 21:27:29 +08:00
parent 9c8379e5eb
commit ebd871018f
19 changed files with 345 additions and 296 deletions
+25 -25
View File
@@ -11,7 +11,7 @@ Web 服务端以**一个 Linux 业务服务**的形式部署运行:Node 进程
**也不部署反向代理**(由另外的服务/团队负责)。
> **需求变更记录**
> 1. 初版按文档 §4.4 实现为 Docker Compose;用户要求改为"运行在 Linux 的网站",
> 1. 初版按文档 4.4 实现为 Docker Compose;用户要求改为"运行在 Linux 的网站",
> 于是替换为 nginx + systemd 原生站点部署,并删除 `deploy/docker-compose.yml`、
> `web-server/Dockerfile`、`web-server/admin/Dockerfile`。
> 2. 随后用户明确:"web 端仅需要业务需求,反代等工作交由另外的服务处理。
@@ -24,12 +24,12 @@ Web 服务端以**一个 Linux 业务服务**的形式部署运行:Node 进程
> 管理端端口给浏览器(管理页面 + 全部接口)。`ADMIN_PORT === PORT` 保留为
> 单端口兼容模式,只 warn 不中止。install.sh 的汇总与反代说明改为两个端口。
> 4. 本次追加两项:
> a) **首次启动的 .env 引导**:新增 `deploy/init-env.sh`(交互式逐项询问数据库
> a) **首次启动的.env 引导**:新增 `deploy/init-env.sh`(交互式逐项询问数据库
> 与端口,写盘前校验必填项/端口范围,可用 Node + mysql2 做连通性自检);
> `install.sh` 在缺 `deploy/.env` 时先调用它,失败才 die(安装目录已有上次
> 安装留下的 .env 则直接复用,保持原语义)。
> 安装留下的.env 则直接复用,保持原语义)。
> b) **默认监听 0.0.0.0 固化**:确认 `configuration.ts` 的
> `bindHost = process.env.HOST ?? '0.0.0.0'`、`adminHost` 默认跟随 HOST,
> `bindHost = process.env.HOST?? '0.0.0.0'`、`adminHost` 默认跟随 HOST,
> 两个 `.env.example` 与 `init-env.sh` 的 HOST 默认值均为 `0.0.0.0`。
## load
@@ -50,14 +50,14 @@ deploy/proxy/README.md
**职责边界**:本端只提供**业务服务**;反向代理由另外的服务/团队负责。
```
Windows/Godot 客户端、Android App ─▶ 软件 API 端口 :3000(只提供接口,无管理页面)
Windows/Godot 客户端、Android App ─▶ 软件 API 端口:3000(只提供接口,无管理页面)
├── /api/ REST API
├── /health 健康检查(数据库不可用返回 503)
├── /static/ 上传文件
└── /contracts/ 契约快照
(/、/songs 等返回 404,有意设计)
浏览器 ─▶ 反向代理(外部提供,IIS / nginx / 网关…)──▶ 管理端端口 :3001
浏览器 ─▶ 反向代理(外部提供,IIS / nginx / 网关...)──▶ 管理端端口:3001
│ 整端口转发,不需要托管静态文件 ├── / 管理端页面(含 SPA 深链接回退)
│ ├── /assets/ 前端静态资源
│ └── 上面全部接口
@@ -74,8 +74,8 @@ Windows/Godot 客户端、Android App ─▶ 软件 API 端口 :3000(只提供
## 交付物
| 文件 | 说明 |
|---|---|
| `deploy/install.sh` | 一键部署:前置检查 → 建用户/目录 → 复制并校验文件 → 装依赖 → 迁移 → 注册系统服务 → 健康检查;汇总打印两个端口与防火墙放行提示;缺 `deploy/.env` 时先调用 `init-env.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` 说明与上传上限告警) |
@@ -93,11 +93,11 @@ Windows/Godot 客户端、Android App ─▶ 软件 API 端口 :3000(只提供
```
install.sh 检测到缺 deploy/.env
├── $APP_DIR/deploy/.env 存在 → 直接复用(上次安装留下的配置,原语义保留)
├── $APP_DIR/deploy/.env 存在 -> 直接复用(上次安装留下的配置,原语义保留)
└── 否则调用 deploy/init-env.sh
├── 缺 .env → 从 .env.example 复制,chmod 600
├── 交互(TTY / 管道)→ 逐项询问并写回
└── 非交互(CI、</dev/null、-y)→ 只校验,绝不阻塞;失败则 install.sh die
├── 缺.env -> 从.env.example 复制,chmod 600
├── 交互(TTY / 管道)-> 逐项询问并写回
└── 非交互(CI、</dev/null、-y)-> 只校验,绝不阻塞;失败则 install.sh die
```
逐项询问 `DB_HOST / DB_PORT / DB_USER / DB_PASS / DB_NAME / PORT / ADMIN_PORT / HOST`
@@ -107,7 +107,7 @@ install.sh 检测到缺 deploy/.env
结尾打印摘要(口令 `***`)与本次变更列表。
| 开关 | 说明 |
|---|---|
|--|--|
| (无) | 交互引导;`.env` 已存在且必填项齐全时跳过询问 |
| `--force` | 已有 `.env` 也重新逐项询问 |
| `--check` | 只校验,不修改文件、不提问 |
@@ -123,11 +123,11 @@ install.sh 检测到缺 deploy/.env
`OPENDANCE_SKIP_DB_CHECK` / `OPENDANCE_ENV_FILE` / `OPENDANCE_ENV_EXAMPLE` /
`OPENDANCE_PROMPT_TIMEOUT`。
**非交互绝不挂住**:stdin 非 TTY 且是 `</dev/null` 或已关闭,或带 `-y` 等开关 →
**非交互绝不挂住**:stdin 非 TTY 且是 `</dev/null` 或已关闭,或带 `-y` 等开关 ->
只校验不提问;所有 read 带超时,EOF/超时立即 `exit 1`。
**默认监听 0.0.0.0**:`configuration.ts` 的
`bindHost = process.env.HOST ?? '0.0.0.0'`,`adminHost` 默认跟随 `HOST`
`bindHost = process.env.HOST?? '0.0.0.0'`,`adminHost` 默认跟随 `HOST`
(HOST 未设置时同样回落 `0.0.0.0`);`deploy/.env.example` 与
`web-server/.env.example` 均写 `HOST=0.0.0.0`;`install.sh` 的
`BIND_HOST="${HOST:-0.0.0.0}"`;`init-env.sh` 的 HOST 默认值也是 `0.0.0.0`。
@@ -151,23 +151,23 @@ install.sh 检测到缺 deploy/.env
(注意:`nohup` 不能调用 shell 函数,故 `ctl.sh` 用
`spawn_service_process()` 拼装外部命令,而非包装函数。)
**不使用 sudo**:脚本已要求 root,降权到服务用户用 `runuser → setpriv → su`
**不使用 sudo**:脚本已要求 root,降权到服务用户用 `runuser -> setpriv -> su`
依次回退(部分服务器根本没装 sudo)。
## 关键实现
- **双端口**:一个 Node 进程创建两个 HTTP server——软件 API 端口(`PORT`,默认 3000)
- **双端口**:一个 Node 进程创建两个 HTTP server--软件 API 端口(`PORT`,默认 3000)
与管理端端口(`ADMIN_PORT`,默认 3001),复用同一个 express 实例;用
`req.socket.localPort` 判断请求来自哪个端口,决定是否提供管理页面。
`ADMIN_PORT === PORT` 时只建一个 server(单端口兼容模式,避免 EADDRINUSE)
- **服务自身托管管理端**:`app.useStaticAssets(adminDir, …)` + SPA 回退中间件
- **服务自身托管管理端**:`app.useStaticAssets(adminDir,...)` + SPA 回退中间件
(仅管理端口生效);回退会跳过 `/api/`、`/health`、`/static/`、`/contracts/`
以及带扩展名的路径,保证未知 API 仍返回 404 而不是 HTML
- **软件 API 端口不提供管理页面**:`/`、`/songs` 等在该端口一律 404(有意设计)
- **`HOST` / `ADMIN_HOST` 绑定地址**:同机反代建议 `127.0.0.1`,异机反代用
`0.0.0.0` + 防火墙;`ADMIN_HOST` 可选(默认跟随 `HOST`),可单独把管理端限制在
本机或内网;`HOST` 默认取 `0.0.0.0` 以保证开箱可用
- **首次部署的 .env 由引导生成**:`install.sh` 缺 `deploy/.env` 时先调用
- **首次部署的.env 由引导生成**:`install.sh` 缺 `deploy/.env` 时先调用
`deploy/init-env.sh`(安装目录已有上次安装的 `.env` 则直接复用);引导脚本在
非交互环境下只校验、绝不阻塞,校验失败时 install.sh 才 die
- **端口校验**:`PORT` / `ADMIN_PORT` 必须是 1..65535 的整数;非法值在生产环境
@@ -185,17 +185,17 @@ install.sh 检测到缺 deploy/.env
`glb` 通用,两个客户端都原生加载;`vrm` 在 GLB 之上补 `VRMC_vrm` 扩展
(humanoid 骨骼 / 表情语义),**humanoid 骨骼名映射不全时自动回退交付 GLB**
并写进 `models.conversion_note`。产物一律 `assimp info` 回读校验
- `MODEL_CONVERT_PMX` / `MODEL_CONVERT_TIMEOUT_MS` 语义见 README §四
- `MODEL_CONVERT_PMX` / `MODEL_CONVERT_TIMEOUT_MS` 语义见 README 四
- **迁移用编译产物**:`web-server/scripts/migrate.mjs` 检测到 `dist/` 时走
`typeorm -d dist/db/data-source.js`,无需在服务器上装 devDependencies;
本版本迁移共 3 个:`InitSchema1789382903060`、
`AddModelConversionFields1789450000000`、**`AddModelStats1789460000000`**
(新增 `models.stats TEXT`,存模型分析结果,已在真实库执行)
- **依赖安装显式带 dev**:安装脚本先 `set -a; . deploy/.env` 导出了
- **依赖安装显式带 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/` 锚定
(曾导致 `MODULE_NOT_FOUND...storage.module`),排除规则一律用 `/storage/` 锚定
- **`/contracts/` 静态暴露**:另两端可直接从站点取契约快照
## 验收
@@ -210,7 +210,7 @@ curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3001/ # 期望 200
```
| 验收项 | 状态 |
|---|---|
|--|--|
| `deploy/install.sh` 脚本语法与幂等性 | ✅ `bash -n` 通过;重复执行会覆盖程序文件、保留 `.env` |
| 连接现有 MySQL 成功 | ✅ 已用真实实例验证(`/health` 200,`database.connected=true`) |
| TypeORM migration 执行成功 | ✅ 5 张业务表已创建并验证;v0.2.0 的 `AddModelStats1789460000000`(`models.stats TEXT`)已在真实库执行 |
@@ -248,7 +248,7 @@ curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3001/ # 期望 200
```bash
./scripts/package-release.sh
# → dist-release/opendance-web-v0.2.0.tar.gz
# -> dist-release/opendance-web-v0.2.0.tar.gz
```
内容:`web-server/`(源码 + dist)、`admin/`(构建产物)、`deploy/`
@@ -260,6 +260,6 @@ 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`**
> **发布包名变更**:`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` 一致)。