school-computer-toolkit/src/main.rs

333 lines
10 KiB
Rust

#![feature(maybe_uninit_slice)]
#![feature(maybe_uninit_uninit_array)]
#[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)
}
macro_rules! swtools_path {
($( $path:literal )/+) => {
Path::new(concat!("C:\\SWTools" $(, "\\", $path )+))
};
}
pub fn desktop_path() -> Option<PathBuf> {
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")))
}
#[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);
}
let desktop_path = desktop_path()
.ok_or_else(|| miette!("Could not find your desktop directory."))?;
let nppp_zip = swtools_path!("temp" / "notepad-plus-plus.zip");
let nppp_dir = swtools_path!("notepad-plus-plus");
let arduino_zip = swtools_path!("temp" / "arduino.zip");
let arduino_dir = swtools_path!("arduino");
let jdk_19_zip = swtools_path!("temp" / "jdk-19.zip");
let jdk_19_dir = swtools_path!("jdk-19");
let epp_zip = swtools_path!("temp" / "explorer-plus-plus.zip");
let epp_dir = swtools_path!("explorer-plus-plus");
let minecraft_dir = swtools_path!("minecraft");
//let psiphon_dir = swtools_path!("psiphon");
let psiphon_bin = swtools_path!("psiphon" / "psiphon3.exe");
let rustup_init = swtools_path!("temp" / "rustup-init.exe");
let deno_zip = swtools_path!("temp" / "deno.zip");
let deno_dir = swtools_path!("deno");
//let deno_exe = swtools_path!("deno" / "deno.exe");
let nppp_pl = [
Step::DownloadFile {
file: nppp_zip.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.into(),
dest: nppp_dir.into(),
},
Step::CreateShortcut {
target: ShortcutTarget::Executable {
file: nppp_dir.join("notepad++.exe").into(),
args: "",
},
file: desktop_path.join("Notepad++.lnk").into(),
},
];
let epp_pl = [
Step::DownloadFile {
file: epp_zip.into(),
res: RemoteResource::Url(
"https://explorerplusplus.com/software/explorer++_1.3.5_x64.zip",
),
},
Step::ExtractFile {
file: epp_zip.into(),
dest: epp_dir.into(),
},
Step::CreateShortcut {
target: ShortcutTarget::Executable {
file: epp_dir.join("Explorer++.exe").into(),
args: "",
},
file: desktop_path.join("Explorer++.lnk").into(),
},
];
let psiphon_pl = [
Step::DownloadFile {
file: psiphon_bin.into(),
res: RemoteResource::Url("https://s3.amazonaws.com/f58p-mqce-k1yj/psiphon3.exe"),
},
Step::CreateShortcut {
target: ShortcutTarget::Executable {
file: psiphon_bin.into(),
args: "",
},
file: desktop_path.join("Psiphon3.lnk").into(),
},
];
let minecraft_pl = [
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(),
},
];
let c_drive_lnk_pl = [Step::CreateShortcut {
target: ShortcutTarget::Path {
path: Path::new("C:\\").into(),
},
file: desktop_path.join("OSDisk (C).lnk").into(),
}];
let pwsh_pl = [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 arduino_pl = [
Step::DownloadFile {
file: arduino_zip.into(),
res: RemoteResource::GitHubArtifact {
repo: "arduino/arduino-ide",
pattern: "arduino-ide_{{tag_name}}_Windows_64bit.zip",
},
},
Step::ExtractFile {
file: arduino_zip.into(),
dest: arduino_dir.into(),
},
Step::CreateShortcut {
target: ShortcutTarget::Executable {
file: arduino_dir.join("Arduino IDE.exe").into(),
args: "",
},
file: desktop_path.join("Arduino IDE.lnk").into(),
},
// see https://docs.arduino.cc/software/ide-v1/tutorials/PortableIDE
// TODO: assess if this is actually necessary
//Step::CreateDirectory {
// target: arduino_dir.join("portable").into(),
// parents: false,
//}
];
let jdk_19_pl = [
Step::DownloadFile {
file: jdk_19_zip.into(),
// TODO: expand GitHubArtifact templating support and replace the hardcoded
// url with that.
//res: RemoteResource::GitHubArtifact {
// repo: "adoptium/temurin19-binaries",
// pattern: "OpenJDK19U-jdk_x64_windows_hotspot_19.0.1_10.zip arduino-ide_{{tag_name}}_Windows_64bit.zip",
//},
res: RemoteResource::Url(
"https://github.com/adoptium/temurin19-binaries/releases/download/jdk-19.0.1%2B10/OpenJDK19U-jdk_x64_windows_hotspot_19.0.1_10.zip",
),
},
Step::ExtractFile {
file: jdk_19_zip.into(),
dest: jdk_19_dir.into(),
},
Step::AppendPath(jdk_19_dir.join("bin").into()),
];
let rust_pl = [
Step::DownloadFile {
file: rustup_init.into(),
res: RemoteResource::Url(
"https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe",
),
},
Step::ExecuteCommand {
file: rustup_init.into(),
args: &[
"--default-host",
"x86_64-pc-windows-gnu",
"--default-toolchain",
"nightly",
"--profile",
"default",
],
},
];
let deno_pl = [
Step::CreateDirectory {
target: deno_dir.into(),
parents: true,
},
Step::DownloadFile {
file: deno_zip.into(),
res: RemoteResource::Url(
"https://github.com/denoland/deno/releases/latest/download/deno-x86_64-pc-windows-msvc.zip"
),
},
Step::ExtractFile {
file: deno_zip.into(),
dest: deno_dir.into(),
},
Step::AppendPath(deno_dir.into()),
];
let pipelines = [
Pipeline::new("Install Notepad++", nppp_pl.as_slice()),
Pipeline::new("Install Explorer++", epp_pl.as_slice()),
Pipeline::new("Install Psiphon VPN", psiphon_pl.as_slice()),
Pipeline::new("Install Minecraft (Java Edition)", minecraft_pl.as_slice()),
Pipeline::new("Create C:\\ Shortcut", c_drive_lnk_pl.as_slice()),
Pipeline::new("Create PowerShell Shortcut", pwsh_pl.as_slice()),
Pipeline::new("Install Arduino IDE v2", arduino_pl.as_slice()),
Pipeline::new("Install Java (19/temurin)", jdk_19_pl.as_slice()),
Pipeline::new("Install Rust (nightly)", rust_pl.as_slice()),
// TODO: add an option to install from src
Pipeline::new("Install Deno (pre-compiled)", deno_pl.as_slice()),
];
let mut args = std::env::args_os();
let _ = args.next();
if let Some(arg) = args.next() {
match arg.to_str() {
Some("list") => {
println!("Pipelines:");
for pipeline in pipelines {
let name = pipeline.name.unwrap_or("Unnamed");
println!("\t{name}");
}
return Ok(());
}
Some("run") => {}
Some(cmd) => {
println!("Unrecognized command: {cmd}");
}
None => {
println!("Unrecognized non-utf8 command");
}
}
}
let ctx = Context {
reqwest: reqwest::Client::builder()
.user_agent(USER_AGENT_STR)
.build()
.into_diagnostic()
.wrap_err("Could not initialize HTTP client.")?,
};
let ctx = &ctx;
let (_, results) =
async_scoped::Scope::<'_, _, async_scoped::Tokio>::scope_and_block(move |scope| {
pipelines
.into_iter()
.for_each(|pipeline| scope.spawn(async move { pipeline.invoke(ctx).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(())
}
}