1use bincode::{Decode, Encode};
3use snafu::{ResultExt, Snafu};
4use std::{
5 path::{Path, PathBuf},
6 process::Command,
7 time::{Duration, Instant},
8};
9use tracing::{debug, debug_span, error, info, info_span, instrument};
10
11mod store;
13
14pub use store::{StoreError, StoreState};
15
16#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
28pub struct NixConfiguration {
29 pub store_path: PathBuf,
31}
32
33impl Default for NixConfiguration {
34 fn default() -> Self {
35 Self {
36 store_path: "/nix/store/".into(),
37 }
38 }
39}
40
41#[derive(Debug, Snafu)]
43#[non_exhaustive]
44pub enum NixError {
45 ProcessSpawn {
47 source: std::io::Error,
49 },
50 #[snafu(display(
52 "Error calling `nix store sign`:\nexit_code:{:?}\nstdout:{}\n\nstderr:{}\npaths:{:?}`",
53 exit_code,
54 stdout,
55 stderr,
56 paths
57 ))]
58 SignatureError {
59 exit_code: Option<i32>,
61 paths: Vec<PathBuf>,
63 stdout: String,
65 stderr: String,
67 },
68 #[snafu(display(
70 "Error calling `nix copy`:\nexit_code:{:?}\nstdout:{}\n\nstderr:{}`",
71 exit_code,
72 stdout,
73 stderr
74 ))]
75 CopyError {
76 exit_code: Option<i32>,
78 paths: Vec<PathBuf>,
80 stdout: String,
82 stderr: String,
84 },
85}
86
87pub struct SignatureResults {
89 pub count: u64,
91 pub duration: Duration,
93}
94
95#[instrument(skip(paths, key_path))]
103pub fn sign_store_paths(
104 paths: impl IntoIterator<Item = impl AsRef<Path>>,
105 key_path: impl AsRef<Path>,
106 fanout_factor: usize,
107) -> Result<SignatureResults, NixError> {
108 let start = Instant::now();
110 let key_path = key_path.as_ref();
111 debug!(?key_path);
112 let paths: Vec<PathBuf> = paths.into_iter().map(|x| x.as_ref().to_owned()).collect();
114 let count = paths
115 .chunks(fanout_factor)
116 .map(|paths| {
117 debug_span!("nix store sign inner loop");
118 let start = Instant::now();
120 debug!("Attempting to sign {} paths", paths.len());
122 let output = Command::new("nix")
123 .args(["store", "sign", "-r", "-v", "--key-file"])
125 .arg(key_path)
127 .args(paths)
129 .output()
131 .context(ProcessSpawnSnafu)?;
132 if !output.status.success() {
134 error!(?output);
135 return SignatureSnafu {
136 exit_code: output.status.code(),
137 stdout: String::from(String::from_utf8_lossy(&output.stdout)),
138 stderr: String::from(String::from_utf8_lossy(&output.stderr)),
139 paths,
140 }
141 .fail();
142 }
143 let stderr: String = String::from_utf8_lossy(&output.stderr).into();
145 let count: u64 = match stderr.split(' ').nth(1) {
147 Some(x) => match x.parse::<u64>() {
148 Ok(x) => x,
149 Err(_) => {
150 return SignatureSnafu {
151 exit_code: output.status.code(),
152 stdout: String::from(String::from_utf8_lossy(&output.stdout)),
153 stderr: String::from(String::from_utf8_lossy(&output.stderr)),
154 paths,
155 }
156 .fail();
157 }
158 },
159 None => {
160 return SignatureSnafu {
161 exit_code: output.status.code(),
162 stdout: String::from(String::from_utf8_lossy(&output.stdout)),
163 stderr: String::from(String::from_utf8_lossy(&output.stderr)),
164 paths,
165 }
166 .fail();
167 }
168 };
169
170 let end = Instant::now();
172 let duration = end - start;
173 debug!(?count, ?duration, "nix sign store completed");
174 Ok::<u64, NixError>(count)
175 })
176 .collect::<Result<Vec<_>, _>>()?
177 .into_iter()
178 .sum();
179 let end = Instant::now();
181 let duration = end - start;
182 info!(?duration, ?count, "Completed signatures");
183 Ok(SignatureResults { count, duration })
184}
185
186#[instrument(skip(paths))]
188pub fn upload_paths_to_cache(
189 paths: impl IntoIterator<Item = impl AsRef<Path>>,
190 cache: &str,
191 fanout_factor: usize,
192) -> Result<(), NixError> {
193 let paths: Vec<PathBuf> = paths.into_iter().map(|x| x.as_ref().to_owned()).collect();
195 for paths in paths.chunks(fanout_factor) {
196 info_span!("nix store sign inner loop");
197 info!("Attempting to upload {} paths", paths.len());
198 let output = Command::new("nix")
199 .args(["copy", "--no-recursive", "--to"])
201 .arg(cache)
203 .args(paths)
205 .output()
207 .context(ProcessSpawnSnafu)?;
208 if !output.status.success() {
210 error!(?output);
211 return CopySnafu {
212 exit_code: output.status.code(),
213 stdout: String::from(String::from_utf8_lossy(&output.stdout)),
214 stderr: String::from(String::from_utf8_lossy(&output.stderr)),
215 paths,
216 }
217 .fail();
218 }
219 }
220 Ok(())
221}