fix(apps): repair netbird login and iframe focus

This commit is contained in:
archipelago
2026-05-19 19:21:43 -04:00
parent eeb08fc78f
commit bd69ef41d5
13 changed files with 281 additions and 77 deletions

2
core/Cargo.lock generated
View File

@@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "archipelago"
version = "1.7.72-alpha"
version = "1.7.74-alpha"
dependencies = [
"anyhow",
"archipelago-container",

View File

@@ -1,6 +1,6 @@
[package]
name = "archipelago"
version = "1.7.73-alpha"
version = "1.7.74-alpha"
edition = "2021"
description = "Archipelago Bitcoin Node OS - Native backend"
authors = ["Archipelago Team"]

View File

@@ -495,7 +495,11 @@ pub(super) fn all_container_names(package_id: &str) -> Vec<String> {
"indeedhub-ffmpeg".into(),
"indeedhub".into(),
],
"netbird" => vec!["netbird".into(), "netbird-server".into()],
"netbird" => vec![
"netbird".into(),
"netbird-dashboard".into(),
"netbird-server".into(),
],
"nostr-vpn" => vec![
"nostr-vpn".into(),
"archy-nostr-vpn".into(),

View File

@@ -288,7 +288,7 @@ pub(super) fn startup_order(package_id: &str) -> &'static [&'static str] {
"btcpay-server" | "btcpayserver" | "btcpay" => {
&["archy-btcpay-db", "archy-nbxplorer", "btcpay-server"]
}
"netbird" => &["netbird-server", "netbird"],
"netbird" => &["netbird-server", "netbird-dashboard", "netbird"],
"penpot" | "penpot-frontend" => &[
"penpot-postgres",
"penpot-valkey",
@@ -392,7 +392,10 @@ mod tests {
#[test]
fn netbird_start_order_starts_server_before_dashboard() {
assert_eq!(startup_order("netbird"), &["netbird-server", "netbird"]);
assert_eq!(
startup_order("netbird"),
&["netbird-server", "netbird-dashboard", "netbird"]
);
}
#[test]

View File

@@ -98,6 +98,7 @@ async fn repair_stack_before_adopt(stack_name: &str) {
}
}
"indeedhub" => repair_indeedhub_network_aliases().await,
"netbird" => repair_netbird_unified_origin().await,
_ => {}
}
}
@@ -144,6 +145,73 @@ pub(in crate::api::rpc::package) async fn repair_indeedhub_network_aliases() {
}
}
async fn repair_netbird_unified_origin() {
let host_ip = detect_netbird_public_host_ip()
.await
.unwrap_or_else(|| "127.0.0.1".to_string());
let _ = write_netbird_config_files(&host_ip).await;
let names = tokio::process::Command::new("podman")
.args(["ps", "-a", "--format", "{{.Names}}"])
.output()
.await
.ok()
.map(|o| String::from_utf8_lossy(&o.stdout).to_string())
.unwrap_or_default();
let has_proxy = names.lines().any(|n| n.trim() == "netbird");
let has_dashboard = names.lines().any(|n| n.trim() == "netbird-dashboard");
if has_proxy && has_dashboard {
return;
}
if has_proxy && !has_dashboard {
let _ = tokio::process::Command::new("podman")
.args(["rm", "-f", "netbird"])
.output()
.await;
}
let _ = tokio::process::Command::new("podman")
.args(["network", "create", "netbird-net"])
.output()
.await;
let _ = tokio::process::Command::new("podman")
.args([
"run",
"-d",
"--name",
"netbird-dashboard",
"--network",
"netbird-net",
"--restart=unless-stopped",
"--env-file",
"/var/lib/archipelago/netbird/dashboard.env",
NETBIRD_DASHBOARD_IMAGE,
])
.output()
.await;
let _ = tokio::process::Command::new("podman")
.args([
"run",
"-d",
"--name",
"netbird",
"--network",
"netbird-net",
"--restart=unless-stopped",
"-p",
"8087:80",
"-v",
"/var/lib/archipelago/netbird/nginx.conf:/etc/nginx/conf.d/default.conf:ro",
NETBIRD_PROXY_IMAGE,
])
.output()
.await;
}
async fn run_required_stack_command(
stack_name: &str,
label: &str,
@@ -313,6 +381,7 @@ const REGISTRY: &str = "146.59.87.168:3000/lfg2025";
const NETBIRD_DASHBOARD_IMAGE: &str = "docker.io/netbirdio/dashboard:v2.38.0";
const NETBIRD_SERVER_IMAGE: &str = "docker.io/netbirdio/netbird-server:0.71.2";
const NETBIRD_PROXY_IMAGE: &str = "docker.io/library/nginx:1.27-alpine";
/// Pull an image with retry and exponential backoff (3 attempts).
async fn pull_image_with_retry(image: &str) -> Result<()> {
@@ -1364,8 +1433,12 @@ impl RpcHandler {
/// Install self-hosted NetBird (dashboard + combined management/signal/relay server).
pub(super) async fn install_netbird_stack(&self) -> Result<serde_json::Value> {
if let Some(adopted) =
adopt_stack_if_exists("netbird", "netbird", &["netbird", "netbird-server"]).await?
if let Some(adopted) = adopt_stack_if_exists(
"netbird",
"netbird",
&["netbird-server", "netbird-dashboard", "netbird"],
)
.await?
{
return Ok(adopted);
}
@@ -1375,18 +1448,22 @@ impl RpcHandler {
self.set_install_phase("netbird", InstallPhase::PullingImage)
.await;
for (i, image) in [NETBIRD_DASHBOARD_IMAGE, NETBIRD_SERVER_IMAGE]
.iter()
.enumerate()
for (i, image) in [
NETBIRD_DASHBOARD_IMAGE,
NETBIRD_SERVER_IMAGE,
NETBIRD_PROXY_IMAGE,
]
.iter()
.enumerate()
{
self.set_install_progress("netbird", i as u64, 2).await;
self.set_install_progress("netbird", i as u64, 3).await;
pull_image_with_retry(image)
.await
.with_context(|| format!("Failed to pull NetBird image: {}", image))?;
}
self.set_install_progress("netbird", 2, 2).await;
self.set_install_progress("netbird", 3, 3).await;
for name in ["netbird", "netbird-server"] {
for name in ["netbird", "netbird-dashboard", "netbird-server"] {
let _ = tokio::process::Command::new("podman")
.args(["rm", "-f", name])
.status()
@@ -1407,58 +1484,7 @@ impl RpcHandler {
let host_ip = detect_netbird_public_host_ip()
.await
.unwrap_or_else(|| self.config.host_ip.clone());
let dashboard_origin = format!("http://{}:8087", host_ip);
let mgmt_origin = format!("http://{}:8086", host_ip);
let relay_secret = read_or_generate_b64_secret("netbird-relay-auth-secret").await;
let encryption_key = read_or_generate_b64_secret("netbird-store-encryption-key").await;
let config = format!(
r#"server:
listenAddress: ":80"
exposedAddress: "{mgmt_origin}"
stunPorts:
- 3478
metricsPort: 9090
healthcheckAddress: ":9000"
logLevel: "info"
logFile: "console"
authSecret: "{relay_secret}"
dataDir: "/var/lib/netbird"
auth:
issuer: "{mgmt_origin}/oauth2"
localAuthDisabled: false
signKeyRefreshEnabled: true
dashboardRedirectURIs:
- "{dashboard_origin}/nb-auth"
- "{dashboard_origin}/nb-silent-auth"
cliRedirectURIs:
- "http://localhost:53000/"
store:
engine: "sqlite"
encryptionKey: "{encryption_key}"
"#
);
tokio::fs::write("/var/lib/archipelago/netbird/config.yaml", config)
.await
.context("Failed to write NetBird config.yaml")?;
let dashboard_env = format!(
r#"NETBIRD_MGMT_API_ENDPOINT={mgmt_origin}
NETBIRD_MGMT_GRPC_API_ENDPOINT={mgmt_origin}
AUTH_AUDIENCE=netbird-dashboard
AUTH_CLIENT_ID=netbird-dashboard
AUTH_CLIENT_SECRET=
AUTH_AUTHORITY={mgmt_origin}/oauth2
USE_AUTH0=false
AUTH_SUPPORTED_SCOPES=openid profile email groups
AUTH_REDIRECT_URI=/nb-auth
AUTH_SILENT_REDIRECT_URI=/nb-silent-auth
NGINX_SSL_PORT=443
LETSENCRYPT_DOMAIN=none
"#
);
tokio::fs::write("/var/lib/archipelago/netbird/dashboard.env", dashboard_env)
.await
.context("Failed to write NetBird dashboard.env")?;
write_netbird_config_files(&host_ip).await?;
let _ = tokio::process::Command::new("podman")
.args(["network", "create", "netbird-net"])
@@ -1499,19 +1525,39 @@ LETSENCRYPT_DOMAIN=none
"run",
"-d",
"--name",
"netbird",
"netbird-dashboard",
"--network",
"netbird-net",
"--restart=unless-stopped",
"-p",
"8087:80",
"--env-file",
"/var/lib/archipelago/netbird/dashboard.env",
NETBIRD_DASHBOARD_IMAGE,
]);
run_required_stack_command("netbird", "create dashboard", &mut dashboard_cmd).await?;
wait_for_stack_containers("netbird", &["netbird-server", "netbird"], 60).await?;
let mut proxy_cmd = tokio::process::Command::new("podman");
proxy_cmd.args([
"run",
"-d",
"--name",
"netbird",
"--network",
"netbird-net",
"--restart=unless-stopped",
"-p",
"8087:80",
"-v",
"/var/lib/archipelago/netbird/nginx.conf:/etc/nginx/conf.d/default.conf:ro",
NETBIRD_PROXY_IMAGE,
]);
run_required_stack_command("netbird", "create unified proxy", &mut proxy_cmd).await?;
wait_for_stack_containers(
"netbird",
&["netbird-server", "netbird-dashboard", "netbird"],
60,
)
.await?;
self.set_install_phase("netbird", InstallPhase::WaitingHealthy)
.await;
@@ -1546,6 +1592,104 @@ async fn read_or_generate_b64_secret(name: &str) -> String {
secret
}
async fn write_netbird_config_files(host_ip: &str) -> Result<()> {
let public_origin = format!("http://{}:8087", host_ip);
let server_origin = format!("http://{}:8086", host_ip);
let relay_secret = read_or_generate_b64_secret("netbird-relay-auth-secret").await;
let encryption_key = read_or_generate_b64_secret("netbird-store-encryption-key").await;
let config = format!(
r#"server:
listenAddress: ":80"
exposedAddress: "{public_origin}"
stunPorts:
- 3478
metricsPort: 9090
healthcheckAddress: ":9000"
logLevel: "info"
logFile: "console"
authSecret: "{relay_secret}"
dataDir: "/var/lib/netbird"
auth:
issuer: "{public_origin}/oauth2"
localAuthDisabled: false
signKeyRefreshEnabled: true
dashboardRedirectURIs:
- "{public_origin}/nb-auth"
- "{public_origin}/nb-silent-auth"
cliRedirectURIs:
- "http://localhost:53000/"
store:
engine: "sqlite"
encryptionKey: "{encryption_key}"
"#
);
tokio::fs::write("/var/lib/archipelago/netbird/config.yaml", config)
.await
.context("Failed to write NetBird config.yaml")?;
let dashboard_env = format!(
r#"NETBIRD_MGMT_API_ENDPOINT={public_origin}
NETBIRD_MGMT_GRPC_API_ENDPOINT={public_origin}
AUTH_AUDIENCE=netbird-dashboard
AUTH_CLIENT_ID=netbird-dashboard
AUTH_CLIENT_SECRET=
AUTH_AUTHORITY={public_origin}/oauth2
USE_AUTH0=false
AUTH_SUPPORTED_SCOPES=openid profile email groups
AUTH_REDIRECT_URI=/nb-auth
AUTH_SILENT_REDIRECT_URI=/nb-silent-auth
NETBIRD_TOKEN_SOURCE=accessToken
NGINX_SSL_PORT=443
LETSENCRYPT_DOMAIN=none
"#
);
tokio::fs::write("/var/lib/archipelago/netbird/dashboard.env", dashboard_env)
.await
.context("Failed to write NetBird dashboard.env")?;
let nginx_conf = format!(
r#"server {{
listen 80;
server_name _;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
location ~ ^/(relay|ws-proxy/) {{
proxy_pass http://netbird-server:80;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 1d;
}}
location ~ ^/(api|oauth2)/ {{
proxy_pass http://netbird-server:80;
}}
location ~ ^/(signalexchange\.SignalExchange|management\.ManagementService)/ {{
grpc_pass grpc://netbird-server:80;
grpc_read_timeout 1d;
grpc_send_timeout 1d;
}}
location / {{
proxy_pass http://netbird-dashboard:80;
}}
}}
# Direct server remains available for diagnostics at {server_origin}.
"#
);
tokio::fs::write("/var/lib/archipelago/netbird/nginx.conf", nginx_conf)
.await
.context("Failed to write NetBird nginx.conf")?;
Ok(())
}
async fn detect_netbird_public_host_ip() -> Option<String> {
let output = tokio::process::Command::new("hostname")
.args(["-I"])

View File

@@ -62,6 +62,7 @@ impl DockerPackageScanner {
"indeedhub-build_relay_1",
"indeedhub-build_ffmpeg-worker_1",
"netbird-server",
"netbird-dashboard",
"buildx_buildkit_default",
];

View File

@@ -169,6 +169,7 @@ fn image_var_for_app(app_id: &str) -> Option<&'static str> {
"portainer" => Some("PORTAINER_IMAGE"),
"tailscale" => Some("TAILSCALE_IMAGE"),
"netbird" => Some("NETBIRD_DASHBOARD_IMAGE"),
"netbird-dashboard" => Some("NETBIRD_DASHBOARD_IMAGE"),
"netbird-server" => Some("NETBIRD_SERVER_IMAGE"),
// Fedimint
@@ -302,7 +303,8 @@ pub fn containers_for_stack(app_id: &str) -> Vec<(&'static str, &'static str)> {
("penpot-frontend", "PENPOT_FRONTEND_IMAGE"),
],
"netbird" => vec![
("netbird", "NETBIRD_DASHBOARD_IMAGE"),
("netbird", "NETBIRD_PROXY_IMAGE"),
("netbird-dashboard", "NETBIRD_DASHBOARD_IMAGE"),
("netbird-server", "NETBIRD_SERVER_IMAGE"),
],
_ => vec![],