Show whether the data folder is a real volume (Settings and startup log)
build-container / build (push) Successful in 1m56s

Passkeys, calendars and sessions were being lost on redeploy. The server now
reports whether /data is a mounted volume, whether it is writable, what it
holds, and where the session secret comes from, so a wiped volume is obvious.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-20 15:36:17 -04:00
1 parent 18266267e5
commit 5c7917e57c
2 files changed
+24 -26

No files matched your search

+9
View File
@@ -935,6 +935,7 @@ function openNow() {
function viewSettings() {
const signed = !!sf.token();
$('#view').innerHTML = `<div class="wrap"><h1>Settings</h1>
<h2>Storage</h2><div class="list" id="stor"><div class="row"><span class="grow"><small>Checking…</small></span></div></div>
<h2>Passkeys</h2><div class="list" id="pks"><div class="row"><span class="grow"><small>Loading…</small></span></div></div>
<div class="bar"><button class="btn ghost" id="addpk">Add a passkey</button><button class="btn ghost" id="invpk">Invite a device</button><button class="btn danger" id="hubout">Sign out of NE Hub</button></div>
<h2>Connections</h2><div class="list">
@@ -963,6 +964,14 @@ function viewSettings() {
el.textContent = st.signedIn ? (st.server ? 'Signed in · ' + st.server : 'Signed in · no server chosen') : 'Signed out';
if (st.signedIn) { const b = $('#plexout'); b.hidden = false; b.onclick = async () => { await jfetch('/api/plex/logout', { method: 'POST' }); pl.a.pause(); pl.q = []; pl.i = -1; renderMini(); toast('Signed out of Plex'); viewSettings(); }; }
}).catch(() => { const el = $('#plexst'); if (el) el.textContent = 'Unavailable'; });
jfetch('/api/system').then(i => {
const ok = i.mounted !== false && i.writable, row = (t, d, bad) => `<div class="row"><span class="grow"><b>${t}</b><small style="white-space:normal;${bad ? 'color:var(--danger)' : ''}">${d}</small></span></div>`;
$('#stor').innerHTML =
row(ok ? 'Data is saved on a volume' : 'Data is NOT being kept', i.mounted === false ? 'The data folder is not a mounted volume, so passkeys, calendars and sign-ins are lost when the container is recreated. Mount a volume or a host folder at ' + esc(i.dir) + '.' : !i.writable ? 'The data folder is not writable by the app.' : esc(i.dir) + (i.mounted ? ' (mounted)' : ''), !ok)
+ row('What is stored', `${i.passkeys} passkey${i.passkeys === 1 ? '' : 's'} · ${i.events} NE Hub event${i.events === 1 ? '' : 's'} · ${i.calendarLinks} calendar link${i.calendarLinks === 1 ? '' : 's'}${i.icloud ? ' · iCloud connected' : ''}`)
+ row('Session secret', i.secretFromEnv ? 'Set in the stack (sign-ins survive restarts)' : 'Generated and kept in the data folder', false)
+ row('Running since', new Date(i.startedAt).toLocaleString(), false);
}).catch(() => { const e = $('#stor'); if (e) e.innerHTML = '<div class="row"><small>Could not read storage status</small></div>'; });
const loadPks = async () => {
try {
const pks = await (await fetch('/api/auth/passkeys')).json();