avalanche_installer/subnet_evm/
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(
14 arch: Option<Arch>,
15 os: Option<Os>,
16 target_file_path: &str,
17) -> io::Result<()> {
18 download(arch, os, None, target_file_path).await
19}
20
21pub const DEFAULT_TAG_NAME: &str = "v0.5.1";
23
24pub async fn download(
26 arch: Option<Arch>,
27 os: Option<Os>,
28 release_tag: Option<String>,
29 target_file_path: &str,
30) -> io::Result<()> {
31 let tag_name = if let Some(v) = release_tag {
33 if v.eq("latest") {
35 log::warn!("falling back 'latest' to {DEFAULT_TAG_NAME}");
36 DEFAULT_TAG_NAME.to_owned()
37 } else {
38 v
39 }
40 } else {
41 log::info!("fetching the latest git tags");
42 let mut release_info = crate::github::ReleaseResponse::default();
43 for round in 0..10 {
44 let info = match crate::github::fetch_latest_release("ava-labs", "subnet-evm").await {
45 Ok(v) => v,
46 Err(e) => {
47 log::warn!(
48 "failed fetch_latest_release {} -- retrying {}...",
49 e,
50 round + 1
51 );
52 sleep(Duration::from_secs((round + 1) * 3)).await;
53 continue;
54 }
55 };
56
57 release_info = info;
58 if release_info.tag_name.is_some() {
59 break;
60 }
61
62 log::warn!("release_info.tag_name is None -- retrying {}...", round + 1);
63 sleep(Duration::from_secs((round + 1) * 3)).await;
64 }
65
66 if release_info.tag_name.is_none() {
67 log::warn!("release_info.tag_name not found -- defaults to {DEFAULT_TAG_NAME}");
68 release_info.tag_name = Some(DEFAULT_TAG_NAME.to_string());
69 }
70
71 if release_info.prerelease {
72 log::warn!(
73 "latest release '{}' is prerelease, falling back to default tag name '{}'",
74 release_info.tag_name.unwrap(),
75 DEFAULT_TAG_NAME
76 );
77 DEFAULT_TAG_NAME.to_string()
78 } else {
79 release_info.tag_name.unwrap()
80 }
81 };
82
83 log::info!(
85 "detecting arch and platform for the release version tag {}",
86 tag_name
87 );
88 let arch = {
89 if arch.is_none() {
90 match env::consts::ARCH {
91 "x86_64" => String::from("amd64"),
92 "aarch64" => String::from("arm64"),
93 _ => String::from(""),
94 }
95 } else {
96 let arch = arch.unwrap();
97 arch.to_string()
98 }
99 };
100
101 let (file_name, dir_decoder) = {
103 if os.is_none() {
104 if cfg!(target_os = "macos") {
105 (
106 format!(
107 "subnet-evm_{}_darwin_{arch}.tar.gz",
108 tag_name.trim_start_matches("v")
109 ),
110 DirDecoder::TarGzip,
111 )
112 } else if cfg!(unix) {
113 (
114 format!(
115 "subnet-evm_{}_linux_{arch}.tar.gz",
116 tag_name.trim_start_matches("v")
117 ),
118 DirDecoder::TarGzip,
119 )
120 } else {
121 return Err(Error::new(ErrorKind::Other, "unknown OS"));
122 }
123 } else {
124 let os = os.unwrap();
125 match os {
126 Os::MacOs => (
127 format!(
128 "subnet-evm_{}_darwin_{arch}.tar.gz",
129 tag_name.trim_start_matches("v")
130 ),
131 DirDecoder::TarGzip,
132 ),
133 Os::Linux => (
134 format!(
135 "subnet-evm_{}_linux_{arch}.tar.gz",
136 tag_name.trim_start_matches("v")
137 ),
138 DirDecoder::TarGzip,
139 ),
140 Os::Windows => return Err(Error::new(ErrorKind::Other, "windows not supported")),
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 subnet-evm '{}'", file_name);
152 let download_url = format!(
153 "https://github.com/ava-labs/subnet-evm/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 subnet_evm_path = Path::new(&dst_dir_path).join("subnet-evm");
175 {
176 let f = File::open(&subnet_evm_path)?;
177 f.set_permissions(PermissionsExt::from_mode(0o777))?;
178 }
179 log::info!(
180 "copying {} to {target_file_path}",
181 subnet_evm_path.display()
182 );
183 fs::copy(&subnet_evm_path, &target_file_path)?;
184 fs::remove_file(&subnet_evm_path)?;
185
186 Ok(())
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}