docs: README feature 17 (async-job feedback) + CONTEXT for v0.147.x

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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
This commit is contained in:
2026-07-19 10:05:07 +02:00
parent 10ca8ac884
commit 86de16f6dd
2 changed files with 98 additions and 1 deletions
+35 -1
View File
@@ -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:
+63
View File
@@ -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