fix: container DNS, nginx chown, onboarding guard, seed UX, install flow
Backend: - Add --add-host host.containers.internal:host-gateway to LND and Bitcoin Knots containers (fixes DNS resolution failure in rootless podman) - Add --user 0:0 and DAC_OVERRIDE to nginx UI sidecar containers (fixes chown crash in rootless podman for bitcoin-ui, electrs-ui, lnd-ui) - Add hostadd to Rust Podman API client for web UI container installs - Add Chromium privacy flags to kiosk launcher (disable telemetry) Frontend: - Fix onboarding reset on raw IP visits (trust localStorage as first-class signal, skip boot screen when server is up but not onboarded) - Fix seed regression: persist challenge indices in sessionStorage so going back from Verify doesn't change which words are asked - Remove glass container from seed Generate/Verify/Restore screens - Add Back button to Restore from Seed screen - Replace Network card: Tor (purple), VPN status (orange), Bitcoin sync (orange) - Add ElectrumX to curated app list with correct .webp icon - Install flow: navigate to My Apps immediately with toast, hide installed/installing apps from marketplace and discover views Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b4a57e83d0
commit
6d3704fff5
@ -310,6 +310,7 @@ impl PodmanClient {
|
|||||||
"portmappings": port_mappings,
|
"portmappings": port_mappings,
|
||||||
"mounts": mounts,
|
"mounts": mounts,
|
||||||
"env": env_map,
|
"env": env_map,
|
||||||
|
"hostadd": ["host.containers.internal:host-gateway"],
|
||||||
"devices": manifest.app.devices.iter().map(|d| {
|
"devices": manifest.app.devices.iter().map(|d| {
|
||||||
serde_json::json!({"path": d})
|
serde_json::json!({"path": d})
|
||||||
}).collect::<Vec<_>>(),
|
}).collect::<Vec<_>>(),
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -47,6 +47,11 @@ while true; do
|
|||||||
--disable-background-networking \
|
--disable-background-networking \
|
||||||
--disable-background-timer-throttling \
|
--disable-background-timer-throttling \
|
||||||
--disable-backgrounding-occluded-windows \
|
--disable-backgrounding-occluded-windows \
|
||||||
|
--disable-breakpad \
|
||||||
|
--disable-metrics \
|
||||||
|
--disable-metrics-reporting \
|
||||||
|
--metrics-recording-only \
|
||||||
|
--disable-domain-reliability \
|
||||||
--js-flags="--max-old-space-size=128" \
|
--js-flags="--max-old-space-size=128" \
|
||||||
--user-data-dir=/home/archipelago/.config/chromium-kiosk
|
--user-data-dir=/home/archipelago/.config/chromium-kiosk
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|||||||
@ -19,9 +19,11 @@ async function callWithRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function isOnboardingComplete(): Promise<boolean> {
|
export async function isOnboardingComplete(): Promise<boolean> {
|
||||||
|
// localStorage is set on completion and survives backend restarts/resets
|
||||||
|
if (localStorage.getItem('neode_onboarding_complete') === '1') return true
|
||||||
const result = await callWithRetry(() => rpcClient.isOnboardingComplete(), 2)
|
const result = await callWithRetry(() => rpcClient.isOnboardingComplete(), 2)
|
||||||
if (result !== null) return result
|
if (result !== null) return result
|
||||||
return localStorage.getItem('neode_onboarding_complete') === '1'
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function completeOnboarding(): Promise<void> {
|
export async function completeOnboarding(): Promise<void> {
|
||||||
|
|||||||
@ -144,6 +144,7 @@ import { useServerStore } from '@/stores/server'
|
|||||||
import { rpcClient } from '@/api/rpc-client'
|
import { rpcClient } from '@/api/rpc-client'
|
||||||
import { useMarketplaceApp } from '@/composables/useMarketplaceApp'
|
import { useMarketplaceApp } from '@/composables/useMarketplaceApp'
|
||||||
import { useAppLauncherStore } from '@/stores/appLauncher'
|
import { useAppLauncherStore } from '@/stores/appLauncher'
|
||||||
|
import { useToast } from '@/composables/useToast'
|
||||||
import DiscoverHero from './discover/DiscoverHero.vue'
|
import DiscoverHero from './discover/DiscoverHero.vue'
|
||||||
import FeaturedApps from './discover/FeaturedApps.vue'
|
import FeaturedApps from './discover/FeaturedApps.vue'
|
||||||
import AppGrid from './discover/AppGrid.vue'
|
import AppGrid from './discover/AppGrid.vue'
|
||||||
@ -427,9 +428,13 @@ function startInstallPolling(appId: string, statusMessage: string) {
|
|||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
async function installApp(app: MarketplaceApp) {
|
async function installApp(app: MarketplaceApp) {
|
||||||
if (installingApps.has(app.id) || isInstalled(app.id)) return
|
if (installingApps.has(app.id) || isInstalled(app.id)) return
|
||||||
installingApps.set(app.id, { id: app.id, title: app.title ?? app.id, status: 'downloading', progress: 10, message: 'Preparing installation...', attempt: 0 })
|
installingApps.set(app.id, { id: app.id, title: app.title ?? app.id, status: 'downloading', progress: 10, message: 'Preparing installation...', attempt: 0 })
|
||||||
|
toast.info(`Installing ${app.title ?? app.id} — check My Apps`)
|
||||||
|
router.push('/dashboard/apps').catch(() => {})
|
||||||
try {
|
try {
|
||||||
const installUrl = app.url || app.manifestUrl || app.s9pkUrl
|
const installUrl = app.url || app.manifestUrl || app.s9pkUrl
|
||||||
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'downloading', progress: 30, message: 'Downloading package...' })
|
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'downloading', progress: 30, message: 'Downloading package...' })
|
||||||
@ -446,6 +451,8 @@ async function installApp(app: MarketplaceApp) {
|
|||||||
async function installCommunityApp(app: MarketplaceApp) {
|
async function installCommunityApp(app: MarketplaceApp) {
|
||||||
if (installingApps.has(app.id) || isInstalled(app.id) || !app.dockerImage) return
|
if (installingApps.has(app.id) || isInstalled(app.id) || !app.dockerImage) return
|
||||||
installingApps.set(app.id, { id: app.id, title: app.title ?? app.id, status: 'downloading', progress: 10, message: 'Pulling Docker image...', attempt: 0 })
|
installingApps.set(app.id, { id: app.id, title: app.title ?? app.id, status: 'downloading', progress: 10, message: 'Pulling Docker image...', attempt: 0 })
|
||||||
|
toast.info(`Installing ${app.title ?? app.id} — check My Apps`)
|
||||||
|
router.push('/dashboard/apps').catch(() => {})
|
||||||
try {
|
try {
|
||||||
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'downloading', progress: 20, message: 'Downloading container image...' })
|
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 })
|
await rpcClient.call({ method: 'package.install', params: { id: app.id, dockerImage: app.dockerImage, version: app.version }, timeout: 180000 })
|
||||||
|
|||||||
@ -140,16 +140,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="home-card-stats space-y-3 mb-4 flex-1 min-h-0">
|
<div class="home-card-stats space-y-3 mb-4 flex-1 min-h-0">
|
||||||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||||||
<div class="flex items-center gap-3"><div class="w-2 h-2 rounded-full" :class="servicesDotColor"></div><span class="text-sm text-white/80">{{ t('home.servicesStatus') }}</span></div>
|
<div class="flex items-center gap-3"><div class="w-2 h-2 rounded-full" :class="torConnected ? 'bg-purple-400' : 'bg-white/40'"></div><span class="text-sm text-white/80">Tor</span></div>
|
||||||
<span class="text-sm font-medium" :class="servicesStatusColor">{{ servicesStatusText }}</span>
|
<span class="text-sm font-medium" :class="torConnected ? 'text-purple-400' : 'text-white/40'">{{ torConnected ? 'Connected' : 'Offline' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||||||
<div class="flex items-center gap-3"><div class="w-2 h-2 rounded-full" :class="connectivityDotColor"></div><span class="text-sm text-white/80">{{ t('home.connectivity') }}</span></div>
|
<div class="flex items-center gap-3"><div class="w-2 h-2 rounded-full" :class="vpnConnected ? 'bg-orange-400' : 'bg-white/40'"></div><span class="text-sm text-white/80">VPN</span></div>
|
||||||
<span class="text-sm font-medium" :class="connectivityColor">{{ connectivityText }}</span>
|
<span class="text-sm font-medium" :class="vpnConnected ? 'text-orange-400' : 'text-white/40'">{{ vpnConnected ? 'Connected' : 'Not configured' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||||||
<div class="flex items-center gap-3"><div class="w-2 h-2 rounded-full bg-blue-400"></div><span class="text-sm text-white/80">{{ t('home.runningApps') }}</span></div>
|
<div class="flex items-center gap-3"><div class="w-2 h-2 rounded-full" :class="systemStats.bitcoinAvailable ? 'bg-orange-400' : 'bg-white/40'"></div><span class="text-sm text-white/80">Bitcoin</span></div>
|
||||||
<span class="text-sm text-white/80 font-medium">{{ runningCount }}</span>
|
<span class="text-sm font-medium" :class="systemStats.bitcoinAvailable ? 'text-orange-400' : 'text-white/40'">{{ bitcoinSyncDisplay }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="home-card-buttons flex gap-2 mt-auto pt-4 shrink-0">
|
<div class="home-card-buttons flex gap-2 mt-auto pt-4 shrink-0">
|
||||||
@ -306,13 +306,20 @@ const quickLaunchApps = [
|
|||||||
{ id: '484-kitchen', name: '484 Kitchen', icon: '/assets/img/app-icons/484-kitchen.png', bg: '', padded: false },
|
{ id: '484-kitchen', name: '484 Kitchen', icon: '/assets/img/app-icons/484-kitchen.png', bg: '', padded: false },
|
||||||
]
|
]
|
||||||
|
|
||||||
const servicesAllRunning = computed(() => appCount.value > 0 && runningCount.value === appCount.value)
|
// Network card data
|
||||||
const servicesStatusText = computed(() => appCount.value === 0 ? t('home.noApps') : servicesAllRunning.value ? t('home.allRunning') : `${runningCount.value}/${appCount.value} ${t('home.runningLabel')}`)
|
const torConnected = computed(() => {
|
||||||
const servicesStatusColor = computed(() => appCount.value === 0 ? 'text-white/60' : servicesAllRunning.value ? 'text-green-400' : 'text-yellow-400')
|
const torAddr = store.data?.['server-info']?.['tor-address']
|
||||||
const servicesDotColor = computed(() => appCount.value === 0 ? 'bg-white/40' : servicesAllRunning.value ? 'bg-green-400' : 'bg-yellow-400')
|
return !!torAddr && torAddr.length > 0
|
||||||
const connectivityText = computed(() => store.isConnected ? t('common.connected') : t('common.disconnected'))
|
})
|
||||||
const connectivityColor = computed(() => store.isConnected ? 'text-green-400' : 'text-red-400')
|
const vpnConnected = computed(() => {
|
||||||
const connectivityDotColor = computed(() => store.isConnected ? 'bg-green-400' : 'bg-red-400')
|
const pkg = packages.value['tailscale']
|
||||||
|
return !!pkg && pkg.state === PackageState.Running
|
||||||
|
})
|
||||||
|
const bitcoinSyncDisplay = computed(() => {
|
||||||
|
if (!systemStats.bitcoinAvailable) return 'Not running'
|
||||||
|
if (systemStats.bitcoinSyncPercent >= 99.9) return 'Synced'
|
||||||
|
return `${systemStats.bitcoinSyncPercent.toFixed(1)}%`
|
||||||
|
})
|
||||||
|
|
||||||
// Quick Start
|
// Quick Start
|
||||||
const quickStartDismissed = ref(false)
|
const quickStartDismissed = ref(false)
|
||||||
|
|||||||
@ -116,6 +116,7 @@ import { useServerStore } from '@/stores/server'
|
|||||||
import { rpcClient } from '@/api/rpc-client'
|
import { rpcClient } from '@/api/rpc-client'
|
||||||
import { useMarketplaceApp } from '@/composables/useMarketplaceApp'
|
import { useMarketplaceApp } from '@/composables/useMarketplaceApp'
|
||||||
import { useAppLauncherStore } from '@/stores/appLauncher'
|
import { useAppLauncherStore } from '@/stores/appLauncher'
|
||||||
|
import { useToast } from '@/composables/useToast'
|
||||||
import MarketplaceAppCard from './marketplace/MarketplaceAppCard.vue'
|
import MarketplaceAppCard from './marketplace/MarketplaceAppCard.vue'
|
||||||
import MarketplaceFilterModal from './marketplace/MarketplaceFilterModal.vue'
|
import MarketplaceFilterModal from './marketplace/MarketplaceFilterModal.vue'
|
||||||
import {
|
import {
|
||||||
@ -135,6 +136,7 @@ const { t } = useI18n()
|
|||||||
const showStagger = !marketplaceAnimationDone
|
const showStagger = !marketplaceAnimationDone
|
||||||
const { setCurrentApp } = useMarketplaceApp()
|
const { setCurrentApp } = useMarketplaceApp()
|
||||||
const appLauncher = useAppLauncherStore()
|
const appLauncher = useAppLauncherStore()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
// Category state — read initial value from query param (set by Discover page navigation)
|
// Category state — read initial value from query param (set by Discover page navigation)
|
||||||
const selectedCategory = ref((route.query.category as string) || 'all')
|
const selectedCategory = ref((route.query.category as string) || 'all')
|
||||||
@ -300,11 +302,8 @@ const filteredApps = computed(() => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
apps.sort((a, b) => {
|
// Hide installed and installing apps from marketplace — they belong in My Apps
|
||||||
const aInstalled = isInstalled(a.id) ? 1 : 0
|
apps = apps.filter(app => !isInstalled(app.id) && !installingApps.has(app.id))
|
||||||
const bInstalled = isInstalled(b.id) ? 1 : 0
|
|
||||||
return aInstalled - bInstalled
|
|
||||||
})
|
|
||||||
|
|
||||||
return apps
|
return apps
|
||||||
})
|
})
|
||||||
@ -433,6 +432,10 @@ async function installApp(app: MarketplaceApp) {
|
|||||||
id: app.id, title: app.title ?? app.id, status: 'downloading', progress: 10, message: 'Preparing installation...', attempt: 0
|
id: app.id, title: app.title ?? app.id, status: 'downloading', progress: 10, message: 'Preparing installation...', attempt: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Navigate to My Apps immediately and show toast
|
||||||
|
toast.info(`Installing ${app.title ?? app.id} — check My Apps`)
|
||||||
|
router.push('/dashboard/apps').catch(() => {})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const installUrl = app.url || app.manifestUrl || app.s9pkUrl
|
const installUrl = app.url || app.manifestUrl || app.s9pkUrl
|
||||||
|
|
||||||
@ -457,6 +460,10 @@ async function installCommunityApp(app: MarketplaceApp) {
|
|||||||
id: app.id, title: app.title ?? app.id, status: 'downloading', progress: 10, message: 'Pulling Docker image...', attempt: 0
|
id: app.id, title: app.title ?? app.id, status: 'downloading', progress: 10, message: 'Pulling Docker image...', attempt: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Navigate to My Apps immediately and show toast
|
||||||
|
toast.info(`Installing ${app.title ?? app.id} — check My Apps`)
|
||||||
|
router.push('/dashboard/apps').catch(() => {})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'downloading', progress: 20, message: 'Downloading container image...' })
|
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'downloading', progress: 20, message: 'Downloading container image...' })
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-[100dvh] flex items-center justify-center p-3 sm:p-4 md:p-6">
|
<div class="h-[100dvh] flex items-center justify-center p-3 sm:p-4 md:p-6">
|
||||||
<div class="max-w-[800px] w-full max-h-full relative z-10 path-glass-container onb-scroll-container flex flex-col">
|
<div class="max-w-[800px] w-full max-h-full relative z-10 onb-scroll-container flex flex-col">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="text-center flex-shrink-0 px-3 sm:px-4 pt-4 sm:pt-6 pb-2 sm:pb-3">
|
<div class="text-center flex-shrink-0 px-3 sm:px-4 pt-4 sm:pt-6 pb-2 sm:pb-3">
|
||||||
<h1 class="text-xl sm:text-2xl md:text-[26px] font-semibold text-white/96 mb-1.5 drop-shadow-[0_2px_6px_rgba(0,0,0,0.4)]">
|
<h1 class="text-xl sm:text-2xl md:text-[26px] font-semibold text-white/96 mb-1.5 drop-shadow-[0_2px_6px_rgba(0,0,0,0.4)]">
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-[100dvh] flex items-center justify-center p-3 sm:p-4 md:p-6">
|
<div class="h-[100dvh] flex items-center justify-center p-3 sm:p-4 md:p-6">
|
||||||
<div class="max-w-[800px] w-full max-h-full relative z-10 path-glass-container onb-scroll-container flex flex-col">
|
<div class="max-w-[800px] w-full max-h-full relative z-10 onb-scroll-container flex flex-col">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="text-center flex-shrink-0 px-3 sm:px-4 pt-4 sm:pt-6 pb-2 sm:pb-3">
|
<div class="text-center flex-shrink-0 px-3 sm:px-4 pt-4 sm:pt-6 pb-2 sm:pb-3">
|
||||||
<h1 class="text-xl sm:text-2xl md:text-[26px] font-semibold text-white/96 mb-1.5 drop-shadow-[0_2px_6px_rgba(0,0,0,0.4)]">
|
<h1 class="text-xl sm:text-2xl md:text-[26px] font-semibold text-white/96 mb-1.5 drop-shadow-[0_2px_6px_rgba(0,0,0,0.4)]">
|
||||||
@ -69,7 +69,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Fixed Footer -->
|
<!-- Fixed Footer -->
|
||||||
<div class="flex-shrink-0 flex justify-center px-3 sm:px-4 pt-3 pb-4 sm:pb-6">
|
<div class="flex-shrink-0 flex items-center justify-center gap-4 max-w-[600px] mx-auto w-full px-6 sm:px-8 pt-3 pb-4 sm:pb-6">
|
||||||
|
<span
|
||||||
|
v-if="!restored"
|
||||||
|
@click="goBack"
|
||||||
|
class="path-action-button path-action-button--continue cursor-pointer select-none inline-flex items-center justify-center"
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</span>
|
||||||
<button
|
<button
|
||||||
v-if="!restored"
|
v-if="!restored"
|
||||||
@click="restore"
|
@click="restore"
|
||||||
@ -116,6 +123,11 @@ const restoredDid = ref('')
|
|||||||
|
|
||||||
const allFilled = computed(() => seedWords.value.every(w => w.trim().length > 0))
|
const allFilled = computed(() => seedWords.value.every(w => w.trim().length > 0))
|
||||||
|
|
||||||
|
function goBack() {
|
||||||
|
playNavSound('action')
|
||||||
|
router.push('/onboarding/path').catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
setTimeout(() => wordInputs.value[0]?.focus({ preventScroll: true }), 300)
|
setTimeout(() => wordInputs.value[0]?.focus({ preventScroll: true }), 300)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-[100dvh] flex items-center justify-center p-3 sm:p-4 md:p-6">
|
<div class="h-[100dvh] flex items-center justify-center p-3 sm:p-4 md:p-6">
|
||||||
<div class="max-w-[800px] w-full max-h-full relative z-10 path-glass-container onb-scroll-container flex flex-col">
|
<div class="max-w-[800px] w-full max-h-full relative z-10 onb-scroll-container flex flex-col">
|
||||||
<!-- Header (hidden after verification) -->
|
<!-- Header (hidden after verification) -->
|
||||||
<div v-if="!verified" class="text-center flex-shrink-0 px-3 sm:px-4 pt-4 sm:pt-6 pb-2 sm:pb-3">
|
<div v-if="!verified" class="text-center flex-shrink-0 px-3 sm:px-4 pt-4 sm:pt-6 pb-2 sm:pb-3">
|
||||||
<h1 class="text-xl sm:text-2xl md:text-[26px] font-semibold text-white/96 mb-1.5 drop-shadow-[0_2px_6px_rgba(0,0,0,0.4)]">
|
<h1 class="text-xl sm:text-2xl md:text-[26px] font-semibold text-white/96 mb-1.5 drop-shadow-[0_2px_6px_rgba(0,0,0,0.4)]">
|
||||||
@ -168,7 +168,24 @@ onMounted(() => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
words.value = JSON.parse(stored)
|
words.value = JSON.parse(stored)
|
||||||
challengeIndices.value = pickRandomIndices(4, 24)
|
|
||||||
|
// Restore challenge indices so going back and returning asks the same words
|
||||||
|
const savedIndices = sessionStorage.getItem('_seed_challenge_indices')
|
||||||
|
if (savedIndices) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(savedIndices)
|
||||||
|
if (Array.isArray(parsed) && parsed.length === 4) {
|
||||||
|
challengeIndices.value = parsed
|
||||||
|
} else {
|
||||||
|
challengeIndices.value = pickRandomIndices(4, 24)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
challengeIndices.value = pickRandomIndices(4, 24)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
challengeIndices.value = pickRandomIndices(4, 24)
|
||||||
|
}
|
||||||
|
sessionStorage.setItem('_seed_challenge_indices', JSON.stringify(challengeIndices.value))
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
setTimeout(() => inputRefs.value[0]?.focus({ preventScroll: true }), 300)
|
setTimeout(() => inputRefs.value[0]?.focus({ preventScroll: true }), 300)
|
||||||
@ -232,6 +249,7 @@ async function verify() {
|
|||||||
localStorage.setItem('neode_did', res.did)
|
localStorage.setItem('neode_did', res.did)
|
||||||
if (res.nostr_npub) localStorage.setItem('neode_nostr_npub', res.nostr_npub)
|
if (res.nostr_npub) localStorage.setItem('neode_nostr_npub', res.nostr_npub)
|
||||||
sessionStorage.removeItem('_seed_words')
|
sessionStorage.removeItem('_seed_words')
|
||||||
|
sessionStorage.removeItem('_seed_challenge_indices')
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
setTimeout(() => continueButton.value?.focus({ preventScroll: true }), 100)
|
setTimeout(() => continueButton.value?.focus({ preventScroll: true }), 100)
|
||||||
|
|||||||
@ -124,10 +124,12 @@ onMounted(async () => {
|
|||||||
proceedToApp()
|
proceedToApp()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log('server up + NOT onboarded → boot screen')
|
log('server up + NOT onboarded → onboarding intro')
|
||||||
|
router.replace('/onboarding/intro').catch(() => {})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server not ready OR first boot — show boot screen
|
// Server not ready — show boot screen (waiting for backend)
|
||||||
showBootScreen.value = true
|
showBootScreen.value = true
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export function getCuratedAppList(): MarketplaceApp[] {
|
|||||||
{ 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: '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: '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: '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: '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: 'electrumx', title: 'ElectrumX', version: '1.18.0', description: 'Electrum protocol server. Index the blockchain for fast wallet lookups, privately.', icon: '/assets/img/app-icons/electrumx.webp', author: 'Luke Childs', dockerImage: `${R}/electrumx:v1.18.0`, repoUrl: 'https://github.com/spesmilo/electrumx' },
|
||||||
{ id: 'fedimint', title: 'Fedimint', version: '0.10.0', description: 'Federated Bitcoin mint. Private, scalable Bitcoin through federated guardians.', icon: '/assets/img/app-icons/fedimint.png', author: 'Fedimint', dockerImage: `${R}/fedimintd:v0.10.0`, repoUrl: 'https://github.com/fedimint/fedimint' },
|
{ id: 'fedimint', title: 'Fedimint', version: '0.10.0', description: 'Federated Bitcoin mint. Private, scalable Bitcoin through federated guardians.', icon: '/assets/img/app-icons/fedimint.png', author: 'Fedimint', dockerImage: `${R}/fedimintd:v0.10.0`, repoUrl: 'https://github.com/fedimint/fedimint' },
|
||||||
{ id: 'nostr-rs-relay', title: 'Nostr Relay', version: '0.9.0', category: 'nostr', description: 'Your own Nostr relay. Store events locally, relay for friends, publish over Tor.', icon: '/assets/img/app-icons/nostr-rs-relay.svg', author: 'scsiblade', dockerImage: `${R}/nostr-rs-relay:0.9.0`, repoUrl: 'https://sr.ht/~gheartsfield/nostr-rs-relay/' },
|
{ id: 'nostr-rs-relay', title: 'Nostr Relay', version: '0.9.0', category: 'nostr', description: 'Your own Nostr relay. Store events locally, relay for friends, publish over Tor.', icon: '/assets/img/app-icons/nostr-rs-relay.svg', author: 'scsiblade', dockerImage: `${R}/nostr-rs-relay:0.9.0`, repoUrl: 'https://sr.ht/~gheartsfield/nostr-rs-relay/' },
|
||||||
{ id: 'indeedhub', title: 'Indeehub', version: '0.1.0', description: 'Bitcoin documentary streaming with Nostr identity. Stream sovereignty content.', icon: '/assets/img/app-icons/indeedhub.png', author: 'Indeehub Team', dockerImage: 'localhost/indeedhub:latest', repoUrl: 'https://github.com/indeedhub/indeedhub' },
|
{ id: 'indeedhub', title: 'Indeehub', version: '0.1.0', description: 'Bitcoin documentary streaming with Nostr identity. Stream sovereignty content.', icon: '/assets/img/app-icons/indeedhub.png', author: 'Indeehub Team', dockerImage: 'localhost/indeedhub:latest', repoUrl: 'https://github.com/indeedhub/indeedhub' },
|
||||||
|
|||||||
@ -303,6 +303,17 @@ export function getCuratedAppList(): MarketplaceApp[] {
|
|||||||
manifestUrl: undefined,
|
manifestUrl: undefined,
|
||||||
repoUrl: 'https://github.com/immich-app/immich'
|
repoUrl: 'https://github.com/immich-app/immich'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
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: `${REGISTRY}/electrumx:v1.18.0`,
|
||||||
|
manifestUrl: undefined,
|
||||||
|
repoUrl: 'https://github.com/spesmilo/electrumx'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'filebrowser',
|
id: 'filebrowser',
|
||||||
title: 'File Browser',
|
title: 'File Browser',
|
||||||
|
|||||||
@ -368,6 +368,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'bitcoin-knots|arch
|
|||||||
if $DOCKER run -d --name bitcoin-knots --restart unless-stopped \
|
if $DOCKER run -d --name bitcoin-knots --restart unless-stopped \
|
||||||
--health-cmd="bitcoin-cli -rpcuser=\$BITCOIN_RPC_USER -rpcpassword=\$BITCOIN_RPC_PASS getblockchaininfo || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
--health-cmd="bitcoin-cli -rpcuser=\$BITCOIN_RPC_USER -rpcpassword=\$BITCOIN_RPC_PASS getblockchaininfo || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
||||||
--memory=$(mem_limit bitcoin-knots) --network archy-net \
|
--memory=$(mem_limit bitcoin-knots) --network archy-net \
|
||||||
|
--add-host host.containers.internal:host-gateway \
|
||||||
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
|
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
|
||||||
--security-opt no-new-privileges:true \
|
--security-opt no-new-privileges:true \
|
||||||
-p 8332:8332 -p 8333:8333 -p 28332:28332 -p 28333:28333 \
|
-p 8332:8332 -p 8333:8333 -p 28332:28332 -p 28333:28333 \
|
||||||
@ -483,21 +484,21 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q electrs-ui; then
|
|||||||
log "Starting ElectrumX UI from pre-built image..."
|
log "Starting ElectrumX UI from pre-built image..."
|
||||||
$DOCKER run -d --name archy-electrs-ui --network host --restart unless-stopped \
|
$DOCKER run -d --name archy-electrs-ui --network host --restart unless-stopped \
|
||||||
--health-cmd="curl -sf http://localhost:80/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
--health-cmd="curl -sf http://localhost:80/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
||||||
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
|
--user 0:0 \
|
||||||
--security-opt no-new-privileges:true \
|
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
|
||||||
localhost/electrs-ui:local 2>>"$LOG" || \
|
localhost/electrs-ui:local 2>>"$LOG" || \
|
||||||
$DOCKER run -d --name archy-electrs-ui --network host --restart unless-stopped \
|
$DOCKER run -d --name archy-electrs-ui --network host --restart unless-stopped \
|
||||||
--health-cmd="curl -sf http://localhost:80/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
--health-cmd="curl -sf http://localhost:80/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
||||||
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
|
--user 0:0 \
|
||||||
--security-opt no-new-privileges:true \
|
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
|
||||||
electrs-ui:local 2>>"$LOG" || true
|
electrs-ui:local 2>>"$LOG" || true
|
||||||
elif [ -d /opt/archipelago/docker/electrs-ui ]; then
|
elif [ -d /opt/archipelago/docker/electrs-ui ]; then
|
||||||
log "Building and starting ElectrumX UI from source..."
|
log "Building and starting ElectrumX UI from source..."
|
||||||
$DOCKER build -t electrs-ui:local /opt/archipelago/docker/electrs-ui 2>>"$LOG" && \
|
$DOCKER build -t electrs-ui:local /opt/archipelago/docker/electrs-ui 2>>"$LOG" && \
|
||||||
$DOCKER run -d --name archy-electrs-ui --network host --restart unless-stopped \
|
$DOCKER run -d --name archy-electrs-ui --network host --restart unless-stopped \
|
||||||
--health-cmd="curl -sf http://localhost:80/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
--health-cmd="curl -sf http://localhost:80/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
||||||
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
|
--user 0:0 \
|
||||||
--security-opt no-new-privileges:true \
|
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
|
||||||
electrs-ui:local 2>>"$LOG" || true
|
electrs-ui:local 2>>"$LOG" || true
|
||||||
else
|
else
|
||||||
log "ElectrumX UI: no image or source found, skipping"
|
log "ElectrumX UI: no image or source found, skipping"
|
||||||
@ -607,6 +608,7 @@ LNDCONF
|
|||||||
$DOCKER run -d --name lnd --restart unless-stopped \
|
$DOCKER run -d --name lnd --restart unless-stopped \
|
||||||
--health-cmd="curl -sf --insecure https://localhost:8080/v1/getinfo || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
--health-cmd="curl -sf --insecure https://localhost:8080/v1/getinfo || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
||||||
--memory=$(mem_limit lnd) --network archy-net \
|
--memory=$(mem_limit lnd) --network archy-net \
|
||||||
|
--add-host host.containers.internal:host-gateway \
|
||||||
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE --cap-add NET_RAW \
|
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE --cap-add NET_RAW \
|
||||||
--security-opt no-new-privileges:true \
|
--security-opt no-new-privileges:true \
|
||||||
-p 9735:9735 -p 10009:10009 -p 8080:8080 \
|
-p 9735:9735 -p 10009:10009 -p 8080:8080 \
|
||||||
@ -994,23 +996,23 @@ for ui in bitcoin-ui lnd-ui; do
|
|||||||
log "Starting $ui from pre-built image..."
|
log "Starting $ui from pre-built image..."
|
||||||
IMG=$($DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep "$ui" | head -1)
|
IMG=$($DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep "$ui" | head -1)
|
||||||
$DOCKER run -d --name "$CONTAINER_NAME" $PORT_ARG --restart unless-stopped --memory=$(mem_limit "$CONTAINER_NAME") $NET_ARG \
|
$DOCKER run -d --name "$CONTAINER_NAME" $PORT_ARG --restart unless-stopped --memory=$(mem_limit "$CONTAINER_NAME") $NET_ARG \
|
||||||
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
|
--user 0:0 \
|
||||||
--security-opt no-new-privileges:true \
|
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
|
||||||
"$IMG" 2>>"$LOG" || true
|
"$IMG" 2>>"$LOG" || true
|
||||||
elif [ -d "/opt/archipelago/docker/$ui" ]; then
|
elif [ -d "/opt/archipelago/docker/$ui" ]; then
|
||||||
log "Building $ui from source (/opt/archipelago/docker/$ui)..."
|
log "Building $ui from source (/opt/archipelago/docker/$ui)..."
|
||||||
if $DOCKER build -t "$ui:local" "/opt/archipelago/docker/$ui" 2>>"$LOG"; then
|
if $DOCKER build -t "$ui:local" "/opt/archipelago/docker/$ui" 2>>"$LOG"; then
|
||||||
$DOCKER run -d --name "$CONTAINER_NAME" $PORT_ARG --restart unless-stopped --memory=$(mem_limit "$CONTAINER_NAME") $NET_ARG \
|
$DOCKER run -d --name "$CONTAINER_NAME" $PORT_ARG --restart unless-stopped --memory=$(mem_limit "$CONTAINER_NAME") $NET_ARG \
|
||||||
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
|
--user 0:0 \
|
||||||
--security-opt no-new-privileges:true \
|
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
|
||||||
"$ui:local" 2>>"$LOG" || true
|
"$ui:local" 2>>"$LOG" || true
|
||||||
fi
|
fi
|
||||||
elif [ -d "/home/archipelago/archy/docker/$ui" ]; then
|
elif [ -d "/home/archipelago/archy/docker/$ui" ]; then
|
||||||
log "Building $ui from source (/home/archipelago/archy/docker/$ui)..."
|
log "Building $ui from source (/home/archipelago/archy/docker/$ui)..."
|
||||||
if $DOCKER build -t "$ui:local" "/home/archipelago/archy/docker/$ui" 2>>"$LOG"; then
|
if $DOCKER build -t "$ui:local" "/home/archipelago/archy/docker/$ui" 2>>"$LOG"; then
|
||||||
$DOCKER run -d --name "$CONTAINER_NAME" $PORT_ARG --restart unless-stopped --memory=$(mem_limit "$CONTAINER_NAME") $NET_ARG \
|
$DOCKER run -d --name "$CONTAINER_NAME" $PORT_ARG --restart unless-stopped --memory=$(mem_limit "$CONTAINER_NAME") $NET_ARG \
|
||||||
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
|
--user 0:0 \
|
||||||
--security-opt no-new-privileges:true \
|
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
|
||||||
"$ui:local" 2>>"$LOG" || true
|
"$ui:local" 2>>"$LOG" || true
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user