feat: Lightning channel backup, Web5 mobile tab active, file path fix
Task 14: Lightning Channel Backup - New lnd.export-channel-backup RPC — exports SCB (Static Channel Backup) - Settings UI: "Lightning Channel Backup" section with export + copy - Returns base64 backup data, channel count, timestamp Web5 mobile tab active state - Fixed combined tab matching for Web5: includes /web5, /federation, /mesh routes - Previously only matched /cloud and /server (wrong branch) Content file path fix - Allow forward slashes in filenames for subdirectories (Music/song.mp3) - Still block .., \, null bytes, hidden files, absolute paths Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -938,6 +938,53 @@ impl RpcHandler {
|
||||
"grpc_port": 10009,
|
||||
}))
|
||||
}
|
||||
|
||||
/// lnd.export-channel-backup — Export all channel static backups (SCB).
|
||||
/// Returns base64-encoded multi-channel backup that can restore channels on a new node.
|
||||
pub(super) async fn handle_lnd_export_channel_backup(&self) -> Result<serde_json::Value> {
|
||||
let macaroon_path = "/var/lib/archipelago/lnd/data/chain/bitcoin/mainnet/admin.macaroon";
|
||||
let macaroon_bytes = tokio::fs::read(macaroon_path)
|
||||
.await
|
||||
.context("Failed to read LND admin macaroon")?;
|
||||
let macaroon_hex = hex::encode(&macaroon_bytes);
|
||||
|
||||
let cert_path = "/var/lib/archipelago/lnd/tls.cert";
|
||||
let client = reqwest::Client::builder()
|
||||
.danger_accept_invalid_certs(true)
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.build()
|
||||
.context("Failed to build HTTP client")?;
|
||||
|
||||
let resp = client
|
||||
.get("https://127.0.0.1:8080/v1/channels/backup")
|
||||
.header("Grpc-Metadata-macaroon", &macaroon_hex)
|
||||
.send()
|
||||
.await
|
||||
.context("Failed to reach LND REST API")?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
anyhow::bail!("LND returned {}", resp.status());
|
||||
}
|
||||
|
||||
let data: serde_json::Value = resp.json().await.context("Invalid JSON from LND")?;
|
||||
|
||||
// Extract the multi_chan_backup bytes
|
||||
let backup_b64 = data
|
||||
.get("multi_chan_backup")
|
||||
.and_then(|m| m.get("multi_chan_backup"))
|
||||
.and_then(|b| b.as_str())
|
||||
.unwrap_or("");
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"backup": backup_b64,
|
||||
"channel_count": data.get("multi_chan_backup")
|
||||
.and_then(|m| m.get("chan_points"))
|
||||
.and_then(|c| c.as_array())
|
||||
.map(|a| a.len())
|
||||
.unwrap_or(0),
|
||||
"timestamp": chrono::Utc::now().to_rfc3339(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// Channel types
|
||||
|
||||
@@ -514,6 +514,7 @@ impl RpcHandler {
|
||||
"lnd.create-raw-tx" => self.handle_lnd_create_raw_tx(params).await,
|
||||
"lnd.gettransactions" => self.handle_lnd_gettransactions().await,
|
||||
"lnd.connect-info" => self.handle_lnd_connect_info().await,
|
||||
"lnd.export-channel-backup" => self.handle_lnd_export_channel_backup().await,
|
||||
|
||||
// Multi-identity management
|
||||
"identity.list" => self.handle_identity_list(params).await,
|
||||
|
||||
Reference in New Issue
Block a user