difftreelog
feat fleetdata
in: trunk
9 files changed
src/cmds/build_systems.rsdiffbeforeafterboth--- a/src/cmds/build_systems.rs
+++ b/src/cmds/build_systems.rs
@@ -1,20 +1,13 @@
use std::process::Command;
-use crate::{
- command::CommandExt,
- db::{secret::SecretDb, Db, DbData},
- host::FleetOpts,
- nix::SYSTEMS_ATTRIBUTE,
-};
+use crate::{command::CommandExt, host::Config, nix::SYSTEMS_ATTRIBUTE};
use anyhow::Result;
use clap::Clap;
-use log::{info, warn};
+use log::info;
#[derive(Clap)]
#[clap(group = clap::ArgGroup::new("target"))]
pub struct BuildSystems {
- #[clap(flatten)]
- fleet_opts: FleetOpts,
/// --builders arg for nix
#[clap(long)]
builders: Option<String>,
@@ -53,18 +46,18 @@
}
impl BuildSystems {
- pub fn run(self) -> Result<()> {
- let fleet = self.fleet_opts.build()?;
- let db = Db::new(".fleet")?;
- let hosts = fleet.list_hosts()?;
- let data = SecretDb::open(&db)?.generate_nix_data()?;
+ pub fn run(self, config: &Config) -> Result<()> {
+ println!("Build");
+ // let db = Db::new(".fleet")?;
+ let hosts = config.list_hosts()?;
+ dbg!(&hosts);
+ // let data = SecretDb::open(&db)?.generate_nix_data()?;
for host in hosts.iter() {
- if host.skip() {
- warn!("Skipping host {}", host.hostname);
+ if config.should_skip(host) {
continue;
}
- info!("Building host {}", host.hostname);
+ info!("Building host {}", host);
let built = {
let dir = tempfile::tempdir()?;
dir.path().to_owned()
@@ -82,9 +75,9 @@
.arg(&built)
.arg(format!(
"{}.{}.config.system.build.toplevel",
- SYSTEMS_ATTRIBUTE, host.hostname,
- ))
- .env("SECRET_DATA", data.clone());
+ SYSTEMS_ATTRIBUTE, host,
+ ));
+ // .env("SECRET_DATA", data.clone());
if let Some(builders) = &self.builders {
println!("Using builders: {}", builders);
@@ -101,11 +94,11 @@
nix_build.inherit_stdio().run()?;
let built = std::fs::canonicalize(built)?;
info!("Built closure: {:?}", built);
- if !host.is_local() {
+ if !config.is_local(host) {
info!("Uploading system closure");
Command::new("nix")
.args(&["copy", "--to"])
- .arg(format!("ssh://root@{}", host.hostname))
+ .arg(format!("ssh://root@{}", host))
.arg(&built)
.inherit_stdio()
.run()?;
@@ -113,18 +106,20 @@
if let Some(subcommand) = &self.subcommand {
if subcommand.should_switch_profile() {
info!("Switching generation");
- host.command_on("nix-env", true)
+ dbg!(&mut config
+ .command_on(host, "nix-env", true)
.args(&["-p", "/nix/var/nix/profiles/system", "--set"])
.arg(&built)
- .inherit_stdio()
- .run()?;
+ .inherit_stdio())
+ .run()?;
}
info!("Executing activation script");
let mut switch_script = built.clone();
switch_script.push("bin");
switch_script.push("switch-to-configuration");
info!("{:?}", switch_script);
- host.command_on(switch_script, true)
+ config
+ .command_on(host, switch_script, true)
.arg(subcommand.name())
.inherit_stdio()
.run()?;
src/cmds/generate_secrets.rsdiffbeforeafterboth--- a/src/cmds/generate_secrets.rs
+++ b/src/cmds/generate_secrets.rs
@@ -4,13 +4,19 @@
use clap::Clap;
use log::info;
-use crate::db::{
- secret::{list_secrets, SecretDb},
- Db, DbData,
+use crate::{
+ db::{
+ secret::{list_secrets, SecretDb},
+ Db, DbData,
+ },
+ host::FleetOpts,
};
#[derive(Clap)]
pub struct GenerateSecrets {
+ #[clap(flatten)]
+ fleet_opts: FleetOpts,
+
/// If set - remove orphaned secrets
#[clap(long)]
cleanup: bool,
@@ -23,8 +29,8 @@
let defined_secrets = list_secrets()?;
for (secret, data) in defined_secrets.iter() {
- // let keys = KeyDb::open(&db)?;
- // secrets.ensure_generated(&keys, &secret, &data)?;
+ //let keys = KeyDb::open(&db)?;
+ secrets.ensure_generated(&self.fleet_opts, secret, data)?;
}
let key_names = defined_secrets
.keys()
src/cmds/mod.rsdiffbeforeafterboth--- a/src/cmds/mod.rs
+++ b/src/cmds/mod.rs
@@ -1,3 +1,4 @@
pub mod build_systems;
-pub mod fetch_keys;
+// pub mod fetch_keys;
pub mod generate_secrets;
+pub mod secrets;
src/command.rsdiffbeforeafterboth--- a/src/command.rs
+++ b/src/command.rs
@@ -23,14 +23,14 @@
fn run(&mut self) -> Result<()> {
let out = self.output()?;
if !out.status.success() {
- anyhow::bail!("command failed");
+ anyhow::bail!("command failed with status {}", out.status);
}
Ok(())
}
fn run_json<T: DeserializeOwned>(&mut self) -> Result<T> {
let str = self.run_string()?;
- Ok(serde_json::from_str(&str).with_context(|| format!("{:?}", str))?)
+ serde_json::from_str(&str).with_context(|| format!("{:?}", str))
}
fn run_string(&mut self) -> Result<String> {
src/fleetdata.rsdiffbeforeafterboth--- /dev/null
+++ b/src/fleetdata.rs
@@ -0,0 +1,16 @@
+use serde::{Deserialize, Serialize};
+use std::collections::BTreeMap;
+
+#[derive(Serialize, Deserialize, Default)]
+pub struct HostData {
+ #[serde(default)]
+ pub encryption_key: String,
+ #[serde(default)]
+ pub encrypted_secrets: BTreeMap<String, String>,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct FleetData {
+ #[serde(default)]
+ pub hosts: BTreeMap<String, HostData>,
+}
src/host.rsdiffbeforeafterboth1use std::{1use std::{2 cell::{Ref, RefCell, RefMut},2 env::current_dir,3 env::current_dir,3 ffi::{OsStr, OsString},4 ffi::{OsStr, OsString},4 ops::Deref,5 ops::Deref,10use anyhow::Result;11use anyhow::Result;11use clap::Clap;12use clap::Clap;121313use crate::command::CommandExt;14use crate::{command::CommandExt, fleetdata::FleetData};141515pub struct FleetConfigInternals {16pub struct FleetConfigInternals {16 pub directory: PathBuf,17 pub directory: PathBuf,17 pub opts: FleetOpts,18 pub opts: FleetOpts,19 pub data: RefCell<FleetData>,18}20}192120#[derive(Clone)]22#[derive(Clone)]21pub struct FleetConfig(Arc<FleetConfigInternals>);23pub struct Config(Arc<FleetConfigInternals>);222423impl Deref for FleetConfig {25impl Deref for Config {24 type Target = FleetConfigInternals;26 type Target = FleetConfigInternals;252726 fn deref(&self) -> &Self::Target {28 fn deref(&self) -> &Self::Target {27 &self.029 &self.028 }30 }29}31}303231impl FleetConfig {33impl Config {34 pub fn should_skip(&self, host: &str) -> bool {35 if !self.opts.skip.is_empty() {36 self.opts.skip.iter().any(|h| h as &str == host)37 } else if !self.opts.only.is_empty() {38 !self.opts.only.iter().any(|h| h as &str == host)39 } else {40 false41 }42 }43 pub fn is_local(&self, host: &str) -> bool {44 self.opts.localhost.as_ref().map(|s| s as &str) == Some(host)45 }4632 pub fn data_dir(&self) -> PathBuf {47 pub fn command_on(&self, host: &str, program: impl AsRef<OsStr>, sudo: bool) -> Command {48 if self.is_local(host) {49 if sudo {33 let mut out = self.directory.clone();50 let mut cmd = Command::new("sudo");51 cmd.arg(program);52 cmd53 } else {54 Command::new(program)55 }56 } else {57 let mut cmd = Command::new("ssh");34 out.push(".fleet");58 cmd.arg(host).arg("--");35 out59 if sudo {60 cmd.arg("sudo");61 }62 cmd.arg(program);63 cmd64 }36 }65 }376638 pub fn full_attr_name(&self, attr_name: &str) -> OsString {67 pub fn full_attr_name(&self, attr_name: &str) -> OsString {39 let mut str = self.directory.as_os_str().to_owned();68 let mut str = self.directory.as_os_str().to_owned();40 str.push("#");69 str.push("#");41 str.push(attr_name);70 str.push(attr_name);7172 println!("{:?}", str);42 str73 str43 }74 }447545 pub fn list_host_names(&self) -> Result<Vec<String>> {76 pub fn list_hosts(&self) -> Result<Vec<String>> {46 Ok(Command::new("nix")77 Command::new("nix")47 .arg("eval")78 .arg("eval")48 .arg(self.full_attr_name("fleetConfigurations.default.configuredHosts"))79 .arg(self.full_attr_name("fleetConfigurations.default.configuredHosts"))49 .args(&["--apply", "builtins.attrNames", "--json"])80 .args(&["--apply", "builtins.attrNames", "--json", "--show-trace"])50 .inherit_stdio()51 .run_json()?)81 .inherit_stdio()82 .run_json()52 }83 }8485 pub fn data(&self) -> Ref<FleetData> {86 self.data.borrow()87 }88 pub fn data_mut(&self) -> RefMut<FleetData> {89 self.data.borrow_mut()90 }539154 pub fn list_hosts(&self) -> Result<Vec<Host>> {92 pub fn save(&self) -> Result<()> {55 Ok(self93 let mut fleet_data_path = self.directory.clone();56 .list_host_names()?94 fleet_data_path.push("fleet.nix");57 .into_iter()58 .map(|hostname| Host {95 let data = nixlike::serialize(&self.data() as &FleetData)?;59 fleet_config: self.clone(),96 std::fs::write(fleet_data_path, data)?;60 hostname,97 Ok(())61 })62 .collect())63 }98 }64}99}6566pub struct Host {67 pub fleet_config: FleetConfig,6869 pub hostname: String,70}7172impl Host {73 pub fn skip(&self) -> bool {74 self.fleet_config.0.opts.should_skip(&self.hostname)75 }76 pub fn is_local(&self) -> bool {77 self.fleet_config.0.opts.is_local(&self.hostname)78 }79 pub fn command_on(&self, cmd: impl AsRef<OsStr>, sudo: bool) -> Command {80 if !self.is_local() {81 let mut out = Command::new("ssh");82 out.arg(&self.hostname).arg("--");83 if sudo {84 out.arg("sudo");85 }86 out.arg(cmd);87 out88 } else if sudo {89 let mut out = Command::new("sudo");90 out.arg(cmd);91 out92 } else {93 Command::new(cmd)94 }95 }96}9710098#[derive(Clap, Clone)]101#[derive(Clap, Clone)]99#[clap(group = clap::ArgGroup::new("target_hosts"))]102#[clap(group = clap::ArgGroup::new("target_hosts"))]112}115}113116114impl FleetOpts {117impl FleetOpts {115 pub fn should_skip(&self, host: &str) -> bool {116 if self.skip.len() > 0 {117 self.skip.iter().find(|h| h as &str == host).is_some()118 } else if self.only.len() > 0 {119 self.only.iter().find(|h| h as &str == host).is_none()120 } else {121 false122 }123 }124 pub fn is_local(&self, host: &str) -> bool {125 self.localhost.as_ref().map(|s| &s as &str) == Some(host)126 }127 pub fn build(mut self) -> Result<FleetConfig> {118 pub fn build(mut self) -> Result<Config> {128 if self.localhost.is_none() {119 if self.localhost.is_none() {129 self.localhost120 self.localhost130 .replace(hostname::get().unwrap().to_str().unwrap().to_owned());121 .replace(hostname::get().unwrap().to_str().unwrap().to_owned());131 }122 }132 let directory = current_dir()?;123 let directory = current_dir()?;124125 let mut fleet_data_path = directory.clone();126 fleet_data_path.push("fleet.nix");127 let bytes = std::fs::read_to_string(fleet_data_path)?;128 let data = nixlike::parse_str(&bytes)?;129133 Ok(FleetConfig(Arc::new(FleetConfigInternals {130 Ok(Config(Arc::new(FleetConfigInternals {134 opts: self,131 opts: self,135 directory,132 directory,133 data,136 })))134 })))137 }135 }138}136}src/keys.rsdiffbeforeafterboth--- a/src/keys.rs
+++ b/src/keys.rs
@@ -1,65 +1,67 @@
-use crate::{
- command::CommandExt,
- host::{FleetConfig, Host},
-};
-use anyhow::Result;
+use std::str::FromStr;
+
+use crate::{command::CommandExt, host::Config};
+use anyhow::{anyhow, Result};
use log::warn;
-use std::{
- fs::{create_dir_all, metadata, read, read_dir, write},
- path::PathBuf,
-};
-impl FleetConfig {
- fn host_keys_dir(&self) -> Result<PathBuf> {
- let mut out = self.data_dir().clone();
- out.push("host_keys");
- create_dir_all(&out)?;
- Ok(out)
+impl Config {
+ pub fn cached_key(&self, host: &str) -> Option<String> {
+ let data = self.data();
+ let key = data.hosts.get(host).map(|h| &h.encryption_key);
+ if let Some(key) = key {
+ if key.is_empty() {
+ return None;
+ }
+ }
+ key.cloned()
}
-
- fn host_key_file(&self, host: &str) -> Result<PathBuf> {
- let mut dir = self.host_keys_dir()?;
- dir.push(format!("{}.asc", host));
- Ok(dir)
+ pub fn update_key(&self, host: &str, key: String) {
+ let mut data = self.data_mut();
+ let host = data.hosts.entry(host.to_string()).or_default();
+ host.encryption_key = key.trim().to_string();
}
-
- pub fn list_orphaned_keys(&self) -> Result<Vec<(String, PathBuf)>> {
- let mut out = Vec::new();
- let host_names = self.list_host_names()?;
- for file in read_dir(&self.host_keys_dir()?)? {
- let file = file?;
- anyhow::ensure!(
- file.file_type()?.is_file(),
- "host_keys dir should contain only files"
- );
- let name = file.file_name();
- let name = name.to_str().unwrap();
- if let Some(hostname) = name.strip_suffix(".asc") {
- if !host_names.contains(&hostname.to_owned()) {
- out.push((hostname.to_owned(), file.path()))
- }
- } else {
- out.push(("<unknown>".to_owned(), file.path()))
- }
- }
-
- Ok(out)
+ pub fn update_secret(&self, host: &str, name: &str, value: &[u8]) {
+ let mut data = self.data_mut();
+ let host = data.hosts.entry(host.to_string()).or_default();
+ host.encrypted_secrets.insert(
+ name.to_string(),
+ format!("[ENCRYPTED:{}]", base64::encode(value)),
+ );
}
-}
-impl Host {
- pub fn key(&self) -> anyhow::Result<String> {
- let key_path = self.fleet_config.host_key_file(&self.hostname)?;
- if metadata(&key_path).map(|m| m.is_file()).unwrap_or(false) {
- Ok(String::from_utf8(read(key_path)?)?)
+ pub fn key(&self, host: &str) -> anyhow::Result<String> {
+ if let Some(key) = self.cached_key(host) {
+ Ok(key)
} else {
- warn!("Loading key for {}", self.hostname);
+ warn!("Loading key for {}", host);
let key = self
- .command_on("cat", false)
+ .command_on("host", "cat", false)
.arg("/etc/ssh/ssh_host_ed25519_key.pub")
.run_string()?;
- write(key_path, key.clone())?;
+ self.update_key(host, key.clone());
Ok(key)
}
}
+ pub fn recipient(&self, host: &str) -> anyhow::Result<age::ssh::Recipient> {
+ let key = self.key(host)?;
+ age::ssh::Recipient::from_str(&key).map_err(|e| anyhow!("parse recipient error: {:?}", e))
+ }
+
+ pub fn orphaned_data(&self) -> Result<Vec<String>> {
+ let mut out = Vec::new();
+ let host_names = self.list_hosts()?;
+ for hostname in self
+ .data()
+ .hosts
+ .iter()
+ .filter(|(_, host)| !host.encryption_key.is_empty())
+ .map(|(n, _)| n)
+ {
+ if !host_names.contains(&hostname.to_owned()) {
+ out.push(hostname.to_owned())
+ }
+ }
+
+ Ok(out)
+ }
}
src/main.rsdiffbeforeafterboth--- a/src/main.rs
+++ b/src/main.rs
@@ -8,10 +8,14 @@
pub mod db;
pub mod nix;
+mod fleetdata;
+
use anyhow::Result;
use clap::Clap;
-use cmds::{build_systems::BuildSystems, fetch_keys::FetchKeys, generate_secrets::GenerateSecrets};
+use cmds::{build_systems::BuildSystems, generate_secrets::GenerateSecrets, secrets::Secrets};
+use host::{Config, FleetOpts};
+
#[derive(Clap)]
#[clap(version = "1.0", author = "CertainLach <iam@lach.pw>")]
enum Opts {
@@ -23,16 +27,38 @@
Secrets(Secrets),
}
+#[derive(Clap)]
+struct RootOpts {
+ #[clap(flatten)]
+ fleet_opts: FleetOpts,
+ #[clap(subcommand)]
+ command: Opts,
+}
+
+fn run_command(config: &Config, command: Opts) -> Result<()> {
+ match command {
+ Opts::BuildSystems(c) => c.run(config)?,
+ Opts::GenerateSecrets(c) => c.run()?,
+ Opts::Secrets(s) => s.run(config)?,
+ };
+ Ok(())
+}
+
fn main() -> Result<()> {
env_logger::Builder::new()
.filter_level(log::LevelFilter::Info)
.init();
- let opts = Opts::parse();
+ let opts = RootOpts::parse();
+ let config = opts.fleet_opts.build()?;
- match opts {
- Opts::FetchKeys(c) => c.run()?,
- Opts::BuildSystems(c) => c.run()?,
- Opts::GenerateSecrets(c) => c.run()?,
- };
- Ok(())
+ match run_command(&config, opts.command) {
+ Ok(()) => {
+ config.save()?;
+ Ok(())
+ }
+ Err(e) => {
+ let _ = config.save();
+ Err(e)
+ }
+ }
}
src/nixlike.rsdiffbeforeafterbothno changes