ゲームサーバー管理ポータルWebアプリ
この記事は英語で書かれ、翻訳されています。
制作のきっかけ
友人たちが『アセットコルサ』のレーシングサーバーを自分たちで運営できるように、小さなウェブサイトを作りました。コースを選び、車種を選んで、「保存」をクリックするだけで、サーバーが再起動し、新しい設定が適用されます。
サーバーは、このブログの隣にある自宅のLinuxマシンで稼働しています。このポータルを作る前は、何かを変更するにはSSHセッションを開き、nanoで2つの設定ファイルを編集し、手動でサーバーを再起動する必要がありました。それができるのは私だけで、タイプミスを1つするたびに、その夜のレースが台無しになっていました。
今では、フレンドリストに登録されている人なら誰でも、メールで送られてきたコードを使ってログインし、ブラウザから、スマホからでも設定を変更できるようになりました。

機能の概要
このポータルでは、これまでターミナルで行っていたすべての操作を、ドロップダウンやスライダーを備えたシンプルなフォームで実行できます。
ダッシュボード:サーバーのリアルタイムステータス、稼働時間、現在オンライン中のユーザー、および「起動」「再起動」「停止」ボタンが表示されます。参加用アドレスには、ワンクリックで「コンテンツマネージャーで開く」リンクが付属しています。
コースとレイアウト:インストール済みのすべてのコースおよびMODコースが表示され、それぞれのレイアウトとピットボックス数の上限が確認できます。プレイヤーに必要なカスタムシェーダーパッチ(CSP)のビルドの最低バージョンを設定するフィールドがあります。
グリッド:各行は1台の車、1つのスキン、およびスロット数で構成されます。合計数がサーバーのプレイヤー上限となり、トラックのピットボックス数を超えることはできません。
保存済みグリッド:グリッドに名前(「ドリフトナイト」など)を付け、後でワンクリックで読み込むことができます。
セッション設定:ダメージとタイヤ摩耗のスライダー、レース周回数、気温と路面温度、および通常の24時間表記で入力するスタート時間。
車とコース:新しいMODはフォルダ名で追加され、スキンやレイアウトも含まれます。後から編集や削除が可能です。ワンクリックで、サーバーが現在使用しているすべての要素を追加できます。
最近の変更:誰が、何を、いつ変更したかのログ。
サインイン:友人がメールアドレスを入力すると、ワンタイムコードが発行されます。それ以外のユーザーはこのページにアクセスすることさえできません。

技術スタック
このブログと同じWebスタックを使用しているため、最新の状態に保つ必要があるツールセットは1つだけです。
Next.js 16 (App Router) - ページはサーバー側でレンダリングされ、変更はすべて Server Actions によって処理されます
React 19 + TypeScript - インタラクティブなフォーム、エンドツーエンドで完全な型付け
Tailwind CSS 4 - スタイリング。1セットの色トークンでライトテーマとダークテーマの両方に対応
Zod - フォームの送信データや保存されたファイルが使用される前に、すべてをチェックします
systemd ユーザーサービス - ウェブサイトとゲームサーバーを実行し、クラッシュ後に再起動し、起動時に自動起動します
Cloudflare Tunnel - ルーターのポートを開くことなく、ポータルをインターネット上に公開
Cloudflare Access - メールで送信されるワンタイムコードによるサインイン。友人のリストに限定
AssettoServer - 改造コンテンツを適切に処理する、オープンソースの『Assetto Corsa』サーバー
Nodeのテストランナー + tsx - サンプルサーバーフォルダに対して実行される27個のユニットテスト
GitHub - コードホスティング。サーバーはそこから更新を取得します
データベースは存在しません。ゲーム自身の設定ファイルが唯一の真実の源であり、ポータルに追加されたわずかな機能(変更履歴、手動で追加された車やコース、保存されたグリッド)は、小さなJSONファイルに格納されています。
仕組み
サーバーへは2つの別々の経路があります。1つはウェブサイト用、もう1つはゲーム用です。

友人のブラウザは、Cloudflareのサインインとトンネルを通過してポータルにアクセスします。ポータルは設定ファイルを編集し、systemdを通じてAssettoServerを起動または停止します。ゲーム自体は2つの転送されたポートを介してサーバーに直接接続するため、レースはウェブサイトには一切依存しません。
その過程で遭遇した興味深い問題
設定ファイルを破損させずに編集すること。Assetto CorsaのファイルはWindows形式のINIファイルであり、ポータルが認識できない設定が多数含まれています。そこで、必要な行のみを変更し、コメント、順序、改行コードを元のまま維持する小さなINIエディタを作成しました。各ファイルの以前のバージョンはバックアップとして保存されます。
手作業で加えた変更を元に戻さないこと。エントリーリスト(誰がどのスロットでどの車を運転するか)には、AIトラフィックのスロット設定など、手作業で調整された設定が含まれることがあります。ポータルはグリッドが実際に変更された場合にのみこれを上書きするため、サーキットを切り替えてもその作業が無駄になることはありません。
サーバーに存在しないMOD。サーバーにはMODのフォルダ名のみが必要であり、各プレイヤーは自分のPCにファイルを保持しています。AssettoServerはそれらのファイルの一部がないと起動を拒否するため、ポータルはYAML設定の適切なセクションで、そのチェックを緩和する2つのオプションを設定します。
角度としての時刻。サーバーの開始時刻は太陽角として保存されます。AssettoServerのソースコードで計算式を確認したところ、0は13:00、1時間ごとに16度となります。ポータルは双方向に変換を行うため、友人は単に「14:30」と入力するだけで済みます。
隠されたフォーマット規則。コンテンツマネージャーは、最低限の「Custom Shaders Patch」ビルドを必要とするトラックを、csp/4116/../D/../track のようなパスでマークします。ポータルはこれを個別に読み取り、トラックが変更された際も要件を維持しつつ、ユーザーがビルド番号を編集できるようにしています。
セキュリティについて。このサイトはコンピュータを制御するため、Cloudflareはリストにないユーザーをサイトに到達する前にブロックします。また、ポータルはリクエストごとにCloudflareの署名付きトークンを確認します。コマンドはシェルなしで実行され、固定された設定ファイルに記載されたサービス名のみが許可されます。
導入と、ホームサーバーから学んだこと
更新の流れは次の通りです。私がGitHubにプッシュすると、サーバー上の単一のスクリプトがコードを取得し、ビルドを行い、Webサイトのみを再起動します。テスト用サーバーは、その間ずっと稼働し続けます。
本番サーバーでは、整然としたテスト環境では決して明らかにならないいくつかの予期せぬ事態が発生しました:
インストールが中断されると厄介だ。SSH接続が切断されてaptコマンドが中断された結果、システムは更新が中途半端な状態になり、次の起動時には緊急ディスクチェックで停止してしまった。1行のfsckコマンドで修正できた。今では、長時間かかるジョブはマシン自体か、tmux内で実行するようにしている。
Node.jsの2つのバージョン。うっかりしたnvmのインストールにより、ターミナル上でNode 16がシステムのNode 24よりも優先されてしまいました。デプロイスクリプトは現在、常にシステム側のNodeを選択するようにしています。
設定ファイルのコピー&ペーストにはルールがある。サーバーのプラグイン設定に1行が欠けていたため、AssettoServerが起動しなくなった。現在は、ポータルがそのファイルにアクセスするたびに、その行を自動的に復元するようになっている。
早い段階で本番環境でのテストを行う。実際のサーバー設定のコピーに対してポータルを実行したところ、友人がバグに気づく前に、サンプルデータにはなかった3つのフォーマットが見つかりました。
また、リポジトリにはハンドブックも用意しました。起動・停止コマンド、フレンドやMODの追加方法、トラブルシューティング表などが記載されています。
今後の予定
『Assetto Corsa』が最初のゲームでしたが、次は『Valheim』と『Minecraft』のサーバーです。ポータルは当初からそのために構築されていました。各ゲームは1つの小さなアダプターを介して接続され、ダッシュボード、起動/停止コントロール、ログ、変更履歴は共有されます。ゲームを追加する際は、新しいウェブサイトを作成するのではなく、そのゲーム用のアダプターと設定フォームを作成するだけです。