feat: LUKS2 encryption, boot sequence fixes, onboarding auth, CI/CD
Some checks failed
Build Archipelago ISO / build-iso (push) Has been cancelled

- LUKS2 full-partition encryption for /var/lib/archipelago/ (TASK-42)
  4-partition layout: BIOS + EFI + root (30GB) + encrypted data
  AES-256-XTS with AES-NI detection, ChaCha20 fallback for ARM
  Auto-unlock via crypttab + random key file

- Fix EFI boot errors: remove shim-signed, clean shim artifacts
- Fix first-boot sequence: always show boot animation before onboarding
- Fix stale localStorage causing login instead of onboarding (BUG-47)

- Add auth.setup + auth.isSetup RPC handlers for password on clean install
- Add onboarding methods to UNAUTHENTICATED_METHODS (DID sign 403 fix)

- FileBrowser bundled in unbundled ISO, fix auto-login Secure cookie (BUG-46)
- Kiosk mode: xorg/chromium in rootfs, toggle script, MOTD instructions

- Add Gitea Actions CI/CD workflow for automatic ISO builds

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-26 09:12:16 +00:00
parent 5c15c52113
commit 08bb2c80d4
8 changed files with 463 additions and 52 deletions

View File

@@ -65,7 +65,9 @@ class FileBrowserClient {
const token = text.replace(/^"|"$/g, '')
// Store token as cookie — the only auth mechanism we use
const expires = new Date(Date.now() + 24 * 60 * 60 * 1000).toUTCString()
document.cookie = `auth=${token}; path=/app/filebrowser; SameSite=Strict; Secure; expires=${expires}`
// Only set Secure flag on HTTPS — on HTTP it silently prevents the cookie from being stored
const secure = window.location.protocol === 'https:' ? '; Secure' : ''
document.cookie = `auth=${token}; path=/app/filebrowser; SameSite=Lax${secure}; expires=${expires}`
this._authenticated = true
return true
} catch {

View File

@@ -38,6 +38,19 @@ async function quickHealthCheck(): Promise<boolean> {
}
}
async function checkOnboarded(): Promise<boolean> {
try {
const result = await Promise.race([
isOnboardingComplete(),
new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 3000)),
])
return result
} catch {
// Backend unreachable — fall back to localStorage only as last resort
return localStorage.getItem('neode_onboarding_complete') === '1'
}
}
async function proceedToApp() {
const devMode = import.meta.env.VITE_DEV_MODE
if (devMode === 'setup' || devMode === 'existing') {
@@ -45,23 +58,10 @@ async function proceedToApp() {
return
}
const localComplete = localStorage.getItem('neode_onboarding_complete') === '1'
if (localComplete) {
router.replace('/login').catch(() => {})
return
}
let seenOnboarding = false
try {
const result = await Promise.race([
isOnboardingComplete(),
new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 3000)),
])
seenOnboarding = result
} catch {
seenOnboarding = false
}
router.replace(seenOnboarding ? '/login' : '/onboarding/intro').catch(() => {})
// Always check backend for authoritative onboarding state
// (localStorage can be stale from a previous install on the same IP)
const onboarded = await checkOnboarded()
router.replace(onboarded ? '/login' : '/onboarding/intro').catch(() => {})
}
function onServerReady() {
@@ -98,14 +98,23 @@ onMounted(async () => {
return
}
// Production: quick health check
// Production: check server health
const isUp = await quickHealthCheck()
if (isUp) {
proceedToApp()
return
// Server is up — check if onboarding is complete
const onboarded = await checkOnboarded()
if (onboarded) {
// Returning user, server is up — go straight to login
proceedToApp()
return
}
// First boot: server is up but onboarding not done yet.
// Show boot animation anyway — it lets services fully warm up
// (containers, DID resolver, etc.) before onboarding starts.
}
// Server not ready — show boot screen
// Server not ready OR first boot — show boot screen
showBootScreen.value = true
})
</script>