fix(apps): stabilize btcpay and public proxy launch flows

This commit is contained in:
archipelago 2026-05-19 09:26:43 -04:00
parent e9898ead76
commit d736364ad7
22 changed files with 322 additions and 114 deletions

View File

@ -1,5 +1,18 @@
# Changelog # 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) ## 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. - 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.

View File

@ -52,13 +52,13 @@
{ {
"id": "btcpay-server", "id": "btcpay-server",
"title": "BTCPay Server", "title": "BTCPay Server",
"version": "1.13.7", "version": "2.3.9",
"description": "Self-hosted Bitcoin payment processor.", "description": "Self-hosted Bitcoin payment processor.",
"icon": "/assets/img/app-icons/btcpay-server.png", "icon": "/assets/img/app-icons/btcpay-server.png",
"author": "BTCPay Server Foundation", "author": "BTCPay Server Foundation",
"category": "commerce", "category": "commerce",
"tier": "core", "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", "repoUrl": "https://github.com/btcpayserver/btcpayserver",
"requires": [ "requires": [
"bitcoin-knots" "bitcoin-knots"

View File

@ -1,11 +1,11 @@
app: app:
id: btcpay-server id: btcpay-server
name: BTCPay Server name: BTCPay Server
version: 1.13.7 version: 2.3.9
description: Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries. description: Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries.
container: container:
image: git.tx1138.com/lfg2025/btcpayserver:1.13.7 image: docker.io/btcpayserver/btcpayserver:2.3.9
pull_policy: if-not-present pull_policy: if-not-present
network: archy-net network: archy-net
secret_env: secret_env:

View File

@ -262,7 +262,7 @@ pub(super) fn get_health_check_args(app_id: &str, _rpc_pass: &str) -> Vec<String
"bitcoin" | "bitcoin-core" | "bitcoin-knots" => return vec![], "bitcoin" | "bitcoin-core" | "bitcoin-knots" => return vec![],
"lnd" => ("lncli getinfo || exit 1", "30s", "3"), "lnd" => ("lncli getinfo || exit 1", "30s", "3"),
"btcpay-server" | "btcpayserver" => { "btcpay-server" | "btcpayserver" => {
("curl -sf http://localhost:49392/ || exit 1", "30s", "3") ("bash -ec '</dev/tcp/127.0.0.1/49392'", "30s", "3")
} }
"mempool-api" => ( "mempool-api" => (
http_probe_cmd("http://localhost:8999/api/v1/backend-info"), http_probe_cmd("http://localhost:8999/api/v1/backend-info"),

View File

@ -874,6 +874,7 @@ async fn repair_before_package_start(container_name: &str) {
} }
async fn repair_nginx_proxy_manager_container() { async fn repair_nginx_proxy_manager_container() {
repair_nginx_proxy_manager_dirs().await;
if !nginx_proxy_manager_has_legacy_admin_port().await { if !nginx_proxy_manager_has_legacy_admin_port().await {
cleanup_nginx_proxy_manager_ports().await; cleanup_nginx_proxy_manager_ports().await;
return; 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 { async fn nginx_proxy_manager_has_legacy_admin_port() -> bool {
if let Ok(output) = podman_control(&["port", "nginx-proxy-manager", "81/tcp"]).await { if let Ok(output) = podman_control(&["port", "nginx-proxy-manager", "81/tcp"]).await {
if output.status.success() if output.status.success()
@ -927,12 +949,21 @@ async fn recreate_nginx_proxy_manager_container() -> Result<()> {
.args([ .args([
"mkdir", "mkdir",
"-p", "-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", "/var/lib/archipelago/nginx-proxy-manager/letsencrypt",
]) ])
.output() .output()
.await .await
.context("failed to create nginx-proxy-manager data directories")?; .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") 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()); .unwrap_or_else(|| "docker.io/jc21/nginx-proxy-manager:latest".to_string());

View File

@ -587,7 +587,7 @@ impl RpcHandler {
let images = [ let images = [
&format!("{}/postgres:15.17", REGISTRY), &format!("{}/postgres:15.17", REGISTRY),
&format!("{}/nbxplorer:2.6.0", 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) self.set_install_phase("btcpay-server", InstallPhase::PullingImage)
.await; .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", "BTCPAY_POSTGRES=User ID=btcpay;Password={};Host=archy-btcpay-db;Port=5432;Database=btcpay;Include Error Detail=true",
db_pass db_pass
), ),
&format!("{}/btcpayserver:1.13.7", REGISTRY), "docker.io/btcpayserver/btcpayserver:2.3.9",
]) ])
.output() .output()
.await .await

View File

@ -1169,7 +1169,7 @@
<div class="card-header"><span class="card-name">btcpay-server</span><span class="card-badge badge-red">archy-net</span></div> <div class="card-header"><span class="card-name">btcpay-server</span><span class="card-badge badge-red">archy-net</span></div>
<div class="card-desc">Self-hosted Bitcoin payment processor. Accept Bitcoin payments with invoices, checkout pages, and POS.</div> <div class="card-desc">Self-hosted Bitcoin payment processor. Accept Bitcoin payments with invoices, checkout pages, and POS.</div>
<div class="card-layman">Your own payment terminal for Bitcoin. Create invoices, get paid, no middleman taking a cut.</div> <div class="card-layman">Your own payment terminal for Bitcoin. Create invoices, get paid, no middleman taking a cut.</div>
<div class="card-image">btcpayserver:1.13.7</div> <div class="card-image">btcpayserver:2.3.9</div>
<div class="card-ports">Ports: <span>23000</span></div> <div class="card-ports">Ports: <span>23000</span></div>
<div class="card-details"> <div class="card-details">
<div class="detail-row"><span class="detail-label">Memory</span><span class="detail-value">1 GB</span></div> <div class="detail-row"><span class="detail-label">Memory</span><span class="detail-value">1 GB</span></div>

View File

@ -8,7 +8,7 @@ resolver 1.1.1.1 8.8.8.8 valid=300s ipv6=off;
resolver_timeout 5s; resolver_timeout 5s;
server { server {
listen 80; listen 80 default_server;
server_name _; server_name _;
root /opt/archipelago/web-ui; root /opt/archipelago/web-ui;
@ -23,6 +23,13 @@ server {
add_header X-DNS-Prefetch-Control "off" always; 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; 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 # AIUI SPA (Chat mode iframe) — SPA fallback for client-side routing
location /aiui/ { location /aiui/ {
try_files $uri $uri/ /aiui/index.html; try_files $uri $uri/ /aiui/index.html;
@ -908,7 +915,7 @@ server {
# HTTPS - required for PWA install (Add to Home Screen) from dev servers # HTTPS - required for PWA install (Add to Home Screen) from dev servers
server { server {
listen 443 ssl; listen 443 ssl default_server;
server_name _; server_name _;
ssl_certificate /etc/archipelago/ssl/archipelago.crt; ssl_certificate /etc/archipelago/ssl/archipelago.crt;

View File

@ -52,13 +52,13 @@
{ {
"id": "btcpay-server", "id": "btcpay-server",
"title": "BTCPay Server", "title": "BTCPay Server",
"version": "1.13.7", "version": "2.3.9",
"description": "Self-hosted Bitcoin payment processor.", "description": "Self-hosted Bitcoin payment processor.",
"icon": "/assets/img/app-icons/btcpay-server.png", "icon": "/assets/img/app-icons/btcpay-server.png",
"author": "BTCPay Server Foundation", "author": "BTCPay Server Foundation",
"category": "commerce", "category": "commerce",
"tier": "core", "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", "repoUrl": "https://github.com/btcpayserver/btcpayserver",
"requires": [ "requires": [
"bitcoin-knots" "bitcoin-knots"

View File

@ -160,6 +160,19 @@ const appUrl = computed(() => {
return resolveAppUrl(appId.value, route.query.path as string | undefined) 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 --- // --- Identity & Nostr bridge ---
const iframeRef = computed(() => frameRef.value?.iframeRef ?? null) const iframeRef = computed(() => frameRef.value?.iframeRef ?? null)
@ -295,7 +308,7 @@ function handleBackdropClick() {
function closeSession() { function closeSession() {
if (document.fullscreenElement) document.exitFullscreen().catch(() => {}) if (document.fullscreenElement) document.exitFullscreen().catch(() => {})
if (isInlinePanel.value) emit('close') if (isInlinePanel.value) emit('close')
else router.back() else closeRouteSession()
} }
function onKeyDown(e: KeyboardEvent) { function onKeyDown(e: KeyboardEvent) {
@ -332,7 +345,7 @@ onMounted(() => {
if (mustOpenNewTab.value && appUrl.value) { if (mustOpenNewTab.value && appUrl.value) {
window.open(appUrl.value, '_blank', 'noopener,noreferrer') window.open(appUrl.value, '_blank', 'noopener,noreferrer')
if (isInlinePanel.value) emit('close') if (isInlinePanel.value) emit('close')
else router.back() else closeRouteSession()
return return
} }

View File

@ -344,10 +344,11 @@
</Transition> </Transition>
</Teleport> </Teleport>
<Teleport to="body">
<!-- Add-mirror modal --> <!-- Add-mirror modal -->
<Transition name="fade"> <Transition name="fade">
<div v-if="addingMirror" class="fixed inset-0 z-50 flex items-center justify-center bg-black/10 backdrop-blur-md" @click.self="cancelAddMirror"> <div v-if="addingMirror" class="fixed inset-0 z-[3000] flex items-center justify-center p-4 bg-black/60 backdrop-blur-md" @click.self="cancelAddMirror">
<div class="glass-card p-6 max-w-md w-full mx-4"> <div class="glass-card p-6 max-w-md w-full">
<h3 class="text-lg font-semibold text-white mb-1">Add update mirror</h3> <h3 class="text-lg font-semibold text-white mb-1">Add update mirror</h3>
<p class="text-sm text-white/60 mb-5"> <p class="text-sm text-white/60 mb-5">
The URL should point directly at a <span class="font-mono text-white/80">manifest.json</span> served by a Gitea mirror or equivalent. It's added to the end of the list; use "Make primary" to change order. The URL should point directly at a <span class="font-mono text-white/80">manifest.json</span> served by a Gitea mirror or equivalent. It's added to the end of the list; use "Make primary" to change order.
@ -391,8 +392,8 @@
<!-- Confirmation modal --> <!-- Confirmation modal -->
<Transition name="fade"> <Transition name="fade">
<div v-if="confirmAction" class="fixed inset-0 z-50 flex items-center justify-center bg-black/10 backdrop-blur-md" @click.self="cancelConfirm"> <div v-if="confirmAction" class="fixed inset-0 z-[3000] flex items-center justify-center p-4 bg-black/60 backdrop-blur-md" @click.self="cancelConfirm">
<div class="glass-card p-6 max-w-sm w-full mx-4"> <div class="glass-card p-6 max-w-sm w-full">
<h3 class="text-lg font-semibold text-white mb-3"> <h3 class="text-lg font-semibold text-white mb-3">
{{ confirmAction === 'rollback' {{ confirmAction === 'rollback'
? t('systemUpdate.rollbackTitle') ? t('systemUpdate.rollbackTitle')
@ -432,6 +433,7 @@
</div> </div>
</div> </div>
</Transition> </Transition>
</Teleport>
</div> </div>
</template> </template>

View File

@ -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" 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') }} {{ t('common.launch') }}
<svg v-if="opensInTab(id)" class="w-3.5 h-3.5 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg v-if="opensInTab(id)" class="hidden md:block w-3.5 h-3.5 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg>
</button> </button>
<!-- Start (play icon) --> <!-- Start (play icon) -->
<button <button

View File

@ -78,7 +78,7 @@ export function getCuratedAppList(): MarketplaceApp[] {
return [ return [
{ id: 'bitcoin-knots', title: 'Bitcoin Knots', version: '28.1.0', description: 'Run a full Bitcoin node. Validate and relay blocks and transactions on the Bitcoin network.', icon: '/assets/img/app-icons/bitcoin-knots.webp', author: 'Bitcoin Knots', dockerImage: `${R}/bitcoin-knots:latest`, repoUrl: 'https://github.com/bitcoinknots/bitcoin' }, { id: 'bitcoin-knots', title: 'Bitcoin Knots', version: '28.1.0', description: 'Run a full Bitcoin node. Validate and relay blocks and transactions on the Bitcoin network.', icon: '/assets/img/app-icons/bitcoin-knots.webp', author: 'Bitcoin Knots', dockerImage: `${R}/bitcoin-knots:latest`, repoUrl: 'https://github.com/bitcoinknots/bitcoin' },
{ id: 'bitcoin-core', title: 'Bitcoin Core', version: '28.4', description: 'Reference implementation of the Bitcoin protocol. Run a full node validating and relaying blocks on the Bitcoin network.', icon: '/assets/img/app-icons/bitcoin-core.svg', author: 'Bitcoin Core contributors', dockerImage: 'docker.io/bitcoin/bitcoin:28.4', repoUrl: 'https://github.com/bitcoin/bitcoin' }, { id: 'bitcoin-core', title: 'Bitcoin Core', version: '28.4', description: 'Reference implementation of the Bitcoin protocol. Run a full node validating and relaying blocks on the Bitcoin network.', icon: '/assets/img/app-icons/bitcoin-core.svg', author: 'Bitcoin Core contributors', dockerImage: 'docker.io/bitcoin/bitcoin:28.4', repoUrl: 'https://github.com/bitcoin/bitcoin' },
{ id: 'btcpay-server', title: 'BTCPay Server', version: '1.13.7', description: 'Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries or fees.', icon: '/assets/img/app-icons/btcpay-server.png', author: 'BTCPay Server Foundation', dockerImage: `${R}/btcpayserver:1.13.7`, repoUrl: 'https://github.com/btcpayserver/btcpayserver' }, { id: 'btcpay-server', title: 'BTCPay Server', version: '2.3.9', description: 'Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries or fees.', icon: '/assets/img/app-icons/btcpay-server.png', author: 'BTCPay Server Foundation', dockerImage: 'docker.io/btcpayserver/btcpayserver:2.3.9', repoUrl: 'https://github.com/btcpayserver/btcpayserver' },
{ id: 'lnd', title: 'LND', version: '0.18.4', description: 'Lightning Network Daemon. Fast and cheap Bitcoin payments through the Lightning Network.', icon: '/assets/img/app-icons/lnd.svg', author: 'Lightning Labs', dockerImage: `${R}/lnd:v0.18.4-beta`, repoUrl: 'https://github.com/lightningnetwork/lnd' }, { id: 'lnd', title: 'LND', version: '0.18.4', description: 'Lightning Network Daemon. Fast and cheap Bitcoin payments through the Lightning Network.', icon: '/assets/img/app-icons/lnd.svg', author: 'Lightning Labs', dockerImage: `${R}/lnd:v0.18.4-beta`, repoUrl: 'https://github.com/lightningnetwork/lnd' },
{ id: 'mempool', title: 'Mempool Explorer', version: '3.0.0', description: 'Self-hosted Bitcoin blockchain and mempool visualizer. Monitor transactions without revealing your addresses to third parties.', icon: '/assets/img/app-icons/mempool.webp', author: 'Mempool', dockerImage: `${R}/mempool-frontend:v3.0.0`, repoUrl: 'https://github.com/mempool/mempool' }, { id: 'mempool', title: 'Mempool Explorer', version: '3.0.0', description: 'Self-hosted Bitcoin blockchain and mempool visualizer. Monitor transactions without revealing your addresses to third parties.', icon: '/assets/img/app-icons/mempool.webp', author: 'Mempool', dockerImage: `${R}/mempool-frontend:v3.0.0`, repoUrl: 'https://github.com/mempool/mempool' },
{ id: 'homeassistant', title: 'Home Assistant', version: '2024.1', description: 'Open-source home automation. Control smart home devices privately, on your own hardware.', icon: '/assets/img/app-icons/homeassistant.png', author: 'Home Assistant', dockerImage: `${R}/home-assistant:2024.1`, repoUrl: 'https://github.com/home-assistant/core' }, { id: 'homeassistant', title: 'Home Assistant', version: '2024.1', description: 'Open-source home automation. Control smart home devices privately, on your own hardware.', icon: '/assets/img/app-icons/homeassistant.png', author: 'Home Assistant', dockerImage: `${R}/home-assistant:2024.1`, repoUrl: 'https://github.com/home-assistant/core' },

View File

@ -148,11 +148,11 @@ export function getCuratedAppList(): MarketplaceApp[] {
{ {
id: 'btcpay-server', id: 'btcpay-server',
title: 'BTCPay Server', title: 'BTCPay Server',
version: '1.13.7', version: '2.3.9',
description: 'Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries or fees.', description: 'Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries or fees.',
icon: '/assets/img/app-icons/btcpay-server.png', icon: '/assets/img/app-icons/btcpay-server.png',
author: 'BTCPay Server Foundation', author: 'BTCPay Server Foundation',
dockerImage: `${REGISTRY}/btcpayserver:1.13.7`, dockerImage: 'docker.io/btcpayserver/btcpayserver:2.3.9',
manifestUrl: undefined, manifestUrl: undefined,
repoUrl: 'https://github.com/btcpayserver/btcpayserver' repoUrl: 'https://github.com/btcpayserver/btcpayserver'
}, },

View File

@ -510,6 +510,22 @@ fix_missing_rootless_ports() {
$fixed && return 0 || return 1 $fixed && return 0 || return 1
} }
# ── Fix 11: Nginx Proxy Manager public host bridge ───────────
# Host nginx owns public 80/443 on Archipelago. Mirror NPM proxy hosts into
# host nginx so issued certs and public traffic reach the intended upstreams.
fix_npm_public_hosts() {
local script="/opt/archipelago/scripts/sync-npm-public-hosts.sh"
[ -x "$script" ] || script="$SCRIPT_DIR/sync-npm-public-hosts.sh"
[ -x "$script" ] || return 1
[ -f /var/lib/archipelago/nginx-proxy-manager/data/database.sqlite ] || return 1
if "$script" >/dev/null 2>&1; then
log "Synced Nginx Proxy Manager public hosts into host nginx"
return 0
fi
return 1
}
# ── Main ───────────────────────────────────────────────────── # ── Main ─────────────────────────────────────────────────────
# If remote host provided, run via SSH # If remote host provided, run via SSH
@ -539,6 +555,7 @@ run_fix "exit-127" fix_exit_127
run_fix "netns-egress" fix_rootless_netns_egress run_fix "netns-egress" fix_rootless_netns_egress
run_fix "stopped-core" fix_stopped_core_containers run_fix "stopped-core" fix_stopped_core_containers
run_fix "rootless-ports" fix_missing_rootless_ports run_fix "rootless-ports" fix_missing_rootless_ports
run_fix "npm-public-hosts" fix_npm_public_hosts
echo "" echo ""
if [ $FIXES_APPLIED -gt 0 ]; then if [ $FIXES_APPLIED -gt 0 ]; then

View File

@ -273,7 +273,7 @@ load_spec_btcpay-server() {
SPEC_PORTS="23000:49392" SPEC_PORTS="23000:49392"
SPEC_VOLUMES="/var/lib/archipelago/btcpay:/datadir" SPEC_VOLUMES="/var/lib/archipelago/btcpay:/datadir"
SPEC_MEMORY="$(mem_limit btcpay-server)" SPEC_MEMORY="$(mem_limit btcpay-server)"
SPEC_HEALTH_CMD="curl -sf http://localhost:49392/ || exit 1" SPEC_HEALTH_CMD="bash -ec '</dev/tcp/127.0.0.1/49392'"
SPEC_ENV="ASPNETCORE_URLS=http://0.0.0.0:49392 BTCPAY_PROTOCOL=http BTCPAY_HOST=$HOST_IP:23000 BTCPAY_CHAINS=btc BTCPAY_BTCEXPLORERURL=http://archy-nbxplorer:32838 BTCPAY_BTCRPCURL=http://bitcoin-knots:8332 BTCPAY_BTCRPCUSER=$BITCOIN_RPC_USER BTCPAY_BTCRPCPASSWORD=$BITCOIN_RPC_PASS BTCPAY_POSTGRES=Username=btcpay;Password=$BTCPAY_DB_PASS;Host=archy-btcpay-db;Port=5432;Database=btcpay" SPEC_ENV="ASPNETCORE_URLS=http://0.0.0.0:49392 BTCPAY_PROTOCOL=http BTCPAY_HOST=$HOST_IP:23000 BTCPAY_CHAINS=btc BTCPAY_BTCEXPLORERURL=http://archy-nbxplorer:32838 BTCPAY_BTCRPCURL=http://bitcoin-knots:8332 BTCPAY_BTCRPCUSER=$BITCOIN_RPC_USER BTCPAY_BTCRPCPASSWORD=$BITCOIN_RPC_PASS BTCPAY_POSTGRES=Username=btcpay;Password=$BTCPAY_DB_PASS;Host=archy-btcpay-db;Port=5432;Database=btcpay"
SPEC_TIER="2" SPEC_TIER="2"
SPEC_DATA_DIR="/var/lib/archipelago/btcpay" SPEC_DATA_DIR="/var/lib/archipelago/btcpay"

View File

@ -625,7 +625,7 @@ deploy_node() {
echo ' Creating btcpay-server...' echo ' Creating btcpay-server...'
sudo mkdir -p /var/lib/archipelago/btcpay sudo mkdir -p /var/lib/archipelago/btcpay
\$DOCKER run -d --name btcpay-server --restart unless-stopped \$NET_OPT \ \$DOCKER run -d --name btcpay-server --restart unless-stopped \$NET_OPT \
--health-cmd 'curl -sf http://localhost:49392/' --health-interval=30s --health-timeout=10s --health-retries=3 \ --health-cmd "bash -ec '</dev/tcp/127.0.0.1/49392'" --health-interval=30s --health-timeout=10s --health-retries=3 \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \ --cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \ --security-opt no-new-privileges:true \
-p 23000:49392 -v /var/lib/archipelago/btcpay:/datadir \ -p 23000:49392 -v /var/lib/archipelago/btcpay:/datadir \
@ -909,7 +909,8 @@ LNDCONF
if \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx nginx-proxy-manager; then if \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx nginx-proxy-manager; then
\$DOCKER start nginx-proxy-manager 2>/dev/null || true \$DOCKER start nginx-proxy-manager 2>/dev/null || true
else else
sudo mkdir -p /var/lib/archipelago/nginx-proxy-manager/data /var/lib/archipelago/nginx-proxy-manager/letsencrypt sudo mkdir -p /var/lib/archipelago/nginx-proxy-manager/data/letsencrypt-acme-challenge/.well-known/acme-challenge /var/lib/archipelago/nginx-proxy-manager/letsencrypt
sudo chown -R 1000:1000 /var/lib/archipelago/nginx-proxy-manager 2>/dev/null || true
\$DOCKER run -d --name nginx-proxy-manager --restart unless-stopped \ \$DOCKER run -d --name nginx-proxy-manager --restart unless-stopped \
--health-cmd 'curl -sf http://localhost:81/' --health-interval=30s --health-timeout=5s --health-retries=3 \ --health-cmd 'curl -sf http://localhost:81/' --health-interval=30s --health-timeout=5s --health-retries=3 \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add NET_BIND_SERVICE \ --cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add NET_BIND_SERVICE \

View File

@ -482,9 +482,12 @@ log "Fixing rootless podman UID mapping..."
# Containers running as root (UID 0 → host UID 100000) # Containers running as root (UID 0 → host UID 100000)
for dir in lnd electrumx btcpay nbxplorer jellyfin vaultwarden \ for dir in lnd electrumx btcpay nbxplorer jellyfin vaultwarden \
home-assistant fedimint fedimint-gateway photoprism ollama filebrowser \ home-assistant fedimint fedimint-gateway photoprism ollama filebrowser \
nextcloud uptime-kuma nginx-proxy-manager portainer nostr-rs-relay; do nextcloud uptime-kuma portainer nostr-rs-relay; do
[ -d "/var/lib/archipelago/$dir" ] && chown -R 100000:100000 "/var/lib/archipelago/$dir" 2>/dev/null [ -d "/var/lib/archipelago/$dir" ] && chown -R 100000:100000 "/var/lib/archipelago/$dir" 2>/dev/null
done done
# Nginx Proxy Manager runs as root in the rootless user namespace, which maps to
# the archipelago user on host bind mounts. Keep certbot's webroot writable.
[ -d /var/lib/archipelago/nginx-proxy-manager ] && chown -R 1000:1000 /var/lib/archipelago/nginx-proxy-manager 2>/dev/null
# Bitcoin Knots: container UID 101 → host UID 100101 # Bitcoin Knots: container UID 101 → host UID 100101
[ -d /var/lib/archipelago/bitcoin ] && chown -R 100101:100101 /var/lib/archipelago/bitcoin 2>/dev/null [ -d /var/lib/archipelago/bitcoin ] && chown -R 100101:100101 /var/lib/archipelago/bitcoin 2>/dev/null
# Postgres: container UID 70 → host UID 100070 # Postgres: container UID 70 → host UID 100070
@ -875,7 +878,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q btcpay-server; then
log "Creating BTCPay Server..." log "Creating BTCPay Server..."
mkdir -p /var/lib/archipelago/btcpay/Main mkdir -p /var/lib/archipelago/btcpay/Main
$DOCKER run -d --name btcpay-server --restart unless-stopped \ $DOCKER run -d --name btcpay-server --restart unless-stopped \
--health-cmd="curl -sf http://localhost:49392/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \ --health-cmd="bash -ec '</dev/tcp/127.0.0.1/49392'" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit btcpay-server) --network archy-net --network-alias btcpay-server \ --memory=$(mem_limit btcpay-server) --network archy-net --network-alias btcpay-server \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \ --cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \ --security-opt no-new-privileges:true \
@ -1181,6 +1184,8 @@ track_container "filebrowser"
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q nginx-proxy-manager; then if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q nginx-proxy-manager; then
log "Creating Nginx Proxy Manager..." log "Creating Nginx Proxy Manager..."
mkdir -p /var/lib/archipelago/nginx-proxy-manager/data /var/lib/archipelago/nginx-proxy-manager/letsencrypt mkdir -p /var/lib/archipelago/nginx-proxy-manager/data /var/lib/archipelago/nginx-proxy-manager/letsencrypt
mkdir -p /var/lib/archipelago/nginx-proxy-manager/data/letsencrypt-acme-challenge/.well-known/acme-challenge
chown -R 1000:1000 /var/lib/archipelago/nginx-proxy-manager 2>/dev/null || true
NPM_ADMIN_PORT=$(alloc_port nginx-proxy-manager 8081) NPM_ADMIN_PORT=$(alloc_port nginx-proxy-manager 8081)
NPM_HTTP_PORT=$(alloc_port nginx-proxy-manager-http 8084) NPM_HTTP_PORT=$(alloc_port nginx-proxy-manager-http 8084)
NPM_HTTPS_PORT=$(alloc_port nginx-proxy-manager-https 8444) NPM_HTTPS_PORT=$(alloc_port nginx-proxy-manager-https 8444)

View File

@ -24,7 +24,7 @@ MEMPOOL_WEB_IMAGE="$ARCHY_REGISTRY/mempool-frontend:v3.0.0"
MARIADB_IMAGE="$ARCHY_REGISTRY/mariadb:11.4.10" MARIADB_IMAGE="$ARCHY_REGISTRY/mariadb:11.4.10"
# BTCPay # BTCPay
BTCPAY_IMAGE="$ARCHY_REGISTRY/btcpayserver:1.13.7" BTCPAY_IMAGE="docker.io/btcpayserver/btcpayserver:2.3.9"
NBXPLORER_IMAGE="$ARCHY_REGISTRY/nbxplorer:2.6.0" NBXPLORER_IMAGE="$ARCHY_REGISTRY/nbxplorer:2.6.0"
POSTGRES_IMAGE="$ARCHY_REGISTRY/postgres:15.17" POSTGRES_IMAGE="$ARCHY_REGISTRY/postgres:15.17"
BTCPAY_POSTGRES_IMAGE="$ARCHY_REGISTRY/postgres:15.17" BTCPAY_POSTGRES_IMAGE="$ARCHY_REGISTRY/postgres:15.17"

View File

@ -186,7 +186,7 @@ fi
# for backward compatibility with older binaries that still look there. # for backward compatibility with older binaries that still look there.
SCRIPTS_DEST="/opt/archipelago/scripts" SCRIPTS_DEST="/opt/archipelago/scripts"
sudo mkdir -p "$SCRIPTS_DEST" sudo mkdir -p "$SCRIPTS_DEST"
for script in image-versions.sh reconcile-containers.sh container-specs.sh container-doctor.sh app-surface-smoke-test.sh bitcoin-stack-lifecycle-test.sh; do for script in image-versions.sh reconcile-containers.sh container-specs.sh container-doctor.sh sync-npm-public-hosts.sh app-surface-smoke-test.sh bitcoin-stack-lifecycle-test.sh; do
src="$REPO_DIR/scripts/$script" src="$REPO_DIR/scripts/$script"
if [ -f "$src" ]; then if [ -f "$src" ]; then
sudo install -m 755 "$src" "$SCRIPTS_DEST/$script" sudo install -m 755 "$src" "$SCRIPTS_DEST/$script"

View File

@ -0,0 +1,119 @@
#!/bin/bash
set -euo pipefail
DB="/var/lib/archipelago/nginx-proxy-manager/data/database.sqlite"
OUT="/etc/nginx/conf.d/public-npm-proxy-hosts.conf"
ACME_ROOT="/var/lib/archipelago/nginx-proxy-manager/data/letsencrypt-acme-challenge"
LE_ROOT="/var/lib/archipelago/nginx-proxy-manager/letsencrypt/live"
[ -f "$DB" ] || exit 0
mkdir -p "$ACME_ROOT/.well-known/acme-challenge"
chown -R 1000:1000 /var/lib/archipelago/nginx-proxy-manager 2>/dev/null || true
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
python3 - "$DB" "$ACME_ROOT" "$LE_ROOT" >"$tmp" <<'PY'
import json
import os
import sqlite3
import sys
db, acme_root, le_root = sys.argv[1:]
con = sqlite3.connect(db)
con.row_factory = sqlite3.Row
rows = con.execute(
"""
select p.id, p.domain_names, p.forward_scheme, p.forward_host, p.forward_port,
p.certificate_id, p.ssl_forced, c.provider
from proxy_host p
left join certificate c on c.id = p.certificate_id
where p.enabled = 1 and p.certificate_id > 0
order by p.id
"""
).fetchall()
print("# Generated by sync-npm-public-hosts.sh; do not edit by hand.")
for row in rows:
try:
domains = [d for d in json.loads(row["domain_names"] or "[]") if d]
except Exception:
domains = []
if not domains:
continue
cert_id = row["certificate_id"]
cert = f"{le_root}/npm-{cert_id}/fullchain.pem"
key = f"{le_root}/npm-{cert_id}/privkey.pem"
if row["provider"] != "letsencrypt":
continue
if not os.path.isfile(cert) or not os.path.isfile(key):
continue
names = " ".join(domains)
scheme = row["forward_scheme"] or "http"
host = row["forward_host"]
port = row["forward_port"]
if not host or not port:
continue
# NPM containers use this name to reach host-published services; host nginx
# itself should use loopback for the same services.
nginx_host = "127.0.0.1" if host == "host.containers.internal" else host
print(f"""
server {{
listen 80;
server_name {names};
location ^~ /.well-known/acme-challenge/ {{
default_type text/plain;
root {acme_root};
try_files $uri =404;
}}
location / {{
return 301 https://$host$request_uri;
}}
}}
server {{
listen 443 ssl;
server_name {names};
ssl_certificate {cert};
ssl_certificate_key {key};
location / {{
proxy_pass {scheme}://{nginx_host}:{port};
proxy_http_version 1.1;
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 https;
proxy_set_header X-Forwarded-Scheme https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}}
}}
""")
PY
backup=""
if [ -f "$OUT" ]; then
backup=$(mktemp)
cp "$OUT" "$backup"
fi
restore_previous() {
if [ -n "$backup" ] && [ -f "$backup" ]; then
install -m 0644 "$backup" "$OUT"
else
rm -f "$OUT"
fi
}
if ! install -m 0644 "$tmp" "$OUT" || ! nginx -t >/dev/null; then
restore_previous
nginx -t >/dev/null 2>&1 || true
exit 1
fi
systemctl reload nginx

View File

@ -135,7 +135,7 @@ image_for() {
case "$1" in case "$1" in
bitcoin-knots) echo "146.59.87.168:3000/lfg2025/bitcoin-knots:latest" ;; bitcoin-knots) echo "146.59.87.168:3000/lfg2025/bitcoin-knots:latest" ;;
bitcoin-core) echo "docker.io/bitcoin/bitcoin:28.4" ;; bitcoin-core) echo "docker.io/bitcoin/bitcoin:28.4" ;;
btcpay-server) echo "146.59.87.168:3000/lfg2025/btcpayserver:1.13.7" ;; btcpay-server) echo "docker.io/btcpayserver/btcpayserver:2.3.9" ;;
lnd) echo "146.59.87.168:3000/lfg2025/lnd:v0.18.4-beta" ;; lnd) echo "146.59.87.168:3000/lfg2025/lnd:v0.18.4-beta" ;;
mempool) echo "146.59.87.168:3000/lfg2025/mempool-frontend:v3.0.0" ;; mempool) echo "146.59.87.168:3000/lfg2025/mempool-frontend:v3.0.0" ;;
homeassistant) echo "146.59.87.168:3000/lfg2025/home-assistant:2024.1" ;; homeassistant) echo "146.59.87.168:3000/lfg2025/home-assistant:2024.1" ;;