333 lines
10 KiB
Rust
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(())
|
|
}
|
|
}
|