Compare commits

...

4 Commits

Author SHA1 Message Date
archipelago
8c3c79543e chore: sync core/Cargo.lock to 1.7.96-alpha (release leftover)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 10:15:24 -04:00
archipelago
7aa1ca013f chore: release v1.7.96-alpha 2026-06-15 10:14:05 -04:00
archipelago
5af9a22b98 feat(fips): selectable TCP/UDP transport when adding a seed anchor
The add-anchor form previously hardcoded transport=udp. Expose a
TCP/UDP selector (default tcp) so public internet anchors and
local-network anchors can both be added. Includes changelog + What's
New entry for v1.7.96-alpha.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 10:12:23 -04:00
archipelago
786498a57a fix(kiosk): remove kiosk launcher grid, show normal app on the display
The kiosk attached-display showed a separate app-tile launcher grid
(Kiosk.vue at /kiosk) instead of the normal onboarding/login/dashboard.
The grid is auth-gated, so it only surfaced once the kiosk browser held a
persisted session; otherwise it bounced to login — masking the issue.

Remove the grid entirely. /kiosk now just persists kiosk mode + safe-area
insets and redirects to the root app. The launcher keeps pointing at
/kiosk (not directly at /) so the 'kiosk' localStorage flag is still set —
App.vue uses it to skip the remote relay, which would otherwise double
xdotool input on the kiosk display. Route made public so the auth guard
doesn't bounce it before the redirect runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 10:03:07 -04:00
12 changed files with 80 additions and 329 deletions

View File

@ -1,5 +1,12 @@
# Changelog
## v1.7.96-alpha (2026-06-15)
- The screen attached to your node now shows the normal Archipelago interface and your dashboard after you sign in, instead of a separate, stripped-down grid of app icons that could appear in its place. That extra screen has been removed so the attached display matches what you see everywhere else.
- On a brand-new node, the attached screen now walks through the same welcome and setup steps you'd see on a phone or laptop, and shows the normal sign-in screen once the node is set up — so the on-device display always matches the rest of the interface.
- When adding a FIPS network anchor, you can now choose whether it connects over TCP (for a public anchor reached across the internet) or UDP (for one on your local network), instead of it always assuming the local-network option.
- Behind the scenes, a new automated two-node test now exercises real node-to-node features — browsing another node's shared files and handling a removed node — against live nodes before each release, so node-to-node problems are caught earlier.
## v1.7.95-alpha (2026-06-15)
- Browsing another node's shared files now works over the fast encrypted mesh. Opening a peer's cloud could fail with a generic "Operation failed" message because the request for their file list wasn't permitted over the mesh and came back as "not found" — and it never retried over Tor. The mesh now serves the file list directly, and if a peer can't answer over the mesh the node automatically falls back to Tor instead of giving up.

2
core/Cargo.lock generated
View File

@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "archipelago"
version = "1.7.95-alpha"
version = "1.7.96-alpha"
dependencies = [
"anyhow",
"archipelago-container",

View File

@ -1,6 +1,6 @@
[package]
name = "archipelago"
version = "1.7.95-alpha"
version = "1.7.96-alpha"
edition = "2021"
description = "Archipelago Bitcoin Node OS - Native backend"
authors = ["Archipelago Team"]

View File

@ -1,12 +1,12 @@
{
"name": "neode-ui",
"version": "1.7.95-alpha",
"version": "1.7.96-alpha",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "neode-ui",
"version": "1.7.95-alpha",
"version": "1.7.96-alpha",
"dependencies": {
"@types/dompurify": "^3.0.5",
"@vue-leaflet/vue-leaflet": "^0.10.1",

View File

@ -1,7 +1,7 @@
{
"name": "neode-ui",
"private": true,
"version": "1.7.95-alpha",
"version": "1.7.96-alpha",
"type": "module",
"scripts": {
"start": "./start-dev.sh",

View File

@ -84,12 +84,18 @@ const router = createRouter({
meta: { public: true },
},
{
// The kiosk display no longer has its own launcher screen. It runs the
// normal app (onboarding → login → dashboard) like any other client.
// This route only persists kiosk mode + safe-area insets, then redirects
// to the root app. The launcher still points Chromium here (not directly
// at `/`) so the 'kiosk' flag gets set — App.vue uses it to skip the
// remote relay, which would otherwise double xdotool input on the kiosk
// display. Public so the auth guard doesn't bounce us before beforeEnter.
path: '/kiosk',
name: 'kiosk',
component: () => import('../views/Kiosk.vue'),
meta: { public: true },
component: () => import('../views/RootRedirect.vue'),
beforeEnter: (to) => {
// Persist kiosk mode before redirect so App.vue can skip the remote relay
// (relay duplicates xdotool input on the kiosk display)
localStorage.setItem('kiosk', 'true')
const safeArea = to.query.safe_area
const safeAreaPx = Array.isArray(safeArea) ? safeArea[0] : safeArea
@ -106,6 +112,8 @@ const router = createRouter({
if (safeAreaYPx && /^\d{1,3}$/.test(safeAreaYPx)) {
localStorage.setItem('archipelago_kiosk_safe_area_y_px', safeAreaYPx)
}
// Grid screen removed — hand off to the normal app flow.
return { path: '/' }
},
},
{

View File

@ -62,7 +62,6 @@
.glass-card:focus-visible,
.sidebar-nav-item:focus-visible,
.path-option-card:focus-visible,
.kiosk-app-tile:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible {

View File

@ -1,286 +0,0 @@
<template>
<div class="kiosk-root" tabindex="0" ref="kioskRoot">
<!-- Kiosk launcher grid -->
<div class="kiosk-launcher">
<!-- Header -->
<div class="kiosk-header">
<div class="flex items-center gap-4">
<img :src="FALLBACK_ICON" alt="Archipelago" class="w-10 h-10" />
<div>
<h1 class="text-2xl font-bold text-white font-archipelago">Archipelago</h1>
<p class="text-sm text-white/50">{{ currentTime }}</p>
</div>
</div>
<div class="flex items-center gap-3">
<div class="kiosk-status-pill" :class="isConnected ? 'status-success' : 'status-error'">
<div class="w-2 h-2 rounded-full" :class="isConnected ? 'bg-green-400' : 'bg-red-400'"></div>
{{ isConnected ? t('kiosk.online') : t('kiosk.offline') }}
</div>
</div>
</div>
<!-- App grid -->
<div class="kiosk-grid">
<button
v-for="app in launchableApps"
:key="app.id"
class="kiosk-app-tile"
@click="openApp(app)"
:data-controller-focusable="true"
>
<div class="kiosk-app-icon-wrap">
<img
:src="app.icon"
:alt="app.title"
class="kiosk-app-icon"
@error="($event.target as HTMLImageElement).src = FALLBACK_ICON"
/>
<div
class="kiosk-app-status"
:class="app.running ? 'bg-green-400' : 'bg-white/30'"
/>
</div>
<span class="kiosk-app-label">{{ app.title }}</span>
</button>
</div>
<!-- Footer -->
<div class="kiosk-footer">
<span class="text-white/30 text-sm">{{ t('kiosk.navHint') }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/stores/app'
import { useAppLauncherStore } from '@/stores/appLauncher'
const { t } = useI18n()
const store = useAppStore()
const appLauncher = useAppLauncherStore()
const kioskRoot = ref<HTMLElement | null>(null)
interface KioskApp {
id: string
title: string
icon: string
url: string
running: boolean
}
// Public asset path construct with BASE_URL to avoid Vite resolving it as a module import
const FALLBACK_ICON = `${import.meta.env.BASE_URL}assets/img/favico.png`
const currentTime = ref('')
const isConnected = computed(() => store.isConnected)
// Build list of launchable apps from the store's package data
const launchableApps = computed<KioskApp[]>(() => {
const pkgs = store.data?.['package-data'] || {}
const apps: KioskApp[] = []
// App URL mappings. Bitcoin UI uses its direct host-network port; loading it
// through /app/bitcoin-ui/ can render a blank shell because its assets are
// rooted at /.
const urlMap: Record<string, string> = {
'bitcoin-knots': 'http://' + window.location.hostname + ':8334',
'lnd': '/app/lnd/',
'mempool': '/app/mempool/',
'btcpay-server': '/app/btcpay/',
'homeassistant': '/app/homeassistant/',
'grafana': '/app/grafana/',
'jellyfin': '/app/jellyfin/',
'nextcloud': '/app/nextcloud/',
'immich': '/app/immich/',
'photoprism': '/app/photoprism/',
'vaultwarden': '/app/vaultwarden/',
'filebrowser': '/app/filebrowser/',
'searxng': '/app/searxng/',
'ollama': '/app/ollama/',
'portainer': '/app/portainer/',
'uptime-kuma': '/app/uptime-kuma/',
'nginx-proxy-manager': '/app/nginx-proxy-manager/',
'tailscale': '/app/tailscale/',
'fedimint': '/app/fedimint/',
'fedimint-gateway': '/app/fedimint-gateway/',
'indeedhub': 'http://localhost:7778',
'botfights': 'http://localhost:9100',
'nwnn': 'https://nwnn.l484.com',
'484-kitchen': 'https://484.kitchen',
'call-the-operator': 'https://cta.tx1138.com',
'arch-presentation': 'https://present.l484.com',
'syntropy-institute': 'https://syntropy.institute',
't-zero': 'https://teeminuszero.net',
}
for (const [id, pkg] of Object.entries(pkgs)) {
const url = urlMap[id]
if (!url) continue
const isRunning = pkg.state === 'running' ||
pkg.installed?.status === 'running'
apps.push({
id,
title: pkg.manifest?.title || id,
icon: pkg['static-files']?.icon || FALLBACK_ICON,
url,
running: isRunning,
})
}
// Sort: running apps first, then alphabetical
return apps.sort((a, b) => {
if (a.running !== b.running) return a.running ? -1 : 1
return a.title.localeCompare(b.title)
})
})
function openApp(app: KioskApp) {
// Delegate to the app launcher handles iframe overlay vs new-tab
appLauncher.open({ url: app.url, title: app.title })
}
// Clock updater
let clockInterval: ReturnType<typeof setInterval> | undefined
function updateClock() {
const now = new Date()
currentTime.value = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
}
onMounted(() => {
updateClock()
clockInterval = setInterval(updateClock, 30000)
kioskRoot.value?.focus()
// Connect WebSocket if not already
if (!store.isConnected) {
store.connectWebSocket().catch(() => {})
}
})
onUnmounted(() => {
if (clockInterval) clearInterval(clockInterval)
})
</script>
<style scoped>
.kiosk-root {
position: fixed;
left: var(--kiosk-safe-area-x, 0px);
top: var(--kiosk-safe-area-y, 0px);
width: calc(100vw - (var(--kiosk-safe-area-x, 0px) * 2));
height: calc(100vh - (var(--kiosk-safe-area-y, 0px) * 2));
background: #000;
outline: none;
overflow: hidden;
z-index: 9999;
}
.kiosk-launcher {
height: 100%;
display: flex;
flex-direction: column;
padding: clamp(1rem, 3vh, 2rem) clamp(1.5rem, 4vw, 3rem);
background: linear-gradient(180deg, #0a0a12 0%, #000 100%);
box-sizing: border-box;
}
.kiosk-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 2rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
margin-bottom: 2rem;
}
.kiosk-status-pill {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.375rem 0.75rem;
border-radius: 9999px;
font-size: 0.875rem;
font-weight: 500;
}
.kiosk-grid {
flex: 1;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 1.5rem;
align-content: start;
overflow-y: auto;
padding: 0.5rem;
}
.kiosk-app-tile {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
padding: 1.25rem 0.75rem;
border-radius: 1rem;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.06);
transition: all 0.25s ease;
cursor: pointer;
}
.kiosk-app-tile:hover,
.kiosk-app-tile:focus-visible {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(251, 146, 60, 0.4);
transform: scale(1.05);
box-shadow: 0 0 30px rgba(251, 146, 60, 0.15);
outline: none;
}
.kiosk-app-icon-wrap {
position: relative;
width: 64px;
height: 64px;
}
.kiosk-app-icon {
width: 64px;
height: 64px;
border-radius: 16px;
object-fit: cover;
background: rgba(255, 255, 255, 0.05);
}
.kiosk-app-status {
position: absolute;
bottom: -2px;
right: -2px;
width: 14px;
height: 14px;
border-radius: 50%;
border: 3px solid #000;
}
.kiosk-app-label {
font-size: 0.8125rem;
font-weight: 500;
color: rgba(255, 255, 255, 0.85);
text-align: center;
line-height: 1.2;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.kiosk-footer {
padding-top: 1.5rem;
text-align: center;
border-top: 1px solid rgba(255, 255, 255, 0.06);
margin-top: 1.5rem;
}
</style>

View File

@ -69,11 +69,18 @@
</label>
<label class="flex flex-col gap-1">
<span class="text-xs text-white/60">Address (host:port)</span>
<input v-model="draft.address" type="text" placeholder="192.168.1.116:8668" class="px-3 py-2 rounded-md bg-white/5 border border-white/10 text-sm text-white focus:border-white/30 focus:outline-none" />
<input v-model="draft.address" type="text" placeholder="185.18.221.160:8443" class="px-3 py-2 rounded-md bg-white/5 border border-white/10 text-sm text-white focus:border-white/30 focus:outline-none" />
</label>
<label class="flex flex-col gap-1">
<span class="text-xs text-white/60">Transport</span>
<select v-model="draft.transport" class="px-3 py-2 rounded-md bg-white/5 border border-white/10 text-sm text-white focus:border-white/30 focus:outline-none">
<option value="tcp">tcp (public anchor / over internet)</option>
<option value="udp">udp (same LAN)</option>
</select>
</label>
<label class="flex flex-col gap-1 sm:col-span-2">
<span class="text-xs text-white/60">Label (optional)</span>
<input v-model="draft.label" type="text" placeholder="Home anchor" class="px-3 py-2 rounded-md bg-white/5 border border-white/10 text-sm text-white focus:border-white/30 focus:outline-none" />
<input v-model="draft.label" type="text" placeholder="Public anchor" class="px-3 py-2 rounded-md bg-white/5 border border-white/10 text-sm text-white focus:border-white/30 focus:outline-none" />
</label>
<button type="submit" class="sm:col-span-2 min-h-[44px] glass-button rounded-lg text-sm font-medium disabled:opacity-60" :disabled="adding || !draft.npub || !draft.address">{{ adding ? 'Adding…' : 'Add anchor' }}</button>
</form>
@ -106,9 +113,10 @@ const applying = ref(false)
const statusMessage = ref('')
const statusIsError = ref(false)
const draft = reactive<Pick<SeedAnchor, 'npub' | 'address' | 'label'>>({
const draft = reactive<Pick<SeedAnchor, 'npub' | 'address' | 'transport' | 'label'>>({
npub: '',
address: '',
transport: 'tcp',
label: '',
})
@ -136,7 +144,7 @@ async function addAnchor() {
params: {
npub: draft.npub.trim(),
address: draft.address.trim(),
transport: 'udp',
transport: draft.transport,
label: draft.label.trim(),
},
})

View File

@ -188,6 +188,19 @@ init()
</button>
</div>
<div class="overflow-y-auto flex-1 min-h-0 space-y-6 pr-1">
<!-- v1.7.96-alpha -->
<div>
<div class="flex items-center gap-2 mb-3">
<span class="text-xs font-mono px-2 py-0.5 rounded bg-orange-500/20 text-orange-300">v1.7.96-alpha</span>
<span class="text-xs text-white/40">June 15, 2026</span>
</div>
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
<p>The screen attached to your node now shows the normal Archipelago interface and your dashboard after you sign in, instead of a separate, stripped-down grid of app icons that could appear in its place. That extra screen has been removed so the attached display matches what you see everywhere else.</p>
<p>On a brand-new node, the attached screen now walks through the same welcome and setup steps you'd see on a phone or laptop, and shows the normal sign-in screen once the node is set up so the on-device display always matches the rest of the interface.</p>
<p>When adding a FIPS network anchor, you can now choose whether it connects over TCP (for a public anchor reached across the internet) or UDP (for one on your local network), instead of it always assuming the local-network option.</p>
<p>Behind the scenes, a new automated two-node test now exercises real node-to-node features browsing another node's shared files and handling a removed node against live nodes before each release, so node-to-node problems are caught earlier.</p>
</div>
</div>
<!-- v1.7.95-alpha -->
<div>
<div class="flex items-center gap-2 mb-3">

View File

@ -1,27 +1,28 @@
{
"version": "1.7.95-alpha",
"version": "1.7.96-alpha",
"release_date": "2026-06-15",
"changelog": [
"Browsing another node's shared files now works over the fast encrypted mesh. Opening a peer's cloud could fail with a generic \"Operation failed\" message because the request for their file list wasn't permitted over the mesh and came back as \"not found\" \u2014 and it never retried over Tor. The mesh now serves the file list directly, and if a peer can't answer over the mesh the node automatically falls back to Tor instead of giving up.",
"Nodes you remove from your federation now stay removed. Previously a deleted node could quietly come back the next time you synced with another node that still listed it. Removed nodes are now remembered as removed and won't reappear on their own \u2014 only if you add them back yourself.",
"The app credentials pop-up now appears as a normal centred box with a dimmed background over the whole screen, instead of stretching to fill the entire screen."
"The screen attached to your node now shows the normal Archipelago interface and your dashboard after you sign in, instead of a separate, stripped-down grid of app icons that could appear in its place. That extra screen has been removed so the attached display matches what you see everywhere else.",
"On a brand-new node, the attached screen now walks through the same welcome and setup steps you'd see on a phone or laptop, and shows the normal sign-in screen once the node is set up \u2014 so the on-device display always matches the rest of the interface.",
"When adding a FIPS network anchor, you can now choose whether it connects over TCP (for a public anchor reached across the internet) or UDP (for one on your local network), instead of it always assuming the local-network option.",
"Behind the scenes, a new automated two-node test now exercises real node-to-node features \u2014 browsing another node's shared files and handling a removed node \u2014 against live nodes before each release, so node-to-node problems are caught earlier."
],
"components": [
{
"name": "archipelago",
"current_version": "1.7.95-alpha",
"new_version": "1.7.95-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.95-alpha/archipelago",
"sha256": "1036f6822387449f6ce0c2544c81c96d424d11c16343d239ae215b3ef1fcb827",
"size_bytes": 44360792
"current_version": "1.7.96-alpha",
"new_version": "1.7.96-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.96-alpha/archipelago",
"sha256": "147672b4ebecd6042d4606a4fa2fdd60a5de8b9f518a7c0263ee2b6f49aed113",
"size_bytes": 44358704
},
{
"name": "archipelago-frontend-1.7.95-alpha.tar.gz",
"current_version": "1.7.95-alpha",
"new_version": "1.7.95-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.95-alpha/archipelago-frontend-1.7.95-alpha.tar.gz",
"sha256": "d78ec0b2f57cde9722c43bf540730ae91d91a4317ccb1ea26eb27645b35e66c8",
"size_bytes": 184075611
"name": "archipelago-frontend-1.7.96-alpha.tar.gz",
"current_version": "1.7.96-alpha",
"new_version": "1.7.96-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.96-alpha/archipelago-frontend-1.7.96-alpha.tar.gz",
"sha256": "c91c73abd078dd85f2515b553d60348ab8fe169f7ec00e9d4a6b4f131a96c2fa",
"size_bytes": 184071997
}
]
}

View File

@ -1,27 +1,28 @@
{
"version": "1.7.95-alpha",
"version": "1.7.96-alpha",
"release_date": "2026-06-15",
"changelog": [
"Browsing another node's shared files now works over the fast encrypted mesh. Opening a peer's cloud could fail with a generic \"Operation failed\" message because the request for their file list wasn't permitted over the mesh and came back as \"not found\" \u2014 and it never retried over Tor. The mesh now serves the file list directly, and if a peer can't answer over the mesh the node automatically falls back to Tor instead of giving up.",
"Nodes you remove from your federation now stay removed. Previously a deleted node could quietly come back the next time you synced with another node that still listed it. Removed nodes are now remembered as removed and won't reappear on their own \u2014 only if you add them back yourself.",
"The app credentials pop-up now appears as a normal centred box with a dimmed background over the whole screen, instead of stretching to fill the entire screen."
"The screen attached to your node now shows the normal Archipelago interface and your dashboard after you sign in, instead of a separate, stripped-down grid of app icons that could appear in its place. That extra screen has been removed so the attached display matches what you see everywhere else.",
"On a brand-new node, the attached screen now walks through the same welcome and setup steps you'd see on a phone or laptop, and shows the normal sign-in screen once the node is set up \u2014 so the on-device display always matches the rest of the interface.",
"When adding a FIPS network anchor, you can now choose whether it connects over TCP (for a public anchor reached across the internet) or UDP (for one on your local network), instead of it always assuming the local-network option.",
"Behind the scenes, a new automated two-node test now exercises real node-to-node features \u2014 browsing another node's shared files and handling a removed node \u2014 against live nodes before each release, so node-to-node problems are caught earlier."
],
"components": [
{
"name": "archipelago",
"current_version": "1.7.95-alpha",
"new_version": "1.7.95-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.95-alpha/archipelago",
"sha256": "1036f6822387449f6ce0c2544c81c96d424d11c16343d239ae215b3ef1fcb827",
"size_bytes": 44360792
"current_version": "1.7.96-alpha",
"new_version": "1.7.96-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.96-alpha/archipelago",
"sha256": "147672b4ebecd6042d4606a4fa2fdd60a5de8b9f518a7c0263ee2b6f49aed113",
"size_bytes": 44358704
},
{
"name": "archipelago-frontend-1.7.95-alpha.tar.gz",
"current_version": "1.7.95-alpha",
"new_version": "1.7.95-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.95-alpha/archipelago-frontend-1.7.95-alpha.tar.gz",
"sha256": "d78ec0b2f57cde9722c43bf540730ae91d91a4317ccb1ea26eb27645b35e66c8",
"size_bytes": 184075611
"name": "archipelago-frontend-1.7.96-alpha.tar.gz",
"current_version": "1.7.96-alpha",
"new_version": "1.7.96-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.96-alpha/archipelago-frontend-1.7.96-alpha.tar.gz",
"sha256": "c91c73abd078dd85f2515b553d60348ab8fe169f7ec00e9d4a6b4f131a96c2fa",
"size_bytes": 184071997
}
]
}