feat(mesh): server name in adverts + clear-all button + CI fix

- Mesh adverts now use the node's configured server name (e.g. "ThinkPad",
  "Arch Dev") instead of DID key fragments ("Archy-z6MkmkSB")
- Added mesh.clear-all RPC to reset peers, messages, contacts, and history
- Added "Clear All" button in Mesh UI peers panel
- Both glibc and musl builds verified

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-04-18 11:53:06 -04:00
co-authored by Claude Opus 4.6
parent 0c02d06a66
commit 1736f6f99e
7 changed files with 86 additions and 6 deletions
+6 -2
View File
@@ -59,6 +59,8 @@ pub enum MeshCommand {
/// Broadcast pre-encoded binary on a mesh channel.
BroadcastChannel { channel: u8, payload: Vec<u8> },
SendAdvert,
/// Re-fetch contact list from the radio device.
RefreshContacts,
}
/// Shared state for the mesh listener, accessible from RPC handlers.
@@ -86,7 +88,7 @@ pub struct MeshState {
/// Steganography mode for outgoing/incoming messages.
pub stego_mode: super::steganography::SteganographyMode,
/// Chunk reassembly buffer for multi-frame messages.
chunk_buffer: RwLock<HashMap<(u32, u8), ChunkAssembly>>,
pub(crate) chunk_buffer: RwLock<HashMap<(u32, u8), ChunkAssembly>>,
/// Double Ratchet session manager for forward-secret encryption.
pub session_manager: Arc<super::session::SessionManager>,
/// Whether to encrypt directed relay messages (config toggle for rollback).
@@ -121,7 +123,7 @@ pub struct ContactEntry {
}
/// In-progress chunk reassembly for a multi-frame message.
struct ChunkAssembly {
pub(crate) struct ChunkAssembly {
chunks: HashMap<u8, String>,
total: u8,
created: std::time::Instant,
@@ -255,6 +257,7 @@ pub fn spawn_mesh_listener(
our_ed_pubkey_hex: String,
our_x25519_secret: [u8; 32],
our_x25519_pubkey_hex: String,
server_name: Option<String>,
shutdown: tokio::sync::watch::Receiver<bool>,
cmd_rx: mpsc::Receiver<MeshCommand>,
) -> tokio::task::JoinHandle<()> {
@@ -275,6 +278,7 @@ pub fn spawn_mesh_listener(
&our_ed_pubkey_hex,
&our_x25519_secret,
&our_x25519_pubkey_hex,
server_name.as_deref(),
&mut shutdown,
&mut cmd_rx,
)
+13 -3
View File
@@ -249,6 +249,7 @@ pub(super) async fn run_mesh_session(
_our_ed_pubkey_hex: &str,
our_x25519_secret: &[u8; 32],
_our_x25519_pubkey_hex: &str,
server_name: Option<&str>,
shutdown: &mut tokio::sync::watch::Receiver<bool>,
cmd_rx: &mut mpsc::Receiver<MeshCommand>,
) -> Result<()> {
@@ -284,9 +285,15 @@ pub(super) async fn run_mesh_session(
let _ = state.event_tx.send(MeshEvent::DeviceConnected(device_info));
// Set advert name to something identifiable
let short_did = our_did.chars().skip(8).take(8).collect::<String>();
let advert_name = format!("Archy-{}", short_did);
// Set advert name to the server's human-readable name (e.g. "ThinkPad"),
// falling back to the DID fragment if no name is configured.
let advert_name = if let Some(name) = server_name {
// Meshcore firmware limits advert names — truncate to 20 chars
name.chars().take(20).collect::<String>()
} else {
let short_did = our_did.chars().skip(8).take(8).collect::<String>();
format!("Archy-{}", short_did)
};
if let Err(e) = device.set_advert_name(&advert_name).await {
warn!("Failed to set advert name: {}", e);
} else {
@@ -440,5 +447,8 @@ async fn handle_send_command(
*consecutive_write_failures = 0;
}
}
MeshCommand::RefreshContacts => {
refresh_contacts(device, state).await;
}
}
}
+14
View File
@@ -204,6 +204,8 @@ pub struct MeshService {
our_x25519_secret: [u8; 32],
our_x25519_pubkey_hex: String,
signing_key: SigningKey,
/// Human-readable server name (e.g. "Arch Dev", "ThinkPad") for mesh adverts.
server_name: Option<String>,
// Phase 4: off-grid Bitcoin operations
pub block_header_cache: Arc<BlockHeaderCache>,
pub relay_tracker: Arc<RelayTracker>,
@@ -277,12 +279,18 @@ impl MeshService {
our_x25519_secret: x25519_secret,
our_x25519_pubkey_hex: x25519_pubkey_hex,
signing_key: signing_key.clone(),
server_name: None,
block_header_cache,
relay_tracker,
dead_man_switch,
})
}
/// Set the human-readable server name used in mesh adverts.
pub fn set_server_name(&mut self, name: Option<String>) {
self.server_name = name;
}
/// Start the background mesh listener.
pub fn start(&mut self) -> Result<()> {
if self.listener_handle.is_some() {
@@ -302,6 +310,7 @@ impl MeshService {
self.our_ed_pubkey_hex.clone(),
self.our_x25519_secret,
self.our_x25519_pubkey_hex.clone(),
self.server_name.clone(),
shutdown_rx,
cmd_rx,
);
@@ -518,6 +527,11 @@ impl MeshService {
self.state.status.read().await.clone()
}
/// Get a reference to the shared mesh state.
pub fn state(&self) -> &Arc<listener::MeshState> {
&self.state
}
/// Get list of discovered peers.
pub async fn peers(&self) -> Vec<MeshPeer> {
self.state.peers.read().await.values().cloned().collect()