Commit Graph

235 Commits

Author SHA1 Message Date
admin aa967fbf69 controller: Felhom-offsite status card — three honest states from local data (opt-in pitch / Aktív-nincs-kijelölt / the regular status block); display-only by design (decision 2: never a state change), state-tested incl. the no-form/no-button invariant
Claude-Session: https://claude.ai/code/session_01GzammAMzsJTgpQHqxwM2bC
2026-07-13 09:37:05 +02:00
admin 0ece2ba85c controller: backups IA split — four sub-pages (Áttekintés /backups, Távoli mentés /backups/remote, Alkalmazások /backups/apps, Visszaállítás /backups/restore); sections MOVED verbatim (backups_split_move_check.py enforces vs df7ad37); shared data builder extracted; offbox restore-to-verify relocated to the restore page; flash redirects + tier-3 anchors + tier2 back-link retargeted
Claude-Session: https://claude.ai/code/session_01GzammAMzsJTgpQHqxwM2bC
2026-07-13 09:35:01 +02:00
admin b482860c03 controller: zero-toggle honesty — a configured+escrowed offbox with no app selected shows 'Nincs távoli mentésre jelölt alkalmazás…' on the toggle list, and a zero-toggle run reports 'Sikeres — nincs mentésre jelölt alkalmazás' instead of bare success
Claude-Session: https://claude.ai/code/session_01GzammAMzsJTgpQHqxwM2bC
2026-07-13 08:27:52 +02:00
admin a00afcc79d controller: Tier-3 rename — customer-facing 'NAS-mentés' branding becomes 'Távoli mentés' (backups page, offbox flashes, quota copy); manual-target form generalized to any SFTP target; offbox_rename_gate.py enforces zero regressions
Claude-Session: https://claude.ai/code/session_01GzammAMzsJTgpQHqxwM2bC
2026-07-13 08:23:43 +02:00
admin 8987ce0f67 controller: drill F-11 — inline two-step confirm (felhomConfirm + data-confirm) replaces every native confirm(); native_confirm_gate.py enforces zero; stale-data delete keeps its double acknowledgement inline
Claude-Session: https://claude.ai/code/session_01GzammAMzsJTgpQHqxwM2bC
2026-07-13 08:19:39 +02:00
admin b4e0a197f9 controller: F-15 instant reset codes — reset-request response hash applied via the ACK's generation-guarded ClaimSync (emailed code works immediately; old-hub bare response = no-op)
Claude-Session: https://claude.ai/code/session_01GzammAMzsJTgpQHqxwM2bC
2026-07-13 08:05:07 +02:00
admin 3cf49c7fd5 controller: customer-claim password gate v0.122.0 (closes DRILL-day0-vm F-4/F-5)
The customer sets + owns the dashboard password via a hub-emailed one-time
claim code. An unclaimed box (code hash present, no password) serves ONLY the
claim page — every other route → claim page (302) or 401, so a Day-0 box is
never open on the internet. A set password disables the gate (auth wins).
Reset rides the same code engine (login "Elfelejtett jelszó"). Legacy-open
(no password, no hash) shows a red transition banner until the hub delivers a
hash. Report ACK caches the code state idempotently by generation; report
carries claimed (set-only). --print-reset-code root escape hatch. Requires
hub v0.50.0. Gate-coverage signature test + 4 red-proofs proven.
2026-07-12 18:42:39 +02:00
admin 9d05fa5c35 controller v0.121.0: backups page truth pass — remove dead Részletek card, real Tier-3 off-box state, SQLite-honest DB messaging
MinAgent: 0.81.0 (unchanged). Controller-only; no agent-API change, no backup-engine
behavior change. Fixes the self-contradicting /backups page (v0.120.0 live):

- Remove the dead "Részletek" card (redundant; kills never-set Tier2DriveGroups/
  ResticPassword fields + restic-pw element + toggleTier/toggleResticPw/copyResticPw JS).
- Per-app "3. mentés" row shows real off-box state via pure tier3State
  (unconfigured/off/escrow_pending/active) — "Hamarosan" placeholder gone.
- SQLite-honest DB messaging via pure dbSectionState (dumps/pending/embedded).
- Populate Tier1LastRun/Tier1LastStatus from ListRestorePoints; Tier-1/Tier-2 labels
  via timeAgoStr (relative), confirm() dialog keeps raw timestamp.
- Terminology split: "Távoli mentés (3. mentés)" (off-box, +#offbox-section anchor)
  vs "Távoli rendszermentés" (PBS whole-CT).
- Deploy page: add "Mentési beállítások →" link.

+9 internal/web tests (pure helper tables + buildAppBackupRows wiring + template
renders), 4 companion red-proofs run→fail→revert.
2026-07-12 12:26:30 +02:00
admin 40498254c6 fix-6: raise ring display cap to 5000 (viewer + Entries clamp); viewer default limit 1000
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CDMFpFx84pfviCTVuGGhf
2026-07-12 10:21:34 +02:00
admin e030b8d9a9 test: clean message in fix-6 spill tests 2026-07-12 10:09:22 +02:00
admin 6ab8f943af test: io.Discard logger in fix-3 tests 2026-07-12 10:08:39 +02:00
admin d8f6069b46 controller: fix-3 dead-app alerting + fix-6 ring cap/spill/spam (WIP, pre-build)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CDMFpFx84pfviCTVuGGhf
2026-07-12 10:07:47 +02:00
admin 5d91fc8cce controller: F8 share-row stub fusion + F4 mapped_uid range validation (WIP, pre-build)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CDMFpFx84pfviCTVuGGhf
2026-07-12 09:37:12 +02:00
admin 68b3a3932e controller: F7 atomic volume dumps + F6 no-single-copy + F5 stale-primary sweep (WIP, pre-build)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CDMFpFx84pfviCTVuGGhf
2026-07-12 09:00:16 +02:00
admin 4a9c54a105 test: pin netProbeFSClass=network in the C5 child-body suite (real statfs on linux correctly refuses tempdir)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-11 21:15:48 +02:00
admin c0f3e12483 v0.117.0: consuming-namespace NAS verification + deploy-view truth (RCA fixes 2+4)
statfs fsclass helper (network/autofs/stub/unknown, fail-open); probe not_network_fs
assertion (stub can never verify — red-proven); deploy-time stub refusal (idle autofs
proceeds — red-proven); distinct stub badge, stub wins over unreachable (unreachable line
byte-identical); deployed select shows stored HDD_PATH (red-proven vs IsDefault-only).
MinAgent unchanged 0.81.0. Gates green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-11 21:12:48 +02:00
admin 458d7e1ebc v0.116.1: ungate /debug + /api/debug from logging.level — the viewer must exist wherever the ring does
Live validation at logging.level=info 404'd the whole debug surface (the
motivating incident's blind spot). Auth unchanged (RequireAuth + CSRF).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-11 17:12:20 +02:00
admin 26a43708b7 v0.116.0: observability pass — always-on debug ring + leveled sweep + agent tab + self-log pull — MinAgent: 0.81.0
Capture layer: LogBuffer always exists; logger = MultiWriter(LevelFilterWriter
(stdout, logging.level), ring) so DEBUG detail exists remotely without a config
flip while docker logs keep respecting the level. New internal/logx leveled
helpers. Report ACK gains controller_log_requested (additive); next report
ships controller_log_tail (128KB, consume-once, app-tail wire byte-compatible).
Debug page: Vezérlő|Ügynök tabs; agent tab proxies agent /debug/logs with the
pre-0.83 notice on typed 404. Sweep: netstorage_job phases, netprobe, handler
validation refusals + orphan WARN, SupportsWithSource gate line, agentapi
per-call DEBUG, migrate phase lines, tier2/offbox unswallowed persists.
Red-proofs: filter-disabled, drain-removed, dropped-phase-line all FAIL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-11 16:45:57 +02:00
admin b6842a4bc3 v0.115.0: version-aware Supports (agent header channel) + DSM-validated NFS guidance — MinAgent: 0.81.0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-11 15:03:41 +02:00
admin 53ab971fe4 feat: debug-log the capability-gate verdict per add + SupportState.String()
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-11 13:59:23 +02:00
admin 4a5c40b7cb test: fix TestBackupTier2Restore_DoubleClickRefused scheduling flake
IsRunning flips before the restore goroutine parks in StopStack; under parallel
load the ==1 assertion raced it and read 0. Wait for the first stop to land, then
assert — the red-proof property (no-fast-path mutant → count 2) is preserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-11 13:50:35 +02:00
admin 50da7329ac feat(ui): agent-outdated banner on the NAS settings page (capability gate, Part 3)
- networkStoragePageData threads NetAddSupport (yes/no/unknown) via the short-budget
  netAddSupport probe (2 s ctx + cache — a down agent cannot stall the page)
- storage_network.html: support=no replaces the add form with the Hungarian
  alert-warning banner; share list + remove render in every state; yes/unknown
  render the form unchanged; load-time JS guarded for the formless render
- T5 render test (banner/no-form on 'no', form on yes/unknown); red-proof RP5 run
  (conditional dropped → banner assertions fail) and reverted
- template_id_gate + emoji_gate green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-11 13:49:29 +02:00
admin d347dc48d2 feat: agent-capability gate for coupled features — typed StatusError + Supports probe/cache + netstorage add gate (option-1)
- agentapi: non-2xx GETs now surface as typed *StatusError (same text); features.go
  adds Feature/SupportState/SupportCache (route probe, TTL 5m, Yes/No cached,
  Unknown never cached or refused) + Client.Supports
- web: handleNetStorageAdd refuses up front (412, code agent_outdated, Hungarian
  message) when the agent predates /netstorage/verify-status (= pre-0.81 add
  semantics); gate runs BEFORE the single-flight claim; SupportUnknown passes
  through to the existing agent-error paths
- netAddSupport page-render helper lands here; its template consumer follows
- tests: T1 gate refusal (job never starts, slot free), T2 unchanged happy path +
  warm-cache negative assertion, T3 indeterminate never 'too old', T4
  classification incl. the string-match trap, T6 TTL, wire-level 404-typing;
  red-proofs RP1-RP4 run and reverted

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-11 13:45:49 +02:00
admin a65dcff85a storage_network: full redesign on the canonical form pattern + protocol-honest guidance (v0.113.0 UI)
Kills the details/summary-button hack and the nonexistent form-row/form-input
classes (the unstyled-look root cause); rebuilds on the storage_attach pattern
(settings-card / form-group / form-control / form-actions / alert-*). SMB listed
first (consumer reality), NFS two-recipe guidance (map-all-users simple recipe +
full-fidelity anonuid=<uid+100000> with a live computed host-id), §3.2 staged
poll progress (Kapcsolódás/Csatolási teszt/Írásteszt/Regisztrálás), categorized
Hungarian errors with a collapsible raw-detail block, orphan rows with an Árva
badge + remove-only action. C8 render smoke guards the class regression.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-11 10:05:08 +02:00
admin bb8737a81f netstorage: verify-before-commit orchestration — agentapi verify fields, uid-1000 re-exec probe, detached add job, orphan rows
Controller half of the verify pipeline (SPIKE-nas-verify b57f6c1): AddNetStorage
gains verify/job fields + typed NetAddRefusedError; NetVerifyStatus polls the
agent slot; --netprobe hidden re-exec mode (SysProcAttr.Credential uid/gid 1000,
no shell) proves in-guest writability; the add handler starts a detached
single-flight job (agent_add → verifying → probing → registering LAST) with full
rollback on any failure incl. verify-lost-after-restart (Scenario F); §3.2
Hungarian error map server-side; live-but-unregistered shares surface as remove-
only 'Árva megosztás' rows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-11 09:59:33 +02:00
admin b25ca60ab7 v0.112.0: self-update without credentials — anonymous Docker v2 token flow
Root cause (live on Peti's box): the updater piggybacked on Git Sync creds and refused when absent,
but the registry serves the public package anonymously (verified 2026-07-10). Credentials become what
they were meant to be — optional, for private catalogs only.

- queryRegistry: both creds empty → anonymous flow (plain GET → parse WWW-Authenticate realm/service
  from the header, never hardcoded → credential-free token → Bearer retry); creds present → BasicAuth
  path unchanged; half-configured pair → loud incomplete-credentials error
- pullImage: no creds → skip docker login entirely (docker's native anonymous flow); denied anonymous
  access → clear 'registry denied anonymous access — a private registry requires Git Sync credentials'
- settings page: 'Registry: nyilvános (hitelesítés nélkül) / hitelesített' mode line — credential-less
  is no longer an error state; DryRun.PullCapable counts anonymous as capable
- tests: fake registry httptest token dance (zero creds, no auth on token request, correct scope),
  creds path unchanged (BasicAuth, no dance), both denial paths, WWW-Authenticate parser table
  (quoted/bare/order/comma-in-quotes/missing-realm), fake-runner pull tests (no login invoked
  anonymously; login/pull/logout order + stdin token with creds; partial creds refuse)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-10 17:52:03 +02:00
admin 8917014991 v0.109.0: soft-quota gate + usage bar + offsite report status (SLICE 4)
QuotaGB rides the descriptor into OffboxTarget; RepoSizeBytes persisted
from restic stats. Pre-run gate: >=100% refuses NEW backups (Hungarian
notice + operator alert) but prune STILL runs (red-proofed) and restore is
never gated; >=80% warns. /backups usage bar (quota>0 only). The hub
report gains the non-secret offsite status object for the OffsiteChecker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-09 23:48:42 +02:00
admin febf6757dc v0.108.0: hub-verified escrow auto-confirm on current-password hash match (SLICE 3)
EscrowAutoConfirmer flips pending->escrowed ONLY when sha256(local repo
password) matches the ACK's restic_pw_sha256 (blob-presence alone never
confirms — red-proofed). Mismatch warns once per hash naming the ceremony;
never un-confirms; wipes the staged secret on flip. Pinned cross-repo hash
vector; manual confirm deprecated to a legacy-blob fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-09 23:19:17 +02:00
admin a38c743926 v0.107.0: key-auth-first bridge + staged-secret wipe on escrow confirm
Key-auth-first: a KeyAuthProber seam lets the bridge skip consume+install
when the already-installed key still authenticates (pinned to the freshly
verified host key) — descriptor changes on provisioned guests no longer
loop on consume-404. Fingerprint verify still precedes everything.

Wipe-on-escrowed: confirm-escrow now calls the agent's new
DELETE /escrow/stage-secret (v0.78.0) best-effort, closing the hygiene gap
where a ceremony-less confirm left the staged password file behind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-09 22:26:53 +02:00
admin 0b09a799cb v0.105.0: fork-4 offsite password custody — hand-off + atomicity gate + DR inject + coord
Pairs with agent v0.77.0. StageEscrowSecret pushes the repo password to the
agent (POST /escrow/stage-secret) at offsite-enable → EscrowState="pending".
Atomicity gate: RunOffboxBackup (scheduler + handler) refuses until
EscrowState="escrowed" (operator POST /backup/offbox/confirm-escrow after the
escrow ceremony) — no un-recoverable offsite ciphertext can exist. DR:
POST /backup/offbox/inject-password pre-places a recovered 64-hex password 0600
(honored by WriteOffboxSecrets' IsNotExist guard; refuses clobber without
force). DR recipe gains non-secret offsite_restic coords (DRResticCoord); SFTP
key regenerated at DR, not escrowed. New settings.OffboxTarget.EscrowState.
Tests + atomicity & inject companion red-proofs green; UI gates pass. NOT yet
live-validated (supervised ceremony).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-09 15:13:59 +02:00
admin 908e4b906a v0.104.0: off-box unit discovery (durable, deployment-independent) + no-silent-success
offbox located each toggled app's recovery unit via AppNamespaceRoot→GetAppDrivePath,
which reads the app's LIVE app.yaml HDD_PATH and silently falls back to systemDataPath
when the app isn't deployed → looked on the wrong drive, backed up nothing, reported
ok/0 (DIAG root cause). Now:

- discoverOffboxUnit/offboxCandidateNSRoots scan the durable storage registry
  (schedulable non-decommissioned paths ∪ systemDataPath) for backups/primary/<app>,
  independent of deploy state; newest-by-manifest-CreatedAt wins on drive churn.
- RunOffboxBackup: runOffboxInternal returns (backedUp, missing, err); 0-of-N toggled →
  hard error + operator alert; partial → ok + new OffboxTarget.LastWarning (shown on
  /backups, preserved across config edit).
- AppNamespaceRoot + primary WRITE paths unchanged.
- 6 non-hollow tests (A-E + edge) + both companion red-proofs run (reverted).
- NOT yet live-validated against the Storage Box (spike creds torn down).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-09 12:32:05 +02:00
admin c529a455af feat(backup): async restore family — no proxy-timeout error page on a succeeding restore (v0.102.0)
Re-adjudicates F4: /backup/restore, /backup/tier2/restore, /backup/offbox/restore
blocked the HTTP request until completion, so through cloudflared's 100s cap a
customer got an error page while the restore succeeded (offbox worse — bounded
on r.Context(), canceling the SFTP restore mid-flight). Convert all three to the
offboxRun async shape: fast-path IsRunning refuse, background goroutine
(offbox ctx off r.Context() -> Background+30m), instant redirect. Add mutex-
guarded op-status (opstatus.go) + GET /api/backup/restore-status + a 3s-polling
backups.html banner (neutral running, red on failure). Restore single-flight
unchanged. Tests + red-proof (sync handler blocks indefinitely vs <500ms async).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-06 20:23:49 +02:00
admin 27aeb415f4 feat(web): C2 Parts 2+3 — POST /backup/tier2/restore + "Fájlok visszaállítása" button
- Endpoint next to /backup/restore; handler mirrors backupRestoreHandler
  (ParseForm → validStackName → backupMgr guard → WARN with RemoteAddr →
  RestoreTier2Files → flash). Flash strings: "<stack>: N fájl visszaállítva a
  másodlagos másolatból." / "Nincs hiányzó fájl — minden fájl megvan a helyén."
  / "Fájl-visszaállítás sikertelen: <err>" (refusals carry the Hungarian
  reasons from the engine).
- backups.html: the button on the healthy Tier-2 layer row only (the
  Tier2Configured branch already excludes disconnected/inactive; additionally
  gated on Tier2LastRun), inline POST form with CSRF + confirm dialog naming
  the additive-only semantics and the last-copy timestamp. Template gates
  (id + emoji) green.
- Handler guard test (C6): traversal/empty → exact Hungarian flash, no work
  started (nil backupMgr would panic if reached).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 13:20:56 +02:00
admin d0c70a7da6 ux(storage): hide 'Eltávolítás a listából' on wizard-enrolled drives
List-removal only deletes the registry entry — it's the undo of a
manual path add. On an enrolled drive (/mnt/felhom-drives/) it leaves a
de-registered-but-still-bound limbo nobody wants; the real lifecycle
there is Biztonságos leválasztás / Végleges leszerelés. New
StoragePathView.IsEnrolled gates the button; manual paths keep it; the
decommissioned-branch removal (final cleanup) is unchanged. Endpoint
untouched.
2026-07-02 21:29:14 +02:00
admin d256166fdd ux(storage): dedupe Leválasztás + self-documenting drive action labels
Two Leválasztás buttons rendered per USB drive (registry safe-disconnect
+ the agent eject the D1 merge carried over like-for-like) and four
near-synonymous labels covered very different operations. The agent
eject now hides when the card offers the registry safe-disconnect (one
detach per card; non-USB + unregistered drives keep eject). Labels are
self-documenting with explanatory title tooltips: Új telepítések
letiltása/engedélyezése, Biztonságos leválasztás, Eltávolítás a
listából, Végleges leszerelés, Formázás…. Endpoints and semantics
unchanged.
2026-07-02 20:36:48 +02:00
admin 611c5ffbdf fix(storage): spacing between the enrichment tag row and action rows
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.
2026-07-02 20:25:49 +02:00
admin d3c97c62fe storage IA: split /storage into Meghajtók + /storage/network (NAS)
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.
2026-07-02 19:34:27 +02:00
admin 622d9328f8 D1 Part 3 leftovers: remove 8 residual emoji + Python emoji gate
- The D0 grep gate false-negatived multibyte emoji on Windows (its zero
  was wrong). scripts/emoji_gate.py scans by Unicode codepoint; it found
  8 survivors: backups.html (📁→file-text icon, 🔄 restore-info text),
  debug.html (🔄→'fut', 🔒→'titkosított'), deploy.html (📦→upload icon,
  ★ default-marker → '(alapértelmezett)'), storage.html (📦→upload icon,
  📦 in the migrate-target option → plain text). All → sprite icons or
  plain words.
- Gates: scripts/emoji_gate.py = 0; new Go TestNoEmojiInTemplates
  codepoint scan keeps it enforced (allowlist: ✓✗✔✘•●○■▶ monochrome
  text marks). go build/vet/test ./... green (18 pkgs).
2026-07-02 19:19:23 +02:00
admin cb6f04c8fc D1 Part 3: unified drive view + overlay migration
- 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.
2026-07-02 19:17:19 +02:00
admin f8e18a9ec9 D1 Part 2: settings.html split into four pages + sidebar restructure
- 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).
2026-07-02 19:07:32 +02:00
admin d50a919404 D1 Part 1: settings-split routes + per-page data builders
- server.go: GET /storage (Tárhely page), GET /settings/notifications
  (GET->page, POST->save dispatch on the same path), GET
  /settings/security; the enrollment wizards move to /storage/init +
  /storage/attach with 301s from the old /settings/storage/* URLs.
- handlers.go: settingsData() decomposed into settingsBaseData +
  systemPageData / storagePageData / notificationsPageData /
  securityPageData; the legacy merge remains only while the monolithic
  settings.html exists (Part 2 deletes it). All five storage action
  redirects (add/remove/default/schedulable/label) now land on
  /storage?storage_msg=... (incl. the two error-branch redirects).
- Every page keeps rendering the full legacy template in this commit —
  the site stays functional; the split lands in Part 2.
- Tests: four pages 200, wizard 301s + new URLs render, storage-label
  redirect Location prefix + flash renders on /storage, wrong-password
  inline re-render. Red-proven vs pre-split code (Location was
  /settings?..., no 301s).
2026-07-02 19:00:29 +02:00
admin 4906524c11 fix(backups): off-box LastRun is an RFC3339 string — 500'd the backups page
Found during the D0 click-through: OffboxTarget.LastRun persists as an
RFC3339 string (settings.json), but backups.html passed it to timeAgo
(expects time.Time), so GET /backups returned 500 on any node where an
off-box backup had ever run. Pre-existing since v0.93.0 (NAS Part B),
unrelated to the re-skin. New timeAgoStr funcmap helper parses the
string (falls back to the raw value); also v2-token fix for the offbox
error hint (var(--danger) fallback hex) and the dead stat-running/
stat-stopped classes on the offbox status card.
2026-07-02 14:47:22 +02:00
admin 7df061c00f D0 Part 4: settings visual pass + remaining pages; grep gate zero
- 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.
2026-07-02 14:38:51 +02:00
admin f100cef842 D0 Part 3: deploy + backups + monitoring in v2
- deploy.html: stacked memory bar -> meter track with two segments
  (committed solid blue, new app 35%-opacity blue), neutral threshold
  ticks; integration status badges -> tags; meta pills -> metarows;
  3-step deploy progress icons -> sprite (check/x/triangle-alert/
  spinning rotate-cw); emoji -> icons or plain text incl. the JS
  confirm string.
- backups.html: storage bars -> meters, disconnected drives render an
  empty track with a neutral 'Leválasztva' (no red), drive-state badges
  -> warn tags, checkmark/warning glyphs -> sprite icons, JS flash/info
  strings de-emojified.
- monitoring.html: bars -> meters in both template and JS-generated
  host-storage markup (usageColorClass now emits nominal/warn/crit);
  Chart.js palette per spec (cpu #2EA8F5, memory #8E7CE8, temp #E0A93E,
  load #5EC4B6 — categorical data series keep distinct hues); tooltip/
  grid/tick colors moved to v2 literals; mem-dist palette rebalanced.
- Templates repo-wide: legacy var(--*) names in inline styles and JS
  string literals renamed to v2 tokens (old names no longer resolve).
- style.css: memory-bar family rewritten for the two-segment meter,
  .ico-spin animation added (reduced-motion respected).
2026-07-02 14:32:00 +02:00
admin 5dc277f2b1 D0 Part 2: design system v2 — tokens, meter/tag/metarow, funcmap remap, dashboard + stacks
- style.css: navy token palette (--bg-0/1/2, --line, --text-1/2/3, --blue,
  --warn, --crit), single 2px radius, all box-shadows and the bg grid
  overlay removed, fonts via --font-ui/--font-data.
- Components: .meter (3px hairline track, blue nominal fill, neutral
  70/85 ticks, warn/crit flag), .tag (square 2px state chip + dot, pulse
  on progress, reduced-motion respected), .metarow (icon + text, no
  container), .panel/.list/.section-h primitives, boxless .stats with
  hairline dividers, buttons 2px (danger = crit outline).
- funcmap: stateColor -> run/progress/warn/neutral/off (stopped is
  neutral, NOT red — operator-approved exception-color change),
  usageColor/tempColor -> nominal/warn/crit (thresholds unchanged);
  stateLabel Hungarian copy untouched (guarded by test).
- layout.html: sprite nav icons (layout-grid/cloud/shield/cpu/wrench/
  settings), alert banner emoji -> triangle-alert/info icons.
- dashboard.html: meters with disk warn/crit flags (Fogyóban a hely /
  Kritikusan kevés hely), boxless stats (Leállítva 0 muted, >0 amber),
  single-panel stack list with 2px state edges, tags instead of badges,
  icon action buttons.
- stacks.html: state tag + metarow rows; catalog keeps its grid.
- setup minimalCSS retokened to v2 (drops GitHub-dark hexes).
- Tests: §8 truth tables for stateColor/usageColor/tempColor +
  stateLabel byte-identity guard (red-proven vs pre-change funcmap:
  stopped->red and 0->green failed as required).
2026-07-02 14:22:27 +02:00
admin b073cc474d D0 Part 1: vendored fonts + Lucide sprite + setup CSS fix
- 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).
2026-07-02 14:05:41 +02:00
admin bea05ea600 Impl-2b: resolve a raw candidate's fs-UUID via /disks/candidates (enroll fix)
runStorageInit/runStorageAttach resolved the fs UUID only via agent.Disks(),
which does NOT include a raw (unenrolled, non-PVE-storage) device — so a raw
candidate could be offered but never enrolled ("no fs identifier"). New
resolveEnrollUUID falls back to the raw-device scan (/disks/candidates), which
reports each free disk's durable_id (uuid:<fs-uuid>). Both enroll paths use it;
legacy re-attach (drive in /disks) still works. Test + red-proof.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:19:15 +02:00
admin feab92ccfc v0.95.0: enrollment wizards use the raw-device scan /disks/candidates (Impl-2b)
Both wizards now source candidates from the agent's Impl-2a raw-device scan
(GET /disks/candidates, proxied) instead of the Observe-based /api/disks — so a
brand-new non-PVE-storage drive is finally discoverable + enrollable end-to-end.
agentapi.ListCandidates + a passthrough proxy (no controller-side filtering; the
agent's unclaimed filter is authoritative). storage_init renders `initialize`,
storage_attach renders `attach`; the enroll flow + Impl-1 guarded mkfs unchanged.
Tests + go build/vet/test clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:04:49 +02:00
admin 5e0625410a security(offbox): validate host/user/repo before the ssh exec (option-injection guard)
Background commit review flagged command/option injection: operator-provided host/user/
repo_path flow into restic's ssh -s sftp command. Reject leading '-' (ssh option
injection, e.g. -oProxyCommand) + metacharacters/traversal; OffboxConfigured fails closed
on an invalid target. Companion test covers the injection cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
2026-06-30 15:39:22 +02:00
admin 2a7deadc93 controller v0.93.0: NAS Part B off-box backup target (restic-over-SFTP)
Encrypted restic repo over SFTP for the app-data tier (the off-site 3-2-1 leg). A dead
NAS fails fast via -oConnectTimeout (spike Q8), never hangs the runner; secrets are 0600
files (ride DR via PBS whole-CT); init-if-absent, retention forget --prune, restore,
single-flight, per-app toggle + UI. restic re-added to the image.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
2026-06-30 15:26:38 +02:00