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 出盘。
This commit is contained in:
189
deploy/offline/docker-compose.yml
Normal file
189
deploy/offline/docker-compose.yml
Normal file
@@ -0,0 +1,189 @@
|
||||
# =============================================================================
|
||||
# s-file 信创文件安全传输系统 —— 客户侧单台服务器部署(内网机 / 外网机通用同一份)
|
||||
#
|
||||
# 一台服务器一套本栈;两台机器靠光闸摆渡目录互通,彼此之间不开任何网络端口。
|
||||
# 所有随角色变化的取值都在同目录 .env 里,本文件现场不用改。
|
||||
#
|
||||
# ./scripts/install.sh iw # 内网机(第一次会把 .env.iw 拷成 .env)
|
||||
# ./scripts/install.sh ow # 外网机
|
||||
#
|
||||
# 手动起也行:cp .env.iw .env && vi .env -> docker load -i images/*.tar -> docker compose up -d
|
||||
#
|
||||
# 只发布前端端口;后端 / PostgreSQL / Redis / RabbitMQ 仅在容器网络内可达。
|
||||
# =============================================================================
|
||||
|
||||
name: sfile-${SFILE_ROLE:?缺少 SFILE_ROLE(内网机 iw / 外网机 ow)}
|
||||
|
||||
services:
|
||||
web:
|
||||
image: ${WEB_IMAGE:?缺少 WEB_IMAGE}
|
||||
container_name: sfile-${SFILE_ROLE}-web
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${WEB_HOST:-0.0.0.0}:${WEB_PORT:-80}:80"
|
||||
environment:
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
# nginx 官方镜像的 /etc/nginx/templates/*.template 会按 NGINX_ENVSUBST_FILTER
|
||||
# 过滤后 envsubst 生成 conf;限定只替换 SFILE_ 开头的变量,避免动到 $uri/$http_host
|
||||
NGINX_ENVSUBST_FILTER: ^SFILE_
|
||||
SFILE_SITE_ROOT: ${SFILE_SITE_ROOT:?缺少 SFILE_SITE_ROOT}
|
||||
SFILE_APP_UPSTREAM: app
|
||||
volumes:
|
||||
- ./nginx/default.conf.template:/etc/nginx/templates/default.conf.template:ro
|
||||
depends_on:
|
||||
app:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf -o /dev/null http://127.0.0.1:80/ || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
networks: [sfile]
|
||||
|
||||
app:
|
||||
image: ${APP_IMAGE:?缺少 APP_IMAGE}
|
||||
container_name: sfile-${SFILE_ROLE}-app
|
||||
restart: unless-stopped
|
||||
stop_grace_period: 30s
|
||||
environment:
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
# 发号器 workerId 按「容器 IP」取键,取不到会回退 99 而 99 > 31 直接启动失败,
|
||||
# 因此这里锁死 app 的容器 IP,并把同一个键写进 JVM 参数,两处永远一致
|
||||
JAVA_OPTS: "${APP_JAVA_OPTS:--Xms1g -Xmx3g -Dfile.encoding=UTF-8} -Dcode.snowflake.workid.${APP_NODE_IP:?缺少 APP_NODE_IP}=${APP_WORK_ID:?缺少 APP_WORK_ID}"
|
||||
SPRING_CONFIG_ADDITIONAL_LOCATION: file:/config/application.yml
|
||||
|
||||
SPRING_DATASOURCE_URL: jdbc:postgresql://pg:5432/${POSTGRES_DB:?缺少 POSTGRES_DB}
|
||||
SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:?缺少 POSTGRES_USER}
|
||||
SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD:?缺少 POSTGRES_PASSWORD}
|
||||
|
||||
SPRING_REDIS_HOST: redis
|
||||
SPRING_REDIS_PORT: 6379
|
||||
|
||||
SPRING_RABBITMQ_HOST: mq
|
||||
SPRING_RABBITMQ_PORT: 5672
|
||||
SPRING_RABBITMQ_USERNAME: ${MQ_USER:?缺少 MQ_USER}
|
||||
SPRING_RABBITMQ_PASSWORD: ${MQ_PASSWORD:?缺少 MQ_PASSWORD}
|
||||
# 两侧交换机/广播 ID 必须不同,防止有人把两套栈并到一台机器时串消息
|
||||
RABBITMQ_CUSTOM_TOPIC_EXCHANGE_NAME: s-file-${SFILE_ROLE}-topic
|
||||
RABBITMQ_CUSTOM_DELAYED_EXCHANGE_NAME: s-file-${SFILE_ROLE}-delayed
|
||||
RABBITMQ_CUSTOM_BROADCAST_ID: s-file-${SFILE_ROLE}-broadcast
|
||||
|
||||
SECURITY_JWT_SECRET: ${JWT_SECRET:?缺少 JWT_SECRET}
|
||||
USER_DEFAULT_PASSWORD: ${USER_DEFAULT_PASSWORD:?缺少 USER_DEFAULT_PASSWORD}
|
||||
|
||||
# 内网机 1 / 外网机 2,写反了摆渡方向就反了
|
||||
FILE_TRANSFER_LOCATION: "${SFILE_TRANSFER_LOCATION:?缺少 SFILE_TRANSFER_LOCATION}"
|
||||
FILE_TRANSFER_SEND_DIR: /data/transfer/send
|
||||
FILE_TRANSFER_RECEIVE_DIR: /data/transfer/receive
|
||||
FILE_TRANSFER_MONITOR_CRON: "${FILE_TRANSFER_MONITOR_CRON:-0 */1 * * * ?}"
|
||||
|
||||
MESHSAFE_ENABLED: ${MESHSAFE_ENABLED:-true}
|
||||
MESHSAFE_BASE_URL: ${MESHSAFE_BASE_URL:-}
|
||||
MESHSAFE_CLIENT_ID: ${MESHSAFE_CLIENT_ID:-}
|
||||
MESHSAFE_SECRET: ${MESHSAFE_SECRET:-}
|
||||
MESHSAFE_CHANNEL: ${MESHSAFE_CHANNEL:-sfile-${SFILE_ROLE}}
|
||||
MESHSAFE_FAIL_OPEN: ${MESHSAFE_FAIL_OPEN:-true}
|
||||
MESHSAFE_TIMEOUT_SECONDS: ${MESHSAFE_TIMEOUT_SECONDS:-120}
|
||||
MESHSAFE_POLL_TIMEOUT_MINUTES: ${MESHSAFE_POLL_TIMEOUT_MINUTES:-60}
|
||||
MESHSAFE_POLL_CRON: ${MESHSAFE_POLL_CRON:-0/10 * * * * ?}
|
||||
volumes:
|
||||
- ./config/application.yml:/config/application.yml:ro
|
||||
- ${SFILE_DATA_ROOT:?缺少 SFILE_DATA_ROOT}/upload:/data/upload
|
||||
- ${SFILE_DATA_ROOT}/logs:/app/logs
|
||||
# 摆渡目录:容器内固定路径,宿主机侧由光闸设备负责单向搬运
|
||||
- ${GATE_SEND_DIR:?缺少 GATE_SEND_DIR}:/data/transfer/send
|
||||
- ${GATE_RECV_DIR:?缺少 GATE_RECV_DIR}:/data/transfer/receive
|
||||
# MeshSafe 与应用同机部署时靠它从容器内访问宿主机端口;
|
||||
# Docker < 20.10 不支持 host-gateway,或 MeshSafe 在独立服务器时,删掉这两行即可
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
depends_on:
|
||||
pg:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
mq:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -s --max-time 5 -o /dev/null http://127.0.0.1:8080/ || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 8s
|
||||
retries: 5
|
||||
# 首次建库要跑全量 DDL,冷启动给足时间
|
||||
start_period: 180s
|
||||
networks:
|
||||
sfile:
|
||||
ipv4_address: ${APP_NODE_IP}
|
||||
|
||||
pg:
|
||||
image: ${PG_IMAGE:?缺少 PG_IMAGE}
|
||||
container_name: sfile-${SFILE_ROLE}-pg
|
||||
restart: unless-stopped
|
||||
stop_grace_period: 60s
|
||||
environment:
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
POSTGRES_DB: ${POSTGRES_DB:?缺少 POSTGRES_DB}
|
||||
POSTGRES_USER: ${POSTGRES_USER:?缺少 POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?缺少 POSTGRES_PASSWORD}
|
||||
# 空库首次启动才执行 initdb.d,已有数据卷时会被忽略
|
||||
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C"
|
||||
volumes:
|
||||
# postgres 18+ 数据目录挂载点是 /var/lib/postgresql,15- 是 .../data,见 .env 的 PG_DATA_DIR
|
||||
- ${SFILE_DATA_ROOT}/pgdata:${PG_DATA_DIR:-/var/lib/postgresql}
|
||||
- ./sql/01-init-table.sql:/docker-entrypoint-initdb.d/01-init-table.sql:ro
|
||||
- ./sql/02-init-data.sql:/docker-entrypoint-initdb.d/02-init-data.sql:ro
|
||||
healthcheck:
|
||||
# 必须强制走 TCP(-h 127.0.0.1):首次 initdb 期间镜像会临时起一个只听 unix socket
|
||||
# 的实例,此时用默认 socket 探测也会返回成功,导致 app 抢跑连库失败
|
||||
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 60s
|
||||
networks: [sfile]
|
||||
|
||||
redis:
|
||||
image: ${REDIS_IMAGE:?缺少 REDIS_IMAGE}
|
||||
container_name: sfile-${SFILE_ROLE}-redis
|
||||
restart: unless-stopped
|
||||
command: ["redis-server", "--appendonly", "yes", "--maxmemory", "${REDIS_MAXMEMORY:-512mb}", "--maxmemory-policy", "allkeys-lru"]
|
||||
environment:
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
volumes:
|
||||
- ${SFILE_DATA_ROOT}/redis:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
networks: [sfile]
|
||||
|
||||
mq:
|
||||
image: ${MQ_IMAGE:?缺少 MQ_IMAGE}
|
||||
container_name: sfile-${SFILE_ROLE}-mq
|
||||
restart: unless-stopped
|
||||
stop_grace_period: 30s
|
||||
environment:
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
RABBITMQ_DEFAULT_USER: ${MQ_USER:?缺少 MQ_USER}
|
||||
RABBITMQ_DEFAULT_PASS: ${MQ_PASSWORD:?缺少 MQ_PASSWORD}
|
||||
volumes:
|
||||
- ${SFILE_DATA_ROOT}/mq:/var/lib/rabbitmq
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "rabbitmq-diagnostics -q ping"]
|
||||
interval: 20s
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
start_period: 60s
|
||||
networks: [sfile]
|
||||
|
||||
networks:
|
||||
sfile:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
# 锁 app 容器 IP 是为了发号器 workerId 取键;subnet/网关/app IP 三个值必须同网段。
|
||||
# 若与客户机器上已有 docker 网段冲突,整段换掉(如 172.31.88.0/24)即可
|
||||
- subnet: ${APP_SUBNET:?缺少 APP_SUBNET}
|
||||
gateway: ${APP_GATEWAY:?缺少 APP_GATEWAY}
|
||||
Reference in New Issue
Block a user