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) <noreply@anthropic.com>
This commit is contained in:
parent
1807ceeebd
commit
e19094739b
@ -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,
|
||||
|
||||
@ -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 '</head>' '<script src=\"/nostr-provider.js\"></script></head>';",
|
||||
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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -444,6 +444,39 @@ server {
|
||||
sub_filter "src='/" "src='/app/indeedhub/";
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
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 '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
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 '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
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 '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
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 '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
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 _;
|
||||
|
||||
@ -261,6 +261,38 @@ location /app/bitcoin-ui/ {
|
||||
sub_filter_once on;
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
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 '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location /app/electrumx/ {
|
||||
proxy_pass http://127.0.0.1:50002/;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
@ -38,8 +38,13 @@
|
||||
@open-new-tab-and-back="openNewTabAndBack"
|
||||
/>
|
||||
|
||||
<!-- Mobile bottom browser bar — part of flex layout, doesn't overlay content -->
|
||||
<div class="md:hidden app-session-mobile-bar">
|
||||
<!-- Mobile: gamepad for botfights, browser bar for everything else -->
|
||||
<MobileGamepad
|
||||
v-if="isMobile && appId === 'botfights'"
|
||||
:iframe-ref="iframeRef ?? null"
|
||||
:player="1"
|
||||
/>
|
||||
<div v-else class="md:hidden app-session-mobile-bar">
|
||||
<button class="app-session-bar-btn" aria-label="Back" @click="iframeGoBack">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
@ -81,6 +86,7 @@ import { useAppLauncherStore } from '@/stores/appLauncher'
|
||||
import NostrIdentityPicker from '@/components/NostrIdentityPicker.vue'
|
||||
import AppSessionHeader from './appSession/AppSessionHeader.vue'
|
||||
import AppSessionFrame from './appSession/AppSessionFrame.vue'
|
||||
import MobileGamepad from './appSession/MobileGamepad.vue'
|
||||
import {
|
||||
type DisplayMode, DISPLAY_MODE_KEY, NEW_TAB_APPS, IFRAME_BLOCKED_APPS,
|
||||
resolveAppUrl, resolveAppTitle,
|
||||
|
||||
216
neode-ui/src/views/appSession/MobileGamepad.vue
Normal file
216
neode-ui/src/views/appSession/MobileGamepad.vue
Normal file
@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<!-- Mobile gamepad overlay — NES-styled D-pad + action buttons.
|
||||
Sends postMessage({ type: 'arcade-input', key, player, action }) to iframe. -->
|
||||
<div class="mobile-gamepad">
|
||||
<!-- D-Pad (left side) -->
|
||||
<div class="gamepad-dpad">
|
||||
<button
|
||||
class="dpad-btn dpad-up"
|
||||
@touchstart.prevent="down('ArrowUp')"
|
||||
@touchend.prevent="up('ArrowUp')"
|
||||
@touchcancel.prevent="up('ArrowUp')"
|
||||
aria-label="Up"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 4l-6 8h12z"/></svg>
|
||||
</button>
|
||||
<button
|
||||
class="dpad-btn dpad-left"
|
||||
@touchstart.prevent="down('ArrowLeft')"
|
||||
@touchend.prevent="up('ArrowLeft')"
|
||||
@touchcancel.prevent="up('ArrowLeft')"
|
||||
aria-label="Left"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4 12l8-6v12z"/></svg>
|
||||
</button>
|
||||
<div class="dpad-center" />
|
||||
<button
|
||||
class="dpad-btn dpad-right"
|
||||
@touchstart.prevent="down('ArrowRight')"
|
||||
@touchend.prevent="up('ArrowRight')"
|
||||
@touchcancel.prevent="up('ArrowRight')"
|
||||
aria-label="Right"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20 12l-8-6v12z"/></svg>
|
||||
</button>
|
||||
<button
|
||||
class="dpad-btn dpad-down"
|
||||
@touchstart.prevent="down('ArrowDown')"
|
||||
@touchend.prevent="up('ArrowDown')"
|
||||
@touchcancel.prevent="up('ArrowDown')"
|
||||
aria-label="Down"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 20l6-8H6z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Center: START / SELECT -->
|
||||
<div class="gamepad-meta">
|
||||
<button
|
||||
class="meta-btn"
|
||||
@touchstart.prevent="tap('Escape')"
|
||||
aria-label="Select"
|
||||
>SEL</button>
|
||||
<button
|
||||
class="meta-btn"
|
||||
@touchstart.prevent="tap('Enter')"
|
||||
aria-label="Start"
|
||||
>START</button>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons (right side) -->
|
||||
<div class="gamepad-actions">
|
||||
<button
|
||||
class="action-btn action-b"
|
||||
@touchstart.prevent="down('b')"
|
||||
@touchend.prevent="up('b')"
|
||||
@touchcancel.prevent="up('b')"
|
||||
aria-label="Kick"
|
||||
>B</button>
|
||||
<button
|
||||
class="action-btn action-a"
|
||||
@touchstart.prevent="down('a')"
|
||||
@touchend.prevent="up('a')"
|
||||
@touchcancel.prevent="up('a')"
|
||||
aria-label="Punch"
|
||||
>A</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
iframeRef: HTMLIFrameElement | null
|
||||
player?: number
|
||||
}>()
|
||||
|
||||
function send(key: string, action: 'down' | 'up') {
|
||||
props.iframeRef?.contentWindow?.postMessage(
|
||||
{ type: 'arcade-input', key, player: props.player ?? 1, action },
|
||||
'*'
|
||||
)
|
||||
}
|
||||
|
||||
function down(key: string) { send(key, 'down') }
|
||||
function up(key: string) { send(key, 'up') }
|
||||
function tap(key: string) { send(key, 'down'); setTimeout(() => send(key, 'up'), 80) }
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mobile-gamepad {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
padding: 12px 20px;
|
||||
padding-bottom: calc(12px + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)));
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
/* ── D-Pad ── */
|
||||
.gamepad-dpad {
|
||||
display: grid;
|
||||
grid-template-columns: 48px 48px 48px;
|
||||
grid-template-rows: 48px 48px 48px;
|
||||
gap: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dpad-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 6px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
transition: background 0.1s;
|
||||
}
|
||||
.dpad-btn:active {
|
||||
background: rgba(251, 146, 60, 0.3);
|
||||
color: white;
|
||||
}
|
||||
.dpad-btn svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.dpad-up { grid-column: 2; grid-row: 1; }
|
||||
.dpad-left { grid-column: 1; grid-row: 2; }
|
||||
.dpad-center {
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.dpad-right { grid-column: 3; grid-row: 2; }
|
||||
.dpad-down { grid-column: 2; grid-row: 3; }
|
||||
|
||||
/* ── Meta buttons (START / SELECT) ── */
|
||||
.gamepad-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.meta-btn {
|
||||
padding: 6px 16px;
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.meta-btn:active {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
/* ── Action buttons (A / B) ── */
|
||||
.gamepad-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid;
|
||||
transition: background 0.1s, transform 0.1s;
|
||||
}
|
||||
.action-btn:active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
.action-a {
|
||||
background: rgba(251, 146, 60, 0.2);
|
||||
border-color: rgba(251, 146, 60, 0.5);
|
||||
color: #fb923c;
|
||||
}
|
||||
.action-a:active {
|
||||
background: rgba(251, 146, 60, 0.45);
|
||||
}
|
||||
|
||||
.action-b {
|
||||
background: rgba(96, 165, 250, 0.2);
|
||||
border-color: rgba(96, 165, 250, 0.5);
|
||||
color: #60a5fa;
|
||||
}
|
||||
.action-b:active {
|
||||
background: rgba(96, 165, 250, 0.45);
|
||||
}
|
||||
</style>
|
||||
@ -41,14 +41,14 @@ export const APP_PORTS: Record<string, number> = {
|
||||
'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<string, string> = {}
|
||||
|
||||
@ -87,6 +87,7 @@ export const HTTPS_PROXY_PATHS: Record<string, string> = {
|
||||
'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) {
|
||||
|
||||
@ -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' },
|
||||
|
||||
@ -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'
|
||||
},
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user