build: only sign if a gpg key able to sign is present
Some checks failed
CI / build (push) Failing after 1m50s

This commit is contained in:
2026-01-06 18:07:34 +01:00
parent b3365afe5b
commit 1c9f6cccd2
6 changed files with 107 additions and 2 deletions

View File

@@ -28,6 +28,7 @@ serde_json = "1.0.145"
directories = "6.0.0"
ssh2 = "0.9.5"
tempfile = "3.10.1"
gpgme = "0.11"
[dev-dependencies]
test-log = "0.2.19"

View File

@@ -2,19 +2,59 @@ use std::error::Error;
use std::path::Path;
use std::process::Command;
use crate::changelog::parse_changelog_footer;
use crate::utils::gpg;
/// Build a Debian source package (to a .dsc)
pub fn build_source_package(cwd: Option<&Path>) -> Result<(), Box<dyn Error>> {
let cwd = cwd.unwrap_or_else(|| Path::new("."));
// Parse changelog to get maintainer information from the last modification entry
let changelog_path = cwd.join("debian/changelog");
let (maintainer_name, maintainer_email) = parse_changelog_footer(&changelog_path)?;
// Check if a GPG key matching the maintainer's email exists
let signing_key = match gpg::find_signing_key_for_email(&maintainer_email) {
Ok(key) => key,
Err(e) => {
// If GPG is not available or there's an error, continue without signing
log::warn!("Failed to check for GPG key: {}", e);
None
}
};
// Build arguments
let mut args = vec!["-S", "-I", "-i", "-nc", "-d"];
// If a signing key is found, use it for signing
if let Some(key_id) = &signing_key {
args.push("-sa"); // Sign the source package
args.push("-k");
args.push(key_id);
log::info!("Using GPG key {} for signing", key_id);
} else {
log::info!(
"No GPG key found for {} ({}), building without signing",
maintainer_name,
maintainer_email
);
}
let status = Command::new("dpkg-buildpackage")
.current_dir(cwd)
.args(["-S", "-I", "-i", "-nc", "-d"])
.args(&args)
.status()?;
if !status.success() {
return Err(format!("dpkg-buildpackage failed with status: {}", status).into());
}
if signing_key.is_some() {
println!("Package built and signed successfully!");
} else {
println!("Package built successfully (unsigned).");
}
Ok(())
}

View File

@@ -114,7 +114,8 @@ fn increment_suffix(version: &str, suffix: &str) -> String {
}
}
/// Parse a changelog file first entry header, to obtain (package, version, series)
/// Parse a changelog file first entry header
/// Returns (package, version, series) tuple from the last modification entry
pub fn parse_changelog_header(
path: &Path,
) -> Result<(String, String, String), Box<dyn std::error::Error>> {
@@ -135,6 +136,33 @@ pub fn parse_changelog_header(
}
}
/// Parse a changelog file footer to extract maintainer information
/// Returns (name, email) tuple from the last modification entry
pub fn parse_changelog_footer(path: &Path) -> Result<(String, String), Box<dyn std::error::Error>> {
let mut file = File::open(path)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
// Find the last maintainer line (format: -- Name <email> Date)
let re = Regex::new(r"--\s*([^<]+?)\s*<([^>]+)>\s*")?;
if let Some(last_match) = re.captures_iter(&content).last() {
let name = last_match
.get(1)
.map_or("", |m| m.as_str())
.trim()
.to_string();
let email = last_match
.get(2)
.map_or("", |m| m.as_str())
.trim()
.to_string();
Ok((name, email))
} else {
Err(format!("No maintainer information found in {}", path.display()).into())
}
}
/*
* Obtain all commit messages as a list since a tagged version in a git repository
*/

View File

@@ -17,6 +17,9 @@ pub mod pull;
/// Handle context for .deb building: locally, over ssh, in a chroot...
pub mod context;
/// Utility functions
pub(crate) mod utils;
/// Optional callback function (taking 4 arguments)
/// - Name of the current main operation (e.g. pulling package)
/// - Name of the current nested operation (e.g. cloning git repo)

32
src/utils/gpg.rs Normal file
View File

@@ -0,0 +1,32 @@
use gpgme::{Context, Protocol};
/// Check if a GPG key matching 'email' exists
/// Returns the key ID if found, None otherwise
pub fn find_signing_key_for_email(
email: &str,
) -> Result<Option<String>, Box<dyn std::error::Error>> {
// Create a new GPG context
let mut ctx = Context::from_protocol(Protocol::OpenPgp)?;
// List all secret keys
let keys = ctx.secret_keys()?;
// Find a key that matches the email and can sign
for key_result in keys {
let key = key_result?;
// Check if the key has signing capability
if key.can_sign() {
// Check user IDs for email match
for user_id in key.user_ids() {
if let Ok(userid_email) = user_id.email()
&& userid_email.eq_ignore_ascii_case(email)
&& let Ok(fingerprint) = key.fingerprint()
{
return Ok(Some(fingerprint.to_string()));
}
}
}
}
Ok(None)
}

1
src/utils/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod gpg;