Forest
🚀 Astro 博客从零部署到全自动发布

🚀 Astro 博客从零部署到全自动发布

本文记录了从一个空目录开始,到实现 Obsidian 笔记自动同步、媒体资源自动下载、Astro 自动编译发布的完整过程。


目录

  1. 第一阶段:Astro 博客从 0 到 1 部署
  2. 第二阶段:实现全自动同步发布
  3. 完整自动化工作流
  4. 踩坑记录与解决方案
  5. 日常使用指南

第一阶段: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 博客路径不匹配
  • 图片需要手动复制到对应目录

临时解决方案(人工操作)

  1. 手动复制 Obsidian 笔记到 src/content/blog/
  2. 手动转换图片路径格式
  3. 手动拷贝图片到 src/images/
  4. 手动执行构建部署

这个流程虽然可用,但每次发布都需要手动操作多个步骤,效率低下,容易出错。


第二阶段:实现全自动同步发布

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,如果用 ScriptObsidianPlugin 头会被拒绝。

获取笔记列表

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

博客写作规范

  1. 文章位置:保存到 Obsidian 的 04-博客文章/ 目录
  2. Frontmatter:文章开头需要包含
    ---
    title: 文章标题
    description: 文章描述
    heroImage: 资源/封面图.jpg
    date: 2026-05-03
    publish: true
    featured: true
    ---
    
  3. 图片引用:使用相对路径 资源/xxx.jpg
  4. 自动发布:写完保存后,最多等待 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 中专注写作,剩下的一切都会自动完成!