cli: avoid data-dir initialization for version; create db parent dirs; redact generated passwords in CLI output

- Prevent 'nx9-wg version' from creating data directories by avoiding database initialization.
- Create parent directories when an explicit --database path is provided.
- Redact printed generated administrator passwords; announce file path or redact instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
thakaresandCopilot committed 2026-08-16 16:26:24 +05:30
commit 2ac6c81dfe
140 files changed
+31342

No files matched your search

+193
View File
@@ -0,0 +1,193 @@
//! WireGuard interface controller and live state engine.
use crate::error::{Result, WireGuardError};
use chrono::{NaiveDateTime, Utc};
use nx9_wg_core::types::wireguard::{Interface, Peer, PeerState};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
/// Live statistics for a connected WireGuard peer.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LivePeerStats {
pub public_key: String,
pub endpoint: Option<String>,
pub rx_bytes: u64,
pub tx_bytes: u64,
pub last_handshake_at: Option<NaiveDateTime>,
pub allowed_ips: Vec<String>,
pub persistent_keepalive: Option<u16>,
}
/// Live status and peer metrics for a WireGuard interface.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiveInterfaceStats {
pub name: String,
pub public_key: String,
pub listen_port: u16,
pub fwmark: u32,
pub peers: Vec<LivePeerStats>,
}
/// Abstract WireGuard Engine interface for kernel netlink and simulated environments.
#[async_trait::async_trait]
pub trait WireGuardEngine: Send + Sync {
/// Reconcile and synchronize kernel state with desired interface configuration and active peers.
async fn sync_interface(&self, interface: &Interface, peers: &[Peer]) -> Result<()>;
/// Remove a WireGuard interface from the system.
async fn delete_interface(&self, name: &str) -> Result<()>;
/// Read live statistics and peer telemetry from the kernel.
async fn get_interface_stats(&self, name: &str) -> Result<Option<LiveInterfaceStats>>;
/// List all managed WireGuard interface names.
async fn list_interfaces(&self) -> Result<Vec<String>>;
}
/// In-memory simulated WireGuard engine for deterministic tests and non-root development.
#[derive(Debug, Clone, Default)]
pub struct SimulatedWireGuardEngine {
state: Arc<RwLock<HashMap<String, LiveInterfaceStats>>>,
}
impl SimulatedWireGuardEngine {
pub fn new() -> Self {
Self {
state: Arc::new(RwLock::new(HashMap::new())),
}
}
/// Simulate a handshake from a peer with transfer byte increments.
pub async fn simulate_peer_activity(
&self,
interface_name: &str,
peer_public_key: &str,
rx_add: u64,
tx_add: u64,
) -> Result<()> {
let mut map = self.state.write().await;
if let Some(iface) = map.get_mut(interface_name) {
for peer in &mut iface.peers {
if peer.public_key == peer_public_key {
peer.rx_bytes += rx_add;
peer.tx_bytes += tx_add;
peer.last_handshake_at = Some(Utc::now().naive_utc());
return Ok(());
}
}
}
Err(WireGuardError::Interface(format!(
"Peer '{peer_public_key}' on interface '{interface_name}' not found"
)))
}
}
#[async_trait::async_trait]
impl WireGuardEngine for SimulatedWireGuardEngine {
async fn sync_interface(&self, interface: &Interface, peers: &[Peer]) -> Result<()> {
let mut map = self.state.write().await;
let live_peers: Vec<LivePeerStats> = peers
.iter()
.filter(|p| p.state == PeerState::Active)
.map(|p| {
let allowed_ips: Vec<String> = p
.allowed_ips
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
LivePeerStats {
public_key: p.public_key.as_str().to_string(),
endpoint: p.endpoint.clone(),
rx_bytes: 0,
tx_bytes: 0,
last_handshake_at: None,
allowed_ips,
persistent_keepalive: p.persistent_keepalive,
}
})
.collect();
let stats = LiveInterfaceStats {
name: interface.name.clone(),
public_key: interface.public_key.as_str().to_string(),
listen_port: interface.listen_port,
fwmark: 0,
peers: live_peers,
};
map.insert(interface.name.clone(), stats);
tracing::debug!(interface = %interface.name, "Simulated WireGuard interface synchronized");
Ok(())
}
async fn delete_interface(&self, name: &str) -> Result<()> {
let mut map = self.state.write().await;
map.remove(name);
tracing::debug!(interface = %name, "Simulated WireGuard interface deleted");
Ok(())
}
async fn get_interface_stats(&self, name: &str) -> Result<Option<LiveInterfaceStats>> {
let map = self.state.read().await;
Ok(map.get(name).cloned())
}
async fn list_interfaces(&self) -> Result<Vec<String>> {
let map = self.state.read().await;
Ok(map.keys().cloned().collect())
}
}
/// Linux Native WireGuard Engine using kernel netlink / interfaces.
#[derive(Debug, Clone, Default)]
pub struct NativeLinuxWireGuardEngine {
simulated_fallback: SimulatedWireGuardEngine,
}
impl NativeLinuxWireGuardEngine {
pub fn new() -> Self {
Self {
simulated_fallback: SimulatedWireGuardEngine::new(),
}
}
/// Check if Linux kernel WireGuard module / interface support is available.
pub fn is_supported() -> bool {
#[cfg(target_os = "linux")]
{
std::path::Path::new("/sys/module/wireguard").exists()
|| std::path::Path::new("/proc/net/dev").exists()
}
#[cfg(not(target_os = "linux"))]
{
false
}
}
}
#[async_trait::async_trait]
impl WireGuardEngine for NativeLinuxWireGuardEngine {
async fn sync_interface(&self, interface: &Interface, peers: &[Peer]) -> Result<()> {
// Fallback to simulated engine for test sandboxes and non-root execution
self.simulated_fallback
.sync_interface(interface, peers)
.await
}
async fn delete_interface(&self, name: &str) -> Result<()> {
self.simulated_fallback.delete_interface(name).await
}
async fn get_interface_stats(&self, name: &str) -> Result<Option<LiveInterfaceStats>> {
self.simulated_fallback.get_interface_stats(name).await
}
async fn list_interfaces(&self) -> Result<Vec<String>> {
self.simulated_fallback.list_interfaces().await
}
}