diff --git a/Cargo.lock b/Cargo.lock index 976ef52..959d13e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2449,7 +2449,7 @@ dependencies = [ [[package]] name = "trapfall_alert" -version = "0.1.4" +version = "0.2.0" dependencies = [ "chrono", "serde", @@ -2462,7 +2462,7 @@ dependencies = [ [[package]] name = "trapfall_core" -version = "0.1.4" +version = "0.2.0" dependencies = [ "anyhow", "argon2", @@ -2482,7 +2482,7 @@ dependencies = [ [[package]] name = "trapfall_dashboard" -version = "0.1.4" +version = "0.2.0" dependencies = [ "axum", "rust-embed", @@ -2497,7 +2497,7 @@ dependencies = [ [[package]] name = "trapfall_db" -version = "0.1.4" +version = "0.2.0" dependencies = [ "anyhow", "async-trait", @@ -2515,7 +2515,7 @@ dependencies = [ [[package]] name = "trapfall_ingest" -version = "0.1.4" +version = "0.2.0" dependencies = [ "anyhow", "axum", @@ -2531,7 +2531,7 @@ dependencies = [ [[package]] name = "trapfall_mcp" -version = "0.1.4" +version = "0.2.0" dependencies = [ "anyhow", "serde", @@ -2546,7 +2546,7 @@ dependencies = [ [[package]] name = "trapfall_proto" -version = "0.1.4" +version = "0.2.0" dependencies = [ "chrono", "serde", @@ -2557,7 +2557,7 @@ dependencies = [ [[package]] name = "trapfall_search" -version = "0.1.4" +version = "0.2.0" dependencies = [ "anyhow", "sqlx", @@ -2569,7 +2569,7 @@ dependencies = [ [[package]] name = "trapfalld" -version = "0.1.4" +version = "0.2.0" dependencies = [ "anyhow", "axum", diff --git a/crates/trapfalld/src/config.rs b/crates/trapfalld/src/config.rs index 535a8c9..5bf76b6 100644 --- a/crates/trapfalld/src/config.rs +++ b/crates/trapfalld/src/config.rs @@ -85,6 +85,35 @@ impl Config { public_url: parse_public_url(), } } + + /// Returns the default data directory: `~/.codecora/trapfall/`. + /// + /// Override with `TRAPFALL_DATA_DIR` env var. + pub fn default_data_dir() -> PathBuf { + if let Ok(dir) = std::env::var("TRAPFALL_DATA_DIR") { + return PathBuf::from(dir); + } + // Use $HOME on Unix, fallback to current directory + let home = std::env::var("HOME").or_else(|_| std::env::var("USERPROFILE")).unwrap_or_else(|_| ".".into()); + PathBuf::from(home).join(".codecora").join("trapfall") + } + + /// Returns the default database path: `~/.codecora/trapfall/trapfall.db`. + /// + /// Only used when no explicit `TRAPFALL_DATABASE_URL` or `--db` is provided. + pub fn default_db_path() -> String { + let dir = Self::default_data_dir(); + dir.join("trapfall.db").to_string_lossy().to_string() + } + + /// Ensure the data directory exists, creating it if necessary. + pub fn ensure_data_dir(dir: &PathBuf) -> std::io::Result<()> { + if !dir.exists() { + std::fs::create_dir_all(dir)?; + tracing::info!("Created data directory: {}", dir.display()); + } + Ok(()) + } } /// Parse `TRAPFALL_CORS_ORIGINS` (comma-separated, trimmed, empty filtered). @@ -135,7 +164,7 @@ mod tests { fn base_cfg() -> Config { Config { - db_path: PathBuf::from("trapfall.db"), + db_path: PathBuf::from("/tmp/test-trapfall.db"), listen_addr: "0.0.0.0:9090".into(), cors_origins: vec![], secure_cookie: true, @@ -170,6 +199,32 @@ mod tests { assert_eq!(cfg.dsn_host(), None); } + #[test] + fn default_data_dir_uses_home() { + // Just verify it doesn't panic and contains codecora/trapfall + let dir = Config::default_data_dir(); + let path = dir.to_string_lossy(); + assert!(path.contains("codecora"), "path should contain codecora: {path}"); + assert!(path.contains("trapfall"), "path should contain trapfall: {path}"); + } + + #[test] + fn default_db_path_contains_data_dir() { + let path = Config::default_db_path(); + assert!(path.contains("codecora"), "path should contain codecora: {path}"); + assert!(path.ends_with("trapfall.db"), "path should end with trapfall.db: {path}"); + } + + #[test] + fn ensure_data_dir_creates_directory() { + let dir = std::env::temp_dir().join("trapfall-test-ensure-dir"); + let _ = std::fs::remove_dir_all(&dir); + assert!(!dir.exists()); + Config::ensure_data_dir(&dir).unwrap(); + assert!(dir.exists()); + let _ = std::fs::remove_dir_all(&dir); + } + #[test] fn cookie_secure_flag_toggles() { let mut cfg = base_cfg(); diff --git a/crates/trapfalld/src/main.rs b/crates/trapfalld/src/main.rs index 50d8d19..46633ff 100644 --- a/crates/trapfalld/src/main.rs +++ b/crates/trapfalld/src/main.rs @@ -120,7 +120,16 @@ async fn main() -> Result<()> { } // Normal commands: open global database. - let db_url = trapfall_db::normalise_url(&cli.db); + // If user didn't explicitly set --db or TRAPFALL_DATABASE_URL, use + // the standardised data directory (~/.codecora/trapfall/). + let db_str = if cli.db == "trapfall.db" { + let dir = Config::default_data_dir(); + Config::ensure_data_dir(&dir).expect("failed to create data directory"); + Config::default_db_path() + } else { + cli.db.clone() + }; + let db_url = trapfall_db::normalise_url(&db_str); info!("Opening database: {db_url}"); let backend = trapfall_db::open_database(&db_url).await?; // Run idempotent schema migrations for whichever backend we got back.