use super::RpcHandler; use crate::{backup, identity, nostr_discovery}; use crate::container::docker_packages; use anyhow::Result; impl RpcHandler { pub(super) async fn handle_node_did(&self) -> Result { let (data, _) = self.state_manager.get_snapshot().await; let did = identity::did_key_from_pubkey_hex(&data.server_info.pubkey)?; Ok(serde_json::json!({ "did": did, "pubkey": data.server_info.pubkey })) } /// Sign a challenge to prove control of the node DID (proof-of-control for onboarding). pub(super) async fn handle_node_sign_challenge( &self, params: Option, ) -> Result { let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?; let challenge = params .get("challenge") .and_then(|v| v.as_str()) .ok_or_else(|| anyhow::anyhow!("Missing challenge string"))?; let identity_dir = self.config.data_dir.join("identity"); let identity = identity::NodeIdentity::load_or_create(&identity_dir).await?; let signature = identity.sign(challenge.as_bytes()); Ok(serde_json::json!({ "signature": signature })) } /// Create an encrypted backup of the node identity (for onboarding). pub(super) async fn handle_node_create_backup( &self, params: Option, ) -> Result { let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?; let passphrase = params .get("passphrase") .and_then(|v| v.as_str()) .ok_or_else(|| anyhow::anyhow!("Missing passphrase"))?; let (data, _) = self.state_manager.get_snapshot().await; let did = identity::did_key_from_pubkey_hex(&data.server_info.pubkey)?; let identity_dir = self.config.data_dir.join("identity"); let backup = backup::create_encrypted_backup( &identity_dir, passphrase, &did, &data.server_info.pubkey, ) .await?; Ok(backup) } pub(super) async fn handle_node_tor_address(&self) -> Result { let tor_address = docker_packages::read_tor_address("archipelago"); Ok(serde_json::json!({ "tor_address": tor_address })) } pub(super) async fn handle_node_nostr_publish(&self) -> Result { if !self.config.nostr_discovery_enabled || self.config.nostr_relays.is_empty() { anyhow::bail!( "Nostr discovery disabled. Set ARCHIPELAGO_NOSTR_DISCOVERY_ENABLED=true and ARCHIPELAGO_NOSTR_RELAYS=wss://... to enable." ); } let (data, _) = self.state_manager.get_snapshot().await; let did = identity::did_key_from_pubkey_hex(&data.server_info.pubkey)?; let node_address = data .server_info .node_address .as_deref() .unwrap_or("archipelago://unknown"); let identity_dir = self.config.data_dir.join("identity"); let output = nostr_discovery::publish_node_identity( &identity_dir, &did, node_address, &data.server_info.version, &self.config.nostr_relays, self.config.nostr_tor_proxy.as_deref(), ) .await?; Ok(serde_json::json!({ "event_id": output.id().to_hex(), "success": output.success.len(), "failed": output.failed.len(), })) } pub(super) async fn handle_node_nostr_pubkey(&self) -> Result { let identity_dir = self.config.data_dir.join("identity"); let pubkey = nostr_discovery::get_nostr_pubkey(&identity_dir).await?; Ok(serde_json::json!({ "nostr_pubkey": pubkey })) } pub(super) async fn handle_node_nostr_verify_revoked(&self) -> Result { let identity_dir = self.config.data_dir.join("identity"); let status = nostr_discovery::verify_revocation( &identity_dir, self.config.nostr_tor_proxy.as_deref(), ) .await?; Ok(serde_json::json!({ "revoked": status.revoked, "nostr_pubkey": status.nostr_pubkey, "latest_content": status.latest_content, "error": status.error, })) } }