use bincode::{Decode, Encode};
use snafu::{ResultExt, Snafu};
use std::{
path::{Path, PathBuf},
process::Command,
time::{Duration, Instant},
};
use tracing::{debug, debug_span, error, info, info_span, instrument};
mod store;
pub use store::{StoreError, StoreState};
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
pub struct NixConfiguration {
pub store_path: PathBuf,
}
impl Default for NixConfiguration {
fn default() -> Self {
Self {
store_path: "/nix/store/".into(),
}
}
}
#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum NixError {
ProcessSpawn {
source: std::io::Error,
},
#[snafu(display(
"Error calling `nix store sign`:\nexit_code:{:?}\nstdout:{}\n\nstderr:{}\npaths:{:?}`",
exit_code,
stdout,
stderr,
paths
))]
SignatureError {
exit_code: Option<i32>,
paths: Vec<PathBuf>,
stdout: String,
stderr: String,
},
#[snafu(display(
"Error calling `nix copy`:\nexit_code:{:?}\nstdout:{}\n\nstderr:{}`",
exit_code,
stdout,
stderr
))]
CopyError {
exit_code: Option<i32>,
paths: Vec<PathBuf>,
stdout: String,
stderr: String,
},
}
pub struct SignatureResults {
pub count: u64,
pub duration: Duration,
}
#[instrument(skip(paths, key_path))]
pub fn sign_store_paths(
paths: impl IntoIterator<Item = impl AsRef<Path>>,
key_path: impl AsRef<Path>,
fanout_factor: usize,
) -> Result<SignatureResults, NixError> {
let start = Instant::now();
let key_path = key_path.as_ref();
debug!(?key_path);
let paths: Vec<PathBuf> = paths.into_iter().map(|x| x.as_ref().to_owned()).collect();
let count = paths
.chunks(fanout_factor)
.map(|paths| {
debug_span!("nix store sign inner loop");
let start = Instant::now();
debug!("Attempting to sign {} paths", paths.len());
let output = Command::new("nix")
.args(["store", "sign", "-r", "-v", "--key-file"])
.arg(key_path)
.args(paths)
.output()
.context(ProcessSpawnSnafu)?;
if !output.status.success() {
error!(?output);
return SignatureSnafu {
exit_code: output.status.code(),
stdout: String::from(String::from_utf8_lossy(&output.stdout)),
stderr: String::from(String::from_utf8_lossy(&output.stderr)),
paths,
}
.fail();
}
let stderr: String = String::from_utf8_lossy(&output.stderr).into();
let count: u64 = match stderr.split(' ').nth(1) {
Some(x) => match x.parse::<u64>() {
Ok(x) => x,
Err(_) => {
return SignatureSnafu {
exit_code: output.status.code(),
stdout: String::from(String::from_utf8_lossy(&output.stdout)),
stderr: String::from(String::from_utf8_lossy(&output.stderr)),
paths,
}
.fail();
}
},
None => {
return SignatureSnafu {
exit_code: output.status.code(),
stdout: String::from(String::from_utf8_lossy(&output.stdout)),
stderr: String::from(String::from_utf8_lossy(&output.stderr)),
paths,
}
.fail();
}
};
let end = Instant::now();
let duration = end - start;
debug!(?count, ?duration, "nix sign store completed");
Ok::<u64, NixError>(count)
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.sum();
let end = Instant::now();
let duration = end - start;
info!(?duration, ?count, "Completed signatures");
Ok(SignatureResults { count, duration })
}
#[instrument(skip(paths))]
pub fn upload_paths_to_cache(
paths: impl IntoIterator<Item = impl AsRef<Path>>,
cache: &str,
fanout_factor: usize,
) -> Result<(), NixError> {
let paths: Vec<PathBuf> = paths.into_iter().map(|x| x.as_ref().to_owned()).collect();
for paths in paths.chunks(fanout_factor) {
info_span!("nix store sign inner loop");
info!("Attempting to upload {} paths", paths.len());
let output = Command::new("nix")
.args(["copy", "--no-recursive", "--to"])
.arg(cache)
.args(paths)
.output()
.context(ProcessSpawnSnafu)?;
if !output.status.success() {
error!(?output);
return CopySnafu {
exit_code: output.status.code(),
stdout: String::from(String::from_utf8_lossy(&output.stdout)),
stderr: String::from(String::from_utf8_lossy(&output.stderr)),
paths,
}
.fail();
}
}
Ok(())
}