Enhance README and RPC for package management

- Added instructions to README.md for building an ISO from source and flashing it to USB.
- Introduced a new RPC method for package installation, including security checks and container management.
- Updated Docker and Podman integration in build scripts to support both container runtimes.
- Enhanced Nginx configuration for improved timeout settings and WebSocket support.
- Added new app metadata for additional applications in the Docker package scanner.
This commit is contained in:
Dorian
2026-02-01 18:46:35 +00:00
parent 22024bde84
commit 0f40cb88b5
59 changed files with 3473 additions and 360 deletions
+37
View File
@@ -18,6 +18,9 @@ export class WebSocketClient {
private reconnectTimer: ReturnType<typeof setTimeout> | null = null
private visibilityChangeHandler: (() => void) | null = null
private onlineHandler: (() => void) | null = null
private heartbeatTimer: ReturnType<typeof setTimeout> | null = null
private lastMessageTime: number = Date.now()
private heartbeatInterval = 10000 // Check connection every 10 seconds
constructor(url: string = '/ws/db') {
this.url = url
@@ -132,8 +135,10 @@ export class WebSocketClient {
this.ws.onopen = () => {
clearTimeout(connectionTimeout)
this.reconnectAttempts = 0
this.lastMessageTime = Date.now()
console.log('[WebSocket] Connected successfully')
this.notifyConnectionState(true)
this.startHeartbeat()
resolve()
}
@@ -145,6 +150,7 @@ export class WebSocketClient {
}
this.ws.onmessage = (event) => {
this.lastMessageTime = Date.now()
try {
const update: Update = JSON.parse(event.data)
this.callbacks.forEach((callback) => callback(update))
@@ -155,6 +161,7 @@ export class WebSocketClient {
this.ws.onclose = (event) => {
clearTimeout(connectionTimeout)
this.stopHeartbeat()
console.log('[WebSocket] Closed', { code: event.code, reason: event.reason, wasClean: event.wasClean })
// Notify connection state changed
@@ -243,9 +250,39 @@ export class WebSocketClient {
this.connectionStateCallbacks.forEach((callback) => callback(connected))
}
private startHeartbeat(): void {
this.stopHeartbeat()
this.heartbeatTimer = setInterval(() => {
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
console.warn('[WebSocket] Heartbeat detected closed connection')
this.stopHeartbeat()
return
}
// Check if we've received a message recently
const timeSinceLastMessage = Date.now() - this.lastMessageTime
// If no message for more than 60 seconds, assume connection is stale
if (timeSinceLastMessage > 60000) {
console.warn('[WebSocket] No messages for 60s, reconnecting...')
this.ws.close()
return
}
}, this.heartbeatInterval)
}
private stopHeartbeat(): void {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer)
this.heartbeatTimer = null
}
}
disconnect(): void {
this.shouldReconnect = false
this.reconnectAttempts = 0
this.stopHeartbeat()
// Clear reconnect timer
if (this.reconnectTimer) {
+1 -1
View File
@@ -335,7 +335,7 @@ export const dummyApps: Record<string, PackageDataEntry> = {
'static-files': {
license: 'MIT',
instructions: 'Local AI model runner',
icon: '/assets/img/ollama.webp'
icon: '/assets/img/app-icons/ollama.png'
},
manifest: {
id: 'ollama',
+87 -117
View File
@@ -111,7 +111,7 @@
Already Installed
</button>
<button
v-else-if="app.source === 'local' || app.manifestUrl"
v-else-if="app.source === 'local' || app.dockerImage"
@click.stop="app.source === 'local' ? installApp(app) : installCommunityApp(app)"
:disabled="installing === app.id || installing !== null"
class="flex-1 px-4 py-2 gradient-button rounded-lg text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed"
@@ -268,7 +268,10 @@
<div class="flex items-center gap-3">
<!-- Category Icon -->
<div class="flex-shrink-0 w-10 h-10 rounded-lg bg-white/10 flex items-center justify-center">
<svg v-if="category.id === 'community'" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg v-if="category.id === 'all'" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
</svg>
<svg v-else-if="category.id === 'community'" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
<svg v-else-if="category.id === 'commerce'" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -323,9 +326,10 @@ const { setCurrentApp } = useMarketplaceApp()
const { bottomPosition } = useMobileBackButton()
// Category state
const selectedCategory = ref('community')
const selectedCategory = ref('all')
const categories = [
{ id: 'all', name: 'All' },
{ id: 'community', name: 'Community' },
{ id: 'commerce', name: 'Commerce' },
{ id: 'money', name: 'Money' },
@@ -358,68 +362,33 @@ const communityApps = ref<any[]>([])
const searchQuery = ref('')
// Available apps in marketplace
const availableApps = ref([
{
id: 'atob',
title: 'A to B Bitcoin',
version: '0.1.0',
icon: '/assets/img/atob.png',
category: 'community',
description: {
short: 'Bitcoin tools and services for seamless transactions',
long: 'A to B Bitcoin provides tools and services for Bitcoin transactions. Access the A to B platform through your Archipelago server with full privacy and control.'
},
s9pkUrl: '/packages/atob.s9pk'
},
{
id: 'k484',
title: 'K484',
version: '0.1.0',
icon: '/assets/img/k484.png',
category: 'commerce',
description: {
short: 'Point of Sale and Admin system for Archipelago',
long: 'K484 provides a complete POS and administration system for your Archipelago server. Choose between POS mode for transactions or Admin mode for management.'
},
s9pkUrl: '/packages/k484.s9pk'
},
{
id: 'btcpay',
title: 'BTCPay Server',
version: '0.1.0',
icon: '/assets/img/btcpay.png',
category: 'commerce',
description: {
short: 'Self-hosted Bitcoin payment processor',
long: 'BTCPay Server is a free, open-source cryptocurrency payment processor. Accept Bitcoin payments without intermediaries or fees. Complete merchant solution with invoicing, point of sale, and more.'
},
s9pkUrl: '/packages/btcpay.s9pk'
},
{
id: 'nextcloud',
title: 'Nextcloud',
version: '0.1.0',
icon: '/assets/img/nextcloud.png',
category: 'data',
description: {
short: 'Self-hosted file sync and collaboration platform',
long: 'Nextcloud provides file storage, sync, and sharing with calendar, contacts, mail, and office suite integration. Your own private cloud with complete control over your data.'
},
s9pkUrl: '/packages/nextcloud.s9pk'
},
{
id: 'home-assistant',
title: 'Home Assistant',
version: '0.1.0',
icon: '/assets/img/home-assistant.png',
category: 'home',
description: {
short: 'Open-source home automation platform',
long: 'Home Assistant is a powerful home automation hub that allows you to control and automate your smart home devices privately. No cloud required - everything runs locally on your Archipelago server.'
},
s9pkUrl: '/packages/home-assistant.s9pk'
}
])
// Note: s9pk packages disabled until sideload functionality is implemented
// const availableApps = ref([
// {
// id: 'atob',
// title: 'A to B Bitcoin',
// version: '0.1.0',
// icon: '/assets/img/atob.png',
// category: 'community',
// description: {
// short: 'Bitcoin tools and services for seamless transactions',
// long: 'A to B Bitcoin provides tools and services for Bitcoin transactions. Access the A to B platform through your Archipelago server with full privacy and control.'
// },
// s9pkUrl: '/packages/atob.s9pk'
// },
// {
// id: 'k484',
// title: 'K484',
// version: '0.1.0',
// icon: '/assets/img/k484.png',
// category: 'commerce',
// description: {
// short: 'Point of Sale and Admin system for Archipelago',
// long: 'K484 provides a complete POS and administration system for your Archipelago server. Choose between POS mode for transactions or Admin mode for management.'
// },
// s9pkUrl: '/packages/k484.s9pk'
// },
// ])
const installedPackages = computed(() => {
return store.data?.['package-data'] || {}
@@ -486,14 +455,8 @@ function categorizeCommunityApp(app: any): string {
// Combine local and community apps with categories
const allApps = computed(() => {
// Local apps already have categories - normalize field names
const local = availableApps.value.map(app => ({
...app,
source: 'local',
// Add manifestUrl and url for consistency with community apps
manifestUrl: app.s9pkUrl,
url: app.s9pkUrl
}))
// Local apps disabled until s9pk support is implemented
const local: any[] = []
// Categorize community apps intelligently
const community = communityApps.value.map(app => {
@@ -567,14 +530,14 @@ function getCuratedAppList() {
return [
{
id: 'bitcoin',
title: 'Bitcoin Core',
title: 'Bitcoin Knots',
version: '27.0.0',
description: 'Run a full Bitcoin node. Validate and relay blocks and transactions on the Bitcoin network.',
icon: '/assets/img/app-icons/bitcoin.svg',
author: 'Bitcoin Core',
dockerImage: 'lncm/bitcoind:v27.0',
icon: '/assets/img/app-icons/bitcoin-knots.webp',
author: 'Bitcoin Knots',
dockerImage: 'docker.io/lncm/bitcoind:v27.0',
manifestUrl: null,
repoUrl: 'https://github.com/bitcoin/bitcoin'
repoUrl: 'https://github.com/bitcoinknots/bitcoin'
},
{
id: 'btcpay-server',
@@ -583,7 +546,7 @@ function getCuratedAppList() {
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/btcpayserver:1.13.5',
dockerImage: 'docker.io/btcpayserver/btcpayserver:1.13.5',
manifestUrl: null,
repoUrl: 'https://github.com/btcpayserver/btcpayserver'
},
@@ -592,9 +555,9 @@ function getCuratedAppList() {
title: 'LND',
version: '0.17.4',
description: 'Lightning Network Daemon. Fast and cheap Bitcoin payments through the Lightning Network.',
icon: '/assets/img/app-icons/lightning-stack.png',
icon: '/assets/img/app-icons/lnd.svg',
author: 'Lightning Labs',
dockerImage: 'lightninglabs/lnd:v0.17.4-beta',
dockerImage: 'docker.io/lightninglabs/lnd:v0.17.4-beta',
manifestUrl: null,
repoUrl: 'https://github.com/lightningnetwork/lnd'
},
@@ -603,9 +566,9 @@ function getCuratedAppList() {
title: 'Mempool Explorer',
version: '2.5.0',
description: 'Self-hosted Bitcoin blockchain and mempool visualizer with beautiful explorer interface.',
icon: '/assets/img/app-icons/mempool.png',
icon: '/assets/img/app-icons/mempool.webp',
author: 'Mempool',
dockerImage: 'mempool/frontend:v2.5.0',
dockerImage: 'docker.io/mempool/frontend:v2.5.0',
manifestUrl: null,
repoUrl: 'https://github.com/mempool/mempool'
},
@@ -616,7 +579,7 @@ function getCuratedAppList() {
description: 'Open-source home automation platform. Control and automate your smart home devices privately.',
icon: '/assets/img/app-icons/homeassistant.png',
author: 'Home Assistant',
dockerImage: 'homeassistant/home-assistant:2024.1',
dockerImage: 'docker.io/homeassistant/home-assistant:2024.1',
manifestUrl: null,
repoUrl: 'https://github.com/home-assistant/core'
},
@@ -625,9 +588,9 @@ function getCuratedAppList() {
title: 'Grafana',
version: '10.2.0',
description: 'Analytics and monitoring platform. Create dashboards and visualize data from multiple sources.',
icon: '/assets/img/grafana.png',
icon: '/assets/img/app-icons/grafana.png',
author: 'Grafana Labs',
dockerImage: 'grafana/grafana:10.2.0',
dockerImage: 'docker.io/grafana/grafana:10.2.0',
manifestUrl: null,
repoUrl: 'https://github.com/grafana/grafana'
},
@@ -638,7 +601,7 @@ function getCuratedAppList() {
description: 'Privacy-respecting metasearch engine. Search without tracking or ads.',
icon: '/assets/img/app-icons/searxng.png',
author: 'SearXNG',
dockerImage: 'searxng/searxng:latest',
dockerImage: 'docker.io/searxng/searxng:latest',
manifestUrl: null,
repoUrl: 'https://github.com/searxng/searxng'
},
@@ -647,9 +610,9 @@ function getCuratedAppList() {
title: 'Ollama',
version: '0.1.0',
description: 'Run large language models locally. Download and run AI models like Llama, Mistral on your own hardware.',
icon: '/assets/img/ollama.webp',
icon: '/assets/img/app-icons/ollama.png',
author: 'Ollama',
dockerImage: 'ollama/ollama:latest',
dockerImage: 'docker.io/ollama/ollama:latest',
manifestUrl: null,
repoUrl: 'https://github.com/ollama/ollama'
},
@@ -658,9 +621,9 @@ function getCuratedAppList() {
title: 'OnlyOffice',
version: '7.5.1',
description: 'Office suite for document collaboration. Edit docs, spreadsheets, and presentations.',
icon: '/assets/img/onlyoffice.webp',
icon: '/assets/img/app-icons/onlyoffice.webp',
author: 'Ascensio System SIA',
dockerImage: 'onlyoffice/documentserver:7.5.1',
dockerImage: 'docker.io/onlyoffice/documentserver:7.5.1',
manifestUrl: null,
repoUrl: 'https://github.com/ONLYOFFICE/DocumentServer'
},
@@ -671,7 +634,7 @@ function getCuratedAppList() {
description: 'Open-source design and prototyping platform. Self-hosted alternative to Figma.',
icon: '/assets/img/penpot.webp',
author: 'Penpot',
dockerImage: 'penpotapp/frontend:latest',
dockerImage: 'docker.io/penpotapp/frontend:latest',
manifestUrl: null,
repoUrl: 'https://github.com/penpot/penpot'
},
@@ -680,9 +643,9 @@ function getCuratedAppList() {
title: 'Nextcloud',
version: '28.0',
description: 'Self-hosted cloud storage and collaboration platform. Your own private cloud.',
icon: null,
icon: '/assets/img/app-icons/nextcloud.webp',
author: 'Nextcloud',
dockerImage: 'nextcloud:28',
dockerImage: 'docker.io/library/nextcloud:28',
manifestUrl: null,
repoUrl: 'https://github.com/nextcloud/server'
},
@@ -691,9 +654,9 @@ function getCuratedAppList() {
title: 'Vaultwarden',
version: '1.30.0',
description: 'Self-hosted password manager (Bitwarden-compatible). Secure vault for passwords and secrets.',
icon: null,
icon: '/assets/img/app-icons/vaultwarden.webp',
author: 'Vaultwarden',
dockerImage: 'vaultwarden/server:1.30.0-alpine',
dockerImage: 'docker.io/vaultwarden/server:1.30.0-alpine',
manifestUrl: null,
repoUrl: 'https://github.com/dani-garcia/vaultwarden'
},
@@ -702,9 +665,9 @@ function getCuratedAppList() {
title: 'Jellyfin',
version: '10.8.0',
description: 'Free media server system. Stream your movies, music, and photos to any device.',
icon: null,
icon: '/assets/img/app-icons/jellyfin.webp',
author: 'Jellyfin',
dockerImage: 'jellyfin/jellyfin:10.8.13',
dockerImage: 'docker.io/jellyfin/jellyfin:10.8.13',
manifestUrl: null,
repoUrl: 'https://github.com/jellyfin/jellyfin'
},
@@ -713,9 +676,9 @@ function getCuratedAppList() {
title: 'PhotoPrism',
version: '231128',
description: 'AI-powered photo management. Organize and browse photos with facial recognition.',
icon: null,
icon: '/assets/img/app-icons/photoprims.svg',
author: 'PhotoPrism',
dockerImage: 'photoprism/photoprism:latest',
dockerImage: 'docker.io/photoprism/photoprism:latest',
manifestUrl: null,
repoUrl: 'https://github.com/photoprism/photoprism'
},
@@ -724,7 +687,7 @@ function getCuratedAppList() {
title: 'Immich',
version: '1.90.0',
description: 'High-performance self-hosted photo and video backup. Mobile-first with ML features.',
icon: null,
icon: '/assets/img/app-icons/immich.png',
author: 'Immich',
dockerImage: 'ghcr.io/immich-app/immich-server:release',
manifestUrl: null,
@@ -735,9 +698,9 @@ function getCuratedAppList() {
title: 'File Browser',
version: '2.27.0',
description: 'Web-based file manager. Browse, upload, and manage files through a web interface.',
icon: null,
icon: '/assets/img/app-icons/file-browser.webp',
author: 'File Browser',
dockerImage: 'filebrowser/filebrowser:v2.27.0',
dockerImage: 'docker.io/filebrowser/filebrowser:v2.27.0',
manifestUrl: null,
repoUrl: 'https://github.com/filebrowser/filebrowser'
},
@@ -746,9 +709,9 @@ function getCuratedAppList() {
title: 'Nginx Proxy Manager',
version: '2.11.0',
description: 'Easy proxy management with SSL. Beautiful web interface for managing reverse proxies.',
icon: null,
icon: '/assets/img/app-icons/nginx.svg',
author: 'Nginx Proxy Manager',
dockerImage: 'jc21/nginx-proxy-manager:latest',
dockerImage: 'docker.io/jc21/nginx-proxy-manager:latest',
manifestUrl: null,
repoUrl: 'https://github.com/NginxProxyManager/nginx-proxy-manager'
},
@@ -757,9 +720,9 @@ function getCuratedAppList() {
title: 'Portainer',
version: '2.19.0',
description: 'Container management UI. Manage Docker containers through a beautiful web interface.',
icon: null,
icon: '/assets/img/app-icons/portainer.webp',
author: 'Portainer',
dockerImage: 'portainer/portainer-ce:2.19.4',
dockerImage: 'docker.io/portainer/portainer-ce:2.19.4',
manifestUrl: null,
repoUrl: 'https://github.com/portainer/portainer'
},
@@ -768,12 +731,23 @@ function getCuratedAppList() {
title: 'Uptime Kuma',
version: '1.23.0',
description: 'Self-hosted monitoring tool. Monitor uptime for HTTP(s), TCP, DNS, and more.',
icon: null,
icon: '/assets/img/app-icons/uptime-kuma.webp',
author: 'Uptime Kuma',
dockerImage: 'louislam/uptime-kuma:1.23.11',
dockerImage: 'docker.io/louislam/uptime-kuma:1.23.11',
manifestUrl: null,
repoUrl: 'https://github.com/louislam/uptime-kuma'
},
{
id: 'tailscale',
title: 'Tailscale',
version: '1.78.0',
description: 'Zero-config VPN for secure remote access. Connect all your devices with WireGuard mesh network.',
icon: '/assets/img/app-icons/tailscale.webp',
author: 'Tailscale',
dockerImage: 'docker.io/tailscale/tailscale:stable',
manifestUrl: null,
repoUrl: 'https://github.com/tailscale/tailscale'
},
{
id: 'fedimint',
title: 'Fedimint',
@@ -781,7 +755,7 @@ function getCuratedAppList() {
description: 'Federated Bitcoin mint. Private, scalable Bitcoin through federated guardians.',
icon: '/assets/img/icon-fedimint.jpeg',
author: 'Fedimint',
dockerImage: 'fedimint/fedimintd:v0.3.0',
dockerImage: 'docker.io/fedimint/fedimintd:v0.3.0',
manifestUrl: null,
repoUrl: 'https://github.com/fedimint/fedimint'
}
@@ -863,7 +837,7 @@ async function installApp(app: any) {
}
async function installCommunityApp(app: any) {
if (installing.value || isInstalled(app.id) || !app.manifestUrl) return
if (installing.value || isInstalled(app.id) || !app.dockerImage) return
installing.value = app.id
@@ -876,7 +850,8 @@ async function installCommunityApp(app: any) {
id: app.id,
dockerImage: app.dockerImage,
version: app.version
}
},
timeout: 180000 // 3 minutes for large images like Nextcloud
})
// Wait for installation to complete (poll for package to appear)
@@ -948,11 +923,6 @@ function handleImageError(event: Event) {
</script>
<style scoped>
.marketplace-container {
max-width: 1400px;
margin: 0 auto;
}
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;