内网机/外网机各起一套栈,两台之间不开任何端口,文件只经光闸在 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 出盘。
16 KiB
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. 已知坑与取舍
- 占位值检查是有意的,别图省事先注释掉:
install.sh只在「MESHSAFE_ENABLED=true且MESHSAFE_SECRET还是占位值」时拒绝启动,其余CHANGE_ME也会拦。没接检测就老实写MESHSAFE_ENABLED=false。 - 交付盘必须整目录拷:
install.sh会核 5 个 tar 的 sha256、个数、以及.env里 5 个镜像 tag 是否都能 load 到。只拷脚本不拷images/(tar 不进气仓,见.gitignore)是最常见的现场翻车。 - 架构不符装不上:
sfile-app/sfile-web是在构建机上docker build出来的,信创 ARM 机必须用TARGET_PLATFORM=linux/arm64重新出盘(install.sh会拦你)。 - PostgreSQL 18 的数据目录挂载点是
/var/lib/postgresql(15 及以前是/var/lib/postgresql/data)。自己换库版本时改.env的PG_DATA_DIR,否则容器会报「检测到未挂载的数据」并反复重启。 - nginx 反代必须透传带端口的 Host(模板里已用
$http_host)。改成$host会让后端RequestUtils.getRootDomain对无端口地址抛IllegalStateException,症状就是登录 500。 - 发号器 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,需自行权衡。 - MeshSafe 凭证:
MESHSAFE_CLIENT_ID/SECRET必须在 MeshSafe 侧MESHSAFE_API_CLIENTS白名单里存在,否则送检全部 401。MESHSAFE_FAIL_OPEN=true时检测服务挂了不阻断业务(只记失败),等保从严请置false。 host.docker.internal需要 Docker ≥ 20.10;MeshSafe 在独立服务器时不需要它,可把 compose 里那两行extra_hosts删掉。- PDF 盖章依赖 spire.pdf 是桩实现:本机因拿不到商业包做了替代 jar,运行时调用会抛
UnsupportedOperationException。交付前若客户要电子签章功能,必须换回e-iceblue:spire.pdf:3.7.4正式包重新打 jar。 - 摆渡延迟固定为监听周期(默认 1 分钟一次)。客户嫌慢就调
FILE_TRANSFER_MONITOR_CRON,不要改成秒级——它会整目录扫描。 - 两侧数据库完全独立,同一份
init-data.sql灌出来 admin 初始口令一致,交付时两侧都要改。 - 装完才发现角色装反了(内网机上跑了
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 <正确角色>。 - 检测结论按文件内容复用:MeshSafe 的
fileCode是内容寻址,同一份字节重复上传会直接沿用上次结论,不会重新扫。这是性能上的设计,但等保评审可能会问「改判是否会被复用绕过」,现场演示验收时请用每次内容都不同的测试文件。