道具の手触りや暮らしの思索を重ねながら、革しごとやガジェットに向き合う日々の試行錯誤の記録

デジタル手仕事 07|Obsidianを身軽に保つ。MinIOとCloudflareでokadai.meの画像を支える

デジタル手仕事
デジタル手仕事 07|Obsidianを身軽に保つ。MinIOとCloudflareでokadai.meの画像を支える
目次 ▼
嫁ちゃん 嫁ちゃん
なんか最近、Obsidianのノートが他のMacに反映されへんことない?
わたし わたし
それな。Syncの容量が上限に達してたみたい。革しごとの写真とか、スクリーンショットとか、気づいたら1GB全部埋まってて。
嫁ちゃん 嫁ちゃん
じゃあ上のプランにすればええんちゃうの?
わたし わたし
月額上げる前に、「そもそも置かなければいい」と思って。画像だけ外に逃がすことにした。

1GBという壁に気づいた日
#

Obsidian Syncは、私の執筆環境の中心にある。

手元のM4 Mac miniで書いたMarkdownが、クラウドを経由して自宅サーバーのM1 Mac miniへ届き、Hugoがそれを静的ページへと焼き上げる。その一本の流れが快適に動くのも、Obsidian Syncが何も考えなくてよい同期を実現してくれているからだ。以前は自前プラグインやiCloudで試行錯誤を重ね、ファイルが衝突するたびに嫌な手触りが残った。納得して有料プランへ切り替えたのは、正しい判断だったと今でも思っている。

ところがある日、手元のMacで下書きを書き直して保存したにもかかわらず、M1 Mac miniで立ち上げているサーバーの方に変更が届いていないことに気づいた。念のためiPhoneのObsidianを確認しても、更新されていない。

Obsidian Syncの設定画面を開いて、ようやく理解した。ストレージ使用量が上限に張り付いていた。

Standardプラン($4/月・年払い)は1GBが上限だ。ひとつのVaultで手元の思考すべてを一元管理しようとした結果、革しごとで撮った革の質感の写真、工房での作業記録、スクリーンショット、そしてブログ記事のためのPDF資料が、少しずつ積み上がっていた。上位のSync Plus($8/月・年払い)に切り替えれば10GBまで使えるが、容量が増えてもやがてまた同じ問題に直面するだろうという予感があった。

「プランを上げるより、置かない方がいい。」

その一言が、自然と浮かんだ。


画像だけ外へ逃がす
#

画像とPDFをVaultの外に追い出し、ObsidianではURLで参照するだけにする。文字情報だけをObsidian Syncに同期させれば、1GBの上限は当面の問題ではなくなる。

Mac miniと外付けHDDに光の糸が流れ込む

では、どこに置くか。

選んだのはMinIOだった。AWSのS3と同じAPIを話す、自前で立てられるオブジェクトストレージだ。クラウドのストレージサービスを使う選択肢もあったが、どうせM1 Mac miniは24時間回っている。追加の月額費用をかけず、そこに同居させれば足りる話だった。

OrbStack上のDockerでコンテナを立ち上げ、バケット名を lili-memory-image-bucket とした。実データの置き場所には外付けHDD(/Volumes/HDD4TB/minio_data)を充てた。内蔵ストレージは静かに使い続けたいという気持ちがあったので、重いバイナリはHDDに背負わせることにした。

そしてもうひとつ、こだわったのはURLだ。

MinIOに上げた画像を http://192.168.10.15:9000/... というローカルアドレスで参照していては、ブログ記事に埋め込んだとき外部から画像が見えない。Cloudflare TunnelとNginxの設定を通じて、https://okadai.me/lili-memory-image-bucket/... というドメインでひけるURLを確立した。Obsidian上で書いた記事のMarkdown画像記法がそのまま、Hugo公開時の画像として機能する。VaultとブログがひとつのURLで繋がったことで、執筆フローに余計な変換処理が入らなくなった。


TOMLに2回、足を踏み抜いた
#

実はこの構築、スムーズには進まなかった。MinIOでの配信URLが通ったので、さっそくサイトのトップを飾るヒーロー画像もここからランダム表示させようと試みたのだが、Hugoの設定ファイルに画像URLのリストを定義する段階で、TOMLの文法に2回しっかりと足を踏み抜いた。

Neovimでconfig.tomlを開きながらメモを書く

一発目:配列がルートパラメータとして認識されない
#

ヒーロー画像をアクセスのたびにランダムで切り替えるために、config/_default/params.toml にMinIOのURLリストを配列で書いた。テンプレート側からは .Site.Params.hero_images として読み取るつもりだったのだが、Hugoのビルドを走らせても画像が一切表示されない。

MinIOのコンテナを curl で叩いてみると 200 OK が返ってくる。画像自体は生きている。ではなぜ表示されないのか。

生成されたHTMLを地道に読んでいくと、テンプレートが参照しているパラメータが null になっていた。じっくり params.toml を読み返して気づいた。[advertisement] というテーブル宣言の直後に hero_images = [...] を書いていたのだ。TOMLは記述の文脈を解釈する。テーブル宣言の下に書かれた変数は、そのテーブルの子として扱われる。私が書いたつもりのルートパラメータは、advertisement.hero_images として解釈されていたのだった。

解決策は単純だった。hero_images の配列定義をファイルの先頭行に移動する。それだけで、テンプレートは正しくリストを拾えるようになった。

二発目:jsonifyで配列が文字列に化ける
#

画像リストはテンプレートからJavaScriptへ渡して、ランダム選択の処理をさせる構成にしていた。テンプレートで {{ site.Params.hero_images | jsonify }} と書き、JavaScript側でそれを受け取る。

ビルドは通る。だが、画像が表示されない。

また curl でMinIOを叩く。200 OK。またHTMLを読む。JavaScriptに渡っているはずの配列を確認してみると、こんな文字列が来ていた。

"[\"https://okadai.me/...\",\"https://okadai.me/...\"]"

配列がエスケープされて、文字列になっていた。JavaScript側はこれを配列として扱えず、文字列のインデックスで各文字("h", "t", "t"…)を順番に取り出して src に当てていたのだ。画像URLの先頭の "h" を読み込もうとして、当然何も表示されない。

jsonify パイプライン単体では、出力をHTMLエスケープしてしまう。解決策は safeJS を繋ぐことだった。

{{ site.Params.hero_images | jsonify | safeJS }}

safeJS を添えることで、エスケープが解除され、JavaScriptが扱える生の配列リテラルとして展開された。エラーログを読むより、生成されたHTMLを開いて目で確認したことで、ようやく真因が見えた。


Cloudflareのエッジで画像をキャッシュさせる
#

MinIOの画像URLが確立しても、リクエストのたびに自宅のM1 Mac miniまで問い合わせが走るようでは、画像の表示が遅い。距離があるし、Mac miniの処理もその都度発生する。

Cloudflare TunnelとCache Rulesを組み合わせた。/lili-memory-image-bucket/ 以下のパスへのリクエストをCloudflareのエッジでキャッシュするよう設定することで、2回目以降のアクセスは自宅サーバーまで届かず、世界中に散らばるCloudflareのサーバーから即座に返ってくるようになった。

体感的な表示速度は上がり、M1 Mac miniへの負荷も落ち着いた。


Vaultには文字だけを置く
#

運用を始めてから、Obsidian Vaultが目に見えて身軽になった。

ローカルに放り込むだけだった頃に比べれば、外部ストレージを介するという一段のステップは増えた。けれど、Obsidianのエディタ上で挿入したい位置にドラッグ&ドロップすると、バックグラウンドでMinIOへのアップロードが走り、登録後のURLが自動でMarkdownの画像記法として差し込まれる仕組みを整えた。一手間といっても、慣れてしまえば手のひらに馴染む所作だ。

ブログ記事の画像と、プライベートなメモ用の画像が同じバケットに混在しているのが気持ち悪い、という感覚もある。公開設定はURL直接アクセスのみでフォルダ一覧は遮断しているので実害はないのだが、将来的にバケットを「ブログ用」と「プライベート用」に分ける引き算も考えている。今は急がない。

黒背景にグリッドで並ぶ自作のサムネイルギャラリー
MinIOのWebコンソールでは画像を一枚ずつしか確認できなかったので、Hugo の static/ にシンプルなHTMLを1枚置いて、自作のサムネイルギャラリーを作った。黒背景にグリッドで並ぶ画像の一覧。機能として必要だから作ったのだが、眺めていると、積み重なってきた記録の重みのようなものが伝わってきて悪くない。

Obsidianには文字情報だけを置きたい、という感覚が私にはある。思考のための道具に、バイナリを詰め込みたくない。画像を外出しすることで、VaultはMarkdownのファイルたちだけが静かに並ぶ場所になった。それが、私の引き算の美学に、ちょうど合っている。

私自身のObsidian運用は、Tiago Forte著の「セカンドブレイン」のCODEフレームワークに近い。気になったことや記事のネタ、日々の思いをとにかくVaultに集め(Capture)、フォルダやAIの力を借りて整理する(Organize)。Distillは苦手で、ニュアンスが消えるのが嫌なので、基本的にログとして残している。そしてブログ記事として書くこと、暮らしを便利にするプログラミングに落とし込むことが、私にとってのExpress(表現)なのだと思う。

嫁ちゃん 嫁ちゃん
で、ちゃんと同期されるようになったん?
わたし わたし
うん。Vaultが軽くなったら、すっと同期されるようになったわ。
嫁ちゃん 嫁ちゃん
そっか。じゃあ次の記事、どんどん書けるな。
わたし わたし
そうやね。ツールの話より、革の話の方が書きやすいんやけどな。

※本サイトの漫画やイラストはフィクションであり、実在の製品・団体・人物・地名とは関係ありません。

※本サイトに掲載する情報には充分に注意を払っておりますが、その内容について保証するものではありません。

※本サイトの使用ならびに閲覧によって生じたいかなる損害にも責任を負いかねます。