feat(models): 模型压缩包自动转换 + 默认模型写进源码;首次启动 .env 引导;HOST 默认 0.0.0.0
需求 1:模型上传支持压缩包(MMD 模型包) - common/zip-reader.ts:纯 TS 极简 ZIP 读取器(EOCD/中央目录、STORE+DEFLATE、 ZIP64 大小字段、Shift-JIS/GBK 文件名回退、拒绝 zip-slip 与加密条目) - models/model-convert.ts:解压 → 选主模型(指名字/最浅/最大)→ Assimp 导出 FBX → **assimp info 回读校验**(Assimp 对带蒙皮大骨架模型会写出损坏文件: 实测 714 骨骼 PMX 导出退出码 11、产物回读报 FBX-DOM no Objects dictionary found) → 失败自动回退 GLB;两种都失败则 422 - models/glb-textures.ts:把 Assimp 写成外部 URI 的贴图内嵌进 GLB 的 BIN chunk, 保证模型库里单文件即可加载(否则是白模) - 溯源:压缩包原件存为 model-archive 资产;models 新增 source_asset_id / conversion_note(迁移 1789450000000-AddModelConversionFields,已在真实库上 验证 up/down 可逆) - 默认模型:转换成功且 autoDefault 不为 false → is_primary=1 + published, 并把身份(名称/格式/产物 sha256)写进源码 src/config/default-model.ts; resolveDefaultModelId 增加 DEFAULT_MODEL_ID 与源码种子两步(按 sha256 匹配, 换库仍有效),实测库中有 2 个 VRM 时默认模型仍解析为该 GLB - scripts/import-default-model.mjs:运维 CLI(上传 zip 并写源码 / --write-source-only) - 修复 multipart 中文文件名乱码(upload.util.decodeUploadName) - DTO/契约新增 conversionNote、fileSha256;管理端 accept 增加 .zip 并展示转换来源 需求 2:首次启动 .env 引导 - deploy/init-env.sh:交互式逐项填写 8 个键(回车保留默认、口令不回显不加引号、 端口校验、mysql2 连通性自检且口令只经环境变量传递、只改这 8 个键并保留模板注释、 chmod 600、结尾打码摘要);--force/--check/-y/-i/--skip-db-check 等开关; 非交互(-y、OPENDANCE_NONINTERACTIVE、stdin 关闭)绝不提问、读取带超时 - install.sh 缺 .env 时改为调用引导,失败才 die 需求 3:默认监听 0.0.0.0 - 代码默认值、两份 .env 模板、install.sh、init-env.sh 五处一致,并新增测试守护 (未设置 HOST 时为 0.0.0.0;两份模板都写着 HOST=0.0.0.0) 契约:rest-api / data-models / model-formats → 0.3.0,database → 0.2.0(另两库已同步) 验证:125 项单测通过(7 文件);契约 6 项与 29 条路由一致;typecheck/build 通过; 真实包端到端 HTTP 201 / 0.89s / format=glb / isPrimary=true / published; GLB 1,152,464 字节、1 mesh / 240 nodes / 1 skin(238 joints) / 2 张内嵌贴图; 迁移 revert→run 实测可逆且数据无损恢复
This commit is contained in:
@@ -12,10 +12,17 @@
|
||||
```
|
||||
DSH.md
|
||||
contracts/rest-api.yaml # 锚点:#/paths/~1api~1models
|
||||
contracts/data-models.md # 锚点:§4 Model、§4.1 VRM 优先规则
|
||||
contracts/model-formats.md # 锚点:§1 角色模型
|
||||
contracts/data-models.md # 锚点:§4 Model、§4.1 默认模型选择规则
|
||||
contracts/model-formats.md # 锚点:§1 角色模型、§1.1 模型压缩包转换
|
||||
web-server/src/models/*
|
||||
web-server/src/models/vrm-inspector.ts
|
||||
web-server/src/common/zip-reader.ts
|
||||
web-server/src/models/model-convert.ts
|
||||
web-server/src/models/glb-textures.ts
|
||||
web-server/src/models/default-model-source.ts
|
||||
web-server/src/config/default-model.ts
|
||||
web-server/src/migrations/1789450000000-AddModelConversionFields.ts
|
||||
web-server/scripts/import-default-model.mjs
|
||||
```
|
||||
|
||||
## 端点
|
||||
@@ -23,16 +30,17 @@ web-server/src/models/vrm-inspector.ts
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| GET | `/api/models` | 分页 + `q`/`format`/`status`/`primaryOnly` |
|
||||
| POST | `/api/models` | multipart 上传(`file` + 名称 + 来源五元组 + `isPrimary`) |
|
||||
| POST | `/api/models` | multipart 上传(`file` + 名称 + 来源五元组 + `isPrimary`);`file` 也接受 `.zip`(MMD 模型包,自动转换) |
|
||||
| GET | `/api/models/:id` | 详情 |
|
||||
| PATCH | `/api/models/:id` | 更新 |
|
||||
| DELETE | `/api/models/:id` | 软删除 |
|
||||
| POST | `/api/models/:id/primary` | 设为全局默认模型 |
|
||||
|
||||
## VRM 优先规则(`contracts/data-models.md` §4.1,已实现于 `resolveDefaultModelId()`)
|
||||
## 默认模型选择规则(`contracts/data-models.md` §4.1,已实现于 `resolveDefaultModelId()`)
|
||||
|
||||
```
|
||||
1. 显式 modelId(客户端侧)
|
||||
0. 环境变量 DEFAULT_MODEL_ID 指定,且该模型存在且已发布
|
||||
1. 源码种子 src/config/default-model.ts 的 DEFAULT_MODEL_SEED(按转换产物 sha256 匹配)
|
||||
2. format='vrm' 且 published 且 is_primary=1
|
||||
3. format='vrm' 且 published,按 id 升序取第一个
|
||||
4. published,按 (format='vrm' DESC, id ASC) 取第一个
|
||||
@@ -40,6 +48,64 @@ web-server/src/models/vrm-inspector.ts
|
||||
```
|
||||
|
||||
`POST /:id/primary` 在**同一事务**内先清零其余 `is_primary`,保证互斥。
|
||||
第 0/1 步为本次(v0.3.0)新增:上传 MMD 压缩包转换成功后自动设为默认并写源码种子。
|
||||
|
||||
## 压缩包自动转换(`.zip` → FBX / GLB)
|
||||
|
||||
`POST /api/models` 的 `file` 接受 `.zip`,视为 MMD 模型包。链路:
|
||||
|
||||
```
|
||||
.zip ─▶ readZipEntries() ─▶ pickModelFile() ─▶ Assimp 导出 ─▶ assimp info 回读 ─▶ glb-textures 内嵌
|
||||
zip-reader.ts 优先 MODEL_CONVERT_PMX FBX 优先 失败回退 GLB
|
||||
```
|
||||
|
||||
| 环节 | 实现 | 规则 |
|
||||
|---|---|---|
|
||||
| ZIP 读取 | `common/zip-reader.ts` | 中央目录/EOCD、STORE(0)+DEFLATE(8)、ZIP64 大小字段、Shift-JIS/GBK 文件名回退;拒绝 zip-slip 与加密条目;不支持的压缩方式报错 |
|
||||
| 选主模型 | `models/model-convert.ts` `pickModelFile()` | 优先 `MODEL_CONVERT_PMX`(默认 `Hatsune Miku.pmx`,大小写不敏感);否则目录最浅,其次文件最大;只认 `.pmx` |
|
||||
| 转换 | `model-convert.ts` | `MODEL_CONVERT_FORMAT`(默认 `fbx`)→ `ASSIMP_BIN`;超时 `MODEL_CONVERT_TIMEOUT_MS`(默认 180000) |
|
||||
| 回读校验 | `exportAndValidate()` | 导出后必须 `assimp info` 回读;退出码为 0 但产物损坏也判失败 |
|
||||
| 回退 | `convertModelArchive()` | FBX 失败 → GLB;两次尝试写入 `models.conversion_note` |
|
||||
| 贴图内嵌 | `models/glb-textures.ts` | 把 GLB 中外部 URI 贴图追加 bufferView 并内嵌进 BIN chunk(4 字节对齐) |
|
||||
| 默认模型 | `models.service.ts` `createFromArchive()` | `autoDefault` 默认 true → `is_primary=1` + `status=published`,并写 `src/config/default-model.ts` |
|
||||
| 溯源 | 迁移 `AddModelConversionFields1789450000000` | 压缩包原件存为 `model-archive` 资产 → `models.source_asset_id`;说明 → `models.conversion_note` |
|
||||
| 失败 | `createFromArchive()` | 无 `.pmx` / Assimp 不可用 / 两种格式都失败 → **HTTP 422**,不产生模型记录 |
|
||||
| 文件名编码 | `common/upload.util.ts` `decodeUploadName()` | 修正 multipart 文件名的 latin1/UTF-8 乱码(中文文件名原先存成 `模å_…`) |
|
||||
| 管理端 | `admin/src/pages/Models.tsx` | `accept` 增加 `.zip`;提示会自动转换并设为默认;预览面板显示「转换来源」与「文件 sha256」 |
|
||||
| DTO | `conversionNote` / `fileSha256` | 契约 `data-models.md` §4、`rest-api.yaml` |
|
||||
|
||||
### 实测(Assimp 6.0.4)
|
||||
|
||||
- 上传 `示例文件/模型_初音未来Hatsune Miku_by_Boku_Wa_Luka.zip`(1,093,100 字节)
|
||||
→ **HTTP 201**、0.89s、`format=glb`、`isPrimary=true`、`status=published`。
|
||||
- 服务端日志:FBX 导出失败(退出码 11,产物回读报
|
||||
`FBX-DOM no Objects dictionary found`)→ 自动回退 GLB,回读通过。
|
||||
- 入库产物:**1,152,464 字节、238 关节、2 张贴图已内嵌**,
|
||||
`conversionNote` = `Hatsune Miku.pmx → GLB,已内嵌 2 张贴图`。
|
||||
- 源码种子:id=16、`初音未来 Hatsune Miku`、`glb`、
|
||||
sha256=`6d0ab0dce97c43dd7de84aba29b09f3b64f2953722d31b5421a39189ff2a8464`、
|
||||
`convertedFrom` = `Hatsune Miku.pmx → GLB`。
|
||||
- 默认模型解析:库里有 2 个 VRM 的情况下,`GET /api/catalog/export` 的
|
||||
`defaultModelId` 仍为 **16**(源码种子按 sha256 命中)。
|
||||
|
||||
### 运维 CLI:`web-server/scripts/import-default-model.mjs`
|
||||
|
||||
服务进程在 systemd 加固(`ProtectSystem=strict`)下写不了程序目录,源码种子
|
||||
通常由本脚本在沙箱外补写。需先 `npm run build`(复用 `dist/models/default-model-source.js`)。
|
||||
|
||||
```bash
|
||||
cd web-server
|
||||
node scripts/import-default-model.mjs 模型.zip --url http://127.0.0.1:3000 \
|
||||
--name "初音未来 Hatsune Miku" --author "Boku Wa Luka" \
|
||||
--license "MMD 模型使用条款(见压缩包内说明)" --source-type other
|
||||
# 上传 → 服务端转换入库 → 设为默认 → 写 src/config/default-model.ts
|
||||
|
||||
node scripts/import-default-model.mjs --write-source-only --model-id 16 # 只补写源码
|
||||
node scripts/import-default-model.mjs --write-source-only --model-id 16 --dry-run
|
||||
```
|
||||
|
||||
上传时固定带 `autoDefault=true`;`--url` 默认 `http://127.0.0.1:3000`,
|
||||
`--source-type` 默认 `other`。
|
||||
|
||||
## VRM 版本识别
|
||||
|
||||
@@ -52,10 +118,26 @@ web-server/src/models/vrm-inspector.ts
|
||||
## 验收
|
||||
|
||||
```bash
|
||||
npm run test # test/vrm-inspector.spec.ts,6 例
|
||||
cd web-server
|
||||
npm run test # 单元测试 125 项 / 7 个文件
|
||||
npm run test:smoke # 冒烟 [4] 段落
|
||||
```
|
||||
|
||||
覆盖:真实 GLB 构造下识别 1.0 / 0.0 / null、文本 glTF 不误判、过小文件报错、
|
||||
非 JSON 首 chunk 报错、上传 201、`isPrimary` 置位、缺来源 400、按格式过滤列表、
|
||||
列表含来源信息(管理端展示用)。
|
||||
| 验收项 | 状态 |
|
||||
|---|---|
|
||||
| 上传 `.zip` 自动转换入库 | ✅ 实测 HTTP 201、0.89s、`format=glb`、`isPrimary=true`、`status=published` |
|
||||
| FBX 失败自动回退 GLB | ✅ Assimp 6.0.4 退出码 11 + 回读失败 → 回退 GLB,`conversionNote` 记录两次尝试 |
|
||||
| GLB 贴图内嵌 | ✅ 2 张贴图内嵌进 BIN chunk(产物 1,152,464 字节、238 关节) |
|
||||
| 默认模型写入源码并按种子解析 | ✅ 种子 id=16;库里有 2 个 VRM 时 `defaultModelId` 仍为 16 |
|
||||
| 失败语义 | ✅ 无 `.pmx` / Assimp 不可用 / 两格式均失败 → HTTP 422,不产生模型记录 |
|
||||
| ZIP 安全 | ✅ 拒绝 zip-slip、加密条目、未知压缩方式(`model-archive.spec.ts`) |
|
||||
| 文件名编码修复 | ✅ `decodeUploadName()` 修正中文文件名乱码 |
|
||||
| VRM 版本识别 | ✅ `test/vrm-inspector.spec.ts`(6 例) |
|
||||
|
||||
`test/model-archive.spec.ts`(**22 项**)覆盖:ZIP 解析 / zip-slip / 加密 / 未知压缩
|
||||
方式、主模型挑选规则、GLB 贴图内嵌、用**假 Assimp** 对照「FBX 导出失败」与
|
||||
「退出码 0 但产物损坏」两种回退路径、源码渲染 + `tsc` 语法校验。
|
||||
|
||||
`test/vrm-inspector.spec.ts`(6 例)覆盖:真实 GLB 构造下识别 1.0 / 0.0 / null、
|
||||
文本 glTF 不误判、过小文件报错、非 JSON 首 chunk 报错、上传 201、`isPrimary`
|
||||
置位、缺来源 400、按格式过滤列表、列表含来源信息(管理端展示用)。
|
||||
|
||||
+67
-2
@@ -23,12 +23,21 @@ Web 服务端以**一个 Linux 业务服务**的形式部署运行:Node 进程
|
||||
> 只给客户端调接口(`/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
|
||||
@@ -66,7 +75,8 @@ Windows/Godot 客户端、Android App ─▶ 软件 API 端口 :3000(只提供
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `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` 等安全加固 |
|
||||
@@ -77,6 +87,53 @@ Windows/Godot 客户端、Android App ─▶ 软件 API 端口 :3000(只提供
|
||||
| `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、</dev/null、-y)→ 只校验,绝不阻塞;失败则 install.sh die
|
||||
```
|
||||
|
||||
逐项询问 `DB_HOST / DB_PORT / DB_USER / DB_PASS / DB_NAME / PORT / ADMIN_PORT / HOST`
|
||||
共 8 个键,回车保留默认;`DB_PASS` 用 `read -rs` 不回显、按约定写盘**不加引号**;
|
||||
校验必填项与端口范围;可用 Node + mysql2 做连通性自检(口令只经环境变量传给子进程,
|
||||
失败仅告警);只替换/追加这 8 个键、保留模板注释,写完 `chmod 600` 并回读校验;
|
||||
结尾打印摘要(口令 `***`)与本次变更列表。
|
||||
|
||||
| 开关 | 说明 |
|
||||
|---|---|
|
||||
| (无) | 交互引导;`.env` 已存在且必填项齐全时跳过询问 |
|
||||
| `--force` | 已有 `.env` 也重新逐项询问 |
|
||||
| `--check` | 只校验,不修改文件、不提问 |
|
||||
| `-y` / `--yes` / `--non-interactive` | 非交互:只校验缺字段与端口,绝不提问 |
|
||||
| `-i` / `--interactive` | 即使 stdin 不是 TTY 也强制提问(读取超时仍生效) |
|
||||
| `--skip-db-check` | 跳过数据库连通性自检 |
|
||||
| `--dir DIR` | 等价于 `--env-file DIR/.env --example DIR/.env.example` |
|
||||
| `--env-file PATH` / `--example PATH` | 指定 `.env` / 模板路径 |
|
||||
| `--timeout SECONDS` | 非 TTY 下每次读取超时秒数(默认 10) |
|
||||
| `-h` / `--help` | 显示帮助 |
|
||||
|
||||
环境变量:`OPENDANCE_NONINTERACTIVE` / `OPENDANCE_INTERACTIVE` /
|
||||
`OPENDANCE_SKIP_DB_CHECK` / `OPENDANCE_ENV_FILE` / `OPENDANCE_ENV_EXAMPLE` /
|
||||
`OPENDANCE_PROMPT_TIMEOUT`。
|
||||
|
||||
**非交互绝不挂住**: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`
|
||||
(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`。
|
||||
注意 `install.sh` 里的 `HEALTH_HOST` / `API_HOST` 用的是**回环地址**做本机健康检查,
|
||||
与对外监听的默认值无关。
|
||||
|
||||
## 进程管理:四层降级
|
||||
|
||||
"运行在 Linux 上"不应等于"必须有 systemd"。安装时按以下顺序探测并选择:
|
||||
@@ -110,6 +167,9 @@ Windows/Godot 客户端、Android App ─▶ 软件 API 端口 :3000(只提供
|
||||
- **`HOST` / `ADMIN_HOST` 绑定地址**:同机反代建议 `127.0.0.1`,异机反代用
|
||||
`0.0.0.0` + 防火墙;`ADMIN_HOST` 可选(默认跟随 `HOST`),可单独把管理端限制在
|
||||
本机或内网;`HOST` 默认取 `0.0.0.0` 以保证开箱可用
|
||||
- **首次部署的 .env 由引导生成**:`install.sh` 缺 `deploy/.env` 时先调用
|
||||
`deploy/init-env.sh`(安装目录已有上次安装的 `.env` 则直接复用);引导脚本在
|
||||
非交互环境下只校验、绝不阻塞,校验失败时 install.sh 才 die
|
||||
- **端口校验**:`PORT` / `ADMIN_PORT` 必须是 1..65535 的整数;非法值在生产环境
|
||||
启动时快速失败(`validateEnv` 的 errors),`ADMIN_PORT === PORT` 只 warn
|
||||
- **install.sh 汇总与反代说明**:结尾分别打印两个端口的访问地址、监听配置与
|
||||
@@ -130,7 +190,7 @@ Windows/Godot 客户端、Android App ─▶ 软件 API 端口 :3000(只提供
|
||||
## 验收
|
||||
|
||||
```bash
|
||||
cp deploy/.env.example deploy/.env # 填写 DB_*
|
||||
bash deploy/init-env.sh # 或手工 cp deploy/.env.example deploy/.env 后填写 DB_*
|
||||
bash deploy/install.sh # 需 root(不需要 sudo)
|
||||
curl -fsS http://localhost:3000/health
|
||||
curl -fsS http://localhost:3001/health
|
||||
@@ -152,6 +212,9 @@ curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3001/ # 期望 200
|
||||
| 不部署、不管理 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)
|
||||
@@ -163,6 +226,8 @@ curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3001/ # 期望 200
|
||||
> - `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` 实机验收。
|
||||
|
||||
Reference in New Issue
Block a user