tftsr-devops_investigation/src-tauri/src/lib.rs
Shaun Arman f0358cfb13 fix(db,auth): auto-generate encryption keys for release builds
Fixes two critical issues preventing Mac release builds from working:

1. Database encryption key auto-generation: Release builds now
   auto-generate and persist the SQLCipher encryption key to
   ~/.../trcaa/.dbkey (mode 0600) instead of requiring the
   TFTSR_DB_KEY env var. This prevents 'file is not a database'
   errors when users don't set the env var.

2. Plain SQLite to encrypted migration: When a release build
   encounters a plain SQLite database (from a previous debug build),
   it now automatically migrates it to encrypted SQLCipher format
   using ATTACH DATABASE + sqlcipher_export. Creates a backup at
   .db.plain-backup before migration.

3. Credential encryption key auto-generation: Applied the same
   pattern to TFTSR_ENCRYPTION_KEY for encrypting AI provider API
   keys and integration tokens. Release builds now auto-generate
   and persist to ~/.../trcaa/.enckey (mode 0600) instead of
   failing with 'TFTSR_ENCRYPTION_KEY must be set'.

4. Refactored app data directory helper: Moved dirs_data_dir()
   from lib.rs to state.rs as get_app_data_dir() so it can be
   reused by both database and auth modules.

Testing:
- All unit tests pass (db::connection::tests + integrations::auth::tests)
- Verified manual migration from plain to encrypted database
- No clippy warnings

Impact: Users installing the Mac release build will now have a
working app out-of-the-box without needing to set environment
variables. Developers switching from debug to release builds will
have their databases automatically migrated.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-07 09:35:34 -05:00

150 lines
5.7 KiB
Rust

pub mod ai;
pub mod audit;
pub mod commands;
pub mod db;
pub mod docs;
pub mod integrations;
pub mod ollama;
pub mod pii;
pub mod state;
use sha2::{Digest, Sha256};
use state::AppState;
use std::sync::{Arc, Mutex};
use tauri::Manager;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
// Initialize tracing subscriber for structured logging
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
)
.init();
tracing::info!("Starting Troubleshooting and RCA Assistant application");
// Determine data directory
let data_dir = state::get_app_data_dir().expect("Failed to determine app data directory");
// Initialize database
let conn = db::connection::init_db(&data_dir).expect("Failed to initialize database");
tracing::info!("Database initialized at {:?}", data_dir);
let app_state = AppState {
db: Arc::new(Mutex::new(conn)),
settings: Arc::new(Mutex::new(state::AppSettings::default())),
app_data_dir: data_dir.clone(),
integration_webviews: Arc::new(Mutex::new(std::collections::HashMap::new())),
};
let stronghold_salt = format!(
"tftsr-stronghold-salt-v1-{:x}",
Sha256::digest(data_dir.to_string_lossy().as_bytes())
);
tauri::Builder::default()
.plugin(
tauri_plugin_stronghold::Builder::new(move |password| {
let mut hasher = Sha256::new();
hasher.update(password);
hasher.update(stronghold_salt.as_bytes());
hasher.finalize().to_vec()
})
.build(),
)
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_http::init())
.manage(app_state)
.setup(|app| {
// Restore persistent browser windows from previous session
let app_handle = app.handle().clone();
let state: tauri::State<AppState> = app.state();
// Clone Arc fields for 'static lifetime
let db = state.db.clone();
let settings = state.settings.clone();
let app_data_dir = state.app_data_dir.clone();
let integration_webviews = state.integration_webviews.clone();
tauri::async_runtime::spawn(async move {
let app_state = AppState {
db,
settings,
app_data_dir,
integration_webviews,
};
if let Err(e) =
commands::integrations::restore_persistent_webviews(&app_handle, &app_state)
.await
{
tracing::warn!("Failed to restore persistent webviews: {}", e);
}
});
Ok(())
})
.invoke_handler(tauri::generate_handler![
// DB / Issue CRUD
commands::db::create_issue,
commands::db::get_issue,
commands::db::update_issue,
commands::db::delete_issue,
commands::db::list_issues,
commands::db::search_issues,
commands::db::get_issue_messages,
commands::db::add_five_why,
commands::db::update_five_why,
commands::db::add_timeline_event,
// Analysis / PII
commands::analysis::upload_log_file,
commands::analysis::detect_pii,
commands::analysis::apply_redactions,
// AI
commands::ai::analyze_logs,
commands::ai::chat_message,
commands::ai::test_provider_connection,
commands::ai::list_providers,
// Docs
commands::docs::generate_rca,
commands::docs::generate_postmortem,
commands::docs::update_document,
commands::docs::export_document,
// Integrations
commands::integrations::test_confluence_connection,
commands::integrations::publish_to_confluence,
commands::integrations::test_servicenow_connection,
commands::integrations::create_servicenow_incident,
commands::integrations::test_azuredevops_connection,
commands::integrations::create_azuredevops_workitem,
commands::integrations::initiate_oauth,
commands::integrations::handle_oauth_callback,
commands::integrations::authenticate_with_webview,
commands::integrations::extract_cookies_from_webview,
commands::integrations::save_manual_token,
commands::integrations::save_integration_config,
commands::integrations::get_integration_config,
commands::integrations::get_all_integration_configs,
commands::integrations::add_ado_comment,
// System / Settings
commands::system::check_ollama_installed,
commands::system::get_ollama_install_guide,
commands::system::list_ollama_models,
commands::system::pull_ollama_model,
commands::system::delete_ollama_model,
commands::system::detect_hardware,
commands::system::recommend_models,
commands::system::get_settings,
commands::system::update_settings,
commands::system::get_audit_log,
commands::system::save_ai_provider,
commands::system::load_ai_providers,
commands::system::delete_ai_provider,
])
.run(tauri::generate_context!())
.expect("Error running Troubleshooting and RCA Assistant application");
}