From 86de16f6dda287858f6084d8c0afd2aa2958ec55 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Sun, 19 Jul 2026 10:05:07 +0200 Subject: [PATCH] docs: README feature 17 (async-job feedback) + CONTEXT for v0.147.x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README gains the async-job feedback section, the TOC entries it was missing (SMB was also absent), and the three new endpoints. CONTEXT records the slice and, more usefully, that three of its four versions exist because the cards were watched against real runs — the failure modes were all invisible to unit tests. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE --- CONTEXT.md | 36 ++++++++++++++++++++++++- controller/README.md | 63 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/CONTEXT.md b/CONTEXT.md index ce01c06..f2982db 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -7,7 +7,41 @@ > > Ask Claude Code: "Please update CONTEXT.md with what we did today" -Last updated: 2026-07-18 (v0.145.0 — R-7b: share data enters the live backup runs, Model B′) +Last updated: 2026-07-19 (v0.147.3 — feedback slice 1: pressing a button says something) + +> **2026-07-19 — v0.147.0 → v0.147.3: feedback slice 1.** The systemic complaint, twice in one +> evening: you press a button and nothing happens. Three worst offenders fixed on the two patterns +> that already existed (deploy 3-step panel; storage-init status poll). **Deliberately NOT a +> framework** — that is ROADMAP **R-45**, and the two lessons it must encode are already written +> down there: a terminal state must be **probed, not inferred** (`compose up -d` exits 0 on a +> crash-loop), and a progress source reporting nothing is **normal, not broken** (restic reports 0 +> bytes for a whole incremental run). +> +> **4a** — the offsite verification restore names its **full path** in the flash, and +> `/backups/restore` lists existing verification copies (app · size · date · path) with a +> double-confirmed per-copy delete. That delete takes a **stack name, never a path**; red-proofed +> (neutralise the name guard and `stack:""` resolves to the offsite-restore ROOT and takes every copy +> with it). `backups/offsite-restore` now has ONE home, `offsiteRestoreRootFor`. +> **4b** — Megosztás enable/password no longer reconcile inside the POST; detached job + poll, with +> „képfájl letöltése" vs „indítás" decided BEFORE the work starts (afterwards the image is always +> present and the distinction is unrecoverable). +> **4c** — „Távoli mentés most" streams restic `--json`. **Manual only**; the nightly stays silent, +> pinned by a test. +> +> **Three of the four versions exist because the cards were watched against real runs on the demo +> box** — none of these would have surfaced from unit tests: (.1) an incremental run reports 0 bytes +> for its whole duration, so a byte-only bar looks hung in the COMMON case; (.2) restic 0.14 counts a +> file only when it completes, so one big archive freezes the file counters too — fall back to +> current file + elapsed; (.3) the run does not end with the last app — the shares leg and +> `forget --prune` took 40 of a 57-second run, and the card froze on the last app until phases were +> added. +> +> Also: `infra.Images()` + `--print-infra-images` close the golden/controller infra-image drift at +> the source. The golden's own copy had already drifted (felhom-samba missing → 3 of 4 baked), which +> is **why** enabling Megosztás pulled at runtime in the first place. Effective at the next bake +> (`felhom-agent` build-golden v2.1.0); no golden rebuilt. **Floor NOT raised — Viktor decides.** +> +> Earlier: 2026-07-18 (v0.145.0 — R-7b: share data enters the live backup runs, Model B′) > **2026-07-18 — v0.145.0: R-7b — the „Felhőmentés" toggle is now TRUE (Model B′), + samba liveness.** > Until v0.144.0 a share could be marked „Felhőmentés: bekapcsolva" while its files were in NO backup: diff --git a/controller/README.md b/controller/README.md index 84ecb92..3c34d3b 100644 --- a/controller/README.md +++ b/controller/README.md @@ -44,6 +44,8 @@ backups, monitoring and notifications. All Proxmox/disk operations are delegated - [Debug Mode](#13-debug-mode) - [Geo-Restriction](#14-geo-restriction) - [App-to-App Integrations](#15-app-to-app-integrations) + - [Network File Sharing — SMB](#16-network-file-sharing--smb-megosztás-v01440-r-7-slice-1) + - [Async-job feedback](#17-async-job-feedback-v0147x-feedback-slice-1) - [Repository Layout](#repository-layout) - [Configuration](#configuration) - [REST API](#rest-api) @@ -2415,6 +2417,64 @@ e-mail. Note the container name is NOT the stack name (`samba` vs `felhom-samba` --- +### 17. Async-job feedback (v0.147.x, feedback slice 1) + +Three long operations that used to be silent now report what they are doing. **These are three +targeted cards on the two existing patterns** (the deploy 3-step panel and the storage-init status +poll), NOT a framework — a unified async-job layer is ROADMAP R-45. + +**Verification-restore visibility** (`backup/offbox_verify_copies.go`, `web/offbox_handlers.go`, +`templates/backups_restore.html`). The offsite verification-restore flash now names the **full path** +it wrote to, and `/backups/restore` lists existing verification copies (app · size · date · path) +with a per-copy delete. + +- `offsiteRestoreRootFor(drivePath)` is **THE** place `backups/offsite-restore` is spelled; + `offboxRestoreScratchDir` builds on it so listing and delete resolve byte-identical paths to what + the restore wrote. +- `ListOffsiteRestoreCopies()` sweeps every candidate drive in the same preference order the restore + path uses to *choose* one, so it can never miss a copy the restore was capable of creating. +- `DeleteOffsiteRestoreCopy(stack)` takes a **stack name, never a path** — the customer cannot hand + the controller a directory to remove. Guarded by `isSafeStackName` plus a containment assertion on + the resolved path. `POST /backup/offbox/verify-copy/delete` additionally requires `confirm=1`, is + double-confirmed in the UI, and refuses while any backup/restore op is running. + +**SMB bring-up progress** (`web/samba_ensure_job.go`, `templates/sharing.html`). `/sharing/enable` +and `/sharing/password` no longer run `ReconcileSamba()` inside the POST — they start a detached +single-flight job (the `storage_init_job.go` shape) and the page polls `GET /sharing/status`. + +- The opening phase is decided **before** the work starts, from `stacks.SambaImagePresent()`: + `pulling` („képfájl letöltése") when the pinned image is not in local Docker storage, else + `starting`. Afterwards the image is always present, so the distinction is unrecoverable later. +- Terminal success is **probed** via `stacks.SambaRunning()` — `compose up -d` exits 0 on a + crash-loop. A `nil` reconcile with `UserSet == false` reports `needs_password`, not `running`. +- `/sharing/status` lets live container state win over a stale/absent job, so a page loaded after a + restart still tells the truth. + +**Offsite backup progress** (`backup/offbox_progress.go`, `templates/backups_remote.html`). +`RunOffboxBackupWithProgress` (the **manual** trigger only; the nightly `RunOffboxBackup` is +unchanged and stays silent) installs a progress sink, adds `--json` to the app-backup leg and scans +restic's stdout line-by-line through `offboxStreamRunner` — a streaming sibling of the existing +`offboxRunner` seam, injectable for tests. Published on the existing `GET /backup/offbox/status` +under `progress`. + +- Restic's status object is only partly usable in practice, and the fallbacks matter more than the + percentage: on an **incremental** run restic transfers no bytes (`bytes_done` is `omitempty`, so + absent) and `percent_done` stays 0 for the whole run; and because restic 0.14 counts a file only + when it **completes**, an app dominated by one large archive freezes the file counters too. The + card therefore degrades: bytes -> files -> current file + elapsed seconds. +- A run is **not** only the per-app loop. `Phase` (`shares`, `retention`) names the post-app stages + and clears app-scoped counters, so the card never shows the last app's finished numbers against + work that is no longer about that app. + +**Infra image pins** (`internal/infra`). `infra.Images()` returns every controller-managed infra +image, derived from the existing pinned consts. `felhom-controller --print-infra-images` prints it +(config-free by design — no `controller.yaml`, data dir or settings are touched) so the golden bake +(`felhom-agent configs/build-golden.sh`) can ask the controller image it is about to bake instead of +keeping its own list, which had already drifted. A `go/ast` test fails if a `*Image` const is added +without reaching `Images()`. + +--- + ## Repository Layout ``` @@ -2646,6 +2706,9 @@ All daily jobs use Europe/Budapest timezone. Skip-if-running prevents concurrent | POST | `/api/stacks/{name}/cross-backup/run` | Trigger cross-drive backup | | GET | `/api/stacks/{name}/cross-backup/status` | Cross-drive status | | POST | `/api/backup/cross-drive/run-all` | Run all scheduled cross-drive backups | +| GET | `/backup/offbox/status` | Offsite run status **+ live `progress` for a manual run** (v0.147.x) | +| POST | `/backup/offbox/verify-copy/delete` | Delete ONE verification copy (stack name + `confirm=1`; v0.147.0) | +| GET | `/sharing/status` | SMB bring-up phase + live container state (v0.147.0) | ### Storage