Commit Graph

885 Commits

Author SHA1 Message Date
admin 419d3d0b4e controller v0.94.0: pull-based config-refresh (re-pull + self-restart on config_version change)
PushResponse.ConfigVersion from the report ACK; ConfigRefresher reconciles vs.
the last-applied version (settings.applied_config_version) and on a change calls
bootstrap.RefreshConfig (re-pull controller.yaml + re-merge local_api) then
GracefulSelfRestart. First-run records baseline (no restart); unchanged = no-op
(no storm); failed pull keeps config + retries. Companion to hub v0.26.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
2026-06-30 21:49:47 +02:00
admin 464b14f029 docs: REPORT + README for v0.93.0 (NAS Part B off-box backup, live-validated A-D)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
2026-06-30 15:43:03 +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 02820d6550 fix(offbox): use restic -o sftp.command (0.14 has no sftp.args)
Live validation surfaced 'option sftp.args is not known' on restic 0.14.0; switch to the
portable sftp.command SSH invocation (ConnectTimeout/StrictHostKeyChecking preserved).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
2026-06-30 15:32:43 +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
admin ddeb509d1d docs: REPORT + README for controller v0.92.0 (NAS Part A2, live-validated A-D)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
2026-06-30 14:53:54 +02:00
admin 364dc50794 controller v0.92.0: NAS network storage Part A2 (registry + UI + per-share health)
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
2026-06-30 14:40:47 +02:00
admin 9391f4ea71 context: self-health arc complete (hub-side leaf-fp re-key detection, agent v0.48.0 + hub v0.22.1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pg8ANF97SEeKYSN5Jxw3qJ
2026-06-29 23:32:46 +02:00
admin f4a603f2e5 context: v0.91.0 F2 born-down alerting + the prevention slice (agent v0.46.0, hub v0.21.0)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pg8ANF97SEeKYSN5Jxw3qJ
2026-06-29 22:02:39 +02:00
admin a72ebfb0a5 docs: REPORT for v0.91.0 F2 born-down alert (live-validated)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pg8ANF97SEeKYSN5Jxw3qJ
2026-06-29 22:01:39 +02:00
admin b2ad871720 channelhealth: F2 — alert on born/persistent-down (alerted flag), not only transitions v0.91.0
A channel broken at startup/reseed (e.g. controller boots into pin_mismatch) was dashboard-only,
no operator email ever. New 'alerted' flag drives alerting instead of prev=='': born-down
non-transient alerts cycle 1; transient still N>=2; healthy first-obs silent; recovery re-arms.
Red-proof + companion included.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pg8ANF97SEeKYSN5Jxw3qJ
2026-06-29 21:45:57 +02:00
admin 0f7ba7b665 docs: REPORT + CONTEXT + README for v0.90.0 channel health-check (live-validated, hub v0.20.0 allowlist)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pg8ANF97SEeKYSN5Jxw3qJ
2026-06-29 20:43:13 +02:00
admin a277b18981 channelhealth: controller->agent channel health-check (periodic probe + classified operator alert) v0.90.0
New internal/channelhealth Checker: ~60s probe via the PRODUCTION memoized client
(Server.ProbeAgentChannel, GET /storage), classifies failures (spike Q1 map), debounces transient
reasons (N>=2; construction error latches distinctly), seeds first obs, alerts operator+dashboard on
transition. Notifier.NotifyAgentChannelDown/Recovered (English, operator-only), AlertManager dashboard
banner (Hungarian). No agent/hub change. Spike-proven.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EPZ4GJ8L5Jqf8UiPwbn1kt
2026-06-29 20:28:02 +02:00
admin 77bccf1212 context: OPS R1 — agent local-API leaf restored, controller↔agent service restored (no code change)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EPZ4GJ8L5Jqf8UiPwbn1kt
2026-06-29 15:08:59 +02:00
admin f10d74f993 docs: REPORT — v0.89.0 gap closure, calcom+nextcloud send-tested live
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 13:56:51 +02:00
admin a405505e81 v0.89.0: app-email plaintext-only listener (:2526) + split-From mapping
Gap 1: third shim listener :2526, plaintext, does NOT advertise STARTTLS (TLSConfig
nil) — for opportunistic-STARTTLS clients with no cert-skip (cal.com, nextcloud).
Gap 2: SMTPMapping tls_mode (picks port 2525/2526/2465) + from_domain_var (split
local-part + domain for nextcloud's MAIL_FROM_ADDRESS/MAIL_DOMAIN). Default keeps
existing apps on 2525. Hub untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 13:13:40 +02:00
admin 6692a2f631 docs: REPORT — confirm inline CID image renders in Gmail (Resend preview unreliable)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 09:08:28 +02:00
admin 9c0166dddd docs: README — app-email relay (mailrelay) feature section
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 08:59:19 +02:00
admin 3776d972f7 docs: REPORT — live end-to-end validation results (v0.88.0)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 08:58:05 +02:00
admin 0e20eb19c1 v0.88.0: app-email SMTP relay (in-process shim + per-app injection)
In-process go-smtp shim (Shape 1): apps → shim → hub → Resend, Resend key stays
hub-side. From-header allowlist (reject 5xx pre-hub), single-shot raw-MIME forward,
status→SMTP mapping. Global + per-app toggles gate compose-time env injection from
.felhom.yml smtp_mapping. Hungarian UI on settings + app config pages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 08:45:04 +02:00
admin 7cddb885e0 docs: v0.86.0 live floor-test results — auto-update 0.86.0→0.87.0 (auto-floor)
REPORT/CHANGELOG: dogfood 0.85.1→0.86.0 (Phase 1 endpoint), global floor 0.87.0,
auto-update fired (initiated_by=auto-floor, success), no-flap at floor confirmed,
hub controller_version=0.87.0. Demo left at 0.87.0 / floor 0.87.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSZmmSFVzGwEzhYmxbkgBK
2026-06-27 12:22:03 +02:00
admin 1310a0ebd7 v0.86.0: Phase 2 managed updates — floor-driven auto-update
The controller honors an operator-enforced minimum version (FLOOR) on the hub
report ACK and auto-updates to the floor when below it (managed default, no click),
reusing the Phase 1 in-guest-pull + agent-swap + rollback. Latest stays the opt-in
button; the floor is the auto-target, never latest.

- pusher.go: PushResponse += min_controller_version, latest_version (existing ACK seam)
- main.go: OnPushResponse → updater.SetFloor + MaybeAutoUpdate (rides report cycle)
- updater.go: SetFloor/GetFloor + MaybeAutoUpdate reusing performUpdate (auto-floor);
  no-op at/above floor, floor>latest, dev/no-agent/backup; no flap (in-mem+persisted)
- settings UI (HU): floor display + auto restart-poll during an auto-update
- tests: below/at/floor>latest/no-flap/raised-floor; below-floor red-proof verified
- no agent change (reuses Phase 1 POST /controller/swap)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSZmmSFVzGwEzhYmxbkgBK
2026-06-27 11:59:47 +02:00
admin 3aa9777f1c docs: v0.85.0 self-update rework — README/CHANGELOG/REPORT (live-validated)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtXesNa2LGbMmE4DNL6SE7
2026-06-26 21:43:23 +02:00
admin 3c1e91b5f0 v0.85.0: self-update reworked — in-guest pull + agent swap (Phase 1)
Replaces the dead in-container docker-compose self-update (composePath doesn't
exist in the LXC guest). The controller now docker-logins+pulls the target image
in-guest (shared socket, its registry token), then delegates the container swap to
the host agent (agentapi.SwapController -> POST /controller/swap), which owns the
restart + health-verify + rollback. Removed performUpdate compose flow /
updateComposeFile / composePath. NewUpdater takes an AgentSwapper. DryRun reports
agent_reachable + pull_capable. UI button + poll unchanged; latest-only.

Tests: up-to-date no-op / pull-fail agent-not-called / happy pull-then-swap /
no-agent unavailable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtXesNa2LGbMmE4DNL6SE7
2026-06-26 21:26:14 +02:00
admin e0cf78bb90 docs: v0.84.0 REPORT/CONTEXT/README — catalog-driven initial-login extraction 2026-06-26 11:03:56 +02:00
admin 1705d71dd5 v0.84.0: catalog-driven initial_credentials — read an app's auto-generated first-login from a file and show it on the app page 2026-06-26 11:00:06 +02:00
admin 49f6dbf847 docs: v0.83.0 REPORT/CONTEXT/README — scoped Traefik serversTransport for self-signed HTTPS backends 2026-06-26 10:33:16 +02:00
admin 2d6df0f6d5 v0.83.0: Traefik scoped insecure-skip-verify serversTransport for self-signed HTTPS backends (fixes crafty 502) 2026-06-26 10:28:58 +02:00
admin 5c08dcfc35 REPORT: live verification of v0.82.0 on guest 9201 (F2 no-op + change paths, F1 NO-RESTIC)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FpBYrZCt9sFDqLgbG5GRGD
2026-06-24 11:06:44 +02:00
admin 9cd566ded7 v0.82.0: gate FileBrowser recreate on actual change (F2); drop unused restic binary (F1)
syncFileBrowserMounts no longer force-recreates FileBrowser unconditionally:
captures config.yaml+compose before writes, re-reads final content after, and
recreates only when they actually changed (new pure helper fbNeedsRecreate).
Controller restarts / no-op storage syncs now issue a plain up -d and do NOT
bounce the customer's file UI. Restore-mode DB reset still forces a recreate.

Dockerfile: removed the unused restic apt package (disk-tier restic moved to the
host agent; no controller code execs the binary). ResticSchedule/migrateResticToRsync
config+settings paths untouched (still live in the dashboard).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FpBYrZCt9sFDqLgbG5GRGD
2026-06-24 11:01:18 +02:00
admin 036a6078bb docs: REPORT.md — v0.81.0 deployed to guest 9201 + live-verified
Banner gone, "Kiszolgáló újraindítása" card present + ordered after the
controller-restart card, /api/server/reboot -> 202, /api/storage/activate -> 404,
/api/selfrestart regression OK. Live end-to-end reboot of guest 9201 confirmed
via agent logs (guest back up, drives re-bound, controller recovered healthy).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW
2026-06-23 18:54:09 +02:00
admin 7109e88e07 docs: REPORT.md for v0.81.0 (server-reboot button / banner retirement)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW
2026-06-23 18:47:28 +02:00
admin 242b835a19 v0.81.0: retire drive-activation banner; add standalone "Kiszolgáló újraindítása" button
In the intermediary-mount model an enrolled drive binds live into the running
guest (no reboot), so the "… meghajtó aktiválásra vár / Újraindítás most" banner
was an obsolete relic — also dead since v0.78 (pendingActivationDrives keyed on
the raw MountPath vs the now-stable sp.Path). Removed the banner block +
activatePendingDrives JS (settings.html), the PendingDrives feed (handlers.go),
and the dead pendingActivationDrives helper + its unused internal/system import.

Renamed handleStorageActivate -> HandleServerReboot (split out a testable
serverReboot core), removed the /api/storage/activate case, and mounted the
handler at the new non-storage route /api/server/reboot (RequireAuth+CsrfProtect).
The agent GuestReboot primitive is reused unchanged.

Added the standalone "Kiszolgáló újraindítása" settings card (sibling to the
controller-only "Vezérlő újraindítása"), reusing the pollRestart() loop.

Test: TestHandleServerReboot_CallsGuestReboot (fake diskAgent asserts GuestReboot
invoked once + 202). diskAgent/mockAgent gained GuestReboot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW
2026-06-23 18:46:31 +02:00
admin 7cce19797e REPORT: v0.80.0 deployed + verified — disk card shows/acts on the stable path
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW
2026-06-23 17:57:25 +02:00
admin fc94d91d15 v0.80.0: disk card shows + acts on the stable path, not the raw host mount
The storage card displayed each external drive's raw host PVE mount (/mnt/<name>,
which doesn't exist in the guest) instead of the stable in-guest path
(/mnt/felhom-drives/<name> = guest_path) the registry/HDD_PATH/FileBrowser use.
The eject/wipe buttons also posted the raw path, so they would unmount the drive
but leave the stable registry entry orphaned, and the impact warning found no apps.

Fix: card sub-line + eject/wipe buttons use the stable path (regKey); type-to-confirm
name uses the basename; register keeps the raw path. handleStorageWipe maps to raw
via agentWhere() for the agent eject (matching handleStorageEject). Agent ops
unchanged (same raw paths); display + registry bookkeeping corrected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW
2026-06-23 17:54:21 +02:00
admin 9596c8e563 REPORT: v0.79.0 deployed + verified — disk view keys registered on stable path (badge/button now correct)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW
2026-06-23 17:45:05 +02:00
admin a9f0de4c63 v0.79.0: disk view keys the "registered" check on the stable path
Follow-up to v0.78.0. The disk-view JS (settings.html regBadge/actions) checked
registration by the raw mount_path, but the registry stores the stable path since
v0.78.0 — so enrolled drives showed a spurious "Nem regisztrált" badge + Register
button. Fix: regKey(d) = d.guest_path || d.mount_path (agent reports the stable
guest_path per disk); registerDrive still posts the raw path. Display-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW
2026-06-23 17:42:37 +02:00
admin 081c1cd77f REPORT: v0.78.0 deployed to 9201 + registry remediated — drives now register at the stable path, 2 connected, badge+banner gone
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW
2026-06-23 17:37:12 +02:00
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 0b7163e5af docs: v0.76.0 REPORT/CONTEXT — campaign-#3 hardening fixes + live re-test evidence
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 23:39:20 +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 141d51bf19 test campaign #3: Phase 7 brutal recovery (B1-B3 PASS, B4 skip) + final findings/cleanup
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 22:04:55 +02:00
admin eb5cd8375e test campaign #3: Phase 6 security fuzzing (F1-F4; no tree escape; restore stack_name validation gap finding)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:51:46 +02:00
admin f40fedfbfc test campaign #3: Phase 5 network partitions (N1 tunnel block, N2 PBS block mid-backup — fail-safe + unquiesce, both PASS)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:40:11 +02:00
admin 7187ef6b62 test campaign #3: Phase 4 time chaos SKIP (unprivileged guest shares host clock; host jump risks certs/DooPlex)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:31:33 +02:00
admin 8845496c0e test campaign #3: Phase 3 concurrency storms (C1 mutex, C2 flap+gate, C3 agent-kill unquiesce — all PASS)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:30:08 +02:00
admin 850f0d4568 test campaign #3: Phase 2 state corruption (S1 settings crash-loop FINDING; S2/S3/S4 pass; symlink no-exfil)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:23:05 +02:00
admin 192bb30a4b test campaign #3: Phase 1 resource starvation (R1-R4 fail-safe held)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:16:45 +02:00