#![forbid(unsafe_code)] #![feature(gethostname)] use crate::commands::{ Command, CommandMap, Mode }; use crate::error::*; use std::io::Write; use std::process; use ::clap::Parser; mod commands; mod error; #[derive(Parser, Debug)] struct CommandLineParameters { flake_path: String, #[arg(long = "store")] nix_store_path: Option, } struct TopLevelMode { flake_path: String, configuration_name: String, command_map: Box>, } impl TopLevelMode { fn new(flake_path: String, configuration_name: String) -> Result { let mut command_map = Box::new(CommandMap::new()); command_map.insert(Command::new("help", |mode, _params| { println!("list List all defined secrets."); println!(""); println!("help Print this message."); println!("quit Exit the program."); Ok(Some(mode)) }))?; command_map.insert(Command::new("quit", |_mode, _params| { Ok(None) }))?; command_map.insert(Command::::new("list", |mode, _params| { let _secrets = read_secret_metadata(&mode.flake_path, &mode.configuration_name)?; // TODO do something with them Ok(Some(mode)) }))?; Ok(TopLevelMode { flake_path, configuration_name, command_map }) } } impl Mode for TopLevelMode { fn prompt_text(&self) -> Result<&str> { Ok("secrets>") } fn dispatch(self: Box, params: &[&str]) -> Result>> { if params.len() > 0 { if let Some(command) = self.command_map.get(params[0]) { (command.implementation)(self, ¶ms[1..]) } else { println!("No command named {}. Type help if you need help.", params[0]); Ok(Some(self)) } } else { default_empty_command()?; Ok(Some(self)) } } } fn default_empty_command() -> Result<()> { println!("No command? Type help if you need help."); Ok(()) } fn main() -> () { match main_h() { Ok(()) => { }, Err(error) => { println!("{}", error.to_string()); }, } } fn main_h() -> Result<()> { let options = CommandLineParameters::try_parse()?; println!("{:?}", options); let hostname: String = std::net::hostname()? .into_string().map_err(|_| Error::UTF8)?; let mut current_mode: Option> = Some(Box::new(TopLevelMode::new(options.flake_path.clone(), hostname)?)); while let Some(mode) = current_mode { if let Some(input) = prompt(mode.prompt_text()?)? { let words: Vec<&str> = input.split_whitespace().collect(); current_mode = mode.dispatch(&words[..])?; } else { current_mode = None; } } Ok(()) } fn prompt(raw: &str) -> Result> { let mut stdout = std::io::stdout(); stdout.write_all(format!("\n{} ", raw).as_bytes())?; stdout.flush()?; Ok(std::io::stdin().lines().next().transpose()?) } fn read_secret_metadata(flake_path: &str, configuration_name: &str) -> Result<()> { let mut nix_expression = String::new(); nix_expression.push_str("let config = (builtins.getFlake \""); nix_expression.push_str(flake_path); nix_expression.push_str("\")"); nix_expression.push_str(".nixosConfigurations."); nix_expression.push_str(configuration_name); nix_expression.push_str(".config; in "); nix_expression.push_str("if config ? \"secrets\" "); nix_expression.push_str("then config.secrets.export "); nix_expression.push_str("else { }"); let nix_output = process::Command::new("nix") .env_clear() .args([ "--extra-experimental-features", "nix-command", "eval", "--impure", "--json", "--expr", &nix_expression, ]) .output()?; if !nix_output.status.success() { println!("{}", String::from_utf8(nix_output.stderr)?); return Err(Error::Internal("nix subprocess failed".to_string())); } let nix_json = String::from_utf8(nix_output.stdout)?; println!("{}", nix_json); // TODO parse the JSON Ok(()) }