From 83f10c4b4772796a62bc13d83194a0479537d7cd Mon Sep 17 00:00:00 2001 From: arkanoider Date: Tue, 17 Mar 2026 19:15:29 +0100 Subject: [PATCH 1/3] feat: fixed error on first launch of mostrix on windows, now user is guided --- .gitignore | 5 ++++- src/settings.rs | 40 ++++++++++++++++++++++++++++------------ 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 7f58502..65eebbe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ +# build /target *log +# ide .cursorrules .cursor/ .vscode/ -.cursor/commands + + diff --git a/src/settings.rs b/src/settings.rs index d2fe58e..cfc2b9c 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,9 +1,10 @@ use crate::SETTINGS; use serde::{Deserialize, Serialize}; -use std::{ - env, fs, - path::{Path, PathBuf}, -}; +use std::{env, fs, path::PathBuf}; + +/// Embedded default `settings.toml` used to bootstrap configuration on first run. +/// This is generated at compile time from the repository root `settings.toml`. +const DEFAULT_SETTINGS_TOML: &str = include_str!("../settings.toml"); #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Settings { @@ -108,9 +109,6 @@ fn init_or_load_settings_from_disk() -> Result { let hidden_dir = home_dir.join(format!(".{package_name}")); let hidden_file = hidden_dir.join("settings.toml"); - // Path to the settings.toml included in the repo (next to Cargo.toml) - let default_file: PathBuf = Path::new(env!("CARGO_MANIFEST_DIR")).join("settings.toml"); - // Create ~/.mostrix if it doesn't exist if !hidden_dir.exists() { fs::create_dir(&hidden_dir).map_err(|e| { @@ -118,10 +116,11 @@ fn init_or_load_settings_from_disk() -> Result { })?; } - // Copy settings.toml if it isn't already in ~/.mostrix + // Write default settings.toml if it isn't already in ~/.mostrix if !hidden_file.exists() { - fs::copy(&default_file, &hidden_file) - .map_err(|e| anyhow::anyhow!("Could not copy default settings.toml: {}", e))?; + fs::write(&hidden_file, DEFAULT_SETTINGS_TOML) + .map_err(|e| anyhow::anyhow!("Could not write default settings.toml: {}", e))?; + println!("Default settings.toml written to {}", hidden_file.display()); } validate_currencies_config(&hidden_file)?; @@ -132,8 +131,25 @@ fn init_or_load_settings_from_disk() -> Result { .build() .map_err(|e| anyhow::anyhow!("settings.toml malformed: {}", e))?; - cfg.try_deserialize::() - .map_err(|e| anyhow::anyhow!("Error deserializing settings.toml: {}", e)) + let settings: Settings = cfg + .try_deserialize() + .map_err(|e| anyhow::anyhow!("Error deserializing settings.toml: {}", e))?; + + // Detect first-launch / placeholder configuration and exit with a clear message + // instead of trying to run with invalid keys. + if settings.mostro_pubkey == "mostro_pubkey_hex_format" + || settings.nsec_privkey == "nsec1_privkey_format" + { + let path_display = hidden_file.display(); + anyhow::bail!( + "Default settings.toml has been created at {}.\n\ +Please edit this file and replace placeholder values (mostro_pubkey, nsec_privkey, etc.) \ +with your real keys before running Mostrix again.", + path_display + ); + } + + Ok(settings) } /// Public helper: reload current settings from disk (reflects all previous saves) From ce671c949a509b9f1e4896840aa6aa197d316e98 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Tue, 17 Mar 2026 19:41:29 +0100 Subject: [PATCH 2/3] feat: added linux file permission set for settings.toml and improved error messages --- src/settings.rs | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/settings.rs b/src/settings.rs index cfc2b9c..e58fae2 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -117,9 +117,29 @@ fn init_or_load_settings_from_disk() -> Result { } // Write default settings.toml if it isn't already in ~/.mostrix - if !hidden_file.exists() { - fs::write(&hidden_file, DEFAULT_SETTINGS_TOML) - .map_err(|e| anyhow::anyhow!("Could not write default settings.toml: {}", e))?; + let created_default = !hidden_file.exists(); + if created_default { + #[cfg(unix)] + { + use std::io::Write; + use std::os::unix::fs::OpenOptionsExt; + + let mut file = fs::OpenOptions::new() + .write(true) + .create_new(true) + .mode(0o600) + .open(&hidden_file) + .map_err(|e| anyhow::anyhow!("Could not write default settings.toml: {}", e))?; + file.write_all(DEFAULT_SETTINGS_TOML.as_bytes()) + .map_err(|e| anyhow::anyhow!("Could not write default settings.toml: {}", e))?; + } + + #[cfg(not(unix))] + { + fs::write(&hidden_file, DEFAULT_SETTINGS_TOML) + .map_err(|e| anyhow::anyhow!("Could not write default settings.toml: {}", e))?; + } + println!("Default settings.toml written to {}", hidden_file.display()); } @@ -141,12 +161,21 @@ fn init_or_load_settings_from_disk() -> Result { || settings.nsec_privkey == "nsec1_privkey_format" { let path_display = hidden_file.display(); - anyhow::bail!( - "Default settings.toml has been created at {}.\n\ + if created_default { + anyhow::bail!( + "Default settings.toml has been created at {}.\n\ Please edit this file and replace placeholder values (mostro_pubkey, nsec_privkey, etc.) \ with your real keys before running Mostrix again.", - path_display - ); + path_display + ); + } else { + anyhow::bail!( + "Default settings.toml already exists at {} but still contains placeholder values.\n\ +Please edit this file and replace placeholder values (mostro_pubkey, nsec_privkey, etc.) \ +with your real keys before running Mostrix again.", + path_display + ); + } } Ok(settings) From 930b4c8241cb4fd0eb4e85166a6948de8a2fbf2e Mon Sep 17 00:00:00 2001 From: arkanoider Date: Wed, 18 Mar 2026 22:04:04 +0100 Subject: [PATCH 3/3] feat: Generate first-run settings.toml in ~/.mostrix --- src/settings.rs | 141 +++++++++++++++++++++++++++++------------------- 1 file changed, 86 insertions(+), 55 deletions(-) diff --git a/src/settings.rs b/src/settings.rs index e58fae2..b10481f 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,4 +1,5 @@ use crate::SETTINGS; +use nostr_sdk::prelude::*; use serde::{Deserialize, Serialize}; use std::{env, fs, path::PathBuf}; @@ -102,73 +103,37 @@ fn validate_currencies_config(settings_path: &PathBuf) -> Result<(), anyhow::Err /// Internal helper: ensure settings file exists and load it from disk fn init_or_load_settings_from_disk() -> Result { - // HOME and package name at compile time + // Legacy location: ~/.mostrix/settings.toml (kept for backwards compatibility). let home_dir = dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?; let package_name = env!("CARGO_PKG_NAME"); let hidden_dir = home_dir.join(format!(".{package_name}")); let hidden_file = hidden_dir.join("settings.toml"); - // Create ~/.mostrix if it doesn't exist - if !hidden_dir.exists() { - fs::create_dir(&hidden_dir).map_err(|e| { - anyhow::anyhow!("The configuration directory could not be created: {}", e) - })?; - } + // Helper: load a settings file from the given path. + fn load_settings_from_path(path: &PathBuf) -> Result { + validate_currencies_config(path)?; - // Write default settings.toml if it isn't already in ~/.mostrix - let created_default = !hidden_file.exists(); - if created_default { - #[cfg(unix)] - { - use std::io::Write; - use std::os::unix::fs::OpenOptionsExt; - - let mut file = fs::OpenOptions::new() - .write(true) - .create_new(true) - .mode(0o600) - .open(&hidden_file) - .map_err(|e| anyhow::anyhow!("Could not write default settings.toml: {}", e))?; - file.write_all(DEFAULT_SETTINGS_TOML.as_bytes()) - .map_err(|e| anyhow::anyhow!("Could not write default settings.toml: {}", e))?; - } + let cfg = config::Config::builder() + .add_source(config::File::from(path.as_path())) + .build() + .map_err(|e| anyhow::anyhow!("settings.toml malformed: {}", e))?; - #[cfg(not(unix))] - { - fs::write(&hidden_file, DEFAULT_SETTINGS_TOML) - .map_err(|e| anyhow::anyhow!("Could not write default settings.toml: {}", e))?; - } + let settings: Settings = cfg + .try_deserialize() + .map_err(|e| anyhow::anyhow!("Error deserializing settings.toml: {}", e))?; - println!("Default settings.toml written to {}", hidden_file.display()); + Ok(settings) } - validate_currencies_config(&hidden_file)?; - - // Use the `config` crate to deserialize to the Settings struct - let cfg = config::Config::builder() - .add_source(config::File::from(hidden_file.as_path())) - .build() - .map_err(|e| anyhow::anyhow!("settings.toml malformed: {}", e))?; + // Case B: legacy ~/.mostrix/settings.toml exists -> load with the old placeholder guard. + if hidden_file.exists() { + let settings = load_settings_from_path(&hidden_file)?; - let settings: Settings = cfg - .try_deserialize() - .map_err(|e| anyhow::anyhow!("Error deserializing settings.toml: {}", e))?; - - // Detect first-launch / placeholder configuration and exit with a clear message - // instead of trying to run with invalid keys. - if settings.mostro_pubkey == "mostro_pubkey_hex_format" - || settings.nsec_privkey == "nsec1_privkey_format" - { - let path_display = hidden_file.display(); - if created_default { - anyhow::bail!( - "Default settings.toml has been created at {}.\n\ -Please edit this file and replace placeholder values (mostro_pubkey, nsec_privkey, etc.) \ -with your real keys before running Mostrix again.", - path_display - ); - } else { + if settings.mostro_pubkey == "mostro_pubkey_hex_format" + || settings.nsec_privkey == "nsec1_privkey_format" + { + let path_display = hidden_file.display(); anyhow::bail!( "Default settings.toml already exists at {} but still contains placeholder values.\n\ Please edit this file and replace placeholder values (mostro_pubkey, nsec_privkey, etc.) \ @@ -176,8 +141,74 @@ with your real keys before running Mostrix again.", path_display ); } + + return Ok(settings); + } + + // Case C: Truly first run: no config anywhere. + // Auto-generate in HOME/.mostrix with sensible defaults as per + // https://github.com/MostroP2P/mostrix/issues/40. + if !hidden_dir.exists() { + fs::create_dir_all(&hidden_dir).map_err(|e| { + anyhow::anyhow!("The configuration directory could not be created: {}", e) + })?; + } + + // Start from the embedded default template, then override fields. + let mut settings: Settings = toml::from_str(DEFAULT_SETTINGS_TOML) + .map_err(|e| anyhow::anyhow!("Embedded DEFAULT_SETTINGS_TOML is malformed: {}", e))?; + + // Generate a fresh Nostr keypair for the user. + let keys = Keys::generate(); + let sk = keys.secret_key(); + let nsec = sk + .to_bech32() + .map_err(|e| anyhow::anyhow!("Failed to encode generated Nostr secret key: {}", e))?; + let npub = keys + .public_key() + .to_bech32() + .map_err(|e| anyhow::anyhow!("Failed to encode generated Nostr public key: {}", e))?; + + // Apply sensible defaults from the issue. + settings.nsec_privkey = nsec; + settings.relays = vec!["wss://relay.mostro.network".to_string()]; + settings.user_mode = "user".to_string(); + settings.pow = 0; + settings.currencies_filter = Vec::new(); + settings.mostro_pubkey = + "82fa8cb978b43c79b2156585bac2c022276a21d2aead6d9f7c575c005be88390".to_string(); + + // Serialize to TOML. + let toml_string = toml::to_string_pretty(&settings) + .map_err(|e| anyhow::anyhow!("Failed to serialize generated settings: {}", e))?; + + #[cfg(unix)] + { + use std::io::Write; + use std::os::unix::fs::OpenOptionsExt; + + let mut file = fs::OpenOptions::new() + .write(true) + .create_new(true) + .mode(0o600) + .open(&hidden_file) + .map_err(|e| anyhow::anyhow!("Could not write generated settings.toml: {}", e))?; + file.write_all(toml_string.as_bytes()) + .map_err(|e| anyhow::anyhow!("Could not write generated settings.toml: {}", e))?; } + #[cfg(not(unix))] + { + fs::write(&hidden_file, toml_string) + .map_err(|e| anyhow::anyhow!("Could not write generated settings.toml: {}", e))?; + } + + println!( + "First run: generated settings.toml at {}.\nYour Nostr public key (npub) is: {}", + hidden_file.display(), + npub + ); + Ok(settings) }