diff --git a/mutant-daemon/src/app.rs b/mutant-daemon/src/app.rs index 52b8ff25..6ba363aa 100644 --- a/mutant-daemon/src/app.rs +++ b/mutant-daemon/src/app.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, net::SocketAddr, sync::Arc}; use xdg::BaseDirectories; use mutant_lib::{config::NetworkChoice, MutAnt}; -use tokio::sync::RwLock; +use tokio::sync::{RwLock, OnceCell}; use warp::Filter; use crate::error::Error; @@ -13,6 +13,46 @@ use crate::wallet; use std::path::PathBuf; use tokio::signal; +// Thread-safe singleton to track public-only mode +pub static PUBLIC_ONLY_MODE: OnceCell = OnceCell::const_new(); + +/// Helper function to initialize MutAnt based on network choice and private key +async fn init_mutant(network_choice: NetworkChoice, private_key: Option) -> Result<(MutAnt, bool), Error> { + let is_public_only = private_key.is_none(); + + let mutant = match (network_choice, private_key) { + // Full access with private key + (NetworkChoice::Devnet, Some(_)) => { + log::info!("Running in local mode"); + MutAnt::init_local().await + } + (NetworkChoice::Alphanet, Some(key)) => { + log::info!("Running in alphanet mode"); + MutAnt::init_alphanet(&key).await + } + (NetworkChoice::Mainnet, Some(key)) => { + log::info!("Running in mainnet mode"); + MutAnt::init(&key).await + } + + // Public-only mode (no private key) + (NetworkChoice::Devnet, None) => { + log::info!("Running in local public-only mode"); + MutAnt::init_public_local().await + } + (NetworkChoice::Alphanet, None) => { + log::info!("Running in alphanet public-only mode"); + MutAnt::init_public_alphanet().await + } + (NetworkChoice::Mainnet, None) => { + log::info!("Running in mainnet public-only mode"); + MutAnt::init_public().await + } + }.map_err(Error::MutAnt)?; + + Ok((mutant, is_public_only)) +} + #[derive(serde::Deserialize, serde::Serialize)] struct NetworkConfig { public_key: String, @@ -129,50 +169,49 @@ pub async fn run(options: AppOptions) -> Result<(), Error> { let mut config = Config::load()?; - let private_key_for_mutant: String; - - match config.get_private_key(network_choice)? { + // Try to get a private key from config + let private_key = match config.get_private_key(network_choice)? { Some((key_from_file, pk_hex)) => { log::info!( "Loaded private key from file for network {:?}: {}", network_choice, pk_hex ); - private_key_for_mutant = key_from_file; + Some(key_from_file) } None => { - log::info!("No private key found in config or file, attempting to scan wallets for network {:?}", network_choice); - let (selected_private_key, pk_hex) = wallet::scan_and_select_wallet().await?; - - config.set_public_key(network_choice, pk_hex.clone()); - config.save()?; - log::info!( - "Saved newly selected public key {} to config for network {:?}", - pk_hex, - network_choice - ); - private_key_for_mutant = selected_private_key; - } - } - - let mutant = Arc::new( - match network_choice { - NetworkChoice::Devnet => { - log::info!("Running in local mode"); - MutAnt::init_local().await - } - NetworkChoice::Alphanet => { - log::info!("Running in alphanet mode"); - MutAnt::init_alphanet(&private_key_for_mutant).await - } - NetworkChoice::Mainnet => { - log::info!("Running in mainnet mode"); - MutAnt::init(&private_key_for_mutant).await + // Try to scan for wallets + match wallet::scan_and_select_wallet().await { + Ok((selected_private_key, pk_hex)) => { + // Save the selected wallet for future use + config.set_public_key(network_choice, pk_hex.clone()); + config.save()?; + log::info!( + "Saved newly selected public key {} to config for network {:?}", + pk_hex, + network_choice + ); + Some(selected_private_key) + } + Err(e) => { + // No wallet found, initialize in public-only mode + log::warn!("No wallet found: {}. Initializing in public-only mode.", e); + log::info!("Only public downloads (mutant get -p) will be available."); + None + } } } - .map_err(Error::MutAnt)?, - ); - log::info!("MutAnt initialized successfully."); + }; + + // Initialize MutAnt with the appropriate mode + let (mutant, is_public_only) = init_mutant(network_choice, private_key).await?; + let mutant = Arc::new(mutant); + + // Set the public-only mode flag + PUBLIC_ONLY_MODE.set(is_public_only).expect("PUBLIC_ONLY_MODE should only be set once"); + + log::info!("MutAnt initialized successfully{}", + if is_public_only { " in public-only mode" } else { "" }); // Initialize Task Management let tasks: TaskMap = Arc::new(RwLock::new(HashMap::new())); diff --git a/mutant-daemon/src/handlers/data_operations.rs b/mutant-daemon/src/handlers/data_operations.rs index c1c9dd68..98948c3f 100644 --- a/mutant-daemon/src/handlers/data_operations.rs +++ b/mutant-daemon/src/handlers/data_operations.rs @@ -3,7 +3,7 @@ use tokio::fs; use uuid::Uuid; use crate::error::Error as DaemonError; -use super::{TaskEntry, TaskMap, ActiveKeysMap, try_register_key, release_key}; +use super::{TaskEntry, TaskMap, ActiveKeysMap, try_register_key, release_key, is_public_only_mode, PUBLIC_ONLY_ERROR_MSG}; use mutant_lib::storage::ScratchpadAddress; use mutant_lib::MutAnt; use mutant_protocol::{ @@ -22,6 +22,16 @@ pub(crate) async fn handle_put( active_keys: ActiveKeysMap, original_request_str: &str, ) -> Result<(), DaemonError> { + // Check if we're in public-only mode + if is_public_only_mode() { + return update_tx + .send(Response::Error(ErrorResponse { + error: PUBLIC_ONLY_ERROR_MSG.to_string(), + original_request: Some(original_request_str.to_string()), + })) + .map_err(|e| DaemonError::Internal(format!("Update channel send error: {}", e))); + } + let task_id = Uuid::new_v4(); let user_key = req.user_key.clone(); let source_path = req.source_path.clone(); // Keep path for logging @@ -428,6 +438,16 @@ pub(crate) async fn handle_rm( active_keys: ActiveKeysMap, original_request_str: &str, ) -> Result<(), DaemonError> { + // Check if we're in public-only mode + if is_public_only_mode() { + return update_tx + .send(Response::Error(ErrorResponse { + error: PUBLIC_ONLY_ERROR_MSG.to_string(), + original_request: Some(original_request_str.to_string()), + })) + .map_err(|e| DaemonError::Internal(format!("Update channel send error: {}", e))); + } + let task_id = Uuid::new_v4(); let user_key = req.user_key.clone(); log::info!("Starting RM task: user_key={}", user_key); diff --git a/mutant-daemon/src/handlers/import_export.rs b/mutant-daemon/src/handlers/import_export.rs index 9fb5ca94..b7dab474 100644 --- a/mutant-daemon/src/handlers/import_export.rs +++ b/mutant-daemon/src/handlers/import_export.rs @@ -2,10 +2,11 @@ use std::sync::Arc; use tokio::fs; use crate::error::Error as DaemonError; +use super::{is_public_only_mode, PUBLIC_ONLY_ERROR_MSG}; use mutant_lib::storage::PadInfo; use mutant_lib::MutAnt; use mutant_protocol::{ - ExportRequest, ExportResponse, ExportResult, ImportRequest, ImportResponse, ImportResult, + ErrorResponse, ExportRequest, ExportResponse, ExportResult, ImportRequest, ImportResponse, ImportResult, Response, }; @@ -18,6 +19,16 @@ pub(crate) async fn handle_import( ) -> Result<(), DaemonError> { log::debug!("Handling Import request"); + // Check if we're in public-only mode + if is_public_only_mode() { + return update_tx + .send(Response::Error(ErrorResponse { + error: PUBLIC_ONLY_ERROR_MSG.to_string(), + original_request: Some(serde_json::to_string(&req).unwrap_or_default()), + })) + .map_err(|e| DaemonError::Internal(format!("Update channel send error: {}", e))); + } + let file_path = req.file_path.clone(); let pads_hex = fs::read(&file_path) .await @@ -50,6 +61,16 @@ pub(crate) async fn handle_export( ) -> Result<(), DaemonError> { log::debug!("Handling Export request"); + // Check if we're in public-only mode + if is_public_only_mode() { + return update_tx + .send(Response::Error(ErrorResponse { + error: PUBLIC_ONLY_ERROR_MSG.to_string(), + original_request: Some(serde_json::to_string(&req).unwrap_or_default()), + })) + .map_err(|e| DaemonError::Internal(format!("Update channel send error: {}", e))); + } + let destination_path = req.destination_path.clone(); let pads_hex = mutant.export_raw_pads_private_key().await?; diff --git a/mutant-daemon/src/handlers/mod.rs b/mutant-daemon/src/handlers/mod.rs index 97bc0f58..6dea74dc 100644 --- a/mutant-daemon/src/handlers/mod.rs +++ b/mutant-daemon/src/handlers/mod.rs @@ -13,3 +13,12 @@ mod key_management; pub use websocket::handle_ws; pub use task_management::{TaskEntry, TaskMap}; pub use key_management::{ActiveKeysMap, try_register_key, release_key}; + +/// Check if the daemon is running in public-only mode +pub fn is_public_only_mode() -> bool { + // If the OnceCell hasn't been initialized yet, we're not in public-only mode + crate::app::PUBLIC_ONLY_MODE.get().copied().unwrap_or(false) +} + +/// Error message for operations that require a wallet when in public-only mode +pub const PUBLIC_ONLY_ERROR_MSG: &str = "This operation requires a wallet, but the daemon is running in public-only mode. To enable full functionality, please set up an Autonomi wallet using 'ant wallet import' or 'ant wallet create' and restart the daemon."; diff --git a/mutant-daemon/src/handlers/system_operations.rs b/mutant-daemon/src/handlers/system_operations.rs index 59523db4..275dd864 100644 --- a/mutant-daemon/src/handlers/system_operations.rs +++ b/mutant-daemon/src/handlers/system_operations.rs @@ -2,10 +2,10 @@ use std::sync::Arc; use uuid::Uuid; use crate::error::Error as DaemonError; -use super::{TaskMap, TaskEntry}; +use super::{TaskMap, TaskEntry, is_public_only_mode, PUBLIC_ONLY_ERROR_MSG}; use mutant_lib::MutAnt; use mutant_protocol::{ - HealthCheckCallback, HealthCheckEvent, HealthCheckRequest, PurgeCallback, PurgeEvent, + ErrorResponse, HealthCheckCallback, HealthCheckEvent, HealthCheckRequest, PurgeCallback, PurgeEvent, PurgeRequest, Response, SyncCallback, SyncEvent, SyncRequest, Task, TaskCreatedResponse, TaskProgress, TaskResult, TaskResultResponse, TaskResultType, TaskStatus, TaskType, TaskUpdateResponse, @@ -19,6 +19,16 @@ pub(crate) async fn handle_sync( mutant: Arc, tasks: TaskMap, ) -> Result<(), DaemonError> { + // Check if we're in public-only mode + if is_public_only_mode() { + return update_tx + .send(Response::Error(ErrorResponse { + error: PUBLIC_ONLY_ERROR_MSG.to_string(), + original_request: Some(serde_json::to_string(&SyncRequest { push_force: req.push_force }).unwrap_or_default()), + })) + .map_err(|e| DaemonError::Internal(format!("Update channel send error: {}", e))); + } + let task_id = Uuid::new_v4(); let task = Task { @@ -159,6 +169,16 @@ pub(crate) async fn handle_purge( mutant: Arc, tasks: TaskMap, ) -> Result<(), DaemonError> { + // Check if we're in public-only mode + if is_public_only_mode() { + return update_tx + .send(Response::Error(ErrorResponse { + error: PUBLIC_ONLY_ERROR_MSG.to_string(), + original_request: Some(serde_json::to_string(&PurgeRequest { aggressive: req.aggressive }).unwrap_or_default()), + })) + .map_err(|e| DaemonError::Internal(format!("Update channel send error: {}", e))); + } + let task_id = Uuid::new_v4(); let task = Task { @@ -300,6 +320,19 @@ pub(crate) async fn handle_health_check( mutant: Arc, tasks: TaskMap, ) -> Result<(), DaemonError> { + // Check if we're in public-only mode + if is_public_only_mode() { + return update_tx + .send(Response::Error(ErrorResponse { + error: PUBLIC_ONLY_ERROR_MSG.to_string(), + original_request: Some(serde_json::to_string(&HealthCheckRequest { + key_name: req.key_name.clone(), + recycle: req.recycle + }).unwrap_or_default()), + })) + .map_err(|e| DaemonError::Internal(format!("Update channel send error: {}", e))); + } + let task_id = Uuid::new_v4(); let task = Task {