Revise BUILD-GUIDE and enhance ISO build process

- Updated BUILD-GUIDE.md to streamline instructions for building the Archipelago Auto-Installer ISO, including prerequisites and post-installation steps.
- Added detailed sections on capturing the live server state and building from source.
- Enhanced Docker and Podman integration in build scripts for improved backend and web UI capture.
- Introduced new app metadata for "IndeedHub" in the Docker package scanner and updated UI components for better installation progress tracking.
- Improved styling and functionality in the Bitcoin UI for a more cohesive user experience.
This commit is contained in:
Dorian
2026-02-03 21:43:33 +00:00
parent 0f40cb88b5
commit 337ebee510
22 changed files with 2619 additions and 509 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

@@ -0,0 +1,95 @@
<svg width="2480" height="2480" viewBox="0 0 2480 2480" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="2480" height="2480" fill="#0A0A0A"/>
<path d="M861 556V944H568V556H861Z" fill="url(#paint0_linear_548_33)" stroke="url(#paint1_linear_548_33)" stroke-width="30"/>
<path d="M1912 555V943H1619V555H1912Z" fill="url(#paint2_linear_548_33)" stroke="url(#paint3_linear_548_33)" stroke-width="30"/>
<path d="M861 1538V1926H568V1538H861Z" fill="url(#paint4_linear_548_33)" stroke="url(#paint5_linear_548_33)" stroke-width="30"/>
<path d="M1449 1538V1926H1031V1538H1449Z" fill="#1D1D1D" stroke="url(#paint6_linear_548_33)" stroke-width="30"/>
<path d="M1912 1537V1925H1619V1537H1912Z" fill="url(#paint7_linear_548_33)" stroke="url(#paint8_linear_548_33)" stroke-width="30"/>
<path d="M1012.25 1112L1160.05 1368H1030.49L882.688 1112H1012.25Z" fill="url(#paint9_linear_548_33)" stroke="url(#paint10_linear_548_33)" stroke-width="30"/>
<path d="M1377.71 1112L1525.51 1368H1395.89L1248.08 1112H1377.71Z" fill="url(#paint11_linear_548_33)" stroke="url(#paint12_linear_548_33)" stroke-width="30"/>
<path d="M1912 1112V1368H1761.35L1613.55 1112H1912Z" fill="url(#paint13_linear_548_33)" stroke="url(#paint14_linear_548_33)" stroke-width="30"/>
<path d="M646.852 1112L794.653 1368H568V1112H646.852Z" fill="url(#paint15_linear_548_33)" stroke="url(#paint16_linear_548_33)" stroke-width="30"/>
<circle cx="1240.5" cy="750.5" r="195.5" fill="#1D1D1D" stroke="url(#paint17_linear_548_33)" stroke-width="30"/>
<defs>
<linearGradient id="paint0_linear_548_33" x1="677.804" y1="242.431" x2="1904.77" y2="353.601" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0003D"/>
<stop offset="0.369792" stop-color="#FA4727"/>
<stop offset="0.776042" stop-color="#6B90F4"/>
</linearGradient>
<linearGradient id="paint1_linear_548_33" x1="553" y1="541" x2="876" y2="959" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F52532"/>
</linearGradient>
<linearGradient id="paint2_linear_548_33" x1="1728.8" y1="241.431" x2="2955.77" y2="352.601" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0003D"/>
<stop offset="0.369792" stop-color="#FA4727"/>
<stop offset="0.776042" stop-color="#6B90F4"/>
</linearGradient>
<linearGradient id="paint3_linear_548_33" x1="1604" y1="540" x2="1927" y2="958" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F52532"/>
</linearGradient>
<linearGradient id="paint4_linear_548_33" x1="677.804" y1="1224.43" x2="1904.77" y2="1335.6" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0003D"/>
<stop offset="0.369792" stop-color="#FA4727"/>
<stop offset="0.776042" stop-color="#6B90F4"/>
</linearGradient>
<linearGradient id="paint5_linear_548_33" x1="876" y1="1607.2" x2="553" y2="1856.8" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F52532"/>
</linearGradient>
<linearGradient id="paint6_linear_548_33" x1="1240" y1="1941" x2="1240" y2="1523" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-color="#666666"/>
</linearGradient>
<linearGradient id="paint7_linear_548_33" x1="1728.8" y1="1223.43" x2="2955.77" y2="1334.6" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0003D"/>
<stop offset="0.369792" stop-color="#FA4727"/>
<stop offset="0.776042" stop-color="#6B90F4"/>
</linearGradient>
<linearGradient id="paint8_linear_548_33" x1="1927" y1="1606.2" x2="1604" y2="1855.8" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F52532"/>
</linearGradient>
<linearGradient id="paint9_linear_548_33" x1="350.36" y1="1032.44" x2="1729.93" y2="1206.26" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0003D"/>
<stop offset="0.369792" stop-color="#FA4727"/>
<stop offset="0.776042" stop-color="#6B90F4"/>
</linearGradient>
<linearGradient id="paint10_linear_548_33" x1="856.707" y1="1097" x2="1115.61" y2="1436.97" gradientUnits="userSpaceOnUse">
<stop stop-color="#FAFAFA"/>
<stop offset="1" stop-color="#A5729F"/>
</linearGradient>
<linearGradient id="paint11_linear_548_33" x1="715.651" y1="1032.44" x2="2095.49" y2="1206.34" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0003D"/>
<stop offset="0.369792" stop-color="#FA4727"/>
<stop offset="0.776042" stop-color="#6B90F4"/>
</linearGradient>
<linearGradient id="paint12_linear_548_33" x1="1222.1" y1="1097" x2="1480.99" y2="1437.02" gradientUnits="userSpaceOnUse">
<stop stop-color="#FAFAFA"/>
<stop offset="1" stop-color="#A5729F"/>
</linearGradient>
<linearGradient id="paint13_linear_548_33" x1="1065.68" y1="1032.44" x2="2486.21" y2="1216.92" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0003D"/>
<stop offset="0.369792" stop-color="#FA4727"/>
<stop offset="0.776042" stop-color="#6B90F4"/>
</linearGradient>
<linearGradient id="paint14_linear_548_33" x1="1587.57" y1="1097" x2="1844.29" y2="1444.46" gradientUnits="userSpaceOnUse">
<stop stop-color="#FAFAFA"/>
<stop offset="1" stop-color="#A5729F"/>
</linearGradient>
<linearGradient id="paint15_linear_548_33" x1="141.502" y1="1032.44" x2="1268.63" y2="1147.85" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0003D"/>
<stop offset="0.369792" stop-color="#FA4727"/>
<stop offset="0.776042" stop-color="#6B90F4"/>
</linearGradient>
<linearGradient id="paint16_linear_548_33" x1="553" y1="1097" x2="821" y2="1383" gradientUnits="userSpaceOnUse">
<stop stop-color="#FAFAFA"/>
<stop offset="1" stop-color="#A5729F"/>
</linearGradient>
<linearGradient id="paint17_linear_548_33" x1="1240.5" y1="540" x2="1240.5" y2="961" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-color="#666666"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

+36
View File
@@ -473,6 +473,42 @@ export const dummyApps: Record<string, PackageDataEntry> = {
},
status: ServiceStatus.Running
}
},
'indeedhub': {
state: PackageState.Running,
'static-files': {
license: 'MIT',
instructions: 'Decentralized media streaming platform',
icon: '/assets/img/app-icons/indeedhub.png'
},
manifest: {
id: 'indeedhub',
title: 'IndeedHub',
version: '0.1.0',
description: {
short: 'Decentralized media streaming platform',
long: 'IndeedHub is a decentralized media streaming platform built on Nostr. Stream Bitcoin-focused documentaries, educational content, and independent films. Netflix-inspired interface with glassmorphism design, supporting content creators through the decentralized web.'
},
'release-notes': 'Initial release with Netflix-inspired interface',
license: 'MIT',
'wrapper-repo': 'https://github.com/indeedhub/indeedhub',
'upstream-repo': 'https://github.com/indeedhub/indeedhub',
'support-site': 'https://github.com/indeedhub/indeedhub/issues',
'marketing-site': 'https://indeedhub.com',
'donation-url': null
},
installed: {
'current-dependents': {},
'current-dependencies': {},
'last-backup': null,
'interface-addresses': {
main: {
'tor-address': 'indeedhub.onion',
'lan-address': 'http://localhost:7777'
}
},
status: ServiceStatus.Running
}
}
}
+215 -54
View File
@@ -2,21 +2,73 @@
<div class="marketplace-container flex flex-col h-full overflow-hidden -mt-4 md:mt-0">
<!-- Fixed Header Section -->
<div class="flex-shrink-0 -mt-4 md:mt-0">
<!-- Installation Progress Banner -->
<div v-if="installing" class="mb-6 glass-card p-4 border-l-4 border-blue-500">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<svg class="animate-spin h-5 w-5 text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<div>
<p class="text-white font-medium">Installing {{ installing }}...</p>
<p class="text-white/70 text-sm">Checking for Docker image and starting container</p>
<!-- Installation Progress Banner - Multiple Apps -->
<div v-if="installingApps.size > 0" class="mb-6 space-y-3">
<div
v-for="[appId, progress] in installingApps"
:key="appId"
class="glass-card p-4 border-l-4"
:class="{
'border-blue-500': progress.status === 'downloading' || progress.status === 'installing',
'border-orange-500': progress.status === 'starting',
'border-green-500': progress.status === 'complete',
'border-red-500': progress.status === 'error'
}"
>
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-3">
<svg
v-if="progress.status !== 'complete' && progress.status !== 'error'"
class="animate-spin h-5 w-5 text-blue-400"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<svg
v-else-if="progress.status === 'complete'"
class="h-5 w-5 text-green-400"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<svg
v-else
class="h-5 w-5 text-red-400"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div>
<p class="text-white font-medium">{{ progress.title }}</p>
<p class="text-white/70 text-sm">{{ progress.message }}</p>
</div>
</div>
<div class="text-white/60 text-sm">
{{ progress.progress }}%
</div>
</div>
<div class="text-white/60 text-sm">
{{ installAttempt }}/{{ maxAttempts }}s
<!-- Progress Bar -->
<div class="w-full bg-white/10 rounded-full h-2 overflow-hidden">
<div
class="h-full rounded-full transition-all duration-500"
:class="{
'bg-gradient-to-r from-blue-500 to-blue-400': progress.status === 'downloading' || progress.status === 'installing',
'bg-gradient-to-r from-orange-500 to-orange-400': progress.status === 'starting',
'bg-gradient-to-r from-green-500 to-green-400': progress.status === 'complete',
'bg-gradient-to-r from-red-500 to-red-400': progress.status === 'error'
}"
:style="{ width: `${progress.progress}%` }"
></div>
</div>
</div>
</div>
@@ -113,15 +165,15 @@
<button
v-else-if="app.source === 'local' || app.dockerImage"
@click.stop="app.source === 'local' ? installApp(app) : installCommunityApp(app)"
:disabled="installing === app.id || installing !== null"
:disabled="installingApps.has(app.id)"
class="flex-1 px-4 py-2 gradient-button rounded-lg text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed"
>
<span v-if="installing === app.id" class="flex items-center justify-center gap-2">
<span v-if="installingApps.has(app.id)" class="flex items-center justify-center gap-2">
<svg class="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Installing...
{{ installingApps.get(app.id)?.message || 'Installing...' }}
</span>
<span v-else>Install</span>
</button>
@@ -340,10 +392,18 @@ const categories = [
{ id: 'other', name: 'Other' }
]
// Local apps state
const installing = ref<string | null>(null)
const installAttempt = ref(0)
const maxAttempts = ref(30)
// Installation state - support multiple concurrent installations
interface InstallProgress {
id: string
title: string
status: 'downloading' | 'installing' | 'starting' | 'complete' | 'error'
progress: number // 0-100
message: string
attempt: number
}
const installingApps = ref<Map<string, InstallProgress>>(new Map())
const maxAttempts = ref(60)
// Sideload modal state
const showSideloadModal = ref(false)
@@ -529,13 +589,13 @@ async function loadCommunityMarketplace() {
function getCuratedAppList() {
return [
{
id: 'bitcoin',
id: 'bitcoin-knots',
title: 'Bitcoin Knots',
version: '27.0.0',
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: 'docker.io/lncm/bitcoind:v27.0',
dockerImage: 'docker.io/bitcoinknots/bitcoin:latest',
manifestUrl: null,
repoUrl: 'https://github.com/bitcoinknots/bitcoin'
},
@@ -790,14 +850,30 @@ function viewAppDetails(app: any) {
}
async function installApp(app: any) {
if (installing.value || isInstalled(app.id)) return
if (installingApps.value.has(app.id) || isInstalled(app.id)) return
installing.value = app.id
// Add to installing map
installingApps.value.set(app.id, {
id: app.id,
title: app.title,
status: 'downloading',
progress: 10,
message: 'Preparing installation...',
attempt: 0
})
try {
const installUrl = app.url || app.manifestUrl || app.s9pkUrl
console.log('[Marketplace] Installing local app:', { id: app.id, url: installUrl, version: app.version })
// Update progress
installingApps.value.set(app.id, {
...installingApps.value.get(app.id)!,
status: 'downloading',
progress: 30,
message: 'Downloading package...'
})
await rpcClient.call({
method: 'package.install',
params: {
@@ -807,43 +883,93 @@ async function installApp(app: any) {
}
})
// Wait for installation to complete (poll for package to appear)
installAttempt.value = 0
// Update progress
installingApps.value.set(app.id, {
...installingApps.value.get(app.id)!,
status: 'installing',
progress: 60,
message: 'Installing package...'
})
// Wait for installation to complete (poll for package to appear)
const checkInstalled = setInterval(() => {
installAttempt.value++
const current = installingApps.value.get(app.id)
if (!current) {
clearInterval(checkInstalled)
return
}
const newAttempt = current.attempt + 1
installingApps.value.set(app.id, {
...current,
attempt: newAttempt,
progress: Math.min(60 + (newAttempt * 0.5), 95),
message: 'Starting application...'
})
if (isInstalled(app.id)) {
clearInterval(checkInstalled)
installing.value = null
installAttempt.value = 0
installingApps.value.set(app.id, {
...current,
status: 'complete',
progress: 100,
message: 'Installation complete!'
})
setTimeout(() => {
router.push('/dashboard/apps')
}, 500)
} else if (installAttempt.value >= maxAttempts.value) {
installingApps.value.delete(app.id)
}, 2000)
} else if (newAttempt >= maxAttempts.value) {
clearInterval(checkInstalled)
installing.value = null
installAttempt.value = 0
alert('Installation timeout. Please check the backend logs or try refreshing the page.')
installingApps.value.set(app.id, {
...current,
status: 'error',
progress: 0,
message: 'Installation timeout'
})
setTimeout(() => {
installingApps.value.delete(app.id)
}, 5000)
}
}, 1000)
} catch (err) {
console.error('Installation failed:', err)
alert(`Failed to install ${app.title}: ${err}`)
installing.value = null
installAttempt.value = 0
installingApps.value.set(app.id, {
...installingApps.value.get(app.id)!,
status: 'error',
progress: 0,
message: `Failed: ${err}`
})
setTimeout(() => {
installingApps.value.delete(app.id)
}, 5000)
}
}
async function installCommunityApp(app: any) {
if (installing.value || isInstalled(app.id) || !app.dockerImage) return
if (installingApps.value.has(app.id) || isInstalled(app.id) || !app.dockerImage) return
installing.value = app.id
// Add to installing map
installingApps.value.set(app.id, {
id: app.id,
title: app.title,
status: 'downloading',
progress: 10,
message: 'Pulling Docker image...',
attempt: 0
})
try {
console.log(`[Marketplace] Installing Docker app ${app.title} using image ${app.dockerImage}`)
// Update progress
installingApps.value.set(app.id, {
...installingApps.value.get(app.id)!,
status: 'downloading',
progress: 20,
message: 'Downloading container image...'
})
await rpcClient.call({
method: 'package.install',
params: {
@@ -854,31 +980,66 @@ async function installCommunityApp(app: any) {
timeout: 180000 // 3 minutes for large images like Nextcloud
})
// Wait for installation to complete (poll for package to appear)
installAttempt.value = 0
// Update progress
installingApps.value.set(app.id, {
...installingApps.value.get(app.id)!,
status: 'installing',
progress: 60,
message: 'Starting container...'
})
// Wait for installation to complete (poll for package to appear)
const checkInstalled = setInterval(() => {
installAttempt.value++
const current = installingApps.value.get(app.id)
if (!current) {
clearInterval(checkInstalled)
return
}
const newAttempt = current.attempt + 1
installingApps.value.set(app.id, {
...current,
attempt: newAttempt,
progress: Math.min(60 + (newAttempt * 0.5), 95),
message: 'Initializing application...'
})
if (isInstalled(app.id)) {
clearInterval(checkInstalled)
installing.value = null
installAttempt.value = 0
installingApps.value.set(app.id, {
...current,
status: 'complete',
progress: 100,
message: 'Installation complete!'
})
setTimeout(() => {
router.push('/dashboard/apps')
}, 500)
} else if (installAttempt.value >= maxAttempts.value) {
installingApps.value.delete(app.id)
}, 2000)
} else if (newAttempt >= maxAttempts.value) {
clearInterval(checkInstalled)
installing.value = null
installAttempt.value = 0
console.error('[Marketplace] Installation timeout')
installingApps.value.set(app.id, {
...current,
status: 'error',
progress: 0,
message: 'Installation timeout'
})
setTimeout(() => {
installingApps.value.delete(app.id)
}, 5000)
}
}, 1000)
} catch (err) {
console.error('[Marketplace] Installation failed:', err)
installing.value = null
installAttempt.value = 0
installingApps.value.set(app.id, {
...installingApps.value.get(app.id)!,
status: 'error',
progress: 0,
message: `Failed: ${err}`
})
setTimeout(() => {
installingApps.value.delete(app.id)
}, 5000)
}
}