1use serde::{Deserialize, Serialize};
18#[cfg(not(target_arch = "wasm32"))]
19use std::fs;
20use std::path::PathBuf;
21pub mod ports {
27 pub const P2P_DAEMON: u16 = 6070;
29 pub const P2P_NODE: u16 = 6071;
31 pub const P2P_HOST: u16 = 6072;
33
34 pub const API_DAEMON: u16 = 16002;
36 pub const API_NODE: u16 = 16003;
38 pub const API_HOST: u16 = 16004;
40
41 pub const PROXY: u16 = 17001;
43 pub const DNS: u16 = 53;
45 pub const BACKEND: u16 = 80;
47 pub const PAC: u16 = 16001;
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct KineticConfig {
56 pub daemon: DaemonConfig,
58 pub network: P2pConfig,
60 #[serde(default)]
62 pub drand: DrandConfig,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct DrandConfig {
68 #[serde(
70 default = "default_drand_endpoints",
71 skip_serializing_if = "is_default_drand_endpoints"
72 )]
73 pub endpoints: Vec<String>,
74 #[serde(default = "default_drand_seed_domain")]
76 pub drand_domain: Vec<String>,
77 #[serde(default)]
80 pub p2p_only: bool,
81}
82
83fn default_drand_endpoints() -> Vec<String> {
84 crate::constants::DRAND_HTTP_ENDPOINTS
85 .iter()
86 .map(|s| s.to_string())
87 .collect()
88}
89
90fn is_default_drand_endpoints(val: &Vec<String>) -> bool {
91 val == &default_drand_endpoints()
92}
93
94fn default_drand_seed_domain() -> Vec<String> {
95 vec![format!("drand.{}", crate::constants::BASE_DOMAIN)]
96}
97
98impl Default for DrandConfig {
99 fn default() -> Self {
100 Self {
101 endpoints: default_drand_endpoints(),
102 drand_domain: default_drand_seed_domain(),
103 p2p_only: false,
104 }
105 }
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct DaemonConfig {
111 #[serde(default = "local_bind_ip", skip_serializing_if = "is_default_bind_ip")]
113 pub bind_ip: String,
114 #[serde(default = "default_pac_bind_ip")]
116 pub pac_bind_ip: String,
117 #[serde(
119 default = "default_api_port",
120 skip_serializing_if = "is_default_api_port"
121 )]
122 pub api_port: u16,
123 #[serde(default = "default_dns_port")]
125 pub dns_port: u16,
126 #[serde(default = "default_proxy_port")]
128 pub proxy_port: u16,
129 #[serde(
131 default = "default_backend_port",
132 skip_serializing_if = "is_default_backend_port"
133 )]
134 pub backend_port: u16,
135 #[serde(default = "default_true")]
137 pub enable_dns: bool,
138 pub storage_dir: PathBuf,
140 #[serde(default = "default_network_mode")]
143 pub network_mode: String,
144 #[serde(default = "default_auto_update")]
146 pub auto_update: bool,
147 #[serde(
149 default = "default_pac_port",
150 skip_serializing_if = "is_default_pac_port"
151 )]
152 pub pac_port: u16,
153 #[serde(default = "default_ipfs_gateway")]
155 pub ipfs_gateway: String,
156 #[serde(default = "default_atlas_port")]
158 pub atlas_port: u16,
159}
160
161fn local_bind_ip() -> String {
162 crate::constants::LOCAL_BIND_IP.to_string()
163}
164
165fn default_pac_bind_ip() -> String {
166 crate::constants::LOCAL_BIND_IP.to_string()
167}
168
169fn is_default_bind_ip(val: &String) -> bool {
170 val == crate::constants::LOCAL_BIND_IP
171}
172
173fn default_true() -> bool {
174 true
175}
176
177fn default_auto_update() -> bool {
178 true
179}
180
181fn default_network_mode() -> String {
182 "FullNode".to_string()
183}
184
185fn default_api_port() -> u16 {
186 ports::API_DAEMON
187}
188
189fn default_dns_port() -> u16 {
190 ports::DNS
191}
192
193fn default_proxy_port() -> u16 {
194 ports::PROXY
195}
196
197fn default_backend_port() -> u16 {
198 ports::BACKEND
199}
200
201fn default_pac_port() -> u16 {
202 ports::PAC
203}
204
205fn is_default_api_port(val: &u16) -> bool {
206 *val == ports::API_DAEMON
207}
208fn is_default_backend_port(val: &u16) -> bool {
209 *val == ports::BACKEND
210}
211fn is_default_pac_port(val: &u16) -> bool {
212 *val == ports::PAC
213}
214
215fn default_atlas_port() -> u16 {
216 34291
217}
218
219fn default_ipfs_gateway() -> String {
220 crate::constants::IPFS_GATEWAY.to_string()
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct P2pConfig {
226 #[serde(
228 default = "default_p2p_daemon",
229 skip_serializing_if = "is_default_p2p_daemon"
230 )]
231 pub daemon_port: u16,
232 #[serde(
234 default = "default_p2p_daemon_quic",
235 skip_serializing_if = "is_default_p2p_daemon_quic"
236 )]
237 pub daemon_quic_port: u16,
238 #[serde(
240 default = "default_p2p_node",
241 skip_serializing_if = "is_default_p2p_node"
242 )]
243 pub node_port: u16,
244 #[serde(
246 default = "default_p2p_node_quic",
247 skip_serializing_if = "is_default_p2p_node_quic"
248 )]
249 pub node_quic_port: u16,
250 #[serde(
252 default = "default_p2p_host",
253 skip_serializing_if = "is_default_p2p_host"
254 )]
255 pub host_port: u16,
256 #[serde(
258 default = "default_p2p_host_quic",
259 skip_serializing_if = "is_default_p2p_host_quic"
260 )]
261 pub host_quic_port: u16,
262 pub bootstrap_nodes: Vec<String>,
264 #[serde(default)]
266 pub seed_domain: Vec<String>,
267 #[serde(default = "default_true")]
269 pub enable_mdns: bool,
270 #[serde(default, skip_serializing_if = "Option::is_none")]
272 pub external_address: Option<String>,
273}
274
275fn default_p2p_daemon() -> u16 {
276 ports::P2P_DAEMON
277}
278
279fn default_p2p_daemon_quic() -> u16 {
280 ports::P2P_DAEMON
281}
282
283fn default_p2p_node() -> u16 {
284 ports::P2P_NODE
285}
286
287fn default_p2p_node_quic() -> u16 {
288 ports::P2P_NODE
289}
290
291fn default_p2p_host() -> u16 {
292 ports::P2P_HOST
293}
294
295fn default_p2p_host_quic() -> u16 {
296 ports::P2P_HOST
297}
298
299fn is_default_p2p_daemon(val: &u16) -> bool {
300 *val == ports::P2P_DAEMON
301}
302fn is_default_p2p_daemon_quic(val: &u16) -> bool {
303 *val == ports::P2P_DAEMON
304}
305fn is_default_p2p_node(val: &u16) -> bool {
306 *val == ports::P2P_NODE
307}
308fn is_default_p2p_node_quic(val: &u16) -> bool {
309 *val == ports::P2P_NODE
310}
311fn is_default_p2p_host(val: &u16) -> bool {
312 *val == ports::P2P_HOST
313}
314fn is_default_p2p_host_quic(val: &u16) -> bool {
315 *val == ports::P2P_HOST
316}
317
318impl Default for KineticConfig {
319 fn default() -> Self {
320 #[cfg(not(target_arch = "wasm32"))]
321 let storage_dir = crate::config::get_base_dir().join("db");
322
323 #[cfg(target_arch = "wasm32")]
324 let storage_dir = PathBuf::from("/kinetic-db");
325
326 Self {
327 daemon: DaemonConfig {
328 bind_ip: crate::constants::LOCAL_BIND_IP.to_string(),
329 pac_bind_ip: crate::constants::LOCAL_BIND_IP.to_string(),
330 api_port: ports::API_DAEMON,
331 dns_port: ports::DNS,
332 proxy_port: ports::PROXY,
333 backend_port: ports::BACKEND,
334 enable_dns: true,
335 storage_dir,
336 network_mode: "FullNode".to_string(),
337 auto_update: true,
338 pac_port: ports::PAC,
339 ipfs_gateway: crate::constants::IPFS_GATEWAY.to_string(),
340 atlas_port: 34291,
341 },
342 network: P2pConfig {
343 daemon_port: ports::P2P_DAEMON,
344 daemon_quic_port: ports::P2P_DAEMON,
345 node_port: ports::P2P_NODE,
346 node_quic_port: ports::P2P_NODE,
347 host_port: ports::P2P_HOST,
348 host_quic_port: ports::P2P_HOST,
349 bootstrap_nodes: crate::constants::BOOTSTRAP_NODES
350 .iter()
351 .map(|s| s.to_string())
352 .collect(),
353 seed_domain: vec![format!("seed.{}", crate::constants::BASE_DOMAIN)],
354 enable_mdns: true,
355 external_address: None,
356 },
357 drand: DrandConfig::default(),
358 }
359 }
360}
361
362impl KineticConfig {
363 #[cfg(not(target_arch = "wasm32"))]
376 pub fn load() -> Self {
377 let config_path = std::env::var(crate::constants::ENV_CONFIG_PATH)
378 .map(PathBuf::from)
379 .unwrap_or_else(|_| crate::config::get_base_dir().join("config.toml"));
380
381 let config = match fs::read_to_string(&config_path) {
382 Ok(config_str) => match toml::from_str(&config_str) {
383 Ok(config) => config,
384 Err(e) => {
385 tracing::error!("Failed to parse config.toml: {}. Refusing to start to avoid fail-open vulnerability.", e);
386 std::process::exit(1);
387 }
388 },
389 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
390 let default_cfg = Self::default();
392 if let Some(parent) = config_path.parent() {
393 let _ = fs::create_dir_all(parent);
394 if let Ok(toml_str) = toml::to_string_pretty(&default_cfg) {
395 let _ = fs::write(&config_path, toml_str);
396 }
397 }
398 default_cfg
399 }
400 Err(e) => {
401 tracing::error!("Failed to read config.toml: {}. Refusing to start to avoid fail-open vulnerability.", e);
402 std::process::exit(1);
403 }
404 };
405
406 config
407 }
408
409 #[cfg(target_arch = "wasm32")]
410 pub fn load() -> Self {
412 Self::default()
413 }
414
415 #[cfg(not(target_arch = "wasm32"))]
421 pub fn save(&self) -> Result<(), std::io::Error> {
422 let config_path = std::env::var(crate::constants::ENV_CONFIG_PATH)
423 .map(PathBuf::from)
424 .unwrap_or_else(|_| crate::config::get_base_dir().join("config.toml"));
425
426 if let Some(parent) = config_path.parent() {
427 fs::create_dir_all(parent)?;
428 }
429
430 let toml_str = toml::to_string_pretty(self)
431 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
432 fs::write(&config_path, toml_str)
433 }
434
435 #[cfg(target_arch = "wasm32")]
436 pub fn save(&self) -> Result<(), std::io::Error> {
438 Ok(())
439 }
440}
441
442pub fn is_dev_mode() -> bool {
446 cfg!(feature = "simulation")
447}
448
449pub fn get_zones_dir() -> PathBuf {
451 get_base_dir().join("zones")
452}
453
454pub fn get_base_dir() -> PathBuf {
460 if let Ok(path) = std::env::var(crate::constants::ENV_DATA_DIR) {
461 return PathBuf::from(path);
462 }
463
464 let network_dir = crate::constants::NETWORK_ID;
465
466 #[cfg(not(target_arch = "wasm32"))]
467 {
468 dirs::data_local_dir()
469 .unwrap_or_else(|| PathBuf::from("."))
470 .join(network_dir)
471 }
472
473 #[cfg(target_arch = "wasm32")]
474 {
475 PathBuf::from(format!("/{}", network_dir))
476 }
477}
478
479pub fn get_api_tokens_dir() -> PathBuf {
481 get_base_dir().join("tokens")
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487
488 #[test]
489 fn test_default_config() {
490 let config = KineticConfig::default();
491 assert_eq!(config.daemon.api_port, ports::API_DAEMON);
492 assert_eq!(config.network.daemon_port, ports::P2P_DAEMON);
493 assert!(config.network.enable_mdns);
494 assert!(config.daemon.enable_dns);
495 }
496
497 #[test]
498 fn test_bundled_network_json_sync() {
499 let root_json_path = PathBuf::from("../network.json");
500 let bundled_json_path = PathBuf::from("default_network.json");
501
502 if root_json_path.exists() && bundled_json_path.exists() {
503 let root_content = fs::read_to_string(&root_json_path).expect("Failed to read root network.json");
504 let bundled_content = fs::read_to_string(&bundled_json_path).expect("Failed to read bundled default_network.json");
505
506 assert_eq!(
507 root_content, bundled_content,
508 "The bundled default_network.json in kinetic-core must perfectly match the root network.json!"
509 );
510 }
511 }
512}