Files
senmesh-zc/deploy/offline/部署说明.md
KodouWZF 4eb67e246e feat(交付): 客户侧两台服务器离线部署包
内网机/外网机各起一套栈,两台之间不开任何端口,文件只经光闸在
gate/out 与对端 gate/in 之间单向摆渡。一份 compose + 一份 config +
一份 nginx 模板,全部差异收在 .env 里。

- docker-compose.yml:5 服务只发布前端端口;app 容器 IP 锁死并由
  compose 注入 -Dcode.snowflake.workid.<IP>=<WORK_ID>,绕开兜底 99
  超雪花 5 位上限导致启动即崩的问题
- .env.iw / .env.ow:出厂配置与随机口令已填好,仅 MESHSAFE_SECRET
  故意留占位(以 MeshSafe 侧白名单为准);按 maintainer 决定原样入库
- install.sh iw|ow:按角色建 .env -> 自检 -> sha256 校验 tar -> 架构
  核对 -> docker load 后校验 tag -> 建目录/属主/SELinux 标签 -> 起栈
  并逐个等健康。修掉 pipefail 下 `grep | head -1` 静默中断、架构字段
  误读 manifest.json(实际在 Config blob)、die() 未定义先被调用
- export-images.sh:就地刷新 images/*.tar、sql、.env 的 tag 与
  SHA256SUMS,再打 dist 交付盘;tar 与 SHA256SUMS 由 .gitignore 排除
- sql/01+02:从 backend/db 的 PG 全量与初始数据覆盖拷贝。三个
  upgrade-*.sql 的 PG 段(x_upload_config、x_file_security)已在
  init-table-postgres-full.sql 内,空库跑 01->02 即完整现状
- smoke_test.py:18 条断言的现场验收,凭证全部走环境变量
- 部署说明.md:拓扑、交付清单、出厂口令表、iw/ow 差异、验收清单、
  13 条已知坑

镜像 tar 不进 git,装机包由 export-images.sh 出盘。
2026-11-02 20:40:16 +08:00

16 KiB
Raw Blame History

s-file 客户侧离线部署(两台服务器:内网机 + 外网机)

本目录是交付用部署;本机联调用的两套栈同起在一台机器上,见 ../部署说明.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. 出盘(我方,需要联网一次)

# 前置: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. 装机(客户侧,两台机器各执行一次)

# 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),现场没条件就手工点。

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. 运维

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,人工执行

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 是内容寻址,同一份字节重复上传会直接沿用上次结论,不会重新扫。这是性能上的设计,但等保评审可能会问「改判是否会被复用绕过」,现场演示验收时请用每次内容都不同的测试文件。