🚀 Astro 博客从零部署到全自动发布
本文记录了从一个空目录开始,到实现 Obsidian 笔记自动同步、媒体资源自动下载、Astro 自动编译发布的完整过程。
目录
第一阶段:Astro 博客从 0 到 1 部署
1. 项目初始化
使用克隆官方仓库:
mkdir -p /opt/blog-web & cd /opt/blog-web
# 使用官方 Sobasic 主题
git clone https://github.com/lukeska/sobasic.git .
# 安装依赖
npm install
项目结构:
/opt/blog-web/
├── src/
│ ├── content/
│ │ └── blog/ # 博客文章目录
│ ├── images/ # 主题图片
│ └── components/ # Astro 组件
├── public/ # 静态资源
├── dist/ # 编译输出
└── package.json
2. Caddy 反向代理配置
Caddy 是一个现代化的 Web 服务器,自动 HTTPS:
blog.yourdomain.com {
# 注意docker容器隔离, 静态资源要放到对应的volume中去代理才能正常生效
root * /data/blog-static
file_server
encode gzip zstd
# 缓存策略
@static path *.css *.js *.png *.jpg *.jpeg *.gif *.svg *.ico *.woff *.woff2
header @static Cache-Control "public, max-age=31536000"
# HTML不缓存,便于更新
@html path *.html
header @html Cache-Control "no-cache"
# 安全头
header X-Content-Type-Options nosniff
header X-Frame-Options DENY
}
部署步骤:
# 1. 构建静态产物
npm run build
# 2. 复制静态资源到 Caddy 数据卷(根据不同docker而异)
cp -r dist/* /var/lib/docker/volumes/matrix-service_caddy-data/_data/blog-static/
# 3. 重载 Caddy 配置(在docker容器中执行热重载)
docker exec caddy caddy reload
至此,blog 网站已经可以正常访问!
3. 路径与图片资源处理
初期遇到的问题:
- 文章 Markdown 中的相对路径
./资源/xxx.jpg无法正确解析 - Obsidian 笔记路径与 Astro 博客路径不匹配
- 图片需要手动复制到对应目录
临时解决方案(人工操作):
- 手动复制 Obsidian 笔记到
src/content/blog/ - 手动转换图片路径格式
- 手动拷贝图片到
src/images/ - 手动执行构建部署
这个流程虽然可用,但每次发布都需要手动操作多个步骤,效率低下,容易出错。
第二阶段:实现全自动同步发布
1. 整体架构设计
目标:实现 Obsidian 写笔记 → 自动同步 → 自动编译 → 自动发布
┌─────────────────┐
│ Obsidian │ 用户在本地写笔记
│ (本地笔记) │
└────────┬────────┘
│ FNS 同步
▼
┌─────────────────┐ REST API ┌──────────────────┐
│ FNS 服务 │ ◄────────────► │ 同步脚本 │
│ (Obsidian 云) │ HTTP/9000 │ sync_blog.py │
└─────────────────┘ └────────┬─────────┘
│
▼
┌──────────────────┐
│ Astro 项目 │
│ /opt/blog-web │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ 编译构建 │
│ npm run build │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ 静态资源部署 │
│ blog-static/ │
└──────────────────┘
2. FNS REST API 集成
FNS (Fast Note Sync) 提供了完整的 REST API:
**登录认证:
POST /api/user/login
Headers: {"X-Client": "WebGui"}
Body: {"Credentials": "your-username", "Password": "your-password"}
⚠️ 重要:登录接口只接受
X-Client: WebGui头。FNS 的 Token 绑定 WebGui client type,如果用Script或ObsidianPlugin头会被拒绝。
获取笔记列表:
GET /api/files?page=1&page_size=50&vault=vault-name
获取笔记内容:
GET /api/note?vault=vault-name&path=04-博客文章/xxx.md
下载文件:
GET /api/file?vault=vault-name&path=xxx/资源/xxx.jpg
3. 同步脚本核心逻辑
创建 /opt/blog-automation/scripts/sync_blog.py,实现:
3.1 笔记增量同步
使用 JSON 文件记录同步状态,只同步有变更的笔记:
# 同步状态结构
{
"last_sync": "2026-05-03T03:00:00",
"synced_notes": {
"笔记路径": {
"last_modified": "...",
"size": 1234
}
}
}
3.2 智能图片路径处理
实现多级图片路径匹配策略:
def process_image(image_path, note_dir):
# 处理图片: 从 FNS 下载并转换路径
# 策略 1: 笔记目录 + 相对路径
# 笔记: 04-博客文章/测试.md
# 图片: 资源/xxx.jpg
# FNS 完整路径: 04-博客文章/资源/xxx.jpg
# 下载图片到按日期组织的目录
# src/images/2026-05-03/xxx.jpg
3.3 —force 强制同步参数
支持强制完整同步,用于初始化或故障恢复:
./scripts/deploy.sh --force
强制模式会:
- ✅ 清空同步状态文件
- ✅ 清理旧的博客源文件(.md 和图片)
- ✅ 清理构建缓存(node_modules/.astro、dist/)
- ✅ 重新同步所有笔记和图片
- ✅ 强制完整重新构建
4. 部署脚本流水线
创建 /opt/blog-automation/scripts/deploy.sh,实现按退出码条件部署的流水线
#!/bin/bash
# 步骤 1: 同步 FNS 笔记
# - exit 0: 无变更,跳过构建
# - exit 1: 有变更,进入构建
# - exit >=2: 错误,退出
python3 sync_blog.py || HAS_CHANGES=$?
if [ $HAS_CHANGES -ge 2 ]; then
echo "同步失败,退出码: $HAS_CHANGES"
exit $HAS_CHANGES
fi
if [ $HAS_CHANGES -eq 1 ] || [ "$FORCE" = true ]; then
cd /opt/blog-web
npm run build
DEPLOY_DIR="/var/lib/docker/volumes/matrix-service_caddy-data/_data/blog-static"
rm -rf "${DEPLOY_DIR:?}"/*
cp -r dist/* "$DEPLOY_DIR"
fi
退出码协议:
0=无变更跳过构建,1=有变更触发构建,>=2=错误立即退出。这避免了无变更时的重复构建,每次定时执行平均节省约 1 分钟。
ini
[Unit]
Description=Blog Auto Sync Service
[Service]
Type=oneshot
ExecStart=/opt/blog-automation/scripts/deploy.sh
WorkingDirectory=/opt/blog-automation
**定时器配置 `/etc/systemd/system/blog-sync.timer`**:
```ini
[Unit]
Description=Run blog sync every 15 minutes
[Timer]
OnBootSec=5min
OnUnitActiveSec=15min
[Install]
WantedBy=timers.target
启用定时器:
systemctl daemon-reload
systemctl enable --now blog-sync.timer
systemctl list-timers blog-sync.timer
完整自动化工作流
日常发布流程(完全自动化)
1. 用户在 Obsidian 中写笔记
└─ 保存到 "04-博客文章/" 目录
2. FNS 自动同步到云端
└─ Obsidian 插件后台同步
3. Systemd Timer 触发(每 15 分钟)
└─ 执行 deploy.sh
4. 同步脚本检测变更
├─ 拉取 FNS 中的笔记列表
├─ 对比同步状态
└─ 只处理有变更的笔记
5. 媒体资源自动处理
├─ 智能路径匹配
└─ 通过 REST API 下载资源
6. Astro 自动编译
└─ npm run build
7. 自动部署到静态目录
├─ 清理旧资源(安全检查)
└─ 复制新静态文件
8. ✅ 博客自动更新完成!
项目目录结构
/opt/blog-automation/
├── scripts/
│ ├── deploy.sh # 部署入口脚本
│ └── sync_blog.py # FNS 同步核心逻辑
├── state/
│ └── sync_state.json # 同步状态记录
└── logs/ # 运行日志
/opt/blog-web/
├── src/
│ ├── content/
│ │ ├── blog/ # 同步过来的 .md 文章
│ │ └── images/ # 同步过来的图片(按日期组织)
│ └── images/ # 主题自带图片
└── dist/ # 编译输出
/var/lib/docker/volumes/.../blog-static/ # 最终发布目录
踩坑记录与解决方案
1. 部署脚本安全问题
问题:清理目标目录时,如果变量为空,rm -rf /* 可能误删系统根目录。
解决方案:使用 Bash 参数展开进行安全检查:
# ${DEPLOY_DIR:?} 表示变量为空时报错退出
rm -rf "${DEPLOY_DIR:?}"/*
日常使用指南
手动操作命令
# 正常增量同步(快速)
cd /opt/blog-automation && ./scripts/deploy.sh
# 强制完整同步(重新下载所有内容)
cd /opt/blog-automation && ./scripts/deploy.sh --force
# 查看定时器状态
systemctl list-timers blog-sync.timer
# 查看同步日志
journalctl -u blog-sync.service -n 50 -f
# 查看最近同步记录
cat /opt/blog-automation/state/sync_state.json
博客写作规范
- 文章位置:保存到 Obsidian 的
04-博客文章/目录 - Frontmatter:文章开头需要包含
--- title: 文章标题 description: 文章描述 heroImage: 资源/封面图.jpg date: 2026-05-03 publish: true featured: true --- - 图片引用:使用相对路径
资源/xxx.jpg - 自动发布:写完保存后,最多等待 15 分钟自动发布
⚠️ 发布前确认
以下内容已做脱敏处理,请逐一确认是否需要调整:
- 域名
blog.yourdomain.com→ 替换为实际域名 - API 服务地址
127.0.0.1:9000→ 确认是否公开 - 配置中的用户名/密码 → 已替换为占位符
- Caddyfile 中的域名配置 → 替换为实际域名
- 路径
/opt/blog-web、/opt/blog-automation→ 确认是否需要泛化
总结
已实现的功能清单
✅ 完全云同步:从 FNS/Obsidian 自动拉取笔记
✅ 媒体资源自动下载:通过 REST API 下载图片等资源
✅ 资源路径处理:路径自动修正
✅ 增量同步:只同步有变更的内容,高效快速
✅ 强制完整同步:—force 参数支持初始化和恢复
✅ 定时自动同步:每 15 分钟自动执行一次
✅ 安全部署:防止误删系统文件的安全机制
✅ 部署统计:清理文件数、新文件数、耗时统计
最终效果
以前:写笔记 → 手动复制 → 手动转路径 → 手动拷图片 → 手动构建 → 手动部署(5分钟+)
现在:写笔记 → 保存 → ✅ 自动完成(最多等待15分钟)
🎉 现在只需要在 Obsidian 中专注写作,剩下的一切都会自动完成!