Merge pull request 'feat(rebrand): rename binary to trcaa and auto-generate DB key' (#18) from feat/rebrand-binary-trcaa into master

Reviewed-on: #18
This commit is contained in:
sarman 2026-04-05 23:17:05 +00:00
commit a40bc2304f
4 changed files with 62 additions and 15 deletions

View File

@ -1,5 +1,5 @@
[package] [package]
name = "tftsr" name = "trcaa"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"

View File

@ -1,7 +1,34 @@
use rusqlite::Connection; use rusqlite::Connection;
use std::path::Path; use std::path::Path;
fn get_db_key() -> anyhow::Result<String> { fn generate_key() -> String {
use rand::RngCore;
let mut bytes = [0u8; 32];
rand::rngs::OsRng.fill_bytes(&mut bytes);
hex::encode(bytes)
}
#[cfg(unix)]
fn write_key_file(path: &Path, key: &str) -> anyhow::Result<()> {
use std::io::Write;
use std::os::unix::fs::OpenOptionsExt;
let mut f = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.mode(0o600)
.open(path)?;
f.write_all(key.as_bytes())?;
Ok(())
}
#[cfg(not(unix))]
fn write_key_file(path: &Path, key: &str) -> anyhow::Result<()> {
std::fs::write(path, key)?;
Ok(())
}
fn get_db_key(data_dir: &Path) -> anyhow::Result<String> {
if let Ok(key) = std::env::var("TFTSR_DB_KEY") { if let Ok(key) = std::env::var("TFTSR_DB_KEY") {
if !key.trim().is_empty() { if !key.trim().is_empty() {
return Ok(key); return Ok(key);
@ -12,9 +39,22 @@ fn get_db_key() -> anyhow::Result<String> {
return Ok("dev-key-change-in-prod".to_string()); return Ok("dev-key-change-in-prod".to_string());
} }
Err(anyhow::anyhow!( // Release: load or auto-generate a per-installation key stored in the
"TFTSR_DB_KEY must be set in release builds" // app data directory. This lets the app work out of the box without
)) // requiring users to set an environment variable.
let key_path = data_dir.join(".dbkey");
if key_path.exists() {
let key = std::fs::read_to_string(&key_path)?;
let key = key.trim().to_string();
if !key.is_empty() {
return Ok(key);
}
}
let key = generate_key();
std::fs::create_dir_all(data_dir)?;
write_key_file(&key_path, &key)?;
Ok(key)
} }
pub fn open_encrypted_db(path: &Path, key: &str) -> anyhow::Result<Connection> { pub fn open_encrypted_db(path: &Path, key: &str) -> anyhow::Result<Connection> {
@ -45,8 +85,7 @@ pub fn init_db(data_dir: &Path) -> anyhow::Result<Connection> {
std::fs::create_dir_all(data_dir)?; std::fs::create_dir_all(data_dir)?;
let db_path = data_dir.join("tftsr.db"); let db_path = data_dir.join("tftsr.db");
// In dev/test mode use unencrypted DB; in production use encryption let key = get_db_key(data_dir)?;
let key = get_db_key()?;
let conn = if cfg!(debug_assertions) { let conn = if cfg!(debug_assertions) {
open_dev_db(&db_path)? open_dev_db(&db_path)?
@ -62,18 +101,26 @@ pub fn init_db(data_dir: &Path) -> anyhow::Result<Connection> {
mod tests { mod tests {
use super::*; use super::*;
fn temp_dir(name: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("tftsr-test-{}", name));
std::fs::create_dir_all(&dir).unwrap();
dir
}
#[test] #[test]
fn test_get_db_key_uses_env_var_when_present() { fn test_get_db_key_uses_env_var_when_present() {
let dir = temp_dir("env-var");
std::env::set_var("TFTSR_DB_KEY", "test-db-key"); std::env::set_var("TFTSR_DB_KEY", "test-db-key");
let key = get_db_key().unwrap(); let key = get_db_key(&dir).unwrap();
assert_eq!(key, "test-db-key"); assert_eq!(key, "test-db-key");
std::env::remove_var("TFTSR_DB_KEY"); std::env::remove_var("TFTSR_DB_KEY");
} }
#[test] #[test]
fn test_get_db_key_debug_fallback_for_empty_env() { fn test_get_db_key_debug_fallback_for_empty_env() {
let dir = temp_dir("empty-env");
std::env::set_var("TFTSR_DB_KEY", " "); std::env::set_var("TFTSR_DB_KEY", " ");
let key = get_db_key().unwrap(); let key = get_db_key(&dir).unwrap();
assert_eq!(key, "dev-key-change-in-prod"); assert_eq!(key, "dev-key-change-in-prod");
std::env::remove_var("TFTSR_DB_KEY"); std::env::remove_var("TFTSR_DB_KEY");
} }

View File

@ -124,13 +124,13 @@ fn dirs_data_dir() -> std::path::PathBuf {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
if let Ok(xdg) = std::env::var("XDG_DATA_HOME") { if let Ok(xdg) = std::env::var("XDG_DATA_HOME") {
return std::path::PathBuf::from(xdg).join("tftsr"); return std::path::PathBuf::from(xdg).join("trcaa");
} }
if let Ok(home) = std::env::var("HOME") { if let Ok(home) = std::env::var("HOME") {
return std::path::PathBuf::from(home) return std::path::PathBuf::from(home)
.join(".local") .join(".local")
.join("share") .join("share")
.join("tftsr"); .join("trcaa");
} }
} }
@ -140,17 +140,17 @@ fn dirs_data_dir() -> std::path::PathBuf {
return std::path::PathBuf::from(home) return std::path::PathBuf::from(home)
.join("Library") .join("Library")
.join("Application Support") .join("Application Support")
.join("tftsr"); .join("trcaa");
} }
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
if let Ok(appdata) = std::env::var("APPDATA") { if let Ok(appdata) = std::env::var("APPDATA") {
return std::path::PathBuf::from(appdata).join("tftsr"); return std::path::PathBuf::from(appdata).join("trcaa");
} }
} }
// Fallback // Fallback
std::path::PathBuf::from("./tftsr-data") std::path::PathBuf::from("./trcaa-data")
} }

View File

@ -1,7 +1,7 @@
{ {
"productName": "Troubleshooting and RCA Assistant", "productName": "Troubleshooting and RCA Assistant",
"version": "0.2.10", "version": "0.2.10",
"identifier": "com.tftsr.devops", "identifier": "com.trcaa.app",
"build": { "build": {
"frontendDist": "../dist", "frontendDist": "../dist",
"devUrl": "http://localhost:1420", "devUrl": "http://localhost:1420",