node: Remove dependency on `anyhow`

`anyhow` is only used to capture a handful of errors. It also turns out that
usage of `lexopt` was wrong, since it features `parse_with`, which neatly
integrates with `FromStr`. The types, luckily, implement this trait already –
making for cleaner parsing.

For now, the execution errors are all "transparent", which shouldn't be
much of a regression, if at all.
This commit is contained in:
Lorenz Leutgeb 2025-08-27 12:24:14 +02:00 committed by Fintan Halpenny
parent 4bf3ab6fb8
commit 79d928551b
3 changed files with 74 additions and 57 deletions

1
Cargo.lock generated
View File

@ -2851,7 +2851,6 @@ name = "radicle-node"
version = "0.15.0" version = "0.15.0"
dependencies = [ dependencies = [
"amplify", "amplify",
"anyhow",
"bloomy", "bloomy",
"bytes", "bytes",
"chrono", "chrono",

View File

@ -16,7 +16,6 @@ test = ["radicle/test", "radicle-crypto/test", "radicle-crypto/cyphernet", "radi
[dependencies] [dependencies]
amplify = { workspace = true } amplify = { workspace = true }
anyhow = { workspace = true }
bloomy = "1.2" bloomy = "1.2"
bytes = { workspace = true } bytes = { workspace = true }
chrono = { workspace = true, features = ["clock"] } chrono = { workspace = true, features = ["clock"] }

View File

@ -1,11 +1,14 @@
use std::io; use std::io;
use std::{env, fs, net, path::PathBuf, process}; use std::net::SocketAddr;
use std::path::PathBuf;
use std::process::exit;
use anyhow::Context;
use crossbeam_channel as chan; use crossbeam_channel as chan;
use thiserror::Error;
use radicle::node::device::Device; use radicle::node::device::Device;
use radicle::profile; use radicle::profile;
use radicle_node::crypto::ssh::keystore::{Keystore, MemorySigner}; use radicle_node::crypto::ssh::keystore::{Keystore, MemorySigner};
use radicle_node::{Runtime, VERSION}; use radicle_node::{Runtime, VERSION};
#[cfg(unix)] #[cfg(unix)]
@ -39,65 +42,74 @@ Options
--help Print help --help Print help
"#; "#;
#[derive(Debug)]
struct Options { struct Options {
config: Option<PathBuf>, config: Option<PathBuf>,
listen: Vec<net::SocketAddr>, listen: Vec<SocketAddr>,
log: Option<log::Level>, log: Option<log::Level>,
force: bool, force: bool,
} }
impl Options { fn parse_options() -> Result<Options, lexopt::Error> {
fn from_env() -> Result<Self, anyhow::Error> { use lexopt::prelude::*;
use lexopt::prelude::*; use std::str::FromStr as _;
let mut parser = lexopt::Parser::from_env(); let mut parser = lexopt::Parser::from_env();
let mut listen = Vec::new(); let mut listen = Vec::new();
let mut config = None; let mut config = None;
let mut force = false; let mut force = false;
let mut log = None; let mut log = None;
while let Some(arg) = parser.next()? { while let Some(arg) = parser.next()? {
match arg { match arg {
Long("force") => { Long("force") => {
force = true; force = true;
} }
Long("config") => { Long("config") => {
let value = parser.value()?; config = Some(parser.value()?.parse_with(PathBuf::from_str)?);
let path = PathBuf::from(value); }
config = Some(path); Long("listen") => {
} let addr = parser.value()?.parse_with(SocketAddr::from_str)?;
Long("listen") => { listen.push(addr);
let addr = parser.value()?.parse()?; }
listen.push(addr); Long("log") => {
} log = Some(parser.value()?.parse_with(log::Level::from_str)?);
Long("log") => { }
log = Some(parser.value()?.parse()?); Long("help") | Short('h') => {
} println!("{HELP_MSG}");
Long("help") | Short('h') => { exit(0);
println!("{HELP_MSG}"); }
process::exit(0); Long("version") => {
} let _ = VERSION.write(&mut io::stdout());
Long("version") => { exit(0);
VERSION.write(&mut io::stdout())?; }
process::exit(0); _ => {
} return Err(arg.unexpected());
_ => anyhow::bail!(arg.unexpected()),
} }
} }
Ok(Self {
force,
listen,
log,
config,
})
} }
Ok(Options {
force,
listen,
log,
config,
})
} }
fn execute() -> anyhow::Result<()> { #[derive(Error, Debug)]
enum ExecutionError {
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
ConfigurationLoading(#[from] profile::config::LoadError),
#[error(transparent)]
MemorySigner(#[from] radicle::crypto::ssh::keystore::MemorySignerError),
#[error(transparent)]
Runtime(#[from] radicle_node::runtime::Error),
}
fn execute(options: Options) -> Result<(), ExecutionError> {
let home = profile::home()?; let home = profile::home()?;
let options = Options::from_env()?;
// Up to now, the active log level was `LOG_LEVEL_DEFAULT`. // Up to now, the active log level was `LOG_LEVEL_DEFAULT`.
// The first thing we do after reading command line options is // The first thing we do after reading command line options is
@ -126,16 +138,14 @@ fn execute() -> anyhow::Result<()> {
let passphrase = profile::env::passphrase(); let passphrase = profile::env::passphrase();
let keystore = Keystore::new(&home.keys()); let keystore = Keystore::new(&home.keys());
let signer = Device::from( let signer = Device::from(MemorySigner::load(&keystore, passphrase)?);
MemorySigner::load(&keystore, passphrase).context("couldn't load secret key")?,
);
log::info!(target: "node", "Node ID is {}", signer.public_key()); log::info!(target: "node", "Node ID is {}", signer.public_key());
// Add the preferred seeds as persistent peers so that we reconnect to them automatically. // Add the preferred seeds as persistent peers so that we reconnect to them automatically.
config.node.connect.extend(config.preferred_seeds); config.node.connect.extend(config.preferred_seeds);
let listen: Vec<std::net::SocketAddr> = if !options.listen.is_empty() { let listen = if !options.listen.is_empty() {
options.listen.clone() options.listen.clone()
} else { } else {
config.node.listen.clone() config.node.listen.clone()
@ -161,7 +171,7 @@ fn execute() -> anyhow::Result<()> {
if options.force { if options.force {
log::debug!(target: "node", "Removing existing control socket.."); log::debug!(target: "node", "Removing existing control socket..");
fs::remove_file(home.socket()).ok(); std::fs::remove_file(home.socket()).ok();
} }
Runtime::init(home, config.node, listen, signals, signer)?.run()?; Runtime::init(home, config.node, listen, signals, signer)?.run()?;
@ -186,7 +196,7 @@ fn initialize_logging() {
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[error("Error connecting to systemd journal: {0}")] #[error("Error connecting to systemd journal: {0}")]
struct JournalError(std::io::Error); struct JournalError(io::Error);
radicle_systemd::journal::logger::<&str, &str, _>("radicle-node".to_string(), []) radicle_systemd::journal::logger::<&str, &str, _>("radicle-node".to_string(), [])
.map_err(|err| Box::new(JournalError(err)) as Error) .map_err(|err| Box::new(JournalError(err)) as Error)
@ -226,8 +236,17 @@ fn main() {
initialize_logging(); initialize_logging();
if let Err(err) = execute() { let options = match parse_options() {
Ok(options) => options,
Err(err) => {
// The lexopt errors read nicely with a comma.
log::error!(target: "node", "Failed to parse options, {err:#}");
exit(2);
}
};
if let Err(err) = execute(options) {
log::error!(target: "node", "{err:#}"); log::error!(target: "node", "{err:#}");
process::exit(1); exit(1);
} }
} }