rust_cache_server/
config.rs1use std::fs;
2use std::net::{IpAddr, Ipv4Addr, SocketAddr};
3
4use anyhow::Context;
5use serde::Deserialize;
6
7#[derive(Debug, Deserialize, Clone)]
8pub struct Config {
9 #[serde(default = "default_host")]
10 pub host: String,
11 pub listen: Option<String>,
13 #[serde(default = "default_port")]
14 pub port: u16,
15 pub max_memory_mb: Option<u64>,
17 pub read_buffer_kb: Option<usize>,
19 pub username: Option<String>,
21 pub password: Option<String>,
23 #[serde(default = "default_max_failed_attempts")]
25 pub max_failed_attempts: u32,
26 #[serde(default = "default_lockout_duration")]
28 pub lockout_duration_seconds: u64,
29}
30
31fn default_host() -> String {
32 "127.0.0.1".to_string()
33}
34
35fn default_port() -> u16 {
36 7777
37}
38
39fn default_max_failed_attempts() -> u32 {
40 3
41}
42
43fn default_lockout_duration() -> u64 {
44 600 }
46
47impl Config {
48 pub fn load() -> anyhow::Result<Self> {
49 let config_paths = if let Ok(env_path) = std::env::var("RUST_CACHE_CONFIG") {
57 vec![env_path]
58 } else {
59 let home = std::env::var("HOME").unwrap_or_else(|_| "~".to_string());
60 vec![
61 "/etc/rust-cache/rust-cache.conf".to_string(),
62 format!("{}/.config/rust-cache/rust-cache.toml", home),
63 "rust-cache.toml".to_string(),
64 ]
65 };
66
67 for path in config_paths {
68 if let Ok(text) = fs::read_to_string(&path) {
69 let mut cfg: Config = toml::from_str(&text)
70 .with_context(|| format!("failed to parse config file at {path}"))?;
71
72 if cfg.max_memory_mb.is_none() {
73 cfg.max_memory_mb = Self::auto_max_memory_mb();
74 }
75
76 return Ok(cfg);
77 }
78 }
79
80 Self::default_with_auto()
82 }
83
84 fn default_with_auto() -> anyhow::Result<Self> {
85 let max_memory_mb = Self::auto_max_memory_mb();
86 Ok(Config {
87 host: default_host(),
88 listen: None,
89 port: default_port(),
90 max_memory_mb,
91 read_buffer_kb: None,
92 username: None,
93 password: None,
94 max_failed_attempts: default_max_failed_attempts(),
95 lockout_duration_seconds: default_lockout_duration(),
96 })
97 }
98
99 fn auto_max_memory_mb() -> Option<u64> {
100 detect_total_memory_bytes().map(|bytes| {
101 (bytes / 2) / (1024 * 1024)
103 })
104 }
105
106 pub fn addr(&self) -> SocketAddr {
107 let host_str = self.listen.as_ref().unwrap_or(&self.host);
108 let ip: IpAddr = host_str
109 .parse()
110 .unwrap_or_else(|_| IpAddr::V4(Ipv4Addr::LOCALHOST));
111 SocketAddr::new(ip, self.port)
112 }
113
114 pub fn requires_auth(&self) -> bool {
115 self.username.is_some() || self.password.is_some()
116 }
117
118 pub fn check_auth(&self, username: Option<&str>, password: Option<&str>) -> bool {
119 if !self.requires_auth() {
120 return true;
121 }
122
123 let username_ok = self.username.as_ref().map(|u| u.as_str()) == username;
124 let password_ok = self.password.as_ref().map(|p| p.as_str()) == password;
125
126 if self.username.is_some() {
128 username_ok && password_ok
129 } else {
130 password_ok
131 }
132 }
133
134 pub fn max_memory_bytes(&self) -> Option<u64> {
135 self.max_memory_mb.map(|mb| mb * 1024 * 1024)
136 }
137
138 pub fn read_buffer_size(&self) -> usize {
139 self.read_buffer_kb.unwrap_or(4) * 1024
140 }
141}
142
143fn detect_total_memory_bytes() -> Option<u64> {
144 #[cfg(target_os = "linux")]
145 {
146 let contents = fs::read_to_string("/proc/meminfo").ok()?;
147 for line in contents.lines() {
148 if let Some(rest) = line.strip_prefix("MemTotal:") {
149 let kb_str = rest
151 .split_whitespace()
152 .next()
153 .unwrap_or_default();
154 if let Ok(kb) = kb_str.parse::<u64>() {
155 return Some(kb * 1024);
156 }
157 }
158 }
159 None
160 }
161 #[cfg(not(target_os = "linux"))]
162 {
163 Some(1024 * 1024 * 1024)
165 }
166}
167
168