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:
Dorian
2026-03-17 23:56:37 +00:00
co-authored by Claude Opus 4.6
parent d1ac098edb
commit f273816405
48 changed files with 3432 additions and 438 deletions
+52 -18
View File
@@ -182,12 +182,23 @@ impl MemoryTracker {
/// Query container memory stats from podman.
async fn check_container_memory() -> HashMap<String, u64> {
let output = match tokio::process::Command::new("sudo")
.args(["podman", "stats", "--no-stream", "--format", "{{.Name}} {{.MemUsage}}"])
.output()
.await
let output = match tokio::time::timeout(
std::time::Duration::from_secs(30),
tokio::process::Command::new("sudo")
.args(["podman", "stats", "--no-stream", "--format", "{{.Name}} {{.MemUsage}}"])
.output(),
)
.await
{
Ok(o) if o.status.success() => o,
Ok(Ok(o)) if o.status.success() => o,
Ok(Err(e)) => {
debug!("podman stats failed: {}", e);
return HashMap::new();
}
Err(_) => {
debug!("podman stats timed out (30s)");
return HashMap::new();
}
_ => return HashMap::new(),
};
@@ -230,12 +241,23 @@ fn parse_memory_string(s: &str) -> Option<u64> {
/// Query all containers and their health status.
async fn check_containers() -> Vec<ContainerHealth> {
let output = match tokio::process::Command::new("sudo")
.args(["podman", "ps", "-a", "--format", "json"])
.output()
.await
let output = match tokio::time::timeout(
std::time::Duration::from_secs(30),
tokio::process::Command::new("sudo")
.args(["podman", "ps", "-a", "--format", "json"])
.output(),
)
.await
{
Ok(o) if o.status.success() => o,
Ok(Ok(o)) if o.status.success() => o,
Ok(Err(e)) => {
debug!("podman ps failed: {}", e);
return Vec::new();
}
Err(_) => {
debug!("podman ps timed out (30s)");
return Vec::new();
}
_ => return Vec::new(),
};
@@ -243,7 +265,7 @@ async fn check_containers() -> Vec<ContainerHealth> {
let containers: Vec<serde_json::Value> =
serde_json::from_str(&stdout).unwrap_or_default();
// Backend services to skip
// Backend services and one-shot init containers to skip
let skip = [
"btcpay-db", "nbxplorer", "mempool-db", "mempool-api",
"penpot-postgres", "penpot-backend", "penpot-exporter", "penpot-valkey",
@@ -271,6 +293,11 @@ async fn check_containers() -> Vec<ContainerHealth> {
return None;
}
// Skip podman-compose infrastructure and one-shot init containers
if name.starts_with("indeedhub-build_") || name.contains("-init") {
return None;
}
let state = c.get("State")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
@@ -291,25 +318,32 @@ async fn check_containers() -> Vec<ContainerHealth> {
/// Try to restart a container.
async fn restart_container(name: &str) -> bool {
info!("Auto-restarting unhealthy container: {}", name);
let result = tokio::process::Command::new("sudo")
.args(["podman", "start", name])
.output()
.await;
let result = tokio::time::timeout(
std::time::Duration::from_secs(30),
tokio::process::Command::new("sudo")
.args(["podman", "start", name])
.output(),
)
.await;
match result {
Ok(output) if output.status.success() => {
Ok(Ok(output)) if output.status.success() => {
info!("Successfully restarted container: {}", name);
true
}
Ok(output) => {
Ok(Ok(output)) => {
let stderr = String::from_utf8_lossy(&output.stderr);
warn!("Failed to restart container {}: {}", name, stderr.trim());
false
}
Err(e) => {
Ok(Err(e)) => {
warn!("Failed to execute podman start for {}: {}", name, e);
false
}
Err(_) => {
warn!("Timeout starting container {} (30s)", name);
false
}
}
}