D44/D45 = all picks applied (R-1265, R-1266 closed; R-1280–R-1283 opened); R-232 dated check done; release-2026-10-26 evidence: fix A/B live on rc.6, D47 set-aside alarm on rc.7
gates / gates (push) Successful in 13m3s
gates / gates (push) Successful in 13m3s
This commit is contained in:
1 parent
a86ab64f6c
commit
ade357a14e
38 files changed
+4907
-58
No files matched your search
@@ -23,8 +23,10 @@ records *Felhom's decisions*, not Proxmox behaviour.
|
||||
|
||||
## 1. Model at a glance
|
||||
|
||||
Three components. **Control is always box-initiated** — the hub never connects *into* a
|
||||
customer box.
|
||||
Three components. **Control is always box-initiated** — the hub never ~~connects~~ sends a command *into* a
|
||||
customer box. **[DESIGN — corrected 2026-10-25, D45 = all picks, R-1266]** The hub may POKE a box: through ep0, one empty UDP datagram to the box's WireGuard
|
||||
address (port 51822; `hub/internal/poke/client.go`, agent `internal/poke/poke.go`). A poke carries no command; it only
|
||||
makes the box pull its desired state at once. The box still pulls.
|
||||
|
||||
```
|
||||
operator side customer box (per Proxmox host)
|
||||
@@ -98,8 +100,12 @@ credentials.
|
||||
|
||||
- CGNAT does **not** force this: the Cloudflare Tunnel already makes a box reachable through
|
||||
Cloudflare's edge. We *choose* box-initiated control for the smallest attack surface — the
|
||||
box exposes no control endpoint at all.
|
||||
- The agent and the controller **poll** the hub; the hub never initiates inbound.
|
||||
box exposes no control endpoint ~~at all~~: no route on the box takes a command from the hub. **[DESIGN — corrected 2026-10-25, D45 = all picks, R-1266]**
|
||||
Two listeners sit behind the WireGuard tunnel: the agent's poke listener (a poke carries no command; the box still
|
||||
pulls) and the operator door, `felhom-sshd` on the host, reachable only from the operator's `/32` peer — the
|
||||
documented break-glass (`03` §5, `06` §4.5).
|
||||
- The agent and the controller **poll** the hub; the hub never initiates ~~inbound~~ a command inbound — a poke only
|
||||
makes them poll at once (corrected 2026-10-25, D45, R-1266).
|
||||
- Operator actions are delivered as **signed jobs**: the agent verifies an operator signature
|
||||
before executing, so a compromised hub database alone cannot forge commands.
|
||||
- **[FACT, 2026-10-16, R-970]** The last contradiction is gone: until controller v0.322.0 the box's own hub API key,
|
||||
@@ -245,7 +251,8 @@ app. Controller down → the gated app answers an error, never the app. Measured
|
||||
2026-10-08, R-900). Replacing it with our own relay is deferred until after the first customers (R-904, `09` §3
|
||||
decision 184).
|
||||
- **Outbound only** for control/report/backup (poll to hub, push to PBS). No inbound control
|
||||
endpoint exists in the chosen model.
|
||||
endpoint exists in the chosen model. **[DESIGN — corrected 2026-10-25, D45 = all picks, R-1266]** A poke carries no command; the box still pulls. The operator
|
||||
door (`felhom-sshd` over the WireGuard tunnel) is the documented break-glass (§4, `03` §5).
|
||||
- **Every customer has their OWN domain — never a name under `felhom.eu`** (operator ruling
|
||||
2026-09-14). The free Cloudflare tier's certificate covers one level below a zone, so nested names
|
||||
such as `felhom.<customer>.felhom.eu` are not covered; the customer's own domain is a few thousand
|
||||
@@ -330,7 +337,12 @@ app. Controller down → the gated app answers an error, never the app. Measured
|
||||
- **Key custody (zero-knowledge default):** three tiers the customer chooses —
|
||||
*customer-only* / *zero-knowledge escrow (default)* / *operator-managed*. Default escrows
|
||||
the **PBS passphrase-protected keyfile** in the hub, wrapped under a **customer recovery
|
||||
code** the operator can't open; DR needs the customer's code. Access-notification is an
|
||||
code**; DR needs the customer's code. ~~the operator can't open~~ **[DESIGN — corrected 2026-10-25, D45 = all picks, R-1266]** The hub keeps only the
|
||||
locked key package, and every older one it retained; without the customer's code the operator cannot open any of
|
||||
them, and from the off-site storage and the hub nobody can read the backup. Two exact limits, as the website says
|
||||
since R-985: while the box runs, the operator has root access for support and with it could reach the data and the
|
||||
backup key on the box; and the operator can open an OLDER off-site store from a retained package, by hand, when the
|
||||
household types its old code on its own box (`runbooks/RUNBOOK-open-retained-package.md`, R-304). Access-notification is an
|
||||
audit signal, never the primary guard. (Don't build bespoke crypto — use PBS's native
|
||||
keyfile passphrase.)
|
||||
|
||||
@@ -355,8 +367,11 @@ app. Controller down → the gated app answers an error, never the app. Measured
|
||||
|
||||
## 10. How this embodies the product values
|
||||
|
||||
- **Zero-knowledge offsite** — the operator holds the offsite backup but cannot read it.
|
||||
- **Box-initiated control + signed jobs** — no standing operator backdoor; a hub compromise
|
||||
- **Zero-knowledge offsite** — the operator holds the offsite backup but cannot read it without the customer's code
|
||||
(the two exact limits are in §8 — corrected 2026-10-25, D45, R-1266).
|
||||
- **Box-initiated control + signed jobs** — ~~no standing operator backdoor~~ no hidden operator door: the one
|
||||
operator door is `felhom-sshd` over the WireGuard tunnel, the documented break-glass (`03` §5), and the website tells
|
||||
the household about the operator's root access (R-985) — corrected 2026-10-25, D45, R-1266; a hub compromise
|
||||
alone can't forge commands.
|
||||
- **Customer-visible audit log** — every operator action is visible to the customer.
|
||||
- **Never hold data hostage** — subscriptions cover ongoing labour (monitoring, offsite,
|
||||
|
||||
@@ -64,7 +64,8 @@ Explicitly does **not**:
|
||||
- Serve application traffic or sit in the data path. **Control plane, not data plane**: if the agent dies, apps keep serving (Docker + LXC run without it); only *management* degrades — no new backups, no provisioning, hub loses the heartbeat.
|
||||
- Hold or proxy customer application data.
|
||||
- Run inside a guest. It is the thing that recovers guests and the host; it cannot be one of them.
|
||||
- Manage **geo-restriction / the Cloudflare API**. Geo is hub-owned: the customer sets it in the controller UI, the controller reports the geo desired-state to the hub, and the **hub** (holding the CF API token) reconciles the WAF (S4). The agent manages only the *tunnel* service (`cloudflared`, §3/§5), never WAF rules.
|
||||
- Manage **geo-restriction / the Cloudflare API**. Geo is hub-owned: the customer sets it in the controller UI, the controller reports the geo desired-state to the hub, and the **hub** (holding the CF API token) reconciles the WAF (S4). ~~The agent manages only the *tunnel* service (`cloudflared`, §3/§5), never WAF rules.~~ The agent manages no
|
||||
Cloudflare piece: it only reads the guest's `cloudflared` container (§3) — corrected 2026-10-25, D45, R-1266.
|
||||
|
||||
## 3. Process model & host integration
|
||||
|
||||
@@ -240,10 +241,16 @@ alerts the operator when an agent misses its expected check-in window. This is t
|
||||
failure mode for a managed service, so it gets first-class treatment hub-side.
|
||||
|
||||
**Break-glass.** Standing inbound control is off. But when the poll loop *itself* is wedged
|
||||
(agent hung, host sick) you cannot fix it through the poll loop. So there is an explicit,
|
||||
**off-by-default, customer-consented, fully-audited** emergency path: SSH to the host via
|
||||
(agent hung, host sick) you cannot fix it through the poll loop. So there is an explicit emergency path.
|
||||
~~**off-by-default, customer-consented, fully-audited** emergency path: SSH to the host via
|
||||
the Cloudflare Tunnel behind Cloudflare Access (or on-site). Enabling it is itself a signed,
|
||||
logged operation; it auto-expires.
|
||||
logged operation; it auto-expires.~~ **[DESIGN — corrected 2026-10-25, D45 = all picks, R-1266]** The path is the operator door: the operator's WireGuard
|
||||
peer reaches a dedicated `felhom-sshd` on the host (a non-22 port, reachable only from the operator's `/32` over
|
||||
`wg-felhom`, key-only login; `06` §4.5, TASK H1). It does not use the Cloudflare Tunnel. It is installed by default on
|
||||
an appliance install, and a BYO install refuses it (the owner's call; `scripts/felhom-host-install.sh`). It stays on;
|
||||
it is not opened per incident. When SSH itself is broken, the hub-vaulted `root@pam` password opens the Proxmox web
|
||||
console (`runbooks/break-glass.md`). The website tells the household that the operator has root access for support
|
||||
(R-985).
|
||||
|
||||
## 6. Agent ↔ controller local API
|
||||
|
||||
@@ -315,11 +322,11 @@ A controller can only `POST /rollback` (or snapshot/backup) **its own** guest
|
||||
token → guest and authorizes per guest, so a compromised controller's blast radius is
|
||||
**self-scoped and bounded** to its own guest.
|
||||
|
||||
**[DESIGN — undocumented, found 2026-10-24, review round 2 — confirmed through R-1265]:** the surface listed above is
|
||||
the slice-8 design; on `main` the local API registers **41** routes (`felhom-agent/internal/localapi/server.go`; 24 of
|
||||
them are not listed above — the backup tiers and target, disk candidates, decommission, format status, guest attach,
|
||||
the network storage routes, the controller swap, the escrow stage/preflight/ceremony routes, the debug logs, guest
|
||||
memory). Among them the controller can start a HOST action: `POST /host/line-test/run` (about 55 s; bounded by the due
|
||||
**[DESIGN — confirmed by the operator 2026-10-25 (D44 = all picks, R-1265); every route listed below since
|
||||
2026-10-25]:** the surface listed above is the slice-8 design; on `main` the local API registers **41** routes
|
||||
(`felhom-agent/internal/localapi/server.go:562-634`). The list above names 15 of them; the other 26 are in the table
|
||||
below (recounted 2026-10-25 — the review counted 24). Every route is behind the same per-guest token (`withGuest`).
|
||||
Among them the controller can start a HOST action: `POST /host/line-test/run` (about 55 s; bounded by the due
|
||||
date, the window, 6 tries a day and the heavy-operation gate — R-1250, unreleased). The local API also listens on the
|
||||
R-50 island bridge (host `169.254.253.1`, guest `net1` `169.254.253.2/30`, `internal/config/config.go:260-269`). The
|
||||
weekly line test keeps its state in the OOB state dir (`line-test.json`) and has an off switch, `line_test.disable`.
|
||||
@@ -327,6 +334,37 @@ The served WireGuard block carries `oob_peer_ip` and `oob_operator_ssh_key` (`in
|
||||
runs only inside the tunnel watchdog, and its 15-minute count lives in memory — an agent restart starts it again (the
|
||||
once-an-hour bound survives in `wg/last-rekey`).
|
||||
|
||||
The routes the list above does not name (read from `server.go:562-634` and each handler's own comment, 2026-10-25):
|
||||
|
||||
| Route | What it does |
|
||||
|---|---|
|
||||
| `GET /backup/tiers` | this guest's backup tiers, each with its cadence and whether its Proxmox storage exists now (`present` / `absent` / `unknown`) |
|
||||
| `POST /backup/target` | moves the primary backup tier to a new storage: create → grant → config; answers `restart_required`, and the caller restarts the agent after its own in-flight check |
|
||||
| `GET /host/line-test` | HOST: the weekly upload line test's last measurement; `present:false` when never measured (R-1247) |
|
||||
| `POST /host/line-test/run` | HOST: starts a line test now — about 55 s, bounded as above (R-1250) |
|
||||
| `GET /disks/candidates` | the host disks free for Felhom to enroll (`initialize`, and `attach` for those with a mountable file system) |
|
||||
| `POST /disks/same-disk` | whether two of the guest's paths sit on the same physical disk, from the agent's own read (`16` §4) |
|
||||
| `POST /disks/decommission` | takes the household's own drive out for good: a permanent intent, the guest bind pruned, unmounted; never formats; a system or backup mount is refused (403) |
|
||||
| `GET /disks/format/status` | the newest or running format job, for a caller whose request timed out |
|
||||
| `POST /disks/guest-attach` | binds an enrolled drive's `felhom-data` folder into this guest (read-write); the rest of the drive stays hidden |
|
||||
| `GET /guest/memory` | this guest's RAM and its bounds |
|
||||
| `POST /guest/memory` | a bounded RAM resize, applied live (R-24) |
|
||||
| `POST /netstorage/add` | mounts a NAS share on the host and starts a verify job; a failed verify rolls it back |
|
||||
| `GET /netstorage` | the NAS shares |
|
||||
| `GET /netstorage/verify-status` | the verify job's state |
|
||||
| `POST /netstorage/remove` | removes a NAS share |
|
||||
| `POST /controller/swap` | swaps the controller image (202, then async); the agent verifies and rolls back |
|
||||
| `GET /controller/swap/status` | the swap's state |
|
||||
| `POST /escrow/stage-secret` | stages the off-site repository password for the escrow ceremony (0600, never logged) |
|
||||
| `DELETE /escrow/stage-secret` | wipes the staged password |
|
||||
| `POST /escrow/recover-offsite-password` | with the recovery code in the body, returns only the off-site repository password (§8a) |
|
||||
| `GET /escrow/preflight` | the ceremony's checklist |
|
||||
| `POST /escrow/ceremony` | starts the root ceremony job |
|
||||
| `GET /escrow/ceremony/status` | the ceremony's state |
|
||||
| `POST /escrow/ceremony/claim` | the one-shot hand-off of the new recovery code; a later claim is 410 |
|
||||
| `POST /escrow/ceremony/confirm` | the household's two words; only then does the package reach the hub (R-1151) |
|
||||
| `GET /debug/logs` | the agent's debug log ring, for the controller's Debug page |
|
||||
|
||||
### 6a. Implementation (slice 8A — implemented)
|
||||
|
||||
**Status: implemented** (agent v0.10.0 `internal/localapi`; controller v0.35.0 `internal/bootstrap`
|
||||
|
||||
@@ -77,10 +77,10 @@ nonce (DoS-safe). Each layer is mandatory and was proven to reject independently
|
||||
A cannot be replayed at box B);
|
||||
- **time window** — `now ∈ [issued_at, expires_at]`; as built, `issued_at` may be up to **2 minutes** in the future
|
||||
(clock skew), `expires_at` is never extended — the agent (`felhom-agent/internal/authz/verifier.go:19-22,148-155`) and
|
||||
the root wrapper (`configs/felhom-os-apply`) alike (corrected 2026-10-24). **[DESIGN — undocumented, found
|
||||
2026-10-24]:** neither caps the window's LENGTH (`expires_at − issued_at`); `felhom-opsign` defaults to 30 min
|
||||
(`cmd/felhom-opsign/main.go:57`), and the nonce store keeps a record until its expiry. A register row asks the
|
||||
operator to confirm or cap it;
|
||||
the root wrapper (`configs/felhom-os-apply`) alike (corrected 2026-10-24). **[DESIGN — the operator picked a code change
|
||||
2026-10-25 (D44); row R-1280]:** neither caps the window's LENGTH (`expires_at − issued_at`); `felhom-opsign` defaults to 30 min
|
||||
(`cmd/felhom-opsign/main.go:57`), and the nonce store keeps a record until its expiry. The pick: the agent and the
|
||||
root wrapper refuse a window longer than 24 h. Not built yet;
|
||||
- **nonce** — unseen within the window (the nonce store **must be persistent across agent restarts**
|
||||
and expiry-pruned; a non-persistent store reopens the replay window after a restart).
|
||||
|
||||
|
||||
@@ -330,12 +330,16 @@ traverse `/root`) — create datastores under a world-traversable parent (`/srv`
|
||||
## 6. Trust-model conformance (01 §3/§4 preserved)
|
||||
|
||||
- The box **only ever dials out** (UDP to the endpoint; the same posture as hub-poll and
|
||||
PBS-push, 01 §4/§7). No inbound listener is added to any customer box; the hub still never
|
||||
initiates into a box.
|
||||
PBS-push, 01 §4/§7). ~~No inbound listener is added to any customer box; the hub still never
|
||||
initiates into a box.~~ **[DESIGN — corrected 2026-10-25, D45 = all picks, R-1266]** Two listeners sit on the box's tunnel side, reachable only through
|
||||
the tunnel: the agent's poke listener (UDP 51822 on the box's WireGuard address — a poke carries no command, the box
|
||||
still pulls) and, on an appliance install, the operator door `felhom-sshd` (only from the operator's `/32`, §4.5) —
|
||||
the documented break-glass (`03` §5). The hub still never sends a command into a box.
|
||||
- The endpoint VM cannot reach into boxes either: it terminates tunnels whose per-peer routes
|
||||
(§4.5) admit only box→PBS traffic; it holds no box credentials.
|
||||
- **Out of scope, explicitly:** (a) operator-inbound access to boxes — a *separate* future
|
||||
problem with its own trust decision; nothing here creates or presumes it; (b) game-server
|
||||
- **Out of scope, explicitly:** (a) ~~operator-inbound access to boxes — a *separate* future
|
||||
problem with its own trust decision; nothing here creates or presumes it;~~ operator-inbound access to boxes was
|
||||
added on 2026-07-05 (TASK H1) and rides this tunnel — §4.5 (corrected 2026-10-25, D45, R-1266); (b) game-server
|
||||
ingress — public players are not tunnel peers; that is a different ingress problem (public
|
||||
reachability), not a backup-transport one.
|
||||
|
||||
@@ -387,7 +391,7 @@ venue; it gates nothing. Runbook:
|
||||
|
||||
**MVP that puts offsite backup in a real customer's hands = S1 → S2 → S3; then S4.** S5/S6
|
||||
complete the story. The cloudflared service (`internal/hub/cloudflared.go:9-12` — then a
|
||||
read-only prober that named itself "the seam for the tunnel-management slice") adopts the
|
||||
S3-piloted agent-managed-service pattern in a later, separate slice. *(Dated note 2026-10-24: cloudflared is a GUEST
|
||||
container the agent only reads — four states, `cloudflared.go:12-23`; `03` §2 says the agent will not manage it.
|
||||
Whether this plan is withdrawn is the operator's — a row of review round 2.)*
|
||||
read-only prober that named itself "the seam for the tunnel-management slice") ~~adopts the
|
||||
S3-piloted agent-managed-service pattern in a later, separate slice.~~ **[DESIGN — corrected 2026-10-25, D45 = all picks, R-1266]** This plan is withdrawn:
|
||||
cloudflared is a GUEST container that the controller renders and keeps up; the agent only reads it (four states,
|
||||
`cloudflared.go:12-23`) and will not manage it — `03` §2/§3 wins.
|
||||
@@ -1561,7 +1561,8 @@ crosses the line — **R-158**.
|
||||
> design, **row 14** is proven and merely never stopwatched, **row 11b** is a note not a row, and
|
||||
> **row 15 was an open DEFECT (R-104) that this line did NOT cover** — *R-104 was CLOSED 2026-10-05 (controller
|
||||
v0.297.0: a lock that survives the self-heal gets the class `locked`, its own note and a retry, `offbox.go:156-162`;
|
||||
CLOSED-ITEMS.md; corrected 2026-10-24).*
|
||||
CLOSED-ITEMS.md; corrected 2026-10-24).* **Row 16** (added 2026-10-25, D44 #6, R-1265 — after this banner) has a blank RTO
|
||||
too: a registry rebuild from the ep0 release copy was never timed.
|
||||
>
|
||||
> **NO STATUS MOVED ON THE DAY THIS WAS WRITTEN, because nothing was proven that day.** A stopping
|
||||
> line that promotes a row is a stopping line that lies. Full text, and the conditions that reopen
|
||||
@@ -1601,6 +1602,7 @@ CLOSED-ITEMS.md; corrected 2026-10-24).*
|
||||
| 13 | **Customer loses R** | every byte, every tier, the Recipe | **on-premises recovery is unaffected** — Tier-1/2/3 and the whole-guest tiers all work while the guest and host live. What is lost: the ability to re-establish **host identity** and to open the escrowed offsite key material after a host loss | — | | | **NONE for host-loss** | R exists in **zero** system copies by design (INV Part C, row 24). Whether the operator should be able to recover is **open** → §11-A, §11-B. **D5 narrows this further:** Tier-1/2 app recovery needs only the drive, so losing R now costs the offsite route and host identity, never local app recovery (§7.4) |
|
||||
| 14 | **Host SSH management plane dies** | everything | 3-layer break-glass: tmpfiles → 60 s watchdog → hub-vaulted `root@pam` on the PVE console | operator | | | **PROVEN** | `runbooks/break-glass.md`; the original incident and its fix |
|
||||
| 15 | **Interrupted offsite run leaves a stale lock** | everything | manual `restic unlock --remove-all` | **operator** (SSH) | | | **FIXED 2026-10-05 (R-104, v0.297.0; was DEFECT — corrected 2026-10-24)** | the self-heal exists (`offbox.go:634-648`) but the probe fails first and `classifyResticProbe` has no lock case → fail-fast, operator told *„ismeretlen okból"* → **R-104** |
|
||||
| 16 | **The container registry is lost with DooPlex** (the released images and agent packages) | the release copy on ep0 (D33): the newest 3 release tags of `felhom-controller`, `felhom-hub` and `felhom-samba`, and the newest 5 agent packages (binary + config bundle), in a plain folder outside PBS (`/srv/felhom-release-copy`) | push the copy back into a registry with the same name — `runbooks/total-loss-of-dooplex.md` „Restore the release copy” (same digests, no rebuild; `scripts/release-copy/`) | **operator** (ep0 root) | | 24 h (nightly 01:40) | **PARTIAL** | `audits/day-2026-10-23/08-release-copy-D33.txt`: the copy was pulled back, every sha256 and digest matched, and a controller image from it ran on a scratch box. Not yet walked: the push into a real new registry and the agent-package upload. Added 2026-10-25 (D44 = all picks, R-1265) |
|
||||
|
||||
### 8.2 The ONE exception to row 5's derived-copy rule (R-403, controller v0.230.0)
|
||||
|
||||
@@ -1740,7 +1742,7 @@ put-back leaves (§8 D20/D22), so no new retention rule. A watcher looks 30 more
|
||||
turns to success („…elindult: a visszaállítás kész.") and the copy is removed, as after any successful restore; if it
|
||||
never runs, the copy stays for support.
|
||||
|
||||
### 8.2c Decisions in code that no document recorded [DESIGN — undocumented, found 2026-10-24, review round 2 — the operator confirms them through one register row]
|
||||
### 8.2c Decisions in code that no document recorded [DESIGN — confirmed by the operator 2026-10-25 (D44 = all picks, R-1265)]
|
||||
|
||||
Read at `file:line` on `main` (controller `321df1f`, hub `44d19042`) by the main session:
|
||||
|
||||
@@ -1756,8 +1758,9 @@ Read at `file:line` on `main` (controller `321df1f`, hub `44d19042`) by the main
|
||||
a box that declares it waits for the household's recovery code (`awaiting_recovery_key`) is skipped — no event, no
|
||||
heal (R-1230, `:103-110`).
|
||||
- **The release copy (D33)** — nightly 01:40, the newest 3 image tags and 5 agent packages to ep0 under its own
|
||||
limited user, capped by the sender (`felhom.eu/scripts/release-copy/`) — has no row in this matrix; its homes are
|
||||
the poster facts and `runbooks/total-loss-of-dooplex.md`.
|
||||
limited user, capped by the sender (`felhom.eu/scripts/release-copy/`). ~~has no row in this matrix~~ It is row
|
||||
**16** of the matrix since 2026-10-25 (D44 = all picks, R-1265); its other homes are the poster facts and
|
||||
`runbooks/total-loss-of-dooplex.md`.
|
||||
|
||||
### 8.1 The blank cells, listed explicitly
|
||||
|
||||
@@ -1776,6 +1779,7 @@ Per the rule that a blank is a finding, here they are:
|
||||
| 13 | RTO, RPO | not a timed recovery; a capability loss |
|
||||
| 14 | RTO | the break-glass path is proven but was never timed |
|
||||
| 15 | RTO | the manual unlock was performed but not timed |
|
||||
| 16 | RTO | a registry rebuild from the ep0 release copy has never been timed (row added 2026-10-25, D44 #6, R-1265) |
|
||||
|
||||
**Also unmeasured, and not representable as a row** (INV Part F.3): any restore larger than 155.5 MB
|
||||
from restic; any restore of a customer data drive (no tier holds one whole); the weekly PBS
|
||||
|
||||
@@ -286,8 +286,11 @@ builds them (`audits/update-rulings-2026-09-23/`).
|
||||
13. **The test decides, not the tag** (Q3). **REPLACES decision 3's second half, "never across a
|
||||
major".** The box applies by itself every step the catalog holds, because **the catalog holds only
|
||||
tested steps**; the size of the version jump does not matter, the test does. The box does not
|
||||
parse tags to decide. The tag rule (`stacks.CompareImageRefs`) moves to the **catalog gate** as a
|
||||
push-time safety net: an image move with no test record is refused there. Two exceptions, both
|
||||
parse tags to decide. ~~The tag rule (`stacks.CompareImageRefs`) moves to the **catalog gate** as a
|
||||
push-time safety net:~~ **[DESIGN — corrected 2026-10-25, D45 = all picks, R-1266]** The tag rule (`stacks.CompareImageRefs`) did not move: it
|
||||
stays in the controller (`internal/stacks/updateorder.go`), where it only tells whether an installed app is
|
||||
AHEAD of the catalog; it never decides an update. The catalog gate is the push-time safety net, and it asks for a
|
||||
proven test instead (§6.4 part 4): an image move with no test record is refused there. Two exceptions, both
|
||||
**marks the test sets on the step**: *files may change* → automatic only when a fresh copy holds
|
||||
the files, else a person (this is Q2's refinement); *needs a person* → the tester writes why.
|
||||
**Why:** decision 3 was a proxy. "Within a major" was standing in for "known to work", and the
|
||||
@@ -299,7 +302,11 @@ builds them (`audits/update-rulings-2026-09-23/`).
|
||||
unchanged for every other move: a step a box takes was still tested, on at least the box venue.
|
||||
|
||||
14. **The update ladder** (Q3). A box more than one step behind **climbs one tested step at a time, in
|
||||
order, and never jumps**. Each step is the full guarded update of §6.1. **Why:** a step was tested
|
||||
order, and ~~never jumps~~ the night leg never jumps**. Each step is the full guarded update of §6.1.
|
||||
**[DESIGN — corrected 2026-10-25, D45 = all picks, R-1266]** An app older than the ladder (its installed version matches no entry's `from`) is never
|
||||
pressed by the night leg (`internal/stacks/unattended.go`). A PERSON's press on such an app takes the catalog's
|
||||
current definition, logged by name (`internal/stacks/ladder.go`, `TestLadder_UnknownInstalledJumpsAndSaysSo`); the
|
||||
guarded update and its undo (decision 15) still run around it. **Why:** a step was tested
|
||||
from the version before it, not from three versions before it; R-40's multi-major jump is what
|
||||
this prevents. This is the "stepping" §6.1 already assigned to slice 6.
|
||||
|
||||
@@ -988,8 +995,11 @@ its length, and both fixes cost something the household would notice — operato
|
||||
installs the vouched golden and updates itself to the floor (not yet proven end to end — R-1052). **The golden
|
||||
stays weekly:** a CHANGED golden selection is still refused when it is older than the fleet's newest controller
|
||||
(`TestVouchGoldenChangedToOlderIsStillRefused`).
|
||||
5. The push gate judges the golden by its AGE (older than 7 days while the floor is above it), not by the newest
|
||||
CHANGELOG heading (`scripts/golden_currency_gate.py`).
|
||||
5. The push gate judges the golden by its AGE, not by the newest CHANGELOG heading
|
||||
(`scripts/golden_currency_gate.py`). **[DESIGN — corrected 2026-10-25, D45 = all picks, R-1266]** ~~(older than 7 days while the floor is above
|
||||
it)~~ The gate runs offline and cannot read the floor, so „a release is ahead of the newest bake” stands in for
|
||||
„the floor is above it”: it convicts when a release is ahead AND the newest bake is more than 7 days old; ahead
|
||||
and at most 7 days old is an advisory. The floor follows a release within hours, so the gate is the stricter one.
|
||||
251. **Scripts use scoped hub tokens, not the operator password; CC's floor step uses its own.** *Operator ruling
|
||||
2026-10-15 11:38.* Tokens with groups (read/write per group), managed on the Configuration tab, shown once, stored
|
||||
hashed, every use logged by name (`05` §17, hub v0.152.0). Never by any token: a secret shown or minted, a delete or
|
||||
@@ -2570,7 +2580,7 @@ and E are the ones that unblock a decision; leg A is the one that takes the time
|
||||
harness on DooPlex for the image-side ones.
|
||||
|
||||
|
||||
### 6.4.x Decisions in code no section records [DESIGN — undocumented, found 2026-10-24, review round 2 — the operator confirms them through one register row]
|
||||
### 6.4.x Decisions in code no section records [DESIGN — confirmed by the operator 2026-10-25 (D44 = all picks, R-1265)]
|
||||
|
||||
Read at `file:line` on controller `main` (`321df1f`) by the main session:
|
||||
|
||||
|
||||
@@ -78,12 +78,13 @@ OS packages (exact versions, apt-signed), the installer script (by tag, R-980).
|
||||
| **added:** catalog ↔ every box | **design gap** | a catalog push reaches every box with no check on the box (R-971). **Re-check (2026-10-24, review round 2):** the box checks every rendered compose against its allow-list before any write since controller v0.328.0 (`internal/privpolicy/privpolicy.go`) |
|
||||
| **added 2026-10-23 (R-1241):** a hacked app pod on DooPlex ↔ Felhom's secrets | **does not hold**; not used in the Plex2Shell window | two no-login doors: the Longhorn NFS export (the hub's unencrypted nightly copies with the unsealed tokens of R-975; by a published file-handle escape the whole sda1 disk, the operator signing keys included — not exercised) and the Longhorn API (backup a volume to any target). Plex2Shell, 2026-10-17 → 10-23: **NOT REACHED** — 0 new NFS connections in the window, no off-schedule backup or volume; the hub, the k8s API and the kubelet refuse a caller with no credential (measured). The export was narrowed to the pod network on 2026-10-23; the pod side stays open (`audits/plex2shell-felhom-impact-2026-10-23/`). **Felhom half, 2026-10-23 night:** the last plain tokens sealed in hub 0.168.0 (deployed 2026-10-24 — corrected 2026-10-24; `audits/hub-token-seal-2026-10-23/`); the two plain Longhorn copies of the hub volume already in the folder are R-1243's decision. A household box's version of this attacker (a hacked app sending or mining) is what the upload watch (R-1244, released 2026-10-24 in controller v0.342.0 + hub v0.169.0 — corrected 2026-10-24) detects |
|
||||
|
||||
**[DESIGN — undocumented in `01` §5, found 2026-10-24, review round 2 — confirmed through R-1265]:** the file manager's one
|
||||
**[DESIGN — the operator picked a code change 2026-10-25 (D44); row R-1281]:** the file manager's one
|
||||
sign-in (R-1100) — FileBrowser trusts its proxy header blindly, and every app container can reach `filebrowser:80`
|
||||
directly; the only guard is that the header NAME is a per-box secret, which traefik's forwardAuth strips from every
|
||||
client request and sets only for a browser holding the dashboard's files pass
|
||||
(`felhom-controller/controller/internal/infra/filebrowser_sso.go:15-22`, `internal/web/files_sso.go`). The name file's
|
||||
creation race was fixed by review round 2 (controller `b01bb4b`).
|
||||
creation race was fixed by review round 2 (controller `b01bb4b`). The pick: FileBrowser joins only the network traefik uses, so no app
|
||||
container reaches it directly. Not built yet.
|
||||
|
||||
## 6. What we accept (do not re-report)
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ alternative (an out-of-band confirmation) needs a second channel the box does no
|
||||
- ~~The website FAQ line („to turn it off: Beállítások → Rendszer → A gép kikapcsolása") — a later website task (R-1000).~~ Published — `website/gyik.html:128,603` (corrected 2026-10-24).
|
||||
- The BIOS „after power loss" value of the N100 was not read (no monitor); the household text promises only the button.
|
||||
|
||||
## Decisions in code no section records [DESIGN — undocumented, found 2026-10-24, review round 2 — confirmed through one register row]
|
||||
## Decisions in code no section records [DESIGN — confirmed by the operator 2026-10-25 (D44 = all picks, R-1265)]
|
||||
|
||||
- **A re-enroll while the household power-off hold is active counts as „gone"** (a reinstall over the old host), not
|
||||
„live" (R-1154; `hub/internal/api/reenroll.go:67-68`).
|
||||
|
||||
@@ -166,7 +166,11 @@ directory) with that tag. Facts the link code needs:
|
||||
no capture set (`stacks/classify_binds.go` `classifyRoot`). Measured over the live templates: Transmission's unlisted
|
||||
`watch` bind goes off-site; the download folders are `optional`, so a second drive gets a nightly copy of every
|
||||
download (R-1019). *(Fixed since: catalog `c50adfc` marks the download and watch folders `excluded` —
|
||||
`app-catalog-felhom.eu/templates/transmission/.felhom.yml:46-51`; noted 2026-10-24.)* **[DESIGN]** The per-drive media bind is declared `excluded` for every media app — the family's
|
||||
`app-catalog-felhom.eu/templates/transmission/.felhom.yml:46-51`; noted 2026-10-24.)* **[DESIGN — the operator picked a code change 2026-10-25 (D44); row R-1283]:** the same commit also
|
||||
marks the download folders of NZBGet, SABnzbd and MeTube `excluded` (`templates/{nzbget,sabnzbd,metube}/.felhom.yml`).
|
||||
NZBGet and SABnzbd stay excluded (their files can be fetched again). The pick for MeTube: `optional`, so the
|
||||
second-drive copy carries it and the off-site copy does not — a MeTube video can be gone from its source site. Not
|
||||
built yet. **[DESIGN]** The per-drive media bind is declared `excluded` for every media app — the family's
|
||||
media is no single app's data; whether the library gets its own household-level backup is a separate decision.
|
||||
|
||||
## 8. Removal and export assume one drive per app
|
||||
@@ -268,7 +272,10 @@ returns** (G7f).
|
||||
|
||||
|
||||
|
||||
**[DESIGN — undocumented, found 2026-10-24, review round 2 — confirmed through one register row]:** the „Később"
|
||||
**[DESIGN — the operator picked a code change 2026-10-25 (D44); row R-1282]:** the „Később"
|
||||
03:30 media restart is an IN-MEMORY timer — a controller restart forgets it and only the notice stays
|
||||
(`internal/web/media_events.go` `handleMediaRestart`, `time.AfterFunc`); the Sonarr/Radarr API key is READ from `/config/config.xml`, the „set at
|
||||
install" option §6 calls cleaner was not built (`internal/mediawire/mediawire.go:336-345`).
|
||||
(`internal/web/media_events.go` `handleMediaRestart`, `time.AfterFunc`). The pick: the controller stores the due
|
||||
time and runs the restart after a restart of its own. Not built yet.
|
||||
|
||||
**[DESIGN — confirmed by the operator 2026-10-25 (D44 = all picks, R-1265)]:** the Sonarr/Radarr API key is READ from
|
||||
`/config/config.xml`; the „set at install" option §6 calls cleaner was not built (`internal/mediawire/mediawire.go:336-345`).
|
||||
@@ -240,7 +240,7 @@ Also in build B: R-1084 (the whole-box backup offer asks the same-disk question)
|
||||
offered to attach, by device). A bundle that adds a path needs a step bundle on boxes with an older wrapper (R16, R-880):
|
||||
0.158.1 was delivered through `0.158.1-step1`.
|
||||
|
||||
## Decisions in code no section records [DESIGN — undocumented, found 2026-10-24, review round 2 — confirmed through one register row]
|
||||
## Decisions in code no section records [DESIGN — confirmed by the operator 2026-10-25 (D44 = all picks, R-1265)]
|
||||
|
||||
Read at `file:line` on `main` by the main session:
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+34
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"app": "tandoor",
|
||||
"install": true,
|
||||
"seed": true,
|
||||
"backup": {
|
||||
"code": "200",
|
||||
"seconds": 75,
|
||||
"status": "{\"db_dump\": {\"count\": 3, \"duration\": \"1m13.056908559s\", \"last_run\": \"2026-10-25T16:40:16.886510867Z\", \"success\": true}, \"enabled\": true, \"running\": false}"
|
||||
},
|
||||
"reinstall": true,
|
||||
"set_aside_line": "2026/10/25 16:44:21 unit_owner.go:182: [INFO] [backup] tandoor: the recovery unit of an earlier install (c3b2e33d5bc82152; the install ids differ) is SET ASIDE whole → /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T164421Z; this install (8c1656d41509efbc) gets its own unit from its first backup (D47)",
|
||||
"set_aside_dirs": [
|
||||
"20261025T164421Z"
|
||||
],
|
||||
"set_aside_byte_identical": true,
|
||||
"restore_page_offers": true,
|
||||
"restore": {
|
||||
"seconds": 95,
|
||||
"status": "{\"running\": false, \"op\": \"restore\", \"stack\": \"tandoor\", \"started_at\": \"2026-10-25T16:44:33.134197317Z\", \"last\": {\"op\": \"restore\", \"stack\": \"tandoor\", \"ok\": true, \"message\": \"A(z) tandoor: 3 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult.\", \"finished_at\": \"2026-10-25T16:46:07.928283511Z\"}, \"last_recent\": true}"
|
||||
},
|
||||
"state_after_restore": "running",
|
||||
"item_after_restore": true,
|
||||
"db_auth_errors_after_restore": "tandoor: 0\ntandoor-postgres: 0",
|
||||
"controller_lines": "2026/10/25 16:39:24 recovery_unit.go:297: [INFO] [backup] Recovery unit captured for tandoor → /mnt/sys_drive/felhom-data/backups/primary/tandoor (images=2, secrets-referenced=2, data_keys=0, portable-carried=2/2, withheld=0)\n2026/10/25 16:40:16 recovery_unit.go:297: [INFO] [backup] Recovery unit captured for tandoor → /mnt/sys_drive/felhom-data/backups/primary/tandoor (images=2, secrets-referenced=2, data_keys=0, portable-carried=2/2, withheld=0)\n2026/10/25 16:44:21 unit_owner.go:182: [INFO] [backup] tandoor: the recovery unit of an earlier install (c3b2e33d5bc82152; the install ids differ) is SET ASIDE whole → /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T164421Z; this install (8c1656d41509efbc) gets its own unit from its \n2026/10/25 16:44:21 recovery_unit.go:297: [INFO] [backup] Recovery unit captured for tandoor → /mnt/sys_drive/felhom-data/backups/primary/tandoor (images=2, secrets-referenced=2, data_keys=0, portable-carried=2/2, withheld=0)\n2026/10/25 16:44:33 kept_copies.go:108: [WARN] [web] Restore of tandoor from a set-aside unit requested (async): /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T164421Z from 172.18.0.4:53858\n2026/10/25 16:44:33 unit_owner.go:324: [INFO] [backup] restore tandoor from the set-aside unit /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T164421Z (an earlier install, D47)\n2026/10/25 16:44:33 restore_dbcreds.go:114: [INFO] [backup] Restore tandoor: database password(s) [DB_PASSWORD] come from the backup, [] stay as installed (older than the data) — values not logged (R-1106)\n2026/10/25 16:44:33 restore_unit.go:410: [INFO] [backup] Restoring tandoor from recovery unit /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T164421Z: images=2, secrets recovered=2/2, data_keys=0\n2026/10/25 16:44:41 offbox_restore_undo.go:169: [INFO] [offbox] tandoor: the live volumes are copied before the restore (3 copied, 0 new from the snapshot) in 4.295s — /mnt/sys_drive/felhom-data/backups/restore-undo/tandoor/20261025T164437Z (R-893)\n2026/10/25 16:44:41 restore_crash_putback.go:89: [INFO] [backup] tandoor: the restore copy is recorded — a power cut from here on puts the app back on start (R-1195)\n2026/10/25 16:44:41 restore.go:225: [INFO] [backup] Restoring Docker volume tandoor_tandoor_media for tandoor\n2026/10/25 16:44:42 restore.go:263: [DEBUG] [backup] Volume tandoor_tandoor_media restored successfully\n2026/10/25 16:44:42 restore.go:225: [INFO] [backup] Restoring Docker volume tandoor_tandoor_postgres_data for tandoor\n2026/10/25 16:44:44 restore.go:263: [DEBUG] [backup] Volume tandoor_tandoor_postgres_data restored successfully\n2026/10/25 16:44:44 restore.go:225: [INFO] [backup] Restoring Docker volume tandoor_tandoor_static for tandoor\n2026/10/25 16:44:47 restore.go:263: [DEBUG] [backup] Volume tandoor_tandoor_static restored successfully\n2026/10/25 16:44:47 restore.go:268: [INFO] [backup] Restored 3 Docker volume(s) for tandoor\n2026/10/25 16:44:47 deploy.go:866: [INFO] [stacks] tandoor: the restored data belongs to install c3b2e33d5bc82152 (was 8c1656d41509efbc) — its backups follow it (D47)\n2026/10/25 16:44:48 restore_db.go:87: [INFO] [backup] Restore tandoor: replaying DB dump into tandoor-postgres (postgres)\n2026/10/25 16:44:52 restore_db.go:97: [INFO] [backup] Restore tandoor: replayed 1 DB dump(s)\n2026/10/25 16:44:56 restore.go:308: [DEBUG] [backup] Post-restore health check: tandoor not yet running, waiting...\n2026/10/25 16:45:01 restore.go:308: [DEBUG] [backup] Post-restore health check: tandoor not yet running, waiting...\n2026/10/25 16:45:07 restore.go:308: [DEBUG] [backup] Post-restore health check: tandoor not yet running, waiting...\n2026/10/25 16:45:12 restore.go:308: [DEBUG] [backup] Post-restore health check: tandoor not yet running, waiting...\n2026/10/25 16:45:17 restore.go:303: [DEBUG] [backup] Post-restore health check: tandoor is running\n2026/10/25 16:46:07 restore_unit.go:591: [INFO] [backup] Restore-from-unit completed: tandoor — 3 volume(s) of 3 listed, 1 database(s) of 1 listed\n",
|
||||
"after_remove": {
|
||||
"containers": "",
|
||||
"volumes": "",
|
||||
"appyaml": "",
|
||||
"appdata": "",
|
||||
"kept": "",
|
||||
"subdomain": "404"
|
||||
},
|
||||
"set_aside_after_remove": "ls: cannot access '/mnt/sys_drive/felhom-data/backups/set-aside/tandoor': No such file or directory"
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
Megőrzött másolatok</h3>
|
||||
<p class="form-hint" style="margin-bottom:.75rem">Ezek a második meghajtón lévő másolatok olyan mappákhoz tartoznak, amelyek most nem érhetők el: a meghajtó nincs csatlakoztatva, már nincs a gépen, vagy a mappa hiányzik. Megőrizzük őket, amíg te el nem távolítod őket.</p>
|
||||
<div class="app-row-list">
|
||||
|
||||
|
||||
<div class="app-row">
|
||||
<img class="app-row-icon" src="/static/assets/-logo.svg" alt="" data-fallback="/static/app-placeholder.svg"
|
||||
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
|
||||
<div class="app-row-text">
|
||||
<span class="app-row-name">tandoor</span>
|
||||
<span class="app-row-secondary">a(z) sys_drive meghajtóról megőrzött másolat · egy korábbi telepítés mentése (újratelepítés előtt készült) · 144,3 MB · 2026. 10. 25. 17:44</span>
|
||||
</div>
|
||||
<div class="app-row-actions">
|
||||
|
||||
<form method="POST" action="/backup/kept/restore" style="display:inline">
|
||||
<input type="hidden" name="_csrf" value="[csrf blanked]">
|
||||
<input type="hidden" name="id" value="9a89871d393b93e6">
|
||||
<input type="hidden" name="confirm" value="1">
|
||||
<button type="button" class="btn btn-xs btn-outline"
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
761d5637752bf2695aaff064bcd7973fd5301bff2536e067a762e348a58cfad6 ./compose/.felhom.yml
|
||||
6b80a31a42b9a7fbe879a639944604ebd304fa59987855780a6b093a3d09daaa ./compose/app.yaml
|
||||
851523d2779a6739bd464fd2dc415a191164afcd7aae9d195fbc482a28ec8b25 ./compose/docker-compose.yml
|
||||
f2a36c7337e3593cd9d405707290412e9c874d9ecc8812e69940c97ff12a2ca0 ./data-stamps.json
|
||||
603c168bffcc4e1a88e037195c24324e5e04a0cb39759c020bedc13f4a3cfd0d ./db-dumps/tandoor-postgres.sql
|
||||
e5949ef862580bdcb06f7b5d7f5f9fda852bd33cda2a2d91906ea7c4a7d74d1e ./manifest.json
|
||||
10e76681727084401b338c29b57154e994686da0c2314cdcf5f1a34aa85e7fc2 ./volume-dumps/tandoor_tandoor_media.tar
|
||||
271591a0dd6e8525d7f0dc2ada6a27ac6ddb608fc2840afaf3c686e4a9efd628 ./volume-dumps/tandoor_tandoor_postgres_data.tar
|
||||
ffa924a80b1061ab26b98fd991cec247d109dd133a1c48d2b91d731e95e0a237 ./volume-dumps/tandoor_tandoor_static.tar
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
761d5637752bf2695aaff064bcd7973fd5301bff2536e067a762e348a58cfad6 ./compose/.felhom.yml
|
||||
6b80a31a42b9a7fbe879a639944604ebd304fa59987855780a6b093a3d09daaa ./compose/app.yaml
|
||||
851523d2779a6739bd464fd2dc415a191164afcd7aae9d195fbc482a28ec8b25 ./compose/docker-compose.yml
|
||||
f2a36c7337e3593cd9d405707290412e9c874d9ecc8812e69940c97ff12a2ca0 ./data-stamps.json
|
||||
603c168bffcc4e1a88e037195c24324e5e04a0cb39759c020bedc13f4a3cfd0d ./db-dumps/tandoor-postgres.sql
|
||||
e5949ef862580bdcb06f7b5d7f5f9fda852bd33cda2a2d91906ea7c4a7d74d1e ./manifest.json
|
||||
10e76681727084401b338c29b57154e994686da0c2314cdcf5f1a34aa85e7fc2 ./volume-dumps/tandoor_tandoor_media.tar
|
||||
271591a0dd6e8525d7f0dc2ada6a27ac6ddb608fc2840afaf3c686e4a9efd628 ./volume-dumps/tandoor_tandoor_postgres_data.tar
|
||||
ffa924a80b1061ab26b98fd991cec247d109dd133a1c48d2b91d731e95e0a237 ./volume-dumps/tandoor_tandoor_static.tar
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
761d5637752bf2695aaff064bcd7973fd5301bff2536e067a762e348a58cfad6 ./compose/.felhom.yml
|
||||
691bea19ea9a4b6dec01538da153b239e860a619666472c9226377acc0399ca2 ./compose/app.yaml
|
||||
851523d2779a6739bd464fd2dc415a191164afcd7aae9d195fbc482a28ec8b25 ./compose/docker-compose.yml
|
||||
6c74f6c6dccf492a998f49217223ef03195daf910588376217b0f800830dbcc7 ./manifest.json
|
||||
@@ -0,0 +1,17 @@
|
||||
16:35:08Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
|
||||
16:38:44Z [1] deployed, controller state=running, pinned={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:17-alpine'}
|
||||
16:38:44Z gate: recipes is gated — passed as the household (cookie set)
|
||||
16:38:58Z tandoor: createsuperuser :: Running django-vite in production mode (no HMR) Superuser created successfully.
|
||||
16:39:03Z tandoor: seeded superuser drill4c8145
|
||||
16:39:03Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
|
||||
16:40:56Z [5] stop 200, remove KEEPING data -> 200 {'ok': True, 'data': {'removed': 'tandoor', 'volumes_removed': ['tandoor_tandoor_media', 'tandoor_tandoor_postgres_data', 'tandoor_tandoor_static'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
|
||||
16:41:02Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
|
||||
16:43:43Z [1] deployed, controller state=running, pinned={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:17-alpine'}
|
||||
16:44:23Z set aside: 2026/10/25 16:44:21 unit_owner.go:182: [INFO] [backup] tandoor: the recovery unit of an earlier install (c3b2e33d5bc82152; the install ids differ) is SET ASIDE whole → /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T164421Z; this install (8c1656d41509efbc) gets its own unit from its fi
|
||||
16:44:29Z the set-aside unit is install A's unit byte for byte: True (9 files)
|
||||
16:44:33Z [6] POST /backup/kept/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
|
||||
16:46:08Z restore status: {"running": false, "op": "restore", "stack": "tandoor", "started_at": "2026-10-25T16:44:33.134197317Z", "last": {"op": "restore", "stack": "tandoor", "ok": true, "message": "A(z) tandoor: 3 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult.", "finished_at": "2026-10-25T16:46:07.9282
|
||||
16:46:37Z tandoor: readback of the seeded account found=True
|
||||
16:46:41Z state after restore: running, item: True, db auth errors: tandoor: 0
|
||||
tandoor-postgres: 0
|
||||
16:47:19Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'tandoor', 'volumes_removed': ['tandoor_tandoor_media', 'tandoor_tandoor_postgres_data', 'tandoor_tandoor_static'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
|
||||
@@ -0,0 +1,17 @@
|
||||
16:35:08Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
|
||||
16:38:44Z [1] deployed, controller state=running, pinned={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:17-alpine'}
|
||||
16:38:44Z gate: recipes is gated — passed as the household (cookie set)
|
||||
16:38:58Z tandoor: createsuperuser :: Running django-vite in production mode (no HMR) Superuser created successfully.
|
||||
16:39:03Z tandoor: seeded superuser drill4c8145
|
||||
16:39:03Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
|
||||
16:40:56Z [5] stop 200, remove KEEPING data -> 200 {'ok': True, 'data': {'removed': 'tandoor', 'volumes_removed': ['tandoor_tandoor_media', 'tandoor_tandoor_postgres_data', 'tandoor_tandoor_static'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
|
||||
16:41:02Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
|
||||
16:43:43Z [1] deployed, controller state=running, pinned={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:17-alpine'}
|
||||
16:44:23Z set aside: 2026/10/25 16:44:21 unit_owner.go:182: [INFO] [backup] tandoor: the recovery unit of an earlier install (c3b2e33d5bc82152; the install ids differ) is SET ASIDE whole → /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T164421Z; this install (8c1656d41509efbc) gets its own unit from its fi
|
||||
16:44:29Z the set-aside unit is install A's unit byte for byte: True (9 files)
|
||||
16:44:33Z [6] POST /backup/kept/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
|
||||
16:46:08Z restore status: {"running": false, "op": "restore", "stack": "tandoor", "started_at": "2026-10-25T16:44:33.134197317Z", "last": {"op": "restore", "stack": "tandoor", "ok": true, "message": "A(z) tandoor: 3 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult.", "finished_at": "2026-10-25T16:46:07.9282
|
||||
16:46:37Z tandoor: readback of the seeded account found=True
|
||||
16:46:41Z state after restore: running, item: True, db auth errors: tandoor: 0
|
||||
tandoor-postgres: 0
|
||||
16:47:19Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'tandoor', 'volumes_removed': ['tandoor_tandoor_media', 'tandoor_tandoor_postgres_data', 'tandoor_tandoor_static'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
|
||||
@@ -0,0 +1,6 @@
|
||||
15:54:21Z preflight 200 {"data": {"agent_supported": true, "escrow_state": "escrowed", "items": [{"id": "pbs_storage_id", "ok": true, "detail": "felhom-pbs"}, {"id": "dr_tier", "ok": true, "detail": "DR tier applied"}, {"id": "age_binary", "ok": true, "detail": "/usr/bin/age"}, {"id": "hub_upload", "ok": true, "detail": "h
|
||||
15:54:21Z start 200 {"data": {"job_id": "escrow-1792943661704480846", "phase": "running"}, "error": "", "ok": true}
|
||||
15:54:25Z status 200 {"phase": "done", "claimable": true, "claimed": false, "restic_pw_sealed": true, "uploaded": false, "awaiting_confirm": null, "key_fingerprint": "82:c6:61:ba:ae:36:47:af:0f:dc:17:0b:0d:5c:5d:63:cd:e7:47:e6:fe:9a:14:fe:66:c1:f0:e6:8f:a1:44:3b", "detail": ""}
|
||||
15:54:25Z claim 200 code received: True confirm_required: True positions: [7, 9]
|
||||
15:54:25Z confirm 200 {"data": {"uploaded": true}, "error": "", "ok": true}
|
||||
15:54:27Z final status 200 {"phase": "done", "claimable": false, "claimed": true, "restic_pw_sealed": true, "uploaded": true, "awaiting_confirm": null, "key_fingerprint": "82:c6:61:ba:ae:36:47:af:0f:dc:17:0b:0d:5c:5d:63:cd:e7:47:e6:fe:9a:14:fe:66:c1:f0:e6:8f:a1:44:3b", "detail": ""}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,23 @@
|
||||
16:11:20Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
|
||||
16:11:30Z [1] deployed, controller state=running, pinned={'vikunja': 'vikunja/vikunja:2.6.0'}
|
||||
16:11:30Z gate: tasks is gated — passed as the household (cookie set)
|
||||
16:11:31Z vikunja: register http=200
|
||||
16:11:31Z vikunja: create project http=201
|
||||
16:11:31Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
|
||||
16:12:52Z [A] the unit now runs: 12: image: busybox:1.36.1 | 13: command: ["sh", "-c", "sleep 20; exit 1"] |
|
||||
16:12:52Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
|
||||
16:15:23Z [A] restore: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T16:12:52.419318838Z", "last": {"op": "restore", "stack": "vikunja", "ok": false, "message": "A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így marad, szólj nekünk.", "finished_at": "2026-10-25T16:15:22.850064238Z", "not_started": true}, "last_r
|
||||
16:15:29Z [A] containers: /vikunja 2026-10-25T16:15:21.005421057Z 7 running | kept undo copy: 20261025T161255Z
|
||||
16:16:33Z [A] 60 s later: /vikunja 2026-10-25T16:16:22.058355517Z 10 running
|
||||
16:16:42Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
|
||||
16:17:52Z [A] normal restore: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T16:16:42.621586165Z", "last": {"op": "restore", "stack": "vikunja", "ok": true, "message": "A(z) vikunja: 2 adatkötet visszaállítva — az alkalmazás újraindult.", "finished_at": "2026-10-25T16:17:50.752711036Z"}, "last_
|
||||
16:17:56Z [A] the copy right after the success: 20261025T161654Z
|
||||
16:21:06Z vikunja: readback of the seeded project http=200 ok=True
|
||||
16:21:06Z [A] after the window: ls: cannot access '/mnt/sys_drive/felhom-data/backups/restore-undo/vikunja': No such file or directory; item True
|
||||
16:21:06Z [B] export start -> 200 {'ok': True}
|
||||
16:21:14Z [B] the test bundle: 12: image: vikunja/vikunja:0.0.0-felhom-r1279-missing | -rw-r--r-- 1 root root 61278 Oct 25 16:21 /mnt/felhom-drives/scratch_hdd/exports/vikunja-r1279-test.fab |
|
||||
16:21:21Z [B] import -> 200 {'ok': True}
|
||||
16:21:32Z vikunja: readback of the seeded project http=200 ok=True
|
||||
16:21:32Z [B] import status: {"display_name": "Vikunja", "done": true, "error": "Ennek a mentésnek a verzióját (vikunja:0.0.0-felhom-r1279-missing) nem lehet letölteni: a készítője már nem kínálja. Az alkalmazáshoz nem nyúltunk. Válassz egy másik mentést.", "job_type": "import", "ok": true, "output_path": "", "output_size": "", "running": false, "stack_name": "vikunja", "steps": [{"label": "Csomag megnyitása", "status": "fail
|
||||
16:21:32Z [B] containers and volumes untouched: True; item True
|
||||
16:22:11Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'vikunja', 'volumes_removed': ['vikunja_vikunja_data', 'vikunja_vikunja_db'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt
|
||||
@@ -0,0 +1,23 @@
|
||||
12:08:09Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
|
||||
12:08:19Z [1] deployed, controller state=running, pinned={'vikunja': 'vikunja/vikunja:2.6.0'}
|
||||
12:08:19Z gate: tasks is gated — passed as the household (cookie set)
|
||||
12:08:19Z vikunja: register http=200
|
||||
12:08:20Z vikunja: create project http=201
|
||||
12:08:20Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
|
||||
12:09:35Z [A] the unit now runs: 12: image: busybox:1.36.1 | 13: command: ["sh", "-c", "sleep 20; exit 1"] |
|
||||
12:09:35Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
|
||||
12:12:06Z [A] restore: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T12:09:35.490986371Z", "last": {"op": "restore", "stack": "vikunja", "ok": false, "message": "A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így marad, szólj nekünk.", "finished_at": "2026-10-25T12:12:03.605529263Z", "not_started": true}, "last_r
|
||||
12:12:12Z [A] containers: /vikunja 2026-10-25T12:12:01.767033363Z 7 running | kept undo copy: 20261025T120937Z
|
||||
12:13:16Z [A] 60 s later: /vikunja 2026-10-25T12:13:02.760336787Z 10 running
|
||||
12:13:25Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
|
||||
12:16:06Z [A] normal restore: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T12:13:25.792676954Z", "last": {"op": "restore", "stack": "vikunja", "ok": false, "message": "A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így m
|
||||
12:16:09Z [A] the copy right after the success: 20261025T121337Z
|
||||
12:22:50Z vikunja: readback of the seeded project http=200 ok=True
|
||||
12:22:50Z [A] after the window: 20261025T121337Z; item True
|
||||
12:22:50Z [B] export start -> 200 {'ok': True}
|
||||
12:22:58Z [B] the test bundle: 12: image: vikunja/vikunja:0.0.0-felhom-r1279-missing | -rw-r--r-- 1 root root 61273 Oct 25 12:22 /mnt/felhom-drives/scratch_hdd/exports/vikunja-r1279-test.fab |
|
||||
12:23:05Z [B] import -> 200 {'ok': True}
|
||||
12:23:16Z vikunja: readback of the seeded project http=200 ok=True
|
||||
12:23:16Z [B] import status: {"display_name": "Vikunja", "done": true, "error": "Ennek a mentésnek a verzióját (vikunja:0.0.0-felhom-r1279-missing) nem lehet letölteni: a készítője már nem kínálja. Az alkalmazáshoz nem nyúltunk. Válassz egy másik mentést.", "job_type": "import", "ok": true, "output_path": "", "output_size": "", "running": false, "stack_name": "vikunja", "steps": [{"label": "Csomag megnyitása", "status": "fail
|
||||
12:23:16Z [B] containers and volumes untouched: True; item True
|
||||
12:23:55Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'vikunja', 'volumes_removed': ['vikunja_vikunja_data', 'vikunja_vikunja_db'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"install": true,
|
||||
"seed": true,
|
||||
"backup": {
|
||||
"code": "200",
|
||||
"seconds": 65,
|
||||
"status": "{\"db_dump\": {\"count\": 2, \"duration\": \"1m0.644813656s\", \"last_run\": \"2026-10-25T12:09:20.752646875Z\", \"success\": true}, \"enabled\": true, \"running\": false}"
|
||||
},
|
||||
"a_restore": {
|
||||
"points": 1,
|
||||
"location": [
|
||||
"location: /backups/restore?flash=flash.restore.started"
|
||||
],
|
||||
"seconds": 151,
|
||||
"status": "{\"running\": false, \"op\": \"restore\", \"stack\": \"vikunja\", \"started_at\": \"2026-10-25T12:09:35.490986371Z\", \"last\": {\"op\": \"restore\", \"stack\": \"vikunja\", \"ok\": false, \"message\": \"A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így marad, szólj nekünk.\", \"finished_at\": \"2026-10-25T12:12:03.605529263Z\", \"not_started\": true}, \"last_recent\": true}",
|
||||
"state": "unhealthy"
|
||||
},
|
||||
"a_containers": "/vikunja 2026-10-25T12:12:01.767033363Z 7 running",
|
||||
"a_undo": "20261025T120937Z",
|
||||
"a_containers_60s_later": "/vikunja 2026-10-25T12:13:02.760336787Z 10 running",
|
||||
"original_back": true,
|
||||
"a_normal_restore": {
|
||||
"points": 1,
|
||||
"location": [
|
||||
"location: /backups/restore?flash=flash.restore.started"
|
||||
],
|
||||
"seconds": 161,
|
||||
"status": "{\"running\": false, \"op\": \"restore\", \"stack\": \"vikunja\", \"started_at\": \"2026-10-25T12:13:25.792676954Z\", \"last\": {\"op\": \"restore\", \"stack\": \"vikunja\", \"ok\": false, \"message\": \"A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így marad, szólj nekünk.\", \"finished_at\": \"2026-10-25T12:16:04.094885331Z\", \"not_started\": true}, \"last_recent\": true}",
|
||||
"state": "running"
|
||||
},
|
||||
"a_undo_right_after": "20261025T121337Z",
|
||||
"a_undo_after_window": "20261025T121337Z",
|
||||
"a_item_after": true,
|
||||
"b_bundle": [
|
||||
"/mnt/felhom-drives/scratch_hdd/exports/vikunja_20261025-122251.fab"
|
||||
],
|
||||
"b_import_status": "{\"display_name\": \"Vikunja\", \"done\": true, \"error\": \"Ennek a mentésnek a verzióját (vikunja:0.0.0-felhom-r1279-missing) nem lehet letölteni: a készítője már nem kínálja. Az alkalmazáshoz nem nyúltunk. Válassz egy másik mentést.\", \"job_type\": \"import\", \"ok\": true, \"output_path\": \"\", \"output_size\": \"\", \"running\": false, \"stack_name\": \"vikunja\", \"steps\": [{\"label\": \"Csomag megnyitása\", \"status\": \"failed\", \"error\": \"Ennek a mentésnek a verzióját (vikunja:0.0.0-felhom-r1279-missing) nem lehet letölteni: a készítője már nem kínálja. Az alkalmazáshoz nem nyúltunk. Válassz egy másik mentést.\"}, {\"label\": \"Alkalmazás előkészítése\", \"status\": \"pending\"}, {\"label\": \"Konfiguráció visszaállítása\", \"status\": \"pending\"}, {\"label\": \"Felhasználói adatok visszaállítása\", \"status\": \"pending\"}, {\"label\": \"Adatbázis visszaállítása\", \"status\": \"pending\"}, {\"label\": \"Alkalmazás indítása\", \"status\": \"pending\"}]}",
|
||||
"b_containers_before": "/vikunja 2026-10-25T12:13:39.260893929Z 0 running",
|
||||
"b_containers_after": "/vikunja 2026-10-25T12:13:39.260893929Z 0 running",
|
||||
"b_volumes_before": "vikunja_vikunja_data 2026-10-25T12:13:38Z\nvikunja_vikunja_db 2026-10-25T12:13:38Z",
|
||||
"b_volumes_after": "vikunja_vikunja_data 2026-10-25T12:13:38Z\nvikunja_vikunja_db 2026-10-25T12:13:38Z",
|
||||
"b_untouched": true,
|
||||
"b_item_still_there": true,
|
||||
"controller_lines": "2026/10/25 12:09:44 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 12:10:04 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 12:10:14 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 12:10:24 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 12:10:44 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 12:11:04 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 12:11:24 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 12:11:44 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 12:12:03 restore_verdict.go:54: [ERROR] [backup] vikunja: the unit restore put the data back, but the app did not start: stack vikunja did not run stably for 45s (it restarted or turned unhealthy) after restore — NOT reported as a success; the restore copy is kept (/mnt/sys_drive/felhom-data/backups/restor\n2026/10/25 12:12:04 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 12:12:14 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 12:12:34 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 12:12:44 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 12:12:54 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 12:13:14 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 12:13:34 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 12:16:04 restore_verdict.go:54: [ERROR] [backup] vikunja: the unit restore put the data back, but the app did not start: stack vikunja did not run stably for 45s (it restarted or turned unhealthy) after restore — NOT reported as a success; the restore copy is kept (/mnt/sys_drive/felhom-data/backups/restor\n2026/10/25 12:23:05 handler_export.go:328: [DEBUG] [web] apiImportStart: path=\"/mnt/felhom-drives/scratch_hdd/exports/vikunja-r1279-test.fab\" hasPassword=<redacted>\n2026/10/25 12:23:05 export.go:166: [DEBUG] [appexport] StartImport: path=/mnt/felhom-drives/scratch_hdd/exports/vikunja-r1279-test.fab size=59.8 KB encrypted=false\n2026/10/25 12:23:05 handler_export.go:352: [INFO] [web] Import started from /mnt/felhom-drives/scratch_hdd/exports/vikunja-r1279-test.fab\n2026/10/25 12:23:05 restore.go:223: [INFO] [appexport] Import started for vikunja-r1279-test.fab\n2026/10/25 12:23:05 export.go:166: [DEBUG] [appexport] === IMPORT START: path=/mnt/felhom-drives/scratch_hdd/exports/vikunja-r1279-test.fab encrypted=false ===\n2026/10/25 12:23:05 restore.go:330: [INFO] Import: opening bundle for vikunja (Vikunja)\n2026/10/25 12:23:06 restore_pull.go:96: [ERROR] [backup] Restore REFUSED for vikunja before anything was touched: the image vikunja/vikunja:0.0.0-felhom-r1279-missing could not be downloaded (the version does not exist any more; after 1s): exit status 1 — Error response from daemon: manifest for vikunja/vikunja:0.0.0\n2026/10/25 12:23:06 restore.go:355: [WARN] Import: REFUSED before anything was touched — an image of vikunja could not be downloaded: Ennek a mentésnek a verzióját (vikunja:0.0.0-felhom-r1279-missing) nem lehet letölteni: a készítője már nem kínálja. Az Line truncated
|
||||
"after_remove": {
|
||||
"containers": "",
|
||||
"volumes": "",
|
||||
"appyaml": "",
|
||||
"appdata": "",
|
||||
"kept": "",
|
||||
"subdomain": "404"
|
||||
},
|
||||
"cleanup": "ls: cannot access '/mnt/sys_drive/felhom-data/backups/fix-a-test-original/vikunja': No such file or directory\nls: cannot access '/mnt/sys_drive/felhom-data/backups/restore-undo/vikunja': No such file or directory"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
12:08:09Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
|
||||
12:08:19Z [1] deployed, controller state=running, pinned={'vikunja': 'vikunja/vikunja:2.6.0'}
|
||||
12:08:19Z gate: tasks is gated — passed as the household (cookie set)
|
||||
12:08:19Z vikunja: register http=200
|
||||
12:08:20Z vikunja: create project http=201
|
||||
12:08:20Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
|
||||
12:09:35Z [A] the unit now runs: 12: image: busybox:1.36.1 | 13: command: ["sh", "-c", "sleep 20; exit 1"] |
|
||||
12:09:35Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
|
||||
12:12:06Z [A] restore: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T12:09:35.490986371Z", "last": {"op": "restore", "stack": "vikunja", "ok": false, "message": "A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így marad, szólj nekünk.", "finished_at": "2026-10-25T12:12:03.605529263Z", "not_started": true}, "last_r
|
||||
12:12:12Z [A] containers: /vikunja 2026-10-25T12:12:01.767033363Z 7 running | kept undo copy: 20261025T120937Z
|
||||
12:13:16Z [A] 60 s later: /vikunja 2026-10-25T12:13:02.760336787Z 10 running
|
||||
12:13:25Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
|
||||
12:16:06Z [A] normal restore: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T12:13:25.792676954Z", "last": {"op": "restore", "stack": "vikunja", "ok": false, "message": "A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így m
|
||||
12:16:09Z [A] the copy right after the success: 20261025T121337Z
|
||||
12:22:50Z vikunja: readback of the seeded project http=200 ok=True
|
||||
12:22:50Z [A] after the window: 20261025T121337Z; item True
|
||||
12:22:50Z [B] export start -> 200 {'ok': True}
|
||||
12:22:58Z [B] the test bundle: 12: image: vikunja/vikunja:0.0.0-felhom-r1279-missing | -rw-r--r-- 1 root root 61273 Oct 25 12:22 /mnt/felhom-drives/scratch_hdd/exports/vikunja-r1279-test.fab |
|
||||
12:23:05Z [B] import -> 200 {'ok': True}
|
||||
12:23:16Z vikunja: readback of the seeded project http=200 ok=True
|
||||
12:23:16Z [B] import status: {"display_name": "Vikunja", "done": true, "error": "Ennek a mentésnek a verzióját (vikunja:0.0.0-felhom-r1279-missing) nem lehet letölteni: a készítője már nem kínálja. Az alkalmazáshoz nem nyúltunk. Válassz egy másik mentést.", "job_type": "import", "ok": true, "output_path": "", "output_size": "", "running": false, "stack_name": "vikunja", "steps": [{"label": "Csomag megnyitása", "status": "fail
|
||||
12:23:16Z [B] containers and volumes untouched: True; item True
|
||||
12:23:55Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'vikunja', 'volumes_removed': ['vikunja_vikunja_data', 'vikunja_vikunja_db'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"install": true,
|
||||
"seed": true,
|
||||
"backup": {
|
||||
"code": "200",
|
||||
"seconds": 70,
|
||||
"status": "{\"db_dump\": {\"count\": 2, \"duration\": \"1m6.261101247s\", \"last_run\": \"2026-10-25T16:12:37.692662134Z\", \"success\": true}, \"enabled\": true, \"running\": false}"
|
||||
},
|
||||
"a_restore": {
|
||||
"points": 1,
|
||||
"location": [
|
||||
"location: /backups/restore?flash=flash.restore.started"
|
||||
],
|
||||
"seconds": 151,
|
||||
"status": "{\"running\": false, \"op\": \"restore\", \"stack\": \"vikunja\", \"started_at\": \"2026-10-25T16:12:52.419318838Z\", \"last\": {\"op\": \"restore\", \"stack\": \"vikunja\", \"ok\": false, \"message\": \"A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így marad, szólj nekünk.\", \"finished_at\": \"2026-10-25T16:15:22.850064238Z\", \"not_started\": true}, \"last_recent\": true}",
|
||||
"state": "unhealthy"
|
||||
},
|
||||
"a_containers": "/vikunja 2026-10-25T16:15:21.005421057Z 7 running",
|
||||
"a_undo": "20261025T161255Z",
|
||||
"a_containers_60s_later": "/vikunja 2026-10-25T16:16:22.058355517Z 10 running",
|
||||
"original_back": true,
|
||||
"a_normal_restore": {
|
||||
"points": 1,
|
||||
"location": [
|
||||
"location: /backups/restore?flash=flash.restore.started"
|
||||
],
|
||||
"seconds": 70,
|
||||
"status": "{\"running\": false, \"op\": \"restore\", \"stack\": \"vikunja\", \"started_at\": \"2026-10-25T16:16:42.621586165Z\", \"last\": {\"op\": \"restore\", \"stack\": \"vikunja\", \"ok\": true, \"message\": \"A(z) vikunja: 2 adatkötet visszaállítva — az alkalmazás újraindult.\", \"finished_at\": \"2026-10-25T16:17:50.752711036Z\"}, \"last_recent\": true}",
|
||||
"state": "running"
|
||||
},
|
||||
"a_undo_right_after": "20261025T161654Z",
|
||||
"a_undo_after_window": "ls: cannot access '/mnt/sys_drive/felhom-data/backups/restore-undo/vikunja': No such file or directory",
|
||||
"a_item_after": true,
|
||||
"b_bundle": [
|
||||
"/mnt/felhom-drives/scratch_hdd/exports/vikunja_20261025-162107.fab"
|
||||
],
|
||||
"b_import_status": "{\"display_name\": \"Vikunja\", \"done\": true, \"error\": \"Ennek a mentésnek a verzióját (vikunja:0.0.0-felhom-r1279-missing) nem lehet letölteni: a készítője már nem kínálja. Az alkalmazáshoz nem nyúltunk. Válassz egy másik mentést.\", \"job_type\": \"import\", \"ok\": true, \"output_path\": \"\", \"output_size\": \"\", \"running\": false, \"stack_name\": \"vikunja\", \"steps\": [{\"label\": \"Csomag megnyitása\", \"status\": \"failed\", \"error\": \"Ennek a mentésnek a verzióját (vikunja:0.0.0-felhom-r1279-missing) nem lehet letölteni: a készítője már nem kínálja. Az alkalmazáshoz nem nyúltunk. Válassz egy másik mentést.\"}, {\"label\": \"Alkalmazás előkészítése\", \"status\": \"pending\"}, {\"label\": \"Konfiguráció visszaállítása\", \"status\": \"pending\"}, {\"label\": \"Felhasználói adatok visszaállítása\", \"status\": \"pending\"}, {\"label\": \"Adatbázis visszaállítása\", \"status\": \"pending\"}, {\"label\": \"Alkalmazás indítása\", \"status\": \"pending\"}]}",
|
||||
"b_containers_before": "/vikunja 2026-10-25T16:16:56.67656Z 0 running",
|
||||
"b_containers_after": "/vikunja 2026-10-25T16:16:56.67656Z 0 running",
|
||||
"b_volumes_before": "vikunja_vikunja_data 2026-10-25T16:16:55Z\nvikunja_vikunja_db 2026-10-25T16:16:55Z",
|
||||
"b_volumes_after": "vikunja_vikunja_data 2026-10-25T16:16:55Z\nvikunja_vikunja_db 2026-10-25T16:16:55Z",
|
||||
"b_untouched": true,
|
||||
"b_item_still_there": true,
|
||||
"controller_lines": "2026/10/25 16:11:31 dbdump.go:149: [DEBUG] DiscoverDatabases: docker ps output: 652806f76d48\tvikunja\tvikunja\tvikunja/vikunja:2.6.0\n2026/10/25 16:13:04 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 16:13:12 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — the stack does not read running\n2026/10/25 16:13:14 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 16:13:22 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — the stack does not read running\n2026/10/25 16:13:32 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — the stack does not read running\n2026/10/25 16:13:34 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 16:13:42 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — the stack does not read running\n2026/10/25 16:13:44 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 16:13:52 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — the stack does not read running\n2026/10/25 16:14:02 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — the stack does not read running\n2026/10/25 16:14:04 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 16:14:12 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — the stack does not read running\n2026/10/25 16:14:14 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 16:14:22 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — the stack does not read running\n2026/10/25 16:14:32 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — the stack does not read running\n2026/10/25 16:14:34 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 16:14:42 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — the stack does not read running\n2026/10/25 16:14:44 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 16:14:52 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — the stack does not read running\n2026/10/25 16:15:02 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — the stack does not read running\n2026/10/25 16:15:04 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 16:15:12 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — the stack does not read running\n2026/10/25 16:15:22 restore_verdict.go:54: [ERROR] [backup] vikunja: the unit restore put the data back, but the app did not start: stack vikunja did not run stably for 45s (it restarted or turned unhealthy) after restore — NOT reported as a success; the restore copy is kept (/mnt/sys_drive/felhom-data/backups/restor\n2026/10/25 16:15:22 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — the stack does not read running\n2026/10/25 16:15:24 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 16:15:32 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — the stack does not read running\n2026/10/25 16:15:42 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — the stack does not read running\n2026/10/25 16:15:44 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info → Get \"http://vikunja:3456/api/v1/info\": dial tcp 172.18.0.7:3456: connect: connection refused\n2026/10/25 16:15:53 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — the stack does not read running\n2026/10/25 16:15:54 healthprobe.go:196: [WARN] Health probe vikunja: API GET :3456/api/v1/info Line truncated
|
||||
"after_remove": {
|
||||
"containers": "",
|
||||
"volumes": "",
|
||||
"appyaml": "",
|
||||
"appdata": "",
|
||||
"kept": "",
|
||||
"subdomain": "404"
|
||||
},
|
||||
"cleanup": "ls: cannot access '/mnt/sys_drive/felhom-data/backups/fix-a-test-original/vikunja': No such file or directory\nls: cannot access '/mnt/sys_drive/felhom-data/backups/restore-undo/vikunja': No such file or directory"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
16:11:20Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
|
||||
16:11:30Z [1] deployed, controller state=running, pinned={'vikunja': 'vikunja/vikunja:2.6.0'}
|
||||
16:11:30Z gate: tasks is gated — passed as the household (cookie set)
|
||||
16:11:31Z vikunja: register http=200
|
||||
16:11:31Z vikunja: create project http=201
|
||||
16:11:31Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
|
||||
16:12:52Z [A] the unit now runs: 12: image: busybox:1.36.1 | 13: command: ["sh", "-c", "sleep 20; exit 1"] |
|
||||
16:12:52Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
|
||||
16:15:23Z [A] restore: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T16:12:52.419318838Z", "last": {"op": "restore", "stack": "vikunja", "ok": false, "message": "A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így marad, szólj nekünk.", "finished_at": "2026-10-25T16:15:22.850064238Z", "not_started": true}, "last_r
|
||||
16:15:29Z [A] containers: /vikunja 2026-10-25T16:15:21.005421057Z 7 running | kept undo copy: 20261025T161255Z
|
||||
16:16:33Z [A] 60 s later: /vikunja 2026-10-25T16:16:22.058355517Z 10 running
|
||||
16:16:42Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
|
||||
16:17:52Z [A] normal restore: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T16:16:42.621586165Z", "last": {"op": "restore", "stack": "vikunja", "ok": true, "message": "A(z) vikunja: 2 adatkötet visszaállítva — az alkalmazás újraindult.", "finished_at": "2026-10-25T16:17:50.752711036Z"}, "last_
|
||||
16:17:56Z [A] the copy right after the success: 20261025T161654Z
|
||||
16:21:06Z vikunja: readback of the seeded project http=200 ok=True
|
||||
16:21:06Z [A] after the window: ls: cannot access '/mnt/sys_drive/felhom-data/backups/restore-undo/vikunja': No such file or directory; item True
|
||||
16:21:06Z [B] export start -> 200 {'ok': True}
|
||||
16:21:14Z [B] the test bundle: 12: image: vikunja/vikunja:0.0.0-felhom-r1279-missing | -rw-r--r-- 1 root root 61278 Oct 25 16:21 /mnt/felhom-drives/scratch_hdd/exports/vikunja-r1279-test.fab |
|
||||
16:21:21Z [B] import -> 200 {'ok': True}
|
||||
16:21:32Z vikunja: readback of the seeded project http=200 ok=True
|
||||
16:21:32Z [B] import status: {"display_name": "Vikunja", "done": true, "error": "Ennek a mentésnek a verzióját (vikunja:0.0.0-felhom-r1279-missing) nem lehet letölteni: a készítője már nem kínálja. Az alkalmazáshoz nem nyúltunk. Válassz egy másik mentést.", "job_type": "import", "ok": true, "output_path": "", "output_size": "", "running": false, "stack_name": "vikunja", "steps": [{"label": "Csomag megnyitása", "status": "fail
|
||||
16:21:32Z [B] containers and volumes untouched: True; item True
|
||||
16:22:11Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'vikunja', 'volumes_removed': ['vikunja_vikunja_data', 'vikunja_vikunja_db'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt
|
||||
@@ -0,0 +1,6 @@
|
||||
# D47 operator-line red-proof: the set-aside never notifies. 2026-10-25T16:15:50Z
|
||||
unit_owner_test.go:320: the operator hears once, with both identities: []
|
||||
--- FAIL: TestD47_ASetAsideTellsTheOperatorOnce (0.00s)
|
||||
FAIL
|
||||
FAIL gitea.dooplex.hu/admin/felhom-controller/internal/backup 0.011s
|
||||
FAIL
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
15:46:04Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
|
||||
15:46:14Z [1] deployed, controller state=running, pinned={'vikunja': 'vikunja/vikunja:2.6.0'}
|
||||
15:46:14Z gate: tasks is gated — passed as the household (cookie set)
|
||||
15:46:14Z vikunja: register http=200
|
||||
15:46:14Z vikunja: create project http=201
|
||||
15:46:14Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
|
||||
15:47:34Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
|
||||
15:50:05Z restore: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T15:47:34.995178121Z", "last": {"op": "restore", "stack": "vikunja", "ok": false, "message": "A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így marad, szólj nekünk.", "finished_at": "2026-10-25T15:50:03.904088759Z", "not_started": true}, "last_r
|
||||
15:50:09Z the copy right after: 20261025T154736Z
|
||||
15:58:10Z vikunja: readback of the seeded project http=200 ok=True
|
||||
15:58:10Z the copy after the window: 20261025T154736Z; item True
|
||||
15:58:19Z [7] stop 200, remove with data -> 409 {'ok': False, 'error': 'Az alkalmazáson mentés vagy visszaállítás fut. Várd meg, amíg befejeződik.'}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
15:46:04Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
|
||||
15:46:14Z [1] deployed, controller state=running, pinned={'vikunja': 'vikunja/vikunja:2.6.0'}
|
||||
15:46:14Z gate: tasks is gated — passed as the household (cookie set)
|
||||
15:46:14Z vikunja: register http=200
|
||||
15:46:14Z vikunja: create project http=201
|
||||
15:46:14Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
|
||||
15:47:34Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
|
||||
15:50:05Z restore: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T15:47:34.995178121Z", "last": {"op": "restore", "stack": "vikunja", "ok": false, "message": "A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így marad, szólj nekünk.", "finished_at": "2026-10-25T15:50:03.904088759Z", "not_started": true}, "last_r
|
||||
15:50:09Z the copy right after: 20261025T154736Z
|
||||
15:58:10Z vikunja: readback of the seeded project http=200 ok=True
|
||||
15:58:10Z the copy after the window: 20261025T154736Z; item True
|
||||
15:58:19Z [7] stop 200, remove with data -> 409 {'ok': False, 'error': 'Az alkalmazáson mentés vagy visszaállítás fut. Várd meg, amíg befejeződik.'}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
15:57:59Z [1] vikunja already deployed — reusing
|
||||
15:58:00Z gate: tasks is gated — passed as the household (cookie set)
|
||||
15:58:00Z vikunja: register http=200
|
||||
15:58:00Z vikunja: create project http=201
|
||||
15:58:00Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
|
||||
15:58:55Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
|
||||
16:01:26Z restore: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T15:58:55.824882192Z", "last": {"op": "restore", "stack": "vikunja", "ok": false, "message": "A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így marad, szólj nekünk.", "finished_at": "2026-10-25T16:01:24.880744612Z", "not_started": true}, "last_r
|
||||
16:01:30Z the copy right after: 20261025T154736Z
|
||||
20261025T155857Z
|
||||
16:09:28Z vikunja: readback of the seeded project http=200 ok=True
|
||||
16:09:28Z the copy after the window: 20261025T154736Z
|
||||
20261025T155857Z; item True
|
||||
16:10:03Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'vikunja', 'volumes_removed': ['vikunja_vikunja_data', 'vikunja_vikunja_db'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"install": true,
|
||||
"backup": {
|
||||
"code": "200",
|
||||
"seconds": 55,
|
||||
"status": "{\"db_dump\": {\"count\": 2, \"duration\": \"54.787148146s\", \"last_run\": \"2026-10-25T15:58:55.262677837Z\", \"success\": true}, \"enabled\": true, \"running\": false}"
|
||||
},
|
||||
"restore": {
|
||||
"points": 1,
|
||||
"location": [
|
||||
"location: /backups/restore?flash=flash.restore.started"
|
||||
],
|
||||
"seconds": 151,
|
||||
"status": "{\"running\": false, \"op\": \"restore\", \"stack\": \"vikunja\", \"started_at\": \"2026-10-25T15:58:55.824882192Z\", \"last\": {\"op\": \"restore\", \"stack\": \"vikunja\", \"ok\": false, \"message\": \"A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így marad, szólj nekünk.\", \"finished_at\": \"2026-10-25T16:01:24.880744612Z\", \"not_started\": true}, \"last_recent\": true}",
|
||||
"state": "running"
|
||||
},
|
||||
"copy_right_after": "20261025T154736Z\n20261025T155857Z",
|
||||
"copy_after_window": "20261025T154736Z\n20261025T155857Z",
|
||||
"item": true,
|
||||
"lines": "2026/10/25 15:59:03 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.State.Health>: map has no entry for key \"\n2026/10/25 15:59:13 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.State.Health>: map has no entry for key \"\n2026/10/25 15:59:23 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.State.Health>: map has no entry for key \"\n2026/10/25 15:59:33 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.State.Health>: map has no entry for key \"\n2026/10/25 15:59:43 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.State.Health>: map has no entry for key \"\n2026/10/25 15:59:54 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.State.Health>: map has no entry for key \"\n2026/10/25 16:00:04 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.State.Health>: map has no entry for key \"\n2026/10/25 16:00:14 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.State.Health>: map has no entry for key \"\n2026/10/25 16:00:24 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.State.Health>: map has no entry for key \"\n2026/10/25 16:00:34 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.State.Health>: map has no entry for key \"\n2026/10/25 16:00:44 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.State.Health>: map has no entry for key \"\n2026/10/25 16:00:54 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.State.Health>: map has no entry for key \"\n2026/10/25 16:01:04 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.State.Health>: map has no entry for key \"\n2026/10/25 16:01:14 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.State.Health>: map has no entry for key \"\n2026/10/25 16:01:24 restore_verdict.go:54: [ERROR] [backup] vikunja: the unit restore put the data back, but the app did not start: stack vikunja did not run stably for 45s (it restarted or turned unhealthy) after restore — NOT reported as a success; the res\n2026/10/25 16:01:24 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.State.Health>: map has no entry for key \"\n2026/10/25 16:01:35 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.State.Health>: map has no entry for key \"\n2026/10/25 16:01:45 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.State.Health>: map has no entry for key \"\n2026/10/25 16:01:55 restore_verdict.go:172: [DEBUG] [backup] stable-start sample vikunja: not ok — no container sample (0, err docker inspect: exit status 1: template parsing error: template: :1:55: executing \"\" at <.Line truncated
|
||||
"after_remove": {
|
||||
"containers": "",
|
||||
"volumes": "",
|
||||
"appyaml": "",
|
||||
"appdata": "",
|
||||
"kept": "",
|
||||
"subdomain": "404"
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
15:57:59Z [1] vikunja already deployed — reusing
|
||||
15:58:00Z gate: tasks is gated — passed as the household (cookie set)
|
||||
15:58:00Z vikunja: register http=200
|
||||
15:58:00Z vikunja: create project http=201
|
||||
15:58:00Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
|
||||
15:58:55Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
|
||||
16:01:26Z restore: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T15:58:55.824882192Z", "last": {"op": "restore", "stack": "vikunja", "ok": false, "message": "A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így marad, szólj nekünk.", "finished_at": "2026-10-25T16:01:24.880744612Z", "not_started": true}, "last_r
|
||||
16:01:30Z the copy right after: 20261025T154736Z
|
||||
20261025T155857Z
|
||||
16:09:28Z vikunja: readback of the seeded project http=200 ok=True
|
||||
16:09:28Z the copy after the window: 20261025T154736Z
|
||||
20261025T155857Z; item True
|
||||
16:10:03Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'vikunja', 'volumes_removed': ['vikunja_vikunja_data', 'vikunja_vikunja_db'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt
|
||||
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
"""escrow_ceremony.py — the recovery-code ceremony on Tester 1 through the wizard's own calls (operator yes,
|
||||
2026-10-25): login → GET /api/escrow/preflight → POST /api/escrow/start (password re-auth) → poll /api/escrow/status →
|
||||
POST /api/escrow/claim (the ONE-SHOT code) → POST /api/escrow/confirm (the two words the agent names). The code is
|
||||
NEVER printed or logged: it goes only into the 0600 file named by argv[1] (the caller hands it to `sec put`, which
|
||||
shreds it). Run through `sec run TESTER1_PASSWORD -- python3 escrow_ceremony.py <codefile>`."""
|
||||
import json, os, re, subprocess, sys, time
|
||||
|
||||
PW = os.environ["TESTER1_PASSWORD"]
|
||||
SSH = ["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=20", "root@192.168.0.154"]
|
||||
HOST = "Host: felhom.enkicsifelhom.hu"
|
||||
|
||||
|
||||
def run(args, inp=None):
|
||||
return subprocess.run(args, input=inp, capture_output=True, text=True, timeout=300)
|
||||
|
||||
|
||||
def base():
|
||||
r = run(SSH + ["pct exec 9201 -- ip -4 -o addr show eth0"])
|
||||
return "https://" + re.search(r"inet (\d+\.\d+\.\d+\.\d+)/", r.stdout).group(1)
|
||||
|
||||
|
||||
def say(*a):
|
||||
print(time.strftime("%H:%M:%SZ", time.gmtime()), *a, flush=True)
|
||||
|
||||
|
||||
B = base()
|
||||
h = run(["curl", "-sk", "-D", "-", "-o", "/dev/null", "-H", HOST, "-X", "POST", "--data-urlencode", "password@-", f"{B}/login"], inp=PW).stdout
|
||||
sess = re.search(r"felhom_session=[A-Za-z0-9._-]+", h).group(0)
|
||||
page = run(["curl", "-sk", "-L", "-H", HOST, "-H", f"Cookie: {sess}", f"{B}/backup/escrow"]).stdout
|
||||
csrf = re.search(r'<meta name="csrf-token" content="([^"]+)"', page).group(1)
|
||||
|
||||
|
||||
def api(method, path, form=None, js=None):
|
||||
a = ["curl", "-sk", "-H", HOST, "-H", f"Cookie: {sess}", "-H", f"X-CSRF-Token: {csrf}", "-X", method, "-w", "\n%{http_code}"]
|
||||
inp = None
|
||||
if form is not None:
|
||||
a += ["--data-urlencode", f"_csrf={csrf}", "--data-urlencode", "password@-"]
|
||||
inp = form
|
||||
if js is not None:
|
||||
a += ["-H", "Content-Type: application/json", "--data-binary", "@-"]
|
||||
inp = json.dumps(js)
|
||||
out = run(a + [f"{B}{path}"], inp=inp).stdout
|
||||
body, _, code = out.rpartition("\n")
|
||||
try:
|
||||
return code, json.loads(body)
|
||||
except ValueError:
|
||||
return code, {"raw": body[:200]}
|
||||
|
||||
|
||||
c, pre = api("GET", "/api/escrow/preflight")
|
||||
say("preflight", c, json.dumps(pre, ensure_ascii=False)[:300])
|
||||
c, st = api("POST", "/api/escrow/start", form=PW)
|
||||
say("start", c, json.dumps(st, ensure_ascii=False)[:300])
|
||||
if c != "200":
|
||||
sys.exit(1)
|
||||
KEEP = ("phase", "claimable", "claimed", "restic_pw_sealed", "uploaded", "awaiting_confirm", "key_fingerprint", "detail")
|
||||
for _ in range(120):
|
||||
time.sleep(2)
|
||||
c, s = api("GET", "/api/escrow/status")
|
||||
dd = s.get("data") or {}
|
||||
if dd.get("claimable") is True or dd.get("phase") in ("failed", "unclaimed_void"):
|
||||
break
|
||||
say("status", c, json.dumps({k: dd.get(k) for k in KEEP}, ensure_ascii=False))
|
||||
c, cl = api("POST", "/api/escrow/claim")
|
||||
d = cl.get("data") or {}
|
||||
code = d.get("recovery_code", "")
|
||||
say("claim", c, "code received:", bool(code), "confirm_required:", d.get("confirm_required"), "positions:", d.get("verify_positions"))
|
||||
if not code:
|
||||
sys.exit(2)
|
||||
fd = os.open(sys.argv[1], os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
||||
os.write(fd, code.encode())
|
||||
os.close(fd)
|
||||
words = code.split("-")
|
||||
if d.get("confirm_required"):
|
||||
p = d["verify_positions"]
|
||||
c, cf = api("POST", "/api/escrow/confirm", js={"words": [words[p[0] - 1], words[p[1] - 1]]})
|
||||
say("confirm", c, json.dumps(cf, ensure_ascii=False)[:300])
|
||||
code, words = "", []
|
||||
for _ in range(30):
|
||||
time.sleep(2)
|
||||
c, s = api("GET", "/api/escrow/status")
|
||||
dd = s.get("data") or {}
|
||||
if dd.get("uploaded") is True or dd.get("phase") in ("failed", "unconfirmed_void"):
|
||||
break
|
||||
say("final status", c, json.dumps({k: dd.get(k) for k in KEEP}, ensure_ascii=False))
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
"""stableprobe.py — fix A, the healthy half: a normal unit restore of a healthy throwaway app → the page says success,
|
||||
the undo copy is there right after, and gone after the 3-minute stable window. The controller's DEBUG lines say why
|
||||
any sample was not ok. usage: SC=… SWEEP_EV=… TARGET=9202 python3 stableprobe.py <app>"""
|
||||
import json, os, sys, time
|
||||
|
||||
TOOLS = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../catalog-sweep-2026-10-24/tools")
|
||||
sys.path.insert(0, TOOLS)
|
||||
import sweep as sw # noqa: E402
|
||||
|
||||
w, fxm = sw.w, sw.fxm
|
||||
|
||||
|
||||
def main(app):
|
||||
d = os.path.join(sw.EV, "stableprobe", app)
|
||||
os.makedirs(d, exist_ok=True)
|
||||
log = open(os.path.join(d, "walk.log"), "a")
|
||||
|
||||
def say(*a):
|
||||
line = time.strftime("%H:%M:%SZ", time.gmtime()) + " " + " ".join(str(x) for x in a)
|
||||
print(line, flush=True)
|
||||
log.write(sw.scrub(line) + "\n")
|
||||
log.flush()
|
||||
|
||||
w.say = say
|
||||
fx = fxm.FIXTURES[app]
|
||||
sub = getattr(fx, "sub", app)
|
||||
undo = f"/mnt/sys_drive/felhom-data/backups/restore-undo/{app}"
|
||||
rec = {}
|
||||
t0 = int(time.time())
|
||||
w.login()
|
||||
try:
|
||||
rec["install"] = w.deploy(app, sub) and w.wait_app(sub, "/", tries=90, delay=5)
|
||||
tok = fx.seed(w, sub, say)
|
||||
sw.backup_now(rec, say)
|
||||
sw.restore_unit(app, rec, say)
|
||||
say(" restore: " + rec["restore"].get("status", "")[:400])
|
||||
rec["copy_right_after"] = sw.g(f"ls -1 {undo} 2>&1").strip()
|
||||
say(" the copy right after: " + rec["copy_right_after"])
|
||||
for _ in range(36):
|
||||
time.sleep(10)
|
||||
if "No such file" in sw.g(f"ls -1 {undo} 2>&1"):
|
||||
break
|
||||
rec["copy_after_window"] = sw.g(f"ls -1 {undo} 2>&1").strip()
|
||||
rec["item"] = bool(tok) and bool(fx.verify(w, sub, tok, say))
|
||||
say(f" the copy after the window: {rec['copy_after_window']}; item {rec['item']}")
|
||||
finally:
|
||||
rec["lines"] = sw.scrub(sw.g(f"docker logs --since {t0} felhom-controller 2>&1 | grep -E 'stable-start sample {app}|{app}: ran stably|{app}.*not start|{app}.*did not run stably|Restore-from-unit completed: {app}' | cut -c1-260 | head -40"))
|
||||
sw.remove(app, keep=False, say=say)
|
||||
rec["after_remove"] = sw.leftovers(app, sub)
|
||||
json.dump(rec, open(os.path.join(d, "record.json"), "w"), ensure_ascii=False, indent=1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1])
|
||||
@@ -26,6 +26,13 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-10-25 — the attended release run (full text of the rows: `git show 9647f1d1:documentation/backlog/OPEN-ITEMS.md`)
|
||||
|
||||
| Row | What | Closed | Evidence |
|
||||
|---|---|---|---|
|
||||
| **R-1265** | **Decisions found in code with no document — the operator confirms them (review round 2).** (P3) | CLOSED 2026-10-25 — **D44 = all picks** (operator, attended release run): the confirm lines carry a normal `[DESIGN — confirmed by the operator 2026-10-25 (D44 = all picks, R-1265)]` (`07` §8.2c, `09` §6.4.x, `14`, `15`, `16`); `03` §6 lists the 26 unnamed agent routes; `07` §8 gains row 16 (the registry lost with DooPlex, RTO blank and listed in §8.1); the four code-change picks became rows **R-1280–R-1283** (no code changed) | `audits/review-round-2-2026-10-24/D44-D45-picks.md`; the architecture diffs of this commit |
|
||||
| **R-1266** | **Rulings and promises in the documents that the code does not do as written.** (P3) | CLOSED 2026-10-25 — **D45 = all picks**: the text follows the code, each change tagged `[DESIGN — corrected 2026-10-25, D45 = all picks, R-1266]`, old words struck through, not deleted (`09` decisions 13, 14, 250 item 5; `01` the poke and the operator door, the locked packages with both limits named; `03` §2/§6 break-glass; `06` the two tunnel-side listeners, the withdrawn plan) | the architecture diffs of this commit |
|
||||
|
||||
## 2026-10-24 — the hub's tokens deployed; D40–D42 ruled; the line test; the watch released (full text of the rows: `git show 3363ffa8:documentation/backlog/OPEN-ITEMS.md`; R-1244 and R-1247: `git show 1a788f33:documentation/backlog/OPEN-ITEMS.md`)
|
||||
|
||||
| Row | What | Closed | Evidence |
|
||||
|
||||
@@ -158,12 +158,12 @@ stopping line that lies.
|
||||
| **R-683** | App updates | P3 | **[P3-LOW] Watch: after a power cut during an update's health check, the hold named an HOUR-OLD second-drive copy, not the one the update's own backup should have just made.** 2026-09-24 chaos round 3 (nextcloud, `backup_max_age: 1m`): no `backing-up` phase was seen and the hold named Tier 2 at 13:04 for an update pressed at 14:04; the pre-cut controller log was lost with the container (the runner now saves it at arm time — R-320). Round 11, the same action without a power cut, named a fresh 14:34 copy and logged the Tier-2 copy. The sentence was TRUE (it named the copy it offered); the question is why the update did not back up first. Not reproduced; watch the next power-cut drill. `audits/night-2026-09-24/E/round-03*.json`, `E/round-11-controller-pre.log` | **OPEN — P3; owner: CC (watch)** | — | — | CC |
|
||||
| **R-785** | App updates | P3 | **[P3-LOW] SparkyFitness is pinned 11 releases and a major behind upstream (v0.17.3; upstream v1.7.3, v1.6.0 dated 2026-07-24).** READ 2026-10-01 (`audits/visitors-2026-10-01/C/bench/C1-previous-tag.txt`). **Needs:** an update walk 0.17 → 1.x through the ladder (bench + box), after R-784 is decided. | **OPEN — rank P3-LOW; owner: CC (after R-784)** | — | — | CC |
|
||||
|
||||
## Backup & restore — 58 rows (P1 1, P2 14, P3 25, P4 18)
|
||||
## Backup & restore — 59 rows (P1 1, P2 14, P3 26, P4 18)
|
||||
|
||||
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **R-1251** | Backup & restore | P1 | **A power cut during a restore of a DRIVE-backed app: the app then runs on the cut restore's data, and a later controller restart pours the pre-restore copy back over everything written since — the D22 promise is false for every drive-backed app.** Found 2026-10-24 (review round 2, HUNT-1 H1). Read at `file:line` (controller `321df1f`): the undo copy lives on the app's drive (`internal/backup/offbox_restore_undo.go:47-48`); `AppStopGuard.Recover` runs at start BEFORE the drive is bound, finds the copy „gone” and restarts „as before” (`appstop_marker.go:362-367`); the gated start refuses (drive absent) → marker and Undo KEPT (`:383-387,408-411`); the drive binds and the boot recreate starts the app checking only „held” (`internal/web/intermediary.go:470-476`); the household writes; the next controller start (e.g. the nightly self-update) finds the copy present and pours it back. **Proven with the real `Recover` code** in a lean clone (`audits/review-round-2-2026-10-24/``proofs/H1-drive-cut-restore/`); NOT reproduced on 9202 (its drive is mounted at boot and its agent API is off, so the recreate never runs there). Same family: H2 — a FAILED crash put-back leaves the app stopped and unheld, so Start or the recreate runs it and a later restart pours an old copy back (`appstop_marker.go:368-371`; `api/router.go:690-709`); H7 — Stop pressed during a restore skips the put-back after a cut (`appstop_marker.go:356-361`). **Fixed on main 2026-10-24 night** (controller `bb2809d` + `062f9b4`; restore-holes brief Part 1): a copy on an unbound drive WAITS (restore hold `putback_pending`), every start path honours the hold, the put-back runs first when the drive binds; H2 a failed put-back holds (D20); H7 Stop still gets the put-back; the copy's folders are synced before the marker names it (run 1 of the live proof found `undo.json.tmp` after the cut). **Proven live on Tester 1** (`audits/restore-holes-2026-10-24/P1-tester1/`). | **VERIFY** — fixed on main, waits for the release | the next controller release | release the controller (Monday list); re-read P1's two observables on the released image (a waiting hold at start, the put-back before the start) | CC |
|
||||
| **R-232** | Backup & restore | P2 | **DooPlex's backup makes every copy inside the same box — and nothing tells anyone when it fails.** Surveyed read-only 2026-08-06 (`audits/RECON-dooplex-backup-2026-08-06.md`). **What works:** five sets, 14/14 successful runs in 14 days; a file was restored from the `data` repo and matched the live original **byte for byte**; every set except two is cross-disk; k3s is integrity-checked on every run. **What the matrix exposes, ranked:** (a) **`notify_failure` is a no-op** — `NOTIFY_ON_FAILURE=true` but `NOTIFY_WEBHOOK_URL` is commented out, so a failed backup notifies **nobody**; the project already has a working Resend path that CI uses. Cheapest item, and it makes every other failure visible. (b) **Nothing leaves the box** — no rclone, no remote repo, no off-site target anywhere; Longhorn's target is `nfs://192.168.0.180:` pointing at DooPlex itself, and the only outbound-looking cron pulls *inbound* from Hetzner for a different project. The machine that runs the hub managing the customers' off-site chain has no off-site copy of its own. (c) **The backup tree is a single writable path** and the restic repos are not append-only — one bad script or ransomware destroys every copy at once. (d) **Two same-disk sets**: `.claude-memory` and the PostgreSQL dumps, whose source directory sits *inside* the backup tree. (e) **Longhorn `retain=1`** — one generation per volume, so a corruption noticed a day late has no earlier copy. (f) **`/opt/backup/docs/BACKUP-RESTORE.md` does not exist** though the systemd unit advertises it. (g) **`secrets/restic-repo` has never held a snapshot** — `backup-secrets.sh` contains no `restic` call; the secrets are GPG files on `sda1` only. (h) **No restore has ever been run** beyond today's single-file probe — the matrix's "ever demonstrated?" column is otherwise entirely empty. **Not a finding:** the restic passphrase. The on-box copy is on `sdb1`, a different disk from the backups, and the **operator holds an offline copy out of band** — so a disk loss is recoverable. The narrow residual is that it is operator-held rather than system-held, unlike the customer case's hub-vaulted escrow, so it should be confirmed current and findable by someone else. **Nothing was changed by the recon.** | NARROWED — **2026-10-23 (operator yes D33, D34):** the hub DB now leaves every 6 hours (hub 0.167.0 + `scripts/hub-db-backup/`, alarm at 13 h; restored back byte-identical) and the released images and agent packages leave nightly (`scripts/release-copy/`, R-1227 closed). (c)–(g) unchanged. **NARROWED 2026-10-08 — owner Viktor.** (a) DONE with the operator's yes in chat: `notify_failure` now mails admin@felhom.eu through Resend; proven by one test mail that reached the inbox (`audits/day-2026-10-08/r232/`; no backup was started). (b) partly: the hub database leaves DooPlex nightly to ep0 (R-173); everything else stays on the box. (c)–(h) unchanged. **2026-10-09 (operator yes in chat): (b) and (h) DONE for Gitea and the secrets.** Nightly 00:20 `felhom-dooplex-offsite` pushes Gitea (614 MB of repositories, the `gitea` database dump taken first, `app.ini`) and the nightly GPG secrets export, encrypted with a NEW key, to ep0 `operator` (`host/dooplex-gitea`) on the hub-DB write-only token — no change on ep0; first push 57 s; restore test weekly (manifest, `git fsck` every repo, `pg_restore --list`), run once: 27 805 files, 10 repos OK; `DooplexGiteaOffsiteStale`/`DooplexGiteaRestoreTestStale` in force (`absent()` included, promtool + 2 red-proofs); every backup unit now mails on failure (`OnFailure=`, dry run reached the inbox). **First real Gitea restore:** into a throwaway on the bench, 10/10 repos, product `main` = live, a file byte for byte, a login (`runbooks/gitea-restore.md`). The hub came back too (R-173, closed). **The registry is left out on purpose:** its images rebuild from the code. **Correction to the recon §7:** the API mirror holds ONE repo (`homelab-manifests`), not all — Gitea had a single copy, on DooPlex. `audits/dooplex-survival-2026-10-09/` **LEFT:** (c) append-only for the on-box tree, (d)–(g) as written; the paper copy of the new key (operator). **READY** for the rest **2026-10-24 (attended): `DooplexGiteaOffsiteStale` fired from 00:57Z — not this path's fault.** The 00:20 push refused, as designed, a 12 h old Gitea dump: the PostgreSQL dump CronJob had failed every run since 2026-10-23 16:00Z (`28P01`) because the window reboot restarted the database and CNPG put the bootstrap secret's password in force (homelab `531023d`, operator's pick: the CronJob only). Fixed; dump, push and restore test run by hand (26 456 files, 12 repos `git fsck`, `gitea.dump` 1114 entries); cleared 10:51Z (`audits/dooplex-alarms-2026-10-24/`). Backup alarms now arrive as ONE mail every 4 h (homelab `22d7697`). **2026-10-23 night — survey again, read only (`audits/dooplex-survival-2026-10-22/PLAN.md`):** both nightly off-site jobs Line truncated
|
||||
| **R-232** | Backup & restore | P2 | **DooPlex's backup makes every copy inside the same box — and nothing tells anyone when it fails.** Surveyed read-only 2026-08-06 (`audits/RECON-dooplex-backup-2026-08-06.md`). **What works:** five sets, 14/14 successful runs in 14 days; a file was restored from the `data` repo and matched the live original **byte for byte**; every set except two is cross-disk; k3s is integrity-checked on every run. **What the matrix exposes, ranked:** (a) **`notify_failure` is a no-op** — `NOTIFY_ON_FAILURE=true` but `NOTIFY_WEBHOOK_URL` is commented out, so a failed backup notifies **nobody**; the project already has a working Resend path that CI uses. Cheapest item, and it makes every other failure visible. (b) **Nothing leaves the box** — no rclone, no remote repo, no off-site target anywhere; Longhorn's target is `nfs://192.168.0.180:` pointing at DooPlex itself, and the only outbound-looking cron pulls *inbound* from Hetzner for a different project. The machine that runs the hub managing the customers' off-site chain has no off-site copy of its own. (c) **The backup tree is a single writable path** and the restic repos are not append-only — one bad script or ransomware destroys every copy at once. (d) **Two same-disk sets**: `.claude-memory` and the PostgreSQL dumps, whose source directory sits *inside* the backup tree. (e) **Longhorn `retain=1`** — one generation per volume, so a corruption noticed a day late has no earlier copy. (f) **`/opt/backup/docs/BACKUP-RESTORE.md` does not exist** though the systemd unit advertises it. (g) **`secrets/restic-repo` has never held a snapshot** — `backup-secrets.sh` contains no `restic` call; the secrets are GPG files on `sda1` only. (h) **No restore has ever been run** beyond today's single-file probe — the matrix's "ever demonstrated?" column is otherwise entirely empty. **Not a finding:** the restic passphrase. The on-box copy is on `sdb1`, a different disk from the backups, and the **operator holds an offline copy out of band** — so a disk loss is recoverable. The narrow residual is that it is operator-held rather than system-held, unlike the customer case's hub-vaulted escrow, so it should be confirmed current and findable by someone else. **Nothing was changed by the recon.** | NARROWED — **2026-10-23 (operator yes D33, D34):** the hub DB now leaves every 6 hours (hub 0.167.0 + `scripts/hub-db-backup/`, alarm at 13 h; restored back byte-identical) and the released images and agent packages leave nightly (`scripts/release-copy/`, R-1227 closed). (c)–(g) unchanged. **NARROWED 2026-10-08 — owner Viktor.** (a) DONE with the operator's yes in chat: `notify_failure` now mails admin@felhom.eu through Resend; proven by one test mail that reached the inbox (`audits/day-2026-10-08/r232/`; no backup was started). (b) partly: the hub database leaves DooPlex nightly to ep0 (R-173); everything else stays on the box. (c)–(h) unchanged. **2026-10-09 (operator yes in chat): (b) and (h) DONE for Gitea and the secrets.** Nightly 00:20 `felhom-dooplex-offsite` pushes Gitea (614 MB of repositories, the `gitea` database dump taken first, `app.ini`) and the nightly GPG secrets export, encrypted with a NEW key, to ep0 `operator` (`host/dooplex-gitea`) on the hub-DB write-only token — no change on ep0; first push 57 s; restore test weekly (manifest, `git fsck` every repo, `pg_restore --list`), run once: 27 805 files, 10 repos OK; `DooplexGiteaOffsiteStale`/`DooplexGiteaRestoreTestStale` in force (`absent()` included, promtool + 2 red-proofs); every backup unit now mails on failure (`OnFailure=`, dry run reached the inbox). **First real Gitea restore:** into a throwaway on the bench, 10/10 repos, product `main` = live, a file byte for byte, a login (`runbooks/gitea-restore.md`). The hub came back too (R-173, closed). **The registry is left out on purpose:** its images rebuild from the code. **Correction to the recon §7:** the API mirror holds ONE repo (`homelab-manifests`), not all — Gitea had a single copy, on DooPlex. `audits/dooplex-survival-2026-10-09/` **LEFT:** (c) append-only for the on-box tree, (d)–(g) as written; the paper copy of the new key (operator). **READY** for the rest **2026-10-24 (attended): `DooplexGiteaOffsiteStale` fired from 00:57Z — not this path's fault.** The 00:20 push refused, as designed, a 12 h old Gitea dump: the PostgreSQL dump CronJob had failed every run since 2026-10-23 16:00Z (`28P01`) because the window reboot restarted the database and CNPG put the bootstrap secret's password in force (homelab `531023d`, operator's pick: the CronJob only). Fixed; dump, push and restore test run by hand (26 456 files, 12 repos `git fsck`, `gitea.dump` 1114 entries); cleared 10:51Z (`audits/dooplex-alarms-2026-10-24/`). Backup alarms now arrive as ONE mail every 4 h (homelab `22d7697`). **2026-10-23 night — survey again, read only (`audits/dooplex-survival-2026-10-22/PLAN.md`):** both nightly off-site jobs Line truncated
|
||||
| **R-895** | Backup & restore | P2 | **The hub's clean-up-window check trusts the snapshot counts the box sends, so a broken-into box (or past-dated fakes added through the add-only key) can shrink the real off-site history without an alarm.** READ 2026-10-06 night in source (R-822's design): the before/after comparison uses counts the box itself reports (`hub/internal/offsitekeys/service.go:284`, `:343`); new fakes keep the count level. Decision 68 already accepts a box-trusted count. | **NARROWED 2026-10-18 — slice 2 built, not deployed** (hub v0.158.0): the 07:10 audit compares the `snapshots/` listing with yesterday's by name — files gone that no clean-up window explains → `offsite_snapshots_vanished` (error), files no reported box run explains → `offsite_snapshots_unexplained` (warning); operator-only, hub-origin (not in `allowedEventTypes`). First listing = baseline; a move-aside or RESET drops it; vanishing judged on a pinned tier only; files younger than 1 h judged the next day. Red-proofed twice (`snapshot_watch_test.go`). **Not built:** the window's before/after on the hub's own count (design §3.1) and refusing a window while unexplained files exist (§3.2). Unmeasured: the provider shell's `ls -l` time zone (± 3 h slack until then). **Deployed 2026-10-18 12:23 (hub v0.158.0).** The first listing (tomorrow 07:10) is a baseline; the first verdict is 2026-10-20 07:10. **Known limit (independent review 2026-10-18, F2):** `_unexplained` is explained by the box's OWN reported `offsite.last_run`, so whoever holds a box's key can silence it; `_vanished` is judged on the hub's own listing and is not affected. **2026-10-20 03:5x UTC: due date moved 10-20 → 10-21** — the first verdict lands at 07:10 hub time (05:10 UTC), after the night session that reached it ended; that brief did not include this read. Nothing is lost: the hub keeps its listing; read the 2026-10-20 07:10 lines on 10-21 (or later on 10-20). **FIRST VERDICT READ 2026-10-21 (rehearsal session, read-only):** the 07:10 CEST run judged three tiers against yesterday — demo-felhom 5, demo-hp 14, tester-1 4 new snapshot files, „each inside a reported box run"; nothing vanished, nothing unexplained; Tester4 stored its first baseline (44 files); Tester-2's own listing failed (no `snapshots/` dir — the box has never pushed). No `offsite_snapshots_vanished` / `_unexplained` mail in the operator mailbox (second channel). Evidence `audits/due-checks-2026-10-21/R-895-hub-0710.txt`. The positive alarm path is still proven only by the unit red-proofs. | the operator's yes to deploy hub v0.158.0 | Deploy; read the first 07:10 lines (newest upload vs the box's last run = the time-zone calibration), shrink the slack; then decide §3.1/§3.2 | CC |
|
||||
| **R-923** | Backup & restore | P2 | **The password manager (Vaultwarden) runs on DooPlex and holds the keys that open DooPlex's own off-site copies — a loss of DooPlex loses the keys with the copies.** Operator finding, 2026-10-09 11:19: "the password manager also runs on DooPlex (Vaultwarden), so I probably need to back up its DB somewhere too." Measured: `vaultwarden-system/vaultwarden` (1.37.3), PVC `vaultwarden-data` 7.4 MB (`db.sqlite3` + WAL, `rsa_key.pem`, no attachments); its only backups were Longhorn on DooPlex. **A key in a password manager that runs on DooPlex is not off DooPlex** — R-173's "both keys are off DooPlex" (2026-10-05) and the hub-DB runbook Step 0 were wrong in that sense. Also seen: Vaultwarden warns its `ADMIN_TOKEN` is plain text (an Argon2 hash is its guidance). | NARROWED — **2026-10-23 (D32):** the sheet is current — names and places only, one A4, HU + EN (`runbooks/break-glass-sheet.md`); every place checked to exist. LEFT: the operator prints it, fills it by hand, keeps it outside the home. **NARROWED 2026-10-09 (operator yes in chat):** Vaultwarden's own `backup` copy + `rsa_key.pem` ride the nightly encrypted DooPlex→ep0 job (`scripts/dooplex-offsite/`), checked on every push and every Sunday (integrity, users, items — rows only). That copy opens only with the DooPlex off-site key, and the vault only with the master password — so the circle is broken only once both are on paper outside DooPlex: `runbooks/break-glass-sheet.md`, `runbooks/total-loss-of-dooplex.md`. **LEFT:** the operator prints the sheet and stores it away from home; the `ADMIN_TOKEN` hash (a DooPlex change, operator's word). **2026-10-23 night — survey (`audits/dooplex-survival-2026-10-22/PLAN.md`, G1/G3/G10):** unchanged; the sheet is still the one act that breaks the circle, and S5's offline form is unknown to CC — on the sheet too (decide list in STATUS). | operator: print the sheet | Operator: print + store the sheet; then close | operator |
|
||||
| **R-1252** | Backup & restore | P2 | **One rejected backup block in an app's `.felhom.yml` makes that night's second-drive copy DELETE the app's copies of the household's files.** REPRODUCED on 9202 2026-10-24 (`audits/review-round-2-2026-10-24/``proofs/H2-bad-backup-block/`): audiobookshelf with a marker file in `media/audiobooks`; two unchanged runs kept the copy (the positive control); one bogus path in the block → „backup block rejected … matches no compose bind” → the legacy legs (appdata only) → „Tier 2 reconcile: removed stale dest dir userdata/media”; the original on the drive untouched. Mechanism: `internal/stacks/metadata.go:550-557` (any defect rejects the WHOLE block) → `appbackup/classify.go:217-221` → `backup/tier2_capture.go:84-100` → `tier2.go:492` `tier2Reconcile` (`:325-329`), against D25's own header (`tier2_kept.go:17-18`). Same family (static): a D25 kept copy is reconciled away when its leg leaves the declared set and its record stays (HUNT-2 H2, `tier2.go:485-492`); Remove with `remove_backups` deletes kept copies and leaves their records (HUNT-2 H9, `api/router.go:1086-1087`). **Fixed on main 2026-10-24 night** (controller `518a135`; Part 2): reconcile only under a valid classification; a rejected block is said on the app's status and once a day to the operator (`backup_run_failures`, leg `tier2-settings-unreadable`); kept copies stay in the keep set (H2); Remove drops the kept records it deleted (H9). **Proven live on 9202** (`audits/restore-holes-2026-10-24/P2-9202/`: two control runs, then the bogus path → „REJECTED … every existing copy … KEPT”). | **VERIFY** — fixed on main, waits for the release | the next controller release | release the controller (Monday list) | CC |
|
||||
@@ -220,8 +220,9 @@ stopping line that lies.
|
||||
| **R-1232** | Backup & restore | P2 | **A reinstalled box may take its first whole-box copy to ep0 up to a week late: the old box's copy in the same household space seems to count as its last one.** Test night 2026-10-23 (`audits/night-2026-10-23/P4-testnight/`): the new Tester4 box (guest 9201) had its PBS tier applied at 19:28 UTC, but no whole-box run to ep0 came all night; the only archive in the household's ep0 space is the rehearsal box's `ct/9201` of 2026-10-20 (same guest number), and the tier's cadence is 168 h. The agent's due-check reads the newest archive for that vmid on the tier's storage (`localapi/server.go` `newestArchiveOn`) — so the old box's copy reads as „2.6 days old, not due”. If so, the ep0 copy is the OLD box's for up to a week. PLAUSIBLE — read from the code and the night's silence, not proven by a log line (the controller logs the due-check only at debug). | OPEN | — | Prove: read `GET /backup/tiers` for the box (the agent's answer per tier) on the next reinstall; fix: a tier's „last backup” counts only archives this host wrote (or after the host's enrollment); an agent test with a foreign archive in the namespace | CC |
|
||||
| **R-1235** | Backup & restore | P3 | **The release copy on ep0 (D33) is proven to load, not yet to restore a registry — and it follows the newest versions, not the ones the fleet needs.** Built 2026-10-23 (`scripts/release-copy/`, `audits/day-2026-10-23/08-release-copy-D33.txt`). (a) `total-loss-of-dooplex.md` „Restore the release copy” steps 2 (skopeo push back, same digest) and 3 (agent packages back) were never walked into a real new registry — only a `docker load` on 9202. (b) The copy keeps the newest 3 image tags and 5 agent versions; it does not ask the hub for the floor and the vouched agent and always include them — a vouch more than 5 releases behind would fall out. (c) The golden (`felhom-golden`, 637 MB for 0.331.0) is not copied: a new install after a DooPlex loss needs it (the brief named images + agent packages only; one golden fits under the 3 GB cap). | OPEN | — | (a) a throwaway registry on demo-hp, push one tag back, compare digests; (b) read `/configuration`'s floor + vouched agent; (c) the operator says whether the golden goes in | CC |
|
||||
| **R-1263** | Backup & restore | P4 | **Small items from review round 2:** a LOCAL restore's put-back mails the operator „its off-site restore FAILED” (`felhom-controller/controller/cmd/controller/main.go:1514-1518`, shared via `restore_unit.go:517-518`); Remove — even „keep my data” — deletes undeclared volumes labelled with the app's project, which no tier holds (`internal/stacks/delete.go:763-764`, R-1117); the capped copy's `--delete` reads the source in the daemon's mount view, the emptiness check in the controller's (`tier2.go:834` vs `:464`); stale comments — `felhom-agent/internal/localapi/backup_cap.go:15` („keep-last”) and `restore_crash_putback.go:32` (names a test file that does not exist); the qBittorrent password goes to whatever answers the DNS name `qbittorrent` (`upwatch/explain.go:94,108-113`); kept-copy Remove has a check-to-remove gap (`tier2_kept.go:92-111`); the release-copy restore check trusts hashes stored on ep0 (`runbooks/total-loss-of-dooplex.md`); rrsync without `-munge`/`-wo` (`audits/day-2026-10-23/08-release-copy-D33.txt:6`). | OPEN | — | fix in each repo's next attended pass | CC |
|
||||
| **R-1283** | Backup & restore | P3 | **MeTube's downloads are excluded from every backup — make them `optional` (D44 #25).** Catalog `c50adfc` (R-1019) marks the download folders of qBittorrent, Transmission, NZBGet, SABnzbd and MeTube `excluded` (`app-catalog-felhom.eu/templates/metube/.felhom.yml:33`). Risk: a MeTube video may be gone from its source site, so after a drive failure it cannot be fetched again. Pick: MeTube's `media/metube` becomes `optional` — the second-drive copy carries it, the off-site copy does not; NZBGet and SABnzbd stay excluded (re-downloadable). Cost: second-drive space for MeTube's folder. Operator ruling D44 = all picks, 2026-10-25. Note: `architecture/15-media-library.md` §7. | OPEN | — | change the class in the catalog template, run the catalog gates (the download-binds gate and its decoys must accept MeTube as `optional`), check on a Tier-0 box that a second-drive copy carries the folder and the off-site run does not | CC |
|
||||
|
||||
## Storage & devices — 13 rows (P3 4, P4 9)
|
||||
## Storage & devices — 14 rows (P3 5, P4 9)
|
||||
|
||||
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
@@ -238,8 +239,9 @@ stopping line that lies.
|
||||
| **R-1213** | Storage & devices | P4 | **Operator detail still on two household pages:** the registered drive card's `ext4 · /dev/sdb1 · Model` line (`templates/storage.html`; W14 hid only the agent drive cards), and the heading „Távoli mentés (3. mentés) — titkosított, offsite" with the English „offsite". Found by the night helper 2026-10-21 (W11/W14 follow-up). | OPEN | — | Move the line behind „Részletek"; „…titkosított, házon kívüli" | CC |
|
||||
| **R-1275** | Storage & devices | P4 | **An English household sees the drive names in Hungarian** on the install page (and wherever a storage label shows): „Belső tárhely — 52.0 GB free (default)”, „Külső tárhely (hdd_1)” (catalog sweep 2026-10-24, `audits/catalog-sweep-2026-10-24/navidrome/page-install-en.txt`). The label is stored once, in Hungarian, when the drive is found (`felhom-controller/controller/internal/settings/settings.go` `InferStorageLabel`); a household may rename it. | OPEN | — | Render a label that still equals the inferred default in the reader's language; a test per language | CC |
|
||||
| **R-1276** | Storage & devices | P4 | **„Remove with data” leaves the app's empty parent folder** (`<drive>/appdata/<app>`) when the template binds a subfolder: Grimmory, RomM, Paperless-ngx (catalog sweep 2026-10-24; 4 KB each; the bound folders were removed). Harmless (empty, not counted as old data), untidy for a household that browses the drive. | OPEN | — | Remove the parent when it is empty after the bound folders go; a test | CC |
|
||||
| **R-1282** | Storage & devices | P3 | **The „Később" 03:30 media restart is forgotten by a controller restart — store the due time (D44 #24).** The restart is an in-memory timer (`felhom-controller/controller/internal/web/media_events.go` `handleMediaRestart`, `time.AfterFunc`); after a controller restart only the notice stays. Risk: media apps do not see the new drives until the household presses „now". Pick: store the due time and run the restart after a controller restart (a due time already past runs at once, within the same rules). Operator ruling D44 = all picks, 2026-10-25. Note: `architecture/15-media-library.md` §11. | OPEN | — | a small controller change + a test that a stored due time survives a restart and fires once; rides a controller release | CC |
|
||||
|
||||
## Security & access — 25 rows (P1 2, P2 4, P3 13, P4 6)
|
||||
## Security & access — 27 rows (P1 2, P2 5, P3 14, P4 6)
|
||||
|
||||
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
@@ -268,6 +270,8 @@ stopping line that lies.
|
||||
| **R-1194** | Security & access | P3 | **On a fresh box the operator's break-glass SSH door was broken from the first minute: `oob_degraded` „felhom-sshd config invalid (sshd -t fails)".** Rehearsal 2026-10-20, Tester4-816e21, mail at 12:03 UTC (one minute after the host record was created): details `active:false, config_invalid:true, felhom_sshd_port:0, operator_key_configured:false, operator_key_reported:true, reachable:false`. Possibly the shape of a customer with no operator key set (Tester4 is a test customer) — then the alarm is a false one on every such box; or a real config defect. Not diagnosed (no shell). Checked the same day: the only `oob_degraded` in the operator mailbox in 30 days, and no recovery mail followed; the report says port 0. Two candidate mechanisms in the agent (`internal/felhomsshd/health.go:63-66`): `sshd -t -f <conf>` run by the NON-root agent fails on unreadable host keys, or a config rendered before the desired state arrived (port 0). **CAUSE FOUND AND FIXED 2026-10-21 night** (`audits/night-2026-10-21/P5-checks/`): reproduced on a fresh box from the public ISO — `oob_degraded` mailed 20:34 CEST while felhom-sshd was applied 0.4 s after the check and is healthy (port 8822 listening, `sshd -t` passes as the agent's user through its sudo grant — so the non-root theory is FALSE; without sudo it fails on the host key, which the agent never does). The first host report runs before the felhom-sshd loop's first pass and reads a config never rendered. Fix: agent `0da792e` — no OOB stanza until the first pass has run (the hub does not evaluate a missing one; a failed pass counts). `TestR1194_NoOOBStanzaBeforeTheFirstPass` red-proved. | WATCHING — released in agent v0.162.0 (2026-10-22, operator yes; on the 3 test boxes); the false alarm comes only on a FRESH box's first report | the next fresh install with agent ≥ 0.162.0 | At the next fresh install read the hub's events for `oob_degraded` in the first hour (none expected); then close | CC |
|
||||
| **R-1237** | Security & access | P3 | **The nightly CC-secrets sweep found stored one-time codes in a session record dated 2026-10-23: 14 names in one file (bind tokens and setup codes of test boxes — NIGHT_2026_10_23_*, FIXES_2026_10_21_*, NIGHT_2026_10_21_*, REHEARSAL_2026_10_20_* — and R_DEMO_FELHOM in a file of 2026-10-20).** Mail „[DooPlex CC secrets] 2 secret name(s) found in old session records", 2026-10-23 01:57 UTC, read in the day session (names only). One file holding all of them suggests one command printed many stored values at once (a listing or a dump) in the night session. All but R_DEMO_FELHOM are one-time install codes of test boxes; NIGHT_2026_10_23_* die when Tester4-16fdef is deleted (the operator's do list). | OPEN | — | CC: find the file and the command by the sweep's own report (names only), add a hook shape for that command if it is not one; R_DEMO_FELHOM (demo-felhom's recovery code) — the operator decides whether to rotate it | CC |
|
||||
| **R-1248** | Security & access | P3 | **An app's own numbers can lie: an attacker with code inside an app's container can answer the app's API with whatever matches its flood, and the upload watch (R-1244) would then call it explained.** Measured remedy, not built: a per-container upload ceiling with `tc` from a helper container in the app's network namespace — on scratch 9202 (unprivileged) a flood was held at 19.9–20.1 Mbit/s and the app kept answering (`audits/SPIKE-upload-watch-2026-10-23.md` §5). Costs: a helper image with `tc` (a new external image, or `tc` in the controller image), `NET_ADMIN` for that helper, re-applied after each app restart. | OPEN | an operator decision (the new image / capability) | Decide; if yes, CC builds the ceiling for the download and media apps first | operator |
|
||||
| **R-1280** | Security & access | P3 | **A signed operation's window has no maximum length — cap it at 24 h (D44 #22).** The agent (`felhom-agent/internal/authz/verifier.go`) and the root wrapper (`configs/felhom-os-apply`) check `now ∈ [issued_at, expires_at]` with a 2-minute skew on `issued_at`, but neither checks `expires_at − issued_at`; `felhom-opsign` defaults to 30 min (`cmd/felhom-opsign/main.go:57`) and the nonce store keeps a record until its expiry. Risk: a signed job made with a long window by mistake stays usable for that whole time. Pick: the agent and the root wrapper refuse a window longer than 24 h. Operator ruling D44 = all picks, 2026-10-25. Note: `architecture/04-control-plane-authorization.md` §2. | OPEN | — | one agent change in both checkers + a test that a 24 h + 1 s window is refused (red first) and a 24 h one is accepted; rides an agent release (agent releases stop for the operator's vouch) | CC |
|
||||
| **R-1281** | Security & access | P2 | **Every app container can reach FileBrowser directly; the only guard is the secret header name — take FileBrowser off the app networks (D44 #23).** FileBrowser trusts its proxy header (`auth.methods.proxy`); every app container can reach `filebrowser:80` without traefik; the guard is that the header NAME is a per-box secret that traefik's forwardAuth strips from client requests (`felhom-controller/controller/internal/infra/filebrowser_sso.go:15-22`). Risk: an app that learns the name (a hacked app on the box) reads and writes every household file. Pick: FileBrowser joins only the network traefik uses, so no app container reaches it directly. Operator ruling D44 = all picks, 2026-10-25. Note: `architecture/13-threat-model.md` §5. | OPEN | — | a compose/network change in the controller's infra render + a test on the rendered networks; a live check on a Tier-0 box that a forged header from an app container no longer reaches FileBrowser (the 2026-10-18 measurement, now refused) while the dashboard's file manager still signs in; one controller release | CC |
|
||||
|
||||
## Box system & updates — 12 rows (P2 1, P3 7, P4 4)
|
||||
|
||||
@@ -323,7 +327,7 @@ stopping line that lies.
|
||||
| **R-31** | Hub & operator | P3 | **[P2-HIGH] Offsite provisioning is synchronous with no status affordance.** Save runs the Hetzner sync in-request, so the request can hit the nginx 504 **while succeeding server-side**: the operator cannot tell failed from slow, and a retry races the first attempt. **MIGRATED FROM `ROADMAP.md` 2026-08-22 (R-369) — originally filed 2026-07-21, size M, roadmap state `idea`.** Moved verbatim; nothing added or reinterpreted. The roadmap keeps its copy as history, marked moved. | **OPEN — migrated from ROADMAP 2026-08-22, rank unchanged** **Re-ranked 2026-10-03: P2→P3: operator-only; a known workaround (click once, wait, verify) exists.** **2026-10-06 night: the race half fixed on felhom.eu main (hub, unreleased):** a second Save while the first still provisions is refused with 409 („already running — wait about a minute, then reload; do not save again"); nothing saved, nothing created; per customer, in memory. `TestProvision_R31_*`, red-proof `audits/night-burndown-2026-10-06/hub/R-31-red.txt`. LEFT: the async save with a status card (the escrow-card idiom). | — | Direction: make it async + a status card, reusing the proven **awaiting-card/poll idiom** (v0.138.0 escrow card). **Interim mitigation belongs in R-3 as an operator note: click once, wait, verify — do not re-click.** | CC |
|
||||
| **R-882** | Hub & operator | P3 | **Longhorn on DooPlex could not grow a volume online: its `instance-manager` (116 days up) called a host process that no longer existed** — `nsenter: cannot open /host/proc/196610/ns/mnt` on every expansion retry, and an offline growth was blocked by the expansion's own attachment ticket (found 2026-10-05 growing `hub-data` to 2 Gi). A restart of the instance-manager (operator-approved) fixed it: 77/77 volumes back `attached/healthy` in 110 s. **Why the cached PID went stale was not established** (likely a containerd/k3s or iscsid restart after the instance-manager started), so it will recur after the next such restart and stay invisible until a volume needs to grow. `audits/hub-db-offsite-2026-10-05/partA/step1-*.txt` | **OPEN** | — | Find which host process the PID was and whether Longhorn 1.10.x re-resolves it; until then, before growing any volume, check the instance-manager's age against the last k3s/containerd restart | operator |
|
||||
| **R-883** | Hub & operator | P3 | **8 DooPlex workloads run an image by a moving tag (`:latest` or none), so any pod restart is a silent upgrade.** Measured 2026-10-05: the Longhorn restart restarted zipline on `ghcr.io/diced/zipline:latest` (pull Always), which pulled 4.8.0; 4.8.0 refused its database (`cannot safely migrate from prisma to drizzle: expected migration 20260508022000 … was not applied`) and crash-looped. Fixed for zipline by pinning `4.7.0` (homelab-manifests `90f60e4`, `4c8ec7a`; 4.7.0 applied the four missing migrations; a dump from before is kept out-of-band). The other 7 were counted, not named or changed. **Checked from source 2026-10-05 (burn-down round 2):** homelab-manifests@87dfc29 still has moving tags (grep image lines without a numeric tag): admin-system/toolbox.yaml:12 nicolaka/netshoot:latest (a bare Pod); calibre-system/cwa.yaml:826 calibre-web-automated:dev; outline-system/outline.yaml:270 minio/minio:latest; tandoor-system/recipe-importer.yaml:26 gitea.dooplex.hu/admin/recipe-importer:latest (pull Always :27); adventurelog-system/adventurelog.yaml:100 and :256 adventurelog-backend/frontend:latest (pull Always :101,:257); jarrs-system/jarr- | **OPEN** | — | List the 7 (`kubectl get deploy,sts -A` images without a fixed tag), pin each to the running version, and let Renovate move them | operator |
|
||||
| **R-1183** | Hub & operator | P3 | **On Sunday 2026-10-25 (the clocks go back) every hub daily job after 02:00 runs TWICE: the alarm check at 04:00 and 05:00, the hub database snapshot twice (keep 2 → the previous day's copy pruned a day early), the prune and the key audit twice.** `hub/cmd/hub/main.go:1057-1062` adds 24 h to an absolute time; the controller fixed this (`scheduler.go:511-516`), the hub did not. PROVEN by replaying the hub's exact arithmetic (`audits/dataloss-hunt-2026-10-20/C3-T9-hub-dst/replay.txt`). On 2027-03-28 each runs once, an hour late. No architecture document mentions DST. Data-loss hunt 2026-10-20 (T9). | VERIFY **FIXED in hub v0.163.0, deployed 2026-10-21 11:46 UTC with the operator (D24 = yes):** `scheduleDaily` takes the calendar day after (`nextDailyRun`, `time.Date` day+1) and never repeats the slot that ran last; `TestR1183_*` for 2026-10-25 and 2027-03-28, red-proved against the old arithmetic (`04:00 job ran 2 time(s) on 2026-10-25`; the 02:00 snapshot also ran at 02:00 CEST and 02:00 CET). No other hub scheduler computes a wall-clock time. `05` §16.5 | Sunday 2026-10-25 has to happen | On 2026-10-26 read the hub log: every daily job (db-snapshot, prune, deadline-check, offsite-key-audit) logged ONE run on 2026-10-25, and `/data/snapshots` holds one file for that day; then close | CC |
|
||||
| **R-1183** | Hub & operator | P3 | **On Sunday 2026-10-25 (the clocks go back) every hub daily job after 02:00 runs TWICE: the alarm check at 04:00 and 05:00, the hub database snapshot twice (keep 2 → the previous day's copy pruned a day early), the prune and the key audit twice.** `hub/cmd/hub/main.go:1057-1062` adds 24 h to an absolute time; the controller fixed this (`scheduler.go:511-516`), the hub did not. PROVEN by replaying the hub's exact arithmetic (`audits/dataloss-hunt-2026-10-20/C3-T9-hub-dst/replay.txt`). On 2027-03-28 each runs once, an hour late. No architecture document mentions DST. Data-loss hunt 2026-10-20 (T9). | VERIFY **FIXED in hub v0.163.0, deployed 2026-10-21 11:46 UTC with the operator (D24 = yes):** `scheduleDaily` takes the calendar day after (`nextDailyRun`, `time.Date` day+1) and never repeats the slot that ran last; `TestR1183_*` for 2026-10-25 and 2027-03-28, red-proved against the old arithmetic (`04:00 job ran 2 time(s) on 2026-10-25`; the 02:00 snapshot also ran at 02:00 CEST and 02:00 CET). No other hub scheduler computes a wall-clock time. `05` §16.5 **2026-10-25 (partial read, the attended release run):** `/data/snapshots` holds `hub-20261025T070000Z.db` and `hub-20261025T130000Z.db` — one file per slot, each on its hour (keep 2, so the 02:00 slot is pruned); the hub restarted at 16:51 (the 0.170.0 deploy), so the day's earlier log is gone. The full read stays on 2026-10-26 (the 20:00 slot and the log from 16:51). | Sunday 2026-10-25 has to happen | On 2026-10-26 read the hub log: every daily job (db-snapshot, prune, deadline-check, offsite-key-audit) logged ONE run on 2026-10-25, and `/data/snapshots` holds one file for that day; then close | CC |
|
||||
| **R-264** | Hub & operator | P4 | **Twenty-one facts the boxes report that the hub can now decode nowhere, each allowlisted with a reason rather than silently skipped — and for these the reason is "no consumer today, and one is arguably owed".** Split out of R-260 on 2026-08-08 so that closing the CLASS (gated) and fixing its sharpest instance (`operator_key_configured`) could not be mistaken for having decided what the hub should do with the rest. **The list, grouped by what a consumer would be for.** **(a) Guest-network health — `guest_net` and its seven children** (`checked_at`, `has_route`, `dhclient_alive`, `heal_succeeded`, `heals_last_hour`, `last_heal_at`, `damped`). The R-54 watchdog reports per-guest network state and self-heal counts every cycle and the hub — the component that emails the operator — models none of it. There is a live incident in this project's own record where a killed `dhclient` took a tunnel down for 1 h 15 m (`audits/INCIDENT-guest-dhclient-killed-2026-07-20.md`); a recurring-heal signal is exactly what would have surfaced it. **This is the strongest candidate of the twenty-one.** **(b) `selfupdate_pending` + `selfupdate_pending_version`** — an agent that has flipped its binary and never committed reports pending on every heartbeat so that "the operator sees WHY the version isn't advancing", and no operator can see it. **(c) `mgmt_plane.healed_recently`** — bounded: the hub DOES alarm on the `privsep_healed_at` timestamp beside it, so the recurring-clobber signal is not lost, only this flag. **(d) `restore_tests.mount_parity` + `mount_inventory`** — R-262's subject; the verdict is not lost (a mismatch fails before `Pass` is set) but the hub cannot tell a full-fidelity pass from a boot-only one. **(e) `pbs_dr.applied_at`.** **(f) Controller-side: `config_hash`, `reporting_disabled`, `stacks`, `storage.migrated_to`, `backup.last_db_dump`, `backup.last_integrity_check`** — the last two are backup-integrity timestamps, which is the "presence is not success" neighbourhood. **For each the question is the same and is NOT answered here: is it wanted? If the hub should act on it, model it and name what consults it. If it should not, the honest end is that the emitter stops sending it** — a fact emitted forever and consumed nowhere is a future false green waiting for someone to write a check against it. **Deliberately not decided in the G-1 session**, whose scope was the gate plus the operator-access instance; unilaterally removing emitters would also break the byte-identical cross-repo host-report golden and is a coordinated two-repo change. **⚠ DISPOSITIONS RECORDED 2026-08-13 — and the first thing to say is that they were NOT in this register before today.** The rulings were made on 2026-08-12; this row still read **READY — owner Viktor** and the gate's twenty entries still all said *"arguably owed"*, so a session told to *"re-read the dispositions from the register"* would have found none. They are written down now, which is the point of writing them down. **THE COUNT WAS ALSO WRONG:** this row says *twenty-one*; the gate's allowlist held **twenty**, measured. Twenty is the number the dispositions below account for, exactly. **(1) BUILD A READER — four groups, fourteen facts.** (a) guest-network health, (b) the staged-update-pending pair, (d) the restore-test depth pair, (f-part) the two backup-integrity timestamps. **(2) NO READER WANTED — five facts, now recorded as `not consumed, DELIBERATELY` with the ruling and its date** in `scripts/wire_contract_gate.py`, each with its own reason rather than a bare refusal: `mgmt_plane.healed_recently` (the hub already alarms on the timestamp beside it), `pbs_dr.applied_at` (`pbs_dr.state` is the verdict; the timestamp alone is the attempt-read-as-result trap), `config_hash` (the hub authors the config and knows its own generation), `stacks` (the app view is built from the purpose-built `app_telemetry` wire), `storage.migrated_to` (box-local bookkeeping with no hub-side intent to reconcile against). **The emitters are deliberately left alone** — removing one is a coordinated two-repo change and breaks the host-report golden; the honest end here is a recorded decision, not a deletion. The gate grew a THIRD entry kind to carry them, because an undecided fact and a decided one must not read alike. **(3) `reporting_disabled`, decided on its own merits: RECLASSIFIED `redundant`** — `health.status = "disabled"` travels in the same minimal report, is decoded into `reports.health_status`, and IS rendered. **The decision surfaced a real defect the flag would not have fixed: the staleness checker is age-only, so a deliberately-silent box still alarms → R-321.** **PROGRESS, 2026-08-13: the first reader is BUILT — guest-network health, R-319.** Its eight allowlist entries are **removed** (an allowlisted tag is skipped, so leaving them would have meant the new reader's fields were never checked); the gate's checked-tLine truncated
|
||||
| **R-451** | Hub & operator | P4 | **[P3-LOW] UPDATE ARC SLICE 7 — a fleet sweep: the operator can SEE, and MOVE, how far behind every box is.** Slices 1 and 2 make one box's state visible on that box's own pages. The operator has no fleet view, and **it is not derivable from what is already reported: the hub's report payload carries container name, state, CPU and memory, and NO image field at all** (spike §5, which is why Peti's box could only be recorded UNKNOWN). So this is a hub-side change as well as a controller one. Rank LOW today because the fleet is two enrolled boxes; it rises with the fleet. `architecture/09-update-architecture.md` §6, §8.4 **BOTH SIDES VERIFIED 2026-09-21, and it is cheaper than this row implies.** The controller's payload carries no image (`internal/report/types.go` L98–103) and the hub's `Store.SaveReport` (`hub/internal/store/store.go:965`) denormalises only container **counts** — but **the hub stores the raw report JSON whole**, so a new controller field lands there the day it is sent. What is missing is the denormalisation and the page, not the transport. Shape in `09` §6.2–6.3; the payload question is `09` §3b **Q7**. **-- RULED 2026-09-23 (`09` §3 decision 18):** the report carries, per compose service (database included), the installed reference, the catalog reference and the badge state. **Built later, when the fleet grows** — Q7's recommendation, confirmed. | **DEFERRED** (2026-10-03 triage: the row's verdict was finished, but it names open work no other row carries — the fleet sweep (report field, hub denormalisation, fleet page) is ruled and not built — deferred until the fleet grows (`09` §6.3)) — **RULED — build deferred until the fleet grows; owner: CC** | — | — | CC |
|
||||
| **R-814** | Hub & operator | P4 | `PBS-storage-1` (u629193, box 611421) still `status=active`, 19.9 MB | **VERIFY** (2026-10-03 triage: a July watch row with no id; given R-814. WAITING-ON-OPERATOR — no record found that the box was deleted.) — WAITING-ON-OPERATOR | operator console | Delete the box | operator |
|
||||
@@ -352,13 +356,11 @@ stopping line that lies.
|
||||
| **R-990** | Business & legal | P4 | **Operator: read `documentation/audits/forum-research-2026-10-12/FELHOM.md` §5 and rule on each recommendation.** READ-ONLY research 2026-10-12: three years (#7943–#11092, 2,908 posts, 195 people, 82 beginner threads) of the public PROHARDVER! topic „Home server / házi szerver építése”, tagged and counted by person (`FINDINGS.md`, method and spot-check in `METHOD.md`; no username or post text in git). Ten ranked recommendations, none acted on; the top three: a „which machine works” page (8 of 33 beginners who name their hardware own only a Synology, a router disk or a Raspberry Pi — the installer cannot run there; the same gap is R-989's first recommendation from a different source, and the shipped-page half is R-991), „you do not choose an operating system — we do” (the biggest beginner question, 20 of 82 threads), and „bring your old office PC” as the cheap entry. Catalog gap (≥ 3 people): a DLNA server (7), an ad/DNS blocker (5), a camera recorder (5). | **WAITING-ON-OPERATOR** — filed 2026-10-12 | the operator's reading | Operator: rule on each of the ten (yes / no / later). If nothing is done: nothing changes; the research ages | operator |
|
||||
| **R-1035** | Business & legal | P4 | **Did the „felhő tárhely” page earn anything? Two dated checks.** **Baseline (reported by the operator from Search Console, 2026-10-14, last 3 months):** 1 click, 48 impressions, CTR 2.1 %, average position **73.6**; „felhő tárhely” 19 impressions, „felhőtárhely” 5, „felhő alapú tárhely” 3. CC measured separately that the phrase „felhő tárhely” appeared **0 times** on the home, FAQ and backup pages — Google was showing the home page for those queries by accident. `/sajat-felho-tarhely` + `/en/own-cloud-storage` published **2026-10-14**; full baseline, price sources and the power provenance in `audits/seo-2026-10-14/BASELINE.md`. **What to measure, both times:** impressions and average position for „felhő tárhely” and „saját felhő tárhely”, plus the new page's own impressions, clicks and position. **A null result at +4 weeks means nothing on its own** — a new page on a site at position 73.6 takes weeks to months to move, and „felhő tárhely” alone is held by Microsoft, Google and the big list sites; the page aims at the two longer phrases. That is why there are two checks and not one, and why neither should be read as a verdict on the copy. **There is no Search Console connector in this project, so CC cannot read these numbers** — the operator reads them, CC records and compares. **Blocked until the operator asks for indexing** (Search Console → URL-ellenőrzés → the URL → „Indexelés kérése”): until that happens, or Google finds the page itself, both checks read zero for a reason that is not the page. **This is a slow channel and does not replace talking to people.** | **WATCHING** | operator: request indexing, then supply the Search Console read | 2026-11-11: first read (+4 weeks); 2026-12-09: second (+8 weeks), compared with the first | operator + CC |
|
||||
|
||||
## Process & tooling — 34 rows (P3 9, P4 25)
|
||||
## Process & tooling — 32 rows (P3 7, P4 25)
|
||||
|
||||
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **R-1083** | Process & tooling | P3 | **The scratch box 9202 cannot use any feature that needs the agent: its `bootstrap.json` has no `local_api` block, so its controller never gets an agent client.** MEASURED 2026-10-15 (storage-identity build A): 9202 put on controller 0.317.0 by hand to show that a NAS share no longer hides the one-copy notice (R-1075); its `bootstrap.json` (read on the box) holds no `local_api`, so the controller has no agent to ask and keeps the old `st_dev` rule — the check could not tell old from new — 9202 was put back on 0.314.0. Every agent-coupled feature (drives, same-disk, format, network storage) is untestable on the scratch box today, and the NAS leg of build A is proven by a red-proofed unit test only. Evidence: `audits/storage-identity-buildA-2026-10-15/9202-upgrade.txt`. | **OPEN — the exact gap, read 2026-10-19 (no agent change tonight):** the agent mints a per-guest local-API token in ONE place, `--selftest=provision`'s back half (`cmd/felhom-agent/main.go` `bh.Provision` after `engine.RunBringUp`), which only runs for a guest it brings up from a golden archive in the same call. There is no product way to mint for an EXISTING guest (9202) — no subcommand, no local-API route; the token store `/var/lib/felhom-agent/local-tokens.log` is written only by that path. Options: an agent subcommand `--mint-guest-token <vmid>` (owner-only, writes bootstrap's `local_api`), or re-provision 9202 through `selftest=provision` (destroys and rebuilds the scratch guest — not done; rule 6). Until then 9202 is controller-only. OPEN **2026-10-20 — left out of agent 0.161.0 on purpose:** the code would be small (a root-only subcommand that mints the token and adds `local_api` to an existing guest's `bootstrap.json`), but an agent key makes 9202's controller book the agent's whole-guest backups, one tier of which writes to ep0 (the demo-hp namespace) — and 9202 is outside the `felhom` pool. Needs, before it ships: a way to give a scratch guest a key with NO whole-guest tiers (or the operator's word that 9202 may write to ep0), and 9202 in the pool. | — | Give 9202 a local-API token the normal way (the agent mints it for its guest), or record in `runbooks/target-selection.md` that 9202 is controller-only; then show the NAS notice live. | CC |
|
||||
| **R-1265** | Process & tooling | P3 | **Decisions found in code with no document — written as `[DESIGN — undocumented, found 2026-10-24]` notes; the operator confirms them (review round 2).** The notes: `04` §2 (a signed op's window has no maximum length; opsign's default 30 min); `07` §8.2c (the 60-min copy limit and stray handling; Eltávolítás refused during a backup; a start that fails after a put-back is not held; the hub's off-site delivery checker and its R-1230 skip; the release copy has no matrix row); `09` §6.4.x (build.sh's three refusals; the once-retry; stopped_by_household; the manual leg's deadline; two preflight refusals; the unreadable-template row and the R-971 admission); `14` (a re-enroll during the hold is „gone”; two info events; three confirmation words); `15` (the „Később” timer is in memory; the Sonarr/Radarr key read from config.xml); `16` (detect-virt read once; an unreadable path = the same disk; the internal place's lifecycle refusals and its size rule); and R-1019's extension to MeTube, NZBGet and SABnzbd (catalog `c50adfc`: the brief named only qBittorrent and Transmission; MeTube's downloads may be gone from their source site — HUNT-2 H3). **Picks prepared 2026-10-24 night:** `audits/review-round-2-2026-10-24/D44-D45-picks.md` — 25 notes (21 „safe to confirm”, 4 that matter, each with a pick and its cost). | **WAITING-ON-OPERATOR** | the operator's word (D44) | answer „all picks” or name the ones to change | operator |
|
||||
| **R-1266** | Process & tooling | P3 | **Rulings and promises in the documents that the code does not do as written — the operator rules whether the text or the code moves (review round 2).** (1) `09` decision 13: „the tag rule (`CompareImageRefs`) moves to the catalog gate” — it did not (`felhom-controller/controller/internal/stacks/updateorder.go:134`; `09` §6.4 part 4 says so); (2) decision 14: a box „never jumps” — a person's press on an app older than the ladder takes the catalog's current definition (`ladder.go:34-36,175-176`; the night leg does not jump); (3) decision 250 item 5: the golden gate judges „by its AGE … while the floor is above it” — the gate cannot read the floor and uses „release ahead” (`scripts/golden_currency_gate.py:8-12`); (4) `01` §1/§4 „the hub never connects into a box”, „no inbound control endpoint”, „no standing operator backdoor” — the hub pokes a box (UDP to its WG /32:51822 via ep0, `hub/internal/poke/client.go`; agent `internal/poke/poke.go:35`) and the operator's WG peer reaches felhom-sshd (`06` §4.5); `03` §5's break-glass „via the Cloudflare Tunnel behind Access” and `06` §6's „no inbound listener” are the same old text; (5) `01` §8: the operator „cannot open / read” the off-site backup — R-985 already softened the website; (6) `06` §8's plan that the agent manages cloudflared — `03` §2 says it will not. **Picks prepared 2026-10-24 night:** `audits/review-round-2-2026-10-24/D44-D45-picks.md` — 7 passages, each „text follows code” with its reason. | **WAITING-ON-OPERATOR** | the operator's choice (D45) | answer „all picks” or name the ones to change | operator |
|
||||
| **R-1240** | Process & tooling | P4 | **Scratch guest 9202 holds 34 saved copies of its `controller.yaml`, each carrying the catalog's git token** (`controller.yaml.pre-<session>`, 2026-09-21 → 2026-10-17, root 0600, inside the guest's controller volume). Every drill session that repointed 9202 saved one and none removed it after its byte-identical restore. Found 2026-10-23 (the D39 session removed its own after `cmp` proved it identical). Not read: whether the token in them is still live (R-925 rotated tokens). | **OPEN** | — | read which token the copies carry (by hash, never printed); if the live one, delete the copies; add „remove the save after an identical restore" to `repoint.py restore` | CC |
|
||||
| **R-1081** | Process & tooling | P4 | **Two published controller tags hold content that is not what their number says: `0.315.0` and `0.316.3`.** Both were caused by CC on 2026-10-15, and both by the same shape — a guard that was not on the path the work takes. **`0.316.3`:** the build directory's `build.sh` is a symlink into the SHARED clone's working copy, which predated the `FELHOM_REPO_DIR` override, so the override was silently ignored and the build copied that tree — which held 31 uncommitted files of another session's physical-disk storage work. The banner printed `Commit: 0225255` with no `Repo:` line. **Nobody deployed it; that was luck, not a control.** Fixed at the root in controller v0.316.4: `build.sh` now refuses `--push` from a tree that is dirty, not at `origin/main`, or not a git checkout, with four test cases red-proved. **Neither tag is re-pushed or deleted** — a version number is pushed once, and that applies to numbers CC spoiled. **No box runs either:** demo-hp 9201 is on 0.317.0, the floor is 0.314.0, and the button offers the highest tag. **`0.315.0`:** Caused by CC on 2026-10-15 while proving R-1080's own fix: the guard was added to `controller/build.sh` in the repo, but the documented release command runs `$FELHOM_ROOT/build/felhom-controller/build.sh`, which was a **separate copy taken 2026-07-18** and had no guard. Running the refusal against a real existing tag therefore rebuilt and re-pushed 0.315.0 — a fourth time — with v0.316.0's code under it (digest `8887343011d6` → `b809201841f7`). **Fixed at the root in the same session:** the build directory's script is now a symlink to the repo's, the old copy kept beside it as `build.sh.stale-copy-2026-07-18`, and the refusal was then proven to fire from the documented path. **No box is affected:** nothing runs 0.315.0 (demo-hp 9201 is on 0.316.2), the floor is 0.314.0, and the household button offers the highest tag. What remains is two tags that must never be deployed and are no longer reproducible from the registry. | **OPEN** — filed 2026-10-15; nothing to do unless someone needs one of those two images, and then the answer is the current release | — | Leave them. If an audit ever needs the real 0.315.0 or 0.316.3, build it from the matching commit under a NEW number and say so | CC |
|
||||
| **R-1034** | Process & tooling | P3 | **Gate 17 checks the FAQ JSON-LD on two pages only, by a hard-coded list — a FAQPage anywhere else is unchecked.** `scripts/site_gates.py` gate 17 loops `for p in ("gyik.html", os.path.join("en", "faq.html"))`, so its „JSON-LD equals the visible questions and answers” guarantee stops at those two files. **Since 2026-10-14 two more pages carry a FAQPage block the gate never reads:** `/sajat-felho-tarhely` and `/en/own-cloud-storage`. Their JSON-LD was *derived* from the visible headings and paragraphs by a script and then verified verbatim against the rendered text, so it is correct today — but nothing stops the next copy edit moving the visible text and leaving the structured data behind, which is exactly the failure gate 17 exists to catch, and the one Google penalises. **Why it is not a one-line fix:** gate 17's extractor is written for the FAQ pages' accordion markup (`<button class="faq-question">` … `<div class="faq-answer-inner">`); the new pages use plain `<h2>` + `<h3>`/`<p>` sections, so widening the scope needs a second extractor beside the first. **Scope is a fact** (repo `CLAUDE.md`: prefer a walk over a hand-maintained list) — the gate should scan **every page containing a FAQPage block**, not a named pair, or the next page to carry one is unguarded in the same way. A decoy is owed for the widened gate (R-421). | **OPEN** | — | widen gate 17 to every page carrying a FAQPage block, second extractor + decoy | CC |
|
||||
@@ -399,7 +401,6 @@ stopping line that lies.
|
||||
the R-row. Duplicating them here would create the second source this design avoids. -->
|
||||
| item | due (UTC) | what to measure |
|
||||
|---|---|---|
|
||||
| R-232 | 2026-10-26 | the 03:14 local restic sweep (`dooplex-backup`, `pgdump` repo) took a snapshot of `/mnt/5_hdd/backup/postgresql/dumps` that holds a folder with `SUCCESS` from 2026-10-25 (the CronJob's 00:00/06:00 local runs on `postgres-superuser`), and `PostgresqlDumpStale` / `DooplexGiteaOffsiteStale` did not fire overnight |
|
||||
| R-435 | 2026-10-27 | Tester 1's weekly off-site clean-up ran inside a hub window with no drop alarm (box log + mailbox); detail in the R-435 row |
|
||||
| R-1183 | 2026-10-26 | every hub daily job ran ONCE on 2026-10-25 (hub log + one snapshot file per slot — four slots since hub 0.167.0, D34, the 02:00 one not twice); detail in the R-1183 row |
|
||||
| R-1228 | 2026-10-30 | D35 (yes, after the release week): start moving the website + installer to Cloudflare; detail in the R-1228 row |
|
||||
|
||||
@@ -5,6 +5,13 @@ Moved here byte for byte on 2026-10-14 (night session), newest first, so that `S
|
||||
|
||||
**Updated 2026-10-13 (night): demo-hp, demo-felhom and Tester 1 run agent 0.155.0; demo-felhom runs controller 0.310.0, the other two 0.309.0 until the floor is raised. The open-items list is at 127. Reports: `REPORT-poweroff.md`, `REPORT-plugmeter.md`, `REPORT-usenet-2026-10-12.md`, `REPORT-newapps-2026-10-12.md`, `REPORT-disk-evening-2026-10-12.md`, `REPORT-answers-2026-10-12.md`, `REPORT-m3-2026-10-11.md`, `REPORT-redesign-2026-10-10.md`, `REPORT-testers-2026-10-10.md`, `REPORT-register-shrink-2026-10-10.md`, `REPORT-new-apps-2026-10-10.md`, `REPORT-release-2026-10-10.md`, `REPORT-break-the-circle-2026-10-09.md`, `REPORT-dooplex-survival-2026-10-09.md`, `REPORT-day4-2026-10-09.md`.**
|
||||
|
||||
## Moved from STATUS on 2026-10-25 (attended release run)
|
||||
|
||||
Decided: **D44 = all picks**, **D45 = all picks** (operator, 2026-10-25; R-1265 and R-1266 closed, the four code picks are R-1280–R-1283).
|
||||
|
||||
| D45 | R-1266 | Seven places where a design document says one thing and the code does another. One line each, with a pick: `documentation/audits/review-round-2-2026-10-24/D44-D45-picks.md`. Answer „all picks" or name the ones you disagree with. | **Yes — the text follows the code** (each is how the system works today; you read every changed sentence) | the code follows the text: six code changes, one of them to the box's door for you | the documents keep saying what is not true | — |
|
||||
| D44 | R-1265 | 25 behaviours the code has but no document named. One line each, with a pick (21 confirm, 4 small changes): the same file. Answer „all picks" or name the ones you disagree with. | **All picks** (each was read in the code) | name the ones to change; each becomes a row | the notes stay marked „undocumented" | — |
|
||||
|
||||
## Moved from STATUS on 2026-10-25 (Sunday)
|
||||
|
||||
**Updated 2026-10-25 (night).** 65 catalog apps walked on a fresh box: 52 work, 10 with a problem, 0 broken; worst: a restore after a reinstall breaks an app (R-1269).
|
||||
|
||||
Reference in new issue
Block a user