Second review of the night's own code: a stray clears when a later removal works (N2), the start-up check never removes this process's own copy (N3), one copy per destination at a time (N4), RunTier2 refuses during a restore for every caller (N5), the import refusal no longer advises removal (N9); red-proved, race-clean; README; REPORT; unreleased
gates / gates (push) Successful in 1m40s

This commit is contained in:
2026-10-22 22:45:42 +02:00
parent 943c517550
commit 9d9e4041c9
9 changed files with 341 additions and 62 deletions
+8
View File
@@ -1399,6 +1399,14 @@ retired in v0.126.0 when the moved blocks were legitimately rewritten onto the s
contain no symlink (`tier2CappedPath`, `filepath.EvalSymlinks(p) == p`: a link could point the mount elsewhere). A
docker start error (exit 125) falls back to plain rsync; any other failure is returned. Measured on 9202 (4 GB, 1.8 GB
held, 6 GB copy): full stall 5.7 s → 0.37 s. Tests `r1214_capped_copy_test.go`.
- **The copy container never outlives the copy (the stray-copy fix, unreleased, rides 0.340.0).** Each copy container has
a fixed name (`felhom-tier2-copy-<app>-<8 hex of the destination>`) and the label `felhom.tier2-copy=1`; a leftover of
the same copy is removed before it starts, and on any error — the timeout included — it is removed by name. At
controller start, BEFORE the scheduler and the web server, every labelled container is removed (logged; retried every
minute until it succeeds). A restore is refused while any second-drive copy runs or a leftover may run
(`backup.Tier2CopyBlocksRestore` → `restoreOpBlocked`). The run uses `--mount` (a path the daemon does not see is
refused, exit 125 → the in-process copy), the controller's image ID with `--pull never`, and `--no-healthcheck`.
Tests `tier2_copy_lifecycle_test.go`, `web/straycopy_restore_refused_test.go`, `cmd/controller/straycopy_wiring_test.go`.
- **A template may leave a named volume out of the app-data backups (unreleased, rides 0.340.0; R-49, operator D21 = B).**
`.felhom.yml` `backup.volumes: [{name: <volume>, class: excluded}]` — every named volume is dumped except
`<project>_<name>` (`appbackup.ExcludedDockerVolumes` → `backup.Manager.dumpableVolumes`), so it leaves Tier 1, Tier 2