Major update for stability

This commit is contained in:
Mark Gregory
2026-09-08 15:22:19 +01:00
parent 3b95f53a25
commit b2f3c5aef6
24 changed files with 1059 additions and 212 deletions
+22 -18
View File
@@ -10,8 +10,7 @@ struct SettingsView: View {
@AppStorage("backgroundSchedulesEnabled") private var backgroundSchedulesEnabled = true
@AppStorage("fileWatchingEnabled") private var fileWatchingEnabled = true
@AppStorage("defaultWatchDebounceSeconds") private var defaultWatchDebounceSeconds = 3
@State private var defaultPassword = ""
@State private var passwordSaved = false
@AppStorage("historyRetentionDays") private var historyRetentionDays = 90
@State private var launchAtLogin = false
@State private var launchError: String?
@@ -30,18 +29,9 @@ struct SettingsView: View {
}
}
SecureField("Default password", text: $defaultPassword)
HStack {
Text("Stored securely in the macOS Keychain, never in SwiftData or UserDefaults.")
.font(.caption)
.foregroundStyle(.secondary)
Spacer()
Button(passwordSaved ? "Saved" : "Save Password") {
KeychainStore.set(defaultPassword, for: "defaultSSHPassword")
passwordSaved = true
}
.disabled(defaultPassword.isEmpty)
}
Label("Authentication uses an SSH private key or your macOS SSH agent. Passwords are intentionally not stored or passed on the command line.", systemImage: "lock.shield")
.font(.caption)
.foregroundStyle(.secondary)
}
Section("New Jobs") {
@@ -71,6 +61,13 @@ struct SettingsView: View {
.foregroundStyle(.secondary)
}
Section("History & Privacy") {
Stepper("Keep run history for \(historyRetentionDays) days", value: $historyRetentionDays, in: 7...3650, step: 7)
Text("Run output is sanitised, ANSI control sequences are removed, and individual stdout/stderr logs are capped at approximately 2 MB before storage.")
.font(.caption)
.foregroundStyle(.secondary)
}
Section("File Watching") {
Toggle("Watch enabled jobs for changes", isOn: $fileWatchingEnabled)
Stepper("Default debounce: \(defaultWatchDebounceSeconds) seconds", value: $defaultWatchDebounceSeconds, in: 1...60)
@@ -91,7 +88,16 @@ struct SettingsView: View {
Form {
Section("Rsync") {
LabeledContent("Detected executable", value: RsyncLocator.locate())
Text("Rsync Manager prefers Homebrew rsync, then /usr/local/bin/rsync, and finally the version supplied with macOS.")
LabeledContent("Executable validation", value: RsyncLocator.isTrustedExecutableCandidate(RsyncLocator.locate()) ? "Passed" : "Failed")
Text("Only executable regular-file rsync candidates are accepted. Symlinks are resolved before validation.")
.font(.caption)
.foregroundStyle(.secondary)
}
Section("Security") {
LabeledContent("SSH mode", value: "Batch / key or agent")
LabeledContent("Host-key verification", value: "System default")
Text("SSH connections use BatchMode, connection timeouts and keepalive limits. Rsync Manager never disables SSH host-key verification.")
.font(.caption)
.foregroundStyle(.secondary)
}
@@ -107,11 +113,9 @@ struct SettingsView: View {
}
.frame(width: 650, height: 455)
.onAppear {
defaultPassword = KeychainStore.string(for: "defaultSSHPassword")
passwordSaved = !defaultPassword.isEmpty
KeychainStore.delete("defaultSSHPassword")
launchAtLogin = SMAppService.mainApp.status == .enabled
}
.onChange(of: defaultPassword) { _, _ in passwordSaved = false }
.onChange(of: backgroundSchedulesEnabled) { _, _ in automation.refreshConfiguration() }
.onChange(of: fileWatchingEnabled) { _, _ in automation.refreshConfiguration() }
.alert("Launch at Login", isPresented: Binding(