@unieai/uad-ssh
v0.1.21
Published
The machines a person can already reach, and one multiplexed connection to each
Readme
@unieai/uad-ssh
English | 中文
一個人本來就連得到的機器,以及對每一台的共用連線。
為什麼用 ssh 客戶端,而不是 SSH 函式庫
一個打得出 ssh build-box 的開發者,早就把連線需要的一切寫下來了:前面的跳板機、要用哪把金鑰、非標準的埠、要不要轉發 agent、host key 檢查多嚴。那個檔案——~/.ssh/config——就是機器名冊,Rabi 不另外再存一份。
讀得懂它,跟同意它,是兩回事。 Host pattern、Match 區塊、Include 檔案與命令列選項之間的互動順序由 OpenSSH 定義,而且它還會繼續擴充;今天寫得對的解析器,會在下一個版本開始分歧,而分歧會表現成「終端機連得到、但 Rabi 連不到的機器」。所以這個套件改成去問客戶端:ssh -G <alias> 會印出生效後的設定,而執行指令走的是同一個印出設定的執行檔。
驗證也一併解決了。agent 金鑰、硬體 token、ProxyJump、known_hosts 政策,以及其他所有憑證路徑都原封不動屬於 OpenSSH,Rabi 從頭到尾不持有任何需要保管的祕密。
到底是哪一個檔案
這本機器簿讀的是客戶端會打開的那個檔案,而它未必是 HOME 指到的那個:OpenSSH 從密碼資料庫展開 ~,並且忽略環境變數。一個以不同 HOME 啟動的行程——容器、sudo、某些桌面啟動器——否則就會從一個檔案列出別名,而連線讀的是另一個;失敗看起來會是 Could not resolve hostname <別名>,因為對一個從未看過那個別名的客戶端來說,它從頭到尾就只是一個主機名。
在設定裡指名一個檔案,會同時操控清單與每一條連線,所以那兩者也永遠不會不一致。預設不指名;此時客戶端還會讀系統層級的設定檔,而一個顯式的 -F 會把它排除掉。
它提供什麼
list()——設定檔裡的 alias,會跟著Include。pattern(Host *、Host !prod、Host *.internal)不列入:它們是用來設定連線的,不是用來指名一台機器。不做快取,所以執行中新增的機器立刻可選。resolve(alias)——這個 alias 實際代表什麼,來自ssh -G。argvFor(alias, remoteCommand?, { tty })——adapter 要執行的客戶端引數。probe(alias)——機器有沒有回應,失敗時附上客戶端自己的訊息。能回答得出來全靠BatchMode:否則一台要求 passphrase 的機器會停在沒有人看的提示前面。disconnect(alias)——關掉共用連線。
沒有東西可以在沒人看著的終端機上要密碼
除了終端機工作階段以外,每一次調用都帶 BatchMode=yes。
少了它,一台要密碼或金鑰通行碼的機器會讓客戶端去要——而且把子行程的 stdin 導成管線擋不住它:OpenSSH 會直接開 /dev/tty。於是從 shell 啟動的 app 會在一個使用者根本不是在工作的終端機上問,從 dock 啟動的則問在沒有人能回答的地方。兩種情況下指令都停在那裡等一個不會到來的按鍵,而提出這個要求的介面什麼都不顯示。使用者看到的是一個當掉的程式。
批次模式把那件事變成客戶端自己的拒絕——Permission denied (publickey)——那是介面可以顯示、而人可以處理的(把金鑰加進 agent)。
例外是真正的終端機工作階段(-tt):那一個使用者就是在看著它,而在那裡跳出通行碼提示,代表連線正在運作。
連線重用
每次呼叫都帶 ControlMaster=auto,所以第一個指令做完握手,後面的都接上去。對本機伺服器實測:200 ms,然後 7 ms。沒有它的話,每一次讀檔、每一個指令都要付一次完整金鑰交換,遠端工作區就會有那種感覺。
socket 放在 harness home 底下。OpenSSH 會把 %C 換成 40 個字元的摘要,而當實際綁定的路徑超過平台的 sun_path 上限(macOS 104 bytes、Linux 108 bytes)時,它拒絕的是整條連線,不只是連線重用。因此一個深到會溢位的 harness home,代價是失去連線重用,而不是失去那台機器;檢查量的是展開後的路徑,不是樣板。
遠端命令列
ssh host <command> 是把指令交給那個人的登入 shell,不管那是什麼。有三個細節是跟真實 shell 碰撞後留下來的:
- 用
exec,讓連線和指令之間不多站一個 wrapper 行程。 - 環境變數用賦值前綴(
A=b exec cmd),絕不用env A=b -- cmd:POSIX 的env只在賦值之前接受--,否則會回'--': No such file or directory。 - 指令前面也不放
--。exec -- cmd是 bash 的擴充;dash 會回exec: --: not found。--本來要防的事情改成事前拒絕:以-開頭的指令名直接不送出。
工作目錄不存在時,指令會失敗(exit 127),而不是跑到登入目錄去執行——指定了目錄的呼叫端是認真的。
Model Experience
無。此套件不註冊任何 tool、prompt、schema 或 context:它回答有哪些機器,並為真正做事的 adapter 組出連線引數。
KV Cache effect
無。這裡不貢獻任何 prompt 片段、工具定義或 context 條目,因此不會移動任何重用邊界。
已知限制與未完成項目
- 遠端登入 shell 必須是 POSIX 類。 命令列用到
cd、賦值前綴與exec,sh、bash、dash、ksh、zsh 的讀法都一致。登入 shell 是 csh 或 fish 的話需要另一套組法,而那還沒寫。 - 不會問 passphrase。
probe()跑在BatchMode,所以鎖住的金鑰會以 OpenSSH 的訊息回報為連不到,而不是跳出來要密碼。解鎖是 agent 的事,在 Rabi 之外。 - 列舉是淺的。
list()讀的是Host行;只透過Match區塊才連得到的機器,或只在命令列上用主機名指定的機器,連得上但不會出現在清單裡。 Include的萬用字元不展開。Include裡的字面路徑會跟進去,pattern 則跳過,所以~/.ssh/config.d/*.conf裡的 alias 不會出現在清單中,即使ssh找得到。
