import SwiftUI import AppKit struct SSHKeysView: View { @EnvironmentObject var store: ProfileStore @Environment(\.dismiss) var dismiss @State private var showingAdd = false @State private var newName = "" @State private var newPath = "" @State private var newPassphrase = "" var body: some View { VStack { Text("SSH-Keys verwalten").font(.headline).padding() List { ForEach(store.sshKeys) { key in VStack(alignment: .leading) { Text(key.name).font(.headline) Text(key.privateKeyPath).font(.caption).foregroundStyle(.secondary).textSelection(.enabled) } .contextMenu { Button("Löschen", role: .destructive) { store.sshKeys.removeAll { $0.id == key.id } store.save() } } } .onDelete { idx in store.sshKeys.remove(atOffsets: idx) store.save() } } if showingAdd { Form { TextField("Name", text: $newName) HStack { TextField("Privater Key-Pfad (~/.ssh/id_ed25519)", text: $newPath) Button("Wählen…") { chooseFile() } } SecureField("Passphrase (Keychain)", text: $newPassphrase) } .padding() } HStack { Button("Import aus ~/.ssh") { importSSHDir() } Button(showingAdd ? "Abbrechen" : "Hinzufügen") { showingAdd.toggle() } Spacer() if showingAdd { Button("Speichern") { saveNew() }.keyboardShortcut(.defaultAction).disabled(newName.isEmpty || newPath.isEmpty) } else { Button("Neuen Key generieren (ssh-keygen)") { generateKey() } Button("Fertig") { dismiss() }.keyboardShortcut(.defaultAction) } } .padding() } .frame(width: 600, height: 500) } func chooseFile() { let panel = NSOpenPanel() panel.canChooseFiles = true panel.allowsMultipleSelection = false panel.directoryURL = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".ssh") if panel.runModal() == .OK, let url = panel.url { newPath = url.path if newName.isEmpty { newName = url.lastPathComponent } } } func saveNew() { var entry = StoredSSHKey(name: newName, privateKeyPath: newPath, publicKeyPath: newPath + ".pub") if !newPassphrase.isEmpty { entry.passphraseAccount = "sshkey-\(entry.id.uuidString)" _ = KeychainHelper.save(newPassphrase, account: entry.passphraseAccount!) } store.sshKeys.append(entry) store.save() newName = ""; newPath = ""; newPassphrase = ""; showingAdd = false } func importSSHDir() { let sshDir = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".ssh").path guard let files = try? FileManager.default.contentsOfDirectory(atPath: sshDir) else { return } for f in files where !f.hasSuffix(".pub") && !f.contains("known_hosts") && !f.contains("config") { let full = (sshDir as NSString).appendingPathComponent(f) if store.sshKeys.contains(where: { $0.privateKeyPath == full }) { continue } store.sshKeys.append(StoredSSHKey(name: f, privateKeyPath: full)) } store.save() } func generateKey() { let task = Process() task.executableURL = URL(fileURLWithPath: "/usr/bin/ssh-keygen") let outPath = FileManager.default.homeDirectoryForCurrentUser .appendingPathComponent(".ssh/remminamac_\(Int(Date().timeIntervalSince1970))").path task.arguments = ["-t", "ed25519", "-f", outPath, "-N", ""] try? task.run() task.waitUntilExit() store.sshKeys.append(StoredSSHKey(name: URL(fileURLWithPath: outPath).lastPathComponent, privateKeyPath: outPath, publicKeyPath: outPath + ".pub")) store.save() } }