#[macro_use] extern crate miette; #[macro_use] extern crate serde; mod install; use std::path::{Path, PathBuf}; use miette::{IntoDiagnostic, Result, WrapErr}; use install::{Pipeline, RemoteResource, ShortcutTarget, Step}; const SWTOOLS_PATH: &'static str = "C:\\SWTools"; const USER_AGENT_STR: &'static str = "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"; pub fn swtools_path() -> &'static Path { Path::new(SWTOOLS_PATH) } pub fn desktop_path_fallible() -> Option { dirs::desktop_dir() .filter(|dir| dir.exists()) .or_else(|| Some(Path::new("H:\\Profile\\Desktop").to_owned())) .filter(|dir| dir.exists()) .or_else(|| dirs::home_dir().map(|dir| dir.join("Desktop"))) } pub fn desktop_path() -> PathBuf { desktop_path_fallible().expect("Desktop directory should have been resolved by now.") } #[derive(Clone)] pub struct Context { pub reqwest: reqwest::Client, } #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { println!("Bootstrapping..."); if cfg!(not(windows)) { bail!("Your platform is not supported."); } if !swtools_path().exists() { bail!("Could not find or access {}", SWTOOLS_PATH); } if desktop_path_fallible().is_none() { bail!("Could not find your desktop directory."); } let nppp_zip = swtools_path().join("temp").join("notepad-plus-plus.zip"); let nppp_dir = swtools_path().join("notepad-plus-plus"); let epp_zip = swtools_path().join("temp").join("explorer-plus-plus.zip"); let epp_dir = swtools_path().join("explorer-plus-plus"); let minecraft_dir = swtools_path().join("minecraft"); let psiphon_dir = swtools_path().join("psiphon"); let psiphon_bin = psiphon_dir.join("psiphon3.exe"); let pipelines = [ Pipeline::new( "Install Notepad++", vec![ Step::DownloadFile { file: nppp_zip.clone().into(), res: RemoteResource::GitHubArtifact { repo: "notepad-plus-plus/notepad-plus-plus", pattern: "npp.{{tag_name_strip_prefix}}.portable.x64.zip", }, }, Step::ExtractFile { file: nppp_zip.clone().into(), dest: nppp_dir.clone().into(), }, Step::CreateShortcut { target: ShortcutTarget::Executable { file: nppp_dir.join("notepad++.exe").into(), args: "", }, file: desktop_path().join("Notepad++.lnk").into(), }, ], ), Pipeline::new( "Install Explorer++", vec![ Step::DownloadFile { file: epp_zip.clone().into(), res: RemoteResource::Url( "https://explorerplusplus.com/software/explorer++_1.3.5_x64.zip", ), }, Step::ExtractFile { file: epp_zip.clone().into(), dest: epp_dir.clone().into(), }, Step::CreateShortcut { target: ShortcutTarget::Executable { file: epp_dir.join("Explorer++.exe").into(), args: "", }, file: desktop_path().join("Explorer++.lnk").into(), }, ], ), Pipeline::new( "Install Psiphon VPN", vec![ Step::DownloadFile { file: psiphon_bin.clone().into(), res: RemoteResource::Url( "https://s3.amazonaws.com/f58p-mqce-k1yj/psiphon3.exe", ), }, Step::CreateShortcut { target: ShortcutTarget::Executable { file: psiphon_bin.clone().into(), args: "", }, file: desktop_path().join("Psiphon3.lnk").into(), }, ], ), Pipeline::new( "Install Minecraft (Java Edition)", vec![ Step::DownloadFile { file: minecraft_dir.join("Minecraft.exe").into(), res: RemoteResource::Url("https://launcher.mojang.com/download/Minecraft.exe"), }, Step::CreateShortcut { target: ShortcutTarget::Executable { file: minecraft_dir.join("Minecraft.exe").into(), args: "", }, file: desktop_path().join("Minecraft.lnk").into(), }, ], ), Pipeline::new( "Create C:\\ Shortcut", vec![Step::CreateShortcut { target: ShortcutTarget::Path { path: Path::new("C:\\").into(), }, file: desktop_path().join("OSDisk (C).lnk").into(), }], ), Pipeline::new( "Create PowerShell Shortcut", vec![Step::CreateShortcut { target: ShortcutTarget::Executable { file: Path::new("C:\\WINDOWS\\system32\\cmd.exe").into(), args: "/C powershell", }, file: desktop_path().join("PowerShell.lnk").into(), }], ), ]; let ctx = Context { reqwest: reqwest::Client::builder() .user_agent(USER_AGENT_STR) .build() .into_diagnostic() .wrap_err("Could not initialize HTTP client.")?, }; let results = futures_util::future::join_all( pipelines .into_iter() .map(|pipeline| tokio::spawn({ let ctx = ctx.clone(); async move { pipeline.invoke(&ctx).await } })), ) .await; let results = results .into_iter() .map(|result| match result.into_diagnostic().wrap_err("Task error.") { Ok(Ok(t)) => Ok(t), Ok(Err(e)) => Err(e), Err(e) => Err(e), }); let mut had_error = false; for result in results { if let Err(e) = result { had_error = true; eprintln!("{e:?}"); } } if had_error { Err(miette!("One or more errors in pipelines.")) } else { Ok(()) } }