最近在折腾 Hugo 配合 PaperMod 主题搭建个人博客时,不管是调整样式还是更新文章,发布流程都让我感到相当头大。由于我不把 Hugo 的构建产物(public 目录)纳入源码版本控制,所以每次发布都需要手动重建 Git 历史。
经过一番折腾,我决定写一个 Shell 脚本来接管这个流程。这篇备忘录主要记录脚本的实现逻辑和一些需要注意的坑,防止以后自己忘了。
1. 背景与痛点
在没有自动化脚本之前,我每次更新博客都要经历一套繁琐的流程,不仅效率低下,而且极易出错(例如输错仓库地址):
- 运行
hugo命令生成静态文件。 cd public进入构建目录。git init初始化临时仓库。git remote add关联 GitHub Pages 远程仓库。git add .和git commit提交更改。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 将直接泄露。
- 安全隐患:此命令会将你的 Personal Access Token (PAT) 以 纯文本 (Plain Text) 形式保存在本地的
-
安全优化方案: 为了修补上述漏洞,如果必须使用 HTTPS,请严格遵循以下步骤:
-
使用安全的凭证管理器: 不要使用
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 一小时。
- 桌面环境 (GNOME/KDE):推荐配置
-
遵循最小权限原则 (Least Privilege): 在 GitHub 生成 Token 时,仅勾选
public_repo权限。 -
紧急补救(如果你已经使用了
store): 如果你之前不小心执行过store模式,请立即按以下顺序操作以消除隐患:- 清理配置:运行
git config --global --unset credential.helper。 - 删除文件:手动删除用户根目录下的
~/.git-credentials文件(里面存着明文密码!)。 - 重置凭证:务必去 GitHub 后台 撤销 旧的 Token 并生成新的,因为旧 Token 理论上已视为泄露。
- 清理配置:运行
-
4. 技术原理与安全性分析
在引入脚本前,我主要担心它会不会误删文件。经过对 Bash 行为的分析,这个脚本在设计上包含了几处关键的安全保障:
- 进程熔断机制 (
set -e): 脚本启用了set -e选项。这指示 Bash 解释器:一旦任何指令返回非零状态码(Exit Code != 0),立即终止执行。这意味着如果hugo构建失败(例如 Markdown 语法错误),脚本会立刻退出,绝不会执行后续的git push,防止了将空站点或错误内容推送到线上。 - 构建环境隔离 (Isolation):
所有的 Git 版本控制操作(Init, Add, Commit)都被严格限制在
public目录下。脚本通过rm -rf public保证每次构建都是“纯净”的,避免了 Git 仓库体积随着构建产物的二进制变动而无限膨胀。 - 强制推送的合理性:
通常我们慎用
git push -f,但在静态站点部署场景下,public目录被视为构建产物 (Artifacts) 而非源码。我们不需要保留构建产物的历史,只关心最终呈现结果,因此覆盖远程历史是符合预期的。
5. 避坑指南
在使用这套流程的过程中,我踩了一些坑,特此总结如下,避免重蹈覆辙:
注意:执行脚本前请务必仔细核对以下配置点。
-
仓库地址配置错误(高危)
- 问题:如果在
REPO_URL中错误填写了存放 Hugo 源码 的仓库地址。 - 后果:脚本执行
git push -f后,你的源码仓库历史将被public目录的内容完全覆盖,且极难恢复! - 对策:务必反复确认
REPO_URL指向的是username.github.io(Pages 仓库)。
- 问题:如果在
-
源码仓库的 .gitignore 配置
- 问题:如果不配置
.gitignore,public/目录可能会被误提交到源码仓库中。 - 对策:必须在源码根目录的
.gitignore文件中添加一行public/,保持源码分支的整洁。
- 问题:如果不配置
-
警惕
credential.helper store陷阱- 问题:在解决 HTTPS 免密推送时,千万不要图省事直接运行
git config --global credential.helper store。 - 后果:这是一种极其不安全的做法。它会在你的用户根目录下生成一个
.git-credentials文件,里面明文存储了你的 GitHub 账号和 Token。一旦恶意软件扫描该文件,你的仓库权限将彻底暴露。 - 对策:坚持使用 SSH 协议。如果必须用 HTTPS,请确保使用基于系统加密的凭证管理器(Windows 的 GCM、macOS 的 Keychain 或 Linux 的 Libsecret/Cache),绝不要裸奔存储凭证。
- 问题:在解决 HTTPS 免密推送时,千万不要图省事直接运行