DAICHI OKAMOTO
BLOG 一覧へ

2026.06.19

全国100拠点の“現地でしか直せない”を、ブラウザ1枚に

全国100拠点の“現地でしか直せない”を、ブラウザ1枚に

ホテルの客室に入っている VOD(ビデオ・オン・デマンド)システムを、全国100拠点以上で運用している事業者の社内保守業務を支える、単一オペレータ向けの Web アプリを作りました。守秘のため会社名やサービス名は伏せ、技術的な中身だけを書きます。冒頭の図が全体像です。

どんな仕事か

各拠点には、客室端末に映像やメニュー画面を配信する小さな Linux サーバーが置かれています。その多くが10年以上前の CentOS / RHEL。設定ファイルを直す・配信ページを差し替える・状態を確認する——その保守のたびに、SSH の鍵やホストを思い出し、拠点ごとに違う接続条件(古い暗号方式、踏み台の有無)に対処し、vi で直してバックアップを取り、結果は現地に見に行く……という属人的で事故りやすい流れになっていました。これを「ブラウザを開けば全拠点に触れて、安全に直せる」状態にするのが目的です。

作ったもの

ブラウザから、全拠点に対して次のことができます。

  • SFTP でディレクトリを辿り、エディタで開いて編集・保存
  • 保存前の差分プレビューと、保存時のリモート自動バックアップ(元ファイル.bak.日時
  • コマンド実行と、ブラウザ内の本物のターミナル(PTY)
  • 設定変更が客室ページにどう反映されるかのライブプレビュー
  • ドラッグ&ドロップでのファイルアップロード
  • 拠点・踏み台の登録、接続テスト、全操作の監査ログ

技術スタック

フロントは React + Vite + TypeScript / Tailwind CSS / shadcn 風 UI、エディタは Monaco、端末は xterm.js。バックエンドは Fastify(127.0.0.1 のみにバインド)+ TypeScript、SSH/SFTP は ssh2、DB は better-sqlite3。pnpm モノレポで、手元の Mac に launchd で常駐させています。拠点へは Tailscale のメッシュ VPN 越し、必要に応じて踏み台(中継ホスト)経由で繋ぎます。

難しかったところ ①:古すぎて今の SSH では繋がらないサーバー

現代の SSH クライアントは安全性の低い旧式アルゴリズムを既定で無効にします。ところが相手は旧 CentOS。まずモダンな方式だけで接続を試み、ハンドシェイク失敗を検知したらレガシー暗号を足して自動で繰り返すフォールバックを実装しました。鍵フォーマットも、古い PEM・OpenSSH 新形式・DSA・パスフレーズ付きまで受け付けます。

難しかったところ ②:直接は届かない拠点を踏み台で越える

現地 LAN からしか SSH を受け付けない拠点向けに、多段トンネルを用意しました。ssh2 の forwardOut で踏み台から目的拠点への TCP チャネルを開き、2 段目 SSH のソケットとして渡します。踏み台側に追加ソフトは不要。接続はプールして使い回し、同じ踏み台を複数拠点が共有する場合は 1 本の SSH に束ね、アイドル 5 分で連鎖的に切断します。

難しかったところ ③:戻せる安心感

設定ファイルを壊すのが一番怖いので、保存前に差分を確認でき、書き込み時には既存ファイルを必ず SFTP でコピーしてバックアップしてから上書きします。バックアップも SFTP ストリームで行うため、cp やシェルが使えない拠点でも動きます。いつ・何を書いたかは操作ログに backup → write の 2 行で残ります。

難しかったところ ④:Shift-JIS を壊さずにライブプレビュー

直した結果を客室ページでその場に見たい。SSH トンネル経由で拠点の HTTP サーバーに繋ぎ、ツール側がプロキシして iframe に表示します。客室ページの多くが Shift-JIS で、UTF-8 として安易にデコードすると文字が壊れ、ログインフォームの POST まで通らなくなる。そこで本文をデコードせず、バイト列のままルート相対 URL だけを書き換える処理を書きました。Shift-JIS の 2 バイト文字を 1 単位で読み飛ばし、ASCII と衝突するトレイルバイトを誤検出しないようにしています。

難しかったところ ⑤:ブラウザの中の本物のターミナル

xterm.js と WebSocket で、拠点に PTY 付きの対話シェルを張ります。ブラウザは WebSocket の Upgrade に認証ヘッダを付けられないため、JWT はクエリで渡して検証。ウィンドウのリサイズも双方向に同期し、セッションの送受信バイト数や時間を監査ログに残します。

セキュリティ

SSH 秘密鍵は AES-256-GCM で暗号化して保存し、API には鍵そのものを一切返しません(「鍵があるか」の boolean だけ)。バックエンドは 127.0.0.1 のみにバインド、パスワードは bcrypt、API は JWT、ログイン失敗を含む全操作を監査ログに記録します。

できるようになったこと

  • 現地対応や SSH の段取りが要った保守が、ブラウザを開くだけで全拠点に対して完結
  • 「古くて繋がらない」「直接は届かない」拠点も、意識せず同じ UI で扱える
  • 保存前差分+自動バックアップで、設定を壊しても戻せる安心感
  • 直した結果をその場でプレビューでき、現地確認の往復が不要に
  • 誰が何をしたかがログに残り、属人化していた作業が追える状態に

まとめ

派手な機能というより、古い現場の制約に一つずつ実装で折り合いをつけていくタイプの仕事でした。レガシー暗号、多段トンネル、文字コード、PTY——どれも「動いて当たり前」に見えて、間に挟まる泥臭い差異を吸収するのが一番の山。現場の手間を仕組みで減らす、という自分のものづくりの軸がそのまま出たプロジェクトです。