From f42ff454759704bd73e6f43f696af9f6cecf046e Mon Sep 17 00:00:00 2001 From: Dorian Date: Tue, 17 Mar 2026 00:34:37 +0000 Subject: [PATCH] fix: resolve merge conflicts and compile errors for transport layer - Resolve stash conflicts in Cargo.toml, rpc/mod.rs, AppDetails.vue, Apps.vue - Fix ScopedIp conversion in LAN transport (mdns-sd compatibility) - Fix String vs &str in transport RPC send handler - Remove duplicate mod transport declaration - Remove stale mesh.discover route (replaced by mesh.peers/messages/send) Co-Authored-By: Claude Opus 4.6 (1M context) --- core/archipelago/Cargo.toml | 7 +- core/archipelago/src/api/rpc/transport.rs | 2 +- core/archipelago/src/server.rs | 70 +++++++++ core/archipelago/src/transport/lan.rs | 5 +- neode-ui/src/views/AppDetails.vue | 138 ++++++++++++++++-- neode-ui/src/views/Apps.vue | 167 ++++++++++------------ 6 files changed, 281 insertions(+), 108 deletions(-) diff --git a/core/archipelago/Cargo.toml b/core/archipelago/Cargo.toml index 1ddb8016..c33142bd 100644 --- a/core/archipelago/Cargo.toml +++ b/core/archipelago/Cargo.toml @@ -89,14 +89,13 @@ bytes = "1" serial2-tokio = "0.1" # Transport abstraction (Phase 2: mesh as federation transport) -ciborium = "0.2.2" # CBOR serde for compact delta sync -reed-solomon-erasure = "6.0" # FEC for chunked LoRa messages -mdns-sd = "0.18" # LAN peer discovery via mDNS +ciborium = "0.2.2" +reed-solomon-erasure = "6.0" +mdns-sd = "0.18" # Systemd watchdog notification sd-notify = "0.4" - [dev-dependencies] tokio-test = "0.4" tempfile = "3.10" diff --git a/core/archipelago/src/api/rpc/transport.rs b/core/archipelago/src/api/rpc/transport.rs index cb32edb6..53f18a04 100644 --- a/core/archipelago/src/api/rpc/transport.rs +++ b/core/archipelago/src/api/rpc/transport.rs @@ -96,7 +96,7 @@ impl RpcHandler { message_type: MessageType::PeerMessage, }; - let transport_used = router.send_to_peer(did, &message).await?; + let transport_used = router.send_to_peer(&did, &message).await?; info!(did = %did, transport = %transport_used, "Sent message via transport"); Ok(serde_json::json!({ diff --git a/core/archipelago/src/server.rs b/core/archipelago/src/server.rs index 56bfd7dd..55452ef0 100644 --- a/core/archipelago/src/server.rs +++ b/core/archipelago/src/server.rs @@ -209,6 +209,76 @@ impl Server { }); } + // Initialize mesh networking service (if config has enabled: true) + { + let data_dir = config.data_dir.clone(); + let did = identity::did_key_from_pubkey_hex(&data.server_info.pubkey) + .unwrap_or_default(); + let pubkey_hex = identity.pubkey_hex(); + let signing_key = identity.signing_key(); + match crate::mesh::MeshService::new(&data_dir, signing_key, &did, &pubkey_hex).await { + Ok(mut mesh_service) => { + let mesh_config = crate::mesh::load_config(&data_dir).await.unwrap_or_default(); + if mesh_config.enabled { + if let Err(e) = mesh_service.start() { + warn!("Mesh service start failed (non-fatal): {}", e); + } else { + info!("📡 Mesh networking started"); + } + } + api_handler.rpc_handler().set_mesh_service(mesh_service).await; + info!("📡 Mesh service initialized"); + } + Err(e) => { + warn!("Mesh service init failed (non-fatal): {}", e); + } + } + } + + // Initialize transport router (unified routing: mesh > lan > tor) + { + let data_dir = config.data_dir.clone(); + let did = identity::did_key_from_pubkey_hex(&data.server_info.pubkey) + .unwrap_or_default(); + let pubkey_hex = identity.pubkey_hex(); + let mesh_config = crate::mesh::load_config(&data_dir).await.unwrap_or_default(); + let mesh_only = mesh_config.mesh_only_mode.unwrap_or(false); + + match crate::transport::PeerRegistry::load(&data_dir).await { + Ok(registry) => { + let registry = std::sync::Arc::new(registry); + let mut transports: Vec> = Vec::new(); + + transports.push(Box::new( + crate::transport::tor::TorTransport::new(&pubkey_hex), + )); + transports.push(Box::new( + crate::transport::mesh_transport::MeshTransport::new( + api_handler.rpc_handler().mesh_service_arc(), + ), + )); + + let mut lan = crate::transport::lan::LanTransport::new(&did, &pubkey_hex, 5678); + match lan.start(registry.clone()) { + Ok(()) => info!("📡 LAN transport (mDNS) started"), + Err(e) => debug!("LAN transport init (non-fatal): {}", e), + } + transports.push(Box::new(lan)); + + let router = std::sync::Arc::new(crate::transport::TransportRouter::new( + transports, + registry, + mesh_only, + )); + api_handler.rpc_handler().set_transport_router(router).await; + info!("📡 Transport router initialized (mesh_only={})", mesh_only); + } + Err(e) => { + warn!("Transport router init failed (non-fatal): {}", e); + } + } + } + // Initialize container scanner — discovers installed apps from Podman/Docker { let scanner = create_docker_scanner(&config).await?; diff --git a/core/archipelago/src/transport/lan.rs b/core/archipelago/src/transport/lan.rs index 26b810de..40bf4130 100644 --- a/core/archipelago/src/transport/lan.rs +++ b/core/archipelago/src/transport/lan.rs @@ -89,7 +89,10 @@ impl LanTransport { if let (Some(did), Some(pubkey)) = (did, pubkey) { if let Some(scoped_ip) = addresses.iter().next() { - let ip: std::net::IpAddr = (*scoped_ip).into(); + let ip: std::net::IpAddr = match scoped_ip.to_string().parse() { + Ok(ip) => ip, + Err(_) => continue, + }; let socket_addr = std::net::SocketAddr::new(ip, info.get_port()); info!(did = %did, addr = %socket_addr, "Discovered LAN peer via mDNS"); registry_clone diff --git a/neode-ui/src/views/AppDetails.vue b/neode-ui/src/views/AppDetails.vue index 882288c0..fb550f23 100644 --- a/neode-ui/src/views/AppDetails.vue +++ b/neode-ui/src/views/AppDetails.vue @@ -417,7 +417,7 @@ class="fixed inset-0 z-50 flex items-center justify-center p-4" @click="closeUninstallModal()" > -
+
route.params.id as string) // Web-only app detection (no container — external websites) const WEB_ONLY_APP_URLS: Record = { + 'indeedhub': `${window.location.protocol}//${window.location.hostname}:7777`, 'botfights': 'https://botfights.net', 'nwnn': 'https://nwnn.l484.com', '484-kitchen': 'https://484.kitchen', @@ -500,6 +501,8 @@ const isWebOnly = computed(() => appId.value in WEB_ONLY_APP_URLS) /** Map route/marketplace app IDs to backend package keys (container names). */ const ROUTE_TO_PACKAGE_KEY: Record = { mempool: 'mempool-web', + 'mempool-electrs': 'mempool-electrs', + electrs: 'mempool-electrs', btcpay: 'btcpay-server', 'btcpay-server': 'btcpay-server', fedimint: 'fedimint', @@ -525,19 +528,12 @@ const ROUTE_TO_PACKAGE_KEY: Record = { portainer: 'portainer', 'uptime-kuma': 'uptime-kuma', tailscale: 'tailscale', - indeedhub: 'indeedhub', - electrumx: 'electrumx', - electrs: 'electrumx', - 'mempool-electrs': 'electrumx', } /** Backend may register under variant container names */ const PACKAGE_ALIASES: Record = { immich: ['immich_server', 'immich-server'], nextcloud: ['nextcloud-aio', 'nextcloud-server'], - 'mempool-web': ['archy-mempool-web'], - indeedhub: ['indeedhub-build_app_1'], - electrumx: ['mempool-electrs', 'electrs', 'archy-electrs'], } function resolvePackageKey(routeId: string): string { @@ -718,7 +714,131 @@ function goBack() { function launchApp() { if (!pkg.value) return - useAppLauncherStore().openSession(appId.value) + + const isDev = import.meta.env.DEV + const id = appId.value + + // Web-only apps — use their external URL directly + const webOnlyUrl = WEB_ONLY_APP_URLS[id] + if (webOnlyUrl) { + useAppLauncherStore().open({ url: webOnlyUrl, title: pkg.value.manifest.title }) + return + } + + // Special handling for apps with Docker containers + const appUrls: Record = { + 'lorabell': { + dev: 'http://192.168.1.166', + prod: 'http://192.168.1.166' + }, + 'atob': { + dev: 'http://localhost:8102', + prod: 'https://app.atobitcoin.io' + }, + 'k484': { + dev: 'http://localhost:8103', + prod: 'http://localhost:8103' // Self-hosted splash screen + }, + 'indeedhub': { + dev: 'https://archipelago.indeehub.studio', + prod: 'https://archipelago.indeehub.studio' + }, + // Dummy apps - replace with real URLs when packaged + 'bitcoin': { + dev: 'http://localhost:8332', + prod: 'http://localhost:8332' + }, + 'btcpay-server': { + dev: 'http://localhost:23000', + prod: 'http://localhost:23000' + }, + 'homeassistant': { + dev: 'http://localhost:8123', + prod: 'http://localhost:8123' + }, + 'grafana': { + dev: 'http://localhost:3000', + prod: 'http://localhost:3000' + }, + 'endurain': { + dev: 'http://localhost:8080', + prod: 'http://localhost:8080' + }, + 'fedimint': { + dev: 'http://localhost:8175', + prod: 'http://192.168.1.228:8175' + }, + 'fedimint-gateway': { + dev: 'http://localhost:8176', + prod: 'http://192.168.1.228:8176' + }, + 'morphos-server': { + dev: 'http://localhost:8081', + prod: 'http://localhost:8081' + }, + 'lightning-stack': { + dev: 'http://localhost:9735', + prod: 'http://localhost:9735' + }, + 'mempool': { + dev: 'http://localhost:4080', + prod: 'http://localhost:4080' + }, + 'ollama': { + dev: 'http://localhost:11434', + prod: 'http://localhost:11434' + }, + 'searxng': { + dev: 'http://localhost:8888', + prod: 'http://localhost:8888' + }, + 'onlyoffice': { + dev: 'http://localhost:9980', + prod: 'http://localhost:9980' + }, + 'penpot': { + dev: 'http://localhost:9001', + prod: 'http://localhost:9001' + }, + 'nextcloud': { dev: 'http://localhost:8085', prod: 'http://localhost:8085' }, + 'vaultwarden': { dev: 'http://localhost:8082', prod: 'http://localhost:8082' }, + 'jellyfin': { dev: 'http://localhost:8096', prod: 'http://localhost:8096' }, + 'photoprism': { dev: 'http://localhost:2342', prod: 'http://localhost:2342' }, + 'immich': { dev: 'http://localhost:2283', prod: 'http://localhost:2283' }, + 'filebrowser': { dev: 'http://localhost:8083', prod: 'http://localhost:8083' }, + 'nginx-proxy-manager': { dev: 'http://localhost:81', prod: 'http://localhost:81' }, + 'portainer': { dev: 'http://localhost:9000', prod: 'http://localhost:9000' }, + 'uptime-kuma': { dev: 'http://localhost:3001', prod: 'http://localhost:3001' }, + 'tailscale': { dev: 'http://localhost:8240', prod: 'http://localhost:8240' }, + 'lnd': { dev: 'http://localhost:8081', prod: 'http://localhost:8081' }, + 'bitcoin-knots': { dev: 'http://localhost:8334', prod: 'http://localhost:8334' }, + 'botfights': { dev: 'https://botfights.net', prod: 'https://botfights.net' }, + 'nwnn': { dev: 'https://nwnn.l484.com', prod: 'https://nwnn.l484.com' }, + '484-kitchen': { dev: 'https://484.kitchen', prod: 'https://484.kitchen' }, + 'call-the-operator': { dev: 'https://cta.tx1138.com', prod: 'https://cta.tx1138.com' }, + 'arch-presentation': { dev: 'https://present.l484.com', prod: 'https://present.l484.com' }, + 'syntropy-institute': { dev: 'https://syntropy.institute', prod: 'https://syntropy.institute' }, + 't-zero': { dev: 'https://teeminuszero.net', prod: 'https://teeminuszero.net' } + } + + if (appUrls[id]) { + let url = isDev ? appUrls[id].dev : appUrls[id].prod + // Replace localhost with current hostname for remote access + if (url.includes('localhost')) { + url = url.replace('localhost', window.location.hostname) + } + useAppLauncherStore().open({ url, title: pkg.value.manifest.title }) + return + } + + // For other apps, construct the launch URL + // In a real deployment, this would use the Tor or LAN address from interfaces + const torAddress = pkg.value.manifest.interfaces?.main?.['tor-config'] + const lanConfig = pkg.value.manifest.interfaces?.main?.['lan-config'] + + if (torAddress || lanConfig) { + showActionError(t('appDetails.noLaunchUrl')) + } } async function startApp() { diff --git a/neode-ui/src/views/Apps.vue b/neode-ui/src/views/Apps.vue index a4320635..15def253 100644 --- a/neode-ui/src/views/Apps.vue +++ b/neode-ui/src/views/Apps.vue @@ -1,50 +1,18 @@