库里 xs_file_record.file_path 存的是 file.upload-dir 拼出来的绝对路径,老系统是 /var/s-file/upload,新包之前写 /data/upload 会让接老库的历史文件全部读不到(只能迁库), 现在容器内逐字一致,宿主机侧仍由 .env 的 SFILE_DATA_ROOT 决定。 顺带把两处老配置没真正落地的补上: - logging.file.name 沿用 /usr/local/logs/s-file.log。之前 /app/logs 挂载是空的——yml 没设 文件日志,输出只进 docker 的 json-file 且不封顶;同时给 app 的 json 日志加 20m×5 上限 - java.io.tmpdir 用 -D 指到 upload 挂载内。老项目把这行写在 application.properties 里 并不会改 JVM 系统属性,等于没生效;现在 500MB 上传缓冲落在数据盘而不是容器 overlay 层 摆渡目录保持容器内 /data/transfer/*:光闸需要能单独挂它,嵌进 upload 树会把整个文件库 暴露给光闸,而传输包是过路文件、路径不入库。宿主机侧照旧可与老机器同路径(GATE_*)。 内外网两套 compose/config/install.sh 仍字节一致。
207 lines
9.3 KiB
YAML
207 lines
9.3 KiB
YAML
# =============================================================================
|
||
# s-file 信创文件安全传输系统 —— 单台服务器一套(内网机 iw / 外网机 ow 通用同一份)
|
||
#
|
||
# 两台机器之间只靠光闸摆渡目录互通,彼此不开任何网络端口。
|
||
# 随角色变化的取值全在同目录 .env 里,本文件现场不用改。
|
||
#
|
||
# ./scripts/install.sh 自检 -> 校验并载入 images/*.tar -> 建目录 -> 起栈 -> 等健康
|
||
#
|
||
# 数据一律落在宿主机,不建 docker 卷(默认在本目录 volumes/ 下,见 .env 的 SFILE_DATA_ROOT):
|
||
# volumes/pgdata PostgreSQL 数据 volumes/redis Redis AOF
|
||
# volumes/mq RabbitMQ 数据 volumes/upload 用户上传的文件本体
|
||
# volumes/logs 后端日志(+logs/nginx) volumes/gate/out|in 光闸发出/收到的传输包
|
||
#
|
||
# 前端是已构建好的静态包,直接放在 web/ 目录挂进 nginx:
|
||
# 改页面 = 替换 web/ 下的文件,docker compose restart web 即可,不用重新构建镜像。
|
||
#
|
||
# 只发布前端端口;后端 / PostgreSQL / Redis / RabbitMQ 仅在容器网络内可达。
|
||
# =============================================================================
|
||
|
||
name: sfile-${SFILE_ROLE:?缺少 SFILE_ROLE(内网机 iw / 外网机 ow)}
|
||
|
||
services:
|
||
web:
|
||
image: ${NGINX_IMAGE:?缺少 NGINX_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}
|
||
volumes:
|
||
- ./nginx/web.conf:/etc/nginx/conf.d/default.conf:ro
|
||
# 前端静态包:宿主机 web/ 目录即站点根,替换文件即换页面
|
||
- ./web:/usr/share/nginx/html:ro
|
||
- ${SFILE_DATA_ROOT:?缺少 SFILE_DATA_ROOT}/logs/nginx:/var/log/nginx
|
||
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.io.tmpdir 指到数据盘挂载内:Tomcat 接收大文件时先在这里落盘(multipart 上限 500MB),
|
||
# 不指就写进容器 overlay 层,把系统盘吃掉几个 G。老系统那行 java.io.tmpdir 写在
|
||
# application.properties 里其实没生效,这里用 -D 才真正生效
|
||
JAVA_OPTS: "${APP_JAVA_OPTS:--Xms1g -Xmx3g -Dfile.encoding=UTF-8} -Djava.io.tmpdir=/var/s-file/upload/tmpdir -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}"
|
||
# 摆渡目录不进 upload 树(老系统是 ${file.upload-dir}/transfer/send):光闸要单独挂这两个目录,
|
||
# 嵌在 upload 里就得把整个文件库暴露给光闸。这里只是容器内路径,宿主机侧看 .env 的 GATE_*;
|
||
# 传输包是过路文件、路径不入库,所以改它不影响历史数据
|
||
FILE_TRANSFER_SEND_DIR: /data/transfer/send
|
||
FILE_TRANSFER_RECEIVE_DIR: /data/transfer/receive
|
||
FILE_TRANSFER_MONITOR_CRON: "${FILE_TRANSFER_MONITOR_CRON:-0/5 * * * * ?}"
|
||
|
||
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 * * * * ?}
|
||
# 容器标准输出留一份即可(应用日志已写 /usr/local/logs/s-file.log 并按天轮转),
|
||
# 不限速的话 docker 的 json-file 默认不封顶,跑半年能把系统盘吃满
|
||
logging:
|
||
driver: json-file
|
||
options:
|
||
max-size: "20m"
|
||
max-file: "5"
|
||
volumes:
|
||
- ./config/application.yml:/config/application.yml:ro
|
||
# 容器内路径与老系统逐字一致(库里存的是绝对路径前缀,不能改),宿主机侧由 .env 决定
|
||
- ${SFILE_DATA_ROOT}/upload:/var/s-file/upload
|
||
- ${SFILE_DATA_ROOT}/logs:/usr/local/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}
|