From bd69ef41d5551f7b5dfc14827985569af4e24702 Mon Sep 17 00:00:00 2001 From: archipelago Date: Tue, 19 May 2026 19:21:43 -0400 Subject: [PATCH] fix(apps): repair netbird login and iframe focus --- CHANGELOG.md | 8 + core/Cargo.lock | 2 +- core/archipelago/Cargo.toml | 2 +- .../archipelago/src/api/rpc/package/config.rs | 6 +- .../src/api/rpc/package/dependencies.rs | 7 +- .../archipelago/src/api/rpc/package/stacks.rs | 272 +++++++++++++----- .../src/container/docker_packages.rs | 1 + .../src/container/image_versions.rs | 4 +- neode-ui/package-lock.json | 4 +- neode-ui/package.json | 2 +- .../src/views/appSession/AppSessionFrame.vue | 24 +- .../src/views/settings/AccountInfoSection.vue | 25 ++ scripts/image-versions.sh | 1 + 13 files changed, 281 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95788f2d..1824fd84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## v1.7.74-alpha (2026-05-19) + +- App-session right panels now re-focus the iframe after load and when the frame area is activated, so wheel/touch scrolling works immediately after switching tabs or selecting an app on shorter screens. +- NetBird now launches through a unified local origin on port `8087` that proxies the dashboard plus `/oauth2`, `/api`, relay, WebSocket, and gRPC routes to `netbird-server`, fixing the embedded login flow that previously ended in `Unauthenticated` or `404 page not found` after logout. +- Existing NetBird installs are repaired on adopt/start by rewriting `config.yaml`, `dashboard.env`, and the local nginx proxy config, then creating the missing `netbird-dashboard` and `netbird` proxy containers when needed while preserving NetBird data. +- Saleor is still pending and is not included in this release; its registry/installer work remains local until it can be validated separately. +- Validation passed with catalog JSON checks, `npm run type-check`, `cargo fmt --all --check --manifest-path core/Cargo.toml`, and `cargo check -p archipelago --manifest-path core/Cargo.toml`. + ## v1.7.73-alpha (2026-05-19) - Mobile app launches for iframe-blocked apps now open the direct app URL in a new browser tab immediately instead of landing in a broken in-shell webview that requires a second tap. diff --git a/core/Cargo.lock b/core/Cargo.lock index 5849e0ac..81927647 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "archipelago" -version = "1.7.72-alpha" +version = "1.7.74-alpha" dependencies = [ "anyhow", "archipelago-container", diff --git a/core/archipelago/Cargo.toml b/core/archipelago/Cargo.toml index c6647d72..0294837b 100644 --- a/core/archipelago/Cargo.toml +++ b/core/archipelago/Cargo.toml @@ -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"] diff --git a/core/archipelago/src/api/rpc/package/config.rs b/core/archipelago/src/api/rpc/package/config.rs index 5cc40a68..13df17d0 100644 --- a/core/archipelago/src/api/rpc/package/config.rs +++ b/core/archipelago/src/api/rpc/package/config.rs @@ -495,7 +495,11 @@ pub(super) fn all_container_names(package_id: &str) -> Vec { "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(), diff --git a/core/archipelago/src/api/rpc/package/dependencies.rs b/core/archipelago/src/api/rpc/package/dependencies.rs index b6c10ba7..8e2e7af2 100644 --- a/core/archipelago/src/api/rpc/package/dependencies.rs +++ b/core/archipelago/src/api/rpc/package/dependencies.rs @@ -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] diff --git a/core/archipelago/src/api/rpc/package/stacks.rs b/core/archipelago/src/api/rpc/package/stacks.rs index 2b97eb01..ce4c327f 100644 --- a/core/archipelago/src/api/rpc/package/stacks.rs +++ b/core/archipelago/src/api/rpc/package/stacks.rs @@ -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 { - 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 { let output = tokio::process::Command::new("hostname") .args(["-I"]) diff --git a/core/archipelago/src/container/docker_packages.rs b/core/archipelago/src/container/docker_packages.rs index f7b13e12..f2b09d9d 100644 --- a/core/archipelago/src/container/docker_packages.rs +++ b/core/archipelago/src/container/docker_packages.rs @@ -62,6 +62,7 @@ impl DockerPackageScanner { "indeedhub-build_relay_1", "indeedhub-build_ffmpeg-worker_1", "netbird-server", + "netbird-dashboard", "buildx_buildkit_default", ]; diff --git a/core/archipelago/src/container/image_versions.rs b/core/archipelago/src/container/image_versions.rs index fd41353f..d15756a9 100644 --- a/core/archipelago/src/container/image_versions.rs +++ b/core/archipelago/src/container/image_versions.rs @@ -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![], diff --git a/neode-ui/package-lock.json b/neode-ui/package-lock.json index 3114d5b3..c67b0849 100644 --- a/neode-ui/package-lock.json +++ b/neode-ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "neode-ui", - "version": "1.7.73-alpha", + "version": "1.7.74-alpha", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "neode-ui", - "version": "1.7.73-alpha", + "version": "1.7.74-alpha", "dependencies": { "@types/dompurify": "^3.0.5", "@vue-leaflet/vue-leaflet": "^0.10.1", diff --git a/neode-ui/package.json b/neode-ui/package.json index b5c13eba..538f4584 100644 --- a/neode-ui/package.json +++ b/neode-ui/package.json @@ -1,7 +1,7 @@ { "name": "neode-ui", "private": true, - "version": "1.7.73-alpha", + "version": "1.7.74-alpha", "type": "module", "scripts": { "start": "./start-dev.sh", diff --git a/neode-ui/src/views/appSession/AppSessionFrame.vue b/neode-ui/src/views/appSession/AppSessionFrame.vue index 26d93d31..cf750f9d 100644 --- a/neode-ui/src/views/appSession/AppSessionFrame.vue +++ b/neode-ui/src/views/appSession/AppSessionFrame.vue @@ -9,7 +9,13 @@ -
+