2026-05-15 15:08:20 +02:00
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
|
|
2026-05-16 03:17:45 +02:00
|
|
|
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"
|
|
|
|
|
"#;
|
|
|
|
|
|
2026-05-15 15:08:20 +02:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub struct GethPaths {
|
|
|
|
|
home: PathBuf,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl GethPaths {
|
|
|
|
|
pub fn resolve() -> Result<Self, ConfigError> {
|
|
|
|
|
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<PathBuf>) -> 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")
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-16 01:54:00 +02:00
|
|
|
#[must_use]
|
|
|
|
|
pub fn iroh_key(&self) -> PathBuf {
|
|
|
|
|
self.identity_dir().join("iroh.ed25519")
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 15:08:20 +02:00
|
|
|
#[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),
|
2026-05-16 03:17:45 +02:00
|
|
|
#[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<Self, ConfigError> {
|
|
|
|
|
if !path.exists() {
|
|
|
|
|
return Ok(Self::default());
|
|
|
|
|
}
|
|
|
|
|
Self::parse(&std::fs::read_to_string(path)?)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn parse(text: &str) -> Result<Self, ConfigError> {
|
|
|
|
|
let document = text.parse::<toml_edit::DocumentMut>()?;
|
|
|
|
|
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<Self, ConfigError> {
|
|
|
|
|
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(_)));
|
|
|
|
|
}
|
2026-05-15 15:08:20 +02:00
|
|
|
}
|