5 files changed
--- 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(())
}
--- 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))
}
--- 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(())
}
1use crate::nix::{NixBuild, NixEval, SECRETS_ATTRIBUTE};1use crate::{command::CommandExt, nix::SECRETS_ATTRIBUTE};
2use anyhow::{bail, Result};2use anyhow::{bail, Result};
3use log::info;3use log::info;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use std::{5use std::{
6 collections::{BTreeMap, BTreeSet, HashMap},6 collections::{BTreeMap, BTreeSet, HashMap},
7 process::Command,
7 time::Instant,8 time::Instant,
8 time::SystemTime,9 time::SystemTime,
9};10};
18 renew_in: Option<u64>,19 renew_in: Option<u64>,
19}20}
20pub fn list_secrets() -> Result<HashMap<String, SecretListData>> {21pub fn list_secrets() -> Result<HashMap<String, SecretListData>> {
21 NixEval::new(format!("{}", SECRETS_ATTRIBUTE))22 Command::new("nix")
23 .inherit_stdio()
24 .arg("eval")
25 .arg(SECRETS_ATTRIBUTE)
26 .arg("--apply")
22 .apply(27 .arg(
23 r#"28 r#"
24 s: (builtins.mapAttrs (n: {owners, expireIn, ...}: {29 s: (builtins.mapAttrs (n: {owners, expireIn, ...}: {
25 inherit owners expireIn;30 inherit owners expireIn;
26 }) s)31 }) s)
27 "#32 "#,
28 .into(),
29 )33 )
30 .run_json()34 .run_json()
31}35}
122 let renew_at = data126 let renew_at = data
123 .renew_in127 .renew_in
124 .map(|hours| created_at + Duration::hours(hours as i64));128 .map(|hours| created_at + Duration::hours(hours as i64));
125 let built = NixBuild::new(format!("{}.{}.generator", SECRETS_ATTRIBUTE, secret))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")
126 .env("RAGE_KEYS".into(), rage_keys)138 .env("RAGE_KEYS", rage_keys)
127 .env("IMPURITY_SOURCE".into(), format!("{:?}", Instant::now()))139 .env("IMPURITY_SOURCE", format!("{:?}", Instant::now()))
128 .run()?;140 .run()?;
129 let path = built.path().to_owned();141 let path = built.path().to_owned();
130 let mut secret_data = SecretData {142 let mut secret_data = SecretData {
--- 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())
- }
-}