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
+18 -1
View File
@@ -47,12 +47,13 @@ type Syncer struct {
fallbackDir string // /usr/share/felhom/assets — baked-in fallback
httpClient *http.Client
logger *log.Logger
debug bool
mu sync.Mutex
status SyncStatus
}
// New creates a Syncer that downloads assets from the Hub.
func New(hubURL, apiKey, assetsDir, fallbackDir string, logger *log.Logger) *Syncer {
func New(hubURL, apiKey, assetsDir, fallbackDir string, logger *log.Logger, debug bool) *Syncer {
return &Syncer{
hubURL: strings.TrimSuffix(hubURL, "/"),
apiKey: apiKey,
@@ -60,6 +61,7 @@ func New(hubURL, apiKey, assetsDir, fallbackDir string, logger *log.Logger) *Syn
fallbackDir: fallbackDir,
httpClient: &http.Client{Timeout: 60 * time.Second},
logger: logger,
debug: debug,
status: SyncStatus{LastStatus: "never"},
}
}
@@ -78,11 +80,17 @@ func (s *Syncer) Sync(ctx context.Context) error {
}
// 1. Fetch Hub manifest
if s.debug {
s.logger.Printf("[DEBUG] Asset sync: fetching manifest from %s/api/v1/assets/manifest", s.hubURL)
}
manifest, err := s.fetchManifest(ctx)
if err != nil {
s.setError(fmt.Errorf("fetch manifest: %w", err))
return err
}
if s.debug {
s.logger.Printf("[DEBUG] Asset sync: manifest has %d files", len(manifest.Files))
}
// 2. Build local hash map
localHashes, err := s.buildLocalHashes()
@@ -90,6 +98,9 @@ func (s *Syncer) Sync(ctx context.Context) error {
s.setError(fmt.Errorf("scan local assets: %w", err))
return err
}
if s.debug {
s.logger.Printf("[DEBUG] Asset sync: %d local files found", len(localHashes))
}
// 3. Download changed/new files
hubFiles := make(map[string]bool, len(manifest.Files))
@@ -105,6 +116,9 @@ func (s *Syncer) Sync(ctx context.Context) error {
continue
}
if s.debug {
s.logger.Printf("[DEBUG] Asset sync: downloading %s (remote sha256=%s)", entry.Filename, entry.SHA256[:12]+"...")
}
if err := s.downloadFile(ctx, entry.Filename); err != nil {
s.logger.Printf("[WARN] Failed to download asset %s: %v", entry.Filename, err)
continue
@@ -117,6 +131,9 @@ func (s *Syncer) Sync(ctx context.Context) error {
for name := range localHashes {
if !hubFiles[name] {
path := filepath.Join(s.assetsDir, name)
if s.debug {
s.logger.Printf("[DEBUG] Asset sync: removing stale file %s", name)
}
if err := os.Remove(path); err != nil {
s.logger.Printf("[WARN] Failed to remove stale asset %s: %v", name, err)
} else {