feat: v1.2.0-alpha — E2E encrypted mesh relay, steganography, relay status polling
Phase 5 mesh networking: - E2E encrypted TX relay (X25519 + ChaCha20-Poly1305) — non-Archy nodes relay encrypted blobs transparently via Meshcore native routing - Steganographic encoding modes (WeatherStation, SensorNetwork) — traffic looks like sensor data on the wire, 0xAA marker, configurable per-node - Pre-flight Bitcoin Core health check on relay node — specific error codes (bitcoin_unreachable, bitcoin_syncing, tx_rejected) instead of generic fails - mesh.relay-status RPC endpoint — frontend polls for relay result every 3s - On-Chain / Lightning tabs in Off-Grid Bitcoin panel - Archy Peers vs Mesh Broadcast relay mode selector - Mesh view fills viewport (no page scroll), internal panel scrolling - Version bump to 1.2.0-alpha Also includes: deploy hardening, container fixes, IndeedHub updates, boot screen, dashboard improvements, MASTER_PLAN task tracking Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,33 @@
|
||||
# Archipelago Project Memory Index
|
||||
|
||||
- [pending-features.md](pending-features.md) — Feature requests: kiosk mode, sideloading, Nostr login, etc.
|
||||
## Setup & Architecture
|
||||
- [claude-proxy-setup.md](claude-proxy-setup.md) — Claude proxy OAuth setup details
|
||||
- [deploy-automation.md](deploy-automation.md) — Deploy script automation TODOs (API key, AIUI nginx, swap)
|
||||
|
||||
## Servers & Deploy
|
||||
- [tailscale_servers.md](tailscale_servers.md) — Tailscale server details (archipelago-2, archipelago-3)
|
||||
- [reference_tailscale_nodes.md](reference_tailscale_nodes.md) — All node IPs and SSH commands
|
||||
- [second-server.md](second-server.md) — Second dev server (archipelago-2 via Tailscale)
|
||||
- [third-server.md](third-server.md) — Third dev server (archipelago-3 via Tailscale)
|
||||
- [deploy-automation.md](deploy-automation.md) — Deploy script automation TODOs
|
||||
- [claude-proxy-setup.md](claude-proxy-setup.md) — Claude proxy OAuth setup details
|
||||
|
||||
## Features & Plans
|
||||
- [pending-features.md](pending-features.md) — Feature requests: kiosk mode, sideloading, Nostr login, etc.
|
||||
- [project-plan.md](project-plan.md) — Overall project plan status
|
||||
- [web-only-apps.md](web-only-apps.md) — Web-only apps (L484 category) and iframe compatibility
|
||||
|
||||
## User Feedback
|
||||
- [feedback_app_display_modes.md](feedback_app_display_modes.md) — App browser: 3 display modes with persistent setting
|
||||
- [feedback_fullscreen_modals.md](feedback_fullscreen_modals.md) — Fullscreen modal preferences
|
||||
- [feedback_local_dev.md](feedback_local_dev.md) — Local dev: use `cd neode-ui && ./start-dev.sh`
|
||||
- [feedback_apps_always_direct_port.md](feedback_apps_always_direct_port.md) — Apps MUST open at direct port, NEVER proxy paths
|
||||
- [feedback_indeedhub_nginx_ips.md](feedback_indeedhub_nginx_ips.md) — IndeedHub nginx must use hardcoded container IPs
|
||||
- [feedback_searxng_no_cap_drop.md](feedback_searxng_no_cap_drop.md) — SearXNG: no cap-drop ALL
|
||||
|
||||
## ISO Build
|
||||
- [iso-build-session-2026-03-10.md](iso-build-session-2026-03-10.md) — ISO build session notes
|
||||
- [unbundled-iso.md](unbundled-iso.md) — Unbundled ISO approach notes
|
||||
- [web-only-apps.md](web-only-apps.md) — Web-only apps (L484 category) and iframe compatibility
|
||||
- [feedback_app_display_modes.md](feedback_app_display_modes.md) — App browser: 3 display modes (right panel, full overlay, fullscreen) with persistent setting
|
||||
|
||||
## Completed Work
|
||||
- [project_mesh_198_issue.md](project_mesh_198_issue.md) — Mesh .198: 3 bugs fixed and deployed
|
||||
- [project_indeedhub_arch3_fix.md](project_indeedhub_arch3_fix.md) — IndeedHub Arch 3: corrupted combined tarball fixed
|
||||
- [project_demo_deploy.md](project_demo_deploy.md) — Demo prod deployment via Portainer
|
||||
|
||||
35
.claude/memory/feedback_apps_always_direct_port.md
Normal file
35
.claude/memory/feedback_apps_always_direct_port.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: Apps MUST open at direct port — NEVER proxy paths
|
||||
description: CRITICAL — All apps in iframes must open at their direct port (http(s)://{host}:{port}), NEVER through /app/{id}/ proxy paths. This is the #1 cause of broken app loading across all nodes.
|
||||
type: feedback
|
||||
---
|
||||
|
||||
## CRITICAL RULE: Apps load at DIRECT PORT, never proxy paths
|
||||
|
||||
All Archipelago apps that open in iframes MUST use the direct port URL:
|
||||
```
|
||||
{protocol}://{hostname}:{port}
|
||||
```
|
||||
|
||||
**NEVER** use path-based proxy URLs like `/app/indeedhub/` or `/app/mempool/` for iframe loading. Path proxies break apps because:
|
||||
1. The main nginx SPA catch-all serves the Archipelago dashboard instead of the app
|
||||
2. sub_filter URL rewrites break client-side routing in Vue/React apps
|
||||
3. Different nodes have different nginx configs — path proxies are unreliable
|
||||
|
||||
**Why:** This was broken THREE TIMES in one session (2026-03-17). Every time the iframe URL used a proxy path instead of the direct port, the app showed the Archipelago dashboard or a blank page. .228 and .198 work correctly because they use HTTP which naturally hits the direct port. Tailscale nodes use HTTPS which was falling through to the proxy path.
|
||||
|
||||
**How to apply:**
|
||||
- In `AppSession.vue`, apps like IndeedHub must ALWAYS construct `{protocol}://{hostname}:{port}` — even on HTTPS
|
||||
- The `HTTPS_PROXY_PATHS` mapping should NOT include apps that have X-Frame-Options removed (like IndeedHub)
|
||||
- When adding new apps: use PORT_APPS for the port mapping, do NOT add to HTTPS_PROXY_PATHS unless absolutely necessary
|
||||
- The deploy script removes X-Frame-Options from IndeedHub's internal nginx, enabling direct port iframe access
|
||||
|
||||
**Also critical for IndeedHub specifically:**
|
||||
- IndeedHub nginx MUST use hardcoded container IPs (not DNS names) — see feedback_indeedhub_nginx_ips.md
|
||||
- nostr-provider.js must be injected via sub_filter in the IndeedHub internal nginx
|
||||
- SearXNG must NOT use --cap-drop ALL — see feedback_searxng_no_cap_drop.md
|
||||
|
||||
**When recreating containers:**
|
||||
- NEVER recreate containers without reapplying ALL patches (X-Frame-Options removal, nostr-provider injection, IP hardcoding)
|
||||
- After any container IP change (restart, recreation), update the hardcoded IPs in IndeedHub's nginx config
|
||||
- Deploy the SAME frontend build to ALL nodes — version mismatch causes different behavior
|
||||
17
.claude/memory/feedback_indeedhub_nginx_ips.md
Normal file
17
.claude/memory/feedback_indeedhub_nginx_ips.md
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: IndeedHub nginx must use hardcoded container IPs
|
||||
description: IndeedHub's internal nginx config MUST use hardcoded container IPs, not DNS names — podman DNS resolver 127.0.0.11 is unreliable and causes 502 errors
|
||||
type: feedback
|
||||
---
|
||||
|
||||
IndeedHub's internal nginx config MUST use hardcoded container IPs (e.g., `http://10.89.2.11:4000`) for API, MinIO, and relay upstreams — NEVER DNS names like `http://api:4000` with `resolver 127.0.0.11`.
|
||||
|
||||
**Why:** Podman's embedded DNS resolver (127.0.0.11) is unreliable inside containers. Using DNS names with `resolver 127.0.0.11 valid=30s ipv6=off;` causes nginx to return 502 Bad Gateway errors. This broke IndeedHub on all Tailscale nodes — frontend loaded but API calls failed.
|
||||
|
||||
**How to apply:** After creating/recreating IndeedHub containers, the deploy script must:
|
||||
1. Look up container IPs: `podman inspect indeedhub-build_api_1 --format "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}"`
|
||||
2. Patch the nginx config inside the container to replace DNS names with IPs
|
||||
3. Also patch `$host` → `$http_host` in proxy_set_header Host directives
|
||||
4. Reload nginx inside the container
|
||||
|
||||
This is now handled in `deploy-to-target.sh` and `fix-indeedhub-containers.sh`.
|
||||
15
.claude/memory/feedback_searxng_no_cap_drop.md
Normal file
15
.claude/memory/feedback_searxng_no_cap_drop.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: SearXNG must NOT use --cap-drop ALL
|
||||
description: SearXNG container needs write access to /etc/searxng/ for settings.yml — cap-drop ALL causes Permission denied and exit 127
|
||||
type: feedback
|
||||
---
|
||||
|
||||
Do NOT use `--cap-drop ALL` or `--security-opt no-new-privileges:true` when creating the SearXNG container. SearXNG needs to create `/etc/searxng/settings.yml` on first run.
|
||||
|
||||
**Why:** SearXNG's entrypoint creates a settings file from a template. With `--cap-drop ALL`, it gets "Permission denied: can't create '/etc/searxng/settings.yml'" and exits with code 127. The .228 reference server runs SearXNG with default capabilities (only drops CAP_AUDIT_WRITE, CAP_MKNOD, CAP_NET_RAW).
|
||||
|
||||
**How to apply:** When creating SearXNG containers, use:
|
||||
```bash
|
||||
sudo podman run -d --name searxng --restart unless-stopped -p 8888:8080 docker.io/searxng/searxng:latest
|
||||
```
|
||||
No `--cap-drop ALL`, no `--security-opt no-new-privileges:true`.
|
||||
62
.claude/memory/project_demo_deploy.md
Normal file
62
.claude/memory/project_demo_deploy.md
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
name: Demo Deploy Status
|
||||
description: Status and details of the demo prod server deployment via Portainer Stacks from Gitea repos
|
||||
type: project
|
||||
---
|
||||
|
||||
## Demo Prod Deployment — In Progress (2026-03-17)
|
||||
|
||||
### Two Separate Portainer Stacks
|
||||
|
||||
**1. IndeedHub** — DEPLOYED SUCCESSFULLY on :7755
|
||||
- Repo: `https://git.tx1138.com/lfg2025/indee-demo`
|
||||
- Compose: `docker-compose.yml` (root)
|
||||
- Env vars loaded from `.env.portainer` — update DOMAIN, FRONTEND_URL, S3_PUBLIC_BUCKET_URL
|
||||
- APP_PORT defaulted to 7755 (changed from 7777 to avoid conflicts)
|
||||
- Healthcheck fix: pg_isready uses `${POSTGRES_USER}` env var (was hardcoded)
|
||||
- Full 7-service stack: app, api, postgres, redis, minio, minio-init, relay, ffmpeg-worker
|
||||
- Nostr auth is built-in (NIP-98) — users sign in with browser extension (Alby, nos2x)
|
||||
|
||||
**2. Archipelago** — DEPLOYING (last attempt pending)
|
||||
- Repo: `https://git.tx1138.com/lfg2025/archy-demo`
|
||||
- Compose: `docker-compose.demo.yml`
|
||||
- Env vars: `ANTHROPIC_API_KEY` for Claude chat
|
||||
- Port: 4848
|
||||
- Pre-built frontend in `web-dist/` (built locally on Mac, no server-side build)
|
||||
- Backend: `neode-ui/Dockerfile.backend` (Node mock backend on :5959)
|
||||
- Web: `neode-ui/Dockerfile.web` (nginx serving pre-built static files)
|
||||
|
||||
### Issues Resolved So Far
|
||||
- IndeedHub postgres healthcheck hardcoded username → fixed to use env var
|
||||
- Port 7777 conflict → changed to 7755
|
||||
- Archy repo too large (8GB) for Portainer clone → created lightweight `archy-demo` repo
|
||||
- Frontend build failing on server → switched to pre-built static files (no npm/vite on server)
|
||||
- `.dockerignore` blocking `neode-ui/dist` → moved to `web-dist/` at repo root
|
||||
- Docker build cache stale → moved dist outside neode-ui to avoid gitignore conflicts
|
||||
|
||||
### Current Blocker
|
||||
- Last deploy attempt: Docker build cache may still be referencing old paths
|
||||
- If still failing: need to prune Docker build cache on server (`docker builder prune`)
|
||||
|
||||
### Frontend Changes Made
|
||||
- `Apps.vue` and `AppDetails.vue`: IndeedHub removed from WEB_ONLY_APP_URLS (linter change)
|
||||
- IndeedHub will be accessed as a real container or via direct URL to :7755
|
||||
|
||||
### Repo Structure (archy-demo)
|
||||
```
|
||||
archy-demo/
|
||||
├── docker-compose.demo.yml
|
||||
├── .dockerignore
|
||||
├── web-dist/ ← pre-built Vue frontend (from local Mac build)
|
||||
├── demo/aiui/ ← pre-built AIUI chat app
|
||||
└── neode-ui/ ← source + mock backend + docker configs
|
||||
├── Dockerfile.web ← nginx + copy web-dist (no build)
|
||||
├── Dockerfile.backend ← Node mock backend
|
||||
├── docker/nginx-demo.conf
|
||||
├── docker/docker-entrypoint.sh
|
||||
├── mock-backend.js
|
||||
└── src/...
|
||||
```
|
||||
|
||||
**Why:** Demo for showcasing Archipelago + IndeedHub together. Needs to be functional with nostr signing.
|
||||
**How to apply:** When resuming, check if Portainer deploy succeeded. If not, may need to SSH to prune Docker cache or debug further.
|
||||
33
.claude/memory/project_indeedhub_arch3_fix.md
Normal file
33
.claude/memory/project_indeedhub_arch3_fix.md
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: IndeedHub Arch 3 Fix — 2026-03-17
|
||||
description: Fixed IndeedHub on Arch 3 (100.124.105.113) — corrupted image tarball was root cause, all 7 containers now running
|
||||
type: project
|
||||
---
|
||||
|
||||
## Status: FIXED and working (verified 2026-03-17)
|
||||
|
||||
IndeedHub on Arch 3 (`100.124.105.113`) is fully operational — all 7 containers running, frontend on :7777, API healthy, NIP-07 nostr-provider injected.
|
||||
|
||||
## Root Cause
|
||||
|
||||
The `/tmp/indeedhub-all-images.tar` on Arch 3 was corrupted — `podman save` with multiple images collapsed ALL 7 images to the same image ID (the frontend nginx image `7222645f0b38`). So redis, minio, API, ffmpeg-worker, postgres, and relay were all running the frontend nginx binary.
|
||||
|
||||
**Why:** `podman save` with multiple images sharing layers can produce broken tarballs where all images get the same config/ID.
|
||||
|
||||
## What Was Done
|
||||
|
||||
1. Removed all broken containers and images
|
||||
2. Pulled fresh standard images from Docker Hub (postgres:16-alpine, redis:7-alpine, minio:latest, nostr-rs-relay:latest)
|
||||
3. Exported each custom image as **individual tarballs** from .228 (NOT combined):
|
||||
- `indeedhub-frontend.tar` (149MB, ID: `7222645f0b38`)
|
||||
- `indeedhub-api.tar` (403MB, ID: `2ae2665fc6c7`)
|
||||
- `indeedhub-ffmpeg.tar` (525MB, ID: `cb05b5cf8c25`)
|
||||
4. Transferred via Mac (`.228` → Mac → Arch 3 over Tailscale)
|
||||
5. Loaded images individually, created all 7 containers manually (bypassed the deploy script's broken `podman load` step)
|
||||
6. Copied nostr-provider.js + nginx config with sub_filter from .228 container into Arch 3 container via `podman cp`
|
||||
|
||||
## Remaining Issue — Deploy Script
|
||||
|
||||
The deploy script at `/tmp/deploy-indeedhub.sh` on Arch 3 still references the broken `/tmp/indeedhub-all-images.tar`. If it's run again it will re-corrupt the images. The individual tarballs (`/tmp/indeedhub-frontend.tar`, `/tmp/indeedhub-api.tar`, `/tmp/indeedhub-ffmpeg.tar`) are on Arch 3 and should be used instead.
|
||||
|
||||
**How to apply:** Next time deploying IndeedHub to any node, always export images individually, never as a combined tarball. Consider updating the deploy script to load individual tarballs.
|
||||
20
.claude/memory/project_mesh_198_issue.md
Normal file
20
.claude/memory/project_mesh_198_issue.md
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Mesh .198 fix — COMPLETED
|
||||
description: Fixed mesh radio on .198 — duplicate init, no reconnect on write fail, wrong device path. All deployed.
|
||||
type: project
|
||||
---
|
||||
|
||||
## Status: COMPLETED (2026-03-17)
|
||||
|
||||
Three bugs were found and fixed:
|
||||
|
||||
1. **Duplicate mesh init in `server.rs`** — removed duplicate block
|
||||
2. **Serial write failures don't trigger reconnection** — added `consecutive_write_failures` counter, bail after 3
|
||||
3. **Device path on .198** — set `/var/lib/archipelago/mesh-config.json` to `/dev/ttyUSB1`
|
||||
|
||||
All changes deployed to both .228 and .198.
|
||||
|
||||
### Files Changed
|
||||
- `core/archipelago/src/server.rs` — removed duplicate mesh/transport init block
|
||||
- `core/archipelago/src/mesh/listener.rs` — added write failure tracking + reconnection
|
||||
- `neode-ui/src/stores/mesh.ts` — fixed TS union type for `typed_payload`
|
||||
21
.claude/memory/reference_tailscale_nodes.md
Normal file
21
.claude/memory/reference_tailscale_nodes.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Tailscale node addresses
|
||||
description: Complete list of all Tailscale node IPs and hostnames for SSH access
|
||||
type: reference
|
||||
---
|
||||
|
||||
## Tailscale Nodes
|
||||
|
||||
| Name | Tailscale IP | Hostname | SSH |
|
||||
|------|-------------|----------|-----|
|
||||
| Arch 1 | 100.82.97.63 | — | `ssh -i ~/.ssh/archipelago-deploy archipelago@100.82.97.63` |
|
||||
| Arch 2 | 100.122.84.60 | archipelago-2.tail2b6225.ts.net | `ssh -i ~/.ssh/archipelago-deploy archipelago@archipelago-2.tail2b6225.ts.net` |
|
||||
| Arch 3 | 100.124.105.113 | archipelago-3.tail2b6225.ts.net | `ssh -i ~/.ssh/archipelago-deploy archipelago@100.124.105.113` |
|
||||
|
||||
Note: `archipelago-3.tail2b6225.ts.net` and `100.124.105.113` are the SAME machine.
|
||||
|
||||
## LAN Nodes
|
||||
| Name | IP | SSH |
|
||||
|------|-----|-----|
|
||||
| Primary (.228) | 192.168.1.228 | `ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228` |
|
||||
| Secondary (.198) | 192.168.1.198 | `ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.198` |
|
||||
173
.claude/plans/synchronous-greeting-rose.md
Normal file
173
.claude/plans/synchronous-greeting-rose.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# Mesh Phase 4 Completion + Phase 5 Implementation
|
||||
|
||||
## Context
|
||||
|
||||
Mesh Phases 1-3 are complete: serial driver, transport layer (Mesh>LAN>Tor), Double Ratchet encryption, typed messages, store-and-forward, chat UI. Phase 4 is 40% done — data structures, builders, and tests exist (`bitcoin_relay.rs`, `alerts.rs`, `message_types.rs`) but nothing is wired into the listener, MeshService, or RPC layer. Phase 5 (steganographic modes, adaptive routing, multi-hardware) is not started.
|
||||
|
||||
## Phase 4: Wire Up Off-Grid Bitcoin Operations (Weeks 8-11)
|
||||
|
||||
### Week 8: Typed Message Dispatch in Listener
|
||||
|
||||
**The critical foundation — everything else depends on this.**
|
||||
|
||||
**`mesh/listener.rs`:**
|
||||
- Add `MeshCommand::SendRaw { dest_pubkey_prefix: [u8; 6], payload: Vec<u8> }` and `BroadcastChannel { channel: u8, payload: Vec<u8> }` variants
|
||||
- In `handle_frame()`: after extracting message bytes, check for `0x02` TypedEnvelope prefix
|
||||
- New `handle_typed_message()` dispatches by type:
|
||||
- `BlockHeader` → validate Ed25519 sig, store in `BlockHeaderCache`, emit event
|
||||
- `TxRelay` → spawn task: Bitcoin RPC `sendrawtransaction`, send `TxRelayResponse` back
|
||||
- `TxRelayResponse` → complete pending in `RelayTracker`, store as MeshMessage
|
||||
- `LightningRelay` → spawn task: LND REST `payinvoice`, send response back
|
||||
- `LightningRelayResponse` → complete pending, store
|
||||
- `Alert` → verify sig, store, emit `MeshEvent::AlertReceived`
|
||||
- Handle `SendRaw` and `BroadcastChannel` in `tokio::select!` command dispatch
|
||||
|
||||
**`mesh/types.rs`:** New `MeshEvent` variants: `BlockHeaderReceived`, `AlertReceived`, `TxRelayCompleted`, `LightningRelayCompleted`
|
||||
|
||||
**Key design:** Spawn separate tokio tasks for Bitcoin/LND HTTP calls (don't block serial read loop). Response sent back via `cmd_tx` channel.
|
||||
|
||||
### Week 9: MeshService Integration + Dead Man's Switch Task
|
||||
|
||||
**`mesh/mod.rs`:**
|
||||
- Add fields: `block_header_cache: Arc<BlockHeaderCache>`, `relay_tracker: Arc<RelayTracker>`, `dead_man_switch: Arc<DeadManSwitch>`, `signing_key: ed25519_dalek::SigningKey`
|
||||
- Init in `new()`, pass cache + tracker into listener via `MeshState`
|
||||
- Accessor methods for RPC layer
|
||||
|
||||
**Dead Man background task** (spawned in `start()`):
|
||||
- Check every 60s: if triggered → build signed alert → broadcast on channel 0 + direct to emergency contacts
|
||||
- Persist `last_check_in_time` as unix timestamp on disk (survives restarts)
|
||||
|
||||
### Week 10: RPC Endpoints
|
||||
|
||||
**`api/rpc/mesh.rs`** — New handlers:
|
||||
|
||||
| Endpoint | Params | Description |
|
||||
|----------|--------|-------------|
|
||||
| `mesh.relay-tx` | `{ tx_hex }` | Queue TX for relay via internet peer |
|
||||
| `mesh.block-headers` | `{ count? }` | Return cached block headers |
|
||||
| `mesh.relay-lightning` | `{ bolt11, amount_sats }` | Queue LN invoice for payment |
|
||||
| `mesh.deadman-status` | — | Query switch state |
|
||||
| `mesh.deadman-configure` | `{ enabled, interval_secs, lat, lng, contacts, custom_message }` | Configure |
|
||||
| `mesh.deadman-checkin` | — | Heartbeat reset |
|
||||
|
||||
**Fix `mesh.send-invoice`:** Replace placeholder bolt11 with real LND `POST /v1/invoices` call.
|
||||
|
||||
**`api/rpc/mod.rs`:** Register all new routes (~line 643).
|
||||
|
||||
### Week 11: Block Header Announcer + Frontend
|
||||
|
||||
**Backend:** Optional background task: poll Bitcoin Core `getblockchaininfo` every 30s → on new block → signed announcement → broadcast channel 0. Config: `announce_block_headers: bool`.
|
||||
|
||||
**Frontend `stores/mesh.ts`:** New methods for all Phase 4 RPC calls.
|
||||
|
||||
**Frontend `views/Mesh.vue`:**
|
||||
- "Off-Grid Bitcoin" panel: block height, headers, TX relay form, LN relay form
|
||||
- "Dead Man's Switch" panel: enable/disable, interval, GPS, contacts, countdown, check-in
|
||||
- Uses `.path-option-card`, `.glass-button`, `.info-card`
|
||||
|
||||
## Phase 5: Mesh Network Intelligence (Weeks 12-15)
|
||||
|
||||
### Week 12: Steganographic Modes
|
||||
|
||||
**New: `mesh/steganography.rs`**
|
||||
|
||||
- `SteganographyMode` enum: `Normal`, `WeatherStation`, `SensorNetwork`
|
||||
- **Weather Station:** Map payload bytes → plausible weather readings (temp, humidity, pressure, wind). Marker `0xAA` replaces `0x02`.
|
||||
- **Sensor Network:** Industrial sensor format (voltage, current, vibration)
|
||||
- `to_wire_steganographic(mode)` / `from_wire_steganographic(data)` on TypedEnvelope
|
||||
- Listener detects `0xAA` → decode stego → normal dispatch
|
||||
- Config: `steganography_mode` in `MeshConfig`
|
||||
- Budget: ~80 bytes real data per 160-byte LoRa frame with stego overhead
|
||||
|
||||
### Week 13: Adaptive Routing & Signal Intelligence
|
||||
|
||||
**New: `mesh/routing.rs`**
|
||||
|
||||
- `LinkQuality` per peer: RSSI/SNR rolling 1h history, packet loss, hop count
|
||||
- `RoutingTable`: link quality per peer + best route per destination DID
|
||||
- Score: `(rssi+120)*0.4 + (snr+20)*0.3 + (1-loss)*100*0.3`
|
||||
- Best relay selection for TX/LN relay (highest quality peer with internet)
|
||||
- Multi-hop forwarding: if dest DID != ours and hops < 3, forward to best next-hop
|
||||
- Extract RSSI from v3 frames (bytes 1-2, currently unused)
|
||||
- RPC: `mesh.routing-table`
|
||||
|
||||
### Week 14: LoRa Radio Parameter Control
|
||||
|
||||
**`mesh/protocol.rs`:** Builders for `SET_RADIO_PARAMS` (0x0B), `SET_TX_POWER` (0x0C), `SET_TUNING_PARAMS` (0x15). Parse `RESP_STATS` (0x18).
|
||||
|
||||
**RPC:** `mesh.set-radio-params`, `mesh.set-tx-power`, `mesh.get-radio-stats`
|
||||
|
||||
**Auto-adaptive SF:** If link quality drops → increase spreading factor (longer range, slower). Config toggle.
|
||||
|
||||
**Frontend:** Radio tuning panel with SF/TX power sliders, stats, auto-adaptive toggle.
|
||||
|
||||
### Week 15: Multi-Hardware + Topology UI
|
||||
|
||||
**New: `mesh/device_trait.rs`**
|
||||
|
||||
```rust
|
||||
#[async_trait]
|
||||
pub trait MeshDevice: Send + Sync {
|
||||
async fn open(path: &str) -> Result<Self> where Self: Sized;
|
||||
async fn initialize(&mut self) -> Result<DeviceInfo>;
|
||||
async fn send_text(&mut self, dest: &[u8; 6], msg: &[u8]) -> Result<()>;
|
||||
async fn try_recv_frame(&mut self) -> Result<Option<InboundFrame>>;
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
- Implement for `MeshcoreDevice`, stub Meshtastic/WiFi/BLE
|
||||
- `listener.rs` uses `Box<dyn MeshDevice>`
|
||||
- **Topology UI:** SVG graph (this node center, peers as satellites), edge thickness = quality, color = green/yellow/red, tooltips with RSSI/SNR/hops
|
||||
- Stego mode selector, block relay status panel
|
||||
|
||||
## Key Challenges
|
||||
|
||||
1. **TX hex > 160 bytes:** Use Reed-Solomon chunking (already in `transport/chunking.rs`)
|
||||
2. **Async in listener:** Spawn tasks for Bitcoin/LND calls, don't block serial loop
|
||||
3. **Dead man false triggers:** Persist check-in time as unix timestamp on disk
|
||||
4. **Stego overhead:** ~80 bytes real data per 160-byte frame
|
||||
|
||||
## Files Modified
|
||||
|
||||
**Phase 4:**
|
||||
- `core/archipelago/src/mesh/listener.rs` — typed dispatch, new MeshCommand variants
|
||||
- `core/archipelago/src/mesh/mod.rs` — new fields, init, background tasks
|
||||
- `core/archipelago/src/mesh/types.rs` — new MeshEvent variants
|
||||
- `core/archipelago/src/api/rpc/mesh.rs` — 6+ new endpoints, fix send-invoice
|
||||
- `core/archipelago/src/api/rpc/mod.rs` — register routes
|
||||
- `neode-ui/src/stores/mesh.ts` — new store methods
|
||||
- `neode-ui/src/views/Mesh.vue` — off-grid + dead man panels
|
||||
|
||||
**Phase 5 new files:**
|
||||
- `core/archipelago/src/mesh/steganography.rs`
|
||||
- `core/archipelago/src/mesh/routing.rs`
|
||||
- `core/archipelago/src/mesh/device_trait.rs`
|
||||
|
||||
## Existing Code to Reuse
|
||||
|
||||
- `bitcoin_relay.rs`: `BlockHeaderCache`, `RelayTracker`, all `build_*` functions
|
||||
- `alerts.rs`: `DeadManSwitch`, `AlertConfig`, `load_config`/`save_config`
|
||||
- `message_types.rs`: All payload types, `TypedEnvelope`, `encode_payload`/`decode_payload`
|
||||
- `api/rpc/lnd.rs:128-141`: `lnd_client()` pattern for LND REST calls
|
||||
- `api/rpc/bitcoin.rs:74-107`: `bitcoin_rpc_call()` for Bitcoin Core RPC
|
||||
- `transport/chunking.rs`: Reed-Solomon FEC for payloads > 160 bytes
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
# Unit tests on server
|
||||
ssh archipelago@192.168.1.228 'cd ~/archy/core && source ~/.cargo/env && cargo test --all-features -- mesh'
|
||||
|
||||
# Type check frontend
|
||||
cd neode-ui && npm run type-check
|
||||
|
||||
# Deploy to both
|
||||
./scripts/deploy-to-target.sh --both
|
||||
|
||||
# E2E tests:
|
||||
# 1. .228 (internet) relays TX from .198 (mesh-only)
|
||||
# 2. .228 announces block headers, .198 receives them
|
||||
# 3. Dead man's switch triggers after interval, broadcasts alert
|
||||
# 4. Steganographic packet looks like weather data on wire
|
||||
```
|
||||
Reference in New Issue
Block a user