diff --git a/Cargo.lock b/Cargo.lock index 8086973fb9..7b4b3f2dbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2124,6 +2124,14 @@ dependencies = [ "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "semver" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "semver" version = "0.9.0" @@ -2630,6 +2638,7 @@ dependencies = [ "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.9.19 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3892,6 +3901,7 @@ dependencies = [ "checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" "checksum security-framework 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfab8dda0e7a327c696d893df9ffa19cadc4bd195797997f5223cf5831beaf05" "checksum security-framework-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3d6696852716b589dff9e886ff83778bb635150168e83afa8ac6b8a78cb82abc" +"checksum semver 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fdd61b85a0fa777f7fb7c454b9189b2941b110d1385ce84d7f76efdf1606a85" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)" = "076a696fdea89c19d3baed462576b8f6d663064414b5c793642da8dfeb99475b" diff --git a/book/src/testnet-participation.md b/book/src/testnet-participation.md index 7aeb7c494b..acb642a0a2 100644 --- a/book/src/testnet-participation.md +++ b/book/src/testnet-participation.md @@ -74,8 +74,7 @@ The `solana-install` tool can be used to easily install and upgrade the cluster software on Linux x86_64 and mac OS systems. ```bash -$ export SOLANA_RELEASE=v0.16.0 # skip this line to install the latest release -$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.16.0/install/solana-install-init.sh | sh -s +$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.16.5/install/solana-install-init.sh | sh -s ``` Alternatively build the `solana-install` program from source and run the diff --git a/install/Cargo.toml b/install/Cargo.toml index db6f32455d..9f75b75c78 100644 --- a/install/Cargo.toml +++ b/install/Cargo.toml @@ -27,6 +27,7 @@ lazy_static = "1.3.0" log = "0.4.7" nix = "0.14.1" reqwest = "0.9.19" +semver = "0.7.0" serde = "1.0.94" serde_derive = "1.0.94" serde_yaml = "0.8.9" diff --git a/install/src/command.rs b/install/src/command.rs index a1e6194303..eb140d4200 100644 --- a/install/src/command.rs +++ b/install/src/command.rs @@ -492,13 +492,19 @@ pub fn init( json_rpc_url: &str, update_manifest_pubkey: &Pubkey, no_modify_path: bool, + release_semver: Option<&str>, ) -> Result<(), String> { let config = { // Write new config file only if different, so that running |solana-install init| // repeatedly doesn't unnecessarily re-download let mut current_config = Config::load(config_file).unwrap_or_default(); current_config.current_update_manifest = None; - let config = Config::new(data_dir, json_rpc_url, update_manifest_pubkey); + let config = Config::new( + data_dir, + json_rpc_url, + update_manifest_pubkey, + release_semver, + ); if current_config != config { config.save(config_file)?; } @@ -519,24 +525,42 @@ pub fn init( Ok(()) } +fn github_download_url(release_semver: &str) -> String { + format!( + "https://github.com/solana-labs/solana/releases/download/v{}/solana-release-{}.tar.bz2", + release_semver, + crate::build_env::TARGET + ) +} + pub fn info(config_file: &str, local_info_only: bool) -> Result, String> { let config = Config::load(config_file)?; - println_name_value("JSON RPC URL:", &config.json_rpc_url); - println_name_value( - "Update manifest pubkey:", - &config.update_manifest_pubkey.to_string(), - ); + println_name_value("Configuration:", &config_file); println_name_value( "Active release directory:", &config.active_release_dir().to_str().unwrap_or("?"), ); + if let Some(release_semver) = &config.release_semver { + println_name_value(&format!("{}Release version:", BULLET), &release_semver); + println_name_value( + &format!("{}Release URL:", BULLET), + &github_download_url(release_semver), + ); + return Ok(None); + } + + println_name_value("JSON RPC URL:", &config.json_rpc_url); + println_name_value( + "Update manifest pubkey:", + &config.update_manifest_pubkey.to_string(), + ); fn print_update_manifest(update_manifest: &UpdateManifest) { let when = Local.timestamp(update_manifest.timestamp_secs as i64, 0); - println_name_value(&format!("{}release date", BULLET), &when.to_string()); + println_name_value(&format!("{}release date:", BULLET), &when.to_string()); println_name_value( - &format!("{}download URL", BULLET), + &format!("{}download URL:", BULLET), &update_manifest.download_url, ); } @@ -669,44 +693,66 @@ fn symlink_dir, Q: AsRef>(src: P, dst: Q) -> std::io::Resul } pub fn update(config_file: &str) -> Result { - let update_manifest = info(config_file, false)?; - if update_manifest.is_none() { - return Ok(false); - } - let update_manifest = update_manifest.unwrap(); - - if timestamp_secs() - < u64::from_str_radix(crate::build_env::BUILD_SECONDS_SINCE_UNIX_EPOCH, 10).unwrap() - { - Err("Unable to update as system time seems unreliable".to_string())? - } - let mut config = Config::load(config_file)?; - if let Some(ref current_update_manifest) = config.current_update_manifest { - if update_manifest.timestamp_secs < current_update_manifest.timestamp_secs { - Err("Unable to update to an older version".to_string())? + let update_manifest = info(config_file, false)?; + + let release_dir = if let Some(release_semver) = &config.release_semver { + let download_url = github_download_url(release_semver); + let release_dir = config.release_dir(&release_semver); + let ok_dir = release_dir.join(".ok"); + if ok_dir.exists() { + return Ok(false); } - } + let (_temp_dir, temp_archive, _temp_archive_sha256) = + download_to_temp_archive(&download_url, None) + .map_err(|err| format!("Unable to download {}: {}", download_url, err))?; + extract_release_archive(&temp_archive, &release_dir).map_err(|err| { + format!( + "Unable to extract {:?} to {:?}: {}", + temp_archive, release_dir, err + ) + })?; + let _ = fs::create_dir_all(ok_dir); - let (_temp_dir, temp_archive, _temp_archive_sha256) = download_to_temp_archive( - &update_manifest.download_url, - Some(&update_manifest.download_sha256), - ) - .map_err(|err| { - format!( - "Unable to download {}: {}", - update_manifest.download_url, err + release_dir + } else { + if update_manifest.is_none() { + return Ok(false); + } + let update_manifest = update_manifest.unwrap(); + + if timestamp_secs() + < u64::from_str_radix(crate::build_env::BUILD_SECONDS_SINCE_UNIX_EPOCH, 10).unwrap() + { + Err("Unable to update as system time seems unreliable".to_string())? + } + + if let Some(ref current_update_manifest) = config.current_update_manifest { + if update_manifest.timestamp_secs < current_update_manifest.timestamp_secs { + Err("Unable to update to an older version".to_string())? + } + } + let release_dir = config.release_dir(&update_manifest.download_sha256); + let (_temp_dir, temp_archive, _temp_archive_sha256) = download_to_temp_archive( + &update_manifest.download_url, + Some(&update_manifest.download_sha256), ) - })?; + .map_err(|err| { + format!( + "Unable to download {}: {}", + update_manifest.download_url, err + ) + })?; + extract_release_archive(&temp_archive, &release_dir).map_err(|err| { + format!( + "Unable to extract {:?} to {:?}: {}", + temp_archive, release_dir, err + ) + })?; - let release_dir = config.release_dir(&update_manifest.download_sha256); - - extract_release_archive(&temp_archive, &release_dir).map_err(|err| { - format!( - "Unable to extract {:?} to {:?}: {}", - temp_archive, release_dir, err - ) - })?; + config.current_update_manifest = Some(update_manifest); + release_dir + }; let release_target = load_release_target(&release_dir).map_err(|err| { format!( @@ -733,7 +779,6 @@ pub fn update(config_file: &str) -> Result { ) })?; - config.current_update_manifest = Some(update_manifest); config.save(config_file)?; println!(" {}{}", SPARKLE, style("Update successful").bold()); diff --git a/install/src/config.rs b/install/src/config.rs index 6acfcb0a9b..5c566e0c23 100644 --- a/install/src/config.rs +++ b/install/src/config.rs @@ -11,17 +11,24 @@ pub struct Config { pub update_manifest_pubkey: Pubkey, pub current_update_manifest: Option, pub update_poll_secs: u64, + pub release_semver: Option, releases_dir: PathBuf, active_release_dir: PathBuf, } impl Config { - pub fn new(data_dir: &str, json_rpc_url: &str, update_manifest_pubkey: &Pubkey) -> Self { + pub fn new( + data_dir: &str, + json_rpc_url: &str, + update_manifest_pubkey: &Pubkey, + release_semver: Option<&str>, + ) -> Self { Self { json_rpc_url: json_rpc_url.to_string(), update_manifest_pubkey: *update_manifest_pubkey, current_update_manifest: None, update_poll_secs: 60, // check for updates once a minute + release_semver: release_semver.map(|s| s.to_string()), releases_dir: PathBuf::from(data_dir).join("releases"), active_release_dir: PathBuf::from(data_dir).join("active_release"), } @@ -64,7 +71,7 @@ impl Config { self.active_release_dir.join("bin") } - pub fn release_dir(&self, release_sha256: &str) -> PathBuf { - self.releases_dir.join(release_sha256) + pub fn release_dir(&self, release_id: &str) -> PathBuf { + self.releases_dir.join(release_id) } } diff --git a/install/src/lib.rs b/install/src/lib.rs index 540470823d..5a014d4892 100644 --- a/install/src/lib.rs +++ b/install/src/lib.rs @@ -33,6 +33,13 @@ fn is_pubkey(string: String) -> Result<(), String> { } } +fn is_semver(string: String) -> Result<(), String> { + match semver::Version::parse(&string) { + Ok(_) => Ok(()), + Err(err) => Err(format!("{:?}", err)), + } +} + pub fn main() -> Result<(), String> { solana_logger::setup(); @@ -98,7 +105,15 @@ pub fn main() -> Result<(), String> { Some(default_value) => arg.default_value(default_value), None => arg, } - }), + }) + .arg( + Arg::with_name("release_semver") + .value_name("release-semver") + .index(1) + .conflicts_with_all(&["json_rpc_url", "update_manifest_pubkey"]) + .validator(is_semver) + .help("The exact version to install. Updates will not be available if this argument is used"), + ), ) .subcommand( SubCommand::with_name("info") @@ -191,6 +206,7 @@ pub fn main() -> Result<(), String> { .unwrap(); let data_dir = matches.value_of("data_dir").unwrap(); let no_modify_path = matches.is_present("no_modify_path"); + let release_semver = matches.value_of("release_semver"); command::init( config_file, @@ -198,6 +214,7 @@ pub fn main() -> Result<(), String> { json_rpc_url, &update_manifest_pubkey, no_modify_path, + release_semver, ) } ("info", Some(matches)) => { @@ -292,6 +309,14 @@ pub fn main_init() -> Result<(), String> { None => arg, } }) + .arg( + Arg::with_name("release_semver") + .value_name("release-semver") + .index(1) + .conflicts_with_all(&["json_rpc_url", "update_manifest_pubkey"]) + .validator(is_semver) + .help("The exact version to install. Updates will not be available if this argument is used"), + ) .get_matches(); let config_file = matches.value_of("config_file").unwrap(); @@ -304,6 +329,7 @@ pub fn main_init() -> Result<(), String> { .unwrap(); let data_dir = matches.value_of("data_dir").unwrap(); let no_modify_path = matches.is_present("no_modify_path"); + let release_semver = matches.value_of("release_semver"); command::init( config_file, @@ -311,5 +337,6 @@ pub fn main_init() -> Result<(), String> { json_rpc_url, &update_manifest_pubkey, no_modify_path, + release_semver, ) }