这篇记录一下这个博客的搭建过程。
需求很明确,本地用 Markdown 写文章,本地预览,发布到自己的服务器。服务器上不跑博客后台,不维护数据库,不开放登录入口,只用 OpenResty 提供静态文件。
最后用的是这套:
Hugo + PaperMod + GitHub 私有仓库 + systemd timer + 1Panel OpenResty
一开始想过 WordPress 和 Ghost,但暂时没必要。它们适合需要后台编辑器、账号、评论、订阅、插件生态的场景。我现在更需要的是一个可以长期放笔记的地方,写作和发布都尽量简单。
本地站点
先创建 Hugo 项目。
hugo new site blog
cd blog
git init
主题先用 PaperMod。
git submodule add https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
hugo.yaml 里主要配置这些:
baseURL: https://example.com/
title: my blog
theme: PaperMod
timeZone: Asia/Shanghai
hasCJKLanguage: true
outputs:
home:
- HTML
- RSS
- JSON
outputs.home 里的 JSON 是给搜索用的。PaperMod 的搜索页依赖 index.json,如果这里没开,页面可能能打开,但搜不到内容。
新文章放在 content/posts/。
hugo new posts/my-note.md
文章头部大概这样:
---
title: "文章标题"
date: 2026-06-16T04:00:00+08:00
draft: false
tags:
- Hugo
---
本地预览:
hugo server -D
默认地址:
http://127.0.0.1:1313/
生成静态文件:
hugo --gc --minify
生成结果在 public/。这个目录不提交到 Git,只作为构建产物。
服务器
服务器这边用 1Panel 管理 OpenResty,建一个普通静态站点。站点目录指向最终发布目录,概念上类似:
server {
server_name example.com;
root /path/to/site/root;
index index.html;
}
1Panel 会生成 OpenResty 配置,尽量不要手动改整份配置。更稳的是在面板里处理域名、证书、HTTPS 和站点目录,发布脚本只负责把 Hugo 生成结果同步过去。
最早的发布方式是本地构建后直接同步:
hugo --gc --minify
rsync -a --delete public/ user@your-server:/path/to/site/root/
这个方法能用,但不适合多端写作。换一台电脑,就要重新配置 SSH、远端路径和发布脚本。后面改成 GitHub 做中心仓库,服务器自己拉取并构建。
现在的发布链路是:
本地写文章
git push 到 GitHub
服务器定时 git pull
服务器本地 hugo 构建
rsync 到 OpenResty 站点目录
这样本地电脑、VSCode、Obsidian、Codex 都只需要和 GitHub 交互。日常不需要 SSH 到服务器发布。
GitHub 同步
GitHub 仓库存源码和文章,不存 public/。服务器用只读 Deploy Key 拉仓库。这个 key 只需要读权限,不需要写权限,也不是 GitHub 账号密码。
服务器同步脚本的核心逻辑:
git fetch --prune origin main
git reset --hard origin/main
git submodule update --init --recursive
hugo --gc --minify --destination /tmp/blog-build
rsync -a --delete /tmp/blog-build/ /path/to/site/root/
这里要注意 --delete。它会让线上目录和构建目录保持一致,适合静态站发布,但前提是源目录和目标目录都写对。脚本里最好固定构建目录和站点目录,不要临时拼危险路径。
定时任务用 systemd timer。
service 大概是:
[Service]
Type=oneshot
ExecStart=/usr/local/bin/blog-sync
timer 大概是:
[Timer]
OnBootSec=30s
OnUnitActiveSec=2min
Persistent=true
这样不用开 webhook,也不用让 GitHub 主动访问服务器。缺点是会有一点延迟,通常一两分钟。个人博客可以接受。
排查时看这几个:
systemctl status blog-sync.service
systemctl status blog-sync.timer
journalctl -u blog-sync.service -n 80 --no-pager
这套跑通后,本地的 scripts/deploy.sh 就删掉了。发布入口只保留 Git。
DNS 和 HTTPS
域名接入 Cloudflare 后,排查时不要只看浏览器结果。浏览器缓存、旧跳转规则、Cloudflare 规则都可能让判断变复杂。
先看响应头:
curl -I https://example.com/
再看浏览器最终跳转。
这次主要检查了几类东西:
- DNS 是否指向正确服务器;
- Cloudflare 里是否还有旧跳转规则;
- 1Panel/OpenResty 站点是否绑定了正确域名;
- HTTPS 模式和源站证书是否一致;
- 访问根域名和文章页是否都返回 200。
静态站本身不复杂,真正容易误判的是缓存和跳转。
写作流程
以后写文章就按这个来:
cd blog
git pull --rebase
hugo new posts/my-note.md
hugo server -D
git add .
git commit -m "docs: add my note"
git push
服务器定时任务会自动拉取并发布。
VSCode 建议直接打开整个 Hugo 项目,因为 Git、主题、配置和终端都在同一个目录里。
Obsidian 有两种用法:
- 只写文章,可以把
content/当成 vault; - 想用 Obsidian Git 插件,就打开整个仓库。
如果用 Obsidian,记得忽略 .obsidian/,不要把编辑器配置提交到博客仓库。
图片
静态博客最麻烦的不是 Markdown,而是图片放哪里。
这次接了一个独立图床。仓库里只保留上传脚本和变量示例,真正的 token 放在本地 .env,并加入 .gitignore。
示例配置:
LSKY_BASE_URL=https://your-image-host.example
LSKY_TOKEN=
上传图片:
./scripts/lsky-upload.sh /path/to/image.png
脚本返回图片 URL 后,再写进文章头部:
cover:
image: "https://your-image-host.example/i/2026/06/17/example.png"
alt: "图片说明"
这样文章源码里只有公开图片地址,没有账号和密钥。换电脑写作时,只需要重新配置本机 .env。
以后再补
现在先保持简单。后续真需要时再加:
- 评论;
- 搜索优化;
- 统计;
- 图片压缩;
- 主题细节调整;
- 自动备份。
第一版先到这里。现在这套流程已经能满足基本写作:本地写,GitHub 同步,服务器自动发布。
