build: only sign if a gpg key able to sign is present
Some checks failed
CI / build (push) Failing after 1m50s
Some checks failed
CI / build (push) Failing after 1m50s
This commit is contained in:
@@ -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"
|
||||
|
||||
42
src/build.rs
42
src/build.rs
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
32
src/utils/gpg.rs
Normal 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
1
src/utils/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod gpg;
|
||||
Reference in New Issue
Block a user