1use std::str::FromStr as _;23use age::Recipient;4use anyhow::{Result, anyhow};5use futures::{StreamExt as _, TryStreamExt as _};6use itertools::Itertools as _;7use tracing::warn;89use crate::host::Config;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 host = self.host(host).await?;34 let mut cmd = host.cmd("cat").await?;35 cmd.arg("/etc/ssh/ssh_host_ed25519_key.pub");36 let key = cmd.run_string().await?;37 self.update_key(&host.name, key.clone());38 Ok(key)39 }40 }41 42 pub async fn recipient(&self, host: &str) -> anyhow::Result<impl Recipient + use<>> {43 let key = self.key(host).await?;44 age::ssh::Recipient::from_str(&key).map_err(|e| anyhow!("parse recipient error: {:?}", e))45 }4647 pub async fn recipients(&self, hosts: Vec<String>) -> Result<Vec<impl Recipient + use<>>> {48 let hosts = self.expand_owner_set(hosts).await?;49 futures::stream::iter(hosts.iter())50 .then(|m| self.recipient(m.as_ref()))51 .try_collect::<Vec<_>>()52 .await53 }5455 #[allow(dead_code)]56 pub async fn orphaned_data(&self) -> Result<Vec<String>> {57 let mut out = Vec::new();58 let host_names = self59 .list_hosts()60 .await?61 .into_iter()62 .map(|h| h.name)63 .collect_vec();64 for hostname in self65 .data()66 .hosts67 .iter()68 .filter(|(_, host)| !host.encryption_key.is_empty())69 .map(|(n, _)| n)70 {71 if !host_names.contains(hostname) {72 out.push(hostname.to_owned())73 }74 }7576 Ok(out)77 }78}