fix: AIUI /aiui/ base path, nginx alias cycle, VPN auth, container boot
All checks were successful
Build Archipelago ISO (dev) / build-iso (push) Successful in 11m17s

- AIUI: rebuild with /aiui/ base path (router, chunk loader, SW scope)
- nginx: remove alias from /aiui/ location (caused try_files redirect cycle)
- VPN: WireGuard standalone setup, auth improvements
- ISO: build script hardening, service file updates
- first-boot-containers: networking stack fixes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-04-09 20:42:09 +02:00
parent 56e04a9df8
commit fe3c844fe6
94 changed files with 382 additions and 233 deletions

View File

@@ -32,6 +32,26 @@ impl RpcHandler {
}
tracing::info!("[onboarding] login successful");
// Ensure NostrVPN config exists — covers the case where onboardingComplete
// was never called (e.g., user took the "already set up" shortcut).
let data_dir = self.config.data_dir.clone();
tokio::spawn(async move {
// Quick check: if config.toml already exists, skip
let config_path = data_dir.join("nostr-vpn/.config/nvpn/config.toml");
if config_path.exists() {
return;
}
// Identity must exist for VPN config
if !data_dir.join("identity/nostr_pubkey").exists() {
return;
}
match crate::vpn::configure_nostr_vpn(&data_dir).await {
Ok(()) => tracing::info!("[login] NostrVPN auto-configured on first login"),
Err(e) => tracing::debug!("[login] NostrVPN auto-config skipped: {}", e),
}
});
Ok(serde_json::Value::Null)
}

View File

@@ -36,7 +36,8 @@ impl RpcHandler {
Err(_) => None,
};
let node_npub = vpn::read_nvpn_config_value("nostr", "public_key").await;
let node_npub = vpn::read_nvpn_config_value("nostr", "public_key").await
.map(|k| vpn::ensure_npub(&k));
let (relay_onion, relay_direct) = vpn::get_relay_urls().await;
// Prefer onion (always works), fall back to direct IP
let relay_url = relay_onion.clone().or(relay_direct.clone());
@@ -260,9 +261,10 @@ impl RpcHandler {
}
}
// Read nvpn config to build invite
// Read nvpn config to build invite (convert hex to npub1 if needed)
let npub = vpn::read_nvpn_config_value("nostr", "public_key").await
.ok_or_else(|| anyhow::anyhow!("No Nostr public key in nvpn config"))?;
.map(|k| vpn::ensure_npub(&k))
.ok_or_else(|| anyhow::anyhow!("No Nostr public key in nvpn config — VPN not configured"))?;
// network_id is in [[networks]] array — read first entry
let network_id = vpn::read_nvpn_config_list_entry("networks", "network_id").await
.unwrap_or_else(|| "nostr-vpn".to_string());

View File

@@ -4,6 +4,7 @@
//! VPN interface status for remote access to the Archipelago node.
use anyhow::{Context, Result};
use nostr_sdk::ToBech32;
use serde::{Deserialize, Serialize};
use std::path::Path;
use tokio::fs;
@@ -336,24 +337,61 @@ async fn get_nostr_vpn_status() -> Result<VpnStatus> {
})
}
/// Convert a hex public key to npub1... bech32 format.
/// Returns the original string if already npub1 or conversion fails.
pub fn ensure_npub(key: &str) -> String {
let key = key.trim();
if key.starts_with("npub1") {
return key.to_string();
}
nostr_sdk::PublicKey::from_hex(key)
.ok()
.and_then(|pk| pk.to_bech32().ok())
.unwrap_or_else(|| key.to_string())
}
/// Convert a hex secret key to nsec1... bech32 format.
/// Returns the original string if already nsec1 or conversion fails.
fn ensure_nsec(key: &str) -> String {
let key = key.trim();
if key.starts_with("nsec1") {
return key.to_string();
}
nostr_sdk::SecretKey::from_hex(key)
.ok()
.and_then(|sk| sk.to_bech32().ok())
.unwrap_or_else(|| key.to_string())
}
/// Configure NostrVPN with the node's Nostr identity.
/// Writes both the env file (for systemd) and config.toml (for vpn.invite/status).
pub async fn configure_nostr_vpn(data_dir: &Path) -> Result<()> {
let nostr_secret = tokio::fs::read_to_string(
let nostr_secret_hex = 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(
let nostr_pubkey_hex = tokio::fs::read_to_string(
data_dir.join("identity/nostr_pubkey")
).await.unwrap_or_default();
let nostr_secret_hex = nostr_secret_hex.trim();
let nostr_pubkey_hex = nostr_pubkey_hex.trim();
if nostr_pubkey_hex.is_empty() {
anyhow::bail!("Empty Nostr public key — identity not ready");
}
// Convert hex keys to bech32 (npub1.../nsec1...)
let npub = ensure_npub(nostr_pubkey_hex);
let nsec = ensure_nsec(nostr_secret_hex);
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()
nostr_secret_hex, nostr_pubkey_hex
);
tokio::fs::write(vpn_dir.join("env"), &env_content)
.await
@@ -368,6 +406,55 @@ pub async fn configure_nostr_vpn(data_dir: &Path) -> Result<()> {
).ok();
}
// Write nvpn config.toml so vpn.invite and vpn.status can read the node identity.
// This is the primary fix: previously only env was written, but all VPN RPC handlers
// read from config.toml — not env vars.
let config_dir = vpn_dir.join(".config/nvpn");
tokio::fs::create_dir_all(&config_dir).await.context("Failed to create nvpn config dir")?;
let config_path = config_dir.join("config.toml");
// Only write if config doesn't exist or has no public_key
// (avoid clobbering participants list added by vpn.add-participant)
let should_write = if let Ok(existing) = tokio::fs::read_to_string(&config_path).await {
!existing.contains("public_key")
} else {
true
};
if should_write {
// Gather relay URLs for the config
let (relay_onion, relay_direct) = get_relay_urls().await;
let mut relays = Vec::new();
if let Some(ref onion) = relay_onion {
relays.push(format!("\"{}\"", onion));
}
if let Some(ref direct) = relay_direct {
relays.push(format!("\"{}\"", direct));
}
if relays.is_empty() {
relays.push("\"wss://relay.damus.io\"".to_string());
relays.push("\"wss://relay.primal.net\"".to_string());
}
let config_toml = format!(
"[nostr]\npublic_key = \"{npub}\"\nsecret_key = \"{nsec}\"\nrelays = [{relays}]\n\n[[networks]]\nnetwork_id = \"archipelago\"\nparticipants = []\n",
npub = npub,
nsec = nsec,
relays = relays.join(", "),
);
tokio::fs::write(&config_path, &config_toml)
.await
.context("Failed to write nvpn config.toml")?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&config_path, std::fs::Permissions::from_mode(0o600)).ok();
}
tracing::info!("Wrote nvpn config.toml with node npub");
}
// Reset any previous failure state (systemd rate-limits restarts before onboarding)
let _ = tokio::process::Command::new("systemctl")
.args(["reset-failed", "nostr-vpn"])