avalanche_installer/avalanchego/
github.rs1use std::{
2 env, fmt,
3 fs::{self, File},
4 io::{self, copy, Cursor, Error, ErrorKind},
5 os::unix::fs::PermissionsExt,
6 path::Path,
7};
8
9use compress_manager::DirDecoder;
10use tokio::time::{sleep, Duration};
11
12pub async fn download_latest(arch: Option<Arch>, os: Option<Os>) -> io::Result<String> {
14 download(arch, os, None).await
15}
16
17pub const DEFAULT_TAG_NAME: &str = "v1.10.3";
19
20pub async fn download(
30 arch: Option<Arch>,
31 os: Option<Os>,
32 release_tag: Option<String>,
33) -> io::Result<String> {
34 let tag_name = if let Some(v) = release_tag {
36 if v.eq("latest") {
38 log::warn!("falling back 'latest' to {DEFAULT_TAG_NAME}");
39 DEFAULT_TAG_NAME.to_owned()
40 } else {
41 v
42 }
43 } else {
44 log::info!("fetching the latest git tags");
45 let mut release_info = crate::github::ReleaseResponse::default();
46 for round in 0..10 {
47 let info = match crate::github::fetch_latest_release("ava-labs", "avalanchego").await {
48 Ok(v) => v,
49 Err(e) => {
50 log::warn!(
51 "failed fetch_latest_release {} -- retrying {}...",
52 e,
53 round + 1
54 );
55 sleep(Duration::from_secs((round + 1) * 3)).await;
56 continue;
57 }
58 };
59
60 release_info = info;
61 if release_info.tag_name.is_some() {
62 break;
63 }
64
65 log::warn!("release_info.tag_name is None -- retrying {}...", round + 1);
66 sleep(Duration::from_secs((round + 1) * 3)).await;
67 }
68
69 if release_info.tag_name.is_none() {
70 log::warn!("release_info.tag_name not found -- defaults to {DEFAULT_TAG_NAME}");
71 release_info.tag_name = Some(DEFAULT_TAG_NAME.to_string());
72 }
73
74 if release_info.prerelease {
75 log::warn!(
76 "latest release '{}' is prerelease, falling back to default tag name '{}'",
77 release_info.tag_name.unwrap(),
78 DEFAULT_TAG_NAME
79 );
80 DEFAULT_TAG_NAME.to_string()
81 } else {
82 release_info.tag_name.unwrap()
83 }
84 };
85
86 log::info!(
88 "detecting arch and platform for the release version tag {}",
89 tag_name
90 );
91 let arch = {
92 if arch.is_none() {
93 match env::consts::ARCH {
94 "x86_64" => String::from("amd64"),
95 "aarch64" => String::from("arm64"),
96 _ => String::from(""),
97 }
98 } else {
99 let arch = arch.unwrap();
100 arch.to_string()
101 }
102 };
103
104 let (file_name, dir_decoder) = {
107 if os.is_none() {
108 if cfg!(target_os = "macos") {
109 (
110 format!("avalanchego-macos-{}.zip", tag_name),
111 DirDecoder::Zip,
112 )
113 } else if cfg!(unix) {
114 (
115 format!("avalanchego-linux-{}-{}.tar.gz", arch, tag_name),
116 DirDecoder::TarGzip,
117 )
118 } else if cfg!(windows) {
119 (
120 format!("avalanchego-win-{}-experimental.zip", tag_name),
121 DirDecoder::Zip,
122 )
123 } else {
124 (String::new(), DirDecoder::Zip)
125 }
126 } else {
127 let os = os.unwrap();
128 match os {
129 Os::MacOs => (
130 format!("avalanchego-macos-{}.zip", tag_name),
131 DirDecoder::Zip,
132 ),
133 Os::Linux => (
134 format!("avalanchego-linux-{}-{}.tar.gz", arch, tag_name),
135 DirDecoder::TarGzip,
136 ),
137 Os::Windows => (
138 format!("avalanchego-win-{}-experimental.zip", tag_name),
139 DirDecoder::Zip,
140 ),
141 }
142 }
143 };
144 if file_name.is_empty() {
145 return Err(Error::new(
146 ErrorKind::Other,
147 format!("unknown platform '{}'", env::consts::OS),
148 ));
149 }
150
151 log::info!("downloading latest avalanchego '{}'", file_name);
152 let download_url = format!(
153 "https://github.com/ava-labs/avalanchego/releases/download/{}/{}",
154 tag_name, file_name
155 );
156 let tmp_file_path = random_manager::tmp_path(10, Some(dir_decoder.suffix()))?;
157 download_file(&download_url, &tmp_file_path).await?;
158
159 let dst_dir_path = random_manager::tmp_path(10, None)?;
160 log::info!("unpacking {} to {}", tmp_file_path, dst_dir_path);
161 compress_manager::unpack_directory(&tmp_file_path, &dst_dir_path, dir_decoder.clone())?;
162
163 log::info!("cleaning up downloaded file {}", tmp_file_path);
165 match fs::remove_file(&tmp_file_path) {
166 Ok(_) => log::info!("removed downloaded file {}", tmp_file_path),
167 Err(e) => log::warn!(
168 "failed to remove downloaded file {} ({}), skipping for now...",
169 tmp_file_path,
170 e
171 ),
172 }
173
174 let avalanchego_path = if dir_decoder.clone().suffix() == DirDecoder::Zip.suffix() {
175 Path::new(&dst_dir_path).join("build").join("avalanchego")
176 } else {
177 Path::new(&dst_dir_path)
178 .join(format!("avalanchego-{}", tag_name))
179 .join("avalanchego")
180 };
181
182 {
183 let f = File::open(&avalanchego_path)?;
184 f.set_permissions(PermissionsExt::from_mode(0o777))?;
185 }
186 Ok(String::from(avalanchego_path.as_os_str().to_str().unwrap()))
187}
188
189#[derive(Eq, PartialEq, Clone)]
191pub enum Arch {
192 Amd64,
193 Arm64,
194}
195
196impl fmt::Display for Arch {
200 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
201 match self {
202 Arch::Amd64 => write!(f, "amd64"),
203 Arch::Arm64 => write!(f, "arm64"),
204 }
205 }
206}
207
208impl Arch {
209 pub fn new(arch: &str) -> io::Result<Self> {
210 match arch {
211 "amd64" => Ok(Arch::Amd64),
212 "arm64" => Ok(Arch::Arm64),
213 _ => Err(Error::new(
214 ErrorKind::InvalidInput,
215 format!("unknown arch {}", arch),
216 )),
217 }
218 }
219}
220
221#[derive(Eq, PartialEq, Clone)]
223pub enum Os {
224 MacOs,
225 Linux,
226 Windows,
227}
228
229impl fmt::Display for Os {
233 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
234 match self {
235 Os::MacOs => write!(f, "macos"),
236 Os::Linux => write!(f, "linux"),
237 Os::Windows => write!(f, "win"),
238 }
239 }
240}
241
242impl Os {
243 pub fn new(os: &str) -> io::Result<Self> {
244 match os {
245 "macos" => Ok(Os::MacOs),
246 "linux" => Ok(Os::Linux),
247 "win" => Ok(Os::Windows),
248 _ => Err(Error::new(
249 ErrorKind::InvalidInput,
250 format!("unknown os {}", os),
251 )),
252 }
253 }
254}
255
256pub async fn download_file(ep: &str, file_path: &str) -> io::Result<()> {
258 log::info!("downloading the file via {}", ep);
259 let resp = reqwest::get(ep)
260 .await
261 .map_err(|e| Error::new(ErrorKind::Other, format!("failed reqwest::get {}", e)))?;
262
263 let mut content = Cursor::new(
264 resp.bytes()
265 .await
266 .map_err(|e| Error::new(ErrorKind::Other, format!("failed bytes {}", e)))?,
267 );
268
269 let mut f = File::create(file_path)?;
270 copy(&mut content, &mut f)?;
271
272 Ok(())
273}