Files
hfb_sys/deploy
2026-08-16 23:57:19 +08:00
..
2026-08-06 15:58:14 +08:00
2026-08-16 22:32:07 +08:00
2026-08-16 23:57:19 +08:00

生产部署说明

目标访问地址:

https://主站域名          # CADDY_DOMAIN,哈夫币系统
https://选号网域名        # CADDY_SHOW_DOMAIN(可选),hfb_show 静态选号站
https://monitor.主站域名  # Beszel 监控

生产部署使用 Caddy 作为公网入口,只暴露宿主机 80443 端口。Caddy 会自动申请和续签 HTTPS 证书,并直接托管前端静态资源(主站 dist 与可选的选号网 show-dist 在构建 Caddy 镜像时打入);后端、MySQL、Redis 均通过 Docker 内网通信,图片对象存储使用阿里云 OSS。选号网与主站共用同一 backend,浏览器访问选号网域名下的 /api/* 由 Caddy 反代到本机 backend不会直连数据库

部署前请确认:

  • 主站域名 A 记录已解析到服务器公网 IP;启用选号网时,选号网域名也要解析到同一 IP。
  • 服务器安全组或防火墙已放行 80/tcp443/tcp
  • 服务器上没有其他服务占用 80443
  • 国内服务器如需对外访问,请先确认域名备案和云厂商限制。

1. 准备环境变量

cp backend/.env.prod.example backend/.env

编辑 backend/.env,至少替换以下占位值:

CADDY_DOMAIN
CADDY_EMAIL
MYSQL_ROOT_PASSWORD
MYSQL_PASSWORD
MYSQL_DSN
JWT_SECRET
STORAGE_ACCESS_KEY_ID
STORAGE_SECRET_ACCESS_KEY
BACKEND_UID
BACKEND_GID
BACKUP_DIR
BACKUP_STATUS_DIR
BACKUP_RESTORE_DIR
BACKUP_OSS_URI
BACKUP_PASSPHRASE
BACKUP_MYSQL_PASSWORD

BACKEND_UIDBACKEND_GID 填写服务器业务用户的数值 ID,例如:

id -u yml
id -g yml

后端容器会使用该 UID/GID 运行。部署脚本由 root 执行时会自动把已有日志修正为该属主;非 root 执行且发现旧的 root 日志时,会给出需要执行的 chown 命令后停止。

对象存储:阿里云 OSS

图片等业务文件存储在私有 OSS Bucket,backend/.envSTORAGE_* 直接指向 OSS

STORAGE_ENDPOINT=https://oss-cn-hangzhou-internal.aliyuncs.com
STORAGE_BUCKET=hfb-sys-assets
STORAGE_ACCESS_KEY_ID=change-oss-access-key-id
STORAGE_SECRET_ACCESS_KEY=change-oss-access-key-secret
STORAGE_REGION=cn-hangzhou
STORAGE_BUCKET_LOOKUP=dns

RAM 凭证只授予该 Bucket 的列举、读取、写入对象权限,不要使用阿里云主账号 AccessKey。杭州 ECS 必须使用内网 Endpoint,避免跨公网访问。

历史 MinIO 数据已通过 scripts/migrate-minio-to-oss.sh 迁移完成,生产编排不再包含 MinIO;旧 MinIO volume 观察期结束后可删除。

数据库在线备份与时间点恢复

生产 MySQL 开启了 ROW 格式 binlog 和崩溃安全刷盘。备份期间业务持续读写:每日用 XtraBackup 做物理全量备份,调度器每分钟检查 binlog 位点,仅在有新增已提交事务时轮转并归档。备份对象先做客户端加密,再由 Bucket 的服务器端加密保护;远端仅在 payload、checksum 和 manifest 都上传成功后写入 complete.env

部署脚本会创建或更新 BACKUP_MYSQL_USER,该用户只有 XtraBackup 所需的全局备份权限和业务库只读权限,不开放远程 root。

备份 Bucket 必须独立于业务 Bucket,开启“OSS 完全托管”服务器端加密和版本控制。备份 RAM 身份仅授予该 Bucket 的列举、读写对象权限;若填写 BACKUP_KMS_KEY_ID,再授予该 Key 的加解密权限。推荐配置不可变保留策略。

部署机需安装 ossutilopensslflock,并使用与 cron 相同的业务用户完成 ossutil 凭证配置;部署脚本会在启动服务前检查这些依赖。

# 每日全量热备,本地留存并上传 OSS 备份桶
./scripts/backup-online.sh full

# 检查并归档有新增事务的关闭 binlog,配合全量实现时间点恢复
./scripts/archive-binlog.sh archive

# 校验本地状态对应的远端完整对象 / 清理过期本地备份
./scripts/archive-binlog.sh verify
./scripts/backup-online.sh status
./scripts/backup-online.sh prune

后台“数据备份”页面只读取 ${BACKUP_STATUS_DIR}/status.json;该目录以只读方式挂载给 backend,不包含备份载荷。首次部署后先生成状态摘要:

./scripts/backup-status.sh write

推荐 cron(以部署用户运行,并把日志写到受限目录):

# 每分钟读取后台定时配置,按需归档 binlog 和执行全量热备。
* * * * * cd /srv/hfb_sys && ./scripts/backup-scheduler.sh >> /data/backups/backup-scheduler.log 2>&1
15 5 * * * cd /srv/hfb_sys && ./scripts/backup-online.sh prune >> /data/backups/prune.log 2>&1
10 5 * * 0 cd /srv/hfb_sys && ./scripts/archive-binlog.sh verify >> /data/backups/archive-verify.log 2>&1

全量备份时间、是否启用定时任务和 binlog 间隔在后台“数据备份”页面配置;宿主机 cron 必须保留每分钟的调度器入口。不要将 Docker socket、备份目录或恢复操作暴露给 backend。

OSS 生命周期建议:mysql/full/daily/ 30 天、weekly/ 60 天、monthly/ 365 天、mysql/binlog/ 至少 45 天。时间点恢复窗口受最早全量备份和 binlog 保留期共同限制。

每周必须在隔离目录做一次恢复演练,绝不向生产容器回放:

# 从 status 输出选取一个带 complete.env 的全量备份 URI。
./scripts/restore-online.sh prepare oss://hfb-backup/mysql/full/daily/<server_uuid>/<timestamp>/complete.env
./scripts/restore-online.sh fetch-all /data/restore/<server_uuid>/<timestamp>
./scripts/restore-online.sh start /data/restore/<server_uuid>/<timestamp>
./scripts/restore-online.sh replay /data/restore/<server_uuid>/<timestamp> hfb-restore-<timestamp> '2026-08-16 20:00:00'

恢复容器使用 --network none,只可通过 docker exec 检查。演练结束后手动删除名为 hfb-restore-* 的恢复容器和 /data/restore 对应目录;生产容器不在恢复工具的可选目标中。

同机部署选号网(hfb_show,可选)

backend/.env 增加(域名不要带 https://):

CADDY_SHOW_DOMAIN=show.example.com
HFB_SHOW_DIR=../hfb_show

要求:

  1. 服务器上 hfb_showhfb_sys 同级目录(或 HFB_SHOW_DIR 指向真实路径)。
  2. 部署机有 Docker 即可;不必安装 Node/npm(无 npm 时脚本用 node:24-alpine 容器构建选号网)。
  3. 选号网域名 DNS 已指向本机。

一键脚本会自动:

  • npm ci && npm run build 选号网;
  • dist 同步到 deploy/caddy/show-dist
  • 生成 Caddy 站点配置 deploy/caddy/conf.d/show.caddy
  • 构建 Caddy 镜像时打入主站 + 选号网静态资源;
  • 为选号网域名申请 HTTPS,并把 /api/* 反代到 backend:8080

不配置 CADDY_SHOW_DOMAIN 则行为与原来一致,仅部署主站。

如果正式对外运营,还需要配置真实短信和实名服务,避免继续使用测试配置。

2. 一键部署

./scripts/deploy-prod.sh

脚本会自动完成:

  • 检查 backend/.env 是否仍指向 127.0.0.1localhost、占位值或已下线的 MinIO。
  • 构建并启动生产容器。
  • 使用配置的非 root UID/GID 运行后端,并检查日志目录属主。
  • 通过 Caddy 自动申请或续签 HTTPS 证书。
  • 等待 MySQL、Redis 就绪。
  • 按顺序执行尚未应用的数据库迁移。
  • 重启后端并检查 https://你的域名/api/health

如需部署完成后直接跟随查看后端日志:

./scripts/deploy-prod.sh --logs

后端运行日志会写入宿主机:

backend/logs/app-YYYY-MM-DD.log

测试阶段需要清空并重建数据库时:

./scripts/deploy-prod.sh --reset-db

3. 手动启动服务

set -a
source backend/.env
set +a
docker compose -f deploy/docker-compose.prod.yml up -d --build

手动执行 docker compose 时需要先导出 CADDY_DOMAINCADDY_EMAIL,否则 Caddy 无法读取证书域名配置。优先推荐使用一键部署脚本,它会自动从 backend/.env 导出这两个变量。

4. 手动执行数据库迁移

通常直接使用一键部署脚本即可。确实需要手动迁移时,使用后端镜像内置的 goose:

MYSQL_DSN="$(awk -F= '$1=="MYSQL_DSN"{sub(/^[^=]*=/,""); print; exit}' backend/.env)"
case "$MYSQL_DSN" in
  *multiStatements=*) GOOSE_DSN="$MYSQL_DSN" ;;
  *\?*) GOOSE_DSN="${MYSQL_DSN}&multiStatements=true" ;;
  *) GOOSE_DSN="${MYSQL_DSN}?multiStatements=true" ;;
esac

docker compose -f deploy/docker-compose.prod.yml run --rm --no-deps backend \
  /app/goose -dir /app/migrations mysql "$GOOSE_DSN" up

5. 验证

curl https://主站域名/health
curl https://主站域名/api/health
# 若启用了选号网:
curl -I https://选号网域名/
curl https://选号网域名/api/health
curl "https://选号网域名/api/listings?page=1&page_size=1"

浏览器访问:

https://主站域名
https://主站域名/你配置的后台入口/login
https://选号网域名          # 选号网首页

Caddy 证书和 ACME 账号数据保存在 Docker 卷 caddy_datacaddy_config 中。不要随意删除这两个卷,否则 Caddy 会重新申请证书,频繁重建可能触发证书签发频率限制。