1use std::str::FromStr;23use crate::{command::CommandExt, host::Config};4use anyhow::{anyhow, Result};5use tracing::warn;67impl Config {8 pub fn cached_key(&self, host: &str) -> Option<String> {9 let data = self.data();10 let key = data.hosts.get(host).map(|h| &h.encryption_key);11 if let Some(key) = key {12 if key.is_empty() {13 return None;14 }15 }16 key.cloned()17 }18 pub fn update_key(&self, host: &str, key: String) {19 let mut data = self.data_mut();20 let host = data.hosts.entry(host.to_string()).or_default();21 host.encryption_key = key.trim().to_string();22 }2324 pub async fn key(&self, host: &str) -> anyhow::Result<String> {25 if let Some(key) = self.cached_key(host) {26 Ok(key)27 } else {28 warn!("Loading key for {}", host);29 let key = self30 .command_on(host, "cat", false)31 .arg("/etc/ssh/ssh_host_ed25519_key.pub")32 .run_string()33 .await?;34 self.update_key(host, key.clone());35 Ok(key)36 }37 }38 39 pub async fn identity(&self, host: &str) -> anyhow::Result<age::ssh::Identity> {40 warn!("Loading private key for {host}");41 let key = self42 .command_on(host, "cat", true)43 .arg("/etc/ssh/ssh_host_ed25519_key")44 .run_string()45 .await?;46 Ok(age::ssh::Identity::from_buffer(key.as_bytes(), None)?)47 }48 pub async fn recipient(&self, host: &str) -> anyhow::Result<age::ssh::Recipient> {49 let key = self.key(host).await?;50 age::ssh::Recipient::from_str(&key).map_err(|e| anyhow!("parse recipient error: {:?}", e))51 }5253 pub async fn orphaned_data(&self) -> Result<Vec<String>> {54 let mut out = Vec::new();55 let host_names = self.list_hosts().await?;56 for hostname in self57 .data()58 .hosts59 .iter()60 .filter(|(_, host)| !host.encryption_key.is_empty())61 .map(|(n, _)| n)62 {63 if !host_names.contains(hostname) {64 out.push(hostname.to_owned())65 }66 }6768 Ok(out)69 }70}