diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index 28aaf9a89..a6e0728f6 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -2522,6 +2522,7 @@ dependencies = [ "tempfile", "tokio", "tokio-rustls", + "toml 0.5.11", "tracing", "tracing-subscriber", "url", diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 28aaf9a89..a6e0728f6 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -2522,6 +2522,7 @@ dependencies = [ "tempfile", "tokio", "tokio-rustls", + "toml 0.5.11", "tracing", "tracing-subscriber", "url", diff --git a/payjoin-cli/Cargo.toml b/payjoin-cli/Cargo.toml index d4d2f4db1..cff23a42e 100644 --- a/payjoin-cli/Cargo.toml +++ b/payjoin-cli/Cargo.toml @@ -51,8 +51,11 @@ url = { version = "2.5.4", features = ["serde"] } dirs = "6.0.0" tracing = "0.1.41" tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } +toml = "0.5.11" [dev-dependencies] nix = { version = "0.30.1", features = ["aio", "process", "signal"] } payjoin-test-utils = { version = "0.0.1" } tempfile = "3.20.0" + + diff --git a/payjoin-cli/README.md b/payjoin-cli/README.md index 566a12043..fc0717fb1 100644 --- a/payjoin-cli/README.md +++ b/payjoin-cli/README.md @@ -132,6 +132,8 @@ Congratulations! You've completed a version 2 payjoin, which can be used for che Config options can be passed from the command line, or manually edited in a `config.toml` file within the directory you run `payjoin-cli` from. +You can persist command-line flags into a configuration file. If a config file exists in the current working directory it will be used; otherwise, one in the default directory is used. This behavior is enabled via the `--set-config` flag. + See the [example.config.toml](https://github.com/payjoin/rust-payjoin/blob/fde867b93ede767c9a50913432a73782a94ef40b/payjoin-cli/example.config.toml) for inspiration. diff --git a/payjoin-cli/src/app/config.rs b/payjoin-cli/src/app/config.rs index de0ca7c38..15b4571c2 100644 --- a/payjoin-cli/src/app/config.rs +++ b/payjoin-cli/src/app/config.rs @@ -204,6 +204,84 @@ impl Config { Ok(config) } + #[cfg(feature = "v2")] + pub fn save_config(cli: &Cli) -> Result<(), anyhow::Error> { + let version = Self::determine_version(cli) + .map_err(|e| anyhow::anyhow!("Failed to determine version: {e}"))?; + + if version != Version::Two { + return Err(anyhow::anyhow!("--set-config is only available for bip77 (v2) mode")); + } + + if cli.ohttp_relays.is_none() || cli.pj_directory.is_none() { + return Err(anyhow::anyhow!( + "--set-config requires ALL of: \ + --pj-directory, --ohttp-relays" + )); + } + + let cookie_provided = cli.cookie_file.is_some(); + let rpc_provided = + cli.rpcuser.is_some() && cli.rpcpassword.is_some() && cli.rpchost.is_some(); + + // Ensure all required parameters are present + + if !(cookie_provided || rpc_provided) { + return Err(anyhow::anyhow!( + "--set-config requires ALL of: \ + --rpcuser, --rpcpassword, --rpchost, or \ + --cookie-file" + )); + } + + // Build the TOML map , this feels a bit hacky but it works + let mut toml_map = toml::map::Map::new(); + let mut bitcoind_map = toml::map::Map::new(); + + bitcoind_map.insert("rpcuser".into(), toml::Value::String(cli.rpcuser.clone().unwrap())); + bitcoind_map + .insert("rpcpassword".into(), toml::Value::String(cli.rpcpassword.clone().unwrap())); + bitcoind_map.insert( + "rpchost".into(), + toml::Value::String(cli.rpchost.clone().unwrap().to_string()), + ); + toml_map.insert("bitcoind".into(), toml::Value::Table(bitcoind_map)); + + let mut v2_map = toml::map::Map::new(); + v2_map.insert( + "pj_directory".into(), + toml::Value::String(cli.pj_directory.as_ref().unwrap().to_string()), + ); + let relay_list: Vec = cli + .ohttp_relays + .as_ref() + .unwrap() + .iter() + .map(|url| toml::Value::String(url.to_string())) + .collect(); + v2_map.insert("ohttp_relays".into(), toml::Value::Array(relay_list)); + toml_map.insert("v2".into(), toml::Value::Table(v2_map)); + + let toml_content = toml::Value::Table(toml_map); + let toml_str = toml::to_string_pretty(&toml_content)?; + + let config_path = std::env::current_dir()?.join("config.toml"); + let final_path = if !config_path.exists() { + if let Some(config_dir) = dirs::config_dir() { + let global_dir = config_dir.join(CONFIG_DIR); + std::fs::create_dir_all(&global_dir)?; + global_dir.join("config.toml") + } else { + config_path + } + } else { + config_path + }; + + std::fs::write(final_path, toml_str)?; + Ok(()) + } + #[cfg(feature = "v1")] pub fn v1(&self) -> Result<&V1Config, anyhow::Error> { match &self.version { diff --git a/payjoin-cli/src/cli/mod.rs b/payjoin-cli/src/cli/mod.rs index a3db87d46..50dff42ff 100644 --- a/payjoin-cli/src/cli/mod.rs +++ b/payjoin-cli/src/cli/mod.rs @@ -76,6 +76,10 @@ pub struct Cli { #[arg(long = "pj-directory", help = "The directory to store payjoin requests", value_parser = value_parser!(Url))] pub pj_directory: Option, + #[cfg(feature = "v2")] + #[arg(long = "set-config", help = "Save current configuration parameters to config.toml", value_parser = value_parser!(bool))] + pub set_config: bool, + #[cfg(feature = "_manual-tls")] #[arg(long = "root-certificate", help = "Specify a TLS certificate to be added as a root", value_parser = value_parser!(PathBuf))] pub root_certificate: Option, diff --git a/payjoin-cli/src/main.rs b/payjoin-cli/src/main.rs index aac4ae9ab..6f72e03dc 100644 --- a/payjoin-cli/src/main.rs +++ b/payjoin-cli/src/main.rs @@ -20,6 +20,11 @@ async fn main() -> Result<()> { tracing_subscriber::fmt().with_target(true).with_level(true).with_env_filter(env_filter).init(); let cli = Cli::parse(); + + #[cfg(feature = "v2")] + if cli.set_config { + Config::save_config(&cli)?; + } let config = Config::new(&cli)?; #[allow(clippy::if_same_then_else)]