diff options
| author | David Lönnhager <david.l@mullvad.net> | 2025-08-29 13:07:57 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2025-09-03 09:27:56 +0200 |
| commit | 46dfafc5b8fc42d05efc9355c6bbab8515595853 (patch) | |
| tree | 5210cfa12ee674b4cd70e1965c52ad0042fe5f35 | |
| parent | 4ce272c4e3f0a8b5457d56dee5964bf61d35677b (diff) | |
| download | mullvadvpn-46dfafc5b8fc42d05efc9355c6bbab8515595853.tar.xz mullvadvpn-46dfafc5b8fc42d05efc9355c6bbab8515595853.zip | |
Pull 'latest' metadata file with 'mullvad-release pull'
| -rw-r--r-- | mullvad-update/mullvad-release/src/main.rs | 45 | ||||
| -rw-r--r-- | mullvad-update/src/client/api.rs | 15 |
2 files changed, 59 insertions, 1 deletions
diff --git a/mullvad-update/mullvad-release/src/main.rs b/mullvad-update/mullvad-release/src/main.rs index 36ba666ec6..f5d6dd9181 100644 --- a/mullvad-update/mullvad-release/src/main.rs +++ b/mullvad-update/mullvad-release/src/main.rs @@ -5,17 +5,21 @@ use anyhow::{Context, bail}; use clap::Parser; -use std::str::FromStr; +use std::{path::Path, str::FromStr}; +use tokio::fs; use config::Config; use io_util::create_dir_and_write; use platform::Platform; use mullvad_update::{ + api::HttpVersionInfoProvider, format::{self, SignedResponse, key}, version::Rollout, }; +use crate::io_util::wait_for_confirm; + mod artifacts; mod config; mod github; @@ -28,6 +32,9 @@ const DEFAULT_EXPIRY_MONTHS: usize = 6; /// Rollout to use when not specified const DEFAULT_ROLLOUT: f32 = 1.; +/// Filename for latest.json metadata +const LATEST_FILENAME: &str = "latest.json"; + /// A tool that generates signed Mullvad version metadata. /// /// Unsigned work is stored in `work/`, and signed work is stored in `signed/` @@ -51,6 +58,10 @@ pub enum Opt { /// Replace signed files without asking for confirmation #[arg(long, short = 'y')] assume_yes: bool, + + /// Also update the latest.json file + #[arg(long, default_value_t = false)] + latest_file: bool, }, /// List releases in `work/` @@ -151,10 +162,42 @@ async fn main() -> anyhow::Result<()> { Opt::Pull { platforms, assume_yes, + latest_file, } => { for platform in all_platforms_if_empty(platforms) { platform.pull(assume_yes).await?; } + + // Download latest.json metadata if available + if latest_file { + match HttpVersionInfoProvider::get_latest_versions_file() + .await + .and_then(|json| { + serde_json::to_string_pretty(&json).context("Failed to format JSON") + }) { + Ok(json) => { + let path = Path::new(LATEST_FILENAME); + + if !assume_yes && path.exists() { + let msg = format!( + "This will replace the existing file at {}. Continue?", + path.display() + ); + if !wait_for_confirm(&msg).await { + bail!("Aborted"); + } + } + + fs::write(path, json).await.context("Failed to write")?; + + println!("Updated {}", path.display()); + } + Err(err) => { + eprintln!("Failed to retrieve latest.json file: {err}"); + } + } + } + Ok(()) } Opt::Sign { diff --git a/mullvad-update/src/client/api.rs b/mullvad-update/src/client/api.rs index 472224fe12..6f4f7bc51b 100644 --- a/mullvad-update/src/client/api.rs +++ b/mullvad-update/src/client/api.rs @@ -8,6 +8,7 @@ use tokio::fs; #[cfg(test)] use vec1::Vec1; +use crate::defaults::META_REPOSITORY_URL; use crate::format; use crate::version::{VersionInfo, VersionParameters}; @@ -155,6 +156,20 @@ impl HttpVersionInfoProvider { Ok(signed_response) } + /// Retrieve the `latest.json` file. + /// + /// By default, `pinned_certificate` will be set to the LE root certificate. The contents are + /// unsigned. + pub async fn get_latest_versions_file() -> anyhow::Result<Vec<u8>> { + Self::get( + &format!("{META_REPOSITORY_URL}/latest.json"), + Some(crate::defaults::PINNED_CERTIFICATE.clone()), + Some((API_HOST_DEFAULT, API_IP_DEFAULT)), + ) + .await + .context("Failed to get latest.json file") + } + /// Perform a simple GET request, with a size limit, and return it as bytes /// /// # Arguments |
