跳过正文
  1. AI 技术/

Hugo + Gitea CI/CD 自动部署全过程记录:踩了多少坑才跑通

作者
清幽
分享自托管、副业、被动收入的实战经验

这篇文章记录的是 ooya.site 从零到上线的完整过程,不是教程,是实战日志——包括所有踩过的坑。

目标
#

一套自托管的静态站发布流程:

  1. 代码写好,推送到自建 Gitea
  2. Gitea Actions 自动触发构建
  3. 构建产物通过 SCP 推送到香港服务器
  4. Nginx 软链接切换版本,支持秒级回滚

基础设施
#

  • Hugo:静态站生成器
  • Blowfish 主题:颜值在线,中文友好
  • Gitea:自托管 Git 服务(git.ooya.site
  • Gitea Act Runner:Docker 部署的 CI 执行器
  • 香港服务器:Nginx + HTTPS,无需备案

部署架构
#

本地编辑 → git push → Gitea
                   Act Runner(Docker)
                   Hugo 构建 public/
                   SCP 推送到香港服务器
                   /var/www/ooya.site/release/<commit-id>/
                   ln -sfn release/<id> current
                   Nginx root → current/

每次部署保留历史版本,回滚只需换个软链接。

踩坑记录
#

坑一:hugo.toml 里 baseURL 忘了改
#

初始化项目时 baseURL = 'https://example.org/',Hugo 构建出来的所有链接都是错的。

解决:改成 https://www.ooya.site/ 再构建。


坑二:Checkout action 缓存损坏
#

第一次跑 CI,actions/checkout@v4 报错:

Unable to pull refs/heads/v4: non-fast-forward update

Runner 本地缓存的 action 引用损坏了,无法快进更新。

解决:改用完整 URL 引用 action,绕过本地缓存:

uses: https://github.com/actions/checkout@v4

坑三:submodules: true 拉不到 submodule
#

Blowfish 主题作为 git submodule 引入,checkout 时加了 submodules: true,但报错:

exitcode '1': failure(Fetching submodules 阶段)

查了半天,发现仓库里根本没有 .gitmodules——主题是直接复制进去的,不是 submodule。submodules: true 找不到 submodule 就报错了。

解决:去掉 submodules: true


坑四:Runner 根本连不上 GitHub
#

解决了上面的问题后,改成真正的 submodule 方式引入 Blowfish,CI 又挂了:

read tcp 172.18.0.2:34458->198.18.0.51:443: read: connection reset by peer

Runner 所在服务器无法访问 GitHub——被墙了。

这导致两个问题:

  1. actions/checkoutpeaceiris/actions-hugo 这些 action 拉不到
  2. Blowfish submodule 的源地址是 GitHub,也拉不到

解决方案:把所有 GitHub 依赖都镜像到自建 Gitea:

# 在 Gitea 上创建镜像仓库
# hermes/checkout  ← actions/checkout
# hermes/actions-hugo  ← peaceiris/actions-hugo
# hermes/blowfish  ← nunocoracao/blowfish

workflow 里改用本地地址:

uses: https://git.ooya.site/hermes/checkout@v4
uses: https://git.ooya.site/hermes/actions-hugo@v2

坑五:Gitea 镜像仓库是空的
#

把 blowfish 镜像到 Gitea 时,因为仓库体积大(~400MB),migrate API 请求超时,仓库创建了但内容是空的。

CI 拉 submodule 时报:

warning: You appear to have cloned an empty repository.
fatal: remote error: upload-pack: not our ref e9699d8c5da9a64a105eb8401fd2cf79c6015c6f

submodule 锁定了一个特定 commit hash,空仓库里当然找不到。

解决:放弃 submodule 机制,直接在 CI 里 clone:

- name: Clone Blowfish theme
  run: |
    git clone --depth 1 https://github.com/nunocoracao/blowfish.git themes/blowfish

--depth 1 只拉最新一个 commit,速度快,不需要完整历史。Runner 这台服务器能访问 GitHub(不同于之前那台),这步顺利通过。


坑六:Hugo 构建出来没有 index.html
#

第一次 CI 成功,访问网站却是 403。SSH 进服务器一看,current/ 目录里只有:

categories/
index.xml
sitemap.xml
tags/

没有 index.html

原因:仓库里根本没有文章内容,Hugo 只生成了分类和标签的 XML,不会生成首页 HTML。

解决:加了一篇文章,重新构建,这次有 index.html 了。


坑七:首页不显示文章列表
#

站跑起来了,但首页空空的,文章不见踪影。

查 Blowfish 配置,找到两处问题:

  1. params.tomlmainSections = ["docs"]——指向的是示例站的文档目录,我的文章放在 posts/,改成 mainSections = ["posts"]
  2. 首页 layout = "custom",用的是示例站的自定义布局,改成 layout = "profile" 并开启 showRecent = true

最终 workflow
#

经过七个坑之后,最终跑通的 deploy.yml

name: Deploy Hugo Site

on:
  push:
    branches:
      - main

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: https://git.ooya.site/hermes/checkout@v4
        with:
          submodules: false
          fetch-depth: 0

      - name: Clone Blowfish theme
        run: |
          git clone --depth 1 https://github.com/nunocoracao/blowfish.git themes/blowfish

      - name: Setup Hugo
        uses: https://git.ooya.site/hermes/actions-hugo@v2
        with:
          hugo-version: 'latest'
          extended: true

      - name: Build
        run: hugo --minify

      - name: Deploy via SCP
        env:
          SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
        run: |
          COMMIT_ID=${{ gitea.sha }}
          RELEASE_DIR=/var/www/ooya.site/release/${COMMIT_ID}
          CURRENT_LINK=/var/www/ooya.site/current

          mkdir -p ~/.ssh
          echo "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          ssh-keyscan -H <服务器IP> >> ~/.ssh/known_hosts

          ssh -i ~/.ssh/deploy_key root@<服务器IP> "mkdir -p ${RELEASE_DIR}"
          scp -i ~/.ssh/deploy_key -r public/* root@<服务器IP>:${RELEASE_DIR}/
          ssh -i ~/.ssh/deploy_key root@<服务器IP> "ln -sfn ${RELEASE_DIR} ${CURRENT_LINK}"

香港服务器 Nginx 配置
#

server {
    listen 443 ssl http2;
    server_name ooya.site www.ooya.site;

    ssl_certificate     /etc/nginx/ssl/ooya.site.pem;
    ssl_certificate_key /etc/nginx/ssl/ooya.site.key;

    root /var/www/ooya.site/current;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

current 是软链接,指向最新 release 目录。回滚时:

ln -sfn /var/www/ooya.site/release/<上一个commit-id> /var/www/ooya.site/current

总结
#

整个过程最大的教训是:自托管环境的网络限制是隐形的。在本地测通的 CI 脚本,放到 Runner 上可能因为访问不了 GitHub 而全部失败。解决方法就是把所有外部依赖都镜像到内网。

搭完这套之后,日常发文章的流程变成:

  1. 在本地写 Markdown
  2. git push
  3. 等一分钟,站自动更新

这就是为什么值得花时间把 CI/CD 调通——一次投入,长期省事。


这套系统的搭建经验后续会整理成教程包,如果你也想搭一套自己的,关注后续更新。

相关文章