feat: bitcoin-ui CSS fix, HTTPS proxy support, deploy script improvements

Bitcoin UI:
- Replace cdn.tailwindcss.com with locally bundled tailwind.css (CSP blocks external scripts)
- Make all asset paths relative for nginx proxy compatibility
- Add bitcoin-ui build/deploy to deploy-to-target.sh (was missing entirely)
- Use --network host (bitcoin-ui proxies Bitcoin RPC at 127.0.0.1:8332)

HTTPS mixed content fix:
- Add HTTPS_PROXY_PATHS in AppSession.vue — when parent page is HTTPS,
  iframe loads through nginx proxy instead of direct HTTP port
- Prevents browser blocking HTTP iframes inside HTTPS pages
- All Tailscale servers use HTTPS, this was breaking all app iframes

Deploy & first-boot improvements:
- first-boot-containers.sh auto-detects disk size for pruning vs txindex
- first-boot-containers.sh checks fallback source path for UI containers
- Added mempool-electrs to APP_PORTS mapping
- ElectrumX container creation in first-boot
- Podman doctor/fix/uptime skills added

Also includes: session persistence, identity management, LND transactions,
ElectrumX status UI, nostr-provider improvements, Web5 enhancements

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-16 12:58:35 +00:00
parent 4e54b8bd4d
commit 367b483a72
49 changed files with 6180 additions and 495 deletions

View File

@@ -1,4 +1,4 @@
//! Electrs sync status: fetches indexed height from Electrum RPC and network height from Bitcoin Core.
//! ElectrumX sync status: fetches indexed height from Electrum RPC and network height from Bitcoin Core.
use anyhow::{Context, Result};
use serde::Serialize;
@@ -6,12 +6,12 @@ use std::io::{BufRead, BufReader, Write};
use std::net::TcpStream;
use std::time::Duration;
const ELECTRS_HOST: &str = "127.0.0.1";
const ELECTRS_PORT: u16 = 50001;
const ELECTRUMX_HOST: &str = "127.0.0.1";
const ELECTRUMX_PORT: u16 = 50001;
const BITCOIN_RPC_URL: &str = "http://127.0.0.1:8332/";
const ELECTRS_DATA_DIR: &str = "/var/lib/archipelago/mempool-electrs";
// Approximate final index size in bytes for mainnet with --lightmode (~35GB)
const ESTIMATED_FULL_INDEX_BYTES: f64 = 35_000_000_000.0;
const ELECTRUMX_DATA_DIR: &str = "/var/lib/archipelago/electrumx";
// Approximate final index size in bytes for mainnet (~55GB for ElectrumX full index)
const ESTIMATED_FULL_INDEX_BYTES: f64 = 55_000_000_000.0;
/// Build Bitcoin RPC Basic auth header from env vars.
/// Falls back to cookie auth file if env vars are not set.
@@ -61,10 +61,10 @@ fn format_bytes(bytes: u64) -> String {
}
}
/// Fetch electrs indexed height via Electrum protocol (TCP JSON-RPC).
fn electrs_indexed_height() -> Result<u64> {
let mut stream = TcpStream::connect((ELECTRS_HOST, ELECTRS_PORT))
.context("Failed to connect to electrs")?;
/// Fetch ElectrumX indexed height via Electrum protocol (TCP JSON-RPC).
fn electrumx_indexed_height() -> Result<u64> {
let mut stream = TcpStream::connect((ELECTRUMX_HOST, ELECTRUMX_PORT))
.context("Failed to connect to ElectrumX")?;
stream
.set_read_timeout(Some(Duration::from_secs(5)))
.context("set_read_timeout")?;
@@ -83,11 +83,11 @@ fn electrs_indexed_height() -> Result<u64> {
reader.read_line(&mut line)?;
let line = line.trim();
if line.is_empty() {
anyhow::bail!("Empty response from electrs");
anyhow::bail!("Empty response from ElectrumX");
}
let json: serde_json::Value = serde_json::from_str(line)?;
// blockchain.numblocks.subscribe returns result as number; headers.subscribe returns {block_height: N}
// blockchain.numblocks.subscribe returns result as number; headers.subscribe returns {block_height: N, hex: ...}
let height = json
.get("result")
.and_then(|r| r.as_u64())
@@ -96,7 +96,13 @@ fn electrs_indexed_height() -> Result<u64> {
.and_then(|r| r.get("block_height"))
.and_then(|h| h.as_u64())
})
.context("Missing height in electrs response")?;
.or_else(|| {
// ElectrumX returns {"result": {"height": N, "hex": "..."}}
json.get("result")
.and_then(|r| r.get("height"))
.and_then(|h| h.as_u64())
})
.context("Missing height in ElectrumX response")?;
Ok(height)
}
@@ -130,10 +136,10 @@ async fn bitcoin_network_height() -> Result<u64> {
Ok(height)
}
/// Get electrs sync status. Runs blocking electrs call in spawn_blocking.
/// Get ElectrumX sync status. Runs blocking ElectrumX call in spawn_blocking.
pub async fn get_electrs_sync_status() -> ElectrsSyncStatus {
// Get index data size (non-blocking, fast filesystem stat)
let data_bytes = dir_size_bytes(ELECTRS_DATA_DIR);
let data_bytes = dir_size_bytes(ELECTRUMX_DATA_DIR);
let index_size = if data_bytes > 0 {
Some(format_bytes(data_bytes))
} else {
@@ -154,10 +160,10 @@ pub async fn get_electrs_sync_status() -> ElectrsSyncStatus {
}
};
let indexed_height = match tokio::task::spawn_blocking(electrs_indexed_height).await {
let indexed_height = match tokio::task::spawn_blocking(electrumx_indexed_height).await {
Ok(Ok(h)) => h,
Ok(Err(e)) => {
// Electrs doesn't listen on 50001 until indexing completes (can take hours)
// ElectrumX may not be ready on 50001 during initial sync
let err_msg = e.to_string();
let (status, error) = if err_msg.contains("connect") || err_msg.contains("Connection refused") {
// Estimate progress from data directory size
@@ -170,12 +176,12 @@ pub async fn get_electrs_sync_status() -> ElectrsSyncStatus {
(
"indexing".to_string(),
Some(format!(
"Building index ({} / ~35 GB estimated). Electrum RPC will be available when complete.",
"Building index ({} / ~55 GB estimated). Electrum RPC will be available when complete.",
size_str
)),
)
} else {
("error".to_string(), Some(format!("Electrs: {}", e)))
("error".to_string(), Some(format!("ElectrumX: {}", e)))
};
// Use estimated progress when indexing
let progress_pct = if status == "indexing" && data_bytes > 0 {