ferripfs_network/
daemon.rs1use std::fs;
11use std::path::{Path, PathBuf};
12use std::sync::Arc;
13
14use libp2p::identity::Keypair;
15use tokio::sync::oneshot;
16
17use ferripfs_config::Config;
18use ferripfs_repo::{FsRepo, RepoLock, API_FILE};
19
20use crate::{
21 host::{HostConfig, NetworkHost},
22 NetworkError, NetworkResult, PeerInfo,
23};
24
25#[derive(Debug, Clone)]
27pub struct DaemonConfig {
28 pub repo_path: PathBuf,
30 pub offline: bool,
32 pub enable_mdns: bool,
34 pub enable_pubsub: bool,
36 pub enable_ipns_pubsub: bool,
38 pub routing: String,
40}
41
42impl Default for DaemonConfig {
43 fn default() -> Self {
44 Self {
45 repo_path: PathBuf::new(),
46 offline: false,
47 enable_mdns: true,
48 enable_pubsub: false,
49 enable_ipns_pubsub: false,
50 routing: "dht".into(),
51 }
52 }
53}
54
55pub struct DaemonHandle {
57 shutdown_tx: Option<oneshot::Sender<()>>,
59 host: Arc<NetworkHost>,
61 _lock: RepoLock,
63 repo_path: PathBuf,
65}
66
67impl DaemonHandle {
68 pub fn host(&self) -> &NetworkHost {
70 &self.host
71 }
72
73 pub async fn peer_info(&self) -> NetworkResult<PeerInfo> {
75 self.host.peer_info().await
76 }
77
78 pub async fn shutdown(mut self) -> NetworkResult<()> {
80 if let Some(tx) = self.shutdown_tx.take() {
82 let _ = tx.send(());
83 }
84
85 self.host.shutdown().await?;
87
88 let api_file = self.repo_path.join(API_FILE);
90 let _ = fs::remove_file(&api_file);
91
92 Ok(())
93 }
94}
95
96impl Drop for DaemonHandle {
97 fn drop(&mut self) {
98 let api_file = self.repo_path.join(API_FILE);
100 let _ = fs::remove_file(&api_file);
101 }
102}
103
104pub struct Daemon;
106
107impl Daemon {
108 pub async fn start(config: DaemonConfig) -> NetworkResult<DaemonHandle> {
110 let repo_path = config.repo_path.clone();
111
112 if !FsRepo::is_initialized(&repo_path) {
114 return Err(NetworkError::Daemon(format!(
115 "Repository not initialized at {}. Run 'ferripfs init' first.",
116 repo_path.display()
117 )));
118 }
119
120 let lock = RepoLock::acquire(&repo_path).map_err(|e| {
122 if matches!(e, ferripfs_repo::RepoError::Locked) {
123 NetworkError::AlreadyRunning
124 } else {
125 NetworkError::Repo(e)
126 }
127 })?;
128
129 let repo = FsRepo::open(&repo_path)?;
131 let ferripfs_config = repo.config().clone();
132
133 let keypair = Self::load_keypair(&ferripfs_config)?;
135
136 let host_config = if config.offline {
138 HostConfig {
140 keypair,
141 listen_addrs: Vec::new(),
142 bootstrap_peers: Vec::new(),
143 swarm_config: crate::swarm::SwarmConfig {
144 mdns_enabled: false,
145 relay_enabled: false,
146 ..Default::default()
147 },
148 }
149 } else {
150 HostConfig::from_config(&ferripfs_config, keypair)
151 };
152
153 let host = NetworkHost::start(host_config).await?;
155 let host = Arc::new(host);
156
157 let api_addr = ferripfs_config
159 .addresses
160 .api
161 .first()
162 .cloned()
163 .unwrap_or_else(|| "/ip4/127.0.0.1/tcp/5001".into());
164 let api_file = repo_path.join(API_FILE);
165 fs::write(&api_file, &api_addr).map_err(NetworkError::Io)?;
166
167 let (shutdown_tx, _shutdown_rx) = oneshot::channel();
169
170 Ok(DaemonHandle {
171 shutdown_tx: Some(shutdown_tx),
172 host,
173 _lock: lock,
174 repo_path,
175 })
176 }
177
178 fn load_keypair(config: &Config) -> NetworkResult<Keypair> {
180 let priv_key_str = config
182 .identity
183 .priv_key
184 .as_ref()
185 .ok_or_else(|| NetworkError::Init("Identity.PrivKey not found in config".into()))?;
186
187 use base64::Engine;
189 let key_bytes = base64::engine::general_purpose::STANDARD
190 .decode(priv_key_str)
191 .map_err(|e| NetworkError::Init(format!("Invalid private key encoding: {}", e)))?;
192
193 let keypair = Keypair::from_protobuf_encoding(&key_bytes)
195 .map_err(|e| NetworkError::Init(format!("Invalid keypair: {}", e)))?;
196
197 Ok(keypair)
198 }
199
200 pub fn is_running(repo_path: impl AsRef<Path>) -> bool {
202 RepoLock::is_locked(repo_path)
203 }
204
205 pub fn read_api_addr(repo_path: impl AsRef<Path>) -> Option<String> {
207 let api_file = repo_path.as_ref().join(API_FILE);
208 fs::read_to_string(api_file).ok()
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_daemon_config_default() {
218 let config = DaemonConfig::default();
219 assert!(!config.offline);
220 assert!(config.enable_mdns);
221 assert_eq!(config.routing, "dht");
222 }
223}