217 lines
6.6 KiB
Rust
217 lines
6.6 KiB
Rust
//! APT keyring management for mmdebstrap and PPA packages
|
|
//!
|
|
//! Provides functions to ensure that archive keyrings are available
|
|
//! for mmdebstrap operations and for PPA packages by downloading them.
|
|
|
|
use crate::context;
|
|
use crate::distro_info;
|
|
use serde::Deserialize;
|
|
use std::error::Error;
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::Arc;
|
|
|
|
/// Launchpad API response structure for PPA information
|
|
#[derive(Deserialize)]
|
|
struct LaunchpadPpaResponse {
|
|
signing_key_fingerprint: String,
|
|
}
|
|
|
|
/// Download a keyring to the application cache directory and return the path
|
|
///
|
|
/// This function downloads the keyring to a user-writable cache directory
|
|
/// instead of the system apt keyring directory, allowing non-root usage.
|
|
/// The returned path can be passed to mmdebstrap via --keyring.
|
|
///
|
|
/// For Debian keyrings (which are ASCII-armored .asc files), the key is
|
|
/// converted to binary GPG format using gpg --dearmor.
|
|
///
|
|
/// # Arguments
|
|
/// * `ctx` - Optional context to use
|
|
/// * `series` - The distribution series (e.g., "noble", "sid")
|
|
///
|
|
/// # Returns
|
|
/// The path to the downloaded keyring file (in binary GPG format)
|
|
pub async fn download_cache_keyring(
|
|
ctx: Option<Arc<context::Context>>,
|
|
series: &str,
|
|
) -> Result<PathBuf, Box<dyn Error>> {
|
|
let ctx = ctx.unwrap_or_else(context::current);
|
|
|
|
// Obtain keyring URL from distro_info
|
|
let keyring_url = distro_info::get_keyring_url(series).await?;
|
|
log::debug!("Downloading keyring from: {}", keyring_url);
|
|
|
|
// Get the application cache directory
|
|
let proj_dirs = directories::ProjectDirs::from("com", "pkh", "pkh")
|
|
.ok_or("Could not determine project directories")?;
|
|
let cache_dir = proj_dirs.cache_dir();
|
|
|
|
// Create cache directory if it doesn't exist
|
|
if !ctx.exists(cache_dir)? {
|
|
ctx.command("mkdir").arg("-p").arg(cache_dir).status()?;
|
|
}
|
|
|
|
// Extract the original filename from the keyring URL
|
|
let filename = keyring_url
|
|
.split('/')
|
|
.next_back()
|
|
.unwrap_or("pkh-{}.gpg")
|
|
.replace("{}", series);
|
|
let download_path = cache_dir.join(&filename);
|
|
|
|
// Download the keyring using curl
|
|
let mut curl_cmd = ctx.command("curl");
|
|
curl_cmd
|
|
.arg("-s")
|
|
.arg("-f")
|
|
.arg("-L")
|
|
.arg(&keyring_url)
|
|
.arg("--output")
|
|
.arg(&download_path);
|
|
|
|
let status = curl_cmd.status()?;
|
|
if !status.success() {
|
|
return Err(format!("Failed to download keyring from {}", keyring_url).into());
|
|
}
|
|
|
|
// If the downloaded file is an ASCII-armored key (.asc), convert it to binary GPG format
|
|
// mmdebstrap's --keyring option expects binary GPG keyrings
|
|
let keyring_path = if filename.ends_with(".asc") {
|
|
let binary_filename = filename.strip_suffix(".asc").unwrap_or(&filename);
|
|
let binary_path = cache_dir.join(format!("{}.gpg", binary_filename));
|
|
|
|
log::debug!("Converting ASCII-armored key to binary GPG format");
|
|
let mut gpg_cmd = ctx.command("gpg");
|
|
gpg_cmd
|
|
.arg("--dearmor")
|
|
.arg("--output")
|
|
.arg(&binary_path)
|
|
.arg(&download_path);
|
|
|
|
let status = gpg_cmd.status()?;
|
|
if !status.success() {
|
|
return Err("Failed to convert keyring to binary format"
|
|
.to_string()
|
|
.into());
|
|
}
|
|
|
|
// Remove the original .asc file
|
|
let _ = ctx.command("rm").arg("-f").arg(&download_path).status();
|
|
|
|
binary_path
|
|
} else {
|
|
download_path
|
|
};
|
|
|
|
log::info!(
|
|
"Successfully downloaded keyring for {} to {}",
|
|
series,
|
|
keyring_path.display()
|
|
);
|
|
Ok(keyring_path)
|
|
}
|
|
|
|
/// Download and import a PPA key using Launchpad API
|
|
///
|
|
/// # Arguments
|
|
/// * `ctx` - Optional context to use
|
|
/// * `ppa_owner` - PPA owner (username)
|
|
/// * `ppa_name` - PPA name
|
|
///
|
|
/// # Returns
|
|
/// Result indicating success or failure
|
|
pub async fn download_trust_ppa_key(
|
|
ctx: Option<Arc<context::Context>>,
|
|
ppa_owner: &str,
|
|
ppa_name: &str,
|
|
) -> Result<(), Box<dyn Error>> {
|
|
let ctx = ctx.unwrap_or_else(context::current);
|
|
|
|
// Create trusted.gpg.d directory if it doesn't exist
|
|
let trusted_gpg_d = "/etc/apt/trusted.gpg.d";
|
|
if !ctx.exists(Path::new(trusted_gpg_d))? {
|
|
ctx.command("mkdir").arg("-p").arg(trusted_gpg_d).status()?;
|
|
}
|
|
|
|
let key_filename = format!("{}-{}.asc", ppa_owner, ppa_name);
|
|
let key_path = format!("{}/{}", trusted_gpg_d, key_filename);
|
|
|
|
log::debug!(
|
|
"Retrieving PPA key for {}/{} using Launchpad API",
|
|
ppa_owner,
|
|
ppa_name
|
|
);
|
|
|
|
// Get PPA information from Launchpad API to get signing key fingerprint
|
|
// Use the correct devel API endpoint
|
|
let api_url = format!(
|
|
"https://api.launchpad.net/1.0/~{}/+archive/ubuntu/{}",
|
|
ppa_owner, ppa_name
|
|
);
|
|
log::debug!("Querying Launchpad API: {}", api_url);
|
|
|
|
let api_response = ctx
|
|
.command("curl")
|
|
.arg("-s")
|
|
.arg("-f")
|
|
.arg("-H")
|
|
.arg("Accept: application/json")
|
|
.arg(&api_url)
|
|
.output()?;
|
|
|
|
if !api_response.status.success() {
|
|
return Err(format!(
|
|
"Failed to query Launchpad API for PPA {}/{}",
|
|
ppa_owner, ppa_name
|
|
)
|
|
.into());
|
|
}
|
|
|
|
// Parse the JSON response to extract the signing key fingerprint
|
|
let api_response_str = String::from_utf8_lossy(&api_response.stdout);
|
|
let ppa_response: LaunchpadPpaResponse =
|
|
serde_json::from_str(&api_response_str).map_err(|e| {
|
|
format!(
|
|
"Failed to parse JSON response from Launchpad API for {}/{}: {}",
|
|
ppa_owner, ppa_name, e
|
|
)
|
|
})?;
|
|
let fingerprint = ppa_response.signing_key_fingerprint;
|
|
|
|
log::debug!("Found PPA signing key fingerprint: {}", fingerprint);
|
|
|
|
// Download the actual key from the keyserver using the fingerprint
|
|
let keyserver_url = format!(
|
|
"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x{}",
|
|
fingerprint
|
|
);
|
|
log::debug!("Downloading key from keyserver: {}", keyserver_url);
|
|
|
|
let mut curl_cmd = ctx.command("curl");
|
|
curl_cmd
|
|
.arg("-s")
|
|
.arg("-f")
|
|
.arg("-L")
|
|
.arg(&keyserver_url)
|
|
.arg("--output")
|
|
.arg(&key_path);
|
|
|
|
let status = curl_cmd.status()?;
|
|
if !status.success() {
|
|
return Err(format!(
|
|
"Failed to download PPA key from keyserver for fingerprint {}",
|
|
fingerprint
|
|
)
|
|
.into());
|
|
}
|
|
|
|
log::info!(
|
|
"Successfully downloaded and installed PPA key for {}/{} (fingerprint: {}) to {}",
|
|
ppa_owner,
|
|
ppa_name,
|
|
fingerprint,
|
|
key_path
|
|
);
|
|
Ok(())
|
|
}
|