From f234446e2d62aff9c02b0f832bdb34234fc419f3 Mon Sep 17 00:00:00 2001 From: Chris O'Neil Date: Thu, 7 Dec 2023 16:05:42 +0000 Subject: [PATCH] feat: provide `upgrade` command The upgrade process simple: * Get the latest version * Download the new binary * Stop the running node * Overwrite the old binary with the new binary * Start the node It avoids doing anything if all the nodes are already at the latest version, and it also only downloads the new binary once, even if there are multiple nodes to upgrade. Also provided here is a Bash script that can spin up a quick test machine on Digital Ocean. This is useful for adding nodes that connect to real networks without having to be concerned about NAT on the local VM. I tried to use the DO provider for Vagrant, but sadly it didn't work correctly; I think it was out of date with the current API. The branch for the `remove` command was also rebased on top of this commit. As a result, the `safenode_path` on the `Node` struct was also set to an optional field to make it consistent with `data_dir_path` and `log_dir_path`. --- Cargo.toml | 1 + Justfile | 63 ++++++++++++++ README.md | 15 +++- src/add_service.rs | 74 +++-------------- src/control.rs | 78 ++++++++++++++++-- src/helpers.rs | 65 +++++++++++++++ src/main.rs | 200 +++++++++++++++++++++++++++++++++++++++------ src/node.rs | 2 +- 8 files changed, 404 insertions(+), 94 deletions(-) create mode 100644 src/helpers.rs 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)]