Files
senmesh-zc/deployment/部署说明.md
KodouWZF d39b3b035d feat(部署): 新增 deployment/ 内外网两套按侧部署包
按 demo/ 的写法把交付包改成「角色由目录决定」:iw/ 拷内网机、ow/ 拷外网机,
各自含镜像 tar、SQL、配置与 install.sh,install.sh 不再收 iw/ow 参数。

前端不再打进镜像,改为静态包放在 <侧>/web/ 只读挂进 nginx:1.27-alpine,
现场临时改页面直接替换文件 + restart web 即可。数据仍全部挂宿主机,
子目录命名与 deploy/offline 一致(pgdata/redis/mq/upload/logs/gate),
默认根改为包内 ./volumes,放数据盘只改 .env 的 SFILE_DATA_ROOT。

后端镜像 sfile-app:1.0.1 含传输优化那一批(实测组包 11.4×、解包 9.9×)。
出包脚本 deployment/scripts/build-package.sh 负责 jar→镜像→前端双包→sql→tar→VERSION。
2026-11-04 21:44:56 +08:00

239 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# s-file 部署说明 —— deployment/ 内网机包 + 外网机包
> 本目录是**按侧分开**的两套部署包:`iw/` 拷内网机,`ow/` 拷外网机,各自自包含(含镜像 tar、前端静态包、SQL、配置、安装脚本)。
> 与仓库里另外两套部署的关系见 §1,别混用。
---
## 0. 三套部署目录的关系
| 目录 | 用途 | 一台机器几个栈 | 前端形态 | 数据落哪 |
|---|---|---|---|---|
| `deploy/docker-compose.yml` | 我方本机联调 | 一台机器同起内+外两套 | 打进 `sfile-web` 镜像 | 宿主 `D:/zcodedata/sfile` |
| `deploy/offline/` | 单一交付包 + 参数选角色 | 一台一个栈,两台共用同一份盘 | 打进 `sfile-web` 镜像(内含内/外两套站点) | 宿主 `/data/sfile` |
| **`deployment/iw`、`deployment/ow`** | **按侧分目录的正式包** | 一台一个栈,两份包各拷各的 | **静态包直接放 `<侧>/web/`,改页面替换文件即可** | `<侧>/volumes/`(可改绝对路径) |
`deployment/` 相比 `deploy/offline/` 的实质差别只有两点:**前端不再打进镜像**(便于现场临时改页面)、**角色由目录决定而不是命令行参数**(防止在内网机上跑 `install.sh ow`)。镜像、SQL、配置项名称与 `deploy/offline/` 保持一致,运维口径不用重学。
---
## 1. 拓扑
```
内网(客户办公网) 外网(外部报送网)
┌─────────────────────────┐ ┌─────────────────────────┐
│ 内网服务器 跑 iw/ │ │ 外网服务器 跑 ow/ │
│ ┌───────────────────┐ │ 光闸/单向 │ ┌───────────────────┐ │
│ │ sfile-iw-web :80 │◄─┼── 摆渡设备 ──►┼─ │ sfile-ow-web :80 │ │
│ │ sfile-iw-app :8080 │ │ (不开网络端口) │ │ sfile-ow-app │ │
│ │ pg/redis/mq(内部) │ │ │ │ pg/redis/mq(内部) │ │
│ └───────────────────┘ │ │ └───────────────────┘ │
│ volumes/gate/out ───发──┼──────────────┼──▶ volumes/gate/in │
│ volumes/gate/in ◀──收──┼──────────────┼── volumes/gate/out ───发──┼─▶
└───────────┬─────────────┘ └─────────────────────────┘
│ http://host.docker.internal:<MeshSafe端口>
┌──────┴───────────┐
│ MeshSafe 文件安全检测 │ 同机容器 / 独立服务器 / 集群均可,只要求该侧能 HTTP 访问到
└──────────────────┘
```
- 两台服务器之间**不开任何 TCP 端口**,文件只经光闸在「本侧 `gate/out` → 对端 `gate/in`」单向搬运。
- 每台机器一个独立 compose 栈(项目名 `sfile-iw` / `sfile-ow`)。
- 应用只认容器内固定路径 `/data/transfer/send`、`/data/transfer/receive`,宿主机侧路径由 `.env` 的 `GATE_SEND_DIR`/`GATE_RECV_DIR` 决定。
---
## 2. 包内容
```
deployment/
├── 部署说明.md 本文件
├── scripts/build-package.sh 我方出包脚本(现场用不到)
├── iw/ ← 拷内网机
│ ├── docker-compose.yml 5 个服务(web/app/pg/redis/mq),现场不改
│ ├── .env 本机全部可变项(角色/端口/口令/目录/MeshSafe)← 只改这个
│ ├── config/application.yml 后端静态默认配置,不放任何口令
│ ├── nginx/web.conf 前端站点 + /api 反代(站点根 = ./web)
│ ├── web/ 前端静态包(内网包),只读挂进 nginx
│ ├── sql/01-init-table.sql 全量建表(92K,含 x_file_security 与 md5 索引)
│ ├── sql/02-init-data.sql 初始数据(admin、机构、平台编码)
│ ├── sql/upgrade/ 已上线老库的增量脚本,新装不执行
│ ├── images/*.tar 5 个镜像 + SHA256SUMS.txt(不进气仓,走交付盘)
│ ├── scripts/install.sh 一键安装(无参数,角色看 .env)
│ ├── README.md 现场速查(装机/改页面/运维)
│ ├── VERSION.txt 本次出包的版本/平台/时间/镜像清单
│ └── volumes/ 运行时数据,install.sh 自动建(不进气仓)
└── ow/ ← 拷外网机,结构与 iw 完全一致
```
`iw/` 与 `ow/` 只有三处不同:`.env`(角色/方向/网段/机器号/口令/MeshSafe 通道)、`web/`(前端内网包 vs 外网包)、`README.md` 里的角色字样。其余文件出包脚本保证逐字节一致。
### 2.1 镜像清单
| 镜像 | tag | tar | 说明 |
|---|---|---|---|
| `sfile-app` | `1.0.1` | ~313M | JDK8 运行时 `eclipse-temurin:8-jre-jammy`,内含 s-file.jar |
| `nginx` | `1.27-alpine` | ~21M | 只做静态站点 + 反代,前端在宿主机 `web/` 里 |
| `postgres` | `18` | 161M | 验证过 18.3 |
| `redis` | `8` | 53M | 验证过 8.6.3 |
| `rabbitmq` | `3.13-management-alpine` | 84M | 验证过 3.13.7 |
> `sfile-app:1.0.1`(对应 git tag `v1.0.1`)相比 `1.0.0` 是传输链路性能优化那一批(监听周期 60s→5s、读写缓冲 64KB、已压缩格式不再二次压缩、`md5_code` 加索引、单轮时间预算)。对照实测:端到端 123.1s → 9.9s,组包 11.4×、解包 9.9×,包体 +3.63%;详见 `backend/docs/传输链路性能优化方案.md` §10。
> 版本对应关系:git tag `v1.0.1` = 镜像 `sfile-app:1.0.1` = `.env` 的 `APP_IMAGE`,出包脚本按版本号一次对齐。
### 2.2 数据挂载对照(命名/位置与之前一致)
| 数据 | 容器内路径 | `deployment/`(本包) | `deploy/offline/` |
|---|---|---|---|
| PostgreSQL | `/var/lib/postgresql` | `volumes/pgdata` | `/data/sfile/pgdata` |
| Redis | `/data` | `volumes/redis` | `/data/sfile/redis` |
| RabbitMQ | `/var/lib/rabbitmq` | `volumes/mq` | `/data/sfile/mq` |
| 上传文件本体 | `/data/upload` | `volumes/upload` | `/data/sfile/upload` |
| 后端日志 | `/app/logs` | `volumes/logs` | `/data/sfile/logs` |
| 摆渡发出/收到 | `/data/transfer/send`、`/receive` | `volumes/gate/out`、`in` | `/data/sfile/gate/out`、`in` |
子目录名与 compose 里的写法完全沿用,只是根目录默认从 `/data/sfile` 改成包内 `./volumes`(整目录自包含、拷走就能跑)。放独立数据盘就把 `.env` 的 `SFILE_DATA_ROOT`、`GATE_SEND_DIR`、`GATE_RECV_DIR` 改成绝对路径,重跑 `install.sh` 即可,compose 不用动。
---
## 3. 现场要先定的 6 件事(都在 `.env`)
| 项 | 变量 | 建议 |
|---|---|---|
| 服务器 CPU 架构 | — | `uname -m`:x86_64→amd64,aarch64(鲲鹏/飞腾)→arm64。**出包前必须确认**,架构不符装不上 |
| 前端对外端口 | `WEB_PORT` | 出厂 80;被占用改 8080/9080,同步改客户访问地址 |
| 数据盘根目录 | `SFILE_DATA_ROOT` | 正式环境改绝对路径指到独立数据盘。上传文件+库+日志都在这,按 500MB/文件上限估:`平均文件大小 × 日均份数 × 保留天数`,一般 ≥200G |
| 光闸摆渡目录 | `GATE_SEND_DIR` / `GATE_RECV_DIR` | 与光闸厂商确认挂载点,**发出与收到是两个不同目录**,两侧机器各自配自己的 |
| MeshSafe | `MESHSAFE_ENABLED` / `_BASE_URL` / `_CLIENT_ID` / `_SECRET` | 没接检测 → `MESHSAFE_ENABLED=false`(前后端自动隐藏「安全检测」列,摆渡不受影响);接了 → secret 必须与 MeshSafe 侧 `MESHSAFE_API_CLIENTS` 白名单里的 `client_id:secret` **逐字一致** |
| 各类口令 | `POSTGRES_PASSWORD` 等 | 出厂已给随机值可直接跑,正式交付按客户安全要求轮换(§4.1) |
机器规格建议:8 核 / 16G 起(应用堆 `-Xmx` 3G,PG + MQ + Redis 还要约 2-3G,留足页缓存),系统盘 100G + 数据盘独立。
---
## 4. 出包与装机
### 4.1 出包(我方构建机,需要联网一次)
```bash
# 前置:仓库源码就位,docker 可用
./deployment/scripts/build-package.sh 1.0.1 # amd64
TARGET_PLATFORM=linux/arm64 ./deployment/scripts/build-package.sh 1.0.1 # 鲲鹏/飞腾
# 只想重出镜像 tar / 只刷前端:SKIP_JAR=1 / SKIP_WEB=1 / SKIP_SAVE=1
```
脚本会依次:建 jar → 建 `sfile-app:<版本>` → 前端内/外网包导出到 `iw/web`、`ow/web` → 从 `backend/db` 刷 `sql/` → `docker save` 5 个镜像到两侧 `images/` 并写 `SHA256SUMS.txt` → 写两侧 `VERSION.txt` 并对齐 `.env` 的 `APP_IMAGE`。
不动两侧 `volumes/`(现场运行时数据)与 `.env` 里的口令。
带盘就是带 `iw/` 和 `ow/` 两个目录(或各自压缩后分发),**必须整目录拷**——`images/*.tar` 不进气仓。
### 4.2 装机(每台服务器各执行一次)
```bash
# 内网机
cd /opt/sfile-iw
vi .env # 至少确认 MESHSAFE_ENABLED/SECRET
./scripts/install.sh
# 外网机
cd /opt/sfile-ow
vi .env
./scripts/install.sh
```
`install.sh` 做六件事:环境自检 → 校验交付物(5 个 tar + sha256 + 架构 + 前端包完整 + 端口占用探测)→ `docker load` → 建数据/摆渡目录(含 pgdata 属主 999、SELinux `chcon`)→ 起栈 → 逐个等健康,最后打印访问地址。
**幂等**:重复执行只重建有差异的容器,不动已初始化好的数据库。
首次登录后**立刻改掉 admin 的初始口令**。
### 4.3 出厂口令(交付单抄一份,建议现场轮换)
| 变量 | 内网机 iw | 外网机 ow | 现场是否必须改 |
|---|---|---|---|
| `POSTGRES_PASSWORD` | `Sfile-iw-Pg-N1tOz1MxgdaS` | `Sfile-ow-Pg-pT-673--i1rk` | 建议(只在本机容器网络内生效,不对网暴露) |
| `MQ_PASSWORD` | `Sfile-iw-Mq-UTqVSnCDZ0LB` | `Sfile-ow-Mq-sfW5Cv-Nk-cA` | 建议(同上) |
| `JWT_SECRET` | `86a6b015…7d70`(64 位) | `9eeeea03…6988`(64 位) | 必须保密;泄露等于能伪造登录态,两侧已不同 |
| `USER_DEFAULT_PASSWORD` | `Sfile-iw-Usr-FBLS-0fA` | `Sfile-ow-Usr-WqDJ7PDS` | **必须改**:新建用户的初始密码,业务员要知道 |
| admin 登录口令 | 由 `sql/02-init-data.sql` 灌入 | 同左 | **必须改**:装完第一次登录就改 |
| `MESHSAFE_SECRET` | `CHANGE_ME_meshsafe_secret` | 同左 | **故意留占位**:以 MeshSafe 侧白名单为准;装了且 `ENABLED=true` 时 `install.sh` 会拦住 |
> 本包的 `.env` 是**随包提交的出厂配置**(一台机器一份),与 `deploy/offline/` 用 `.env.iw`/`.env.ow` 再拷成 `.env` 的做法等价,只是省掉了拷贝步骤。现场改过的 `.env` 属于该台机器的实际配置,不要提交回仓库。
### 4.4 内网机 / 外网机配置差异
| 变量 | iw | ow |
|---|---|---|
| `SFILE_ROLE` | `iw` | `ow` |
| `SFILE_TRANSFER_LOCATION` | `1`(内网) | `2`(外网) |
| `web/` 前端包 | 内网包(页面显示「内网」标识) | 外网包 |
| `GATE_SEND_DIR` / `GATE_RECV_DIR` | 本侧 out=内→外发出,in=外→内投递 | 本侧 out=外→内发出,in=内→外投递 |
| `MESHSAFE_CLIENT_ID` / `MESHSAFE_CHANNEL` | `sfile-iw` | `sfile-ow` |
| `APP_SUBNET` / `APP_NODE_IP` / `APP_WORK_ID` | `172.30.18.0/24` / `.10` / `1` | `172.30.28.0/24` / `.10` / `2` |
| `JWT_SECRET` / `POSTGRES_PASSWORD` / `MQ_PASSWORD` | 一套 | 另一套(已确认不同) |
| `WEB_PORT` | 80 | 80(两台机器各占自己的 80,不冲突) |
> 两侧网段/机器号故意错开:客户先把两台合并成一台机器做演示时,两份包同起也不冲突。
> `SFILE_TRANSFER_LOCATION` 写反是最容易踩的坑:值对调后两侧都以为自己是发送端,摆渡目录里的包永远不会被解包。
---
## 5. 手工验收(不依赖任何测试脚本)
| # | 动作 | 期望 |
|---|---|---|
| 1 | `docker compose ps` | 两台机各 5 个 `healthy`,无 `Restarting`/`unhealthy` |
| 2 | 浏览器开 `http://<内网机IP>:<端口>` | 左上角有「内网」标识,能登录;外网机同地址无该标识 |
| 3 | 内网上传一个文件 → 传输到外网 | 内网 `volumes/gate/out` 出现 `<n>_<id>.zip`;一分钟内「我的接收」出现 `<原名>_<时间戳>` 文件夹 |
| 4 | 接了 MeshSafe 时看「安全检测」列 | 几秒内从**检测中**变**检测通过**;未接检测则该列不显示 |
| 5 | `docker logs --tail 100 sfile-iw-app` | 无 `ERROR`,能看到监听任务每 5 秒一轮的日志 |
---
## 6. 运维
```bash
docker compose ps # 状态
docker logs -f sfile-iw-app # 后端日志(含送检/摆渡全过程)
tail -f volumes/logs/*.log # 宿主机侧日志(已外置,不用进容器)
docker compose restart app # 改完 config/application.yml 后重启后端
docker compose up -d # 改完 .env(端口/口令/网段)后重建容器
docker compose restart web # 替换 web/ 里的前端文件后刷新站点
docker compose down # 停止(不动数据)
```
**改前端页面**(本包的主要便利点):`web/` 就是站点根,只读挂载进 nginx。
换整包 `rm -rf web && cp -r <新 dist> web && docker compose restart web`;
临时改文案可以直接编辑 `web/` 里的文件,浏览器强刷一次生效。
**备份只需三样**:`volumes/pgdata`(库)、`volumes/upload`(文件本体)、`.env`(口令)。
恢复:换机器后按原路径放回 + `install.sh`(它会把镜像重新 load 并起栈)。
**老库升级**:`sql/01`、`sql/02` 只在**空库首次启动**执行,已有数据卷会被忽略;
已上线环境要补的增量在 `sql/upgrade/`(都是「第 1 节 MySQL/达梦、第 2 节 PostgreSQL」双库混排,只能跑第 2 节):
```bash
awk '/^-- 2\. PostgreSQL/{f=1} f' sql/upgrade/upgrade-20261103-transfer-perf.sql \
| docker exec -i sfile-iw-pg psql -U sfile -d s_file
```
---
## 7. 已知坑与取舍
1. **交付盘必须整目录拷**:`install.sh` 会核 5 个 tar 的个数、sha256、架构,以及 `.env` 里 5 个镜像 tag 是否都能 load 到,还会核 `web/index.html` 与 `web/assets` 非空。只拷脚本不拷 `images/` 或 `web/` 是最常见的现场翻车。
2. **架构不符装不上**:`sfile-app` 与前端包是在构建机上按平台产出的,信创 ARM 机必须用 `TARGET_PLATFORM=linux/arm64` 重新出包(`install.sh` 会拦你)。
3. **前端改了不生效**:`web/` 是只读挂载,容器内看不到宿主机改动是正常的(宿主机改完 restart 就见效);另外 nginx 对 `js/css/字体/图片` 设了 `expires 7d`,直接替换同名文件时用户要强刷。构建产物文件名带 hash,正规换包不受这个影响。
4. **PostgreSQL 18 的数据目录挂载点是 `/var/lib/postgresql`**(15 及以前是 `/var/lib/postgresql/data`)。自己换库版本时改 `.env` 的 `PG_DATA_DIR`,否则容器会报「检测到未挂载的数据」并反复重启。
5. **nginx 反代必须透传带端口的 Host**(`nginx/web.conf` 里用的是 `$http_host`)。改成 `$host` 会让后端 `RequestUtils.getRootDomain` 对无端口地址抛 `IllegalStateException`,症状就是**登录 500**。
6. **发号器 workerId 必须能取到,否则应用起不来。** 后端按 `code.snowflake.workid.<app容器IP>` 取机器号,取不到回退默认 `99`,而 99 超出雪花 5 位上限,启动即 `worker Id can't be greater than 31`。本方案把 app 的容器 IP 锁死(`APP_NODE_IP`)并由 compose 自动写进 JVM 参数,`.env` 里 `APP_SUBNET / APP_GATEWAY / APP_NODE_IP` 三个值必须同网段。
7. **MeshSafe 凭证两边要逐字对上**:调用方 `.env` 的 `MESHSAFE_CLIENT_ID`/`MESHSAFE_SECRET` 必须存在于 MeshSafe 侧 `MESHSAFE_API_CLIENTS`(格式 `client_id:secret,client_id2:secret2`),否则送检全部 401;改白名单要重启 MeshSafe 的 api 容器。`MESHSAFE_CHANNEL` 只是日志追溯标签,服务端不校验,不需要登记。`MESHSAFE_FAIL_OPEN=true` 时检测服务挂了只记失败不阻断,等保从严请置 `false`。
8. **`host.docker.internal` 需要 Docker ≥ 20.10**;MeshSafe 在独立服务器时不需要它,可删掉 compose 里那两行 `extra_hosts`。
9. **PDF 盖章依赖 spire.pdf 是桩实现**:本机因拿不到商业包做了替代 jar,运行时调用会抛 `UnsupportedOperationException`。交付前若客户要电子签章,必须换回 `e-iceblue:spire.pdf:3.7.4` 正式包重新打 jar。
10. **端到端延迟里有一半是监听周期带来的空等**。收发两侧各轮询一次,默认 5 秒一轮 → 平均空等 5 秒、最坏 10 秒(早期默认 1 分钟时是平均 60 秒、最坏 120 秒,这是「一页文件也要 2 分钟」的主因)。客户嫌慢就调 `.env` 的 `FILE_TRANSFER_MONITOR_CRON`;**改成秒级是安全的**:空目录一轮只有两次 `Files.list` + 过滤,且 Spring 的 cron 任务要等上一轮跑完才排下一轮,同实例内不会重叠。副作用是**损坏的包会被更频繁重试**(失败包标 `.error` 后停止重试,但仍在目录里)。改周期要同步看 `config/application.yml` 的 `round-budget-seconds`(留 1 秒余量)。
11. **两侧数据库完全独立**,同一份 `02-init-data.sql` 灌出来 admin 初始口令一致,交付时两侧都要改。
12. **网段/端口与机器上已有容器冲突**:`install.sh` 会探测 `WEB_PORT` 占用并提示;`APP_SUBNET` 冲突时报 `invalid pool request: Pool overlaps with other one on this address space`,整段换掉 `.env` 的三个网络值即可。
13. **检测结论按文件内容复用**:MeshSafe 的 `fileCode` 是内容寻址,同一份字节重复上传会直接沿用上次结论不重新扫。等保评审若问「改判是否会被复用绕过」,现场演示请用**每次内容都不同**的测试文件。
14. **相对路径依赖包目录**:`SFILE_DATA_ROOT=./volumes` 是相对 `docker-compose.yml` 解析的。若把包挪位置或从别处 `docker compose -f` 指过来,数据目录会跟着变——正式环境建议一律改成绝对路径。
15. **随包文本一律 LF**:`.gitattributes` 已把 `*.sh / *.yml / *.sql / *.conf / .env` 锁成 LF,`build-package.sh` 出包时再兜一遍。若有人用 Windows 记事本改过 `install.sh` 再拷去现场,先 `tr -d '\r' < install.sh > i && mv i install.sh`,否则 bash 会在第一行报 `bad interpreter` 或 `set: pipefail\r: invalid option`。