Initial commands implementation
This commit is contained in:
parent
4e029d81a2
commit
f286bbac99
|
@ -8,20 +8,29 @@ repository = "https://github.com/solana-labs/solana"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
homepage = "https://solana.com/"
|
homepage = "https://solana.com/"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
cuda = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bincode = "1.1.2"
|
bincode = "1.1.2"
|
||||||
bs58 = "0.2.0"
|
bs58 = "0.2.0"
|
||||||
clap = { version = "2.32.0"}
|
bzip2 = "0.3.3"
|
||||||
|
console = "0.7.5"
|
||||||
chrono = { version = "0.4.0", features = ["serde"] }
|
chrono = { version = "0.4.0", features = ["serde"] }
|
||||||
|
clap = { version = "2.32.0"}
|
||||||
dirs = "1.0.5"
|
dirs = "1.0.5"
|
||||||
|
indicatif = "0.11.0"
|
||||||
lazy_static = "1.3.0"
|
lazy_static = "1.3.0"
|
||||||
log = "0.4.2"
|
log = "0.4.2"
|
||||||
|
reqwest = "0.9.11"
|
||||||
|
ring = "0.13.2"
|
||||||
serde = "1.0.89"
|
serde = "1.0.89"
|
||||||
serde_derive = "1.0.89"
|
serde_derive = "1.0.89"
|
||||||
serde_yaml = "0.8.8"
|
serde_yaml = "0.8.8"
|
||||||
solana-client = { path = "../client", version = "0.13.0" }
|
solana-client = { path = "../client", version = "0.13.0" }
|
||||||
|
solana-config-api = { path = "../programs/config_api", version = "0.13.0" }
|
||||||
solana-logger = { path = "../logger", version = "0.13.0" }
|
solana-logger = { path = "../logger", version = "0.13.0" }
|
||||||
solana-sdk = { path = "../sdk", version = "0.13.0" }
|
solana-sdk = { path = "../sdk", version = "0.13.0" }
|
||||||
|
tar = "0.4.22"
|
||||||
[features]
|
tempdir = "0.3.7"
|
||||||
cuda = []
|
url = "1.7.2"
|
||||||
|
|
|
@ -1,57 +1,481 @@
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
use crate::update_manifest::{SignedUpdateManifest, UpdateManifest};
|
||||||
|
use chrono::{Local, TimeZone};
|
||||||
|
use console::{style, Emoji};
|
||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
use ring::digest::{Context, Digest, SHA256};
|
||||||
|
use solana_client::rpc_client::RpcClient;
|
||||||
|
use solana_config_api::ConfigInstruction;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use std::time::Duration;
|
use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil, Signable};
|
||||||
|
use solana_sdk::transaction::Transaction;
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::io::{self, BufReader, Read};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::thread::sleep;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use tempdir::TempDir;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
static TRUCK: Emoji = Emoji("🚚 ", "");
|
||||||
|
static LOOKING_GLASS: Emoji = Emoji("🔍 ", "");
|
||||||
|
static BULLET: Emoji = Emoji("• ", "* ");
|
||||||
|
static SPARKLE: Emoji = Emoji("✨ ", "");
|
||||||
|
static PACKAGE: Emoji = Emoji("📦 ", "");
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
progress_bar
|
||||||
|
.set_style(ProgressStyle::default_spinner().template("{spinner:.green} {wide_msg}"));
|
||||||
|
progress_bar.enable_steady_tick(100);
|
||||||
|
progress_bar
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pretty print a "name value"
|
||||||
|
fn println_name_value(name: &str, value: &str) {
|
||||||
|
println!("{} {}", style(name).bold(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downloads the release archive at `url` to a temporary location. If `expected_sha256` is
|
||||||
|
/// Some(_), produce an error if the release SHA256 doesn't match.
|
||||||
|
///
|
||||||
|
/// Returns a tuple consisting of:
|
||||||
|
/// * TempDir - drop this value to clean up the temporary location
|
||||||
|
/// * PathBuf - path to the downloaded release (within `TempDir`)
|
||||||
|
/// * String - SHA256 of the release
|
||||||
|
///
|
||||||
|
fn download_to_temp_archive(
|
||||||
|
url: &str,
|
||||||
|
expected_sha256: Option<&str>,
|
||||||
|
) -> Result<(TempDir, PathBuf, String), Box<dyn std::error::Error>> {
|
||||||
|
fn sha256_digest<R: Read>(mut reader: R) -> Result<Digest, Box<dyn std::error::Error>> {
|
||||||
|
let mut context = Context::new(&SHA256);
|
||||||
|
let mut buffer = [0; 1024];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let count = reader.read(&mut buffer)?;
|
||||||
|
if count == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
context.update(&buffer[..count]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(context.finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sha256_file_digest<P: AsRef<Path>>(path: P) -> Result<Digest, Box<dyn std::error::Error>> {
|
||||||
|
let input = File::open(path)?;
|
||||||
|
let reader = BufReader::new(input);
|
||||||
|
sha256_digest(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = Url::parse(url).map_err(|err| format!("Unable to parse {}: {}", url, err))?;
|
||||||
|
|
||||||
|
let temp_dir = TempDir::new(clap::crate_name!())?;
|
||||||
|
let temp_file = temp_dir.path().join("release.tar.bz2");
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let progress_bar = new_spinner_progress_bar();
|
||||||
|
progress_bar.set_message(&format!("{}Downloading...", TRUCK));
|
||||||
|
|
||||||
|
let response = client.get(url).send()?;
|
||||||
|
let download_size = {
|
||||||
|
response
|
||||||
|
.headers()
|
||||||
|
.get(reqwest::header::CONTENT_LENGTH)
|
||||||
|
.and_then(|content_length| content_length.to_str().ok())
|
||||||
|
.and_then(|content_length| content_length.parse().ok())
|
||||||
|
.unwrap_or(0)
|
||||||
|
};
|
||||||
|
|
||||||
|
progress_bar.set_length(download_size);
|
||||||
|
progress_bar.set_style(
|
||||||
|
ProgressStyle::default_bar()
|
||||||
|
.template(&format!(
|
||||||
|
"{}{}{}",
|
||||||
|
"{spinner:.green} ",
|
||||||
|
TRUCK,
|
||||||
|
"Downloading [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})"
|
||||||
|
))
|
||||||
|
.progress_chars("=> "),
|
||||||
|
);
|
||||||
|
|
||||||
|
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,
|
||||||
|
response,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut file = File::create(&temp_file)?;
|
||||||
|
std::io::copy(&mut source, &mut file)?;
|
||||||
|
|
||||||
|
let temp_file_sha256 = sha256_file_digest(&temp_file)
|
||||||
|
.map_err(|err| format!("Unable to hash {:?}: {}", temp_file, err))?;
|
||||||
|
let temp_file_sha256 = bs58::encode(temp_file_sha256).into_string();
|
||||||
|
|
||||||
|
if expected_sha256.is_some() && expected_sha256 != Some(&temp_file_sha256) {
|
||||||
|
Err(io::Error::new(io::ErrorKind::Other, "Incorrect hash"))?;
|
||||||
|
}
|
||||||
|
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>> {
|
||||||
|
use bzip2::bufread::BzDecoder;
|
||||||
|
use tar::Archive;
|
||||||
|
|
||||||
|
let progress_bar = new_spinner_progress_bar();
|
||||||
|
progress_bar.set_message(&format!("{}Extracting...", PACKAGE));
|
||||||
|
|
||||||
|
let _ = fs::remove_dir_all(extract_dir);
|
||||||
|
fs::create_dir_all(extract_dir)?;
|
||||||
|
|
||||||
|
let tar_bz2 = File::open(archive)?;
|
||||||
|
let tar = BzDecoder::new(BufReader::new(tar_bz2));
|
||||||
|
let mut release = Archive::new(tar);
|
||||||
|
release.unpack(extract_dir)?;
|
||||||
|
|
||||||
|
progress_bar.finish_and_clear();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the supported TARGET triple for the given release
|
||||||
|
fn load_release_target(release_dir: &Path) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
use serde_derive::Deserialize;
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct ReleaseVersion {
|
||||||
|
pub target: String,
|
||||||
|
pub commit: String,
|
||||||
|
channel: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut version_yml = PathBuf::from(release_dir);
|
||||||
|
version_yml.push("solana-release");
|
||||||
|
version_yml.push("version.yml");
|
||||||
|
|
||||||
|
let file = File::open(&version_yml)?;
|
||||||
|
let version: ReleaseVersion = serde_yaml::from_reader(file)?;
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
let recect_blockhash = rpc_client.get_recent_blockhash()?;
|
||||||
|
|
||||||
|
let new_account = ConfigInstruction::new_account::<SignedUpdateManifest>(
|
||||||
|
&from_keypair.pubkey(),
|
||||||
|
&update_manifest_keypair.pubkey(),
|
||||||
|
1, // lamports
|
||||||
|
);
|
||||||
|
let mut transaction = Transaction::new(vec![new_account]);
|
||||||
|
transaction.sign(&[from_keypair], recect_blockhash);
|
||||||
|
|
||||||
|
rpc_client.send_and_confirm_transaction(&mut transaction, from_keypair)?;
|
||||||
|
}
|
||||||
|
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>> {
|
||||||
|
let recect_blockhash = rpc_client.get_recent_blockhash()?;
|
||||||
|
|
||||||
|
let new_store = ConfigInstruction::new_store::<SignedUpdateManifest>(
|
||||||
|
&from_keypair.pubkey(),
|
||||||
|
&update_manifest_keypair.pubkey(),
|
||||||
|
update_manifest,
|
||||||
|
);
|
||||||
|
let mut transaction = Transaction::new(vec![new_store]);
|
||||||
|
transaction.sign(&[from_keypair, update_manifest_keypair], recect_blockhash);
|
||||||
|
rpc_client.send_and_confirm_transaction(&mut transaction, from_keypair)?;
|
||||||
|
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> {
|
||||||
|
let data = rpc_client
|
||||||
|
.get_account_data(update_manifest_pubkey)
|
||||||
|
.map_err(|err| format!("Unable to fetch update manifest: {}", err))?;
|
||||||
|
|
||||||
|
let signed_update_manifest =
|
||||||
|
SignedUpdateManifest::deserialize(update_manifest_pubkey, &data)
|
||||||
|
.map_err(|err| format!("Unable to deserialize update manifest: {}", err))?;
|
||||||
|
Ok(signed_update_manifest.manifest)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bug the user if bin_dir is not in their PATH
|
||||||
|
fn check_env_path_for_bin_dir(config: &Config) {
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
let found = match env::var_os("PATH") {
|
||||||
|
Some(paths) => env::split_paths(&paths).any(|path| {
|
||||||
|
if let Ok(path) = path.canonicalize() {
|
||||||
|
if path == *config.bin_dir() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}),
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
println!(
|
||||||
|
"\nPlease update your PATH environment variable to include the solana programs:\n PATH=\"{}:$PATH\"\n",
|
||||||
|
config.bin_dir().to_str().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
config_file: &str,
|
config_file: &str,
|
||||||
data_dir: &str,
|
data_dir: &str,
|
||||||
json_rpc_url: &str,
|
json_rpc_url: &str,
|
||||||
update_pubkey: &Pubkey,
|
update_manifest_pubkey: &Pubkey,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
println!("init {:?} {:?}", json_rpc_url, update_pubkey);
|
let config = Config::new(data_dir, json_rpc_url, update_manifest_pubkey);
|
||||||
Config {
|
config.save(config_file)?;
|
||||||
data_dir: data_dir.to_string(),
|
update(config_file)?;
|
||||||
json_rpc_url: json_rpc_url.to_string(),
|
Ok(())
|
||||||
update_pubkey: *update_pubkey,
|
|
||||||
current_install: None,
|
|
||||||
}
|
|
||||||
.save(config_file)?;
|
|
||||||
|
|
||||||
Err("Not implemented".to_string())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn info(config_file: &str, local_info_only: bool) -> Result<(), String> {
|
pub fn info(config_file: &str, local_info_only: bool) -> Result<Option<UpdateManifest>, String> {
|
||||||
let config = Config::load(config_file)?;
|
let config = Config::load(config_file)?;
|
||||||
println!("config dir: {:?}", config_file);
|
println_name_value("JSON RPC URL:", &config.json_rpc_url);
|
||||||
println!("configuration: {:?}", config);
|
println_name_value(
|
||||||
if local_info_only {
|
"Update manifest pubkey:",
|
||||||
return Ok(());
|
&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.format("%c").to_string(),
|
||||||
|
);
|
||||||
|
println_name_value(
|
||||||
|
&format!("{}download URL", BULLET),
|
||||||
|
&update_manifest.download_url,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: fetch info about current update manifest from the cluster
|
match config.current_update_manifest {
|
||||||
|
Some(ref update_manifest) => {
|
||||||
|
println_name_value("Installed version:", "");
|
||||||
|
print_update_manifest(&update_manifest);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
println_name_value("Installed version:", "None");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Err("Not implemented".to_string())
|
if local_info_only {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
let progress_bar = new_spinner_progress_bar();
|
||||||
|
progress_bar.set_message(&format!("{}Checking for updates...", LOOKING_GLASS));
|
||||||
|
let rpc_client = RpcClient::new(config.json_rpc_url.clone());
|
||||||
|
let manifest = get_update_manifest(&rpc_client, &config.update_manifest_pubkey)?;
|
||||||
|
progress_bar.finish_and_clear();
|
||||||
|
|
||||||
|
if Some(&manifest) == config.current_update_manifest.as_ref() {
|
||||||
|
println!("\n{}", style("Installation is up to date").italic());
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
println!("\n{}", style("An update is available:").bold());
|
||||||
|
print_update_manifest(&manifest);
|
||||||
|
Ok(Some(manifest))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deploy(
|
pub fn deploy(
|
||||||
config_file: &str,
|
json_rpc_url: &str,
|
||||||
|
from_keypair_file: &str,
|
||||||
download_url: &str,
|
download_url: &str,
|
||||||
update_manifest_keypair: &str,
|
update_manifest_keypair_file: &str,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
println!("deploy {:?} {:?}", download_url, update_manifest_keypair);
|
let from_keypair = read_keypair(from_keypair_file)
|
||||||
let _config = Config::load(config_file)?;
|
.map_err(|err| format!("Unable to read {}: {}", from_keypair_file, err))?;
|
||||||
Err("Not implemented".to_string())
|
let update_manifest_keypair = read_keypair(update_manifest_keypair_file)
|
||||||
|
.map_err(|err| format!("Unable to read {}: {}", update_manifest_keypair_file, err))?;
|
||||||
|
|
||||||
|
// Download the release
|
||||||
|
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 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| {
|
||||||
|
format!(
|
||||||
|
"Unable to extract {:?} into {:?}: {}",
|
||||||
|
temp_archive, temp_release_dir, err
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let release_target = load_release_target(&temp_release_dir).map_err(|err| {
|
||||||
|
format!(
|
||||||
|
"Unable to load release target from {:?}: {}",
|
||||||
|
temp_release_dir, err
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
println_name_value("JSON RPC URL:", json_rpc_url);
|
||||||
|
println_name_value("Update target:", &release_target);
|
||||||
|
println_name_value(
|
||||||
|
"Update manifest pubkey:",
|
||||||
|
&update_manifest_keypair.pubkey().to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let progress_bar = new_spinner_progress_bar();
|
||||||
|
progress_bar.set_message(&format!("{}Deploying update...", PACKAGE));
|
||||||
|
|
||||||
|
// 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
|
||||||
|
let rpc_client = RpcClient::new(json_rpc_url.to_string());
|
||||||
|
|
||||||
|
new_update_manifest(&rpc_client, &from_keypair, &update_manifest_keypair)
|
||||||
|
.map_err(|err| format!("Unable to create update manifest: {}", err))?;
|
||||||
|
store_update_manifest(
|
||||||
|
&rpc_client,
|
||||||
|
&from_keypair,
|
||||||
|
&update_manifest_keypair,
|
||||||
|
&update_manifest,
|
||||||
|
)
|
||||||
|
.map_err(|err| format!("Unable to store update manifest: {:?}", err))?;
|
||||||
|
|
||||||
|
progress_bar.finish_and_clear();
|
||||||
|
println!(" {}{}", SPARKLE, style("Deployment successful").bold());
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(config_file: &str) -> Result<(), String> {
|
pub fn update(config_file: &str) -> Result<bool, String> {
|
||||||
println!(
|
let update_manifest = info(config_file, false)?;
|
||||||
"update: BUILD_SECONDS_SINCE_UNIX_EPOCH={:?}",
|
if update_manifest.is_none() {
|
||||||
Duration::from_secs(
|
return Ok(false);
|
||||||
u64::from_str_radix(crate::build_env::BUILD_SECONDS_SINCE_UNIX_EPOCH, 10).unwrap()
|
}
|
||||||
|
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 (_temp_dir, temp_archive, _temp_archive_sha256) = download_to_temp_archive(
|
||||||
|
&update_manifest.download_url,
|
||||||
|
Some(&update_manifest.download_sha256),
|
||||||
)
|
)
|
||||||
);
|
.map_err(|err| {
|
||||||
let _config = Config::load(config_file)?;
|
format!(
|
||||||
Err("Not implemented".to_string())
|
"Unable to download {}: {}",
|
||||||
|
update_manifest.download_url, 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
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let release_target = load_release_target(&release_dir).map_err(|err| {
|
||||||
|
format!(
|
||||||
|
"Unable to load release target from {:?}: {}",
|
||||||
|
release_dir, err
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if release_target != crate::build_env::TARGET {
|
||||||
|
Err(format!("Incompatible update target: {}", release_target))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = fs::remove_dir_all(config.bin_dir());
|
||||||
|
std::os::unix::fs::symlink(
|
||||||
|
release_dir.join("solana-release").join("bin"),
|
||||||
|
config.bin_dir(),
|
||||||
|
)
|
||||||
|
.map_err(|err| {
|
||||||
|
format!(
|
||||||
|
"Unable to symlink {:?} to {:?}: {}",
|
||||||
|
release_dir,
|
||||||
|
config.bin_dir(),
|
||||||
|
err
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
config.current_update_manifest = Some(update_manifest);
|
||||||
|
config.save(config_file)?;
|
||||||
|
|
||||||
|
check_env_path_for_bin_dir(&config);
|
||||||
|
println!(" {}{}", SPARKLE, style("Update successful").bold());
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(
|
pub fn run(
|
||||||
|
@ -59,7 +483,63 @@ pub fn run(
|
||||||
program_name: &str,
|
program_name: &str,
|
||||||
program_arguments: Vec<&str>,
|
program_arguments: Vec<&str>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
println!("run {:?} {:?}", program_name, program_arguments);
|
let config = Config::load(config_file)?;
|
||||||
let _config = Config::load(config_file)?;
|
|
||||||
Err("Not implemented".to_string())
|
let full_program_path = config.bin_dir().join(program_name);
|
||||||
|
if !full_program_path.exists() {
|
||||||
|
Err(format!(
|
||||||
|
"{} does not exist",
|
||||||
|
full_program_path.to_str().unwrap()
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut child_option: Option<std::process::Child> = None;
|
||||||
|
let mut now = Instant::now();
|
||||||
|
loop {
|
||||||
|
child_option = match child_option {
|
||||||
|
Some(mut child) => match child.try_wait() {
|
||||||
|
Ok(Some(status)) => {
|
||||||
|
println_name_value(
|
||||||
|
&format!("{} exited with:", program_name),
|
||||||
|
&status.to_string(),
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Ok(None) => Some(child),
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Error attempting to wait for program to exit: {}", err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
match std::process::Command::new(&full_program_path)
|
||||||
|
.args(&program_arguments)
|
||||||
|
.spawn()
|
||||||
|
{
|
||||||
|
Ok(child) => Some(child),
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to spawn {}: {:?}", program_name, err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if now.elapsed().as_secs() > config.update_poll_secs {
|
||||||
|
match update(config_file) {
|
||||||
|
Ok(true) => {
|
||||||
|
// Update successful, kill current process so it will be restart
|
||||||
|
if let Some(ref mut child) = child_option {
|
||||||
|
println!("Killing program: {:?}", child.kill());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(false) => {} // No update available
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to apply update: {:?}", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
now = Instant::now();
|
||||||
|
}
|
||||||
|
sleep(Duration::from_secs(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,36 @@
|
||||||
use crate::update_manifest::SignedUpdateManifest;
|
use crate::update_manifest::UpdateManifest;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use serde_yaml;
|
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use std::fs::{create_dir_all, File};
|
use std::fs::{create_dir_all, File};
|
||||||
use std::io::{Error, ErrorKind, Write};
|
use std::io::{self, Write};
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub data_dir: String,
|
|
||||||
pub json_rpc_url: String,
|
pub json_rpc_url: String,
|
||||||
pub update_pubkey: Pubkey,
|
pub update_manifest_pubkey: Pubkey,
|
||||||
pub current_install: Option<SignedUpdateManifest>,
|
pub current_update_manifest: Option<UpdateManifest>,
|
||||||
|
pub update_poll_secs: u64,
|
||||||
|
releases_dir: PathBuf,
|
||||||
|
bin_dir: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
fn _load(config_file: &str) -> Result<Self, Error> {
|
pub fn new(data_dir: &str, json_rpc_url: &str, update_manifest_pubkey: &Pubkey) -> 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
|
||||||
|
releases_dir: PathBuf::from(data_dir).join("releases"),
|
||||||
|
bin_dir: PathBuf::from(data_dir).join("bin"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _load(config_file: &str) -> Result<Self, io::Error> {
|
||||||
let file = File::open(config_file.to_string())?;
|
let file = File::open(config_file.to_string())?;
|
||||||
let config = serde_yaml::from_reader(file)
|
let config = serde_yaml::from_reader(file)
|
||||||
.map_err(|err| Error::new(ErrorKind::Other, format!("{:?}", err)))?;
|
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,9 +38,9 @@ impl Config {
|
||||||
Self::_load(config_file).map_err(|err| format!("Unable to load {}: {:?}", config_file, err))
|
Self::_load(config_file).map_err(|err| format!("Unable to load {}: {:?}", config_file, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _save(&self, config_file: &str) -> Result<(), Error> {
|
fn _save(&self, config_file: &str) -> Result<(), io::Error> {
|
||||||
let serialized = serde_yaml::to_string(self)
|
let serialized = serde_yaml::to_string(self)
|
||||||
.map_err(|err| Error::new(ErrorKind::Other, format!("{:?}", err)))?;
|
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
|
||||||
|
|
||||||
if let Some(outdir) = Path::new(&config_file).parent() {
|
if let Some(outdir) = Path::new(&config_file).parent() {
|
||||||
create_dir_all(outdir)?;
|
create_dir_all(outdir)?;
|
||||||
|
@ -43,4 +55,12 @@ impl Config {
|
||||||
self._save(config_file)
|
self._save(config_file)
|
||||||
.map_err(|err| format!("Unable to save {}: {:?}", config_file, err))
|
.map_err(|err| format!("Unable to save {}: {:?}", config_file, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bin_dir(&self) -> &PathBuf {
|
||||||
|
&self.bin_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release_dir(&self, release_sha256: &str) -> PathBuf {
|
||||||
|
self.releases_dir.join(release_sha256)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,24 +2,29 @@ pub const JSON_RPC_URL: &str = "https://api.testnet.solana.com/";
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref CONFIG_FILE: Option<String> = {
|
pub static ref CONFIG_FILE: Option<String> = {
|
||||||
dirs::config_dir().map(|mut path| {
|
dirs::home_dir().map(|mut path| {
|
||||||
path.push("solana");
|
path.extend(&[".config", "solana", "install", "config.yml"]);
|
||||||
path.push("install.yml");
|
path.to_str().unwrap().to_string()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
pub static ref USER_KEYPAIR: Option<String> = {
|
||||||
|
dirs::home_dir().map(|mut path| {
|
||||||
|
path.extend(&[".config", "solana", "id.json"]);
|
||||||
path.to_str().unwrap().to_string()
|
path.to_str().unwrap().to_string()
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
pub static ref DATA_DIR: Option<String> = {
|
pub static ref DATA_DIR: Option<String> = {
|
||||||
dirs::data_dir().map(|mut path| {
|
dirs::home_dir().map(|mut path| {
|
||||||
path.push("solana");
|
path.extend(&[".local", "share", "solana", "install"]);
|
||||||
path.to_str().unwrap().to_string()
|
path.to_str().unwrap().to_string()
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_pubkey(target: &str) -> Option<&str> {
|
pub fn update_manifest_pubkey(target: &str) -> Option<&str> {
|
||||||
match target {
|
match target {
|
||||||
"x86_64-apple-darwin" => Some("9XX329sPuskWhH4DQh6k16c87dHKhXLBZTL3Gxmve8Gp"), // TODO: This pubkey is invalid
|
"x86_64-apple-darwin" => None,
|
||||||
"x86_64-unknown-linux-gnu" => Some("8XX329sPuskWhH4DQh6k16c87dHKhXLBZTL3Gxmve8Gp"), // TODO: This pubkey is invalid
|
"x86_64-unknown-linux-gnu" => Some("EVS4V6wha5J5qzw8ZJBroMq9g6EKMg5iFWqCRrKwfo3z"), // SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,20 @@ mod config;
|
||||||
mod defaults;
|
mod defaults;
|
||||||
mod update_manifest;
|
mod update_manifest;
|
||||||
|
|
||||||
|
fn url_validator(url: String) -> Result<(), String> {
|
||||||
|
match url::Url::parse(&url) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(err) => Err(format!("{:?}", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pubkey_validator(pubkey: String) -> Result<(), String> {
|
||||||
|
match pubkey.parse::<Pubkey>() {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(err) => Err(format!("{:?}", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), String> {
|
fn main() -> Result<(), String> {
|
||||||
solana_logger::setup();
|
solana_logger::setup();
|
||||||
|
|
||||||
|
@ -39,10 +53,11 @@ fn main() -> Result<(), String> {
|
||||||
.long("data_dir")
|
.long("data_dir")
|
||||||
.value_name("PATH")
|
.value_name("PATH")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
|
.required(true)
|
||||||
.help("Directory to store install data");
|
.help("Directory to store install data");
|
||||||
match *defaults::DATA_DIR {
|
match *defaults::DATA_DIR {
|
||||||
Some(ref data_dir) => arg.default_value(&data_dir),
|
Some(ref data_dir) => arg.default_value(&data_dir),
|
||||||
None => arg.required(true),
|
None => arg,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.arg(
|
.arg(
|
||||||
|
@ -52,23 +67,22 @@ fn main() -> Result<(), String> {
|
||||||
.value_name("URL")
|
.value_name("URL")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.default_value(defaults::JSON_RPC_URL)
|
.default_value(defaults::JSON_RPC_URL)
|
||||||
|
.validator(url_validator)
|
||||||
.help("JSON RPC URL for the solana cluster"),
|
.help("JSON RPC URL for the solana cluster"),
|
||||||
)
|
)
|
||||||
.arg({
|
.arg({
|
||||||
let arg = Arg::with_name("update_pubkey")
|
let arg = Arg::with_name("update_manifest_pubkey")
|
||||||
.short("p")
|
.short("p")
|
||||||
.long("pubkey")
|
.long("pubkey")
|
||||||
.value_name("PUBKEY")
|
.value_name("PUBKEY")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.validator(|value| match value.parse::<Pubkey>() {
|
.required(true)
|
||||||
Ok(_) => Ok(()),
|
.validator(pubkey_validator)
|
||||||
Err(err) => Err(format!("{:?}", err)),
|
|
||||||
})
|
|
||||||
.help("Public key of the update manifest");
|
.help("Public key of the update manifest");
|
||||||
|
|
||||||
match defaults::update_pubkey(build_env::TARGET) {
|
match defaults::update_manifest_pubkey(build_env::TARGET) {
|
||||||
Some(default_value) => arg.default_value(default_value),
|
Some(default_value) => arg.default_value(default_value),
|
||||||
None => arg.required(true),
|
None => arg,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
@ -89,14 +103,38 @@ fn main() -> Result<(), String> {
|
||||||
SubCommand::with_name("deploy")
|
SubCommand::with_name("deploy")
|
||||||
.about("deploys a new update")
|
.about("deploys a new update")
|
||||||
.setting(AppSettings::DisableVersion)
|
.setting(AppSettings::DisableVersion)
|
||||||
|
.arg({
|
||||||
|
let arg = Arg::with_name("from_keypair_file")
|
||||||
|
.short("k")
|
||||||
|
.long("keypair")
|
||||||
|
.value_name("PATH")
|
||||||
|
.takes_value(true)
|
||||||
|
.required(true)
|
||||||
|
.help("Keypair file of the account that funds the deployment");
|
||||||
|
match *defaults::USER_KEYPAIR {
|
||||||
|
Some(ref config_file) => arg.default_value(&config_file),
|
||||||
|
None => arg,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("json_rpc_url")
|
||||||
|
.short("u")
|
||||||
|
.long("url")
|
||||||
|
.value_name("URL")
|
||||||
|
.takes_value(true)
|
||||||
|
.default_value(defaults::JSON_RPC_URL)
|
||||||
|
.validator(url_validator)
|
||||||
|
.help("JSON RPC URL for the solana cluster"),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("download_url")
|
Arg::with_name("download_url")
|
||||||
.index(1)
|
.index(1)
|
||||||
.required(true)
|
.required(true)
|
||||||
|
.validator(url_validator)
|
||||||
.help("URL to the solana release archive"),
|
.help("URL to the solana release archive"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("update_manifest_keypair")
|
Arg::with_name("update_manifest_keypair_file")
|
||||||
.index(2)
|
.index(2)
|
||||||
.required(true)
|
.required(true)
|
||||||
.help("Keypair file for the update manifest (/path/to/keypair.json)"),
|
.help("Keypair file for the update manifest (/path/to/keypair.json)"),
|
||||||
|
@ -132,24 +170,32 @@ fn main() -> Result<(), String> {
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
("init", Some(matches)) => {
|
("init", Some(matches)) => {
|
||||||
let json_rpc_url = matches.value_of("json_rpc_url").unwrap();
|
let json_rpc_url = matches.value_of("json_rpc_url").unwrap();
|
||||||
let update_pubkey = matches
|
let update_manifest_pubkey = matches
|
||||||
.value_of("update_pubkey")
|
.value_of("update_manifest_pubkey")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.parse::<Pubkey>()
|
.parse::<Pubkey>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let data_dir = matches.value_of("data_dir").unwrap();
|
let data_dir = matches.value_of("data_dir").unwrap();
|
||||||
command::init(config_file, data_dir, json_rpc_url, &update_pubkey)
|
command::init(config_file, data_dir, json_rpc_url, &update_manifest_pubkey)
|
||||||
}
|
}
|
||||||
("info", Some(matches)) => {
|
("info", Some(matches)) => {
|
||||||
let local_info_only = matches.is_present("local_info_only");
|
let local_info_only = matches.is_present("local_info_only");
|
||||||
command::info(config_file, local_info_only)
|
command::info(config_file, local_info_only).map(|_| ())
|
||||||
}
|
}
|
||||||
("deploy", Some(matches)) => {
|
("deploy", Some(matches)) => {
|
||||||
|
let from_keypair_file = matches.value_of("from_keypair_file").unwrap();
|
||||||
|
let json_rpc_url = matches.value_of("json_rpc_url").unwrap();
|
||||||
let download_url = matches.value_of("download_url").unwrap();
|
let download_url = matches.value_of("download_url").unwrap();
|
||||||
let update_manifest_keypair = matches.value_of("update_manifest_keypair").unwrap();
|
let update_manifest_keypair_file =
|
||||||
command::deploy(config_file, download_url, update_manifest_keypair)
|
matches.value_of("update_manifest_keypair_file").unwrap();
|
||||||
|
command::deploy(
|
||||||
|
json_rpc_url,
|
||||||
|
from_keypair_file,
|
||||||
|
download_url,
|
||||||
|
update_manifest_keypair_file,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
("update", Some(_matches)) => command::update(config_file),
|
("update", Some(_matches)) => command::update(config_file).map(|_| ()),
|
||||||
("run", Some(matches)) => {
|
("run", Some(matches)) => {
|
||||||
let program_name = matches.value_of("program_name").unwrap();
|
let program_name = matches.value_of("program_name").unwrap();
|
||||||
let program_arguments = matches
|
let program_arguments = matches
|
||||||
|
|
|
@ -1,19 +1,61 @@
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use solana_sdk::signature::Signature;
|
use solana_config_api::ConfigState;
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use solana_sdk::signature::{Signable, Signature};
|
||||||
|
use std::error;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
/// Information required to download and apply a given update
|
/// Information required to download and apply a given update
|
||||||
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
|
||||||
pub struct UpdateManifest {
|
pub struct UpdateManifest {
|
||||||
pub target: String, // Target triple (TARGET)
|
|
||||||
pub commit: String, // git sha1 of this update, must match the commit sha1 in the release tar.bz2
|
|
||||||
pub timestamp_secs: u64, // When the release was deployed in seconds since UNIX EPOCH
|
pub timestamp_secs: u64, // When the release was deployed in seconds since UNIX EPOCH
|
||||||
pub download_url: String, // Download URL to the release tar.bz2
|
pub download_url: String, // Download URL to the release tar.bz2
|
||||||
pub download_signature: Signature, // Signature of the release tar.bz2 file, verify with the Account public key
|
pub download_sha256: String, // SHA256 digest of the release tar.bz2 file
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Userdata of an Update Manifest program Account.
|
/// Userdata of an Update Manifest program Account.
|
||||||
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
|
||||||
pub struct SignedUpdateManifest {
|
pub struct SignedUpdateManifest {
|
||||||
pub manifest: UpdateManifest,
|
pub manifest: UpdateManifest,
|
||||||
pub manifest_signature: Signature, // Signature of UpdateInfo, verify with the Account public key
|
pub manifest_signature: Signature,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub account_pubkey: Pubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Signable for SignedUpdateManifest {
|
||||||
|
fn pubkey(&self) -> Pubkey {
|
||||||
|
self.account_pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signable_data(&self) -> Vec<u8> {
|
||||||
|
bincode::serialize(&self.manifest).expect("serialize")
|
||||||
|
}
|
||||||
|
fn get_signature(&self) -> Signature {
|
||||||
|
self.manifest_signature
|
||||||
|
}
|
||||||
|
fn set_signature(&mut self, signature: Signature) {
|
||||||
|
self.manifest_signature = signature
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignedUpdateManifest {
|
||||||
|
pub fn deserialize(account_pubkey: &Pubkey, input: &[u8]) -> Result<Self, Box<error::Error>> {
|
||||||
|
let mut manifest: SignedUpdateManifest = bincode::deserialize(input)?;
|
||||||
|
manifest.account_pubkey = *account_pubkey;
|
||||||
|
if !manifest.verify() {
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"Manifest failed to verify",
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
Ok(manifest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigState for SignedUpdateManifest {
|
||||||
|
fn max_space() -> u64 {
|
||||||
|
// TODO: Use a fully populated manifest to compute a better value
|
||||||
|
// bincode::serialized_size(&Self::default()).unwrap()
|
||||||
|
256
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,18 +67,18 @@ use solana_sdk::signature::Signature;
|
||||||
|
|
||||||
/// Information required to download and apply a given update
|
/// Information required to download and apply a given update
|
||||||
pub struct UpdateManifest {
|
pub struct UpdateManifest {
|
||||||
pub target: String, // Target triple (TARGET)
|
pub timestamp_secs: u64, // When the release was deployed in seconds since UNIX EPOCH
|
||||||
pub commit: String, // git sha1 of this update, must match the commit sha1 in the release tar.bz2
|
|
||||||
pub timestamp_secs: u64, // When the release was deployed (seconds since UNIX EPOC)
|
|
||||||
pub download_url: String, // Download URL to the release tar.bz2
|
pub download_url: String, // Download URL to the release tar.bz2
|
||||||
pub download_signature: Signature, // Signature of the release tar.bz2 file, verify with the Account public key
|
pub download_sha256: String, // SHA256 digest of the release tar.bz2 file
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Userdata of an Update Manifest program Account.
|
/// Userdata of an Update Manifest program Account.
|
||||||
|
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
|
||||||
pub struct SignedUpdateManifest {
|
pub struct SignedUpdateManifest {
|
||||||
pub manifest: UpdateManifest,
|
pub manifest: UpdateManifest,
|
||||||
pub manifest_signature: Signature, // Signature of UpdateInfo, verify with the Account public key
|
pub manifest_signature: Signature,
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the `manifest` field itself contains a corresponding signature
|
Note that the `manifest` field itself contains a corresponding signature
|
||||||
|
@ -92,9 +92,8 @@ update with an older `timestamp_secs` than what is currently installed.
|
||||||
A release archive is expected to be a tar file compressed with
|
A release archive is expected to be a tar file compressed with
|
||||||
bzip2 with the following internal structure:
|
bzip2 with the following internal structure:
|
||||||
|
|
||||||
* `/version.yml` - a simple YAML file containing the fields (1) `"commit"` - the git
|
* `/version.yml` - a simple YAML file containing the field `"target"` - the
|
||||||
sha1 for this release, and (2) `"target"` - the target tuple. Any additional
|
target tuple. Any additional fields are ignored.
|
||||||
fields are ignored.
|
|
||||||
* `/bin/` -- directory containing available programs in the release.
|
* `/bin/` -- directory containing available programs in the release.
|
||||||
`solana-install` will symlink this directory to
|
`solana-install` will symlink this directory to
|
||||||
`~/.local/share/solana-install/bin` for use by the `PATH` environment
|
`~/.local/share/solana-install/bin` for use by the `PATH` environment
|
||||||
|
@ -106,10 +105,9 @@ bzip2 with the following internal structure:
|
||||||
The `solana-install` tool is used by the user to install and update their cluster software.
|
The `solana-install` tool is used by the user to install and update their cluster software.
|
||||||
|
|
||||||
It manages the following files and directories in the user's home directory:
|
It manages the following files and directories in the user's home directory:
|
||||||
* `~/.config/solana/updater.json` - user configuration and information about currently installed software version
|
* `~/.config/solana/install/config.yml` - user configuration and information about currently installed software version
|
||||||
* `~/.local/share/solana-install/bin` - a symlink to the current release. eg, `~/.local/share/solana-update/<update-pubkey>-<manifest_signature>/bin`
|
* `~/.local/share/solana/install/bin` - a symlink to the current release. eg, `~/.local/share/solana-update/<update-pubkey>-<manifest_signature>/bin`
|
||||||
* `~/.local/share/solana-install/<update-pubkey>-<manifest_signature>/` - contents of the release
|
* `~/.local/share/solana/install/releases/<download_sha256>/` - contents of a release
|
||||||
* `~/.local/share/solana-install/<update-pubkey>-<manifest_signature>.tmp/` - temporary directory used while downloading a new release
|
|
||||||
|
|
||||||
#### Command-line Interface
|
#### Command-line Interface
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue