🤖
xterm.jsでCloudShellっぽいものを作る
xterm.jsを使って自前のCloudShellみたいなものを作ったのでその記録です。
背景
AWS CloudShellやGoogle Cloud Shellを使うと、Webブラウザ上ですぐにLinuxコマンドを実行できる環境が確保できます。
AWSアカウントやGoogle Cloudの認証情報だけで利用でき、SSHクライアントやSSHキーなどを用意する必要がないのでとても強力で便利です。
一方で強力で便利な分、コンソールログを残す仕組みがなかったり、サーバーでCloud Shellからの接続を許可したい場合にかなり広い範囲のアクセス許可が必要になるなど、セキュリティポリシー上の不都合もあります。
やったこと
自前でCloudShellと同じ用にWebブラウザからアクセスできるシェルコンソールを作成しました。
動かすとアプリケーションが動作しているDockerコンテナ内で動作するbashにWebブラウザからアクセスできます。
docker compose exec server /bin/bash
をしたのと同じことがWebブラウザからできることになります。
使用したもの
xterm.jsを使うとほぼそのまま目的のものが作れます。
今回はWebブラウザ上で動作するxterm.jsの入出力と、サーバーでbashを動かした仮想ターミナルの入出力をWebsocketでつなぎました。
- フロントエンド (Webブラウザ)
- Angular / Typescript
-
xterm.js
- コンソールのサイズを自動調整するために xterm-addon-fit も使用
- バックエンド (サーバー)
※今回 Angular や Echo を使っている意義はあんまりない。
処理概要
- フロントエンドは読み込まれるとバックエンドにWebsocketで接続に行きます。
- バックエンドは接続を受け付けるとgithub.com/creack/ptyを使用して仮想ターミナルとbashを起動します。
- それ以降は以下のように互いの入出力を接続します。
- ターミナルへの入力: Terminal.onData() -> websocket -> 仮想ターミナルのファイルハンドラへのWrite
- フロントエンド側: https://github.com/ikedam/webpty/blob/v0.0.1/client/src/app/app.component.ts#L47
- バックエンド側: https://github.com/ikedam/webpty/blob/v0.0.1/main.go#L60
- 最低限のターミナルとして機能させるのであれば、入力を加工せずそのまま送ってしまえばよいのですが、ブラウザのウィンドウサイズの変更に合わせてターミナルサイズの変更を行うためにターミナルへの入力とターミナルサイズの変更コマンドを分けられるような構造を入れ込んでいます。
- ターミナルへの出力: 仮想ターミナルのファイルハンドラからのRead -> websocket -> Terminal.write()
- ターミナルへの入力: Terminal.onData() -> websocket -> 仮想ターミナルのファイルハンドラへのWrite
終了処理
以下が起きると終了処理を行います。
- Websocketからの入力が終了した(フロントエンドで処理を終了した)
- bashプロセスが終了した(バックエンドで処理を終了した。ただし実際にはフロントエンドでexitを入力した場合が該当)
終了時には以下を行います。
- bashプロセスにSIGHUPを送信してbashプロセスを強制終了。
- ちなみにSIGTERM等では終了しない。
- Websocketを切断。
課題事項など
検証程度の実装のため、この仕組みを実際の環境で利用するには以下の考慮が必要です。
xterm.jsには多数のアドオンがあるので、それを使うと解決するものもあると思います:
- 日本語文字の入出力ができない。
-
less
などを使った感じ、どうもちゃんとターミナルサイズを設定できていない様子。 - 子プロセスを起動したままクライアントを閉じた場合に子プロセスを停止する処理が必要。
Discussion