220 lines
16 KiB
Markdown
220 lines
16 KiB
Markdown
|
|
# s-file 客户侧离线部署(两台服务器:内网机 + 外网机)
|
|||
|
|
|
|||
|
|
> 本目录是**交付用**部署;本机联调用的两套栈同起在一台机器上,见 [../部署说明.md](../部署说明.md)。
|
|||
|
|
> 客户现场没有网络、没有光闸模拟目录,一切靠 `docker load` 的镜像 tar + 真实光闸摆渡目录。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. 拓扑
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
内网(客户办公网) 外网(外部报送网)
|
|||
|
|
┌────────────────────────┐ ┌────────────────────────┐
|
|||
|
|
│ 内网服务器 A │ │ 外网服务器 B │
|
|||
|
|
│ ┌──────────────────┐ │ 光闸/单向 │ ┌──────────────────┐ │
|
|||
|
|
│ │ sfile-iw-web :80 │◄─┼── 摆渡设备 ──►┼─ │ sfile-ow-web :80 │ │
|
|||
|
|
│ │ sfile-iw-app :8080│ │ (不开放网络) │ │ sfile-ow-app │ │
|
|||
|
|
│ │ pg/redis/mq(内部) │ │ │ │ pg/redis/mq(内部)│
|
|||
|
|
│ └──────────────────┘ │ │ └──────────────────┘ │
|
|||
|
|
│ /data/sfile/gate/out │─── 发 ────► │ /data/sfile/gate/in │
|
|||
|
|
│ /data/sfile/gate/in │◄─── 收 ──── │ /data/sfile/gate/out │
|
|||
|
|
└───────────┬────────────┘ └───────────┬────────────┘
|
|||
|
|
│ http://host.docker.internal:<MeshSafe端口>
|
|||
|
|
┌──────┴───────────┐
|
|||
|
|
│ MeshSafe 文件安全检测 │ 同机容器 / 独立服务器 / 集群均可,只要求 内网机能 HTTP 访问到
|
|||
|
|
└──────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- 两台服务器之间**不开任何 TCP 端口**,文件只经光闸在 `gate/out` ⇄ 对端 `gate/in` 之间单向搬运。
|
|||
|
|
- 每台机器一个独立 compose 栈,同一份 `docker-compose.yml`,差别全在 `.env`。
|
|||
|
|
- 摆渡目录由光闸设备挂载/同步进宿主机,应用只认容器内固定路径 `/data/transfer/send`、`/data/transfer/receive`。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 交付包内容
|
|||
|
|
|
|||
|
|
本目录本身**就是**交付包,`.env`、`sql`、`images/*.tar` 都已就位,整目录拷走即可装机(不需要现场再回仓库取任何东西)。
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
deploy/offline/
|
|||
|
|
├── docker-compose.yml 一台机器的 5 个服务(web/app/pg/redis/mq),现场不改
|
|||
|
|
├── .env.iw 内网机出厂配置(已填好,含口令)← install.sh iw 会拷成 .env
|
|||
|
|
├── .env.ow 外网机出厂配置(同上,口令与内网机不同)
|
|||
|
|
├── .env.example 空白模板,只在客户环境差异大到出厂值不适用时才用
|
|||
|
|
├── VERSION.txt 本次出盘的版本/平台/时间/镜像清单
|
|||
|
|
├── config/application.yml 静态默认配置,里面不放任何口令
|
|||
|
|
├── nginx/default.conf.template 前端反代(内/外网共用,靠 SFILE_SITE_ROOT 选站点)
|
|||
|
|
├── sql/01-init-table.sql 全量建表(92K,含 x_file_security)
|
|||
|
|
├── sql/02-init-data.sql 初始数据(2.8K,admin、机构、平台编码)
|
|||
|
|
├── images/*.tar 5 个镜像,共 650M(docker save 原始大小)
|
|||
|
|
├── images/SHA256SUMS.txt 交付盘校验值,install.sh 开机先比对它
|
|||
|
|
└── scripts/
|
|||
|
|
├── install.sh 现场一键安装(要带 iw / ow 参数)
|
|||
|
|
├── smoke_test.py 现场自动化验收(18 条断言)
|
|||
|
|
└── export-images.sh 我方出盘脚本,现场用不到
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> 仓库里 `images/*.tar`、`SHA256SUMS.txt`、`dist/` 已被 `.gitignore` 排除——镜像 tar 不进气仓,只走交付盘。
|
|||
|
|
|
|||
|
|
镜像清单与已验证版本(tag 必须与 tar 里的完全一致,`install.sh` 会核对 sha256、架构与 tag 三者):
|
|||
|
|
|
|||
|
|
| 镜像 | 交付 tag | tar 大小 | 本机验证版本 | 说明 |
|
|||
|
|
|---|---|---|---|---|
|
|||
|
|
| `sfile-app` | `1.0.0` | 313M | JDK8 运行时 eclipse-temurin:8-jre-jammy | 内含 s-file.jar |
|
|||
|
|
| `sfile-web` | `1.0.0` | 39M | nginx:alpine | **内网包与外网包都在同一个镜像里** |
|
|||
|
|
| `postgres` | `18` | 161M | 18.3 | |
|
|||
|
|
| `redis` | `8` | 53M | 8.6.3 | |
|
|||
|
|
| `rabbitmq` | `3.13-management-alpine` | 84M | 3.13.7 | |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. 现场要先定的 6 件事
|
|||
|
|
|
|||
|
|
| 项 | 变量 | 建议 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 服务器 CPU 架构 | — | `uname -m`:x86_64→amd64,aarch64(鲲鹏/飞腾)→arm64。**出盘前必须确认**,架构不符装不上 |
|
|||
|
|
| 前端对外端口 | `WEB_PORT` | 出厂值 80;被占用改 8080/9080,同步改客户访问地址 |
|
|||
|
|
| 数据盘根目录 | `SFILE_DATA_ROOT` | 独立挂载点,出厂值 `/data/sfile`。上传文件+库+日志都在这,按 500MB/文件上限估:`文件平均大小 × 日均份数 × 保留天数`,一般 ≥200G |
|
|||
|
|
| 光闸摆渡目录 | `GATE_SEND_DIR` / `GATE_RECV_DIR` | 与光闸厂商确认挂载点,**发出/收到是两个不同目录**,两侧机器各自配自己的 |
|
|||
|
|
| MeshSafe 地址与密钥 | `MESHSAFE_BASE_URL` / `MESHSAFE_SECRET` | 同机:`http://host.docker.internal:44380`;独立机:`http://<IP>:<端口>`;`MESHSAFE_SECRET` 出厂是占位值,**接了检测就必须按 MeshSafe 交付单改**,没接就 `MESHSAFE_ENABLED=false` |
|
|||
|
|
| 各类口令 | `POSTGRES_PASSWORD` 等 | 出厂已给随机值可直接跑,正式交付按客户安全要求轮换(见 §5.1) |
|
|||
|
|
|
|||
|
|
机器规格建议:8 核 / 16G 起(应用堆 `-Xmx` 3G,PG + MQ + Redis 还要约 2-3G,留足页缓存),系统盘 100G + 数据盘独立。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. 出盘(我方,需要联网一次)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 前置:backend/target/s-file.jar 与 frontend/ 已构建
|
|||
|
|
cd deploy/offline
|
|||
|
|
./scripts/export-images.sh 1.0.0 # amd64
|
|||
|
|
TARGET_PLATFORM=linux/arm64 ./scripts/export-images.sh 1.0.0 # 鲲鹏/飞腾
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
脚本会把 `images/*.tar`、`sql/*.sql`、`.env.iw/.env.ow` 的镜像 tag、`SHA256SUMS.txt` 就地刷新,
|
|||
|
|
再打一份 `dist/sfile-offline-<版本>-<平台>.tar.gz` 用于拷盘/刻录。带盘就带这个 gz。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. 装机(客户侧,两台机器各执行一次)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 1) 解包(两台机器用同一个盘,解出来的目录一模一样)
|
|||
|
|
tar -xzf sfile-offline-1.0.0-linux-amd64.tar.gz -C /opt/
|
|||
|
|
cd /opt/sfile-offline-1.0.0-linux-amd64
|
|||
|
|
|
|||
|
|
# 2) 只改必须改的:MeshSafe 密钥(不接检测就关掉开关)
|
|||
|
|
vi .env.iw # 内网机;外网机改 .env.ow
|
|||
|
|
|
|||
|
|
# 3) 一键装:按角色建 .env -> 自检 -> 校验 tar -> docker load -> 建目录 -> 起栈 -> 等健康
|
|||
|
|
./scripts/install.sh iw # 内网机
|
|||
|
|
./scripts/install.sh ow # 外网机
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`install.sh` 第一次执行会把 `.env.iw`/`.env.ow` 拷成 `.env`,之后只读 `.env`——现场改动不会被覆盖。
|
|||
|
|
想回到出厂值:`rm .env && ./scripts/install.sh iw`。
|
|||
|
|
参数与已有 `.env` 的角色不一致时直接拒绝,防止在内网机上起了外网栈。
|
|||
|
|
|
|||
|
|
装完会打印访问地址。首次登录后**立刻改掉 admin 的初始口令**。
|
|||
|
|
|
|||
|
|
### 5.1 出厂口令(交付单要抄一份,并建议现场轮换)
|
|||
|
|
|
|||
|
|
| 变量 | 内网机出厂值 | 外网机出厂值 | 现场是否必须改 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `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 登录口令 | 由 `backend/db/init-data.sql` 灌入 | 同左 | **必须改**:装完第一次登录就改 |
|
|||
|
|
| `MESHSAFE_SECRET` | `CHANGE_ME_meshsafe_secret` | `CHANGE_ME_meshsafe_secret` | **故意留空**:以 MeshSafe 侧 `MESHSAFE_API_CLIENTS` 白名单为准,装机前替换 |
|
|||
|
|
|
|||
|
|
> `install.sh` 只对**启用了 MeshSafe 且 secret 还是占位值**的情况卡住;`MESHSAFE_ENABLED=false` 时其余占位值不拦,方便先装后接检测。
|
|||
|
|
|
|||
|
|
### 5.2 内网机 / 外网机配置差异
|
|||
|
|
|
|||
|
|
`.env.iw` 与 `.env.ow` 已按下表分别填好,这里列出来只为让客户网络/安全评审能对上:
|
|||
|
|
|
|||
|
|
| 变量 | 内网机 | 外网机 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `SFILE_ROLE` | `iw` | `ow` |
|
|||
|
|
| `SFILE_TRANSFER_LOCATION` | `1` | `2` |
|
|||
|
|
| `SFILE_SITE_ROOT` | `/usr/share/nginx/inside` | `/usr/share/nginx/outside` |
|
|||
|
|
| `GATE_SEND_DIR` | `/data/sfile/gate/out`(光闸的**内→外**发出目录) | `/data/sfile/gate/out`(光闸的**外→内**发出目录) |
|
|||
|
|
| `GATE_RECV_DIR` | `/data/sfile/gate/in`(光闸的**外→内**投递目录) | `/data/sfile/gate/in`(光闸的**内→外**投递目录) |
|
|||
|
|
| `MESHSAFE_CHANNEL` / `MESHSAFE_CLIENT_ID` | `sfile-iw` | `sfile-ow` |
|
|||
|
|
| `JWT_SECRET` / `POSTGRES_PASSWORD` / `MQ_PASSWORD` | 一套 | 另一套(已确认不同) |
|
|||
|
|
| `WEB_PORT` | 80 | 80(两台机器各占自己的 80,不冲突) |
|
|||
|
|
| `APP_SUBNET` / `APP_NODE_IP` / `APP_WORK_ID` | `172.30.88.0/24` / `.10` / `1` | `172.30.98.0/24` / `.10` / `2` |
|
|||
|
|
|
|||
|
|
> 两侧网段/机器号故意错开:客户如果先把两台合并成一台机器做演示,不改也能同起。
|
|||
|
|
> `SFILE_TRANSFER_LOCATION` 写反是本方案最容易踩的坑:值对调后,两侧都以为自己是发送端,摆渡目录里的包永远不会被解包。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. 验收清单(现场按这个走一遍)
|
|||
|
|
|
|||
|
|
| # | 动作 | 期望 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 1 | 浏览器开 `http://<内网机IP>:<端口>` | 左上角有「内网」徽标,能登录 |
|
|||
|
|
| 2 | 开 `http://<外网机IP>:<端口>` | 无「内网」徽标(设计上只有内网侧显示标识),能登录 |
|
|||
|
|
| 3 | 内网上传一个正常文件 | 「安全检测」列几秒内从**检测中**变**检测通过** |
|
|||
|
|
| 4 | 内网上传 EICAR 测试串 | 变**检测不通过**,理由含 `Eicar-Test-Signature` 或 `内嵌 EICAR` |
|
|||
|
|
| 5 | 对该文件点分享/传输 | 被拒:`安全检测未通过,禁止分享与传输` |
|
|||
|
|
| 6 | 内网把通过的文件「传输」到外网 | 内网 `GATE_SEND_DIR` 出现 `<n>_<id>.zip` |
|
|||
|
|
| 7 | 等 1 分钟内网监听任务 | 外网「内穿外」目录出现 `<原名>_<时间戳>` 文件夹,文件在内 |
|
|||
|
|
| 8 | 外网该文件的安全检测 | 自动送检并变**检测通过** |
|
|||
|
|
| 9 | 内网对不通过文件做人工复核放行 | 状态变**检测通过**、标记**人工复核**,之后可传输 |
|
|||
|
|
| 10 | `docker compose ps` 两台机各 5 个 healthy | 无 `Restarting`/`unhealthy` |
|
|||
|
|
|
|||
|
|
第 3-9 步可用 `scripts/smoke_test.py` 自动跑(在有 python3 + requests 的机器上,指向客户 IP),现场没条件就手工点。
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
SFILE_IW_BASE=http://<内网机IP>:<端口>/api \
|
|||
|
|
SFILE_OW_BASE=http://<外网机IP>:<端口>/api \
|
|||
|
|
SFILE_ADMIN_USER=admin SFILE_ADMIN_PWD='<现场初始口令>' \
|
|||
|
|
python3 scripts/smoke_test.py
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. 运维
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
docker compose ps # 状态
|
|||
|
|
docker logs -f sfile-iw-app # 后端日志(含送检/摆渡全过程)
|
|||
|
|
docker compose up -d app # 改完 .env 或 config 后重启后端
|
|||
|
|
docker compose down # 停止(不动数据)
|
|||
|
|
docker compose down -v --remove-orphans # ⚠ 数据在 bind 目录里,不会丢;但别顺手执行
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**备份只需三样**:`$SFILE_DATA_ROOT/pgdata`(库)、`$SFILE_DATA_ROOT/upload`(文件本体)、`.env`(口令)。
|
|||
|
|
恢复:换机器后原路径放回 + `docker load` + `install.sh`。
|
|||
|
|
|
|||
|
|
数据库升级:新版本的增量 SQL 放 `backend/db/upgrade-*.sql`,人工执行
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
docker exec -i sfile-iw-pg psql -U sfile -d s_file < upgrade-20261101-meshsafe-file-security.sql
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
(`sql/01/02` 只在**空库首次启动**执行,已有数据卷会被忽略。)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. 已知坑与取舍
|
|||
|
|
|
|||
|
|
1. **占位值检查是有意的,别图省事先注释掉**:`install.sh` 只在「`MESHSAFE_ENABLED=true` 且 `MESHSAFE_SECRET` 还是占位值」时拒绝启动,其余 `CHANGE_ME` 也会拦。没接检测就老实写 `MESHSAFE_ENABLED=false`。
|
|||
|
|
2. **交付盘必须整目录拷**:`install.sh` 会核 5 个 tar 的 sha256、个数、以及 `.env` 里 5 个镜像 tag 是否都能 load 到。只拷脚本不拷 `images/`(tar 不进气仓,见 `.gitignore`)是最常见的现场翻车。
|
|||
|
|
3. **架构不符装不上**:`sfile-app`/`sfile-web` 是在构建机上 `docker build` 出来的,信创 ARM 机必须用 `TARGET_PLATFORM=linux/arm64` 重新出盘(`install.sh` 会拦你)。
|
|||
|
|
4. **PostgreSQL 18 的数据目录挂载点是 `/var/lib/postgresql`**(15 及以前是 `/var/lib/postgresql/data`)。自己换库版本时改 `.env` 的 `PG_DATA_DIR`,否则容器会报「检测到未挂载的数据」并反复重启。
|
|||
|
|
5. **nginx 反代必须透传带端口的 Host**(模板里已用 `$http_host`)。改成 `$host` 会让后端 `RequestUtils.getRootDomain` 对无端口地址抛 `IllegalStateException`,症状就是**登录 500**。
|
|||
|
|
6. **发号器 workerId 必须能取到,否则应用起不来。** 后端按 `code.snowflake.workid.<app容器IP>` 取机器号,取不到时回退默认值 `99`,而 99 超出雪花 5 位上限,启动即 `IllegalArgumentException: worker Id can't be greater than 31`。本方案的做法是把 app 的容器 IP 锁死(`APP_NODE_IP`)并由 compose 自动写进 JVM 参数,`.env` 里 `APP_SUBNET / APP_GATEWAY / APP_NODE_IP` 三个值必须同网段。**只想改代码解决的话**,把 `CodeConfiguration#snowFlakeCodeGenerator` 的兜底 `99L` 改成一个合法值即可解除这层 IP 耦合——但那样同侧多实例会撞 ID,需自行权衡。
|
|||
|
|
7. **MeshSafe 凭证**:`MESHSAFE_CLIENT_ID`/`SECRET` 必须在 MeshSafe 侧 `MESHSAFE_API_CLIENTS` 白名单里存在,否则送检全部 401。`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. **摆渡延迟固定为监听周期**(默认 1 分钟一次)。客户嫌慢就调 `FILE_TRANSFER_MONITOR_CRON`,不要改成秒级——它会整目录扫描。
|
|||
|
|
11. **两侧数据库完全独立**,同一份 `init-data.sql` 灌出来 admin 初始口令一致,交付时两侧都要改。
|
|||
|
|
12. **装完才发现角色装反了**(内网机上跑了 `install.sh ow`):`.env` 与参数角色不一致时 `install.sh` 直接拒绝,所以真发生一定是 `rm .env` 之后重装。compose 项目名跟着角色走,旧项目还在占用 `APP_SUBNET`,会报 `invalid pool request: Pool overlaps with other one on this address space`。先把旧项目拆掉再起:
|
|||
|
|
`docker compose -p sfile-<旧角色> down` → `rm .env` → `./scripts/install.sh <正确角色>`。
|
|||
|
|
13. **检测结论按文件内容复用**:MeshSafe 的 `fileCode` 是内容寻址,同一份字节重复上传会直接沿用上次结论,不会重新扫。这是性能上的设计,但等保评审可能会问「改判是否会被复用绕过」,现场演示验收时请用**每次内容都不同**的测试文件。
|