duoqi-admin/docs/ci-deployment-guide.md
Wang Zhuoxuan e0871d0b7a
Some checks failed
Build & Deploy Admin / deploy (push) Failing after 16s
docs: 添加部署方案文档与 Gitea Actions CI 配置
- 新增 CI 部署指南,包含 Runner 基础设施适配、HTTPS 证书实操经验、故障排查
- 新增 deploy.yml workflow,基于 Act Runner volume 挂载实现原子部署
2026-04-22 03:04:26 +08:00

16 KiB
Raw Permalink Blame History

Duoqi Admin 部署方案

基于 duoqi-api 现有 Gitea + 阿里云轻量应用服务器基础设施,零额外内存的纯前端部署方案

目录


架构概览

核心设计

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

服务器端准备

# 创建部署目录
mkdir -p /opt/duoqi-admin/dist

# 设置初始占位页面(首次部署前)
cat > /opt/duoqi-admin/dist/index.html << 'EOF'
<!DOCTYPE html>
<html><body><h1>Duoqi Admin - Deploying...</h1></body></html>
EOF

Nginx 配置

创建配置文件 /etc/nginx/conf.d/duoqi-admin.conf

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;
}

验证并生效:

nginx -t
systemctl reload nginx

try_files 说明React Router 的前端路由(如 /questions/categories)在浏览器刷新时会向服务器请求这些路径。没有 try_files 回退Nginx 会返回 404。有了它所有未知路径都回退到 index.html,由前端 JS 路由接管。/assets/ 目录下的文件由 Vite 自动添加 content hashassets/index-DK2R8j.css),可以安全设置 1 年强缓存。


HTTPS 证书

将 admin.duoqi.me 加入已有证书(注意 --expand 必须列出所有域名):

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 是替换整张证书,不是追加。
# ❌ 错误:只写新增域名,会导致原有域名被移除!
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
  1. 备案问题:阿里云服务器 + 未备案域名HTTP 请求可能被运营商层拦截(返回 403Server: Beaver),导致 certbot 验证失败。需要先完成 ICP 备案或暂时使用其他验证方式DNS-01

DNS 配置

在域名管理后台添加 A 记录:

主机记录 记录类型 记录值
admin A 服务器公网 IP

前端环境变量

构建时需指向生产 API。推荐使用 Nginx 代理方案,构建时 VITE_API_BASE_URL 留空:

# .env.production
VITE_API_BASE_URL=

这样 ky 的请求会发到 admin.duoqi.me/admin/*,由 Nginx 转发到 localhost:3000,无跨域问题。

如果使用直连方案(不推荐):

# .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

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

systemctl restart act-runner

为什么不用 SSH/rsyncAct Runner 的 job 容器通过 network: host + volume 挂载直接访问宿主机文件系统,无需 SSH 跳转。SSH 方式在容器内需要额外配置密钥,反而更复杂。

2. 自定义 Runner 镜像

Runner 使用自定义镜像 duoqi-runner:bun-git(基于 oven/bun + git + docker CLI。该镜像

  • 包含bun、gitcheckout 需要、docker CLI
  • 不包含curl、rsync、ssh 客户端
  • 固定版本Bun 1.3,确保 CI 环境可复现

因此 CI 中不能使用 curlrsyncssh 等命令,需用 bun/cp/mv 替代。

3. github_mirror 配置

Runner 已配置 github_mirror: 'https://gitea.com',从 gitea.com 镜像拉取 GitHub Actionsactions/checkout),解决国内无法访问 GitHub 的问题。如果 Action 拉取失败:

# 清除缓存的 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

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 操作,更轻量。


部署验证

构建验证

# 本地构建
bun run build

# 本地预览构建产物
bun run preview

线上验证

# 检查首页可访问
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/                        # 运维脚本(已有)

运维管理

手动部署(首次或紧急)

# 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

回滚

# 保留最近一个版本的备份
# 回滚 = 将 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

日常检查

# 检查 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/checkoutoven-sh/setup-bun 拉取超时或 404。

# 检查 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。

# 检查容器网络配置
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

症状cpmv 报 No such file or directory。

# 检查 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 加载失败或路由未回退。

# 检查 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/ 代理未生效。

# 检查 Nginx 代理配置
nginx -T | grep -A5 "location /admin/"

# 直接测试后端
curl http://localhost:3000/health

样式/JS 未更新

原因浏览器缓存。Vite 构建会自动给文件加 hash但如果 HTML 被缓存则不会加载新资源。

# 检查 index.html 的 Cache-Control
curl -I https://admin.duoqi.me/index.html
# 应该没有长缓存头index.html 不缓存,由 try_files 默认处理)

HTTPS 证书问题

# 检查证书覆盖的域名
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