use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::fs; use std::path::PathBuf; #[derive(Debug, Serialize, Deserialize, Default)] pub struct Config { pub database_url: Option, } pub fn config_dir() -> PathBuf { dirs::config_dir() .or_else(|| dirs::home_dir().map(|h| h.join(".config"))) .unwrap_or_else(|| PathBuf::from(".config")) .join("secrets") } pub fn config_path() -> PathBuf { config_dir().join("config.toml") } pub fn load_config() -> Result { let path = config_path(); if !path.exists() { return Ok(Config::default()); } let content = fs::read_to_string(&path) .with_context(|| format!("failed to read config file: {}", path.display()))?; let config: Config = toml::from_str(&content) .with_context(|| format!("failed to parse config file: {}", path.display()))?; Ok(config) } pub fn save_config(config: &Config) -> Result<()> { let dir = config_dir(); fs::create_dir_all(&dir) .with_context(|| format!("failed to create config dir: {}", dir.display()))?; let path = config_path(); let content = toml::to_string_pretty(config).context("failed to serialize config")?; fs::write(&path, &content) .with_context(|| format!("failed to write config file: {}", path.display()))?; // Set file permissions to 0600 (owner read/write only) #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let perms = fs::Permissions::from_mode(0o600); fs::set_permissions(&path, perms) .with_context(|| format!("failed to set file permissions: {}", path.display()))?; } Ok(()) } /// Resolve database URL by priority: /// 1. --db-url CLI flag (if non-empty) /// 2. database_url in ~/.config/secrets/config.toml /// 3. Error with setup instructions pub fn resolve_db_url(cli_db_url: &str) -> Result { if !cli_db_url.is_empty() { return Ok(cli_db_url.to_string()); } let config = load_config()?; if let Some(url) = config.database_url && !url.is_empty() { return Ok(url); } anyhow::bail!("Database not configured. Run:\n\n secrets config set-db \n") }