monerochan_cli/commands/
install_toolchain.rs1use std::{
2 fs::{self},
3 process::Command,
4};
5
6use anyhow::Result;
7use clap::Parser;
8use dirs::home_dir;
9use rand::{distributions::Alphanumeric, Rng};
10use reqwest::Client;
11use monerochan::install::download_file;
12
13#[cfg(target_family = "unix")]
14use std::os::unix::fs::PermissionsExt;
15
16use crate::{
17 get_target, get_toolchain_download_url, is_supported_target, url_exists, RUSTUP_TOOLCHAIN_NAME,
18};
19
20#[derive(Parser)]
21#[command(name = "install-toolchain", about = "Install the cargo-monerochan toolchain.")]
22pub struct InstallToolchainCmd {
23 #[arg(short, long, env = "GITHUB_TOKEN")]
24 pub token: Option<String>,
25}
26
27impl InstallToolchainCmd {
28 pub fn run(&self) -> Result<()> {
29 if Command::new("rustup")
31 .arg("--version")
32 .stdout(std::process::Stdio::null())
33 .stderr(std::process::Stdio::null())
34 .status()
35 .is_err()
36 {
37 return Err(anyhow::anyhow!(
38 "Rust is not installed. Please install Rust from https://rustup.rs/ and try again."
39 ));
40 }
41
42 let client_builder = Client::builder().user_agent("Mozilla/5.0");
44 let client = if let Some(ref token) = self.token {
45 client_builder
46 .default_headers({
47 let mut headers = reqwest::header::HeaderMap::new();
48 headers.insert(
49 reqwest::header::AUTHORIZATION,
50 reqwest::header::HeaderValue::from_str(&format!("token {token}")).unwrap(),
51 );
52 headers
53 })
54 .build()?
55 } else {
56 client_builder.build()?
57 };
58
59 let root_dir = home_dir().unwrap().join(".monerochan");
61 match fs::read_dir(&root_dir) {
62 Ok(entries) =>
63 {
64 #[allow(clippy::manual_flatten)]
65 for entry in entries {
66 if let Ok(entry) = entry {
67 let entry_path = entry.path();
68 let entry_name = entry_path.file_name().unwrap();
69 if entry_path.is_dir() &&
70 entry_name != "bin" &&
71 entry_name != "circuits" &&
72 entry_name != "toolchains"
73 {
74 if let Err(err) = fs::remove_dir_all(&entry_path) {
75 println!("Failed to remove directory {entry_path:?}: {err}");
76 }
77 } else if entry_path.is_file() {
78 if let Err(err) = fs::remove_file(&entry_path) {
79 println!("Failed to remove file {entry_path:?}: {err}");
80 }
81 }
82 }
83 }
84 }
85 Err(_) => println!("No existing ~/.monerochan directory to remove."),
86 }
87 println!("Successfully cleaned up ~/.monerochan directory.");
88 match fs::create_dir_all(&root_dir) {
89 Ok(_) => println!("Successfully created ~/.monerochan directory."),
90 Err(err) => println!("Failed to create ~/.monerochan directory: {err}"),
91 };
92
93 assert!(
94 is_supported_target(),
95 "Unsupported architecture. Please build the toolchain from source."
96 );
97 let target = get_target();
98 let toolchain_asset_name = format!("rust-toolchain-{target}.tar.gz");
99 let toolchain_archive_path = root_dir.join(toolchain_asset_name.clone());
100 let toolchain_dir = root_dir.join(&target);
101 let rt = tokio::runtime::Runtime::new()?;
102
103 let toolchain_download_url =
104 rt.block_on(get_toolchain_download_url(&client, target.to_string()));
105
106 let artifact_exists = rt.block_on(url_exists(&client, toolchain_download_url.as_str()));
107 if !artifact_exists {
108 return Err(anyhow::anyhow!(
109 "Unsupported architecture. Please build the toolchain from source."
110 ));
111 }
112
113 let mut file = fs::File::create(toolchain_archive_path)?;
115 rt.block_on(download_file(&client, toolchain_download_url.as_str(), &mut file)).unwrap();
116
117 let res = Command::new("rustup")
119 .current_dir(&root_dir)
120 .args(["toolchain", "remove", RUSTUP_TOOLCHAIN_NAME])
121 .stdout(std::process::Stdio::null())
122 .stderr(std::process::Stdio::null())
123 .status();
124 match res {
125 Ok(_) => println!("Successfully removed existing toolchain."),
126 Err(_) => println!("Failed to remove existing toolchain."),
127 }
128
129 fs::create_dir_all(toolchain_dir.clone())?;
131 Command::new("tar")
132 .current_dir(&root_dir)
133 .args(["-xzf", &toolchain_asset_name, "-C", &toolchain_dir.to_string_lossy()])
134 .status()?;
135
136 let toolchains_dir = root_dir.join("toolchains");
138 fs::create_dir_all(&toolchains_dir)?;
139 let random_string: String =
140 rand::thread_rng().sample_iter(&Alphanumeric).take(10).map(char::from).collect();
141 let new_toolchain_dir = toolchains_dir.join(random_string);
142 fs::rename(&toolchain_dir, &new_toolchain_dir)?;
143
144 Command::new("rustup")
146 .current_dir(&root_dir)
147 .args([
148 "toolchain",
149 "link",
150 RUSTUP_TOOLCHAIN_NAME,
151 &new_toolchain_dir.to_string_lossy(),
152 ])
153 .status()?;
154 println!("Successfully linked toolchain to rustup.");
155
156 let bin_dir = new_toolchain_dir.join("bin");
158 let rustlib_bin_dir = new_toolchain_dir.join(format!("lib/rustlib/{target}/bin"));
159 for entry in fs::read_dir(bin_dir)?.chain(fs::read_dir(rustlib_bin_dir)?) {
160 let entry = entry?;
161 if entry.path().is_file() {
162 let mut perms = entry.metadata()?.permissions();
163 perms.set_mode(0o755);
164 fs::set_permissions(entry.path(), perms)?;
165 }
166 }
167
168 Ok(())
169 }
170}