From a0fc23647bcc635ca55c7f6b89414444de332b3d Mon Sep 17 00:00:00 2001 From: yml2213 Date: Thu, 16 Jul 2026 22:24:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=90=8C=E6=9C=BA=E5=8F=8C?= =?UTF-8?q?=E5=9F=9F=E5=90=8D=E9=83=A8=E7=BD=B2=E9=80=89=E5=8F=B7=E7=BD=91?= =?UTF-8?q?hfb=5Fshow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Caddy按CADDY_SHOW_DOMAIN托管选号网静态并反代API;一键脚本可构建hfb_show并写入配置。 --- .gitignore | 6 ++ backend/.env.prod.example | 4 ++ deploy/README.md | 46 +++++++++++-- deploy/caddy/Caddyfile | 63 ++++++++++-------- deploy/caddy/Dockerfile | 7 +- deploy/caddy/conf.d/.gitkeep | 0 deploy/caddy/conf.d/00-base.caddy | 4 ++ deploy/caddy/show-dist/.gitkeep | 0 deploy/caddy/show-dist/index.html | 32 +++++++++ deploy/caddy/show.caddy.tmpl | 35 ++++++++++ deploy/docker-compose.prod.yml | 3 + scripts/deploy-prod.sh | 105 +++++++++++++++++++++++++++++- 12 files changed, 267 insertions(+), 38 deletions(-) create mode 100644 deploy/caddy/conf.d/.gitkeep create mode 100644 deploy/caddy/conf.d/00-base.caddy create mode 100644 deploy/caddy/show-dist/.gitkeep create mode 100644 deploy/caddy/show-dist/index.html create mode 100644 deploy/caddy/show.caddy.tmpl diff --git a/.gitignore b/.gitignore index 9ec36b9..8973387 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,9 @@ frontend/components.d.ts backups/ .DS_Store backend/api + +# 选号网构建产物(由 deploy-prod.sh 生成) +deploy/caddy/show-dist/** +!deploy/caddy/show-dist/.gitkeep +!deploy/caddy/show-dist/index.html +deploy/caddy/conf.d/show.caddy diff --git a/backend/.env.prod.example b/backend/.env.prod.example index 6a0823a..cfaa9a3 100644 --- a/backend/.env.prod.example +++ b/backend/.env.prod.example @@ -5,6 +5,10 @@ APP_ADDR=:8080 # Caddy 自动申请和续签 HTTPS 证书使用。 CADDY_DOMAIN=hfb.221329.cc.cd CADDY_EMAIL=admin@example.com +# 选号网(hfb_show)独立域名,与主站同机;不部署选号网可留空。 +# CADDY_SHOW_DOMAIN=show.example.com +# 选号网源码目录(相对 hfb_sys 根或绝对路径),默认 ../hfb_show +# HFB_SHOW_DIR=../hfb_show # MySQL 容器初始化变量,同时供后端 DSN 使用。 MYSQL_ROOT_PASSWORD=change-root-password diff --git a/deploy/README.md b/deploy/README.md index 2740680..daf91f6 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -3,14 +3,16 @@ 目标访问地址: ```text -https://hfb.221329.cc.cd +https://主站域名 # CADDY_DOMAIN,哈夫币系统 +https://选号网域名 # CADDY_SHOW_DOMAIN(可选),hfb_show 静态选号站 +https://monitor.主站域名 # Beszel 监控 ``` -生产部署使用 Caddy 作为公网入口,只暴露宿主机 `80`、`443` 端口。Caddy 会自动申请和续签 HTTPS 证书,并直接托管前端静态资源(前端 `dist` 在构建 Caddy 镜像时打入);后端、MySQL、Redis、MinIO 均通过 Docker 内网通信。 +生产部署使用 Caddy 作为公网入口,只暴露宿主机 `80`、`443` 端口。Caddy 会自动申请和续签 HTTPS 证书,并直接托管前端静态资源(主站 `dist` 与可选的选号网 `show-dist` 在构建 Caddy 镜像时打入);后端、MySQL、Redis、MinIO 均通过 Docker 内网通信。选号网与主站**共用同一 backend**,浏览器访问选号网域名下的 `/api/*` 由 Caddy 反代到本机 backend,**不会直连数据库**。 部署前请确认: -- 域名 A 记录已解析到服务器公网 IP。 +- 主站域名 A 记录已解析到服务器公网 IP;启用选号网时,选号网域名也要解析到同一 IP。 - 服务器安全组或防火墙已放行 `80/tcp` 和 `443/tcp`。 - 服务器上没有其他服务占用 `80`、`443`。 - 国内服务器如需对外访问,请先确认域名备案和云厂商限制。 @@ -36,6 +38,31 @@ STORAGE_ACCESS_KEY_ID STORAGE_SECRET_ACCESS_KEY ``` +### 同机部署选号网(hfb_show,可选) + +在 `backend/.env` 增加(域名不要带 `https://`): + +```text +CADDY_SHOW_DOMAIN=show.example.com +HFB_SHOW_DIR=../hfb_show +``` + +要求: + +1. 服务器上 `hfb_show` 与 `hfb_sys` 同级目录(或 `HFB_SHOW_DIR` 指向真实路径)。 +2. 部署机可执行 `npm`(脚本会在 build Caddy 前构建选号网 dist)。 +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. 一键部署 @@ -102,15 +129,20 @@ docker compose -f deploy/docker-compose.prod.yml run --rm --no-deps backend \ ## 5. 验证 ```bash -curl https://你的域名/health -curl https://你的域名/api/health +curl https://主站域名/health +curl https://主站域名/api/health +# 若启用了选号网: +curl -I https://选号网域名/ +curl https://选号网域名/api/health +curl "https://选号网域名/api/listings?page=1&page_size=1" ``` 浏览器访问: ```text -https://你的域名 -https://你的域名/你配置的后台入口/login +https://主站域名 +https://主站域名/你配置的后台入口/login +https://选号网域名 # 选号网首页 ``` Caddy 证书和 ACME 账号数据保存在 Docker 卷 `caddy_data`、`caddy_config` 中。不要随意删除这两个卷,否则 Caddy 会重新申请证书,频繁重建可能触发证书签发频率限制。 diff --git a/deploy/caddy/Caddyfile b/deploy/caddy/Caddyfile index 76b0326..691cae2 100644 --- a/deploy/caddy/Caddyfile +++ b/deploy/caddy/Caddyfile @@ -8,6 +8,35 @@ header_up X-Real-IP {remote_host} } +# 公开 API 反代片段:主站与选号网共用。 +(public_api) { + @api_sse { + path /api/chats/events /api/admin/chats/events + } + + handle @api_sse { + reverse_proxy backend:8080 { + flush_interval -1 + import backend_proxy + } + } + + handle /api/* { + request_body { + max_size 11MB + } + reverse_proxy backend:8080 { + import backend_proxy + } + } + + handle /health { + reverse_proxy backend:8080 { + import backend_proxy + } + } +} + {$CADDY_DOMAIN} { encode zstd gzip @@ -20,33 +49,7 @@ Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data: blob:; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'" } - # 聊天 SSE 长连接:关闭缓冲,逐条推送。 - @api_sse { - path /api/chats/events /api/admin/chats/events - } - - handle @api_sse { - reverse_proxy backend:8080 { - flush_interval -1 - import backend_proxy - } - } - - handle /api/* { - # 后端上传上限 10MB,这里前置拦掉超大请求体(留 1MB 余量)。 - request_body { - max_size 11MB - } - reverse_proxy backend:8080 { - import backend_proxy - } - } - - handle /health { - reverse_proxy backend:8080 { - import backend_proxy - } - } + import public_api # 生产后台页面使用构建时配置的隐藏入口,旧 /admin 入口直接返回 404。 @legacy_admin path /admin /admin/* @@ -54,7 +57,7 @@ respond 404 } - # 其余流量返回前端静态资源,未命中的路径交给 SPA 处理。 + # 其余流量返回主站前端静态资源。 handle { root * /srv/dist try_files {path} /index.html @@ -62,6 +65,10 @@ } } +# 选号网(hfb_show):仅当 CADDY_SHOW_DOMAIN 有值时由部署脚本启用 conf.d/show.caddy。 +# 未配置时 conf.d 为空,不影响主站。 +import /etc/caddy/conf.d/*.caddy + # Beszel 自带登录与权限控制;单独子域避免与业务站点的 CSP、路由相互影响。 monitor.{$CADDY_DOMAIN} { encode zstd gzip diff --git a/deploy/caddy/Dockerfile b/deploy/caddy/Dockerfile index 8ce75ba..b058afb 100644 --- a/deploy/caddy/Dockerfile +++ b/deploy/caddy/Dockerfile @@ -1,4 +1,4 @@ -# 第一阶段:构建前端静态资源 +# 第一阶段:构建主站前端静态资源 FROM node:24-alpine AS build ARG VITE_ADMIN_BASE_PATH=/manage-7x9k2p @@ -10,8 +10,11 @@ RUN npm install COPY frontend/ . RUN npm run build -# 第二阶段:Caddy 作为公网入口,同时托管前端 dist +# 第二阶段:Caddy 作为公网入口,托管主站 + 选号网静态资源 FROM caddy:2.10-alpine COPY --from=build /src/dist /srv/dist +# show-dist 由 deploy-prod.sh 在 build 前准备(hfb_show 构建产物或占位页) +COPY deploy/caddy/show-dist /srv/show-dist + EXPOSE 80 443 diff --git a/deploy/caddy/conf.d/.gitkeep b/deploy/caddy/conf.d/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/deploy/caddy/conf.d/00-base.caddy b/deploy/caddy/conf.d/00-base.caddy new file mode 100644 index 0000000..51834fe --- /dev/null +++ b/deploy/caddy/conf.d/00-base.caddy @@ -0,0 +1,4 @@ +# 占位片段:保证 import conf.d/*.caddy 至少匹配一个文件。 +# 选号网站点由 deploy-prod.sh 写入 conf.d/show.caddy。 +(optional_sites_base) { +} diff --git a/deploy/caddy/show-dist/.gitkeep b/deploy/caddy/show-dist/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/deploy/caddy/show-dist/index.html b/deploy/caddy/show-dist/index.html new file mode 100644 index 0000000..4fd9796 --- /dev/null +++ b/deploy/caddy/show-dist/index.html @@ -0,0 +1,32 @@ + + + + + 选号网未构建 + + + + +

选号网静态资源尚未构建

+

+ 请配置 CADDY_SHOW_DOMAINHFB_SHOW_DIR 后重新执行 + ./scripts/deploy-prod.sh,或手动构建 + hfb_show 并将 dist/ 复制到 + deploy/caddy/show-dist/。 +

+ + diff --git a/deploy/caddy/show.caddy.tmpl b/deploy/caddy/show.caddy.tmpl new file mode 100644 index 0000000..b31401e --- /dev/null +++ b/deploy/caddy/show.caddy.tmpl @@ -0,0 +1,35 @@ +# 由 scripts/deploy-prod.sh 根据 CADDY_SHOW_DOMAIN 生成到 conf.d/show.caddy +# 选号网静态页 + 同源 /api 反代到 hfb_sys backend(本机 Docker 网络,非公网绕行) + +__CADDY_SHOW_DOMAIN__ { + encode zstd gzip + + header { + Strict-Transport-Security "max-age=31536000" + X-Content-Type-Options "nosniff" + X-Frame-Options "SAMEORIGIN" + Referrer-Policy "strict-origin-when-cross-origin" + } + + # 与主站共用后端公开接口;浏览器访问选号网域名下的 /api/* 即可,无需跨域。 + handle /api/* { + request_body { + max_size 11MB + } + reverse_proxy backend:8080 { + header_up X-Real-IP {remote_host} + } + } + + handle /health { + reverse_proxy backend:8080 { + header_up X-Real-IP {remote_host} + } + } + + handle { + root * /srv/show-dist + try_files {path} /index.html + file_server + } +} diff --git a/deploy/docker-compose.prod.yml b/deploy/docker-compose.prod.yml index a7dd237..f38f91e 100644 --- a/deploy/docker-compose.prod.yml +++ b/deploy/docker-compose.prod.yml @@ -10,11 +10,14 @@ services: TZ: Asia/Shanghai CADDY_DOMAIN: ${CADDY_DOMAIN:?请先导出 CADDY_DOMAIN,或使用 scripts/deploy-prod.sh 部署} CADDY_EMAIL: ${CADDY_EMAIL:?请先导出 CADDY_EMAIL,或使用 scripts/deploy-prod.sh 部署} + # 选号网域名可选;未配置时 conf.d 为空,不影响主站 + CADDY_SHOW_DOMAIN: ${CADDY_SHOW_DOMAIN:-} ports: - "80:80" - "443:443" volumes: - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro + - ./caddy/conf.d:/etc/caddy/conf.d:ro - caddy_data:/data - caddy_config:/config deploy: diff --git a/scripts/deploy-prod.sh b/scripts/deploy-prod.sh index 0bb5599..a1d8463 100755 --- a/scripts/deploy-prod.sh +++ b/scripts/deploy-prod.sh @@ -75,6 +75,12 @@ show_help() { PROD_READY_TIMEOUT=120 等待服务就绪秒数 +选号网(hfb_show,可选,写在 backend/.env): + CADDY_SHOW_DOMAIN=show.example.com + 选号网独立域名,A 记录指向本机;留空则不启用 + HFB_SHOW_DIR=../hfb_show + 选号网源码目录(相对 hfb_sys 根目录或绝对路径) + 示例: ./scripts/deploy-prod.sh ./scripts/deploy-prod.sh --no-build @@ -209,8 +215,100 @@ compose_user_cmd() { export_caddy_env() { export CADDY_DOMAIN export CADDY_EMAIL + export CADDY_SHOW_DOMAIN CADDY_DOMAIN="$(require_env CADDY_DOMAIN)" CADDY_EMAIL="$(require_env CADDY_EMAIL)" + CADDY_SHOW_DOMAIN="$(env_value CADDY_SHOW_DOMAIN || true)" + export CADDY_SHOW_DOMAIN +} + +# 准备选号网静态资源与 Caddy 站点配置(可选) +prepare_show_site() { + local conf_dir="${ROOT_DIR}/deploy/caddy/conf.d" + local show_dist="${ROOT_DIR}/deploy/caddy/show-dist" + local show_tmpl="${ROOT_DIR}/deploy/caddy/show.caddy.tmpl" + local show_conf="${conf_dir}/show.caddy" + local show_dir show_domain + + mkdir -p "${conf_dir}" + rm -f "${show_conf}" + + show_domain="$(env_value CADDY_SHOW_DOMAIN || true)" + if [[ -z "${show_domain}" ]]; then + log "未配置 CADDY_SHOW_DOMAIN,跳过选号网站点" + # 保留占位页,避免 Caddy 镜像 COPY 目录为空时异常 + if [[ ! -f "${show_dist}/index.html" ]]; then + mkdir -p "${show_dist}" + printf '%s\n' 'show disabled' >"${show_dist}/index.html" + fi + return 0 + fi + + if [[ "${show_domain}" == http://* || "${show_domain}" == https://* || "${show_domain}" == */* ]]; then + log_error "CADDY_SHOW_DOMAIN 只填写域名本身,不要包含 http(s):// 或路径,当前值:${show_domain}" + exit 1 + fi + if [[ "${show_domain}" == "${CADDY_DOMAIN}" ]]; then + log_error "CADDY_SHOW_DOMAIN 不能与 CADDY_DOMAIN 相同" + exit 1 + fi + + show_dir="$(env_value HFB_SHOW_DIR || true)" + if [[ -z "${show_dir}" ]]; then + show_dir="${ROOT_DIR}/../hfb_show" + elif [[ "${show_dir}" != /* ]]; then + show_dir="${ROOT_DIR}/${show_dir}" + fi + + if [[ ! -d "${show_dir}" ]]; then + log_error "已配置 CADDY_SHOW_DOMAIN,但找不到选号网目录:${show_dir}" + log_error "请设置 HFB_SHOW_DIR,或把仓库放在与 hfb_sys 同级的 hfb_show" + exit 1 + fi + if [[ ! -f "${show_dir}/package.json" ]]; then + log_error "选号网目录无效(缺少 package.json):${show_dir}" + exit 1 + fi + + if [[ "${NO_BUILD}" == "1" ]]; then + if [[ ! -f "${show_dist}/index.html" ]] || grep -q '选号网静态资源尚未构建' "${show_dist}/index.html" 2>/dev/null; then + log_error "--no-build 时需要已有 deploy/caddy/show-dist 构建产物,请先完整部署一次" + exit 1 + fi + log_warn "使用已有 show-dist(--no-build)" + else + need_cmd npm + log "构建选号网静态资源:${show_dir}" + ( + cd "${show_dir}" + if [[ -f package-lock.json ]]; then + npm ci + else + npm install + fi + npm run build + ) + if [[ ! -d "${show_dir}/dist" ]]; then + log_error "选号网构建失败:未生成 dist/" + exit 1 + fi + rm -rf "${show_dist}" + mkdir -p "${show_dist}" + cp -a "${show_dir}/dist/." "${show_dist}/" + log_success "选号网 dist 已同步到 deploy/caddy/show-dist" + fi + + if [[ ! -f "${show_tmpl}" ]]; then + log_error "缺少 ${show_tmpl}" + exit 1 + fi + # 域名仅允许主机名字符,避免注入 Caddyfile + if [[ ! "${show_domain}" =~ ^[A-Za-z0-9.-]+$ ]]; then + log_error "CADDY_SHOW_DOMAIN 含非法字符:${show_domain}" + exit 1 + fi + sed "s/__CADDY_SHOW_DOMAIN__/${show_domain}/g" "${show_tmpl}" >"${show_conf}" + log_success "已启用选号网站点:https://${show_domain}" } public_url() { @@ -351,6 +449,8 @@ main() { validate_env export_caddy_env prepare_log_dir + # 选号网需在 build caddy 之前准备 show-dist 与 conf.d + prepare_show_site log "检查 Docker Compose 配置..." compose config >/dev/null @@ -379,7 +479,7 @@ main() { log "构建后端镜像..." compose build backend - log "构建 Caddy 镜像(含前端静态资源)..." + log "构建 Caddy 镜像(含主站 + 选号网静态资源)..." compose build caddy log "启动生产服务..." @@ -405,6 +505,9 @@ main() { fi log_success "部署完成:$(public_url)" + if [[ -n "$(env_value CADDY_SHOW_DOMAIN || true)" ]]; then + log_success "选号网:https://$(env_value CADDY_SHOW_DOMAIN)" + fi log "常用命令:$(compose_user_cmd) logs -f caddy backend" if [[ "${FOLLOW_LOGS}" == "1" ]]; then