feat: NostrVPN as native system service, Claude API key input, fix duplicate password
Some checks failed
Build Archipelago ISO (dev) / build-iso (push) Failing after 1m0s
Some checks failed
Build Archipelago ISO (dev) / build-iso (push) Failing after 1m0s
- Add NostrVPN as a native systemd service (extracted from container) - Add VPN status detection for nostr-vpn in backend vpn.rs - ISO build extracts nvpn binary from container image - First-boot auto-configures NostrVPN with node's Nostr identity - Change Claude Auth from login iframe to API key input field - Remove duplicate ChangePasswordSection from Settings.vue - FIPS and Routstr remain as installable container apps Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ const VPN_CONFIG_FILE: &str = "vpn-config.json";
|
||||
pub enum VpnProvider {
|
||||
Tailscale,
|
||||
Wireguard,
|
||||
NostrVpn,
|
||||
}
|
||||
|
||||
/// Persisted VPN configuration.
|
||||
@@ -172,6 +173,11 @@ pub fn generate_wireguard_conf(config: &WireGuardConfig) -> String {
|
||||
|
||||
/// Get the current VPN status by checking network interfaces.
|
||||
pub async fn get_status() -> VpnStatus {
|
||||
// Check for NostrVPN (native system service)
|
||||
if let Ok(nvpn) = get_nostr_vpn_status().await {
|
||||
return nvpn;
|
||||
}
|
||||
|
||||
// Check for Tailscale interface
|
||||
if let Ok(tailscale) = get_tailscale_status().await {
|
||||
return tailscale;
|
||||
@@ -194,6 +200,101 @@ pub async fn get_status() -> VpnStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if NostrVPN system service is running and get its status.
|
||||
async fn get_nostr_vpn_status() -> Result<VpnStatus> {
|
||||
// Check if nostr-vpn service is active
|
||||
let active = tokio::process::Command::new("systemctl")
|
||||
.args(["is-active", "nostr-vpn"])
|
||||
.output()
|
||||
.await
|
||||
.map(|o| o.status.success())
|
||||
.unwrap_or(false);
|
||||
|
||||
if !active {
|
||||
anyhow::bail!("nostr-vpn service not active");
|
||||
}
|
||||
|
||||
// Try to get status from nvpn CLI
|
||||
let output = tokio::process::Command::new("nvpn")
|
||||
.arg("status")
|
||||
.output()
|
||||
.await;
|
||||
|
||||
let (peers, ip) = match output {
|
||||
Ok(o) if o.status.success() => {
|
||||
let stdout = String::from_utf8_lossy(&o.stdout);
|
||||
let peers = stdout.lines()
|
||||
.filter(|l| l.contains("peer") || l.contains("connected"))
|
||||
.count() as u32;
|
||||
let ip = stdout.lines()
|
||||
.find(|l| l.contains("address") || l.contains("ip"))
|
||||
.and_then(|l| l.split_whitespace().last())
|
||||
.map(|s| s.to_string());
|
||||
(peers, ip)
|
||||
}
|
||||
_ => (0, None),
|
||||
};
|
||||
|
||||
Ok(VpnStatus {
|
||||
connected: true,
|
||||
provider: Some("nostr-vpn".to_string()),
|
||||
interface: Some("nvpn0".to_string()),
|
||||
ip_address: ip,
|
||||
hostname: None,
|
||||
peers_connected: peers,
|
||||
bytes_in: 0,
|
||||
bytes_out: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Configure NostrVPN with the node's Nostr identity.
|
||||
pub async fn configure_nostr_vpn(data_dir: &Path) -> Result<()> {
|
||||
let nostr_secret = tokio::fs::read_to_string(
|
||||
data_dir.join("identity/nostr_secret")
|
||||
).await.context("No Nostr secret key — complete onboarding first")?;
|
||||
|
||||
let nostr_pubkey = tokio::fs::read_to_string(
|
||||
data_dir.join("identity/nostr_pubkey")
|
||||
).await.unwrap_or_default();
|
||||
|
||||
let vpn_dir = data_dir.join("nostr-vpn");
|
||||
tokio::fs::create_dir_all(&vpn_dir).await.context("Failed to create nostr-vpn dir")?;
|
||||
|
||||
// Write env file for the systemd service
|
||||
let env_content = format!(
|
||||
"NOSTR_SECRET={}\nNOSTR_PUBKEY={}\n",
|
||||
nostr_secret.trim(),
|
||||
nostr_pubkey.trim()
|
||||
);
|
||||
tokio::fs::write(vpn_dir.join("env"), &env_content)
|
||||
.await
|
||||
.context("Failed to write nostr-vpn env")?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::set_permissions(
|
||||
vpn_dir.join("env"),
|
||||
std::fs::Permissions::from_mode(0o600),
|
||||
).ok();
|
||||
}
|
||||
|
||||
// Enable and start the service
|
||||
tokio::process::Command::new("systemctl")
|
||||
.args(["enable", "--now", "nostr-vpn"])
|
||||
.output()
|
||||
.await
|
||||
.context("Failed to enable nostr-vpn service")?;
|
||||
|
||||
let mut config = load_config(data_dir).await?;
|
||||
config.provider = VpnProvider::NostrVpn;
|
||||
config.enabled = true;
|
||||
config.configured_at = Some(chrono::Utc::now().to_rfc3339());
|
||||
save_config(data_dir, &config).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_tailscale_status() -> Result<VpnStatus> {
|
||||
// Check if tailscale0 interface exists
|
||||
let output = tokio::process::Command::new("ip")
|
||||
|
||||
Reference in New Issue
Block a user