WordPressで記事を投稿したらGitHub Actions経由でGridsomeのビルドをする。

Markdownに変換した既存のブログ記事をちまちま修正するのがしんどいのと、WordPressをHeadless CMSとして運用した方が楽だと判断したので、以下の構成でこのブログを運用することにしました。

  • 記事の作成
    • Headless CMS化したWordPress
  • 記事・RSS・サイトマップのビルド
    • Gridsome
  • ホスティング
    • 自分が借りてるVPS

自分以外の人はWordPressにアクセスできないため、セキュリティ対策やプラグインの脆弱性に煩わされることがなくなり、運用の負荷が軽くなったのが最高です。あとテンプレートを修正するのにPHPをいじる必要がないのがうれしすぎる。
今の構成だと、WordPressで記事を投稿したタイミングでGridsomeにビルドしてもらう必要があるのですが、毎回自分でビルドコマンドをたたくのは馬鹿らしいし、手元にNode.jsの実行環境がないとビルドできなくて詰むという問題点があります。GitHub ActionsならWebhook経由でgridsomeコマンドが実行できるので、GitHub Actionsを使ってこの問題を解消することにします。

全体の流れ

前提条件として、サイトジェネレーターでビルドするためのソースコードをGitHub上にpushしておく必要があります。プライベートかパブリックかは問いません。

  1. WordPressのアクションフックを使って、記事投稿時にrepository dispatch eventでGitHub Actionsのワークフローを呼び出す
  2. GitHub Actionsのワークフローでyarn->gridsome buildを実行
  3. ビルド後にdistの中身をVPS(ホスティング先)にrsync

1の作業はWordPressのプラグインの作成が必要で、2,3の作業はGitHub Actionsでワークフローを定義する必要があります。

WordPressのプラグインを作成する

gistにNetlifyのWebhookを叩くプラグインがあったので、それを流用しました。ありがたいです。

GitHubのWebhookを呼び出すにあたり、repoにアクセス権のあるPersonal Access Tokenが必要になるので、ない場合は発行しておきます。

<?php
/**
 * Plugin Name: GitHub Actions build hook
 */

// 記事予約投稿時。私は使わないのでコメントアウト
// add_action('publish_future_post', 'nb_webhook_future_post', 10);

// 記事投稿時
add_action('publish_post', 'nb_webhook_post', 10, 2);

// 記事削除時
add_action('delete_post', 'nb_webhook_post', 10, 2);

// 固定ページ投稿時。私は使わないのでコメントアウト
// add_action('publish_page', 'nb_webhook_post', 10, 2);

//  記事更新時。私の環境では記事投稿時にupdatedのアクションも実行されるため、コメントアウト
// add_action('post_updated', 'nb_webhook_update', 10, 3);

function nb_webhook_future_post( $post_id ) {
  nb_webhook_post($post_id, get_post($post_id));
}

function nb_webhook_update($post_id, $post_after, $post_before) {
  nb_webhook_post($post_id, $post_after);
}

function nb_webhook_post($post_id, $post) {
  $header = [
    'Authorization: token {GitHubのPersonal Access Token}',
    'Accept: application/vnd.github.everest-preview+json',
    // phpのcURLはデフォルトでUAが設定されていない
    // GitHubのAPI実行にはUAが必須
    // http://developer.github.com/v3/#user-agent-required
    'User-Agent: WordPress_webhook_post'
  ];
  $data = [
    'event_type' => '{Workflowで使いたい名前}',
  ];
  if ($post->post_status === 'publish') {
    $url = curl_init('https://api.github.com/repos/{アカウント or organization名}/{リポジトリ名}/dispatches');
    curl_setopt($url, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($url, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($url, CURLOPT_HEADER, true);
    curl_setopt($url, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($url, CURLOPT_HTTPHEADER, $header);
    curl_exec($url);
  }
}
?>

GitHub Actionsでワークフローを定義する

特筆すべきことはないです。他人に知られたくない情報はsecretsに設定して、rsync用のアクションに Burnett01/rsync-deployments: GitHub Action for deploying code via rsync over ssh を使っています。

name: webhook-trigger

on:
  # 手動でも実行したいのでworkflow_dispatchを設定
  workflow_dispatch:
  repository_dispatch:
    types:
      - {WordPressプラグインのevent_typeに設定した名前}

jobs:
  build:
    name: build
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@v2
    # 私の環境では .envにWordPressのURLを設定している
    # - name: echo env
    #   run: echo "${{ secrets.API_URL }}" > .env
    - name: Install modules
      run: yarn
    - name: Run build
      run:  ./node_modules/.bin/gridsome build > /dev/null
    - name: rsync deployments
      uses: burnett01/rsync-deployments@4.1
      with:
        switches: -avzr
        path: dist/
        remote_path: ${{secrets.DEPLOY_PATH}}
        remote_host: ${{secrets.SSH_HOSTNAME}}
        remote_port: ${{secrets.SSH_PORT}}
        remote_user: ${{secrets.SSH_USERNAME}}
        remote_key: ${{secrets.SSH_PRIVATE_KEY}}

これで記事を投稿したときにGitHub Actions経由でビルド結果がrsyncされるようになりました。

問題点

このままでも特に困らないのですが、repository_dispatchがデフォルトブランチでしか使えないのはかなり痛いです。event_typeの値に応じてワークフロー内でブランチを切り替えるとか、工夫が必要です。
ワークフローをトリガーするイベント – GitHub Docs

Note: This event will only trigger a workflow run if the workflow file is on the default branch

そもそも

ホスティングにNetlifyVercelを使っている場合は、Webhook経由でビルドからデプロイまで勝手にやってくれるので、ここまで苦労しません。
Netlifyは運が悪いとSSL周りでトラブルを引きまくるのと、日本からだと遅いので個人的にはVercelを推しています。シンプルでとても使いやすいですし、情報量はNetlifyに劣りますが、ドキュメントを見れば大体わかるので今のところ困っていないです。

Gridsome周りでいくつか質問を受けたので、やる気が出たら詳細を記事にしようと思います。

RTX1200をCATVで使う。

RTX1100をCATVで使う。 – return $lock;で「RTX1200かRTX810の安いやつが手に入ったら使いたい」と書いたのですが、RTX1200を譲っていただきました。うれしいです。ありがとうございます。(NETGEAR JGS524もいただきましたが、こちらはどう使うか考え中です。)

LAN2をWAN(ケーブルモデムに接続)、LAN1をLANで使用することにします。

セットアップ

前回RTX1100をいじった以上の知識は持ち合わせていない状態からスタートします。ドキュメントを見たり、ぐぐりながらいじってみます。

初期化

前面のDOWNLOADとMicroSDとUSBのボタンを同時押ししながら電源を入れます。ちゃんと押せてなかったようで、私は2回失敗しました。指が短い人には難しいですね。

ファームウェアアップデート

RTX1100と違って、初期化するとIPv4のIP 192.168.100.1が振られるのでtelnet 192.168.100.1でアクセスできます。show configでファームウェアのバージョンが初期状態に戻っていることを確認したため、アップデートします。なお、Rev.10.01.76以下は脆弱性があります。

TFTPを使うしかないと思っていたのですが、USBメモリからもファームウェアの更新ができるそうなので、試してみます。
USBメモリを利用してRTX1200のファームウェアーをアップデートする – YAMAHA RTXルーターの操作方法 設定 Tips
USBボタンとDOWNLOADボタンを押すとアップデートできます。楽でいいですね…。特にトラブルも起こらず完了できました。

初期設定

RTX1100の時と変わらないので割愛。時刻同期の設定ができるので追加で設定しました。午前3時7分にntp.nict.jpに問合せします。
40.1 スケジュールの設定

schedule at 1 */* 03:07 * ntpdate ntp.nict.jp syslog

CATV向けの設定

公式サイトの設定例をそのまま流用しました。
CATVインターネットなどイーサネット回線を利用する
RTX1100の設定をしたときは、設定例と同様にDNSサーバのIPを自分で指定したのですが、前回と同じだとあまり勉強にならないので今回はDNSサーバのIPを自動取得する設定にしてみました。
DNS の設定

dns server dhcp lan2
dns server select 500001 dhcp lan2 any .
dns private address spoof on

WAN側の状態を確認してみましたが、正しくIPが取得できていました。

# 状態確認
# show status dhcpc
Interface: LAN2 primary
            IP address: 163.xxx.xxx.xxx/24
           DHCP server: 119.xxx.xxx.xxx
       Remaining lease: 6hours 59min. 16secs.
      (type) Client ID: xxxxxxxxxxxxxxx
Common information
            DNS server: 219.xxx.xxx.xxx
                      : 219.xxx.xxx.xxx
       Default gateway: 163.xxx.xxx.xxx

無事に外につながるようになりました。ポートもふさがっています。

LAN1に接続した端末のIPアドレスの固定

IPが変わると困る端末はIPを固定します。公式サイトにそのまま設定例がありました。
ゲーム端末のIPアドレスを固定する方法 Example for YAMAHA RT Series / Network Game
家の端末は以下のコマンドを使わないと固定できませんでした。

dhcp scope bind 1 192.168.0.2 ethernet 00:80:98:e0:ee:6f

セキュリティ対策

(RTX1100でできたかわからないのですが、)RTX1200ではできるようなのでやってみます。
9.1.17 侵入検知機能の動作の設定

ip lan2 intrusion detection in on reject=on
ip lan2 intrusion detection out on reject=on
# 破棄されたパケットをログに出力する
syslog notice on

1回だけNintendo SwitchがAWS宛てにTCP FIN and no ACKしてるログが出てきたんですが、謎いです。ネット対戦は普通にできていますし、NATタイプもBなので様子見です。

今後必要になるかもしれない設定

今のところ困ってないですが、VPNの接続先によっては設定が必要になるかもしれないです。
IPsecパススルー : コマンド設定 + Web GUI設定

所感

ルータ入れ替え前と入れ替え後でダウンロード速度を計測しましたが、遅くなった等の問題はなく、むしろちょっと速くなったかな(誤差の範囲)?という感じです。

以前使っていたルータではできないブラックリスト形式のアクセス制限や、(今は必要ないですが、前住んでいる家でやりたかった)ポリシーベースルーティングでIPv6 IPoEを使いながら自宅サーバの公開もできるので、積もっていた不満が解消できました。
RTXシリーズでコマンドが統一されているので、RTX1100と特に変わらず設定できるのは本当にありがたいです。あと、使っている人が多いので、調べるとすぐに解決策が出てくるのは最高だなと思いました。使わないけど無駄にVLAN切ってみたくなります。

自宅サーバを外に出そうと思っているので、DMZ周りの設定をしていこうと思います。
自社サーバーを公開(1つの固定グローバルIPアドレス / DMZセグメント : LAN3) : コマンド設定

CompletableFutureのFutureはデザインパターンの名前。

自分の無知を恥じて省みるための記事なので有益ではないです。結論から言うと、「デザインパターンそのものは知らなくても、そのパターンが基になって実装された機能がある」という当たり前のことを、頭の片隅においておこうね、という自分向けの記事です。

(今はよっぽどのことがないとJavaでマルチスレッドプログラミングなんてしないですが、)Java 8でマルチスレッド処理というとCompletableFutureが思い浮かびます。ただ、Futureが機能の名前ではなくてマルチスレッドプログラミングのデザインパターンの名前だというのを、今の今まで知りませんでした。

Future パターン – Wikipedia

第二に promise は単なる言語表現だが、future は現物(actuals)に対する先物(futures)という意味もある(つまり、実際の物に対する代用品)。

今は中身が無いけれど未来に結果が取得できるからFutureなんですね。

もっというとFuture自体はJDK5の時点で導入されてたんですね…。ThreadとRunnableしか使えないと思っていたのですが、全然そんなことはなかった。
8. java.util.concurrentパッケージ2 (2) | TECHSCORE(テックスコア)
ExecutorServiceCallableを送信するとCallableのタスクを別スレッドで開始して、メインスレッドには即Futureのオブジェクトを返すようになっています。パーフェクトJavaにもありますが、Futureをメインスレッドでgetすると結局メインスレッドが停止するので、JDK5しか使えないとかいう環境でない限りCompletableFuture使ったほうがスマートですね。

JavaScriptのPromiseもデザインパターンの名前だったし、なんとなく使ってちゃだめだと反省しました。

参考

このブログをWordPressからGridsomeに移行しました。

このブログをWordPressからGridsomeに移行しました。最初Gatsbyに移行する予定だったのですが、ReactよりもVueを書きたかったので、Vueに詳しい方にGatsbyのVue版がないか聞いたところ、Gridsomeを紹介していただきました。ありがとうございます。
Gatsbyのほうが機能が多いし安定してはいるのですが、私のブログぐらいであればGridsomeでも問題なくビルドできますし、Gatsbyで詰まっていた既存のWordPressテーマからテンプレートへの書きなおしも、Vueなのでかなり進めやすかったです。記事のパス・URLは移行前と同じにしています。
Markdown化した既存記事の修正がまだ終わっていないため、記事の作成とサイトマップの生成はWordPressで行っています。(RSSフィードはGridsomeでビルドしています。)今はWordPressで書いた記事をGridsomeから読んでビルドしてるのですが、これだとWordPressを完全に捨てられないので、ちまちま直していきたいと思います。
メールフォームと記事検索は準備中です。どちらも用意出来たらWordPressからGridsomeへの移行手順を書こうと思います。
Gridsomeの不満点ですが、デフォルトのページネーション機能だと自分が使いたいパスを指定できないのがかなりつらいです。(/page/2を指定したいのに/2にしかできない。)今のところは自作するしかないですかね。
Paginate data – Gridsome
Gridsomeについては、まだバージョンがv1にも到達していないので、今後のアップデートに期待しています。

2020/09/22 追記

GridsomeのテンプレートをGitHubにpushしました。
retrorocket/gridsome-blog: Gridsomeで構築したブログ

1年前に作ったGitHubで名前にエイリアスを設定するやつを公開しました。

GitHubでアカウント名を別の名前で表示できるChrome拡張を作った。 – return $lock;で作って1年放置していたChrome拡張をストアに登録しました。審査早かったです。

GitHubで名前にエイリアスを設定するやつ – Chrome ウェブストア

昨日何となくMOONGIFTさんのGoogle Chrome拡張機能の紹介を眺めていたら、自分の作った拡張が紹介されていたのを見つけてハチャメチャにテンションが上がったので、リファクタリングをあきらめてそのままリリースしました。MOONGIFTさんに紹介されるのは目標にしていたのでとてもうれしいです。
GitHub name Alias Chrome Extension – GitHubのユーザ名にエイリアスを追加してサジェストを使いやすく MOONGIFT