Commit Graph

553 Commits

Author SHA1 Message Date
admin 68e7e07838 v0.78.0: storage register uses the STABLE intermediary path, not the raw path
handleStorageRegister (the "Regisztrálás" action for an already-mounted,
unregistered drive) registered the raw /mnt/<name> path verbatim, unlike
runStorageInit/runStorageAttach which map to the stable /mnt/felhom-drives/<name>
path the agent actually binds the drive at. The controller then watched an empty
placeholder dir on the guest rootfs → "Rendszermeghajtón" + stuck "activation
pending" banner after a re-provision.

Fix: register stablePathForName(path.Base(req.Where)); attachIntoGuest still uses
the raw path (the agent operates on raw). Test + red-proof added.

Diagnosis: felhom.eu/documentation/audits/DIAGNOSE-drive-bind-after-reprovision-2026-06-23.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW
2026-06-23 17:28:07 +02:00
admin 66d84627b8 docs: v0.77.0 open_path — REPORT + README 2026-06-23 12:05:29 +02:00
admin 904e787dda v0.77.0: per-app open_path for the Megnyitás link
Optional .felhom.yml open_path appended to the open-app URL in dashboard/deploy/app-info
(via .Meta.OpenPath). Empty=bare root. Fixes apps whose UI isn't at / (gokapi /admin, ghost /ghost/).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 12:01:12 +02:00
admin b0dd13154b v0.76.0: campaign-#3 hardening (settings .bak recovery, restore stack_name validation, quiesce marker quarantine)
S1: corrupt settings.json recovers from .bak / safe-defaults+preserve, no crash-loop.
F2: validStackName gates restore + export handlers (reject /,\,..,NUL traversal).
S3: corrupt quiesce marker logged + quarantined, not silently dropped.
Tests T-S1/F2/S3 + red-proofs. Agent/hub untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 23:34:33 +02:00
admin 0b2605c5a8 v0.75.0: gate userdata MkdirAll on a live mountpoint (no writes into an absent drive)
Belt (ensureUserdataMounts) + FileBrowser sync skip ensure/mount when an external
drive root is not a live mountpoint -> no 'mkdir userdata: permission denied' + no
rootfs-shadow during a drive-absent window. System/local path never gated. Reuses
system.IsMountPoint; matches planDriveGates external-only rule. T1-T4 + red-proofs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 20:47:08 +02:00
admin c208ddf308 docs: v0.74.0 REPORT/CONTEXT/README — agent connection-leak fix + live proof
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 17:52:12 +02:00
admin 2a5b88fbf9 v0.74.0: fix controller->agent connection leak (reuse one agentapi client)
agentClient() built a new agentapi.Client (new bare http.Transport, IdleConnTimeout:0)
per call and discarded it without closing idle conns -> one leaked idle ESTABLISHED
socket per call to the agent :8443, exhausting the ephemeral port range after ~5 days
(EADDRNOTAVAIL). Memoize one shared client via sync.Once; harden Transport
(MaxIdleConns/PerHost + IdleConnTimeout 90s). Agent/firewall untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 17:19:24 +02:00
admin f3146ac7bf controller v0.73.0: DR recipe — emit secret-free customer+apps half in hub report
DR recipe slice (controller half), grounded in SPIKE-dr-recipe-2026-06-16. The
controller emitter is the BOUNDARY enforcement point: v1 ships an explicit
allowlist {catalog_ref, enabled, storage_bindings} and reads NOTHING from
AppConfig.Env, so no ENC:/token/password can leak. storage_bindings parsed from
the compose (${HDD_PATH}/${USERDATA_PATH} volume binds -> {container_path,
drive, subpath}).

Load-bearing tests: TestBuildAppRecipe_NoSecrets (synthetic-secret app -> none
leak) + TestBuildAppRecipe_AllowlistIsLoadBearing (red-proof companion) +
TestAppStorageBindings + TestBuildDRRecipeAppHalf. Red-proofed live: forcing the
emitter to dump Env makes the boundary test fail. recipe_version=1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:38:59 +02:00
admin 0d159d7e34 docs: REPORT/CHANGELOG/README — v0.72.0 FileBrowser boot-recreate convergence + live x2 reboot acceptance
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:09:58 +02:00
admin 6ea25388d7 controller v0.72.0: FileBrowser converges on boot-recreate
processGuestBootChange recreated the drive-backed app stacks but never re-synced
FileBrowser (base-infra, no HDD_PATH), so its drive mounts went stale after a
reboot. Now, AFTER pollLiveBinds confirms the live binds and the apps are
recreated, trigger go s.SyncFileBrowserMounts() so FileBrowser converges against
the now-live drives. Refactored into pure recreateDriveBackedApps(stacks, present,
recreate, syncFB). Tests: FB sync runs once after recreate (red-proofed companion);
runs even when nothing recreated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 17:37:12 +02:00
admin e2de234325 controller v0.71.0: fix guest-reboot recovery (boot-race + agent-path blocker)
Live diagnosis of drive-backed apps stuck Exited after a pct reboot pinned THREE
sub-causes, fixed together (hardening the existing processGuestBootChange, not a
parallel mechanism):

1. Agent-path blocker (live root cause): agentClient() returned "agent not
   configured" (cfg.LocalAPI.Endpoint empty), so processGuestBootChange AND the
   whole drive gate bailed at the first guard. bootstrap.json had a complete
   local_api block, but MaybeIngest returned immediately on "already configured"
   so a controller.yaml seeded before local_api existed never got the agent path.
   Fix: MaybeIngest now calls ensureLocalAPI on the already-configured path,
   merging local_api from bootstrap.json into the existing controller.yaml when
   missing (no hub re-pull, config preserved; idempotent + fail-safe).

2. Boot-race readiness gate: processGuestBootChange sampled BoundUnderParent once
   during fast startup, racing the ~18s rebind, recreated nothing, burned its
   boot-id one-shot. Fix: gate on the REAL live in-guest bind -- driveBindLive
   checks /mnt/felhom-drives/<drive> is a mountpoint in the controller's own /mnt
   rslave /proc/self/mountinfo; pollLiveBinds waits for it (bounded ~120s) before
   recreating via the normal pipeline. shouldRecreateOnBoot stays state-independent
   so stuck-Exited create-time-failure apps are included.

3. Single-shot fragility: processGuestBootChange ran only once at startup; a
   briefly-unreachable agent right after a guest reboot stranded recovery. Fix:
   driveGateLoop runs it every periodic tick too (idempotent, boot-id gated).

Tests (non-hollow, pre-fix companions, red-proofed): pollLiveBinds waits then
reports live / never-live stays absent / single early sample misses; ensureLocalAPI
merges local_api into a configured controller.yaml that lacks it / no-ops when
present. Live-accepted with repeated pct reboot 9201.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:12:49 +02:00
admin 25e5cb5850 controller v0.71.0: fix guest-reboot boot-race stranding drive-backed apps
Sub-cause: on guest pct reboot, in-guest dockerd auto-starts unless-stopped
drive-backed apps ~18s BEFORE the agent re-binds the drive; the create-time
volume bind fails (mkdir /mnt/felhom-drives/<drive>/userdata: permission denied)
and RestartCount=0 means it's never retried -> stuck Exited. The existing
recovery (processGuestBootChange) RAN but raced the rebind: it sampled the
agent's BoundUnderParent once during fast startup (not live yet), recreated
nothing, and persisted the new boot-id -> burned its one-shot. The periodic gate
never recovered them either (first observation after the rebind -> no transition).

Fix (harden the existing mechanism, no parallel one): processGuestBootChange now
gates on the REAL live in-guest bind. driveBindLive checks whether
/mnt/felhom-drives/<drive> is an actual mountpoint in the controller's own /mnt
(rslave) /proc/self/mountinfo -- true only once the agent's bind propagated,
exactly when docker can recreate the app. pollLiveBinds waits for that (bounded
~120s, poll 2s; rebind lands ~18s) and only then recreates via the normal
pipeline, including stuck-Exited create-time-failure apps (shouldRecreateOnBoot
is state-independent). Single-flight; absent-after-window drives left to the gate;
host-reboot path unaffected; guest-only reboot path now covered.

Tests: pollLiveBinds waits through the rebind then reports live (recreate fires);
never-live drive stays absent; pre-fix companion (single early sample misses the
not-yet-live bind). Red-proofed against a no-wait single-sample.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 15:46:51 +02:00
admin 9b7a26585c fix(settings): render restart button unconditionally
The Vezérlő újraindítása card was accidentally placed inside the
{{if .RetrievalPassword}} block, so it was hidden whenever no retrieval
password is set (e.g. the demo). Moved it outside so the self-serve restart
is always available. Found during live verification of v0.70.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:13:18 +02:00
admin c514c0042b docs: CHANGELOG + README for v0.70.0 (config-apply self-restart, restart button, geo report push, always-report geo, autocomplete fix)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:03:08 +02:00
admin 09d75c1e2e controller: fix country autocomplete — reveal dropdown with display:block (C)
Root cause (diagnosed live, not the hypothesised JS throw): filterCountries runs
fine and correctly populates the list, but reveals it with `list.style.display=''`.
The .geo-country-list CSS default is `display:none` (style.css), so clearing the
inline style falls back to none and the populated list never shows — no console
error, just an invisible dropdown. Latent since the geo feature's first commit.
Fix: reveal with 'block'. Verified live (typing "Német" now lists Németország).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:01:11 +02:00
admin 02a4ba0491 controller: immediate hub report push on geo change + always-report geo (B)
- Geo settings save and manual geo sync now fire an out-of-band, non-blocking hub
  report push (Router.reportPushNow seam, wired in main.go to BuildReport+Push in a
  goroutine) so the hub reflects the new geo state / clears a stale last_sync_error
  within seconds instead of after the next ~15-min cycle. Scope: geo handlers only.
- builder.go always populates report.GeoRestriction (Enabled=false, empty countries
  when nil/disabled) via new buildGeoRestrictionReport helper, so the hub always
  renders the geo section ("Inaktív" when off) instead of hiding it via omitempty.
- Tests: geo save success → push once; invalid country → no push (companion);
  buildGeoRestrictionReport(nil) → non-nil disabled (companion vs old nil-omit).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:00:32 +02:00
admin ba87412508 controller: config-apply self-restart + manual restart button (A)
POST /api/config/apply now takes effect via a graceful SELF-RESTART instead of
logging "restart needed" and leaving stale in-process singletons (the CF client
is built once at startup, so a rotated Cloudflare token never applied until a
manual LXC restart). Container is restart:unless-stopped, so a clean os.Exit(0)
auto-restarts with fresh config.

- New gracefulSelfRestart helper behind an injectable Restarter seam (Router.restart
  + SetRestarter) so the exit is unit-testable.
- configApply: no-op guard (byte-identical re-push → no write, no restart), else
  write → 200 (flushed) → restart. Removed stale "restart needed" wording.
- Removed the dead OnConfigApplied hook (Phase-1-retired infra-backup push; the
  self-restart reloads everything and a fresh report is pushed on startup).
- New POST /api/selfrestart (auth+CSRF via /api/ mount) + "Vezérlő újraindítása"
  settings button: confirm → POST → poll GET / every 2s → reload.
- Tests: changed→restart once; identical→not called (companion); invalid→not called;
  selfrestart→restart once.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 12:56:51 +02:00
admin 9f59bc2146 controller v0.69.0: remove dead infra-backup stubs + unused restic-password report field
Controller half of Phase-1 Infra Backup retirement (hub v0.12.0;
SPIKE-infra-backup-2026-06-15). Pure dead-code removal, no behaviour change.

- Remove Pusher.PushInfraBackup (caller-less; hub endpoint gone).
- Remove Notifier.NotifyBackupCompleted / backup_completed event (caller-less
  since slice 8C; hub deadline check now reads agent host-report PBS snapshots).
- Remove report.BackupReport.ResticPassword — builder never sets it post-8C
  (confirmed in source + live), but it historically leaked the restic password
  into the hub's plaintext reports store.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 11:11:56 +02:00
admin a821a9d7d0 fix: Beállítások endless-refresh loop after a migration (v0.68.3)
The migration journal keeps returning the last completed job; the resume-view
watched any job and migWatch's done-branch reloads the page -> endless reload
loop after any migration. Resume-view now watches only in-progress jobs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 21:43:00 +02:00
admin 37ed7570ca fix: stack-card state-badge clipping on unhealthy apps (v0.68.2)
.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>
2026-06-15 21:24:33 +02:00
admin 740db8db4d docs: REPORT + README — storage lifecycle + acceptance sweep (controller half)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 20:42:05 +02:00
admin 7a85732fc1 controller v0.68.1: boot-id recreate ALL deployed drive-backed apps (state-independent)
E1 caught it: filtering on State!=stopped missed apps docker hadn't auto-restarted
yet at the one-shot instant (5 apps exited after host reboot). Now recreates every
deployed present drive-backed app regardless of state (deployed=should run).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:42:21 +02:00
admin 42f69dadda controller v0.68.0: storage lifecycle on intermediary model (H2/H3/M1/M3 + boot-id)
H2 decommission UI button (migrate / anyway); H3 one-click re-enroll of a
decommissioned drive; M1 default reassignment (auto-promote + block-if-none);
M3 migrate re-asserts 2775 setgid on userdata dirs; deterministic guest-reboot
recreate via agent boot_id (replaces the timed sample). Fixes the {path}/{where}
H1 JS bug. Non-hollow tests + companions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:26:02 +02:00
admin bc41acf4da docs: REPORT + README — intermediary-mount model (controller half)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 18:01:51 +02:00
admin 783c79016a controller v0.67.5: startup recreate waits for stack scan
recreateBootStaleApps ran before the stack manager finished scanning (GetStacks
empty) so it found no apps; add a bounded wait for stacks before the one-time
boot-stale recreate. Deterministic guest-reboot convergence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 17:51:26 +02:00
admin be95a16246 controller v0.67.4: startup recreate also recovers exited/restarting drive-backed apps
recreateBootStaleApps recreates a present drive-backed app when boot-stale OR
exited/restarting/unhealthy (the recency-only gate missed already-exited apps).
Still skips healthy long-running + cleanly user-stopped apps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 17:46:08 +02:00
admin 91a6dcfa75 controller v0.67.3: startup recreate of boot-stale drive-backed apps
Completes guest-reboot convergence. driveGateLoop runs recreateBootStaleApps once
at startup: deployed drive-backed apps whose drive is present (BoundUnderParent)
and whose containers started recently (fresh guest boot, not a controller-only
restart) are recreated (down+up) onto the re-propagated drive. Paired with agent
v0.35.0's drive re-propagation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 17:40:19 +02:00
admin 3adfa41a09 controller v0.67.2: gate keys present on BoundUnderParent (reboot convergence)
The drive-absent gate treats a stable path usable only when bound under the parent
(BoundUnderParent), not merely host-mounted. Makes a host reboot converge: apps
stay gated until the agent binds the drive under the parent, then are restarted
(recreated) on the populated path. Test updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 17:10:02 +02:00
admin 38294d4eb5 controller v0.67.1: gate only acts on external drives under /mnt/felhom-drives/
Fix caught live: planDriveGates falsely marked the internal SSD path
/mnt/sys_drive/felhom-data disconnected (agent never reports it), which would
block starting SSD-resident apps. Gate now skips non-/mnt/felhom-drives/ paths.
Regression case added. No apps were stopped (none depended on the SSD path).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 16:54:33 +02:00
admin 55c896624f controller v0.67.0: intermediary-mount — HDD_PATH repoint + drive-absent gate + H1 routes
Drives are visible in-guest only at the STABLE /mnt/felhom-drives/<name>; the
registered path + HDD_PATH + FileBrowser source repoint there while agent calls
map back to raw /mnt/<name> (agentWhere). Enroll binds-under-parent before
register. Drive-absent GATE (planDriveGates + 30s driveGateLoop) stops/blocks
apps when a drive vanishes and auto-restarts on return; start-gate refuses start
when the drive is absent. H1 endpoints (disconnect/reconnect/restart-apps) routed
onto host-side ops. Non-hollow tests + companions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 16:45:13 +02:00
admin 1201dcbab7 docs: README FileBrowser umask + REPORT for userdata-arc close-out (v0.66.2)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 08:29:02 +02:00
admin 98d5504097 v0.66.2: FileBrowser umask 002 via entrypoint wrapper
gtstef/filebrowser is a single Go binary that ignores a UMASK env (verified live:
-e UMASK=002 leaves PID1 0022), so RenderFileBrowserCompose wraps the entrypoint
sh -c 'umask 002; exec /home/filebrowser/filebrowser'. Customer-created folders now
come out 2775 (group-writable) so group-1000 apps can write into them. Test asserts
the wrapper is rendered.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 08:00:47 +02:00
admin d0718e8d0e docs: README userdata convention + REPORT for v0.66.x (userdata layout)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 22:31:30 +02:00
admin 3b7d08979c v0.66.1: inject USERDATA_PATH on first deploy too (shared withUserdataPath)
DeployStack's initial compose-up builds env from deploy values (not stackEnv), so
v0.66.0 missed USERDATA_PATH on first deploy → ${USERDATA_PATH} resolved to '' and
Docker bound a root-owned dir at the container root (found live: radarr /media/movies
was 0:0 755). Shared withUserdataPath injector now used by stackEnv AND
composeExecWithEnv. Regression test included.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 22:23:36 +02:00
admin c48f95fe06 v0.66.0: userdata layout + shared-storage ownership convention
appbackup/userdata.go: EnsureUserdataDir (MkdirAll + explicit setgid Chmod 2775 +
chown gid 1000), UserdataSkeleton, EnsureUserdataSkeleton; linux chown/StatGID +
non-linux stubs. stackEnv injects USERDATA_PATH=<HDD_PATH>/userdata. Skeleton
pre-created on register + FileBrowser sync; deploy belt (composeExecCustomEnv on
'up') pre-creates every ${USERDATA_PATH} bind source. FileBrowser mounts userdata
(was appdata) — uid 1000 can now write into 2775 setgid. #8: migrate merge walk +
copyFile preserve source setgid+group so the convention survives MigrateAll.
Non-hollow tests incl. Linux setgid assertions + migration-preserve companion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 21:58:49 +02:00
admin cbaa53f565 docs: README migrate/decommission section + REPORT for B1+B2 (v0.65.0)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 20:17:47 +02:00
admin f2596ea433 B2b: decommission orchestration + missing-storage indicator + re-enroll fix (v0.65.0)
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>
2026-06-14 20:08:52 +02:00
admin 16a4c3e878 B1: migrate UI wiring — /api/storage/migrate{,-app,/status} + settings & app pages
ServeStorageAPI gains POST /api/storage/migrate (whole-namespace), POST
/api/storage/migrate-app (single app), GET /api/storage/migrate/status (poll).
settings.html: the greyed migrate-all span becomes a real target-select + button +
shared progress panel; app_info.html gains a per-app 'Áthelyezés másik tárhelyre'
control. Both poll the shared status endpoint and render Hungarian phase progress.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 19:48:22 +02:00
admin b57150e3ab B1: data-migration engine (MigrateAll + MigrateApp) + backup mutual-exclusion
internal/stacks/migrate.go: crash-safe, resumable namespace migration over the
controller's /mnt RW mount. Two entry points (whole-namespace + per-app) share one
journaled pipeline: validate -> stop -> copy (rsync -a --checksum, additive; conflict-
merge walk for non-app content) -> verify -> flip+redeploy (RedeployFromEnv) -> cleanup.
CLEANUP (the only destructive step) is gated on all units verified AND all apps
redeployed. Single-flight; mutual exclusion with the backup orchestrator (Change 3).
Non-hollow tests incl. mutation-proven collision + cleanup-gate companions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 19:42:57 +02:00
admin 2d4d43203f v0.64.0: additive storage discovery (A1) + internal-SSD label (A2)
A1: AutoDiscoverStoragePaths no longer bails on a non-empty registry;
registers only deployed-app paths missing from the registry. Never
mutates/removes existing entries, never re-adds or reactivates a path
present in ANY state (incl. Decommissioned), never flips IsDefault.
A2: InferStorageLabel maps base==felhom-data namespace dir to
'Belső SSD (rendszer)' to disambiguate the internal system volume.
Table-driven tests incl. a companion that fails without the
skip-by-presence guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 17:33:08 +02:00
admin 688ba0d2a5 v0.63.0: surface agent wipe_durable_id (F20-BUG2) + guest_attached (F9) on /api/disks
The agent (v0.31.0) now returns these on /disks, but the controller dropped them when
re-marshalling into its agentapi.DiskInfo mirror. Added both fields (additive) so they reach
/api/disks + the dashboard: wipe_durable_id (gate scheme, for the wipe-confirm) and guest_attached
(drive bound into THIS guest vs merely host-present). Controller behaviour otherwise unchanged.
2026-06-14 15:21:29 +02:00
admin 246213bd11 docs: v0.62.0 CHANGELOG (M18 perf + M19 correctness) + README AppBackup note 2026-06-14 14:18:48 +02:00
admin f8afe5c055 M18: cache DB-dump validation; skip re-validate on unchanged dumps
ListDumpFiles ran ValidateDump (line-by-line scan) for every dump on every ~5-min
RefreshCache cycle — wasted I/O+CPU on large customer dumps. ListDumpFiles now takes
an optional cached(name,size,mod) lookup; on a (size+modtime) match it reuses the
prior result and skips ValidateDump. settings.DBValidationCache gains Size+ModTime;
listAllDumpFiles builds the lookup from the persisted cache and writes back only fresh
validations (cache miss), so an unchanged dump triggers neither a re-validation nor a
settings.json write each cycle. nil cached = legacy validate-always (back-compat).
Tests: cache-hit skips validate (sentinel), cache-miss validates, nil validates.
2026-06-14 14:13:09 +02:00
admin 6bab68b132 M19: deriveStackName cross-references deployed stacks (fix DB-container misattribution)
deriveStackName pure-suffix-stripped on '-' (postgres/db/mariadb/.../cache), so a
stack whose slug ENDS in a role token (e.g. 'my-cache') was misattributed (stripped
to 'my') — filing its DB dump under the wrong/nonexistent stack. Now threads the set
of deployed stack names (m.knownStackNames() <- ListDeployedStacks) into
DiscoverDatabases and cross-references: candidate suffix-strip if known, else the
container name if it IS a known stack, else longest known stack that is a prefix
(handles <stack>_postgres / <stack>-1), else legacy strip. nil/empty known = legacy
behaviour (appexport passes nil). Table test incl. the my-cache case (fails pre-fix).
2026-06-14 14:08:56 +02:00
admin 84979932e7 docs: v0.61.0 CHANGELOG + README (F1 mem/F17 restore/F5 route) + FIXSPEC statuses + REPORT 2026-06-14 10:25:00 +02:00
admin 4989513a96 F1 (rework): source guest RAM cap from docker info; deploy guard uses committed memory
The cgroup-only approach was a no-op on the demo: the controller container's OWN
cgroup is unlimited (the 2GB cap is on the LXC ancestor, hidden), and /proc has no
lxcfs, so it kept reporting the host's 16GB. The Docker daemon runs IN the LXC, so
'docker info' MemTotal reports the guest's real cap (2048MB) — now the authoritative
source (cgroup limit preferred when present, e.g. non-nested). The deploy memory
guard now uses the controller's committed-app memory (sum of running mem requests)
for 'used' — accurate and cheap — instead of host /proc RSS (unobservable per guest;
would make the guard never/always fire). /api/system/info reports the guest cap as
total and committed memory as used. Tests: cgroup-limit path, docker-info fallback
(nested case), GuestMemTotalMB fallback (dockerMemTotalFn stub).
2026-06-14 10:18:22 +02:00
admin 0b9450e356 F17: per-app restore now replays the captured .sql DB dump (.sql wins)
The restore paths (RestoreFromRecoveryUnit + the RestoreApp fallback) repopulated
Docker volume tars but NEVER replayed the captured <stack>-<dbtype>.sql dump, so
DB-resident data (e.g. rows in a DB whose data dir is a bind mount) did not come
back — the romm marker round-trip in the audit lost the row.

New appbackup.ImportDump (read-side counterpart to DumpOne) replays a .sql/.sql.gz
into the running DB using the live container's OWN discovered credentials (no env
threading; reuses DiscoveredDB + getMariaDBPassword). backup.reimportDBDumps
orchestrates it AFTER volume restore + stack bring-up, so the logical dump WINS
over any volume-tar copy of the DB (operator-chosen precedence). pg_dump
--clean --if-exists and mariadb-dump (default --add-drop-table) make replay
idempotent; psql ON_ERROR_STOP=1 surfaces real import errors.

Also: volume-restore per-volume failures and DB-import failures now SURFACE (the
restore returns an error) instead of a swallowed WARN, so a failed data restore
cannot read as success.

Tests (restore_db_test.go, injectable discover/import seams): imports when dump+DB
present, failure surfaces, no-dump skips discovery, dump-but-no-matching-DB is a
non-fatal skip. Live DB round-trip to be validated post-deploy.
2026-06-14 10:02:58 +02:00
admin 803ce50578 F5 (dashboard): surface 'route unpublished' for unhealthy/restarting deployed apps
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.
2026-06-14 09:56:30 +02:00
admin 68684892d8 F8: persist controller.yaml as 0600 (holds infra secrets)
The Hub config-apply handler wrote controller.yaml 0644; it holds cf_api_token,
cf_tunnel_token and hub api_key in plaintext. New writeConfig0600 helper writes
0600 atomically (tmp+rename, bind-mount fallback) and chmods to enforce 0600 even
when the file pre-existed 0644 (os.WriteFile doesn't chmod existing files).
Test asserts mode 0600 (Linux; skipped on Windows). Setup path already used 0600.
2026-06-14 09:50:50 +02:00
admin 4938cc8985 F7: tighten status-refresh cadence 30s->10s (dashboard state lag)
The dashboard stacks list served the in-memory map refreshed by a 30s ticker, so
container state lagged Docker health by up to ~30s after a deploy. RefreshStatus
is a cheap docker-ps refresh; 10s (matching health-probes) cuts the lag without
loading Docker.
2026-06-14 09:49:05 +02:00