1pub mod handle;
2pub mod thread;
3
4use std::fmt::Debug;
5use std::path::PathBuf;
6use std::str::FromStr as _;
7use std::sync::mpsc;
8use std::{fs, io, net};
9
10#[cfg(unix)]
11use std::os::unix::net::UnixListener;
12#[cfg(windows)]
13use uds_windows::UnixListener;
14
15use handle::Handle;
16use localtime::LocalTime;
17use protocol::service;
18use protocol::service::gossip;
19use radicle::cob::migrate;
20use radicle::crypto::{Signer as _, SigningKey};
21use radicle::node;
22use radicle::node::Event;
23use radicle::node::UserAgent;
24use radicle::node::address;
25use radicle::node::address::Store as _;
26use radicle::node::events::Emitter;
27use radicle::node::notifications;
28use radicle::node::policy::config as policy;
29use radicle::node::{NodeId, routing};
30use radicle::profile::Home;
31use radicle::{Storage, cob, git, storage};
32use radicle_signals::Signal;
33use thiserror::Error;
34
35use crate::control;
36use crate::reactor;
37use crate::reactor::Reactor;
38use crate::wire::Wire;
39use crate::worker;
40
41pub const MAX_PENDING_TASKS: usize = 1024;
43
44#[derive(Error, Debug)]
46pub enum Error {
47 #[error("routing database error: {0}")]
49 Routing(#[from] routing::Error),
50 #[error("cobs cache database error: {0}")]
52 CobsCache(#[from] cob::cache::Error),
53 #[error("node database error: {0}")]
55 Database(#[from] node::db::Error),
56 #[error("storage error: {0}")]
58 Storage(#[from] storage::Error),
59 #[error("policies database error: {0}")]
61 Policy(#[from] policy::Error),
62 #[error("notifications database error: {0}")]
64 Notifications(#[from] notifications::Error),
65 #[error("gossip database error: {0}")]
67 Gossip(#[from] gossip::Error),
68 #[error("address database error: {0}")]
70 Address(#[from] address::Error),
71 #[error("service error: {0}")]
73 Service(Box<service::Error>),
74 #[error("i/o error: {0}")]
76 Io(#[from] io::Error),
77 #[error("control socket error: {0}")]
79 Control(#[from] control::Error),
80 #[error(
82 "another node appears to be running; \
83 if this isn't the case, delete the socket file at '{0}' \
84 and restart the node"
85 )]
86 AlreadyRunning(PathBuf),
87 #[error("git version error: {0}")]
89 GitVersion(#[from] git::VersionError),
90}
91
92impl From<service::Error> for Error {
93 fn from(e: service::Error) -> Self {
94 Self::Service(Box::new(e))
95 }
96}
97
98pub enum ControlSocket {
100 Bound(UnixListener, PathBuf),
102 Received(UnixListener),
104}
105
106pub struct Runtime {
108 pub id: NodeId,
109 pub home: Home,
110 pub control: ControlSocket,
111 pub handle: Handle,
112 pub storage: Storage,
113 pub reactor: Reactor,
114 pub pool: worker::Pool,
115 pub local_addrs: Vec<net::SocketAddr>,
116 pub signals: mpsc::Receiver<Signal>,
117}
118
119impl Runtime {
120 pub fn init(
124 home: Home,
125 config: radicle::node::Config,
126 socket: PathBuf,
127 listen: Vec<net::SocketAddr>,
128 signals: mpsc::Receiver<Signal>,
129 secret_key: SigningKey,
130 ) -> Result<Runtime, Error> {
131 let id = NodeId::from(*secret_key.public_key());
132 let alias = config.alias.clone();
133 let network = config.network;
134 let rng = fastrand::Rng::new();
135 let clock = LocalTime::now();
136 let timestamp = clock.into();
137 let storage = Storage::open(home.storage(), git::UserInfo { alias, key: id })?;
138 let policy = config.seeding_policy.into();
139
140 for (key, _) in &config.extra {
141 log::warn!(target: "node", "Unused or deprecated configuration attribute {key:?}");
142 }
143
144 log::info!(target: "node", "Opening policy database..");
145 let policies = home.policies_mut()?;
146 let policies = policy::Config::new(policy, policies);
147 let notifications = home.notifications_mut()?;
148 let mut cobs_cache = cob::cache::Store::open(home.cobs().join(cob::cache::COBS_DB_FILE))?;
149
150 match cobs_cache.check_version() {
151 Ok(()) => {}
152 Err(cob::cache::Error::OutOfDate) => {
153 log::info!(target: "node", "Migrating COBs cache..");
154 let version = cobs_cache.migrate(migrate::log)?;
155 log::info!(target: "node", "Migration of COBs cache complete (version={version})..");
156 }
157 Err(e) => return Err(e.into()),
158 }
159
160 log::info!(target: "node", "Default seeding policy set to '{}'", policy);
161 log::info!(target: "node", "Initializing service ({network:?})..");
162
163 let announcement = service::gossip::node(&config, timestamp)
164 .solve(Default::default())
165 .expect("Runtime::init: unable to solve proof-of-work puzzle");
166
167 log::info!(target: "node", "Opening node database..");
168 let db = home.database_mut(config.database)?.init(
169 &id,
170 announcement.features,
171 &announcement.alias,
172 &announcement.agent,
173 announcement.timestamp,
174 announcement.addresses.iter(),
175 )?;
176 let mut stores: service::Stores<_> = db.clone().into();
177
178 if config.connect.is_empty() && stores.addresses().is_empty()? {
179 log::info!(target: "node", "Address book is empty. Adding bootstrap nodes..");
180
181 for (alias, version, addrs) in config.network.bootstrap() {
182 for addr in addrs {
183 let (id, addr) = addr.into();
184
185 stores.addresses_mut().insert(
186 &id,
187 version,
188 radicle::node::Features::SEED,
189 &alias,
190 0,
191 &UserAgent::from_str("/radicle/runtime/bootstrap/")
192 .expect("valid user agent"),
193 clock.into(),
194 [node::KnownAddress::new(addr, address::Source::Bootstrap)],
195 )?;
196 }
197 }
198 log::info!(target: "node", "{} nodes added to address book", stores.addresses().len()?);
199 }
200
201 let emitter: Emitter<Event> = Default::default();
202 let mut service = service::Service::new(
203 config.clone(),
204 stores,
205 storage.clone(),
206 policies,
207 secret_key.clone(),
208 rng,
209 announcement,
210 emitter.clone(),
211 );
212 service.initialize(clock)?;
213
214 let (worker_send, worker_recv) =
215 crossbeam_channel::bounded::<worker::Task>(MAX_PENDING_TASKS);
216 let mut wire = Wire::new(service, worker_send, secret_key.clone());
217 let mut local_addrs = Vec::new();
218
219 for addr in listen {
220 let listener = reactor::Listener::bind(addr)?;
221 let local_addr = listener.local_addr();
222
223 local_addrs.push(local_addr);
224 wire.listen(listener);
225 }
226 let reactor = Reactor::new(wire, thread::name(&id, "service"))?;
227 let handle = Handle::new(home.clone(), socket.clone(), reactor.controller(), emitter);
228
229 let nid = NodeId::from(*secret_key.public_key());
230 let fetch = worker::FetchConfig {
231 local: nid,
232 expiry: worker::garbage::Expiry::default(),
233 };
234 let pool = worker::Pool::with(
235 worker_recv,
236 nid,
237 handle.clone(),
238 notifications,
239 cobs_cache,
240 db,
241 worker::Config {
242 capacity: config.workers.into(),
243 storage: storage.clone(),
244 fetch,
245 policy,
246 policies_db: home.node().join(node::POLICIES_DB_FILE),
247 },
248 )?;
249 let control = Self::bind(socket)?;
250
251 Ok(Runtime {
252 id,
253 home,
254 control,
255 storage,
256 reactor,
257 handle,
258 pool,
259 signals,
260 local_addrs,
261 })
262 }
263
264 pub fn run(self) -> Result<(), Error> {
265 let home = self.home;
266 let (listener, remove) = match self.control {
267 ControlSocket::Bound(listener, path) => (listener, Some(path)),
268 ControlSocket::Received(listener) => (listener, None),
269 };
270
271 log::info!(target: "node", "Running node {} in {}..", self.id, home.path().display());
272
273 thread::spawn(&self.id, "control", {
274 let handle = self.handle.clone();
275 || control::listen(listener, handle)
276 });
277
278 let _signals = thread::spawn(&self.id, "signals", move || {
279 loop {
280 use radicle::node::Handle as _;
281
282 match self.signals.recv() {
283 Ok(Signal::Terminate | Signal::Interrupt) => {
284 log::info!(target: "node", "Termination signal received; shutting down..");
285 self.handle.shutdown().ok();
286 break;
287 }
288 Ok(Signal::Hangup) => {
289 log::debug!(target: "node", "Hangup signal (SIGHUP) received; ignoring..");
290 }
291 Ok(Signal::WindowChanged) => {}
292 Err(e) => {
293 log::warn!(target: "node", "Signal notifications channel error: {e}");
294 break;
295 }
296 }
297 }
298 });
299
300 self.pool.run().unwrap();
301 self.reactor.join().unwrap();
302
303 remove.map(|path| fs::remove_file(path).ok());
308
309 log::debug!(target: "node", "Node shutdown completed for {}", self.id);
310
311 Ok(())
312 }
313
314 #[cfg(all(feature = "socket2", feature = "systemd", target_os = "linux"))]
315 fn receive_listener() -> Option<UnixListener> {
316 let fd = match unsafe { radicle_systemd::listen::fd("control") } {
318 Ok(Some(fd)) => fd,
319 Ok(None) => return None,
320 Err(err) => {
321 log::error!(target: "node", "Error receiving listener from systemd: {err}");
322 return None;
323 }
324 };
325
326 let socket: socket2::Socket = unsafe { std::os::fd::FromRawFd::from_raw_fd(fd) };
327
328 let domain = match socket.domain() {
329 Ok(domain) => domain,
330 Err(err) => {
331 log::error!(target: "node", "Error receiving listener from systemd when inspecting domain of socket: {err}");
332 return None;
333 }
334 };
335
336 if domain != socket2::Domain::UNIX {
337 log::error!(target: "node", "Dropping listener received from systemd: Domain is not AF_UNIX.");
338 return None;
339 }
340
341 Some(UnixListener::from(socket))
342 }
343
344 fn bind(path: PathBuf) -> Result<ControlSocket, Error> {
345 #[cfg(all(feature = "socket2", feature = "systemd", target_os = "linux"))]
346 {
347 if let Some(listener) = Self::receive_listener() {
348 log::info!(target: "node", "Received control socket.");
349 return Ok(ControlSocket::Received(listener));
350 }
351 }
352
353 log::info!(target: "node", "Binding control socket {}..", path.display());
354 match UnixListener::bind(&path) {
355 Ok(sock) => Ok(ControlSocket::Bound(sock, path)),
356 Err(err) if err.kind() == io::ErrorKind::AddrInUse => Err(Error::AlreadyRunning(path)),
357 Err(err) => Err(err.into()),
358 }
359 }
360}