Woodzow 个人网站搭建与部署笔记
本文记录当前网站从本地开发到云服务器自动部署的主要结构、命令和验证方法。网站地址为 https://woodzow.top,源码仓库为 Woodzow/woodzow_web。
1. 技术方案
- 静态网站生成器:Hugo Extended
- 主题:PaperMod(Git 子模块)
- 本地环境:macOS、VS Code、Git
- 代码托管:GitHub
- 服务器:Ubuntu、Nginx、Certbot
- 更新方式:服务器定时拉取 GitHub
main分支,构建成功后发布
整体流程:
本地修改并预览 → 推送 GitHub → systemd 定时检查 → Hugo 构建 → rsync 发布 → Nginx 提供 HTTPS 访问
2. 项目结构
woodzow_web/
├── hugo.yaml # Hugo、菜单、主页和 PaperMod 参数
├── content/ # 页面和 Markdown 内容
│ ├── about.md
│ ├── academic/
│ ├── blog/
│ └── paper-notes/
├── layouts/ # 对 PaperMod 的模板覆盖和短代码
├── assets/css/extended/ # 自定义样式
├── static/
│ ├── fonts/ # 随网站部署的字体
│ └── images/ # 头像等静态图片
├── archetypes/ # 新建内容时使用的模板
├── themes/PaperMod/ # PaperMod Git 子模块
├── .vscode/tasks.json # VS Code 预览、构建和新建文章任务
├── .gitmodules # PaperMod 子模块地址
└── .gitignore # 忽略 public、构建缓存和系统文件
不要直接修改 themes/PaperMod/。需要定制时,应在项目根目录的 layouts/ 或 assets/ 中创建同路径文件进行覆盖。
当前站点的主要定制
- 站点仅保留中文,导航栏为 HomePage、Blog、Academic、PaperNotes、About。
- Blog 下分 Technology、Research、Essays、Reflections。
- Academic 页面组织 Research 与 Publications,论文通过 publication 短代码显示为卡片。
- 首页头像位于 static/images/woodzow2.png。
- 中文使用霞鹜文楷等宽,英文使用 Source Sans 3,代码使用 Fira Mono。
- 自定义字体、悬停变色、下拉菜单、卡片和悬浮目录主要写在 assets/css/extended/custom.css。
PaperMod 的几个关键参数:
params:
defaultTheme: auto
disableThemeToggle: false
ShowToc: true
profileMode:
enabled: true
- profileMode.enabled: true 使用 Profile 首页;改成 false 后使用 homeInfoParams。
- defaultTheme: auto 跟随系统明暗模式。
- disableThemeToggle: false 显示月亮/太阳按钮,该按钮只切换明暗主题。
- ShowToc: true 允许文章显示目录,页面 Front Matter 可以单独覆盖。
- 邮箱链接必须使用 mailto:邮箱地址。
PaperMod 官方演示站的语言入口可能加载不同内容和参数,所以切换语言后看起来像更换模板;语言按钮本身并不负责切换 Profile/Home Info 模式。
3. 本地安装与首次拉取
macOS 安装必要工具:
brew install hugo git
hugo version
git --version
克隆仓库及主题:
git clone --recurse-submodules git@github.com:Woodzow/woodzow_web.git
cd woodzow_web
git submodule update --init --recursive
Hugo 应显示 extended。PaperMod 子模块状态前没有 - 或 + 表示正常:
git submodule status
4. VS Code 编辑与浏览器预览
在 VS Code 中打开仓库:
code /Users/zhongwu/woodzow_web
启动本地预览:
hugo server -D --navigateToChanged
浏览器访问 http://localhost:1313/。其中:
-D:预览draft: true的草稿。--navigateToChanged:内容变化后浏览器跳转到相应页面。
也可以在 VS Code 中运行任务 Hugo: 本地预览。停止服务使用 Ctrl+C。
正式构建验证:
hugo --cleanDestinationDir --gc --minify
正式部署不包含草稿,准备发布的文章应设置:
draft: false
5. 日常内容更新
新建内容:
hugo new content paper-notes/new-note.md
hugo new content blog/technology/new-post.md
本地预览正常后提交:
git status
git add <修改的文件>
git commit -m "描述本次修改"
git push origin main
服务器将在约 5 分钟内自动更新。不要把生成目录 public/ 提交到 Git;服务器会自行构建。
6. 服务器基础部署
服务器使用独立的 deploy 用户执行 Git 拉取和 Hugo 构建,该用户不需要 sudo 权限:
sudo adduser --disabled-password --gecos "" deploy
sudo install -d -m 755 -o deploy -g deploy /home/deploy/woodzow_web
sudo install -d -m 755 -o deploy -g deploy /home/deploy/woodzow-build
sudo install -d -m 755 -o deploy -g deploy /var/www/woodzow.top
为服务器创建只读 GitHub 密钥,并将公钥添加到仓库的 Deploy keys:
sudo install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
sudo -u deploy ssh-keygen -t ed25519 \
-f /home/deploy/.ssh/github_woodzow_readonly \
-C "woodzow server deploy"
/home/deploy/.ssh/config 指定该密钥:
Host github.com
HostName github.com
User git
IdentityFile /home/deploy/.ssh/github_woodzow_readonly
IdentitiesOnly yes
验证并克隆:
sudo -u deploy ssh -T git@github.com
sudo -u deploy git clone --recurse-submodules \
git@github.com:Woodzow/woodzow_web.git \
/home/deploy/woodzow_web
GitHub 返回“successfully authenticated, but GitHub does not provide shell access”表示认证成功;该测试返回状态码 1 是正常现象。
服务器应安装官方 Hugo Extended 软件包,不使用 Snap 版 Hugo。验证:
hugo version
sudo -u deploy hugo version
两条命令都应显示 extended linux/amd64。
首次构建和发布:
sudo -u deploy hugo \
--source /home/deploy/woodzow_web \
--destination /home/deploy/woodzow-build \
--cleanDestinationDir --gc --minify
sudo -u deploy rsync -a --delete \
/home/deploy/woodzow-build/ \
/var/www/woodzow.top/
7. Nginx、域名与 HTTPS
DNS 应将根域名指向服务器公网 IP,并让 www 指向根域名。验证:
dig +short woodzow.top
dig +short www.woodzow.top
Nginx 站点的核心配置:
server {
listen 80;
server_name woodzow.top www.woodzow.top;
root /var/www/woodzow.top;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
检查并加载配置:
sudo nginx -t
sudo systemctl reload nginx
curl -I -H 'Host: woodzow.top' http://127.0.0.1
申请 HTTPS 证书并启用 HTTP 自动跳转:
sudo certbot --nginx --redirect \
-d woodzow.top \
-d www.woodzow.top
证书验证:
sudo certbot certificates
sudo certbot renew --dry-run
systemctl list-timers --all | grep -i certbot
curl -I https://woodzow.top
阿里云安全组应允许公网访问 TCP 80、443;SSH 22 最好只允许可信 IP。
8. GitHub 自动部署
自动部署脚本位于:
/usr/local/bin/deploy-woodzow
它按顺序执行:拉取 origin/main、更新 PaperMod 子模块、在独立目录构建、构建成功后用 rsync --delete 发布,并记录已经部署的 Git 提交。构建失败时不会覆盖当前线上网站。
对应的 systemd 单元:
/etc/systemd/system/woodzow-deploy.service
/etc/systemd/system/woodzow-deploy.timer
加载并启用:
sudo systemctl daemon-reload
sudo systemctl start woodzow-deploy.service
sudo systemctl enable --now woodzow-deploy.timer
验证结果:
systemctl show woodzow-deploy.service -p Result -p ExecMainStatus
systemctl status woodzow-deploy.timer --no-pager
systemctl list-timers --all | grep woodzow
sudo journalctl -u woodzow-deploy.service -n 100 --no-pager
正常的一次性服务执行结束后会显示 inactive (dead),这是 Type=oneshot 的预期状态。真正需要常驻等待的是 timer。成功日志应包含:
Successfully deployed revision: <Git 提交哈希>
9. 本次搭建中遇到的主要问题
9.1 浏览器显示 documentation example domain
这是因为配置或内容中仍存在 example.com、example.org 等专门用于文档示例的占位域名,不是 Hugo 错误。可以搜索:
rg -n "example\\.com|example\\.org|example\\.net" . \
-g '!themes/**' \
-g '!public/**'
把对应 URL 换成真实地址。邮件按钮应使用:
- name: email
url: "mailto:name@example.com"
9.2 本站和 PaperMod 官方预览不同
PaperMod 提供布局和功能,官方演示站还包含自己的内容、语言和参数,新站不会自动复制全部演示效果。重点检查:
rg -n "profileMode|homeInfoParams|defaultTheme|languages|menu:" hugo.yaml
需要修改模板时使用根目录的 layouts/ 覆盖主题,不要直接改 themes/PaperMod/。
9.3 Markdown 标题旁出现 # 号
这是标题锚点。点击后 URL 会带上标题标识,用于直接分享文章中的某一节,不是错误。
9.4 dig 返回 198.18.0.x
如果本机开启代理软件,这通常是代理的 Fake IP,不是云服务器真实公网 IP。可以临时关闭代理,或指定公共 DNS:
dig @1.1.1.1 +short woodzow.top
dig @8.8.8.8 +short woodzow.top
结果应与云服务器控制台显示的公网 IP 一致。
9.5 SSH 提示 PTY allocation failed
当 deploy 账号或授权密钥禁止交互终端时,连接可能显示:
PTY allocation request failed on channel 0
这不一定代表认证失败。用远程命令验证:
ssh -i ~/.ssh/woodzow_deploy \
deploy@<SERVER_IP> 'id'
能返回用户信息,说明密钥认证和远程命令执行正常。
9.6 GitHub SSH 验证成功但仓库仍无法拉取
ssh -T 成功只代表 GitHub 接受了某把密钥,不一定代表该密钥拥有目标仓库权限。服务器只读公钥应添加到目标仓库的 Settings → Deploy keys,并在 /home/deploy/.ssh/config 中指定私钥:
Host github.com
HostName github.com
User git
IdentityFile /home/deploy/.ssh/github_woodzow_readonly
IdentitiesOnly yes
设置权限并检查实际选择的密钥:
sudo chown deploy:deploy /home/deploy/.ssh/config
sudo chmod 600 /home/deploy/.ssh/config
sudo -u deploy ssh -G github.com | grep -i identityfile
sudo -u deploy git ls-remote \
git@github.com:Woodzow/woodzow_web.git HEAD
GitHub 返回 successfully authenticated, but GitHub does not provide shell access,表示 SSH 认证成功。GitHub 不提供交互式 Shell,所以 ssh -T 返回状态码 1 是正常的。
SSH 主机指纹用于确认连接目标确实是 GitHub,降低中间人攻击风险。首次写入 known_hosts 前,应与 GitHub 官方公布的指纹核对。
9.7 PaperMod 子模块没有拉取
检查:
git submodule status
开头符号的含义:
- -:尚未初始化。
- +:当前提交与主仓库记录不一致。
- 没有异常前缀:正常。
修复:
git submodule sync --recursive
git submodule update --init --recursive
9.8 public 已加入 .gitignore,但仍被提交
.gitignore 不会停止跟踪已经提交过的文件,需要执行一次:
git rm -r --cached public
git rm --cached .hugo_build.lock
git add .gitignore
git commit -m "Stop tracking generated Hugo files"
–cached 只取消 Git 跟踪,不删除本地生成文件。
9.9 Snap Hugo 的 cgroup 错误
在 admin 会话中执行 sudo -u deploy hugo 时,Snap 版 Hugo 曾出现:
... is not a snap cgroup for tag snap.hugo.hugo
这是 Snap 隔离和用户会话问题,不是 Hugo 内容错误。服务器最终改用 Hugo 官方 Extended .deb。示例安装如下,版本变化时以 Hugo Releases 页面为准:
cd /tmp
HUGO_VERSION="0.164.0"
wget -O "hugo_extended_${HUGO_VERSION}_linux-amd64.deb" \
"https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb"
wget -O "hugo_${HUGO_VERSION}_checksums.txt" \
"https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_checksums.txt"
sha256sum --check --ignore-missing \
"hugo_${HUGO_VERSION}_checksums.txt"
sudo apt install \
"./hugo_extended_${HUGO_VERSION}_linux-amd64.deb"
安装后同时验证 admin 和 deploy 用户:
hugo version
sudo -u deploy hugo version
两条结果都应包含 extended linux/amd64。
9.10 Hugo 安装包校验失败
下载中断可能产生 .deb.1、.deb.2 等文件并导致:
FAILED
computed checksum did NOT match
此时不能安装。删除明确的临时下载文件,使用 wget -O 固定文件名重新下载,再执行 SHA-256 校验。校验失败通常表示文件不完整、文件选错或代理返回了异常页面。
9.11 Hugo 输出 deprecated 警告
WARN deprecated 表示主题调用了未来将移除的 Hugo 接口。只要命令最终成功、退出码为 0 且生成页面,当前部署仍然完成。后续更新 PaperMod 即可;警告不等同于构建失败。
9.12 systemd 服务显示 inactive/dead 和 exit 3
woodzow-deploy.service 是 Type=oneshot,执行完就退出,因此成功后显示 Active: inactive (dead) 是正常的。systemctl status 对不活动服务可能返回退出码 3。
使用以下命令判断上次执行结果:
systemctl show woodzow-deploy.service \
-p Result \
-p ExecMainStatus
sudo journalctl -u woodzow-deploy.service \
-n 100 --no-pager
正常结果:
Result=success
ExecMainStatus=0
Successfully deployed revision: <Git 提交哈希>
真正持续等待的是 woodzow-deploy.timer,应显示 active (waiting)。
9.13 Certbot timer 有下一次时间但没有上次记录
刚启用的定时器可能尚未运行。验证:
sudo certbot renew --dry-run
systemctl status snap.certbot.renew.timer --no-pager
systemctl list-timers --all | grep -i certbot
定时器只负责周期性检查,证书接近到期时才会续期,不会每次检查都签发新证书。
10. 自动部署脚本详解
服务器脚本 /usr/local/bin/deploy-woodzow 的完整核心逻辑:
#!/usr/bin/env bash
set -Eeuo pipefail
REPO="/home/deploy/woodzow_web"
BUILD="/home/deploy/woodzow-build"
WEBROOT="/var/www/woodzow.top"
STATE_DIR="/home/deploy/.local/state/woodzow"
STATE_FILE="${STATE_DIR}/deployed-revision"
mkdir -p "$BUILD" "$STATE_DIR"
echo "Checking GitHub for updates..."
git -C "$REPO" fetch --prune origin main
TARGET_REVISION="$(git -C "$REPO" rev-parse origin/main)"
DEPLOYED_REVISION=""
if [[ -f "$STATE_FILE" ]]; then
DEPLOYED_REVISION="$(<"$STATE_FILE")"
fi
if [[ "$TARGET_REVISION" == "$DEPLOYED_REVISION" ]]; then
echo "Already up to date: $TARGET_REVISION"
exit 0
fi
git -C "$REPO" switch main
git -C "$REPO" merge --ff-only origin/main
git -C "$REPO" submodule sync --recursive
git -C "$REPO" submodule update --init --recursive
echo "Building Hugo site..."
hugo \
--source "$REPO" \
--destination "$BUILD" \
--cleanDestinationDir --gc --minify
echo "Publishing site..."
rsync -a --delete "$BUILD"/ "$WEBROOT"/
printf '%s\n' "$TARGET_REVISION" > "$STATE_FILE"
echo "Successfully deployed revision: $TARGET_REVISION"
权限:
sudo chown root:root /usr/local/bin/deploy-woodzow
sudo chmod 755 /usr/local/bin/deploy-woodzow
状态文件只在发布成功后写入。即使 Git 已拉到新提交,只要构建失败,下一轮仍会重试,同时不会覆盖线上版本。
手动测试、启用和观察:
sudo systemctl daemon-reload
sudo systemctl start woodzow-deploy.service
sudo systemctl enable --now woodzow-deploy.timer
systemctl status woodzow-deploy.timer --no-pager
systemctl list-timers --all | grep woodzow
sudo journalctl -u woodzow-deploy.service -f
日志跟踪使用 Ctrl+C 退出。
11. 日常自动发布流程
本地修改并预览:
cd /Users/zhongwu/woodzow_web
hugo server -D --navigateToChanged
另开终端验证并提交:
hugo --cleanDestinationDir --gc --minify
git status
git diff
git add <修改的文件>
git commit -m "Update website"
git push origin main
约 5~6 分钟后检查:
sudo journalctl -u woodzow-deploy.service \
-n 50 --no-pager
curl -I https://woodzow.top
浏览器仍显示旧内容时可强制刷新,macOS 常用 Command+Shift+R。不要直接修改服务器仓库或 /var/www/woodzow.top 中的生成文件;修复应在本地完成后重新推送。
12. 最终检查清单
每次重要更新后可以依次检查:
# 本地
hugo --cleanDestinationDir --gc --minify
git status
# 服务器
sudo journalctl -u woodzow-deploy.service -n 50 --no-pager
sudo nginx -t
curl -I https://woodzow.top
systemctl --failed
同时确认:
- 首页、菜单、文章、图片、字体和移动端显示正常。
- HTTPS 可访问,HTTP 会跳转到 HTTPS。
- GitHub 新提交能被自动部署。
- 页脚使用真实的网站 ICP 备案号;公安联网备案通过后再添加真实的公安备案号和图标。
- 定期执行系统安全更新,并在可控时间重启服务器。
