1mod addresses;
12mod api;
13mod datastore;
14mod gateway;
15mod identity;
16mod profile;
17mod routing;
18mod swarm;
19mod types;
20
21pub use addresses::*;
22pub use api::*;
23pub use datastore::*;
24pub use gateway::*;
25pub use identity::*;
26pub use profile::*;
27pub use routing::*;
28pub use swarm::*;
29pub use types::*;
30
31use serde::{Deserialize, Serialize};
32use std::collections::HashMap;
33
34pub const DEFAULT_PATH_NAME: &str = ".ipfs";
36
37pub const DEFAULT_PATH_ROOT: &str = "~/.ipfs";
39
40pub const DEFAULT_CONFIG_FILE: &str = "config";
42
43pub const ENV_DIR: &str = "IPFS_PATH";
45
46#[derive(Debug, Clone, Default, Serialize, Deserialize)]
48#[serde(rename_all = "PascalCase")]
49pub struct Config {
50 #[serde(default)]
52 pub identity: Identity,
53
54 #[serde(default)]
56 pub datastore: Datastore,
57
58 #[serde(default)]
60 pub addresses: Addresses,
61
62 #[serde(default)]
64 pub mounts: Mounts,
65
66 #[serde(default)]
68 pub discovery: Discovery,
69
70 #[serde(default)]
72 pub routing: Routing,
73
74 #[serde(default, rename = "Ipns")]
76 pub ipns: Ipns,
77
78 #[serde(default, rename = "Bootstrap")]
80 pub bootstrap: Vec<String>,
81
82 #[serde(default)]
84 pub gateway: Gateway,
85
86 #[serde(default, rename = "API")]
88 pub api: Api,
89
90 #[serde(default)]
92 pub swarm: SwarmConfig,
93
94 #[serde(default, rename = "AutoNAT")]
96 pub auto_nat: AutoNatConfig,
97
98 #[serde(default)]
100 pub pubsub: PubsubConfig,
101
102 #[serde(default)]
104 pub peering: Peering,
105
106 #[serde(default, rename = "DNS")]
108 pub dns: DnsConfig,
109
110 #[serde(default)]
112 pub migration: Migration,
113
114 #[serde(default)]
116 pub experimental: Experiments,
117
118 #[serde(default)]
120 pub pinning: Pinning,
121
122 #[serde(default)]
124 pub import: Import,
125
126 #[serde(default)]
128 pub internal: Internal,
129}
130
131impl Config {
132 pub fn parse(s: &str) -> Result<Self, serde_json::Error> {
139 serde_json::from_str(s)
140 }
141
142 pub fn from_reader<R: std::io::Read>(reader: R) -> Result<Self, serde_json::Error> {
149 serde_json::from_reader(reader)
150 }
151
152 pub fn validate(&self) -> Result<Vec<String>, ConfigError> {
165 let mut warnings = Vec::new();
166
167 for addr in &self.addresses.swarm {
169 if !Self::is_valid_multiaddr_format(addr) {
170 warnings.push(format!("Swarm address may be malformed: {}", addr));
171 }
172 }
173
174 for addr in self.addresses.api.iter() {
176 if !Self::is_valid_multiaddr_format(addr) {
177 warnings.push(format!("API address may be malformed: {}", addr));
178 }
179 }
180
181 for addr in self.addresses.gateway.iter() {
183 if !Self::is_valid_multiaddr_format(addr) {
184 warnings.push(format!("Gateway address may be malformed: {}", addr));
185 }
186 }
187
188 for addr in &self.bootstrap {
190 if !Self::is_valid_multiaddr_format(addr) {
191 warnings.push(format!("Bootstrap address may be malformed: {}", addr));
192 }
193 }
194
195 if !self.identity.peer_id.is_empty()
197 && !Self::is_valid_peer_id_format(&self.identity.peer_id)
198 {
199 return Err(ConfigError::InvalidKey(format!(
200 "Invalid PeerID format: {}",
201 self.identity.peer_id
202 )));
203 }
204
205 let conn_mgr = &self.swarm.conn_mgr;
207 if let (Some(ref low), Some(ref high)) = (&conn_mgr.low_water, &conn_mgr.high_water) {
208 if let (Some(low_val), Some(high_val)) = (low.value(), high.value()) {
209 if low_val > high_val {
210 warnings.push(format!(
211 "ConnMgr.LowWater ({}) > HighWater ({})",
212 low_val, high_val
213 ));
214 }
215 }
216 }
217
218 Ok(warnings)
219 }
220
221 fn is_valid_multiaddr_format(addr: &str) -> bool {
225 if !addr.starts_with('/') {
227 return false;
228 }
229 let known_protocols = [
231 "/ip4/",
232 "/ip6/",
233 "/tcp/",
234 "/udp/",
235 "/quic/",
236 "/quic-v1/",
237 "/ws/",
238 "/wss/",
239 "/p2p/",
240 "/ipfs/",
241 "/dns/",
242 "/dns4/",
243 "/dns6/",
244 "/dnsaddr/",
245 "/unix/",
246 ];
247 known_protocols.iter().any(|p| addr.contains(p))
248 }
249
250 fn is_valid_peer_id_format(peer_id: &str) -> bool {
254 if peer_id.is_empty() {
259 return false;
260 }
261 peer_id
263 .chars()
264 .all(|c| c.is_ascii_alphanumeric() && c != '0' && c != 'O' && c != 'I' && c != 'l')
265 }
266
267 pub fn to_string_pretty(&self) -> Result<String, serde_json::Error> {
269 serde_json::to_string_pretty(self)
270 }
271
272 pub fn get_key(&self, key: &str) -> Option<serde_json::Value> {
274 let json = serde_json::to_value(self).ok()?;
275 let parts: Vec<&str> = key.split('.').collect();
276 let mut current = &json;
277
278 for part in parts {
279 current = current.get(part)?;
280 }
281
282 Some(current.clone())
283 }
284
285 pub fn set_key(&mut self, key: &str, value: serde_json::Value) -> Result<(), ConfigError> {
287 let mut json = serde_json::to_value(&self)?;
288 let parts: Vec<&str> = key.split('.').collect();
289
290 if parts.is_empty() {
291 return Err(ConfigError::InvalidKey(key.to_string()));
292 }
293
294 let mut current = &mut json;
296 for part in &parts[..parts.len() - 1] {
297 current = current
298 .get_mut(*part)
299 .ok_or_else(|| ConfigError::InvalidKey(key.to_string()))?;
300 }
301
302 let last_part = parts.last().unwrap();
304 if let Some(obj) = current.as_object_mut() {
305 obj.insert(last_part.to_string(), value);
306 } else {
307 return Err(ConfigError::InvalidKey(key.to_string()));
308 }
309
310 *self = serde_json::from_value(json)?;
311 Ok(())
312 }
313}
314
315#[derive(Debug, Clone, Default, Serialize, Deserialize)]
317#[serde(rename_all = "PascalCase")]
318pub struct Mounts {
319 #[serde(default, rename = "IPFS")]
320 pub ipfs: String,
321 #[serde(default, rename = "IPNS")]
322 pub ipns: String,
323 #[serde(default, rename = "FuseAllowOther")]
324 pub fuse_allow_other: bool,
325}
326
327#[derive(Debug, Clone, Default, Serialize, Deserialize)]
329#[serde(rename_all = "PascalCase")]
330pub struct Discovery {
331 #[serde(default, rename = "MDNS")]
332 pub mdns: Mdns,
333}
334
335#[derive(Debug, Clone, Default, Serialize, Deserialize)]
337#[serde(rename_all = "PascalCase")]
338pub struct Mdns {
339 #[serde(default)]
340 pub enabled: bool,
341}
342
343#[derive(Debug, Clone, Default, Serialize, Deserialize)]
345#[serde(rename_all = "PascalCase")]
346pub struct Ipns {
347 #[serde(default)]
348 pub republish_period: String,
349 #[serde(default)]
350 pub record_lifetime: String,
351 #[serde(default)]
352 pub resolve_cache_size: i32,
353 #[serde(default, skip_serializing_if = "Option::is_none")]
354 pub max_cache_ttl: Option<OptionalDuration>,
355 #[serde(default, skip_serializing_if = "Option::is_none")]
356 pub use_pubsub: Option<Flag>,
357}
358
359#[derive(Debug, Clone, Default, Serialize, Deserialize)]
361#[serde(rename_all = "PascalCase")]
362pub struct AutoNatConfig {
363 #[serde(default, skip_serializing_if = "Option::is_none")]
364 pub service_mode: Option<String>,
365 #[serde(default, skip_serializing_if = "Option::is_none")]
366 pub throttle: Option<AutoNatThrottle>,
367}
368
369#[derive(Debug, Clone, Default, Serialize, Deserialize)]
371#[serde(rename_all = "PascalCase")]
372pub struct AutoNatThrottle {
373 #[serde(default, skip_serializing_if = "Option::is_none")]
374 pub global_limit: Option<i32>,
375 #[serde(default, skip_serializing_if = "Option::is_none")]
376 pub peer_limit: Option<i32>,
377 #[serde(default, skip_serializing_if = "Option::is_none")]
378 pub interval: Option<OptionalDuration>,
379}
380
381#[derive(Debug, Clone, Default, Serialize, Deserialize)]
383#[serde(rename_all = "PascalCase")]
384pub struct PubsubConfig {
385 #[serde(default, skip_serializing_if = "Option::is_none")]
386 pub enabled: Option<Flag>,
387 #[serde(default)]
388 pub router: String,
389}
390
391#[derive(Debug, Clone, Default, Serialize, Deserialize)]
393#[serde(rename_all = "PascalCase")]
394pub struct Peering {
395 #[serde(default)]
396 pub peers: Vec<PeeringPeer>,
397}
398
399#[derive(Debug, Clone, Default, Serialize, Deserialize)]
401#[serde(rename_all = "PascalCase")]
402pub struct PeeringPeer {
403 #[serde(default, rename = "ID")]
404 pub id: String,
405 #[serde(default)]
406 pub addrs: Vec<String>,
407}
408
409#[derive(Debug, Clone, Default, Serialize, Deserialize)]
411#[serde(rename_all = "PascalCase")]
412pub struct DnsConfig {
413 #[serde(default)]
414 pub resolvers: HashMap<String, String>,
415}
416
417#[derive(Debug, Clone, Default, Serialize, Deserialize)]
419#[serde(rename_all = "PascalCase")]
420pub struct Migration {
421 #[serde(default, skip_serializing_if = "Option::is_none")]
422 pub download_sources: Option<Vec<String>>,
423 #[serde(default, skip_serializing_if = "Option::is_none")]
424 pub keep: Option<String>,
425}
426
427#[derive(Debug, Clone, Default, Serialize, Deserialize)]
429#[serde(rename_all = "PascalCase")]
430pub struct Experiments {
431 #[serde(default, skip_serializing_if = "Option::is_none")]
432 pub filestore_enabled: Option<bool>,
433 #[serde(default, skip_serializing_if = "Option::is_none")]
434 pub url_store_enabled: Option<bool>,
435 #[serde(
436 default,
437 skip_serializing_if = "Option::is_none",
438 rename = "GraphsyncEnabled"
439 )]
440 pub graphsync_enabled: Option<bool>,
441 #[serde(default, skip_serializing_if = "Option::is_none")]
442 pub libp2p_stream_mounting: Option<bool>,
443 #[serde(default, skip_serializing_if = "Option::is_none")]
444 pub p2p_http_proxy: Option<bool>,
445 #[serde(
446 default,
447 skip_serializing_if = "Option::is_none",
448 rename = "OptimisticProvide"
449 )]
450 pub optimistic_provide: Option<bool>,
451 #[serde(
452 default,
453 skip_serializing_if = "Option::is_none",
454 rename = "OptimisticProvideJobsPoolSize"
455 )]
456 pub optimistic_provide_jobs_pool_size: Option<i32>,
457}
458
459#[derive(Debug, Clone, Default, Serialize, Deserialize)]
461#[serde(rename_all = "PascalCase")]
462pub struct Pinning {
463 #[serde(default)]
464 pub remote_services: HashMap<String, RemotePinningService>,
465}
466
467#[derive(Debug, Clone, Default, Serialize, Deserialize)]
469#[serde(rename_all = "PascalCase")]
470pub struct RemotePinningService {
471 #[serde(default, rename = "API")]
472 pub api: RemotePinningApi,
473 #[serde(default)]
474 pub policies: RemotePinningPolicies,
475}
476
477#[derive(Debug, Clone, Default, Serialize, Deserialize)]
479#[serde(rename_all = "PascalCase")]
480pub struct RemotePinningApi {
481 #[serde(default)]
482 pub endpoint: String,
483 #[serde(default)]
484 pub key: String,
485}
486
487#[derive(Debug, Clone, Default, Serialize, Deserialize)]
489#[serde(rename_all = "PascalCase")]
490pub struct RemotePinningPolicies {
491 #[serde(default, rename = "MFS")]
492 pub mfs: MfsPinPolicy,
493}
494
495#[derive(Debug, Clone, Default, Serialize, Deserialize)]
497#[serde(rename_all = "PascalCase")]
498pub struct MfsPinPolicy {
499 #[serde(default)]
500 pub enabled: bool,
501 #[serde(default)]
502 pub pin_name: String,
503 #[serde(default)]
504 pub repinning_interval: String,
505}
506
507#[derive(Debug, Clone, Default, Serialize, Deserialize)]
509#[serde(rename_all = "PascalCase")]
510pub struct Import {
511 #[serde(
512 default,
513 skip_serializing_if = "Option::is_none",
514 rename = "CidVersion"
515 )]
516 pub cid_version: Option<OptionalInteger>,
517 #[serde(default, skip_serializing_if = "Option::is_none")]
518 pub hash_function: Option<OptionalString>,
519 #[serde(default, skip_serializing_if = "Option::is_none")]
520 pub unixfs_raw_leaves: Option<Flag>,
521 #[serde(
522 default,
523 skip_serializing_if = "Option::is_none",
524 rename = "UnixFSChunker"
525 )]
526 pub unixfs_chunker: Option<OptionalString>,
527}
528
529#[derive(Debug, Clone, Default, Serialize, Deserialize)]
531#[serde(rename_all = "PascalCase")]
532pub struct Internal {
533 #[serde(default, skip_serializing_if = "Option::is_none")]
534 pub bitswap: Option<InternalBitswap>,
535 #[serde(
536 default,
537 skip_serializing_if = "Option::is_none",
538 rename = "UnixFSShardingSizeThreshold"
539 )]
540 pub unixfs_sharding_size_threshold: Option<OptionalString>,
541 #[serde(default, skip_serializing_if = "Option::is_none")]
542 pub libp2p_force_pnet: Option<Flag>,
543 #[serde(default, skip_serializing_if = "Option::is_none")]
544 pub backoff_init: Option<OptionalDuration>,
545 #[serde(default, skip_serializing_if = "Option::is_none")]
546 pub backoff_max: Option<OptionalDuration>,
547}
548
549#[derive(Debug, Clone, Default, Serialize, Deserialize)]
551#[serde(rename_all = "PascalCase")]
552pub struct InternalBitswap {
553 #[serde(default, skip_serializing_if = "Option::is_none")]
554 pub task_worker_count: Option<OptionalInteger>,
555 #[serde(default, skip_serializing_if = "Option::is_none")]
556 pub engine_block_store_worker_count: Option<OptionalInteger>,
557 #[serde(default, skip_serializing_if = "Option::is_none")]
558 pub engine_task_worker_count: Option<OptionalInteger>,
559 #[serde(default, skip_serializing_if = "Option::is_none")]
560 pub max_outstanding_bytes_per_peer: Option<OptionalInteger>,
561 #[serde(default, skip_serializing_if = "Option::is_none")]
562 pub provide_enabled: Option<Flag>,
563}
564
565#[derive(Debug, thiserror::Error)]
567pub enum ConfigError {
568 #[error("JSON error: {0}")]
569 Json(#[from] serde_json::Error),
570 #[error("Invalid key: {0}")]
571 InvalidKey(String),
572 #[error("IO error: {0}")]
573 Io(#[from] std::io::Error),
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579
580 #[test]
581 fn test_default_config() {
582 let config = Config::default();
583 let json = config.to_string_pretty().unwrap();
584 assert!(json.contains("Identity"));
585 }
586
587 #[test]
588 fn test_config_roundtrip() {
589 let config = Config::default();
590 let json = config.to_string_pretty().unwrap();
591 let parsed: Config = Config::parse(&json).unwrap();
592 assert_eq!(config.identity.peer_id, parsed.identity.peer_id);
593 }
594}