Compare commits
2 Commits
v1.7.31-al
...
v1.7.33-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65582d67c6 | ||
|
|
fd3f5d2701 |
2
core/Cargo.lock
generated
2
core/Cargo.lock
generated
@@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "archipelago"
|
||||
version = "1.7.31-alpha"
|
||||
version = "1.7.33-alpha"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"archipelago-container",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "archipelago"
|
||||
version = "1.7.31-alpha"
|
||||
version = "1.7.33-alpha"
|
||||
edition = "2021"
|
||||
description = "Archipelago Bitcoin Node OS - Native backend"
|
||||
authors = ["Archipelago Team"]
|
||||
|
||||
@@ -229,5 +229,14 @@ async fn main() -> Result<()> {
|
||||
crash_recovery::remove_pid_marker(&config.data_dir).await;
|
||||
|
||||
info!("Archipelago shut down cleanly");
|
||||
Ok(())
|
||||
|
||||
// Hard-exit after logging. All business state is persisted by now
|
||||
// (connections drained, PID marker removed, disk flushes done via
|
||||
// tokio::fs awaits). Letting tokio try to drop the runtime instead
|
||||
// can stall for 15s+ on non-daemon OS threads we don't directly
|
||||
// own (mdns_sd daemon, reqwest resolver pool, etc.) — long enough
|
||||
// for systemd's TimeoutStopSec to SIGKILL us and mark the service
|
||||
// Failed, which makes an otherwise-successful update look like a
|
||||
// crash in `systemctl status`.
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
@@ -160,6 +160,18 @@ impl LanTransport {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LanTransport {
|
||||
// The mdns_sd daemon runs on its own OS thread and the browse
|
||||
// listener task blocks on a sync channel. Without this call both
|
||||
// keep the process alive past SIGTERM, long enough for systemd to
|
||||
// SIGKILL us — which makes a normal update look like a crash.
|
||||
fn drop(&mut self) {
|
||||
if let Some(daemon) = self.daemon.take() {
|
||||
let _ = daemon.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeTransport for LanTransport {
|
||||
fn kind(&self) -> TransportKind {
|
||||
TransportKind::Lan
|
||||
|
||||
@@ -19,11 +19,13 @@ async function callWithRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T
|
||||
}
|
||||
|
||||
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
|
||||
// Prefer the backend — localStorage gets stale across nodes (a
|
||||
// browser that onboarded node A would otherwise treat fresh node B
|
||||
// as already-onboarded and skip the wizard entirely). Only fall
|
||||
// back to localStorage if the backend is unreachable.
|
||||
const result = await callWithRetry(() => rpcClient.isOnboardingComplete(), 2)
|
||||
if (result !== null) return result
|
||||
return false
|
||||
return localStorage.getItem('neode_onboarding_complete') === '1'
|
||||
}
|
||||
|
||||
export async function completeOnboarding(): Promise<void> {
|
||||
|
||||
@@ -10,10 +10,19 @@ describe('useLoginTransitionStore', () => {
|
||||
it('starts with all flags false', () => {
|
||||
const store = useLoginTransitionStore()
|
||||
expect(store.justLoggedIn).toBe(false)
|
||||
expect(store.justCompletedOnboarding).toBe(false)
|
||||
expect(store.pendingWelcomeTyping).toBe(false)
|
||||
expect(store.startWelcomeTyping).toBe(false)
|
||||
})
|
||||
|
||||
it('setJustCompletedOnboarding updates justCompletedOnboarding', () => {
|
||||
const store = useLoginTransitionStore()
|
||||
store.setJustCompletedOnboarding(true)
|
||||
expect(store.justCompletedOnboarding).toBe(true)
|
||||
store.setJustCompletedOnboarding(false)
|
||||
expect(store.justCompletedOnboarding).toBe(false)
|
||||
})
|
||||
|
||||
it('setJustLoggedIn updates justLoggedIn', () => {
|
||||
const store = useLoginTransitionStore()
|
||||
store.setJustLoggedIn(true)
|
||||
|
||||
@@ -4,6 +4,13 @@ import { ref } from 'vue'
|
||||
/** Signals that we just logged in - Dashboard uses this for zoom + oomph */
|
||||
export const useLoginTransitionStore = defineStore('loginTransition', () => {
|
||||
const justLoggedIn = ref(false)
|
||||
/**
|
||||
* True only when the user just finished the onboarding wizard
|
||||
* (first password setup), as distinct from a regular re-login.
|
||||
* Dashboard uses this to decide whether to play the full glitchy
|
||||
* reveal vs just a quick interface-draw.
|
||||
*/
|
||||
const justCompletedOnboarding = ref(false)
|
||||
/** Show empty welcome block until typing starts (hide static text) */
|
||||
const pendingWelcomeTyping = ref(false)
|
||||
/** Trigger welcome typing on Home - set true after dashboard animation finishes */
|
||||
@@ -13,6 +20,10 @@ export const useLoginTransitionStore = defineStore('loginTransition', () => {
|
||||
justLoggedIn.value = value
|
||||
}
|
||||
|
||||
function setJustCompletedOnboarding(value: boolean) {
|
||||
justCompletedOnboarding.value = value
|
||||
}
|
||||
|
||||
function setPendingWelcomeTyping(value: boolean) {
|
||||
pendingWelcomeTyping.value = value
|
||||
}
|
||||
@@ -24,6 +35,8 @@ export const useLoginTransitionStore = defineStore('loginTransition', () => {
|
||||
return {
|
||||
justLoggedIn,
|
||||
setJustLoggedIn,
|
||||
justCompletedOnboarding,
|
||||
setJustCompletedOnboarding,
|
||||
pendingWelcomeTyping,
|
||||
setPendingWelcomeTyping,
|
||||
startWelcomeTyping,
|
||||
|
||||
@@ -264,10 +264,13 @@ watch(() => route.path, (newPath) => {
|
||||
onMounted(() => {
|
||||
previousRoutePath = route.path
|
||||
document.body.classList.add('dashboard-active')
|
||||
if (loginTransition.justLoggedIn) {
|
||||
if (loginTransition.justCompletedOnboarding) {
|
||||
// Full glitchy reveal — only on the very first dashboard entry
|
||||
// right after onboarding (one-time event, persists in feel).
|
||||
playDashboardLoadOomph()
|
||||
showZoomIn.value = true
|
||||
loginTransition.setPendingWelcomeTyping(true)
|
||||
loginTransition.setJustCompletedOnboarding(false)
|
||||
loginTransition.setJustLoggedIn(false)
|
||||
const triggerRevealGlitch = () => {
|
||||
isGlitching.value = true
|
||||
@@ -281,6 +284,18 @@ onMounted(() => {
|
||||
loginTransition.setStartWelcomeTyping(true)
|
||||
loginTransition.setPendingWelcomeTyping(false)
|
||||
}, 4000)
|
||||
} else if (loginTransition.justLoggedIn) {
|
||||
// Regular re-login — quick interface draw, no triple glitch flashes.
|
||||
// Just the zoom-in for a short beat, then welcome typing fires fast.
|
||||
playDashboardLoadOomph()
|
||||
showZoomIn.value = true
|
||||
loginTransition.setPendingWelcomeTyping(true)
|
||||
loginTransition.setJustLoggedIn(false)
|
||||
scheduledTimeout(() => { showZoomIn.value = false }, 1200)
|
||||
scheduledTimeout(() => {
|
||||
loginTransition.setStartWelcomeTyping(true)
|
||||
loginTransition.setPendingWelcomeTyping(false)
|
||||
}, 600)
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleKioskShortcuts)
|
||||
|
||||
@@ -408,6 +408,7 @@ async function handleSetup() {
|
||||
stopSynthwave()
|
||||
whooshAway.value = true
|
||||
playLoginSuccessWhoosh()
|
||||
loginTransition.setJustCompletedOnboarding(true)
|
||||
loginTransition.setJustLoggedIn(true)
|
||||
await new Promise(r => setTimeout(r, 520))
|
||||
await router.replace(loginRedirectTo.value).catch(() => {
|
||||
|
||||
@@ -86,9 +86,23 @@ const videoBackgroundRoutes = ['/onboarding/intro', '/login']
|
||||
// Login uses video when coming from splash, or static + glitch when direct
|
||||
const isLoginRoute = computed(() => route.path === '/login')
|
||||
|
||||
// True once onboarding is complete. Used to skip the intro video on
|
||||
// the /login route so that returning (logged-out) users go straight
|
||||
// to the screensaver-style static + glitch background instead of
|
||||
// replaying the full intro every time.
|
||||
const onboardingDone = computed(() => {
|
||||
try {
|
||||
return localStorage.getItem('neode_onboarding_complete') === '1'
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
// Check if current route should use video background
|
||||
const useVideoBackground = computed(() => {
|
||||
return videoBackgroundRoutes.includes(route.path)
|
||||
if (!videoBackgroundRoutes.includes(route.path)) return false
|
||||
if (route.path === '/login' && onboardingDone.value) return false
|
||||
return true
|
||||
})
|
||||
|
||||
// Map each route to a specific background image
|
||||
|
||||
@@ -129,7 +129,31 @@ onMounted(async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// Server not ready — show boot screen (waiting for backend)
|
||||
// Server not ready. The full BootScreen is meant for a genuine
|
||||
// cold-start (fresh install), not for the brief blip during an
|
||||
// OTA update where the backend restarts. If onboarding has already
|
||||
// completed we just keep the spinner and retry until the server
|
||||
// responds again.
|
||||
const wasOnboardedBefore = localStorage.getItem('neode_onboarding_complete') === '1'
|
||||
if (wasOnboardedBefore) {
|
||||
log('server down + onboarded → polling without boot screen')
|
||||
let retries = 0
|
||||
const maxRetries = 30 // 30 * 2s = 60s before giving up and showing boot screen
|
||||
const poll = setInterval(async () => {
|
||||
retries++
|
||||
if (await quickHealthCheck()) {
|
||||
clearInterval(poll)
|
||||
proceedToApp()
|
||||
return
|
||||
}
|
||||
if (retries >= maxRetries) {
|
||||
clearInterval(poll)
|
||||
log('server still down after retries → falling back to boot screen')
|
||||
showBootScreen.value = true
|
||||
}
|
||||
}, 2000)
|
||||
return
|
||||
}
|
||||
showBootScreen.value = true
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -94,7 +94,7 @@ export default defineConfig({
|
||||
urlPattern: /\/assets\/.*/i,
|
||||
handler: 'CacheFirst',
|
||||
options: {
|
||||
cacheName: 'assets-cache-v2',
|
||||
cacheName: 'assets-cache-v3',
|
||||
expiration: {
|
||||
maxEntries: 100,
|
||||
maxAgeSeconds: 60 * 60 * 24 * 30 // 30 days
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
{
|
||||
"version": "1.7.31-alpha",
|
||||
"version": "1.7.33-alpha",
|
||||
"release_date": "2026-04-22",
|
||||
"changelog": [
|
||||
"Installing IndeedHub is now fully self-healing — if a previous install was interrupted, re-running from the App Store automatically cleans up the leftover containers and tries again instead of failing with a 'name already in use' error.",
|
||||
"New default app registries and update mirrors now appear automatically on existing nodes after an update — no more needing to manually add Server 3 (OVH) from the settings page. Anything you've explicitly removed stays removed.",
|
||||
"Fixed the 'Test' button on registries that protect their API endpoint — it used to falsely report those registries as unreachable. It now correctly recognizes a protected-but-alive registry as reachable.",
|
||||
"First-boot cleanup: removed an old IndeedHub stub from the first-boot script that used to race the main installer and occasionally leave a half-installed IndeedHub behind."
|
||||
"Onboarding fix: a fresh node would skip the full onboarding wizard and dump you straight at 'set password' if your browser had ever onboarded another node. The check now asks the actual node first instead of trusting browser memory.",
|
||||
"Lock screen fix: logging out used to replay the full intro video every single time. Now once you've onboarded, logging out drops you on the static lock-screen background — login is instant, no movie.",
|
||||
"Update fix: a brief network hiccup during an OTA update no longer triggers the full mock 'boot screen' animation on already-onboarded nodes. The page just shows a quiet spinner and reconnects when the backend is back. Boot screen is reserved for genuine fresh boots.",
|
||||
"Login animation: the glitchy zoom-and-flash reveal now only plays the very first time you reach the dashboard after onboarding. Every subsequent login gets a quick interface draw — fast, no glitches.",
|
||||
"PWA cache bumped so old browsers/devices reliably pick up new UI versions instead of serving stale cached assets after an update."
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"name": "archipelago",
|
||||
"current_version": "1.7.30-alpha",
|
||||
"new_version": "1.7.31-alpha",
|
||||
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.31-alpha/archipelago",
|
||||
"sha256": "ce2f899d3c4b615136223ae6295ee4b5de4009d1db926f7648a788c0ad3c84b8",
|
||||
"size_bytes": 40786728
|
||||
"current_version": "1.7.32-alpha",
|
||||
"new_version": "1.7.33-alpha",
|
||||
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.33-alpha/archipelago",
|
||||
"sha256": "c75a226658cb8af7ecb4eff937cbc221bb2b1c93bf1dadd61c99b2f550376c8b",
|
||||
"size_bytes": 40793648
|
||||
},
|
||||
{
|
||||
"name": "archipelago-frontend-1.7.31-alpha.tar.gz",
|
||||
"current_version": "1.7.30-alpha",
|
||||
"new_version": "1.7.31-alpha",
|
||||
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.31-alpha/archipelago-frontend-1.7.31-alpha.tar.gz",
|
||||
"sha256": "00f474725edaf14dc41d0c02abd3afcff1b30fa50846adec9e11b3c5b2188564",
|
||||
"size_bytes": 77008771
|
||||
"name": "archipelago-frontend-1.7.33-alpha.tar.gz",
|
||||
"current_version": "1.7.32-alpha",
|
||||
"new_version": "1.7.33-alpha",
|
||||
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.33-alpha/archipelago-frontend-1.7.33-alpha.tar.gz",
|
||||
"sha256": "d6fd4648046d4ea05d33ef56180afda80e118f6d655ba7d339a2135a0a28e838",
|
||||
"size_bytes": 77011007
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
BIN
releases/v1.7.32-alpha/archipelago
Executable file
BIN
releases/v1.7.32-alpha/archipelago
Executable file
Binary file not shown.
BIN
releases/v1.7.32-alpha/archipelago-frontend-1.7.32-alpha.tar.gz
Normal file
BIN
releases/v1.7.32-alpha/archipelago-frontend-1.7.32-alpha.tar.gz
Normal file
Binary file not shown.
BIN
releases/v1.7.33-alpha/archipelago
Executable file
BIN
releases/v1.7.33-alpha/archipelago
Executable file
Binary file not shown.
BIN
releases/v1.7.33-alpha/archipelago-frontend-1.7.33-alpha.tar.gz
Normal file
BIN
releases/v1.7.33-alpha/archipelago-frontend-1.7.33-alpha.tar.gz
Normal file
Binary file not shown.
Reference in New Issue
Block a user