From 9e414285500179328d00ac711e7b1dd677896f50 Mon Sep 17 00:00:00 2001 From: b5 Date: Thu, 1 Sep 2022 08:12:39 -0400 Subject: [PATCH 1/2] refactor: use OS-specific config & data dirs, default iroh-store dir switch to using https://dirs.dev standard application directories instead of $HOME/.iroh iroh-store now uses a default directory for datastore data instead of explicitly requiring a store path --- iroh-ctl/src/main.rs | 4 +-- iroh-gateway/src/main.rs | 4 +-- iroh-one/src/main.rs | 4 +-- iroh-p2p/src/keys.rs | 6 ++--- iroh-p2p/src/main.rs | 4 +-- iroh-store/src/config.rs | 20 ++++++++++++++- iroh-store/src/main.rs | 9 +++---- iroh-util/Cargo.toml | 3 ++- iroh-util/src/lib.rs | 54 ++++++++++++++++++++++++++++++---------- 9 files changed, 77 insertions(+), 31 deletions(-) diff --git a/iroh-ctl/src/main.rs b/iroh-ctl/src/main.rs index 8ff95ab24b..6162ba8a1c 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,7 @@ enum Commands { async fn main() -> anyhow::Result<()> { let cli = Cli::parse(); - let sources = vec![iroh_home_path(CONFIG_FILE_NAME), cli.cfg.clone()]; + let sources = vec![iroh_config_path(CONFIG_FILE_NAME), 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..760e295c3e 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,7 @@ 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 sources = vec![iroh_config_path(CONFIG_FILE_NAME), 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..10764ec0cb 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,7 @@ 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 sources = vec![iroh_config_path(CONFIG_FILE_NAME), 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..fbcb1c105e 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().ok_or_else(|| anyhow!("missing iroh configuration path"))?; Self::with_root(root).await } diff --git a/iroh-p2p/src/main.rs b/iroh-p2p/src/main.rs index 4cae640501..20cb1c7d49 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,7 @@ 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 sources = vec![iroh_config_path(CONFIG_FILE_NAME), 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..85235df4d1 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,14 @@ 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 +/// falls back to an empty path in the unlikely scenario where iroh_data_path() is `None` +/// likely b/c iroh is not running on a Linux/Windows/MacOS system +pub fn config_data_path(arg_path: Option) -> PathBuf { + arg_path.unwrap_or_else(|| iroh_data_path("store").unwrap_or_else(|| PathBuf::from(""))) +} + /// The configuration for the store. #[derive(PartialEq, Debug, Deserialize, Serialize, Clone)] pub struct Config { @@ -129,4 +137,14 @@ 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())); + assert_eq!(path_given.display().to_string(), path.display().to_string()); + + let no_path_given = config_data_path(None).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..95910efb19 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,10 @@ 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 sources = vec![iroh_config_path(CONFIG_FILE_NAME), args.cfg.clone()]; let config = make_config( // default - Config::new_grpc(args.path.clone().unwrap_or_else(|| PathBuf::from(""))), + Config::new_grpc(config_data_path(args.path.clone())), // potential config files sources, // env var prefix for this config diff --git a/iroh-util/Cargo.toml b/iroh-util/Cargo.toml index 1d37c09e86..852dc076ee 100644 --- a/iroh-util/Cargo.toml +++ b/iroh-util/Cargo.toml @@ -18,4 +18,5 @@ 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..fa5042bc98 100644 --- a/iroh-util/src/lib.rs +++ b/iroh-util/src/lib.rs @@ -1,7 +1,7 @@ use std::{ cell::RefCell, collections::HashMap, - path::{Path, PathBuf}, + path::PathBuf, sync::{ atomic::{AtomicUsize, Ordering}, Arc, @@ -14,10 +14,10 @@ use cid::{ 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,15 +44,43 @@ 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() -> Option { + let cfg = dirs_next::config_dir()?; + Some(cfg.join(&IROH_DIR)) +} + +// Path that leads to a file in the iroh config directory. +pub fn iroh_config_path(file_name: &str) -> Option { + let path = iroh_config_root()?.join(file_name); + Some(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() -> Option { + let path = dirs_next::data_dir()?; + Some(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); +// Path that leads to a file in the iroh data directory. +pub fn iroh_data_path(file_name: &str) -> Option { + let path = iroh_data_root()?.join(file_name); Some(path) } @@ -174,9 +202,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")); } } From 4f6c296ee2f2b8ffe1b87b51399acb4868d55560 Mon Sep 17 00:00:00 2001 From: b5 Date: Thu, 1 Sep 2022 22:51:49 -0400 Subject: [PATCH 2/2] refactor: util config/data paths return Results --- iroh-ctl/src/main.rs | 3 ++- iroh-gateway/src/main.rs | 3 ++- iroh-one/src/main.rs | 3 ++- iroh-p2p/src/keys.rs | 2 +- iroh-p2p/src/main.rs | 3 ++- iroh-store/src/config.rs | 16 ++++++++++------ iroh-store/src/main.rs | 6 ++++-- iroh-util/Cargo.toml | 1 - iroh-util/src/lib.rs | 27 +++++++++++++++------------ 9 files changed, 38 insertions(+), 26 deletions(-) diff --git a/iroh-ctl/src/main.rs b/iroh-ctl/src/main.rs index 6162ba8a1c..7bcf141b25 100644 --- a/iroh-ctl/src/main.rs +++ b/iroh-ctl/src/main.rs @@ -53,7 +53,8 @@ enum Commands { async fn main() -> anyhow::Result<()> { let cli = Cli::parse(); - let sources = vec![iroh_config_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 760e295c3e..91db5426ea 100644 --- a/iroh-gateway/src/main.rs +++ b/iroh-gateway/src/main.rs @@ -17,7 +17,8 @@ use tracing::{debug, error}; async fn main() -> Result<()> { let args = Args::parse(); - let sources = vec![iroh_config_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 10764ec0cb..03b83380b5 100644 --- a/iroh-one/src/main.rs +++ b/iroh-one/src/main.rs @@ -21,7 +21,8 @@ use tracing::{debug, error}; async fn main() -> Result<()> { let args = Args::parse(); - let sources = vec![iroh_config_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 fbcb1c105e..c19ccd70c2 100644 --- a/iroh-p2p/src/keys.rs +++ b/iroh-p2p/src/keys.rs @@ -115,7 +115,7 @@ impl Keychain { impl Keychain { /// Creates a new on disk keychain, with the root defaulting to the iroh config directory pub async fn new() -> Result { - let root = iroh_config_root().ok_or_else(|| anyhow!("missing iroh configuration 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 20cb1c7d49..0e5a9801a3 100644 --- a/iroh-p2p/src/main.rs +++ b/iroh-p2p/src/main.rs @@ -15,7 +15,8 @@ async fn main() -> anyhow::Result<()> { let args = Args::parse(); // TODO: configurable network - let sources = vec![iroh_config_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 85235df4d1..021fc2817a 100644 --- a/iroh-store/src/config.rs +++ b/iroh-store/src/config.rs @@ -19,10 +19,11 @@ 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 -/// falls back to an empty path in the unlikely scenario where iroh_data_path() is `None` -/// likely b/c iroh is not running on a Linux/Windows/MacOS system -pub fn config_data_path(arg_path: Option) -> PathBuf { - arg_path.unwrap_or_else(|| iroh_data_path("store").unwrap_or_else(|| PathBuf::from(""))) +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. @@ -141,10 +142,13 @@ mod tests { #[test] fn test_config_data_path() { let path = PathBuf::new().join("arg_path"); - let path_given = config_data_path(Some(path.clone())); + 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).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 95910efb19..b3d3d51dfa 100644 --- a/iroh-store/src/main.rs +++ b/iroh-store/src/main.rs @@ -15,10 +15,12 @@ async fn main() -> anyhow::Result<()> { let version = env!("CARGO_PKG_VERSION"); println!("Starting iroh-store, version {version}"); - let sources = vec![iroh_config_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(config_data_path(args.path.clone())), + 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 852dc076ee..ed4ebb7acd 100644 --- a/iroh-util/Cargo.toml +++ b/iroh-util/Cargo.toml @@ -14,7 +14,6 @@ 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" diff --git a/iroh-util/src/lib.rs b/iroh-util/src/lib.rs index fa5042bc98..0d3307336e 100644 --- a/iroh-util/src/lib.rs +++ b/iroh-util/src/lib.rs @@ -8,7 +8,7 @@ use std::{ }, }; -use anyhow::Result; +use anyhow::{anyhow, Result}; use cid::{ multihash::{Code, MultihashDigest}, Cid, @@ -53,15 +53,16 @@ pub async fn block_until_sigint() { /// | 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() -> Option { - let cfg = dirs_next::config_dir()?; - Some(cfg.join(&IROH_DIR)) +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) -> Option { +pub fn iroh_config_path(file_name: &str) -> Result { let path = iroh_config_root()?.join(file_name); - Some(path) + Ok(path) } /// Returns the path to the user's iroh data directory. @@ -73,15 +74,17 @@ pub fn iroh_config_path(file_name: &str) -> Option { /// | 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() -> Option { - let path = dirs_next::data_dir()?; - Some(path.join(&IROH_DIR)) +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 data directory. -pub fn iroh_data_path(file_name: &str) -> Option { +/// 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); - Some(path) + Ok(path) } /// insert a value into a `config::Map`