feat: comprehensive debug logging across all controller modules
Add detailed [DEBUG] logging to every controller module when logging.level is set to "debug". Each module with stateful debug uses SetDebug(bool) wired from main.go. Covers stacks, backup, cloudflare, integrations, system, monitor, settings, scheduler, web handlers, storage, metrics, API, selfupdate, and assets. Also includes the app export/import (.fab bundles) feature from v0.32.0 and its debug page integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/api"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/appexport"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/assets"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
|
||||
cf "gitea.dooplex.hu/admin/felhom-controller/internal/cloudflare"
|
||||
@@ -71,6 +73,11 @@ func main() {
|
||||
|
||||
logger, logBuffer := setupLogger(cfg)
|
||||
|
||||
// --- Wire system package debug logging ---
|
||||
if cfg.Logging.Level == "debug" {
|
||||
system.DebugLogger = logger
|
||||
}
|
||||
|
||||
// --- Setup mode: if no customer ID configured, run setup wizard ---
|
||||
if setup.NeedsSetup(cfg) {
|
||||
logger.Printf("[INFO] felhom-controller %s — setup mode", Version)
|
||||
@@ -87,6 +94,7 @@ func main() {
|
||||
if err != nil {
|
||||
logger.Fatalf("[FATAL] Failed to load settings from %s: %v", settingsPath, err)
|
||||
}
|
||||
sett.SetDebug(cfg.Logging.Level == "debug")
|
||||
|
||||
// --- Auto-discover storage paths from deployed apps ---
|
||||
discoveredPaths := discoverHDDPaths(cfg.Paths.StacksDir, logger)
|
||||
@@ -159,6 +167,7 @@ func main() {
|
||||
|
||||
// --- Initialize health pinger (legacy, will be removed) ---
|
||||
pinger := monitor.NewPinger(&cfg.Monitoring, logger)
|
||||
pinger.SetDebug(cfg.Logging.Level == "debug")
|
||||
|
||||
// Deprecation notice for ping UUIDs
|
||||
uuids := cfg.Monitoring.PingUUIDs
|
||||
@@ -215,6 +224,7 @@ func main() {
|
||||
|
||||
// --- Initialize scheduler ---
|
||||
sched := scheduler.New(logger)
|
||||
sched.SetDebug(cfg.Logging.Level == "debug")
|
||||
|
||||
// Existing periodic tasks (migrated from ad-hoc goroutines)
|
||||
sched.Every("status-refresh", 30*time.Second, func(ctx context.Context) error {
|
||||
@@ -614,6 +624,7 @@ func main() {
|
||||
cfClient := cf.New(cfg.Infrastructure.CFAPIToken, logger, cfg.Logging.Level == "debug")
|
||||
geoStacks := &geoStackAdapter{mgr: stackMgr, domain: cfg.Customer.Domain}
|
||||
geoSync = cf.NewGeoSyncManager(cfClient, sett, cfg.Customer.Domain, geoStacks, logger)
|
||||
geoSync.SetDebug(cfg.Logging.Level == "debug")
|
||||
apiRouter.SetGeoSync(geoSync)
|
||||
|
||||
// Re-sync geo rules when apps are deployed/removed
|
||||
@@ -651,11 +662,19 @@ func main() {
|
||||
// --- Initialize integration manager ---
|
||||
integrationStacks := &integrationStackAdapter{mgr: stackMgr}
|
||||
integrationMgr := integrations.NewManager(sett, integrationStacks, cfg.Customer.Domain, cfg.Paths.StacksDir, encKey, logger)
|
||||
integrationMgr.SetDebug(cfg.Logging.Level == "debug")
|
||||
apiRouter.SetIntegrationManager(integrationMgr)
|
||||
|
||||
// --- Initialize app exporter ---
|
||||
exportProv := &exportAdapter{mgr: stackMgr, encKey: encKey}
|
||||
appExporter := appexport.NewExporter(exportProv, logger, Version)
|
||||
appExporter.SetDebug(cfg.Logging.Level == "debug")
|
||||
apiRouter.SetDebug(cfg.Logging.Level == "debug")
|
||||
|
||||
// --- Initialize web server ---
|
||||
webServer := web.NewServer(cfg, stackMgr, cpuCollector, backupMgr, crossDriveRunner, sched, sett, alertMgr, notifier, updater, logger, Version)
|
||||
webServer.SetEncryptionKey(encKey)
|
||||
webServer.SetAppExporter(appExporter)
|
||||
webServer.SetIntegrationManager(integrationMgr)
|
||||
webServer.SetStorageWatchdog(storageWatchdog)
|
||||
if assetsSyncer != nil {
|
||||
@@ -773,6 +792,8 @@ func main() {
|
||||
mux.HandleFunc("/api/health", apiRouter.HealthHandler)
|
||||
// Storage API routes handled by web server (longer prefix takes precedence over /api/)
|
||||
mux.Handle("/api/storage/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeStorageAPI))))
|
||||
// App export/import API routes handled by web server
|
||||
mux.Handle("/api/export/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeExportAPI))))
|
||||
// Debug API routes handled by web server (debug-mode gating inside handler)
|
||||
mux.Handle("/api/debug/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeDebugAPI))))
|
||||
// Self-update API — accepts session auth OR hub API key (for external triggering)
|
||||
@@ -1063,6 +1084,158 @@ func (a *driveMigrateStackAdapter) StackExists(name string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// exportAdapter implements appexport.ExportStackProvider using stacks.Manager.
|
||||
type exportAdapter struct {
|
||||
mgr *stacks.Manager
|
||||
encKey []byte
|
||||
}
|
||||
|
||||
func (a *exportAdapter) GetStackDir(name string) (string, bool) {
|
||||
s, ok := a.mgr.GetStack(name)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return filepath.Dir(s.ComposePath), true
|
||||
}
|
||||
|
||||
func (a *exportAdapter) GetStackComposePath(name string) (string, bool) {
|
||||
s, ok := a.mgr.GetStack(name)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return s.ComposePath, true
|
||||
}
|
||||
|
||||
func (a *exportAdapter) GetStackHDDMounts(name string) []string {
|
||||
s, ok := a.mgr.GetStack(name)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
stackDir := filepath.Dir(s.ComposePath)
|
||||
appCfg := stacks.LoadAppConfig(stackDir)
|
||||
if appCfg != nil && appCfg.Env["HDD_PATH"] != "" {
|
||||
return stacks.ParseComposeHDDMounts(s.ComposePath, appCfg.Env["HDD_PATH"])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *exportAdapter) GetStackHDDPath(name string) string {
|
||||
s, ok := a.mgr.GetStack(name)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
stackDir := filepath.Dir(s.ComposePath)
|
||||
appCfg := stacks.LoadAppConfig(stackDir)
|
||||
if appCfg != nil && appCfg.Env["HDD_PATH"] != "" {
|
||||
return filepath.Clean(appCfg.Env["HDD_PATH"])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a *exportAdapter) IsStackRunning(name string) bool {
|
||||
s, ok := a.mgr.GetStack(name)
|
||||
return ok && s.State == stacks.StateRunning
|
||||
}
|
||||
|
||||
func (a *exportAdapter) StopStack(name string) error {
|
||||
return a.mgr.StopStack(name)
|
||||
}
|
||||
|
||||
func (a *exportAdapter) StartStack(name string) error {
|
||||
return a.mgr.StartStack(name)
|
||||
}
|
||||
|
||||
func (a *exportAdapter) GetStackDisplayName(name string) string {
|
||||
s, ok := a.mgr.GetStack(name)
|
||||
if !ok {
|
||||
return name
|
||||
}
|
||||
return s.Meta.DisplayName
|
||||
}
|
||||
|
||||
func (a *exportAdapter) GetStackNeedsHDD(name string) bool {
|
||||
s, ok := a.mgr.GetStack(name)
|
||||
return ok && s.Meta.Resources.NeedsHDD
|
||||
}
|
||||
|
||||
func (a *exportAdapter) GetDockerVolumes(name string) []string {
|
||||
s, ok := a.mgr.GetStack(name)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
vols := backup.ParseComposeNamedVolumes(s.ComposePath)
|
||||
var names []string
|
||||
for _, v := range vols {
|
||||
names = append(names, v.Name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (a *exportAdapter) IsStackDeployed(name string) bool {
|
||||
s, ok := a.mgr.GetStack(name)
|
||||
return ok && s.Deployed
|
||||
}
|
||||
|
||||
func (a *exportAdapter) GetDecryptedEnv(name string) map[string]string {
|
||||
s, ok := a.mgr.GetStack(name)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
stackDir := filepath.Dir(s.ComposePath)
|
||||
cfg := stacks.LoadAppConfigDecrypted(stackDir, a.encKey)
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
return cfg.Env
|
||||
}
|
||||
|
||||
func (a *exportAdapter) GetStacksBaseDir() string {
|
||||
return a.mgr.GetStacksBaseDir()
|
||||
}
|
||||
|
||||
func (a *exportAdapter) SaveEncryptedAppConfig(stackDir string, env map[string]string) error {
|
||||
meta := stacks.LoadMetadata(stackDir)
|
||||
sensitiveVars := stacks.SensitiveEnvVars(&meta)
|
||||
cfg := &stacks.AppConfig{
|
||||
Deployed: true,
|
||||
DeployedAt: time.Now().Format(time.RFC3339),
|
||||
Env: env,
|
||||
}
|
||||
return stacks.SaveAppConfig(stackDir, cfg, a.encKey, sensitiveVars)
|
||||
}
|
||||
|
||||
func (a *exportAdapter) RefreshStacks() error {
|
||||
return a.mgr.RefreshStatus()
|
||||
}
|
||||
|
||||
func (a *exportAdapter) RemoveStackVolumes(name string) error {
|
||||
s, ok := a.mgr.GetStack(name)
|
||||
if !ok {
|
||||
return fmt.Errorf("stack %q not found", name)
|
||||
}
|
||||
stackDir := filepath.Dir(s.ComposePath)
|
||||
|
||||
// Build env from decrypted app config
|
||||
cmdEnv := os.Environ()
|
||||
appCfg := stacks.LoadAppConfigDecrypted(stackDir, a.encKey)
|
||||
if appCfg != nil {
|
||||
for k, v := range appCfg.Env {
|
||||
cmdEnv = append(cmdEnv, k+"="+v)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(ctx, "docker", "compose", "down", "--volumes")
|
||||
cmd.Dir = stackDir
|
||||
cmd.Env = cmdEnv
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("compose down --volumes: %s — %w", strings.TrimSpace(string(out)), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pushInfraBackup builds and sends the infrastructure snapshot to the Hub.
|
||||
func pushInfraBackup(cfg *config.Config, sett *settings.Settings,
|
||||
stackProv *stackAdapter, pusher *report.Pusher, logger *log.Logger) {
|
||||
|
||||
Reference in New Issue
Block a user