私のブログ自動化プロセス

✍🏼 作成日 2021年12月21日    💡 更新日 2022年01月19日
❗️ 注意:この記事が作成されてから既に 日が経過しています。情報の鮮度にご注意ください

以前、ある学生からブログの仕組みについて聞かれた時、私は「Github PagesのJekyllを使い、ビルド後のHTMLをホスティングしている」と答えました。しかし、この説明は少し大雑把で、実際には様々なサービスを統合する作業が含まれています。そこで、ここでは詳細を説明するとともに、コードも公開します。

全体の流れ

Markdownファイルの全体的なフローチャート:

Image

ブログ画像の全体的なフローチャート:

Image

画像処理に関する補足説明:

  1. ローカル開発時、画像リポジトリはブログリポジトリのサブモジュールです。ブログ記事に新しい画像を追加した場合、まず画像を画像リポジトリにプッシュし、リポジトリのアクションがTencent Cloud COSに同期するのを待ってからブログコードをプッシュする必要があります。そうしないと画像参照リンクが見つかりません。

  2. ブログ記事を書く際、画像を参照する時は相対パスを使用します。Jekyllには3つの設定ファイルがあり、それぞれローカル開発用、.comサイト参照用、.cnサイト参照用に使われます。3つのymlファイルの唯一の違いは静的リソースの参照方法です:

  3. ローカル設定ファイル:static_url: /static

  4. .com設定ファイル:static_url: https://static.xheldon.com

  5. .cn設定ファイル:static_url: https://static.xheldon.cn

  6. 画像リソースを参照する必要がある場合、markdown内での記述方法は:\!\[\]\(https://static.xheldon.cn/img/in-post/qing-zheng-lu-yu/IMG_3789.png) となります(JekyllはまずMarkdownファイルを処理し、中のLiquid変数を置換した後、HTMLにビルドします)。

きっかけ

以前、Notionをデータソースとして使用し、ブログを更新したいと考えていました。そのためにはサーバーが必要だったので、Tencent Cloudの軽量サーバーをNotionデータ取得用のサーバーとして購入しました。効果はこちらで確認できます:

購読&有料ソフトウェア - Xheldon Blog

さらに、もう一つの理由は、ブログを書く際には必ず画像を掲載する必要があることです。最初はjsDelivrのGithubリポジトリ加速サービスを使用していました:

jsDelivr - オープンソース向けの無料で高速かつ信頼性の高いCDN

しかし、困ったことにjsDelivrの加速サービスには各リポジトリに対して50MBの制限があり、公共のjs/cssなどを加速するには適していますが、画像には明らかに向いていません。

このため、私がサーバーを所有している状況では、答えは明白でした:もう一つドメインを購入し、CDN加速サービスを有効化すればよいのです。

そしてさらに考えを進めると、すでにドメインを購入しているし、私のブログにアクセスするユーザーのほとんどが中国大陸のIPであるなら、なぜ中国版のブログも作らないのでしょうか?ドメイン名はhttps://xheldon.cnにしましょう。

というわけで、すぐに行動に移しました。以下がその過程のまとめです。

サーバーとドメイン

購入

サーバーはTencent Cloudの軽量サーバーを購入しました。4コア8GBメモリ、4M帯域幅で、大割引(キャンペーンで0.数割の価格)の時に購入したので非常に安く済みました。

ドメインを購入した後、中国大陸では当然ながらICP登録が必要です。登録がないとドメインの解析が許可されず、そのドメインにアクセスすると「このドメインは登録されておらず、解析が停止されています」などのメッセージが表示されます。幸いTencent Cloudは無料でICP登録代行サービスを提供しており、現在はプロセスも大幅に簡素化されています。Tencent Cloudの登録申請フォームに、自宅の住所、連絡先、ウェブサイトの用途、申請理由などの情報を記入するだけで済みます。もし申請理由欄に「早く登録しろ」などと書いてしまうと、通信管理局(ドメイン審査を行う政府機関)に直接提出された時点で却下されるでしょう。記入内容に問題がある場合、Tencent Cloudの担当者から連絡があり、修正を確認した上で提出されます。

設定

設定には以下の手順がありますが、詳細は割愛します:

  1. 無料のHTTPS証明書を申請し、HTTPSを有効化する。

  2. CDN加速ドメインを設定する。

  3. 画像保存用にCOSを購入する(新規ユーザーには無料で提供されます)。

  4. ExpressのNodeサービスを起動し、Nginxリバースプロキシを通じて外部に80ポートを公開します。サーバーはGiteeから静的HTMLリソースをプルし、いくつかのAPIリクエストに応答します。なぜ直接Githubからプルせず、わざわざGiteeから行うのかというと、これらのAPIリクエストには以下のようなものがあるからです:

  5. GiteeからのWebhooksに応答し、サーバーに最新のHTMLファイルをGiteeからプルするよう通知します。

  6. ブログからのNotionクエリリクエストに応答し、サーバー側でNotionのサーバーにリクエストを送信してクエリを実行します。

いくつか説明が必要な点があります:

  1. 軽量サーバーとしては、Node + NginxのDockerイメージを選択しました。Javaベースのものやカスタムイメージを選んでも構いません。

  2. 軽量サーバーにデフォルトでインストールされているNodeとNginxは、lighthouseユーザー権限でインストールされています。パッケージインストール時に権限不足のエラーが頻発したため、手間を省くためにデフォルトのNginxとNodeを削除し、rootユーザーで再インストールしました。

  3. サーバーにFTPでファイルをアップロードする必要がある場合があります。前述のSSL証明書などです。FTP関連の設定が必要で、Tencent Cloudには関連ドキュメントがありますので、検索すれば見つかります。ここでは割愛します。

  4. 登録には少なくとも1週間以上かかります。私の場合は2週間ほど待ちました。

リポジトリ設定

Github Actionsで静的ファイルをビルドする設定

ブログのソースコードファイルを公開したくないこと、またGithub PagesがサポートするJekyllプラグインには制限があり(例えばホームページだけでなくカテゴリページでもページネーションを行いたい場合など)、私の要件を満たせないため、ソースコードをHTMLにビルドすることにしました。

また、Github Pagesの無料版の制限により、プライベートリポジトリでGithub Pagesを有効にすることができないため、別のリポジトリを公開設定にし、ソースコードリポジトリはプライベートに設定しています。コードをプライベートリポジトリにプッシュすると、Github Actionでビルドされた後、その公開リポジトリにプッシュされます。

Github PagesとGithub Actionの使用については、以前私が書いた以下の記事を参照してください:

プライベートリポジトリで無料公開するGitHub Pages - Xheldon Blog

ただし、国内ドメインを追加で取得したため、設定ファイルにもいくつか変更がありました。以下が新しい設定ファイルです:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
name: 博客打包任务

# 代码 push 到 master 分支的时候运行该 workflow
# TODO:不运行 commit 信息中包含特定关键词的 push
on:
push:
branches: [ master ]

jobs:
Build:
runs-on: ubuntu-latest

steps:
- name: 检出分支
uses: actions/checkout@v2
with:
persist-credentials: fasle # false 是用 personal token,true 是使用 GitHub token
fetch-depth: 0 # 保证能够 push 成功

# 设置 ruby 环境
- name: 设置 Ruby 环境
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
ruby-version: 2.6

# 安装依赖
- name: bundle 安装依赖
run: bundle install

# 打包静态资源
- name: 构建 xheldon.com 页面
if: ${{ startsWith(github.event.head_commit.message, 'com') || startsWith(github.event.head_commit.message, 'all') }}
run: bundle exec jekyll build

- name: xheldon.com 写入信息
if: ${{ hashFiles('./_site') }}
working-directory: ./_site
run: |
echo "www.xheldon.com" > CNAME
echo -e "# [Xheldon's blog](https://www.xheldon.com)" > README.md

- name: 推送到 x_blog 仓库
if: ${{ hashFiles('./_site') }}
working-directory: ./_site
run: |
pwd
git init
git checkout -b master
git add -A
git -c user.name='github actions by ${{github.actor}}' -c user.email='NO' commit -m '${{github.event.head_commit.message}}'
git push "https://${{github.actor}}:${{secrets.X_BLOG_SITE}}@github.com/Xheldon/x_blog.git" HEAD:master -f -q

- name: 构建 xheldon.cn 页面
if: ${{ startsWith(github.event.head_commit.message, 'cn') || startsWith(github.event.head_commit.message, 'all') }}
run: bundle exec jekyll build --config=_config.cn.yml -d _site_cn

# gitee 和 github 的用户名一样
- name: 推送到 x_blog_cn 仓库
if: ${{ hashFiles('./_site_cn') }}
working-directory: ./_site_cn
run: |
pwd
git init
git checkout -b master
git add -A
git -c user.name='gitub actions by ${{github.actor}} push to gitee' -c user.email='NO' commit -m '${{github.event.head_commit.message}}'
git push "https://${{github.actor}}:${{secrets.X_BLOG_SITE_CN}}@gitee.com/Xheldon/x_blog_cn.git" HEAD:master -f -q

別のリポジトリを設定し、Github Pagesを有効化

この手順については特に説明する必要はありません。Githubリポジトリの設定を有効にするだけです。

Conding Webhooksの設定

Giteeのリポジトリ管理-Webhooksで、あなたのWebHookアドレスを設定します。私の場合はhttps://www.xheldon.cn/hooks_cn_push。です。

サーバー側のWebhooks応答設定

サーバー側ではExpressサービスを起動しています。コードは以下の通りです:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
app.post('/hooks_cn_push', async (req, res) => {
// Note: 收到 x_blog_cn 的 webhooks, 执行 git pull, 将 x_blog_cn 拉取到 public 目录
// Note: 签名验证,暂时略
// Note: 拉取分支
const branch = req.body.repository.clone_url;
const headers = req.headers;
console.log('headers:', headers);
// Note: 验证一下 header 的合法性
if (
headers['x-gitee-event'] === 'Push Hook'
&& headers['x-gitee-token'] === GITEE_WEBHOOKS_SECRET
) {
exec(`sudo rm -rf ./_public && sudo git clone ${branch} ./_public && sudo rsync -chir --delete ./_public/ ./public/ && sudo rm -rf ./_public`, {
cwd: './',

}, (err, stdout, stderr) => {
if (err) {
console.log('err:', err, stderr);
res.status(400).send({
msg: '服务器内部 git clone 仓库失败:',
stderr,
stdout,
err,
})
} else {
// Note: stdout 没有任何输出表示正常
console.log('out:', stdout);
if (!stdout) {
res.json({
msg: '没问题',
status: 200,
stderr,
stdout,
err,
});
} else {
res.json({
msg: 'git clone 的时候返回了一些内容,请过目',
status: 200,
stderr,
stdout,
err,
});
}
}
});
} else {
res.json({
msg: '恶意请求!',
status: 403
});
}
});

画像リポジトリのAction設定

画像を画像リポジトリx_blog-staticにアップロードすると、Github Actionがトリガーされ、Tencent Cloud COSに増分更新されます。コードは以下の通りです:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
name: 腾讯云 cos 同步任务

# 代码 push 到本仓库的时候触发该 ci
# 分析提交的 commits(不只是 head commit),将全部的 added modify 归结到一起(去重)调用上传方法
# 将 delete 的,调用删除方法
on:
push:
branches: [master]

jobs:
CheckOutAndUpload:
runs-on: ubuntu-latest

steps:
- name: 检出分支
uses: actions/checkout@v2
with:
persist-credentials: false
fetch-depth: 0

- name: 设置 node 环境
uses: actions/setup-node@v2.4.1
with:
node-version: 14.x
architecture: x64
cache: npm

- name: 安装依赖
run: npm i

- name: 运行上传脚本
uses: actions/github-script@v5
env:
COS_SECRET_ID: ${{secrets.COS_SECRET_ID}}
COS_SECRET_KEY: ${{secrets.COS_SECRET_KEY}}
COS_BUCKET: ${{secrets.COS_BUCKET}}
COS_REGION: ${{secrets.COS_REGION}}
with:
script: |
const script = require('./upload.js')
await script({github, context, core})

もちろん、COSなどの秘密鍵といった環境変数があります。これらは前述のGithub Actionの記事を参照し、自身のリポジトリで設定してください。

上記ではjsファイルを実行しており、コミットの状況を取得し、今回のコミットで何が追加、削除、変更、リネームされたかを確認した後、一括でアップロードします。リポジトリは公開されており、こちらにあります。

今後の計画

この記事で述べられているように、本記事もCraftのプラグインを使ってGithubリポジトリに同期されています。その利点については既に詳しく説明されているので、ここでは繰り返しません。現在唯一の問題は、Craftから挿入された画像の扱いです。公式ではCORS制限のないfetch API(Macのみ利用可能)が公開されていますが、Craftにアップロードされた画像をTencent Cloudにエレガントに同期する方法についてはまだ検討中です。

現在の計画では、Githubに画像を保存する手順を廃止し、Craft内の画像をextension経由で直接Tencent Cloud COSに送信する予定です。

- EOF -
この記事の初出: 私のブログ自動化プロセス - Xheldon Blog