From e0871d0b7a0d55233f6e68cb93ea7e5374fdaf5a Mon Sep 17 00:00:00 2001 From: Wang Zhuoxuan Date: Wed, 22 Apr 2026 03:04:26 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E6=96=B9=E6=A1=88=E6=96=87=E6=A1=A3=E4=B8=8E=20Gitea=20Actions?= =?UTF-8?q?=20CI=20=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 CI 部署指南,包含 Runner 基础设施适配、HTTPS 证书实操经验、故障排查 - 新增 deploy.yml workflow,基于 Act Runner volume 挂载实现原子部署 --- .gitea/workflows/deploy.yml | 35 +++ docs/ci-deployment-guide.md | 535 ++++++++++++++++++++++++++++++++++++ 2 files changed, 570 insertions(+) create mode 100644 .gitea/workflows/deploy.yml create mode 100644 docs/ci-deployment-guide.md diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..3bae3ac --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,35 @@ +name: Build & Deploy Admin + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Lint & Type check + run: | + bun run lint + bun run typecheck + + - name: Build + run: bun run build + env: + VITE_API_BASE_URL: "" + + - name: Deploy to server + run: | + # 原子替换:通过 volume 挂载直接操作宿主机目录 + mkdir -p /opt/duoqi-admin/dist-new + cp -r dist/* /opt/duoqi-admin/dist-new/ + # 备份当前版本 + 切换(mv 在同一文件系统上是原子操作) + mv /opt/duoqi-admin/dist /opt/duoqi-admin/dist-old || true + mv /opt/duoqi-admin/dist-new /opt/duoqi-admin/dist + rm -rf /opt/duoqi-admin/dist-old diff --git a/docs/ci-deployment-guide.md b/docs/ci-deployment-guide.md new file mode 100644 index 0000000..c906b93 --- /dev/null +++ b/docs/ci-deployment-guide.md @@ -0,0 +1,535 @@ +# Duoqi Admin 部署方案 + +> 基于 duoqi-api 现有 Gitea + 阿里云轻量应用服务器基础设施,零额外内存的纯前端部署方案 + +## 目录 + +- [架构概览](#架构概览) +- [资源规划](#资源规划) +- [服务器端准备](#服务器端准备) +- [Nginx 配置](#nginx-配置) +- [HTTPS 证书](#https-证书) +- [DNS 配置](#dns-配置) +- [前端环境变量](#前端环境变量) +- [CI/CD 流程](#cicd-流程) +- [部署验证](#部署验证) +- [API 代理方案对比](#api-代理方案对比) +- [服务器端目录结构](#服务器端目录结构) +- [运维管理](#运维管理) +- [故障排查](#故障排查) + +--- + +## 架构概览 + +### 核心设计 + +duoqi-admin 是纯前端 SPA,不需要 Docker,直接由 Nginx 托管静态文件。零额外内存开销,复用 duoqi-api 已有的 Gitea + Nginx + Act Runner 基础设施。 + +### 为什么不套 Docker + +当前服务器 2GB 内存已用约 1.1GB。静态站点用 Docker 是浪费——Nginx 本身已在运行,直接加一个 `server` 块托管 `dist/` 即可,内存增量约等于 0。这也符合 duoqi-api 部署文档中"每 MB 都有意义"的设计原则。 + +### 架构图 + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 阿里云轻量应用服务器 (2C/2G) │ +│ │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ Nginx 反向代理 (:80/:443) │ │ +│ │ ┌────────────┐ ┌──────────────┐ ┌────────┐ ┌──────────────┐ │ │ +│ │ │api.duoqi.me│ │test-api. │ │git. │ │admin.duoqi.me│ │ │ +│ │ │→ :3000 prod│ │duoqi.me │ │duoqi.me│ │→ 静态文件 │ │ │ +│ │ │ │ │→ :3001 test │ │ │ │/opt/duoqi- │ │ │ +│ │ │ │ │ │ │ │ │admin/dist/ │ │ │ +│ │ └────────────┘ └──────────────┘ └────────┘ └──────────────┘ │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │ +│ │ duoqi-api │ │ duoqi-api │ │ Gitea + Act Runner │ │ +│ │ (prod) │ │ (test) │ │ + duoqi-admin 仓库 │ │ +│ │ :3000 │ │ :3001 │ │ CI/CD: build → copy │ │ +│ └─────────────┘ └──────────────┘ └──────────────────────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ /opt/duoqi-admin/dist/ ← 构建产物(纯静态文件) │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 资源规划 + +### 内存增量:约 0MB + +| 组件 | 新增占用 | 说明 | +|------|----------|------| +| Nginx server 块 | ~0MB | Nginx 已运行,静态文件不占额外进程内存 | +| dist/ 磁盘 | ~5-10MB | Vite 构建产物极小 | +| CI/CD | ~0MB | 复用已有 Act Runner | + +### DNS 记录 + +| 子域名 | 类型 | 值 | +|--------|------|----| +| admin.duoqi.me | A | 服务器公网 IP | + +--- + +## 服务器端准备 + +```bash +# 创建部署目录 +mkdir -p /opt/duoqi-admin/dist + +# 设置初始占位页面(首次部署前) +cat > /opt/duoqi-admin/dist/index.html << 'EOF' + +

Duoqi Admin - Deploying...

+EOF +``` + +--- + +## Nginx 配置 + +创建配置文件 `/etc/nginx/conf.d/duoqi-admin.conf`: + +```nginx +server { + listen 80; + server_name admin.duoqi.me; + + # 限制访问 IP(仅管理员网络,上线后取消注释) + # allow 123.56.78.90; + # deny all; + + root /opt/duoqi-admin/dist; + index index.html; + + # SPA 路由回退 — 所有未匹配路径返回 index.html + location / { + try_files $uri $uri/ /index.html; + } + + # API 代理 — 转发到后端生产环境 + location /admin/ { + proxy_pass http://localhost:3000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # 静态资源缓存(Vite 带 hash 的文件) + location /assets/ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # 安全头 + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; +} +``` + +验证并生效: + +```bash +nginx -t +systemctl reload nginx +``` + +> **`try_files` 说明**:React Router 的前端路由(如 `/questions`、`/categories`)在浏览器刷新时会向服务器请求这些路径。没有 `try_files` 回退,Nginx 会返回 404。有了它,所有未知路径都回退到 `index.html`,由前端 JS 路由接管。`/assets/` 目录下的文件由 Vite 自动添加 content hash(如 `assets/index-DK2R8j.css`),可以安全设置 1 年强缓存。 + +--- + +## HTTPS 证书 + +将 admin.duoqi.me 加入已有证书(注意 `--expand` 必须列出所有域名): + +```bash +sudo certbot --nginx --expand \ + -d api.duoqi.me -d test-api.duoqi.me -d git.duoqi.me -d admin.duoqi.me + +# 验证自动续期 +certbot renew --dry-run +``` + +### `--expand` 注意事项 + +> **来自 duoqi-api 部署实操的经验**: + +1. **DNS 必须先生效**:`--expand` 要求新域名的 DNS 已指向服务器且可公网访问,否则 certbot 的 HTTP-01 验证会失败。 +2. **必须列出所有域名**:`--expand` 是替换整张证书,不是追加。 + +```bash +# ❌ 错误:只写新增域名,会导致原有域名被移除! +sudo certbot --nginx --expand -d admin.duoqi.me + +# ✅ 正确:列出所有域名(原有 + 新增) +sudo certbot --nginx --expand \ + -d api.duoqi.me -d test-api.duoqi.me -d git.duoqi.me -d admin.duoqi.me +``` + +3. **备案问题**:阿里云服务器 + 未备案域名,HTTP 请求可能被运营商层拦截(返回 403,`Server: Beaver`),导致 certbot 验证失败。需要先完成 ICP 备案,或暂时使用其他验证方式(DNS-01)。 + +--- + +## DNS 配置 + +在域名管理后台添加 A 记录: + +| 主机记录 | 记录类型 | 记录值 | +|----------|----------|--------| +| admin | A | 服务器公网 IP | + +--- + +## 前端环境变量 + +构建时需指向生产 API。推荐使用 Nginx 代理方案,构建时 `VITE_API_BASE_URL` 留空: + +```env +# .env.production +VITE_API_BASE_URL= +``` + +这样 `ky` 的请求会发到 `admin.duoqi.me/admin/*`,由 Nginx 转发到 `localhost:3000`,无跨域问题。 + +如果使用直连方案(不推荐): + +```env +# .env.production(直连方案) +VITE_API_BASE_URL=https://api.duoqi.me +``` + +--- + +## CI/CD 流程 + +### 前置条件:Runner 基础设施 + +> duoqi-admin 复用 duoqi-api 已搭建的 Gitea + Act Runner 基础设施。 +> 以下配置需要在 Runner 端**一次性完成**,不需要每次部署时重复操作。 + +#### 1. Runner 配置文件更新 + +Runner 的 job 容器需要能访问 `/opt/duoqi-admin` 目录。编辑 `/opt/act-runner/config.yaml`: + +```yaml +container: + # 容器使用宿主机网络(继承自 duoqi-api 配置) + network: "host" + # 在原有挂载基础上,增加 admin 目录 + options: "-v /opt/duoqi-api:/opt/duoqi-api -v /opt/duoqi-admin:/opt/duoqi-admin" + # 在原有白名单基础上,增加 admin 目录 + valid_volumes: + - /opt/duoqi-api + - /opt/duoqi-admin + # 不强制每次拉取镜像(国内网络下减少失败风险) + force_pull: false +``` + +修改后重启 Runner: + +```bash +systemctl restart act-runner +``` + +> **为什么不用 SSH/rsync**:Act Runner 的 job 容器通过 `network: host` + volume 挂载直接访问宿主机文件系统,无需 SSH 跳转。SSH 方式在容器内需要额外配置密钥,反而更复杂。 + +#### 2. 自定义 Runner 镜像 + +Runner 使用自定义镜像 `duoqi-runner:bun-git`(基于 `oven/bun` + git + docker CLI)。该镜像: + +- **包含**:bun、git(checkout 需要)、docker CLI +- **不包含**:curl、rsync、ssh 客户端 +- **固定版本**:Bun 1.3,确保 CI 环境可复现 + +因此 CI 中不能使用 `curl`、`rsync`、`ssh` 等命令,需用 `bun`/`cp`/`mv` 替代。 + +#### 3. github_mirror 配置 + +Runner 已配置 `github_mirror: 'https://gitea.com'`,从 gitea.com 镜像拉取 GitHub Actions(如 `actions/checkout`),解决国内无法访问 GitHub 的问题。如果 Action 拉取失败: + +```bash +# 清除缓存的 action(修复损坏的缓存) +rm -rf /root/.cache/act/ +systemctl restart act-runner +``` + +### Pipeline 流程 + +``` +推送代码到 Gitea (main 分支) + │ + ▼ +┌───────────────┐ +│ quality │ ← Lint + 类型检查 +│ (自动触发) │ +└───────┬───────┘ + │ + ▼ +┌───────────────┐ +│ build │ ← bun run build → dist/ +│ (自动触发) │ +└───────┬───────┘ + │ + ▼ +┌───────────────┐ +│ deploy │ ← 原子替换 dist/ 目录 +│ (手动触发) │ +└───────────────┘ +``` + +### Gitea Actions 配置 + +创建 `.gitea/workflows/deploy.yml`: + +```yaml +name: Build & Deploy Admin + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Lint & Type check + run: | + bun run lint + bun run typecheck + + - name: Build + run: bun run build + env: + VITE_API_BASE_URL: "" + + - name: Deploy to server + run: | + # 原子替换:通过 volume 挂载直接操作宿主机目录 + mkdir -p /opt/duoqi-admin/dist-new + cp -r dist/* /opt/duoqi-admin/dist-new/ + # 备份当前版本 + 切换(mv 在同一文件系统上是原子操作) + mv /opt/duoqi-admin/dist /opt/duoqi-admin/dist-old || true + mv /opt/duoqi-admin/dist-new /opt/duoqi-admin/dist + rm -rf /opt/duoqi-admin/dist-old +``` + +> **原子替换说明**:直接 `rm -rf dist && cp` 在中间有个时间窗口,用户会看到 404 或不完整文件。用 `dist-new → mv swap → rm old` 三步替换,`mv` 是原子操作(同一文件系统),切换瞬间完成,零停机。 +> +> **与 duoqi-api 的区别**:duoqi-api 通过 Docker 容器部署,CI 中需要 `docker build`/`docker compose`。duoqi-admin 是纯静态文件,CI 中只需 `cp` + `mv` 操作,更轻量。 + +--- + +## 部署验证 + +### 构建验证 + +```bash +# 本地构建 +bun run build + +# 本地预览构建产物 +bun run preview +``` + +### 线上验证 + +```bash +# 检查首页可访问 +curl -I https://admin.duoqi.me + +# 检查静态资源缓存头 +curl -I https://admin.duoqi.me/assets/ + +# 功能验证(浏览器) +# 1. 打开 https://admin.duoqi.me +# 2. 登录 +# 3. 执行 CRUD 操作 +# 4. 验证 API 连通(浏览器 DevTools → Network) +``` + +--- + +## API 代理方案对比 + +| 方案 | 优点 | 缺点 | +|------|------|------| +| **Nginx 代理(推荐)** | 无跨域问题、Cookie 共享、CDN 友好 | Nginx 配置多几行 | +| 前端直连 api.duoqi.me | 简单 | 需要后端配 CORS、浏览器发跨域预检请求 | + +--- + +## 服务器端目录结构 + +``` +/opt/ +├── gitea/ # Gitea(已有) +├── duoqi-api/ # API 部署(已有) +│ ├── docker-compose.yml +│ ├── .env.prod / .env.test +│ └── repo/ +├── duoqi-admin/ # ← 新增 +│ ├── dist/ # 当前线上版本 +│ ├── dist-new/ # (部署时临时存在) +│ └── dist-old/ # (部署后临时存在,自动清理) +├── backups/ # 备份(已有) +└── scripts/ # 运维脚本(已有) +``` + +--- + +## 运维管理 + +### 手动部署(首次或紧急) + +```bash +# SSH 登录服务器 +ssh root@your-server-ip + +# 拉取最新代码 +cd /opt/duoqi-admin/repo && git pull origin main + +# 构建 +bun install --frozen-lockfile +bun run build + +# 原子替换 +mkdir -p /opt/duoqi-admin/dist-new +cp -r dist/* /opt/duoqi-admin/dist-new/ +mv /opt/duoqi-admin/dist /opt/duoqi-admin/dist-old +mv /opt/duoqi-admin/dist-new /opt/duoqi-admin/dist +rm -rf /opt/duoqi-admin/dist-old +``` + +### 回滚 + +```bash +# 保留最近一个版本的备份 +# 回滚 = 将 dist-old 恢复 +mv /opt/duoqi-admin/dist /opt/duoqi-admin/dist-failed +mv /opt/duoqi-admin/dist-old /opt/duoqi-admin/dist +rm -rf /opt/duoqi-admin/dist-failed +``` + +### 日常检查 + +```bash +# 检查 Nginx 状态 +systemctl status nginx + +# 检查站点响应 +curl -s -o /dev/null -w "%{http_code}" https://admin.duoqi.me + +# 检查磁盘占用 +du -sh /opt/duoqi-admin/dist/ +``` + +--- + +## 故障排查 + +### CI Action 拉取失败 + +**症状**:`actions/checkout` 或 `oven-sh/setup-bun` 拉取超时或 404。 + +```bash +# 检查 github_mirror 配置 +grep github_mirror /opt/act-runner/config.yaml +# 应输出:github_mirror: 'https://gitea.com' + +# 清除缓存的 action(修复损坏的缓存) +rm -rf /root/.cache/act/ +systemctl restart act-runner +``` + +### CI checkout 失败(ECONNREFUSED) + +**症状**:job 容器无法连接 Gitea API。 + +```bash +# 检查容器网络配置 +grep -A2 "network:" /opt/act-runner/config.yaml +# 应包含:network: "host" + +# 确认 Gitea API 可达 +curl http://localhost:3200/api/v1/repos/search?q=duoqi-admin +``` + +### CI deploy 步骤找不到 /opt/duoqi-admin + +**症状**:`cp` 或 `mv` 报 No such file or directory。 + +```bash +# 检查 Runner 容器的 volume 挂载配置 +grep "options:" /opt/act-runner/config.yaml +# 应包含:-v /opt/duoqi-admin:/opt/duoqi-admin + +grep "valid_volumes:" -A3 /opt/act-runner/config.yaml +# 应包含:- /opt/duoqi-admin +``` + +### 白屏但 HTTP 200 + +**原因**:JS 加载失败或路由未回退。 + +```bash +# 检查 Nginx try_files 配置 +nginx -T | grep try_files + +# 检查 dist/ 文件完整性 +ls -la /opt/duoqi-admin/dist/ +ls -la /opt/duoqi-admin/dist/assets/ +``` + +### API 请求 404 + +**原因**:`/admin/` 代理未生效。 + +```bash +# 检查 Nginx 代理配置 +nginx -T | grep -A5 "location /admin/" + +# 直接测试后端 +curl http://localhost:3000/health +``` + +### 样式/JS 未更新 + +**原因**:浏览器缓存。Vite 构建会自动给文件加 hash,但如果 HTML 被缓存则不会加载新资源。 + +```bash +# 检查 index.html 的 Cache-Control +curl -I https://admin.duoqi.me/index.html +# 应该没有长缓存头(index.html 不缓存,由 try_files 默认处理) +``` + +### HTTPS 证书问题 + +```bash +# 检查证书覆盖的域名 +echo | openssl s_client -connect admin.duoqi.me:443 2>/dev/null | openssl x509 -noout -text | grep DNS + +# 重新申请 +sudo certbot --nginx --expand \ + -d api.duoqi.me -d test-api.duoqi.me -d git.duoqi.me -d admin.duoqi.me +``` + +--- + +**文档版本**: v1.1.0 +**最后更新**: 2026-04-22 +**维护者**: Duoqi Team