feat: Settings persistent + Auto-Login + Installer Update-Modus

Settings-Persistenz:
- Config wird in OS-AppData gespeichert
  (Windows: %APPDATA%/MiniCloud Sync/config.json,
   Linux: ~/.config/MiniCloud Sync/config.json,
   Mac: ~/Library/Application Support/MiniCloud Sync/config.json)
- Gespeichert werden: Server-URL, Username, Sync-Pfade
- Passwort wird im OS-Keychain gespeichert (Windows Credential Manager,
  macOS Keychain, Linux Secret Service) - nicht in der Config-Datei

Auto-Login:
- Beim Start wird gespeicherte Config geladen
- Wenn Credentials im Keychain vorhanden: automatischer Login
- Wenn Sync-Pfade konfiguriert: Sync startet sofort automatisch
- Bei Fehler: Login-Screen mit vorausgefuellten Feldern

Config ueberlebt Updates:
- Config liegt ausserhalb des Installationsverzeichnisses
- NSIS-Installer ueberschreibt nur App-Dateien, nicht AppData
- installMode: "both" erlaubt per-User und per-Machine Installation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Hacker
2026-04-12 01:11:42 +02:00
parent 81574c8991
commit ac5a0a3367
5 changed files with 158 additions and 2 deletions
@@ -0,0 +1,61 @@
use crate::sync::engine::SyncPath;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AppConfig {
pub server_url: String,
pub username: String,
pub sync_paths: Vec<SyncPath>,
#[serde(default)]
pub auto_start: bool,
}
impl AppConfig {
/// Get the config file path (OS-specific app data directory)
fn config_path() -> PathBuf {
let config_dir = dirs::config_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("MiniCloud Sync");
std::fs::create_dir_all(&config_dir).ok();
config_dir.join("config.json")
}
/// Load config from disk
pub fn load() -> Self {
let path = Self::config_path();
if path.exists() {
match std::fs::read_to_string(&path) {
Ok(content) => {
match serde_json::from_str(&content) {
Ok(config) => return config,
Err(e) => eprintln!("[Config] Parse-Fehler: {}", e),
}
}
Err(e) => eprintln!("[Config] Lese-Fehler: {}", e),
}
}
Self::default()
}
/// Save config to disk
pub fn save(&self) -> Result<(), String> {
let path = Self::config_path();
let json = serde_json::to_string_pretty(self).map_err(|e| e.to_string())?;
std::fs::write(&path, json).map_err(|e| format!("Config speichern: {}", e))
}
/// Save password to OS keychain
pub fn save_password(server_url: &str, username: &str, password: &str) -> Result<(), String> {
let service = format!("minicloud-sync-{}", server_url);
let entry = keyring::Entry::new(&service, username).map_err(|e| e.to_string())?;
entry.set_password(password).map_err(|e| format!("Keychain: {}", e))
}
/// Load password from OS keychain
pub fn load_password(server_url: &str, username: &str) -> Option<String> {
let service = format!("minicloud-sync-{}", server_url);
let entry = keyring::Entry::new(&service, username).ok()?;
entry.get_password().ok()
}
}
@@ -1,3 +1,4 @@
pub mod api;
pub mod config;
pub mod engine;
pub mod watcher;