From e19094739bd8848cef69c7de59ffaccd0833a9eb Mon Sep 17 00:00:00 2001 From: Dorian Date: Sat, 11 Apr 2026 16:47:54 -0400 Subject: [PATCH] feat: botfights container app + mobile gamepad + indeedhub fixes - Promote botfights from external proxy to container app (port 9100) - Add /app/botfights/ nginx proxy rules (HTTP + HTTPS) - Add ARCHY_EMBEDDED env var to botfights container config - Add BOTFIGHTS_IMAGE to image-versions.sh - Add mobile gamepad overlay (D-pad + A/B + START/SELECT) for botfights arcade mode, sends postMessage arcade-input to iframe - Remove old /ext/botfights/ and port 8901 external proxy blocks - IndeeHub: add post-install nginx patching for NIP-07 provider injection - IndeeHub: fix docker image references to registry (was localhost) - IndeeHub: update port 7777 -> 7778 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../archipelago/src/api/rpc/package/config.rs | 1 + .../src/api/rpc/package/install.rs | 102 +++++++++ .../configs/external-app-proxies.conf | 28 --- image-recipe/configs/nginx-archipelago.conf | 108 +++------ .../archipelago-https-app-proxies.conf | 32 +++ neode-ui/src/views/AppSession.vue | 10 +- .../src/views/appSession/MobileGamepad.vue | 216 ++++++++++++++++++ .../src/views/appSession/appSessionConfig.ts | 7 +- neode-ui/src/views/discover/curatedApps.ts | 2 +- .../src/views/marketplace/marketplaceData.ts | 2 +- scripts/image-versions.sh | 3 + 11 files changed, 401 insertions(+), 110 deletions(-) create mode 100644 neode-ui/src/views/appSession/MobileGamepad.vue diff --git a/core/archipelago/src/api/rpc/package/config.rs b/core/archipelago/src/api/rpc/package/config.rs index 0fdab5fb..1901d86e 100644 --- a/core/archipelago/src/api/rpc/package/config.rs +++ b/core/archipelago/src/api/rpc/package/config.rs @@ -877,6 +877,7 @@ pub(super) async fn get_app_config( "PORT=9100".to_string(), format!("JWT_SECRET={}", jwt_secret), "FIGHT_LOOP_ENABLED=true".to_string(), + "ARCHY_EMBEDDED=1".to_string(), ], None, None, diff --git a/core/archipelago/src/api/rpc/package/install.rs b/core/archipelago/src/api/rpc/package/install.rs index 61ea6c25..f4f1430a 100644 --- a/core/archipelago/src/api/rpc/package/install.rs +++ b/core/archipelago/src/api/rpc/package/install.rs @@ -897,6 +897,108 @@ autopilot.active=false\n", } } + // IndeeHub: inject nostr-provider.js and patch container nginx for NIP-07 signing + if package_id == "indeedhub" { + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + + // 1. Remove X-Frame-Options so iframe embedding works + let _ = tokio::process::Command::new("podman") + .args(["exec", "indeedhub", "sed", "-i", "/X-Frame-Options/d", + "/etc/nginx/conf.d/default.conf"]) + .output() + .await; + + // 2. Copy nostr-provider.js into container + let provider_src = "/opt/archipelago/web-ui/nostr-provider.js"; + if tokio::fs::metadata(provider_src).await.is_ok() { + let _ = tokio::process::Command::new("podman") + .args(["cp", provider_src, "indeedhub:/usr/share/nginx/html/nostr-provider.js"]) + .output() + .await; + } + + // 3. Add nostr-provider.js location block + sub_filter injection + let check = tokio::process::Command::new("podman") + .args(["exec", "indeedhub", "grep", "-q", "nostr-provider", + "/etc/nginx/conf.d/default.conf"]) + .output() + .await; + let already_patched = check.map(|o| o.status.success()).unwrap_or(false); + + if !already_patched { + // Read current nginx config from container + let cat_out = tokio::process::Command::new("podman") + .args(["exec", "indeedhub", "cat", "/etc/nginx/conf.d/default.conf"]) + .output() + .await; + + if let Ok(out) = cat_out { + if out.status.success() { + let conf = String::from_utf8_lossy(&out.stdout).to_string(); + + // Insert provider location block before the sw.js location + let conf = conf.replace( + "location = /sw.js {", + "location = /nostr-provider.js {\n\ + \x20 add_header Cache-Control \"no-cache, no-store, must-revalidate\";\n\ + \x20 expires off;\n\ + \x20 }\n\n\ + \x20 location = /sw.js {" + ); + + // Inject script tag into HTML via sub_filter + let conf = if conf.contains("try_files") && !conf.contains("sub_filter") { + conf.replacen( + "try_files $uri $uri/ /index.html;", + "try_files $uri $uri/ /index.html;\n\ + \x20 sub_filter_once on;\n\ + \x20 sub_filter '' '';", + 1, + ) + } else { + conf + }; + + // Write patched config back into container + let tmp_path = "/tmp/indeedhub-nginx-patch.conf"; + if tokio::fs::write(tmp_path, &conf).await.is_ok() { + let _ = tokio::process::Command::new("podman") + .args(["cp", tmp_path, "indeedhub:/etc/nginx/conf.d/default.conf"]) + .output() + .await; + let _ = tokio::fs::remove_file(tmp_path).await; + } + } + } + } + + // 4. Fix X-Forwarded-Prefix for NIP-98 URL reconstruction in iframe context + let _ = tokio::process::Command::new("podman") + .args(["exec", "indeedhub", "sed", "-i", + "s|proxy_set_header X-Forwarded-Prefix /api;|proxy_set_header X-Forwarded-Prefix $http_x_forwarded_prefix/api;|", + "/etc/nginx/conf.d/default.conf"]) + .output() + .await; + + // 5. Reload nginx to apply changes + let reload = tokio::process::Command::new("podman") + .args(["exec", "indeedhub", "nginx", "-s", "reload"]) + .output() + .await; + match reload { + Ok(o) if o.status.success() => { + info!("IndeeHub: NIP-07 provider injected, nginx patched and reloaded"); + } + Ok(o) => { + tracing::warn!("IndeeHub nginx reload failed: {}", + String::from_utf8_lossy(&o.stderr)); + } + Err(e) => { + tracing::warn!("IndeeHub nginx reload error: {}", e); + } + } + } + if package_id == "nextcloud" { let host_ip = &self.config.host_ip; // Wait for Nextcloud to finish first-run initialization diff --git a/image-recipe/configs/external-app-proxies.conf b/image-recipe/configs/external-app-proxies.conf index 54dac6d6..cc6fa9d5 100644 --- a/image-recipe/configs/external-app-proxies.conf +++ b/image-recipe/configs/external-app-proxies.conf @@ -5,34 +5,6 @@ resolver 1.1.1.1 8.8.8.8 valid=300s; resolver_timeout 5s; -# BotFights (botfights.net) → port 8901 -server { - listen 8901; - server_name _; - - location / { - set $upstream_botfights https://botfights.net; - proxy_pass $upstream_botfights; - proxy_http_version 1.1; - proxy_set_header Host botfights.net; - 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_set_header Accept-Encoding ""; - proxy_ssl_server_name on; - proxy_ssl_name botfights.net; - proxy_hide_header X-Frame-Options; - add_header X-Frame-Options "SAMEORIGIN" always; - proxy_hide_header Content-Security-Policy; - proxy_connect_timeout 60s; - proxy_read_timeout 60s; - - proxy_redirect https://botfights.net/ /; - sub_filter_once off; - sub_filter_types text/html text/css application/javascript; - } -} - # 484 Kitchen (484.kitchen) → port 8902 server { listen 8902; diff --git a/image-recipe/configs/nginx-archipelago.conf b/image-recipe/configs/nginx-archipelago.conf index 9d121ce1..fd78feea 100644 --- a/image-recipe/configs/nginx-archipelago.conf +++ b/image-recipe/configs/nginx-archipelago.conf @@ -444,6 +444,39 @@ server { sub_filter "src='/" "src='/app/indeedhub/"; sub_filter '' ''; } + location /app/botfights/api/ { + proxy_pass http://127.0.0.1:9100/api/; + 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 $scheme; + proxy_read_timeout 300s; + proxy_send_timeout 300s; + } + location /app/botfights/ { + proxy_pass http://127.0.0.1:9100/; + 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 $scheme; + proxy_hide_header X-Frame-Options; + add_header X-Frame-Options "SAMEORIGIN" always; + proxy_hide_header Content-Security-Policy; + proxy_hide_header Cross-Origin-Embedder-Policy; + proxy_hide_header Cross-Origin-Opener-Policy; + proxy_hide_header Cross-Origin-Resource-Policy; + add_header X-Content-Type-Options "nosniff" always; + proxy_set_header Accept-Encoding ""; + sub_filter_types text/css application/javascript application/json; + sub_filter_once off; + sub_filter 'href="/' 'href="/app/botfights/'; + sub_filter 'src="/' 'src="/app/botfights/'; + sub_filter "href='/" "href='/app/botfights/"; + sub_filter "src='/" "src='/app/botfights/"; + sub_filter '' ''; + } location /app/lnd/ { proxy_pass http://127.0.0.1:8081/; proxy_http_version 1.1; @@ -681,30 +714,6 @@ server { # External site proxies — strip X-Frame-Options so iframe embedding works. # add_header here prevents inheritance of server-level X-Frame-Options. - location /ext/botfights/ { - set $upstream_2 "https://botfights.net/"; - - proxy_pass $upstream_2; - proxy_http_version 1.1; - proxy_set_header Host botfights.net; - proxy_set_header Accept-Encoding ""; - proxy_ssl_server_name on; - proxy_hide_header X-Frame-Options; - add_header X-Frame-Options "SAMEORIGIN" always; - proxy_hide_header Content-Security-Policy; - proxy_hide_header Cross-Origin-Embedder-Policy; - proxy_hide_header Cross-Origin-Opener-Policy; - proxy_hide_header Cross-Origin-Resource-Policy; - add_header X-Content-Type-Options "nosniff" always; - sub_filter_once off; - sub_filter_types text/css application/javascript; - sub_filter 'href="/' 'href="/ext/botfights/'; - sub_filter 'src="/' 'src="/ext/botfights/'; - sub_filter 'action="/' 'action="/ext/botfights/'; - sub_filter "href='/" "href='/ext/botfights/"; - sub_filter "src='/" "src='/ext/botfights/"; - sub_filter '' ''; - } location /ext/484-kitchen/ { set $upstream_3 "https://484.kitchen/"; @@ -1081,30 +1090,6 @@ server { # External site proxies — strip X-Frame-Options so iframe embedding works. # add_header here prevents inheritance of server-level X-Frame-Options. - location /ext/botfights/ { - set $upstream_7 "https://botfights.net/"; - - proxy_pass $upstream_7; - proxy_http_version 1.1; - proxy_set_header Host botfights.net; - proxy_set_header Accept-Encoding ""; - proxy_ssl_server_name on; - proxy_hide_header X-Frame-Options; - add_header X-Frame-Options "SAMEORIGIN" always; - proxy_hide_header Content-Security-Policy; - proxy_hide_header Cross-Origin-Embedder-Policy; - proxy_hide_header Cross-Origin-Opener-Policy; - proxy_hide_header Cross-Origin-Resource-Policy; - add_header X-Content-Type-Options "nosniff" always; - sub_filter_once off; - sub_filter_types text/css application/javascript; - sub_filter 'href="/' 'href="/ext/botfights/'; - sub_filter 'src="/' 'src="/ext/botfights/'; - sub_filter 'action="/' 'action="/ext/botfights/'; - sub_filter "href='/" "href='/ext/botfights/"; - sub_filter "src='/" "src='/ext/botfights/"; - sub_filter '' ''; - } location /ext/484-kitchen/ { set $upstream_8 "https://484.kitchen/"; @@ -1187,33 +1172,6 @@ server { # External site reverse proxies — each on its own port so SPAs work at root. # Strips X-Frame-Options to allow iframe embedding from Archipelago UI. # Injects NIP-07 nostr-provider.js for Nostr login integration. -server { - listen 8901; - server_name _; - location / { - set $upstream_11 "https://botfights.net"; - - proxy_pass $upstream_11; - proxy_http_version 1.1; - proxy_set_header Host botfights.net; - proxy_set_header Accept-Encoding ""; - proxy_ssl_server_name on; - proxy_hide_header X-Frame-Options; - add_header X-Frame-Options "SAMEORIGIN" always; - proxy_hide_header Content-Security-Policy; - add_header X-Content-Type-Options "nosniff" always; - proxy_hide_header Cross-Origin-Embedder-Policy; - proxy_hide_header Cross-Origin-Opener-Policy; - proxy_hide_header Cross-Origin-Resource-Policy; - sub_filter '' ''; - sub_filter_once on; - } - # Serve nostr-provider.js from the main web-ui directory - location = /nostr-provider.js { - alias /opt/archipelago/web-ui/nostr-provider.js; - } -} - server { listen 8902; server_name _; diff --git a/image-recipe/configs/snippets/archipelago-https-app-proxies.conf b/image-recipe/configs/snippets/archipelago-https-app-proxies.conf index 4215c64e..9b7bb48b 100644 --- a/image-recipe/configs/snippets/archipelago-https-app-proxies.conf +++ b/image-recipe/configs/snippets/archipelago-https-app-proxies.conf @@ -261,6 +261,38 @@ location /app/bitcoin-ui/ { sub_filter_once on; sub_filter '' ''; } +location /app/botfights/api/ { + proxy_pass http://127.0.0.1:9100/api/; + 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 $scheme; + proxy_read_timeout 300s; + proxy_send_timeout 300s; +} +location /app/botfights/ { + proxy_pass http://127.0.0.1:9100/; + 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 $scheme; + proxy_hide_header X-Frame-Options; + add_header X-Frame-Options "SAMEORIGIN" always; + proxy_hide_header Content-Security-Policy; + proxy_hide_header Cross-Origin-Embedder-Policy; + proxy_hide_header Cross-Origin-Opener-Policy; + proxy_hide_header Cross-Origin-Resource-Policy; + proxy_set_header Accept-Encoding ""; + sub_filter_types text/css application/javascript application/json; + sub_filter_once off; + sub_filter 'href="/' 'href="/app/botfights/'; + sub_filter 'src="/' 'src="/app/botfights/'; + sub_filter "href='/" "href='/app/botfights/"; + sub_filter "src='/" "src='/app/botfights/"; + sub_filter '' ''; +} location /app/electrumx/ { proxy_pass http://127.0.0.1:50002/; proxy_http_version 1.1; diff --git a/neode-ui/src/views/AppSession.vue b/neode-ui/src/views/AppSession.vue index 0aa8917f..75365048 100644 --- a/neode-ui/src/views/AppSession.vue +++ b/neode-ui/src/views/AppSession.vue @@ -38,8 +38,13 @@ @open-new-tab-and-back="openNewTabAndBack" /> - -
+ + +
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+ + + + + diff --git a/neode-ui/src/views/appSession/appSessionConfig.ts b/neode-ui/src/views/appSession/appSessionConfig.ts index 4edfbc8e..2d443b2b 100644 --- a/neode-ui/src/views/appSession/appSessionConfig.ts +++ b/neode-ui/src/views/appSession/appSessionConfig.ts @@ -41,14 +41,14 @@ export const APP_PORTS: Record = { 'nostr-vpn': 8201, 'fips': 8202, 'routstr': 8200, - 'indeedhub': 7777, + 'indeedhub': 7778, 'botfights': 9100, 'dwn': 3100, 'endurain': 8080, } /** Apps that need nginx proxy for iframe embedding. - * IndeedHub loads via direct port 7777 -- deploy script removes X-Frame-Options + * IndeedHub loads via /app/indeedhub/ proxy for nostr-provider.js injection * from the container's internal nginx so iframe works on all servers. */ export const PROXY_APPS: Record = {} @@ -87,6 +87,7 @@ export const HTTPS_PROXY_PATHS: Record = { 'penpot': '/app/penpot/', 'grafana': '/app/grafana/', 'indeedhub': '/app/indeedhub/', + 'botfights': '/app/botfights/', 'routstr': '/app/routstr/', 'nostr-vpn': '/app/nostr-vpn/', 'fips': '/app/fips/', @@ -143,7 +144,7 @@ export function resolveAppUrl(id: string, routeQueryPath?: string): string { const proxyPath = PROXY_APPS[id] if (proxyPath) return `${window.location.origin}${proxyPath}` - // IndeedHub: always direct port (X-Frame-Options removed by deploy script) + // IndeedHub: direct port access (nostr-provider.js baked into container image) if (id === 'indeedhub') { const port = APP_PORTS[id] if (port) { diff --git a/neode-ui/src/views/discover/curatedApps.ts b/neode-ui/src/views/discover/curatedApps.ts index 3d28a92b..28fae9ab 100644 --- a/neode-ui/src/views/discover/curatedApps.ts +++ b/neode-ui/src/views/discover/curatedApps.ts @@ -27,7 +27,7 @@ export function getCuratedAppList(): MarketplaceApp[] { { id: 'electrumx', title: 'ElectrumX', version: '1.18.0', description: 'Electrum protocol server. Index the blockchain for fast wallet lookups, privately.', icon: '/assets/img/app-icons/electrumx.webp', author: 'Luke Childs', dockerImage: `${R}/electrumx:v1.18.0`, repoUrl: 'https://github.com/spesmilo/electrumx' }, { id: 'fedimint', title: 'Fedimint', version: '0.10.0', description: 'Federated Bitcoin mint. Private, scalable Bitcoin through federated guardians.', icon: '/assets/img/app-icons/fedimint.png', author: 'Fedimint', dockerImage: `${R}/fedimintd:v0.10.0`, repoUrl: 'https://github.com/fedimint/fedimint' }, { id: 'nostr-rs-relay', title: 'Nostr Relay', version: '0.9.0', category: 'nostr', description: 'Your own Nostr relay. Store events locally, relay for friends, publish over Tor.', icon: '/assets/img/app-icons/nostr-rs-relay.svg', author: 'scsiblade', dockerImage: `${R}/nostr-rs-relay:0.9.0`, repoUrl: 'https://sr.ht/~gheartsfield/nostr-rs-relay/' }, - { id: 'indeedhub', title: 'Indeehub', version: '0.1.0', description: 'Bitcoin documentary streaming with Nostr identity. Stream sovereignty content.', icon: '/assets/img/app-icons/indeedhub.png', author: 'Indeehub Team', dockerImage: 'localhost/indeedhub:latest', repoUrl: 'https://github.com/indeedhub/indeedhub' }, + { id: 'indeedhub', title: 'Indeehub', version: '0.1.0', description: 'Bitcoin documentary streaming with Nostr identity. Stream sovereignty content.', icon: '/assets/img/app-icons/indeedhub.png', author: 'Indeehub Team', dockerImage: 'git.tx1138.com/lfg2025/indeedhub:latest', repoUrl: 'https://github.com/indeedhub/indeedhub' }, { id: 'dwn', title: 'Decentralized Web Node', version: '0.4.0', description: 'Own your data with DID-based access control. Sync across devices, sovereign.', icon: '/assets/img/app-icons/dwn.svg', author: 'TBD', dockerImage: `${R}/dwn-server:main`, repoUrl: 'https://github.com/TBD54566975/dwn-server' }, { id: 'nostr-vpn', title: 'Nostr VPN', version: '0.3.7', category: 'networking', description: 'Tailscale-style mesh VPN with Nostr control plane. Peer discovery and key exchange over relays, WireGuard tunnels.', icon: '/assets/img/app-icons/nostr-vpn.svg', author: 'Martti Malmi', dockerImage: `${R}/nostr-vpn:v0.3.7`, repoUrl: 'https://github.com/mmalmi/nostr-vpn' }, { id: 'fips', title: 'FIPS', version: '0.1.0', category: 'networking', description: 'Free Internetworking Peering System. Self-organizing encrypted mesh network with Nostr identity.', icon: '/assets/img/app-icons/fips.svg', author: 'Jim Corgan', dockerImage: `${R}/fips:v0.1.0`, repoUrl: 'https://github.com/jmcorgan/fips' }, diff --git a/neode-ui/src/views/marketplace/marketplaceData.ts b/neode-ui/src/views/marketplace/marketplaceData.ts index 245c65d2..556ef9cb 100644 --- a/neode-ui/src/views/marketplace/marketplaceData.ts +++ b/neode-ui/src/views/marketplace/marketplaceData.ts @@ -390,7 +390,7 @@ export function getCuratedAppList(): MarketplaceApp[] { description: 'Bitcoin documentary streaming platform with Nostr identity sign-in. Stream God Bless Bitcoin and other educational content about sovereignty and decentralized technology.', icon: '/assets/img/app-icons/indeedhub.png', author: 'Indeehub Team', - dockerImage: 'localhost/indeedhub:latest', + dockerImage: 'git.tx1138.com/lfg2025/indeedhub:latest', manifestUrl: undefined, repoUrl: 'https://github.com/indeedhub/indeedhub' }, diff --git a/scripts/image-versions.sh b/scripts/image-versions.sh index 41d1ca92..e7a637d1 100644 --- a/scripts/image-versions.sh +++ b/scripts/image-versions.sh @@ -71,6 +71,9 @@ FIPS_UI_IMAGE="$ARCHY_REGISTRY/fips-ui:latest" # AI / Routing ROUTSTR_IMAGE="$ARCHY_REGISTRY/routstr:v0.4.3" +# Community / Gaming +BOTFIGHTS_IMAGE="$ARCHY_REGISTRY/botfights:1.0.0" + # IndeedHub stack (local builds use :local tag, not :latest) MINIO_IMAGE="$ARCHY_REGISTRY/minio:RELEASE.2024-11-07T00-52-20Z" INDEEDHUB_POSTGRES_IMAGE="$ARCHY_REGISTRY/postgres:16.13-alpine"