Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0855ab45d |
@@ -11,9 +11,7 @@ struct EmbeddedTerminalView: View {
|
|||||||
@State private var input = ""
|
@State private var input = ""
|
||||||
@State private var secureInput = false
|
@State private var secureInput = false
|
||||||
@State private var checking = false
|
@State private var checking = false
|
||||||
@State private var hostKeyInfo: HostKeyInfo?
|
@State private var hostKeyRequest: HostKeyRequest?
|
||||||
@State private var hostKeyChange = false
|
|
||||||
@State private var showingHostKey = false
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
@@ -101,17 +99,14 @@ struct EmbeddedTerminalView: View {
|
|||||||
connect() // Auto-Connect beim Öffnen des Tabs
|
connect() // Auto-Connect beim Öffnen des Tabs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showingHostKey) {
|
.sheet(item: $hostKeyRequest) { req in
|
||||||
if let info = hostKeyInfo {
|
HostKeySheet(info: req.info, isChange: req.isChange,
|
||||||
HostKeySheet(info: info, isChange: hostKeyChange,
|
onAccept: { acceptHostKey(req) },
|
||||||
onAccept: acceptHostKey,
|
onCancel: {
|
||||||
onCancel: {
|
hostKeyRequest = nil
|
||||||
showingHostKey = false
|
checking = false
|
||||||
checking = false
|
term.fail("Host-Key nicht akzeptiert — Verbindung abgebrochen.")
|
||||||
hostKeyInfo = nil
|
})
|
||||||
term.fail("Host-Key nicht akzeptiert — Verbindung abgebrochen.")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,40 +120,41 @@ struct EmbeddedTerminalView: View {
|
|||||||
term.fail("Vorab-Check: \(message)")
|
term.fail("Vorab-Check: \(message)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Host-Key vorab per keyscan prüfen (Yes/No-Prompt käme ohne Ctty nicht an)
|
// Host-Key vorab per keyscan prüfen (Yes/No-Prompt käme ohne Ctty nicht an).
|
||||||
let hk = HostKeyService.status(host: profile.host, port: profile.port)
|
// Läuft im Hintergrund: ssh-keyscan blockiert Sekunden (kein UI-Freeze).
|
||||||
switch hk {
|
let host = profile.host
|
||||||
case .known, .unreachable:
|
let port = profile.port
|
||||||
checking = false
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
term.start()
|
let hk = HostKeyService.status(host: host, port: port)
|
||||||
case .unknown(let info):
|
DispatchQueue.main.async {
|
||||||
hostKeyInfo = info
|
switch hk {
|
||||||
hostKeyChange = false
|
case .known, .unreachable:
|
||||||
showingHostKey = true
|
checking = false
|
||||||
case .changed(let info):
|
term.start()
|
||||||
hostKeyInfo = info
|
case .unknown(let info):
|
||||||
hostKeyChange = true
|
hostKeyRequest = HostKeyRequest(info: info, isChange: false)
|
||||||
showingHostKey = true
|
case .changed(let info):
|
||||||
|
hostKeyRequest = HostKeyRequest(info: info, isChange: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func acceptHostKey() {
|
func acceptHostKey(_ req: HostKeyRequest) {
|
||||||
guard let info = hostKeyInfo else { return }
|
hostKeyRequest = nil
|
||||||
showingHostKey = false
|
if req.isChange {
|
||||||
if hostKeyChange {
|
|
||||||
_ = HostKeyService.revoke(host: profile.host, port: profile.port)
|
_ = HostKeyService.revoke(host: profile.host, port: profile.port)
|
||||||
}
|
}
|
||||||
if HostKeyService.accept(info) {
|
if HostKeyService.accept(req.info) {
|
||||||
checking = false
|
checking = false
|
||||||
term.note("Host-Key akzeptiert (\(info.keyType), \(info.fingerprintSHA256.prefix(19))…).")
|
term.note("Host-Key akzeptiert (\(req.info.keyType), \(req.info.fingerprintSHA256.prefix(19))…).")
|
||||||
term.start()
|
term.start()
|
||||||
} else {
|
} else {
|
||||||
checking = false
|
checking = false
|
||||||
term.fail("Host-Key konnte nicht in known_hosts gespeichert werden.")
|
term.fail("Host-Key konnte nicht in known_hosts gespeichert werden.")
|
||||||
}
|
}
|
||||||
hostKeyInfo = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func diagnose() {
|
func diagnose() {
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
/// Sheet-Request (Identifiable): .sheet(item:) rendert IMMER mit Wert —
|
||||||
|
/// ein `if let` im Sheet kann den Empty-Zweig nehmen und bleibt dann leer.
|
||||||
|
struct HostKeyRequest: Identifiable {
|
||||||
|
let id = UUID()
|
||||||
|
var info: HostKeyInfo
|
||||||
|
var isChange: Bool
|
||||||
|
}
|
||||||
|
|
||||||
/// Popup bei unbekanntem/geändertem Host-Key: Fingerprint prüfen, dann
|
/// Popup bei unbekanntem/geändertem Host-Key: Fingerprint prüfen, dann
|
||||||
/// akzeptieren (→ known_hosts + verbinden) oder abbrechen.
|
/// akzeptieren (→ known_hosts + verbinden) oder abbrechen.
|
||||||
struct HostKeySheet: View {
|
struct HostKeySheet: View {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ Stand: 2026-09-21 · Version 0.1.0 · macOS arm64 · Swift 5.9+ · Xcode 15+
|
|||||||
| 16 | 2026-09-22 | Profil-Änderungen wirkungslos | Session-Cache ignorierte Host-/Passwort-/Key-Korrekturen | hoch | ✅ behoben — Fingerprint-Reconfig (9/9 Tests) |
|
| 16 | 2026-09-22 | Profil-Änderungen wirkungslos | Session-Cache ignorierte Host-/Passwort-/Key-Korrekturen | hoch | ✅ behoben — Fingerprint-Reconfig (9/9 Tests) |
|
||||||
| 17 | 2026-09-22 | rzsv: Vorab-Check Timeout | Port 22 wird serverseitig gedroppt (443 ok) — kein App-Fehler | extern | ⬜ Server-Seite: fail2ban/VPN/Firewall prüfen |
|
| 17 | 2026-09-22 | rzsv: Vorab-Check Timeout | Port 22 wird serverseitig gedroppt (443 ok) — kein App-Fehler | extern | ⬜ Server-Seite: fail2ban/VPN/Firewall prüfen |
|
||||||
| 18 | 2026-09-22 | Host-Key-Yes/No unmöglich | ssh ohne Controlling-Terminal: /dev/tty ENXIO → stummer Abbruch | kritisch | ✅ ctty-run-Helper (Double-Fork) + Fingerprint-Popup |
|
| 18 | 2026-09-22 | Host-Key-Yes/No unmöglich | ssh ohne Controlling-Terminal: /dev/tty ENXIO → stummer Abbruch | kritisch | ✅ ctty-run-Helper (Double-Fork) + Fingerprint-Popup |
|
||||||
|
| 19 | 2026-09-22 | Popup leer (s. Screenshot) | `if let` im Sheet nimmt Empty-Zweig ohne Update-Pfad | mittel | ✅ `.sheet(item:)` + Keyscan im Hintergrund |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -241,3 +242,9 @@ Zweite Zeile erzwingt Passwort-Login ohne Agent — scheitert sie auch im Termin
|
|||||||
- `Views/HostKeySheet.swift` + Flow: Vorab per `ssh-keyscan` Fingerprint holen → Popup (SHA256/MD5, Typ) → Akzeptieren (→ `~/.ssh/known_hosts`, geteilt mit Terminal) / Abbrechen; bei GEÄNDERTEM Key rote MITM-Warnung + `ssh-keygen -R`-Pfad.
|
- `Views/HostKeySheet.swift` + Flow: Vorab per `ssh-keyscan` Fingerprint holen → Popup (SHA256/MD5, Typ) → Akzeptieren (→ `~/.ssh/known_hosts`, geteilt mit Terminal) / Abbrechen; bei GEÄNDERTEM Key rote MITM-Warnung + `ssh-keygen -R`-Pfad.
|
||||||
**Verifikation (lokaler paramiko-Server):** Bug reproduziert (kein Prompt, Exit 255) → nach Fix: Ctty per `ps` bewiesen (ttys001), Yes/No sichtbar + beantwortbar, Passwort akzeptiert, Shell aktiv. HostKeyService: unknown/accept/known/changed/revoke/unreachable grün. Regression (stop ohne Waisen) grün.
|
**Verifikation (lokaler paramiko-Server):** Bug reproduziert (kein Prompt, Exit 255) → nach Fix: Ctty per `ps` bewiesen (ttys001), Yes/No sichtbar + beantwortbar, Passwort akzeptiert, Shell aktiv. HostKeyService: unknown/accept/known/changed/revoke/unreachable grün. Regression (stop ohne Waisen) grün.
|
||||||
**Status:** ✅ behoben (v0.6.0) — Fingerprint im Popup mit Terminal-Ausgabe vergleichbar.
|
**Status:** ✅ behoben (v0.6.0) — Fingerprint im Popup mit Terminal-Ausgabe vergleichbar.
|
||||||
|
|
||||||
|
## #19 — Fingerprint-Popup erscheint leer (User-Screenshot)
|
||||||
|
**Bild:** Sheet-Fenster da, aber winzig und ohne Inhalt; Status hängt auf „Prüfe …".
|
||||||
|
**Ursache:** `.sheet(isPresented:)` + `if let info` im Content: Nimmt die Auswertung den Empty-Zweig (Timing beim Präsentieren), hat der leere Zweig keine Abhängigkeit auf die State-Variable → Sheet aktualisiert sich nie, bleibt leer. Klassische SwiftUI-Falle.
|
||||||
|
**Fix:** `.sheet(item: $hostKeyRequest)` mit `HostKeyRequest: Identifiable` — Content existiert strukturell immer. Zusätzlich läuft `HostKeyService.status` (ssh-keyscan blockiert Sekunden) jetzt auf Hintergrund-Queue — vorher fror die UI während „Prüfe …" ein.
|
||||||
|
**Status:** ✅ behoben (v0.6.1).
|
||||||
|
|||||||
Reference in New Issue
Block a user