fix: install/uninstall UI state, progress bar, auto-Tor hidden services

- Install progress bar replaces action buttons (no overlay)
- Hide status badge during install/uninstall
- Uninstall keeps progress state until container disappears from WebSocket
- Uninstall RPC timeout increased to 660s (Bitcoin UTXO flush)
- Installing apps appear in My Apps immediately as placeholders
- Auto-configure Tor hidden service for every app on install
- Widen Tor module visibility for install hooks
- Only clear stale install entries on error status

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-04-08 09:20:18 +02:00
co-authored by Claude Opus 4.6
parent 5c117f5718
commit 8e094c7ce9
7 changed files with 82 additions and 32 deletions
@@ -801,6 +801,48 @@ autopilot.active=false\n",
}
}
// Auto-configure Tor hidden service for protocol services (LND, ElectrumX, Bitcoin)
{
use crate::api::rpc::tor::{
known_service_port, is_protocol_service, load_services_config,
save_services_config, regenerate_torrc, restart_tor, wait_for_hostname,
sync_single_hostname, TorServiceEntry,
};
let tor_port = known_service_port(package_id);
if tor_port > 0 {
let config_dir = self.config.data_dir.join("tor-config");
let mut config = load_services_config(&config_dir).await;
let already_exists = config.services.iter().any(|s| s.name == package_id);
if !already_exists {
let is_proto = is_protocol_service(package_id);
config.services.push(TorServiceEntry {
name: package_id.to_string(),
local_port: tor_port,
remote_port: None,
unauthenticated: is_proto,
enabled: true,
});
if let Err(e) = save_services_config(&config_dir, &config).await {
tracing::warn!("Failed to save Tor config for {}: {}", package_id, e);
} else if let Err(e) = regenerate_torrc(&config).await {
tracing::warn!("Failed to regenerate torrc for {}: {}", package_id, e);
} else if let Err(e) = restart_tor().await {
tracing::warn!("Failed to restart Tor for {}: {}", package_id, e);
} else {
let onion = wait_for_hostname(package_id, 30).await;
if let Some(ref addr) = onion {
sync_single_hostname(package_id, addr).await;
info!("Tor hidden service created for {} → {}", package_id, addr);
} else {
info!("Tor hidden service created for {} (hostname pending)", package_id);
}
}
}
}
}
if package_id == "nextcloud" {
let host_ip = &self.config.host_ip;
// Wait for Nextcloud to finish first-run initialization