diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..2326eab --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,19 @@ +name: deploy +on: + # 手动触发:在 Gitea 仓库 Actions 页面点「Run workflow」才部署, + # 避免每次 push 到 main 的半成品代码被自动上线。 + workflow_dispatch: + +jobs: + deploy: + runs-on: docker + steps: + - name: SSH 到服务器执行上线脚本 + uses: appleboy/ssh-action@v1 + with: + host: ${{ secrets.DEPLOY_HOST }} + username: ${{ secrets.DEPLOY_USER }} + key: ${{ secrets.DEPLOY_KEY }} + script: | + cd /opt/fashion/test + ./deploy.sh diff --git a/DEPLOY.md b/DEPLOY.md index 10c1d5f..5bb5eba 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -1,175 +1,329 @@ -# 部署手册(Docker · 蓝绿零停机) +# 部署手册(1Panel 单机 · Docker 蓝绿 · Gitea Actions) + +> 本文记录**当前实际线上环境**的部署方式。与老模板的差异见文末「与默认模板的差异」。 +> +> 最后更新:2026-10-20 + +--- ## 0. 一图看懂 ``` -浏览器 ──▶ proxy(nginx:80,常驻,发布时从不重建) - ├─ /api ──▶ backend_a 或 backend_b - ├─ / ──▶ web_a 或 web_b - ├─ 18090(仅容器网络)SSR 取数 → 当前活跃后端 - ├─ 127.0.0.1:18091 前端构建期取 SSG - └─ 127.0.0.1:18092 管理后台(SSH 隧道) +浏览器 + │ https://fashion.23cm.cn (443, Let's Encrypt) + ▼ +1Panel OpenResty (80/443,宿主机,已服务 git.23cm.cn 等站点) + │ proxy_pass 127.0.0.1:8080 + ▼ +proxy 容器 (nginx:1.27-alpine,常驻,发布时不重建) + ├─ /api/ ──▶ backend_a 或 backend_b :8090 + ├─ / ──▶ web_a 或 web_b :3000 + ├─ :18090(仅容器网络)SSR 取数 → 当前活跃后端 + ├─ 宿主 127.0.0.1:18091 → 后端 SSG :8091(构建期取数) + └─ 宿主 127.0.0.1:18092 → 后端管理后台 :8092 + │ + ▼ +1Panel PostgreSQL 容器 (5432, 库 fashion, 用户 user_JA83HF) ``` -发布时只重建**空闲槽**,等它健康后改写切换文件并 `nginx -s reload`,在途请求由旧槽跑完。 -镜像以 `RELEASE`(`时间戳-后端提交号`)打标签,**不可变**,历史版本不会被覆盖。 +发布时只重建**空闲槽**,等它 healthy 后改写切换文件并 `nginx -s reload`,在途请求由旧槽跑完。 +镜像以 `RELEASE`(`时间戳-后端提交号`)打标签,不可变,历史版本不被覆盖。 -部署入口在本仓库(后端仓),脚本会自动把前端仓也拉到位。 +部署入口在本仓库(后端仓 `deploy.sh`),脚本会同时同步前端仓。 --- -## 1. 服务器准备(只做一次) +## 1. 服务器现状(只做一次,已就绪) -```bash -# Ubuntu 22.04 / 24.04,2 核 4G 起 -curl -fsSL https://get.docker.com | sh -apt install -y docker-compose-plugin git - -mkdir -p /opt/fashion && cd /opt/fashion -git clone <后端仓库> backend -git clone <前端仓库> test # 必须与 backend 同级 - -cd backend -cp .env.example .env && vi .env -chmod +x deploy.sh init-db.sh -``` - -私有仓库需要配好拉取凭据:SSH deploy key,或 HTTPS token + `git config credential.helper store`。 - ---- - -## 2. 首次部署(只做一次) - -```bash -./init-db.sh /path/to/your.sql # 建库(传入与当前代码匹配的 SQL) -./deploy.sh all # 启动全部服务 -curl http://127.0.0.1/api/health # {"status":"ok"} -``` - -`.env` 必填 6 项: - -| 变量 | 说明 | +| 项 | 值 | | --- | --- | -| `DB_PASSWORD` | 数据库口令,别沿用开发库的 | -| `HASHID_SECRET` | 公开 ID 混淆盐,**上线后不要改**(改了所有旧链接失效) | -| `JWT_SECRET` | 登录令牌密钥 | -| `INGEST_SECRET` | 爬虫上报密钥,须与爬虫端一致 | -| `S4_AK` / `S4_SK` | 对象存储密钥,缺了图片全 404 | +| 主机 | `43.161.253.59`,Ubuntu 24.04,**2 核 / 3.6G 内存 / 1.9G swap** | +| 面板 | 1Panel v2.0.3(已装 Docker 28.3 + Compose v2.38) | +| Gitea | 容器 `1Panel-gitea-1fEW`(EE 24.2.3,Web `:3000` / SSH `:222`,域名 `git.23cm.cn`) | +| Gitea Runner | 容器 `gitea-runner`(act_runner,label `docker`,job 镜像 `node:22-alpine`) | +| 数据库 | 容器 `1Panel-postgresql-4Ndp`(postgres:18.6,`0.0.0.0:5432`,库 `fashion`,用户 `user_JA83HF`) | +| 反向代理 | 容器 `1Panel-openresty-2mkA`(占 80/443,站点配置在 `/opt/1panel/www/conf.d`) | +| 其它 | MySQL / MariaDB / Redis / PHP-FPM×2(供其它站点用,勿动) | + +### 目录 + +``` +/opt/fashion/ +├── backend_v2/ # 后端仓(部署入口,含 deploy.sh / docker-compose.yml / nginx/) +├── frontend_v2/ # 前端仓(Astro) +└── spider/ # 爬虫仓(Go CLI,当前暂不部署) +``` + +三个仓库都以 **`dev`** 分支为准(不是 main)。 + +### 初始化命令(仅重建机器时参考) + +```bash +mkdir -p /opt/fashion && cd /opt/fashion +git clone -b dev https://git.23cm.cn/toom1996/backend_v2.git backend_v2 +git clone -b dev https://git.23cm.cn/toom1996/frontend_v2.git frontend_v2 +git clone -b dev https://git.23cm.cn/toom1996/spider.git spider + +cd backend_v2 +cp .env.example .env # 按第 3 节填写 +chmod +x deploy.sh +``` + +> 数据库已存在且有数据,**不需要** `init-db.sh` / 导入 dump。 --- -## 3. 日常发布 +## 2. 关键配置(已改好,重建机器时照抄) -```bash -./deploy.sh # 拉 main + 只更新后端(默认) -./deploy.sh all # 拉 main + 前后端都更新 -./deploy.sh web # 只更新前端 -./deploy.sh all --no-pull # 不拉代码,用服务器现有代码构建 -GIT_BRANCH=dev ./deploy.sh all # 临时指定分支(优先于 .env) +### 2.1 docker-compose.yml + +相对上游模板做了 3 处适配: + +1. **删掉自带 `db` 服务**:改用 1Panel 现有 Postgres(不再另起 pgvector)。 +2. **后端连库走宿主网关**: + ```yaml + DB_HOST: host.docker.internal + # backend-base 下: + extra_hosts: + - "host.docker.internal:host-gateway" + ``` +3. **proxy 只绑宿主回环**(80/443 已被 OpenResty 占用): + ```yaml + ports: + - "127.0.0.1:${HTTP_PORT:-8080}:80" + - "127.0.0.1:18091:18091" + - "127.0.0.1:18092:18092" + ``` + +### 2.2 nginx/default.conf(重要) + +容器内的 18091 / 18092 **必须监听 0.0.0.0**,不能监听 `127.0.0.1`: + +```nginx +listen 18091; # 容器内回环监听会导致 docker 端口发布不可达 +listen 18092; ``` -**拉代码**(`.env` 的 `GIT_BRANCH`,默认 `main`): +原因:Docker 端口发布是转发到**容器网卡**,不是容器回环。监听 `127.0.0.1` 时, +宿主机 `127.0.0.1:18091` 会连接被重置,导致**前端构建期 SSG 取数为空、首页无数据**。 +宿主机侧仍只发布到 `127.0.0.1`,不会外泄。 -- 后端仓 `.` 和前端仓 `$WEB_DIR`(默认 `../test`)都会 `fetch` → 必要时 `checkout` → `pull --ff-only` -- **不能快进就报错退出**,不会硬覆盖(有本地改动时会提示 `reset --hard` 命令) -- 每次打印实际提交:`后端:main @ a1b2c3d fix: xxx` +### 2.3 deploy.sh(相对模板新增) -**发布过程**: +- 首次部署顺序修复:先 `render_active` 再 `--no-deps` 起代理(否则 map 为空 nginx 起不来)。 +- 新增 `--no-cache`:强制前端不用构建缓存(见第 5 节 SSG)。 + +--- + +## 3. `.env`(敏感值只放服务器,600 权限) + +路径:`/opt/fashion/backend_v2/.env`(已 gitignore,**绝不入库**)。 + +```ini +# 数据库(复用 1Panel 现有 Postgres) +DB_USER=user_JA83HF +DB_NAME=fashion +DB_PASSWORD=<真实口令,见下方说明> + +# 密钥 +HASHID_SECRET= # 留空=内置默认盐,必须与爬虫 ingest.hashid_secret 一致 +JWT_SECRET=<随机长串> +INGEST_SECRET=<与爬虫 ingest.secret 一致> +SSG_TOKEN=<随机串,构建期取 SSG 用> + +# 对象存储(当前关闭;现有图片走 CDN 公开桶,仍可正常显示) +S4_ENABLED=false +S4_AK=disabled +S4_SK=disabled + +# 目录 / 端口 +WEB_DIR=../frontend_v2 +HTTP_PORT=8080 +CORS_ALLOW_ORIGINS=https://fashion.23cm.cn,https://test.23cm.cn + +# 其它 +GIT_BRANCH=dev +GOPROXY=https://goproxy.cn,direct +``` + +> **DB 密码从哪来**:以 Postgres 容器的环境变量为准,而不是旧 `config.yml`: +> ```bash +> docker exec 1Panel-postgresql-4Ndp printenv POSTGRES_PASSWORD +> ``` +> 旧 `/opt/fashionapi/config.yml` 里的密码被行尾 `#` 注释截断过,是错的(28P01 的根因)。 + +--- + +## 4. 数据库 + +- 复用 1Panel 的 `postgres:18.6`,库 `fashion`,用户 `user_JA83HF`。 +- 已开启 pgvector(`vector` 扩展)。 +- 主要表:`brands` / `brand_runways` / `brand_runway_images` / `street_snaps` / `street_snap_images` / `users` / `favorites` / `histories` / `ingest_jobs` … +- 后端不再自动迁移,表结构变更走 `cmd/dbtool`。 + +--- + +## 5. 部署方式 A:Gitea Actions(手动触发) + +三个仓库的 `.gitea/workflows/deploy.yml` 都是 `workflow_dispatch`,**不会 push 自动上线**。 + +### 使用 + +1. 打开 `https://git.23cm.cn/toom1996/` +2. 顶部 **Actions → deploy → Run workflow** +3. 分支选 **`dev`** → Run + +各仓行为: + +| 仓库 | 实际执行 | +| --- | --- | +| `backend_v2` | `cd /opt/fashion/backend_v2 && ./deploy.sh backend` | +| `frontend_v2` | `cd /opt/fashion/backend_v2 && ./deploy.sh web --no-cache` | +| `spider` | `git pull` + `go build -o bin/spider .` | + +### 原理与 Secrets + +- Runner 与服务器同机,job 容器用 `node:22-alpine`,通过 SSH 连 `127.0.0.1` 执行部署。 +- 仓库 Secrets:`DEPLOY_HOST=127.0.0.1`、`DEPLOY_USER=root`、`DEPLOY_KEY_B64`(SSH 私钥 base64)。 +- 注意:**不要用公网 IP 43.161.253.59 做 DEPLOY_HOST**,云主机常拦截「访问自己公网 IP」的 hairpin,会卡住。 + +--- + +## 6. 部署方式 B:手动 SSH + +```bash +ssh -i ~/.ssh/id_rsa_hongkong root@43.161.253.59 +cd /opt/fashion/backend_v2 + +./deploy.sh # 只更新后端(默认) +./deploy.sh web # 只更新前端 +./deploy.sh all # 前后端都更新 +./deploy.sh all --no-pull +./deploy.sh web --no-cache # 强制重建前端(刷新首页 SSG 数据) +./deploy.sh --rollback # 秒级回滚到上一槽 +./deploy.sh --prune # 清理旧镜像(默认保留最近 5 个) +GIT_BRANCH=dev ./deploy.sh all +``` + +**发布流程**: | 步 | 动作 | 用户感受 | | --- | --- | --- | -| 1 | 生成 `RELEASE`,拉代码 | 无感 | +| 1 | 生成 `RELEASE`,`git pull --ff-only`(后端 + 前端) | 无感 | | 2 | `docker compose build` 构建 `fashion-*:$RELEASE` | 无感(旧槽在跑) | | 3 | 重建空闲槽 | 无感 | -| 4 | 轮询 health,未通过则**直接退出** | 旧槽继续服务 | +| 4 | 轮询 health,未通过则退出 | 旧槽继续服务 | | 5 | 改切换文件 → `nginx -t` → `nginx -s reload` | 在途请求不丢 | | 6 | 写 `.deploy/releases.log` | — | --- -## 4. 回滚 +## 7. 域名与 HTTPS + +| 域名 | 指向 | 说明 | +| --- | --- | --- | +| `fashion.23cm.cn` | 127.0.0.1:8080 | **正式入口**,Let's Encrypt 证书,80/443 都通 | +| `test.23cm.cn` | 127.0.0.1:8080 | 同一应用;原为旧静态站,旧配置备份在 `test.23cm.cn.conf.bak` | + +- OpenResty 站点配置:`/opt/1panel/www/conf.d/.conf` +- 证书目录:`/opt/1panel/www/sites//ssl/{fullchain.pem,privkey.pem}` +- `fashion.23cm.cn` 证书由 **certbot(webroot)** 签发,自动续期;续期 hook: + `/etc/letsencrypt/renewal-hooks/deploy/fashion-23cm.sh`(复制证书 + `nginx -s reload`) +- 验证:`curl -I https://fashion.23cm.cn/` + +> 注意:1Panel 面板里的证书是自签(`CN=localhost`),与 certbot 那张不是同一张;面板「证书」列表里看不到 fashion 的证书属正常。 + +还原 `test.23cm.cn` 旧静态站: ```bash -./deploy.sh --rollback -# 回滚 backend:b → a(该槽镜像 fashion-backend:20261018-2210-9f3e1a2) -``` - -不动代码、不重建容器,只改 nginx 指向 + reload,秒级生效。 -前提是该槽容器还在(正常交替发布时,非活跃槽就是上一版)。 - ---- - -## 5. 版本与镜像管理 - -```bash -cat .deploy/releases.log # 发布历史 -docker inspect -f '{{.Config.Image}}' $(docker compose ps -q backend_a) # 线上版本 -./deploy.sh --prune # 清理旧镜像 -KEEP_IMAGES=3 ./deploy.sh --prune # 只留 3 个 -``` - -镜像标签形如 `fashion-backend:20261019-153012-a1b2c3d`(时间 + 提交号),不会被自动清理。 -`latest` 只是手动执行 compose 命令的别名,`deploy.sh` 从不依赖它。 - ---- - -## 6. 必须记住的规则 - -1. **默认发布 `main`,日常开发在 `dev`** —— 直接 `./deploy.sh all` 会部署 main 上的旧代码。要么先 merge,要么 `GIT_BRANCH=dev ./deploy.sh all`。 -2. **改表要向后兼容** —— 切换的几十秒里新旧代码同时在跑。加字段/加表可以;改类型、删字段要拆两次发布。 -3. **`HASHID_SECRET` 上线后别动**。 -4. **镜像会占磁盘**(前端约 250MB/版),每 5 版跑一次 `./deploy.sh --prune`。 - ---- - -## 7. 管理后台 - -```bash -ssh -L 18092:127.0.0.1:18092 user@server -# 本机浏览器打开 http://localhost:18092/admin +cp /opt/1panel/www/conf.d/test.23cm.cn.conf.bak /opt/1panel/www/conf.d/test.23cm.cn.conf +docker exec 1Panel-openresty-2mkA nginx -s reload ``` --- -## 8. 域名 / HTTPS +## 8. 爬虫(当前暂不部署) -前端只监听 80,二选一: +- 已构建:`/opt/fashion/spider/bin/spider` +- 生产配置:`/opt/fashion/spider/config.prod.yml`(ingest 指向 `http://127.0.0.1:18092`) +- 是 **CLI**,不是守护进程,跑完即退出: -- **宿主机 nginx / 宝塔**:`.env` 里 `HTTP_PORT=8080`,外层反代到 `127.0.0.1:8080`,证书在外层做 -- **Caddy(自动证书)**:同 compose 加 caddy 容器,`reverse_proxy 127.0.0.1:80` +```bash +cd /opt/fashion/spider +./bin/spider -c config.prod.yml vogue -b 1 +./bin/spider -c config.prod.yml theimpression -n 5 +``` + +- 若要定时执行,可加 systemd timer(需要时再配)。 --- ## 9. 备份 +数据库在 1Panel Postgres 容器里,备份命令: + ```bash -docker compose exec -T db pg_dump -U fashion fashion | gzip > backups/db-$(date +%F).sql.gz +docker exec 1Panel-postgresql-4Ndp pg_dump -U user_JA83HF fashion \ + | gzip > /opt/fashion/backups/db-$(date +%F).sql.gz ``` -每天 4 点自动备份并保留 7 天: +每日 4 点、保留 7 天(示例): -``` -0 4 * * * cd /opt/fashion/backend && docker compose exec -T db pg_dump -U fashion fashion | gzip > backups/db-$(date +\%F).sql.gz && find backups -name 'db-*.sql.gz' -mtime +7 -delete +```cron +0 4 * * * docker exec 1Panel-postgresql-4Ndp pg_dump -U user_JA83HF fashion | gzip > /opt/fashion/backups/db-$(date +\%F).sql.gz && find /opt/fashion/backups -name 'db-*.sql.gz' -mtime +7 -delete ``` --- -## 10. 排障 +## 10. 排障(真实案例) ```bash -docker compose ps # 各容器状态与健康 -docker compose logs -f proxy # 切换失败先看这里 -docker compose logs -f backend_a +cd /opt/fashion/backend_v2 +docker compose ps +docker compose logs --tail 100 proxy +docker compose logs --tail 100 backend_a docker compose logs --tail 100 web_b ``` -| 现象 | 原因 | +| 现象 | 原因 / 处理 | | --- | --- | +| 连库报 `28P01 password authentication failed` | 密码不对。以容器 `POSTGRES_PASSWORD` 为准;旧 config 里的可能被 `#` 截断 | +| 首页只有 `Loading`、没有图集 | 构建期 SSG 取数失败。确认 nginx 内部 `listen 18091`(不是 `127.0.0.1:18091`)、proxy 在跑、`curl 127.0.0.1:18091/api/internal/ssg/home/runway?token=…` 有数据;再 `./deploy.sh web --no-cache` | +| 改了内容但首页没变 | 首页是构建期 SSG,需重新部署前端(workflow 已带 `--no-cache`) | +| `nginx: bind() to 0.0.0.0:18091 failed (Address in use)` | 旧进程还占着回环端口,`docker compose restart proxy` | | 全站接口 404 | `nginx/default.conf` 的 `proxy_pass` 带了 URI/斜杠(会剥掉 `/api` 前缀) | | 详情页 500 | 前端 `BASE_API_SSR` 必须是 `http://proxy:18090`(容器里的 localhost 是它自己) | -| 图片全 404 | `S4_AK/S4_SK` 没填或填错 | -| 前端构建失败 | 构建期要连 18091 取 SSG:确认 `proxy` 在跑、`backend_a` healthy | -| 发布后仍是旧版本 | 分支不对(默认 `main`,日常在 `dev`);或 `docker compose logs proxy` 里 reload 报错 | -| 回滚提示"槽不可用" | 该槽被更早的发布重建过,回滚只能退一格 | -| 登录后跳回登录页 | `JWT_SECRET` 改过,旧 token 失效 | -| 磁盘涨得快 | `./deploy.sh --prune` | +| 图片全 404 | S4 关闭但现有图片走 CDN 正常;新入库图片需补 `S4_AK/S4_SK` 再开 `S4_ENABLED` | +| 发布后仍是旧版本 | 分支不对(用 `dev`);或看 `docker compose logs proxy` 的 reload 报错 | +| 回滚提示「槽不可用」 | 该槽被更早发布重建过,回滚只能退一格 | +| 磁盘涨得快 | `./deploy.sh --prune`;`docker builder prune -f` | + +--- + +## 11. 性能说明(当前瓶颈) + +服务器 **2 核 / 3.6G**,且已常驻 Gitea、Postgres、MySQL、MariaDB、Redis、PHP×2、OpenResty、两个 Go 后端、Node 前端。体检时 **swap 1.9G 已用 1.7G**,构建期容易换页,`npm ci + astro build` 明显慢。 + +建议(按性价比): + +1. **长期(推荐)**:构建移到本地 **WSL2 + Docker Engine**,推送到 Gitea 容器仓库,服务器只 `pull + up`。 + - 不要把构建目录放在 `/mnt/d/...`(9p 极慢),放 WSL 自己的 `~/project`。 +2. **短期(零成本)**: + - 前端用 `--no-cache` 只用于刷新 SSG;进一步可改 `CACHEBUST` 构建参数,只重跑 `npm run build`,`npm ci` 走缓存。 + - Dockerfile 加 BuildKit cache mount(npm/go 依赖缓存)。 + - `docker builder prune -f` 回收构建缓存;`./deploy.sh --prune` 控制镜像数。 + - 可再加 4G swap 降低 OOM 风险。 + +--- + +## 12. 与默认模板的差异(为什么这么改) + +| 模板假设 | 实际 | +| --- | --- | +| 自带 `db` 服务 + `init-db.sh` | 复用 1Panel 现有 Postgres,库已有数据 | +| 目录 `backend` / `test` | `backend_v2` / `frontend_v2` | +| 默认分支 `main` | `dev` | +| 外层 Caddy / 宿主机 nginx 空着 | 1Panel OpenResty 已占 80/443,应用 proxy 改绑 `127.0.0.1:8080` | +| `S4_AK/S4_SK` 必填 | 当前 `S4_ENABLED=false`,图片走 CDN 公开桶 | +| push 自动部署 | 改为 `workflow_dispatch` 手动触发 | +| nginx 内部 `listen 127.0.0.1:18091` | 容器内必须 `listen 18091`(0.0.0.0),否则端口发布不可达 | diff --git a/deploy.sh b/deploy.sh index 99d9655..425fb2a 100644 --- a/deploy.sh +++ b/deploy.sh @@ -6,6 +6,7 @@ # ./deploy.sh web 只更新前端 # ./deploy.sh --rollback 切回上一个槽(不动代码,秒级) # ./deploy.sh all --no-pull 用服务器上现有代码构建 +# ./deploy.sh web --no-cache 强制不用构建缓存(前端 SSG 依赖后端数据,数据变了要强制重建) # ./deploy.sh --prune 只清理旧镜像(保留最近 KEEP_IMAGES 个,默认 5) # GIT_BRANCH=dev ./deploy.sh all 临时指定分支(优先于 .env) # @@ -16,14 +17,16 @@ cd "$(dirname "$0")" TARGET="backend" NO_PULL=0 +NO_CACHE=0 PRUNE_ONLY=0 for arg in "$@"; do case "$arg" in backend|web|all) TARGET="$arg" ;; --rollback) TARGET="rollback" ;; --no-pull) NO_PULL=1 ;; + --no-cache) NO_CACHE=1 ;; --prune) PRUNE_ONLY=1 ;; - *) echo "用法: ./deploy.sh [backend|web|all|--rollback|--prune] [--no-pull]"; exit 1 ;; + *) echo "用法: ./deploy.sh [backend|web|all|--rollback|--prune] [--no-pull] [--no-cache]"; exit 1 ;; esac done @@ -148,7 +151,9 @@ roll() { # $1=component(backend|web) old=$(cur_slot "$comp"); idle=$(other_slot "$old") echo "==> $comp:活跃槽 $old,更新空闲槽 $idle(镜像 $RELEASE)" - docker compose build "${comp}_a" >/dev/null + local build_flag="" + [ "$NO_CACHE" = "1" ] && build_flag="--no-cache" + docker compose build $build_flag "${comp}_a" >/dev/null # latest 只是给手动执行 compose 命令用的便利别名,deploy 自己从不依赖它 docker tag "fashion-${comp}:${RELEASE}" "fashion-${comp}:latest" 2>/dev/null || true @@ -214,12 +219,14 @@ else export RELEASE="${RELEASE:-latest}" fi -# 首次部署先让代理起来(前端构建期要用 18091 取 SSG 数据) -if ! healthy proxy; then - echo "==> 启动代理(首次会一并拉起 db 与 a 槽)" - docker compose up -d proxy -fi +# 首次部署:先写切换点,再只启动代理。 +# - render_active 必须先于 proxy 启动,否则 include 的 map 为空,nginx 会因未知变量启动失败。 +# - --no-deps 避免首次起代理时顺带构建前端(此时 18091 尚未就绪,SSG 取数会失败)。 render_active +if ! healthy proxy; then + echo "==> 启动代理(首次只起代理,槽位由 roll 流程构建)" + docker compose up -d --no-deps proxy +fi if [ "$TARGET" = "rollback" ]; then do_rollback diff --git a/docker-compose.yml b/docker-compose.yml index 8cbfeee..60ce1c4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,7 +26,7 @@ x-backend-base: &backend-base BACKSTAGE_PORT: 8092 GIN_MODE: release - HASHID_SECRET: ${HASHID_SECRET:?HASHID_SECRET 未设置:复制 .env.example 为 .env 并填写} + HASHID_SECRET: ${HASHID_SECRET:-} JWT_SECRET: ${JWT_SECRET:?JWT_SECRET 未设置} SSG_TOKEN: ${SSG_TOKEN:-} CORS_ALLOW_ORIGINS: ${CORS_ALLOW_ORIGINS:-https://xisoa.com,https://www.xisoa.com} @@ -35,7 +35,8 @@ x-backend-base: &backend-base RATE_LIMIT_RPS: ${RATE_LIMIT_RPS:-20} RATE_LIMIT_BURST: ${RATE_LIMIT_BURST:-40} - DB_HOST: db + # 复用 1Panel 现有 Postgres(宿主 5432)。host-gateway 让容器经宿主访问它。 + DB_HOST: host.docker.internal DB_PORT: 5432 DB_USER: ${DB_USER:-fashion} DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD 未设置} @@ -60,9 +61,8 @@ x-backend-base: &backend-base UPLOAD_DIR: /app/uploads volumes: - uploads:/app/uploads - depends_on: - db: - condition: service_healthy + extra_hosts: + - "host.docker.internal:host-gateway" healthcheck: test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8090/api/health"] interval: 10s @@ -84,29 +84,15 @@ x-web-base: &web-base start_period: 15s services: - # ---- 数据库:PostgreSQL 16 + pgvector ---- - db: - image: pgvector/pgvector:pg16 - restart: unless-stopped - environment: - POSTGRES_USER: ${DB_USER:-fashion} - POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD 未设置} - POSTGRES_DB: ${DB_NAME:-fashion} - TZ: Asia/Shanghai - volumes: - - db_data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-fashion} -d ${DB_NAME:-fashion}"] - interval: 10s - timeout: 5s - retries: 10 + # ---- 数据库:复用 1Panel 现有 Postgres(连接信息见 .env 的 DB_HOST/DB_PORT/DB_USER/DB_PASSWORD)---- # ---- 常驻代理:整个发布过程中不重建,切换只靠 reload ---- proxy: image: nginx:1.27-alpine restart: unless-stopped ports: - - "${HTTP_PORT:-80}:80" + # 只绑宿主回环:80/443 由 1Panel OpenResty 占用,外层反代到 127.0.0.1:${HTTP_PORT} + - "127.0.0.1:${HTTP_PORT:-8080}:80" - "127.0.0.1:18091:18091" # 前端构建期取 SSG - "127.0.0.1:18092:18092" # 管理后台(SSH 隧道) volumes: @@ -151,5 +137,4 @@ services: <<: *web-base volumes: - db_data: uploads: diff --git a/nginx/default.conf b/nginx/default.conf index 1ee7206..8d587a9 100644 --- a/nginx/default.conf +++ b/nginx/default.conf @@ -71,7 +71,7 @@ server { # 内部:前端构建期取 SSG 数据(只绑宿主回环,前端镜像 build 时用 host 网络访问) server { - listen 127.0.0.1:18091; + listen 18091; # 容器内必须监听 0.0.0.0,否则 docker 端口发布到容器网卡时连不上;宿主机只发布到 127.0.0.1 access_log off; location / { proxy_pass http://$ssg_up; @@ -81,7 +81,7 @@ server { # 内部:管理后台(只绑宿主回环;远程访问用 SSH 隧道到 18092) server { - listen 127.0.0.1:18092; + listen 18092; # 同上:容器内回环监听会导致 docker 端口发布不可达(管理后台/爬虫入库走此口) location / { proxy_pass http://$admin_up; proxy_set_header Host $host;