nap_core/server/
config.rs1use anyhow::{Context, Result};
9use rand::RngCore;
10use std::fs;
11use std::io::Write;
12use std::path::Path;
13
14pub fn generate_local_config(nap_home: &Path) -> Result<ConfigFiles> {
21 let lore_config_dir = nap_home.join("lore").join("config");
22 let local_toml_path = lore_config_dir.join("local.toml");
23
24 fs::create_dir_all(&lore_config_dir).context("Failed to create Lore config directory")?;
25
26 if local_toml_path.exists() {
28 let content = fs::read_to_string(&local_toml_path)?;
29 let mut config: toml::Value = toml::from_str(&content)?;
30 if let Some(http) = config
31 .get_mut("server")
32 .and_then(|v| v.get_mut("http"))
33 .and_then(toml::Value::as_table_mut)
34 && http.get("enabled").and_then(toml::Value::as_bool) != Some(false)
35 && !http.contains_key("presigned_url_hmac_key")
36 {
37 http.insert("presigned_url_hmac_key".into(), new_presign_key().into());
38 let mut file = tempfile::NamedTempFile::new_in(&lore_config_dir)?;
39 file.write_all(toml::to_string_pretty(&config)?.as_bytes())?;
40 file.persist(&local_toml_path)?;
41 }
42 tracing::info!("Lore config already exists at {:?}", local_toml_path);
43 return Ok(ConfigFiles {
44 config_path: local_toml_path,
45 config_dir: lore_config_dir,
46 });
47 }
48
49 tracing::info!("Generating Lore server configuration");
50
51 let config = generate_config_toml(nap_home);
52 let mut options = fs::OpenOptions::new();
53 options.write(true).create_new(true);
54 #[cfg(unix)]
55 {
56 use std::os::unix::fs::OpenOptionsExt;
57 options.mode(0o600);
58 }
59 let mut file = options
60 .open(&local_toml_path)
61 .context("Failed to create Lore configuration file")?;
62 file.write_all(config.as_bytes())
63 .context("Failed to write Lore configuration file")?;
64
65 tracing::info!("Lore configuration generated at {:?}", local_toml_path);
66
67 Ok(ConfigFiles {
68 config_path: local_toml_path,
69 config_dir: lore_config_dir,
70 })
71}
72
73fn new_presign_key() -> String {
74 let mut key = [0u8; 32];
75 rand::rng().fill_bytes(&mut key);
76 hex::encode(key)
77}
78
79fn generate_config_toml(nap_home: &Path) -> String {
81 let immutable_path = nap_home.join("lore").join("store").join("immutable");
82 let mutable_path = nap_home.join("lore").join("store").join("mutable");
83 let _cert_path = nap_home.join("lore").join("certs").join("cert.pem");
84 let _key_path = nap_home.join("lore").join("certs").join("key.pem");
85 let presign_key = new_presign_key();
86
87 format!(
88 r#"
89# =============================================================================
90# Lore Server Configuration (Generated by NAP SDK)
91# =============================================================================
92# This configuration is automatically generated by the NAP SDK.
93# Manual modifications may be overwritten.
94
95[server]
96connection_close_timeout_seconds = 5
97runtime_shutdown_timeout_seconds = 25
98
99# Public facing QUIC server settings
100[server.quic]
101enabled = true
102host = "127.0.0.1"
103port = 41337
104verify_client_certs = false
105idle_timeout = 30_000
106keep_alive = 500
107max_bidi_streams = 8
108num_listeners = 10
109transport_bits_per_second = 1_073_741_824 # 1 gbit/s
110transport_rtt = 1 # 1 ms for local development (was 100ms)
111handler_timeout_seconds = 50
112
113# gRPC server settings
114[server.grpc]
115enabled = true
116host = "127.0.0.1"
117port = 41337
118request_handler_timeout_seconds = 50
119verify_client_certs = false
120
121# HTTP server settings
122[server.http]
123enabled = true
124host = "127.0.0.1"
125port = 41339
126max_file_size = 10_485_760 # 10MB
127request_timeout_seconds = 300
128request_body_timeout_seconds = 3600
129available_interval_seconds = 30
130available_timeout_seconds = 5
131store_health_check = false
132presigned_url_hmac_key = "{}"
133
134# =============================================================================
135# Store Configuration
136# =============================================================================
137
138[immutable_store]
139mode = "local"
140
141[immutable_store.local]
142path = "{}"
143flush_delay_seconds = 0
144
145[mutable_store]
146mode = "local"
147
148[mutable_store.local]
149path = "{}"
150flush_delay_seconds = 0
151
152[lock_store]
153mode = "local"
154
155# =============================================================================
156# Tokio Runtime Configuration
157# =============================================================================
158
159[tokio]
160max_blocking_threads = 512
161
162# =============================================================================
163# Telemetry Configuration
164# =============================================================================
165
166[telemetry.logger]
167enable_otlp = false
168format = "json"
169output = "stdout"
170
171[telemetry.metrics]
172export_interval_millis = 30000
173sample_interval_millis = 10000
174
175[telemetry.traces]
176sample_rate = 0.05
177sample_rate_low_tier = 0.001
178
179# =============================================================================
180# Notification Configuration
181# =============================================================================
182
183[notification]
184mode = "local"
185
186# =============================================================================
187# Other Features Configuration
188# =============================================================================
189
190[feature]
191history_step_size = 100
192"#,
193 presign_key,
194 immutable_path.display(),
195 mutable_path.display()
196 )
197}
198
199#[derive(Debug, Clone)]
201pub struct ConfigFiles {
202 pub config_path: std::path::PathBuf,
203 pub config_dir: std::path::PathBuf,
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209 use tempfile::TempDir;
210
211 #[test]
212 fn upgrade_adds_one_stable_key_and_preserves_existing_settings() {
213 let dir = TempDir::new().unwrap();
214 let config = dir.path().join("lore/config");
215 fs::create_dir_all(&config).unwrap();
216 let path = config.join("local.toml");
217 fs::write(
218 &path,
219 "[server.http]\nenabled = true\nport = 4242\n[custom]\nvalue = 7\n",
220 )
221 .unwrap();
222 generate_local_config(dir.path()).unwrap();
223 let first = fs::read_to_string(&path).unwrap();
224 generate_local_config(dir.path()).unwrap();
225 assert_eq!(fs::read_to_string(&path).unwrap(), first);
226 assert!(first.contains("port = 4242"));
227 assert!(first.contains("value = 7"));
228 let doc: toml::Value = toml::from_str(&first).unwrap();
229 assert_eq!(
230 doc["server"]["http"]["presigned_url_hmac_key"]
231 .as_str()
232 .unwrap()
233 .len(),
234 64
235 );
236 }
237
238 #[test]
239 fn test_generate_local_config() {
240 let temp_dir = TempDir::new().unwrap();
241 let nap_home = temp_dir.path();
242
243 let files = generate_local_config(nap_home).unwrap();
244
245 assert!(files.config_path.exists());
246 assert!(files.config_dir.exists());
247
248 let content = fs::read_to_string(&files.config_path).unwrap();
250 assert!(content.contains("[server.quic]"));
251 assert!(content.contains("port = 41337"));
252 assert!(content.contains("[immutable_store.local]"));
253 assert!(content.contains("[mutable_store.local]"));
254 let metadata = fs::metadata(&files.config_path).unwrap();
255 #[cfg(unix)]
256 {
257 use std::os::unix::fs::PermissionsExt;
258 assert_eq!(metadata.permissions().mode() & 0o777, 0o600);
259 }
260
261 let files2 = generate_local_config(nap_home).unwrap();
263 assert_eq!(files.config_path, files2.config_path);
264 }
265
266 #[test]
267 fn test_config_toml_content() {
268 let temp_dir = TempDir::new().unwrap();
269 let nap_home = temp_dir.path();
270
271 let config = generate_config_toml(nap_home);
272
273 assert!(config.contains("[server.quic]"));
275 assert!(config.contains("port = 41337"));
276 assert!(config.contains("[server.grpc]"));
277 assert!(config.contains("[server.http]"));
278 assert!(config.contains("port = 41339"));
279 let key = config
280 .lines()
281 .find_map(|line| {
282 line.strip_prefix("presigned_url_hmac_key = \"")?
283 .strip_suffix('"')
284 })
285 .unwrap();
286 assert_eq!(key.len(), 64);
287 assert!(key.bytes().all(|byte| byte.is_ascii_hexdigit()));
288 assert!(config.contains("[immutable_store]"));
289 assert!(config.contains("mode = \"local\""));
290 assert!(config.contains("[mutable_store]"));
291 assert!(config.contains("[lock_store]"));
292 assert!(config.contains("[telemetry.logger]"));
293 assert!(config.contains("[telemetry.metrics]"));
294 assert!(config.contains("[notification]"));
295 assert!(config.contains("[feature]"));
296
297 assert!(config.contains(&nap_home.display().to_string()));
299 }
300}