use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs; use std::path::PathBuf; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServerConfig { pub port: u16, pub bind: String, pub scrollback: usize, pub auth_ttl_secs: u64, pub attach_ticket_ttl_secs: u64, pub allow_attach_tickets: bool, pub client_timeout_secs: u64, pub retransmit_window: usize, pub default_input_mode: String, pub prewarm_sessions: Vec, pub create_on_attach: bool, pub shell: String, pub sessions_dir: String, pub secret_path: String, } impl Default for ServerConfig { fn default() -> Self { Self { port: 50000, bind: "0.0.0.0".to_string(), scrollback: 5000, auth_ttl_secs: 30, attach_ticket_ttl_secs: 3600, allow_attach_tickets: true, client_timeout_secs: 30, retransmit_window: 256, default_input_mode: "read-write".to_string(), prewarm_sessions: vec!["default".to_string()], create_on_attach: true, shell: std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string()), sessions_dir: "~/.local/share/dosh/sessions".to_string(), secret_path: "~/.config/dosh/secret".to_string(), } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ClientConfig { pub update_repo: Option, pub update_port: Option, pub server: String, pub dosh_host: Option, pub ssh_auth_command: Option, pub ssh_port: Option, pub dosh_port: u16, pub default_session: String, pub reconnect_timeout_secs: u64, pub view_only: bool, #[serde(default)] pub predict: bool, pub cache_attach_tickets: bool, pub credential_cache: String, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct HostConfig { pub ssh: Option, pub dosh_host: Option, pub port: Option, pub ssh_port: Option, pub default_command: Option, pub predict: Option, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct HostsConfig { #[serde(flatten)] pub hosts: HashMap, } impl Default for ClientConfig { fn default() -> Self { Self { update_repo: None, update_port: None, server: "user@example.com".to_string(), dosh_host: None, ssh_auth_command: Some("~/.local/bin/dosh-auth".to_string()), ssh_port: None, dosh_port: 50000, default_session: "new".to_string(), reconnect_timeout_secs: 5, view_only: false, predict: false, cache_attach_tickets: true, credential_cache: "~/.local/share/dosh/credentials".to_string(), } } } pub fn load_server_config(path: Option) -> Result { let path = path.unwrap_or_else(|| expand_tilde("~/.config/dosh/server.toml")); if !path.exists() { return Ok(ServerConfig::default()); } let raw = fs::read_to_string(&path).with_context(|| format!("read {}", path.display()))?; toml::from_str(&raw).with_context(|| format!("parse {}", path.display())) } pub fn load_client_config(path: Option) -> Result { let path = path.unwrap_or_else(|| expand_tilde("~/.config/dosh/client.toml")); if !path.exists() { return Ok(ClientConfig::default()); } let raw = fs::read_to_string(&path).with_context(|| format!("read {}", path.display()))?; toml::from_str(&raw).with_context(|| format!("parse {}", path.display())) } pub fn load_hosts_config(path: Option) -> Result { let path = path.unwrap_or_else(|| expand_tilde("~/.config/dosh/hosts.toml")); if !path.exists() { return Ok(HostsConfig::default()); } let raw = fs::read_to_string(&path).with_context(|| format!("read {}", path.display()))?; toml::from_str(&raw).with_context(|| format!("parse {}", path.display())) } pub fn expand_tilde(path: &str) -> PathBuf { if let Some(rest) = path.strip_prefix("~/") { if let Some(home) = dirs::home_dir() { return home.join(rest); } } PathBuf::from(path) }