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
co-authored by Claude Opus 4.6
parent 07e46dce56
commit 30164fd12a
49 changed files with 6180 additions and 495 deletions
+90 -1
View File
@@ -1,7 +1,7 @@
//! RPC handlers for multi-identity management.
use super::RpcHandler;
use crate::identity_manager::{IdentityManager, IdentityPurpose};
use crate::identity_manager::{IdentityManager, IdentityProfile, IdentityPurpose};
use crate::network::did_dht;
use anyhow::{Context, Result};
use nostr_sdk::ToBech32;
@@ -43,6 +43,7 @@ impl RpcHandler {
"is_default": is_default,
"nostr_pubkey": id.nostr_pubkey,
"nostr_npub": id.nostr_npub,
"profile": id.profile,
})
})
.collect();
@@ -650,6 +651,94 @@ impl RpcHandler {
}))
}
/// Update profile metadata for an identity.
pub(super) async fn handle_identity_update_profile(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.unwrap_or_default();
let id = params.get("id").and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: id"))?;
validate_identity_id(id)?;
let profile = IdentityProfile {
display_name: params.get("display_name").and_then(|v| v.as_str()).map(String::from),
about: params.get("about").and_then(|v| v.as_str()).map(String::from),
picture: params.get("picture").and_then(|v| v.as_str()).map(String::from),
banner: params.get("banner").and_then(|v| v.as_str()).map(String::from),
website: params.get("website").and_then(|v| v.as_str()).map(String::from),
nip05: params.get("nip05").and_then(|v| v.as_str()).map(String::from),
lud16: params.get("lud16").and_then(|v| v.as_str()).map(String::from),
};
let manager = IdentityManager::new(&self.config.data_dir).await?;
manager.update_profile(id, profile).await?;
Ok(serde_json::json!({ "ok": true }))
}
/// Publish kind 0 (metadata) profile to the local Nostr relay.
pub(super) async fn handle_identity_publish_profile(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.unwrap_or_default();
let id = params.get("id").and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: id"))?;
validate_identity_id(id)?;
let relay_url = params.get("relay")
.and_then(|v| v.as_str())
.unwrap_or("ws://localhost:18081");
let manager = IdentityManager::new(&self.config.data_dir).await?;
let event_id = manager.publish_profile(id, relay_url).await?;
Ok(serde_json::json!({
"event_id": event_id,
"relay": relay_url,
"published": true,
}))
}
/// Export private keys for an identity — REQUIRES password verification.
pub(super) async fn handle_identity_export_keys(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.unwrap_or_default();
let id = params
.get("id")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: id"))?;
let password = params
.get("password")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: password"))?;
validate_identity_id(id)?;
// Verify password against auth system
if !self.auth_manager.verify_password(password).await? {
anyhow::bail!("Invalid password");
}
let manager = IdentityManager::new(&self.config.data_dir).await?;
let keys = manager.export_keys(id).await?;
let record = manager.get(id).await?;
Ok(serde_json::json!({
"id": record.id,
"name": record.name,
"pubkey": record.pubkey_hex,
"did": record.did,
"nostr_pubkey": record.nostr_pubkey,
"nostr_npub": record.nostr_npub,
"ed25519_secret_hex": keys["ed25519_secret_hex"],
"nostr_secret_hex": keys["nostr_secret_hex"],
"nostr_nsec": keys["nostr_nsec"],
}))
}
/// identity.dht-status — Check if an identity's did:dht is published and resolvable.
pub(super) async fn handle_identity_dht_status(
&self,