|
1 | | -use crate::error::dfxvm::SelfUpdateError; |
| 1 | +use crate::dist_manifest::lookup_latest_version; |
| 2 | +use crate::download::{download_file, verify_checksum}; |
| 3 | +use crate::error::dfxvm::self_update::CleanupSelfUpdaterError; |
| 4 | +use crate::error::dfxvm::{ |
| 5 | + self_update::{ |
| 6 | + DownloadLatestBinaryError, |
| 7 | + DownloadLatestBinaryError::CreateTempDirIn, |
| 8 | + ExtractBinaryError, |
| 9 | + ExtractBinaryError::{DfxvmNotFound, ReadArchiveEntries, UnpackBinary}, |
| 10 | + FormatTarballUrlError, SelfReplaceError, |
| 11 | + }, |
| 12 | + SelfUpdateError, |
| 13 | + SelfUpdateError::Exec, |
| 14 | +}; |
| 15 | +use crate::fs::{open_file, remove_file}; |
| 16 | +use crate::installation::install_binaries; |
2 | 17 | use crate::locations::Locations; |
| 18 | +use crate::settings::Settings; |
| 19 | +use flate2::read::GzDecoder; |
| 20 | +use reqwest::{Client, Url}; |
| 21 | +use std::os::unix::prelude::CommandExt; |
| 22 | +use std::path::Path; |
| 23 | +use tar::Archive; |
3 | 24 |
|
4 | | -pub fn self_update(_locations: &Locations) -> Result<(), SelfUpdateError> { |
5 | | - println!("update dfxvm to latest"); |
| 25 | +pub async fn self_update(locations: &Locations) -> Result<(), SelfUpdateError> { |
| 26 | + info!("checking for self-update"); |
| 27 | + let settings = Settings::load_or_default(&locations.settings_path())?; |
| 28 | + let latest_version = lookup_latest_version(&settings).await?; |
| 29 | + let our_version = env!("CARGO_PKG_VERSION"); |
| 30 | + if latest_version == our_version { |
| 31 | + info!("dfxvm unchanged - {latest_version}"); |
| 32 | + return Ok(()); |
| 33 | + } |
| 34 | + |
| 35 | + info!("updating to {latest_version}"); |
| 36 | + |
| 37 | + let tarball_url = format_tarball_url(&settings)?; |
| 38 | + let self_update_path = locations.self_update_path(); |
| 39 | + |
| 40 | + download_latest_binary(&tarball_url, &self_update_path, locations).await?; |
| 41 | + |
| 42 | + let mut command = std::process::Command::new(self_update_path); |
| 43 | + command.arg("--self-replace"); |
| 44 | + let err = command.exec(); |
| 45 | + Err(Exec { |
| 46 | + command, |
| 47 | + source: err, |
| 48 | + }) |
| 49 | +} |
| 50 | + |
| 51 | +pub fn self_replace(locations: &Locations) -> Result<(), SelfReplaceError> { |
| 52 | + install_binaries(&locations.bin_dir())?; |
| 53 | + Ok(()) |
| 54 | +} |
| 55 | + |
| 56 | +// called on next execution of dfx or dfxvm |
| 57 | +pub fn cleanup_self_updater(locations: &Locations) -> Result<(), CleanupSelfUpdaterError> { |
| 58 | + let path = locations.self_update_path(); |
| 59 | + |
| 60 | + if path.exists() { |
| 61 | + remove_file(&path)?; |
| 62 | + } |
| 63 | + |
| 64 | + Ok(()) |
| 65 | +} |
| 66 | + |
| 67 | +fn format_tarball_url(settings: &Settings) -> Result<Url, FormatTarballUrlError> { |
| 68 | + #[cfg(target_arch = "aarch64")] |
| 69 | + let architecture = "aarch64-apple-darwin"; |
| 70 | + #[cfg(all(target_os = "macos", target_arch = "x86_64"))] |
| 71 | + let architecture = "x86_64-apple-darwin"; |
| 72 | + #[cfg(target_os = "linux")] |
| 73 | + let architecture = "x86_64-unknown-linux-gnu"; |
| 74 | + |
| 75 | + let basename = format!("dfxvm-{}", architecture); |
| 76 | + let url = format!( |
| 77 | + "{}/{basename}.tar.gz", |
| 78 | + settings.dfxvm_latest_download_root() |
| 79 | + ); |
| 80 | + |
| 81 | + Url::parse(&url).map_err(|source| FormatTarballUrlError { url, source }) |
| 82 | +} |
| 83 | + |
| 84 | +async fn download_latest_binary( |
| 85 | + tarball_url: &Url, |
| 86 | + binary_path: &Path, |
| 87 | + locations: &Locations, |
| 88 | +) -> Result<(), DownloadLatestBinaryError> { |
| 89 | + let shasum_url = Url::parse(&format!("{tarball_url}.sha256"))?; |
| 90 | + |
| 91 | + let download_dir = tempfile::Builder::new() |
| 92 | + .prefix("dfxvm-download") |
| 93 | + .tempdir_in(locations.data_local_dir()) |
| 94 | + .map_err(|source| CreateTempDirIn { |
| 95 | + path: locations.data_local_dir().to_path_buf(), |
| 96 | + source, |
| 97 | + })?; |
| 98 | + |
| 99 | + let downloaded_tarball_path = download_dir.path().join("dfxvm.tar.gz"); |
| 100 | + let downloaded_shasum_path = download_dir.path().join("dfxvm.tar.gz.sha256"); |
| 101 | + |
| 102 | + let client = Client::new(); |
| 103 | + |
| 104 | + download_file(&client, &shasum_url, &downloaded_shasum_path).await?; |
| 105 | + let computed_hash = download_file(&client, tarball_url, &downloaded_tarball_path).await?; |
| 106 | + verify_checksum(computed_hash, &downloaded_shasum_path)?; |
| 107 | + |
| 108 | + extract_binary(binary_path, &downloaded_tarball_path)?; |
| 109 | + Ok(()) |
| 110 | +} |
| 111 | + |
| 112 | +fn extract_binary( |
| 113 | + binary_path: &Path, |
| 114 | + downloaded_tarball_path: &Path, |
| 115 | +) -> Result<(), ExtractBinaryError> { |
| 116 | + let tar_gz = open_file(downloaded_tarball_path)?; |
| 117 | + let tar = GzDecoder::new(tar_gz); |
| 118 | + |
| 119 | + Archive::new(tar) |
| 120 | + .entries() |
| 121 | + .map_err(ReadArchiveEntries)? |
| 122 | + .enumerate() |
| 123 | + .filter_map(|(_i, entry)| entry.ok()) |
| 124 | + .find(|entry| { |
| 125 | + entry |
| 126 | + .header() |
| 127 | + .path() |
| 128 | + .ok() |
| 129 | + .as_ref() |
| 130 | + .and_then(|x| x.to_str()) |
| 131 | + .map(|str_path| str_path.ends_with("dfxvm")) |
| 132 | + .unwrap_or(false) |
| 133 | + }) |
| 134 | + .ok_or(DfxvmNotFound)? |
| 135 | + .unpack(binary_path) |
| 136 | + .map_err(UnpackBinary)?; |
6 | 137 | Ok(()) |
7 | 138 | } |
0 commit comments