Hyperuplink: Discuss like it's 1998

Marius

Hyperuplink:1998年のように語り合おう

原文は Marius により に公開されました。 このブログを購読する

Hyperuplinkを紹介しよう。モダンなHTML5/CSS3で構築され、100% JavaScriptフリーのインターネット掲示板ソフトウェアだ。単一バイナリとして提供され、主要なプラットフォームのほとんどに対応し、ランタイム依存関係なしで動作し、PostgreSQLを使用する。

Hyperuplink:1998年のように語り合おう

2400ボーのモデムがキーキーとハンドシェイクする音、Turboモードで唸るコンピューター、そして午前3時に青白く明滅するCRTの光。その狭間に、インターネットには確かに何かがあった。ここ20年ほどの間にすっかり失われてしまった何かが——だ。生身の人間たちによるコミュニティが掲示板の周りに集まり、たった一度のリクエストで読み込まれる掲示板で、ありふれたハードウェアさえあればトピック一覧を表示するだけでCPUの半分を奪われることもなく、世界全体を画面に呼び出すことができた。もしあなたがあの短くも奇妙な歴史の瞬間を生きていたなら、きっと今でもあの頃への愛着を抱いているだろうし、そこで見つけたインターネットの奇妙な片隅や、探索に費やした何時間ものこと、そして途中で出会った友人のことを覚えているはずだ。

Hyperuplinkは、そうした記憶とそれにまつわる感情を瓶に詰め、2026年の今でも意味をなすものへと注ぎ込もうという私の試みだ。Telnetを必要とせずブラウザの中で動くモダンなインターネット掲示板であり、サーバーサイドで適切かつモダンなHTML5とCSSをレンダリングし、100% JavaScriptフリーで動作し、外部ランタイムもインタプリタもFastCGIも、/var/wwwnode_modulesもなく、ディスク上に散らばる余計なファイルひとつない、単一の静的リンクバイナリとして出荷される。PostgreSQLサーバーやクラスタ全体と通信でき、Redis互換のキャッシュを活用し、ノスタルジックなものからモダンなものまで多彩なテーマを備えている。

さらに重要なのは、Hyperuplinkは楽しいということだ。自分自身を深刻に捉えすぎることもない。phpBBのランタイム環境との格闘や、Discourseの壊れたJavaScript UIにうんざりしたすべての人のためのフォーラムソフトウェアなのだ。そして、そう、Hyperuplinkもまた、本当にllamaのケツをぶっ叩く。

===========================================================================
                     NOW DIALING ...  ::  CARRIER DETECTED
===========================================================================

     █   █ █   █ ████  █████ ████  █   █ ████  █     █████ █   █ █   █
     █   █ █   █ █   █ █     █   █ █   █ █   █ █       █   ██  █ █  █
     █████  ███  ████  ███   ████  █   █ ████  █       █   █ █ █ ███
     █   █   █   █     █     █  █  █   █ █     █       █   █  ██ █  █
     █   █   █   █     █████ █   █  ███  █     █████ █████ █   █ █   █

    ::: A SUPER HIGH SPEED INTERNET BULLETIN BOARD AS SINGLE BINARY :::

===========================================================================

でも……なぜ?

手短に言えば、コミュニティチャンネルに居座っていた人や、以前のステータスアップデートで裏話をすでに読んだ人向けにまとめると、当時、私が作っているツールやプログラム、サービスを使っている人たちのためにコミュニティの議論フォーラムが欲しかったのだが、どうしても我慢して使いたいと思えるソフトウェアがひとつも見つからなかったということだ。

昔ながらの良きBBSシステムを思わせつつ、現代のブラウザで気軽に使えるインターネットフォーラムを探していた。さらに、こんな条件を満たすものが欲しかった……

  • 既存のデータベーステーブルを使ってユーザー認証できること、かつ/または……
  • シンプルなサインアップに対応していること、できればメールアドレスの代わりにXMPP JIDで
  • メール、できればXMPP経由での通知や返信に対応していること
  • 軽量で、山のようなランタイム依存関係を引きずらないこと
  • ユーザーにJavaScriptの有効化を要求しないこと
  • おそらく一生触ることのない管理機能で埋め尽くされていないこと
  • テーマのカスタマイズがそこそこ簡単で、さらに重要なのは長期的にメンテナンスしやすいこと

phpBBは当然最初に当たってみた。何十年も前から存在し、DiscourseLemmyとは違って訪問者にJavaScriptを無理強いしないからだ。しかしphpBBは機能を詰め込みすぎたモンスターで、インストールや設定に時間がかかり、拡張機能やランタイム依存関係まで考えれば、定期的なメンテナンスの儀式を要求される。正直、そんな時間はない。一方のDiscourseLemmyは、JavaScriptなしではそもそも動かないので最初から検討の対象外だ。他に調べたものは、必要な機能が欠けていたり、同様のランタイムの頭痛の種を抱えていたり、必要な数個の機能を得るためだけにフォークして永遠にメンテナンスしなければならなかったりした。だから私は、理性的でバランスの取れた人間として当然のことをした。去年の末に、自分で掲示板ソフトウェアを書き始めたのだ。

技術的なごたごた

さあ、Goだ!

一行も書く前に腰を据えて、おなじみの候補を比較検討した。PHPLaravelPythonDjangoElixirPhoenixGoFiber、そしてZigJetzigだ。サーバーサイドのTypeScriptは一秒たりとも検討しなかった。Node.jsNPMのエコシステムは露骨なマルウェアが蔓延するゴミ箱のようなもので、Hyperuplinkのように意図的に馬鹿げたものでさえ、選ぶ気にはなれないからだ。

スクリプト系のスタックはウェブ開発を快適にし、面倒なHTTPやセッション、フォーム処理を抽象化してくれるので、作りたいものに集中できる。しかし、そのどれもがランタイムとメンテナンスの負担を引きずる。Hyperuplinkにおける私の唯一の目標は、趣味で運営する管理者がスタック全体の面倒を見続けなくても掲示板を運営できるようにすることだった。管理の観点からは、たまに1つか2つのバイナリを更新すればそれで済むようにしたかった。無数にある依存関係のうち一つでも重大なCVEを見逃さないようにするために、たとえばPHPのセキュリティ告知やメーリングリスト、GitHubのセキュリティ勧告、NVDなどをいちいち購読する必要がないようにしたかったのだ。

Goは、CC++Zigのような低レイヤーのコンパイル言語——開発速度と引き換えにパフォーマンスをくれるもの——と、PHPPythonのようなインタプリタ言語——データ構造を扱うのは楽しいが実行コストが高いもの——の間の絶妙な中間点にある。決め手となったのは、Goが単一の静的リンクバイナリにコンパイルされ、それを任意のVPSにコピーしてそのまま起動できることだった。唯一の欠点は、Goが正確には「ウェブネイティブ」な言語ではなく、退屈な部分を加速してくれるDjangoLaravelのようなものが存在しないことだ。そこで私はFiber v3フレームワークの上に小さなウェブアプリケーションフレームワークを自作し、そこから先を進めた。

内部の仕組み

Hyperuplinkは単一の静的バイナリで、CGOを無効にしてコンパイルされ、Linux、macOS、FreeBSD、NetBSD、OpenBSD、そして多数のアーキテクチャ向けにクロスコンパイルされているため、デプロイはそのバイナリを所定の場所にコピーするだけで済む。PostgreSQLネイティブでクラスタにも対応し、マテリアライズドビューを使って読み取りパフォーマンスを最適化している。スキーママイグレーションはバイナリに埋め込まれ、起動時に自動で実行されるため、外部のマイグレーションファイルは不要で、アップグレードは新しいバージョンを起動するだけで済むはずだ。

加えて、キャッシュやセッション、非同期ジョブキューにはRedis互換のサービスが使われる。プロフィール画像や添付ファイル、カスタムアセットはローカルディスクにも、任意のS3互換オブジェクトストア(MinIOなど)にもアップロードでき、サービスを水平スケールする際に便利だ。

HyperuplinkはクライアントサイドのJavaScriptを一切実行しない。つまり、すべてのページはサーバーでレンダリングされたHTML5とCSSであり、パイナップルをピザに乗せるべきかどうかというスレッドにカーソルがふらふらと戻っていく動きを、あなたのアカウントのデータを集めるためだけに記録するようなものは何もない。

ちなみに、アカウントはローカルパスワードでサインアップ/サインインでき、オプションでTOTPによる二要素認証も利用できるが、Hyperuplinkは他のプラットフォームから友人を誘い込むためのOAuthプロバイダ経由のログインにも対応している。そして、メールは古臭いと感じる人のために、サインアップや通知はXMPPでも行える。認可については、アカウントをグループに割り当て、カテゴリごとの権限を設定できるので、良いもの良い人だけが利用できるようにしておける。

Hyperuplinkには多彩なビルトインテーマが用意されており、その一部はclassic-stylesheetsプロジェクトのおかげで美しいレトロな美学を備えている。ややモダンな見た目のテーマもあり、どのテーマでもカラースキームは入れ替え可能なので、Gruvbox色に染めたmacOS 9風の掲示板といったものも実現できる。

掲示板は投稿でのMarkdownをサポートし、プロフィール画像や添付ファイルのアップロードが可能で、管理者向けの通報・モデレーション機能を備え、UIはi18nに対応している。

HyperuplinkにはREST APIも付属しており、LemmyDiscourseが公開しているものよりも扱いやすいと自負している。さらに、Neon Modem Overdriveへの公式Hyperuplink統合として、独自のTUIクライアントも用意されている。

はじめよう

Hyperuplinktty.failで開発され、GitHubにミラーされている。ミラー側でビルド済みバイナリやコンテナイメージがビルド・ホストされている(無料のCPUサイクルに感謝!)。掲示板をどのように運用する場合でも、到達可能なPostgreSQLRedis互換サーバーが必要になる。加えて、アップロードをローカルディスクに置きたくない場合は、オプションでS3互換ストレージも用意しておこう。

ネイティブ

公式リポジトリには、できるだけ早く始められるよう必要なドキュメントや設定がすべて揃っているが、基本的な考え方はシンプルだ。リリースページから自分のプラットフォーム用のバイナリを取得し、好きな場所に置いて実行するだけだ。

$ ./hyperuplink -c "file:///etc/hyperuplink.toml"

Docker / Podman

スタック全体を一気に立ち上げたいなら、DockerやPodman(しかもrootless!)用のセットアップがまるごと用意されている。リポジトリにはPostgreSQLValkeyを備えたdocker-compose.yml/podman-compose.ymlが含まれ、オプションでMinIOのプロファイルも用意されている。

$ docker compose up -d

Quadlets

PodmanセットアップはDockerセットアップと同等のことをrootlessで実現し、podman-compose.ymlに加えて、systemdを使いたい人向けにQuadletユニットのセットまで用意されている。

K8s

Kubernetesももちろん使える。数レプリカの最小限のDeploymentと、Secret経由で渡す設定があれば、ほぼそれだけで十分だ。アップロードにS3を使えるため、Podはステートレスなまま保てる。

Gentoo

リポジトリにはEbuildも用意されているので、自分のGentoo……サーバーで……コンパイルすることもできる。たぶん。

NixOS / nixpkg

Nixに必要なものは一通り含めてみたが、正直なところ自分ではどこでも積極的に使っていないので、積極的にメンテナンスされているものというより、概念実証程度に考えてほしい。もしNix周りを積極的にメンテナンスしたいという気持ちがあれば、ぜひ連絡してほしい。

FreeBSD / OpenBSD / OpenRC / …

リポジトリにはFreeBSD、OpenBSD、OpenRC向けのinitスクリプトや、systemd用のサービス定義も含まれている。

ソースからビルド

自分でHyperuplinkをビルドしたい場合——そのためにはGoが必要だが——同様に簡単だ。

$ git clone https://tty.fail/mrus/hyperuplink.git
$ cd hyperuplink
$ make build

自己完結したバイナリは./build/hyperuplinkに生成され、好きな場所に移動できる状態になる。

注意: はいはい、嘘をついたのは認める。見破られたよ。ランタイム依存関係なしは100%正確ではなく、ユーザーがプロフィール画像をアップロードできないフォーラムを前に呆然としているかもしれない。その理由は、Hyperuplinkには1つだけランタイム依存関係があり、それがImageMagickconvertコマンドだからだ。プロフィール画像を機能させるには、サービスが$PATH内でそのバイナリを見つけられる必要がある。

なぜそうなっているかといえば、話せば長いのだが、要するに画像処理は難しく、たとえばWebP圧縮アルゴリズムのためにネイティブなGo実装を作って車輪の再発明をしたいと思う人は、私を含めてそう多くはないということだ。CGOを使わないことでGoの手軽なクロスコンパイルを保つことをあえて選んだので、convertバイナリを呼び出すのが最も合理的なアプローチだと判断した。そもそも、ウェブ関連のものをホストしたことがあるなら、システムにImageMagickが入っている可能性は高いだろうし。

リバースプロキシ

プロセスをどのように実行するにせよ、TLSを終端するリバースプロキシの背後に置いてほしい。Mode = "production"ではセッションCookieがHTTPS専用になるため、フォーラムをdevelopmentモードで実行したいとはまず思わないはずだ。また、サービスを監視下に置きたい場合は、前述のsystemd用、GentooやAlpine上のOpenRC用、そしてFreeBSDやOpenBSDのrc.d用のサービスファイルがリポジトリに用意されている。

EOF

HyperuplinkSEGV Licenseのもとでオープンソースとして公開されており、コードはtty.failで、ミラーとビルド済みバイナリはGitHubで入手できる。その他、知りたいことはすべてhyperup.linkで、あるいはHelp -> Manualにある組み込みマニュアル内で確認できる。

もしこれらが自分の好みに合うと感じたなら、ぜひチャットルームに来て挨拶してほしい。掲示板をセットアップしたら自慢してほしいし、開発やテストに手を貸したいと思ったら連絡してほしい。なぜなら、最高のコミュニティとは、常に実際の人々が実際に手を動かして作り上げてきたものだからだ。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント