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