Files
felhom.eu/documentation/runbooks/RUNBOOK-local-api-endpoint-drift.md
T
admin 9cfa619ec3 hub v0.74.0: allow local_api_endpoint_drift; R-77 docs + R-78/79/80
The allowlist entry is REQUIRED, not cosmetic: handleEvent 400s an unknown
event_type, so controller v0.173.0's new drift alert would be silently inert
without it. Shipped with the controller that emits it.

Docs:
- RUNBOOK-local-api-endpoint-drift.md — how to repair a drift, including the
  step everyone will want to skip (establish which value is CORRECT from what
  the agent is actually bound to, rather than assuming bootstrap.json wins) and
  what success looks like (SILENCE, not a "recovered" line, because a fresh
  controller's healthy first observation is not logged). Records both
  2026-07-26 repairs.
- ROADMAP: R-77 shipped; R-78 the local_api authority ruling, with the
  clobber-a-working-channel risk spelled out in BOTH directions so it is not
  resolved opportunistically; R-79 the whole-surface English-strings sweep;
  R-80 expected_backup_missed, flagged as likely outranking R-77 because 7.3
  days of stale backup materially exceeds the ~1.5-day channel outage, so the
  causal link the DIAG hedged on cannot be the whole story.
- Capability map: note against the drive-wizard row (every agent-backed
  capability rides this channel) that a silent drift class is now detected.
  NO row status flips — detection is not prevention.
2026-07-26 09:14:40 +02:00

109 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RUNBOOK — repairing a `local_api` endpoint drift
**When to run:** the controller logs
`[ERROR] bootstrap: local_api endpoint DRIFT — …` at startup, or the operator receives a
`local_api_endpoint_drift` event, or the „A tárolókezelő ügynök címe elavult a beállításokban."
banner appears. Detection shipped in controller **v0.173.0** (R-77).
**What drift means:** `controller.yaml` and `bootstrap.json` disagree on `local_api.endpoint`. The
controller dials **`controller.yaml`**. The agent listens wherever it was last configured — which,
after the R-50 island migration, is `169.254.253.1:8443`.
**Why the controller does not fix this itself:** auto-reconcile would clobber a *correct*
`controller.yaml` from a *stale* `bootstrap.json` — a half-completed re-provision or a hand-repaired
guest would lose a working channel on its next restart, fleet-wide and silently. Which file is
authoritative is **R-78**, unresolved. Until it is ruled, repair is a deliberate operator action.
> **Operator-present.** This edits a live guest's config and restarts its controller. The channel
> being down also degrades the drive gate, guest-reboot recovery and the quiesce/backup loop, so it
> is worth doing promptly — but not blind.
---
## 1. Establish which value is correct — do NOT assume `bootstrap.json`
`bootstrap.json` is right *when the drift came from a migration or re-provision* — the common case.
It is **wrong** if someone hand-repaired `controller.yaml` after a failed provision. Decide with
evidence, not by default:
```bash
# What is the agent ACTUALLY listening on? This is the ground truth.
ssh <pve-host> "ss -lntH 'sport = :8443'" # post-island: 169.254.253.1:8443
```
**The correct endpoint is whatever the agent is bound to.** Both files are just claims about it.
## 2. Compare all three fields, not only the endpoint
`mergeLocalAPI` replaces the **whole** block, so a migration may have moved more than the address.
```bash
pct exec <VMID> -- python3 - <<'PY'
import json, re, hashlib
CY="/var/lib/docker/volumes/felhom-controller-data/_data/controller.yaml"
BJ="/etc/felhom-bootstrap/bootstrap.json"
def yget(path):
blk={}; inb=False
for line in open(path):
if re.match(r'^local_api:', line): inb=True; continue
if inb and re.match(r'^\S', line): break
if inb:
m=re.match(r'\s+(\w+):\s*(.*)\s*$', line)
if m: blk[m.group(1)]=m.group(2).strip().strip('"').strip("'")
return blk
cy=yget(CY); bj=(json.load(open(BJ)).get("local_api") or {})
print("endpoint cy/bj :", repr(cy.get("endpoint")), repr(bj.get("endpoint")))
print("fingerprint AGREE :", cy.get("fingerprint")==bj.get("fingerprint"))
print("token AGREE :", cy.get("token")==bj.get("token"))
PY
```
**Never print the fingerprint or token values** — booleans only, and never into a committed file.
> **STOP if `fingerprint` or `token` disagree.** A moved pin or a rotated token is a *different*
> failure: correcting only the address would then fail closed on the pin (`agent_channel_pin_mismatch`)
> or 401 (`agent_channel_unauthorized`). Diagnose that separately before touching anything.
## 3. Back up, edit the endpoint only, restart
```bash
CY=/var/lib/docker/volumes/felhom-controller-data/_data/controller.yaml
pct exec <VMID> -- cp -a $CY $CY.pre-fix.bak
# edit ONLY local_api.endpoint to the value from step 1
pct exec <VMID> -- systemctl restart felhom-controller-bootstrap.service
```
## 4. Verify — and know what success looks like
```bash
pct exec <VMID> -- docker logs felhom-controller 2>&1 | grep -i "\[channel\]"
```
**Expect SILENCE, not a "recovered" line.** On a freshly restarted controller the first probe
succeeds and `Check` returns early (*healthy first-obs → no notify*), so nothing is logged. A
`recovered` line only appears when a *running* controller transitions down→up.
Positive confirmation is therefore:
- **zero** `[channel]` lines after ~90 s (a broken channel logs `transient down` within 60 s and
`DOWN` within 120 s);
- the dashboard banner cleared;
- **zero** new `agent_channel_*` events hub-side;
- no `local_api endpoint DRIFT` line at startup (the two files now agree — if they still disagree
because you corrected `controller.yaml` rather than `bootstrap.json`, the drift alert will keep
firing and that is **correct**: it is telling you the two sources still disagree. Align the other
file too, or accept the alert until R-78).
---
## Executed instances
| date | boxes | before → after | fingerprint/token | outcome |
|---|---|---|---|---|
| 2026-07-26 | demo-felhom 9201 | `192.168.0.162:8443``169.254.253.1:8443` | both AGREE | channel healthy; 0 `[channel]` lines in 90 s; banner cleared |
| 2026-07-26 | demo-hp 9201 | `192.168.0.87:8443``169.254.253.1:8443` | both AGREE | channel healthy; 0 `[channel]` lines in 90 s |
Root cause of both: the R-50 island migration (2026-07-25 12:4212:46) rewrote `bootstrap.json` and
restarted the controllers, which re-read the untouched `controller.yaml`. Full analysis:
`audits/DIAG-agent-channel-2026-07-26.md`.