diff --git a/iroh-ctl/src/main.rs b/iroh-ctl/src/main.rs index 8ff95ab24b..7bcf141b25 100644 --- a/iroh-ctl/src/main.rs +++ b/iroh-ctl/src/main.rs @@ -8,7 +8,7 @@ use iroh_ctl::{ store::{run_command as run_store_command, Store}, }; use iroh_rpc_client::Client; -use iroh_util::{iroh_home_path, make_config}; +use iroh_util::{iroh_config_path, make_config}; use iroh_ctl::{ config::{Config, CONFIG_FILE_NAME, ENV_PREFIX}, @@ -53,7 +53,8 @@ enum Commands { async fn main() -> anyhow::Result<()> { let cli = Cli::parse(); - let sources = vec![iroh_home_path(CONFIG_FILE_NAME), cli.cfg.clone()]; + let cfg_path = iroh_config_path(CONFIG_FILE_NAME)?; + let sources = vec![Some(cfg_path), cli.cfg.clone()]; let config = make_config( // default Config::default(), diff --git a/iroh-gateway/src/main.rs b/iroh-gateway/src/main.rs index 93eae13c32..91db5426ea 100644 --- a/iroh-gateway/src/main.rs +++ b/iroh-gateway/src/main.rs @@ -9,7 +9,7 @@ use iroh_gateway::{ core::Core, metrics, }; -use iroh_util::{iroh_home_path, make_config}; +use iroh_util::{iroh_config_path, make_config}; use tokio::sync::RwLock; use tracing::{debug, error}; @@ -17,7 +17,8 @@ use tracing::{debug, error}; async fn main() -> Result<()> { let args = Args::parse(); - let sources = vec![iroh_home_path(CONFIG_FILE_NAME), args.cfg.clone()]; + let cfg_path = iroh_config_path(CONFIG_FILE_NAME)?; + let sources = vec![Some(cfg_path), args.cfg.clone()]; let mut config = make_config( // default Config::default(), diff --git a/iroh-one/src/main.rs b/iroh-one/src/main.rs index fcc25ed753..03b83380b5 100644 --- a/iroh-one/src/main.rs +++ b/iroh-one/src/main.rs @@ -11,7 +11,7 @@ use iroh_one::{ config::{Config, CONFIG_FILE_NAME, ENV_PREFIX}, }; use iroh_rpc_types::Addr; -use iroh_util::{iroh_home_path, make_config}; +use iroh_util::{iroh_config_path, make_config}; #[cfg(feature = "uds-gateway")] use tempdir::TempDir; use tokio::sync::RwLock; @@ -21,7 +21,8 @@ use tracing::{debug, error}; async fn main() -> Result<()> { let args = Args::parse(); - let sources = vec![iroh_home_path(CONFIG_FILE_NAME), args.cfg.clone()]; + let cfg_path = iroh_config_path(CONFIG_FILE_NAME)?; + let sources = vec![Some(cfg_path), args.cfg.clone()]; let mut config = make_config( // default Config::default(), diff --git a/iroh-p2p/src/keys.rs b/iroh-p2p/src/keys.rs index bef895a5da..c19ccd70c2 100644 --- a/iroh-p2p/src/keys.rs +++ b/iroh-p2p/src/keys.rs @@ -5,7 +5,7 @@ use std::path::{Path, PathBuf}; use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::{Stream, StreamExt, TryStreamExt}; -use iroh_util::iroh_home_root; +use iroh_util::iroh_config_root; use ssh_key::LineEnding; use tokio::fs; use tracing::warn; @@ -113,9 +113,9 @@ impl Keychain { } impl Keychain { - /// Creates a new on disk keychain, with the root defaulting to `.iroh`. + /// Creates a new on disk keychain, with the root defaulting to the iroh config directory pub async fn new() -> Result { - let root = iroh_home_root().ok_or_else(|| anyhow!("missing home path"))?; + let root = iroh_config_root()?; Self::with_root(root).await } diff --git a/iroh-p2p/src/main.rs b/iroh-p2p/src/main.rs index 4cae640501..0e5a9801a3 100644 --- a/iroh-p2p/src/main.rs +++ b/iroh-p2p/src/main.rs @@ -2,7 +2,7 @@ use anyhow::anyhow; use clap::Parser; use iroh_p2p::config::{Config, CONFIG_FILE_NAME, ENV_PREFIX}; use iroh_p2p::{cli::Args, metrics, DiskStorage, Keychain, Node}; -use iroh_util::{iroh_home_path, make_config}; +use iroh_util::{iroh_config_path, make_config}; use tokio::task; use tracing::{debug, error}; @@ -15,7 +15,8 @@ async fn main() -> anyhow::Result<()> { let args = Args::parse(); // TODO: configurable network - let sources = vec![iroh_home_path(CONFIG_FILE_NAME), args.cfg.clone()]; + let cfg_path = iroh_config_path(CONFIG_FILE_NAME)?; + let sources = vec![Some(cfg_path), args.cfg.clone()]; let network_config = make_config( // default Config::default_grpc(), diff --git a/iroh-store/src/config.rs b/iroh-store/src/config.rs index b0bbcecddd..021fc2817a 100644 --- a/iroh-store/src/config.rs +++ b/iroh-store/src/config.rs @@ -6,7 +6,7 @@ use iroh_rpc_types::{ store::{StoreClientAddr, StoreServerAddr}, Addr, }; -use iroh_util::insert_into_config_map; +use iroh_util::{insert_into_config_map, iroh_data_path}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -17,6 +17,15 @@ pub const CONFIG_FILE_NAME: &str = "store.config.toml"; /// For example, `IROH_STORE_PATH=/path/to/config` would set the value of the `Config.path` field pub const ENV_PREFIX: &str = "IROH_STORE"; +/// the path to data directory. If arg_path is `None`, the default iroh_data_path()/store is used +/// iroh_data_path() returns an operating system-specific directory +pub fn config_data_path(arg_path: Option) -> Result { + match arg_path { + Some(p) => Ok(p), + None => iroh_data_path("store"), + } +} + /// The configuration for the store. #[derive(PartialEq, Debug, Deserialize, Serialize, Clone)] pub struct Config { @@ -129,4 +138,17 @@ mod tests { assert_eq!(expect, got); } + + #[test] + fn test_config_data_path() { + let path = PathBuf::new().join("arg_path"); + let path_given = config_data_path(Some(path.clone())).expect("config data path error"); + assert_eq!(path_given.display().to_string(), path.display().to_string()); + + let no_path_given = config_data_path(None) + .expect("config data path error") + .display() + .to_string(); + assert!(no_path_given.ends_with("store")); + } } diff --git a/iroh-store/src/main.rs b/iroh-store/src/main.rs index a9ff5e4649..b3d3d51dfa 100644 --- a/iroh-store/src/main.rs +++ b/iroh-store/src/main.rs @@ -2,11 +2,10 @@ use anyhow::anyhow; use clap::Parser; use iroh_store::{ cli::Args, - config::{CONFIG_FILE_NAME, ENV_PREFIX}, + config::{config_data_path, CONFIG_FILE_NAME, ENV_PREFIX}, metrics, rpc, Config, Store, }; -use iroh_util::{block_until_sigint, iroh_home_path, make_config}; -use std::path::PathBuf; +use iroh_util::{block_until_sigint, iroh_config_path, make_config}; use tracing::{debug, error, info}; #[tokio::main(flavor = "multi_thread")] @@ -16,10 +15,12 @@ async fn main() -> anyhow::Result<()> { let version = env!("CARGO_PKG_VERSION"); println!("Starting iroh-store, version {version}"); - let sources = vec![iroh_home_path(CONFIG_FILE_NAME), args.cfg.clone()]; + let config_path = iroh_config_path(CONFIG_FILE_NAME)?; + let sources = vec![Some(config_path), args.cfg.clone()]; + let config_data_path = config_data_path(args.path.clone())?; let config = make_config( // default - Config::new_grpc(args.path.clone().unwrap_or_else(|| PathBuf::from(""))), + Config::new_grpc(config_data_path), // potential config files sources, // env var prefix for this config diff --git a/iroh-util/Cargo.toml b/iroh-util/Cargo.toml index 1d37c09e86..ed4ebb7acd 100644 --- a/iroh-util/Cargo.toml +++ b/iroh-util/Cargo.toml @@ -14,8 +14,8 @@ futures = "0.3.21" anyhow = "1.0.57" toml = "0.5.9" serde = { version = "1.0", features = ["derive"] } -dirs = "4.0.0" config = "0.13.1" tracing = "0.1.34" temp-env = "0.2.0" -rlimit = "0.8.3" \ No newline at end of file +rlimit = "0.8.3" +dirs-next = "2.0.0" \ No newline at end of file diff --git a/iroh-util/src/lib.rs b/iroh-util/src/lib.rs index 3bb32b7845..0d3307336e 100644 --- a/iroh-util/src/lib.rs +++ b/iroh-util/src/lib.rs @@ -1,23 +1,23 @@ use std::{ cell::RefCell, collections::HashMap, - path::{Path, PathBuf}, + path::PathBuf, sync::{ atomic::{AtomicUsize, Ordering}, Arc, }, }; -use anyhow::Result; +use anyhow::{anyhow, Result}; use cid::{ multihash::{Code, MultihashDigest}, Cid, }; use config::{Config, ConfigError, Environment, File, Map, Source, Value, ValueKind}; -use dirs::home_dir; use tracing::debug; -const IROH_DIR: &str = ".iroh"; +/// name of directory that wraps all iroh files in a given application directory +const IROH_DIR: &str = "iroh"; const DEFAULT_NOFILE_LIMIT: u64 = 65536; const MIN_NOFILE_LIMIT: u64 = 2048; @@ -44,16 +44,47 @@ pub async fn block_until_sigint() { ctrlc_oneshot.await.unwrap(); } -/// Path to the iroh home directory. -pub fn iroh_home_root() -> Option { - let home = home_dir()?; - Some(Path::new(&home).join(IROH_DIR)) +/// Returns the path to the user's iroh config directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// | Platform | Value | Example | +/// | -------- | ------------------------------------- | -------------------------------- | +/// | Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config/iroh | /home/alice/.config/iroh | +/// | macOS | `$HOME`/Library/Application Support/iroh | /Users/Alice/Library/Application Support/iroh | +/// | Windows | `{FOLDERID_RoamingAppData}`/iroh | C:\Users\Alice\AppData\Roaming\iroh | +pub fn iroh_config_root() -> Result { + let cfg = dirs_next::config_dir() + .ok_or_else(|| anyhow!("operating environment provides no directory for configuration"))?; + Ok(cfg.join(&IROH_DIR)) +} + +// Path that leads to a file in the iroh config directory. +pub fn iroh_config_path(file_name: &str) -> Result { + let path = iroh_config_root()?.join(file_name); + Ok(path) +} + +/// Returns the path to the user's iroh data directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// | Platform | Value | Example | +/// | -------- | --------------------------------------------- | ---------------------------------------- | +/// | Linux | `$XDG_DATA_HOME`/iroh or `$HOME`/.local/share/iroh | /home/alice/.local/share/iroh | +/// | macOS | `$HOME`/Library/Application Support/iroh | /Users/Alice/Library/Application Support/iroh | +/// | Windows | `{FOLDERID_RoamingAppData}/iroh` | C:\Users\Alice\AppData\Roaming\iroh | +pub fn iroh_data_root() -> Result { + let path = dirs_next::data_dir().ok_or_else(|| { + anyhow!("operating environment provides no directory for application data") + })?; + Ok(path.join(&IROH_DIR)) } -/// Path that leads to a file in the iroh home directory. -pub fn iroh_home_path(file_name: &str) -> Option { - let path = iroh_home_root()?.join(file_name); - Some(path) +/// Path that leads to a file in the iroh data directory. +pub fn iroh_data_path(file_name: &str) -> Result { + let path = iroh_data_root()?.join(file_name); + Ok(path) } /// insert a value into a `config::Map` @@ -174,9 +205,9 @@ pub fn increase_fd_limit() -> std::io::Result { mod tests { use super::*; #[test] - fn test_iroh_home_path() { - let got = iroh_home_path("foo.bar").unwrap(); + fn test_iroh_config_path() { + let got = iroh_config_path("foo.bar").unwrap(); let got = got.to_str().unwrap().to_string(); - assert!(got.ends_with("/.iroh/foo.bar")); + assert!(got.ends_with("/iroh/foo.bar")); } }