feat: dynamic app catalog, Gitea app polish, registry sync

App catalog served from Gitea repos (app-catalog) with 35 apps.
Nodes fetch catalog dynamically — new apps appear without frontend
rebuild. Test app added and removed to verify pipeline.

Gitea manifest updated with internal_port/nginx_proxy for iframe.
Updated catalog.json, nginx configs, app session configs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-04-12 08:20:18 -04:00
parent 94850b3176
commit ff5ef2951f
18 changed files with 892 additions and 142 deletions

View File

@@ -186,7 +186,7 @@ fun NESController(
}
}
// A/B Buttons in inlay (same size as D-pad inlay, more right margin)
// A/B/C Buttons in inlay (same size as D-pad inlay, more right margin)
Inlay(c, Modifier.align(Alignment.CenterEnd).padding(end = 48.dp).size(140.dp)) {
Row(
Modifier.fillMaxSize(),
@@ -194,15 +194,20 @@ fun NESController(
verticalAlignment = Alignment.CenterVertically,
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Spacer(Modifier.height(10.dp))
RoundBtn(c, 52.dp) { onKey("Escape") }
Text("B", color = c.labelMuted, fontSize = 9.sp, fontWeight = FontWeight.Bold)
RoundBtn(c, 42.dp) { onKey("c") }
Text("C", color = c.labelMuted, fontSize = 8.sp, fontWeight = FontWeight.Bold)
}
Spacer(Modifier.width(16.dp))
Spacer(Modifier.width(10.dp))
Column(horizontalAlignment = Alignment.CenterHorizontally) {
RoundBtn(c, 52.dp) { onKey("Return") }
Text("A", color = c.labelMuted, fontSize = 9.sp, fontWeight = FontWeight.Bold)
Spacer(Modifier.height(10.dp))
Spacer(Modifier.height(8.dp))
RoundBtn(c, 42.dp) { onKey("b") }
Text("B", color = c.labelMuted, fontSize = 8.sp, fontWeight = FontWeight.Bold)
}
Spacer(Modifier.width(10.dp))
Column(horizontalAlignment = Alignment.CenterHorizontally) {
RoundBtn(c, 42.dp) { onKey("a") }
Text("A", color = c.labelMuted, fontSize = 8.sp, fontWeight = FontWeight.Bold)
Spacer(Modifier.height(8.dp))
}
}
}

View File

@@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -24,7 +25,9 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.archipelago.app.R
import com.archipelago.app.ui.theme.ControllerStyle
import com.archipelago.app.ui.theme.NES
@@ -113,16 +116,27 @@ fun NESPortraitController(
Spacer(Modifier.height(12.dp))
// A/B Buttons
// A/B/C Buttons
Inlay(c, Modifier.fillMaxWidth()) {
Row(
Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 10.dp),
Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 10.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
RoundBtn(c, 52.dp) { onKey("Escape") }
Spacer(Modifier.width(24.dp))
RoundBtn(c, 52.dp) { onKey("Return") }
Column(horizontalAlignment = Alignment.CenterHorizontally) {
RoundBtn(c, 46.dp) { onKey("c") }
Text("C", color = c.labelMuted, fontSize = 8.sp, fontWeight = FontWeight.Bold)
}
Spacer(Modifier.width(16.dp))
Column(horizontalAlignment = Alignment.CenterHorizontally) {
RoundBtn(c, 46.dp) { onKey("b") }
Text("B", color = c.labelMuted, fontSize = 8.sp, fontWeight = FontWeight.Bold)
}
Spacer(Modifier.width(16.dp))
Column(horizontalAlignment = Alignment.CenterHorizontally) {
RoundBtn(c, 46.dp) { onKey("a") }
Text("A", color = c.labelMuted, fontSize = 8.sp, fontWeight = FontWeight.Bold)
}
}
}

39
app-catalog/README.md Normal file
View File

@@ -0,0 +1,39 @@
# Archipelago App Catalog
Dynamic app catalog for the Archipelago marketplace. Nodes fetch this catalog to discover available apps.
## How it works
1. The Archipelago frontend fetches `catalog.json` from this repo
2. Apps listed here appear in every node's app store automatically
3. When a user installs an app, the backend pulls the Docker image and creates the container
## Adding a new app
Add an entry to `catalog.json`:
```json
{
"id": "my-app",
"title": "My App",
"version": "1.0.0",
"description": "What it does",
"icon": "/assets/img/app-icons/my-app.svg",
"author": "Author",
"category": "data",
"dockerImage": "git.tx1138.com/lfg2025/my-app:1.0.0",
"repoUrl": "https://github.com/...",
"containerConfig": {
"ports": ["8080:8080"],
"volumes": ["/var/lib/archipelago/my-app:/data"],
"env": ["NODE_ENV=production"]
}
}
```
For apps with hardcoded backend configs (Bitcoin, LND, etc.), `containerConfig` is optional.
For new apps, include `containerConfig` so the backend knows how to create the container.
## Categories
money, commerce, data, home, nostr, networking, community, development, l484

242
app-catalog/catalog.json Normal file
View File

@@ -0,0 +1,242 @@
{
"version": 2,
"updated": "2026-04-12T00:00:00Z",
"registry": "git.tx1138.com/lfg2025",
"featured": {
"id": "indeedhub",
"banner": "/assets/img/featured/indeedhub-banner.jpg",
"headline": "Stream Sovereignty",
"description": "Bitcoin documentaries with Nostr identity.",
"tag": "NOSTR IDENTITY // YOUR NODE"
},
"apps": [
{
"id": "bitcoin-knots", "title": "Bitcoin Knots", "version": "28.1.0",
"description": "Run a full Bitcoin node. Validate and relay blocks and transactions.",
"icon": "/assets/img/app-icons/bitcoin-knots.webp",
"author": "Bitcoin Knots", "category": "money", "tier": "core",
"dockerImage": "git.tx1138.com/lfg2025/bitcoin-knots:latest",
"repoUrl": "https://github.com/bitcoinknots/bitcoin"
},
{
"id": "lnd", "title": "LND", "version": "0.18.4",
"description": "Lightning Network Daemon. Fast Bitcoin payments through Lightning.",
"icon": "/assets/img/app-icons/lnd.svg",
"author": "Lightning Labs", "category": "money", "tier": "core",
"dockerImage": "git.tx1138.com/lfg2025/lnd:v0.18.4-beta",
"repoUrl": "https://github.com/lightningnetwork/lnd",
"requires": ["bitcoin-knots"]
},
{
"id": "btcpay-server", "title": "BTCPay Server", "version": "1.13.7",
"description": "Self-hosted Bitcoin payment processor.",
"icon": "/assets/img/app-icons/btcpay-server.png",
"author": "BTCPay Server Foundation", "category": "commerce", "tier": "core",
"dockerImage": "git.tx1138.com/lfg2025/btcpayserver:1.13.7",
"repoUrl": "https://github.com/btcpayserver/btcpayserver",
"requires": ["bitcoin-knots"]
},
{
"id": "mempool", "title": "Mempool Explorer", "version": "3.0.0",
"description": "Self-hosted Bitcoin blockchain and mempool visualizer.",
"icon": "/assets/img/app-icons/mempool.webp",
"author": "Mempool", "category": "money", "tier": "core",
"dockerImage": "git.tx1138.com/lfg2025/mempool-frontend:v3.0.0",
"repoUrl": "https://github.com/mempool/mempool",
"requires": ["bitcoin-knots", "electrumx"]
},
{
"id": "electrumx", "title": "ElectrumX", "version": "1.18.0",
"description": "Electrum protocol server. Index the blockchain for fast wallet lookups.",
"icon": "/assets/img/app-icons/electrumx.webp",
"author": "Luke Childs", "category": "money", "tier": "core",
"dockerImage": "git.tx1138.com/lfg2025/electrumx:v1.18.0",
"repoUrl": "https://github.com/spesmilo/electrumx",
"requires": ["bitcoin-knots"]
},
{
"id": "indeedhub", "title": "IndeeHub", "version": "1.0.0",
"description": "Bitcoin documentary streaming with Nostr identity.",
"icon": "/assets/img/app-icons/indeedhub.png",
"author": "IndeeHub", "category": "community",
"dockerImage": "git.tx1138.com/lfg2025/indeedhub:1.0.0",
"repoUrl": "https://github.com/indeedhub/indeedhub"
},
{
"id": "botfights", "title": "BotFights", "version": "1.1.0",
"description": "Bot arena + 2-player arcade fighter with controller support and Adventure Mode.",
"icon": "/assets/img/app-icons/botfights.svg",
"author": "BotFights", "category": "community",
"dockerImage": "git.tx1138.com/lfg2025/botfights:1.1.0",
"repoUrl": "https://botfights.net"
},
{
"id": "gitea", "title": "Gitea", "version": "1.23",
"description": "Self-hosted Git service with container registry, CI/CD, issue tracking.",
"icon": "/assets/img/app-icons/gitea.svg",
"author": "Gitea", "category": "development",
"dockerImage": "docker.io/gitea/gitea:1.23",
"repoUrl": "https://gitea.com"
},
{
"id": "filebrowser", "title": "File Browser", "version": "2.27.0",
"description": "Web-based file manager.",
"icon": "/assets/img/app-icons/file-browser.webp",
"author": "File Browser", "category": "data", "tier": "core",
"dockerImage": "git.tx1138.com/lfg2025/filebrowser:v2.27.0",
"repoUrl": "https://github.com/filebrowser/filebrowser"
},
{
"id": "vaultwarden", "title": "Vaultwarden", "version": "1.30.0",
"description": "Self-hosted password vault with zero-knowledge encryption.",
"icon": "/assets/img/app-icons/vaultwarden.webp",
"author": "Vaultwarden", "category": "data", "tier": "recommended",
"dockerImage": "git.tx1138.com/lfg2025/vaultwarden:1.30.0-alpine",
"repoUrl": "https://github.com/dani-garcia/vaultwarden"
},
{
"id": "searxng", "title": "SearXNG", "version": "2024.1.0",
"description": "Privacy-respecting metasearch engine.",
"icon": "/assets/img/app-icons/searxng.png",
"author": "SearXNG", "category": "data", "tier": "recommended",
"dockerImage": "git.tx1138.com/lfg2025/searxng:latest",
"repoUrl": "https://github.com/searxng/searxng"
},
{
"id": "nostr-rs-relay", "title": "Nostr Relay", "version": "0.9.0",
"description": "Your own Nostr relay. Store events locally, relay for friends.",
"icon": "/assets/img/app-icons/nostr-rs-relay.svg",
"author": "scsiblade", "category": "nostr",
"dockerImage": "git.tx1138.com/lfg2025/nostr-rs-relay:0.9.0",
"repoUrl": "https://sr.ht/~gheartsfield/nostr-rs-relay/"
},
{
"id": "fedimint", "title": "Fedimint", "version": "0.10.0",
"description": "Federated Bitcoin mint with privacy through federated guardians.",
"icon": "/assets/img/app-icons/fedimint.png",
"author": "Fedimint", "category": "money",
"dockerImage": "git.tx1138.com/lfg2025/fedimintd:v0.10.0",
"repoUrl": "https://github.com/fedimint/fedimint"
},
{
"id": "ollama", "title": "Ollama", "version": "0.5.4",
"description": "Run AI models locally. Private and on your hardware.",
"icon": "/assets/img/app-icons/ollama.png",
"author": "Ollama", "category": "data",
"dockerImage": "git.tx1138.com/lfg2025/ollama:latest",
"repoUrl": "https://github.com/ollama/ollama"
},
{
"id": "nextcloud", "title": "Nextcloud", "version": "28",
"description": "Your own private cloud. File sync, calendars, contacts.",
"icon": "/assets/img/app-icons/nextcloud.webp",
"author": "Nextcloud", "category": "data",
"dockerImage": "git.tx1138.com/lfg2025/nextcloud:28",
"repoUrl": "https://github.com/nextcloud/server"
},
{
"id": "jellyfin", "title": "Jellyfin", "version": "10.8.13",
"description": "Free media server. Stream movies, music, and photos.",
"icon": "/assets/img/app-icons/jellyfin.webp",
"author": "Jellyfin", "category": "data",
"dockerImage": "git.tx1138.com/lfg2025/jellyfin:10.8.13",
"repoUrl": "https://github.com/jellyfin/jellyfin"
},
{
"id": "immich", "title": "Immich", "version": "1.90.0",
"description": "High-performance photo and video backup with ML.",
"icon": "/assets/img/app-icons/immich.png",
"author": "Immich", "category": "data",
"dockerImage": "git.tx1138.com/lfg2025/immich-server:release",
"repoUrl": "https://github.com/immich-app/immich"
},
{
"id": "homeassistant", "title": "Home Assistant", "version": "2024.1",
"description": "Open-source home automation.",
"icon": "/assets/img/app-icons/homeassistant.png",
"author": "Home Assistant", "category": "home",
"dockerImage": "git.tx1138.com/lfg2025/home-assistant:2024.1",
"repoUrl": "https://github.com/home-assistant/core"
},
{
"id": "grafana", "title": "Grafana", "version": "10.2.0",
"description": "Analytics and monitoring dashboards.",
"icon": "/assets/img/app-icons/grafana.png",
"author": "Grafana Labs", "category": "data", "tier": "recommended",
"dockerImage": "git.tx1138.com/lfg2025/grafana:10.2.0",
"repoUrl": "https://github.com/grafana/grafana"
},
{
"id": "tailscale", "title": "Tailscale", "version": "1.78.0",
"description": "Zero-config VPN with WireGuard mesh networking.",
"icon": "/assets/img/app-icons/tailscale.webp",
"author": "Tailscale", "category": "networking", "tier": "recommended",
"dockerImage": "git.tx1138.com/lfg2025/tailscale:stable",
"repoUrl": "https://github.com/tailscale/tailscale"
},
{
"id": "uptime-kuma", "title": "Uptime Kuma", "version": "1.23.0",
"description": "Self-hosted uptime monitoring.",
"icon": "/assets/img/app-icons/uptime-kuma.webp",
"author": "Uptime Kuma", "category": "data", "tier": "recommended",
"dockerImage": "git.tx1138.com/lfg2025/uptime-kuma:1",
"repoUrl": "https://github.com/louislam/uptime-kuma"
},
{
"id": "nostr-vpn", "title": "Nostr VPN", "version": "0.3.7",
"description": "Tailscale-style mesh VPN with Nostr control plane.",
"icon": "/assets/img/app-icons/nostr-vpn.svg",
"author": "Martti Malmi", "category": "networking",
"dockerImage": "git.tx1138.com/lfg2025/nostr-vpn:v0.3.7",
"repoUrl": "https://github.com/mmalmi/nostr-vpn"
},
{
"id": "fips", "title": "FIPS", "version": "0.1.0",
"description": "Free Internetworking Peering System. Encrypted mesh network.",
"icon": "/assets/img/app-icons/fips.svg",
"author": "Jim Corgan", "category": "networking",
"dockerImage": "git.tx1138.com/lfg2025/fips:v0.1.0",
"repoUrl": "https://github.com/jmcorgan/fips"
},
{
"id": "routstr", "title": "Routstr", "version": "0.4.3",
"description": "Decentralized AI inference proxy with Cashu ecash.",
"icon": "/assets/img/app-icons/routstr.svg",
"author": "Routstr", "category": "community",
"dockerImage": "git.tx1138.com/lfg2025/routstr:v0.4.3",
"repoUrl": "https://github.com/routstr/routstr-core"
},
{
"id": "dwn", "title": "Decentralized Web Node", "version": "0.4.0",
"description": "Own your data with DID-based access control.",
"icon": "/assets/img/app-icons/dwn.svg",
"author": "TBD", "category": "data",
"dockerImage": "git.tx1138.com/lfg2025/dwn-server:main",
"repoUrl": "https://github.com/TBD54566975/dwn-server"
},
{
"id": "endurain", "title": "Endurain", "version": "0.8.0",
"description": "Self-hosted fitness tracking. Strava alternative.",
"icon": "/assets/img/app-icons/endurain.png",
"author": "Endurain", "category": "data",
"dockerImage": "git.tx1138.com/lfg2025/endurain:0.8.0",
"repoUrl": "https://github.com/joaovitoriasilva/endurain"
},
{
"id": "penpot", "title": "Penpot", "version": "2.4",
"description": "Open-source design platform. Self-hosted Figma alternative.",
"icon": "/assets/img/app-icons/penpot.webp",
"author": "Penpot", "category": "data",
"dockerImage": "git.tx1138.com/lfg2025/penpot-frontend:2.4",
"repoUrl": "https://github.com/penpot/penpot"
},
{
"id": "photoprism", "title": "PhotoPrism", "version": "240915",
"description": "AI-powered photo management with facial recognition.",
"icon": "/assets/img/app-icons/photoprism.svg",
"author": "PhotoPrism", "category": "data",
"dockerImage": "git.tx1138.com/lfg2025/photoprism:240915",
"repoUrl": "https://github.com/photoprism/photoprism"
}
]
}

View File

@@ -5,6 +5,7 @@ description: Self-hosted Git service with built-in container registry, CI/CD, an
category: development
icon: git-branch
port: 3000
internal_port: 3001
ssh_port: 2222
image: docker.io/gitea/gitea:1.23
tier: optional
@@ -28,6 +29,16 @@ environment:
GITEA__repository__ENABLE_PUSH_CREATE_USER: "true"
GITEA__repository__ENABLE_PUSH_CREATE_ORG: "true"
# Gitea hardcodes X-Frame-Options: SAMEORIGIN which blocks iframe embedding.
# Container binds to internal_port (3001), nginx proxies public port (3000)
# stripping the X-Frame-Options header so the app works in Archipelago's iframe.
nginx_proxy:
listen: 3000
proxy_pass: "http://127.0.0.1:3001"
extra_headers:
- "proxy_hide_header X-Frame-Options"
- "proxy_hide_header Content-Security-Policy"
health_check:
endpoint: /
interval: 120

View File

@@ -4,7 +4,6 @@ use hyper::{Request, Response};
use hyper_ws_listener::WsStream;
use serde::Deserialize;
use std::time::Instant;
use tokio::process::Command;
use tokio::sync::broadcast;
use tokio_tungstenite::tungstenite::Message;
use tracing::{debug, info, warn};
@@ -72,21 +71,9 @@ enum InputCommand {
Ping,
}
async fn xdotool(args: &[&str]) -> Result<()> {
let output = Command::new("xdotool")
.env("DISPLAY", ":0")
.args(args)
.output()
.await
.context("xdotool execution failed")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
debug!("xdotool error: {}", stderr);
}
Ok(())
}
/// Validate and acknowledge input — relay-only, no xdotool.
/// All input is forwarded to browser clients via the broadcast channel;
/// the browser's remote-relay.ts dispatches DOM events from there.
async fn handle_input(msg: &str) -> Result<Option<String>> {
let cmd: InputCommand = serde_json::from_str(msg)
.context("invalid input command")?;
@@ -97,25 +84,16 @@ async fn handle_input(msg: &str) -> Result<Option<String>> {
warn!("rejected key: {}", k);
return Ok(Some(r#"{"t":"e","m":"invalid key"}"#.to_string()));
}
xdotool(&["key", "--clearmodifiers", k]).await?;
}
InputCommand::MouseMove { x, y } => {
let x = x.clamp(-50, 50);
let y = y.clamp(-50, 50);
let xs = x.to_string();
let ys = y.to_string();
xdotool(&["mousemove_relative", "--", &xs, &ys]).await?;
let _x = x.clamp(-50, 50);
let _y = y.clamp(-50, 50);
}
InputCommand::Click { b } => {
let b = b.clamp(1, 3);
let bs = b.to_string();
xdotool(&["click", &bs]).await?;
let _b = b.clamp(1, 3);
}
InputCommand::Scroll { y } => {
// xdotool: button 4 = scroll up, button 5 = scroll down
let btn = if y < 0 { "4" } else { "5" };
let count = y.unsigned_abs().clamp(1, 10).to_string();
xdotool(&["click", "--repeat", &count, btn]).await?;
let _y = y.clamp(-10, 10);
}
InputCommand::Ping => {
return Ok(Some(r#"{"t":"p"}"#.to_string()));

View File

@@ -214,7 +214,7 @@ pub(super) fn get_health_check_args(app_id: &str, _rpc_pass: &str) -> Vec<String
),
"ollama" => ("curl -sf http://localhost:11434/ || exit 1", "30s", "3"),
"fedimint" => (
"curl -sf http://localhost:8174/health || exit 1",
"curl -sf http://localhost:8175/ || exit 1",
"60s",
"3",
),
@@ -894,8 +894,10 @@ pub(super) async fn get_app_config(
None,
)
}
// Gitea binds to 3001 internally. Nginx on port 3000 strips X-Frame-Options
// so Gitea works in Archipelago's iframe. See nginx-gitea-iframe.conf.
"gitea" => (
vec!["3000:3000".to_string(), "2222:22".to_string()],
vec!["3001:3000".to_string(), "2222:22".to_string()],
vec![
"/var/lib/archipelago/gitea/data:/data".to_string(),
"/var/lib/archipelago/gitea/config:/etc/gitea".to_string(),
@@ -912,6 +914,32 @@ pub(super) async fn get_app_config(
None,
None,
),
_ => (vec![], vec![], vec![], None, None),
_ => {
// Unknown app: try to load config from /var/lib/archipelago/app-configs/{id}.json
// This allows dynamic apps from the remote catalog to be installed
// without hardcoding their config here.
let config_path = format!("/var/lib/archipelago/app-configs/{}.json", app_id);
if let Ok(data) = tokio::fs::read_to_string(&config_path).await {
if let Ok(cfg) = serde_json::from_str::<serde_json::Value>(&data) {
let ports = cfg.get("ports")
.and_then(|v| v.as_array())
.map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect())
.unwrap_or_default();
let volumes = cfg.get("volumes")
.and_then(|v| v.as_array())
.map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect())
.unwrap_or_default();
let env_vars = cfg.get("env")
.and_then(|v| v.as_array())
.map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect())
.unwrap_or_default();
tracing::info!("Loaded dynamic config for app: {}", app_id);
return (ports, volumes, env_vars, None, None);
}
}
// No config found — use minimal defaults (container's own EXPOSE/VOLUME)
tracing::warn!("No config found for app: {} — using minimal defaults", app_id);
(vec![], vec![], vec![], None, None)
},
}
}

View File

@@ -475,17 +475,19 @@ server {
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>';
sub_filter '</head>' '<script src="/nostr-provider.js"></script><script>window.addEventListener("message",function(e){var d=e.data;if(d&&d.type==="arcade-input"&&d.key){var t=d.action==="up"?"keyup":"keydown";document.dispatchEvent(new KeyboardEvent(t,{key:d.key,bubbles:true}))}})</script></head>';
}
location /app/gitea/ {
proxy_pass http://127.0.0.1:3000/;
# Gitea runs on 3001, nginx proxies 3000 stripping X-Frame-Options for iframe
proxy_pass http://127.0.0.1:3001/;
proxy_http_version 1.1;
proxy_set_header Host $http_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;
add_header X-Content-Type-Options nosniff always;
client_max_body_size 1G;
}
location /app/lnd/ {

View File

@@ -0,0 +1,21 @@
# Gitea iframe proxy — strips X-Frame-Options so Gitea works in Archipelago iframe.
# Gitea container binds to port 3001, this proxy listens on port 3000 (the public port).
# Deployed to /etc/nginx/conf.d/gitea-iframe.conf
server {
listen 3000;
server_name _;
client_max_body_size 1G;
location / {
proxy_pass http://127.0.0.1:3001;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_hide_header X-Frame-Options;
proxy_hide_header Content-Security-Policy;
}
}

View File

@@ -291,7 +291,7 @@ location /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>';
sub_filter '</head>' '<script src="/nostr-provider.js"></script><script>window.addEventListener("message",function(e){var d=e.data;if(d&&d.type==="arcade-input"&&d.key){var t=d.action==="up"?"keyup":"keydown";document.dispatchEvent(new KeyboardEvent(t,{key:d.key,bubbles:true}))}})</script></head>';
}
location /app/electrumx/ {
proxy_pass http://127.0.0.1:50002/;

View File

@@ -1,31 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="main_outline" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" viewBox="0 0 640 640" style="enable-background:new 0 0 640 640;" xml:space="preserve">
<g>
<path id="teabag" style="fill:#FFFFFF" d="M395.9,484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5,21.2-17.9,33.8-11.8
c17.2,8.3,27.1,13,27.1,13l-0.1-109.2l16.7-0.1l0.1,117.1c0,0,57.4,24.2,83.1,40.1c3.7,2.3,10.2,6.8,12.9,14.4
c2.1,6.1,2,13.1-1,19.3l-61,126.9C423.6,484.9,408.4,490.3,395.9,484.2z"/>
<g>
<g>
<path style="fill:#609926" d="M622.7,149.8c-4.1-4.1-9.6-4-9.6-4s-117.2,6.6-177.9,8c-13.3,0.3-26.5,0.6-39.6,0.7c0,39.1,0,78.2,0,117.2
c-5.5-2.6-11.1-5.3-16.6-7.9c0-36.4-0.1-109.2-0.1-109.2c-29,0.4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5
c-9.8-0.6-22.5-2.1-39,1.5c-8.7,1.8-33.5,7.4-53.8,26.9C-4.9,212.4,6.6,276.2,8,285.8c1.7,11.7,6.9,44.2,31.7,72.5
c45.8,56.1,144.4,54.8,144.4,54.8s12.1,28.9,30.6,55.5c25,33.1,50.7,58.9,75.7,62c63,0,188.9-0.1,188.9-0.1s12,0.1,28.3-10.3
c14-8.5,26.5-23.4,26.5-23.4s12.9-13.8,30.9-45.3c5.5-9.7,10.1-19.1,14.1-28c0,0,55.2-117.1,55.2-231.1
C633.2,157.9,624.7,151.8,622.7,149.8z M125.6,353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6,321.8,60,295.4
c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5,38.5-30c13.8-3.7,31-3.1,31-3.1s7.1,59.4,15.7,94.2c7.2,29.2,24.8,77.7,24.8,77.7
S142.5,359.9,125.6,353.9z M425.9,461.5c0,0-6.1,14.5-19.6,15.4c-5.8,0.4-10.3-1.2-10.3-1.2s-0.3-0.1-5.3-2.1l-112.9-55
c0,0-10.9-5.7-12.8-15.6c-2.2-8.1,2.7-18.1,2.7-18.1L322,273c0,0,4.8-9.7,12.2-13c0.6-0.3,2.3-1,4.5-1.5c8.1-2.1,18,2.8,18,2.8
l110.7,53.7c0,0,12.6,5.7,15.3,16.2c1.9,7.4-0.5,14-1.8,17.2C474.6,363.8,425.9,461.5,425.9,461.5z"/>
<path style="fill:#609926" d="M326.8,380.1c-8.2,0.1-15.4,5.8-17.3,13.8c-1.9,8,2,16.3,9.1,20c7.7,4,17.5,1.8,22.7-5.4
c5.1-7.1,4.3-16.9-1.8-23.1l24-49.1c1.5,0.1,3.7,0.2,6.2-0.5c4.1-0.9,7.1-3.6,7.1-3.6c4.2,1.8,8.6,3.8,13.2,6.1
c4.8,2.4,9.3,4.9,13.4,7.3c0.9,0.5,1.8,1.1,2.8,1.9c1.6,1.3,3.4,3.1,4.7,5.5c1.9,5.5-1.9,14.9-1.9,14.9
c-2.3,7.6-18.4,40.6-18.4,40.6c-8.1-0.2-15.3,5-17.7,12.5c-2.6,8.1,1.1,17.3,8.9,21.3c7.8,4,17.4,1.7,22.5-5.3
c5-6.8,4.6-16.3-1.1-22.6c1.9-3.7,3.7-7.4,5.6-11.3c5-10.4,13.5-30.4,13.5-30.4c0.9-1.7,5.7-10.3,2.7-21.3
c-2.5-11.4-12.6-16.7-12.6-16.7c-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3c4.7-9.7,9.4-19.3,14.1-29
c-4.1-2-8.1-4-12.2-6.1c-4.8,9.8-9.7,19.7-14.5,29.5c-6.7-0.1-12.9,3.5-16.1,9.4c-3.4,6.3-2.7,14.1,1.9,19.8
C343.2,346.5,335,363.3,326.8,380.1z"/>
</g>
</g>
</g>
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H256V256H0V0Z" fill="url(#paint0_linear)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M60.7921 75.0001C46.339 74.9589 26.9714 85.292 28.0426 111.19C29.7163 151.656 66.5827 155.41 81.3211 155.735C82.9381 163.325 100.279 189.504 113.117 190.884H169.365C203.092 188.359 228.349 76.1915 209.625 75.7653C178.663 77.4046 160.307 78.2293 144.568 78.3679V113.38L139.662 110.943L139.63 78.389C122.634 78.3826 107.561 77.5457 80.7054 76.0546L80.7014 76.0544C79.0119 75.9606 77.2757 75.8642 75.4894 75.7653C73.8441 75.7477 71.9167 75.6034 69.7976 75.4447C67.0518 75.239 63.9842 75.0092 60.7921 75.0001ZM64.5638 89.3204C66.6097 110.05 69.9472 122.179 76.703 140.709C59.4699 138.431 44.8043 132.8 42.1069 111.778C40.6672 100.558 45.7259 88.8146 64.5638 89.3204ZM133.366 110.539L165.771 126.284C169.894 128.288 171.612 133.254 169.609 137.378L153.864 169.782C151.86 173.905 146.894 175.624 142.77 173.62L110.366 157.875C106.242 155.872 104.524 150.905 106.527 146.782L122.273 114.377C124.276 110.254 129.243 108.536 133.366 110.539Z" fill="white"/>
<path d="M142.897 115.105L139.261 113.33L135.027 122.001C134.401 121.988 133.762 122.088 133.137 122.312C130.404 123.296 128.985 126.31 129.969 129.043C130.176 129.618 130.473 130.134 130.837 130.582L123.595 145.415C123.015 145.419 122.425 145.52 121.848 145.728C119.114 146.712 117.696 149.725 118.68 152.459C119.664 155.192 122.677 156.61 125.411 155.626C128.144 154.643 129.563 151.629 128.579 148.896C128.307 148.14 127.88 147.485 127.347 146.954L134.405 132.498C135.158 132.573 135.94 132.485 136.7 132.211C137.333 131.983 137.896 131.647 138.375 131.23C138.699 131.383 139.015 131.533 139.323 131.678C141.612 132.76 143.442 133.625 144.933 134.455C147.461 135.862 148.23 136.777 148.465 137.665C148.738 138.694 148.497 140.358 147.071 143.785C146.036 146.271 144.519 149.382 142.527 153.467L142.527 153.468C142.48 153.563 142.433 153.659 142.386 153.755C141.734 153.731 141.066 153.829 140.414 154.064C137.681 155.048 136.263 158.062 137.246 160.795C138.23 163.528 141.244 164.947 143.977 163.963C146.711 162.979 148.129 159.965 147.145 157.232C146.901 156.553 146.531 155.955 146.072 155.455C146.089 155.421 146.105 155.387 146.122 155.352C148.137 151.22 149.731 147.951 150.817 145.344C152.224 141.964 153.048 139.12 152.387 136.626C151.69 133.992 149.53 132.369 146.906 130.909C145.32 130.026 143.277 129.06 140.856 127.914C140.631 127.807 140.402 127.699 140.17 127.589C140.214 126.894 140.119 126.178 139.868 125.48C139.613 124.771 139.22 124.149 138.732 123.636L142.897 115.105Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear" x1="128" y1="0" x2="128" y2="256" gradientUnits="userSpaceOnUse">
<stop stop-color="#8AB11A"/>
<stop offset="1" stop-color="#609926"/>
<stop offset="1" stop-color="#58921D"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,48 +1,415 @@
{
"version": 1,
"updated": "2026-04-11T00:00:00Z",
"registry": "git.tx1138.com/lfg2025",
"registry": "23.182.128.160:3000/lfg2025",
"featured": {
"id": "indeedhub",
"banner": "/assets/img/featured/indeedhub-banner.jpg",
"headline": "Stream Sovereignty",
"description": "Bitcoin documentaries with Nostr identity. God Bless Bitcoin, The Bitcoin Psyop, and more streaming from your own node.",
"description": "Bitcoin documentaries with Nostr identity. God Bless Bitcoin, The Bitcoin Psyop, and more \u2014 streaming from your own node.",
"tag": "NOSTR IDENTITY // YOUR NODE"
},
"apps": [
{ "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": "bitcoin-knots:latest", "repoUrl": "https://github.com/bitcoinknots/bitcoin", "category": "money", "tier": "core" },
{ "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": "lnd:v0.18.4-beta", "repoUrl": "https://github.com/lightningnetwork/lnd", "category": "money", "tier": "core" },
{ "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": "btcpayserver:1.13.7", "repoUrl": "https://github.com/btcpayserver/btcpayserver", "category": "commerce", "tier": "core" },
{ "id": "mempool", "title": "Mempool Explorer", "version": "3.0.0", "description": "Self-hosted Bitcoin blockchain and mempool visualizer. Monitor transactions without revealing your addresses.", "icon": "/assets/img/app-icons/mempool.webp", "author": "Mempool", "dockerImage": "mempool-frontend:v3.0.0", "repoUrl": "https://github.com/mempool/mempool", "category": "money", "tier": "core" },
{ "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": "electrumx:v1.18.0", "repoUrl": "https://github.com/spesmilo/electrumx", "category": "money", "tier": "core" },
{ "id": "indeedhub", "title": "IndeeHub", "version": "1.0.0", "description": "Bitcoin documentary streaming with Nostr identity. Stream sovereignty content from your node.", "icon": "/assets/img/app-icons/indeedhub.png", "author": "IndeeHub Team", "dockerImage": "indeedhub:1.0.0", "repoUrl": "https://github.com/indeedhub/indeedhub", "category": "community" },
{ "id": "botfights", "title": "BotFights", "version": "1.0.0", "description": "Bot arena + 2-player arcade fighter with controller support.", "icon": "/assets/img/app-icons/botfights.svg", "author": "BotFights", "dockerImage": "botfights:1.1.0", "repoUrl": "https://botfights.net", "category": "community" },
{ "id": "filebrowser", "title": "File Browser", "version": "2.27.0", "description": "Web-based file manager. Browse, upload, and manage files on your server.", "icon": "/assets/img/app-icons/file-browser.webp", "author": "File Browser", "dockerImage": "filebrowser:v2.27.0", "repoUrl": "https://github.com/filebrowser/filebrowser", "category": "data", "tier": "core" },
{ "id": "vaultwarden", "title": "Vaultwarden", "version": "1.30.0", "description": "Self-hosted password vault. Bitwarden-compatible with zero-knowledge encryption.", "icon": "/assets/img/app-icons/vaultwarden.webp", "author": "Vaultwarden", "dockerImage": "vaultwarden:1.30.0-alpine", "repoUrl": "https://github.com/dani-garcia/vaultwarden", "category": "data", "tier": "recommended" },
{ "id": "searxng", "title": "SearXNG", "version": "2024.1.0", "description": "Privacy-respecting metasearch engine. Search the internet without being tracked.", "icon": "/assets/img/app-icons/searxng.png", "author": "SearXNG", "dockerImage": "searxng:latest", "repoUrl": "https://github.com/searxng/searxng", "category": "data", "tier": "recommended" },
{ "id": "nostr-rs-relay", "title": "Nostr Relay", "version": "0.9.0", "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": "nostr-rs-relay:0.9.0", "repoUrl": "https://sr.ht/~gheartsfield/nostr-rs-relay/", "category": "nostr" },
{ "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": "fedimintd:v0.10.0", "repoUrl": "https://github.com/fedimint/fedimint", "category": "money" },
{ "id": "ollama", "title": "Ollama", "version": "0.5.4", "description": "Run AI models locally. Llama, Mistral, and more — on your hardware, completely private.", "icon": "/assets/img/app-icons/ollama.png", "author": "Ollama", "dockerImage": "ollama:latest", "repoUrl": "https://github.com/ollama/ollama", "category": "data" },
{ "id": "nextcloud", "title": "Nextcloud", "version": "28", "description": "Your own private cloud. File sync, calendars, contacts — all on your hardware.", "icon": "/assets/img/app-icons/nextcloud.webp", "author": "Nextcloud", "dockerImage": "nextcloud:28", "repoUrl": "https://github.com/nextcloud/server", "category": "data" },
{ "id": "jellyfin", "title": "Jellyfin", "version": "10.8.13", "description": "Free media server. Stream your movies, music, and photos to any device.", "icon": "/assets/img/app-icons/jellyfin.webp", "author": "Jellyfin", "dockerImage": "jellyfin:10.8.13", "repoUrl": "https://github.com/jellyfin/jellyfin", "category": "data" },
{ "id": "immich", "title": "Immich", "version": "1.90.0", "description": "High-performance photo and video backup. Mobile-first with ML features.", "icon": "/assets/img/app-icons/immich.png", "author": "Immich", "dockerImage": "immich-server:release", "repoUrl": "https://github.com/immich-app/immich", "category": "data" },
{ "id": "homeassistant", "title": "Home Assistant", "version": "2024.1", "description": "Open-source home automation. Control smart home devices privately.", "icon": "/assets/img/app-icons/homeassistant.png", "author": "Home Assistant", "dockerImage": "home-assistant:2024.1", "repoUrl": "https://github.com/home-assistant/core", "category": "home" },
{ "id": "grafana", "title": "Grafana", "version": "10.2.0", "description": "Analytics and monitoring platform. Dashboards for your node metrics.", "icon": "/assets/img/app-icons/grafana.png", "author": "Grafana Labs", "dockerImage": "grafana:10.2.0", "repoUrl": "https://github.com/grafana/grafana", "category": "data", "tier": "recommended" },
{ "id": "tailscale", "title": "Tailscale", "version": "1.78.0", "description": "Zero-config VPN. Secure remote access with WireGuard mesh networking.", "icon": "/assets/img/app-icons/tailscale.webp", "author": "Tailscale", "dockerImage": "tailscale:stable", "repoUrl": "https://github.com/tailscale/tailscale", "category": "networking", "tier": "recommended" },
{ "id": "penpot", "title": "Penpot", "version": "2.4", "description": "Open-source design platform. Self-hosted alternative to Figma.", "icon": "/assets/img/app-icons/penpot.webp", "author": "Penpot", "dockerImage": "penpot-frontend:2.4", "repoUrl": "https://github.com/penpot/penpot", "category": "data" },
{ "id": "photoprism", "title": "PhotoPrism", "version": "240915", "description": "AI-powered photo management with facial recognition, privately.", "icon": "/assets/img/app-icons/photoprism.svg", "author": "PhotoPrism", "dockerImage": "photoprism:240915", "repoUrl": "https://github.com/photoprism/photoprism", "category": "data" },
{ "id": "uptime-kuma", "title": "Uptime Kuma", "version": "1.23.0", "description": "Self-hosted uptime monitoring. Track HTTP, TCP, DNS, and more.", "icon": "/assets/img/app-icons/uptime-kuma.webp", "author": "Uptime Kuma", "dockerImage": "uptime-kuma:1", "repoUrl": "https://github.com/louislam/uptime-kuma", "category": "data", "tier": "recommended" },
{ "id": "nostr-vpn", "title": "Nostr VPN", "version": "0.3.7", "description": "Tailscale-style mesh VPN with Nostr control plane.", "icon": "/assets/img/app-icons/nostr-vpn.svg", "author": "Martti Malmi", "dockerImage": "nostr-vpn:v0.3.7", "repoUrl": "https://github.com/mmalmi/nostr-vpn", "category": "networking" },
{ "id": "fips", "title": "FIPS", "version": "0.1.0", "description": "Free Internetworking Peering System. Self-organizing encrypted mesh.", "icon": "/assets/img/app-icons/fips.svg", "author": "Jim Corgan", "dockerImage": "fips:v0.1.0", "repoUrl": "https://github.com/jmcorgan/fips", "category": "networking" },
{ "id": "routstr", "title": "Routstr", "version": "0.4.3", "description": "Decentralized AI inference proxy. Pay-per-request with Cashu ecash.", "icon": "/assets/img/app-icons/routstr.svg", "author": "Routstr", "dockerImage": "routstr:v0.4.3", "repoUrl": "https://github.com/routstr/routstr-core", "category": "community" },
{ "id": "dwn", "title": "Decentralized Web Node", "version": "0.4.0", "description": "Own your data with DID-based access control. Sync across devices.", "icon": "/assets/img/app-icons/dwn.svg", "author": "TBD", "dockerImage": "dwn-server:main", "repoUrl": "https://github.com/TBD54566975/dwn-server", "category": "data" },
{ "id": "cryptpad", "title": "CryptPad", "version": "2024.12.0", "description": "End-to-end encrypted documents and collaboration. Zero-knowledge.", "icon": "/assets/img/app-icons/cryptpad.webp", "author": "XWiki SAS", "dockerImage": "cryptpad:2024.12.0", "repoUrl": "https://github.com/cryptpad/cryptpad", "category": "data" },
{ "id": "nostrudel", "title": "noStrudel", "version": "0.40.0", "description": "Feature-rich Nostr web client.", "icon": "/assets/img/app-icons/nostrudel.svg", "author": "hzrd149", "dockerImage": "", "repoUrl": "https://github.com/hzrd149/nostrudel", "webUrl": "https://nostrudel.ninja", "category": "nostr" },
{ "id": "nwnn", "title": "Next Web News Network", "version": "1.0.0", "description": "Decentralized news aggregator.", "icon": "/assets/img/app-icons/nwnn.png", "author": "L484", "dockerImage": "", "webUrl": "https://nwnn.l484.com", "category": "l484" },
{ "id": "484-kitchen", "title": "484 Kitchen", "version": "1.0.0", "description": "K484 application platform.", "icon": "/assets/img/app-icons/484-kitchen.png", "author": "L484", "dockerImage": "", "webUrl": "https://484.kitchen", "category": "l484" },
{ "id": "call-the-operator", "title": "Call the Operator", "version": "1.0.0", "description": "Escape the Matrix.", "icon": "/assets/img/app-icons/call-the-operator.png", "author": "TX1138", "dockerImage": "", "webUrl": "https://cta.tx1138.com", "category": "l484" },
{ "id": "arch-presentation", "title": "Arch Presentation", "version": "1.0.0", "description": "The Future of Decentralized Infrastructure.", "icon": "/assets/img/app-icons/arch-presentation.png", "author": "L484", "dockerImage": "", "webUrl": "https://present.l484.com", "category": "l484" },
{ "id": "syntropy-institute", "title": "Syntropy Institute", "version": "1.0.0", "description": "Medicine Reimagined.", "icon": "/assets/img/app-icons/syntropy-institute.png", "author": "Syntropy Institute", "dockerImage": "", "webUrl": "https://syntropy.institute", "category": "l484" },
{ "id": "t-zero", "title": "T-0", "version": "1.0.0", "description": "Documentary series exploring decentralization.", "icon": "/assets/img/app-icons/t-zero.png", "author": "T-0", "dockerImage": "", "webUrl": "https://teeminuszero.net", "category": "l484" }
{
"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": "bitcoin-knots:latest",
"repoUrl": "https://github.com/bitcoinknots/bitcoin",
"category": "money",
"tier": "core"
},
{
"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": "lnd:v0.18.4-beta",
"repoUrl": "https://github.com/lightningnetwork/lnd",
"category": "money",
"tier": "core"
},
{
"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": "btcpayserver:1.13.7",
"repoUrl": "https://github.com/btcpayserver/btcpayserver",
"category": "commerce",
"tier": "core"
},
{
"id": "mempool",
"title": "Mempool Explorer",
"version": "3.0.0",
"description": "Self-hosted Bitcoin blockchain and mempool visualizer. Monitor transactions without revealing your addresses.",
"icon": "/assets/img/app-icons/mempool.webp",
"author": "Mempool",
"dockerImage": "mempool-frontend:v3.0.0",
"repoUrl": "https://github.com/mempool/mempool",
"category": "money",
"tier": "core"
},
{
"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": "electrumx:v1.18.0",
"repoUrl": "https://github.com/spesmilo/electrumx",
"category": "money",
"tier": "core"
},
{
"id": "indeedhub",
"title": "IndeeHub",
"version": "1.0.0",
"description": "Bitcoin documentary streaming with Nostr identity. Stream sovereignty content from your node.",
"icon": "/assets/img/app-icons/indeedhub.png",
"author": "IndeeHub Team",
"dockerImage": "indeedhub:1.0.0",
"repoUrl": "https://github.com/indeedhub/indeedhub",
"category": "community"
},
{
"id": "botfights",
"title": "BotFights",
"version": "1.0.0",
"description": "Bot arena + 2-player arcade fighter with controller support.",
"icon": "/assets/img/app-icons/botfights.svg",
"author": "BotFights",
"dockerImage": "botfights:1.1.0",
"repoUrl": "https://botfights.net",
"category": "community"
},
{
"id": "gitea",
"title": "Gitea",
"version": "1.23",
"description": "Self-hosted Git service with container registry, CI/CD, issue tracking, and package hosting.",
"icon": "/assets/img/app-icons/gitea.svg",
"author": "Gitea",
"dockerImage": "docker.io/gitea/gitea:1.23",
"repoUrl": "https://gitea.com",
"category": "development"
},
{
"id": "filebrowser",
"title": "File Browser",
"version": "2.27.0",
"description": "Web-based file manager. Browse, upload, and manage files on your server.",
"icon": "/assets/img/app-icons/file-browser.webp",
"author": "File Browser",
"dockerImage": "filebrowser:v2.27.0",
"repoUrl": "https://github.com/filebrowser/filebrowser",
"category": "data",
"tier": "core"
},
{
"id": "vaultwarden",
"title": "Vaultwarden",
"version": "1.30.0",
"description": "Self-hosted password vault. Bitwarden-compatible with zero-knowledge encryption.",
"icon": "/assets/img/app-icons/vaultwarden.webp",
"author": "Vaultwarden",
"dockerImage": "vaultwarden:1.30.0-alpine",
"repoUrl": "https://github.com/dani-garcia/vaultwarden",
"category": "data",
"tier": "recommended"
},
{
"id": "searxng",
"title": "SearXNG",
"version": "2024.1.0",
"description": "Privacy-respecting metasearch engine. Search the internet without being tracked.",
"icon": "/assets/img/app-icons/searxng.png",
"author": "SearXNG",
"dockerImage": "searxng:latest",
"repoUrl": "https://github.com/searxng/searxng",
"category": "data",
"tier": "recommended"
},
{
"id": "nostr-rs-relay",
"title": "Nostr Relay",
"version": "0.9.0",
"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": "nostr-rs-relay:0.9.0",
"repoUrl": "https://sr.ht/~gheartsfield/nostr-rs-relay/",
"category": "nostr"
},
{
"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": "fedimintd:v0.10.0",
"repoUrl": "https://github.com/fedimint/fedimint",
"category": "money"
},
{
"id": "ollama",
"title": "Ollama",
"version": "0.5.4",
"description": "Run AI models locally. Llama, Mistral, and more \u2014 on your hardware, completely private.",
"icon": "/assets/img/app-icons/ollama.png",
"author": "Ollama",
"dockerImage": "ollama:latest",
"repoUrl": "https://github.com/ollama/ollama",
"category": "data"
},
{
"id": "nextcloud",
"title": "Nextcloud",
"version": "28",
"description": "Your own private cloud. File sync, calendars, contacts \u2014 all on your hardware.",
"icon": "/assets/img/app-icons/nextcloud.webp",
"author": "Nextcloud",
"dockerImage": "nextcloud:28",
"repoUrl": "https://github.com/nextcloud/server",
"category": "data"
},
{
"id": "jellyfin",
"title": "Jellyfin",
"version": "10.8.13",
"description": "Free media server. Stream your movies, music, and photos to any device.",
"icon": "/assets/img/app-icons/jellyfin.webp",
"author": "Jellyfin",
"dockerImage": "jellyfin:10.8.13",
"repoUrl": "https://github.com/jellyfin/jellyfin",
"category": "data"
},
{
"id": "immich",
"title": "Immich",
"version": "1.90.0",
"description": "High-performance photo and video backup. Mobile-first with ML features.",
"icon": "/assets/img/app-icons/immich.png",
"author": "Immich",
"dockerImage": "immich-server:release",
"repoUrl": "https://github.com/immich-app/immich",
"category": "data"
},
{
"id": "homeassistant",
"title": "Home Assistant",
"version": "2024.1",
"description": "Open-source home automation. Control smart home devices privately.",
"icon": "/assets/img/app-icons/homeassistant.png",
"author": "Home Assistant",
"dockerImage": "home-assistant:2024.1",
"repoUrl": "https://github.com/home-assistant/core",
"category": "home"
},
{
"id": "grafana",
"title": "Grafana",
"version": "10.2.0",
"description": "Analytics and monitoring platform. Dashboards for your node metrics.",
"icon": "/assets/img/app-icons/grafana.png",
"author": "Grafana Labs",
"dockerImage": "grafana:10.2.0",
"repoUrl": "https://github.com/grafana/grafana",
"category": "data",
"tier": "recommended"
},
{
"id": "tailscale",
"title": "Tailscale",
"version": "1.78.0",
"description": "Zero-config VPN. Secure remote access with WireGuard mesh networking.",
"icon": "/assets/img/app-icons/tailscale.webp",
"author": "Tailscale",
"dockerImage": "tailscale:stable",
"repoUrl": "https://github.com/tailscale/tailscale",
"category": "networking",
"tier": "recommended"
},
{
"id": "penpot",
"title": "Penpot",
"version": "2.4",
"description": "Open-source design platform. Self-hosted alternative to Figma.",
"icon": "/assets/img/app-icons/penpot.webp",
"author": "Penpot",
"dockerImage": "penpot-frontend:2.4",
"repoUrl": "https://github.com/penpot/penpot",
"category": "data"
},
{
"id": "photoprism",
"title": "PhotoPrism",
"version": "240915",
"description": "AI-powered photo management with facial recognition, privately.",
"icon": "/assets/img/app-icons/photoprism.svg",
"author": "PhotoPrism",
"dockerImage": "photoprism:240915",
"repoUrl": "https://github.com/photoprism/photoprism",
"category": "data"
},
{
"id": "uptime-kuma",
"title": "Uptime Kuma",
"version": "1.23.0",
"description": "Self-hosted uptime monitoring. Track HTTP, TCP, DNS, and more.",
"icon": "/assets/img/app-icons/uptime-kuma.webp",
"author": "Uptime Kuma",
"dockerImage": "uptime-kuma:1",
"repoUrl": "https://github.com/louislam/uptime-kuma",
"category": "data",
"tier": "recommended"
},
{
"id": "nostr-vpn",
"title": "Nostr VPN",
"version": "0.3.7",
"description": "Tailscale-style mesh VPN with Nostr control plane.",
"icon": "/assets/img/app-icons/nostr-vpn.svg",
"author": "Martti Malmi",
"dockerImage": "nostr-vpn:v0.3.7",
"repoUrl": "https://github.com/mmalmi/nostr-vpn",
"category": "networking"
},
{
"id": "fips",
"title": "FIPS",
"version": "0.1.0",
"description": "Free Internetworking Peering System. Self-organizing encrypted mesh.",
"icon": "/assets/img/app-icons/fips.svg",
"author": "Jim Corgan",
"dockerImage": "fips:v0.1.0",
"repoUrl": "https://github.com/jmcorgan/fips",
"category": "networking"
},
{
"id": "routstr",
"title": "Routstr",
"version": "0.4.3",
"description": "Decentralized AI inference proxy. Pay-per-request with Cashu ecash.",
"icon": "/assets/img/app-icons/routstr.svg",
"author": "Routstr",
"dockerImage": "routstr:v0.4.3",
"repoUrl": "https://github.com/routstr/routstr-core",
"category": "community"
},
{
"id": "dwn",
"title": "Decentralized Web Node",
"version": "0.4.0",
"description": "Own your data with DID-based access control. Sync across devices.",
"icon": "/assets/img/app-icons/dwn.svg",
"author": "TBD",
"dockerImage": "dwn-server:main",
"repoUrl": "https://github.com/TBD54566975/dwn-server",
"category": "data"
},
{
"id": "cryptpad",
"title": "CryptPad",
"version": "2024.12.0",
"description": "End-to-end encrypted documents and collaboration. Zero-knowledge.",
"icon": "/assets/img/app-icons/cryptpad.webp",
"author": "XWiki SAS",
"dockerImage": "cryptpad:2024.12.0",
"repoUrl": "https://github.com/cryptpad/cryptpad",
"category": "data"
},
{
"id": "nostrudel",
"title": "noStrudel",
"version": "0.40.0",
"description": "Feature-rich Nostr web client.",
"icon": "/assets/img/app-icons/nostrudel.svg",
"author": "hzrd149",
"dockerImage": "",
"repoUrl": "https://github.com/hzrd149/nostrudel",
"webUrl": "https://nostrudel.ninja",
"category": "nostr"
},
{
"id": "nwnn",
"title": "Next Web News Network",
"version": "1.0.0",
"description": "Decentralized news aggregator.",
"icon": "/assets/img/app-icons/nwnn.png",
"author": "L484",
"dockerImage": "",
"webUrl": "https://nwnn.l484.com",
"category": "l484"
},
{
"id": "484-kitchen",
"title": "484 Kitchen",
"version": "1.0.0",
"description": "K484 application platform.",
"icon": "/assets/img/app-icons/484-kitchen.png",
"author": "L484",
"dockerImage": "",
"webUrl": "https://484.kitchen",
"category": "l484"
},
{
"id": "call-the-operator",
"title": "Call the Operator",
"version": "1.0.0",
"description": "Escape the Matrix.",
"icon": "/assets/img/app-icons/call-the-operator.png",
"author": "TX1138",
"dockerImage": "",
"webUrl": "https://cta.tx1138.com",
"category": "l484"
},
{
"id": "arch-presentation",
"title": "Arch Presentation",
"version": "1.0.0",
"description": "The Future of Decentralized Infrastructure.",
"icon": "/assets/img/app-icons/arch-presentation.png",
"author": "L484",
"dockerImage": "",
"webUrl": "https://present.l484.com",
"category": "l484"
},
{
"id": "syntropy-institute",
"title": "Syntropy Institute",
"version": "1.0.0",
"description": "Medicine Reimagined.",
"icon": "/assets/img/app-icons/syntropy-institute.png",
"author": "Syntropy Institute",
"dockerImage": "",
"webUrl": "https://syntropy.institute",
"category": "l484"
},
{
"id": "t-zero",
"title": "T-0",
"version": "1.0.0",
"description": "Documentary series exploring decentralization.",
"icon": "/assets/img/app-icons/t-zero.png",
"author": "T-0",
"dockerImage": "",
"webUrl": "https://teeminuszero.net",
"category": "l484"
}
],
"registries": [
"23.182.128.160:3000/lfg2025",
"git.tx1138.com/lfg2025"
]
}

View File

@@ -506,7 +506,12 @@ async function installCommunityApp(app: MarketplaceApp) {
router.push('/dashboard/apps').catch(() => {})
try {
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'downloading', progress: 20, message: 'Downloading container image...' })
await rpcClient.call({ method: 'package.install', params: { id: app.id, dockerImage: app.dockerImage, version: app.version }, timeout: 180000 })
// Pass containerConfig from catalog if available (allows dynamic apps without hardcoded backend config)
const installParams: Record<string, unknown> = { id: app.id, dockerImage: app.dockerImage, version: app.version }
if ((app as Record<string, unknown>).containerConfig) {
installParams.containerConfig = (app as Record<string, unknown>).containerConfig
}
await rpcClient.call({ method: 'package.install', params: installParams, timeout: 180000 })
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'installing', progress: 60, message: 'Starting container...' })
startInstallPolling(app.id, 'Initializing application...')
} catch (err) {

View File

@@ -78,6 +78,13 @@
<!-- Action buttons (right side) -->
<div class="gamepad-actions">
<button
class="action-btn action-c"
@touchstart.prevent="down('c')"
@touchend.prevent="up('c')"
@touchcancel.prevent="up('c')"
aria-label="Special"
>C</button>
<button
class="action-btn action-b"
@touchstart.prevent="down('b')"
@@ -226,17 +233,17 @@ function tap(key: string) { send(key, 'down'); setTimeout(() => send(key, 'up'),
color: rgba(255, 255, 255, 0.8);
}
/* ── Action buttons (A / B) ── */
/* ── Action buttons (A / B / C) ── */
.gamepad-actions {
display: flex;
gap: 12px;
gap: 10px;
align-items: center;
flex-shrink: 0;
}
.action-btn {
width: 60px;
height: 60px;
width: 54px;
height: 54px;
border-radius: 50%;
font-size: 18px;
font-weight: 800;
@@ -267,4 +274,13 @@ function tap(key: string) { send(key, 'down'); setTimeout(() => send(key, 'up'),
.action-b:active {
background: rgba(96, 165, 250, 0.45);
}
.action-c {
background: rgba(74, 222, 128, 0.2);
border-color: rgba(74, 222, 128, 0.5);
color: #4ade80;
}
.action-c:active {
background: rgba(74, 222, 128, 0.45);
}
</style>

View File

@@ -51,9 +51,7 @@ export const APP_PORTS: Record<string, number> = {
/** Apps that need nginx proxy for iframe embedding.
* 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> = {
'gitea': '/app/gitea/',
}
export const PROXY_APPS: Record<string, string> = {}
/** Nginx proxy paths -- used on HTTPS to avoid mixed content (HTTPS parent + HTTP port iframe).
* On HTTP, direct port access is used instead (faster, no proxy). */

View File

@@ -19,27 +19,58 @@ export interface AppCatalog {
}
let cachedCatalog: AppCatalog | null = null
let catalogFetchedAt = 0
const CATALOG_TTL = 60 * 60 * 1000 // 1 hour cache
/** Fetch catalog.json (served by nginx, can be updated independently of the build).
* Returns null if fetch fails — caller should fall back to hardcoded list. */
/** Remote catalog URLs — tried in order. First success wins. */
const CATALOG_URLS = [
// Primary: Gitea raw file (dynamic, updated without frontend rebuild)
'https://git.tx1138.com/lfg2025/app-catalog/raw/branch/main/catalog.json',
// Fallback: direct IP if DNS fails
'http://23.182.128.160:3000/lfg2025/app-catalog/raw/branch/main/catalog.json',
// Last resort: local static file (baked into frontend build)
'/catalog.json',
]
/** Fetch app catalog from remote registry, with local fallback.
* Caches for 1 hour. Returns null only if ALL sources fail. */
export async function fetchAppCatalog(): Promise<AppCatalog | null> {
if (cachedCatalog) return cachedCatalog
try {
const res = await fetch('/catalog.json', { signal: AbortSignal.timeout(5000) })
if (!res.ok) return null
const data = await res.json() as AppCatalog
// Expand short docker image refs to full registry paths
const registry = data.registry || R
for (const app of data.apps) {
if (app.dockerImage && !app.dockerImage.includes('/')) {
app.dockerImage = `${registry}/${app.dockerImage}`
// Return cache if fresh
if (cachedCatalog && Date.now() - catalogFetchedAt < CATALOG_TTL) return cachedCatalog
for (const url of CATALOG_URLS) {
try {
const res = await fetch(url, { signal: AbortSignal.timeout(5000) })
if (!res.ok) continue
const data = await res.json() as AppCatalog
if (!data.apps?.length) continue
// Expand short docker image refs to full registry paths
const registry = data.registry || R
for (const app of data.apps) {
if (app.dockerImage && !app.dockerImage.includes('/')) {
app.dockerImage = `${registry}/${app.dockerImage}`
}
}
}
cachedCatalog = data
return data
} catch {
return null
cachedCatalog = data
catalogFetchedAt = Date.now()
// Cache in localStorage for offline fallback
try { localStorage.setItem('archy_catalog', JSON.stringify(data)) } catch {}
return data
} catch { continue }
}
// Try localStorage cache as final fallback
try {
const stored = localStorage.getItem('archy_catalog')
if (stored) {
cachedCatalog = JSON.parse(stored) as AppCatalog
catalogFetchedAt = Date.now() - CATALOG_TTL + 5 * 60 * 1000 // re-check in 5 min
return cachedCatalog
}
} catch {}
return null
}
// ---------- Hardcoded fallback (used when catalog.json is unavailable) ----------
@@ -75,6 +106,7 @@ export function getCuratedAppList(): MarketplaceApp[] {
{ id: 'routstr', title: 'Routstr', version: '0.4.3', category: 'community', description: 'Decentralized AI inference proxy. Pay-per-request with Cashu ecash, provider discovery via Nostr.', icon: '/assets/img/app-icons/routstr.svg', author: 'Routstr', dockerImage: `${R}/routstr:v0.4.3`, repoUrl: 'https://github.com/routstr/routstr-core' },
{ id: 'nostrudel', title: 'noStrudel', version: '0.40.0', category: 'nostr', description: 'Feature-rich Nostr web client. Browse feeds, post notes, manage relays with NIP-07.', icon: '/assets/img/app-icons/nostrudel.svg', author: 'hzrd149', dockerImage: '', repoUrl: 'https://github.com/hzrd149/nostrudel', webUrl: 'https://nostrudel.ninja' },
{ id: 'botfights', title: 'BotFights', version: '1.0.0', category: 'community', description: 'Bot arena + 2-player arcade fighter with controller support. AI bots battle in trivia, humans duke it out with controllers.', icon: '/assets/img/app-icons/botfights.svg', author: 'BotFights', dockerImage: `${R}/botfights:1.1.0`, repoUrl: 'https://botfights.net' },
{ id: 'gitea', title: 'Gitea', version: '1.23', category: 'development', description: 'Self-hosted Git service with container registry, CI/CD, issue tracking, and package hosting.', icon: '/assets/img/app-icons/gitea.svg', author: 'Gitea', dockerImage: 'docker.io/gitea/gitea:1.23', repoUrl: 'https://gitea.com' },
{ id: 'nwnn', title: 'Next Web News Network', version: '1.0.0', category: 'l484', description: 'Decentralized news aggregator. Community-curated Bitcoin and sovereignty content.', icon: '/assets/img/app-icons/nwnn.png', author: 'L484', dockerImage: '', repoUrl: 'https://nwnn.l484.com', webUrl: 'https://nwnn.l484.com' },
{ id: '484-kitchen', title: '484 Kitchen', version: '1.0.0', category: 'l484', description: 'K484 application platform for the L484 network.', icon: '/assets/img/app-icons/484-kitchen.png', author: 'L484', dockerImage: '', repoUrl: 'https://484.kitchen', webUrl: 'https://484.kitchen' },
{ id: 'call-the-operator', title: 'Call the Operator', version: '1.0.0', category: 'l484', description: 'Escape the Matrix — explore decentralized alternatives and reclaim sovereignty.', icon: '/assets/img/app-icons/call-the-operator.png', author: 'TX1138', dockerImage: '', repoUrl: 'https://cta.tx1138.com', webUrl: 'https://cta.tx1138.com' },

View File

@@ -1,10 +1,21 @@
import type { MarketplaceAppInfo } from '@/composables/useMarketplaceApp'
/** Container config that can be passed from the remote catalog to the backend
* for apps not hardcoded in config.rs */
export interface ContainerConfig {
ports?: string[]
volumes?: string[]
env?: string[]
}
export type MarketplaceApp = Partial<MarketplaceAppInfo> & {
id: string
trustScore?: number
trustTier?: string
relayCount?: number
containerConfig?: ContainerConfig
requires?: string[]
tier?: string
}
export type FeaturedApp = MarketplaceApp & {

View File

@@ -260,7 +260,7 @@ load_spec_fedimint() {
SPEC_PORTS="8173:8173 8174:8174 8175:8175"
SPEC_VOLUMES="/var/lib/archipelago/fedimint:/data"
SPEC_MEMORY="$(mem_limit fedimint)"
SPEC_HEALTH_CMD="curl -sf http://localhost:8174/ || exit 1"
SPEC_HEALTH_CMD="curl -sf http://localhost:8175/ || exit 1"
SPEC_ENV="FM_DATA_DIR=/data FM_BITCOIND_USERNAME=$BITCOIN_RPC_USER FM_BITCOIND_PASSWORD=$BITCOIN_RPC_PASS FM_BITCOIN_NETWORK=bitcoin FM_BIND_P2P=0.0.0.0:8173 FM_BIND_API=0.0.0.0:8174 FM_BIND_UI=0.0.0.0:8175 FM_P2P_URL=fedimint://$HOST_IP:8173 FM_API_URL=ws://$HOST_IP:8174 FM_BITCOIND_URL=http://$HOST_IP:8332"
SPEC_TIER="2"
SPEC_DATA_DIR="/var/lib/archipelago/fedimint"