1use std::str::FromStr;23use crate::command::MyCommand;4use crate::host::Config;5use anyhow::{anyhow, Result};6use itertools::Itertools;7use tracing::warn;89impl Config {10 pub fn cached_key(&self, host: &str) -> Option<String> {11 let data = self.data();12 let key = data.hosts.get(host).map(|h| &h.encryption_key);13 if let Some(key) = key {14 if key.is_empty() {15 return None;16 }17 }18 key.cloned()19 }20 pub fn update_key(&self, host: &str, key: String) {21 let mut data = self.data_mut();22 let host = data.hosts.entry(host.to_string()).or_default();23 host.encryption_key = key.trim().to_string();24 }2526 pub async fn key(&self, host: &str) -> anyhow::Result<String> {27 if let Some(key) = self.cached_key(host) {28 Ok(key)29 } else {30 warn!("Loading key for {}", host);31 let mut cmd = MyCommand::new("cat");32 cmd.arg("/etc/ssh/ssh_host_ed25519_key.pub");33 let key = self.run_string_on(host, cmd, false).await?;34 self.update_key(host, key.clone());35 Ok(key)36 }37 }38 39 pub async fn recipient(&self, host: &str) -> anyhow::Result<age::ssh::Recipient> {40 let key = self.key(host).await?;41 age::ssh::Recipient::from_str(&key).map_err(|e| anyhow!("parse recipient error: {:?}", e))42 }4344 #[allow(dead_code)]45 pub async fn orphaned_data(&self) -> Result<Vec<String>> {46 let mut out = Vec::new();47 let host_names = self48 .list_hosts()49 .await?50 .into_iter()51 .map(|h| h.name)52 .collect_vec();53 for hostname in self54 .data()55 .hosts56 .iter()57 .filter(|(_, host)| !host.encryption_key.is_empty())58 .map(|(n, _)| n)59 {60 if !host_names.contains(hostname) {61 out.push(hostname.to_owned())62 }63 }6465 Ok(out)66 }67}