auto-sudoeditをssh越しのリモートファイルに対応させる
ゴールデンウィークなのでOSSのメンテナンスをすることにしました
ゴールデンウィークなのでみんな何か自作しているのですが、私は特に良さそうなアイデアが思いつかなかったので、 ncaq/auto-sudoedit: automatic do sudo by tramp when need root file に来ている要望を実装しようと思いました。自分自身にも役に立つものですしね。
auto-sudoeditをtramp経由のアクセスに対応させたい
trampのsshをroot接続させれば問題ないのですが、 EC2とかのOSイメージってたいていrootのsshログインを制限してるので、 sudoが必要になります。そこで、遠隔のsudo実行のパスに対応させたい。
まあroot直接開けると驚異は増しますからね…
要望もあることですし。
tramp(ssh)をサポートする · Issue #10 · ncaq/auto-sudoedit
(コメントあるとは言え立てたの自分じゃん)
そういえばtrampのデフォルトのプロトコルsshからscpに変わったらしいけど大丈夫なんですかね
trampのコード見ると(Windowsだと)pscp、scp、ftpの順番で試すらしいですが、 scpは廃止する方向で進んでるんですよね。(その後OpenSSHはデフォルトフラグを元に戻してscpはまた使えるようになりましたが) Deprecating scp [LWN.net]
なのでプロトコルには依存しないように頑張っていきましょう。
tramp経由のsudoどうアクセスするんでしたっけ
例えば、
/ssh:ncaq.net:/etc/nginx/nginx.conf
だと、
/ssh:ncaq.net|sudo:ncaq.net:/etc/nginx/nginx.conf
になります。
sudoの方のホスト名にはユーザー名は不要。 root以外になるときには必要ですが。
参考: Emacs Tramp Mode使い方(port指定+sudo権限) - Qiita
本当にこの形式で正しいのだろうか… 他にもっとシンプルな形式があるのでは? なさそう。じゃあ良いか。
テスト書くの面倒になった
Emacs Lispでテスト書くとバンドルしないようにするのも面倒だし、インタラクティブな部分が多すぎるからユニットテストし辛い。
拡張方針
呼び出し部分は変えないでホスト変換部分を変えれば良さそう。
ELISP> (auto-sudoedit-tramp-path "/etc/nginx/")
"/sudo::/etc/nginx/"
これを変換していきます。
リモートでsudoが使えるか検証する必要があるのでは?
これまではローカルのコンピュータのファイルシステムにアクセスしてsudoを実行することしか考えてなかったので、ローカルでsudoを使えるかチェックしていましたが、この場合リモートのsudoを実行しているように思えるので、リモートでsudoが使えるのか検証する必要があるのでは?
sudo使えないサーバがssh提供してることとかある?
sshの場合は問題ないのですが、 Dockerの内部に接続したり、 Androidのadbで接続してる場合とかは問題になりそうですね。
まあとりあえずは無限に開き直しにならない場合は良いのですが… 安全機構作るのも大変そうですね。
trampの関数を活かしていこう
tramp-dissect-file-name
で分割できるらしい。
ELISP> (tramp-dissect-file-name "/ssh:ncaq.net:/etc/nginx/nginx.conf")
(tramp-file-name "ssh" nil nil "ncaq.net" nil "/etc/nginx/nginx.conf" nil)
Emacs Lispと真面目に付き合って来なかったので構造体を分割代入する方法を知らない… いやclはEmacs Lispなのか?
pacse
でも駄目だな、
cl-destructuring-bind
でも駄目だ。うーんきれいな感じに分割する方法はないのだろうか。
まあ良いか全部getterで取得しましょう。
分解したあとどうやってビルドできるの
とりあえず分割は手動で出来ますが、これを文字列に戻すのにはどういう関数を使えば良いんだ。
(defun auto-sudoedit-tramp-path (s)
"Argument S is normal path."
(let* ((file-name (tramp-dissect-file-name s))
(method (tramp-file-name-method file-name))
(user (tramp-file-name-user file-name))
(host (tramp-file-name-host file-name))
(localname (tramp-file-name-localname file-name))
(hop (tramp-file-name-hop file-name)))))
tramp-make-tramp-file-name
を使えば単一の文字列っぽいものになるっぽい。
hopって何?
まさに今追加しようとしているsudoとかへのパイプを使うと前の処理がhop扱いになるらしい。
本来はsshの踏み台とかやってるからhopって名前っぽいですね。
ELISP> (tramp-dissect-file-name "/ssh:ncaq.net|sudo:ncaq.net:/etc/nginx/nginx.conf")
(tramp-file-name "sudo"
#("root" 0 4
(tramp-default t))
nil "ncaq.net" nil "/etc/nginx/nginx.conf" "ssh:ncaq.net|")
これ追加する関数とかあれば最小限の変更で安全にやっていけるのではと思いましたが、いい感じに追加する関数が見つからないので、結局は自分で構造体を操作することになりそうですね。
妥協しよう
完全に構造的にプログラミングやって文字列処理いれるの嫌だったけど、ある程度文字列弄るのは仕方ないですね…
まあsudo版パスを作成するやつは出来た気がします。
scpだとコマンド実行できるコネクションじゃなくない?
道(@Nperair) と話してて気がついたんですが、 adbはともかくscpとかftpとかはコマンド実行できなくないですか?
いやでも、
/scp:ncaq.net:/etc/nginx/
にアクセスしてrgを実行したら実行されるな…
これ裏でsshを介してるのかな。これの原理がよくわかっていない。
tramp-connectable-p
で雑に処理すれば良いのでは?
これプロトコル指定とかが正しいか見るだけだわ。
f-writable?
で判定すればsudoのコネクション使えるかどうかも判定できるのでは。出来そうなのでヨシ!
古いhopどうしよう
素直に書いてたら古いhopが消去されてしまった。 sudoを使う場合に素直にhopを処理するには…? 最後にsudoが来れば良いのかな。
動かすかどうかではなく動かして正しいか検証する方針に
trampに実際に接続しないとsudoが使えるか分からないため、これまでの、
trampに接続する前にパスを見て接続するか決める方針ではなく、接続できそうなら変換したパスを返して、接続できなさそうならnil
を返す関数を書いて、変換結果がnil
の場合はオリジナルの動作をするhookに関数を変更することにしました。
多少のネットワーク通信の無駄は発生しますが、 auto機構自体がそういうものであると思うので、そこは問題ないとしました。
駄目だこれ無限再帰すると思ったけどしなかった
雑に書いてじっとコードを見て破綻に気が付きました。
無限再帰しないように、
auto-sudoedit-path-from-tramp-ssh-like
はメソッドがsudo
なら変換しないauto-sudoedit-path
では- 変換結果が変換前と同じなら
nil
f-writable?
で変換結果に書き込めないならnil
- 変換結果が変換前と同じなら
としていますが、なんかこれだと無限再帰が起きる気がする。
起きなかった。それどころか一回目の変換も起きなかった…
シリアライズの関数を取り間違ってた。
出来た。なぜ無限再起しないんだ? よくわからない…
いや違う、これsudo使っても対象が書き込めない時に無限再帰になると思うんですよ。
ならなかった。あーhookは一回しか呼び出されないからか。そりゃそうなりますよね。
出来た
tramp(ssh)をサポートする closed #10 by ncaq · Pull Request #12 · ncaq/auto-sudoedit
最初から指摘通りsudo-editをバックエンドにすれば良かったのでは?
の指摘で
even in remote tramp sessions
って書かれているのを最後まで見なかった… 素直に nflath/sudo-edit: Utilities for opening files with sudo をバックエンドにしておけば良かった感はありますね。
まあ開き直しが必要なのか検知する部分は結局書く必要があって、そこが問題の本質なので良いと言うことにします。