git.delta.rocks / fleet / refs/commits / 8aa3354490e6

difftreelog

refactor remove nix command wrappers

Yaroslav Bolyukin2020-11-28parent: #d866c97.patch.diff

5 files changed

modifiedsrc/cmds/build_systems.rsdiffbeforeafterboth
--- a/src/cmds/build_systems.rs
+++ b/src/cmds/build_systems.rs
@@ -1,14 +1,33 @@
+use std::process::Command;
+
 use crate::{
+	command::CommandExt,
 	db::{keys::list_hosts, secret::SecretDb, Db, DbData},
-	nix::{NixBuild, NixCopy, HOSTS_ATTRIBUTE, SYSTEMS_ATTRIBUTE},
+	nix::SYSTEMS_ATTRIBUTE,
 };
 use anyhow::Result;
 use clap::Clap;
-use log::info;
+use log::{info, warn};
 
 #[derive(Clap)]
-pub struct BuildSystems {}
+pub struct BuildSystems {
+	/// Hosts to skip
+	#[clap(long, number_of_values = 1)]
+	skip: Vec<String>,
+	#[clap(subcommand)]
+	subcommand: Option<Subcommand>,
+}
 
+#[derive(Clap)]
+enum Subcommand {
+	/// Switch to built system until reboot
+	Test,
+	/// Switch to built system after reboot
+	Boot,
+	/// test + boot
+	Switch,
+}
+
 impl BuildSystems {
 	pub fn run(self) -> Result<()> {
 		let db = Db::new(".fleet")?;
@@ -16,16 +35,47 @@
 		let data = SecretDb::open(&db)?.generate_nix_data()?;
 
 		for host in hosts.iter() {
+			if self.skip.contains(host) {
+				warn!("Skipping host {}", host);
+				continue;
+			}
 			info!("Building host {}", host);
-			let path = NixBuild::new(format!(
-				"{}.{}.config.system.build.toplevel",
-				SYSTEMS_ATTRIBUTE, host,
-			))
-			.env("SECRET_DATA".into(), data.clone())
-			.run()?;
-			info!("{:?}", path.path());
-			NixCopy::new(path.path().to_owned()).to(format!("ssh://root@{}", host))?;
-			std::thread::sleep_ms(9999999)
+			let built = tempfile::tempdir()?;
+			Command::new("nix")
+				.inherit_stdio()
+				.arg("build")
+				.arg(format!(
+					"{}.{}.config.system.build.toplevel",
+					SYSTEMS_ATTRIBUTE, host,
+				))
+				.arg("--no-link")
+				.arg("--out-link")
+				.arg(built.path())
+				.arg("--impure")
+				.env("SECRET_DATA", data.clone())
+				.run()?;
+			info!("Uploading system closure");
+			let full_path = std::fs::canonicalize(built.path())?;
+			info!("{:?}", full_path);
+			Command::new("nix")
+				.inherit_stdio()
+				.arg("copy")
+				.arg(full_path)
+				.arg("--to")
+				.arg(format!("ssh://root@{}", host))
+				.run()?;
+			match self.subcommand {
+				Some(Subcommand::Test) => {
+					info!("Setting system to test")
+				}
+				Some(Subcommand::Boot) => {
+					info!("Setting system to switch on boot")
+				}
+				Some(Subcommand::Switch) => {
+					info!("Switching to configuration")
+				}
+				_ => {}
+			}
 		}
 		Ok(())
 	}
modifiedsrc/command.rsdiffbeforeafterboth
--- a/src/command.rs
+++ b/src/command.rs
@@ -4,31 +4,46 @@
 };
 
 use anyhow::{Context, Result};
-use serde::Deserialize;
+use serde::de::DeserializeOwned;
 
-pub struct CommandOutput(pub Vec<u8>);
-impl CommandOutput {
-	pub fn into_json<'d, T: Deserialize<'d>>(&'d self) -> Result<T> {
-		let str = self.as_str().ok();
-		Ok(serde_json::from_slice(&self.0).with_context(|| format!("{:?}", str))?)
+pub trait CommandExt {
+	fn run(&mut self) -> Result<()>;
+	fn run_json<T: DeserializeOwned>(&mut self) -> Result<T>;
+	fn run_string(&mut self) -> Result<String>;
+	fn inherit_stdio(&mut self) -> &mut Self;
+	fn ssh_on(host: impl AsRef<OsStr>, command: impl AsRef<OsStr>) -> Self;
+}
+
+impl CommandExt for Command {
+	fn inherit_stdio(&mut self) -> &mut Self {
+		self.stderr(Stdio::inherit());
+		self
 	}
-	pub fn as_str(&self) -> Result<&str> {
-		Ok(std::str::from_utf8(&self.0)?)
+
+	fn run(&mut self) -> Result<()> {
+		let out = self.output()?;
+		if !out.status.success() {
+			anyhow::bail!("command failed");
+		}
+		Ok(())
 	}
-}
 
-pub fn ssh_command<I, S>(host: impl AsRef<OsStr>, command: I) -> Result<CommandOutput>
-where
-	I: IntoIterator<Item = S>,
-	S: AsRef<OsStr>,
-{
-	let out = Command::new("ssh")
-		.stderr(Stdio::inherit())
-		.arg(host)
-		.args(command)
-		.output()?;
-	if !out.status.success() {
-		anyhow::bail!("command failed");
+	fn run_json<T: DeserializeOwned>(&mut self) -> Result<T> {
+		let str = self.run_string()?;
+		Ok(serde_json::from_str(&str).with_context(|| format!("{:?}", str))?)
+	}
+
+	fn run_string(&mut self) -> Result<String> {
+		let out = self.output()?;
+		if !out.status.success() {
+			anyhow::bail!("command failed");
+		}
+		Ok(String::from_utf8(out.stdout)?)
+	}
+
+	fn ssh_on(host: impl AsRef<OsStr>, command: impl AsRef<OsStr>) -> Self {
+		let mut cmd = Command::new("ssh");
+		cmd.arg(host).arg("--").arg(command);
+		cmd
 	}
-	Ok(CommandOutput(out.stdout))
 }
modifiedsrc/db/keys.rsdiffbeforeafterboth
--- a/src/db/keys.rs
+++ b/src/db/keys.rs
@@ -1,20 +1,21 @@
-use std::collections::BTreeMap;
+use std::{collections::BTreeMap, process::Command};
 
 use anyhow::Result;
 use log::*;
 
-use crate::{
-	command::ssh_command,
-	nix::{NixEval, HOSTS_ATTRIBUTE},
-};
+use crate::{command::CommandExt, nix::HOSTS_ATTRIBUTE};
 
 use serde::{Deserialize, Serialize};
 
 use super::db::DbData;
 
 pub fn list_hosts() -> Result<Vec<String>> {
-	Ok(NixEval::new(HOSTS_ATTRIBUTE.into())
-		.apply("builtins.attrNames".into())
+	Ok(Command::new("nix")
+		.inherit_stdio()
+		.arg("eval")
+		.arg(HOSTS_ATTRIBUTE)
+		.arg("--apply")
+		.arg("builtins.attrNames")
 		.run_json()?)
 }
 
@@ -29,10 +30,9 @@
 impl KeyDb {
 	pub fn fetch_key(&mut self, host: &str) -> Result<()> {
 		info!("Fetching key for {}", host);
-		let key = ssh_command(host, &["cat", "/etc/ssh/ssh_host_ed25519_key.pub"])?
-			.as_str()?
-			.trim()
-			.to_owned();
+		let key = Command::ssh_on(host, "cat")
+			.arg("/etc/ssh/ssh_host_ed25519_key.pub")
+			.run_string()?;
 		self.host_keys.insert(host.to_owned(), key);
 		Ok(())
 	}
modifiedsrc/db/secret.rsdiffbeforeafterboth
before · src/db/secret.rs
1use crate::nix::{NixBuild, NixEval, SECRETS_ATTRIBUTE};2use anyhow::{bail, Result};3use log::info;4use serde::{Deserialize, Deserializer, Serialize, Serializer};5use std::{6	collections::{BTreeMap, BTreeSet, HashMap},7	time::Instant,8	time::SystemTime,9};10use time::{Duration, PrimitiveDateTime};1112use super::{db::DbData, keys::KeyDb};1314#[derive(Serialize, Deserialize, Debug)]15pub struct SecretListData {16	pub owners: BTreeSet<String>,17	#[serde(rename = "expireIn")]18	renew_in: Option<u64>,19}20pub fn list_secrets() -> Result<HashMap<String, SecretListData>> {21	NixEval::new(format!("{}", SECRETS_ATTRIBUTE))22		.apply(23			r#"24				s: (builtins.mapAttrs (n: {owners, expireIn, ...}: {25					inherit owners expireIn;26				}) s)27			"#28			.into(),29		)30		.run_json()31}3233struct ReadableDate(PrimitiveDateTime);34impl Serialize for ReadableDate {35	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>36	where37		S: Serializer,38	{39		serializer.serialize_str(&self.0.to_string())40	}41}42impl<'de> Deserialize<'de> for ReadableDate {43	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>44	where45		D: Deserializer<'de>,46	{47		Ok(Self(48			PrimitiveDateTime::parse(String::deserialize(deserializer)?, "%F %T").unwrap(),49		))50	}51}52impl From<PrimitiveDateTime> for ReadableDate {53	fn from(d: PrimitiveDateTime) -> Self {54		Self(d)55	}56}57impl From<ReadableDate> for PrimitiveDateTime {58	fn from(d: ReadableDate) -> Self {59		d.060	}61}6263#[derive(serde::Serialize, serde::Deserialize)]64struct SecretData {65	created_at: ReadableDate,66	renew_at: Option<ReadableDate>,67	owners: BTreeSet<String>,6869	public_data: BTreeMap<String, String>,70	private_files: BTreeMap<String, String>,71}72impl SecretData {73	fn should_renew(&self) -> bool {74		if let Some(renew_at) = &self.renew_at {75			let now: PrimitiveDateTime = SystemTime::now().into();76			renew_at.0 <= now77		} else {78			false79		}80	}81	fn is_valid(&self, data: &SecretListData) -> bool {82		self.owners == data.owners83	}84}8586#[derive(serde::Serialize, serde::Deserialize)]87struct NixDataValue {88	data: BTreeMap<String, String>,89}9091#[derive(serde::Serialize, serde::Deserialize)]92struct NixData {93	secrets: BTreeMap<String, NixDataValue>,94}9596#[derive(serde::Serialize, serde::Deserialize, Default)]97pub struct SecretDb {98	secrets: BTreeMap<String, SecretData>,99}100impl DbData for SecretDb {101	const DB_NAME: &'static str = "secrets";102}103104impl SecretDb {105	// Secrets are generated on machine running fleet command106	pub fn generate_secret(107		&mut self,108		keys: &KeyDb,109		secret: &str,110		data: &SecretListData,111	) -> Result<()> {112		let mut rage_keys = String::new();113		for (i, owner) in data.owners.iter().enumerate() {114			if i != 0 {115				rage_keys.push(' ');116			}117			rage_keys.push_str("--recipient \"");118			rage_keys.push_str(&keys.get_host_key(&owner)?);119			rage_keys.push('"')120		}121		let created_at: PrimitiveDateTime = SystemTime::now().into();122		let renew_at = data123			.renew_in124			.map(|hours| created_at + Duration::hours(hours as i64));125		let built = NixBuild::new(format!("{}.{}.generator", SECRETS_ATTRIBUTE, secret))126			.env("RAGE_KEYS".into(), rage_keys)127			.env("IMPURITY_SOURCE".into(), format!("{:?}", Instant::now()))128			.run()?;129		let path = built.path().to_owned();130		let mut secret_data = SecretData {131			created_at: created_at.into(),132			renew_at: renew_at.map(|v| v.into()),133			owners: data.owners.clone(),134			public_data: BTreeMap::new(),135			private_files: BTreeMap::new(),136		};137		for file in std::fs::read_dir(path)? {138			let entry = file?;139			if !entry.file_type()?.is_file() {140				bail!("Secret generator should produce files, not directories");141			}142			let name = entry.file_name();143			let name = name144				.to_str()145				.ok_or(anyhow::anyhow!("file name should be utf-8"))?;146			let value = String::from_utf8(std::fs::read(entry.path())?)?;147			if let Some(name) = name.strip_prefix("pub_") {148				secret_data.public_data.insert(name.into(), value);149			} else {150				secret_data.private_files.insert(name.into(), value);151			}152		}153		self.secrets.insert(secret.into(), secret_data);154		Ok(())155	}156	pub fn need_to_generate(&self, secret: &str, data: &SecretListData) -> Result<bool> {157		let secret = self.secrets.get(secret);158		if secret.is_none() {159			return Ok(true);160		}161		let secret = secret.unwrap();162163		if secret.should_renew() {164			return Ok(true);165		}166167		if !secret.is_valid(&data) {168			return Ok(true);169		}170171		Ok(false)172	}173	pub fn ensure_generated(174		&mut self,175		keys: &KeyDb,176		secret: &str,177		data: &SecretListData,178	) -> Result<()> {179		if self.need_to_generate(secret, data)? {180			info!("Generating secret {}", secret);181			self.generate_secret(keys, secret, data)?;182		}183184		Ok(())185	}186	pub fn generate_nix_data(&self) -> Result<String> {187		let mut out = BTreeMap::new();188		for (host, secrets) in &self.secrets {189			out.insert(190				host.to_owned(),191				NixDataValue {192					data: secrets193						.public_data194						.clone()195						.iter()196						.map(|(k, v)| (k.to_owned(), v.trim().to_owned()))197						.collect(),198				},199			);200		}201		Ok(serde_json::to_string(&out)?)202	}203204	pub fn has_secret(&self, secret: &str) -> bool {205		self.secrets.contains_key(secret)206	}207208	pub fn remove_secret(&mut self, secret: &str) {209		self.secrets.remove(secret);210	}211}
after · src/db/secret.rs
1use crate::{command::CommandExt, nix::SECRETS_ATTRIBUTE};2use anyhow::{bail, Result};3use log::info;4use serde::{Deserialize, Deserializer, Serialize, Serializer};5use std::{6	collections::{BTreeMap, BTreeSet, HashMap},7	process::Command,8	time::Instant,9	time::SystemTime,10};11use time::{Duration, PrimitiveDateTime};1213use super::{db::DbData, keys::KeyDb};1415#[derive(Serialize, Deserialize, Debug)]16pub struct SecretListData {17	pub owners: BTreeSet<String>,18	#[serde(rename = "expireIn")]19	renew_in: Option<u64>,20}21pub fn list_secrets() -> Result<HashMap<String, SecretListData>> {22	Command::new("nix")23		.inherit_stdio()24		.arg("eval")25		.arg(SECRETS_ATTRIBUTE)26		.arg("--apply")27		.arg(28			r#"29				s: (builtins.mapAttrs (n: {owners, expireIn, ...}: {30					inherit owners expireIn;31				}) s)32			"#,33		)34		.run_json()35}3637struct ReadableDate(PrimitiveDateTime);38impl Serialize for ReadableDate {39	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>40	where41		S: Serializer,42	{43		serializer.serialize_str(&self.0.to_string())44	}45}46impl<'de> Deserialize<'de> for ReadableDate {47	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>48	where49		D: Deserializer<'de>,50	{51		Ok(Self(52			PrimitiveDateTime::parse(String::deserialize(deserializer)?, "%F %T").unwrap(),53		))54	}55}56impl From<PrimitiveDateTime> for ReadableDate {57	fn from(d: PrimitiveDateTime) -> Self {58		Self(d)59	}60}61impl From<ReadableDate> for PrimitiveDateTime {62	fn from(d: ReadableDate) -> Self {63		d.064	}65}6667#[derive(serde::Serialize, serde::Deserialize)]68struct SecretData {69	created_at: ReadableDate,70	renew_at: Option<ReadableDate>,71	owners: BTreeSet<String>,7273	public_data: BTreeMap<String, String>,74	private_files: BTreeMap<String, String>,75}76impl SecretData {77	fn should_renew(&self) -> bool {78		if let Some(renew_at) = &self.renew_at {79			let now: PrimitiveDateTime = SystemTime::now().into();80			renew_at.0 <= now81		} else {82			false83		}84	}85	fn is_valid(&self, data: &SecretListData) -> bool {86		self.owners == data.owners87	}88}8990#[derive(serde::Serialize, serde::Deserialize)]91struct NixDataValue {92	data: BTreeMap<String, String>,93}9495#[derive(serde::Serialize, serde::Deserialize)]96struct NixData {97	secrets: BTreeMap<String, NixDataValue>,98}99100#[derive(serde::Serialize, serde::Deserialize, Default)]101pub struct SecretDb {102	secrets: BTreeMap<String, SecretData>,103}104impl DbData for SecretDb {105	const DB_NAME: &'static str = "secrets";106}107108impl SecretDb {109	// Secrets are generated on machine running fleet command110	pub fn generate_secret(111		&mut self,112		keys: &KeyDb,113		secret: &str,114		data: &SecretListData,115	) -> Result<()> {116		let mut rage_keys = String::new();117		for (i, owner) in data.owners.iter().enumerate() {118			if i != 0 {119				rage_keys.push(' ');120			}121			rage_keys.push_str("--recipient \"");122			rage_keys.push_str(&keys.get_host_key(&owner)?);123			rage_keys.push('"')124		}125		let created_at: PrimitiveDateTime = SystemTime::now().into();126		let renew_at = data127			.renew_in128			.map(|hours| created_at + Duration::hours(hours as i64));129		let built = tempfile::tempdir()?;130		Command::new("nix")131			.inherit_stdio()132			.arg("build")133			.arg(format!("{}.{}.generator", SECRETS_ATTRIBUTE, secret))134			.arg("--no-link")135			.arg("--out-link")136			.arg(built.path())137			.arg("--impure")138			.env("RAGE_KEYS", rage_keys)139			.env("IMPURITY_SOURCE", format!("{:?}", Instant::now()))140			.run()?;141		let path = built.path().to_owned();142		let mut secret_data = SecretData {143			created_at: created_at.into(),144			renew_at: renew_at.map(|v| v.into()),145			owners: data.owners.clone(),146			public_data: BTreeMap::new(),147			private_files: BTreeMap::new(),148		};149		for file in std::fs::read_dir(path)? {150			let entry = file?;151			if !entry.file_type()?.is_file() {152				bail!("Secret generator should produce files, not directories");153			}154			let name = entry.file_name();155			let name = name156				.to_str()157				.ok_or(anyhow::anyhow!("file name should be utf-8"))?;158			let value = String::from_utf8(std::fs::read(entry.path())?)?;159			if let Some(name) = name.strip_prefix("pub_") {160				secret_data.public_data.insert(name.into(), value);161			} else {162				secret_data.private_files.insert(name.into(), value);163			}164		}165		self.secrets.insert(secret.into(), secret_data);166		Ok(())167	}168	pub fn need_to_generate(&self, secret: &str, data: &SecretListData) -> Result<bool> {169		let secret = self.secrets.get(secret);170		if secret.is_none() {171			return Ok(true);172		}173		let secret = secret.unwrap();174175		if secret.should_renew() {176			return Ok(true);177		}178179		if !secret.is_valid(&data) {180			return Ok(true);181		}182183		Ok(false)184	}185	pub fn ensure_generated(186		&mut self,187		keys: &KeyDb,188		secret: &str,189		data: &SecretListData,190	) -> Result<()> {191		if self.need_to_generate(secret, data)? {192			info!("Generating secret {}", secret);193			self.generate_secret(keys, secret, data)?;194		}195196		Ok(())197	}198	pub fn generate_nix_data(&self) -> Result<String> {199		let mut out = BTreeMap::new();200		for (host, secrets) in &self.secrets {201			out.insert(202				host.to_owned(),203				NixDataValue {204					data: secrets205						.public_data206						.clone()207						.iter()208						.map(|(k, v)| (k.to_owned(), v.trim().to_owned()))209						.collect(),210				},211			);212		}213		Ok(serde_json::to_string(&out)?)214	}215216	pub fn has_secret(&self, secret: &str) -> bool {217		self.secrets.contains_key(secret)218	}219220	pub fn remove_secret(&mut self, secret: &str) {221		self.secrets.remove(secret);222	}223}
modifiedsrc/nix.rsdiffbeforeafterboth
--- a/src/nix.rs
+++ b/src/nix.rs
@@ -1,172 +1,3 @@
-use std::{
-	collections::HashMap,
-	ffi::OsStr,
-	path::PathBuf,
-	process::{Command, Stdio},
-};
-
-use anyhow::Result;
-use serde::de::DeserializeOwned;
-
-use crate::command::CommandOutput;
-
 pub const HOSTS_ATTRIBUTE: &str = ".#fleetConfigurations.default.configuredHosts";
 pub const SECRETS_ATTRIBUTE: &str = ".#fleetConfigurations.default.configuredSecrets";
 pub const SYSTEMS_ATTRIBUTE: &str = ".#fleetConfigurations.default.configuredSystems";
-
-pub struct NixCopy {
-	closure: PathBuf,
-}
-impl NixCopy {
-	pub fn new(closure: PathBuf) -> Self {
-		Self { closure }
-	}
-	fn run_internal(&self, f: impl Fn(&mut Command)) -> Result<CommandOutput> {
-		let mut cmd = Command::new("nix");
-		cmd.stderr(Stdio::inherit())
-			.arg("copy")
-			.arg("--substitute-on-destination")
-			.arg(&self.closure);
-		f(&mut cmd);
-
-		let out = cmd.output()?;
-		if !out.status.success() {
-			anyhow::bail!("nix copy failed");
-		}
-		Ok(CommandOutput(out.stdout))
-	}
-	pub fn from(&self, from: impl AsRef<OsStr>) -> Result<()> {
-		let from = from.as_ref();
-		self.run_internal(|cmd| {
-			cmd.arg("--from").arg(from);
-		})?;
-		Ok(())
-	}
-	pub fn to(&self, to: impl AsRef<OsStr>) -> Result<()> {
-		let to = to.as_ref();
-		self.run_internal(|cmd| {
-			cmd.arg("--to").arg(to);
-		})?;
-		Ok(())
-	}
-}
-
-pub struct NixBuild {
-	attribute: String,
-	impure: bool,
-	env: HashMap<String, String>,
-}
-
-impl NixBuild {
-	pub fn new(attribute: String) -> Self {
-		Self {
-			attribute,
-			impure: false,
-			env: HashMap::new(),
-		}
-	}
-	pub fn env(&mut self, name: String, value: String) -> &mut Self {
-		self.impure = true;
-		self.env.insert(name, value);
-		self
-	}
-	pub fn run(&self) -> Result<tempfile::TempDir> {
-		let dir = tempfile::tempdir()?;
-		std::fs::remove_dir(dir.path())?;
-		let mut cmd = Command::new("nix");
-		cmd.stderr(Stdio::inherit())
-			.arg("build")
-			.arg(&self.attribute)
-			.arg("--no-link")
-			.arg("--out-link")
-			.arg(dir.path());
-		if self.impure {
-			cmd.arg("--impure");
-		}
-		if !self.env.is_empty() {
-			cmd.envs(&self.env);
-		}
-
-		let out = cmd.output()?;
-		if !out.status.success() {
-			anyhow::bail!("nix eval failed");
-		}
-		Ok(dir)
-	}
-}
-
-#[derive(Default)]
-pub struct NixEval {
-	attribute: String,
-	impure: bool,
-	apply: Option<String>,
-	env: HashMap<String, String>,
-}
-
-impl NixEval {
-	pub fn new(attribute: String) -> Self {
-		Self {
-			attribute,
-			..Default::default()
-		}
-	}
-	pub fn impure(&mut self) -> &mut Self {
-		self.impure = true;
-		self
-	}
-	/// This is the only and impure way to pass something to flake
-	/// - https://github.com/NixOS/nix/issues/3949
-	/// - https://github.com/NixOS/nixpkgs/issues/101101
-	pub fn env(&mut self, name: String, value: String) -> &mut Self {
-		self.impure = true;
-		self.env.insert(name, value);
-		self
-	}
-	pub fn apply(&mut self, apply: String) -> &mut Self {
-		self.apply = Some(apply);
-		self
-	}
-	fn run_internal(&self, f: impl Fn(&mut Command)) -> Result<CommandOutput> {
-		let mut cmd = Command::new("nix");
-		cmd.stderr(Stdio::inherit())
-			.arg("eval")
-			.arg("--show-trace")
-			.arg(&self.attribute);
-		if let Some(apply) = &self.apply {
-			cmd.arg("--apply").arg(apply);
-		};
-		if self.impure {
-			cmd.arg("--impure");
-		}
-		if !self.env.is_empty() {
-			cmd.envs(&self.env);
-		}
-		f(&mut cmd);
-
-		let out = cmd.output()?;
-		if !out.status.success() {
-			anyhow::bail!("nix eval failed");
-		}
-		Ok(CommandOutput(out.stdout))
-	}
-	pub fn run(&self) -> Result<String> {
-		Ok(self.run_internal(|_cmd| {})?.as_str()?.to_owned())
-	}
-	pub fn run_json<T: DeserializeOwned>(&self) -> Result<T> {
-		Ok(serde_json::from_slice(
-			&self
-				.run_internal(|cmd| {
-					cmd.arg("--json");
-				})?
-				.0,
-		)?)
-	}
-	pub fn run_raw(&self) -> Result<String> {
-		Ok(self
-			.run_internal(|cmd| {
-				cmd.arg("--raw");
-			})?
-			.as_str()?
-			.to_owned())
-	}
-}