Add Tauri launcher with Svelte UI
- Accounts: panel login/sign-up, Microsoft device-code sign-in, offline play; tokens encrypted with a key kept in the OS credential store - Home: instance rail, live server status, news feed, social links, Play button with download progress, speed and ETA - Full settings: accounts, memory/resolution/after-launch behaviour, Java and GC presets, Minecraft keybind editor applied to options.txt, theme overrides, storage manager, self-update via GitHub releases - Game console, crash dialog with hints, per-instance overrides - Live admin branding (colours, fonts, backgrounds, custom CSS) - Online install tests for vanilla/Fabric/Quilt/Forge/NeoForge (CI) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ARcGWxLx21FwXJ3yfGriS
This commit is contained in:
60 files changed
+9424
-47
No files matched your search
@@ -0,0 +1,93 @@
|
||||
//! Self-update via GitHub Releases: find a newer `*-setup.exe`, download
|
||||
//! it and hand over to the installer.
|
||||
|
||||
use crate::state::build;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct UpdateInfo {
|
||||
pub version: String,
|
||||
pub notes: String,
|
||||
pub url: String,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Release {
|
||||
tag_name: String,
|
||||
#[serde(default)]
|
||||
body: String,
|
||||
#[serde(default)]
|
||||
draft: bool,
|
||||
#[serde(default)]
|
||||
prerelease: bool,
|
||||
assets: Vec<Asset>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Asset {
|
||||
name: String,
|
||||
browser_download_url: String,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
fn parse(v: &str) -> Vec<u64> {
|
||||
v.trim_start_matches('v').split(['.', '-', '+']).map_while(|p| p.parse().ok()).collect()
|
||||
}
|
||||
|
||||
pub fn is_newer(candidate: &str, current: &str) -> bool {
|
||||
parse(candidate) > parse(current)
|
||||
}
|
||||
|
||||
pub async fn check(http: &reqwest::Client) -> Result<Option<UpdateInfo>> {
|
||||
let Some(repo) = build::REPO.filter(|r| !r.is_empty()) else { return Ok(None) };
|
||||
let release: Release = http
|
||||
.get(format!("https://api.github.com/repos/{repo}/releases/latest"))
|
||||
.header("Accept", "application/vnd.github+json")
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
if release.draft || release.prerelease || !is_newer(&release.tag_name, env!("CARGO_PKG_VERSION")) {
|
||||
return Ok(None);
|
||||
}
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|a| a.name.to_lowercase().ends_with("-setup.exe"))
|
||||
.ok_or_else(|| anyhow!("release {} has no installer", release.tag_name))?;
|
||||
Ok(Some(UpdateInfo {
|
||||
version: release.tag_name.trim_start_matches('v').to_string(),
|
||||
notes: release.body,
|
||||
url: asset.browser_download_url.clone(),
|
||||
size: asset.size,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Download the installer and start it. The caller exits the app afterwards.
|
||||
pub async fn download_and_run(http: &reqwest::Client, url: &str) -> Result<()> {
|
||||
if !url.starts_with("https://github.com/") && !url.starts_with("https://objects.githubusercontent.com/") {
|
||||
anyhow::bail!("refusing to download an update from an unexpected host");
|
||||
}
|
||||
let dest = std::env::temp_dir().join("scopenet-launcher-setup.exe");
|
||||
let dl = scopenet_core::http::Download::new(url, dest.clone(), None, None);
|
||||
scopenet_core::http::download_one(http, &dl, &|_| {}).await.context("downloading update")?;
|
||||
// /P = passive (progress only), /R = relaunch when done (Tauri NSIS).
|
||||
std::process::Command::new(&dest).args(["/P", "/R"]).spawn().context("starting the installer")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn versions() {
|
||||
assert!(is_newer("v0.2.0", "0.1.9"));
|
||||
assert!(is_newer("1.0.0", "0.9.12"));
|
||||
assert!(!is_newer("v0.1.0", "0.1.0"));
|
||||
assert!(!is_newer("0.1.0-beta", "0.1.0"));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user