fix(config): use platform-specific keyring features and standardize config path

- Use apple-native keyring feature on macOS instead of linux-only sync-secret-service
- Use windows-native keyring feature on Windows
- Move config directory to ~/.config/tangled on all platforms for consistency
- Improve keychain error messages

💘 Generated with Crush

Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>

dunkirk.sh f2ef3568 2950b681

verified
Changed files
+50 -10
crates
tangled-config
+30 -3
Cargo.lock
···
]
[[package]]
+
name = "core-foundation"
+
version = "0.10.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
+
dependencies = [
+
"core-foundation-sys",
+
"libc",
+
]
+
+
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
dependencies = [
+
"byteorder",
"dbus-secret-service",
"log",
"openssl",
+
"security-framework 2.11.1",
+
"security-framework 3.5.1",
+
"windows-sys 0.60.2",
"zeroize",
···
"openssl-probe",
"openssl-sys",
"schannel",
-
"security-framework",
+
"security-framework 2.11.1",
"security-framework-sys",
"tempfile",
···
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags",
-
"core-foundation",
+
"core-foundation 0.9.4",
+
"core-foundation-sys",
+
"libc",
+
"security-framework-sys",
+
]
+
+
[[package]]
+
name = "security-framework"
+
version = "3.5.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
+
dependencies = [
+
"bitflags",
+
"core-foundation 0.10.1",
"core-foundation-sys",
"libc",
"security-framework-sys",
···
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [
"bitflags",
-
"core-foundation",
+
"core-foundation 0.9.4",
"system-configuration-sys",
+1 -1
Cargo.toml
···
# Storage
dirs = "5.0"
-
keyring = { version = "3.6", features = ["sync-secret-service", "vendored"] }
+
keyring = "3.6"
# Error Handling
anyhow = "1.0"
+9 -1
crates/tangled-config/Cargo.toml
···
[dependencies]
anyhow = { workspace = true }
dirs = { workspace = true }
-
keyring = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
toml = { workspace = true }
chrono = { workspace = true }
+
[target.'cfg(target_os = "macos")'.dependencies]
+
keyring = { workspace = true, features = ["apple-native"] }
+
+
[target.'cfg(target_os = "linux")'.dependencies]
+
keyring = { workspace = true, features = ["sync-secret-service", "vendored"] }
+
+
[target.'cfg(target_os = "windows")'.dependencies]
+
keyring = { workspace = true, features = ["windows-native"] }
+
+8 -3
crates/tangled-config/src/config.rs
···
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
-
use dirs::config_dir;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
···
}
pub fn default_config_path() -> Result<PathBuf> {
-
let base = config_dir().context("Could not determine platform config directory")?;
-
Ok(base.join("tangled").join("config.toml"))
+
// Use ~/.config/tangled on all platforms for consistency
+
let home = std::env::var("HOME")
+
.or_else(|_| std::env::var("USERPROFILE"))
+
.context("Could not determine home directory")?;
+
Ok(PathBuf::from(home)
+
.join(".config")
+
.join("tangled")
+
.join("config.toml"))
}
pub fn load_config(path: Option<&Path>) -> Result<Option<RootConfig>> {
+2 -2
crates/tangled-config/src/keychain.rs
···
pub fn set_password(&self, secret: &str) -> Result<()> {
self.entry()?
.set_password(secret)
-
.map_err(|e| anyhow!("keyring error: {e}"))
+
.map_err(|e| anyhow!("Failed to save credentials to keychain: {e}"))
}
pub fn get_password(&self) -> Result<String> {
self.entry()?
.get_password()
-
.map_err(|e| anyhow!("keyring error: {e}"))
+
.map_err(|e| anyhow!("Failed to load credentials from keychain: {e}"))
}
pub fn delete_password(&self) -> Result<()> {