Host-Key-Popup mit Fingerprint + ctty-run-Helper (Ctty fuer ssh-Prompts), Doku

This commit is contained in:
x3
2026-09-22 09:31:08 +02:00
parent 8511e4fdae
commit 4209c481dc
9 changed files with 375 additions and 6 deletions
+37 -3
View File
@@ -80,10 +80,22 @@ final class PTYSession: ObservableObject {
}
masterFD = master
// Ctty-Helper (gleiches Verzeichnis wie dieses Binary — .app wie .build):
// Gibt ssh ein echtes Controlling-Terminal, damit Passwort- und
// Yes/No-Prompts funktionieren (/dev/tty statt stiller Abbrüche).
// Ohne Helper: Legacy-Direktstart (Prompts teils stumm).
let pipe = Pipe()
let p = Process()
p.executableURL = URL(fileURLWithPath: executable)
p.arguments = arguments
if let helper = Self.cttyHelperURL() {
p.executableURL = helper
p.arguments = [slavePath, executable] + arguments
} else {
p.executableURL = URL(fileURLWithPath: executable)
p.arguments = arguments
DispatchQueue.main.async { [weak self] in
self?.note("Hinweis: ohne ctty-run-Helper (eingeschränkte Prompts).")
}
}
var env = ProcessInfo.processInfo.environment
env["TERM"] = "xterm-256color"
if env["LANG"] == nil { env["LANG"] = "de_DE.UTF-8" }
@@ -225,6 +237,17 @@ final class PTYSession: ObservableObject {
lines = [[]]
}
/// ctty-run liegt neben diesem Binary (Contents/MacOS wie .build/debug).
static func cttyHelperURL() -> URL? {
let sibling = URL(fileURLWithPath: CommandLine.arguments[0])
.deletingLastPathComponent()
.appendingPathComponent("ctty-run")
if FileManager.default.isExecutableFile(atPath: sibling.path) {
return sibling
}
return nil
}
func note(_ message: String) {
lines.append([StyledText(text: message, style: TermStyle(fg: 8))])
}
@@ -241,7 +264,7 @@ final class PTYSession: ObservableObject {
func stop() {
let wasRunning = isRunning
process?.terminate()
killTree()
cleanupSync()
if wasRunning {
epoch += 1 // späte terminationHandler-Ausläufer verwerfen
@@ -252,6 +275,14 @@ final class PTYSession: ObservableObject {
}
}
/// Beendet Kind + Enkel (ctty-run-Wächter + ssh teilen die Prozessgruppe).
private func killTree() {
if let pid = process?.processIdentifier, pid > 0 {
Darwin.kill(-pid, SIGTERM)
}
process?.terminate()
}
/// Synchroner Cleanup auf Main: Source canceln, Master schließen, Pipe abklemmen.
/// Laufende ioQueue-Reads bekommen EBADF und beenden sich selbst.
private func cleanupSync() {
@@ -278,6 +309,9 @@ final class PTYSession: ObservableObject {
}
deinit {
if let pid = process?.processIdentifier, pid > 0 {
Darwin.kill(-pid, SIGTERM)
}
process?.terminate()
readSource?.cancel()
if masterFD >= 0 { close(masterFD) }