docs: v0.111.0 CHANGELOG + README (telemetry context/tails) + REUSE entries

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-10 15:45:45 +02:00
parent 544c42a618
commit 30927b6816
3 changed files with 39 additions and 0 deletions
+28
View File
@@ -1,5 +1,33 @@
## Changelog
### v0.111.0 — remote app-log diagnostics: error context + on-demand log tails (2026-07-10)
Extends the app-telemetry pipeline with what the live Peti support session lacked: readable error
context and a way to pull an app's logs WITHOUT any access to the customer box. Pairs with hub v0.43.0.
- **Error context (Part B) — `internal/metrics`:** the log scraper now attaches `LogIssue.Context`
up to ±5 raw lines around the FIRST occurrence of each error-severity issue in the scrape window
(never on repeats; warns carry none). Caps: ≤11 lines, ≤400 chars/line (`…`), and a HARD 16KB
per-report budget enforced in `internal/report` (context dropped from the lowest-count issues
first). The scan loop was extracted into the pure `analyzeLogLines` (first unit tests for the
scanner). Additive `context` field on the report's `issues` — old hubs ignore it.
- **Sanitization (Part E) — `metrics.RedactLine`:** authoritative controller-side redaction applied
to every context + tail line before it leaves the box: `password|passwd|secret|token|api[_-]?key|
authorization|bearer` values → `[REDACTED]` (incl. `Authorization: Bearer <tok>` in one pass) +
64-hex strings → `[REDACTED-HEX64]` (repo-password shape).
- **On-demand log tails (Part D) — pull-based, ACK-flag pattern (same as escrow/config-refresh):**
the report ACK gains `log_tail_requests: [app…]`; the NEXT report ships
`log_tails: [{app, collected_at, lines[]}]` — 200 lines via the existing plumbing
(`stacks.GetLogs` compose-logs for stacks, scanner-style `docker logs` for the controller
container), ordered as emitted, ≤400 chars/line, ≤64KB/app head-truncated (newest kept),
redacted. Consume-once: drained at build; a failed push re-arms from the hub's still-pending
request. NO hub→controller push channel — the guest listens to no one.
- Tests + red-proofs (all three failed exactly as designed, then restored green): context capture
dropped → "context has 0 lines, want 11" FAIL; redaction gutted → `password=hunter2` shipped
visibly → FAIL; consume-once clear removed → "second drain = [gokapi cwa]" (tails every cycle)
→ FAIL. Plus: exact ±5 ordered window, first-occurrence-only context, warn-no-context, truncation,
budget drop order, byte-budget newest-kept, fetch-error skip, empty-ACK clears stale pending.
### v0.110.0 — offbox stale-lock self-heal (campaign C2) + crash-truthful status (C1) (2026-07-10)
Fixes the overnight campaign's HIGH finding: a crash mid-prune left a restic EXCLUSIVE lock the controller
+3
View File
@@ -84,6 +84,7 @@
| `reconcileRestoreSecrets` | controller/internal/backup/restore_unit.go | `(nonSecretEnv, recoveredSecrets, secretNames, dataKeyNames)` | Recovery-unit restore env merge | Units are secret-FREE by design; secrets come from live app.yaml |
| `EncryptFile` / `DecryptFile` / `IsEncryptedFAB` | controller/internal/appexport/crypto.go | password-based file crypto | .fab export bundles | scrypt-derived AES+HMAC keys |
| `maskRepoURL` | controller/internal/sync/sync.go | `(url) string` | Logging git URLs | Strips embedded credentials |
| `metrics.RedactLine` | controller/internal/metrics/redact.go | `(s string) string` | ANY log line shipped off-box (issue context, log tails) | Masks password/passwd/secret/token/api-key/authorization/bearer values + 64-hex; apply BEFORE the line leaves the box — controller-side redaction is authoritative |
### Storage registry + mount detection
@@ -116,6 +117,8 @@
| `Notifier.PushEvent` | controller/internal/notify/notifier.go | `(eventType, severity, message, details)` | Hub events | Async goroutine, 3 attempts/3s backoff. NEW event types MUST be added to hub `allowedEventTypes` or POST /event 400s; hub only emails `warning`/`error` from this path |
| `Notifier.Notify*` convenience methods | controller/internal/notify/notifier.go | typed wrappers (backup/DB/storage/channel/DR…) | Standard events | Add a typed wrapper rather than raw PushEvent calls |
| `report.BuildReport` / `Pusher.Push` | controller/internal/report/builder.go + pusher.go | periodic hub report | Box→hub reporting | ACK carries `config_version``ConfigRefresher.Reconcile` |
| `report.SetPendingLogTails` + `buildLogTailsSection` | controller/internal/report/logtail.go | ACK `log_tail_requests` → next report `log_tails` | THE pull-based ACK-flag pattern (hub asks, controller pushes next cycle) — copy for any new hub→box request | Consume-once drain at BuildReport; failed push re-arms from the hub's still-pending request; NEVER add a hub→controller push channel |
| `metrics.FetchContainerLogTail` | controller/internal/metrics/logscanner.go | `(name, tailLines) (string, error)` | Raw per-container `docker logs --tail=N` | 15s timeout; caller caps/redacts (capTailLines) |
| `ConfigRefresher.Reconcile` | controller/internal/report/config_refresh.go | `(ackVersion int)` | Pull-based config refresh | Re-pulls controller.yaml (re-merging local_api), then graceful self-restart; first-run = baseline, no restart |
| `bootstrap.MaybeIngest` / `RefreshConfig` | controller/internal/bootstrap/bootstrap.go | bootstrap.json → controller.yaml | Day-0 + refresh | Overwrites controller.yaml, NEVER settings.json |
| `api.GracefulSelfRestart` | controller/internal/api/selfrestart.go | `(logger)` | Controller self-restart | Detached exit; bootstrap unit re-runs the image |
+8
View File
@@ -1542,10 +1542,18 @@ Each report push now includes per-app telemetry data:
- Classifies lines by keyword match (errors: `error`, `fatal`, `panic`, `crit`, `oom`, `killed`, `exception`, `traceback`; warnings: `warn`, `warning`) on the first 5 words (case-insensitive).
- Deduplicates via fingerprinting: strips ANSI escape codes, ISO timestamps (with timezone offsets), and syslog timestamps (including mid-line); replaces 6+ digit numbers with `<N>`, 8+ char hex with `<HEX>`, UUIDs with `<UUID>`. Groups identical fingerprints, keeps top 10 per container.
- Returns `[]ContainerLogSummary` with `ErrorCount`, `WarnCount`, `RecentIssues []LogIssue`.
- **Error context** (v0.111.0): each error-severity issue carries `Context []string` — up to ±5 raw lines around its FIRST occurrence in the scrape window (≤11 lines, ≤400 chars/line with `…`, ANSI-stripped, redacted). Warns never carry context. The classification loop is the pure `analyzeLogLines()` (unit-tested with synthetic windows).
- **Redaction** (`redact.go`, v0.111.0): `RedactLine()` masks `password|passwd|secret|token|api[_-]?key|authorization|bearer` values (`[REDACTED]`, incl. `Authorization: Bearer <tok>`) and 64-hex strings (`[REDACTED-HEX64]`) on every context and log-tail line before it leaves the box — controller-side, authoritative.
**Report integration** (`report/telemetry.go`):
- `buildAppTelemetrySection()` calls both, then `buildAppTelemetry()` aggregates by stack — summing container metrics, merging issues, capping at 10 per app. Additionally, `buildControllerTelemetry()` creates a special entry for the controller container itself (`app_name: "felhom-controller"`).
- Results stored as `[]AppTelemetry` in the `Report` struct field `app_telemetry`.
- **Context budget** (v0.111.0): `enforceContextBudget()` caps the per-report total of all issue-context bytes at 16KB, dropping context from the lowest-count issues first.
**On-demand log tails** (`report/logtail.go`, v0.111.0 — pull-based, same ACK-flag pattern as escrow/config-refresh):
- The report ACK (`PushResponse`) gains `log_tail_requests: [app…]` — apps the operator requested logs for on the hub. `main.go`'s `OnPushResponse` hands them to `report.SetPendingLogTails()`; the NEXT `BuildReport` drains them (consume-once) and ships `log_tails: [{app, collected_at, lines[]}]`.
- Collection: `stacks.GetLogs(app, 200)` (compose logs, ordered as emitted) for stacks; `metrics.FetchContainerLogTail()` (`docker logs --tail=200`) for the `felhom-controller` container. Caps: ≤400 chars/line, ≤64KB/app head-truncated (newest lines kept), every line through `RedactLine()`.
- Fail-safe: a failed fetch or failed push leaves the hub's request pending — the next ACK re-arms it. The hub clears the request when a tail arrives. NO hub→controller push channel exists; the guest listens to no one.
#### Infrastructure Backup to Hub — RETIRED (2026-06-16)