豪鬼メモ

一瞬千撃

BikiBikiBob: ぼくのかんがえたさいきょうのCMS

ブログを自前のシステムに移行すべく、簡単なCMSを書いてみた。はてな記法っぽいテキストファイルを用意して、コマンドを実行するとHTMLが作られるというだけの単純なHTMLコンバータとして実現されている。それでいて、Wikiとしての相互参照機能やブログとしての新着機能やコメント機能も備えているので、一般的なCMSとして十分な機能を備えている。

BikiBikiBob v1.0

Wiki(WikiWikiWeb)っぽくも使えるし、blog(weblog)っぽくも使えるので、それらを混ぜてBikiBikiBobだ。GitHub上にリポジトリを作った。Python3と適当なWebサーバさえあれば動くはずだ。デモサイトも作っておいた。

やりたいこと

当ブログは、はてなブログ上で運用しているのだが、概ね満足して使っている。とはいえ、自前のサーバを運用しているのだから、適当なCMSをそこで動かせば、もっと自由に運用できると常々思っていた。そうなると、WordPressとかを入れるのが普通だろうが、既成品の挙動を学ぶよりも自分で作ってしまった方が楽しそうだ。

せっかく自分で作るのであれば、「ぼくのかんがえたさいきょうのCMS」を追求したい。といっても、多機能性を追求するのではなく、最もシンプルな、ミニマリズムを追求したものにしたい。いわば「僕の考えた最小のCMS」である。それにあたっては、自分のユースケースでの要求仕様を抽出した上で、それを満たすに必要十分な機能のみを実装するのだ。要点は以下のものだ。

  • sshで自分のサーバにログインして、テキスト形式で記事を書く。
  • 記事内に埋め込む写真やイラストや動画は、自鯖や外部サービスに保存していおいたもののURLを記述して済ませる。
  • 基本的にCGI的な動的な機構は持たず、データベースも持たず、静的なHTMLだけをサーブする。

つまるところ、テキストをHTMLに変換するコンバータを一回動かすだけでサイト全体の構築を済ませるCMSを作りたい。それをCMSと呼ぶにはあまりに単純すぎるかもしれないが、それでブログやWikiを十分に運用できることを示したい。静的HTMLを配信するだけならシステムとしての負荷も最小になる。管理すべき元データが単なるファイルなので、GitだのSubversionだのバージョン管理システムと組み合わせるのも容易だし、diffだのgrepだののツールも使えて便利だ。

私はテキストを書く作業のはEmacs上でやるのが常だ。既存のブログサービスで記事を書くとなると、エディタで書いたテキストをブラウザにコピペしてからプレビューする手順になるのだが、それはかなりだるい。修正があったときに、ブラウザ上でちゃちゃっと作業できる程度ならばよいのだが、時にエディタに戻って大幅に修正することもあり、そうするとまたコピペの作業が必要になる。それなら、自鯖sshログインして執筆作業を行いつつ、コマンド一発動かしてプレビューした方が楽だし早い。

作業用のディレクトリに入って適当にテキストファイルを書いてから、コマンドを実行すれば、全てのHTMLファイルを作り直してくれるようにしたい。数百くらいのファイル数なら1秒もかからずに終わるので、個人サイトでは差分更新の仕組みは必要ない。とはいえ、プレピュー作業の間は対象のファイルだけを更新して効率化する機能も入れよう。

$ ./bbb_generate.py
INFO	Process started: conf=bbb.conf
INFO	Config: {'input_dir': '/home/mikio/dev/bikibikibob/input', 'output_dir': '/home/mikio/dev/bikibikibob/output', 'script_file': '/home/mikio/dev/bikibikibob/bbb.js', 'style_file': '/home/mikio/dev/bikibikibob/bbb.css', 'title': 'BikiBikiBob', 'subtitle': 'the minimalist CMS', 'author': 'Bob Doe', 'language': 'en'}
INFO	Creating article: /home/mikio/dev/bikibikibob/input/article-format.art -> /home/mikio/dev/bikibikibob/output/article-format.xhtml
INFO	Creating article: /home/mikio/dev/bikibikibob/input/duplicated1.art -> /home/mikio/dev/bikibik
...
INFO	Process done: elapsed_time=0.03s

厳密に言えば、Webブラウザ上で記事を編集できない時点で、このシステムはWikiの要件もブログの要件も満たしてはいない(よってWikiともブログとも名乗っていない)。しかし、必要であれば別途のテキスト編集Webシステムを利用すればいいし、その他の機能も追い追い拡張していけるように設計している。UNIX哲学に基づいてひたすら疎結合にしているのミソだ。

記事の記法とレンダリング

入力データには、はてな記法Wikipedia記法を混ぜたような書式を採用している。以下のような感じだ。

@title 鳩サブレーが食べたい春の一日
@date 2024-04-23

鳩サブレーとは、[[豊島屋]]の看板商品であり、[[鎌倉]]の名物としてお土産にされることが多い。
缶入りのがなんか[[いい感じ|EAST END×YURI]]だ。

@image https://dbmx.net/misc/hatosable.jpg [caption=鳩サブレーの缶] [width=60]

* 由来

鳩サブレーは、豊島屋の初代店主である久保田久次郎が来店した[[外国人]]からもらった[[ビスケット]]が原点である。以下の原料を使っている。

- フレッシュバター
- 小麦粉
- 砂糖

** 名前

鳩サブレーは[[明治時代]]末期の発売当初には「鳩三郎」とも呼ばれていた。

* 製品

|商品|値段|おすすめ度
|鳩サブレー 10枚入り袋|1350円|◯
|鳩サブレー 4枚入り手提げ|615円|◯
|鳩サブレー 16枚缶入り|2430円|◎

HTMLに変換してからPC上のWebブラウザレンダリングすると冒頭のスクショのようになる。携帯端末で表示する場合にも、マージンを減らしたり、文字が十分な大きさになるようにして、本文を読みやすいようにしている。

リンクはWikipediaと同じ形式で、二重の角括弧で囲んだ部分がアンカーになり、中身の文字列と同一のタイトルの記事が行き先になる。行き先のタイトルとアンカーのテキストが異なる場合、縦棒を挟んで行き先のタイトルを指定することができる。アンカーテキストまたはタイトルがURLの場合、そのURLが行き先になる。よって、外部サイトを参照することも可能だ。

文字列の装飾は、HTMLタグで表現できる範囲ではほぼ全てのものが網羅されている。ルビも使えるのが私なりのこだわりだ。全てを記法でサポートすることで、HTMLタグを記事内に入れ込むという醜悪な仕様から抜け出すことができる。

複雑な表もHTMLタグを書かずに記載できるようにした。rowspanやcolspanの調整もできる。Wikiっぽく説明的な文章を書くのに表は欠かせない。

記事のメタデータとして、@titleでタイトルを、@dateで日付を指定する。日付が指定された記事は日付のインデックスに反映されるので、トップページに日付のインデックスを置けば、ブログとしても機能することになる。つまり、Wikiとブログの両方の機能を持つ。このCMSには記事の公開/非公開を区別するのドラフト機能のようなものはないが、日付が指定されていない記事は日付のインデックスに反映されないため、ドラフトと同じような扱いにできる。

旅ブログ的な使い方をする予定なので、写真と動画が簡単に掲載できるようにした。私は写真を自鯖に置いているので、その永続URLを書けば埋め込まれるようにしたい。また、段組みとキャプションも簡単に設定できるようにしたい。例えば二段組の場合はこんな感じだ。

@image https://example.com/data/hikawa.jpg [caption=氷川丸] | https://example.com/data/nihon.jpg [caption=日本丸]

わざわざテーブルのHTMLを書かなくても自動的に段組みとサイズの調整がなされる。端末での編集作業と並んで、この段組機能が欲しかったからこのCMSを開発したようなものだ。同じノリで、@videoと書けば自鯖にある動画データ貼れるし、@youtubeと書けばYoutube上の動画も貼れる。こちらのデモページもご覧いただきたい。

インデックスとページ遷移

ブログとして運用する場合、記事を時系列に並べたリストをインデックスとしてトップページに記載するのが必須である。降順の時系列で記事を並べて、新しい記事を定期的に配信するのがブログの役割だ。そうすれば、記事の間にハイパーリンクを貼る努力を必ずしもしなくてもよい。一方で、日付をいちいち指定する努力をしない代わりに、目次や索引のようなページを自分で管理するならば、Wikiあるいは普通のWebサイトのような位置づけで使えるようになる。

@dateで日付を指定した記事が時系列のインデックスに反映されることは既に述べたが、@titleでタイトルを指定した記事もタイトル順のインデックスに反映される。あとは、それらを表示するページを作れば良いだけだ。以下のような記事を書けば良い。

* 新着記事

@index [order=date] [reverse=false] [max=10]

日付順の記事には前後関係が定義できる。よって、日付のある記事同士は、ページの最後に「前の記事」「次の記事」のリンクが自動的に貼られる。これもブログとして運用する上では重要な機能だ。

ところで、はてなブログでもアメーバブログでも、どうやら左側にある「前」リンクは「より新しい」を意味し、右側にある「次」リンクは「より古い」を意味しているっぽい。つまり左から日付の降順に並んでいる空間を右に向かって進んで時を遡るメタファーだ。私はどうもこれが気に食わない。時間は過去から未来に進むのであり、横書きの本は左から右に進むのであるから、「前」および「左」は「より古い」を意味して、「次」および「右」は「より新しい」を意味してほしい。なので、私のCMSでは当然そうする。自分で作るとこういうところを好きにできるから良い。

フィードバック系の機能

日常生活や旅や趣味を通じて思ったことやメモを書きなぐるのが目的なので、コメントなどのフィードバック機能は必要ない。静的HTMLを生成して公開するだけだ。この割り切りをすることで、システムをめちゃ単純にできたのだ。しかしながら、わざわざインターネットに公開するのだから、少数ながら読者がいるだろう。その中でたまに感想とか有用な情報とかをくれる人がいるかもしれない。有名どころのシェアボタンと、簡単なコメント機能くらいはつけておいてもいいだろう。

シェアボタンは、とりあえずTwitterとLineとFacebookはてなブックマークのものに対応しておいた。各所の公式の方法でボタンを作って貼り付けただけだ。記事の下に表示されるが、どれとどれを表示するかは設定ファイルで選べるようになっている。

コメント機能は、各記事の末尾に名前と任意の文字列を書き込めるようにする機能だ。書き込まれたコメントは時系列で表示される。

コメントの通知機能はないが、記事横断で時系列にコメントが並べられたパネルを表示する機能がある。これを見ればコメントを見逃すことはないだろう。

コメント機能を実装するとなると、自分でデータを更新する必要があるので、もはや静的HTMLでは対処できない。仕方ないので、JavaScriptからXMLHttpRequestでバックエンドと通信してコメントの投稿と一覧取得ができるようにした。バックエンドは何でもいいのだが、簡単なCGIスクリプトで対応した。REST APIとしては、"comment.cgi?action=list-comments&resource=how-to-install&max=10&order=desc" みたいな感じで一覧を取得して、"comment.cgi?action=post&resouce=how-to-install&author=ichiro%&text=hop%20step%20jump&nonce=12345" みたいな感じで投稿する。

セキュリティは、ザルだ。投稿前に取得したnonceを送り返させるだけで、特にユーザ認証などはしない。nonceはリソース名と直前の投稿のnonceで再計算されるので、投稿毎にnonceは異なる。リファラとnonceのチェックを組み合わせれば、HTMLのアンカーに投稿用URLを仕込むとか、フォームの送信先に投稿用URLを仕込むとかのカジュアルな攻撃には耐えられるようになる。それでも簡単なスクリプトを書けばスパム攻撃できてしまうが、個人サイトとしてはこれ以上のセキュリティは必要ないだろう。chaptchaとか仕掛けても一般のユーザの利便性を下げるだけで、プロのスパマーには通用しない。また、各投稿のデータ容量を1KBとかに制限して、リソース毎のコメント容量を1MBとかに制限しておけば、データが溢れてサーバが死ぬことはないだろう。

投稿データは、リソース名に紐付けたファイル名のTSVファイルに追記して管理する。データベースはここでも使わない。タイムスタンプ、IPアドレス、著者名、テキストを各行に保存する。投稿の排他制御はプリミティブにflockで行う。TSVファイルにしておくことで、grepで内容を検索したり、スパム攻撃された際にgrep -vでフィルタしたりするのが簡単になる。

タグ検索機能

各記事にはタグをつけることができる。タグは記事の下端に表示され、同じタグを持った記事同士には自動的に相互リンクが貼られる。

タグの一覧機能もあり、多く使われたタグから先に表示されるようになっている。

タグ機能を使うということは、一つのサイトに複数のトピックが混ざるということだ。このブログのように、写真の話と自転車の話とプログラミングの話が混ざるようなサイトでは便利だと思う。ただ、複数のトピックがあるならサイト自体を分けちゃった方がいいんじゃないかとも思うので、自分でタグ機能を使うかどうかはまだ決めかねている。

タグ検索よりは、全文検索があった方がいいと思っている。ただ、自分で使う分には、サーバ上でgrepを動かせば用は済むので、あえてブラウザ経由で使えるようにする動機づけはあまりない。いずれにせよ、実運用をしながら記事数が増えてきた時に考えることにする。

今後の予定

とりあえず私がWebサイトを運用するにあたって必要な機能は全て実装した。あとは、使うだけだ。自転車と釣りとカメラのネタは、このCMSを使った新しいサイトに移行する予定だ。

追加したい機能を強いて挙げるとすれば、画像管理機能だろうか。任意の画像(というより任意のファイル)をサーバ上に保存した上で、そのパーマリンクを返すだけのCGIを実装するかもしれない。あと、記事に絶対URLで埋め込まれた画像を自前のサーバ上に保存した上で、記事内の参照URLを書き換えて、self-containedなサイトに変換するという機能がちょっと欲しいかなと思っている。いずれにせよ、実運用しながら追い追い欲しい機能を追加していく所存だ。