SSH 越しにローカルクリップボードへコピーしたい、という要求は昔からある。OSC52 を使う方法もあるし、実際今回も改めて試してみた。ただ、私の環境では GNU screen を挟むことが多く、少し大きめの payload を扱うと安定性と信頼性の面で不安が残った。
私自身も2年前、netclip というツールを自作していた。当時使えそうな OSS を探してみたけれど、どれも自分の用途には合わなさそうだったので、結局自作することにした。作った当初は使っていたものの、段々と使わなくなった。最近また同じことをやりたくなってきたので、今回改めて設計し直して作り直した。この記事では、その過程とツール紹介を兼ねて整理してみる。
なおリポジトリはこちら
https://github.com/kamichidu/go-netclip
初版 netclip
当時の設計前提
当時は SSH を前提にしたくなかった。複数端末間でクリップボードを共有したいし、daemon を置きたくない、あるいは置けない環境も扱っていた。copy だけでなく paste も含めた双方向の利用を想定していた。
実装
その結果、実装は gRPC を中心に据え、Firestore や GCS を backend に利用する構成になった。イベント通知を受けて処理を振り分け、必要になったら後から pull して取得する。
結果
ただ、結局は自分でも使わなくなった。
利用シーンが変わったこともあるが、単純に体験設計と実際のユースケースがずれていたと思う。欲しいものに対して、少し回り道が多かったし、動かないときのトラブルシュートも面倒だった。
今回の再整理
設計前提を変えた
作って使って、使わなくなった理由を改めて整理した。支配的なユースケースだけを取り出し、そのための最小コストを目指すことにした。
頻度の低いユースケースは、ツール自身ではなく周辺インフラで吸収する前提にした。複数端末間での共有や Cloud Shell のような環境であれば、Cloud Run に配置して IAM で保護し、GCS を backend にすればよい。それを netclip 自身の機能として持つ必要はない。
代わりに、SSH を前提として受け入れ、小さなローカルdaemonを SSH セッションに対して透過的に起動することにした。環境に依存する部分も、どこへ依存を押し込むのが一番自然な体験になるかを優先して整理した。
整理した責務配置
SSH: transport authentication session lifecycle HTTP: protocol curl: client netclip: clipboard adapter
結果として、netclip が持つ機能はかなり小さくなった。
仕組み
図は README に掲載している Mermaid をみてください。
https://github.com/kamichidu/go-netclip/blob/main/README.adoc
リモート側では curl が Unix Domain Socket (UDS) に対して HTTP で投げ、それを RemoteForward でローカル側へ転送する。ローカルでは netclip daemon が待ち受け、最終的に pbcopy、clip.exe、wl-copy のような OS ごとの clipboard command を呼び出す。
UDS を使っているのは、SSH 先で固定スクリプトで接続するため。TCP ポート 0 を使った案もあったが、SSH 側で動的ポートを取得するために機能が増えるため、固定ポートを利用し、UDS を使って隠蔽するようにした。
セットアップ
SSH 設定、shell helper、利用例を示す。
Host * PermitLocalCommand yes LocalCommand netclip daemon --listen 127.0.0.1:45555 --background RemoteForward /tmp/netclip-%r.sock 127.0.0.1:45555 StreamLocalBindUnlink yes ExitOnForwardFailure yes
この設定によって、SSH するだけでローカル側に netclip daemon が透過的に起動し、待ち受けポートを UDS で隠蔽するところまで OpenSSH の処理に隠せる。しかも一度設定したら外部環境起因での変化は最小になる。
netclip() {
local sock="${NETCLIP_SOCK:-/tmp/netclip-${USER}.sock}"
curl --silent --show-error --fail \
--unix-socket "${sock}" \
-X POST \
--data-binary @- \
http://netclip/copy
}
このヘルパ関数を用意、またはファイル化しておくことで、基本的にメンテナンスフリーで利用できる。
ここまで来れば、git diff | netclip のように普段の操作に組み込んで使い出せる。
制約事項
現状では、同一ユーザーによる複数 SSH セッションを明示的には扱っていない。後から接続したものが socket を取り直すため、必要であれば socket path を分離して利用する。
また、リモート側では curl --unix-socket が利用できることを前提としている。
まとめ
今回やったことは、実装を書き直したというより、前提条件を整理し直しただけだった。まぁ実装も捨てて書き直した訳だが。
SSH を前提にし、ローカルdaemonを許容すると、認証も通知も永続化も不要になり、最小機能で目的を達成できた。
これなら、使っていることを忘れるような体験にできそうな気がしている。しばらく実運用してみて、改めて評価したい。
