変奏現実

パソコンやMMORPGのことなどを思いつくまま・・・記載されている会社名・製品名・システム名などは、各社の商標、または登録商標です。

この画面は、簡易表示です

SSH2

[xterm.js]ssh2接続

ブラウザとシェルはWebSocketで非同期に相互に(つまり自分勝手)に通信する。

node.jsでspawnすればシェルと繋がるけど、改行入力で1行入力(readline)っぽくシェルがやってくれると思ってたら、改行しても応答無しstdinストリームを閉じまで入力したコマンドを実行する気配が無かった、BackSpaceもそのまま渡ってしまう・・・

どうやらsshサービスが頑張ってる様だ。

node.jsからシェル起動は諦めて、ssh2サービスに繋ぐことにする。

1.WebSocketサーバーの作成

# mkdir testServer
# cd testServer
# npm init
・・・・・
# npm install ssh2            ※SSH2パッケージ
# npm install ws              ※WebSocketパッケージ
const WebSocket = require('ws');
const { sshTerm } = require('./sshTerm.js');
// SSH接続情報
const sshInfo = {
user      : '{SSHのログイン・ユーザ名}',
password  : '{SSHのログイン・パスワード}' {または} privateKey: require('fs').readFileSync('/PATH/id_rsa') ,
ipaddress : '{SSHのホストIPアドレス}',
sshport   : {SSHのポート番号},
wsport    : {WebSocketのポート番号},
httpport  : {apacheのポート番号},
httpath   : '{urlのパス}',
};
// ポート${sshInfo.wsport}でWebSocketサーバーを作成
const wss = new WebSocket.Server({ port: sshInfo.wsport });
// 案内文
console.log(`ssh ready, URL http://${sshInfo.ipaddress}:${sshInfo.httpport}/${sshInfo.httpath}`);
// WebSocketクライアント(xterm.js)接続時の処理
wss.on('connection', function connection(ws) {
  const env = process.env;
  console.log('connection WebSocket client.');
   const bash = sshTerm(ws, sshInfo);
});

passwordかprivateKeyかprivateKeyPathのいづれかを指定できる。

const { Client } = require('ssh2');
// SSH接続を確立
const sshTerm = async (ws, sshInfo) => {
  const commandName = 'bash';
  const conn = new Client();
  conn.on('ready', () => {
    // ssh2接続準備完了
    console.log('Client :: ready');
    // shell対応
    conn.shell((err, stream) => {
      if (err) throw err;
      stream.on('close', () => {
        // エラった時
        if (code !== 0) {
          console.log(`${commandName} process exited with code : ${code}.`);
        } else {
          console.log(`${commandName} process exited.`);
        }
        ws.send(`disconnected.`);
        conn.end();
      }).on('data', (data) => {
        // ssh2からデータが送られた時の処理
        console.log(`${commandName} : ${data}`);
        data = data.toString().replaceAll('\n', '\r\n');
        ws.send(data);
      });      
      // WebSocketクライアント(xterm.js)からメッセージ受信時の処理
      ws.on('message', async (message) => {
        console.log('受信したメッセージ: %s', message);
        stream.write(message);
      });      
      // WebSocketクライアント(xterm.js)クライアントから切断された時の処理
      ws.on('close', function close() {
        console.log('クライアントとの接続が切断');
        stream.end();
      });
      // Ctrl+Cで止められた時の処理      
      process.on('SIGHUP', ()=> {
        console.log('Got SIGHUP. ');
        stream.end();
        console.log('クライアントの接続をパージ');
        ws.close();
     });
     // 以上
    });
  }).connect({
    host: sshInfo.ipaddress,
    port: sshInfo.sshport,
    username: sshInfo.user,
    password: sshInfo.password {または} privateKey: sshInfo.privateKey,
  });
};
exports.sshTerm = sshTerm;
  • privateKey
    • ‘~/.ssh/id_rsa` の様な ‘~’ を使うとopenエラー。
      • 代用はprocess.env[‘HOME’]
  • privateKeyPath
    • エラってしまう。

testServerプロジェクトの雰囲気

  • testServer
    • index.html
    • sshTerm.js
      • node_modules
        • ssh2
        • ws

ここで、

# node index.js

でサーバー側の準備は終わり。

2.WebSocketクライアントの作成

クライアントはapacheのhtmlの下にtestディレクトリィを作りxtermのアドオン等を追加する。

# cd /var/www/html
# mkdir test
# cd test
# npm init
・・・
# npm install @xterm/xterm
# npm install @xterm/addon-web-links
# npm install @xterm/addon-attach
# npm install @xterm/addon-clipboard
# npm install @xterm/addon-fit
# npm install @xterm/addon-image
# npm install @xterm/addon-web-links
# npm install @xterm/addon-webgl

必要なのは、node_modules/@xterm下の cssファイル、jsファイル、mapファイルだけなので不要なファイルは削除する。

index.htmlにアドオンのファイルを追記。

<!doctype html>
  <html lang="ja">
    <meta charset="UTF-8">
    <title>xterm.js test page</title>
    <head>
      <link rel="stylesheet" href="node_modules/@xterm/xterm/css/xterm.css" />
      <script src="node_modules/@xterm/xterm/lib/xterm.js"></script>
      <script src="node_modules/@xterm/addon-web-links/lib/addon-web-links.js"></script>
      <script src="node_modules/@xterm/addon-attach/lib/addon-attach.js"></script>
      <script src="node_modules/@xterm/addon-clipboard/lib/addon-clipboard.js"></script>
      <script src="node_modules/@xterm/addon-fit/lib/addon-fit.js"></script>
      <script src="node_modules/@xterm/addon-image/lib/addon-image.js"></script>
      <script src="node_modules/@xterm/addon-web-links/lib/addon-web-links.js"></script>
      <script src="node_modules/@xterm/addon-webgl/lib/addon-webgl.js"></script>
      <script src="test.js"></script>
    </head>
    <body>
      <div id="terminal"></div>
    </body>
  </html>

test.jsにもアドオンの初期化等を追記

// 初期化処理
const sshInfo = {
wsport    : {WebSocketのポート番号},
};
window.addEventListener('load', () => {
  const term = new Terminal();
  //
  term.open(document.getElementById('terminal'));
  term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ');  
  // addon  
  // @xterm/addon-attach
  // initialization
  const webSocket = new WebSocket(`ws://${location.hostname}:${sshInfo.wsport}`);
  const attachAddon = new AttachAddon.AttachAddon(webSocket);
  term.loadAddon(attachAddon);
  // @xterm/addon-clipboard
  // initialization
  const clipboardAddon = new ClipboardAddon.ClipboardAddon();
  term.loadAddon(clipboardAddon);
  // @xterm/addon-fit
  // initialization
  const fitAddon = new FitAddon.FitAddon();
  term.loadAddon(fitAddon);
  fitAddon.fit();
  // @xterm/addon-image
  // customize as needed (showing addon defaults)
  const customSettings = {
    enableSizeReports: true,    // whether to enable CSI t reports (see below)
    pixelLimit: 16777216,       // max. pixel size of a single image
    sixelSupport: true,         // enable sixel support
    sixelScrolling: true,       // whether to scroll on image output
    sixelPaletteLimit: 256,     // initial sixel palette size
    sixelSizeLimit: 25000000,   // size limit of a single sixel sequence
    storageLimit: 128,          // FIFO storage limit in MB
    showPlaceholder: true,      // whether to show a placeholder for evicted images
    iipSupport: true,           // enable iTerm IIP support
    iipSizeLimit: 20000000      // size limit of a single IIP sequence
  }
  // initialization
  const imageAddon = new ImageAddon.ImageAddon(customSettings);
  term.loadAddon(imageAddon);
  // @xterm/addon-web-links
  term.loadAddon(new WebLinksAddon.WebLinksAddon());
  // @xterm/addon-webgl
  term.loadAddon(new WebglAddon.WebglAddon());
});

/var/www/html/testプロジェクトの雰囲気

  • test
    • index.html
    • test.js
      • node_modules
        • (いっぱい)

後はブラウザから http://localhost/test/ を開く。

topコマンドやviコマンドも普通に使える。

ブラウザで複数のセッションを開けるけど、多人数で使うのは無理。

アドオンがうまく動作していない。

とても少ないコードでここまで動くのは大助かり。

もっとも

VisualStudioCodeのSSH接続でソース修正やデバッグができるので

もう不要と云えば不要だけどね。




top