Skip to main content

pwr_core/
config.rs

1use serde::{Deserialize, Serialize};
2use std::fs;
3use std::path::PathBuf;
4
5use crate::error::{PwrError, Result};
6
7/// Client configuration stored at `~/.config/pwr/config.toml`.
8///
9/// Contains the connection details for reaching the pwr-server daemon
10/// and the local directory root where projects are stored.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct PwrConfig {
13    /// Schema version for forward compatibility.
14    pub version: u32,
15
16    /// Hostname or IP address of the NAS running pwr-server.
17    pub server_host: String,
18
19    /// Port the server listens on (default: 9742).
20    #[serde(default = "default_port")]
21    pub server_port: u16,
22
23    /// Hex-encoded 256-bit pre-shared key for authentication.
24    pub server_psk: String,
25
26    /// SHA-256 fingerprint of the server's TLS certificate (hex).
27    /// Used for certificate pinning.
28    #[serde(default)]
29    pub server_fingerprint: Option<String>,
30
31    /// Local root directory where projects live, e.g. "/home/jacob/Projects".
32    pub local_root: String,
33
34    /// Connection timeout in seconds.
35    #[serde(default = "default_timeout")]
36    pub connect_timeout_secs: u64,
37
38    /// Transfer timeout in seconds.
39    #[serde(default = "default_transfer_timeout")]
40    pub transfer_timeout_secs: u64,
41}
42
43fn default_port() -> u16 {
44    9742
45}
46
47fn default_timeout() -> u64 {
48    10
49}
50
51fn default_transfer_timeout() -> u64 {
52    300
53}
54
55impl PwrConfig {
56    /// Create a new client configuration.
57    pub fn new(
58        server_host: String,
59        server_port: u16,
60        server_psk: String,
61        local_root: String,
62    ) -> Self {
63        Self {
64            version: 2,
65            server_host,
66            server_port,
67            server_psk,
68            server_fingerprint: None,
69            local_root,
70            connect_timeout_secs: 10,
71            transfer_timeout_secs: 300,
72        }
73    }
74
75    /// Return the server address as "host:port".
76    pub fn server_addr(&self) -> String {
77        format!("{}:{}", self.server_host, self.server_port)
78    }
79}
80
81/// Determine the config directory: `~/.config/pwr/`.
82pub fn config_dir() -> PathBuf {
83    dirs::config_dir()
84        .unwrap_or_else(|| PathBuf::from("~/.config"))
85        .join("pwr")
86}
87
88/// Path to the main client config file.
89pub fn config_path() -> PathBuf {
90    config_dir().join("config.toml")
91}
92
93/// Path to the age identity file (client-side encryption key).
94pub fn identity_path() -> PathBuf {
95    config_dir().join("identity")
96}
97
98/// Path to the transaction log.
99pub fn transaction_log_path() -> PathBuf {
100    config_dir().join("transactions.log")
101}
102
103/// Check whether a config file exists without loading it.
104pub fn config_exists() -> bool {
105    config_path().exists()
106}
107
108/// Load the client config from disk, or return NoConfig if it doesn't exist.
109pub fn load_config() -> Result<PwrConfig> {
110    let path = config_path();
111    if !path.exists() {
112        return Err(PwrError::NoConfig);
113    }
114    let contents = fs::read_to_string(&path)?;
115    let config: PwrConfig =
116        toml::from_str(&contents).map_err(|e| PwrError::TomlParse {
117            path: path.to_string_lossy().to_string(),
118            source: e,
119        })?;
120    Ok(config)
121}
122
123/// Save the client config to disk.
124pub fn save_config(config: &PwrConfig) -> Result<()> {
125    let dir = config_dir();
126    fs::create_dir_all(&dir)?;
127    let contents = toml::to_string_pretty(config)?;
128    fs::write(config_path(), contents)?;
129    log::info!("Config saved to {}", config_path().display());
130    Ok(())
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_server_addr() {
139        let config = PwrConfig::new(
140            "nas.local".into(),
141            9742,
142            "abcdef0123456789".into(),
143            "/home/jacob/Projects".into(),
144        );
145        assert_eq!(config.server_addr(), "nas.local:9742");
146    }
147
148    #[test]
149    fn test_config_path() {
150        let path = config_path();
151        assert!(path.ends_with("pwr/config.toml"));
152    }
153
154    #[test]
155    fn test_default_port() {
156        assert_eq!(default_port(), 9742);
157    }
158}