2021-03-11 17:52:16 -08:00
|
|
|
|
use {
|
|
|
|
|
crate::{
|
|
|
|
|
config::{Config, ExplicitRelease},
|
|
|
|
|
stop_process::stop_process,
|
|
|
|
|
update_manifest::{SignedUpdateManifest, UpdateManifest},
|
|
|
|
|
},
|
|
|
|
|
chrono::{Local, TimeZone},
|
|
|
|
|
console::{style, Emoji},
|
2022-01-11 02:44:46 -08:00
|
|
|
|
crossbeam_channel::unbounded,
|
2021-03-11 17:52:16 -08:00
|
|
|
|
indicatif::{ProgressBar, ProgressStyle},
|
|
|
|
|
serde::{Deserialize, Serialize},
|
|
|
|
|
solana_config_program::{config_instruction, get_config_data, ConfigState},
|
2022-08-24 09:47:02 -07:00
|
|
|
|
solana_rpc_client::rpc_client::RpcClient,
|
2021-03-11 17:52:16 -08:00
|
|
|
|
solana_sdk::{
|
|
|
|
|
hash::{Hash, Hasher},
|
|
|
|
|
message::Message,
|
|
|
|
|
pubkey::Pubkey,
|
|
|
|
|
signature::{read_keypair_file, Keypair, Signable, Signer},
|
|
|
|
|
transaction::Transaction,
|
|
|
|
|
},
|
|
|
|
|
std::{
|
|
|
|
|
fs::{self, File},
|
|
|
|
|
io::{self, BufReader, Read},
|
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
|
time::{Duration, Instant, SystemTime},
|
|
|
|
|
},
|
|
|
|
|
tempfile::TempDir,
|
|
|
|
|
url::Url,
|
2019-11-22 20:58:26 -08:00
|
|
|
|
};
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
2020-02-10 22:19:52 -08:00
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
|
pub struct ReleaseVersion {
|
|
|
|
|
pub target: String,
|
|
|
|
|
pub commit: String,
|
|
|
|
|
channel: String,
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-20 16:12:50 -07:00
|
|
|
|
static TRUCK: Emoji = Emoji("🚚 ", "");
|
|
|
|
|
static LOOKING_GLASS: Emoji = Emoji("🔍 ", "");
|
|
|
|
|
static BULLET: Emoji = Emoji("• ", "* ");
|
|
|
|
|
static SPARKLE: Emoji = Emoji("✨ ", "");
|
2021-03-11 17:52:16 -08:00
|
|
|
|
static WRAPPED_PRESENT: Emoji = Emoji("🎁 ", "");
|
2019-03-20 16:12:50 -07:00
|
|
|
|
static PACKAGE: Emoji = Emoji("📦 ", "");
|
2019-09-19 12:03:47 -07:00
|
|
|
|
static INFORMATION: Emoji = Emoji("ℹ️ ", "");
|
2021-01-29 15:38:54 -08:00
|
|
|
|
static RECYCLING: Emoji = Emoji("♻️ ", "");
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
|
|
|
|
/// Creates a new process bar for processing that will take an unknown amount of time
|
|
|
|
|
fn new_spinner_progress_bar() -> ProgressBar {
|
|
|
|
|
let progress_bar = ProgressBar::new(42);
|
2022-08-05 14:11:17 -07:00
|
|
|
|
progress_bar.set_style(
|
|
|
|
|
ProgressStyle::default_spinner()
|
|
|
|
|
.template("{spinner:.green} {wide_msg}")
|
|
|
|
|
.expect("ProgresStyle::template direct input to be correct"),
|
|
|
|
|
);
|
|
|
|
|
progress_bar.enable_steady_tick(Duration::from_millis(100));
|
2019-03-20 16:12:50 -07:00
|
|
|
|
progress_bar
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Pretty print a "name value"
|
|
|
|
|
fn println_name_value(name: &str, value: &str) {
|
|
|
|
|
println!("{} {}", style(name).bold(), value);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-10 22:19:52 -08:00
|
|
|
|
/// Downloads a file at `url` to a temporary location. If `expected_sha256` is
|
|
|
|
|
/// Some(_), produce an error if the SHA256 of the file contents doesn't match.
|
2019-03-20 16:12:50 -07:00
|
|
|
|
///
|
|
|
|
|
/// Returns a tuple consisting of:
|
|
|
|
|
/// * TempDir - drop this value to clean up the temporary location
|
2020-02-10 22:19:52 -08:00
|
|
|
|
/// * PathBuf - path to the downloaded file (within `TempDir`)
|
2019-03-20 16:12:50 -07:00
|
|
|
|
/// * String - SHA256 of the release
|
|
|
|
|
///
|
2020-02-10 22:19:52 -08:00
|
|
|
|
fn download_to_temp(
|
2019-03-20 16:12:50 -07:00
|
|
|
|
url: &str,
|
2019-11-22 20:58:26 -08:00
|
|
|
|
expected_sha256: Option<&Hash>,
|
|
|
|
|
) -> Result<(TempDir, PathBuf, Hash), Box<dyn std::error::Error>> {
|
|
|
|
|
fn sha256_file_digest<P: AsRef<Path>>(path: P) -> Result<Hash, Box<dyn std::error::Error>> {
|
2019-07-22 23:11:40 -07:00
|
|
|
|
let input = File::open(path)?;
|
|
|
|
|
let mut reader = BufReader::new(input);
|
2019-11-22 20:58:26 -08:00
|
|
|
|
let mut hasher = Hasher::default();
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
2019-07-22 23:11:40 -07:00
|
|
|
|
let mut buffer = [0; 1024];
|
2019-03-20 16:12:50 -07:00
|
|
|
|
loop {
|
|
|
|
|
let count = reader.read(&mut buffer)?;
|
|
|
|
|
if count == 0 {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-11-22 20:58:26 -08:00
|
|
|
|
hasher.hash(&buffer[..count]);
|
2019-03-20 16:12:50 -07:00
|
|
|
|
}
|
2019-11-22 20:58:26 -08:00
|
|
|
|
Ok(hasher.result())
|
2019-03-20 16:12:50 -07:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-06 06:30:06 -08:00
|
|
|
|
let url = Url::parse(url).map_err(|err| format!("Unable to parse {url}: {err}"))?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
2020-10-11 10:00:12 -07:00
|
|
|
|
let temp_dir = TempDir::new()?;
|
2020-02-10 22:19:52 -08:00
|
|
|
|
let temp_file = temp_dir.path().join("download");
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
2021-12-27 16:04:48 -08:00
|
|
|
|
let client = reqwest::blocking::Client::builder()
|
2021-12-29 21:49:59 -08:00
|
|
|
|
.connect_timeout(Duration::from_secs(30))
|
2021-12-29 15:01:41 -08:00
|
|
|
|
.timeout(None)
|
2021-12-27 16:04:48 -08:00
|
|
|
|
.build()?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
|
|
|
|
let progress_bar = new_spinner_progress_bar();
|
2022-12-06 06:30:06 -08:00
|
|
|
|
progress_bar.set_message(format!("{TRUCK}Downloading..."));
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
2019-09-24 13:10:59 -07:00
|
|
|
|
let response = client.get(url.as_str()).send()?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
let download_size = {
|
|
|
|
|
response
|
2019-09-24 13:10:59 -07:00
|
|
|
|
.headers()
|
|
|
|
|
.get(reqwest::header::CONTENT_LENGTH)
|
|
|
|
|
.and_then(|content_length| content_length.to_str().ok())
|
2019-03-20 16:12:50 -07:00
|
|
|
|
.and_then(|content_length| content_length.parse().ok())
|
|
|
|
|
.unwrap_or(0)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
progress_bar.set_length(download_size);
|
|
|
|
|
progress_bar.set_style(
|
|
|
|
|
ProgressStyle::default_bar()
|
2021-04-14 15:27:50 -07:00
|
|
|
|
.template(
|
|
|
|
|
"{spinner:.green}{wide_msg} [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})",
|
|
|
|
|
)
|
2022-08-05 14:11:17 -07:00
|
|
|
|
.expect("ProgresStyle::template direct input to be correct")
|
2019-03-20 16:12:50 -07:00
|
|
|
|
.progress_chars("=> "),
|
|
|
|
|
);
|
2022-12-06 06:30:06 -08:00
|
|
|
|
progress_bar.set_message(format!("{TRUCK}Downloading"));
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
|
|
|
|
struct DownloadProgress<R> {
|
|
|
|
|
progress_bar: ProgressBar,
|
|
|
|
|
response: R,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<R: Read> Read for DownloadProgress<R> {
|
|
|
|
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
|
|
|
self.response.read(buf).map(|n| {
|
|
|
|
|
self.progress_bar.inc(n as u64);
|
|
|
|
|
n
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut source = DownloadProgress {
|
|
|
|
|
progress_bar,
|
2019-09-24 13:10:59 -07:00
|
|
|
|
response,
|
2019-03-20 16:12:50 -07:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut file = File::create(&temp_file)?;
|
|
|
|
|
std::io::copy(&mut source, &mut file)?;
|
|
|
|
|
|
|
|
|
|
let temp_file_sha256 = sha256_file_digest(&temp_file)
|
2022-12-06 06:30:06 -08:00
|
|
|
|
.map_err(|err| format!("Unable to hash {temp_file:?}: {err}"))?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
|
|
|
|
if expected_sha256.is_some() && expected_sha256 != Some(&temp_file_sha256) {
|
2019-10-02 18:04:18 -07:00
|
|
|
|
return Err(io::Error::new(io::ErrorKind::Other, "Incorrect hash").into());
|
2019-03-20 16:12:50 -07:00
|
|
|
|
}
|
2019-09-29 17:56:33 -07:00
|
|
|
|
|
|
|
|
|
source.progress_bar.finish_and_clear();
|
2019-03-20 16:12:50 -07:00
|
|
|
|
Ok((temp_dir, temp_file, temp_file_sha256))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Extracts the release archive into the specified directory
|
|
|
|
|
fn extract_release_archive(
|
|
|
|
|
archive: &Path,
|
|
|
|
|
extract_dir: &Path,
|
|
|
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
2021-12-03 09:00:31 -08:00
|
|
|
|
use {bzip2::bufread::BzDecoder, tar::Archive};
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
|
|
|
|
let progress_bar = new_spinner_progress_bar();
|
2022-12-06 06:30:06 -08:00
|
|
|
|
progress_bar.set_message(format!("{PACKAGE}Extracting..."));
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
2021-01-28 20:38:33 -08:00
|
|
|
|
if extract_dir.exists() {
|
2022-09-22 15:23:03 -07:00
|
|
|
|
let _ = fs::remove_dir_all(extract_dir);
|
2021-01-28 20:38:33 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let tmp_extract_dir = extract_dir.with_file_name("tmp-extract");
|
|
|
|
|
if tmp_extract_dir.exists() {
|
|
|
|
|
let _ = fs::remove_dir_all(&tmp_extract_dir);
|
|
|
|
|
}
|
|
|
|
|
fs::create_dir_all(&tmp_extract_dir)?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
|
|
|
|
let tar_bz2 = File::open(archive)?;
|
|
|
|
|
let tar = BzDecoder::new(BufReader::new(tar_bz2));
|
|
|
|
|
let mut release = Archive::new(tar);
|
2021-01-28 20:38:33 -08:00
|
|
|
|
release.unpack(&tmp_extract_dir)?;
|
|
|
|
|
|
|
|
|
|
fs::rename(&tmp_extract_dir, extract_dir)?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
|
|
|
|
progress_bar.finish_and_clear();
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-10 22:19:52 -08:00
|
|
|
|
fn load_release_version(version_yml: &Path) -> Result<ReleaseVersion, String> {
|
2022-09-22 15:23:03 -07:00
|
|
|
|
let file = File::open(version_yml)
|
2022-12-06 06:30:06 -08:00
|
|
|
|
.map_err(|err| format!("Unable to open {version_yml:?}: {err:?}"))?;
|
2020-02-10 22:19:52 -08:00
|
|
|
|
let version: ReleaseVersion = serde_yaml::from_reader(file)
|
2022-12-06 06:30:06 -08:00
|
|
|
|
.map_err(|err| format!("Unable to parse {version_yml:?}: {err:?}"))?;
|
2020-02-10 22:19:52 -08:00
|
|
|
|
Ok(version)
|
|
|
|
|
}
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
2020-02-10 22:19:52 -08:00
|
|
|
|
/// Reads the supported TARGET triple for the given release
|
|
|
|
|
fn load_release_target(release_dir: &Path) -> Result<String, String> {
|
2019-03-20 16:12:50 -07:00
|
|
|
|
let mut version_yml = PathBuf::from(release_dir);
|
|
|
|
|
version_yml.push("solana-release");
|
|
|
|
|
version_yml.push("version.yml");
|
|
|
|
|
|
2020-02-10 22:19:52 -08:00
|
|
|
|
let version = load_release_version(&version_yml)?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
Ok(version.target)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Time in seconds since the UNIX_EPOCH
|
|
|
|
|
fn timestamp_secs() -> u64 {
|
|
|
|
|
SystemTime::now()
|
|
|
|
|
.duration_since(SystemTime::UNIX_EPOCH)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.as_secs()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create an empty update manifest for the given `update_manifest_keypair` if it doesn't already
|
|
|
|
|
/// exist on the cluster
|
|
|
|
|
fn new_update_manifest(
|
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
|
from_keypair: &Keypair,
|
|
|
|
|
update_manifest_keypair: &Keypair,
|
|
|
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
|
if rpc_client
|
|
|
|
|
.get_account_data(&update_manifest_keypair.pubkey())
|
|
|
|
|
.is_err()
|
|
|
|
|
{
|
2021-08-13 09:08:20 -07:00
|
|
|
|
let recent_blockhash = rpc_client.get_latest_blockhash()?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
2019-12-24 18:01:21 -08:00
|
|
|
|
let lamports = rpc_client
|
|
|
|
|
.get_minimum_balance_for_rent_exemption(SignedUpdateManifest::max_space() as usize)?;
|
|
|
|
|
|
2020-04-24 12:03:46 -07:00
|
|
|
|
let instructions = config_instruction::create_account::<SignedUpdateManifest>(
|
2019-03-20 16:12:50 -07:00
|
|
|
|
&from_keypair.pubkey(),
|
|
|
|
|
&update_manifest_keypair.pubkey(),
|
2019-12-24 18:01:21 -08:00
|
|
|
|
lamports,
|
2019-07-08 16:33:56 -07:00
|
|
|
|
vec![], // additional keys
|
2019-03-20 16:12:50 -07:00
|
|
|
|
);
|
2020-06-24 13:52:38 -07:00
|
|
|
|
let message = Message::new(&instructions, Some(&from_keypair.pubkey()));
|
2019-08-15 09:05:51 -07:00
|
|
|
|
let signers = [from_keypair, update_manifest_keypair];
|
2020-06-24 13:52:38 -07:00
|
|
|
|
let transaction = Transaction::new(&signers, message, recent_blockhash);
|
2020-05-09 09:06:32 -07:00
|
|
|
|
rpc_client.send_and_confirm_transaction(&transaction)?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Update the update manifest on the cluster with new content
|
|
|
|
|
fn store_update_manifest(
|
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
|
from_keypair: &Keypair,
|
|
|
|
|
update_manifest_keypair: &Keypair,
|
|
|
|
|
update_manifest: &SignedUpdateManifest,
|
|
|
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
2021-08-13 09:08:20 -07:00
|
|
|
|
let recent_blockhash = rpc_client.get_latest_blockhash()?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
2019-05-12 22:47:12 -07:00
|
|
|
|
let signers = [from_keypair, update_manifest_keypair];
|
|
|
|
|
let instruction = config_instruction::store::<SignedUpdateManifest>(
|
2019-03-20 16:12:50 -07:00
|
|
|
|
&update_manifest_keypair.pubkey(),
|
2019-07-09 12:37:18 -07:00
|
|
|
|
true, // update_manifest_keypair is signer
|
2019-07-08 16:33:56 -07:00
|
|
|
|
vec![], // additional keys
|
2019-03-20 16:12:50 -07:00
|
|
|
|
update_manifest,
|
|
|
|
|
);
|
2019-05-12 22:47:12 -07:00
|
|
|
|
|
2020-06-24 13:52:38 -07:00
|
|
|
|
let message = Message::new(&[instruction], Some(&from_keypair.pubkey()));
|
2020-05-09 09:06:32 -07:00
|
|
|
|
let transaction = Transaction::new(&signers, message, recent_blockhash);
|
|
|
|
|
rpc_client.send_and_confirm_transaction(&transaction)?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Read the current contents of the update manifest from the cluster
|
|
|
|
|
fn get_update_manifest(
|
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
|
update_manifest_pubkey: &Pubkey,
|
|
|
|
|
) -> Result<UpdateManifest, String> {
|
2019-08-14 15:54:31 -07:00
|
|
|
|
let data = rpc_client
|
2019-03-20 16:12:50 -07:00
|
|
|
|
.get_account_data(update_manifest_pubkey)
|
2022-12-06 06:30:06 -08:00
|
|
|
|
.map_err(|err| format!("Unable to fetch update manifest: {err}"))?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
2019-08-14 15:54:31 -07:00
|
|
|
|
let config_data = get_config_data(&data)
|
2022-12-06 06:30:06 -08:00
|
|
|
|
.map_err(|err| format!("Unable to get at config_data to update manifest: {err}"))?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
let signed_update_manifest =
|
2019-08-14 15:54:31 -07:00
|
|
|
|
SignedUpdateManifest::deserialize(update_manifest_pubkey, config_data)
|
2022-12-06 06:30:06 -08:00
|
|
|
|
.map_err(|err| format!("Unable to deserialize update manifest: {err}"))?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
Ok(signed_update_manifest.manifest)
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-12 14:13:01 -07:00
|
|
|
|
/// Bug the user if active_release_bin_dir is not in their PATH
|
2019-03-20 16:12:50 -07:00
|
|
|
|
fn check_env_path_for_bin_dir(config: &Config) {
|
|
|
|
|
use std::env;
|
|
|
|
|
|
2019-04-27 09:11:02 -07:00
|
|
|
|
let bin_dir = config
|
|
|
|
|
.active_release_bin_dir()
|
|
|
|
|
.canonicalize()
|
|
|
|
|
.unwrap_or_default();
|
2019-03-20 16:12:50 -07:00
|
|
|
|
let found = match env::var_os("PATH") {
|
|
|
|
|
Some(paths) => env::split_paths(&paths).any(|path| {
|
|
|
|
|
if let Ok(path) = path.canonicalize() {
|
2019-04-12 14:13:01 -07:00
|
|
|
|
if path == bin_dir {
|
2019-03-20 16:12:50 -07:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
false
|
|
|
|
|
}),
|
|
|
|
|
None => false,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if !found {
|
|
|
|
|
println!(
|
|
|
|
|
"\nPlease update your PATH environment variable to include the solana programs:\n PATH=\"{}:$PATH\"\n",
|
2019-06-08 17:23:35 -07:00
|
|
|
|
config.active_release_bin_dir().to_str().unwrap()
|
2019-03-20 16:12:50 -07:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-03-15 10:54:54 -07:00
|
|
|
|
|
2019-06-08 19:25:02 -07:00
|
|
|
|
/// Encodes a UTF-8 string as a null-terminated UCS-2 string in bytes
|
|
|
|
|
#[cfg(windows)]
|
|
|
|
|
pub fn string_to_winreg_bytes(s: &str) -> Vec<u8> {
|
2021-12-03 09:00:31 -08:00
|
|
|
|
use std::{ffi::OsString, os::windows::ffi::OsStrExt};
|
2019-06-08 19:25:02 -07:00
|
|
|
|
let v: Vec<_> = OsString::from(format!("{}\x00", s)).encode_wide().collect();
|
|
|
|
|
unsafe { std::slice::from_raw_parts(v.as_ptr() as *const u8, v.len() * 2).to_vec() }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This is used to decode the value of HKCU\Environment\PATH. If that
|
|
|
|
|
// key is not Unicode (or not REG_SZ | REG_EXPAND_SZ) then this
|
|
|
|
|
// returns null. The winreg library itself does a lossy unicode
|
|
|
|
|
// conversion.
|
|
|
|
|
#[cfg(windows)]
|
|
|
|
|
pub fn string_from_winreg_value(val: &winreg::RegValue) -> Option<String> {
|
2021-12-03 09:00:31 -08:00
|
|
|
|
use {std::slice, winreg::enums::RegType};
|
2019-06-08 19:25:02 -07:00
|
|
|
|
|
|
|
|
|
match val.vtype {
|
|
|
|
|
RegType::REG_SZ | RegType::REG_EXPAND_SZ => {
|
|
|
|
|
// Copied from winreg
|
|
|
|
|
let words = unsafe {
|
|
|
|
|
slice::from_raw_parts(val.bytes.as_ptr() as *const u16, val.bytes.len() / 2)
|
|
|
|
|
};
|
|
|
|
|
let mut s = if let Ok(s) = String::from_utf16(words) {
|
|
|
|
|
s
|
|
|
|
|
} else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
while s.ends_with('\u{0}') {
|
|
|
|
|
s.pop();
|
|
|
|
|
}
|
|
|
|
|
Some(s)
|
2019-06-08 17:15:16 -07:00
|
|
|
|
}
|
2019-06-08 19:25:02 -07:00
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Get the windows PATH variable out of the registry as a String. If
|
|
|
|
|
// this returns None then the PATH variable is not Unicode and we
|
|
|
|
|
// should not mess with it.
|
|
|
|
|
#[cfg(windows)]
|
|
|
|
|
fn get_windows_path_var() -> Result<Option<String>, String> {
|
2021-12-03 09:00:31 -08:00
|
|
|
|
use winreg::{
|
|
|
|
|
enums::{HKEY_CURRENT_USER, KEY_READ, KEY_WRITE},
|
|
|
|
|
RegKey,
|
|
|
|
|
};
|
2019-06-08 19:25:02 -07:00
|
|
|
|
|
|
|
|
|
let root = RegKey::predef(HKEY_CURRENT_USER);
|
|
|
|
|
let environment = root
|
|
|
|
|
.open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE)
|
|
|
|
|
.map_err(|err| format!("Unable to open HKEY_CURRENT_USER\\Environment: {}", err))?;
|
|
|
|
|
|
|
|
|
|
let reg_value = environment.get_raw_value("PATH");
|
|
|
|
|
match reg_value {
|
|
|
|
|
Ok(val) => {
|
|
|
|
|
if let Some(s) = string_from_winreg_value(&val) {
|
|
|
|
|
Ok(Some(s))
|
|
|
|
|
} else {
|
|
|
|
|
println!("the registry key HKEY_CURRENT_USER\\Environment\\PATH does not contain valid Unicode. Not modifying the PATH variable");
|
2021-09-22 14:10:35 -07:00
|
|
|
|
Ok(None)
|
2019-06-08 19:25:02 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(ref e) if e.kind() == io::ErrorKind::NotFound => Ok(Some(String::new())),
|
|
|
|
|
Err(e) => Err(e.to_string()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(windows)]
|
2020-12-13 17:26:34 -08:00
|
|
|
|
fn add_to_path(new_path: &str) -> bool {
|
2021-12-03 09:00:31 -08:00
|
|
|
|
use {
|
|
|
|
|
std::ptr,
|
|
|
|
|
winapi::{
|
|
|
|
|
shared::minwindef::*,
|
|
|
|
|
um::winuser::{
|
|
|
|
|
SendMessageTimeoutA, HWND_BROADCAST, SMTO_ABORTIFHUNG, WM_SETTINGCHANGE,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
winreg::{
|
|
|
|
|
enums::{RegType, HKEY_CURRENT_USER, KEY_READ, KEY_WRITE},
|
|
|
|
|
RegKey, RegValue,
|
|
|
|
|
},
|
2019-06-08 17:15:16 -07:00
|
|
|
|
};
|
2019-03-21 12:00:45 -07:00
|
|
|
|
|
2020-12-24 13:56:20 -08:00
|
|
|
|
let old_path = if let Some(s) =
|
|
|
|
|
get_windows_path_var().unwrap_or_else(|err| panic!("Unable to get PATH: {}", err))
|
|
|
|
|
{
|
2019-06-08 19:25:02 -07:00
|
|
|
|
s
|
|
|
|
|
} else {
|
2020-12-13 17:26:34 -08:00
|
|
|
|
return false;
|
2019-06-08 19:25:02 -07:00
|
|
|
|
};
|
|
|
|
|
|
2022-11-08 06:59:56 -08:00
|
|
|
|
if !old_path.contains(new_path) {
|
2019-06-08 19:25:02 -07:00
|
|
|
|
let mut new_path = new_path.to_string();
|
|
|
|
|
if !old_path.is_empty() {
|
2021-09-22 14:10:35 -07:00
|
|
|
|
new_path.push(';');
|
2019-06-08 19:25:02 -07:00
|
|
|
|
new_path.push_str(&old_path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let root = RegKey::predef(HKEY_CURRENT_USER);
|
|
|
|
|
let environment = root
|
|
|
|
|
.open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE)
|
2020-12-24 13:56:20 -08:00
|
|
|
|
.unwrap_or_else(|err| panic!("Unable to open HKEY_CURRENT_USER\\Environment: {}", err));
|
2019-06-08 19:25:02 -07:00
|
|
|
|
|
|
|
|
|
let reg_value = RegValue {
|
|
|
|
|
bytes: string_to_winreg_bytes(&new_path),
|
|
|
|
|
vtype: RegType::REG_EXPAND_SZ,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
environment
|
|
|
|
|
.set_raw_value("PATH", ®_value)
|
2020-12-24 13:56:20 -08:00
|
|
|
|
.unwrap_or_else(|err| {
|
|
|
|
|
panic!("Unable set HKEY_CURRENT_USER\\Environment\\PATH: {}", err)
|
|
|
|
|
});
|
2019-06-08 19:25:02 -07:00
|
|
|
|
|
|
|
|
|
// Tell other processes to update their environment
|
|
|
|
|
unsafe {
|
|
|
|
|
SendMessageTimeoutA(
|
|
|
|
|
HWND_BROADCAST,
|
|
|
|
|
WM_SETTINGCHANGE,
|
2021-09-22 14:10:35 -07:00
|
|
|
|
0_usize,
|
2019-06-08 19:25:02 -07:00
|
|
|
|
"Environment\0".as_ptr() as LPARAM,
|
|
|
|
|
SMTO_ABORTIFHUNG,
|
|
|
|
|
5000,
|
|
|
|
|
ptr::null_mut(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
println!(
|
|
|
|
|
"\n{}\n {}\n\n{}",
|
|
|
|
|
style("The HKEY_CURRENT_USER/Environment/PATH registry key has been modified to include:").bold(),
|
|
|
|
|
new_path,
|
|
|
|
|
style("Future applications will automatically have the correct environment, but you may need to restart your current shell.").bold()
|
2019-03-21 12:00:45 -07:00
|
|
|
|
);
|
2020-12-13 17:26:34 -08:00
|
|
|
|
true
|
2019-06-08 19:25:02 -07:00
|
|
|
|
}
|
2019-03-21 12:00:45 -07:00
|
|
|
|
|
2019-06-08 19:25:02 -07:00
|
|
|
|
#[cfg(unix)]
|
2020-12-13 17:26:34 -08:00
|
|
|
|
fn add_to_path(new_path: &str) -> bool {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
let shell_export_string = format!("\nexport PATH=\"{new_path}:$PATH\"");
|
2019-06-08 19:25:02 -07:00
|
|
|
|
let mut modified_rcfiles = false;
|
|
|
|
|
|
|
|
|
|
// Look for sh, bash, and zsh rc files
|
2020-10-31 09:09:17 -07:00
|
|
|
|
let mut rcfiles = vec![dirs_next::home_dir().map(|p| p.join(".profile"))];
|
2019-06-08 19:25:02 -07:00
|
|
|
|
if let Ok(shell) = std::env::var("SHELL") {
|
|
|
|
|
if shell.contains("zsh") {
|
|
|
|
|
let zdotdir = std::env::var("ZDOTDIR")
|
|
|
|
|
.ok()
|
|
|
|
|
.map(PathBuf::from)
|
2020-10-31 09:09:17 -07:00
|
|
|
|
.or_else(dirs_next::home_dir);
|
2019-06-08 19:25:02 -07:00
|
|
|
|
let zprofile = zdotdir.map(|p| p.join(".zprofile"));
|
|
|
|
|
rcfiles.push(zprofile);
|
2019-03-21 12:00:45 -07:00
|
|
|
|
}
|
2019-06-08 19:25:02 -07:00
|
|
|
|
}
|
2019-03-21 12:00:45 -07:00
|
|
|
|
|
2020-10-31 09:09:17 -07:00
|
|
|
|
if let Some(bash_profile) = dirs_next::home_dir().map(|p| p.join(".bash_profile")) {
|
2019-06-08 19:25:02 -07:00
|
|
|
|
// Only update .bash_profile if it exists because creating .bash_profile
|
|
|
|
|
// will cause .profile to not be read
|
|
|
|
|
if bash_profile.exists() {
|
|
|
|
|
rcfiles.push(Some(bash_profile));
|
2019-03-21 12:00:45 -07:00
|
|
|
|
}
|
2019-06-08 19:25:02 -07:00
|
|
|
|
}
|
|
|
|
|
let rcfiles = rcfiles.into_iter().filter_map(|f| f.filter(|f| f.exists()));
|
2019-03-21 12:00:45 -07:00
|
|
|
|
|
2019-06-08 19:25:02 -07:00
|
|
|
|
// For each rc file, append a PATH entry if not already present
|
|
|
|
|
for rcfile in rcfiles {
|
|
|
|
|
if !rcfile.exists() {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2019-03-21 12:00:45 -07:00
|
|
|
|
|
2019-06-08 19:25:02 -07:00
|
|
|
|
fn read_file(path: &Path) -> io::Result<String> {
|
|
|
|
|
let mut file = fs::OpenOptions::new().read(true).open(path)?;
|
|
|
|
|
let mut contents = String::new();
|
|
|
|
|
io::Read::read_to_string(&mut file, &mut contents)?;
|
|
|
|
|
Ok(contents)
|
|
|
|
|
}
|
2019-03-21 12:00:45 -07:00
|
|
|
|
|
2019-06-08 19:25:02 -07:00
|
|
|
|
match read_file(&rcfile) {
|
|
|
|
|
Err(err) => {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
println!("Unable to read {rcfile:?}: {err}");
|
2019-06-08 19:25:02 -07:00
|
|
|
|
}
|
|
|
|
|
Ok(contents) => {
|
|
|
|
|
if !contents.contains(&shell_export_string) {
|
|
|
|
|
println!(
|
|
|
|
|
"Adding {} to {}",
|
|
|
|
|
style(&shell_export_string).italic(),
|
|
|
|
|
style(rcfile.to_str().unwrap()).bold()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
fn append_file(dest: &Path, line: &str) -> io::Result<()> {
|
|
|
|
|
use std::io::Write;
|
|
|
|
|
let mut dest_file = fs::OpenOptions::new()
|
|
|
|
|
.write(true)
|
|
|
|
|
.append(true)
|
|
|
|
|
.create(true)
|
|
|
|
|
.open(dest)?;
|
|
|
|
|
|
2022-12-06 06:30:06 -08:00
|
|
|
|
writeln!(&mut dest_file, "{line}")?;
|
2019-06-08 19:25:02 -07:00
|
|
|
|
|
|
|
|
|
dest_file.sync_data()?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
2019-03-21 12:00:45 -07:00
|
|
|
|
}
|
2019-06-08 19:25:02 -07:00
|
|
|
|
append_file(&rcfile, &shell_export_string).unwrap_or_else(|err| {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
format!("Unable to append to {rcfile:?}: {err}");
|
2019-06-08 19:25:02 -07:00
|
|
|
|
});
|
|
|
|
|
modified_rcfiles = true;
|
2019-03-21 12:00:45 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if modified_rcfiles {
|
|
|
|
|
println!(
|
|
|
|
|
"\n{}\n {}\n",
|
2019-04-12 14:13:01 -07:00
|
|
|
|
style("Close and reopen your terminal to apply the PATH changes or run the following in your existing shell:").bold().blue(),
|
2019-03-21 12:00:45 -07:00
|
|
|
|
shell_export_string
|
|
|
|
|
);
|
2019-06-08 19:25:02 -07:00
|
|
|
|
}
|
|
|
|
|
|
2020-12-13 17:26:34 -08:00
|
|
|
|
modified_rcfiles
|
2019-06-08 19:25:02 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn init(
|
|
|
|
|
config_file: &str,
|
|
|
|
|
data_dir: &str,
|
|
|
|
|
json_rpc_url: &str,
|
|
|
|
|
update_manifest_pubkey: &Pubkey,
|
|
|
|
|
no_modify_path: bool,
|
2019-07-31 17:30:17 -07:00
|
|
|
|
explicit_release: Option<ExplicitRelease>,
|
2019-06-08 19:25:02 -07:00
|
|
|
|
) -> 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;
|
2019-07-23 12:51:10 -07:00
|
|
|
|
let config = Config::new(
|
|
|
|
|
data_dir,
|
|
|
|
|
json_rpc_url,
|
|
|
|
|
update_manifest_pubkey,
|
2019-07-31 17:30:17 -07:00
|
|
|
|
explicit_release,
|
2019-07-23 12:51:10 -07:00
|
|
|
|
);
|
2019-06-08 19:25:02 -07:00
|
|
|
|
if current_config != config {
|
|
|
|
|
config.save(config_file)?;
|
|
|
|
|
}
|
|
|
|
|
config
|
|
|
|
|
};
|
|
|
|
|
|
2021-03-11 17:52:16 -08:00
|
|
|
|
init_or_update(config_file, true, false)?;
|
2019-06-08 19:25:02 -07:00
|
|
|
|
|
|
|
|
|
let path_modified = if !no_modify_path {
|
2021-06-18 06:34:46 -07:00
|
|
|
|
add_to_path(config.active_release_bin_dir().to_str().unwrap())
|
2019-03-21 12:00:45 -07:00
|
|
|
|
} else {
|
2019-06-08 19:25:02 -07:00
|
|
|
|
false
|
|
|
|
|
};
|
|
|
|
|
|
2019-06-28 16:45:01 -07:00
|
|
|
|
if !path_modified && !no_modify_path {
|
2019-03-21 12:00:45 -07:00
|
|
|
|
check_env_path_for_bin_dir(&config);
|
|
|
|
|
}
|
2019-03-20 16:12:50 -07:00
|
|
|
|
Ok(())
|
2019-03-15 10:54:54 -07:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-31 17:30:17 -07:00
|
|
|
|
fn github_release_download_url(release_semver: &str) -> String {
|
2019-07-23 12:51:10 -07:00
|
|
|
|
format!(
|
|
|
|
|
"https://github.com/solana-labs/solana/releases/download/v{}/solana-release-{}.tar.bz2",
|
|
|
|
|
release_semver,
|
|
|
|
|
crate::build_env::TARGET
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-31 17:30:17 -07:00
|
|
|
|
fn release_channel_download_url(release_channel: &str) -> String {
|
|
|
|
|
format!(
|
2022-01-27 08:48:37 -08:00
|
|
|
|
"https://release.solana.com/{}/solana-release-{}.tar.bz2",
|
2019-07-31 17:30:17 -07:00
|
|
|
|
release_channel,
|
|
|
|
|
crate::build_env::TARGET
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-10 22:19:52 -08:00
|
|
|
|
fn release_channel_version_url(release_channel: &str) -> String {
|
|
|
|
|
format!(
|
2022-01-27 08:48:37 -08:00
|
|
|
|
"https://release.solana.com/{}/solana-release-{}.yml",
|
2020-02-10 22:19:52 -08:00
|
|
|
|
release_channel,
|
|
|
|
|
crate::build_env::TARGET
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-11 17:52:16 -08:00
|
|
|
|
fn print_update_manifest(update_manifest: &UpdateManifest) {
|
2023-01-17 20:54:02 -08:00
|
|
|
|
let when = Local
|
|
|
|
|
.timestamp_opt(update_manifest.timestamp_secs as i64, 0)
|
|
|
|
|
.unwrap();
|
2022-12-06 06:30:06 -08:00
|
|
|
|
println_name_value(&format!("{BULLET}release date:"), &when.to_string());
|
2021-03-11 17:52:16 -08:00
|
|
|
|
println_name_value(
|
2022-12-06 06:30:06 -08:00
|
|
|
|
&format!("{BULLET}download URL:"),
|
2021-03-11 17:52:16 -08:00
|
|
|
|
&update_manifest.download_url,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn info(config_file: &str, local_info_only: bool, eval: bool) -> Result<(), String> {
|
2019-03-15 10:54:54 -07:00
|
|
|
|
let config = Config::load(config_file)?;
|
2019-07-23 12:51:10 -07:00
|
|
|
|
|
2020-10-20 12:09:12 -07:00
|
|
|
|
if eval {
|
|
|
|
|
println!(
|
|
|
|
|
"SOLANA_INSTALL_ACTIVE_RELEASE={}",
|
|
|
|
|
&config.active_release_dir().to_str().unwrap_or("")
|
|
|
|
|
);
|
2021-01-26 13:15:25 -08:00
|
|
|
|
config
|
|
|
|
|
.explicit_release
|
|
|
|
|
.map(|er| match er {
|
|
|
|
|
ExplicitRelease::Semver(semver) => semver,
|
|
|
|
|
ExplicitRelease::Channel(channel) => channel,
|
|
|
|
|
})
|
|
|
|
|
.and_then(|channel| {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
println!("SOLANA_INSTALL_ACTIVE_CHANNEL={channel}",);
|
2021-01-26 13:15:25 -08:00
|
|
|
|
Option::<String>::None
|
|
|
|
|
});
|
2021-03-11 17:52:16 -08:00
|
|
|
|
return Ok(());
|
2020-10-20 12:09:12 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-18 06:34:46 -07:00
|
|
|
|
println_name_value("Configuration:", config_file);
|
2019-06-08 19:25:02 -07:00
|
|
|
|
println_name_value(
|
|
|
|
|
"Active release directory:",
|
2021-06-18 06:34:46 -07:00
|
|
|
|
config.active_release_dir().to_str().unwrap_or("?"),
|
2019-06-08 19:25:02 -07:00
|
|
|
|
);
|
2019-07-31 17:30:17 -07:00
|
|
|
|
|
2021-03-11 17:52:16 -08:00
|
|
|
|
fn print_release_version(config: &Config) {
|
|
|
|
|
if let Ok(release_version) =
|
|
|
|
|
load_release_version(&config.active_release_dir().join("version.yml"))
|
|
|
|
|
{
|
|
|
|
|
println_name_value(
|
2022-12-06 06:30:06 -08:00
|
|
|
|
&format!("{BULLET}Release commit:"),
|
2021-03-11 17:52:16 -08:00
|
|
|
|
&release_version.commit[0..7],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-31 17:30:17 -07:00
|
|
|
|
if let Some(explicit_release) = &config.explicit_release {
|
|
|
|
|
match explicit_release {
|
|
|
|
|
ExplicitRelease::Semver(release_semver) => {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
println_name_value(&format!("{BULLET}Release version:"), release_semver);
|
2019-07-31 17:30:17 -07:00
|
|
|
|
println_name_value(
|
2022-12-06 06:30:06 -08:00
|
|
|
|
&format!("{BULLET}Release URL:"),
|
2019-07-31 17:30:17 -07:00
|
|
|
|
&github_release_download_url(release_semver),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
ExplicitRelease::Channel(release_channel) => {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
println_name_value(&format!("{BULLET}Release channel:"), release_channel);
|
2019-07-31 17:30:17 -07:00
|
|
|
|
println_name_value(
|
2022-12-06 06:30:06 -08:00
|
|
|
|
&format!("{BULLET}Release URL:"),
|
2019-07-31 17:30:17 -07:00
|
|
|
|
&release_channel_download_url(release_channel),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-11 17:52:16 -08:00
|
|
|
|
print_release_version(&config);
|
|
|
|
|
} else {
|
|
|
|
|
println_name_value("JSON RPC URL:", &config.json_rpc_url);
|
2019-03-20 16:12:50 -07:00
|
|
|
|
println_name_value(
|
2021-03-11 17:52:16 -08:00
|
|
|
|
"Update manifest pubkey:",
|
|
|
|
|
&config.update_manifest_pubkey.to_string(),
|
2019-03-20 16:12:50 -07:00
|
|
|
|
);
|
2019-03-15 10:54:54 -07:00
|
|
|
|
|
2021-03-11 17:52:16 -08:00
|
|
|
|
match config.current_update_manifest {
|
|
|
|
|
Some(ref update_manifest) => {
|
|
|
|
|
println_name_value("Installed version:", "");
|
|
|
|
|
print_release_version(&config);
|
2021-06-18 06:34:46 -07:00
|
|
|
|
print_update_manifest(update_manifest);
|
2021-03-11 17:52:16 -08:00
|
|
|
|
}
|
|
|
|
|
None => {
|
|
|
|
|
println_name_value("Installed version:", "None");
|
|
|
|
|
}
|
2019-03-20 16:12:50 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-03-15 10:54:54 -07:00
|
|
|
|
|
2019-03-20 16:12:50 -07:00
|
|
|
|
if local_info_only {
|
2021-03-11 17:52:16 -08:00
|
|
|
|
Ok(())
|
2019-03-20 16:12:50 -07:00
|
|
|
|
} else {
|
2021-03-11 17:52:16 -08:00
|
|
|
|
update(config_file, true).map(|_| ())
|
2019-03-20 16:12:50 -07:00
|
|
|
|
}
|
2019-03-15 10:54:54 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn deploy(
|
2019-03-20 16:12:50 -07:00
|
|
|
|
json_rpc_url: &str,
|
|
|
|
|
from_keypair_file: &str,
|
2019-03-15 10:54:54 -07:00
|
|
|
|
download_url: &str,
|
2019-03-20 16:12:50 -07:00
|
|
|
|
update_manifest_keypair_file: &str,
|
2019-03-15 10:54:54 -07:00
|
|
|
|
) -> Result<(), String> {
|
2019-10-10 16:01:03 -07:00
|
|
|
|
let from_keypair = read_keypair_file(from_keypair_file)
|
2022-12-06 06:30:06 -08:00
|
|
|
|
.map_err(|err| format!("Unable to read {from_keypair_file}: {err}"))?;
|
2019-10-10 16:01:03 -07:00
|
|
|
|
let update_manifest_keypair = read_keypair_file(update_manifest_keypair_file)
|
2022-12-06 06:30:06 -08:00
|
|
|
|
.map_err(|err| format!("Unable to read {update_manifest_keypair_file}: {err}"))?;
|
2019-03-15 10:54:54 -07:00
|
|
|
|
|
2019-09-19 12:03:47 -07:00
|
|
|
|
println_name_value("JSON RPC URL:", json_rpc_url);
|
|
|
|
|
println_name_value(
|
|
|
|
|
"Update manifest pubkey:",
|
|
|
|
|
&update_manifest_keypair.pubkey().to_string(),
|
|
|
|
|
);
|
|
|
|
|
|
2019-03-21 12:18:33 -07:00
|
|
|
|
// Confirm the `json_rpc_url` is good and that `from_keypair` is a valid account
|
|
|
|
|
let rpc_client = RpcClient::new(json_rpc_url.to_string());
|
|
|
|
|
let progress_bar = new_spinner_progress_bar();
|
2022-12-06 06:30:06 -08:00
|
|
|
|
progress_bar.set_message(format!("{LOOKING_GLASS}Checking cluster..."));
|
2019-03-21 12:18:33 -07:00
|
|
|
|
let balance = rpc_client
|
2020-05-14 11:24:14 -07:00
|
|
|
|
.get_balance(&from_keypair.pubkey())
|
2019-03-21 12:18:33 -07:00
|
|
|
|
.map_err(|err| {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
format!("Unable to get the account balance of {from_keypair_file}: {err}")
|
2019-03-21 12:18:33 -07:00
|
|
|
|
})?;
|
|
|
|
|
progress_bar.finish_and_clear();
|
2020-05-14 11:24:14 -07:00
|
|
|
|
if balance == 0 {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
return Err(format!("{from_keypair_file} account balance is empty"));
|
2019-03-21 12:18:33 -07:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-20 16:12:50 -07:00
|
|
|
|
// Download the release
|
2020-02-10 22:19:52 -08:00
|
|
|
|
let (temp_dir, temp_archive, temp_archive_sha256) = download_to_temp(download_url, None)
|
2022-12-06 06:30:06 -08:00
|
|
|
|
.map_err(|err| format!("Unable to download {download_url}: {err}"))?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
2019-09-19 12:03:47 -07:00
|
|
|
|
if let Ok(update_manifest) = get_update_manifest(&rpc_client, &update_manifest_keypair.pubkey())
|
|
|
|
|
{
|
|
|
|
|
if temp_archive_sha256 == update_manifest.download_sha256 {
|
|
|
|
|
println!(
|
|
|
|
|
" {}{}",
|
|
|
|
|
INFORMATION,
|
|
|
|
|
style("Update is already deployed").bold()
|
|
|
|
|
);
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-20 16:12:50 -07:00
|
|
|
|
// Extract it and load the release version metadata
|
|
|
|
|
let temp_release_dir = temp_dir.path().join("archive");
|
|
|
|
|
extract_release_archive(&temp_archive, &temp_release_dir).map_err(|err| {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
format!("Unable to extract {temp_archive:?} into {temp_release_dir:?}: {err}")
|
2019-03-20 16:12:50 -07:00
|
|
|
|
})?;
|
|
|
|
|
|
2022-12-06 06:30:06 -08:00
|
|
|
|
let release_target = load_release_target(&temp_release_dir)
|
|
|
|
|
.map_err(|err| format!("Unable to load release target from {temp_release_dir:?}: {err}"))?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
|
|
|
|
println_name_value("Update target:", &release_target);
|
|
|
|
|
|
|
|
|
|
let progress_bar = new_spinner_progress_bar();
|
2022-12-06 06:30:06 -08:00
|
|
|
|
progress_bar.set_message(format!("{PACKAGE}Deploying update..."));
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
|
|
|
|
// Construct an update manifest for the release
|
|
|
|
|
let mut update_manifest = SignedUpdateManifest {
|
|
|
|
|
account_pubkey: update_manifest_keypair.pubkey(),
|
|
|
|
|
..SignedUpdateManifest::default()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
update_manifest.manifest.timestamp_secs = timestamp_secs();
|
|
|
|
|
update_manifest.manifest.download_url = download_url.to_string();
|
|
|
|
|
update_manifest.manifest.download_sha256 = temp_archive_sha256;
|
|
|
|
|
|
|
|
|
|
update_manifest.sign(&update_manifest_keypair);
|
|
|
|
|
assert!(update_manifest.verify());
|
|
|
|
|
|
|
|
|
|
// Store the new update manifest on the cluster
|
|
|
|
|
new_update_manifest(&rpc_client, &from_keypair, &update_manifest_keypair)
|
2022-12-06 06:30:06 -08:00
|
|
|
|
.map_err(|err| format!("Unable to create update manifest: {err}"))?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
store_update_manifest(
|
|
|
|
|
&rpc_client,
|
|
|
|
|
&from_keypair,
|
|
|
|
|
&update_manifest_keypair,
|
|
|
|
|
&update_manifest,
|
|
|
|
|
)
|
2022-12-06 06:30:06 -08:00
|
|
|
|
.map_err(|err| format!("Unable to store update manifest: {err:?}"))?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
|
|
|
|
progress_bar.finish_and_clear();
|
|
|
|
|
println!(" {}{}", SPARKLE, style("Deployment successful").bold());
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-04 08:51:20 -07:00
|
|
|
|
#[cfg(windows)]
|
|
|
|
|
fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> std::io::Result<()> {
|
|
|
|
|
std::os::windows::fs::symlink_dir(src, dst)
|
|
|
|
|
}
|
|
|
|
|
#[cfg(not(windows))]
|
|
|
|
|
fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> std::io::Result<()> {
|
|
|
|
|
std::os::unix::fs::symlink(src, dst)
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-29 15:38:54 -08:00
|
|
|
|
pub fn gc(config_file: &str) -> Result<(), String> {
|
|
|
|
|
let config = Config::load(config_file)?;
|
|
|
|
|
|
|
|
|
|
let entries = fs::read_dir(&config.releases_dir)
|
|
|
|
|
.map_err(|err| format!("Unable to read {}: {}", config.releases_dir.display(), err))?;
|
|
|
|
|
|
|
|
|
|
let mut releases = entries
|
|
|
|
|
.filter_map(|entry| entry.ok())
|
|
|
|
|
.filter_map(|entry| {
|
|
|
|
|
entry
|
|
|
|
|
.metadata()
|
|
|
|
|
.ok()
|
|
|
|
|
.map(|metadata| (entry.path(), metadata))
|
|
|
|
|
})
|
|
|
|
|
.filter_map(|(release_path, metadata)| {
|
|
|
|
|
if metadata.is_dir() {
|
|
|
|
|
Some((release_path, metadata))
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.filter_map(|(release_path, metadata)| {
|
|
|
|
|
metadata
|
|
|
|
|
.modified()
|
|
|
|
|
.ok()
|
|
|
|
|
.map(|modified_time| (release_path, modified_time))
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
releases.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); // order by newest releases
|
|
|
|
|
|
2021-02-03 09:32:57 -08:00
|
|
|
|
const MAX_CACHE_LEN: usize = 5;
|
|
|
|
|
if releases.len() > MAX_CACHE_LEN {
|
|
|
|
|
let old_releases = releases.split_off(MAX_CACHE_LEN);
|
|
|
|
|
|
|
|
|
|
if !old_releases.is_empty() {
|
|
|
|
|
let progress_bar = new_spinner_progress_bar();
|
|
|
|
|
progress_bar.set_length(old_releases.len() as u64);
|
|
|
|
|
progress_bar.set_style(
|
|
|
|
|
ProgressStyle::default_bar()
|
2021-04-14 15:27:50 -07:00
|
|
|
|
.template("{spinner:.green}{wide_msg} [{bar:40.cyan/blue}] {pos}/{len} ({eta})")
|
2022-08-05 14:11:17 -07:00
|
|
|
|
.expect("ProgresStyle::template direct input to be correct")
|
2021-02-03 09:32:57 -08:00
|
|
|
|
.progress_chars("=> "),
|
|
|
|
|
);
|
2022-12-06 06:30:06 -08:00
|
|
|
|
progress_bar.set_message(format!("{RECYCLING}Removing old releases"));
|
2021-02-03 09:32:57 -08:00
|
|
|
|
for (release, _modified_type) in old_releases {
|
|
|
|
|
progress_bar.inc(1);
|
2022-11-09 11:39:38 -08:00
|
|
|
|
let _ = fs::remove_dir_all(release);
|
2021-02-03 09:32:57 -08:00
|
|
|
|
}
|
|
|
|
|
progress_bar.finish_and_clear();
|
2021-01-29 15:38:54 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-11 17:52:16 -08:00
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
|
|
|
pub struct GithubRelease {
|
|
|
|
|
pub tag_name: String,
|
|
|
|
|
pub prerelease: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-04 11:28:05 -08:00
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
|
|
|
pub struct GithubError {
|
|
|
|
|
pub message: String,
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-11 17:52:16 -08:00
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
|
|
|
pub struct GithubReleases(Vec<GithubRelease>);
|
|
|
|
|
|
|
|
|
|
fn semver_of(string: &str) -> Result<semver::Version, String> {
|
|
|
|
|
if string.starts_with('v') {
|
|
|
|
|
semver::Version::parse(string.split_at(1).1)
|
|
|
|
|
} else {
|
|
|
|
|
semver::Version::parse(string)
|
|
|
|
|
}
|
|
|
|
|
.map_err(|err| err.to_string())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn check_for_newer_github_release(
|
2023-01-04 11:28:05 -08:00
|
|
|
|
current_release_semver: &str,
|
|
|
|
|
semver_update_type: SemverUpdateType,
|
2021-03-16 18:28:30 -07:00
|
|
|
|
prerelease_allowed: bool,
|
2023-01-04 11:28:05 -08:00
|
|
|
|
) -> Result<Option<String>, String> {
|
2021-03-11 17:52:16 -08:00
|
|
|
|
let client = reqwest::blocking::Client::builder()
|
|
|
|
|
.user_agent("solana-install")
|
2023-01-04 11:28:05 -08:00
|
|
|
|
.build()
|
|
|
|
|
.map_err(|err| err.to_string())?;
|
|
|
|
|
|
|
|
|
|
// If we want a fixed version, we don't need to stress the API to check whether it exists
|
|
|
|
|
if semver_update_type == SemverUpdateType::Fixed {
|
|
|
|
|
let download_url = github_release_download_url(current_release_semver);
|
|
|
|
|
let response = client
|
|
|
|
|
.head(download_url.as_str())
|
|
|
|
|
.send()
|
|
|
|
|
.map_err(|err| err.to_string())?;
|
|
|
|
|
|
|
|
|
|
if response.status() == reqwest::StatusCode::OK {
|
|
|
|
|
return Ok(Some(current_release_semver.to_string()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let version_filter = semver::VersionReq::parse(&format!(
|
|
|
|
|
"{}{}",
|
|
|
|
|
match semver_update_type {
|
|
|
|
|
SemverUpdateType::Fixed => "=",
|
|
|
|
|
SemverUpdateType::Patch => "~",
|
|
|
|
|
SemverUpdateType::_Minor => "^",
|
|
|
|
|
},
|
|
|
|
|
current_release_semver
|
|
|
|
|
))
|
|
|
|
|
.ok();
|
|
|
|
|
|
|
|
|
|
let mut page = 1;
|
|
|
|
|
const PER_PAGE: usize = 100;
|
2021-11-24 12:01:55 -08:00
|
|
|
|
let mut all_releases = vec![];
|
|
|
|
|
let mut releases = vec![];
|
|
|
|
|
|
|
|
|
|
while page == 1 || releases.len() == PER_PAGE {
|
|
|
|
|
let url = reqwest::Url::parse_with_params(
|
|
|
|
|
"https://api.github.com/repos/solana-labs/solana/releases",
|
|
|
|
|
&[
|
2022-12-06 06:30:06 -08:00
|
|
|
|
("per_page", &format!("{PER_PAGE}")),
|
|
|
|
|
("page", &format!("{page}")),
|
2021-11-24 12:01:55 -08:00
|
|
|
|
],
|
2021-03-11 17:52:16 -08:00
|
|
|
|
)
|
2021-11-24 12:01:55 -08:00
|
|
|
|
.unwrap();
|
2023-01-04 11:28:05 -08:00
|
|
|
|
let request = client.get(url).build().map_err(|err| err.to_string())?;
|
|
|
|
|
let response = client.execute(request).map_err(|err| err.to_string())?;
|
|
|
|
|
|
|
|
|
|
if response.status() == reqwest::StatusCode::OK {
|
|
|
|
|
releases = response
|
|
|
|
|
.json::<GithubReleases>()
|
|
|
|
|
.map_err(|err| err.to_string())?
|
|
|
|
|
.0
|
|
|
|
|
.into_iter()
|
|
|
|
|
.filter_map(
|
|
|
|
|
|GithubRelease {
|
|
|
|
|
tag_name,
|
|
|
|
|
prerelease,
|
|
|
|
|
}| {
|
|
|
|
|
if let Ok(version) = semver_of(&tag_name) {
|
|
|
|
|
if (prerelease_allowed || !prerelease)
|
|
|
|
|
&& version_filter
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map_or(true, |version_filter| version_filter.matches(&version))
|
|
|
|
|
{
|
|
|
|
|
return Some(version);
|
|
|
|
|
}
|
2021-11-24 12:01:55 -08:00
|
|
|
|
}
|
2023-01-04 11:28:05 -08:00
|
|
|
|
None
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
all_releases.extend_from_slice(&releases);
|
|
|
|
|
page += 1;
|
|
|
|
|
} else {
|
|
|
|
|
return Err(response
|
|
|
|
|
.json::<GithubError>()
|
|
|
|
|
.map_err(|err| err.to_string())?
|
|
|
|
|
.message);
|
|
|
|
|
}
|
2021-11-24 12:01:55 -08:00
|
|
|
|
}
|
2021-03-11 17:52:16 -08:00
|
|
|
|
|
2021-11-24 12:01:55 -08:00
|
|
|
|
all_releases.sort();
|
|
|
|
|
Ok(all_releases.pop().map(|r| r.to_string()))
|
2021-03-11 17:52:16 -08:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-04 11:28:05 -08:00
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
2021-03-11 17:52:16 -08:00
|
|
|
|
pub enum SemverUpdateType {
|
|
|
|
|
Fixed,
|
|
|
|
|
Patch,
|
|
|
|
|
_Minor,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn update(config_file: &str, check_only: bool) -> Result<bool, String> {
|
|
|
|
|
init_or_update(config_file, false, check_only)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn init_or_update(config_file: &str, is_init: bool, check_only: bool) -> Result<bool, String> {
|
2019-07-23 12:51:10 -07:00
|
|
|
|
let mut config = Config::load(config_file)?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
2021-03-11 17:52:16 -08:00
|
|
|
|
let semver_update_type = if is_init {
|
|
|
|
|
SemverUpdateType::Fixed
|
|
|
|
|
} else {
|
|
|
|
|
SemverUpdateType::Patch
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let (updated_version, download_url_and_sha256, release_dir) = if let Some(explicit_release) =
|
|
|
|
|
&config.explicit_release
|
|
|
|
|
{
|
|
|
|
|
match explicit_release {
|
|
|
|
|
ExplicitRelease::Semver(current_release_semver) => {
|
|
|
|
|
let progress_bar = new_spinner_progress_bar();
|
2022-12-06 06:30:06 -08:00
|
|
|
|
progress_bar.set_message(format!("{LOOKING_GLASS}Checking for updates..."));
|
2021-03-11 17:52:16 -08:00
|
|
|
|
|
|
|
|
|
let github_release = check_for_newer_github_release(
|
2023-01-04 11:28:05 -08:00
|
|
|
|
current_release_semver,
|
|
|
|
|
semver_update_type,
|
2021-03-16 18:28:30 -07:00
|
|
|
|
is_init,
|
2023-01-04 11:28:05 -08:00
|
|
|
|
)?;
|
|
|
|
|
|
2021-03-11 17:52:16 -08:00
|
|
|
|
progress_bar.finish_and_clear();
|
|
|
|
|
|
|
|
|
|
match github_release {
|
|
|
|
|
None => {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
return Err(format!("Unknown release: {current_release_semver}"));
|
2021-03-11 17:52:16 -08:00
|
|
|
|
}
|
|
|
|
|
Some(release_semver) => {
|
|
|
|
|
if release_semver == *current_release_semver {
|
|
|
|
|
if let Ok(active_release_version) = load_release_version(
|
|
|
|
|
&config.active_release_dir().join("version.yml"),
|
|
|
|
|
) {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
if format!("v{current_release_semver}")
|
2021-03-11 17:52:16 -08:00
|
|
|
|
== active_release_version.channel
|
|
|
|
|
{
|
|
|
|
|
println!(
|
2022-12-06 06:30:06 -08:00
|
|
|
|
"Install is up to date. {release_semver} is the latest compatible release"
|
2021-03-11 17:52:16 -08:00
|
|
|
|
);
|
|
|
|
|
return Ok(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
config.explicit_release =
|
|
|
|
|
Some(ExplicitRelease::Semver(release_semver.clone()));
|
|
|
|
|
|
|
|
|
|
let release_dir = config.release_dir(&release_semver);
|
|
|
|
|
let download_url_and_sha256 = if release_dir.exists() {
|
|
|
|
|
// Release already present in the cache
|
|
|
|
|
None
|
|
|
|
|
} else {
|
|
|
|
|
Some((github_release_download_url(&release_semver), None))
|
|
|
|
|
};
|
|
|
|
|
(release_semver, download_url_and_sha256, release_dir)
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-31 17:30:17 -07:00
|
|
|
|
}
|
|
|
|
|
ExplicitRelease::Channel(release_channel) => {
|
2021-01-28 20:38:33 -08:00
|
|
|
|
let version_url = release_channel_version_url(release_channel);
|
|
|
|
|
|
|
|
|
|
let (_temp_dir, temp_file, _temp_archive_sha256) =
|
|
|
|
|
download_to_temp(&version_url, None)
|
2022-12-06 06:30:06 -08:00
|
|
|
|
.map_err(|err| format!("Unable to download {version_url}: {err}"))?;
|
2021-01-28 20:38:33 -08:00
|
|
|
|
|
|
|
|
|
let update_release_version = load_release_version(&temp_file)?;
|
|
|
|
|
|
|
|
|
|
let release_id = format!("{}-{}", release_channel, update_release_version.commit);
|
|
|
|
|
let release_dir = config.release_dir(&release_id);
|
2020-02-11 12:32:26 -08:00
|
|
|
|
let current_release_version_yml =
|
|
|
|
|
release_dir.join("solana-release").join("version.yml");
|
2021-01-28 20:38:33 -08:00
|
|
|
|
|
2021-03-11 17:52:16 -08:00
|
|
|
|
let download_url = release_channel_download_url(release_channel);
|
2020-02-10 22:19:52 -08:00
|
|
|
|
|
2020-02-11 12:32:26 -08:00
|
|
|
|
if !current_release_version_yml.exists() {
|
2021-03-11 17:52:16 -08:00
|
|
|
|
(
|
|
|
|
|
format!(
|
|
|
|
|
"{} commit {}",
|
|
|
|
|
release_channel,
|
|
|
|
|
&update_release_version.commit[0..7]
|
|
|
|
|
),
|
|
|
|
|
Some((download_url, None)),
|
|
|
|
|
release_dir,
|
|
|
|
|
)
|
2020-02-10 22:19:52 -08:00
|
|
|
|
} else {
|
2020-02-11 12:32:26 -08:00
|
|
|
|
let current_release_version =
|
|
|
|
|
load_release_version(¤t_release_version_yml)?;
|
|
|
|
|
if update_release_version.commit == current_release_version.commit {
|
2021-03-11 17:52:16 -08:00
|
|
|
|
if let Ok(active_release_version) =
|
|
|
|
|
load_release_version(&config.active_release_dir().join("version.yml"))
|
|
|
|
|
{
|
|
|
|
|
if current_release_version.commit == active_release_version.commit {
|
|
|
|
|
// Same version, no update required
|
|
|
|
|
println!(
|
|
|
|
|
"Install is up to date. {} is the latest commit for {}",
|
|
|
|
|
&active_release_version.commit[0..7],
|
|
|
|
|
release_channel
|
|
|
|
|
);
|
|
|
|
|
return Ok(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Release already present in the cache
|
|
|
|
|
(
|
|
|
|
|
format!(
|
|
|
|
|
"{} commit {}",
|
|
|
|
|
release_channel,
|
|
|
|
|
&update_release_version.commit[0..7]
|
|
|
|
|
),
|
|
|
|
|
None,
|
|
|
|
|
release_dir,
|
|
|
|
|
)
|
2020-02-11 12:32:26 -08:00
|
|
|
|
} else {
|
2021-03-11 17:52:16 -08:00
|
|
|
|
(
|
|
|
|
|
format!(
|
|
|
|
|
"{} (from {})",
|
2021-01-28 20:38:33 -08:00
|
|
|
|
&update_release_version.commit[0..7],
|
2021-03-11 17:52:16 -08:00
|
|
|
|
¤t_release_version.commit[0..7],
|
2021-01-28 20:38:33 -08:00
|
|
|
|
),
|
2021-03-11 17:52:16 -08:00
|
|
|
|
Some((download_url, None)),
|
|
|
|
|
release_dir,
|
|
|
|
|
)
|
2020-02-11 12:32:26 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-31 17:30:17 -07:00
|
|
|
|
}
|
2019-08-06 11:58:50 -07:00
|
|
|
|
}
|
2019-07-23 12:51:10 -07:00
|
|
|
|
} else {
|
2021-03-11 17:52:16 -08:00
|
|
|
|
let progress_bar = new_spinner_progress_bar();
|
2022-12-06 06:30:06 -08:00
|
|
|
|
progress_bar.set_message(format!("{LOOKING_GLASS}Checking for updates..."));
|
2021-03-11 17:52:16 -08:00
|
|
|
|
let rpc_client = RpcClient::new(config.json_rpc_url.clone());
|
|
|
|
|
let update_manifest = get_update_manifest(&rpc_client, &config.update_manifest_pubkey)?;
|
|
|
|
|
progress_bar.finish_and_clear();
|
|
|
|
|
|
|
|
|
|
if Some(&update_manifest) == config.current_update_manifest.as_ref() {
|
|
|
|
|
println!("Install is up to date");
|
2019-07-23 12:51:10 -07:00
|
|
|
|
return Ok(false);
|
|
|
|
|
}
|
2021-03-11 17:52:16 -08:00
|
|
|
|
println!("\n{}", style("An update is available:").bold());
|
|
|
|
|
print_update_manifest(&update_manifest);
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
2019-07-23 12:51:10 -07:00
|
|
|
|
if timestamp_secs()
|
2021-04-18 10:27:36 -07:00
|
|
|
|
< crate::build_env::BUILD_SECONDS_SINCE_UNIX_EPOCH
|
|
|
|
|
.parse::<u64>()
|
|
|
|
|
.unwrap()
|
2019-07-23 12:51:10 -07:00
|
|
|
|
{
|
2019-10-02 18:04:18 -07:00
|
|
|
|
return Err("Unable to update as system time seems unreliable".to_string());
|
2019-07-23 12:51:10 -07:00
|
|
|
|
}
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
2019-07-23 12:51:10 -07:00
|
|
|
|
if let Some(ref current_update_manifest) = config.current_update_manifest {
|
|
|
|
|
if update_manifest.timestamp_secs < current_update_manifest.timestamp_secs {
|
2019-10-02 18:04:18 -07:00
|
|
|
|
return Err("Unable to update to an older version".to_string());
|
2019-07-23 12:51:10 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-11 17:52:16 -08:00
|
|
|
|
config.current_update_manifest = Some(update_manifest.clone());
|
|
|
|
|
|
2019-11-22 20:58:26 -08:00
|
|
|
|
let release_dir = config.release_dir(&update_manifest.download_sha256.to_string());
|
2021-03-11 17:52:16 -08:00
|
|
|
|
|
|
|
|
|
let download_url = update_manifest.download_url;
|
|
|
|
|
let archive_sha256 = Some(update_manifest.download_sha256);
|
|
|
|
|
(
|
|
|
|
|
"latest manifest".to_string(),
|
|
|
|
|
Some((download_url, archive_sha256)),
|
|
|
|
|
release_dir,
|
2019-03-20 16:12:50 -07:00
|
|
|
|
)
|
2021-03-11 17:52:16 -08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if check_only {
|
|
|
|
|
println!(
|
|
|
|
|
" {}{}",
|
|
|
|
|
WRAPPED_PRESENT,
|
2022-12-06 06:30:06 -08:00
|
|
|
|
style(format!("Update available: {updated_version}")).bold()
|
2021-03-11 17:52:16 -08:00
|
|
|
|
);
|
|
|
|
|
return Ok(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some((download_url, archive_sha256)) = download_url_and_sha256 {
|
|
|
|
|
let (_temp_dir, temp_archive, _temp_archive_sha256) =
|
|
|
|
|
download_to_temp(&download_url, archive_sha256.as_ref())
|
2022-12-06 06:30:06 -08:00
|
|
|
|
.map_err(|err| format!("Unable to download {download_url}: {err}"))?;
|
2019-07-23 12:51:10 -07:00
|
|
|
|
extract_release_archive(&temp_archive, &release_dir).map_err(|err| {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
format!("Unable to extract {temp_archive:?} to {release_dir:?}: {err}")
|
2019-07-23 12:51:10 -07:00
|
|
|
|
})?;
|
2021-03-11 17:52:16 -08:00
|
|
|
|
}
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
2022-12-06 06:30:06 -08:00
|
|
|
|
let release_target = load_release_target(&release_dir)
|
|
|
|
|
.map_err(|err| format!("Unable to load release target from {release_dir:?}: {err}"))?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
|
|
|
|
if release_target != crate::build_env::TARGET {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
return Err(format!("Incompatible update target: {release_target}"));
|
2019-03-20 16:12:50 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-01-29 15:38:54 -08:00
|
|
|
|
// Trigger an update to the modification time for `release_dir`
|
|
|
|
|
{
|
|
|
|
|
let path = &release_dir.join(".touch");
|
|
|
|
|
let _ = fs::OpenOptions::new().create(true).write(true).open(path);
|
|
|
|
|
let _ = fs::remove_file(path);
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-12 14:13:01 -07:00
|
|
|
|
let _ = fs::remove_dir_all(config.active_release_dir());
|
2019-06-04 08:51:20 -07:00
|
|
|
|
symlink_dir(
|
2019-04-12 14:13:01 -07:00
|
|
|
|
release_dir.join("solana-release"),
|
|
|
|
|
config.active_release_dir(),
|
2019-03-20 16:12:50 -07:00
|
|
|
|
)
|
|
|
|
|
.map_err(|err| {
|
|
|
|
|
format!(
|
|
|
|
|
"Unable to symlink {:?} to {:?}: {}",
|
|
|
|
|
release_dir,
|
2019-04-12 14:13:01 -07:00
|
|
|
|
config.active_release_dir(),
|
2019-03-20 16:12:50 -07:00
|
|
|
|
err
|
|
|
|
|
)
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
config.save(config_file)?;
|
2021-01-29 15:38:54 -08:00
|
|
|
|
gc(config_file)?;
|
2019-03-20 16:12:50 -07:00
|
|
|
|
|
2021-03-11 17:52:16 -08:00
|
|
|
|
if is_init {
|
|
|
|
|
println!(
|
|
|
|
|
" {}{}",
|
|
|
|
|
SPARKLE,
|
2022-12-06 06:30:06 -08:00
|
|
|
|
style(format!("{updated_version} initialized")).bold()
|
2021-03-11 17:52:16 -08:00
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
println!(
|
|
|
|
|
" {}{}",
|
|
|
|
|
SPARKLE,
|
2022-12-06 06:30:06 -08:00
|
|
|
|
style(format!("Update successful to {updated_version}")).bold()
|
2021-03-11 17:52:16 -08:00
|
|
|
|
);
|
|
|
|
|
}
|
2019-03-20 16:12:50 -07:00
|
|
|
|
Ok(true)
|
2019-03-15 10:54:54 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn run(
|
|
|
|
|
config_file: &str,
|
|
|
|
|
program_name: &str,
|
|
|
|
|
program_arguments: Vec<&str>,
|
|
|
|
|
) -> Result<(), String> {
|
2019-03-20 16:12:50 -07:00
|
|
|
|
let config = Config::load(config_file)?;
|
|
|
|
|
|
2019-07-02 08:04:27 -07:00
|
|
|
|
let mut full_program_path = config.active_release_bin_dir().join(program_name);
|
2019-07-03 17:45:08 -07:00
|
|
|
|
if cfg!(windows) && full_program_path.extension().is_none() {
|
2019-07-02 08:04:27 -07:00
|
|
|
|
full_program_path.set_extension("exe");
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-20 16:12:50 -07:00
|
|
|
|
if !full_program_path.exists() {
|
2019-10-02 18:04:18 -07:00
|
|
|
|
return Err(format!(
|
2019-03-20 16:12:50 -07:00
|
|
|
|
"{} does not exist",
|
|
|
|
|
full_program_path.to_str().unwrap()
|
2019-10-02 18:04:18 -07:00
|
|
|
|
));
|
2019-03-20 16:12:50 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut child_option: Option<std::process::Child> = None;
|
|
|
|
|
let mut now = Instant::now();
|
2019-07-01 17:10:14 -07:00
|
|
|
|
|
2022-01-11 02:44:46 -08:00
|
|
|
|
let (signal_sender, signal_receiver) = unbounded();
|
2019-07-03 17:45:08 -07:00
|
|
|
|
ctrlc::set_handler(move || {
|
|
|
|
|
let _ = signal_sender.send(());
|
|
|
|
|
})
|
|
|
|
|
.expect("Error setting Ctrl-C handler");
|
2019-07-01 17:10:14 -07:00
|
|
|
|
|
2019-03-20 16:12:50 -07:00
|
|
|
|
loop {
|
|
|
|
|
child_option = match child_option {
|
|
|
|
|
Some(mut child) => match child.try_wait() {
|
|
|
|
|
Ok(Some(status)) => {
|
|
|
|
|
println_name_value(
|
2022-12-06 06:30:06 -08:00
|
|
|
|
&format!("{program_name} exited with:"),
|
2019-03-20 16:12:50 -07:00
|
|
|
|
&status.to_string(),
|
|
|
|
|
);
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
Ok(None) => Some(child),
|
|
|
|
|
Err(err) => {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
eprintln!("Error attempting to wait for program to exit: {err}");
|
2019-03-20 16:12:50 -07:00
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
None => {
|
|
|
|
|
match std::process::Command::new(&full_program_path)
|
|
|
|
|
.args(&program_arguments)
|
|
|
|
|
.spawn()
|
|
|
|
|
{
|
|
|
|
|
Ok(child) => Some(child),
|
|
|
|
|
Err(err) => {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
eprintln!("Failed to spawn {program_name}: {err:?}");
|
2019-03-20 16:12:50 -07:00
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2019-07-31 17:30:17 -07:00
|
|
|
|
if config.explicit_release.is_none() && now.elapsed().as_secs() > config.update_poll_secs {
|
2021-03-11 17:52:16 -08:00
|
|
|
|
match update(config_file, false) {
|
2019-03-20 16:12:50 -07:00
|
|
|
|
Ok(true) => {
|
|
|
|
|
// Update successful, kill current process so it will be restart
|
|
|
|
|
if let Some(ref mut child) = child_option {
|
2019-07-01 14:08:30 -07:00
|
|
|
|
stop_process(child).unwrap_or_else(|err| {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
eprintln!("Failed to stop child: {err:?}");
|
2019-07-01 14:08:30 -07:00
|
|
|
|
});
|
2019-03-20 16:12:50 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(false) => {} // No update available
|
|
|
|
|
Err(err) => {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
eprintln!("Failed to apply update: {err:?}");
|
2019-03-20 16:12:50 -07:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
now = Instant::now();
|
|
|
|
|
}
|
2019-07-01 17:10:14 -07:00
|
|
|
|
|
|
|
|
|
if let Ok(()) = signal_receiver.recv_timeout(Duration::from_secs(1)) {
|
|
|
|
|
// Handle SIGTERM...
|
|
|
|
|
if let Some(ref mut child) = child_option {
|
|
|
|
|
stop_process(child).unwrap_or_else(|err| {
|
2022-12-06 06:30:06 -08:00
|
|
|
|
eprintln!("Failed to stop child: {err:?}");
|
2019-07-01 17:10:14 -07:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
std::process::exit(0);
|
|
|
|
|
}
|
2019-03-20 16:12:50 -07:00
|
|
|
|
}
|
2019-03-15 10:54:54 -07:00
|
|
|
|
}
|