最近在折腾 Hugo 配合 PaperMod 主题搭建个人博客时,不管是调整样式还是更新文章,发布流程都让我感到相当头大。由于我不把 Hugo 的构建产物(public 目录)纳入源码版本控制,所以每次发布都需要手动重建 Git 历史。

经过一番折腾,我决定写一个 Shell 脚本来接管这个流程。这篇备忘录主要记录脚本的实现逻辑和一些需要注意的坑,防止以后自己忘了。

1. 背景与痛点

在没有自动化脚本之前,我每次更新博客都要经历一套繁琐的流程,不仅效率低下,而且极易出错(例如输错仓库地址):

  1. 运行 hugo 命令生成静态文件。
  2. cd public 进入构建目录。
  3. git init 初始化临时仓库。
  4. git remote add 关联 GitHub Pages 远程仓库。
  5. git add .git commit 提交更改。
  6. git push -f 强制推送到远程分支。

为了解决这个重复性劳动的痛点,利用 Shell 脚本实现 CI/CD的本地化版本 是最经济的解决方案。

2. 核心步骤与脚本实现

我在博客项目的根目录下创建了一个名为 deploy.sh 的脚本。该脚本将构建、清理、推送的命令封装在一起,实现了“一键发布”。

2.1 脚本代码

以下是最终调试通过的脚本内容:

#!/bin/bash

# 自动化部署 Hugo 博客到 GitHub Pages
# 脚本功能:构建静态站点 -> 重置 public 目录 Git 历史 -> 强制推送到远程 pages 分支

# --- 配置部分 ---
# GitHub Pages 仓库的远程地址
# [Option A] SSH (推荐): git@github.com:yourname/yourname.github.io.git
# [Option B] HTTPS: https://github.com/yourname/yourname.github.io.git
REPO_URL="git@github.com:yourname/yourname.github.io.git"

# --- 主逻辑 ---

# [安全机制] 遇到任何命令执行失败,立即终止脚本
set -e

echo "[Step 1/4] Cleaning up old build files..."
# 清理旧的构建产物,确保环境纯净
rm -rf public

echo "[Step 2/4] Building the static site..."

hugo

echo "[Step 3/4] Initializing Git and committing changes..."
cd public

# 动态生成提交信息,包含时间戳
COMMIT_MSG="Deploy: Update site at $(date '+%Y-%m-%d %H:%M:%S')"

git init
git branch -M main
git add .
git commit -m "$COMMIT_MSG"

echo "[Step 4/4] Pushing to the GitHub Pages repository..."

git remote add origin "$REPO_URL"

# 因本地是新 init 的仓库,历史记录与远程不一致,必须使用 -f 覆盖
git push -f origin main

# 返回项目根目录
cd ..

echo "Deployment successful!"

2.2 使用方法

在终端(Terminal 或 Git Bash)中赋予脚本执行权限并运行:

chmod +x deploy.sh
./deploy.sh

3. 免密输入配置方案与安全分析

为了实现真正的“无人值守”自动化,必须解决 git push 时的交互式认证。这里主要有两种方案,经过深入研究,我发现方案 B 存在不容忽视的安全隐患。

3.1 方案 A:SSH 协议(强烈推荐)

这是最标准且安全的做法。

  • 配置方式:在本地使用 ssh-keygen 生成密钥对,并将公钥(Public Key)添加到 GitHub 的 Settings -> SSH and GPG keys 中。
  • 脚本设置:将 REPO_URL 变量设置为 git@github.com:... 格式。
  • 优点:基于非对称加密,无需在代码或配置中存储明文密码,且由系统 SSH Agent 管理会话。

3.2 方案 B:HTTPS + Personal Access Token(存在隐患与修复)

如果你由于网络环境限制必须使用 HTTPS,通常的做法是配置 Git 凭证助手。

  • 常规做法(有坑): 许多教程建议使用 git config --global credential.helper store

    • 安全隐患:此命令会将你的 Personal Access Token (PAT) 以 纯文本 (Plain Text) 形式保存在本地的 ~/.git-credentials 文件中。一旦电脑感染恶意软件或配置文件被误上传,Token 将直接泄露。
  • 安全优化方案: 为了修补上述漏洞,如果必须使用 HTTPS,请严格遵循以下步骤:

    1. 使用安全的凭证管理器: 不要使用 store,而应根据操作系统选择加密存储方式。

      • Windows: 推荐安装并使用 GCM (Git Credential Manager),它会将 Token 加密存储在 Windows 凭据管理器中。
      • Mac: 使用 osxkeychain,将 Token 存入系统钥匙串。
      • Linux:
        • 桌面环境 (GNOME/KDE):推荐配置 libsecret 模式,将凭证存入系统 Keyring 中(需安装 libsecret 库并编译 git helper)。
        • 服务器/临时环境:推荐使用 cache 模式 (git config --global credential.helper 'cache --timeout=3600'),仅在内存中缓存 Token 一小时。
    2. 遵循最小权限原则 (Least Privilege): 在 GitHub 生成 Token 时,仅勾选 public_repo 权限。

    3. 紧急补救(如果你已经使用了 store: 如果你之前不小心执行过 store 模式,请立即按以下顺序操作以消除隐患:

      • 清理配置:运行 git config --global --unset credential.helper
      • 删除文件:手动删除用户根目录下的 ~/.git-credentials 文件(里面存着明文密码!)。
      • 重置凭证:务必去 GitHub 后台 撤销 旧的 Token 并生成新的,因为旧 Token 理论上已视为泄露。

4. 技术原理与安全性分析

在引入脚本前,我主要担心它会不会误删文件。经过对 Bash 行为的分析,这个脚本在设计上包含了几处关键的安全保障:

  1. 进程熔断机制 (set -e): 脚本启用了 set -e 选项。这指示 Bash 解释器:一旦任何指令返回非零状态码(Exit Code != 0),立即终止执行。这意味着如果 hugo 构建失败(例如 Markdown 语法错误),脚本会立刻退出,绝不会执行后续的 git push,防止了将空站点或错误内容推送到线上。
  2. 构建环境隔离 (Isolation): 所有的 Git 版本控制操作(Init, Add, Commit)都被严格限制在 public 目录下。脚本通过 rm -rf public 保证每次构建都是“纯净”的,避免了 Git 仓库体积随着构建产物的二进制变动而无限膨胀。
  3. 强制推送的合理性: 通常我们慎用 git push -f,但在静态站点部署场景下,public 目录被视为构建产物 (Artifacts) 而非源码。我们不需要保留构建产物的历史,只关心最终呈现结果,因此覆盖远程历史是符合预期的。

5. 避坑指南

在使用这套流程的过程中,我踩了一些坑,特此总结如下,避免重蹈覆辙:

注意:执行脚本前请务必仔细核对以下配置点。

  1. 仓库地址配置错误(高危)

    • 问题:如果在 REPO_URL 中错误填写了存放 Hugo 源码 的仓库地址。
    • 后果:脚本执行 git push -f 后,你的源码仓库历史将被 public 目录的内容完全覆盖,且极难恢复!
    • 对策:务必反复确认 REPO_URL 指向的是 username.github.io (Pages 仓库)。
  2. 源码仓库的 .gitignore 配置

    • 问题:如果不配置 .gitignorepublic/ 目录可能会被误提交到源码仓库中。
    • 对策:必须在源码根目录的 .gitignore 文件中添加一行 public/,保持源码分支的整洁。
  3. 警惕 credential.helper store 陷阱

    • 问题:在解决 HTTPS 免密推送时,千万不要图省事直接运行 git config --global credential.helper store
    • 后果:这是一种极其不安全的做法。它会在你的用户根目录下生成一个 .git-credentials 文件,里面明文存储了你的 GitHub 账号和 Token。一旦恶意软件扫描该文件,你的仓库权限将彻底暴露。
    • 对策:坚持使用 SSH 协议。如果必须用 HTTPS,请确保使用基于系统加密的凭证管理器(Windows 的 GCM、macOS 的 Keychain 或 Linux 的 Libsecret/Cache),绝不要裸奔存储凭证。