gates / gates (push) Successful in 6m46s
Read-back: live sizing-data.json?v=4 byte-equal to the commit; the presets keep their verdicts, Immich alone on 8 GB still tight; only the set without Immich changed. Spike B2 on 9202: POST /api/assets/jobs regenerate-thumbnail rebuilt exactly the 10 broken assets (1.55 s, 5.3 s CPU), 0 of the other 20 photos' 40 files rewritten (control: all rewrote 40 of 40); an empty file maps to its asset by the first 36 characters of its name. Immich removed through the product, no leftovers. Register 134 -> 133 (opened 0, closed 1). STATUS D9/D10 ruled. Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
199 lines
19 KiB
Markdown
199 lines
19 KiB
Markdown
# Power-cut fixes 2026-10-14 — spike (Part C) and live proof (Part E)
|
||
|
||
Times UTC. Box: demo-hp (Tier 0). No key, token or password in this folder.
|
||
|
||
## C1 — what the night's cut left (from `audits/night-power-2026-10-13/R3/`)
|
||
|
||
| Item | Value |
|
||
|---|---|
|
||
| left on disk | `/var/lib/vz/dump/vzdump-lxc-9201-2026_10_14-04_42_18.tar.dat` (file, `root:root 0644`, 12.4 GB at the cut, 12.2 GB after the boot — the unwritten tail was lost) and `vzdump-lxc-9201-2026_10_14-04_42_18.tmp/` (directory, `root:root 0755`). **No `.log`** (vzdump writes it at the end) |
|
||
| name pattern | `vzdump-lxc-<vmid>-YYYY_MM_DD-HH_MM_SS` + `.tar.dat` / `.tmp`; the timestamp is the host's local time |
|
||
| the task | `UPID:demo-hp:000A8C2E:000DFB52:6ACEEC0A:vzdump:9201:felhom-agent@pve!agent:` — started by the agent's API token |
|
||
| after the boot | 02:52:56 the agent: „a vzdump backup is genuinely in-flight — leaving the lock" — so the dead task WAS in `tasks?source=active` 1 min after the boot. How long it stays: not recorded → C3 |
|
||
| who may delete | the files are root's in a root-owned directory; the agent runs as `felhom-agent` → it **cannot** delete them itself |
|
||
|
||
## C2 — predictions (written before the run)
|
||
|
||
| Question | Prediction |
|
||
|---|---|
|
||
| left on disk after `kill -9` of the vzdump worker | the same pair (`.tar.dat` + `.tmp`); the tar/zstd children may keep writing for a moment, then die on the broken pipe |
|
||
| `StorageContent` (backup) lists it | **no** — Proxmox lists `vzdump-*.tar.zst`/`.vma.zst` etc.; `.tar.dat` is a temporary name |
|
||
| the active-task list keeps the task | **no** — the PVE task daemon sees the worker die and ends the task with an error (unlike a power cut, where nobody ends it) |
|
||
| `lock: backup` on the guest | stays (vzdump's cleanup did not run) |
|
||
| the next backup | the space preflight sizes from the last COMPLETE archive (5.6 GB) and passes while space is free; the partial still occupies the disk |
|
||
|
||
## C2 — a partial without a power cut (`C2/`; 9201, because 9202 has no agent link — its „back up now" cannot run)
|
||
|
||
Backup pressed through the product 08:07:30 (`POST /api/guest-backup/trigger`, 200); at 1.5 GB written the vzdump worker
|
||
(`task UPID:…:vzdump:9201:…`) was killed `-9` at 08:09:44.
|
||
|
||
| Question | Prediction | Measured |
|
||
|---|---|---|
|
||
| left on disk | `.tar.dat` + `.tmp` | **yes** — `vzdump-lxc-9201-2026_10_14-10_08_05.tar.dat` + `.tmp/`, root 0644 / 0755, no `.log` |
|
||
| the writers | die on a broken pipe | **no** — `tar` and `zstd` (children of a `bash -c`, none named „vzdump") kept writing for **4 min 40 s**, to 5.6 GB (a whole archive's size), then exited. The file was never renamed. **A „no vzdump process" check alone would call a file dead while it is being written** |
|
||
| `StorageContent` lists it | no | **no** — only the complete `…09_28_50.tar.zst` is listed |
|
||
| active-task list keeps the task | no | **no** — gone within seconds (the PVE daemon ended it: „backup job failed" 08:09:46) |
|
||
| lock | `backup` stays | the agent's 10-min janitor cleared it at 08:10:18 **while the orphan `tar` still read the snapshot**; its snapshot delete failed („contains a filesystem in use") → `lock: snapshot-delete`; the next janitor pass (08:20:18) cleared that lock and removed the snapshot |
|
||
| the next backup | passes the preflight | none came: a failed manual press is not retried (the night loop retries; this one did not within 30 min) |
|
||
| clean-up | — | the 5.6 GB partial removed by hand at 08:41 (host root 78 % → 63 %) |
|
||
|
||
## C3 — one real power cut mid-write (9201's backup, pressed through the product)
|
||
|
||
**Predictions (written before the cut):**
|
||
|
||
| Question | Prediction |
|
||
|---|---|
|
||
| the dead vzdump task in `tasks?source=active` after the boot | **yes**, and it stays there (nobody ends a task whose node died) — for the whole 15 min |
|
||
| `BackupRunning` (the agent's startup stale-lock line) | true → „genuinely in-flight — leaving the lock"; Proxmox's start-all removes the lock anyway |
|
||
| the guest restart request (`POST /api/server/reboot`, the dashboard's) | refused „backup running" while the dead task is listed |
|
||
| the household power-off request | refused — „too soon" first (host up < 10 min), and on demo-hp „other guests" anyway (9202 and Tester 1 run); it cannot reach the backup check here |
|
||
| files | `.tar.dat` + `.tmp` stay, nothing removes them |
|
||
|
||
|
||
**Measured** (`C3/`): backup pressed 08:42:25; at 1.5 GB the cut: 08:44:08.089 off → 08:44:18.165 on (plug countdown).
|
||
Boot `btime` 1791967481; the dead task's start 1791967345 (136 s before the boot).
|
||
|
||
| Question | Prediction | Measured |
|
||
|---|---|---|
|
||
| dead task in the active list | stays all 15 min | **listed RUNNING at 08:44:57; gone at 08:45:30** — Proxmox's own start-up clears it within ~30–60 s of the boot (at 10:45:00 local `pvesh`/`pve-guests`: „removed left over backup lock from '9201'!") |
|
||
| `BackupRunning` | true | **true at the agent's start** (08:44:59: „genuinely in-flight — leaving the lock") — the agent starts inside that short window |
|
||
| guest restart request (`POST /api/server/reboot`, 08:47:09) | refused „backup running" | **202 — it restarted 9201.** The restart route never asks `BackupRunning`; the `guestpower.go` caller is the guest-power WATCHDOG (it starts stopped guests), not the restart |
|
||
| power-off request (08:48:41) | refused | **404** — demo-hp's 9201 runs controller 0.309.0, which has no power-off route; the guard can be exercised only after the floor raise (Part E) |
|
||
| files | stay | **stay all 15 min**: `…10_42_25.tar.dat` 1.16 GB (1.5 GB at the cut — the unwritten tail lost) + `.tmp/` |
|
||
| lock | Proxmox removes it | removed by Proxmox at boot |
|
||
|
||
**What this changes (STOP-rule check):** the night's mechanism is the same — the partial is the damage, Proxmox does not
|
||
list it as a backup. R-1008's window is **short** (≈ 1 min after a boot): the startup stale-lock pass, the first guest-power
|
||
watchdog sweep and the controller supervisor in that minute see a dead task as running. D1 stays (one helper, cheap,
|
||
correct); its live effect is small. D2 is the fix that matters.
|
||
|
||
## C4 — a backup that runs out of space (prediction before the run)
|
||
|
||
On scratch guest 9202, `vzdump 9202 --storage local --mode snapshot --compress zstd` run as root on the host (9202 has no
|
||
agent link; the question is vzdump's own behaviour), with the host root filled by a test file to ~600 MB free.
|
||
**Prediction:** vzdump fails with „No space left on device" and **removes its own `.tar.dat`/`.tmp`** (its error path runs,
|
||
unlike a power cut); the lock clears; nothing is left.
|
||
|
||
**Measured** (`C4/`, host root filled to 600 MB free by `C4-fill-test.bin`, removed after):
|
||
|
||
| Run | Result |
|
||
|---|---|
|
||
| `vzdump 9202` (dir storage → vzdump's rsync path, `suspend` mode) | „No space left on device" in the first sync; **vzdump removed its `.tmp`**; only `vzdump-lxc-9202-…11_01_36.log` (2 KB) left; space back |
|
||
| `vzdump 9201` (LVM-thin → the snapshot + `tar … \| zstd > .tar.dat` path, the night's path) | `zstd: error 70 … No space left on device`; **vzdump removed its `.tar.dat`**; only the `.log` left. **But the full disk broke LVM:** „Failed to write metadata … Backup of volume group pve metadata failed"; the `vzdump` snapshot was half removed (disk-0's gone, disk-1's left) → `lock: snapshot-delete` |
|
||
| the agent's janitor after it | 11:14:59 local: cleared the lock, then its snapshot delete **failed** („Failed to find logical volume pve/snap_vm-9201-disk-0_vzdump") — it would fail the same way every 10 min, the guest's snapshot list stuck. Fixed by hand 09:19 UTC: `pct unlock 9201; pct delsnapshot 9201 vzdump --force` (removed disk-1's snapshot LV, cleared the entry) |
|
||
|
||
**D3 decided by C4:** vzdump cleans its own partial on ENOSPC, so the preflight's sizing (R-1006 part 2) costs only wasted
|
||
time, never a leftover; `spaceFits` stays as it is. The real danger C4 shows is different: **a host root that fills up
|
||
breaks LVM's own metadata writes** — the night's 12 GB partial took demo-hp to 92 %. Removing a dead partial (D2) is what
|
||
keeps the root disk from filling. New finding: the janitor's snapshot delete has no `--force` path for a half-removed
|
||
snapshot (row).
|
||
|
||
|
||
## F — can Immich repair the power cut's damage by itself? (R-1007, spike only)
|
||
|
||
**Prediction (before the run):** Immich's thumbnail job with „missing" does NOT redo a zero-byte thumbnail (the database
|
||
says it exists); the „all" (force) run does; nothing in Immich restores a deleted original, but a storage/integrity
|
||
report lists it.
|
||
|
||
**Measured** (`F/immich-repair.txt`, `F/immich-delete-then-missing.txt`; Immich v3.2.4 on 9202, 30 photos, deployed and
|
||
removed through the product):
|
||
|
||
| Act | Result |
|
||
|---|---|
|
||
| 10 assets' thumbnail + preview truncated to 0 bytes; 1 original deleted (the night's damage, R2) | the API serves 0-byte thumbnails/previews; the deleted original: 404 |
|
||
| integrity jobs `integrity-missing-files`, `-untracked-files`, `-checksum-mismatch` (`POST /api/jobs`) | **the deleted original is found**: `GET /api/admin/integrity/report?type=missing_file` lists it with its asset id. The zero-byte thumbnails are not reported (the checks cover originals) |
|
||
| thumbnails „missing" (`PUT /api/jobs/thumbnailGeneration {force:false}`) | **does not fix** a zero-byte thumbnail — the database says it exists |
|
||
| zero-byte files deleted first, then „missing" | **still not fixed** (Immich decides from its database, not the disk) |
|
||
| thumbnails „all" (`{force:true}`) | **fixes all 10** — every thumbnail regenerated (30 photos: 16 s; a family library of 50,000: hours of CPU) |
|
||
| the deleted original | nothing in Immich restores it — only a backup |
|
||
|
||
**Prediction:** right on „missing" and „all"; right that nothing restores an original; the integrity report exists in this
|
||
version (`/api/admin/integrity/…`) and finds the missing original.
|
||
|
||
**Proposal for R-1007 (an operator decision):** after an unclean boot, run Immich's „integrity-missing-files" job and show
|
||
its report (operator page, later the household), and run „thumbnails: all" in the night window. Cost: one catalog-level
|
||
hook and CPU in proportion to the library; a missing original then needs a restore from the night backup. If nothing is
|
||
done: broken previews stay until someone forces a regeneration by hand; a lost original stays unnoticed.
|
||
|
||
|
||
## B2 — can Immich rebuild single photos? (R-1007, after the operator ruling „fix only the broken photos", spike only)
|
||
|
||
Immich v3.2.4 on scratch guest 9202, deployed and removed through the product; 30 bench photos (`B2/`).
|
||
|
||
**What the running server answers** (asked, not remembered): `POST /api/assets/jobs` with a bad body → 400 listing the
|
||
names `refresh-faces`, `refresh-metadata`, `regenerate-thumbnail`, `transcode-video`; body `{"assetIds": [<uuid>…],
|
||
"name": "regenerate-thumbnail"}`, permission `AssetUpdate` (an admin or the owner's key). In the server code
|
||
(`dist/services/asset.service.js` `run()`): one `AssetGenerateThumbnails` job per id given — nothing else is queued.
|
||
|
||
**Path layout:** `<upload>/thumbs/<user id>/<a[0:2]>/<a[2:4]>/<asset id>_thumbnail.webp` and `…_preview.jpeg` (+ one
|
||
`.immich` marker file). **Mapping an empty file to its asset:** the first 36 characters of the file name ARE the asset
|
||
id. A box finds the broken photos from the disk alone: `find <upload>/thumbs -type f -size 0`, cut the names to 36
|
||
characters, unique.
|
||
|
||
| Step | Result |
|
||
|---|---|
|
||
| damage | assets 1–5: thumbnail → 0 B; assets 6–10: preview → 0 B; the API served the 0-byte files |
|
||
| found from the disk | 10 zero-byte files → 10 asset ids (the mapping above) |
|
||
| `POST /api/assets/jobs regenerate-thumbnail` for those 10 | **204**; no zero-byte file left after **1.55 s**; queue idle after 16.9 s (5 s polling); **CPU 5.3 s** (all Immich containers, cgroup `usage_usec`) |
|
||
| the 10 assets | all 20 files rewritten (the undamaged sibling too), none empty; the API serves real thumbnails and previews |
|
||
| the other 20 assets | **0 of 40 files rewritten** (mtimes before/after) |
|
||
| positive control: thumbnails „all" (`force=true`) | rewrote **40 of 40** of those same files (the 41st counted file is the `.immich` marker); CPU 16.5 s for 30 photos |
|
||
| teardown | Immich removed through the product; no folder, container, volume or image left; 9202's standing apps untouched |
|
||
|
||
**Answer:** yes — v3.2.4 rebuilds single photos. Cost ≈ 0.5 s CPU per photo here (a family library with 80 broken photos,
|
||
like the night's R2: well under a minute) against „all" in proportion to the whole library. A lost original is still
|
||
found only by `integrity-missing-files` and restored only from a backup.
|
||
|
||
## E — the live proof on demo-hp (agent 0.156.1 + bundle, delivered by signed jobs)
|
||
|
||
Vouched by the operator 2026-10-14 (read back from `/configuration`): agent 0.156.1 `83c7da27…`, golden 0.310.0 `2110d5e4…`,
|
||
min_agent 0.155.0, global floor 0.310.0. demo-hp: `agent_update 0.156.1` (11:00 UTC) → `agent_config_update 0.156.1-step1`
|
||
(11:15, „written=1", probe 70/71 — the helper not yet there) → `agent_config_update 0.156.1` (11:30, „BUNDLE WROTE
|
||
/usr/local/sbin/felhom-vzdump-partial-clean (new)", probe **71/71**). The new agent's first start (11:00) already FOUND C3's
|
||
partial and called it dead — and left it, „the root helper refused: sudo: a password is required" (the helper came with the
|
||
bundle). Real sudo after the bundle: `E/sudo-live-demo-hp.txt` (the two shapes allowed, six attacks refused).
|
||
|
||
**Prediction (before the cut):** after the boot the agent's start-up sweep removes BOTH partials (C3's from 08:44 and this
|
||
one), logged „removed a dead half-written backup … written before this boot"; the dead vzdump task is logged once as „worker
|
||
process is gone" and the start-up stale-lock pass clears the lock itself (or finds Proxmox already did); the next backup runs
|
||
and succeeds; host root returns below its use before this cut.
|
||
|
||
**Measured so far (11:42 UTC):**
|
||
|
||
| Step | Result |
|
||
|---|---|
|
||
| floor 0.310.0 | not delivered: each demo customer (demo-felhom, demo-hp, Tester 1) has its OWN floor 0.309.0 (set 2026-10-12 16:07), which wins over the global one (hub log + `floor_raise_skipped` mail). demo-hp's guest stays on 0.309.0 |
|
||
| backup pressed (11:42:33 UTC) | **the pre-backup cleanup worked live:** `removed a dead half-written backup … vzdump-lxc-9201-2026_10_14-10_42_25.tar.dat` (C3's 1.16 GB) and its `.tmp`, through the root helper (`sudo … felhom-vzdump-partial-clean local 9201 …`); each removal took ~7 s (the helper's `/proc` scan); then „space preflight passed" (`E/agent-cleanup-before-backup.txt`) |
|
||
| the backup itself | **failed** in 1 s: `lvcreate snapshot 'pve/snap_vm-9201-disk-1_vzdump' error: Failed to suspend thin snapshot origin`; kernel: „device-mapper: thin: Creation of new snapshot 4 of device 2 failed" (`E/failed-backup.log`) |
|
||
| why | the thin pool holds an **orphan thin device 4** — a 19 GiB snapshot of 9201's data disk (device 2) created at pool time 138 — which LVM does not know (LVM: devices 1, 2; transaction 559 on both sides). It is what C4's full root disk left: the snapshot cleanup could not write LVM's metadata. Read with a metadata snapshot (`thin_ls --metadata-snap`, reserved and released; `E/thin-ls.txt`) |
|
||
| consequence | **no whole-guest backup of 9201 on demo-hp can run until device 4 is deleted** (every snapshot create picks id 4). Caused by this session's C4 test |
|
||
|
||
**Repair (operator „B2 - A", 12:02 UTC):** `dmsetup message pve-data-tpool 0 "delete 4"` → thin_ls shows devices 1 and 2
|
||
only, pool transaction 559 unchanged, LVM agrees (`E/repair.txt`). Identity, not size: device 4 was the one id LVM does not
|
||
know, a snapshot (CREATE_TIME 138) of device 2.
|
||
|
||
**The cut (`E/`):** fences clear 12:04:48; backup pressed through the product 12:04:48 (`POST /api/guest-backup/trigger`,
|
||
200); the apps were stopped for the snapshot and started again at 12:05:30 (snapshot create worked — the repair held);
|
||
at 1.53 GB written, `plugcut.py --plug hp --off-seconds 10`: **12:06:52.325 off → 12:07:02.409 on** (plug countdown;
|
||
watchdog not needed). Root use at the cut 26,249,101,312 B; before the press 24,720,375,808 B. SSH back 12:07:45.
|
||
|
||
| Prediction | Measured |
|
||
|---|---|
|
||
| dead task logged once as „worker process is gone" | **yes** — 12:07:44 „backup-in-flight: a vzdump task is listed as active but its worker process is gone — it is dead, not running" (the agent started 12:07:42, inside the 30–60 s window in which Proxmox still lists the dead task — the R-1008 case, now judged right) |
|
||
| the start-up stale-lock pass clears the lock, or finds Proxmox did | **both, in the same second:** the agent decided „clearing a stale backup lock" and ran `pct unlock 9201`; Proxmox's start-all had just removed it („removed left over backup lock from '9201'!", 12:07:44) → the agent logged **ERROR „pct unlock failed … no lock found"**. Harmless (the guest started 12:07:47), but an ERROR for an outcome that is the goal — R-1013 |
|
||
| the start-up sweep removes the partial | **yes** — 12:07:48 „removed a dead half-written backup … vzdump-lxc-9201-2026_10_14-14_05_21.tar.dat 964,239,360 B (written before this boot)" and 12:07:52 its `.tmp` — through the root helper. 1.53 GB were written before the cut, 0.96 GB survived (the unwritten tail lost, as in C1) |
|
||
| guards do not refuse because of the dead task | the start-up sweep IS a `BackupRunning` caller: it went ahead only because the dead task was judged not running. The household power-off request at 12:09:53 (uptime 147 s) was refused **`too_soon_after_boot`** only (`E/requests.txt`). The guest restart is not guarded by `BackupRunning` at all (C3) |
|
||
| the next backup succeeds | **yes** — pressed 12:10:06, „found old vzdump snapshot (force removal)" (vzdump removed the cut's LVM snapshot itself), archive `vzdump-lxc-9201-2026_10_14-14_10_50.tar.zst` 5,650,197,172 B at 12:17:08; the older archive pruned (keep-last 1) (`E/next-backup.txt`) |
|
||
| host root back below its use before this cut | **yes** — 24,689,217,536 B after the boot sweep; 24,691,134,464 B after the new backup (before the press: 24,720,375,808 B) |
|
||
| no orphan thin device this time | **yes** — pool transaction 567, LVM lists only vm-9201-disk-0/1 |
|
||
|
||
`E/demo-hp-journal-boot.txt` is the whole boot journal (scanned: no key, token or password). R-1006 and R-1008 are proven
|
||
live on demo-hp.
|
||
|
||
**Delivery to the other two boxes (`E/delivery-tester1-n100.txt`):** signed jobs `agent_update 0.156.1` (12:21:24 UTC) →
|
||
`agent_config_update 0.156.1-step1` (12:35:37) → `agent_config_update 0.156.1` (12:50:14). Tester 1: agent 0.156.1 committed
|
||
12:24, bundle done 12:53 (probe 71/71); N100: agent 12:34 (it reports every 15 min), bundle done 13:04 (probe 71/71). On
|
||
both, real `sudo -l -U felhom-agent` allows the cleanup shape and refuses a finished archive (`.tar.zst`). The operator
|
||
cleared the per-customer floors (demo-hp and demo-felhom 12:00, Tester 1 12:44 UTC): every guest runs controller 0.310.0.
|
||
Both plugs ON: the HP relay back on by its countdown (12:07:02, `E/plugcut.log`), the N100's never cut; both hosts answer.
|