何回目かわからないけどブログを作りました。

試したい技術が現れるたびにブログを作っている気がしますが、今回はちゃんと更新していけるように頑張ります。

このブログは、Obsidianのvault(保管庫 ローカルのディレクトリ)の一部のディレクトリをブログ記事として切り出してSSGで記事化し公開する構成になっているので、その説明をします。

Quartz自体の説明はここではしません。

この構成のメリット

pushごとに公開

Obsidianのノートを更新してpushするたびにデプロイjobが実行されるので公開が楽です。

プライベートなvaultと同一リポジトリ内で独立

通常、QuartzなどのSSGでブログを作るときは、SSGリポジトリの内部にObsidianのvaultを配置することになると思います。

その場合、プライベートなvaultとは独立することになりますが、管理が面倒ですし、LLMの恩恵を受けながら記事を書きたい場合は同一のリポジトリで管理して、同じ場所でドキュメントをindex化して活用したいです。

そういったことができるようになります。

構成

リポジトリは2つ登場します。

  • obsidian-vault
    • プライベートなObsidian vaultで普通にドキュメント管理として使う
    • _published ディレクトリ内のドキュメントのみブログ記事としてビルドして公開したい
  • obsidian-blog
    • ブログのSSGやUIなどを管理する(つまりこのブログの本体)
    • ソースコードはこちら

アプローチは、obsidian-vaultの _published ディレクトリ内に変更があったときにGitHub Actionsでeventをdispatchしてobsidian-blog側のGitHub Actionsのトリガーにし、obdsidian-blogではobsidian-vaultをcloneしてきて _published 内のみSSGで記事を生成して公開するというものです。

ローカルでは obsidian-blog の content で obsidian-vault のシンボリックリンクを貼ればローカルでもプレビューできます。

sequenceDiagram
    participant OV as obsidian-vault
    participant GA1 as GitHub Actions
    participant GB as obsidian-blog
    participant GA2 as GitHub Actions
    participant GH as GitHub Pages

    OV->>GA1: _published/に差分あり
    GA1->>GB: vault-updated event
    GB->>GA2: triggered
    GA2->>OV: クローン
    GA2->>GA2: Quartz 4でビルド
    GA2->>GH: デプロイ

Deploy用yml

これで _published に差分があったときに obsidian-blog のGAが受け取れるeventをdispatchします。

DISPATCH_TOKEN は、obsidian-blogの contents:read,write 権限を持つ personal access token(PAT)です。

obsidian-vault/.github/workflows/deploy.yml
name: Trigger Blog Deploy on Published Changes
 
on:
  push:
    branches:
      - main
    paths:
      - '_published/**/*.md'
 
jobs:
  dispatch:
    runs-on: ubuntu-latest
    steps:
      - name: Trigger deployment in obsidian-blog repo
        uses: peter-evans/repository-dispatch@v3
        with:
          token: ${{ secrets.DISPATCH_TOKEN }}
          repository: tachibanayu24/obsidian-blog
          event-type: vault-updated

これで obsidian-vault から受け取ったeventをトリガーにしてデプロイを実行します。 obsidian-blog でmainブランチにpushしたときにも実行します。

VAULT_ACCESS_TOKEN は、obsidian-vaultの contents:read 権限を持つ personal access token(PAT)です。

obsidian-blog/.github/workflows/deploy.yml
name: Deploy Blog
 
on:
  push:
    branches:
      - main
 
  repository_dispatch:
    types: [vault-updated] # obsidian-vaultの `_published` が更新されたらdispatchされる
 
permissions:
  contents: write
 
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout obsidian-blog Repo
        uses: actions/checkout@v4
        with:
          path: obsidian-blog
 
      - name: Checkout obsidian-vault Repo (to temp location)
        uses: actions/checkout@v4
        with:
          repository: tachibanayu24/obsidian-vault
          path: vault-temp
          token: ${{ secrets.VAULT_ACCESS_TOKEN }}
 
      - name: Prepare content directory
        run: mkdir -p obsidian-blog/content
 
      - name: Copy published content
        run: |
          if [ -d "vault-temp/_published" ] && [ "$(ls -A vault-temp/_published)" ]; then
            cp -r vault-temp/_published/* obsidian-blog/content/
          else
            echo "Warning: vault-temp/_published directory is empty or does not exist."
          fi
 
      # attachmentsは通常 `_published` には配置しないので、画像など正しく表示するためにこれもコピーする
      - name: Copy attachments to content root
        run: |
          if [ -d "vault-temp/_config/attachment" ] && [ "$(ls -A vault-temp/_config/attachment)" ]; then
            cp -r vault-temp/_config/attachment/* obsidian-blog/content/
          else
            echo "Info: vault-temp/_config/attachment directory is empty or does not exist."
          fi
 
 
      - name: Setup Node, Install, Build
        working-directory: obsidian-blog
        env:
          NODE_ENV: production
        run: |
          npm ci
          npx quartz build
 
      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./obsidian-blog/public
          cname: blog.tachibanayu24.com

おわり

これで単一vault内でプライベートな領域と公開用の領域で分けることができました。