Skip to main content

nap_core/server/
config.rs

1// SPDX-FileCopyrightText: 2026 Digital Creations
2// SPDX-License-Identifier: MIT
3//! Lore server configuration generation
4//!
5//! Generates local.toml configuration for Lore server with platform-independent paths
6//! and recommended persistent defaults for NAP-managed deployments.
7
8use anyhow::{Context, Result};
9use std::fs;
10use std::path::Path;
11
12/// Generate Lore server configuration for local deployment
13///
14/// Creates a complete local.toml configuration file with:
15/// - Persistent stores under NAP home directory
16/// - Persistent certificates
17/// - Recommended defaults for local development
18pub fn generate_local_config(nap_home: &Path) -> Result<ConfigFiles> {
19    let lore_config_dir = nap_home.join("lore").join("config");
20    let local_toml_path = lore_config_dir.join("local.toml");
21
22    fs::create_dir_all(&lore_config_dir).context("Failed to create Lore config directory")?;
23
24    // Only regenerate if missing
25    if local_toml_path.exists() {
26        tracing::info!("Lore config already exists at {:?}", local_toml_path);
27        return Ok(ConfigFiles {
28            config_path: local_toml_path,
29            config_dir: lore_config_dir,
30        });
31    }
32
33    tracing::info!("Generating Lore server configuration");
34
35    let config = generate_config_toml(nap_home);
36    fs::write(&local_toml_path, config).context("Failed to write Lore configuration file")?;
37
38    tracing::info!("Lore configuration generated at {:?}", local_toml_path);
39
40    Ok(ConfigFiles {
41        config_path: local_toml_path,
42        config_dir: lore_config_dir,
43    })
44}
45
46/// Generate the TOML configuration content
47fn generate_config_toml(nap_home: &Path) -> String {
48    let immutable_path = nap_home.join("lore").join("store").join("immutable");
49    let mutable_path = nap_home.join("lore").join("store").join("mutable");
50    let _cert_path = nap_home.join("lore").join("certs").join("cert.pem");
51    let _key_path = nap_home.join("lore").join("certs").join("key.pem");
52
53    format!(
54        r#"
55# =============================================================================
56# Lore Server Configuration (Generated by NAP SDK)
57# =============================================================================
58# This configuration is automatically generated by the NAP SDK.
59# Manual modifications may be overwritten.
60
61[server]
62connection_close_timeout_seconds = 5
63runtime_shutdown_timeout_seconds = 25
64
65# Public facing QUIC server settings
66[server.quic]
67enabled = true
68host = "0.0.0.0"
69port = 41337
70verify_client_certs = false
71idle_timeout = 30_000
72keep_alive = 500
73max_bidi_streams = 8
74num_listeners = 10
75transport_bits_per_second = 1_073_741_824  # 1 gbit/s
76transport_rtt = 100  # 100 ms
77handler_timeout_seconds = 50
78
79# gRPC server settings
80[server.grpc]
81enabled = true
82host = "0.0.0.0"
83port = 41337
84request_handler_timeout_seconds = 50
85verify_client_certs = false
86
87# HTTP server settings
88[server.http]
89enabled = true
90host = "0.0.0.0"
91port = 41339
92max_file_size = 10_485_760  # 10MB
93request_timeout_seconds = 300
94request_body_timeout_seconds = 3600
95available_interval_seconds = 30
96available_timeout_seconds = 5
97store_health_check = false
98
99# =============================================================================
100# Store Configuration
101# =============================================================================
102
103[immutable_store]
104mode = "local"
105
106[immutable_store.local]
107path = "{}"
108flush_delay_seconds = 10
109
110[mutable_store]
111mode = "local"
112
113[mutable_store.local]
114path = "{}"
115flush_delay_seconds = 10
116
117[lock_store]
118mode = "local"
119
120# =============================================================================
121# Tokio Runtime Configuration
122# =============================================================================
123
124[tokio]
125max_blocking_threads = 512
126
127# =============================================================================
128# Telemetry Configuration
129# =============================================================================
130
131[telemetry.logger]
132enable_otlp = false
133format = "json"
134output = "stdout"
135
136[telemetry.metrics]
137export_interval_millis = 30000
138sample_interval_millis = 10000
139
140[telemetry.traces]
141sample_rate = 0.05
142sample_rate_low_tier = 0.001
143
144# =============================================================================
145# Notification Configuration
146# =============================================================================
147
148[notification]
149mode = "local"
150
151# =============================================================================
152# Other Features Configuration
153# =============================================================================
154
155[feature]
156history_step_size = 100
157"#,
158        immutable_path.display(),
159        mutable_path.display()
160    )
161}
162
163/// Paths to generated configuration files
164#[derive(Debug, Clone)]
165pub struct ConfigFiles {
166    pub config_path: std::path::PathBuf,
167    pub config_dir: std::path::PathBuf,
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173    use tempfile::TempDir;
174
175    #[test]
176    fn test_generate_local_config() {
177        let temp_dir = TempDir::new().unwrap();
178        let nap_home = temp_dir.path();
179
180        let files = generate_local_config(nap_home).unwrap();
181
182        assert!(files.config_path.exists());
183        assert!(files.config_dir.exists());
184
185        // Verify configuration content
186        let content = fs::read_to_string(&files.config_path).unwrap();
187        assert!(content.contains("[server.quic]"));
188        assert!(content.contains("port = 41337"));
189        assert!(content.contains("[immutable_store.local]"));
190        assert!(content.contains("[mutable_store.local]"));
191
192        // Verify we can regenerate without error (should skip if exists)
193        let files2 = generate_local_config(nap_home).unwrap();
194        assert_eq!(files.config_path, files2.config_path);
195    }
196
197    #[test]
198    fn test_config_toml_content() {
199        let temp_dir = TempDir::new().unwrap();
200        let nap_home = temp_dir.path();
201
202        let config = generate_config_toml(nap_home);
203
204        // Verify key configuration sections
205        assert!(config.contains("[server.quic]"));
206        assert!(config.contains("port = 41337"));
207        assert!(config.contains("[server.grpc]"));
208        assert!(config.contains("[server.http]"));
209        assert!(config.contains("port = 41339"));
210        assert!(config.contains("[immutable_store]"));
211        assert!(config.contains("mode = \"local\""));
212        assert!(config.contains("[mutable_store]"));
213        assert!(config.contains("[lock_store]"));
214        assert!(config.contains("[telemetry.logger]"));
215        assert!(config.contains("[telemetry.metrics]"));
216        assert!(config.contains("[notification]"));
217        assert!(config.contains("[feature]"));
218
219        // Verify paths are included
220        assert!(config.contains(&nap_home.display().to_string()));
221    }
222}