Hugo 连接远端内容

内容框架分离,仓库更加简洁,也更容易进行迁移。使用[[Hugo]]连接远端内容,有至少三种实现方案。

三种方案

方案一:Hugo Modules(推荐:原生、优雅)

Hugo 深度集成了一套基于 Go Modules 的系统,称为 Hugo Modules。它可以直接把一个远程的 GitHub 仓库“挂载”到你项目的 content 目录下。

  • 实现逻辑:hugo.toml 配置文件中声明远程仓库,Hugo 在构建时会自动把远程仓库的代码拉取到内存或临时目录中进行渲染。

  • 优点:

    • 最符合你的学习路径: 因为它本质上使用的是 Go 的模块管理逻辑,对于打算学习 Go 的你来说,这是提前熟悉 Go 生态的最佳方式。

    • 配置最简单: 不需要写复杂的 CI 脚本,只需要几行配置文件。

    • Cloudflare 支持: Cloudflare Pages 完美支持 Hugo Modules,构建时会自动运行 go 环境下载依赖。

  • 缺点:

    • 本地预览时需要电脑安装有 Go 环境。

方案二:Git Submodule(经典、显式)

这是最传统的方式,将内容仓库作为主仓库的一个子模块。

  • 实现逻辑: 运行 git submodule add <内容仓库URL> content

  • 优点: 兼容性最强,不依赖 Hugo 的特殊功能。

  • 缺点: * 更新琐碎: 每次内容仓库更新后,你必须在主仓库运行 git submodule update --remote 并提交一次主仓库,Cloudflare 才会触发更新(除非写额外的 Action 自动化)。

    • 容易出错: Git Submodule 的“游离状态(detached HEAD)”对新手不太友好。

方案三:CI/CD 流水线(灵活、复杂)

使用 GitHub Actions,当“内容仓库”有变动时,触发“Hugo 仓库”的构建。

  • 实现逻辑: 在内容仓库写一个 Action,通过 repository_dispatch 信号通知 Hugo 仓库,或者直接在 Action 里把内容拉下来并推送给 Cloudflare。

  • 优点: 极其灵活,可以在中间加入 Python 脚本做数据预处理。

  • 缺点: 维护成本高,需要管理 GitHub Token。

方案一:它的原理是基于go modules,而这个对文件名的校验很严格,而内容仓库存储的Markdown文件名会更加放飞,所以不采用。 方案二:内容仓库是私有仓库,所以在后期部署的时候会比较麻烦,所以不采用。 这里选择方案三。

GitHub Actions

  1. 在内容仓库编写清洗脚本
# code/sync_to_hugo.py
import os
import re
import shutil
from pathlib import Path

SOURCE_DIR = "."
TARGET_DIR = "hugo_tmp_output" # 临时输出目录名

def sanitize(name):
    return re.sub(r'[^\w\-_]', '_', name).lower()

def process_vault():
    # 确保目标目录存在且为空
    target_path = Path(TARGET_DIR)
    if target_path.exists():
        shutil.rmtree(target_path)
    target_path.mkdir(parents=True)

    for root, dirs, files in os.walk(SOURCE_DIR):
        # 【关键修复】: 排除隐藏目录和目标输出目录
        # 修改 dirs[:] 会直接影响 os.walk 的后续遍历
        dirs[:] = [d for d in dirs if not d.startswith('.') and d != TARGET_DIR]
        
        for file in files:
            if file.endswith('.md'):
                # 使用 pathlib 处理路径,更现代也更安全
                old_path = Path(root) / file
                
                # 计算相对路径,并清洗每一层目录名
                rel_parts = old_path.relative_to(SOURCE_DIR).parts
                clean_parts = [sanitize(p) if i < len(rel_parts)-1 else sanitize(Path(p).stem) + Path(p).suffix 
                               for i, p in enumerate(rel_parts)]
                
                dest_path = target_path.joinpath(*clean_parts)
                dest_path.parent.mkdir(parents=True, exist_ok=True)
                
                shutil.copy2(old_path, dest_path)

if __name__ == "__main__":
    process_vault()
  1. 在内容仓库配置 Github Action,并保存为仓库的 .github/workflows/sync.yml
name: Sync to Hugo Blog

on:
  push:
    branches:
      - main  # 当你的 Obsidian 库有更新时

jobs:
  build-and-sync:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout content repo
        uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.9'

      - name: Run Python Sanitizer
        run: python code/sync_to_hugo.py

      - name: Push to Hugo Repo
        env:
          API_TOKEN_GITHUB: ${{ secrets.PAGES_PUSH_TOKEN }}
        run: |
          # 克隆框架仓库
          git clone https://x-access-token:${{ secrets.PAGES_PUSH_TOKEN }}@github.com/你的用户名/hugo-blog.git framework
          
          # 清空框架仓库的 content 目录并覆盖新内容
          rm -rf framework/content/*
          cp -r hugo_content/* framework/content/
          
          cd framework
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add .
          git commit -m "Auto-sync content from Obsidian: $(date)" || echo "No changes to commit"
          git push origin main
  1. 在 Github 上申请 GitHub Personal Access Token,用于授权更新到框架仓库。记录好Token。
  2. 配置到内容仓库的Secrets中。内容仓库的settings,Secrets and variables,Actions,New repository secret。名字需要和Yaml文件内的变量保持一致。
  3. 进行测试。