Some checks failed
Demo images / Build & push demo images (push) Failing after 1m36s
Fresh offline installs came up without fedimint-clientd and filebrowser (they only appeared after connecting internet). Three root causes: - archipelago-load-images.service ran 'podman load' as root, but every container runs rootless as archipelago — bundled images landed in root's storage where the rootless runtime can't see them, so all container creation silently depended on registry pulls. The loader now loads into the archipelago user's storage (with linger + runtime-dir wait + system migrate). - The unbundled ISO bundled only filebrowser.tar; fmcd (fedimint-clientd) is a baseline first-boot app too and is now part of the unbundled core bundle. - first-boot's pull_with_fallback always hit the network; it now uses an already-loaded local image first and skips the pull entirely. Also: fedimint-clientd added to the UI's hardcoded curated-app fallback list (it was missing when all catalog fetches fail offline), plus its INSTALLED_ALIASES entry, and the stale fmcd bundling comment in image-versions.sh corrected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
201 lines
16 KiB
TypeScript
201 lines
16 KiB
TypeScript
import type { MarketplaceApp } from './types'
|
|
|
|
const R = '146.59.87.168:3000/lfg2025'
|
|
|
|
// ---------- Dynamic catalog from registry ----------
|
|
export interface CatalogFeatured {
|
|
id: string
|
|
banner: string
|
|
headline: string
|
|
description: string
|
|
tag: string
|
|
}
|
|
|
|
export interface AppCatalog {
|
|
version: number
|
|
registry: string
|
|
featured: CatalogFeatured
|
|
apps: MarketplaceApp[]
|
|
}
|
|
|
|
let cachedCatalog: AppCatalog | null = null
|
|
let catalogFetchedAt = 0
|
|
const CATALOG_TTL = 60 * 60 * 1000 // 1 hour cache
|
|
|
|
/** Catalog URLs tried in order. First success wins.
|
|
* Primary is the backend proxy (`/api/app-catalog`) — server-side fetch
|
|
* bypasses CORS on the upstream Gitea and CSP restrictions on the IP-port
|
|
* fallback. If the backend is offline (mid-restart etc.) we fall back
|
|
* to the static copy baked into the frontend build. */
|
|
const CATALOG_URLS = [
|
|
'/api/app-catalog',
|
|
'/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> {
|
|
// 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, { credentials: 'include', signal: AbortSignal.timeout(20000) })
|
|
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
|
|
catalogFetchedAt = Date.now()
|
|
// Cache in localStorage for offline fallback
|
|
try { localStorage.setItem('archy_catalog', JSON.stringify(data)) } catch {}
|
|
return data
|
|
} catch (e) {
|
|
console.warn(`[catalog] fetch failed for ${url}:`, e)
|
|
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 (e) {
|
|
console.warn('[catalog] localStorage fallback unreadable:', e)
|
|
}
|
|
|
|
console.warn('[catalog] all sources failed — using hardcoded app list')
|
|
return null
|
|
}
|
|
|
|
// ---------- Hardcoded fallback (used when catalog.json is unavailable) ----------
|
|
export function getCuratedAppList(): MarketplaceApp[] {
|
|
return [
|
|
{ id: 'bitcoin-knots', title: 'Bitcoin Knots', version: '28.1.0', description: 'Run a full Bitcoin node. Validate and relay blocks and transactions on the Bitcoin network.', icon: '/assets/img/app-icons/bitcoin-knots.webp', author: 'Bitcoin Knots', dockerImage: `${R}/bitcoin-knots:latest`, repoUrl: 'https://github.com/bitcoinknots/bitcoin' },
|
|
{ id: 'bitcoin-core', title: 'Bitcoin Core', version: '28.4', description: 'Reference implementation of the Bitcoin protocol. Run a full node validating and relaying blocks on the Bitcoin network.', icon: '/assets/img/app-icons/bitcoin-core.svg', author: 'Bitcoin Core contributors', dockerImage: 'docker.io/bitcoin/bitcoin:28.4', repoUrl: 'https://github.com/bitcoin/bitcoin' },
|
|
{ id: 'btcpay-server', title: 'BTCPay Server', version: '2.3.9', description: 'Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries or fees.', icon: '/assets/img/app-icons/btcpay-server.png', author: 'BTCPay Server Foundation', dockerImage: 'docker.io/btcpayserver/btcpayserver:2.3.9', repoUrl: 'https://github.com/btcpayserver/btcpayserver' },
|
|
{ id: 'lnd', title: 'LND', version: '0.18.4', description: 'Lightning Network Daemon. Fast and cheap Bitcoin payments through the Lightning Network.', icon: '/assets/img/app-icons/lnd.png', author: 'Lightning Labs', dockerImage: `${R}/lnd:v0.18.4-beta`, repoUrl: 'https://github.com/lightningnetwork/lnd' },
|
|
{ id: 'mempool', title: 'Mempool Explorer', version: '3.0.0', description: 'Self-hosted Bitcoin blockchain and mempool visualizer. Monitor transactions without revealing your addresses to third parties.', icon: '/assets/img/app-icons/mempool.webp', author: 'Mempool', dockerImage: `${R}/mempool-frontend:v3.0.0`, repoUrl: 'https://github.com/mempool/mempool' },
|
|
{ id: 'homeassistant', title: 'Home Assistant', version: '2024.1', description: 'Open-source home automation. Control smart home devices privately, on your own hardware.', icon: '/assets/img/app-icons/homeassistant.png', author: 'Home Assistant', dockerImage: `${R}/home-assistant:2024.1`, repoUrl: 'https://github.com/home-assistant/core' },
|
|
{ id: 'grafana', title: 'Grafana', version: '10.2.0', description: 'Analytics and monitoring platform. Dashboards for your node metrics and system health.', icon: '/assets/img/app-icons/grafana.png', author: 'Grafana Labs', dockerImage: `${R}/grafana:10.2.0`, repoUrl: 'https://github.com/grafana/grafana' },
|
|
{ id: 'searxng', title: 'SearXNG', version: '2024.1.0', description: 'Privacy-respecting metasearch engine. Search the internet without being tracked or profiled.', icon: '/assets/img/app-icons/searxng.png', author: 'SearXNG', dockerImage: `${R}/searxng:latest`, repoUrl: 'https://github.com/searxng/searxng' },
|
|
{ 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: `${R}/ollama:latest`, repoUrl: 'https://github.com/ollama/ollama' },
|
|
{ id: 'cryptpad', title: 'CryptPad', version: '2024.12.0', description: 'End-to-end encrypted documents, spreadsheets, and presentations. Zero-knowledge collaboration.', icon: '/assets/icon/favico-black-v2.svg', author: 'XWiki SAS', dockerImage: `${R}/cryptpad:2024.12.0`, repoUrl: 'https://github.com/cryptpad/cryptpad' },
|
|
{ id: 'nextcloud', title: 'Nextcloud', version: '29', description: 'Your own private cloud. File sync, calendars, contacts — all on your hardware.', icon: '/assets/img/app-icons/nextcloud.webp', author: 'Nextcloud', dockerImage: `${R}/nextcloud:29`, repoUrl: 'https://github.com/nextcloud/server' },
|
|
{ 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: `${R}/vaultwarden:1.30.0-alpine`, repoUrl: 'https://github.com/dani-garcia/vaultwarden' },
|
|
{ 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: `${R}/jellyfin:10.8.13`, repoUrl: 'https://github.com/jellyfin/jellyfin' },
|
|
{ id: 'photoprism', title: 'PhotoPrism', version: '240915', description: 'AI-powered photo management. Organize photos with facial recognition, privately.', icon: '/assets/img/app-icons/photoprism.svg', author: 'PhotoPrism', dockerImage: `${R}/photoprism:240915`, repoUrl: 'https://github.com/photoprism/photoprism' },
|
|
{ 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: `${R}/immich-server:release`, repoUrl: 'https://github.com/immich-app/immich' },
|
|
{ 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: `${R}/filebrowser:v2.27.0`, repoUrl: 'https://github.com/filebrowser/filebrowser' },
|
|
{ id: 'nginx-proxy-manager', title: 'Nginx Proxy Manager', version: '2.12.1', description: 'Reverse proxy with SSL. Beautiful web interface for managing proxies.', icon: '/assets/img/app-icons/nginx.svg', author: 'Nginx Proxy Manager', dockerImage: `${R}/nginx-proxy-manager:latest`, repoUrl: 'https://github.com/NginxProxyManager/nginx-proxy-manager' },
|
|
{ id: 'portainer', title: 'Portainer', version: '2.19.4', description: 'Container management UI. Manage your containerized services through the web.', icon: '/assets/img/app-icons/portainer.webp', author: 'Portainer', dockerImage: `${R}/portainer:latest`, repoUrl: 'https://github.com/portainer/portainer' },
|
|
{ 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: `${R}/uptime-kuma:1`, repoUrl: 'https://github.com/louislam/uptime-kuma' },
|
|
{ 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: `${R}/tailscale:stable`, repoUrl: 'https://github.com/tailscale/tailscale' },
|
|
{ id: 'netbird', title: 'NetBird', version: '0.71.2', description: 'Self-hosted WireGuard mesh VPN control plane with dashboard, embedded identity provider, management API, signal, relay, and STUN.', icon: '/assets/img/app-icons/netbird.svg', author: 'NetBird', dockerImage: 'docker.io/netbirdio/dashboard:v2.38.0', repoUrl: 'https://github.com/netbirdio/netbird' },
|
|
{ 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.png', author: 'Luke Childs', dockerImage: `${R}/electrumx:v1.18.0`, repoUrl: 'https://github.com/spesmilo/electrumx' },
|
|
{ id: 'fedimint', title: 'Fedimint Guardian', 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: 'fedimint-clientd', title: 'Fedimint Client', version: '0.8.0', description: 'Fedimint ecash client daemon (fmcd). Lets the node hold Fedimint ecash and join federations; the wallet talks to it over a local REST API.', icon: '/assets/img/app-icons/fedimint.png', author: 'Fedimint', dockerImage: `${R}/fmcd:0.8.1`, repoUrl: 'https://github.com/minmoto/fmcd' },
|
|
{ id: 'indeedhub', title: 'Indeehub', version: '1.0.0', description: 'Bitcoin documentary streaming with Nostr identity. Stream sovereignty content.', icon: '/assets/img/app-icons/indeedhub.png', author: 'Indeehub Team', dockerImage: `${R}/indeedhub:1.0.0`, repoUrl: 'https://github.com/indeedhub/indeedhub' },
|
|
{ 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' },
|
|
]
|
|
}
|
|
|
|
// Only PRIMARY containers trigger "installed" status.
|
|
// Supporting containers (DBs, caches, workers) do NOT — having only a DB
|
|
// without the main app should not mark the app as installed in the UI.
|
|
export const INSTALLED_ALIASES: Record<string, string[]> = {
|
|
mempool: ['mempool', 'mempool-web', 'archy-mempool-web'],
|
|
bitcoin: ['bitcoin-knots'],
|
|
btcpay: ['btcpay-server'],
|
|
immich: ['immich-server', 'immich-app', 'immich_server'],
|
|
nextcloud: ['nextcloud-aio', 'nextcloud-server'],
|
|
fedimint: ['fedimint-gateway'],
|
|
'fedimint-clientd': ['fedimint-clientd'],
|
|
electrumx: ['electrumx'],
|
|
grafana: ['grafana'],
|
|
jellyfin: ['jellyfin'],
|
|
vaultwarden: ['vaultwarden'],
|
|
searxng: ['searxng'],
|
|
homeassistant: ['homeassistant'],
|
|
photoprism: ['photoprism'],
|
|
lnd: ['lnd'],
|
|
filebrowser: ['filebrowser'],
|
|
tailscale: ['tailscale'],
|
|
netbird: ['netbird'],
|
|
ollama: ['ollama'],
|
|
indeedhub: ['indeedhub'],
|
|
botfights: ['botfights'],
|
|
}
|
|
|
|
// Featured apps shown at the top of the App Store.
|
|
// The first entry with a `banner` is displayed as a full-width hero banner.
|
|
// To change the featured app, move the desired entry to position 0 and set its `banner`.
|
|
export const FEATURED_DEFINITIONS: {
|
|
id: string
|
|
desc: string
|
|
tag: string
|
|
banner?: string // path to banner image (shown as full-width hero)
|
|
}[] = [
|
|
{
|
|
id: 'indeedhub',
|
|
desc: 'Bitcoin documentaries with Nostr identity. God Bless Bitcoin, The Bitcoin Psyop, and more — streaming from your own node. No accounts, no subscriptions. Sign in with Nostr.',
|
|
tag: 'NOSTR IDENTITY // YOUR NODE',
|
|
banner: '/assets/img/featured/indeedhub-banner.jpg',
|
|
},
|
|
{
|
|
id: 'bitcoin-knots',
|
|
desc: 'The foundation of sovereignty. Run a full Bitcoin node to validate every transaction yourself. No trusted third parties. No asking permission. Your node enforces the consensus rules that protect your wealth. Don\'t trust — verify.',
|
|
tag: 'FULL VALIDATION // ZERO TRUST',
|
|
},
|
|
{
|
|
id: 'bitcoin-core',
|
|
desc: 'The reference Bitcoin implementation. Same full-node guarantees as Knots, tracking upstream releases from the Bitcoin Core maintainers. Pick this if you\'d rather run mainline Bitcoin Core than Knots — both validate every block themselves.',
|
|
tag: 'REFERENCE CLIENT // ZERO TRUST',
|
|
},
|
|
{
|
|
id: 'lnd',
|
|
desc: 'Lightning-fast payments over the Lightning Network. Open channels, route transactions, and earn routing fees — all from your sovereign node. Instant settlement. Near-zero fees. The future of money, running on your hardware.',
|
|
tag: 'INSTANT SETTLEMENT // YOUR CHANNELS',
|
|
},
|
|
{
|
|
id: 'btcpay-server',
|
|
desc: 'Accept Bitcoin payments without intermediaries. No fees to payment processors. No KYC. No permission needed. Your commerce, your terms. Self-hosted payment infrastructure that makes you truly independent.',
|
|
tag: 'NO INTERMEDIARIES // NO KYC',
|
|
},
|
|
{
|
|
id: 'vaultwarden',
|
|
desc: 'Your passwords belong to you. Self-hosted password vault with full Bitwarden compatibility. Zero-knowledge encryption means even you can\'t see your passwords without your master key. No cloud required — your secrets, your server.',
|
|
tag: 'ZERO KNOWLEDGE // SELF-HOSTED',
|
|
},
|
|
]
|
|
|
|
export function categorizeCommunityApp(app: MarketplaceApp): string {
|
|
if (app.category) return app.category
|
|
const id = app.id.toLowerCase()
|
|
const title = app.title?.toLowerCase() || ''
|
|
const description = (typeof app.description === 'string' ? app.description : app.description?.short ?? '').toLowerCase()
|
|
const combined = `${id} ${title} ${description}`
|
|
|
|
if (id.includes('bitcoin') || id.includes('btc') || id.includes('lightning') || id.includes('lnd') || id.includes('electr') || id.includes('fedimint') || id.includes('cashu') || combined.includes('wallet')) return 'money'
|
|
if (id.includes('btcpay') || id.includes('commerce') || id.includes('shop') || id.includes('pos') || combined.includes('merchant')) return 'commerce'
|
|
if (id.includes('cloud') || id.includes('nextcloud') || id.includes('storage') || id.includes('file') || id.includes('photo') || id.includes('immich') || id.includes('jellyfin') || id.includes('media') || id.includes('vault') || combined.includes('password manager')) return 'data'
|
|
if (id.includes('home-assistant') || id.includes('homeassistant') || combined.includes('home automation')) return 'home'
|
|
if (id.includes('nostr') || combined.includes('nostr relay')) return 'nostr'
|
|
if (id.includes('vpn') || id.includes('wireguard') || id.includes('tailscale') || id.includes('netbird') || id.includes('proxy') || id.includes('dns') || id.includes('tor') || combined.includes('network')) return 'networking'
|
|
if (id.includes('matrix') || id.includes('mastodon') || id.includes('chat') || id.includes('social') || combined.includes('messaging')) return 'community'
|
|
return 'other'
|
|
}
|