Mint a 160-bit capability URL (/s/<token>) serving a standalone read-only guest
launcher: same tiles, opens apps in new tabs, no account, no admin session.
Information only, zero control — every privilege stays behind each app's own auth.
- /s/ pre-auth pass-through (after the claim gate) + session-CSRF exemption; guest
password POST carries its own pre-auth HMAC CSRF.
- Constant-time token match; empty stored token = disabled = byte-identical mux 404.
- Optional per-share password: separate bcrypt hash + own attempt map; signed cookie
= HMAC(token|passwordHash) keyed with web.session_secret, so rotate/change invalidates.
- Guest labels ride the v0.164.0 ruling; never expose internal state vocabulary.
- Token redacted in logs (/s/<redacted>); never in CHANGELOG/REPORT/CONTEXT.
- Admin modal: copy-link, QR (go-qrcode), set/clear password, rotate, disable.
- Tests: Groups A-G (14) + 3 red-proofs verified red.
(A) The launcher monogram bled through every transparent white logo — it rendered
unconditionally under the img. Now hidden by default (.launch-mono display:none),
revealed only when the img chain fails (onerror adds .launch-tile--noimg).
(B) The /static/app-placeholder.svg default reached only app_list_row. The four
other sanctioned app-logo onerror chains now match the canonical SVG->PNG->placeholder
grammar: backups_apps (aligned row), stacks (infra->infra-logo else placeholder),
app_info hero (screenshots still vanish), deploy (keeps .LogoURL/.LogoPNGURL).
Template/CSS only; no handler/funcmap change. 5 tests + 2 red-proofs.
New /launcher page: a grid of large tappable tiles, one per openable deployed
app (subdomain presence is the single openability criterion, shared with the
Megnyitás button via the extracted Server.subdomainMap helper). Colored tiles
(deterministic slug color or .felhom.yml brand_color), white glyph/monogram
fallback, target=_blank links for operational apps, greyed unclickable tiles for
stopped ones. First sidebar item; / stays the Vezérlőpult.
Universal app placeholder: new AppPlaceholderSVG served at
/static/app-placeholder.svg, now the default FallbackIcon on app_list_row so a
logo-less app shows a placeholder instead of visibility:hidden. Brand mark is
never an app placeholder.
New Metadata.BrandColor; new funcmap tileColor/initial. 10 new test functions +
4 red-proofs. No agent coupling; MinAgent unchanged.
The offsite restore list rendered up to five inline forms per app row. Two of them —
„Helyreállítás az élő adatok közé (csak a hiányzó fájlok)" and „Teljes visszaállítás
(fájlok + adatbázis)" — were sibling buttons whose difference is whether the customer's
data comes back at all. That mis-selection CAUSED the round-2 incident: an operator who
had read the source pressed the missing-only button and /backup/offbox/reconstitute was
never hit (DIAG-immich-restore-round2-2026-07-19, finding 1).
The rule this establishes: two adjacent controls whose difference is "your data comes
back" vs "your data cannot come back" must never be distinguishable only by layout.
Each row now carries ONE „Visszaállítás…" entry linking to a per-app wizard at
GET /backups/restore/app?name=<app>: three intent CARDS with consequence sentences, a
visible phase strip, danger styling plus the R-43 double-confirm carried over verbatim on
the destructive one, and the pair-honesty panel bound to real OffsiteScratchPair facts.
deriveWizardStep is pure over (op running, size-gate flash, scratch ready); the step is
never accepted from the request, and a running op outranks a stale ?full_prep= so no
commit button survives into a restore. While ANY op runs every mutation form is suppressed
server-side instead of being offered and refused.
No new mutation endpoint: every card posts to the pre-existing /backup/offbox/* handler
with the same field names and gates, and the page works with JavaScript disabled.
internal/{backup,appbackup,selfupdate} untouched. R-45's job registry stays its own item.
Fixes a latent bug found on the way: offboxRedirectTo hardcoded "?" when appending its
flash, which against the wizard's ?name=<app> target would have buried the flash inside
the name value.
No new agent coupling — MinAgent stays 0.90.0.
UI-only; no behavioural or backup/restore surface touched. Green: build, vet,
tests all pass; template_id_gate, emoji_gate, native_confirm_gate,
offbox_rename_gate, mojibake_gate, app_row_dedup_gate all PASS.
Scrollbars (style.css): the platform default reads as a bright chunky stripe
against the navy and competes with the content it scrolls. Now thin and
hairline-coloured — scrollbar-width/scrollbar-color for Firefox AND
::-webkit-scrollbar (8px, thumb --line, hover --text-3, --radius) for
WebKit/Blink, because neither alone covers the browsers customers use. The two
surfaces that actually scroll take their own panel background as the track
(.sidebar -> --bg-2, html -> --bg-0) so the gutter never shows as a lighter
channel. Tokens only, no raw hexes.
Collapsible nav groups (layout.html + style.css, vanilla JS, no framework):
Tárhely, Biztonsági mentés and Megosztás become accordions with a chevron;
exactly one open at a time, clicking the open one closes it. Groups without
sub-items are untouched plain links. Hungarian labels unchanged.
- The header is a real <button>, so keyboard and AT reachability come for free
instead of being simulated with tabindex/role on a div. aria-expanded +
aria-controls + a :focus-visible outline.
- Nothing became unreachable when the header stopped being a link: every
group's landing page is ALSO its first sub-item (/storage -> Meghajtók,
/backups -> Áttekintés, /sharing -> Hálózati megosztás). Checked before the
conversion, not assumed.
- Progressive enhancement: the group containing the active page is rendered
open SERVER-SIDE (.is-open), so the right group is open before any JS runs
and stays open if JS never does. The listener only handles clicks.
- No layout jump: collapse animates grid-template-rows 0fr -> 1fr (with
min-height:0 + overflow:hidden on the sub-list) rather than max-height, so
it animates to the content's REAL height and there is no magic number to
drift when a group gains or loses an item — the specific way a max-height
accordion rots. The toggle reserves its 3px active border as transparent so
becoming active adds no width shift. .18s transitions, and both the collapse
and the chevron rotation are disabled under prefers-reduced-motion.
Pre-existing and deliberately NOT bundled: docker_run_volume_path_gate.py still
fails on internal/appexport/estimate.go:179. That is ROADMAP R-29, unrelated to
this change, verified to fail identically on the untouched tree, and R-29 itself
says not to bundle it into an unrelated feature commit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
The .drive-agent-extra slot stacked the agent tag row and the agent
action row with no vertical gap (and sat flush against the registry
action row). The slot is now a flex column with a .6rem gap + top
margin, hidden when empty; enrichCard's inline margin removed.
User feedback on D1: the NAS-add button and the local-drive enrollment
buttons sat side by side with no separation — two different storage
classes interleaved on one page. Now two subpages under Tárhely:
- /storage — Tárhely — Meghajtók: physical drive registry, migrate,
wizard entry points (Új meghajtó inicializálása / Meglévő meghajtó
csatolása), unified agent view, manual add. The enrollment buttons
now live unambiguously in the local-drive context.
- /storage/network — Tárhely — Hálózati tárhely (NAS): the NAS share
list (NAS-megosztások) + add form + its JS (moved verbatim, incl.
its own openDialog copy for the remove overlay).
- layout.html: Tárhely main-nav item gains two always-visible nested
sub-links (Meghajtók / Hálózati tárhely, .nav-links-nested CSS);
parent stays highlighted on both.
- handlers.go: NetworkStoragePaths moves out of storagePageData into
the new networkStoragePageData (page key storage-network) +
storageNetworkPageHandler; GET /storage/network route.
- Tests: /storage must NOT render the NAS section, /storage/network
renders it and nothing drive-related; inventory + no-native-confirm
scans cover the new template. Both template gates green.
- storage.html: the registry cards and the separate 'Meghajtók (ügynök
nézet)' merge into ONE view. Each connected registry card gets an
#agent-extra-<path> slot; the enrichment JS joins the agent /api/disks
list on mount path and decorates the matching card in place (role tag
via i-lock, drive class, durable-id mono line, agent-only
register/eject/wipe actions). Two extra groups render below:
'Rendszermeghajtók' (system/backup — read-only, lock tag, NO actions)
and 'Nem regisztrált meghajtók' (unregistered user-data — register
action only). Agent-down: one warn note into #agent-warn-note, all
registry cards still render from server data (graceful degradation).
The agent-view helpers now emit design-system .tag markup instead of
.badge (roleTag/classTag/dataTag/regTag/appBackingTag); the 🔒 lock
emoji is gone (sprite i-lock).
- Overlay migration: every native confirm()/prompt() on the four pages
routes through a light .confirm-overlay dialog (openDialog; texts
verbatim) — storage remove forms, netStorageRemove, storageMigrateAll,
storageDisconnect, storageDecommission (migrate + the type-to-confirm
anyway branch preserved like-for-like), storageReEnroll; and on the
system page triggerUpdate + controller/server restart; on the security
page the two geo Hungary-removal confirms. Scenario F grep: zero
native confirm/prompt in the four templates.
- Deleted the now-orphaned .badge-lock/.lock-ico CSS (grep-zero first).
- Tests: no-native-confirm scan, agent-down warn-note static assertion;
integrity gate stays green.
- settings.html (1451 lines) deleted; sections moved verbatim into
settings_system.html (Rendszer konfiguráció, Verzió és frissítés,
Vezérlő/Kiszolgáló újraindítása + update/restart JS),
settings_notifications.html (Értesítések, Alkalmazás-email),
settings_security.html (Jelszó módosítás, Földrajzi korlátozás + geo
JS, Vészhelyzeti információk — heading + section copy accents fixed),
storage.html (Adattárolók, NAS, migrate progress, agent view + all
storage JS; wizard entry links now /storage/init|attach with sprite
icons instead of emoji). The NAS + migrate sections were nested inside
{{if .StoragePaths}} in the monolith and vanished with zero drives —
now unconditional on /storage.
- layout.html: Tárhely main-nav item (hard-drive icon) + the
'Beállítások' sidebar group with Rendszer / Értesítések / Biztonság és
hozzáférés sub-links (active-state per page key); orphaned
.sidebar-settings-link CSS deleted (grep-zero), .nav-group-label /
.nav-links-sub added.
- Handlers wired to their own builders + templates; the legacy
settingsData() merge deleted.
- scripts/template_id_gate.py: the §10 JS element-ID integrity gate
(getElementById/querySelector('#…') must resolve in the SAME template;
JS-created + template-parameterized IDs handled; layout modal IDs
allowlisted). Red-proven: a storage function planted in the
notifications template failed the gate with 'static #migrate-progress
not defined'.
- Tests: per-page section markers + cross-leak assertions, h3 section
inventory (all 11 old headings accounted for; typo rename asserted).
- settings.html: Aktív/Inaktív rows -> run-blue check / neutral gray
(Inaktív no longer red), update states -> run/crit/progress with
sprite check/x/spinner, pencil + cancel buttons -> icons, storage
badges -> tags, host-disk bar + JS drive capBar -> meters
(usageColorClass -> nominal/warn/crit), state-text-* consumers on the
new suffixes incl. JS-built class names.
- debug.html, app_info.html, storage_init/attach.html, logs.html:
emoji -> sprite icons or plain text in templates AND JS strings.
- catchall.html (standalone, no sprite): v2 token sweep of its inline
style, status emoji -> inline SVGs; a stopped app renders neutral,
not red.
- login.html: two-tone H1 (last word blue-bright).
- setup_hub_versions.html: stale var(--border,#30363d) fallback -> v2.
- Test Group F grep gate: all 34 banned patterns at ZERO across
internal/web + internal/setup; Scenario E test now asserts the
'--bg-0: #0A1220' token literal.
- Vendor Plus Jakarta Sans + JetBrains Mono as variable woff2 (latin +
latin-ext) under internal/web/static/fonts/, embedded via go:embed and
served at /static/fonts/ (font/woff2, immutable cache). Google Fonts
@import replaced with @font-face rules preserving unicode-range —
removes the CDN dependency that silently broke on offline nodes.
- Add templates/icons.html: vendored Lucide sprite (30 icons, symbol
ids i-<name>), included at the top of <body> in layout.html.
- Fix setup wizard handleCSS: serve the embedded web.StyleCSS() instead
of a dataDir-derived filesystem path that never exists in the
container (production setup silently served minimalCSS). Fallback to
minimalCSS only if the embedded read errors, with a WARN log.
- Tests: font route + StyleCSS accessor (web), Scenario E embedded-CSS
test (setup; red-proven against the pre-fix handler).
Controller-side of NAS network storage, proxying to agent A1 /netstorage/*. Distinct
'network' storage kind (no drive lifecycle); add/list/remove + per-share health UI;
unreachable NAS is a recoverable warning, never the drive missing/stop cascade; SMB
creds pass through to the agent, never persisted.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
.stack-title-row gains flex:1+min-width:0 so it shrinks/wraps; .stack-state-badge
gains flex-shrink:0 so the nowrap badge is never compressed. Only unhealthy cards
clipped (the route-unpublished warning inflated the title-row).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
agentapi.Decommission + handleStorageDecommission (migrate-all-or-none, Change 2):
migrate-then-decommission via the migration done-hook, or decommission-anyway (stop
apps, keep HDD_PATH). 'Hiányzó tárhely' badge on dashboard/stacks/app card when an
app's drive is decommissioned/disconnected/absent. Change 4: registerStoragePath
clears the decommissioned marker on re-enroll (ClearDecommissioned had no callers).
Non-hollow tests incl. mutation-proven Change-4 companion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Traefik only publishes a route to a healthy container, so an unhealthy deployed app
returns 404 at its URL though the container runs — previously shown only as 'Nem
egészséges' with no hint the URL is dead. New routeUnpublished() funcmap helper +
a distinct indicator on the dashboard and stacks cards (gated on .Deployed). Tests:
routeUnpublished across all states, real templateFS parses with the funcmap, and the
card guard renders the indicator only for deployed+unhealthy.
Part 2 of the USB/backup spec. agentapi: StatusResponse.Backup record, DueResponse
age_seconds, RestoreTestStatus(). New "Rendszermentés (teljes mentés)" section
(read-only: last backup/target PBS-vs-local/next-due/restore-test) + "Mentés most"
manual trigger that goes through the quiesce loop (controller owns quiescing):
quiesce.Loop gains mutex + TriggerNow() (single-flight, async). New
/api/guest-backup/{trigger,status} (distinct from apiRouter's /api/backup/*).
App-data rows relabeled under an "Alkalmazás-mentések" divider. Config → slice 10.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace traffic light colors (green/yellow/red) with brand palette:
- Primary actions: blue gradient
- Secondary actions: ghost/outline
- Destructive actions: ghost with red hover (modals keep filled red)
- Running cards: blue glow instead of green border
- Bottom-aligned buttons via flexbox column layout
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add country-based access control managed through the Settings page.
Global allow-list with per-app overrides, searchable country selector,
automatic sync to Cloudflare WAF on settings change / deploy / remove,
plus periodic 6-hour verification.
New package: internal/cloudflare/ (client, zone, waf, countries, geosync)
New API: /api/geo/* (6 endpoints) + /api/stacks/{name}/geo/override
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Deploy page, pre-start check, and deploy validation now use actual
/proc/meminfo usage instead of declared mem_request sums. New
GetMemoryMB() helper for lightweight real-time memory reads. Monitoring
page gains a stacked memory distribution bar showing per-container
usage, OS overhead, and free memory.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove "Automatikusan generálva" badge from domain field (it's not
generated, it's the customer's configured domain)
- Shrink subdomain input width (8rem) so the .domain suffix appears
directly next to it on the same line
- Suppress redundant "Az alkalmazás aldomainje" description hint for
subdomain fields (the warning hint is sufficient)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Users can now customize the subdomain for each app during deployment
instead of using a fixed value. The deploy page shows an editable text
input with the default pre-filled and the base domain as a suffix.
New "subdomain" deploy field type with DNS-safe format validation,
reserved name blocklist, and uniqueness check across deployed stacks.
Locked after deploy — changing requires Remove + Redeploy.
Backward compatible: InjectMissingFields() auto-fills SUBDOMAIN from
.felhom.yml defaults for existing deployed apps on next sync/restart.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Debug-mode-only dashboard (/debug) with 8 collapsible sections:
system diagnostics, notification testing, backup triggers, storage
simulation, hub & connectivity, self-update dry-run, DR/setup wizard,
and in-memory log viewer. Migrates debug dump from API router to web
server. Adds ring buffer log capture, storage disconnect simulation,
event history tracking, and cross-drive/self-update test methods.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 1: Deprecate restic as Tier 2 method (rsync only), auto-migrate on startup
Phase 2: Enhanced per-app migration with backup awareness, DB dump copy, auto-cleanup
Phase 3: Full drive migration with decommissioned state, rollback support, wizard UI
Phase 4: Hub report includes decommissioned drive state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Cross-drive now copies DB dumps (_db/) and config (_config/) alongside user data
- restic cross-drive includes config dir + full DB dump dir
- UI: per-tier rows (1. mentés / 2. mentés) instead of per-layer (DB/Konfig/Data)
- UI: BackupContents label shows what each tier protects (DB + Konfig + Adatok)
- UI: rsync backups show browsable indicator (📁)
- Cleanup: removed unused filterSnapshotsByPaths + pathCovers from router.go
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix 1: HDD data backup is now mandatory for all deployed apps.
resolveAppBackupPaths() iterates ListDeployedStacks() directly — no
longer reads GetAppBackupMap() or checks the Enabled flag. DiscoverAppData()
drops backupPrefs parameter; BackupEnabled is set from HasHDDData.
Five dead settings methods removed: IsAppBackupEnabled, SetAppBackup,
GetAppBackupMap, SetAppBackupBulk, GetAppBackupPrefs.
Fix 2: Cross-drive backup now triggers a fresh DB dump (DumpStackDB)
before running. DBDumper interface added to crossdrive.go; Manager
implements it; SetDBDumper wired in main.go. Non-fatal — proceeds with
user data backup even if DB dump fails.
Fix 3: Restore dropdown shows ALL deployed apps (not just HDD+enabled).
restore.go rewritten: always restores config+DB, adds user data if hasHDD.
UI shows restore type banner (full / config+DB / config only) with
color-coded styling. Snapshot API clarified for non-HDD apps.
Fix 4: "Docker kötetek" → "Konfiguráció" — named volumes are not in
the restic backup paths; compose files + app.yaml are what's backed up.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Bug 1: Remove hardcoded 'v' prefix from templates (layout.html, settings.html); version tag already contains 'v'
- Bug 2: primaryHDDPath() and metrics collector now use GetDefaultStoragePath() instead of paths[0].Path so the real HDD is used, not the first (SSD) path
- Bug 3: Apps without HDD data show green/yellow based on volumeLastStatus instead of always gray
- Bug 5: Add default background rgba(255,255,255,0.1) to .btn to fix white-on-transparent readability
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Bug fixes:
- GetFullStatus() returns deep copy; CrossDriveSummary/UnconfiguredApps/CrossDriveWarnings
are always nil in the copy so the handler builds them fresh (fixes duplicate-apps bug)
- Replace binary IsMountPoint check with tiered CheckBackupDestination() — path-not-exist,
not-writable, system-drive (warning), disk >90-95% full; shown as warning vs critical
- Remove dead settingsAppBackupHandler / POST /settings/app-backup route (toggle wrote
to settings.json but nothing consumed the flag)
Architecture:
- Unified per-app backup rows: new AppBackupRow struct + buildAppBackupRows() replaces
the two old sections with expandable rows showing all 3 layers per app
- Sequential backup chaining: cross-drive runs immediately after restic (removed
independent cross-drive-daily/cross-drive-weekly scheduler jobs)
- Deploy page: remove "Csak kézi indítás" schedule option; add weekly consistency note
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace the complex "Alkalmazás adatok" form (checkboxes, paths, volumes,
save button) with a clean read-only status list. Each app shows its name
(linked to its deploy page) and a simple status: Aktív / Inaktív / N/A.
Also include ALL deployed stacks in the list (not just HDD-capable ones),
so apps without user data appear with N/A status.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Detect stale data on non-active storage paths after migration; show on
deploy/settings page with size info and two-step delete confirmation
- Add POST /api/storage/stale-cleanup handler with safety checks (active
path protection, registered-path validation, ProtectedHDDPaths guard)
- Export ProtectedHDDPaths() from stacks package for reuse in web handlers
- Sync FileBrowser mounts after successful app data migration
- Deploy page title/h2 now shows "Beállítások" for already-deployed apps
instead of always showing "Telepítés"
- Also add delete-old-data button on migration-done card in migrate.html
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Startup ping: fire heartbeat + health + hub report immediately on boot
(5s delay after scheduler start, instead of waiting 5-15 min for first tick)
- Storage init wizard: new internal/storage/ package with disk scanning
(lsblk -J), format+mount pipeline (sfdisk → mkfs.ext4 → blkid → fstab →
mount → chown), safety guards (system disk detection, confirmation "FORMÁZÁS"),
progress channel, auto-register in settings.json
- Data migration: MigrateAppData() with rsync --info=progress2 progress parsing,
stop/rsync/update-config/start flow, rollback on failure, old data preserved
- New pages: /settings/storage/init (wizard), /stacks/{name}/migrate (migration)
- New API routes: /api/storage/{scan,init,init/status,migrate,migrate/status}
- Deploy page: storage info section for deployed apps (path, size, free, migrate link)
- Settings page: "Mozgatás" button per app in storage path details
- Container: privileged: true, /dev:/dev, /etc/fstab:/host-fstab, /run/udev:/run/udev:ro
- Dockerfile: add util-linux, e2fsprogs, rsync, parted for disk ops
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Health severity fix: mount-point check downgraded from issue (FAIL) to warning (WARN)
- All storage health messages translated to Hungarian
- Success flash messages for all storage operations
- Edit storage path labels (inline edit UI + backend)
- App details per storage path on settings page (expandable list with names + sizes)
- Storage badge on stacks page showing which storage each app uses
- Deploy dropdown with free space display and low-space warning (<20%)
- Filesystem & disk info on settings page (ext4/btrfs, device, model via findmnt)
- Backup page storage context with per-app storage label badges
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix backup toggles not appearing (read each app's own HDD_PATH from app.yaml)
- Storage paths registry in settings.json with auto-discovery from deployed apps
- Settings page "Adattárolók" section with disk usage, add/remove/default/schedulable
- Deploy page path field as dropdown of registered storage paths
- Health check storage monitoring (mount point, disk usage alerts)
- Mount-point validation utilities (Linux syscall + cross-platform stubs)
- Controller docker-compose mount changed to /mnt:/mnt:rw for multi-storage
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>