在 Day 21 與 Day 22 中,我們完成了情報沉澱至 Obsidian 的適配器與動態 DataviewJS 戰情看板。然而,當前的同步流程仍存在一個致命痛點:強依賴本地運算環境。
我們必須坐在電腦前、開啟終端機手動執行 python -m scripts.sync_to_obsidian。如果出差在外只有手機或平板,或者本機關機數天,情資沉澱就會中斷。
在現代 PKM 實踐中,許多高階使用者會將 Obsidian Vault 本身建立為一個 GitHub 私有儲存庫(Private Repository),搭配手機端或電腦端的 Git 外掛(如 Obsidian Git)達成多裝置無縫同步。
今天我們將架構推向全自動化——利用 GitHub Actions、SSH Deploy Key 與 Git 跨儲存庫推送,在無人值守巡邏管線執行完畢後,直接在雲端產出結構化 Markdown,並自動提交(Commit & Push)回個人的私有 Vault 儲存庫,達成 100% 雲端自治閉環!
在 CI/CD 中,情報雷達的核心儲存庫(intelligence-radar)與個人筆記儲存庫(my-obsidian-vault)通常是兩個完全獨立的 Private Repositories。
為了遵循最小權限原則(Principle of Least Privilege),我們不能也不該使用具備個人全域權限的 Personal Access Token (PAT),而是採用 SSH Deploy Key:
+----------------------------------------------------------------------------------+
| GitHub Actions Runner (intelligence-radar) |
| |
| 1. Scheduled Patrol 巡邏 ➔ 更新 Google Sheets (PROCESSED) |
| 2. 載入 SSH Deploy Key (私鑰) ➔ 取得目標 Vault 寫入權限 |
| 3. git clone git@github.com:your-user/my-obsidian-vault.git |
| 4. 執行 python -m scripts.sync_to_obsidian --vault-dir ./my-obsidian-vault |
| 5. git add & git commit (若有新筆記) ➔ git push origin main |
+----------------------------------------------------------------------------------+
│
▼ (SSH 加密傳輸)
+----------------------------------------------------------------------------------+
| GitHub Remote: your-user/my-obsidian-vault (Private Repo) |
| - 部署公鑰 (Deploy Key, 具備 Write 權限) 驗證通過 |
| - 00_Inbox/Radar/2026-XX-XX - [SCIENCE] ....md 寫入成功 |
+----------------------------------------------------------------------------------+
在終端機執行以下指令(務必留空密碼,直接按 Enter):
ssh-keygen -t ed25519 -C "github-actions-radar-sync" -f id_ed25519_vault_sync -N ""
這會在目錄下生成兩個檔案:
id_ed25519_vault_sync.pub(公鑰)id_ed25519_vault_sync(私鑰)your-user/my-obsidian-vault)。Radar Sync CI Key。id_ed25519_vault_sync.pub 的全部內容貼上。intelligence-radar)。VAULT_DEPLOY_KEY。id_ed25519_vault_sync 的全部內容(包含 -----BEGIN OPENSSH PRIVATE KEY----- 等標頭)完整貼上。VAULT_REPO_SSH,內容為目標 Vault 的 SSH Clone URL,例如:git@github.com:your-user/my-obsidian-vault.git。為了讓 scripts/sync_to_obsidian.py 同時支援本機開發(讀取環境變數)與 CI/CD Runner(讀取命令列參數),我們使用 argparse 微調入口:
# scripts/sync_to_obsidian.py
import os
import argparse
import logging
from pathlib import Path
from src.adapters.sheets_adapter import SheetsAdapter
from src.adapters.obsidian_adapter import ObsidianVaultAdapter
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - [%(levelname)s] - %(message)s"
)
logger = logging.getLogger(__name__)
def main():
parser = argparse.ArgumentParser(description="將 Google Sheets 已審查高分情資同步至 Obsidian Vault")
parser.add_argument(
"--vault-dir",
type=str,
default=os.getenv("OBSIDIAN_VAULT_PATH", "~/ObsidianVault"),
help="目標 Obsidian Vault 路徑"
)
parser.add_argument(
"--subfolder",
type=str,
default="00_Inbox/Radar",
help="Vault 內存放情報的子目錄"
)
args = parser.parse_args()
vault_path = Path(args.vault_dir).expanduser().resolve()
logger.info(f"初始化 Obsidian 轉接器,目標路徑: {vault_path}")
obsidian_adapter = ObsidianVaultAdapter(
vault_path=vault_path,
subfolder=args.subfolder
)
logger.info("連線至 Google Sheets 讀取已處理情資...")
sheets_adapter = SheetsAdapter()
candidates = sheets_adapter.fetch_processed_items(min_score=4)
logger.info(f"[*] 找到 {len(candidates)} 筆高價值情報候選項目...")
results = obsidian_adapter.batch_sync(candidates)
logger.info(f"[+] 同步完成!新增: {results['synced']} 篇,略過: {results['skipped']} 篇。")
if __name__ == "__main__":
main()
我們將情報同步邏輯無縫掛接在排程巡航(Patrol)之後,或作為獨立的後置 Step。
修改 .github/workflows/patrol.yml(或新建 .github/workflows/obsidian_sync.yml):
name: Intelligence Radar Patrol & Vault Sync
on:
schedule:
# 每天定時 UTC 00:00 與 12:00 自動觸發 (台灣時間 08:00 與 20:00)
- cron: '0 0,12 * * *'
workflow_dispatch: # 支援手動一鍵觸發
jobs:
patrol-and-sync:
name: Run Patrol and Sync to Obsidian Vault
runs-on: ubuntu-latest
timeout-minutes: 8 # 防禦型超時中斷
steps:
- name: Checkout Radar Repo
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Decrypt GCP Credentials
env:
GCP_SA_BASE64: ${{ secrets.GCP_SA_BASE64 }}
run: |
echo "$GCP_SA_BASE64" | base64 --decode > service_account.json
- name: Run Pipeline (Reviewer -> Domain Expert -> Synthesizer -> Editor)
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
SPREADSHEET_KEY: ${{ secrets.SPREADSHEET_KEY }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
run: |
python -m src.services.pipeline
# =========================================================================
# 關鍵步驟:設定 SSH Deploy Key 並 Clone 私有 Obsidian Vault
# =========================================================================
- name: Setup SSH Agent for Obsidian Vault
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.VAULT_DEPLOY_KEY }}
- name: Clone Private Obsidian Vault
env:
VAULT_REPO: ${{ secrets.VAULT_REPO_SSH }}
run: |
git clone "$VAULT_REPO" obsidian_vault_target
- name: Export High-Score Intelligence to Obsidian
env:
SPREADSHEET_KEY: ${{ secrets.SPREADSHEET_KEY }}
run: |
python -m scripts.sync_to_obsidian --vault-dir ./obsidian_vault_target
# =========================================================================
# 關鍵步驟:檢查異動並自動 Commit & Push
# =========================================================================
- name: Commit and Push Notes to Obsidian Vault
run: |
cd obsidian_vault_target
git config user.name "Radar Sentinel Bot"
git config user.email "bot@intelligence-radar.internal"
# 檢查是否有新增或修改的 .md 檔案
if [ -n "$(git status --porcelain)" ]; then
echo "[+] 檢測到新情報筆記,正在提交變更..."
git add 00_Inbox/Radar/
git commit -m "chore(radar): auto sync high-value intelligence [$(date +'%Y-%m-%d %H:%M')]"
git push origin main
else
echo "[*] 無新增情報筆記,略過 Commit。"
fi
- name: Cleanup Sensitive Credentials
if: always()
run: |
rm -f service_account.json
在雲端自動跨 Repo 推送筆記時,有兩個常見的實務問題必須提前防範:
ssh -i id_rsa,可能會因為 GitHub 的 Known Hosts 未預先註冊而噴出 Host key verification failed。webfactory/ssh-agent@v0.9.0 Action。它會自動在背景啟動 ssh-agent、導入私鑰並安全寫入 GitHub 官方的 public keys 進入 ~/.ssh/known_hosts,完全杜絕互動式確認卡死。git commit 會因為沒有 staged changes 拋出非零結束碼(Exit code 1),導致整場 GitHub Actions 被標記為紅色失敗。git status --porcelain 判斷字串是否非空。只有在確認有變更時才執行 git add、commit 與 push,確保管線在無情報時也能優雅綠燈通過。今天我們完成了「雲端無人值守情報沉澱閉環」:
.md 檔案,Day 22 寫好的 Dataview 看板立即呈現最新的前沿技術進展。目前我們的輸出端(Egress Port)已經完美覆蓋了檔案型知識庫(Obsidian)。然而在許多企業協作或多維資料庫愛好者的工作流中,Notion 是另一個不可忽視的知識中樞。
在 Day 24,我們將延續六角架構的精神:「跨平台延伸:實作 Notion API Adapter 將情報同步至 Notion 關聯資料庫」,展示如何將同樣的 IntelligenceItem 模型無縫適配至 Notion 的 Rich Text Blocks 與 Multi-select 結構!