CH-J Tools透過 SSH 管理伺服器
選單

遠端編輯器的儲存與延遲監控

下載檔案

本譯文正在進行語言審校。如有不明確之處,請對照英文原文。

根本原因

先前的編輯器會將工作檔案上傳到目標檔案的上層目錄,而在 /etc 或 /var 下,SSH 帳戶通常沒有該目錄的寫入權限。SFTP 重新命名既不提供經授權的 sudo 路徑,也沒有基準衝突檢查、安全性中繼資料驗證或可靠的覆寫語意。其錯誤處理程式會刪除暫存副本,即使在無法確定作業是否完成時,也會移除可供復原的資料。

工作階段狀態先前只顯示連線狀態。沒有任何 ICMP 或經驗證的 SSH 回應量測可提供給終端機指示器。

儲存通訊協定

  1. 固定的 SSH 輔助程式使用以描述元為基準的相對操作,並對每個路徑元件使用 O_NOFOLLOW,開啟一個一般的單一連結檔案。它會記錄內容、SHA-256 雜湊、裝置/inode、UID、GID、完整的權限位元、奈秒級的修改/變更時間,以及所有可讀取的延伸屬性。Core 會驗證雜湊,並以嚴格的 UTF-8 解碼,同時保留 BOM。每份文件都會取得一個不透明的 editId,其範圍限定於對應的外掛程式、終端機工作階段與伺服器身分。
  2. 一個非特權的準備作業會透過 pwd.getpwuid(os.getuid()) 解析已驗證帳戶的家目錄,以 0700 模式建立 $HOME/ch-j-sm/,並建立一份私有、獨占的復原日誌。不安全的家目錄、以符號連結構成的工作區,或由其他帳戶擁有的工作區都會被拒絕。
  3. Core 透過 SFTP 上傳一個隱藏的、隨機 128 位元的 .tmp 工作檔案,使用獨占建立(wx)、0600 模式,以及六十秒的通道/上傳逾時。日誌包含目標、原始基準與預期的工作檔案雜湊;不包含任何登入憑據。
  4. 一次 SSH exec 完成整個最終處理,並可選擇透過既有的 sudo 執行器執行。輔助程式會安全地開啟私有工作副本、計算其雜湊並將其寫入磁碟。它會鎖定原始 inode、檢查其基準、在目標旁建立一個獨占的目的地檔案、寫入全部內容、套用並驗證中繼資料、再次檢查原始檔案,然後將替換檔案寫入磁碟。
  5. 在同一目錄中使用 os.replace 以不可分割的方式替換目標。上層目錄會被寫入磁碟,並驗證最終路徑、雜湊、inode 與中繼資料。在替換完成之前,原始路徑一律可用。工作檔案絕不會跨檔案系統移動,也絕不會在最終處理過程中被刪除。
  6. Core 收到經驗證的成功結果後,會由獨立的清理步驟再次檢查最終內容與安全性中繼資料,然後才刪除工作檔案與日誌。即使清理失敗,仍會回報已確認的儲存,並附帶 recoveryAvailable: true。

上傳失敗、sudo 失敗、衝突、中繼資料錯誤或替換失敗時,會保留原始檔案以及所有已暫存的資料。未完成的上傳會如實標示,無法當作完整的已編輯文件復原。SSH 通訊中斷、缺少結束狀態、逾時以及非預期的最終處理結果,都會回報為未知,絕不會回報為已儲存。對於結果不確定的文件,Core 會阻止再次儲存;不會自動重試替換。

中繼資料、權限與安全性邊界

輔助程式會保留並驗證 UID、GID 以及所有 POSIX 權限位元,包括可執行、setuid、setgid 與 sticky 位元。它會逐位元組重現 Linux 延伸屬性,包括 POSIX ACL、capabilities 與 SELinux 標籤。擁有者與檔案內容會先於安全性屬性設定,因為寫入/chown 可能會清除 capabilities 或特殊權限位元。原始檔案中不存在、但目的地檔案繼承而來的屬性會被移除。修改時間反映新的寫入,不會從原始檔案複製。

無法讀取或無法表示的屬性會中止作業;中繼資料不會在未告知的情況下被捨棄。IMA/EVM 簽章會被明確拒絕,因為複製與內容/inode 綁定的完整性簽章會使其失效。目標路徑或家目錄路徑中的符號連結、硬連結檔案以及非一般檔案都會被拒絕。不使用任何就地覆寫或較弱的 SFTP 覆寫備援方式。

一般的可寫入儲存會以 SSH 帳戶身分執行。受保護的儲存需要明確指定 { sudo: true };對於不需要密碼的 sudo,sudo 密碼為選用。密碼經驗證後只會透過通道的 stdin 傳遞,絕不會出現在指令、日誌、工作檔案、記錄器或遠端錯誤中。root SSH 工作階段使用既有的直接 root 執行器。授權失敗時,原始檔案與工作副本維持不變。所有以 root 身分執行的儲存(直接 root SSH 或 sudo)也會拒絕由其他帳戶擁有或可寫入、或帶有存取 ACL 的目標目錄/上層目錄,而不會信任由其他帳戶控制的特權路徑名稱。

固定的輔助程式以 python3 -I -B 執行:它會忽略使用者的 Python 路徑、工作目錄與使用者 site-packages,且不寫入位元組碼。路徑與 JSON 參數在 Core 中進行 shell 跳脫。外掛程式絕不會取得任意的 SSH exec 或 SFTP 控制代碼。既有的沙箱、CSP、資訊清單權限、主機金鑰驗證與更新驗證仍持續有效。

Plugin API 與剩餘的 Monaco 整合

另行散布的 Monaco/File Manager UI 不在此簽出內容中。其 UI 整合、sudo 提示、未儲存緩衝區的處理與翻譯無法在此實作或測試。內建的外掛程式原始碼是 Hash & Checksum 外掛程式。並未虛構任何替代的 File Manager 原始碼。

既有的方法仍可使用。新增的方法/選項由沙箱化的 preload 與經過權限檢查的執行環境提供:

const opened = await chjPlugin.files.readText(sessionId, path);
// For a protected read, explicitly pass { sudo: true, sudoPassword }.

const result = await chjPlugin.files.saveText(sessionId, path, editor.getValue(), {
  editId: opened.editId,
  sudo: true,           // Include only after the user authorizes elevation.
  sudoPassword         // Omit for passwordless sudo; never persist this value.
});

if (result.ok && result.value.status === "saved") {
  // Mark this exact submitted editor version as saved. Later edits stay dirty.
} else {
  // Keep the Monaco model dirty. Translate result.error.code/status.
  // result.error.recoveryId/recoveryPath identify retained working material.
}

await chjPlugin.files.closeText(opened.editId);
const recovery = await chjPlugin.files.listRecovery(sessionId);
const copy = await chjPlugin.files.readRecovery(sessionId, recoveryId);
// Review copy.status and copy.text; open a fresh document before another save.
await chjPlugin.files.cleanupRecovery(sessionId, recoveryId);
// Cleanup rejects anything whose target is not currently confirmed.

當檢查目標需要提升權限時,復原方法可接受一個選用的最後參數 { sudo: true, sudoPassword }。即使透過 sudo 執行,它們也會保留已驗證使用者的工作區。日誌在應用程式重新啟動與 SSH 重新連線後仍然存在;復原絕不會自動重複最終處理。在明確儲存復原的緩衝區之前,請先將其與重新開啟的目標進行比對。

files.read 保護讀取、檢查與控制代碼釋放。files.write 保護儲存與復原清理。對於既有的外掛程式,writeText 在失敗時仍會拒絕;saveText 則傳回結構化的 { ok, value/error } 封裝,因為 Electron 會捨棄被拒絕之 Error 物件上的自訂屬性。舊式的三參數 writeText 可使用單一已開啟文件的基準。同一路徑上有多份作用中的文件時需要 editId;不明確的舊式呼叫會安全地失敗,而不會選用另一份文件的基準。外掛程式應釋放不再使用的控制代碼。

外部 UI 必須立即顯示儲存狀態、在失敗時讓緩衝區保持未儲存、僅在發生權限/驗證失敗後才提供 sudo、在不覆寫的情況下顯示衝突,並在連線中斷或結果未知後提供復原。它必須以捷克語、德語和英語翻譯這些狀態。建議的狀態文字:

狀態 捷克語 德語 英語
儲存中 Ukládání… Wird gespeichert… Saving…
已確認 Uloženo Gespeichert Saved
失敗 Uložení selhalo Speichern fehlgeschlagen Save failed
需要 sudo Vyžadováno oprávnění sudo sudo-Autorisierung erforderlich Sudo authorization required
衝突 Původní soubor byl změněn Originaldatei wurde geändert Original file changed
連線中斷 Spojení bylo přerušeno Verbindung unterbrochen Connection lost
未知 Výsledek uložení není znám Speicherergebnis unbekannt Save result unknown
復原 Obnova je dostupná Wiederherstellung verfügbar Recovery available

延遲的定義與生命週期

Ping 是向 SSH 所使用之已解析 IP 傳送 ICMP echo 要求、並由用戶端實際收到回覆所回報的延遲。Core 以固定的參數陣列非同步呼叫平台的 ping 執行檔,不使用 shell,也不要求以管理員身分啟動。Linux、macOS(包括 ping6)與 Windows 的參數會分別選擇。ICMP 遭封鎖、缺少執行檔、逾時、輸出無效,或 Windows 只提供上限值的回覆(例如 <1ms)時,都會產生「無法使用」的值,而不是編造一個數字。

SSH RTT 是透過既有的已驗證 SSH 用戶端執行固定 true exec 要求所花費的單調 performance.now() 時間長度。它包含通道建立、伺服器處理與回應傳遞;它既不是純粹的網路 RTT,也不是 SSH 登入所需的時間。翻譯後的指示器提示會說明這項差異。

終端機一連線,監控就會開始。各項探測會同時且非同步地執行,要求/程序逾時為五秒。下一次取樣會排定在上一次取樣完成後十二秒進行,以避免重疊。中斷連線會中止 ICMP、清除計時器、取消待處理的 SSH 作業並清空數值。紀錄身分檢查可防止舊連線在重新連線後繼續發布資料。ICMP 失敗絕不會改變 SSH 連線狀態。

延遲資料包含在既有的 state 事件 / ssh:state IPC 承載資料中:

latency: { pingMs: 24.1, sshRttMs: 31.25, measuredAt: "…" }

無法取得的數值為 null。精簡的終端機指示器使用毫秒、在地化的「無法使用」文字,以及捷克語/德語/英語標籤。中斷連線時會隱藏。不需要額外的轉譯器執行能力或 IPC 要求。

驗證與作業限制

使用者要求在既有的工作樹(目前為 feature/hash-checksum-plugin)中保留變更;並未切換分支,也未進行無關的清理。

此工作中變更的檔案