Review round 2, part 1 (batch 1): 01/03/04/05/06/08/12/13/_hub-review facts corrected against live code (each at file:line, EDITS.md); 20 capability rows marked re-check; poster timeline text; live-probes path; reviewer candidates and the H1/H2 proofs
gates / gates (push) Successful in 12m37s
gates / gates (push) Successful in 12m37s
This commit is contained in:
1 parent
44d1904286
commit
77015997f8
36 files changed
+886
-91
No files matched your search
@@ -0,0 +1,17 @@
|
||||
Sat Oct 24 12:46:20 UTC 2026
|
||||
gitea.dooplex.hu/admin/felhom-controller:0.342.0
|
||||
repo_url: https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git
|
||||
felhom-controller gitea.dooplex.hu/admin/felhom-controller:0.342.0 Up 4 hours (healthy)
|
||||
filebrowser gtstef/filebrowser:1.5.6-stable Up 21 hours (healthy)
|
||||
immich-machine-learning ghcr.io/immich-app/immich-machine-learning:v3.2.4 Up 12 hours (healthy)
|
||||
immich-postgres ghcr.io/immich-app/postgres:16-vectorchord0.4.3-pgvectors0.2.0 Up 12 hours (healthy)
|
||||
immich-redis redis:7-alpine Up 12 hours (healthy)
|
||||
immich-server ghcr.io/immich-app/immich-server:v3.2.4 Up 12 hours (healthy)
|
||||
paperless-postgres postgres:18-alpine Up 12 hours (healthy)
|
||||
paperless-redis redis:7-alpine Up 12 hours (healthy)
|
||||
paperless-webserver ghcr.io/paperless-ngx/paperless-ngx:2.20.15 Up 12 hours (healthy)
|
||||
traefik traefik:v3.7.13 Up 21 hours
|
||||
2628d11da1accbbb53a7fe9459b1a200 -
|
||||
3
|
||||
/dev/loop0 32G 2.7G 27G 10% /
|
||||
/dev/nvme0n1 938G 227G 664G 26% /mnt/felhom-drives/scratch_hdd
|
||||
@@ -0,0 +1,96 @@
|
||||
# Document edits — review round 2 (2026-10-24)
|
||||
|
||||
Every edit below corrects a FACT the main session read at the cited `file:line` before editing (the reviewer's item id
|
||||
in brackets; the candidate files are in `reviewers/`). Each edited place carries „corrected 2026-10-24" in the text.
|
||||
Rules, fences, „never" lines and promises were NOT edited — those are rows for the operator.
|
||||
|
||||
## 05-hub-architecture.md (DOC-C1)
|
||||
- [S5] „the live hub is v0.11.0" → written against v0.11.0; the deployed version is read from `manifests/hub.yaml` (`:135` = 0.169.0).
|
||||
- [S4] „hub 0.162.0 — not yet deployed" ×2, §15.4b „0.166.0 NOT deployed" → deployed (hub CHANGELOG headers v0.164–v0.169).
|
||||
- [C3] §11 „ADD … the slim DR record onto hosts" → struck through, retired (R-105, §9).
|
||||
- [S3] `autoMintSelfBindLink` four events → plus the fifth path (R-719 / R-1203) — `hub/internal/web/selfbind.go:384-400`.
|
||||
- [S12] „39 event types" → 44 (`mail.event.*` keys counted in `hub/internal/i18n/locales/hu.json`).
|
||||
- [S1/C1] §16.3 nightly 02:00, catch-up 24 h, push 02:30 → four slots, catch-up 6 h, push :30 after each, refuse > 7 h — `hub/internal/dbsnap/dbsnap.go:31,36`; `hub/cmd/hub/main.go:964-968`; `scripts/hub-db-backup/felhom-hub-db-backup.timer:7-10`.
|
||||
- [S2] §16.5 `scheduleDaily` list → the four slots; prune at its configured time — `hub/cmd/hub/main.go:648,859,968,972`.
|
||||
|
||||
## 08-alarm-ladder.md (DOC-C1)
|
||||
- [S11] info dropped before both legs → except the paired recoveries — `hub/internal/notify/dispatcher.go:137-167`.
|
||||
- [S7] operator leg „only … a 1-hour cooldown" → per-type cooldown (5 min / 6 h / 1 h), R-723 first-hour suppression — `dispatcher.go:597-640`.
|
||||
- [S9 — a dated note, the ruling line kept] backup_integrity_failed „default OFF" → superseded by operator Q1 2026-10-12: a default since controller v0.309.0 — `felhom-controller/controller/internal/settings/settings.go:713-721`.
|
||||
- [S8] the per-APP grain table → five more per-app types — `dispatcher.go:470-480`.
|
||||
- [S13] „a disabled box is still skipped" → also a box switched off on purpose (R-998) — `hub/internal/monitor/deadline.go:433-442`.
|
||||
- [S10] §8.1 „NOT BUILT" → slice 1 built in v0.309.0 — `felhom-controller/controller/internal/settings/notif_groups.go:1-16`.
|
||||
- [S6] §9 „NOT released" ×2 → released 2026-10-24 (controller CHANGELOG v0.342.0 header; `manifests/hub.yaml:135`).
|
||||
- [U6/U7, listed as facts — each already has a ruling] the operator-only box alarms wg_tunnel_stale, host_swap_short, bind_old_host_linked, golden_not_vouched, recovery_older_package — `dispatcher.go:777,782,862,866,870`; `hub/internal/api/host_swap.go:25-37`.
|
||||
- §6.5 header „hub main, unreleased" → released in hub v0.144.0 (hub CHANGELOG `:629` inside the v0.144.0 section).
|
||||
|
||||
## _hub-review.md (DOC-C1 S14)
|
||||
- A HISTORICAL banner (stale threshold 45 m by config — `manifests/hub.yaml:88`; cooldown per type; push retired). The body is kept as the record.
|
||||
|
||||
## felhom-system-poster.facts.md + .html (DOC-C1 C2) — text only (the poster rule §6: facts file and matching html text)
|
||||
- Timeline „02:30 hub database → ep0" → „… (and again at 08:30; every 6 h since D34)"; html „02:30 hub DB → ep0, 6-hourly".
|
||||
|
||||
## 03-host-agent.md (DOC-B)
|
||||
- [S1] cloudflared three states → four (`absent`, v0.161.0) — `felhom-agent/internal/hub/cloudflared.go:12-23`.
|
||||
- [S2] D4 „ready patch, NOT shipped" → shipped in v0.163.0 (`git tag --contains 1109092`) + installer-v1.36.0 (`manifests/webpage.yaml:347`).
|
||||
- [S3/S4] `a0c88e5`, `ae69ad4` „unreleased" → in v0.163.0 (`git tag --contains`).
|
||||
- [S5] old key `wg/private.key.prev` → first old key only; later `.prev.<unix>`, newest three; `wg/last-rekey` — `rekey.go:39,105-139`.
|
||||
- [S6] host_busy „Unreleased 56aabc7" → v0.161.0; plus the line-test holder — `internal/linetest/linetest.go:62`.
|
||||
- [S7] R-1010 „Unreleased 56aabc7" → v0.161.0.
|
||||
- [S8] local API „default :8443" → `listen_addr` required, no default — `internal/config/config.go:317-319`.
|
||||
- [S9] guest swap „once a minute after start" → once, one minute after start — `cmd/felhom-agent/main.go:940-955`.
|
||||
- NOT edited (RULE → row): S10 break-glass „via the Cloudflare Tunnel behind Access".
|
||||
|
||||
## 06-offsite-connectivity.md (DOC-B) — dated notes; the design-of-record text kept
|
||||
- [S11] „no slice is implemented yet" → note: S1–S4 shipped.
|
||||
- [S12] sudoers `install … *.mount` → note: `felhom-priv-apply wg` — `felhom-agent/configs/felhom-agent.sudoers:223-229`.
|
||||
- [S14] IdentityBundle `{TunnelToken, PBSToken}` → four fields — `felhom-agent/internal/escrow/identity.go:30-40`.
|
||||
- [S15] the only automatic re-registration → plus D30 — `rekey.go`.
|
||||
- [S16] „No new alarm channel" → `wg_tunnel_stale/ok` — `hub/internal/api/host_tunnel.go`.
|
||||
- [S17] key rotation „design when fleet size warrants" → note: the D30 rotation exists.
|
||||
- [S18] cloudflared „seam" → note: a guest container the agent only reads; withdrawing the plan is the operator's (row).
|
||||
- NOT edited (RULE → row): S19 „no inbound listener … box→PBS only".
|
||||
|
||||
## 12-agent-tooling.md (DOC-B)
|
||||
- [S20] „five byte-identical copies" → measured not identical; aligned (below).
|
||||
- [S21] rules load „when a matching path is touched" → `unconditional: true` / no `paths:` load always — `felhom.eu/.claude/rules/live-probes.md:2`.
|
||||
- [S22] skills „live at once … the list is the folder" → a NEW skill needs `install_skills.py` again (`felhom-web-design` has no link; not installed — no DooPlex change in this run).
|
||||
|
||||
## Instruction files (unprompted-work §5: a stale fact corrected / a proved fact added — named here)
|
||||
- `felhom-agent/.claude/rules/unprompted-work.md`, `app-catalog-felhom.eu/.claude/rules/unprompted-work.md`, workspace root
|
||||
`.claude/rules/unprompted-work.md` (unversioned): the missing item-6 paragraph „A version number is pushed once …"
|
||||
added (before: 95 lines, md5 3b071ad2; after: byte-identical to the felhom.eu and controller copies, md5 b1891bd4).
|
||||
Why: the file's own header says „change all five or none" and two copies had drifted; the paragraph states a fact
|
||||
(`felhom-controller/controller/build.sh:115-125` refuses an existing tag and an unreachable registry).
|
||||
- `felhom.eu/.claude/rules/live-probes.md:41`: before „no `/opt/felhom/stacks/<name>`"; after „no
|
||||
`/opt/docker/stacks/<name>/app.yaml` (the folder itself stays — it holds the catalog's template files)". Why: the path
|
||||
is `/opt/docker/stacks` (`felhom-controller/controller/internal/config/config.go:393`) and the folder stays after a
|
||||
clean removal (the catalog's `box_walk.remove` checks `app.yaml` since 2026-10-24).
|
||||
|
||||
## 01-topology-and-trust.md (DOC-A)
|
||||
- [S7] `docs/proxmox-platform.md` → `documentation/proxmox-platform.md` (the file exists; `docs/` does not).
|
||||
- [S6] „every /api/ route needs a dashboard session" → but the read-only `/api/health` — `felhom-controller/controller/cmd/controller/main.go:4236`.
|
||||
- [S1] claim mail link `templates.go:27` → `FormatClaimEmail` (`hub/internal/notify/templates.go:250-252`).
|
||||
- [S2] the retrieval code „single-use … TTL" → a dated note: not burned, no TTL; D19/R-1159 rule — `hub/internal/api/passphrase_scope.go:19-30`.
|
||||
- [S3] Cloudflare token reach check „hub main, unreleased" → released in hub v0.144.0.
|
||||
- [S4] dead pointer `07:296-299` → a dated note.
|
||||
- [S5] snapshot-before-deploy → [DESIGN — not built]: the agent serves `/snapshot`, `/rollback`; no controller caller (grep of `internal/agentapi`).
|
||||
- NOT edited (RULE/promise → rows): C3 „the hub never connects into a box / no inbound control endpoint / no standing operator backdoor" vs poke + felhom-sshd; C4 „the operator cannot open the off-site backup" vs R-985.
|
||||
|
||||
## 04-control-plane-authorization.md (DOC-A)
|
||||
- [S8] `docs/tests/…`, `docs/architecture/…` → `documentation/…`.
|
||||
- [S10] time window → the 2-minute skew on `issued_at` — `felhom-agent/internal/authz/verifier.go:19-22,148-155`.
|
||||
- [U2 → DESIGN — undocumented note + row] no cap on the window's length; opsign default 30 min — `felhom-agent/cmd/felhom-opsign/main.go:57`.
|
||||
- [S9] §6.1 „controller + hub main, unreleased" → released in controller v0.304.0 + hub v0.144.0 (both CHANGELOGs).
|
||||
|
||||
## 13-threat-model.md (DOC-A) — facts corrected; every verdict cell LEFT AS IT WAS and marked „re-check" (a verdict is a judgement)
|
||||
- [S11] „38 local-API routes" → 41 on main (`felhom-agent/internal/localapi/server.go`).
|
||||
- [S12] hub routes → 54 API entries; `/bind/<token>` added to the no-credential list.
|
||||
- [S13] homepage socket (R-981) → gone; the allow-list empty — catalog `templates/homepage/docker-compose.yml:21`; `privpolicy/allowlist.json:4`.
|
||||
- [S14] R-983 / R-976 → fixed (controller v0.324.0, `internal/web/auth.go:352-366`).
|
||||
- [S15] R-973 / R-978 → narrowed in agent v0.160.0 (`git tag --contains f17e197 / 8561dc8`).
|
||||
- [S16] tokens unsealed (R-975) → sealed since hub 0.168.0 (deployed).
|
||||
- [S17] root password SSH (R-982) → the drop-in shipped in agent v0.163.0.
|
||||
- [S18] R-970 „does not hold" → the routes are removed, pinned by `r970_no_hub_key_door_test.go`.
|
||||
- [S19] R-971 „design gap" → `privpolicy.Admit` before every write (controller v0.328.0).
|
||||
- [S20] „hub 0.168.0 deploy pending", „R-1244 not released" → deployed / released 2026-10-24.
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package backup
|
||||
|
||||
// REVIEW ROUND 2 (2026-10-24), HUNT-1 H1 — an EVIDENCE test kept in the audit, NOT committed to the repo.
|
||||
// A restore of a DRIVE-backed app is cut after its undo copy (kept on the app's drive) is recorded. At the next
|
||||
// start the drive is not bound yet: Recover finds the copy „gone", the gated start refuses (drive absent), the
|
||||
// marker and its Undo are KEPT. The drive then binds and the app runs (the boot recreate checks only „held") and the
|
||||
// household writes. At the NEXT controller start Recover finds the copy present and pours it back.
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type recordingPutBacker struct{ calls []string }
|
||||
|
||||
func (p *recordingPutBacker) PutBackInterruptedRestore(stack, dir string) error {
|
||||
p.calls = append(p.calls, stack+"@"+dir)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestReview2H1_DriveBackedCutRestore_PutBackRunsAfterTheAppAlreadyRan(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
marker := filepath.Join(root, "appstop-state.json")
|
||||
drive := filepath.Join(root, "drive")
|
||||
undo := filepath.Join(drive, "ns", "backups", "restore-undo", "immich")
|
||||
if err := os.MkdirAll(undo, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
quiet := log.New(io.Discard, "", 0)
|
||||
|
||||
// the restore: Begin, the copy recorded, then the power cut (no End)
|
||||
g0 := NewAppStopGuard(marker, quiet)
|
||||
if err := g0.Begin("restore-1", ReasonUnitRestore, []string{"immich"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := g0.SetUndo("immich", undo); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// boot 1: the drive is not bound yet (its path is absent in the controller's view)
|
||||
if err := os.Rename(drive, drive+".unbound"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pb1 := &recordingPutBacker{}
|
||||
g1 := NewAppStopGuard(marker, quiet)
|
||||
g1.SetStarter(&gatingStarter{refuse: map[string]string{"immich": "the data drive is not present"}})
|
||||
g1.SetPutBacker(pb1)
|
||||
r1 := g1.Recover()
|
||||
t.Logf("boot 1: put-backs=%v refused=%v failed=%v restarted=%v marker kept=%v", pb1.calls, r1.Refused, r1.Failed, r1.Restarted, r2Exists(marker))
|
||||
if len(pb1.calls) != 0 || len(r1.Refused) != 1 || !r2Exists(marker) {
|
||||
t.Fatalf("boot 1 did not take the copy-gone, refused, marker-kept path")
|
||||
}
|
||||
|
||||
// the drive binds; the boot recreate starts the app (it checks only appHeld) and the household writes
|
||||
if err := os.Rename(drive+".unbound", drive); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
householdWrite := filepath.Join(drive, "ns", "new-photo-after-boot")
|
||||
if err := os.WriteFile(householdWrite, []byte("x"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// boot 2 (any controller restart, e.g. the nightly self-update): the marker is still active
|
||||
pb2 := &recordingPutBacker{}
|
||||
g2 := NewAppStopGuard(marker, quiet)
|
||||
g2.SetStarter(&fakeStarter{})
|
||||
g2.SetPutBacker(pb2)
|
||||
r2 := g2.Recover()
|
||||
t.Logf("boot 2: put-backs=%v restarted=%v marker kept=%v", pb2.calls, r2.Restarted, r2Exists(marker))
|
||||
if len(pb2.calls) == 1 {
|
||||
t.Logf("DEFECT SHOWN: the copy taken BEFORE the restore is poured back AFTER the app already ran on the drive and the household wrote (%s)", householdWrite)
|
||||
} else {
|
||||
t.Fatalf("no put-back at boot 2 — the chain does not hold as described")
|
||||
}
|
||||
}
|
||||
|
||||
func r2Exists(p string) bool { _, err := os.Stat(p); return err == nil }
|
||||
@@ -0,0 +1,8 @@
|
||||
controller main 321df1f, lean clone, go test -run TestReview2H1 -v
|
||||
=== RUN TestReview2H1_DriveBackedCutRestore_PutBackRunsAfterTheAppAlreadyRan
|
||||
review2_h1_drive_crash_test.go:52: boot 1: put-backs=[] refused=[immich] failed=[] restarted=[] marker kept=true
|
||||
review2_h1_drive_crash_test.go:72: boot 2: put-backs=[immich@<tmp>/drive/ns/backups/restore-undo/immich] restarted=[immich] marker kept=false
|
||||
review2_h1_drive_crash_test.go:74: DEFECT SHOWN: the copy taken BEFORE the restore is poured back AFTER the app already ran on the drive and the household wrote (<tmp>/drive/ns/new-photo-after-boot)
|
||||
--- PASS: TestReview2H1_DriveBackedCutRestore_PutBackRunsAfterTheAppAlreadyRan (0.00s)
|
||||
PASS
|
||||
ok gitea.dooplex.hu/admin/felhom-controller/internal/backup 0.006s
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
14:57:09 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
|
||||
14:57:10 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
|
||||
14:57:40 [1] deployed, controller state=running, pinned={'audiobookshelf': 'ghcr.io/advplyr/audiobookshelf:2.37.1'}
|
||||
deploy: True
|
||||
-rw-r--r-- 1 root 1000 46 Oct 24 12:57 /mnt/felhom-drives/scratch_hdd/userdata/media/audiobooks/review2-h1-marker.txt
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
night chain -> 202 {'data': {'legs': ['db-dump', 'tier2', 'update-leg']}, 'message': 'started', 'ok': True}
|
||||
2026/10/24 12:57:55 night_chain.go:108: [INFO] [night-chain] manual run started from 172.18.0.4:40216: [db-dump tier2 update-leg]
|
||||
2026/10/24 12:57:55 night_chain.go:84: [INFO] [night-chain] manual run of tonight's chain: dump → second drive → off-site → update leg
|
||||
2026/10/24 12:57:55 night_chain.go:77: [INFO] [night-chain] db-dump: started
|
||||
2026/10/24 12:58:50 night_chain.go:82: [INFO] [night-chain] db-dump: done in 55s
|
||||
2026/10/24 12:58:50 night_chain.go:77: [INFO] [night-chain] tier2: started
|
||||
2026/10/24 12:58:52 tier2.go:507: [INFO] [backup] Tier 2 copied audiobookshelf → /mnt/sizing-bench/backups/secondary/audiobookshelf (484.0 KB, 1 leg(s), 2s)
|
||||
2026/10/24 12:59:11 tier2.go:507: [INFO] [backup] Tier 2 copied immich → /mnt/felhom-drives/scratch_hdd/backups/secondary/immich (1.1 GB, 2 leg(s), 19s)
|
||||
2026/10/24 12:59:14 tier2.go:507: [INFO] [backup] Tier 2 copied paperless-ngx → /mnt/sizing-bench/backups/secondary/paperless-ngx (128.2 MB, 1 leg(s), 3s)
|
||||
2026/10/24 12:59:15 tier2.go:507: [INFO] [backup] Tier 2 copied privatebin → /mnt/felhom-drives/scratch_hdd/backups/secondary/privatebin (8.4 KB, 0 leg(s), 1s)
|
||||
2026/10/24 12:59:15 tier2.go:569: [INFO] [backup] Tier 2 run complete: 4 app(s) processed (incl. volume-only — F6)
|
||||
2026/10/24 12:59:15 night_chain.go:82: [INFO] [night-chain] tier2: done in 24s
|
||||
2026/10/24 12:59:15 night_chain.go:90: [INFO] [night-chain] offsite: no off-site target on this box — skipped, as at night
|
||||
2026/10/24 12:59:15 night_chain.go:77: [INFO] [night-chain] update-leg: started
|
||||
2026/10/24 12:59:15 night_chain.go:82: [INFO] [night-chain] update-leg: done in 0s
|
||||
2026/10/24 12:59:15 night_chain.go:93: [INFO] [night-chain] finished in 1m20s
|
||||
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/.felhom-tier2-layout
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/compose
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/compose/.felhom.yml
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/compose/app.yaml
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/compose/docker-compose.yml
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/data-stamps.json
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/manifest.json
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/volume-dumps
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/volume-dumps/audiobookshelf_audiobookshelf_config.tar
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/volume-dumps/audiobookshelf_audiobookshelf_metadata.tar
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/userdata
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/userdata/media
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/userdata/media/audiobooks
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/userdata/media/audiobooks/review2-h1-marker.txt
|
||||
|
||||
total 12
|
||||
drwxrwsr-x 2 root 1000 4096 Oct 24 12:57 .
|
||||
drwxr-xr-x 3 root root 4096 Oct 24 12:58 ..
|
||||
-rw-r--r-- 1 root 1000 46 Oct 24 12:57 review2-h1-marker.txt
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
night chain -> 202 {'data': {'legs': ['db-dump', 'tier2', 'update-leg']}, 'message': 'started', 'ok': True}
|
||||
2026/10/24 12:59:38 night_chain.go:108: [INFO] [night-chain] manual run started from 172.18.0.4:40216: [db-dump tier2 update-leg]
|
||||
2026/10/24 12:59:38 night_chain.go:84: [INFO] [night-chain] manual run of tonight's chain: dump → second drive → off-site → update leg
|
||||
2026/10/24 12:59:38 night_chain.go:77: [INFO] [night-chain] db-dump: started
|
||||
2026/10/24 13:00:40 night_chain.go:82: [INFO] [night-chain] db-dump: done in 1m2s
|
||||
2026/10/24 13:00:40 night_chain.go:77: [INFO] [night-chain] tier2: started
|
||||
2026/10/24 13:00:41 tier2.go:507: [INFO] [backup] Tier 2 copied audiobookshelf → /mnt/sizing-bench/backups/secondary/audiobookshelf (487.0 KB, 1 leg(s), 1s)
|
||||
2026/10/24 13:00:56 tier2.go:507: [INFO] [backup] Tier 2 copied immich → /mnt/felhom-drives/scratch_hdd/backups/secondary/immich (1.1 GB, 2 leg(s), 15s)
|
||||
2026/10/24 13:00:58 tier2.go:507: [INFO] [backup] Tier 2 copied paperless-ngx → /mnt/sizing-bench/backups/secondary/paperless-ngx (128.2 MB, 1 leg(s), 2s)
|
||||
2026/10/24 13:00:59 tier2.go:507: [INFO] [backup] Tier 2 copied privatebin → /mnt/felhom-drives/scratch_hdd/backups/secondary/privatebin (8.4 KB, 0 leg(s), 1s)
|
||||
2026/10/24 13:00:59 tier2.go:569: [INFO] [backup] Tier 2 run complete: 4 app(s) processed (incl. volume-only — F6)
|
||||
2026/10/24 13:00:59 night_chain.go:82: [INFO] [night-chain] tier2: done in 19s
|
||||
2026/10/24 13:00:59 night_chain.go:90: [INFO] [night-chain] offsite: no off-site target on this box — skipped, as at night
|
||||
2026/10/24 13:00:59 night_chain.go:77: [INFO] [night-chain] update-leg: started
|
||||
2026/10/24 13:00:59 night_chain.go:82: [INFO] [night-chain] update-leg: done in 0s
|
||||
2026/10/24 13:00:59 night_chain.go:93: [INFO] [night-chain] finished in 1m21s
|
||||
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/.felhom-tier2-layout
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/compose
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/compose/.felhom.yml
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/compose/app.yaml
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/compose/docker-compose.yml
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/data-stamps.json
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/manifest.json
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/volume-dumps
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/volume-dumps/audiobookshelf_audiobookshelf_config.tar
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/volume-dumps/audiobookshelf_audiobookshelf_metadata.tar
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/userdata
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/userdata/media
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/userdata/media/audiobooks
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/userdata/media/audiobooks/review2-h1-marker.txt
|
||||
|
||||
total 12
|
||||
drwxrwsr-x 2 root 1000 4096 Oct 24 12:57 .
|
||||
drwxr-xr-x 3 root root 4096 Oct 24 12:58 ..
|
||||
-rw-r--r-- 1 root 1000 46 Oct 24 12:57 review2-h1-marker.txt
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
28- # If Viktor rules excluded, flip THIS LINE ONLY before committing.
|
||||
29: - path: media/review2-typo
|
||||
30- class: optional # REVIEW-2 H1 PROOF: a path no compose bind has
|
||||
|
||||
night chain -> 202 {'data': {'legs': ['db-dump', 'tier2', 'update-leg']}, 'message': 'started', 'ok': True}
|
||||
2026/10/24 13:01:28 night_chain.go:108: [INFO] [night-chain] manual run started from 172.18.0.4:40216: [db-dump tier2 update-leg]
|
||||
2026/10/24 13:01:28 night_chain.go:84: [INFO] [night-chain] manual run of tonight's chain: dump → second drive → off-site → update leg
|
||||
2026/10/24 13:01:28 night_chain.go:77: [INFO] [night-chain] db-dump: started
|
||||
2026/10/24 13:01:30 [ERROR] [stacks] .felhom.yml backup block rejected in /opt/docker/stacks/audiobookshelf: userdata["media/review2-typo"]: matches no compose bind (stale or typoed path)
|
||||
2026/10/24 13:01:31 [ERROR] [stacks] .felhom.yml backup block rejected in /opt/docker/stacks/audiobookshelf: userdata["media/review2-typo"]: matches no compose bind (stale or typoed path)
|
||||
2026/10/24 13:01:31 [ERROR] [stacks] .felhom.yml backup block rejected in /opt/docker/stacks/audiobookshelf: userdata["media/review2-typo"]: matches no compose bind (stale or typoed path)
|
||||
2026/10/24 13:01:32 [ERROR] [stacks] .felhom.yml backup block rejected in /opt/docker/stacks/audiobookshelf: userdata["media/review2-typo"]: matches no compose bind (stale or typoed path)
|
||||
2026/10/24 13:01:32 [ERROR] [stacks] .felhom.yml backup block rejected in /opt/docker/stacks/audiobookshelf: userdata["media/review2-typo"]: matches no compose bind (stale or typoed path)
|
||||
2026/10/24 13:01:32 [ERROR] [stacks] .felhom.yml backup block rejected in /opt/docker/stacks/audiobookshelf: userdata["media/review2-typo"]: matches no compose bind (stale or typoed path)
|
||||
2026/10/24 13:01:32 [ERROR] [stacks] .felhom.yml backup block rejected in /opt/docker/stacks/audiobookshelf: userdata["media/review2-typo"]: matches no compose bind (stale or typoed path)
|
||||
2026/10/24 13:01:59 [ERROR] [stacks] .felhom.yml backup block rejected in /opt/docker/stacks/audiobookshelf: userdata["media/review2-typo"]: matches no compose bind (stale or typoed path)
|
||||
2026/10/24 13:02:24 [ERROR] [stacks] .felhom.yml backup block rejected in /opt/docker/stacks/audiobookshelf: userdata["media/review2-typo"]: matches no compose bind (stale or typoed path)
|
||||
2026/10/24 13:02:24 night_chain.go:82: [INFO] [night-chain] db-dump: done in 57s
|
||||
2026/10/24 13:02:24 night_chain.go:77: [INFO] [night-chain] tier2: started
|
||||
2026/10/24 13:02:24 [ERROR] [stacks] .felhom.yml backup block rejected in /opt/docker/stacks/audiobookshelf: userdata["media/review2-typo"]: matches no compose bind (stale or typoed path)
|
||||
2026/10/24 13:02:25 tier2.go:329: [INFO] [backup] Tier 2 reconcile: removed stale dest dir userdata/media
|
||||
2026/10/24 13:02:25 tier2.go:507: [INFO] [backup] Tier 2 copied audiobookshelf → /mnt/sizing-bench/backups/secondary/audiobookshelf (489.9 KB, 0 leg(s), 1s)
|
||||
2026/10/24 13:02:46 tier2.go:507: [INFO] [backup] Tier 2 copied immich → /mnt/felhom-drives/scratch_hdd/backups/secondary/immich (1.1 GB, 2 leg(s), 20s)
|
||||
2026/10/24 13:02:49 tier2.go:507: [INFO] [backup] Tier 2 copied paperless-ngx → /mnt/sizing-bench/backups/secondary/paperless-ngx (128.2 MB, 1 leg(s), 3s)
|
||||
2026/10/24 13:02:50 tier2.go:507: [INFO] [backup] Tier 2 copied privatebin → /mnt/felhom-drives/scratch_hdd/backups/secondary/privatebin (8.4 KB, 0 leg(s), 0s)
|
||||
2026/10/24 13:02:50 tier2.go:569: [INFO] [backup] Tier 2 run complete: 4 app(s) processed (incl. volume-only — F6)
|
||||
2026/10/24 13:02:50 night_chain.go:82: [INFO] [night-chain] tier2: done in 25s
|
||||
2026/10/24 13:02:50 night_chain.go:90: [INFO] [night-chain] offsite: no off-site target on this box — skipped, as at night
|
||||
2026/10/24 13:02:50 night_chain.go:77: [INFO] [night-chain] update-leg: started
|
||||
2026/10/24 13:02:50 [ERROR] [stacks] .felhom.yml backup block rejected in /opt/docker/stacks/audiobookshelf: userdata["media/review2-typo"]: matches no compose bind (stale or typoed path)
|
||||
2026/10/24 13:02:50 night_chain.go:82: [INFO] [night-chain] update-leg: done in 0s
|
||||
2026/10/24 13:02:50 night_chain.go:93: [INFO] [night-chain] finished in 1m22s
|
||||
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/.felhom-tier2-layout
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/compose
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/compose/.felhom.yml
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/compose/app.yaml
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/compose/docker-compose.yml
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/data-stamps.json
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/manifest.json
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/volume-dumps
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/volume-dumps/audiobookshelf_audiobookshelf_config.tar
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/recovery-unit/volume-dumps/audiobookshelf_audiobookshelf_metadata.tar
|
||||
/mnt/sizing-bench/backups/secondary/audiobookshelf/userdata
|
||||
|
||||
ls: cannot access '/mnt/sizing-bench/backups/secondary/audiobookshelf/userdata/media/audiobooks/': No such file or directory
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
0
|
||||
restored
|
||||
|
||||
stop ('200', {'ok': True, 'message': 'Stack audiobookshelf stop completed'})
|
||||
remove ('200', {'ok': True, 'data': {'removed': 'audiobookshelf', 'volumes_removed': ['audiobookshelf_audiobookshelf_config', 'audiobookshelf_audiobookshelf_metadata'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'userdata_kept': ['/mnt/felhom-drives/scratch_hdd/userdata/media/audiobooks (8.0K)', '/mnt/felhom-drives/scratch_hdd/userdata/media/podcasts (4.0K)'], 'backup_paths_removed': ['/mnt/felhom-drives/scratch_hdd/backups/primary/audiobookshelf (520K)', '/mnt/sizing-bench/backups/secondary/audiobookshelf (489.9 KB)'], 'verified': True}, 'message': 'Stack audiobookshelf removed'})
|
||||
total 8
|
||||
drwxrwsr-x 2 root 1000 4096 Oct 24 13:04 .
|
||||
drwxrwsr-x 16 root 1000 4096 Oct 13 19:26 ..
|
||||
paperless-ngx
|
||||
docker-compose.yml
|
||||
0
|
||||
0
|
||||
ls: cannot access '/mnt/felhom-drives/scratch_hdd/appdata/audiobookshelf': No such file or directory
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# DOC-A — 01 topology & trust, 04 control-plane authorization, 13 threat model (reviewer candidates; verified by the main session)
|
||||
|
||||
Reviewer: read-only subagent, 2026-10-24. Counts: claims 62 · true 33 · stale 20 · untestable 6 · undocumented 4 · contradictions 4.
|
||||
|
||||
## STALE (all FACT)
|
||||
- S1 01:135 claim mail link `templates.go:27` → :252
|
||||
- S2 01:220-223 passphrase „single-use (burned on the successful config fetch) plus a short TTL" → not burned; keys until a host exists and a controller ≥ 0.334.0 reported, then 409 (R-1159, D19) — hub/internal/api/passphrase_scope.go:19-29,46-66
|
||||
- S3 01:256 Cloudflare reach check „hub main, unreleased" → released v0.144.0
|
||||
- S4 01:315-316 pointer `07:296-299` „40 of 53 … all-hot" → dead pointer
|
||||
- S5 01:345-346 snapshot-before-deploy / rollback → agent serves /snapshot,/rollback; no controller caller — [DESIGN], not built
|
||||
- S6 01:108 „every /api/ route needs a dashboard session" → /api/health is open (read-only) — cmd/controller/main.go:4236
|
||||
- S7 01:19 `docs/proxmox-platform.md` → `documentation/proxmox-platform.md`
|
||||
- S8 04:18-20 `docs/tests/…`, `docs/architecture/…` → `documentation/…`
|
||||
- S9 04:169 operator actions „unreleased" → hub v0.144.0, controller v0.304.0
|
||||
- S10 04:79 „now ∈ [issued_at, expires_at]" → 2-minute skew allowance — felhom-agent/internal/authz/verifier.go:21,148-155; configs/felhom-os-apply:658
|
||||
- S11 13:51 „38 local-API routes" → 41 — felhom-agent/internal/localapi/server.go:562-634
|
||||
- S12 13:50 hub route counts / no-credential list → 54 API routes; the public `/bind/<token>` page missing from the list
|
||||
- S13 13:61 homepage socket (R-981) → gone; allow-list empty
|
||||
- S14 13:64 R-983 gaps, R-976 family sessions survive a reset → both fixed (auth.go:352-366)
|
||||
- S15 13:65 R-973 flush, R-978 void the code → cadence cap; package uploaded only at the claim
|
||||
- S16 13:73 tokens unsealed (R-975) → sealed `enc:v1:` (hub 0.168.0, deployed)
|
||||
- S17 13:76 root password SSH from the LAN (R-982) → the bundle's key-only drop-in
|
||||
- S18 13:77 hub-key route can replace settings (R-970) → removed; pinned by cmd/controller/r970_no_hub_key_door_test.go
|
||||
- S19 13:78 no box check on a catalog push (R-971) → privpolicy.Admit
|
||||
- S20 13:79 hub 0.168.0 „deploy pending", R-1244 „not released" → deployed / shipped
|
||||
|
||||
## UNDOCUMENTED
|
||||
- U1 FileBrowser SSO trusts a proxy header; the header NAME is the per-box secret; any app container reaches filebrowser:80 — controller internal/infra/filebrowser_sso.go:15-22; internal/web/files_sso.go:20-44 — 01 §5, 13 §5
|
||||
- U2 a signed op's window has no maximum length (opsign default TTL 30 m) — felhom-agent/internal/authz/verifier.go:148-155 — 04 §2
|
||||
- U3 „poke": the hub reaches into a box (SSH to ep0 → unauthenticated UDP to the box's WG /32:51822) — hub/internal/poke/client.go:1-16; agent poke.go:35 — 01 §4/§5
|
||||
- U4 the guest can start a ~55 s line test on request (R-1250) — agent internal/localapi/linetest.go:67-73
|
||||
|
||||
## CONTRADICTIONS
|
||||
- C1 01:105-109 R-970 closed vs 13:77 „does not hold" — code: closed
|
||||
- C2 04:195 sessions end on a reset vs 13:64 „family sessions survive" — code: end
|
||||
- C3 01:26-27,102,244-245,353 „the hub never connects into a box / no inbound control endpoint / no standing operator backdoor" vs 06:279-283 operator WG peer reaches felhom-sshd; 13:51 poke + felhom-sshd — RULE (a „never" line, a product value) → OPERATOR
|
||||
- C4 01:328-329,352 the operator „can't open / cannot read" the off-site backup vs 13:71 + R-985 (a live box lets the operator reach the key) — RULE (a customer promise) → OPERATOR
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
# DOC-B — 03 host agent, 06 off-site connectivity, 12 agent tooling (reviewer candidates; verified by the main session before acting)
|
||||
|
||||
Reviewer: read-only subagent, 2026-10-24. Counts: claims 79 · true 45 · stale 22 · untestable 6 · undocumented 8 · contradictions 6.
|
||||
`A/` = felhom-agent/.
|
||||
|
||||
## STALE
|
||||
- S1 03:50,226 cloudflared three states → four (`absent`, R-1070, v0.161.0) — A/internal/hub/cloudflared.go:12-23 — FACT
|
||||
- S2 03:132 D4 drop-in „ready patch, NOT shipped" → shipped (agent v0.163.0 `1109092`, installer-v1.36.0) — A/configs/felhom-os-apply:189,230; manifests/webpage.yaml:347 — FACT
|
||||
- S3 03:134 `a0c88e5` „unreleased" → in v0.163.0 — FACT
|
||||
- S4 03:136 `ae69ad4` „unreleased" → in v0.163.0 — FACT
|
||||
- S5 03:137-138 old key `wg/private.key.prev` → only the first; later `private.key.prev.<unix>`, newest 3; bound in `wg/last-rekey` — A/internal/wgtunnel/rekey.go:107,137-139,161-173 — FACT
|
||||
- S6 03:252-254 host_busy „Unreleased 56aabc7" → in v0.161.0; holder `line-test` too — A/internal/linetest/linetest.go:62 — FACT
|
||||
- S7 03:423-424 R-1010 „Unreleased 56aabc7" → in v0.161.0 — FACT
|
||||
- S8 03:316-317 local API „default :8443" → no default, `listen_addr` required; island 169.254.253.1:8443 — A/internal/config/config.go:251,260-269,319 — FACT
|
||||
- S9 03:680 „once a minute after agent start" → ONCE, one minute after start — A/cmd/felhom-agent/main.go:940-955 — FACT
|
||||
- S10 03:236-237 break-glass „via the Cloudflare Tunnel behind Access" → felhom-sshd over wg-felhom — A/internal/felhomsshd/claim.go:7 — RULE
|
||||
- S11 06:19-21 „no slice is implemented yet" → S1–S4 shipped — FACT
|
||||
- S12 06:69,142-143 sudoers `install … *.mount` → R-861 removed; `felhom-priv-apply wg` — A/configs/felhom-agent.sudoers:15-20,227-233 — FACT
|
||||
- S13 06:71 report.go line citations moved (:628, :659-666) — FACT
|
||||
- S14 06:172 IdentityBundle `{TunnelToken, PBSToken}` → 4 fields incl. ResticRepoPassword — A/internal/escrow/identity.go:30-40 — FACT
|
||||
- S15 06:201-202 „only automatic re-registration is the pubkey-mismatch path" → D30 too — rekey.go:88-102 — FACT
|
||||
- S16 06:286-288 „No new alarm channel" → hub wg_tunnel_stale/ok — hub/internal/api/host_tunnel.go:19-47 — FACT
|
||||
- S17 06:357-359 „key rotation … design when fleet size warrants" → D30 rotation exists — FACT
|
||||
- S18 06:381-383 cloudflared.go „seam" → reads the guest container; no seam — FACT
|
||||
- S19 06:86,325-329 „No inbound listener … box→PBS only" → poke UDP 51822 on the WG /32, felhom-sshd, operator /32, iperf3 :5201 — A/internal/poke/poke.go:35,133 — RULE
|
||||
- S20 12:23-24 „five byte-identical copies" → two copies carry an extra paragraph (b6638ac4 / 48d8674) — FACT (aligning them is rule text → operator)
|
||||
- S21 12:32 rules load „when a matching path is touched" → some always load (`unconditional: true`, no `paths:`) — FACT
|
||||
- S22 12:40-43 skills „live at once … the list is the folder" → a NEW skill needs install_skills.py again (felhom-web-design not linked) — FACT
|
||||
|
||||
## UNDOCUMENTED
|
||||
- U1 03 §6 lists ~17 of 41 local-API routes — A/internal/localapi/server.go:562-634
|
||||
- U2 the controller can start a HOST action: POST /host/line-test/run — server.go:579-580; linetest.go:346
|
||||
- U3 local API over the R-50 island bridge — config.go:260-269
|
||||
- U4 WG block carries oob_peer_ip, oob_operator_ssh_key — A/internal/hub/report.go:752,758
|
||||
- U5 D30: re-key only inside Watchdog; `futureSince` in memory (a restart restarts the 15 min) — manager.go:621-630; rekey.go:45-80
|
||||
- U6 poke binds UDP 51822 on the WG /32 only — poke.go:32-35,133
|
||||
- U7 the GRANDFATHERED skill has no size cap (240 lines vs R-394's 179) — scripts/check_skills.py:45-47,98-101
|
||||
- U8 line-test state `<oob state dir>/line-test.json`, switch `line_test.disable` — config.go:37; linetest.go:89
|
||||
|
||||
## CONTRADICTIONS
|
||||
- C1 03:67,815 agent manages the tunnel service vs 03:73 cloudflared is a guest container — code: 03:73
|
||||
- C2 06:381-383 cloudflared will adopt agent-managed pattern vs 03:73 — code: 03:73
|
||||
- C3 06:325-333 no inbound listener vs 06:273-285 felhom-sshd over the tunnel — code: 06:273-285
|
||||
- C4 03:234-238 break-glass over Cloudflare vs 06:280-285 over WG — code: 06
|
||||
- C5 13:51 „38 local-API routes" vs 03 §6 ~17 — code: 41 on main
|
||||
- C6 05:459 „0.166.0 NOT deployed" vs hub CHANGELOG v0.167.0 deployed
|
||||
|
||||
## UNTESTABLE
|
||||
ep0:5201 never on the internet (nft lives on ep0); PBS sync/verify schedule (DooPlex job config); WG enabled default vs „production".
|
||||
@@ -0,0 +1,38 @@
|
||||
# DOC-C1 — 05 hub, 08 alarm ladder, _hub-review (reviewer candidates, read-only; the main session verified before acting)
|
||||
|
||||
Reviewer: read-only subagent, 2026-10-24. Counts: claims 52 · true 31 · stale 14 · untestable 5 · undocumented 7 · contradictions 4.
|
||||
Prefixes: `H/` = felhom.eu/hub/, `C/` = felhom-controller/controller/, `F/` = felhom.eu/ (felhom.eu main 44d19042, controller main 321df1f).
|
||||
|
||||
## STALE
|
||||
- S1 05:585,590-592 „Every night at 02:00 … older than 24 h … picks up at 02:30" → 02/08/14/20, catch-up 6 h, push :30 after each, refuse > 7 h (D34) — H/internal/dbsnap/dbsnap.go:32,36; H/cmd/hub/main.go:964-968; F/scripts/hub-db-backup/felhom-hub-db-backup.timer:7-10 — FACT
|
||||
- S2 05:614 „scheduleDaily: db-snapshot 02:00, prune, …" → four db-snapshot slots; prune at the configured schedule (04:30) — H/cmd/hub/main.go:648,859,967-972 — FACT
|
||||
- S3 05:353 „autoMintSelfBindLink runs on four events" → a fifth: the household's „new link" on a used/expired link (R-719, ≤1/h/customer); old host still linked → „oldbox" page + operator told — H/internal/web/selfbind.go:382,395-436 — FACT
|
||||
- S4 05:94,142,459 version literals „hub 0.162.0 … not yet deployed", „0.166.0 built NOT deployed" → the manifest deploys 0.169.0 — F/manifests/hub.yaml:135 — FACT
|
||||
- S5 05:22-26 „the live hub is v0.11.0" → version literal, false — F/manifests/hub.yaml:135 — FACT
|
||||
- S6 08:535,583 „controller v0.342.0 + hub v0.169.0, NOT released" → hub half deployed (hub CHANGELOG:1) — FACT
|
||||
- S7 08:158 „processOperator consults only operatorOn, the address and a 1-hour cooldown" → per-type cooldown (5 min liveness, 6 h storage fill, else 1 h), R-723 first-hour skip, mail hold — H/internal/notify/dispatcher.go:606-614,631-638,670 — FACT
|
||||
- S8 08:291-298 per-app grain lists three types → also app_hold_no_whole_copy, app_stopped_unhealthy, app_repaired_after_crash, app_unexplained_upload/cpu — H/internal/notify/dispatcher.go:473-496,502-507 — FACT
|
||||
- S9 08:182-183 backup_integrity_failed „not in DefaultEnabledEvents … default OFF" → a default since controller v0.309.0 („operator Q1 2026-10-12") — C/internal/settings/settings.go:713-721 — RULE
|
||||
- S10 08:473 §8.1 „NOT BUILT" → slice 1 built (three group switches, v0.309.0) — C/internal/settings/notif_groups.go:4-16 — FACT
|
||||
- S11 08:137-138 „info is dropped … before both legs" → paired info recoveries bypass the gate — H/internal/notify/dispatcher.go:137-167,225-228 — FACT
|
||||
- S12 05:391 „39 event types" → 44 `mail.event.*` keys in hu.json — FACT
|
||||
- S13 08:374-380 only `disabled` skipped → also a box the household switched off (R-998) — H/internal/monitor/deadline.go:433-442 — FACT
|
||||
- S14 _hub-review.md (a dated v0.6.3 review) thresholds/cooldown/push → historical; add a banner — FACT
|
||||
|
||||
## UNDOCUMENTED
|
||||
- U1 bind_old_host_linked (R-1203) — selfbind.go:395-421; dispatcher.go:870 — 05 §13, 08 §6.3
|
||||
- U2 R-1230 the off-site delivery checker skips a box awaiting its recovery key — H/internal/monitor/offsite_delivery.go:105-110,149 — 07 / 05 §8
|
||||
- U3 offsite_delivery_stuck + maybeHeal — offsite_delivery.go:113-125; dispatcher.go:847 — 07 §6
|
||||
- U4 R-723 backup_tier_skipped suppressed in the first hour — dispatcher.go:627-638 — 08 §6.2
|
||||
- U5 System page „Upload line" cell — H/internal/web/system.go:174-197 — 11 §5.7
|
||||
- U6 wg_tunnel_stale, host_swap_short missing from 08 §6.3 — H/internal/api/host_tunnel.go:23,37-47; host_swap.go:31-43
|
||||
- U7 golden_not_vouched, recovery_older_package in no document — dispatcher.go:776-782
|
||||
|
||||
## CONTRADICTIONS
|
||||
- C1 05:569 „6-hourly VACUUM INTO" vs 05:585 „every night" — code: 6-hourly
|
||||
- C2 poster.facts.md:125 (four push times) vs :150 („02:30" only) — code: four
|
||||
- C3 05:291-297 slim DR record RETIRED vs 05:329-330 §11 „ADD … the slim DR record" — code: retired
|
||||
- C4 08:182 „default OFF" vs 08:510 „the 17 defaults" (includes it) — code: default ON
|
||||
|
||||
## UNTESTABLE (live only)
|
||||
05:571 token bytes freed; 05:569 Longhorn speed; 08:553/576 line test numbers; 05:268 R-1159 proof; 08:216 report gap.
|
||||
@@ -0,0 +1,42 @@
|
||||
# DOC-C2 — 09 update architecture (reviewer candidates; verified by the main session before acting)
|
||||
|
||||
Reviewer: read-only subagent, 2026-10-24. Counts: claims 63 · true 38 · stale 19 · untestable 6 · undocumented 6 · contradictions 7.
|
||||
Paths under felhom-controller/controller/ unless named.
|
||||
|
||||
## STALE (all FACT)
|
||||
- S1 09:68-75 `Manager.UpdateStack` „two compose commands … No safety copy. No rollback." → gone; StartGuardedUpdate — internal/api/router.go:794; internal/stacks/update.go:518-540,752-991
|
||||
- S2 09:119-124 RestartStack comment → manager.go:1454; up -d --force-recreate, start-time check (R-1026) — manager.go:1470-1487
|
||||
- S3 09:82-86,97-98,1958,2308 line citations moved (sync.go:319,362,436,700; config.go:399; appstop_marker.go:377; intermediary.go:228)
|
||||
- S4 09:1979-1992 „four writers … UpdateStack" → advancePinTo; plus pinBack and the undo's restoreDefinition — update.go:921-941,1124; undo.go:531
|
||||
- S5 09:2631-2632 recordInstalledImages callers incl. UpdateStack → StartStack, RestartStack, deploy, guarded update end, undo end — manager.go:1368,1495; deploy.go:683; update.go:1007; undo.go:555
|
||||
- S6 09:1994 §5.4 render table → missing row: unreadable template images → compose left alone; R-971 admission before any write — internal/sync/sync.go:622-627; cmd/controller/main.go:421
|
||||
- S7 09:2050,2273 slice 6 „OPEN — R-450" → built; R-450 closed 2026-10-05 — internal/stacks/unattended.go:16-37
|
||||
- S8 09:2064-2075 §6.1 phase table → no `converting` row; bind-data order 2 → 3 → 1 — update.go:969-980; internal/backup/update_guard.go:130-155
|
||||
- S9 09:2374 part 1 „DB up alone … empty-then-load … A volume tar" → what shipped is the folder copy (cp -a + marker) — undo.go:30-34,519-530
|
||||
- S10 09:2378 „a step has no .felhom.yml of its own (R-664)" → steps/<key>.felhom.yml read when present — ladder.go:129-132,209-212
|
||||
- S11 09:2379 „The box half … is not built" → built — digest.go:15-23,94-104,141-155; updateorder.go:76-112
|
||||
- S12 09:2132-2133 R-646 residual → BackfillAppliedMeta at start — undo.go:646-676; cmd/controller/main.go:498
|
||||
- S13 09:2119,2729-2733 „multi-major jump … slice 6" / §8.6 „Tier-2 copy … no automatic rollback" → any tier, undo, one step per press
|
||||
- S14 09:2706-2708 digest comparison „R-446 §3b Q6" → built — updateorder.go:76-112
|
||||
- S15 09:2736-2739 engine-major „until Slice 4" → MariaDB lifted; PostgreSQL with a two-venue proof + engine_conversion
|
||||
- S16 09:269-273 `stacks.update_window` „no Go code reads it" → the field is removed (inside ruling 11: a dated note, not an edit)
|
||||
- S17 09:427 `unattended.go:433–435` → :449-451
|
||||
- S18 09:2344-2345 `config.go` L280-281, default L422 → L278-279, L419
|
||||
- S19 09:2462,2477 refusal `current` → `already_current` — update.go:432
|
||||
|
||||
## UNDOCUMENTED
|
||||
- U1 build.sh refuses --push from a dirty tree / HEAD ≠ origin/main — controller/build.sh:144-167 — 09 §0
|
||||
- U2 the leg retries a transient refusal once in the same leg after RetryWait — unattended.go:65-66,294-306
|
||||
- U3 the leg skips `stopped_by_household` (R-721) — unattended.go:54-55,416-418
|
||||
- U4 a manual chain run's deadline = start + 195 min — unattended.go:234-239
|
||||
- U5 preflight refusals `already_current`, `engine_no_test` — update.go:432,454
|
||||
- U6 the syncer's R-971 allow-list before any compose write — cmd/controller/main.go:421
|
||||
|
||||
## CONTRADICTIONS
|
||||
- C1 09:2050 slice 6 OPEN vs capability map B11 PROVEN-LIVE — code: built
|
||||
- C2 09:68,1984,2632 + 02:838,861 UpdateStack vs controller REUSE.md:154 „DELETED in v0.237.0" — code: deleted
|
||||
- C3 controller REUSE.md:164 lists UpdateStack as a caller vs REUSE.md:154
|
||||
- C4 RULING decision 13 „CompareImageRefs moves to the catalog gate" vs 09:2377 „did NOT move" — code: controller only (updateorder.go:134) — OPERATOR
|
||||
- C5 RULING decision 14 „never jumps" vs 09:2378 „an installed version matching no entry jumps" — a person's press jumps (ladder.go:34-36,175-176,193-194); the leg does not — OPERATOR
|
||||
- C6 check-engine-major.py:24-26 „the Update button still takes no backup" vs 09 §8.6 CLOSED — code: update.go:819-841
|
||||
- C7 RULING decision 250 item 5 „by its AGE (older than 7 days while the floor is above it)" vs golden_currency_gate.py:8-12 („release ahead" stands in for „floor ahead") — OPERATOR to know
|
||||
@@ -0,0 +1,37 @@
|
||||
# DOC-D1 — 07 backup architecture (reviewer candidates; verified by the main session before acting)
|
||||
|
||||
Reviewer: read-only subagent, 2026-10-24. Counts: claims 64 · true 44 · stale 11 · untestable 5 · undocumented 8 · contradictions 5.
|
||||
Paths under felhom-controller/controller/ unless named.
|
||||
|
||||
## STALE
|
||||
- S1 07:756 D22 „controller main, unreleased" → shipped v0.338.0 — CHANGELOG.md:237; internal/backup/restore_unit.go:442-512 — FACT
|
||||
- S2 07:383 Tier-1 „secret-stripped app.yaml" → since D5 (schema 2) the unit's app.yaml (0600) carries the PORTABLE secrets — internal/backup/recovery_unit.go:25,261,272 — FACT
|
||||
- S3 07:1537 (+1498, 1624) row 15 „DEFECT … no lock case → R-104" → R-104 closed v0.297.0; class `locked` — internal/backup/offbox.go:156-159,220-224,317 — FACT
|
||||
- S4 07:1532 row 11 Longhorn target DooPlex „2 restore points" → D40 took the hub volume out of the recurring group — felhom.eu/manifests/hub.yaml:47-51 — FACT
|
||||
- S5 07:256 „Hub v0.158.0 (built, deploy waits)" → deployed 2026-10-18 — hub/CHANGELOG.md:288 — FACT
|
||||
- S6 07:1531 row 10 put-back „next slice", R-973 „agent main, unreleased" → both shipped (v0.336.0; agent 0.160.0) — internal/backup/offbox_restore_undo.go:17-33; felhom-agent/internal/localapi/backup_cap.go:41-52 — FACT
|
||||
- S7 07:440 `cmd/controller/main.go:604-607` → main.go:1260-1264 via backupwindow.LegTimes — FACT
|
||||
- S8 07:1344,1347,1349 line citations moved (GetAppDrivePath backup.go:441, F5 pruneStalePrimaryDirs backup.go:1514); mp1 gone (07:386) — FACT
|
||||
- S9 07:450-451 „keep-last=1 … keep-last + 1" → keep-daily=N (R-1149) — felhom-agent/internal/config/config.go:558-559 — FACT
|
||||
- S10 07:1601 „a restore is refused while any second-drive copy runs" → the install-time kept-data load checks only RestoreStatus/IsRunning — internal/web/restore_wizard.go:226; internal/api/kept_install.go:89-99 — FACT or a small code gap
|
||||
- S11 07:975-976 §6.6 off-site row → the off-site restore also refuses a mixed/mismatched snapshot before anything is touched — internal/backup/offbox_reconstitute.go:829-832 — FACT
|
||||
|
||||
## UNDOCUMENTED
|
||||
- U1 a copy is limited to 60 min; a stray that cannot be removed blocks every restore, retried each minute; copies into one destination serialized — internal/backup/tier2_copy_lifecycle.go:40,113-121,150-172 — 07 §8.2b
|
||||
- U2 D25 „Eltávolítás" refused while any backup/restore/copy runs — internal/web/kept_copies.go:54-58
|
||||
- U3 removing an app WITH its backups deletes all of backups/secondary/<app>, D25 kept legs included; conflicts with „only the household's Eltávolítás removes it" — internal/backup/r474_remove_mirrors.go:61,87; tier2.go:487-488; tier2_kept.go:18,76 — CANDIDATE DEFECT
|
||||
- U4 the local unit restore skips the fit check when free space cannot be read (-1) — fails open — internal/backup/restore_unit.go:460; tier2_whole.go:374-375 — CANDIDATE DEFECT
|
||||
- U5 a LOCAL restore's put-back mails „its off-site restore FAILED" (RunKind offsite-reconstitute) — cmd/controller/main.go:1514-1518; internal/backup/offbox_restore_undo.go:242; restore_unit.go:518 — CANDIDATE DEFECT (text)
|
||||
- U6 a start that fails after a successful put-back: not held, copy kept, `restore_failed_put_back_not_started` — offbox_restore_undo.go:245-247
|
||||
- U7 the release copy (D33) has no row in 07's matrix — felhom.eu/scripts/release-copy/
|
||||
- U8 REUSE.md has no row for the stray-copy seams — tier2_copy_lifecycle.go:85,255,319,341
|
||||
|
||||
## CONTRADICTIONS
|
||||
- C1 07:1021 `locked` class vs 07:1537/1498 open DEFECT; OPEN-ITEMS:94,100 R-104 open vs CLOSED-ITEMS:895 closed — code: closed
|
||||
- C2 00-capability-map G15 „nightly" vs 07:1532 „6-hourly" — code: 6-hourly
|
||||
- C3 07:383 „secret-stripped" vs 07:1523 „portable secrets" — code: portable
|
||||
- C4 agent comment backup_cap.go:15 „keep-last" vs 07:386-387 keep-daily — code: keep-daily (the comment is stale)
|
||||
- C5 controller REUSE.md:84-85 D22 „(unreleased)" vs CHANGELOG v0.338.0 released
|
||||
|
||||
## UNTESTABLE
|
||||
ep0 keep-weekly / prune jobs (ep0 config); hub-DB retention 14 daily + 8 weekly (ep0); R-1214 stall numbers; „0.336.0 is the floor"; the carry end to end.
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
# DOC-D2 — 16 storage identity, 15 media library, _recovery-inventory-2026-07-28 (reviewer candidates; verified by the main session)
|
||||
|
||||
Reviewer: read-only subagent, 2026-10-24. Counts: claims 52 · true 30 · stale 13 · untestable 5 · undocumented 7 · contradictions 4.
|
||||
|
||||
## STALE (all FACT)
|
||||
- S1 16:233-234 an empty partition asks for the typed confirmation (R-1093) → fixed agent 0.160.0 — felhom-agent/internal/storage/hostops.go:459-491
|
||||
- S2 16:18 single-copy notice „a Go constant, not i18n" → i18n note.backup.single_copy(_unproven) — controller internal/backup/backup.go:1485-1494
|
||||
- S3 16:19 `/disks/candidates` „whole disks only" → whole disks with nested partition candidates — agent internal/storage/candidates.go:87-88; partitions.go:84-96
|
||||
- S4 16:17,19,21,129 line citations moved (install.sh:3294 = keep-daily=3 since R-1149; hu.json:330; offer.go:236; install.sh:2475)
|
||||
- S5 15:197,213 a drive added/back → „up -d recreates … household: no" → only the binds are rewritten; the restart waits for „Újraindítás most" / „Később" (03:30); the file browser alone recreates itself — controller internal/stacks/media_library.go:286-288; internal/web/media_events.go:97-110,250-274
|
||||
- S6 15:208 eject DEREGISTERS → a safe removal keeps the drive registered (SetEjected); its return re-binds with no step — media_events.go:304,319-352
|
||||
- S7 15:166-168 Transmission watch/download folders off-site / `optional` → catalog c50adfc (R-1019) marks them `excluded`
|
||||
- S8 inventory:194,392-396 rootfs + mp0 /var/lib/docker + mp1 /mnt/sys_drive → one mp0 at /var/lib/felhom; no mp1 — felhom-agent/configs/build-golden.sh:31-35,99-105,215-225
|
||||
- S9 inventory:89,351,509-517 secrets never from the unit; unit app.yaml secret-stripped → schema-2 units carry the portable secrets; a restore takes the DB password from the unit (R-1106) — recovery_unit.go:30-41; restore_dbcreds.go:16-30
|
||||
- S10 inventory:99,275,363 the copy's recovery-unit/ read by no code path (C9-F4) → RestoreTier2Unit (R-102) — tier2_restore.go:182-211; web/handlers.go:2324
|
||||
- S11 inventory:138,276,373 named-volume tars never unpacked → reconstitute replays them (R-354) — offbox_reconstitute.go:72,1040-1047
|
||||
- S12 inventory:383 ep0 keep-last 2; local keep-last 3 → ep0 keep-weekly 2 at 03:30 (R-1162/R-1165); local keep-daily=3 (R-1149)
|
||||
- S13 inventory:374,656-673 restic 0.14.0 exits 0 on a missing source; namespace „root" → restic 0.19.1 exits 3; namespace from storage.cfg, "" for root
|
||||
|
||||
## UNDOCUMENTED
|
||||
- U1 the „Később" 03:30 media restart is an in-memory timer (a restart forgets it) — media_events.go:250-264 — 15 §11
|
||||
- U2 „Restart now"/„Később"/safe removal force-recreate EVERY deployed media app, including apps the household STOPPED (up -d --force-recreate starts them); held skipped; no backup-run check — media_library.go:270-280; manager.go:1477; media_events.go:196-205,306-315 — CANDIDATE DEFECT
|
||||
- U3 the Sonarr/Radarr key is read from /config/config.xml (the „SET at install" option not built) — mediawire.go:336-345
|
||||
- U4 systemd-detect-virt read once per agent; empty → unknown/vm (fails closed) — agent identity.go:195-205,370-372
|
||||
- U5 an unmapped verdict falls back to st_dev; an unreadable path counts as the SAME disk (R-668) — backup.go:1408-1450
|
||||
- U6 the internal place refuses eject/decommission/wipe, allows a data move — web/internal_place.go:22-25
|
||||
- U7 the internal place subtracts every promised thin volume — agent configs/felhom-internal-place:11-14,71-73
|
||||
|
||||
## CONTRADICTIONS
|
||||
- C1 08:535,583 upload watch „NOT released" vs capability map F13 PROVEN-LIVE on 0.342.0 — code/CHANGELOG: released
|
||||
- C2 15:208 eject deregisters vs 15:251 safe removal keeps it — code: keeps
|
||||
- C3 runbooks/RUNBOOK-manual-guest-restore.md:28,38,93 `mp1 … /mnt/sys_drive` vs 07:386 mp1 gone — code: gone (recovery-relevant)
|
||||
- C4 .claude/rules/live-probes.md:41 `/opt/felhom/stacks/<name>` vs the real `/opt/docker/stacks/<app>/` (a path in a rule file)
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
# DOC-E — 02 controller module map, 10 localisation, 11 OS updates, 14 host power-off (reviewer candidates; verified by the main session)
|
||||
|
||||
Reviewer: read-only subagent, 2026-10-24. Counts: claims 121 · true 92 · stale 22 · untestable 6 · undocumented 5 · contradictions 3.
|
||||
|
||||
## STALE (all FACT)
|
||||
- S1 14:36 heavy-op gate holders → also the weekly line test (agent v0.164.0) — felhom-agent/internal/linetest/linetest.go:62,320
|
||||
- S2 14:25 „two exact sudoers vectors, FELHOM_POWEROFF" → FELHOM_POWEROFF holds only `systemctl poweroff`; `pct shutdown` is FELHOM_GUESTHOOK — felhom-agent.sudoers:81-90
|
||||
- S3 14:89 website FAQ line „a later task (R-1000)" → present — website/gyik.html:128,603
|
||||
- S4 14:39-40 a paragraph breaks the §3 table (rendering) — the doc itself
|
||||
- S5 11:81 cloudflared `2026.6.0` → `2026.9.3` — controller internal/infra/infra.go:26
|
||||
- S6 11:104-106 line citations moved (sudoers:68,228; lanresolver.go:133; manager.go:659)
|
||||
- S7 11:253-256 „DRAFT … nothing installed … --repair-only" → built; sudoers has only `--plan` — sudoers:336-337
|
||||
- S8 11:14 „§8 step 2 BUILT" → steps 2–6 built (the doc's own §8); „NOT RATIFIED" is the operator's
|
||||
- S9 11:318-320 BUNDLE_FILES „22" → 32 — felhom-os-apply:191-233
|
||||
- S10 11:482-483 HOST_SLOW_RE list → also linux-(image|headers|kbuild|modules|base), firmware-, proxmox-secure-boot-support — felhom-os-apply:100-102
|
||||
- S11 10:202-204 the setup wizard still reachable, deletion R-554 → deleted v0.308.0; bootstrap.WaitForConfig
|
||||
- S12 10:131 „Part B is the remaining piece" → built (v0.256.0) — controller internal/config/config.go:149-154
|
||||
- S13 10:184-189 formal-form ceiling 16 → 0 (R-516 item 12) — controller scripts/i18n_missing_gate.py:41
|
||||
- S14 10:237,310,313 „53 apps", „fifth row" → 65 apps; sixth row; fixtures still 53
|
||||
- S15 10:229 metadata.go L316 → :475
|
||||
- S16 02:108 „two in-flight markers" → also update-journal.json and .felhom-install-pending — update.go:49,1306; install_interrupted.go:16-28
|
||||
- S17 02:171 „three holders" → four (1b IsUpdating) — cmd/controller/main.go:2459-2477
|
||||
- S18 02:387-390 GetAlerts → GetBannerAlerts(page) filters before the cap (R-906) — internal/web/alerts.go:350-358
|
||||
- S19 02:267,272,742 „no longer calls the Cloudflare API" → internal/cloudflare still wired when cf_api_token is set — main.go:1994-2000; config.go:160
|
||||
- S20 02:328-329,724-725 state.go/updater.go „DELETE … removed" → both live; the swap goes to the agent (AgentSwapper) — internal/selfupdate/updater.go:38-47,87
|
||||
- S21 02:821-825 line citations moved (sync.go:436; config.go:399)
|
||||
- S22 02:888-890 locked_after_deploy „read by no code" → read into LockedFields; enforced only by an uncalled UpdateStackConfig — deploy.go:428,718-748,837
|
||||
|
||||
## UNDOCUMENTED
|
||||
- U1 a re-enroll during the household power-off hold counts as „gone" (R-1154) — hub/internal/api/reenroll.go:67-68 — 14 §4
|
||||
- U2 host_deliberate_off(_ended) recorded as info events, not mailed — hub/internal/api/deliberate_off.go:12-13,42-50
|
||||
- U3 the unaccented confirmation word „KIKAPCSOLAS" is accepted — controller internal/web/poweroff_handlers.go:29
|
||||
- U4 sockheal (R-860), channelhealth, ~20 post-v0.33 packages missing from 02 §1 — internal/sockheal/sockheal.go:1-6; internal/channelhealth/checker.go:1-6
|
||||
- U5 hub System page „Upload line" cell (amber after 15 days) — hub/internal/web/system.go:174 — 11 §5.7
|
||||
|
||||
## CONTRADICTIONS
|
||||
- C1 10:202-204 setup wizard reachable vs 02:56 deleted v0.308.0 — code: deleted
|
||||
- C2 03:132 D4 „NOT shipped" / 13:76 root password SSH vs shipped in v0.163.0 — code: shipped
|
||||
- C3 02:108 two markers vs 09:2102 update-journal.json — code: four
|
||||
@@ -0,0 +1,15 @@
|
||||
# HUNT-1 — restore and put-back (reviewer candidates; verified by the main session before any row)
|
||||
Paths under felhom-controller/controller/.
|
||||
|
||||
- H1 P1 — a power cut during a restore of a DRIVE-backed app: Recover runs before the drive is bound → „restore copy … is gone — restarting as before" → the gated start refuses → app Refused, marker + Undo kept → intermediary.go:470-476 boot recreate checks only appHeld, not the marker → the app runs on the cut restore's data → the next Recover finds the copy and pours it back over the newer writes — internal/backup/offbox_restore_undo.go:47-48; appstop_marker.go:362-375,408-411; cmd/controller/main.go:2573-2576; internal/web/intermediary.go:400-403,470-476
|
||||
- H2 P2 — a FAILED crash put-back leaves the app stopped, unheld; Start or the boot recreate brings it up; a later restart pours back an old copy — appstop_marker.go:368-371; api/router.go:690-709; intermediary.go:472
|
||||
- H3 P2 — an export with „stop app first" during another app's restore overwrites the single marker file — appstop_marker.go:286-292,318; appexport/export.go:173-178,262-266
|
||||
- H4 P3 — off-site restore: a definition-write failure restarts the app on a half-written definition — offbox_reconstitute.go:875-883,979-986; main.go:3318-3331
|
||||
- H5 P3 — a put-back can merge (failed stop only logged, `volume rm -f` error ignored, tar over), then the copy is deleted — restore_crash_putback.go:155-171; offbox_restore_undo.go:223-225; restore.go:218,251
|
||||
- H6 P3 — a second-drive mirror can start between the web refusal check and the restore's record — web/restore_wizard.go:226; opstatus.go:56-66; handlers.go:2209
|
||||
- H7 P3 — Stop pressed during a restore: after a cut the put-back is skipped, marker cleared, copy left — appstop_marker.go:356-361
|
||||
- H8 P4 — an unused restore copy is never cleaned up — offbox_restore_undo.go
|
||||
- H9 P4 — a comment names a test file that does not exist (`r1195_crash_putback_test.go`) — restore_crash_putback.go:32
|
||||
|
||||
## CHECKED AND SOUND (reviewer)
|
||||
put-back before any start; journal names the same stack; atomic writes; Undo recorded only after a whole copy; fit check before the stop; DBs are named volumes; hold path; capped copy --mount/no symlinks/--pull never; start-up stray clean-up; beginTier2Run under one lock; one single-flight flag.
|
||||
@@ -0,0 +1,14 @@
|
||||
# HUNT-2 — delete and retention (reviewer candidates; verified by the main session before any row)
|
||||
|
||||
- H1 P2 — one bad/rejected `.felhom.yml` backup block deletes the app's second-drive copies of every userdata/ and non-appdata hdd/ leg the same night — controller internal/stacks/metadata.go:550-557 (meta.Backup = nil on any defect, incl. classify.go:185 „matches no compose bind") → appbackup/classify.go:217-221 has=false → backup/tier2_capture.go:84-100 legacy legs hdd/appdata/<name> only → backup/tier2.go:492 tier2Reconcile removes every non-leg dir (tier2.go:325-329); also cs.Skipped legs (tier2_capture.go:56-61). Against D25's header tier2_kept.go:17-18.
|
||||
- H2 P3 — a D25 kept copy is reconciled away when its leg later leaves the declared set; its record stays — tier2.go:485-492; settings/kept_copies.go; tier2_kept.go:93-95
|
||||
- H3 P3 — R-1019 also moved MeTube (`media/metube`), NZBGet, SABnzbd to `excluded`; the reconcile deletes their copies — catalog templates/metube/.felhom.yml:33; commit c50adfc — a design call
|
||||
- H4 P3 — Remove (even keep-data) deletes volumes labelled with the app's project that the definition no longer declares; no tier holds them — controller internal/stacks/delete.go:763-764 (8ae9178, R-1117)
|
||||
- H5 P3 — the capped copy's rsync --delete sees the source in the daemon's mount view; the emptiness check runs in the controller's view — backup/tier2.go:834 vs :464
|
||||
- H6 P3 — the release copy keeps the newest 5 agent versions, vouched or not — felhom.eu/scripts/release-copy/felhom-release-copy:199,218,237
|
||||
- H7 P4 — release copy rsync --delete removes old versions during the transfer; a shrinking registry mirrors deletions — felhom-release-copy:190,237
|
||||
- H8 P4 — hub-DB push has no lower age bound (a future-stamped snapshot stays „newest") — scripts/hub-db-backup/felhom-hub-db-backup:41,49-50
|
||||
- H9 P4 — Remove with remove_backups deletes kept app copies under the mirror and leaves their records — api/router.go:1086-1087
|
||||
|
||||
## CHECKED AND SOUND (reviewer)
|
||||
tier2SafeRemove bounded to /backups/secondary/ (tier2.go:260-269); RemoveKeptCopy same device+inode (tier2_kept.go:92-111); RemoveTier2Mirrors exact paths (r474_remove_mirrors.go:18-19,76-84); shares reconcile drive-scoped (tier2_shares.go:262,347-358); RemoveStack backup path bound (delete.go:839-846); release copy refuses before rsync, size cap first, rrsync; hub-db-backup STAGE default, integrity before push; ep0 prune jobs ns-scoped; agent keep-daily ≥ 1; R-1010 force-delete narrow.
|
||||
@@ -0,0 +1,9 @@
|
||||
# HUNT-3 — keys (reviewer candidates; verified by the main session before any row)
|
||||
No P1.
|
||||
- H1 P2 — the hub stores a household's new escrow package in TWO writes (SaveHostEscrow commits blob+hash, then SaveHostDRBundle sets identity_blob); a hub failure between them leaves the NEW hash beside the OLD identity package; the box's confirm passes and flips to escrowed; on a total loss the code opens nothing — hub/internal/api/handler.go:1415,1445; internal/store/store.go:3496,3511,3793; controller internal/report/escrow_confirm.go:195-217; agent sends once (cmd/felhom-agent/main.go:3366-3386)
|
||||
- H2 P2/P3 — a lost/late hub answer tells the household „your previous code still works" when the hub may hold the new package — agent internal/localapi/escrow_ceremony.go:79,532-534,639-641; controller i18n en.json:2662,2664
|
||||
- H3 P3 — the R-1176 carry can move a package with no repository password (checks identity_blob only), leaving the right package under the deleted host — hub/internal/store/escrow_carry.go:100-110; store.go:3533-3534,3631-3633; controller backup/offbox.go:608
|
||||
- H4 P4 — same-day set-aside name reuse can purge a still-stored copy's package — hub/internal/offsitekeys/offsitekeys.go:378-385; store.go:4295-4300
|
||||
|
||||
## CHECKED AND SOUND (reviewer)
|
||||
D30 key file atomic and heals; once an hour across restarts; hub swaps in place and refuses another host's key; PBS hook inert after a re-key; D31 re-issue changes the token only (comment wording); R-1176 customer-scoped, one transaction, PK stops a double carry; Reconcile before SetFloor; R-1182 purge never touches host_escrow; RESET needs acks; R-975 seal all-or-nothing; the mint guard.
|
||||
@@ -0,0 +1,11 @@
|
||||
# HUNT-4 — time and concurrency (reviewer candidates; verified by the main session before any row)
|
||||
No P1 or P2. R-1250 (the hold + test on request) is committed, not released (agent „next v0.165.0", controller „next v0.343.0").
|
||||
- H1 P4 — 2026-10-25 (fall back): DooPlex pushes the 20:00 hub snapshot again at 02:30 CEST and never the 02:00 one (Go puts the hub's 02:00 slot on the SECOND pass = 01:00Z, after the 00:30Z push); the copy is ~13.5 h old until 08:30 — scripts/hub-db-backup/felhom-hub-db-backup.timer; hub/cmd/hub/main.go:1092; hub/internal/dbsnap/dbsnap.go:32; felhom-hub-db-backup:19,50
|
||||
- H2 P3 (2027-03-28) — spring forward: the leg's deadline is W + 5 h of REAL time, the gate counts wall-clock minutes; a step can start after the full-system backup stopped waiting — controller internal/stacks/unattended.go:225; internal/quiesce/quiesce.go:922-933
|
||||
- H3 P4 (2027-03-28) — W in 02:00–02:59: the DB dump and the second-drive copy start at the same instant (the missing 02:30 maps to 03:30) — internal/scheduler/scheduler.go:509; backupwindow.go:17
|
||||
- H4 P4 — two seeders behind one uplink: the line hold holds only the first, the test is refused „not quiet" every 15 min in the Tuesday window, never counted — controller internal/upwatch/linehold.go:155-160; agent internal/linetest/linetest.go:379-381 (unreleased)
|
||||
- H5 P4 — a hold record of an app removed while held is never cleared; qBittorrent's 50 % rule never runs again — linehold.go:106-108; upwatch/service.go:149 (unreleased)
|
||||
- H6 P4 — qBittorrent alt-speed flip during the hold leaves 1 KiB/s stored — upwatch/explain.go:175; linehold.go:127-128 (unreleased)
|
||||
|
||||
## CHECKED AND SOUND (reviewer)
|
||||
2026-10-25 controller legs fire once each; catch-up uses the same resolution; fall-back leg deadline earlier (safe); hub scheduleDaily day+1 + last guard; release copy 01:40 ends before the repeated hour; line test holds the gate without blocking; 2 min timeout > agent worst case; hold record written first, release deferred, recovery at start; second-drive copy one lock per destination; agent backup cap rolling 24 h.
|
||||
@@ -0,0 +1,10 @@
|
||||
# SEC-1 — hub + controller endpoints changed since 2026-10-17 (reviewer candidates; verified by the main session)
|
||||
No P1 or P2 found.
|
||||
- X1 P3 — the upload watch treats ANY viewer as an explanation for any upload size; a compromised Plex can upload freely while one member watches — controller internal/upwatch/watch.go:275,299; 08:559 states the rule, 08:579's limits list does not name this
|
||||
- X2 P4 — after the operator deletes a household's host, the first enroll with only the passphrase becomes the live host and gets the sealed package (ciphertext; D19 accepts the window) — hub/internal/api/passphrase_scope.go:51-52; internal/store/escrow_carry.go:134-139
|
||||
- X3 P4 — a passphrase holder can make the hub mail passphrase_keys_refused repeatedly — passphrase_scope.go:75-78; handler.go:1271
|
||||
- X4 P4 — the qBittorrent admin password goes to whatever answers the DNS name `qbittorrent` — controller internal/upwatch/explain.go:94,108-113
|
||||
- X5 P4 — kept-copy Remove: a gap between the identity check and RemoveAll — backup/tier2_kept.go:92-111; tier2.go:269
|
||||
|
||||
## CHECKED AND SOUND (reviewer)
|
||||
kept remove behind session+CSRF; debug actions behind RequireAuth(CsrfProtect); .fab path checks; hub /preferences + /box-config self-scoped; /report body id = key; fhp_ refused on /api/v1, unclassified operator routes session-only; line-test run rate-bounded; Upload-line cell escaped; new events operator-only; bind page texts only for a real token; files forwardAuth strips X-Forwarded-*; network_mode refused.
|
||||
@@ -0,0 +1,11 @@
|
||||
# SEC-2 — agent local API, ep0 iperf3, installer sshd drop-in, DooPlex→ep0 jobs (reviewer candidates; verified by the main session)
|
||||
|
||||
- X1 P2 — a BYO host (the household owns it) gets root password SSH switched off by the config bundle; the installer's BYO screen says „NOT touched in byo mode: … OOB sshd" — felhom-agent/configs/felhom-os-apply:230 (the drop-in written for every mode; plan_writes 1985-2000 holds back only policy `oob`); felhom.eu/scripts/felhom-host-install.sh:2118, list 2092-2106 — proof: a byo config on a scratch host, sshd -T before/after the bundle
|
||||
- X2 P3 — any tunnel peer (root on one box) can hold ep0's single iperf3 server (ErrBusy fleet-wide → MaxTriesPerDay) or ask `-R -t <long>` and fill ep0's uplink — felhom.eu/scripts/ep0-iperf3/felhom-iperf3.service:17 (no authorized users, no server-side caps)
|
||||
- X3 P3 — the „in force" check probes only addr=192.0.2.1, so a `Match Address <LAN>` block with PermitRootLogin yes reads as in force — felhom-os-apply:2101-2110
|
||||
- X4 P4 — `sshd -t` failing for any reason refuses and undoes the WHOLE bundle — felhom-os-apply:2102,2112-2115,2054-2061
|
||||
- X5 P4 — the release-copy restore check trusts hashes stored on ep0 itself — runbooks/total-loss-of-dooplex.md
|
||||
- X6 P4 — rrsync without -munge/-wo; a symlink push might escape `data/` depending on `.ssh` ownership — audits/day-2026-10-23/08-release-copy-D33.txt:6
|
||||
|
||||
## CHECKED AND SOUND (reviewer)
|
||||
line-test routes bearer→VMID (server.go:579-580, 692-715); the POST is rate-bounded (due, window, 6/day, heavy-op gate); iperf3 on 10.77.0.1 only, wg0 + /24, forward drop; DynamicUser hardening; the drop-in value pinned to prohibit-password, sshd -t -f before write, undo reloads, uninstall removes; release-copy names validated, host key pinned, 0600 O_EXCL login file; read:package token; ep0 cannot write back to DooPlex; hub-db-backup name pattern, DatastoreBackup scope, shredded staging; ProtectSystem=strict units.
|
||||
@@ -0,0 +1,43 @@
|
||||
"""h1_tier2.py install|chain|copy|break|unbreak|remove — review round 2, HUNT-2 H1 on scratch 9202 (the product's own
|
||||
endpoints; box_walk.py). A bad backup block in an app's .felhom.yml: does the night's second-drive copy then delete the
|
||||
app's household-file copies?"""
|
||||
import json, re, sys, time
|
||||
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
|
||||
import box_walk as w
|
||||
APP, SUB = "audiobookshelf", "audiobookshelf"
|
||||
MARK = "/mnt/felhom-drives/scratch_hdd/userdata/media/audiobooks/review2-h1-marker.txt"
|
||||
COPY = "/mnt/sizing-bench/backups/secondary/audiobookshelf"
|
||||
FY = "/opt/docker/stacks/audiobookshelf/.felhom.yml"
|
||||
w.LOG = []
|
||||
w.login()
|
||||
cmd = sys.argv[1]
|
||||
if cmd == "install":
|
||||
print("deploy:", w.deploy(APP, SUB, {"HDD_PATH": "/mnt/felhom-drives/scratch_hdd"}))
|
||||
print(w.guest(f"echo 'review round 2 H1 marker — a household file' > {MARK}; ls -la {MARK}"))
|
||||
elif cmd == "chain":
|
||||
since = w.guest("date -u +%Y-%m-%dT%H:%M:%SZ").strip()
|
||||
code, d = w.ctl("POST", "/api/debug/backup/night-chain")
|
||||
print("night chain ->", code, str(d)[:160])
|
||||
out = ""
|
||||
for _ in range(120):
|
||||
time.sleep(10)
|
||||
out = w.guest(f"docker logs --since {since} felhom-controller 2>&1 | grep -E 'night-chain|Tier 2|tier2|backup block|reconcile' | grep -v DEBUG | cut -c1-260")
|
||||
if re.search(r"night-chain\] update-leg: done|night-chain\].*finished|update leg \(manual-chain\)", out):
|
||||
break
|
||||
print(out)
|
||||
elif cmd == "copy":
|
||||
print(w.guest(f"find {COPY} -maxdepth 5 | sort | head -40; echo; ls -la {COPY}/userdata/media/audiobooks/ 2>&1 | head"))
|
||||
elif cmd == "break":
|
||||
print(w.guest(f"""cp -p {FY} /root/review2-h1-felhom.yml.orig && python3 - <<'PY'
|
||||
p='{FY}'; s=open(p).read()
|
||||
old=' - path: media/podcasts\\n'
|
||||
assert old in s, 'anchor'
|
||||
s=s.replace(old, ' - path: media/review2-typo\\n class: optional # REVIEW-2 H1 PROOF: a path no compose bind has\\n'+old, 1)
|
||||
open(p,'w').write(s)
|
||||
PY
|
||||
grep -n -B1 -A1 review2-typo {FY}"""))
|
||||
elif cmd == "unbreak":
|
||||
print(w.guest(f"cp -p /root/review2-h1-felhom.yml.orig {FY} && rm -f /root/review2-h1-felhom.yml.orig && grep -c review2-typo {FY}; echo restored"))
|
||||
elif cmd == "remove":
|
||||
print(w.remove(APP))
|
||||
print(w.guest(f"rm -f {MARK}; ls {COPY} 2>&1 | head -3"))
|
||||
Reference in new issue
Block a user