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
+27 -2
View File
@@ -40,6 +40,7 @@ type Updater struct {
dataDir string
composePath string // e.g., "/opt/docker/felhom-controller/docker-compose.yml"
logger *log.Logger
debug bool
mu sync.Mutex
latestVersion string
@@ -49,7 +50,7 @@ type Updater struct {
}
// NewUpdater creates a new Updater instance.
func NewUpdater(cfg *config.SelfUpdateConfig, gitCfg *config.GitConfig, currentVersion, dataDir, composePath string, logger *log.Logger) *Updater {
func NewUpdater(cfg *config.SelfUpdateConfig, gitCfg *config.GitConfig, currentVersion, dataDir, composePath string, logger *log.Logger, debug bool) *Updater {
return &Updater{
cfg: cfg,
gitCfg: gitCfg,
@@ -57,6 +58,7 @@ func NewUpdater(cfg *config.SelfUpdateConfig, gitCfg *config.GitConfig, currentV
dataDir: dataDir,
composePath: composePath,
logger: logger,
debug: debug,
}
}
@@ -131,10 +133,16 @@ func (u *Updater) CheckForUpdate() CheckResult {
return result
}
if latestVer.Compare(currentVer) > 0 {
cmp := latestVer.Compare(currentVer)
if cmp > 0 {
result.UpdateAvailable = true
}
if u.debug {
u.logger.Printf("[DEBUG] [SELFUPDATE] Version comparison: current=%s, latest=%s, cmp=%d, updateAvailable=%v",
u.currentVer, latestStr, cmp, result.UpdateAvailable)
}
u.mu.Lock()
u.latestVersion = latestStr
u.lastCheck = &result
@@ -153,6 +161,10 @@ func (u *Updater) queryRegistry() (string, error) {
// Gitea registry V2: GET /v2/<owner>/<repo>/tags/list
url := fmt.Sprintf("https://gitea.dooplex.hu/v2/%s/tags/list", registryImagePath(u.cfg.Image))
if u.debug {
u.logger.Printf("[DEBUG] [SELFUPDATE] Registry API URL: %s (user: %s)", url, u.gitCfg.Username)
}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", fmt.Errorf("creating request: %w", err)
@@ -181,6 +193,10 @@ func (u *Updater) queryRegistry() (string, error) {
return "", fmt.Errorf("decoding response: %w", err)
}
if u.debug {
u.logger.Printf("[DEBUG] [SELFUPDATE] Registry returned %d tags: %v", len(tagsResp.Tags), tagsResp.Tags)
}
// Find highest semver tag
var highest *Version
for _, tag := range tagsResp.Tags {
@@ -342,6 +358,15 @@ func (u *Updater) updateComposeFile(newImage string) error {
// Replace image line: "image: gitea.dooplex.hu/admin/felhom-controller:..." → new image
re := regexp.MustCompile(`(image:\s*)gitea\.dooplex\.hu/admin/felhom-controller:\S+`)
if u.debug {
// Log old image line for debugging
oldMatch := re.Find(data)
if oldMatch != nil {
u.logger.Printf("[DEBUG] [SELFUPDATE] Compose file edit: %q → %q", string(oldMatch), "image: "+newImage)
}
}
newData := re.ReplaceAll(data, []byte("${1}"+newImage))
if bytes.Equal(data, newData) {