Update Fedimint configuration and enhance onboarding process

- Upgraded Fedimint version to v0.10.0 in docker-compose.yml and manifest.yml, adding support for the built-in Guardian UI.
- Modified .gitignore to exclude deploy-config.sh script.
- Enhanced onboarding process in AuthManager to persist onboarding state and validate password strength during user setup.
- Updated API to handle onboarding completion and password change requests, ensuring a smoother user experience.
- Improved configuration management to support Nostr discovery and Tor proxy settings, enhancing node identity features.
This commit is contained in:
Dorian
2026-02-17 15:03:34 +00:00
parent 6035c93289
commit 1073d9fd2c
73 changed files with 5870 additions and 478 deletions

View File

@@ -1,6 +1,8 @@
use crate::api::ApiHandler;
use crate::config::{Config, ContainerRuntime};
use crate::container::DockerPackageScanner;
use crate::container::{docker_packages, DockerPackageScanner};
use crate::identity::{self, NodeIdentity};
use crate::nostr_discovery;
use crate::state::StateManager;
use anyhow::Result;
use hyper::server::conn::Http;
@@ -13,6 +15,7 @@ use tracing::{debug, error, info};
pub struct Server {
_config: Config,
_identity: Arc<NodeIdentity>,
api_handler: Arc<ApiHandler>,
_state_manager: Arc<StateManager>,
}
@@ -20,17 +23,83 @@ pub struct Server {
impl Server {
pub async fn new(config: Config) -> Result<Self> {
let state_manager = Arc::new(StateManager::new());
// Load node identity and set stable server_info
let identity_dir = config.data_dir.join("identity");
let identity = NodeIdentity::load_or_create(&identity_dir).await?;
let (mut data, _) = state_manager.get_snapshot().await;
data.server_info.id = identity.node_id();
data.server_info.pubkey = identity.pubkey_hex();
data.server_info.tor_address = docker_packages::read_tor_address("archipelago");
if let Some(ref tor) = data.server_info.tor_address {
data.server_info.node_address = Some(identity.node_address(tor));
}
state_manager.update_data(data.clone()).await;
// Revoke any previously published Nostr data (runs before publish so revocation is not overwritten)
let identity_dir = config.data_dir.join("identity");
let tor_proxy_revoke = config.nostr_tor_proxy.clone();
if let Err(e) = nostr_discovery::revoke_if_needed(&identity_dir, tor_proxy_revoke.as_deref()).await {
tracing::debug!("Nostr revoke (non-fatal): {}", e);
}
// Publish node identity to Nostr only when opt-in (nostr_discovery_enabled + relays)
if config.nostr_discovery_enabled
&& !config.nostr_relays.is_empty()
&& data.server_info.node_address.is_some()
{
let identity_dir = config.data_dir.join("identity");
let did = identity::did_key_from_pubkey_hex(&data.server_info.pubkey).unwrap_or_default();
let node_addr = data.server_info.node_address.clone().unwrap_or_default();
let version = data.server_info.version.clone();
let relays = config.nostr_relays.clone();
let tor_proxy = config.nostr_tor_proxy.clone();
tokio::spawn(async move {
if let Err(e) = nostr_discovery::publish_node_identity(
&identity_dir,
&did,
&node_addr,
&version,
&relays,
tor_proxy.as_deref(),
)
.await
{
tracing::debug!("Nostr publish (non-fatal): {}", e);
}
});
}
info!("🔑 Node identity: {} (pubkey: {}...)", identity.node_id(), &identity.pubkey_hex()[..16.min(identity.pubkey_hex().len())]);
let identity = Arc::new(identity);
let api_handler = Arc::new(ApiHandler::new(config.clone(), state_manager.clone()).await?);
// Periodic Tor address refresh (runs regardless of dev_mode)
// Picks up hostname when Tor creates it after startup/rotation (30-60s delay)
{
let state = state_manager.clone();
let identity_clone = identity.clone();
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(30));
loop {
interval.tick().await;
if let Err(e) = refresh_tor_address(&state, identity_clone.as_ref()).await {
debug!("Tor address refresh (non-fatal): {}", e);
}
}
});
}
// Initialize Docker scanner if in dev mode
if config.dev_mode {
let scanner = create_docker_scanner(&config).await?;
let state = state_manager.clone();
let identity_clone = identity.clone();
// Initial scan
tokio::spawn(async move {
info!("🐳 Scanning Docker containers...");
if let Err(e) = scan_and_update_packages(&scanner, &state).await {
if let Err(e) = scan_and_update_packages(&scanner, &state, identity_clone.as_ref()).await {
error!("Failed to scan Docker containers: {}", e);
}
@@ -38,7 +107,7 @@ impl Server {
let mut interval = tokio::time::interval(Duration::from_secs(10));
loop {
interval.tick().await;
if let Err(e) = scan_and_update_packages(&scanner, &state).await {
if let Err(e) = scan_and_update_packages(&scanner, &state, identity_clone.as_ref()).await {
error!("Failed to update Docker containers: {}", e);
}
}
@@ -47,6 +116,7 @@ impl Server {
Ok(Self {
_config: config,
_identity: identity,
api_handler,
_state_manager: state_manager,
})
@@ -108,25 +178,42 @@ async fn create_docker_scanner(config: &Config) -> Result<DockerPackageScanner>
Ok(DockerPackageScanner::new(runtime))
}
async fn refresh_tor_address(state: &StateManager, identity: &NodeIdentity) -> Result<()> {
let tor_addr = docker_packages::read_tor_address("archipelago");
let (current_data, _) = state.get_snapshot().await;
if tor_addr != current_data.server_info.tor_address {
let mut data = current_data;
data.server_info.tor_address = tor_addr.clone();
data.server_info.node_address = tor_addr.as_ref().map(|t| identity.node_address(t));
state.update_data(data).await;
if let Some(ref addr) = tor_addr {
info!("🔒 Tor address updated: {}", addr);
}
}
Ok(())
}
async fn scan_and_update_packages(
scanner: &DockerPackageScanner,
state: &StateManager,
identity: &NodeIdentity,
) -> Result<()> {
let packages = scanner.scan_containers().await?;
// Only update if we have packages AND they're different from current state
if !packages.is_empty() {
let (current_data, _) = state.get_snapshot().await;
// Check if packages actually changed to avoid unnecessary broadcasts
let packages_changed = current_data.package_data != packages;
if packages_changed {
let mut data = current_data;
let (current_data, _) = state.get_snapshot().await;
let packages_changed = !packages.is_empty() && current_data.package_data != packages;
let tor_addr = docker_packages::read_tor_address("archipelago");
let tor_changed = tor_addr != current_data.server_info.tor_address;
if packages_changed || tor_changed {
let mut data = current_data;
if !packages.is_empty() {
data.package_data = packages;
state.update_data(data).await;
debug!("📦 Container state changed, broadcasting update");
}
data.server_info.tor_address = tor_addr.clone();
data.server_info.node_address = tor_addr.as_ref().map(|t| identity.node_address(t));
state.update_data(data).await;
debug!("📦 State changed (packages={}, tor={}), broadcasting update", packages_changed, tor_changed);
}
Ok(())