1use std::str::FromStr;23use crate::host::Config;4use age::Recipient;5use anyhow::{anyhow, Result};6use futures::{StreamExt, TryStreamExt};7use itertools::Itertools;8use tracing::warn;910impl Config {11 pub fn cached_key(&self, host: &str) -> Option<String> {12 let data = self.data();13 let key = data.hosts.get(host).map(|h| &h.encryption_key);14 if let Some(key) = key {15 if key.is_empty() {16 return None;17 }18 }19 key.cloned()20 }21 pub fn update_key(&self, host: &str, key: String) {22 let mut data = self.data_mut();23 let host = data.hosts.entry(host.to_string()).or_default();24 host.encryption_key = key.trim().to_string();25 }2627 pub async fn key(&self, host: &str) -> anyhow::Result<String> {28 if let Some(key) = self.cached_key(host) {29 Ok(key)30 } else {31 warn!("Loading key for {}", host);32 let host = self.host(host).await?;33 let mut cmd = host.cmd("cat").await?;34 cmd.arg("/etc/ssh/ssh_host_ed25519_key.pub");35 let key = cmd.run_string().await?;36 self.update_key(&host.name, key.clone());37 Ok(key)38 }39 }40 41 pub async fn recipient(&self, host: &str) -> anyhow::Result<impl Recipient> {42 let key = self.key(host).await?;43 age::ssh::Recipient::from_str(&key).map_err(|e| anyhow!("parse recipient error: {:?}", e))44 }4546 pub async fn recipients(&self, hosts: Vec<String>) -> Result<Vec<impl Recipient>> {47 futures::stream::iter(hosts.iter())48 .then(|m| self.recipient(m.as_ref()))49 .try_collect::<Vec<_>>()50 .await51 }5253 #[allow(dead_code)]54 pub async fn orphaned_data(&self) -> Result<Vec<String>> {55 let mut out = Vec::new();56 let host_names = self57 .list_hosts()58 .await?59 .into_iter()60 .map(|h| h.name)61 .collect_vec();62 for hostname in self63 .data()64 .hosts65 .iter()66 .filter(|(_, host)| !host.encryption_key.is_empty())67 .map(|(n, _)| n)68 {69 if !host_names.contains(hostname) {70 out.push(hostname.to_owned())71 }72 }7374 Ok(out)75 }76}