summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2025-08-29 13:07:57 +0200
committerDavid Lönnhager <david.l@mullvad.net>2025-09-03 09:27:56 +0200
commit46dfafc5b8fc42d05efc9355c6bbab8515595853 (patch)
tree5210cfa12ee674b4cd70e1965c52ad0042fe5f35
parent4ce272c4e3f0a8b5457d56dee5964bf61d35677b (diff)
downloadmullvadvpn-46dfafc5b8fc42d05efc9355c6bbab8515595853.tar.xz
mullvadvpn-46dfafc5b8fc42d05efc9355c6bbab8515595853.zip
Pull 'latest' metadata file with 'mullvad-release pull'
-rw-r--r--mullvad-update/mullvad-release/src/main.rs45
-rw-r--r--mullvad-update/src/client/api.rs15
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