RemminaMac 0.1.0: SSH/RDP-Client für macOS (SwiftUI, Remmina-Layout, Profile, SSH-Keys, Docs, Release-Skripte)

This commit is contained in:
x3
2026-09-21 22:43:57 +02:00
commit cd834dce98
21 changed files with 1252 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
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()
}
}