Skip to main content

zlayer_core/
config.rs

1//! Configuration structures for `ZLayer`
2
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5use std::time::Duration;
6use zlayer_types::overlay::OverlayMode;
7
8/// Main agent configuration
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
10pub struct AgentConfig {
11    /// Deployment name this agent belongs to
12    pub deployment_name: String,
13
14    /// Unique node identifier
15    pub node_id: String,
16
17    /// Raft consensus configuration
18    #[serde(default)]
19    pub raft: RaftConfig,
20
21    /// Overlay network configuration
22    #[serde(default)]
23    pub overlay: OverlayAgentConfig,
24
25    /// Data directory for state persistence
26    #[serde(default = "default_data_dir")]
27    pub data_dir: PathBuf,
28
29    /// Metrics configuration
30    #[serde(default)]
31    pub metrics: MetricsConfig,
32
33    /// Logging configuration
34    #[serde(default)]
35    pub logging: LoggingConfig,
36
37    /// Registry authentication configuration
38    #[serde(default)]
39    pub auth: crate::auth::AuthConfig,
40
41    /// Cluster-level default overlay mode. When `None`, the daemon picks
42    /// (currently always [`OverlayMode::Shared`] in v0.51). Per-service
43    /// overrides live on the service spec's `overlay` field.
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub overlay_default_mode: Option<OverlayMode>,
46}
47
48fn default_data_dir() -> PathBuf {
49    zlayer_paths::ZLayerDirs::default_data_dir()
50}
51
52/// Raft consensus configuration
53#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
54pub struct RaftConfig {
55    /// Address to bind for Raft RPC
56    pub raft_addr: String,
57
58    /// Advertised address for other nodes to connect
59    pub advertise_addr: Option<String>,
60
61    /// Snapshot threshold (number of logs before snapshot)
62    #[serde(default = "default_snapshot_threshold")]
63    pub snapshot_threshold: u64,
64
65    /// Snapshot policy (logs since last snapshot)
66    #[serde(default = "default_snapshot_policy_count")]
67    pub snapshot_policy_count: u64,
68
69    /// Election timeout minimum
70    #[serde(default = "default_election_timeout_min")]
71    pub election_timeout_min: u64,
72
73    /// Election timeout maximum
74    #[serde(default = "default_election_timeout_max")]
75    pub election_timeout_max: u64,
76
77    /// Heartbeat interval
78    #[serde(default = "default_heartbeat_interval")]
79    pub heartbeat_interval: u64,
80}
81
82impl Default for RaftConfig {
83    fn default() -> Self {
84        Self {
85            raft_addr: "0.0.0.0:27001".to_string(),
86            advertise_addr: None,
87            snapshot_threshold: default_snapshot_threshold(),
88            snapshot_policy_count: default_snapshot_policy_count(),
89            election_timeout_min: default_election_timeout_min(),
90            election_timeout_max: default_election_timeout_max(),
91            heartbeat_interval: default_heartbeat_interval(),
92        }
93    }
94}
95
96fn default_snapshot_threshold() -> u64 {
97    10000
98}
99
100fn default_snapshot_policy_count() -> u64 {
101    8000
102}
103
104fn default_election_timeout_min() -> u64 {
105    150
106}
107
108fn default_election_timeout_max() -> u64 {
109    300
110}
111
112fn default_heartbeat_interval() -> u64 {
113    50
114}
115
116/// Overlay network configuration for agents
117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
118pub struct OverlayAgentConfig {
119    /// Overlay private key (x25519)
120    pub private_key: String,
121
122    /// Overlay public key (derived from private key)
123    pub public_key: Option<String>,
124
125    /// Listen port for overlay network (`WireGuard` protocol)
126    #[serde(default = "default_wg_port")]
127    pub wg_port: u16,
128
129    /// Global overlay network configuration
130    #[serde(default)]
131    pub global: GlobalOverlayConfig,
132
133    /// DNS configuration
134    #[serde(default)]
135    pub dns: DnsConfig,
136}
137
138impl Default for OverlayAgentConfig {
139    fn default() -> Self {
140        Self {
141            private_key: String::new(),
142            public_key: None,
143            wg_port: default_wg_port(),
144            global: GlobalOverlayConfig::default(),
145            dns: DnsConfig::default(),
146        }
147    }
148}
149
150/// Default overlay listen port (`WireGuard` protocol).
151pub const DEFAULT_WG_PORT: u16 = 51420;
152
153fn default_wg_port() -> u16 {
154    DEFAULT_WG_PORT
155}
156
157/// Global overlay network configuration
158#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
159pub struct GlobalOverlayConfig {
160    /// Overlay network CIDR (e.g., "10.0.0.0/8")
161    #[serde(default = "default_overlay_cidr")]
162    pub overlay_cidr: String,
163
164    /// Peer discovery interval
165    #[serde(default = "default_peer_discovery")]
166    pub peer_discovery_interval: Duration,
167}
168
169impl Default for GlobalOverlayConfig {
170    fn default() -> Self {
171        Self {
172            overlay_cidr: default_overlay_cidr(),
173            peer_discovery_interval: default_peer_discovery(),
174        }
175    }
176}
177
178fn default_overlay_cidr() -> String {
179    "10.0.0.0/8".to_string()
180}
181
182fn default_peer_discovery() -> Duration {
183    Duration::from_secs(30)
184}
185
186/// DNS configuration
187#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
188pub struct DnsConfig {
189    /// DNS listen address
190    #[serde(default = "default_dns_addr")]
191    pub listen_addr: String,
192
193    /// DNS TLD for global overlay
194    #[serde(default = "default_global_tld")]
195    pub global_tld: String,
196
197    /// DNS TLD for service-scoped overlay
198    #[serde(default = "default_service_tld")]
199    pub service_tld: String,
200}
201
202impl Default for DnsConfig {
203    fn default() -> Self {
204        Self {
205            listen_addr: default_dns_addr(),
206            global_tld: default_global_tld(),
207            service_tld: default_service_tld(),
208        }
209    }
210}
211
212fn default_dns_addr() -> String {
213    "0.0.0.0:53".to_string()
214}
215
216fn default_global_tld() -> String {
217    "global".to_string()
218}
219
220fn default_service_tld() -> String {
221    "service".to_string()
222}
223
224/// Metrics configuration
225#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
226pub struct MetricsConfig {
227    /// Enable metrics collection
228    #[serde(default = "default_metrics_enabled")]
229    pub enabled: bool,
230
231    /// Metrics exposition address
232    #[serde(default = "default_metrics_addr")]
233    pub listen_addr: String,
234
235    /// Metrics path
236    #[serde(default = "default_metrics_path")]
237    pub path: String,
238}
239
240impl Default for MetricsConfig {
241    fn default() -> Self {
242        Self {
243            enabled: default_metrics_enabled(),
244            listen_addr: default_metrics_addr(),
245            path: default_metrics_path(),
246        }
247    }
248}
249
250fn default_metrics_enabled() -> bool {
251    true
252}
253
254fn default_metrics_addr() -> String {
255    "0.0.0.0:9090".to_string()
256}
257
258fn default_metrics_path() -> String {
259    "/metrics".to_string()
260}
261
262/// Logging configuration
263#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
264pub struct LoggingConfig {
265    /// Log level (trace, debug, info, warn, error)
266    #[serde(default = "default_log_level")]
267    pub level: String,
268
269    /// Log format (json, pretty)
270    #[serde(default = "default_log_format")]
271    pub format: String,
272
273    /// Log to stdout
274    #[serde(default = "default_log_stdout")]
275    pub stdout: bool,
276
277    /// Log file path
278    pub file: Option<PathBuf>,
279}
280
281impl Default for LoggingConfig {
282    fn default() -> Self {
283        Self {
284            level: default_log_level(),
285            format: default_log_format(),
286            stdout: default_log_stdout(),
287            file: None,
288        }
289    }
290}
291
292fn default_log_level() -> String {
293    "info".to_string()
294}
295
296fn default_log_format() -> String {
297    "json".to_string()
298}
299
300fn default_log_stdout() -> bool {
301    true
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307
308    #[test]
309    fn test_agent_config_default() {
310        let config = AgentConfig {
311            deployment_name: "test".to_string(),
312            node_id: "node-1".to_string(),
313            ..Default::default()
314        };
315        assert_eq!(config.deployment_name, "test");
316        assert_eq!(config.node_id, "node-1");
317    }
318}