a73x

4b5c6d59

add server config module with TOML parsing

a73x   2026-03-30 18:38

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

diff --git a/Cargo.toml b/Cargo.toml
index 259a945..e8df7e7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -31,7 +31,7 @@ askama = "0.15"
askama_web = { version = "0.15", features = ["axum-0.8"] }
russh = "0.46"
russh-keys = "0.46"
toml_edit = "0.22"
toml_edit = { version = "0.22", features = ["serde"] }
tracing = "0.1"
tracing-subscriber = "0.3"
async-trait = "0.1"
diff --git a/src/server/config.rs b/src/server/config.rs
new file mode 100644
index 0000000..a18a46c
--- /dev/null
+++ b/src/server/config.rs
@@ -0,0 +1,82 @@
use std::net::SocketAddr;
use std::path::PathBuf;

use serde::Deserialize;

#[derive(Debug, Clone, Deserialize)]
pub struct ServerConfig {
    pub repos_dir: PathBuf,
    #[serde(default = "default_http_bind")]
    pub http_bind: SocketAddr,
    #[serde(default = "default_ssh_bind")]
    pub ssh_bind: SocketAddr,
    pub authorized_keys: PathBuf,
    #[serde(default = "default_site_title")]
    pub site_title: String,
}

fn default_http_bind() -> SocketAddr {
    "0.0.0.0:8080".parse().unwrap()
}

fn default_ssh_bind() -> SocketAddr {
    "0.0.0.0:2222".parse().unwrap()
}

fn default_site_title() -> String {
    "git-collab".to_string()
}

impl ServerConfig {
    pub fn from_toml(content: &str) -> Result<Self, toml_edit::de::Error> {
        toml_edit::de::from_str(content)
    }

    pub fn from_file(path: &std::path::Path) -> Result<Self, Box<dyn std::error::Error>> {
        let content = std::fs::read_to_string(path)?;
        Ok(Self::from_toml(&content)?)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_full_config() {
        let toml = r#"
repos_dir = "/srv/git"
http_bind = "127.0.0.1:3000"
ssh_bind = "127.0.0.1:2222"
authorized_keys = "/etc/git-collab-server/authorized_keys"
site_title = "my repos"
"#;
        let config = ServerConfig::from_toml(toml).unwrap();
        assert_eq!(config.repos_dir, PathBuf::from("/srv/git"));
        assert_eq!(config.http_bind, "127.0.0.1:3000".parse::<SocketAddr>().unwrap());
        assert_eq!(config.ssh_bind, "127.0.0.1:2222".parse::<SocketAddr>().unwrap());
        assert_eq!(config.authorized_keys, PathBuf::from("/etc/git-collab-server/authorized_keys"));
        assert_eq!(config.site_title, "my repos");
    }

    #[test]
    fn parse_minimal_config_uses_defaults() {
        let toml = r#"
repos_dir = "/srv/git"
authorized_keys = "/keys"
"#;
        let config = ServerConfig::from_toml(toml).unwrap();
        assert_eq!(config.repos_dir, PathBuf::from("/srv/git"));
        assert_eq!(config.http_bind, "0.0.0.0:8080".parse::<SocketAddr>().unwrap());
        assert_eq!(config.ssh_bind, "0.0.0.0:2222".parse::<SocketAddr>().unwrap());
        assert_eq!(config.site_title, "git-collab");
    }

    #[test]
    fn parse_missing_required_field_fails() {
        let toml = r#"
authorized_keys = "/keys"
"#;
        assert!(ServerConfig::from_toml(toml).is_err());
    }
}
diff --git a/src/server/main.rs b/src/server/main.rs
index 6934e5d..779073e 100644
--- a/src/server/main.rs
+++ b/src/server/main.rs
@@ -1,3 +1,5 @@
mod config;

fn main() {
    println!("git-collab-server: not yet implemented");
}