A
review

GitHub Actions × Google Drive × Astro Workers でブログ同期を自動化した話

R
Relation
#github-actions #google-drive #astro #cloudflare-pages #automation

このレポートについて

Astro で構築した技術ブログを Cloudflare Pages で公開している環境で、以下を満たす「ブログ記事同期の自動化フロー」を構築したときの記録です。

  • 情報整理は ChatGPT で行い、最終成果物は Markdown(md)として保存したい
  • 下書きや下準備は Google Drive に置きたい(PC/スマホ共通の置き場)
  • 最終的には GitHub リポジトリの src/content/blog に md を自動配置してほしい
  • Astro サイト側から「同期ボタン」を押すと GitHub Actions が走るようにしたい

特に、Astro + @astrojs/cloudflare(Workers モード)と Cloudflare Pages Functions の相性問題でかなりハマったので、そのあたりを重点的に残しています。


やりたかったこと

目的

  1. 調査メモ → 記事構造化 → Markdown 化までを ChatGPT で行う
  2. 生成した Markdown ファイルを、まずは Google Drive に配置
  3. GitHub Actions が Google Drive から md を取得してリポジトリに反映
  4. GitHub への push をトリガーに Cloudflare Pages が再デプロイ
  5. 必要なときだけ、ブログ側 UI から「同期ボタン」で GitHub Actions を任意実行

背景

  • Astro 製の技術ブログを antigravity-techblog リポジトリで運用中
  • Cloudflare Pages でホスティングし、@astrojs/cloudflare アダプタを使用
  • ログイン必須のページやミドルウェアもあり、Workers モード(SSR)を維持したい
  • ただし更新作業が「調査 → md 手打ち → Git commit → push」でダルい

最終的な構成概要

全体フロー

  1. ChatGPT でテンプレートに沿った md を生成してローカル保存
  2. Google Drive(指定フォルダ)にその md を格納
  3. GitHub Actions の workflow(drive-sync.yml)で以下を実行
    • Node スクリプト(scripts/fetch-from-drive.mjs)を呼び出し
    • Google Drive API(サービスアカウント)で md を一覧取得
    • 新規 md を src/content/blog/ にコピー
    • 既に存在するファイル名はスキップ(重複生成を防止)
    • git add/commit/push で main ブランチに反映
  4. GitHub の push をトリガーに Cloudflare Pages が自動デプロイ
  5. Astro サイトのトップに配置した「ブログ同期ボタン」から
    • /api/run-drive-sync に POST
    • その API が GitHub の workflow_dispatch を叩き、3 の処理を即時起動

技術要素

  • Astro(@astrojs/cloudflare アダプタ)
  • Cloudflare Pages(Workers モード / _worker.js)
  • GitHub Actions(workflow_dispatch + PAT)
  • Google Drive API(サービスアカウント + フォルダ ID)
  • Node.js スクリプト(Drive → ローカル → Git 操作)
  • Astro API Route(src/pages/api/run-drive-sync.ts

手順と実装の詳細

1. Google Drive 側の準備

  1. サービスアカウントを作成し、JSON キーを取得
  2. Drive 上で「ブログ同期用」フォルダを用意
  3. そのフォルダをサービスアカウントのメールアドレスと共有
  4. フォルダ ID を GitHub Actions から参照できるようにリポジトリ Secrets に保存

この時点で、サービスアカウント経由で「特定フォルダ内の md を一覧取得」できる状態にする。

2. Node スクリプト scripts/fetch-from-drive.mjs

目的:

  • Google Drive フォルダ内の .md ファイルを取得
  • ローカルの src/content/blog/ に保存
  • 既に同名ファイルが存在する場合はスキップ
  • 新規ファイルがあれば git add/commit する

ざっくりとした処理フロー:

  1. Google API クライアントで Drive の一覧を取得
  2. mimeType = text/markdown 相当のファイルのみ対象にする
  3. ローカルに存在するファイル名を事前にセット化
  4. 存在しないファイルのみダウンロード対象とする
  5. src/content/blog/<filename>.md に保存
  6. 新規ファイルが 1 件以上あれば git コマンドを実行
    • git add src/content/blog
    • git commit -m "chore: import blog markdown from Google Drive YYYY-MM-DD"

※ GitHub Actions 内で動くため、push は Actions 用 PAT で行う

3. GitHub Actions ワークフロー drive-sync.yml

役割:

  • 手動実行(workflow_dispatch)と夜間自動実行(cron)の両方に対応
  • Node をセットアップし、npm installnpm run fetch-from-drive を実行

例(概略):

name: Drive Sync

on:
  workflow_dispatch:
  schedule:
    - cron: "0 12 * * *" # JST 夜に相当するよう調整

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "22"

      - name: Install dependencies
        run: npm ci

      - name: Fetch markdown from Google Drive
        env:
          GOOGLE_SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }}
          DRIVE_FOLDER_ID: ${{ secrets.DRIVE_FOLDER_ID }}
        run: npm run fetch-from-drive

      - name: Push changes
        env:
          GITHUB_TOKEN: ${{ secrets.PERSONAL_GITHUB_PAT }}
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git push

ポイント:

  • workflow_dispatch を必ず定義しておく(API から叩くため)
  • push 用には workflow 権限を含む PAT(PERSONAL_GITHUB_PAT)を使用
  • GOOGLE_SERVICE_ACCOUNT_JSON には JSON キーの中身をそのまま保存

4. Astro × Cloudflare Pages 特有のハマりどころ

4-1. Pages Functions が機能しない原因

当初は Cloudflare Pages の functions/ ディレクトリを使って

  • functions/api/run-drive-sync.ts/api/run-drive-sync

という構成を狙ったが、動作しなかった。

ログから分かったこと:

  • Astro の設定で adapter: @astrojs/cloudflare + output: "server" を使用
  • ビルド時に dist/_worker.js が生成され、Cloudflare はそれを優先採用
  • Found _worker.js in output directory. Uploading. というログが出ている場合、
    • Pages Functions ではなくカスタム Worker モード
    • functions/ ディレクトリは一切見られない

このため、いくら functions/api/*.ts を置いても /api/... が 404 のままだった。

4-2. Build output と functions の共存トライ

一時的に以下も試した:

  • Build output を空にしてリポジトリルートをそのまま配信
  • dist/* をルートにコピーするカスタム build スクリプト(build:cf
  • functions/hello.ts を追加して /hello をテスト

結果として、/hello が動くケースと / が 404 になるケースが混在し、 最終的に _worker.js が存在する限り functions ルートは使えないという結論に至った。

5. 最終解決策:Astro Workers(API ルート)で実装

ミドルウェアによるログインチェックを維持したかったため、 Workers モード(@astrojs/cloudflare)を維持しつつ、Astro の API ルートで GitHub Actions を叩く構成に切り替えた。

5-1. API ルートの実装

src/pages/api/run-drive-sync.ts を新規作成:

  • ルート:POST /api/run-drive-sync
  • やること:
    • Cloudflare の env から GITHUB_TOKEN を取得
    • GitHub REST API actions/workflows/{workflow_file}/dispatches を叩く
    • ref: maindrive-sync.yml を起動

実装のポイント:

  • Cloudflare Pages × @astrojs/cloudflare では、env は context.locals.runtime.env から取得
  • GitHub REST API は User-Agent ヘッダが必須
  • エラー時には GitHub のレスポンスボディを一度確認し、403/404/422 のどれかで切り分ける

5-2. User-Agent ヘッダでハマった点

最初に出たエラー:

{
  "ok": false,
  "status": 403,
  "body": "\r\nRequest forbidden by administrative rules. Please make sure your request has a User-Agent header ..."
}

GitHub REST API v3 は User-Agent が必須のため、以下をヘッダに追加して解決:

'User-Agent': 'noinet-antigravity-techblog-sync'

5-3. ページ側のトリガーボタン

Astro 側では、ブログトップに「ブログ同期」ボタンを設置し、 ハンドラ内で /api/run-drive-sync に POST するだけのシンプルな実装とした。

  • ログイン状態は既存の middleware で判定
  • 成功時/失敗時のトースト表示などはフロント側で制御

実際に動かしてみた結果

良かった点

  • ChatGPT → md → Google Drive → GitHub → Cloudflare Pages という 「調査から公開までの一本のレール」が完成した
  • 手元で md を Drive に置くだけでそのうち公開されるため、 「Git の細かい操作を毎回意識しなくて良い」状態になった
  • 手動トリガー(API 経由)も用意したので、
    • 新しい記事をすぐ出したいとき
    • 夜のバッチ実行を待ちたくないとき に即座に Actions を起動できる

気をつけるべき点

  • GitHub PAT の権限設定を最低限にすること(repo + workflow
  • workflow_dispatch を忘れると API から起動できない
  • Google Drive 側のフォルダ構造を増やしすぎると、 スクリプト側のフィルタ条件が複雑になる可能性がある
  • md の frontmatter はテンプレートを守る(キー欠損でビルド失敗する)

今後やりたい改善

  • GitHub Actions の実行結果をフロント側にフィードバックする仕組み
    • 成功/失敗を UI で分かるようにする
  • Cloudflare Pages のデプロイ完了をポーリングして、 「公開完了まで」を一連の UX として見せる
  • Google Drive から取得した md に対して lint / フォーマットチェックを追加し、 表示崩れや frontmatter ミスを早期検知する

まとめ

  • Astro を Cloudflare Pages(Workers モード)で動かしている場合、
    • dist/_worker.js が生成される
    • その状態では Cloudflare Pages の functions/ ディレクトリは使えない
  • ログインやミドルウェアを維持しつつ API を追加したい場合は、
    • src/pages/api/*.ts として Astro の API ルートを実装するのが素直
  • GitHub Actions と Google Drive API を組み合わせれば、
    • 「ローカル or スマホから md を Drive に置くだけでブログに反映」
    • 「必要なときだけ UI から同期ボタンで即時反映」 という運用が実現できる。

今回構築したフローをベースに、 他のコンテンツ(テンプレート集、Tips 系記事、調査ノートなど)も 同じレールに乗せていくことで、 「調査したことを md にして残す」コストをさらに下げていきたい。