1use std::str::FromStr;23use crate::command::MyCommand;4use crate::host::Config;5use age::Recipient;6use anyhow::{anyhow, Result};7use futures::{StreamExt, TryStreamExt};8use itertools::Itertools;9use tracing::warn;1011impl Config {12 pub fn cached_key(&self, host: &str) -> Option<String> {13 let data = self.data();14 let key = data.hosts.get(host).map(|h| &h.encryption_key);15 if let Some(key) = key {16 if key.is_empty() {17 return None;18 }19 }20 key.cloned()21 }22 pub fn update_key(&self, host: &str, key: String) {23 let mut data = self.data_mut();24 let host = data.hosts.entry(host.to_string()).or_default();25 host.encryption_key = key.trim().to_string();26 }2728 pub async fn key(&self, host: &str) -> anyhow::Result<String> {29 if let Some(key) = self.cached_key(host) {30 Ok(key)31 } else {32 warn!("Loading key for {}", host);33 let mut cmd = MyCommand::new("cat");34 cmd.arg("/etc/ssh/ssh_host_ed25519_key.pub");35 let key = self.run_string_on(host, cmd, false).await?;36 self.update_key(host, 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: &[&str]) -> Result<Vec<impl Recipient>> {47 futures::stream::iter(hosts.iter())48 .then(|m| self.recipient(m))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}