v0.24.0 — Pre-testing observability: debug logging, diagnostic dump, startup self-test

- Add [DEBUG] logging across all modules (backup, storage, sync, selfupdate,
  monitor, notify, report, assets, setup) gated behind logging.level: "debug"
- Add /api/debug/dump endpoint returning full controller state JSON (debug only)
- Add startup self-test validating 9 subsystems (Docker, dirs, storage, hub,
  restic repos, metrics DB) with pass/warn/fail summary
- New packages: internal/selftest, internal/util
- Constructor/signature changes: debug bool params, logger params on
  RunHealthCheck and BuildReport, smart watchdog probe logging

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-21 18:32:26 +01:00
parent 6f02536243
commit be7803c0ac
30 changed files with 1281 additions and 67 deletions
+43 -4
View File
@@ -5,11 +5,14 @@ package storage
import (
"encoding/json"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
)
// lsblkOutput matches the top-level JSON from lsblk -J.
@@ -193,7 +196,13 @@ func partitionToParentDisk(devPath string) string {
// enrichWithBlkid fills in missing FSType, UUID, and Label on partitions using blkid.
// Probes each partition individually via /host-dev (Docker overrides /dev with its own
// tmpfs, so the host block devices are accessible at /host-dev instead).
func enrichWithBlkid(disks []BlockDevice) {
func enrichWithBlkid(disks []BlockDevice, logger *log.Logger, debug bool) {
dbg := func(format string, args ...interface{}) {
if debug && logger != nil {
logger.Printf("[DEBUG] enrichWithBlkid: "+format, args...)
}
}
for i := range disks {
for j := range disks[i].Partitions {
p := &disks[i].Partitions[j]
@@ -202,16 +211,25 @@ func enrichWithBlkid(disks []BlockDevice) {
if p.FSType == "" {
if out, err := exec.Command("blkid", "-o", "value", "-s", "TYPE", hostPath).Output(); err == nil {
p.FSType = strings.TrimSpace(string(out))
dbg("blkid TYPE %s → %q", hostPath, p.FSType)
} else {
dbg("blkid TYPE %s failed: %v", hostPath, err)
}
}
if p.UUID == "" {
if out, err := exec.Command("blkid", "-o", "value", "-s", "UUID", hostPath).Output(); err == nil {
p.UUID = strings.TrimSpace(string(out))
dbg("blkid UUID %s → %q", hostPath, p.UUID)
} else {
dbg("blkid UUID %s failed: %v", hostPath, err)
}
}
if p.Label == "" {
if out, err := exec.Command("blkid", "-o", "value", "-s", "LABEL", hostPath).Output(); err == nil {
p.Label = strings.TrimSpace(string(out))
dbg("blkid LABEL %s → %q", hostPath, p.Label)
} else {
dbg("blkid LABEL %s failed: %v", hostPath, err)
}
}
}
@@ -220,7 +238,13 @@ func enrichWithBlkid(disks []BlockDevice) {
// ScanDisks detects all block devices and classifies them into
// available (not mounted, not system) and system/mounted disks.
func ScanDisks() (*ScanResult, error) {
func ScanDisks(logger *log.Logger, debug bool) (*ScanResult, error) {
dbg := func(format string, args ...interface{}) {
if debug && logger != nil {
logger.Printf("[DEBUG] ScanDisks: "+format, args...)
}
}
out, err := exec.Command(
"lsblk", "-J", "-b",
"-o", "NAME,PATH,SIZE,TYPE,FSTYPE,MOUNTPOINT,MODEL,RM",
@@ -229,11 +253,15 @@ func ScanDisks() (*ScanResult, error) {
return nil, fmt.Errorf("lsblk failed: %w", err)
}
dbg("raw lsblk JSON: %s", util.TruncateStr(string(out), 500))
var parsed lsblkOutput
if err := json.Unmarshal(out, &parsed); err != nil {
return nil, fmt.Errorf("lsblk JSON parse failed: %w", err)
}
dbg("lsblk returned %d block devices", len(parsed.BlockDevices))
// Get system disk names from host fstab (works correctly inside container)
systemDiskNames := getSystemDiskNames()
@@ -287,16 +315,27 @@ func ScanDisks() (*ScanResult, error) {
isSystem := systemDiskNames[dev.Name]
bd.Mounted = anyMounted || isSystem
classification := "available"
if isSystem || anyMounted {
classification = "system"
result.SystemDisks = append(result.SystemDisks, bd)
} else {
result.AvailableDisks = append(result.AvailableDisks, bd)
}
dbg("disk %s: model=%q size=%s partitions=%d removable=%v classification=%s",
bd.Name, bd.Model, bd.Size, len(bd.Partitions), bd.Removable, classification)
for _, p := range bd.Partitions {
dbg(" partition %s: fstype=%q mountpoint=%q size=%s", p.Name, p.FSType, p.MountPoint, p.Size)
}
}
dbg("classification result: %d available, %d system", len(result.AvailableDisks), len(result.SystemDisks))
// Enrich FSType, UUID, Label from blkid (lsblk can't probe fstype in container)
enrichWithBlkid(result.AvailableDisks)
enrichWithBlkid(result.SystemDisks)
enrichWithBlkid(result.AvailableDisks, logger, debug)
enrichWithBlkid(result.SystemDisks, logger, debug)
return result, nil
}