use std::path::{Path, PathBuf}; pub const DEFAULT_CONFIG_TOML: &str = r#"# geth local node config # Remote node-to-node communication is Iroh-only. # # relay_mode controls Iroh relay use for practical internet connectivity. # Values: "default", "staging", "disabled". [iroh] relay_mode = "default" "#; #[derive(Clone, Debug)] pub struct GethPaths { home: PathBuf, } impl GethPaths { pub fn resolve() -> Result { if let Some(home) = std::env::var_os("GETH_HOME") { return Ok(Self { home: PathBuf::from(home), }); } let project_dirs = directories::ProjectDirs::from("local", "geth", "geth") .ok_or(ConfigError::NoDataDirectory)?; Ok(Self { home: project_dirs.data_dir().to_path_buf(), }) } #[must_use] pub fn from_home(home: impl Into) -> Self { Self { home: home.into() } } #[must_use] pub fn home(&self) -> &Path { &self.home } #[must_use] pub fn config_file(&self) -> PathBuf { self.home.join("config.toml") } #[must_use] pub fn metadata_db(&self) -> PathBuf { self.home.join("geth.sqlite") } #[must_use] pub fn identity_dir(&self) -> PathBuf { self.home.join("identity") } #[must_use] pub fn agent_key(&self) -> PathBuf { self.identity_dir().join("agent.ed25519") } #[must_use] pub fn iroh_key(&self) -> PathBuf { self.identity_dir().join("iroh.ed25519") } #[must_use] pub fn cas_dir(&self) -> PathBuf { self.home.join("cas") } #[must_use] pub fn run_dir(&self) -> PathBuf { self.home.join("run") } #[must_use] pub fn socket_path(&self) -> PathBuf { self.run_dir().join("geth.sock") } pub fn ensure_base_dirs(&self) -> Result<(), ConfigError> { std::fs::create_dir_all(self.identity_dir())?; std::fs::create_dir_all(self.cas_dir().join("blobs"))?; std::fs::create_dir_all(self.run_dir())?; Ok(()) } } #[derive(Debug, thiserror::Error)] pub enum ConfigError { #[error("could not determine OS data directory and GETH_HOME is unset")] NoDataDirectory, #[error("io error: {0}")] Io(#[from] std::io::Error), #[error("toml parse error: {0}")] TomlParse(#[from] toml_edit::TomlError), #[error("invalid iroh relay_mode: {0}")] InvalidRelayMode(String), } #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct GethConfig { pub iroh: IrohConfig, } impl GethConfig { #[must_use] pub fn default_toml() -> &'static str { DEFAULT_CONFIG_TOML } pub fn load(path: &Path) -> Result { if !path.exists() { return Ok(Self::default()); } Self::parse(&std::fs::read_to_string(path)?) } pub fn parse(text: &str) -> Result { let document = text.parse::()?; let relay_mode = match document.get("iroh").and_then(|iroh| iroh.get("relay_mode")) { Some(item) => { let value = item .as_str() .ok_or_else(|| ConfigError::InvalidRelayMode(item.to_string()))?; RelayMode::parse(value)? } None => RelayMode::default(), }; Ok(Self { iroh: IrohConfig { relay_mode }, }) } } #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct IrohConfig { pub relay_mode: RelayMode, } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum RelayMode { Disabled, #[default] Default, Staging, } impl RelayMode { pub fn parse(value: &str) -> Result { match value { "disabled" => Ok(Self::Disabled), "default" => Ok(Self::Default), "staging" => Ok(Self::Staging), other => Err(ConfigError::InvalidRelayMode(other.to_owned())), } } #[must_use] pub fn as_str(self) -> &'static str { match self { Self::Disabled => "disabled", Self::Default => "default", Self::Staging => "staging", } } } #[cfg(test)] mod tests { use super::*; #[test] fn default_config_uses_iroh_relays() { let config = GethConfig::parse(GethConfig::default_toml()).expect("parse config"); assert_eq!(config.iroh.relay_mode, RelayMode::Default); } #[test] fn config_parses_disabled_relay_mode() { let config = GethConfig::parse("[iroh]\nrelay_mode = \"disabled\"\n").expect("parse"); assert_eq!(config.iroh.relay_mode, RelayMode::Disabled); } #[test] fn config_rejects_unknown_relay_mode() { let error = GethConfig::parse("[iroh]\nrelay_mode = \"magic\"\n").expect_err("error"); assert!(matches!(error, ConfigError::InvalidRelayMode(value) if value == "magic")); } #[test] fn config_rejects_non_string_relay_mode() { let error = GethConfig::parse("[iroh]\nrelay_mode = true\n").expect_err("error"); assert!(matches!(error, ConfigError::InvalidRelayMode(_))); } }