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:
co-authored by
Claude Opus 4.6
parent
d1ac098edb
commit
f273816405
@@ -119,11 +119,15 @@ pub async fn remove_pid_marker(data_dir: &Path) {
|
||||
/// Save a snapshot of currently running containers to disk.
|
||||
/// Called periodically so we know what to restart after a crash.
|
||||
pub async fn save_container_snapshot(data_dir: &Path) -> Result<()> {
|
||||
let output = tokio::process::Command::new("sudo")
|
||||
.args(["podman", "ps", "--format", "json"])
|
||||
.output()
|
||||
.await
|
||||
.context("Failed to run podman ps")?;
|
||||
let output = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(30),
|
||||
tokio::process::Command::new("sudo")
|
||||
.args(["podman", "ps", "--format", "json"])
|
||||
.output(),
|
||||
)
|
||||
.await
|
||||
.context("podman ps timed out (30s)")?
|
||||
.context("Failed to run podman ps")?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
@@ -181,28 +185,40 @@ pub async fn recover_containers(containers: &[RunningContainerRecord]) -> Recove
|
||||
failed: Vec::new(),
|
||||
};
|
||||
|
||||
for record in containers {
|
||||
for (i, record) in containers.iter().enumerate() {
|
||||
info!("Recovering container: {} (image: {})", record.name, record.image);
|
||||
|
||||
let result = tokio::process::Command::new("sudo")
|
||||
.args(["podman", "start", &record.name])
|
||||
.output()
|
||||
.await;
|
||||
// Rate-limit container starts to avoid overwhelming podman on low-resource systems
|
||||
if i > 0 {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
||||
}
|
||||
|
||||
let result = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(30),
|
||||
tokio::process::Command::new("sudo")
|
||||
.args(["podman", "start", &record.name])
|
||||
.output(),
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(output) if output.status.success() => {
|
||||
Ok(Ok(output)) if output.status.success() => {
|
||||
info!("Successfully restarted container: {}", record.name);
|
||||
report.recovered += 1;
|
||||
}
|
||||
Ok(output) => {
|
||||
Ok(Ok(output)) => {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
warn!("Failed to restart container {}: {}", record.name, stderr.trim());
|
||||
report.failed.push(record.name.clone());
|
||||
}
|
||||
Err(e) => {
|
||||
Ok(Err(e)) => {
|
||||
warn!("Failed to execute podman start for {}: {}", record.name, e);
|
||||
report.failed.push(record.name.clone());
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("Timeout starting container {} (30s)", record.name);
|
||||
report.failed.push(record.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,10 +242,20 @@ fn is_process_running(pid: u32) -> bool {
|
||||
/// Runs on every startup to ensure containers come back after clean reboots.
|
||||
/// The crash recovery (PID-based) handles dirty shutdowns; this handles clean ones.
|
||||
pub async fn start_stopped_containers() -> RecoveryReport {
|
||||
let output = tokio::process::Command::new("sudo")
|
||||
.args(["podman", "ps", "-a", "--filter", "status=exited", "--filter", "status=created", "--format", "{{.Names}}"])
|
||||
.output()
|
||||
.await;
|
||||
let output = match tokio::time::timeout(
|
||||
std::time::Duration::from_secs(30),
|
||||
tokio::process::Command::new("sudo")
|
||||
.args(["podman", "ps", "-a", "--filter", "status=exited", "--filter", "status=created", "--format", "{{.Names}}"])
|
||||
.output(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
warn!("Timeout listing stopped containers (30s)");
|
||||
return RecoveryReport { total: 0, recovered: 0, failed: Vec::new() };
|
||||
}
|
||||
};
|
||||
|
||||
let names: Vec<String> = match output {
|
||||
Ok(o) if o.status.success() => {
|
||||
@@ -256,10 +282,10 @@ pub async fn start_stopped_containers() -> RecoveryReport {
|
||||
/// Spawn a background task that periodically saves the container snapshot.
|
||||
pub fn spawn_snapshot_task(data_dir: PathBuf) {
|
||||
tokio::spawn(async move {
|
||||
// Wait 30s before first snapshot (let containers stabilize after startup)
|
||||
tokio::time::sleep(std::time::Duration::from_secs(30)).await;
|
||||
// Wait 2 minutes before first snapshot (let crash recovery finish and containers stabilize)
|
||||
tokio::time::sleep(std::time::Duration::from_secs(120)).await;
|
||||
|
||||
let mut interval = tokio::time::interval(std::time::Duration::from_secs(60));
|
||||
let mut interval = tokio::time::interval(std::time::Duration::from_secs(120));
|
||||
loop {
|
||||
interval.tick().await;
|
||||
if let Err(e) = save_container_snapshot(&data_dir).await {
|
||||
|
||||
Reference in New Issue
Block a user