diff --git a/Cargo.toml b/Cargo.toml index 9382d9d..bd5ae96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ color-eyre = "~0.6" indicatif = { version = "0.17.5", features = ["tokio"] } libp2p = { version = "0.53", features = [] } libp2p-identity = { version="0.2.7", features = ["rand"] } +semver = "1.0.20" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" service-manager = "0.5.1" diff --git a/Justfile b/Justfile index 2f72fed..eef1385 100644 --- a/Justfile +++ b/Justfile @@ -2,6 +2,69 @@ release_repo := "maidsafe/sn-node-manager" +droplet-testbed: + #!/usr/bin/env bash + + DROPLET_NAME="node-manager-testbed" + REGION="lon1" + SIZE="s-1vcpu-1gb" + IMAGE="ubuntu-20-04-x64" + SSH_KEY_ID="30878672" + + droplet_ip=$(doctl compute droplet list \ + --format Name,PublicIPv4 --no-header | grep "^$DROPLET_NAME " | awk '{ print $2 }') + + if [ -z "$droplet_ip" ]; then + droplet_id=$(doctl compute droplet create $DROPLET_NAME \ + --region $REGION \ + --size $SIZE \ + --image $IMAGE \ + --ssh-keys $SSH_KEY_ID \ + --format ID \ + --no-header \ + --wait) + if [ -z "$droplet_id" ]; then + echo "Failed to obtain droplet ID" + exit 1 + fi + + echo "Droplet ID: $droplet_id" + echo "Waiting for droplet IP address..." + droplet_ip=$(doctl compute droplet get $droplet_id --format PublicIPv4 --no-header) + while [ -z "$droplet_ip" ]; do + echo "Still waiting to obtain droplet IP address..." + sleep 5 + droplet_ip=$(doctl compute droplet get $droplet_id --format PublicIPv4 --no-header) + done + fi + echo "Droplet IP address: $droplet_ip" + + nc -zw1 $droplet_ip 22 + exit_code=$? + while [ $exit_code -ne 0 ]; do + echo "Waiting on SSH to become available..." + sleep 5 + nc -zw1 $droplet_ip 22 + exit_code=$? + done + + cargo build --release --target x86_64-unknown-linux-musl + scp -r ./target/x86_64-unknown-linux-musl/release/safenode-manager \ + root@$droplet_ip:/root/safenode-manager + +kill-testbed: + #!/usr/bin/env bash + + DROPLET_NAME="node-manager-testbed" + + droplet_id=$(doctl compute droplet list \ + --format Name,ID --no-header | grep "^$DROPLET_NAME " | awk '{ print $2 }') + + if [ -z "$droplet_ip" ]; then + echo "Deleting droplet with ID $droplet_id" + doctl compute droplet delete $droplet_id + fi + build-release-artifacts arch: #!/usr/bin/env bash set -e diff --git a/README.md b/README.md index 7b4e1cf..7676652 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ A peer ID will be assigned to a node after it is started for the first time. This command must run as the root user on Linux/macOS and the Administrator user on Windows. -Running the command with no arguments will stop every installed node that is not already stopped. The peer ID or service name can be used to start a specific service. +Running the command with no arguments will stop every node that is not already stopped. The peer ID or service name can be used to start a specific service. If started again, the node's data and peer ID will be retained. @@ -83,6 +83,19 @@ This command must run as the root user on Linux/macOS and the Administrator user Removes the node and its data/log directories. The node must be stopped before running this command. +### Upgrade + +- Command: `upgrade` +- Description: Upgrades a `safenode` service to the latest version. +- Options: + - `--peer_id`: Peer ID of the service to stop. Optional. + - `--service_name`: Name of the service to stop. Optional. +- Usage: `safenode-manager upgrade [OPTIONS]` + +This command must run as the root user on Linux/macOS and the Administrator user on Windows. + +Running the command with no arguments will upgrade every node. The peer ID or service name can be used to upgrade a specific service. + ## License This Safe Network repository is licensed under the General Public License (GPL), version 3 ([LICENSE](LICENSE) http://www.gnu.org/licenses/gpl-3.0.en.html). diff --git a/src/add_service.rs b/src/add_service.rs index e1fee33..d8663eb 100644 --- a/src/add_service.rs +++ b/src/add_service.rs @@ -7,15 +7,14 @@ // permissions and limitations relating to use of the SAFE Network Software. use crate::config::create_owned_dir; +use crate::helpers::download_and_extract_safenode; use crate::node::{Node, NodeRegistry, NodeStatus}; use crate::service::{ServiceConfig, ServiceControl}; use color_eyre::{eyre::eyre, Result}; use colored::Colorize; -use indicatif::{ProgressBar, ProgressStyle}; use libp2p::Multiaddr; -use sn_releases::{get_running_platform, ArchiveType, ReleaseType, SafeReleaseRepositoryInterface}; +use sn_releases::SafeReleaseRepositoryInterface; use std::path::PathBuf; -use std::sync::Arc; pub struct AddServiceOptions { pub count: Option, @@ -39,41 +38,8 @@ pub async fn add( service_control: &dyn ServiceControl, release_repo: Box, ) -> Result<()> { - let pb = Arc::new(ProgressBar::new(0)); - pb.set_style(ProgressStyle::default_bar() - .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})")? - .progress_chars("#>-")); - let pb_clone = pb.clone(); - let callback: Box = Box::new(move |downloaded, total| { - pb_clone.set_length(total); - pb_clone.set_position(downloaded); - }); - - let version = if let Some(version) = install_options.version { - version - } else { - println!("Retrieving latest version for safenode..."); - release_repo - .get_latest_version(&ReleaseType::Safenode) - .await? - }; - - println!("Downloading safenode version {version}..."); - - let temp_dir_path = create_temp_dir()?; - let archive_path = release_repo - .download_release_from_s3( - &ReleaseType::Safenode, - &version, - &get_running_platform()?, - &ArchiveType::TarGz, - &temp_dir_path, - &callback, - ) - .await?; - pb.finish_with_message("Download complete"); - let safenode_download_path = - release_repo.extract_release_archive(&archive_path, &temp_dir_path)?; + let (safenode_download_path, version) = + download_and_extract_safenode(install_options.version, release_repo).await?; let safenode_file_name = safenode_download_path .file_name() .ok_or_else(|| eyre!("Could not get filename from the safenode download path"))? @@ -137,7 +103,7 @@ pub async fn add( peer_id: None, log_dir_path: Some(service_log_dir_path.clone()), data_dir_path: Some(service_data_dir_path.clone()), - safenode_path: service_safenode_path, + safenode_path: Some(service_safenode_path), }); node_number += 1; @@ -160,16 +126,6 @@ pub async fn add( Ok(()) } -/// There is a `tempdir` crate that provides the same kind of functionality, but it was flagged for -/// a security vulnerability. -fn create_temp_dir() -> Result { - let temp_dir = std::env::temp_dir(); - let unique_dir_name = uuid::Uuid::new_v4().to_string(); - let new_temp_dir = temp_dir.join(unique_dir_name); - std::fs::create_dir_all(&new_temp_dir)?; - Ok(new_temp_dir) -} - #[cfg(test)] mod tests { use super::*; @@ -232,7 +188,7 @@ mod tests { node_data_dir.create_dir_all()?; let node_logs_dir = temp_dir.child("logs"); node_logs_dir.create_dir_all()?; - let safenode_download_path = temp_dir.child("safenode"); + let safenode_download_path = temp_dir.child(SAFENODE_FILE_NAME); safenode_download_path.write_binary(b"fake safenode bin")?; let mut seq = Sequence::new(); @@ -358,7 +314,7 @@ mod tests { node_data_dir.create_dir_all()?; let node_logs_dir = temp_dir.child("logs"); node_logs_dir.create_dir_all()?; - let safenode_download_path = temp_dir.child("safenode"); + let safenode_download_path = temp_dir.child(SAFENODE_FILE_NAME); safenode_download_path.write_binary(b"fake safenode bin")?; let mut seq = Sequence::new(); @@ -573,7 +529,7 @@ mod tests { node_data_dir.create_dir_all()?; let node_logs_dir = temp_dir.child("logs"); node_logs_dir.create_dir_all()?; - let safenode_download_path = temp_dir.child("safenode"); + let safenode_download_path = temp_dir.child(SAFENODE_FILE_NAME); safenode_download_path.write_binary(b"fake safenode bin")?; let mut seq = Sequence::new(); @@ -693,17 +649,11 @@ mod tests { status: NodeStatus::Added, pid: None, peer_id: None, -<<<<<<< HEAD log_dir_path: Some(PathBuf::from("/var/log/safenode/safenode1")), data_dir_path: Some(PathBuf::from("/var/safenode-manager/services/safenode1")), -||||||| parent of 1a686eb (feat: each service instance to use its own binary) - log_dir_path: PathBuf::from("/var/log/safenode/safenode1"), - data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"), -======= - log_dir_path: PathBuf::from("/var/log/safenode/safenode1"), - data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"), - safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"), ->>>>>>> 1a686eb (feat: each service instance to use its own binary) + safenode_path: Some(PathBuf::from( + "/var/safenode-manager/services/safenode1/safenode", + )), }], }; let temp_dir = assert_fs::TempDir::new()?; @@ -711,7 +661,7 @@ mod tests { node_data_dir.create_dir_all()?; let node_logs_dir = temp_dir.child("logs"); node_logs_dir.create_dir_all()?; - let safenode_download_path = temp_dir.child("safenode"); + let safenode_download_path = temp_dir.child(SAFENODE_FILE_NAME); safenode_download_path.write_binary(b"fake safenode bin")?; let mut seq = Sequence::new(); diff --git a/src/control.rs b/src/control.rs index 6c4d66f..0c48898 100644 --- a/src/control.rs +++ b/src/control.rs @@ -10,7 +10,15 @@ use crate::node::{Node, NodeRegistry, NodeStatus}; use crate::service::ServiceControl; use color_eyre::{eyre::eyre, Help, Result}; use colored::Colorize; +use semver::Version; use sn_node_rpc_client::RpcActions; +use std::path::PathBuf; + +pub enum UpgradeResult { + NotRequired, + Upgraded(String, String), + Error(String), +} pub async fn start( node: &mut Node, @@ -191,6 +199,7 @@ pub async fn remove( )?; node.data_dir_path = None; node.log_dir_path = None; + node.safenode_path = None; } node.status = NodeStatus::Removed; @@ -200,6 +209,34 @@ pub async fn remove( Ok(()) } +pub async fn upgrade( + node: &mut Node, + upgraded_safenode_path: &PathBuf, + latest_version: &Version, + service_control: &dyn ServiceControl, + rpc_client: &dyn RpcActions, +) -> Result { + let current_version = Version::parse(&node.version)?; + if current_version == *latest_version { + return Ok(UpgradeResult::NotRequired); + } + + stop(node, service_control).await?; + std::fs::copy( + upgraded_safenode_path, + node.safenode_path + .as_ref() + .ok_or_else(|| eyre!("Unable to obtain safenode path for current node"))?, + )?; + start(node, service_control, rpc_client).await?; + node.version = latest_version.to_string(); + + Ok(UpgradeResult::Upgraded( + current_version.to_string(), + latest_version.to_string(), + )) +} + fn format_status(status: &NodeStatus) -> String { match status { NodeStatus::Running => "RUNNING".green().to_string(), @@ -281,7 +318,9 @@ mod tests { peer_id: None, log_dir_path: Some(PathBuf::from("/var/log/safenode/safenode1")), data_dir_path: Some(PathBuf::from("/var/safenode-manager/services/safenode1")), - safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"), + safenode_path: Some(PathBuf::from( + "/var/safenode-manager/services/safenode1/safenode", + )), }; start(&mut node, &mock_service_control, &mock_rpc_client).await?; @@ -336,7 +375,9 @@ mod tests { )?), log_dir_path: Some(PathBuf::from("/var/log/safenode/safenode1")), data_dir_path: Some(PathBuf::from("/var/safenode-manager/services/safenode1")), - safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"), + safenode_path: Some(PathBuf::from( + "/var/safenode-manager/services/safenode1/safenode", + )), }; start(&mut node, &mock_service_control, &mock_rpc_client).await?; @@ -391,7 +432,9 @@ mod tests { )?), log_dir_path: Some(PathBuf::from("/var/log/safenode/safenode1")), data_dir_path: Some(PathBuf::from("/var/safenode-manager/services/safenode1")), - safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"), + safenode_path: Some(PathBuf::from( + "/var/safenode-manager/services/safenode1/safenode", + )), }; start(&mut node, &mock_service_control, &mock_rpc_client).await?; @@ -437,7 +480,9 @@ mod tests { )?), log_dir_path: Some(PathBuf::from("/var/log/safenode/safenode1")), data_dir_path: Some(PathBuf::from("/var/safenode-manager/services/safenode1")), - safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"), + safenode_path: Some(PathBuf::from( + "/var/safenode-manager/services/safenode1/safenode", + )), }; start(&mut node, &mock_service_control, &mock_rpc_client).await?; @@ -476,7 +521,9 @@ mod tests { )?), log_dir_path: Some(PathBuf::from("/var/log/safenode/safenode1")), data_dir_path: Some(PathBuf::from("/var/safenode-manager/services/safenode1")), - safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"), + safenode_path: Some(PathBuf::from( + "/var/safenode-manager/services/safenode1/safenode", + )), }; stop(&mut node, &mock_service_control).await?; @@ -509,7 +556,9 @@ mod tests { peer_id: None, log_dir_path: Some(PathBuf::from("/var/log/safenode/safenode1")), data_dir_path: Some(PathBuf::from("/var/safenode-manager/services/safenode1")), - safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"), + safenode_path: Some(PathBuf::from( + "/var/safenode-manager/services/safenode1/safenode", + )), }; let result = stop(&mut node, &mock_service_control).await; @@ -544,7 +593,9 @@ mod tests { peer_id: None, log_dir_path: Some(PathBuf::from("/var/log/safenode/safenode1")), data_dir_path: Some(PathBuf::from("/var/safenode-manager/services/safenode1")), - safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"), + safenode_path: Some(PathBuf::from( + "/var/safenode-manager/services/safenode1/safenode", + )), }; stop(&mut node, &mock_service_control).await?; @@ -562,6 +613,8 @@ mod tests { log_dir.create_dir_all()?; let data_dir = temp_dir.child("safenode1-data"); data_dir.create_dir_all()?; + let safenode_bin = data_dir.child("safenode"); + safenode_bin.write_binary(b"fake safenode binary")?; let mut mock_service_control = MockServiceControl::new(); mock_service_control @@ -582,12 +635,14 @@ mod tests { peer_id: None, log_dir_path: Some(log_dir.to_path_buf()), data_dir_path: Some(data_dir.to_path_buf()), + safenode_path: Some(safenode_bin.to_path_buf()), }; remove(&mut node, &mock_service_control, false).await?; assert_eq!(node.data_dir_path, None); assert_eq!(node.log_dir_path, None); + assert_eq!(node.safenode_path, None); assert_matches!(node.status, NodeStatus::Removed); log_dir.assert(predicate::path::missing()); @@ -619,6 +674,9 @@ mod tests { )?), log_dir_path: Some(PathBuf::from("/var/log/safenode/safenode1")), data_dir_path: Some(PathBuf::from("/var/safenode-manager/services/safenode1")), + safenode_path: Some(PathBuf::from( + "/var/safenode-manager/services/safenode1/safenode", + )), }; let result = remove(&mut node, &mock_service_control, false).await; @@ -638,6 +696,8 @@ mod tests { log_dir.create_dir_all()?; let data_dir = temp_dir.child("safenode1-data"); data_dir.create_dir_all()?; + let safenode_bin = data_dir.child("safenode"); + safenode_bin.write_binary(b"fake safenode binary")?; let mut mock_service_control = MockServiceControl::new(); mock_service_control @@ -660,6 +720,7 @@ mod tests { )?), log_dir_path: Some(log_dir.to_path_buf()), data_dir_path: Some(data_dir.to_path_buf()), + safenode_path: Some(safenode_bin.to_path_buf()), }; let result = remove(&mut node, &mock_service_control, false).await; @@ -684,6 +745,8 @@ mod tests { log_dir.create_dir_all()?; let data_dir = temp_dir.child("safenode1-data"); data_dir.create_dir_all()?; + let safenode_bin = data_dir.child("safenode"); + safenode_bin.write_binary(b"fake safenode binary")?; let mut mock_service_control = MockServiceControl::new(); mock_service_control @@ -704,6 +767,7 @@ mod tests { peer_id: None, log_dir_path: Some(log_dir.to_path_buf()), data_dir_path: Some(data_dir.to_path_buf()), + safenode_path: Some(safenode_bin.to_path_buf()), }; remove(&mut node, &mock_service_control, true).await?; diff --git a/src/helpers.rs b/src/helpers.rs new file mode 100644 index 0000000..b2329ba --- /dev/null +++ b/src/helpers.rs @@ -0,0 +1,65 @@ +// Copyright (C) 2023 MaidSafe.net limited. +// +// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. +// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed +// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. Please review the Licences for the specific language governing +// permissions and limitations relating to use of the SAFE Network Software. + +use color_eyre::Result; +use indicatif::{ProgressBar, ProgressStyle}; +use sn_releases::{get_running_platform, ArchiveType, ReleaseType, SafeReleaseRepositoryInterface}; +use std::path::PathBuf; +use std::sync::Arc; + +pub async fn download_and_extract_safenode( + safenode_version: Option, + release_repo: Box, +) -> Result<(PathBuf, String)> { + let pb = Arc::new(ProgressBar::new(0)); + pb.set_style(ProgressStyle::default_bar() + .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})")? + .progress_chars("#>-")); + let pb_clone = pb.clone(); + let callback: Box = Box::new(move |downloaded, total| { + pb_clone.set_length(total); + pb_clone.set_position(downloaded); + }); + + let version = if let Some(version) = safenode_version { + version + } else { + println!("Retrieving latest version for safenode..."); + release_repo + .get_latest_version(&ReleaseType::Safenode) + .await? + }; + + println!("Downloading safenode version {version}..."); + let temp_dir_path = create_temp_dir()?; + let archive_path = release_repo + .download_release_from_s3( + &ReleaseType::Safenode, + &version, + &get_running_platform()?, + &ArchiveType::TarGz, + &temp_dir_path, + &callback, + ) + .await?; + pb.finish_with_message("Download complete"); + let safenode_download_path = + release_repo.extract_release_archive(&archive_path, &temp_dir_path)?; + + Ok((safenode_download_path, version)) +} + +/// There is a `tempdir` crate that provides the same kind of functionality, but it was flagged for +/// a security vulnerability. +fn create_temp_dir() -> Result { + let temp_dir = std::env::temp_dir(); + let unique_dir_name = uuid::Uuid::new_v4().to_string(); + let new_temp_dir = temp_dir.join(unique_dir_name); + std::fs::create_dir_all(&new_temp_dir)?; + Ok(new_temp_dir) +} diff --git a/src/main.rs b/src/main.rs index 5865761..67301e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,20 +9,24 @@ mod add_service; mod config; mod control; +mod helpers; mod node; mod service; use crate::add_service::{add, AddServiceOptions}; use crate::config::{get_node_registry_path, get_service_data_dir_path, get_service_log_dir_path}; -use crate::control::{remove, start, status, stop}; +use crate::control::{remove, start, status, stop, upgrade, UpgradeResult}; +use crate::helpers::download_and_extract_safenode; use crate::node::NodeRegistry; use crate::service::{NodeServiceManager, ServiceControl}; use clap::{Parser, Subcommand}; use color_eyre::{eyre::eyre, Help, Result}; +use colored::Colorize; use libp2p_identity::PeerId; +use semver::Version; use sn_node_rpc_client::RpcClient; use sn_peers_acquisition::{parse_peers_args, PeersArgs}; -use sn_releases::SafeReleaseRepositoryInterface; +use sn_releases::{ReleaseType, SafeReleaseRepositoryInterface}; use std::path::PathBuf; use std::str::FromStr; @@ -96,15 +100,15 @@ pub enum SubCmd { }, /// Start a safenode service. /// - /// If no peer ID or service name are supplied, all installed services will be started. + /// If no peer ID(s) or service name(s) are supplied, all services will be started. /// /// This command must run as the root/administrative user. #[clap(name = "start")] Start { - /// The peer ID of the service to start. + /// The peer ID of the service to start #[clap(long)] peer_id: Option, - /// The name of the service to start. + /// The name of the service to start #[clap(long)] service_name: Option, }, @@ -115,17 +119,31 @@ pub enum SubCmd { #[clap(long)] details: bool, }, - /// Stop an installed safenode service. + /// Stop a safenode service. /// - /// If no peer ID or service name are supplied, all installed services will be stopped. + /// If no peer ID(s) or service name(s) are supplied, all services will be stopped. /// /// This command must run as the root/administrative user. #[clap(name = "stop")] Stop { - /// The peer ID of the service to stop. + /// The peer ID of the service to stop #[clap(long)] peer_id: Option, - /// The name of the service to stop. + /// The name of the service to stop + #[clap(long)] + service_name: Option, + }, + /// Upgrade a safenode service. + /// + /// If no peer ID(s) or service name(s) are supplied, all services will be upgraded. + /// + /// This command must run as the root/administrative user. + #[clap(name = "upgrade")] + Upgrade { + /// The peer ID of the service to upgrade + #[clap(long)] + peer_id: Option, + /// The name of the service to upgrade #[clap(long)] service_name: Option, }, @@ -332,6 +350,156 @@ async fn main() -> Result<()> { node_registry.save(&get_node_registry_path()?)?; + Ok(()) + } + SubCmd::Upgrade { + peer_id, + service_name, + } => { + if !is_running_as_root() { + return Err(eyre!("The upgrade command must run as the root user")); + } + + validate_peer_id_and_service_name_args(service_name.clone(), peer_id.clone())?; + + println!("================================================="); + println!(" Upgrade Safenode Services "); + println!("================================================="); + + println!("Retrieving latest version of safenode..."); + let release_repo = ::default_config(); + let latest_version = release_repo + .get_latest_version(&ReleaseType::Safenode) + .await + .map(|v| Version::parse(&v).unwrap())?; + println!("Latest version is {latest_version}"); + + let mut node_registry = NodeRegistry::load(&get_node_registry_path()?)?; + let any_nodes_need_upgraded = node_registry.nodes.iter().any(|n| { + let current_version = Version::parse(&n.version).unwrap(); + current_version < latest_version + }); + + if !any_nodes_need_upgraded { + println!("{} All nodes are at the latest version", "✓".green()); + return Ok(()); + } + + let (safenode_download_path, _) = + download_and_extract_safenode(Some(latest_version.to_string()), release_repo) + .await?; + + let mut upgrade_summary = Vec::new(); + + if let Some(ref name) = service_name { + let node = node_registry + .nodes + .iter_mut() + .find(|x| x.service_name == *name) + .ok_or_else(|| eyre!("No service named '{name}'"))?; + + let rpc_client = RpcClient::new(&format!("https://127.0.0.1:{}", node.rpc_port)); + let result = upgrade( + node, + &safenode_download_path, + &latest_version, + &NodeServiceManager {}, + &rpc_client, + ) + .await; + + match result { + Ok(upgrade_result) => { + upgrade_summary.push((node.service_name.clone(), upgrade_result)); + } + Err(e) => { + upgrade_summary.push(( + node.service_name.clone(), + UpgradeResult::Error(format!("Error: {}", e)), + )); + } + } + } else if let Some(ref peer_id) = peer_id { + let peer_id = PeerId::from_str(peer_id)?; + let node = node_registry + .nodes + .iter_mut() + .find(|x| x.peer_id == Some(peer_id)) + .ok_or_else(|| { + eyre!(format!( + "Could not find node with peer ID '{}'", + peer_id.to_string() + )) + })?; + + let rpc_client = RpcClient::new(&format!("https://127.0.0.1:{}", node.rpc_port)); + let result = upgrade( + node, + &safenode_download_path, + &latest_version, + &NodeServiceManager {}, + &rpc_client, + ) + .await; + + match result { + Ok(upgrade_result) => { + upgrade_summary.push((node.service_name.clone(), upgrade_result)); + } + Err(e) => { + upgrade_summary.push(( + node.service_name.clone(), + UpgradeResult::Error(format!("Error: {}", e)), + )); + } + } + } else { + for node in node_registry.nodes.iter_mut() { + let rpc_client = + RpcClient::new(&format!("https://127.0.0.1:{}", node.rpc_port)); + let result = upgrade( + node, + &safenode_download_path, + &latest_version, + &NodeServiceManager {}, + &rpc_client, + ) + .await; + + match result { + Ok(upgrade_result) => { + upgrade_summary.push((node.service_name.clone(), upgrade_result)); + } + Err(e) => { + upgrade_summary.push(( + node.service_name.clone(), + UpgradeResult::Error(format!("Error: {}", e)), + )); + } + } + } + } + + node_registry.save(&get_node_registry_path()?)?; + + println!("Upgrade summary:"); + for (service_name, upgrade_result) in upgrade_summary { + match upgrade_result { + UpgradeResult::NotRequired => { + println!("- {service_name} was at the latest version"); + } + UpgradeResult::Upgraded(previous_version, new_version) => { + println!( + "{} {service_name} upgraded from {previous_version} to {new_version}", + "✓".green() + ); + } + UpgradeResult::Error(msg) => { + println!("{} {service_name} was not upgraded: {}", "✕".red(), msg); + } + } + } + Ok(()) } } @@ -348,20 +516,6 @@ fn is_running_as_root() -> bool { true } -#[cfg(unix)] -fn get_safenode_install_path() -> Result { - Ok(PathBuf::from("/usr/local/bin")) -} - -#[cfg(windows)] -fn get_safenode_install_path() -> Result { - let path = PathBuf::from("C:\\Program Files\\Maidsafe\\safenode"); - if !path.exists() { - std::fs::create_dir_all(path.clone())?; - } - Ok(path) -} - fn validate_peer_id_and_service_name_args( service_name: Option, peer_id: Option, diff --git a/src/node.rs b/src/node.rs index ce3fa44..0033101 100644 --- a/src/node.rs +++ b/src/node.rs @@ -67,7 +67,7 @@ pub struct Node { pub peer_id: Option, pub data_dir_path: Option, pub log_dir_path: Option, - pub safenode_path: PathBuf, + pub safenode_path: Option, } #[derive(Clone, Debug, Serialize, Deserialize)]