From 46af0176dff907fbd4973beaff39554829a00468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Pacheco?= Date: Wed, 26 Aug 2026 08:23:22 -0500 Subject: [PATCH] feat(relay): make NIP-11 name and description configurable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A self-hosted relay is a distinct workspace to the people using it, but every instance introduces itself as "Buzz Relay" — the product's name, not the operator's. Clients surface this string, so an operator has no way to make their own deployment read as theirs. The Desktop's community Name field only writes to local storage, so it does not close the gap either. Adds RELAY_NAME and RELAY_DESCRIPTION, defaulting to the current strings so no existing deployment moves. Blank or whitespace-only values fall back to the default rather than advertising an empty name. Test covers unset, set-and-trimmed, and blank. Verified to have teeth by mutating the fix back to the hardcoded string and confirming it fails. Signed-off-by: André Pacheco --- crates/buzz-relay/src/nip11.rs | 58 ++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/crates/buzz-relay/src/nip11.rs b/crates/buzz-relay/src/nip11.rs index 7bcebf1ef33..c386472d320 100644 --- a/crates/buzz-relay/src/nip11.rs +++ b/crates/buzz-relay/src/nip11.rs @@ -134,6 +134,31 @@ fn relay_limitation(max_message_length: usize) -> RelayLimitation { } } +/// Operator-facing relay name advertised in NIP-11, from `RELAY_NAME`. +/// +/// Self-hosted deployments are a distinct workspace to the people in them, but +/// every relay currently introduces itself as "Buzz Relay" — the product's +/// name, not the operator's. Clients surface this string, so an operator has no +/// way to make their own instance read as theirs. Blank or whitespace-only +/// values fall back to the default rather than advertising an empty name. +fn relay_display_name() -> String { + non_empty_env("RELAY_NAME").unwrap_or_else(|| "Buzz Relay".to_string()) +} + +/// Relay description advertised in NIP-11, from `RELAY_DESCRIPTION`. +fn relay_description() -> String { + non_empty_env("RELAY_DESCRIPTION") + .unwrap_or_else(|| "Buzz — private team communication relay".to_string()) +} + +/// Reads an env var, treating unset and blank as equivalent. +fn non_empty_env(key: &str) -> Option { + std::env::var(key) + .ok() + .map(|v| v.trim().to_string()) + .filter(|v| !v.is_empty()) +} + impl RelayInfo { /// Builds the relay's NIP-11 information document. /// @@ -187,8 +212,8 @@ impl RelayInfo { }); Self { - name: "Buzz Relay".to_string(), - description: "Buzz — private team communication relay".to_string(), + name: relay_display_name(), + description: relay_description(), icon: icon.filter(|s| !s.is_empty()).map(|s| s.to_string()), pubkey: None, contact: None, @@ -378,6 +403,35 @@ const _RELAY_INFO_BUILD_STATIC_INPUT_FENCE: fn( mod tests { use super::*; + static NAME_ENV_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + #[test] + fn relay_name_and_description_come_from_env_and_ignore_blanks() { + let _guard = NAME_ENV_MUTEX.lock().unwrap(); + + // Unset: the historical defaults, so existing deployments do not move. + std::env::remove_var("RELAY_NAME"); + std::env::remove_var("RELAY_DESCRIPTION"); + assert_eq!(relay_display_name(), "Buzz Relay"); + assert_eq!( + relay_description(), + "Buzz — private team communication relay" + ); + + // Set: the operator's own identity, trimmed. + std::env::set_var("RELAY_NAME", " Cofoundy "); + std::env::set_var("RELAY_DESCRIPTION", "Cofoundy team relay"); + assert_eq!(relay_display_name(), "Cofoundy"); + assert_eq!(relay_description(), "Cofoundy team relay"); + + // Blank is not a name — advertising "" would be worse than the default. + std::env::set_var("RELAY_NAME", " "); + assert_eq!(relay_display_name(), "Buzz Relay"); + + std::env::remove_var("RELAY_NAME"); + std::env::remove_var("RELAY_DESCRIPTION"); + } + #[test] fn push_descriptor_is_gated_by_gateway_configuration_and_tenant_binding() { let keys = nostr::Keys::generate();