wraith/src-tauri/src/lib.rs
Vantz Stockwell bb5b9469d1 feat: Phase 6 complete — CI/CD pipeline + auto-updater
Gitea Actions workflow: Tauri build on Windows, Azure Key Vault
EV code signing via jsign, NSIS installer, Gitea package upload,
release creation with v-prefixed tag. Tauri updater plugin wired
with Gitea releases endpoint. Shell plugin configured for open().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 16:39:42 -04:00

177 lines
6.2 KiB
Rust

pub mod db;
pub mod vault;
pub mod settings;
pub mod connections;
pub mod credentials;
pub mod ssh;
pub mod sftp;
pub mod rdp;
pub mod theme;
pub mod workspace;
pub mod commands;
use std::path::PathBuf;
use std::sync::Mutex;
use db::Database;
use vault::VaultService;
use credentials::CredentialService;
use settings::SettingsService;
use connections::ConnectionService;
use sftp::SftpService;
use ssh::session::SshService;
use rdp::RdpService;
use theme::ThemeService;
use workspace::WorkspaceService;
/// Application state shared across all Tauri commands via State<AppState>.
pub struct AppState {
pub db: Database,
pub vault: Mutex<Option<VaultService>>,
pub settings: SettingsService,
pub connections: ConnectionService,
pub credentials: Mutex<Option<CredentialService>>,
pub ssh: SshService,
pub sftp: SftpService,
pub rdp: RdpService,
pub theme: ThemeService,
pub workspace: WorkspaceService,
}
impl AppState {
pub fn new(data_dir: PathBuf) -> Result<Self, Box<dyn std::error::Error>> {
std::fs::create_dir_all(&data_dir)?;
let db_path = data_dir.join("wraith.db");
let database = Database::open(&db_path)?;
database.migrate()?;
let settings = SettingsService::new(database.clone());
let connections = ConnectionService::new(database.clone());
let ssh = SshService::new(database.clone());
let sftp = SftpService::new();
let rdp = RdpService::new();
let theme = ThemeService::new(database.clone());
// WorkspaceService shares the same SettingsService interface; we clone
// the Database to construct a second SettingsService for the workspace
// module so it can remain self-contained.
let workspace_settings = SettingsService::new(database.clone());
let workspace = WorkspaceService::new(workspace_settings);
Ok(Self {
db: database,
vault: Mutex::new(None),
settings,
connections,
credentials: Mutex::new(None),
ssh,
sftp,
rdp,
theme,
workspace,
})
}
/// Returns true if the vault has never been set up.
pub fn is_first_run(&self) -> bool {
self.settings.get("vault_salt").unwrap_or_default().is_empty()
}
/// Returns true if the vault is currently unlocked.
pub fn is_unlocked(&self) -> bool {
self.vault.lock().unwrap().is_some()
}
}
/// Determine the data directory for Wraith.
pub fn data_directory() -> PathBuf {
// Windows: %APPDATA%\Wraith
if let Ok(appdata) = std::env::var("APPDATA") {
return PathBuf::from(appdata).join("Wraith");
}
// macOS/Linux: XDG_DATA_HOME or ~/.local/share/wraith
if let Ok(home) = std::env::var("HOME") {
if let Ok(xdg) = std::env::var("XDG_DATA_HOME") {
return PathBuf::from(xdg).join("wraith");
}
return PathBuf::from(home).join(".local").join("share").join("wraith");
}
// Fallback
PathBuf::from(".")
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let data_dir = data_directory();
let app_state = AppState::new(data_dir)
.expect("Failed to initialize application state");
// Seed built-in themes (INSERT OR IGNORE — safe to call on every boot).
app_state.theme.seed_builtins();
// Crash recovery detection: log dirty shutdowns so they can be acted on.
if app_state.workspace.was_clean_shutdown() {
app_state
.workspace
.clear_clean_shutdown()
.unwrap_or_else(|e| eprintln!("workspace: failed to clear clean-shutdown flag: {e}"));
} else {
// No clean-shutdown flag found — either first run or a crash/kill.
// Only log if a snapshot exists (i.e. there were open tabs last time).
if app_state.workspace.load().is_some() {
eprintln!("workspace: dirty shutdown detected — a previous session may not have exited cleanly");
}
}
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.manage(app_state)
.invoke_handler(tauri::generate_handler![
commands::vault::is_first_run,
commands::vault::create_vault,
commands::vault::unlock,
commands::vault::is_unlocked,
commands::settings::get_setting,
commands::settings::set_setting,
commands::connections::list_connections,
commands::connections::create_connection,
commands::connections::get_connection,
commands::connections::update_connection,
commands::connections::delete_connection,
commands::connections::list_groups,
commands::connections::create_group,
commands::connections::delete_group,
commands::connections::rename_group,
commands::connections::search_connections,
commands::credentials::list_credentials,
commands::credentials::create_password,
commands::credentials::create_ssh_key,
commands::credentials::delete_credential,
commands::ssh_commands::connect_ssh,
commands::ssh_commands::connect_ssh_with_key,
commands::ssh_commands::ssh_write,
commands::ssh_commands::ssh_resize,
commands::ssh_commands::disconnect_ssh,
commands::ssh_commands::list_ssh_sessions,
commands::sftp_commands::sftp_list,
commands::sftp_commands::sftp_read_file,
commands::sftp_commands::sftp_write_file,
commands::sftp_commands::sftp_mkdir,
commands::sftp_commands::sftp_delete,
commands::sftp_commands::sftp_rename,
commands::rdp_commands::connect_rdp,
commands::rdp_commands::rdp_get_frame,
commands::rdp_commands::rdp_send_mouse,
commands::rdp_commands::rdp_send_key,
commands::rdp_commands::disconnect_rdp,
commands::rdp_commands::list_rdp_sessions,
commands::theme_commands::list_themes,
commands::theme_commands::get_theme,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}