From d736364ad74ba1b739fcc855ce2d8fd7d55b86ac Mon Sep 17 00:00:00 2001 From: archipelago Date: Tue, 19 May 2026 09:26:43 -0400 Subject: [PATCH] fix(apps): stabilize btcpay and public proxy launch flows --- CHANGELOG.md | 13 ++ app-catalog/catalog.json | 4 +- apps/btcpay-server/manifest.yml | 4 +- .../archipelago/src/api/rpc/package/config.rs | 2 +- .../src/api/rpc/package/runtime.rs | 33 +++- .../archipelago/src/api/rpc/package/stacks.rs | 4 +- docs/container-architecture.html | 2 +- image-recipe/configs/nginx-archipelago.conf | 11 +- neode-ui/public/catalog.json | 4 +- neode-ui/src/views/AppSession.vue | 17 +- neode-ui/src/views/SystemUpdate.vue | 174 +++++++++--------- neode-ui/src/views/apps/AppCard.vue | 2 +- neode-ui/src/views/discover/curatedApps.ts | 2 +- .../src/views/marketplace/marketplaceData.ts | 4 +- scripts/container-doctor.sh | 17 ++ scripts/container-specs.sh | 2 +- scripts/deploy-tailscale.sh | 5 +- scripts/first-boot-containers.sh | 11 +- scripts/image-versions.sh | 2 +- scripts/self-update.sh | 2 +- scripts/sync-npm-public-hosts.sh | 119 ++++++++++++ tests/lifecycle/remote-lifecycle.sh | 2 +- 22 files changed, 322 insertions(+), 114 deletions(-) create mode 100644 scripts/sync-npm-public-hosts.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index bfe48e08..a3417ac6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## v1.7.68-alpha (2026-05-19) + +- BTCPay Server now ships on the official `docker.io/btcpayserver/btcpayserver:2.3.9` image, fixing the plugin catalog crash caused by newer plugin dependency version metadata while preserving existing datadirs and Postgres databases. +- BTCPay release and first-boot health checks no longer depend on `curl` inside the container; they use a bash TCP probe that works with the official image out of the box. +- Host nginx now serves Nginx Proxy Manager HTTP-01 challenge files before the Archipelago SPA fallback and is marked as the default HTTP/HTTPS virtual host, so public proxy hosts can issue certificates without hijacking local API traffic. +- Nginx Proxy Manager first-boot, runtime repair, and container-doctor paths now pre-create the ACME webroot, keep bind mounts owned by the rootless Archipelago user, and sync issued public proxy hosts into host nginx vhosts. +- The Nginx Proxy Manager host-nginx sync now skips proxy hosts with missing certificate files and rolls back the generated nginx include if validation fails, preventing a bad certificate path from poisoning later nginx reloads. +- App session close buttons now return to the previous dashboard screen when possible and otherwise fall back to My Apps, avoiding the 404 page after closing an app launched from an invalid or stale history entry. +- System Update confirmation and mirror modals now teleport to the document body with a full-screen overlay, so they cover the whole app instead of only the right-hand dashboard panel. +- Mobile app launches stay inside Archipelago's app-session webview and hide desktop-only new-tab launch affordances, including apps such as Home Assistant that previously looked like they would leave the mobile shell. +- Live recovery on `100.70.96.88` upgraded only the `btcpay-server` container to `docker.io/btcpayserver/btcpayserver:2.3.9`, preserved the existing datadir and Postgres database, and confirmed the container is healthy after a pre-upgrade backup. +- Public validation confirmed `spay.tx1138.com`/`www` redirect to BTCPay login over HTTPS and `sapien.tx1138.com`/`www` serve the L484 page over HTTPS using the issued Let's Encrypt certificates. + ## v1.7.67-alpha (2026-05-18) - Home dashboard status cards now keep the last known good system, VPN, Bitcoin, and FIPS values while route changes or transient RPC failures are in flight, avoiding false "not configured" or "not running" flashes. diff --git a/app-catalog/catalog.json b/app-catalog/catalog.json index 275d52c7..61904087 100644 --- a/app-catalog/catalog.json +++ b/app-catalog/catalog.json @@ -52,13 +52,13 @@ { "id": "btcpay-server", "title": "BTCPay Server", - "version": "1.13.7", + "version": "2.3.9", "description": "Self-hosted Bitcoin payment processor.", "icon": "/assets/img/app-icons/btcpay-server.png", "author": "BTCPay Server Foundation", "category": "commerce", "tier": "core", - "dockerImage": "146.59.87.168:3000/lfg2025/btcpayserver:1.13.7", + "dockerImage": "docker.io/btcpayserver/btcpayserver:2.3.9", "repoUrl": "https://github.com/btcpayserver/btcpayserver", "requires": [ "bitcoin-knots" diff --git a/apps/btcpay-server/manifest.yml b/apps/btcpay-server/manifest.yml index 2cea54ee..6c42c267 100644 --- a/apps/btcpay-server/manifest.yml +++ b/apps/btcpay-server/manifest.yml @@ -1,11 +1,11 @@ app: id: btcpay-server name: BTCPay Server - version: 1.13.7 + version: 2.3.9 description: Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries. container: - image: git.tx1138.com/lfg2025/btcpayserver:1.13.7 + image: docker.io/btcpayserver/btcpayserver:2.3.9 pull_policy: if-not-present network: archy-net secret_env: diff --git a/core/archipelago/src/api/rpc/package/config.rs b/core/archipelago/src/api/rpc/package/config.rs index 8262818e..cbc25897 100644 --- a/core/archipelago/src/api/rpc/package/config.rs +++ b/core/archipelago/src/api/rpc/package/config.rs @@ -262,7 +262,7 @@ pub(super) fn get_health_check_args(app_id: &str, _rpc_pass: &str) -> Vec return vec![], "lnd" => ("lncli getinfo || exit 1", "30s", "3"), "btcpay-server" | "btcpayserver" => { - ("curl -sf http://localhost:49392/ || exit 1", "30s", "3") + ("bash -ec ' ( http_probe_cmd("http://localhost:8999/api/v1/backend-info"), diff --git a/core/archipelago/src/api/rpc/package/runtime.rs b/core/archipelago/src/api/rpc/package/runtime.rs index 0048f88c..1d06ddf1 100644 --- a/core/archipelago/src/api/rpc/package/runtime.rs +++ b/core/archipelago/src/api/rpc/package/runtime.rs @@ -874,6 +874,7 @@ async fn repair_before_package_start(container_name: &str) { } async fn repair_nginx_proxy_manager_container() { + repair_nginx_proxy_manager_dirs().await; if !nginx_proxy_manager_has_legacy_admin_port().await { cleanup_nginx_proxy_manager_ports().await; return; @@ -890,6 +891,27 @@ async fn repair_nginx_proxy_manager_container() { } } +async fn repair_nginx_proxy_manager_dirs() { + let _ = tokio::process::Command::new("sudo") + .args([ + "mkdir", + "-p", + "/var/lib/archipelago/nginx-proxy-manager/data/letsencrypt-acme-challenge/.well-known/acme-challenge", + "/var/lib/archipelago/nginx-proxy-manager/letsencrypt", + ]) + .output() + .await; + let _ = tokio::process::Command::new("sudo") + .args([ + "chown", + "-R", + "1000:1000", + "/var/lib/archipelago/nginx-proxy-manager", + ]) + .output() + .await; +} + async fn nginx_proxy_manager_has_legacy_admin_port() -> bool { if let Ok(output) = podman_control(&["port", "nginx-proxy-manager", "81/tcp"]).await { if output.status.success() @@ -927,12 +949,21 @@ async fn recreate_nginx_proxy_manager_container() -> Result<()> { .args([ "mkdir", "-p", - "/var/lib/archipelago/nginx-proxy-manager/data", + "/var/lib/archipelago/nginx-proxy-manager/data/letsencrypt-acme-challenge/.well-known/acme-challenge", "/var/lib/archipelago/nginx-proxy-manager/letsencrypt", ]) .output() .await .context("failed to create nginx-proxy-manager data directories")?; + let _ = tokio::process::Command::new("sudo") + .args([ + "chown", + "-R", + "1000:1000", + "/var/lib/archipelago/nginx-proxy-manager", + ]) + .output() + .await; let image = crate::container::image_versions::pinned_image_for_app("nginx-proxy-manager") .unwrap_or_else(|| "docker.io/jc21/nginx-proxy-manager:latest".to_string()); diff --git a/core/archipelago/src/api/rpc/package/stacks.rs b/core/archipelago/src/api/rpc/package/stacks.rs index 73962c72..4b7639e0 100644 --- a/core/archipelago/src/api/rpc/package/stacks.rs +++ b/core/archipelago/src/api/rpc/package/stacks.rs @@ -587,7 +587,7 @@ impl RpcHandler { let images = [ &format!("{}/postgres:15.17", REGISTRY), &format!("{}/nbxplorer:2.6.0", REGISTRY), - &format!("{}/btcpayserver:1.13.7", REGISTRY), + "docker.io/btcpayserver/btcpayserver:2.3.9", ]; self.set_install_phase("btcpay-server", InstallPhase::PullingImage) .await; @@ -743,7 +743,7 @@ impl RpcHandler { "BTCPAY_POSTGRES=User ID=btcpay;Password={};Host=archy-btcpay-db;Port=5432;Database=btcpay;Include Error Detail=true", db_pass ), - &format!("{}/btcpayserver:1.13.7", REGISTRY), + "docker.io/btcpayserver/btcpayserver:2.3.9", ]) .output() .await diff --git a/docs/container-architecture.html b/docs/container-architecture.html index 2ff7363e..2a0fe468 100644 --- a/docs/container-architecture.html +++ b/docs/container-architecture.html @@ -1169,7 +1169,7 @@
btcpay-serverarchy-net
Self-hosted Bitcoin payment processor. Accept Bitcoin payments with invoices, checkout pages, and POS.
Your own payment terminal for Bitcoin. Create invoices, get paid, no middleman taking a cut.
-
btcpayserver:1.13.7
+
btcpayserver:2.3.9
Ports: 23000
Memory1 GB
diff --git a/image-recipe/configs/nginx-archipelago.conf b/image-recipe/configs/nginx-archipelago.conf index d52e9856..933ba3a7 100644 --- a/image-recipe/configs/nginx-archipelago.conf +++ b/image-recipe/configs/nginx-archipelago.conf @@ -8,7 +8,7 @@ resolver 1.1.1.1 8.8.8.8 valid=300s ipv6=off; resolver_timeout 5s; server { - listen 80; + listen 80 default_server; server_name _; root /opt/archipelago/web-ui; @@ -23,6 +23,13 @@ server { add_header X-DNS-Prefetch-Control "off" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://*.basemaps.cartocdn.com https://tile.openstreetmap.org; font-src 'self' data:; connect-src 'self' ws: wss: http://$host:* https:; frame-src 'self' http://$host:* https:; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always; + # Serve Nginx Proxy Manager HTTP-01 challenge files before the SPA fallback. + location ^~ /.well-known/acme-challenge/ { + default_type text/plain; + root /var/lib/archipelago/nginx-proxy-manager/data/letsencrypt-acme-challenge; + try_files $uri =404; + } + # AIUI SPA (Chat mode iframe) — SPA fallback for client-side routing location /aiui/ { try_files $uri $uri/ /aiui/index.html; @@ -908,7 +915,7 @@ server { # HTTPS - required for PWA install (Add to Home Screen) from dev servers server { - listen 443 ssl; + listen 443 ssl default_server; server_name _; ssl_certificate /etc/archipelago/ssl/archipelago.crt; diff --git a/neode-ui/public/catalog.json b/neode-ui/public/catalog.json index 275d52c7..61904087 100644 --- a/neode-ui/public/catalog.json +++ b/neode-ui/public/catalog.json @@ -52,13 +52,13 @@ { "id": "btcpay-server", "title": "BTCPay Server", - "version": "1.13.7", + "version": "2.3.9", "description": "Self-hosted Bitcoin payment processor.", "icon": "/assets/img/app-icons/btcpay-server.png", "author": "BTCPay Server Foundation", "category": "commerce", "tier": "core", - "dockerImage": "146.59.87.168:3000/lfg2025/btcpayserver:1.13.7", + "dockerImage": "docker.io/btcpayserver/btcpayserver:2.3.9", "repoUrl": "https://github.com/btcpayserver/btcpayserver", "requires": [ "bitcoin-knots" diff --git a/neode-ui/src/views/AppSession.vue b/neode-ui/src/views/AppSession.vue index ca9071e3..34635780 100644 --- a/neode-ui/src/views/AppSession.vue +++ b/neode-ui/src/views/AppSession.vue @@ -160,6 +160,19 @@ const appUrl = computed(() => { return resolveAppUrl(appId.value, route.query.path as string | undefined) }) +function closeRouteSession() { + const fallback = route.query.returnTo + const fallbackPath = typeof fallback === 'string' && fallback.startsWith('/dashboard') + ? fallback + : '/dashboard/apps' + const previous = router.options.history.state.back + if (typeof previous === 'string' && previous.startsWith('/dashboard') && router.resolve(previous).name !== 'app-session') { + router.back() + return + } + router.replace(fallbackPath).catch(() => {}) +} + // --- Identity & Nostr bridge --- const iframeRef = computed(() => frameRef.value?.iframeRef ?? null) @@ -295,7 +308,7 @@ function handleBackdropClick() { function closeSession() { if (document.fullscreenElement) document.exitFullscreen().catch(() => {}) if (isInlinePanel.value) emit('close') - else router.back() + else closeRouteSession() } function onKeyDown(e: KeyboardEvent) { @@ -332,7 +345,7 @@ onMounted(() => { if (mustOpenNewTab.value && appUrl.value) { window.open(appUrl.value, '_blank', 'noopener,noreferrer') if (isInlinePanel.value) emit('close') - else router.back() + else closeRouteSession() return } diff --git a/neode-ui/src/views/SystemUpdate.vue b/neode-ui/src/views/SystemUpdate.vue index 5ef44c66..d62b7f2d 100644 --- a/neode-ui/src/views/SystemUpdate.vue +++ b/neode-ui/src/views/SystemUpdate.vue @@ -344,94 +344,96 @@ - - -
-
-

Add update mirror

-

- The URL should point directly at a manifest.json served by a Gitea mirror or equivalent. It's added to the end of the list; use "Make primary" to change order. -

-
-
- - -
-
- - -
-
- - -
-
-
-
-
- - - -
-
-

- {{ confirmAction === 'rollback' - ? t('systemUpdate.rollbackTitle') - : confirmAction === 'git-apply' - ? t('systemUpdate.gitApplyTitle') - : confirmAction === 'cancel-download' - ? t('systemUpdate.cancelDownloadTitle') - : t('systemUpdate.applyTitle') }} -

-

- {{ confirmAction === 'rollback' - ? t('systemUpdate.rollbackMessage') - : confirmAction === 'git-apply' - ? t('systemUpdate.gitApplyMessage') - : confirmAction === 'cancel-download' - ? t('systemUpdate.cancelDownloadConfirm') - : t('systemUpdate.applyMessage') }} -

-
- - + + + +
+
+

Add update mirror

+

+ The URL should point directly at a manifest.json served by a Gitea mirror or equivalent. It's added to the end of the list; use "Make primary" to change order. +

+
+
+ + +
+
+ + +
+
+ + +
+
-
- + + + + +
+
+

+ {{ confirmAction === 'rollback' + ? t('systemUpdate.rollbackTitle') + : confirmAction === 'git-apply' + ? t('systemUpdate.gitApplyTitle') + : confirmAction === 'cancel-download' + ? t('systemUpdate.cancelDownloadTitle') + : t('systemUpdate.applyTitle') }} +

+

+ {{ confirmAction === 'rollback' + ? t('systemUpdate.rollbackMessage') + : confirmAction === 'git-apply' + ? t('systemUpdate.gitApplyMessage') + : confirmAction === 'cancel-download' + ? t('systemUpdate.cancelDownloadConfirm') + : t('systemUpdate.applyMessage') }} +

+
+ + +
+
+
+
+
diff --git a/neode-ui/src/views/apps/AppCard.vue b/neode-ui/src/views/apps/AppCard.vue index 983b92e5..40a99fb3 100644 --- a/neode-ui/src/views/apps/AppCard.vue +++ b/neode-ui/src/views/apps/AppCard.vue @@ -145,7 +145,7 @@ class="flex-1 px-4 py-2 glass-button glass-button-sm rounded-lg text-sm font-medium flex items-center justify-center gap-1.5" > {{ t('common.launch') }} - +