mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
chore(gui-client): use new download links (#4754)
ae67064121 works on the live site. However if you click the notification while the tunnel is coming up, there's a chance that the download will fail because Firezone isn't fully up yet. Oops. That will probably only affect us since we have github.com as a resource. If real customers are okay with their Firezone updates coming through normal Internet it'll probably be fine. --------- Signed-off-by: Reactor Scram <ReactorScram@users.noreply.github.com> Co-authored-by: Jamil Bou Kheir <jamilbk@users.noreply.github.com>
This commit is contained in:
@@ -29,9 +29,8 @@ fn check_for_updates() -> Result<()> {
|
||||
client::logging::debug_command_setup()?;
|
||||
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let release = rt.block_on(client::updates::check())?;
|
||||
tracing::info!("{:#?}", serde_json::to_string(&release));
|
||||
tracing::info!("{:?}", release.download_url());
|
||||
let version = rt.block_on(client::updates::check())?;
|
||||
tracing::info!("{:?}", version);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -412,22 +412,16 @@ async fn check_for_updates(ctlr_tx: CtlrTx, always_show_update_notification: boo
|
||||
let release = client::updates::check()
|
||||
.await
|
||||
.context("Error in client::updates::check")?;
|
||||
let Some(download_url) = release.download_url() else {
|
||||
tracing::warn!("No installer for this OS/arch online");
|
||||
return Ok(());
|
||||
};
|
||||
let latest_version = release.version.clone();
|
||||
|
||||
let our_version = client::updates::current_version()?;
|
||||
|
||||
if always_show_update_notification || (our_version < release.tag_name) {
|
||||
tracing::info!(?our_version, ?release.tag_name, "There is a new release");
|
||||
if always_show_update_notification || (our_version < latest_version) {
|
||||
tracing::info!(?our_version, ?latest_version, "There is a new release");
|
||||
// We don't necessarily need to route through the Controller here, but if we
|
||||
// want a persistent "Click here to download the new MSI" button, this would allow that.
|
||||
ctlr_tx
|
||||
.send(ControllerRequest::UpdateAvailable {
|
||||
download_url: download_url.clone(),
|
||||
version_to_download: release.tag_name,
|
||||
})
|
||||
.send(ControllerRequest::UpdateAvailable(release))
|
||||
.await
|
||||
.context("Error while sending UpdateAvailable to Controller")?;
|
||||
return Ok(());
|
||||
@@ -435,7 +429,7 @@ async fn check_for_updates(ctlr_tx: CtlrTx, always_show_update_notification: boo
|
||||
|
||||
tracing::info!(
|
||||
?our_version,
|
||||
?release.tag_name,
|
||||
?latest_version,
|
||||
"Our release is newer than, or the same as, the latest"
|
||||
);
|
||||
Ok(())
|
||||
@@ -489,10 +483,7 @@ pub(crate) enum ControllerRequest {
|
||||
SignIn,
|
||||
SystemTrayMenu(TrayMenuEvent),
|
||||
TunnelReady,
|
||||
UpdateAvailable {
|
||||
download_url: Url,
|
||||
version_to_download: semver::Version,
|
||||
},
|
||||
UpdateAvailable(crate::client::updates::Release),
|
||||
UpdateNotificationClicked(Url),
|
||||
}
|
||||
|
||||
@@ -678,11 +669,8 @@ impl Controller {
|
||||
self.tunnel_ready = true;
|
||||
self.refresh_system_tray_menu()?;
|
||||
}
|
||||
Req::UpdateAvailable {
|
||||
download_url,
|
||||
version_to_download,
|
||||
} => {
|
||||
let title = format!("Firezone {} available for download", version_to_download);
|
||||
Req::UpdateAvailable(release) => {
|
||||
let title = format!("Firezone {} available for download", release.version);
|
||||
|
||||
// We don't need to route through the controller here either, we could
|
||||
// use the `open` crate directly instead of Tauri's wrapper
|
||||
@@ -691,7 +679,7 @@ impl Controller {
|
||||
&title,
|
||||
"Click here to download the new version.",
|
||||
self.ctlr_tx.clone(),
|
||||
Req::UpdateNotificationClicked(download_url),
|
||||
Req::UpdateNotificationClicked(release.download_url),
|
||||
)?;
|
||||
}
|
||||
Req::UpdateNotificationClicked(download_url) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Module to check the Github repo for new releases
|
||||
|
||||
use crate::client::about::get_cargo_version;
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
@@ -9,164 +9,85 @@ use url::Url;
|
||||
/// GUI-friendly release struct
|
||||
///
|
||||
/// Serialize is derived for debugging
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub(crate) struct Release {
|
||||
/// All assets in a given release
|
||||
assets: Vec<Asset>,
|
||||
/// Git tag name / Cargo version name
|
||||
///
|
||||
/// e.g. 1.0.1
|
||||
pub tag_name: semver::Version,
|
||||
pub download_url: url::Url,
|
||||
pub version: semver::Version,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct Asset {
|
||||
browser_download_url: Url,
|
||||
/// Name of the asset, e.g. `firezone-client-gui-windows-x86_64.msi`
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Release {
|
||||
/// Download URL for current OS and arch
|
||||
pub fn download_url(&self) -> Option<&Url> {
|
||||
self.download_url_for(std::env::consts::ARCH, std::env::consts::OS)
|
||||
}
|
||||
|
||||
/// Download URL for the first asset that matches the given arch, OS, and package type
|
||||
fn download_url_for(&self, arch: &str, os: &str) -> Option<&Url> {
|
||||
let package = match os {
|
||||
"linux" => "deb",
|
||||
"macos" => "dmg", // Unused in practice
|
||||
"windows" => "msi",
|
||||
_ => panic!("Don't know what package this OS uses"),
|
||||
};
|
||||
|
||||
let prefix = format!("firezone-client-gui-{os}_");
|
||||
let suffix = format!("_{arch}.{package}");
|
||||
|
||||
let mut iter = self
|
||||
.assets
|
||||
.iter()
|
||||
.filter(|x| x.name.starts_with(&prefix) && x.name.ends_with(&suffix));
|
||||
iter.next().map(|asset| &asset.browser_download_url)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub(crate) enum Error {
|
||||
#[error(transparent)]
|
||||
JsonParse(#[from] serde_json::Error),
|
||||
#[error("Our own semver in the exe is invalid, this should be impossible")]
|
||||
OurVersionIsInvalid(semver::Error),
|
||||
#[error(transparent)]
|
||||
Request(#[from] reqwest::Error),
|
||||
}
|
||||
|
||||
const LATEST_RELEASE_API_URL: &str =
|
||||
"https://api.github.com/repos/firezone/firezone/releases/latest";
|
||||
|
||||
/// <https://docs.github.com/en/rest/about-the-rest-api/api-versions?apiVersion=2022-11-28>
|
||||
const GITHUB_API_VERSION: &str = "2022-11-28";
|
||||
|
||||
/// Returns the latest release, even if ours is already newer
|
||||
pub(crate) async fn check() -> Result<Release> {
|
||||
let client = reqwest::Client::builder().build()?;
|
||||
// Don't follow any redirects, just tell us what the Firezone site says the URL is
|
||||
let client = reqwest::Client::builder()
|
||||
.redirect(reqwest::redirect::Policy::none())
|
||||
.build()?;
|
||||
let arch = std::env::consts::ARCH;
|
||||
let os = std::env::consts::OS;
|
||||
|
||||
// <https://docs.github.com/en/rest/using-the-rest-api/getting-started-with-the-rest-api?apiVersion=2022-11-28#user-agent-required>
|
||||
// We used to send this to Github, couldn't hurt to send it to our own site, too
|
||||
let user_agent = format!("Firezone Client/{:?} ({os}; {arch})", current_version());
|
||||
|
||||
// Reqwest follows up to 10 redirects by default
|
||||
// https://docs.rs/reqwest/latest/reqwest/struct.ClientBuilder.html#method.redirect
|
||||
// https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api?apiVersion=2022-11-28#follow-redirects
|
||||
let mut latest_url = url::Url::parse("https://www.firezone.dev")
|
||||
.context("Impossible: Hard-coded URL should always be parsable")?;
|
||||
latest_url.set_path(&format!("/dl/firezone-client-gui-{os}/latest/{arch}"));
|
||||
|
||||
let response = client
|
||||
.get(LATEST_RELEASE_API_URL)
|
||||
.head(latest_url)
|
||||
.header("User-Agent", user_agent)
|
||||
.header("X-GitHub-Api-Version", GITHUB_API_VERSION)
|
||||
.send()
|
||||
.await?;
|
||||
let status = response.status();
|
||||
if status != reqwest::StatusCode::OK {
|
||||
if status != reqwest::StatusCode::TEMPORARY_REDIRECT {
|
||||
anyhow::bail!("HTTP status: {status}");
|
||||
}
|
||||
let download_url = response
|
||||
.headers()
|
||||
.get(reqwest::header::LOCATION)
|
||||
.context("this URL should always have a redirect")?
|
||||
.to_str()?;
|
||||
tracing::debug!(?download_url);
|
||||
let download_url = Url::parse(download_url)?;
|
||||
let version = parse_version_from_url(&download_url)?;
|
||||
Ok(Release {
|
||||
download_url,
|
||||
version,
|
||||
})
|
||||
}
|
||||
|
||||
let response = response.text().await?;
|
||||
Ok(serde_json::from_str(&response)?)
|
||||
#[allow(clippy::print_stderr)]
|
||||
fn parse_version_from_url(url: &Url) -> Result<semver::Version> {
|
||||
tracing::debug!(?url);
|
||||
let filename = url
|
||||
.path_segments()
|
||||
.context("URL must have a path")?
|
||||
.last()
|
||||
.context("URL path must have a last segment")?;
|
||||
let version_str = filename
|
||||
.split('_')
|
||||
.nth(1)
|
||||
.context("Filename must have 3 parts separated by underscores")?;
|
||||
Ok(semver::Version::parse(version_str)?)
|
||||
}
|
||||
|
||||
// TODO: DRY with about.rs
|
||||
pub(crate) fn current_version() -> Result<semver::Version, Error> {
|
||||
semver::Version::from_str(&get_cargo_version()).map_err(Error::OurVersionIsInvalid)
|
||||
pub(crate) fn current_version() -> Result<semver::Version> {
|
||||
semver::Version::from_str(&get_cargo_version()).context("Our version is invalid")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn new_format() {
|
||||
let s = r#"
|
||||
{
|
||||
"tag_name": "1.0.0-pre.14",
|
||||
"assets": [
|
||||
{
|
||||
"name": "firezone-client-gui-linux_1.0.0-pre.14_aarch64.deb",
|
||||
"browser_download_url": "https://github.com/firezone/firezone/releases/download/1.0.0-pre.14/firezone-client-gui-linux_1.0.0-pre.14_aarch64.deb"
|
||||
},
|
||||
{
|
||||
"name": "firezone-client-gui-linux_1.0.0-pre.14_x86_64.deb",
|
||||
"browser_download_url": "https://github.com/firezone/firezone/releases/download/1.0.0-pre.14/firezone-client-gui-linux_1.0.0-pre.14_x86_64.deb"
|
||||
},
|
||||
{
|
||||
"name": "firezone-client-gui-windows_1.0.0-pre.14_aarch64.msi",
|
||||
"browser_download_url": "https://github.com/firezone/firezone/releases/download/1.0.0-pre.14/firezone-client-gui-windows_1.0.0-pre.14_aarch64.msi"
|
||||
},
|
||||
{
|
||||
"name": "firezone-client-gui-windows_1.0.0-pre.14_x86_64.msi",
|
||||
"browser_download_url": "https://github.com/firezone/firezone/releases/download/1.0.0-pre.14/firezone-client-gui-windows_1.0.0-pre.14_x86_64.msi"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "firezone-client-headless-linux_1.0.0-pre.14_aarch64.deb",
|
||||
"browser_download_url": "https://github.com/firezone/firezone/releases/download/1.0.0-pre.14/firezone-client-headless-linux_1.0.0-pre.14_aarch64.deb"
|
||||
},
|
||||
{
|
||||
"name": "firezone-client-headless-linux_1.0.0-pre.14_x86_64.deb",
|
||||
"browser_download_url": "https://github.com/firezone/firezone/releases/download/1.0.0-pre.14/firezone-client-headless-linux_1.0.0-pre.14_x86_64.deb"
|
||||
},
|
||||
{
|
||||
"name": "firezone-client-headless-windows_1.0.0-pre.14_aarch64.msi",
|
||||
"browser_download_url": "https://github.com/firezone/firezone/releases/download/1.0.0-pre.14/firezone-client-headless-windows_1.0.0-pre.14_aarch64.msi"
|
||||
},
|
||||
{
|
||||
"name": "firezone-client-headless-windows_1.0.0-pre.14_x86_64.msi",
|
||||
"browser_download_url": "https://github.com/firezone/firezone/releases/download/1.0.0-pre.14/firezone-client-headless-windows_1.0.0-pre.14_x86_64.msi"
|
||||
}
|
||||
]
|
||||
}"#;
|
||||
|
||||
let release: super::Release = serde_json::from_str(s).unwrap();
|
||||
let expected_url = "https://github.com/firezone/firezone/releases/download/1.0.0-pre.14/firezone-client-gui-windows_1.0.0-pre.14_x86_64.msi";
|
||||
assert_eq!(
|
||||
release
|
||||
.download_url_for("x86_64", "windows")
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
expected_url
|
||||
);
|
||||
assert_eq!(release.tag_name.to_string(), "1.0.0-pre.14");
|
||||
|
||||
assert!(
|
||||
semver::Version::from_str("1.0.0").unwrap()
|
||||
> semver::Version::from_str("1.0.0-pre.14").unwrap()
|
||||
);
|
||||
assert!(
|
||||
semver::Version::from_str("1.0.0-pre.14").unwrap()
|
||||
> semver::Version::from_str("0.7.0").unwrap()
|
||||
);
|
||||
|
||||
assert!(super::current_version().is_ok());
|
||||
fn parse_version_from_url() {
|
||||
for (input, expected) in [
|
||||
("https://www.github.com/firezone/firezone/releases/download/1.0.0/firezone-client-gui-windows_1.0.0_x86_64.msi", Some((1, 0, 0))),
|
||||
("https://www.github.com/firezone/firezone/releases/download/1.0.1/firezone-client-gui-linux_1.0.1_x86_64.deb", Some((1, 0, 1))),
|
||||
("https://www.github.com/firezone/firezone/releases/download/1.0.1/firezone-client-gui-linux_x86_64.deb", None),
|
||||
] {
|
||||
let input = url::Url::parse(input).unwrap();
|
||||
let expected = expected.map(|(a, b, c)| semver::Version::new(a, b, c));
|
||||
let actual = super::parse_version_from_url(&input).ok();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user