fix: standardize log prefixes, remove duplicates, add missing module tags
Second-pass logging cleanup: consistent [LEVEL] [module] format across all 41 files. Remove stale prefixes ([CF], [SYNC], [SCHED], [API], [STORAGE], [HEALTH], [ROLLBACK]). Remove 5 duplicate log lines. Gate ungated DEBUG lines. Fix wrong log levels (restore start WARN→INFO). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -66,18 +66,18 @@ func (r *Router) geoUpdateSettings(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
if err := r.sett.SetGeoRestriction(geo); err != nil {
|
||||
r.logger.Printf("[API] Failed to save geo settings: %v", err)
|
||||
r.logger.Printf("[ERROR] [api] Failed to save geo settings: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
r.logger.Printf("[API] Geo settings updated: enabled=%v, countries=%v", body.Enabled, body.AllowedCountries)
|
||||
r.logger.Printf("[INFO] [api] Geo settings updated: enabled=%v, countries=%v", body.Enabled, body.AllowedCountries)
|
||||
|
||||
// Trigger async CF sync
|
||||
if r.geoSync != nil {
|
||||
go func() {
|
||||
if err := r.geoSync.Sync(context.Background()); err != nil {
|
||||
r.logger.Printf("[API] Geo sync after settings update failed: %v", err)
|
||||
r.logger.Printf("[ERROR] [api] Geo sync after settings update failed: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func (r *Router) geoTriggerSync(w http.ResponseWriter, _ *http.Request) {
|
||||
|
||||
go func() {
|
||||
if err := r.geoSync.Sync(context.Background()); err != nil {
|
||||
r.logger.Printf("[API] Manual geo sync failed: %v", err)
|
||||
r.logger.Printf("[ERROR] [api] Manual geo sync failed: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -135,18 +135,18 @@ func (r *Router) geoSetAppOverride(w http.ResponseWriter, req *http.Request, app
|
||||
|
||||
override := &settings.AppGeoOverride{AllowedCountries: body.AllowedCountries}
|
||||
if err := r.sett.SetGeoAppOverride(appName, override); err != nil {
|
||||
r.logger.Printf("[API] Failed to save geo override for %s: %v", appName, err)
|
||||
r.logger.Printf("[ERROR] [api] Failed to save geo override for %s: %v", appName, err)
|
||||
writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
r.logger.Printf("[API] Geo override set for %s: countries=%v", appName, body.AllowedCountries)
|
||||
r.logger.Printf("[INFO] [api] Geo override set for %s: countries=%v", appName, body.AllowedCountries)
|
||||
|
||||
// Trigger async CF sync
|
||||
if r.geoSync != nil {
|
||||
go func() {
|
||||
if err := r.geoSync.Sync(context.Background()); err != nil {
|
||||
r.logger.Printf("[API] Geo sync after app override failed: %v", err)
|
||||
r.logger.Printf("[ERROR] [api] Geo sync after app override failed: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -161,18 +161,18 @@ func (r *Router) geoRemoveAppOverride(w http.ResponseWriter, _ *http.Request, ap
|
||||
}
|
||||
|
||||
if err := r.sett.RemoveGeoAppOverride(appName); err != nil {
|
||||
r.logger.Printf("[API] Failed to remove geo override for %s: %v", appName, err)
|
||||
r.logger.Printf("[ERROR] [api] Failed to remove geo override for %s: %v", appName, err)
|
||||
writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
r.logger.Printf("[API] Geo override removed for %s", appName)
|
||||
r.logger.Printf("[INFO] [api] Geo override removed for %s", appName)
|
||||
|
||||
// Trigger async CF sync
|
||||
if r.geoSync != nil {
|
||||
go func() {
|
||||
if err := r.geoSync.Sync(context.Background()); err != nil {
|
||||
r.logger.Printf("[API] Geo sync after override removal failed: %v", err)
|
||||
r.logger.Printf("[ERROR] [api] Geo sync after override removal failed: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -314,14 +314,14 @@ func (r *Router) listStacks(w http.ResponseWriter, _ *http.Request) {
|
||||
}
|
||||
|
||||
func (r *Router) rescanStacks(w http.ResponseWriter, _ *http.Request) {
|
||||
r.logger.Printf("[API] Manual stack rescan requested")
|
||||
r.logger.Printf("[INFO] [api] Manual stack rescan requested")
|
||||
if err := r.stackMgr.ScanStacks(); err != nil {
|
||||
r.logger.Printf("[API] Stack rescan failed: %v", err)
|
||||
r.logger.Printf("[ERROR] [api] Stack rescan failed: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
|
||||
return
|
||||
}
|
||||
stackCount := len(r.stackMgr.GetStacks())
|
||||
r.logger.Printf("[API] Stack rescan completed: %d stacks found", stackCount)
|
||||
r.logger.Printf("[INFO] [api] Stack rescan completed: %d stacks found", stackCount)
|
||||
writeJSON(w, http.StatusOK, apiResponse{
|
||||
OK: true,
|
||||
Message: fmt.Sprintf("Rescan completed: %d stacks found", stackCount),
|
||||
@@ -355,7 +355,7 @@ func (r *Router) getDeployFields(w http.ResponseWriter, _ *http.Request, name st
|
||||
|
||||
func (r *Router) deployStack(w http.ResponseWriter, req *http.Request, name string) {
|
||||
limitBody(w, req)
|
||||
r.logger.Printf("[API] Deploy requested for stack: %s", name)
|
||||
r.logger.Printf("[INFO] [api] Deploy requested for stack: %s", name)
|
||||
r.dbg("deployStack: name=%s contentLength=%d", name, req.ContentLength)
|
||||
|
||||
var body struct {
|
||||
@@ -373,7 +373,7 @@ func (r *Router) deployStack(w http.ResponseWriter, req *http.Request, name stri
|
||||
|
||||
warning, err := r.stackMgr.DeployStack(deployReq)
|
||||
if err != nil {
|
||||
r.logger.Printf("[API] Deploy failed for %s: %v", name, err)
|
||||
r.logger.Printf("[ERROR] [api] Deploy failed for %s: %v", name, err)
|
||||
status := http.StatusInternalServerError
|
||||
if strings.Contains(err.Error(), "already deployed") {
|
||||
status = http.StatusConflict
|
||||
@@ -412,7 +412,7 @@ func (r *Router) deployStack(w http.ResponseWriter, req *http.Request, name stri
|
||||
}
|
||||
|
||||
func (r *Router) actionStack(w http.ResponseWriter, action, name string) {
|
||||
r.logger.Printf("[API] %s requested for stack: %s", action, name)
|
||||
r.logger.Printf("[INFO] [api] %s requested for stack: %s", action, name)
|
||||
r.dbg("actionStack: action=%s name=%s", action, name)
|
||||
|
||||
// Protected stacks only allow restart — block all other actions
|
||||
@@ -483,7 +483,7 @@ func (r *Router) actionStack(w http.ResponseWriter, action, name string) {
|
||||
|
||||
func (r *Router) updateOptionalConfig(w http.ResponseWriter, req *http.Request, name string) {
|
||||
limitBody(w, req)
|
||||
r.logger.Printf("[API] Optional config update requested for stack: %s", name)
|
||||
r.logger.Printf("[INFO] [api] Optional config update requested for stack: %s", name)
|
||||
|
||||
var body struct {
|
||||
Values map[string]string `json:"values"`
|
||||
@@ -494,7 +494,7 @@ func (r *Router) updateOptionalConfig(w http.ResponseWriter, req *http.Request,
|
||||
}
|
||||
|
||||
if err := r.stackMgr.UpdateOptionalConfig(name, body.Values); err != nil {
|
||||
r.logger.Printf("[API] Optional config update failed for %s: %v", name, err)
|
||||
r.logger.Printf("[ERROR] [api] Optional config update failed for %s: %v", name, err)
|
||||
writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
|
||||
return
|
||||
}
|
||||
@@ -535,11 +535,11 @@ func (r *Router) toggleIntegration(w http.ResponseWriter, req *http.Request, pro
|
||||
if !body.Enabled {
|
||||
action = "disable"
|
||||
}
|
||||
r.logger.Printf("[API] Integration %s requested: %s:%s", action, provider, target)
|
||||
r.logger.Printf("[INFO] [api] Integration %s requested: %s:%s", action, provider, target)
|
||||
|
||||
state, err := r.integrationMgr.Toggle(req.Context(), provider, target, body.Enabled)
|
||||
if err != nil {
|
||||
r.logger.Printf("[API] Integration toggle failed for %s:%s: %v", provider, target, err)
|
||||
r.logger.Printf("[ERROR] [api] Integration toggle failed for %s:%s: %v", provider, target, err)
|
||||
writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
|
||||
return
|
||||
}
|
||||
@@ -605,7 +605,7 @@ func (r *Router) removeStack(w http.ResponseWriter, req *http.Request, name stri
|
||||
return
|
||||
}
|
||||
limitBody(w, req)
|
||||
r.logger.Printf("[API] Remove requested for stack: %s", name)
|
||||
r.logger.Printf("[INFO] [api] Remove requested for stack: %s", name)
|
||||
r.dbg("removeStack: name=%s", name)
|
||||
|
||||
var body struct {
|
||||
@@ -632,7 +632,7 @@ func (r *Router) removeStack(w http.ResponseWriter, req *http.Request, name stri
|
||||
|
||||
resp, err := r.stackMgr.RemoveStack(name, body.RemoveHDDData, backupPaths)
|
||||
if err != nil {
|
||||
r.logger.Printf("[API] Remove failed for %s: %v", name, err)
|
||||
r.logger.Printf("[ERROR] [api] Remove failed for %s: %v", name, err)
|
||||
status := http.StatusInternalServerError
|
||||
if strings.Contains(err.Error(), "protected") {
|
||||
status = http.StatusForbidden
|
||||
@@ -650,7 +650,7 @@ func (r *Router) removeStack(w http.ResponseWriter, req *http.Request, name stri
|
||||
// Clean up cross-drive backup config for this stack
|
||||
if r.sett != nil {
|
||||
if err := r.sett.SetCrossDriveConfig(name, nil); err != nil {
|
||||
r.logger.Printf("[WARN] Failed to clean cross-drive config for %s: %v", name, err)
|
||||
r.logger.Printf("[WARN] [api] Failed to clean cross-drive config for %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,7 +674,7 @@ func (r *Router) removeStack(w http.ResponseWriter, req *http.Request, name stri
|
||||
|
||||
func (r *Router) deleteStack(w http.ResponseWriter, req *http.Request, name string) {
|
||||
limitBody(w, req)
|
||||
r.logger.Printf("[API] Delete requested for stack: %s", name)
|
||||
r.logger.Printf("[INFO] [api] Delete requested for stack: %s", name)
|
||||
|
||||
var body struct {
|
||||
RemoveHDDData bool `json:"remove_hdd_data"`
|
||||
@@ -685,7 +685,7 @@ func (r *Router) deleteStack(w http.ResponseWriter, req *http.Request, name stri
|
||||
|
||||
resp, err := r.stackMgr.DeleteStack(name, body.RemoveHDDData)
|
||||
if err != nil {
|
||||
r.logger.Printf("[API] Delete failed for %s: %v", name, err)
|
||||
r.logger.Printf("[ERROR] [api] Delete failed for %s: %v", name, err)
|
||||
status := http.StatusInternalServerError
|
||||
if strings.Contains(err.Error(), "protected") {
|
||||
status = http.StatusForbidden
|
||||
@@ -704,13 +704,13 @@ func (r *Router) deleteStack(w http.ResponseWriter, req *http.Request, name stri
|
||||
}
|
||||
|
||||
func (r *Router) triggerSync(w http.ResponseWriter, _ *http.Request) {
|
||||
r.logger.Println("[API] Manual catalog sync requested")
|
||||
r.logger.Println("[INFO] [api] Manual catalog sync requested")
|
||||
result := r.syncer.TriggerSync()
|
||||
if !result.OK {
|
||||
writeJSON(w, http.StatusTooManyRequests, apiResponse{OK: false, Error: result.Message})
|
||||
return
|
||||
}
|
||||
r.logger.Printf("[API] Catalog sync completed: %s", result.Message)
|
||||
r.logger.Printf("[INFO] [api] Catalog sync completed: %s", result.Message)
|
||||
writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: result.Message, Data: result})
|
||||
}
|
||||
|
||||
@@ -783,7 +783,7 @@ func (r *Router) triggerBackup(w http.ResponseWriter, _ *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
r.logger.Println("[API] Manual backup triggered")
|
||||
r.logger.Println("[INFO] [api] Manual backup triggered")
|
||||
go r.backupMgr.RunFullBackup(context.Background())
|
||||
|
||||
writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: "Mentés elindítva"})
|
||||
@@ -979,12 +979,12 @@ func (r *Router) saveCrossBackupConfig(w http.ResponseWriter, req *http.Request,
|
||||
}
|
||||
|
||||
if err := r.sett.SetCrossDriveConfig(name, cfg); err != nil {
|
||||
r.logger.Printf("[API] Failed to save cross-drive config for %s: %v", name, err)
|
||||
r.logger.Printf("[ERROR] [api] Failed to save cross-drive config for %s: %v", name, err)
|
||||
writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
r.logger.Printf("[API] Cross-drive backup config saved for %s: dest=%s schedule=%s",
|
||||
r.logger.Printf("[INFO] [api] Cross-drive backup config saved for %s: dest=%s schedule=%s",
|
||||
name, body.DestinationPath, body.Schedule)
|
||||
writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: "Cross-drive backup configuration saved"})
|
||||
}
|
||||
@@ -999,10 +999,10 @@ func (r *Router) triggerCrossBackup(w http.ResponseWriter, req *http.Request, na
|
||||
return
|
||||
}
|
||||
|
||||
r.logger.Printf("[API] Cross-drive backup triggered for: %s", name)
|
||||
r.logger.Printf("[INFO] [api] Cross-drive backup triggered for: %s", name)
|
||||
go func() {
|
||||
if err := r.crossDriveRunner.RunAppBackup(context.Background(), name); err != nil {
|
||||
r.logger.Printf("[API] Cross-drive backup failed for %s: %v", name, err)
|
||||
r.logger.Printf("[ERROR] [api] Cross-drive backup failed for %s: %v", name, err)
|
||||
}
|
||||
if r.OnCrossDriveComplete != nil {
|
||||
r.OnCrossDriveComplete()
|
||||
@@ -1037,14 +1037,14 @@ func (r *Router) triggerAllCrossBackups(w http.ResponseWriter, _ *http.Request)
|
||||
writeJSON(w, http.StatusBadRequest, apiResponse{OK: false, Error: "cross-drive runner not available"})
|
||||
return
|
||||
}
|
||||
r.logger.Println("[API] All cross-drive backups triggered")
|
||||
r.logger.Println("[INFO] [api] All cross-drive backups triggered")
|
||||
go func() {
|
||||
ctx := context.Background()
|
||||
if err := r.crossDriveRunner.RunAllScheduled(ctx, "daily"); err != nil {
|
||||
r.logger.Printf("[API] Cross-drive run-all error: %v", err)
|
||||
r.logger.Printf("[ERROR] [api] Cross-drive run-all error: %v", err)
|
||||
}
|
||||
if err := r.crossDriveRunner.RunAllScheduled(ctx, "weekly"); err != nil {
|
||||
r.logger.Printf("[API] Cross-drive run-all weekly error: %v", err)
|
||||
r.logger.Printf("[ERROR] [api] Cross-drive run-all weekly error: %v", err)
|
||||
}
|
||||
if r.OnCrossDriveComplete != nil {
|
||||
r.OnCrossDriveComplete()
|
||||
@@ -1150,7 +1150,7 @@ func (r *Router) selfupdateTrigger(w http.ResponseWriter, _ *http.Request) {
|
||||
writeJSON(w, http.StatusConflict, apiResponse{OK: false, Error: err.Error()})
|
||||
return
|
||||
}
|
||||
r.logger.Println("[API] Manual self-update triggered")
|
||||
r.logger.Println("[INFO] [api] Manual self-update triggered")
|
||||
writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: "Frissítés elindítva"})
|
||||
}
|
||||
|
||||
@@ -1171,7 +1171,7 @@ func (r *Router) configApply(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
// Validate it's parseable YAML by attempting to load it
|
||||
if _, err := config.LoadFromBytes(body); err != nil {
|
||||
r.logger.Printf("[API] Config apply rejected: invalid YAML: %v", err)
|
||||
r.logger.Printf("[WARN] [api] Config apply rejected: invalid YAML: %v", err)
|
||||
writeJSON(w, http.StatusBadRequest, apiResponse{OK: false, Error: fmt.Sprintf("invalid config YAML: %v", err)})
|
||||
return
|
||||
}
|
||||
@@ -1180,7 +1180,7 @@ func (r *Router) configApply(w http.ResponseWriter, req *http.Request) {
|
||||
// (os.Rename fails on Docker bind mounts with "device or resource busy")
|
||||
tmpPath := r.configPath + ".tmp"
|
||||
if err := os.WriteFile(tmpPath, body, 0644); err != nil {
|
||||
r.logger.Printf("[ERROR] Config apply: failed to write temp file: %v", err)
|
||||
r.logger.Printf("[ERROR] [api] Config apply: failed to write temp file: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: "failed to write config"})
|
||||
return
|
||||
}
|
||||
@@ -1189,14 +1189,14 @@ func (r *Router) configApply(w http.ResponseWriter, req *http.Request) {
|
||||
os.Remove(tmpPath)
|
||||
// Rename failed (likely Docker bind mount) — write directly
|
||||
if err := os.WriteFile(r.configPath, body, 0644); err != nil {
|
||||
r.logger.Printf("[ERROR] Config apply: failed to write config: %v", err)
|
||||
r.logger.Printf("[ERROR] [api] Config apply: failed to write config: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: "failed to apply config"})
|
||||
return
|
||||
}
|
||||
r.logger.Printf("[API] Config apply: rename failed, wrote directly (bind mount)")
|
||||
r.logger.Printf("[INFO] [api] Config apply: rename failed, wrote directly (bind mount)")
|
||||
}
|
||||
|
||||
r.logger.Printf("[API] Config applied from Hub (%d bytes), restart needed to take effect", len(body))
|
||||
r.logger.Printf("[INFO] [api] Config applied from Hub (%d bytes), restart needed to take effect", len(body))
|
||||
writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: "Config applied. Restart controller to apply changes."})
|
||||
|
||||
// Push updated infra backup so Hub has fresh config data immediately
|
||||
@@ -1231,10 +1231,10 @@ func (r *Router) triggerAssetSync(w http.ResponseWriter, req *http.Request) {
|
||||
writeJSON(w, http.StatusOK, apiResponse{OK: false, Error: "asset sync not configured"})
|
||||
return
|
||||
}
|
||||
r.logger.Println("[API] Manual asset sync requested")
|
||||
r.logger.Println("[INFO] [api] Manual asset sync requested")
|
||||
go func() {
|
||||
if err := r.assetsSyncer.Sync(context.Background()); err != nil {
|
||||
r.logger.Printf("[WARN] Manual asset sync failed: %v", err)
|
||||
r.logger.Printf("[WARN] [api] Manual asset sync failed: %v", err)
|
||||
}
|
||||
}()
|
||||
writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: "Asset sync started"})
|
||||
@@ -1252,7 +1252,7 @@ func writeJSON(w http.ResponseWriter, status int, v interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
if err := json.NewEncoder(w).Encode(v); err != nil {
|
||||
log.Printf("[ERROR] Failed to write JSON response: %v", err)
|
||||
log.Printf("[ERROR] [api] Failed to write JSON response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ func (s *Syncer) Sync(ctx context.Context) error {
|
||||
s.mu.Unlock()
|
||||
}()
|
||||
|
||||
s.logger.Println("[INFO] Asset sync starting...")
|
||||
s.logger.Println("[INFO] [assets] Asset sync starting...")
|
||||
syncStart := time.Now()
|
||||
|
||||
if err := os.MkdirAll(s.assetsDir, 0755); err != nil {
|
||||
@@ -140,7 +140,7 @@ func (s *Syncer) Sync(ctx context.Context) error {
|
||||
s.dbg("file %s: downloading (%s, %d bytes)", entry.Filename, reason, entry.Size)
|
||||
dlStart := time.Now()
|
||||
if err := s.downloadFile(ctx, entry.Filename); err != nil {
|
||||
s.logger.Printf("[WARN] Failed to download asset %s: %v", entry.Filename, err)
|
||||
s.logger.Printf("[WARN] [assets] Failed to download asset %s: %v", entry.Filename, err)
|
||||
continue
|
||||
}
|
||||
s.dbg("file %s: downloaded in %s", entry.Filename, time.Since(dlStart).Round(time.Millisecond))
|
||||
@@ -154,7 +154,7 @@ func (s *Syncer) Sync(ctx context.Context) error {
|
||||
path := filepath.Join(s.assetsDir, name)
|
||||
s.dbg("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)
|
||||
s.logger.Printf("[WARN] [assets] Failed to remove stale asset %s: %v", name, err)
|
||||
} else {
|
||||
removed++
|
||||
}
|
||||
@@ -174,7 +174,7 @@ func (s *Syncer) Sync(ctx context.Context) error {
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
s.logger.Printf("[INFO] Asset sync complete: %d downloaded, %d unchanged, %d removed (%d total files)",
|
||||
s.logger.Printf("[INFO] [assets] Asset sync complete: %d downloaded, %d unchanged, %d removed (%d total files)",
|
||||
downloaded, skipped, removed, len(manifest.Files))
|
||||
s.dbg("sync completed in %s", time.Since(syncStart).Round(time.Millisecond))
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package backup
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
@@ -105,6 +106,7 @@ func DiscoverAppData(provider StackDataProvider, discoveredDBs []DiscoveredDB) [
|
||||
result = append(result, info)
|
||||
}
|
||||
|
||||
log.Printf("[INFO] [backup] Discovered app data: %d apps", len(result))
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ type BackupStatus struct {
|
||||
// NewManager creates a new backup manager.
|
||||
func NewManager(cfg *config.Config, pinger *monitor.Pinger, sett *settings.Settings, logger *log.Logger) *Manager {
|
||||
if cfg.Paths.SystemDataPath == "" {
|
||||
logger.Printf("[WARN] SystemDataPath is empty in config — SSD-only apps will not have correct backup paths")
|
||||
logger.Printf("[WARN] [backup] SystemDataPath is empty in config — SSD-only apps will not have correct backup paths")
|
||||
}
|
||||
dataDir := cfg.Paths.DataDir
|
||||
if dataDir == "" {
|
||||
@@ -178,7 +178,7 @@ func (m *Manager) GetAppDrivePath(stackName string) string {
|
||||
}
|
||||
}
|
||||
if m.systemDataPath == "" {
|
||||
m.logger.Printf("[ERROR] systemDataPath is empty — cannot determine drive for %s", stackName)
|
||||
m.logger.Printf("[ERROR] [backup] systemDataPath is empty — cannot determine drive for %s", stackName)
|
||||
}
|
||||
return m.systemDataPath
|
||||
}
|
||||
@@ -238,16 +238,16 @@ func (m *Manager) RunDBDumps(ctx context.Context) error {
|
||||
// runDBDumpsInternal is the implementation of RunDBDumps. Caller must hold the running flag.
|
||||
func (m *Manager) runDBDumpsInternal(ctx context.Context) error {
|
||||
start := time.Now()
|
||||
m.logger.Printf("[INFO] Starting database dump run")
|
||||
m.logger.Printf("[INFO] [backup] Starting database dump run")
|
||||
|
||||
dbs, err := DiscoverDatabases(ctx, m.logger, m.isDebug())
|
||||
if err != nil {
|
||||
m.logger.Printf("[ERROR] Database discovery failed: %v", err)
|
||||
m.logger.Printf("[ERROR] [backup] Database discovery failed: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(dbs) == 0 {
|
||||
m.logger.Printf("[INFO] No database containers found")
|
||||
m.logger.Printf("[INFO] [backup] No database containers found")
|
||||
m.mu.Lock()
|
||||
m.lastDBDump = &DBDumpStatus{
|
||||
LastRun: time.Now(),
|
||||
@@ -258,7 +258,7 @@ func (m *Manager) runDBDumpsInternal(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Discovered %d database(s): %s", len(dbs), dbNames(dbs))
|
||||
m.logger.Printf("[INFO] [backup] Discovered %d database(s): %s", len(dbs), dbNames(dbs))
|
||||
|
||||
// Dump each DB to its app's drive path
|
||||
var results []DumpResult
|
||||
@@ -271,12 +271,12 @@ func (m *Manager) runDBDumpsInternal(ctx context.Context) error {
|
||||
|
||||
// Skip if drive is disconnected or decommissioned
|
||||
if m.settings != nil && m.settings.IsDisconnected(drivePath) {
|
||||
m.logger.Printf("[WARN] Skipping DB dump for %s — drive disconnected: %s", db.StackName, drivePath)
|
||||
m.logger.Printf("[WARN] [backup] Skipping DB dump for %s — drive disconnected: %s", db.StackName, drivePath)
|
||||
summary = append(summary, fmt.Sprintf("SKIP %s (drive disconnected)", db.ContainerName))
|
||||
continue
|
||||
}
|
||||
if m.settings != nil && m.settings.IsDecommissioned(drivePath) {
|
||||
m.logger.Printf("[WARN] Skipping DB dump for %s — drive decommissioned: %s", db.StackName, drivePath)
|
||||
m.logger.Printf("[WARN] [backup] Skipping DB dump for %s — drive decommissioned: %s", db.StackName, drivePath)
|
||||
summary = append(summary, fmt.Sprintf("SKIP %s (drive decommissioned)", db.ContainerName))
|
||||
continue
|
||||
}
|
||||
@@ -289,7 +289,7 @@ func (m *Manager) runDBDumpsInternal(ctx context.Context) error {
|
||||
if result.Error != nil {
|
||||
allOK = false
|
||||
summary = append(summary, fmt.Sprintf("FAIL %s: %v", result.DB.ContainerName, result.Error))
|
||||
m.logger.Printf("[ERROR] DB dump failed for %s: %v", result.DB.ContainerName, result.Error)
|
||||
m.logger.Printf("[ERROR] [backup] DB dump failed for %s: %v", result.DB.ContainerName, result.Error)
|
||||
} else {
|
||||
totalSize += result.Size
|
||||
summary = append(summary, fmt.Sprintf("OK %s (%s)", result.DB.ContainerName, humanizeBytes(result.Size)))
|
||||
@@ -306,7 +306,7 @@ func (m *Manager) runDBDumpsInternal(ctx context.Context) error {
|
||||
cache.Error = result.Validation.Error
|
||||
}
|
||||
if err := m.settings.SetDBValidation(filename, cache); err != nil {
|
||||
m.logger.Printf("[WARN] Failed to cache validation for %s: %v", filename, err)
|
||||
m.logger.Printf("[WARN] [backup] Failed to cache validation for %s: %v", filename, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -329,7 +329,7 @@ func (m *Manager) runDBDumpsInternal(ctx context.Context) error {
|
||||
|
||||
if allOK {
|
||||
m.pinger.Ping(uuid, body)
|
||||
m.logger.Printf("[INFO] DB dump completed: %d databases, %s total (%s)",
|
||||
m.logger.Printf("[INFO] [backup] DB dump completed: %d databases, %s total (%s)",
|
||||
len(results), humanizeBytes(totalSize), duration.Round(time.Millisecond))
|
||||
} else {
|
||||
m.pinger.Fail(uuid, body)
|
||||
@@ -352,16 +352,16 @@ func (m *Manager) RunBackup(ctx context.Context) error {
|
||||
func (m *Manager) runBackupInternal(ctx context.Context) error {
|
||||
// Skip if a full drive migration is in progress
|
||||
if m.MigrationActiveCheck != nil && m.MigrationActiveCheck() {
|
||||
m.logger.Printf("[WARN] Skipping nightly backup — drive migration in progress")
|
||||
m.logger.Printf("[WARN] [backup] Skipping nightly backup — drive migration in progress")
|
||||
return nil
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
m.logger.Printf("[INFO] Starting restic backup (per-drive)")
|
||||
m.logger.Printf("[INFO] [backup] Starting restic backup (per-drive)")
|
||||
|
||||
driveStacks := m.groupStacksByDrive()
|
||||
if len(driveStacks) == 0 {
|
||||
m.logger.Printf("[INFO] No deployed stacks — skipping backup")
|
||||
m.logger.Printf("[INFO] [backup] No deployed stacks — skipping backup")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -436,7 +436,7 @@ func (m *Manager) runBackupInternal(ctx context.Context) error {
|
||||
duration.Round(time.Second))
|
||||
m.pinger.Ping(m.cfg.Monitoring.PingUUIDs.Backup, body)
|
||||
|
||||
m.logger.Printf("[INFO] Restic backup completed: %d drives, snapshot %s, %d new, %d changed, %s added (%s)",
|
||||
m.logger.Printf("[INFO] [backup] Restic backup completed: %d drives, snapshot %s, %d new, %d changed, %s added (%s)",
|
||||
driveCount, lastResult.SnapshotID, lastResult.FilesNew, lastResult.FilesChanged, lastResult.DataAdded,
|
||||
duration.Round(time.Millisecond))
|
||||
}
|
||||
@@ -454,11 +454,11 @@ func (m *Manager) runBackupInternal(ctx context.Context) error {
|
||||
func (m *Manager) backupDrive(ctx context.Context, drivePath string, stacks []StackSummary, infraPaths []string) (*SnapshotResult, error) {
|
||||
// Skip disconnected or decommissioned drives
|
||||
if m.settings != nil && m.settings.IsDisconnected(drivePath) {
|
||||
m.logger.Printf("[WARN] Skipping backup for drive %s — disconnected", drivePath)
|
||||
m.logger.Printf("[WARN] [backup] Skipping backup for drive %s — disconnected", drivePath)
|
||||
return nil, nil
|
||||
}
|
||||
if m.settings != nil && m.settings.IsDecommissioned(drivePath) {
|
||||
m.logger.Printf("[WARN] Skipping backup for drive %s — decommissioned", drivePath)
|
||||
m.logger.Printf("[WARN] [backup] Skipping backup for drive %s — decommissioned", drivePath)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -466,7 +466,7 @@ func (m *Manager) backupDrive(ctx context.Context, drivePath string, stacks []St
|
||||
|
||||
// Ensure repo is initialized
|
||||
if err := m.restic.EnsureInitialized(repoPath); err != nil {
|
||||
m.logger.Printf("[ERROR] Restic init failed for %s: %v", repoPath, err)
|
||||
m.logger.Printf("[ERROR] [backup] Restic init failed for %s: %v", repoPath, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -506,19 +506,19 @@ func (m *Manager) backupDrive(ctx context.Context, drivePath string, stacks []St
|
||||
}
|
||||
|
||||
tags := []string{"felhom", m.cfg.Customer.ID, filepath.Base(drivePath)}
|
||||
m.logger.Printf("[INFO] Backing up drive %s (%d apps, %d paths)", drivePath, len(stacks), len(paths))
|
||||
m.logger.Printf("[INFO] [backup] Backing up drive %s (%d apps, %d paths)", drivePath, len(stacks), len(paths))
|
||||
|
||||
result, err := m.restic.Snapshot(repoPath, paths, tags)
|
||||
if err != nil {
|
||||
m.logger.Printf("[ERROR] Restic backup failed for drive %s: %v", drivePath, err)
|
||||
m.logger.Printf("[ERROR] [backup] Restic backup failed for drive %s: %v", drivePath, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Prune check (weekly — Sunday)
|
||||
if shouldPrune(m.cfg.Backup.PruneSchedule) {
|
||||
m.logger.Printf("[INFO] Running weekly prune for %s", repoPath)
|
||||
m.logger.Printf("[INFO] [backup] Running weekly prune for %s", repoPath)
|
||||
if err := m.restic.Prune(repoPath, m.cfg.Backup.Retention); err != nil {
|
||||
m.logger.Printf("[WARN] Restic prune failed for %s: %v", repoPath, err)
|
||||
m.logger.Printf("[WARN] [backup] Restic prune failed for %s: %v", repoPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,7 +548,7 @@ func (m *Manager) TryRunDriveBackup(ctx context.Context, drivePath string) error
|
||||
driveStacks := m.groupStacksByDrive()
|
||||
stacks, ok := driveStacks[drivePath]
|
||||
if !ok || len(stacks) == 0 {
|
||||
m.logger.Printf("[INFO] No deployed stacks on drive %s — skipping backup", drivePath)
|
||||
m.logger.Printf("[INFO] [backup] No deployed stacks on drive %s — skipping backup", drivePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -563,7 +563,7 @@ func (m *Manager) TryRunDriveBackup(ctx context.Context, drivePath string) error
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
m.logger.Printf("[INFO] Single-drive backup for %s: snapshot %s, %d new, %d changed, %s added",
|
||||
m.logger.Printf("[INFO] [backup] Single-drive backup for %s: snapshot %s, %d new, %d changed, %s added",
|
||||
drivePath, result.SnapshotID, result.FilesNew, result.FilesChanged, result.DataAdded)
|
||||
}
|
||||
|
||||
@@ -572,12 +572,12 @@ func (m *Manager) TryRunDriveBackup(ctx context.Context, drivePath string) error
|
||||
|
||||
// RunIntegrityCheck runs restic check on all primary repos and pings healthchecks.
|
||||
func (m *Manager) RunIntegrityCheck(ctx context.Context) error {
|
||||
m.logger.Printf("[INFO] Starting restic integrity check")
|
||||
m.logger.Printf("[INFO] [backup] Starting restic integrity check")
|
||||
start := time.Now()
|
||||
|
||||
drives := m.activeDrives()
|
||||
if len(drives) == 0 {
|
||||
m.logger.Printf("[INFO] No active drives — skipping integrity check")
|
||||
m.logger.Printf("[INFO] [backup] No active drives — skipping integrity check")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -598,7 +598,7 @@ func (m *Manager) RunIntegrityCheck(ctx context.Context) error {
|
||||
m.logger.Printf("[DEBUG] RunIntegrityCheck: checking repo %s", repoPath)
|
||||
}
|
||||
if err := m.restic.Check(repoPath); err != nil {
|
||||
m.logger.Printf("[ERROR] Restic check failed for %s: %v", repoPath, err)
|
||||
m.logger.Printf("[ERROR] [backup] Restic check failed for %s: %v", repoPath, err)
|
||||
checkErr = err
|
||||
} else if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] RunIntegrityCheck: repo %s OK", repoPath)
|
||||
@@ -614,12 +614,12 @@ func (m *Manager) RunIntegrityCheck(ctx context.Context) error {
|
||||
m.mu.Unlock()
|
||||
|
||||
if checkErr != nil {
|
||||
m.logger.Printf("[ERROR] Restic integrity check failed (%s): %v", duration.Round(time.Second), checkErr)
|
||||
m.logger.Printf("[ERROR] [backup] Restic integrity check failed (%s): %v", duration.Round(time.Second), checkErr)
|
||||
m.pinger.Fail(uuid, fmt.Sprintf("restic check failed: %v", checkErr))
|
||||
return checkErr
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Restic integrity check passed (%d repos, %s)", len(drives), duration.Round(time.Second))
|
||||
m.logger.Printf("[INFO] [backup] Restic integrity check passed (%d repos, %s)", len(drives), duration.Round(time.Second))
|
||||
m.pinger.Ping(uuid, fmt.Sprintf("restic check passed (%d repos, %s)", len(drives), duration.Round(time.Second)))
|
||||
return nil
|
||||
}
|
||||
@@ -646,7 +646,7 @@ func (m *Manager) RunFullBackup(ctx context.Context) error {
|
||||
m.logger.Printf("[DEBUG] RunFullBackup: phase 1 — database dumps")
|
||||
}
|
||||
if err := m.runDBDumpsInternal(ctx); err != nil {
|
||||
m.logger.Printf("[WARN] DB dump had errors, continuing with backup anyway")
|
||||
m.logger.Printf("[WARN] [backup] DB dump had errors, continuing with backup anyway")
|
||||
}
|
||||
|
||||
// Step 2: Restic backup
|
||||
@@ -713,7 +713,7 @@ func (m *Manager) ListSnapshots(limit int) ([]SnapshotInfo, error) {
|
||||
}
|
||||
snapshots, err := m.restic.ListSnapshots(repoPath, 0)
|
||||
if err != nil {
|
||||
m.logger.Printf("[WARN] Could not list snapshots from %s: %v", repoPath, err)
|
||||
m.logger.Printf("[WARN] [backup] Could not list snapshots from %s: %v", repoPath, err)
|
||||
continue
|
||||
}
|
||||
for i := range snapshots {
|
||||
@@ -744,7 +744,7 @@ func (m *Manager) ListAllSnapshots(limit int) ([]SnapshotInfo, error) {
|
||||
}
|
||||
snapshots, err := m.restic.ListSnapshots(repoPath, 0)
|
||||
if err != nil {
|
||||
m.logger.Printf("[WARN] Could not list snapshots from %s: %v", repoPath, err)
|
||||
m.logger.Printf("[WARN] [backup] Could not list snapshots from %s: %v", repoPath, err)
|
||||
continue
|
||||
}
|
||||
for i := range snapshots {
|
||||
@@ -782,7 +782,7 @@ func (m *Manager) UnlockRepo(ctx context.Context, repoPath string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("restic unlock: %v (%s)", err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
m.logger.Printf("[INFO] Restic repo unlocked: %s", repoPath)
|
||||
m.logger.Printf("[INFO] [backup] Restic repo unlocked: %s", repoPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -819,14 +819,14 @@ func (m *Manager) DumpStackDB(ctx context.Context, stackName string) error {
|
||||
}
|
||||
dumpDir := AppDBDumpPath(drivePath, stackName)
|
||||
|
||||
m.logger.Printf("[INFO] Running pre-backup DB dump for %s (%d database(s)) → %s", stackName, len(stackDBs), dumpDir)
|
||||
m.logger.Printf("[INFO] [backup] Running pre-backup DB dump for %s (%d database(s)) → %s", stackName, len(stackDBs), dumpDir)
|
||||
|
||||
for _, db := range stackDBs {
|
||||
result := DumpOne(ctx, db, dumpDir, m.logger, m.isDebug())
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("DB dump failed for %s: %w", result.DB.ContainerName, result.Error)
|
||||
}
|
||||
m.logger.Printf("[INFO] Pre-backup DB dump OK: %s (%s)", result.DB.ContainerName, humanizeBytes(result.Size))
|
||||
m.logger.Printf("[INFO] [backup] Pre-backup DB dump OK: %s (%s)", result.DB.ContainerName, humanizeBytes(result.Size))
|
||||
|
||||
// Persist validation to settings
|
||||
if m.settings != nil && result.FilePath != "" {
|
||||
@@ -973,16 +973,16 @@ func (m *Manager) saveSnapshotHistory() {
|
||||
}
|
||||
data, err := json.Marshal(m.snapshotHistory)
|
||||
if err != nil {
|
||||
m.logger.Printf("[WARN] Could not marshal snapshot history: %v", err)
|
||||
m.logger.Printf("[WARN] [backup] Could not marshal snapshot history: %v", err)
|
||||
return
|
||||
}
|
||||
tmp := m.snapshotHistoryFile + ".tmp"
|
||||
if err := os.WriteFile(tmp, data, 0644); err != nil {
|
||||
m.logger.Printf("[WARN] Could not write snapshot history tmp file: %v", err)
|
||||
m.logger.Printf("[WARN] [backup] Could not write snapshot history tmp file: %v", err)
|
||||
return
|
||||
}
|
||||
if err := os.Rename(tmp, m.snapshotHistoryFile); err != nil {
|
||||
m.logger.Printf("[WARN] Could not rename snapshot history file: %v", err)
|
||||
m.logger.Printf("[WARN] [backup] Could not rename snapshot history file: %v", err)
|
||||
return
|
||||
}
|
||||
m.logger.Printf("[INFO] [backup] Saved snapshot history (%d entries)", len(m.snapshotHistory))
|
||||
@@ -997,13 +997,13 @@ func (m *Manager) loadSnapshotHistoryFromFile() []SnapshotRecord {
|
||||
data, err := os.ReadFile(m.snapshotHistoryFile)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
m.logger.Printf("[WARN] Could not read snapshot history file: %v", err)
|
||||
m.logger.Printf("[WARN] [backup] Could not read snapshot history file: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var records []SnapshotRecord
|
||||
if err := json.Unmarshal(data, &records); err != nil {
|
||||
m.logger.Printf("[WARN] Could not parse snapshot history file: %v", err)
|
||||
m.logger.Printf("[WARN] [backup] Could not parse snapshot history file: %v", err)
|
||||
return nil
|
||||
}
|
||||
return records
|
||||
@@ -1033,7 +1033,7 @@ func (m *Manager) LoadSnapshotHistory() {
|
||||
}
|
||||
snapshots, err := m.restic.ListSnapshots(repoPath, 20)
|
||||
if err != nil {
|
||||
m.logger.Printf("[WARN] Could not load snapshot history from %s: %v", repoPath, err)
|
||||
m.logger.Printf("[WARN] [backup] Could not load snapshot history from %s: %v", repoPath, err)
|
||||
continue
|
||||
}
|
||||
allSnapshots = append(allSnapshots, snapshots...)
|
||||
@@ -1064,7 +1064,7 @@ func (m *Manager) LoadSnapshotHistory() {
|
||||
sort.Slice(m.snapshotHistory, func(i, j int) bool {
|
||||
return m.snapshotHistory[i].Time.Before(m.snapshotHistory[j].Time)
|
||||
})
|
||||
m.logger.Printf("[INFO] Loaded %d snapshots from persisted history (merged with %d restic entries)", len(persisted), len(allSnapshots))
|
||||
m.logger.Printf("[INFO] [backup] Loaded %d snapshots from persisted history (merged with %d restic entries)", len(persisted), len(allSnapshots))
|
||||
} else {
|
||||
// No persisted file — fall back to restic-only loading (first run)
|
||||
for _, s := range allSnapshots {
|
||||
@@ -1075,7 +1075,7 @@ func (m *Manager) LoadSnapshotHistory() {
|
||||
Success: true,
|
||||
})
|
||||
}
|
||||
m.logger.Printf("[INFO] Loaded %d historical snapshots from %d restic repos (no persisted history)", len(m.snapshotHistory), len(drives))
|
||||
m.logger.Printf("[INFO] [backup] Loaded %d historical snapshots from %d restic repos (no persisted history)", len(m.snapshotHistory), len(drives))
|
||||
}
|
||||
|
||||
if len(m.snapshotHistory) > 20 {
|
||||
@@ -1139,7 +1139,7 @@ func (m *Manager) RefreshCache(nextDBDump, nextBackup time.Time) {
|
||||
filename := filepath.Base(r.FilePath)
|
||||
if fv, ok := fileValidation[filename]; ok {
|
||||
m.lastDBDump.Results[i].Validation = fv
|
||||
m.logger.Printf("[INFO] Re-validated %s from disk: valid=%v tables=%d",
|
||||
m.logger.Printf("[INFO] [backup] Re-validated %s from disk: valid=%v tables=%d",
|
||||
filename, fv.Valid, fv.TableCount)
|
||||
}
|
||||
}
|
||||
@@ -1155,7 +1155,7 @@ func (m *Manager) RefreshCache(nextDBDump, nextBackup time.Time) {
|
||||
m.cacheTime = time.Now()
|
||||
m.mu.Unlock()
|
||||
|
||||
m.logger.Printf("[INFO] Backup status cache refreshed")
|
||||
m.logger.Printf("[INFO] [backup] Backup status cache refreshed")
|
||||
}
|
||||
|
||||
// GetFullStatus returns the cached backup status for page rendering.
|
||||
|
||||
@@ -117,7 +117,7 @@ func (r *CrossDriveRunner) RunAppBackup(ctx context.Context, stackName string) e
|
||||
})
|
||||
|
||||
start := time.Now()
|
||||
r.logger.Printf("[INFO] Cross-drive backup starting: %s → %s (rsync)",
|
||||
r.logger.Printf("[INFO] [backup] Cross-drive backup starting: %s → %s (rsync)",
|
||||
stackName, cfg.DestinationPath)
|
||||
|
||||
// Trigger fresh DB dump for this app before cross-drive backup
|
||||
@@ -126,7 +126,7 @@ func (r *CrossDriveRunner) RunAppBackup(ctx context.Context, stackName string) e
|
||||
r.logger.Printf("[DEBUG] RunAppBackup: triggering pre-backup DB dump for %s", stackName)
|
||||
}
|
||||
if err := r.dbDumper.DumpStackDB(ctx, stackName); err != nil {
|
||||
r.logger.Printf("[WARN] Pre-backup DB dump failed for %s: %v — proceeding with user data backup", stackName, err)
|
||||
r.logger.Printf("[WARN] [backup] Pre-backup DB dump failed for %s: %v — proceeding with user data backup", stackName, err)
|
||||
// Non-fatal: user data backup is still valuable without fresh dump
|
||||
}
|
||||
}
|
||||
@@ -156,7 +156,7 @@ func (r *CrossDriveRunner) RunAppBackup(ctx context.Context, stackName string) e
|
||||
duration := time.Since(start)
|
||||
|
||||
if runErr != nil {
|
||||
r.logger.Printf("[ERROR] Cross-drive backup failed: %s: %v", stackName, runErr)
|
||||
r.logger.Printf("[ERROR] [backup] Cross-drive backup failed: %s: %v", stackName, runErr)
|
||||
r.updateStatus(stackName, "error", runErr.Error(), duration, "")
|
||||
return runErr
|
||||
}
|
||||
@@ -171,7 +171,7 @@ func (r *CrossDriveRunner) RunAppBackup(ctx context.Context, stackName string) e
|
||||
}
|
||||
}
|
||||
|
||||
r.logger.Printf("[INFO] Cross-drive backup completed: %s (%s)", stackName, duration.Round(time.Second))
|
||||
r.logger.Printf("[INFO] [backup] Cross-drive backup completed: %s (%s)", stackName, duration.Round(time.Second))
|
||||
r.updateStatus(stackName, "ok", "", duration, sizeHuman)
|
||||
return nil
|
||||
}
|
||||
@@ -333,14 +333,14 @@ func (r *CrossDriveRunner) ValidateDestination(path string) error {
|
||||
r.logger.Printf("[DEBUG] ValidateDestination: path=%s, isMountPoint=%v", path, !onSystemDrive)
|
||||
}
|
||||
if onSystemDrive {
|
||||
r.logger.Printf("[WARN] Destination %s is not a separate mount point (system drive) — backup will proceed but data is not protected against drive failure", path)
|
||||
r.logger.Printf("[WARN] [backup] Destination %s is not a separate mount point (system drive) — backup will proceed but data is not protected against drive failure", path)
|
||||
}
|
||||
if !system.IsWritable(path) {
|
||||
return fmt.Errorf("destination %s is not writable", path)
|
||||
}
|
||||
di := system.GetDiskUsage(path)
|
||||
if di == nil {
|
||||
r.logger.Printf("[WARN] Cannot determine disk usage for %s — proceeding without space verification", path)
|
||||
r.logger.Printf("[WARN] [backup] Cannot determine disk usage for %s — proceeding without space verification", path)
|
||||
return nil
|
||||
}
|
||||
if r.debug {
|
||||
@@ -417,7 +417,9 @@ func (r *CrossDriveRunner) runRsyncBackup(ctx context.Context, stackName, destBa
|
||||
"--exclude", "backups/*.sql",
|
||||
"--exclude", "backups/*.dump",
|
||||
src, dst)
|
||||
r.logger.Printf("[DEBUG] rsync: %s → %s", src, dst)
|
||||
if r.debug {
|
||||
r.logger.Printf("[DEBUG] rsync: %s → %s", src, dst)
|
||||
}
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if r.debug {
|
||||
@@ -437,7 +439,7 @@ func (r *CrossDriveRunner) runRsyncBackup(ctx context.Context, stackName, destBa
|
||||
return fmt.Errorf("creating DB dump dest dir: %w", err)
|
||||
}
|
||||
if err := r.copyStackDBDumps(stackName, dbDestDir); err != nil {
|
||||
r.logger.Printf("[WARN] Cross-drive DB dump copy failed for %s: %v", stackName, err)
|
||||
r.logger.Printf("[WARN] [backup] Cross-drive DB dump copy failed for %s: %v", stackName, err)
|
||||
// Non-fatal: user data is the primary concern
|
||||
}
|
||||
|
||||
@@ -451,9 +453,11 @@ func (r *CrossDriveRunner) runRsyncBackup(ctx context.Context, stackName, destBa
|
||||
src := strings.TrimRight(configSrcDir, "/") + "/"
|
||||
dst := strings.TrimRight(configDestDir, "/") + "/"
|
||||
cmd := exec.CommandContext(ctx, "rsync", "-a", "--delete", src, dst)
|
||||
r.logger.Printf("[DEBUG] rsync config: %s → %s", src, dst)
|
||||
if r.debug {
|
||||
r.logger.Printf("[DEBUG] rsync config: %s → %s", src, dst)
|
||||
}
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
r.logger.Printf("[WARN] Cross-drive config rsync failed for %s: %v (%s)", stackName, err, strings.TrimSpace(string(out)))
|
||||
r.logger.Printf("[WARN] [backup] Cross-drive config rsync failed for %s: %v (%s)", stackName, err, strings.TrimSpace(string(out)))
|
||||
// Non-fatal
|
||||
}
|
||||
}
|
||||
@@ -488,9 +492,6 @@ func (r *CrossDriveRunner) copyStackDBDumps(stackName, destDir string) error {
|
||||
}
|
||||
copied++
|
||||
}
|
||||
if copied > 0 {
|
||||
r.logger.Printf("[DEBUG] Copied %d DB dump file(s) to %s", copied, destDir)
|
||||
}
|
||||
r.logger.Printf("[INFO] [backup] Copied %d DB dumps for %s", copied, stackName)
|
||||
return nil
|
||||
}
|
||||
@@ -514,7 +515,7 @@ func (r *CrossDriveRunner) syncInfraConfig(ctx context.Context) {
|
||||
for dest := range destDrives {
|
||||
infraDir := SecondaryInfraPath(dest)
|
||||
if err := os.MkdirAll(infraDir, 0755); err != nil {
|
||||
r.logger.Printf("[WARN] Cannot create infra backup dir %s: %v", infraDir, err)
|
||||
r.logger.Printf("[WARN] [backup] Cannot create infra backup dir %s: %v", infraDir, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -524,7 +525,7 @@ func (r *CrossDriveRunner) syncInfraConfig(ctx context.Context) {
|
||||
stacksSrc := strings.TrimRight(r.stacksDir, "/") + "/"
|
||||
cmd := exec.CommandContext(ctx, "rsync", "-a", "--delete", stacksSrc, stacksDest)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
r.logger.Printf("[WARN] Infra rsync (stacks) failed for %s: %v (%s)", dest, err, strings.TrimSpace(string(out)))
|
||||
r.logger.Printf("[WARN] [backup] Infra rsync (stacks) failed for %s: %v (%s)", dest, err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,11 +533,11 @@ func (r *CrossDriveRunner) syncInfraConfig(ctx context.Context) {
|
||||
if _, err := os.Stat(r.controllerYAMLPath); err == nil {
|
||||
yamlDest := filepath.Join(infraDir, "controller.yaml")
|
||||
if err := copyFile(r.controllerYAMLPath, yamlDest); err != nil {
|
||||
r.logger.Printf("[WARN] Cannot copy controller.yaml to %s: %v", yamlDest, err)
|
||||
r.logger.Printf("[WARN] [backup] Cannot copy controller.yaml to %s: %v", yamlDest, err)
|
||||
}
|
||||
}
|
||||
|
||||
r.logger.Printf("[INFO] Infrastructure config synced to %s", infraDir)
|
||||
r.logger.Printf("[INFO] [backup] Infrastructure config synced to %s", infraDir)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,11 +605,11 @@ func (r *CrossDriveRunner) AutoEnableSmallApps() {
|
||||
Schedule: "daily",
|
||||
}
|
||||
if err := r.sett.SetCrossDriveConfig(stack.Name, cfg); err != nil {
|
||||
r.logger.Printf("[WARN] Auto-enable Tier 2 failed for %s: %v", stack.Name, err)
|
||||
r.logger.Printf("[WARN] [backup] Auto-enable Tier 2 failed for %s: %v", stack.Name, err)
|
||||
continue
|
||||
}
|
||||
autoEnabled++
|
||||
r.logger.Printf("[INFO] Auto-enabled Tier 2 backup for %s → %s (no HDD mounts, daily rsync)", stack.Name, destPath)
|
||||
r.logger.Printf("[INFO] [backup] Auto-enabled Tier 2 backup for %s → %s (no HDD mounts, daily rsync)", stack.Name, destPath)
|
||||
}
|
||||
|
||||
if r.debug && autoEnabled > 0 {
|
||||
|
||||
@@ -117,7 +117,7 @@ func DiscoverDatabases(ctx context.Context, logger *log.Logger, debug bool) ([]D
|
||||
|
||||
// Get env vars from container
|
||||
if err := populateDBEnv(ctx, &db); err != nil {
|
||||
logger.Printf("[WARN] Could not read env vars for %s: %v", name, err)
|
||||
logger.Printf("[WARN] [backup] Could not read env vars for %s: %v", name, err)
|
||||
if debug {
|
||||
logger.Printf("[DEBUG] DiscoverDatabases: skipping %s — env read failed", name)
|
||||
}
|
||||
@@ -310,7 +310,7 @@ func DumpOne(ctx context.Context, db DiscoveredDB, dumpDir string, logger *log.L
|
||||
result.Validation.Valid, result.Validation.TableCount, result.Duration.Round(time.Millisecond))
|
||||
}
|
||||
|
||||
logger.Printf("[INFO] DB dump: %s → %s (%s, %s, %d tables)", db.ContainerName, filename,
|
||||
logger.Printf("[INFO] [backup] DB dump: %s → %s (%s, %s, %d tables)", db.ContainerName, filename,
|
||||
humanizeBytes(stat.Size()), result.Duration.Round(time.Millisecond), result.Validation.TableCount)
|
||||
|
||||
return result
|
||||
@@ -318,7 +318,6 @@ func DumpOne(ctx context.Context, db DiscoveredDB, dumpDir string, logger *log.L
|
||||
|
||||
// ValidateDump checks a SQL dump file for basic structural integrity.
|
||||
func ValidateDump(filePath string, dbType DBType) DumpValidation {
|
||||
log.Printf("[DEBUG] ValidateDump: %s (type=%s)", filePath, dbType)
|
||||
stat, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
return DumpValidation{Error: fmt.Sprintf("stat failed: %v", err)}
|
||||
@@ -331,7 +330,7 @@ func ValidateDump(filePath string, dbType DBType) DumpValidation {
|
||||
|
||||
if stat.Size() < 100 {
|
||||
v.Error = "dump file too small (< 100 bytes)"
|
||||
log.Printf("[WARN] ValidateDump FAIL: %s — %s", filePath, v.Error)
|
||||
log.Printf("[WARN] [backup] ValidateDump FAIL: %s — %s", filePath, v.Error)
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -340,7 +339,7 @@ func ValidateDump(filePath string, dbType DBType) DumpValidation {
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
v.Error = fmt.Sprintf("read failed: %v", err)
|
||||
log.Printf("[WARN] ValidateDump FAIL: %s — %s", filePath, v.Error)
|
||||
log.Printf("[WARN] [backup] ValidateDump FAIL: %s — %s", filePath, v.Error)
|
||||
return v
|
||||
}
|
||||
defer f.Close()
|
||||
@@ -359,7 +358,7 @@ func ValidateDump(filePath string, dbType DBType) DumpValidation {
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
v.Error = fmt.Sprintf("hiba az olvasás közben: %v", err)
|
||||
log.Printf("[WARN] ValidateDump FAIL: %s — read error: %v", filePath, err)
|
||||
log.Printf("[WARN] [backup] ValidateDump FAIL: %s — read error: %v", filePath, err)
|
||||
return v
|
||||
}
|
||||
break // EOF
|
||||
@@ -408,18 +407,17 @@ func ValidateDump(filePath string, dbType DBType) DumpValidation {
|
||||
case DBTypePostgres:
|
||||
v.Error = "PostgreSQL dump missing comment header"
|
||||
}
|
||||
log.Printf("[WARN] ValidateDump FAIL: %s — %s", filePath, v.Error)
|
||||
log.Printf("[WARN] [backup] ValidateDump FAIL: %s — %s", filePath, v.Error)
|
||||
return v
|
||||
}
|
||||
|
||||
if tableCount == 0 {
|
||||
v.Error = "no CREATE TABLE statements found"
|
||||
log.Printf("[WARN] ValidateDump FAIL: %s — %s (header was found, scanned %d lines)", filePath, v.Error, lineNum)
|
||||
log.Printf("[WARN] [backup] ValidateDump FAIL: %s — %s (header was found, scanned %d lines)", filePath, v.Error, lineNum)
|
||||
return v
|
||||
}
|
||||
|
||||
v.Valid = true
|
||||
log.Printf("[DEBUG] ValidateDump OK: %s — %d tables, header found", filePath, tableCount)
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -570,7 +568,7 @@ func cleanupTmpFiles(dumpDir string, logger *log.Logger) {
|
||||
if info.ModTime().Before(cutoff) {
|
||||
path := filepath.Join(dumpDir, e.Name())
|
||||
os.Remove(path)
|
||||
logger.Printf("[INFO] Cleaned up stale tmp file: %s", e.Name())
|
||||
logger.Printf("[INFO] [backup] Cleaned up stale tmp file: %s", e.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ func (r *ResticManager) EnsureInitialized(repoPath string) error {
|
||||
// Check if repo is already initialized
|
||||
configPath := filepath.Join(repoPath, "config")
|
||||
if _, err := os.Stat(configPath); err == nil {
|
||||
r.logger.Printf("[INFO] Restic repo already initialized at %s", repoPath)
|
||||
r.logger.Printf("[INFO] [backup] Restic repo already initialized at %s", repoPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ func (r *ResticManager) EnsureInitialized(repoPath string) error {
|
||||
}
|
||||
|
||||
// Initialize repo
|
||||
r.logger.Printf("[INFO] Initializing restic repository at %s", repoPath)
|
||||
r.logger.Printf("[INFO] [backup] Initializing restic repository at %s", repoPath)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
@@ -108,7 +108,7 @@ func (r *ResticManager) EnsureInitialized(repoPath string) error {
|
||||
return fmt.Errorf("restic init failed: %v — %s", err, truncate(string(out), 200))
|
||||
}
|
||||
|
||||
r.logger.Printf("[INFO] Restic repository initialized successfully")
|
||||
r.logger.Printf("[INFO] [backup] Restic repository initialized successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ func (r *ResticManager) Snapshot(repoPath string, paths []string, tags []string)
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
existingPaths = append(existingPaths, p)
|
||||
} else {
|
||||
r.logger.Printf("[WARN] Backup path does not exist, skipping: %s", p)
|
||||
r.logger.Printf("[WARN] [backup] Backup path does not exist, skipping: %s", p)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,10 +155,10 @@ func (r *ResticManager) Snapshot(repoPath string, paths []string, tags []string)
|
||||
errStr += string(exitErr.Stderr)
|
||||
}
|
||||
if strings.Contains(errStr, "lock") || strings.Contains(errStr, "locked") {
|
||||
r.logger.Printf("[WARN] Restic repo locked — attempting unlock")
|
||||
r.logger.Printf("[WARN] [backup] Restic repo locked — attempting unlock")
|
||||
unlockCmd := r.command(ctx, repoPath, "unlock")
|
||||
if unlockErr := unlockCmd.Run(); unlockErr != nil {
|
||||
r.logger.Printf("[WARN] Restic unlock failed: %v", unlockErr)
|
||||
r.logger.Printf("[WARN] [backup] Restic unlock failed: %v", unlockErr)
|
||||
}
|
||||
// Retry once with a fresh context (H9 fix — original may be nearly expired).
|
||||
retryCtx, retryCancel := context.WithTimeout(context.Background(), 30*time.Minute)
|
||||
@@ -239,7 +239,7 @@ func (r *ResticManager) Prune(repoPath string, retention config.RetentionConfig)
|
||||
if r.debug {
|
||||
r.logger.Printf("[DEBUG] [restic] Prune: completed in %s, output=%d bytes", time.Since(start), len(out))
|
||||
}
|
||||
r.logger.Printf("[INFO] Restic prune completed for %s", repoPath)
|
||||
r.logger.Printf("[INFO] [backup] Restic prune completed for %s", repoPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -420,19 +420,19 @@ func (r *ResticManager) RestoreAppData(repoPath string, snapshotID string, paths
|
||||
}
|
||||
start := time.Now()
|
||||
|
||||
r.logger.Printf("[WARN] RESTORE started: repo=%s, snapshot=%s, paths=%v", repoPath, snapshotID, paths)
|
||||
r.logger.Printf("[INFO] [backup] Restore started: repo=%s, snapshot=%s, paths=%v", repoPath, snapshotID, paths)
|
||||
|
||||
cmd := r.command(ctx, repoPath, args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
r.logger.Printf("[ERROR] Restore failed: %v, output: %s", err, truncate(string(output), 500))
|
||||
r.logger.Printf("[ERROR] [backup] Restore failed: %v, output: %s", err, truncate(string(output), 500))
|
||||
return fmt.Errorf("restic restore failed: %w", err)
|
||||
}
|
||||
|
||||
if r.debug {
|
||||
r.logger.Printf("[DEBUG] [restic] RestoreAppData: completed in %s, output=%d bytes", time.Since(start), len(output))
|
||||
}
|
||||
r.logger.Printf("[INFO] RESTORE completed: snapshot=%s, paths=%v", snapshotID, paths)
|
||||
r.logger.Printf("[INFO] [backup] Restore completed: snapshot=%s, paths=%v", snapshotID, paths)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -483,8 +483,8 @@ func (r *ResticManager) generatePassword() error {
|
||||
return fmt.Errorf("writing password file: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Printf("[INFO] Generated new restic repository password at %s", r.passwordFile)
|
||||
r.logger.Printf("[WARN] Save this password externally — losing it means losing access to ALL backups")
|
||||
r.logger.Printf("[INFO] [backup] Generated new restic repository password at %s", r.passwordFile)
|
||||
r.logger.Printf("[WARN] [backup] Save this password externally — losing it means losing access to ALL backups")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -64,10 +64,10 @@ func (c *Client) do(ctx context.Context, method, path string, body interface{})
|
||||
}
|
||||
bodyReader = bytes.NewReader(data)
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] %s %s body=%s", method, path, string(data))
|
||||
c.logger.Printf("[DEBUG] [cloudflare] %s %s body=%s", method, path, string(data))
|
||||
}
|
||||
} else if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] %s %s", method, path)
|
||||
c.logger.Printf("[DEBUG] [cloudflare] %s %s", method, path)
|
||||
}
|
||||
|
||||
url := apiBase + path
|
||||
@@ -91,7 +91,7 @@ func (c *Client) do(ctx context.Context, method, path string, body interface{})
|
||||
}
|
||||
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] Response %d: %s", resp.StatusCode, string(respBody))
|
||||
c.logger.Printf("[DEBUG] [cloudflare] Response %d: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
var apiResp apiResponse
|
||||
|
||||
@@ -73,20 +73,20 @@ func (c *Client) GetCustomRulesetID(ctx context.Context, zoneID string) (string,
|
||||
}
|
||||
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] GetCustomRulesetID: found %d rulesets for zone %s", len(rulesets), zoneID)
|
||||
c.logger.Printf("[DEBUG] [cloudflare] GetCustomRulesetID: found %d rulesets for zone %s", len(rulesets), zoneID)
|
||||
}
|
||||
|
||||
for _, rs := range rulesets {
|
||||
if rs.Phase == wafPhase {
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] GetCustomRulesetID: matched ruleset %s (phase=%s)", rs.ID, wafPhase)
|
||||
c.logger.Printf("[DEBUG] [cloudflare] GetCustomRulesetID: matched ruleset %s (phase=%s)", rs.ID, wafPhase)
|
||||
}
|
||||
return rs.ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] GetCustomRulesetID: no ruleset with phase %s found", wafPhase)
|
||||
c.logger.Printf("[DEBUG] [cloudflare] GetCustomRulesetID: no ruleset with phase %s found", wafPhase)
|
||||
}
|
||||
|
||||
return "", nil
|
||||
@@ -112,7 +112,7 @@ func (c *Client) CreateCustomRuleset(ctx context.Context, zoneID string) (string
|
||||
return "", fmt.Errorf("decode created ruleset: %w", err)
|
||||
}
|
||||
|
||||
c.logger.Printf("[CF] Created custom ruleset %s for zone %s", rs.ID, zoneID)
|
||||
c.logger.Printf("[INFO] [cloudflare] Created custom ruleset %s for zone %s", rs.ID, zoneID)
|
||||
return rs.ID, nil
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ func (c *Client) GetRules(ctx context.Context, zoneID, rulesetID string) ([]rule
|
||||
}
|
||||
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] GetRules: %d total rules in ruleset %s", len(rs.Rules), rulesetID)
|
||||
c.logger.Printf("[DEBUG] [cloudflare] GetRules: %d total rules in ruleset %s", len(rs.Rules), rulesetID)
|
||||
}
|
||||
|
||||
return rs.Rules, nil
|
||||
@@ -159,7 +159,7 @@ func (c *Client) GetFelhomRules(ctx context.Context, zoneID, rulesetID string) (
|
||||
}
|
||||
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] GetFelhomRules: %d felhom rules out of %d total", len(result), len(rules))
|
||||
c.logger.Printf("[DEBUG] [cloudflare] GetFelhomRules: %d felhom rules out of %d total", len(result), len(rules))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@@ -185,13 +185,13 @@ func (c *Client) CreateRule(ctx context.Context, zoneID, rulesetID string, r rul
|
||||
|
||||
for _, created := range rs.Rules {
|
||||
if created.Description == r.Description {
|
||||
c.logger.Printf("[CF] Created rule %q → %s", r.Description, created.ID)
|
||||
c.logger.Printf("[INFO] [cloudflare] Created rule %q → %s", r.Description, created.ID)
|
||||
if c.debug {
|
||||
expr := r.Expression
|
||||
if len(expr) > 120 {
|
||||
expr = expr[:120] + "..."
|
||||
}
|
||||
c.logger.Printf("[CF-DEBUG] CreateRule: expression: %s", expr)
|
||||
c.logger.Printf("[DEBUG] [cloudflare] CreateRule: expression: %s", expr)
|
||||
}
|
||||
return created.ID, nil
|
||||
}
|
||||
@@ -209,13 +209,13 @@ func (c *Client) UpdateRule(ctx context.Context, zoneID, rulesetID, ruleID strin
|
||||
c.logger.Printf("[ERROR] [cloudflare] Failed to update WAF rule %s: %v", ruleID, err)
|
||||
return fmt.Errorf("update rule %s: %w", ruleID, err)
|
||||
}
|
||||
c.logger.Printf("[CF] Updated rule %q (%s)", r.Description, ruleID)
|
||||
c.logger.Printf("[INFO] [cloudflare] Updated rule %q (%s)", r.Description, ruleID)
|
||||
if c.debug {
|
||||
expr := r.Expression
|
||||
if len(expr) > 120 {
|
||||
expr = expr[:120] + "..."
|
||||
}
|
||||
c.logger.Printf("[CF-DEBUG] UpdateRule: expression: %s", expr)
|
||||
c.logger.Printf("[DEBUG] [cloudflare] UpdateRule: expression: %s", expr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -229,7 +229,7 @@ func (c *Client) DeleteRule(ctx context.Context, zoneID, rulesetID, ruleID strin
|
||||
c.logger.Printf("[ERROR] [cloudflare] Failed to delete WAF rule %s: %v", ruleID, err)
|
||||
return fmt.Errorf("delete rule %s: %w", ruleID, err)
|
||||
}
|
||||
c.logger.Printf("[CF] Deleted rule %s", ruleID)
|
||||
c.logger.Printf("[INFO] [cloudflare] Deleted rule %s", ruleID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,12 @@ type zone struct {
|
||||
// It tries the exact domain first, then strips subdomains progressively.
|
||||
func (c *Client) GetZoneID(ctx context.Context, domain string) (string, error) {
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] GetZoneID: looking up zone for domain %q", domain)
|
||||
c.logger.Printf("[DEBUG] [cloudflare] GetZoneID: looking up zone for domain %q", domain)
|
||||
}
|
||||
|
||||
// Try exact domain first (e.g., "demo-felhom.eu")
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] GetZoneID: trying exact domain %q", domain)
|
||||
c.logger.Printf("[DEBUG] [cloudflare] GetZoneID: trying exact domain %q", domain)
|
||||
}
|
||||
id, err := c.lookupZone(ctx, domain)
|
||||
if err != nil {
|
||||
@@ -40,7 +40,7 @@ func (c *Client) GetZoneID(ctx context.Context, domain string) (string, error) {
|
||||
break
|
||||
}
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] GetZoneID: trying parent domain %q", parent)
|
||||
c.logger.Printf("[DEBUG] [cloudflare] GetZoneID: trying parent domain %q", parent)
|
||||
}
|
||||
id, err = c.lookupZone(ctx, parent)
|
||||
if err != nil {
|
||||
@@ -73,6 +73,6 @@ func (c *Client) lookupZone(ctx context.Context, name string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
c.logger.Printf("[CF] Resolved zone %q → %s", name, zones[0].ID)
|
||||
c.logger.Printf("[INFO] [cloudflare] Resolved zone %q → %s", name, zones[0].ID)
|
||||
return zones[0].ID, nil
|
||||
}
|
||||
|
||||
@@ -142,11 +142,11 @@ func (m *Manager) Toggle(ctx context.Context, provider, target string, enable bo
|
||||
state.Enabled = true
|
||||
state.Status = "active"
|
||||
state.EnabledAt = time.Now().UTC().Format(time.RFC3339)
|
||||
m.logger.Printf("[INFO] Integration %s enabled", key)
|
||||
m.logger.Printf("[INFO] [integrations] Integration %s enabled", key)
|
||||
} else {
|
||||
start := time.Now()
|
||||
if err := handler.Revoke(ac); err != nil {
|
||||
m.logger.Printf("[WARN] Integration revoke failed for %s: %v", key, err)
|
||||
m.logger.Printf("[WARN] [integrations] Integration revoke failed for %s: %v", key, err)
|
||||
state.LastError = err.Error()
|
||||
}
|
||||
if m.isDebug() {
|
||||
@@ -154,7 +154,7 @@ func (m *Manager) Toggle(ctx context.Context, provider, target string, enable bo
|
||||
}
|
||||
state.Enabled = false
|
||||
state.Status = "disabled"
|
||||
m.logger.Printf("[INFO] Integration %s disabled", key)
|
||||
m.logger.Printf("[INFO] [integrations] Integration %s disabled", key)
|
||||
}
|
||||
|
||||
if err := m.sett.SetIntegrationState(key, state); err != nil {
|
||||
@@ -268,7 +268,7 @@ func (m *Manager) ReapplyConfigForTarget(targetName string) {
|
||||
|
||||
ac, err := m.buildApplyContext(provider, target)
|
||||
if err != nil {
|
||||
m.logger.Printf("[WARN] Cannot build context for integration %s reapply: %v", key, err)
|
||||
m.logger.Printf("[WARN] [integrations] Cannot build context for integration %s reapply: %v", key, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ func (m *Manager) ReapplyConfigForTarget(targetName string) {
|
||||
ac.RestartStack = func(string) error { return nil }
|
||||
|
||||
if err := handler.Apply(ac); err != nil {
|
||||
m.logger.Printf("[WARN] Integration config reapply failed for %s: %v", key, err)
|
||||
m.logger.Printf("[WARN] [integrations] Integration config reapply failed for %s: %v", key, err)
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [integrations] ReapplyConfigForTarget: %s failed: %v", key, err)
|
||||
}
|
||||
@@ -292,7 +292,7 @@ func (m *Manager) ReapplyConfigForTarget(targetName string) {
|
||||
state.Status = "active"
|
||||
state.LastError = ""
|
||||
_ = m.sett.SetIntegrationState(key, state)
|
||||
m.logger.Printf("[INFO] Integration %s config reapplied for %s", key, targetName)
|
||||
m.logger.Printf("[INFO] [integrations] Integration %s config reapplied for %s", key, targetName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,6 @@ func (h *OnlyOfficeFileBrowserHandler) Apply(ac *ApplyContext) error {
|
||||
configPath := filepath.Join(ac.StacksDir, "filebrowser", "config.yaml")
|
||||
officeURL := fmt.Sprintf("https://%s.%s", subdomain, ac.Domain)
|
||||
|
||||
ac.Logger.Printf("[DEBUG] [integrations] OnlyOfficeFileBrowser.Apply: jwtSecretPresent=%v subdomain=%s configPath=%s officeURL=%s", jwtSecret != "", subdomain, configPath, officeURL)
|
||||
|
||||
configData, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-FileBrowser apply: %v", err)
|
||||
@@ -59,7 +57,7 @@ func (h *OnlyOfficeFileBrowserHandler) Apply(ac *ApplyContext) error {
|
||||
return fmt.Errorf("config átnevezési hiba: %w", err)
|
||||
}
|
||||
|
||||
ac.Logger.Printf("[INFO] FileBrowser config updated with OnlyOffice integration")
|
||||
ac.Logger.Printf("[INFO] [integrations] FileBrowser config updated with OnlyOffice integration")
|
||||
return ac.RestartStack("filebrowser")
|
||||
}
|
||||
|
||||
@@ -94,7 +92,7 @@ func (h *OnlyOfficeFileBrowserHandler) Revoke(ac *ApplyContext) error {
|
||||
return fmt.Errorf("config átnevezési hiba: %w", err)
|
||||
}
|
||||
|
||||
ac.Logger.Printf("[INFO] FileBrowser config cleaned — OnlyOffice integration removed")
|
||||
ac.Logger.Printf("[INFO] [integrations] FileBrowser config cleaned — OnlyOffice integration removed")
|
||||
return ac.RestartStack("filebrowser")
|
||||
}
|
||||
|
||||
|
||||
@@ -71,16 +71,16 @@ func (h *OnlyOfficeNextcloudHandler) Apply(ac *ApplyContext) error {
|
||||
cancel()
|
||||
if err != nil {
|
||||
if cmd.tolerate != "" && strings.Contains(string(out), cmd.tolerate) {
|
||||
ac.Logger.Printf("[DEBUG] Nextcloud occ: tolerated — %s", strings.TrimSpace(string(out)))
|
||||
ac.Logger.Printf("[DEBUG] [integrations] Nextcloud occ: tolerated — %s", strings.TrimSpace(string(out)))
|
||||
continue
|
||||
}
|
||||
ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-Nextcloud apply: occ %s failed: %v", cmd.args[len(cmd.args)-1], err)
|
||||
return fmt.Errorf("occ parancs sikertelen (%s): %v (kimenet: %s)", cmd.args[len(cmd.args)-1], err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
ac.Logger.Printf("[DEBUG] Nextcloud occ %s: ok", strings.Join(cmd.args[7:], " "))
|
||||
ac.Logger.Printf("[DEBUG] [integrations] Nextcloud occ %s: ok", strings.Join(cmd.args[7:], " "))
|
||||
}
|
||||
|
||||
ac.Logger.Printf("[INFO] OnlyOffice integration applied to Nextcloud")
|
||||
ac.Logger.Printf("[INFO] [integrations] OnlyOffice integration applied to Nextcloud")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -96,13 +96,13 @@ func (h *OnlyOfficeNextcloudHandler) Revoke(ac *ApplyContext) error {
|
||||
if strings.Contains(err.Error(), "No such container") ||
|
||||
strings.Contains(outStr, "not enabled") ||
|
||||
strings.Contains(outStr, "not installed") {
|
||||
ac.Logger.Printf("[DEBUG] Nextcloud occ app:disable skipped — %s", strings.TrimSpace(outStr))
|
||||
ac.Logger.Printf("[DEBUG] [integrations] Nextcloud occ app:disable skipped — %s", strings.TrimSpace(outStr))
|
||||
return nil
|
||||
}
|
||||
ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-Nextcloud revoke: occ app:disable failed: %v", err)
|
||||
return fmt.Errorf("occ app:disable sikertelen: %v (kimenet: %s)", err, strings.TrimSpace(outStr))
|
||||
}
|
||||
|
||||
ac.Logger.Printf("[INFO] OnlyOffice integration revoked from Nextcloud")
|
||||
ac.Logger.Printf("[INFO] [integrations] OnlyOffice integration revoked from Nextcloud")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -69,12 +69,12 @@ func (c *MetricsCollector) loop(ctx context.Context) {
|
||||
func (c *MetricsCollector) sampleWith(ctx context.Context) {
|
||||
sys := c.sampleSystem()
|
||||
if err := c.store.InsertSystemMetrics(sys); err != nil {
|
||||
c.logger.Printf("[WARN] Failed to store system metrics: %v", err)
|
||||
c.logger.Printf("[WARN] [metrics] Failed to store system metrics: %v", err)
|
||||
}
|
||||
|
||||
containers := c.sampleContainers(ctx)
|
||||
if err := c.store.InsertContainerMetrics(containers); err != nil {
|
||||
c.logger.Printf("[WARN] Failed to store container metrics: %v", err)
|
||||
c.logger.Printf("[WARN] [metrics] Failed to store container metrics: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func (c *MetricsCollector) sampleContainers(parentCtx context.Context) []Contain
|
||||
"--format", "{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
c.logger.Printf("[WARN] docker stats failed: %v", err)
|
||||
c.logger.Printf("[WARN] [metrics] docker stats failed: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ func ScanContainerLogs(containerNames []string, since time.Duration, logger *log
|
||||
elapsed := time.Since(start)
|
||||
dbg("log scan completed: %d containers in %s", len(containerNames), elapsed.Round(time.Millisecond))
|
||||
if elapsed > 5*time.Minute && logger != nil {
|
||||
logger.Printf("[WARN] Log scan took %s (>5min) for %d containers", elapsed.Round(time.Second), len(containerNames))
|
||||
logger.Printf("[WARN] [metrics] Log scan took %s (>5min) for %d containers", elapsed.Round(time.Second), len(containerNames))
|
||||
}
|
||||
|
||||
return results
|
||||
@@ -92,7 +92,7 @@ func scanOneContainer(name string, since time.Duration, logger *log.Logger) Cont
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if logger != nil {
|
||||
logger.Printf("[DEBUG] logscanner: docker logs %s: %v", name, err)
|
||||
logger.Printf("[DEBUG] [metrics] logscanner: docker logs %s: %v", name, err)
|
||||
}
|
||||
return summary
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func (s *MetricsStore) GetContainerTelemetry(since time.Time) ([]ContainerTeleme
|
||||
var ct ContainerTelemetry
|
||||
if err := rows.Scan(&ct.ContainerName, &ct.MemoryAvgMB, &ct.MemoryPeakMB,
|
||||
&ct.CPUAvgPercent, &ct.SampleCount); err != nil {
|
||||
log.Printf("[WARN] telemetry row scan failed: %v", err)
|
||||
log.Printf("[WARN] [metrics] telemetry row scan failed: %v", err)
|
||||
continue
|
||||
}
|
||||
results = append(results, ct)
|
||||
|
||||
@@ -38,7 +38,7 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
|
||||
sysInfo := system.GetInfo(hddPath, cpuCollector)
|
||||
|
||||
if debug {
|
||||
logger.Printf("[DEBUG] [HEALTH] Raw values: disk=%.1f%%, hdd=%.1f%% (configured=%v), mem=%.1f%% (%dMB/%dMB), cpu=%.1f%%, temp=%.1f°C (%s)",
|
||||
logger.Printf("[DEBUG] [monitor] Raw values: disk=%.1f%%, hdd=%.1f%% (configured=%v), mem=%.1f%% (%dMB/%dMB), cpu=%.1f%%, temp=%.1f°C (%s)",
|
||||
sysInfo.DiskPercent, sysInfo.HDDPercent, sysInfo.HDDConfigured,
|
||||
sysInfo.MemPercent, sysInfo.UsedMemMB, sysInfo.TotalMemMB,
|
||||
sysInfo.CPUPercent, sysInfo.TemperatureCelsius, sysInfo.TemperatureSource)
|
||||
@@ -52,7 +52,7 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
|
||||
logger.Printf("[WARN] [monitor] Disk (SSD) threshold breached: %.0f%% (limit: %d%%)", sysInfo.DiskPercent, cfg.Monitoring.Thresholds.DiskCritPercent)
|
||||
}
|
||||
if debug {
|
||||
logger.Printf("[DEBUG] [HEALTH] SSD disk: CRITICAL (%.0f%% >= %d%%)", sysInfo.DiskPercent, cfg.Monitoring.Thresholds.DiskCritPercent)
|
||||
logger.Printf("[DEBUG] [monitor] SSD disk: CRITICAL (%.0f%% >= %d%%)", sysInfo.DiskPercent, cfg.Monitoring.Thresholds.DiskCritPercent)
|
||||
}
|
||||
} else if sysInfo.DiskPercent >= float64(cfg.Monitoring.Thresholds.DiskWarnPercent) {
|
||||
report.Warnings = append(report.Warnings, fmt.Sprintf("SSD disk usage high: %.0f%%", sysInfo.DiskPercent))
|
||||
@@ -60,12 +60,12 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
|
||||
logger.Printf("[WARN] [monitor] Disk (SSD) threshold breached: %.0f%% (limit: %d%%)", sysInfo.DiskPercent, cfg.Monitoring.Thresholds.DiskWarnPercent)
|
||||
}
|
||||
if debug {
|
||||
logger.Printf("[DEBUG] [HEALTH] SSD disk: WARN (%.0f%% >= %d%%)", sysInfo.DiskPercent, cfg.Monitoring.Thresholds.DiskWarnPercent)
|
||||
logger.Printf("[DEBUG] [monitor] SSD disk: WARN (%.0f%% >= %d%%)", sysInfo.DiskPercent, cfg.Monitoring.Thresholds.DiskWarnPercent)
|
||||
}
|
||||
} else {
|
||||
report.Info = append(report.Info, fmt.Sprintf("SSD: %.0f%% used", sysInfo.DiskPercent))
|
||||
if debug {
|
||||
logger.Printf("[DEBUG] [HEALTH] SSD disk: OK (%.0f%%)", sysInfo.DiskPercent)
|
||||
logger.Printf("[DEBUG] [monitor] SSD disk: OK (%.0f%%)", sysInfo.DiskPercent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,12 +93,12 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
|
||||
logger.Printf("[WARN] [monitor] Memory threshold breached: %.0f%% (limit: %d%%)", sysInfo.MemPercent, cfg.Monitoring.Thresholds.MemoryWarnPercent)
|
||||
}
|
||||
if debug {
|
||||
logger.Printf("[DEBUG] [HEALTH] Memory: WARN (%.0f%% >= %d%%)", sysInfo.MemPercent, cfg.Monitoring.Thresholds.MemoryWarnPercent)
|
||||
logger.Printf("[DEBUG] [monitor] Memory: WARN (%.0f%% >= %d%%)", sysInfo.MemPercent, cfg.Monitoring.Thresholds.MemoryWarnPercent)
|
||||
}
|
||||
} else {
|
||||
report.Info = append(report.Info, fmt.Sprintf("Memory: %.0f%% used", sysInfo.MemPercent))
|
||||
if debug {
|
||||
logger.Printf("[DEBUG] [HEALTH] Memory: OK (%.0f%%)", sysInfo.MemPercent)
|
||||
logger.Printf("[DEBUG] [monitor] Memory: OK (%.0f%%)", sysInfo.MemPercent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,12 +111,12 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
|
||||
logger.Printf("[WARN] [monitor] CPU threshold breached: %.0f%% (limit: %d%%)", sysInfo.CPUPercent, cfg.Monitoring.Thresholds.CPUWarnPercent)
|
||||
}
|
||||
if debug {
|
||||
logger.Printf("[DEBUG] [HEALTH] CPU: WARN (%.0f%% >= %d%%)", sysInfo.CPUPercent, cfg.Monitoring.Thresholds.CPUWarnPercent)
|
||||
logger.Printf("[DEBUG] [monitor] CPU: WARN (%.0f%% >= %d%%)", sysInfo.CPUPercent, cfg.Monitoring.Thresholds.CPUWarnPercent)
|
||||
}
|
||||
} else {
|
||||
report.Info = append(report.Info, fmt.Sprintf("CPU: %.0f%%", sysInfo.CPUPercent))
|
||||
if debug {
|
||||
logger.Printf("[DEBUG] [HEALTH] CPU: OK (%.0f%%)", sysInfo.CPUPercent)
|
||||
logger.Printf("[DEBUG] [monitor] CPU: OK (%.0f%%)", sysInfo.CPUPercent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,12 +129,12 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
|
||||
logger.Printf("[WARN] [monitor] Temperature threshold breached: %.0f°C (limit: %d°C)", sysInfo.TemperatureCelsius, cfg.Monitoring.Thresholds.TemperatureWarnCelsius)
|
||||
}
|
||||
if debug {
|
||||
logger.Printf("[DEBUG] [HEALTH] Temperature: WARN (%.0f°C >= %d°C)", sysInfo.TemperatureCelsius, cfg.Monitoring.Thresholds.TemperatureWarnCelsius)
|
||||
logger.Printf("[DEBUG] [monitor] Temperature: WARN (%.0f°C >= %d°C)", sysInfo.TemperatureCelsius, cfg.Monitoring.Thresholds.TemperatureWarnCelsius)
|
||||
}
|
||||
} else {
|
||||
report.Info = append(report.Info, fmt.Sprintf("Temperature: %.0f°C", sysInfo.TemperatureCelsius))
|
||||
if debug {
|
||||
logger.Printf("[DEBUG] [HEALTH] Temperature: OK (%.0f°C)", sysInfo.TemperatureCelsius)
|
||||
logger.Printf("[DEBUG] [monitor] Temperature: OK (%.0f°C)", sysInfo.TemperatureCelsius)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,18 +143,18 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
|
||||
if err := checkDocker(); err != nil {
|
||||
report.Issues = append(report.Issues, fmt.Sprintf("Docker: %v", err))
|
||||
if debug {
|
||||
logger.Printf("[DEBUG] [HEALTH] Docker daemon: FAIL (%v)", err)
|
||||
logger.Printf("[DEBUG] [monitor] Docker daemon: FAIL (%v)", err)
|
||||
}
|
||||
} else {
|
||||
report.Info = append(report.Info, "Docker: reachable")
|
||||
if debug {
|
||||
logger.Printf("[DEBUG] [HEALTH] Docker daemon: OK")
|
||||
logger.Printf("[DEBUG] [monitor] Docker daemon: OK")
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Protected containers
|
||||
if debug {
|
||||
logger.Printf("[DEBUG] [HEALTH] Checking %d protected containers: %v", len(cfg.Stacks.Protected), cfg.Stacks.Protected)
|
||||
logger.Printf("[DEBUG] [monitor] Checking %d protected containers: %v", len(cfg.Stacks.Protected), cfg.Stacks.Protected)
|
||||
}
|
||||
missingProtected := checkProtectedContainers(cfg.Stacks.Protected)
|
||||
for _, name := range missingProtected {
|
||||
@@ -162,9 +162,9 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
|
||||
}
|
||||
if debug {
|
||||
if len(missingProtected) > 0 {
|
||||
logger.Printf("[DEBUG] [HEALTH] Protected containers missing: %v", missingProtected)
|
||||
logger.Printf("[DEBUG] [monitor] Protected containers missing: %v", missingProtected)
|
||||
} else {
|
||||
logger.Printf("[DEBUG] [HEALTH] All protected containers running")
|
||||
logger.Printf("[DEBUG] [monitor] All protected containers running")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
|
||||
}
|
||||
|
||||
if debug {
|
||||
logger.Printf("[DEBUG] [HEALTH] Final status: %s (issues=%d, warnings=%d, info=%d)",
|
||||
logger.Printf("[DEBUG] [monitor] Final status: %s (issues=%d, warnings=%d, info=%d)",
|
||||
report.Status, len(report.Issues), len(report.Warnings), len(report.Info))
|
||||
}
|
||||
|
||||
|
||||
@@ -115,6 +115,6 @@ func (p *Pinger) send(uuid, suffix, body string) error {
|
||||
lastErr = fmt.Errorf("HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
p.logger.Printf("[WARN] Health ping failed after 3 attempts (%s): %v", uuid, lastErr)
|
||||
p.logger.Printf("[WARN] [monitor] Health ping failed after 3 attempts (%s): %v", uuid, lastErr)
|
||||
return nil // Never let ping failures affect the caller
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ func (w *StorageWatchdog) handleConnectedProbe(sp settings.StoragePath, state *p
|
||||
|
||||
if result.Status == system.ProbeConnected {
|
||||
if state.consecutiveFailures > 0 {
|
||||
w.logger.Printf("[DEBUG] [STORAGE] Probe recovered for %s after %d failures", sp.Path, state.consecutiveFailures)
|
||||
w.logger.Printf("[DEBUG] [storage] Probe recovered for %s after %d failures", sp.Path, state.consecutiveFailures)
|
||||
}
|
||||
state.consecutiveFailures = 0
|
||||
state.lastStatus = "connected"
|
||||
@@ -209,7 +209,7 @@ func (w *StorageWatchdog) handleConnectedProbe(sp settings.StoragePath, state *p
|
||||
// Every 60 probes (~5 minutes at 5s interval): emit summary
|
||||
if state.probeCount >= 60 {
|
||||
avgLatency := state.totalLatency / time.Duration(state.probeCount)
|
||||
w.logger.Printf("[DEBUG] [STORAGE] Storage watchdog: %s — %d/%d probes OK (last 5m, avg %dms)",
|
||||
w.logger.Printf("[DEBUG] [storage] Storage watchdog: %s — %d/%d probes OK (last 5m, avg %dms)",
|
||||
sp.Path, state.probeOKCount, state.probeCount, avgLatency.Milliseconds())
|
||||
state.probeCount = 0
|
||||
state.probeOKCount = 0
|
||||
@@ -224,11 +224,11 @@ func (w *StorageWatchdog) handleConnectedProbe(sp settings.StoragePath, state *p
|
||||
|
||||
// Debug: log immediately on unexpected failure (was connected, now failing)
|
||||
if w.isDebug() && state.lastStatus == "connected" {
|
||||
w.logger.Printf("[DEBUG] [STORAGE] Storage probe failed for %s (%d/%d before disconnect): %v",
|
||||
w.logger.Printf("[DEBUG] [storage] Storage probe failed for %s (%d/%d before disconnect): %v",
|
||||
sp.Path, state.consecutiveFailures, probeThreshold, result.Err)
|
||||
}
|
||||
|
||||
w.logger.Printf("[WARN] [STORAGE] Probe failed for %s (%d/%d): %v",
|
||||
w.logger.Printf("[WARN] [storage] Probe failed for %s (%d/%d): %v",
|
||||
sp.Path, state.consecutiveFailures, probeThreshold, result.Err)
|
||||
|
||||
if state.consecutiveFailures >= probeThreshold {
|
||||
@@ -249,14 +249,14 @@ func (w *StorageWatchdog) handleDisconnect(sp settings.StoragePath, state *pathP
|
||||
if label == "" {
|
||||
label = sp.Path
|
||||
}
|
||||
w.logger.Printf("[ERROR] [STORAGE] Drive disconnected: %s (%s)", sp.Path, label)
|
||||
w.logger.Printf("[ERROR] [storage] Drive disconnected: %s (%s)", sp.Path, label)
|
||||
|
||||
// 1. Find and stop affected stacks
|
||||
stoppedStacks := w.stopAffectedStacks(sp.Path)
|
||||
|
||||
// 2. Mark disconnected in settings (persists to settings.json)
|
||||
if err := w.settings.SetDisconnected(sp.Path, true, stoppedStacks); err != nil {
|
||||
w.logger.Printf("[ERROR] [STORAGE] Failed to mark disconnected: %v", err)
|
||||
w.logger.Printf("[ERROR] [storage] Failed to mark disconnected: %v", err)
|
||||
}
|
||||
|
||||
// 3. Lazy unmount stale mount (if probe timed out — mount is likely hanging)
|
||||
@@ -302,7 +302,7 @@ func (w *StorageWatchdog) handleReconnectCheck(ctx context.Context, sp settings.
|
||||
}
|
||||
|
||||
if w.isDebug() {
|
||||
w.logger.Printf("[DEBUG] [STORAGE] Reconnect check for %s: UUID=%s, mountPath=%s, isAttachWizard=%v",
|
||||
w.logger.Printf("[DEBUG] [storage] Reconnect check for %s: UUID=%s, mountPath=%s, isAttachWizard=%v",
|
||||
sp.Path, uuid, mountPath, isAttachWizard)
|
||||
}
|
||||
|
||||
@@ -316,35 +316,35 @@ func (w *StorageWatchdog) handleReconnectCheck(ctx context.Context, sp settings.
|
||||
if label == "" {
|
||||
label = sp.Path
|
||||
}
|
||||
w.logger.Printf("[INFO] [STORAGE] Drive reconnected (UUID found), attempting remount: %s (%s)", sp.Path, label)
|
||||
w.logger.Printf("[INFO] [storage] Drive reconnected (UUID found), attempting remount: %s (%s)", sp.Path, label)
|
||||
|
||||
if w.isDebug() {
|
||||
w.logger.Printf("[DEBUG] [STORAGE] UUID %s found at %s, mounting %s (raw=%s, attachWizard=%v)",
|
||||
w.logger.Printf("[DEBUG] [storage] UUID %s found at %s, mounting %s (raw=%s, attachWizard=%v)",
|
||||
uuid, uuidPath, sp.Path, rawPath, isAttachWizard)
|
||||
}
|
||||
|
||||
// Attempt remount
|
||||
if err := w.remount(sp.Path, rawPath, isAttachWizard); err != nil {
|
||||
w.logger.Printf("[ERROR] [STORAGE] Remount failed for %s: %v", sp.Path, err)
|
||||
w.logger.Printf("[ERROR] [storage] Remount failed for %s: %v", sp.Path, err)
|
||||
return // Try again next cycle
|
||||
}
|
||||
|
||||
// Verify with a probe
|
||||
verifyResult := system.ProbeStoragePath(sp.Path)
|
||||
if verifyResult.Status != system.ProbeConnected {
|
||||
w.logger.Printf("[ERROR] [STORAGE] Post-remount probe failed for %s: %v", sp.Path, verifyResult.Err)
|
||||
w.logger.Printf("[ERROR] [storage] Post-remount probe failed for %s: %v", sp.Path, verifyResult.Err)
|
||||
if w.isDebug() {
|
||||
w.logger.Printf("[DEBUG] [STORAGE] Post-mount verification failed for %s: status=%v, err=%v",
|
||||
w.logger.Printf("[DEBUG] [storage] Post-mount verification failed for %s: status=%v, err=%v",
|
||||
sp.Path, verifyResult.Status, verifyResult.Err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if w.isDebug() {
|
||||
w.logger.Printf("[DEBUG] [STORAGE] Post-mount verification succeeded for %s", sp.Path)
|
||||
w.logger.Printf("[DEBUG] [storage] Post-mount verification succeeded for %s", sp.Path)
|
||||
}
|
||||
|
||||
w.logger.Printf("[INFO] [STORAGE] Drive successfully remounted: %s (%s)", sp.Path, label)
|
||||
w.logger.Printf("[INFO] [storage] Drive successfully remounted: %s (%s)", sp.Path, label)
|
||||
|
||||
// Clean stale restic locks
|
||||
w.cleanResticLocks(ctx, sp.Path)
|
||||
@@ -354,7 +354,7 @@ func (w *StorageWatchdog) handleReconnectCheck(ctx context.Context, sp settings.
|
||||
|
||||
// Clear disconnected but preserve StoppedStacks for the restart UI
|
||||
if err := w.settings.SetDisconnected(sp.Path, false, filteredStacks); err != nil {
|
||||
w.logger.Printf("[ERROR] [STORAGE] Failed to clear disconnected: %v", err)
|
||||
w.logger.Printf("[ERROR] [storage] Failed to clear disconnected: %v", err)
|
||||
}
|
||||
|
||||
// Update in-memory state
|
||||
@@ -400,20 +400,20 @@ func (w *StorageWatchdog) stopAffectedStacks(drivePath string) []string {
|
||||
|
||||
// Don't stop protected stacks
|
||||
if w.cfg.IsProtectedStack(stack.Name) {
|
||||
w.logger.Printf("[WARN] [STORAGE] Skipping protected stack: %s", stack.Name)
|
||||
w.logger.Printf("[WARN] [storage] Skipping protected stack: %s", stack.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
w.logger.Printf("[INFO] [STORAGE] Stopping stack %s (drive disconnected: %s)", stack.Name, drivePath)
|
||||
w.logger.Printf("[INFO] [storage] Stopping stack %s (drive disconnected: %s)", stack.Name, drivePath)
|
||||
if err := w.stackProvider.StopStack(stack.Name); err != nil {
|
||||
w.logger.Printf("[ERROR] [STORAGE] Failed to stop stack %s: %v", stack.Name, err)
|
||||
w.logger.Printf("[ERROR] [storage] Failed to stop stack %s: %v", stack.Name, err)
|
||||
continue // Don't add to stopped list if stop failed
|
||||
}
|
||||
stopped = append(stopped, stack.Name)
|
||||
}
|
||||
|
||||
if len(stopped) > 0 {
|
||||
w.logger.Printf("[INFO] [STORAGE] Stopped %d stack(s) due to drive disconnect: %v", len(stopped), stopped)
|
||||
w.logger.Printf("[INFO] [storage] Stopped %d stack(s) due to drive disconnect: %v", len(stopped), stopped)
|
||||
}
|
||||
return stopped
|
||||
}
|
||||
@@ -426,18 +426,18 @@ func (w *StorageWatchdog) lazyUnmount(path string) {
|
||||
// Unmount the bind/main path
|
||||
cmd := exec.Command("umount", "-l", path)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
w.logger.Printf("[WARN] [STORAGE] umount -l %s: %v (%s)", path, err, strings.TrimSpace(string(out)))
|
||||
w.logger.Printf("[WARN] [storage] umount -l %s: %v (%s)", path, err, strings.TrimSpace(string(out)))
|
||||
} else {
|
||||
w.logger.Printf("[INFO] [STORAGE] Lazy unmounted: %s", path)
|
||||
w.logger.Printf("[INFO] [storage] Lazy unmounted: %s", path)
|
||||
}
|
||||
|
||||
// Then unmount the raw path if it's an attach-wizard drive
|
||||
if isAttachWizard && rawPath != "" {
|
||||
cmd = exec.Command("umount", "-l", rawPath)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
w.logger.Printf("[WARN] [STORAGE] umount -l %s: %v (%s)", rawPath, err, strings.TrimSpace(string(out)))
|
||||
w.logger.Printf("[WARN] [storage] umount -l %s: %v (%s)", rawPath, err, strings.TrimSpace(string(out)))
|
||||
} else {
|
||||
w.logger.Printf("[INFO] [STORAGE] Lazy unmounted raw: %s", rawPath)
|
||||
w.logger.Printf("[INFO] [storage] Lazy unmounted raw: %s", rawPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -456,19 +456,19 @@ func (w *StorageWatchdog) remount(path, rawPath string, isAttachWizard bool) err
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("mount raw %s: %v (%s)", rawPath, err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
w.logger.Printf("[INFO] [STORAGE] Mounted raw: %s", rawPath)
|
||||
w.logger.Printf("[INFO] [storage] Mounted raw: %s", rawPath)
|
||||
|
||||
cmd = exec.Command("mount", "-T", hostFstabPath, path)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("mount bind %s: %v (%s)", path, err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
w.logger.Printf("[INFO] [STORAGE] Mounted bind: %s", path)
|
||||
w.logger.Printf("[INFO] [storage] Mounted bind: %s", path)
|
||||
} else {
|
||||
cmd := exec.Command("mount", "-T", hostFstabPath, path)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("mount %s: %v (%s)", path, err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
w.logger.Printf("[INFO] [STORAGE] Mounted: %s", path)
|
||||
w.logger.Printf("[INFO] [storage] Mounted: %s", path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -482,11 +482,11 @@ func (w *StorageWatchdog) cleanResticLocks(ctx context.Context, drivePath string
|
||||
return // No locks dir or no lock files
|
||||
}
|
||||
|
||||
w.logger.Printf("[INFO] [STORAGE] Found %d restic lock file(s) in %s, running unlock", len(entries), repoPath)
|
||||
w.logger.Printf("[INFO] [storage] Found %d restic lock file(s) in %s, running unlock", len(entries), repoPath)
|
||||
|
||||
if w.unlockRepo != nil {
|
||||
if err := w.unlockRepo(ctx, repoPath); err != nil {
|
||||
w.logger.Printf("[WARN] [STORAGE] Restic unlock failed for %s: %v", repoPath, err)
|
||||
w.logger.Printf("[WARN] [storage] Restic unlock failed for %s: %v", repoPath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -529,7 +529,7 @@ func (w *StorageWatchdog) SafeDisconnect(ctx context.Context, path string) (stop
|
||||
if label == "" {
|
||||
label = sp.Path
|
||||
}
|
||||
w.logger.Printf("[INFO] [STORAGE] Safe disconnect requested: %s (%s)", path, label)
|
||||
w.logger.Printf("[INFO] [storage] Safe disconnect requested: %s (%s)", path, label)
|
||||
|
||||
// 1. Stop affected stacks
|
||||
stoppedStacks = w.stopAffectedStacks(path)
|
||||
@@ -544,7 +544,7 @@ func (w *StorageWatchdog) SafeDisconnect(ctx context.Context, path string) (stop
|
||||
cmd := exec.Command("umount", path)
|
||||
if out, umountErr := cmd.CombinedOutput(); umountErr != nil {
|
||||
// Try lazy unmount as fallback
|
||||
w.logger.Printf("[WARN] [STORAGE] umount %s failed, trying lazy: %v", path, umountErr)
|
||||
w.logger.Printf("[WARN] [storage] umount %s failed, trying lazy: %v", path, umountErr)
|
||||
cmd = exec.Command("umount", "-l", path)
|
||||
if out, umountErr = cmd.CombinedOutput(); umountErr != nil {
|
||||
return stoppedStacks, fmt.Errorf("umount %s failed: %v (%s)", path, umountErr, strings.TrimSpace(string(out)))
|
||||
@@ -557,14 +557,14 @@ func (w *StorageWatchdog) SafeDisconnect(ctx context.Context, path string) (stop
|
||||
if out, umountErr := cmd.CombinedOutput(); umountErr != nil {
|
||||
cmd = exec.Command("umount", "-l", rawPath)
|
||||
if out, umountErr = cmd.CombinedOutput(); umountErr != nil {
|
||||
w.logger.Printf("[WARN] [STORAGE] umount raw %s failed: %v (%s)", rawPath, umountErr, strings.TrimSpace(string(out)))
|
||||
w.logger.Printf("[WARN] [storage] umount raw %s failed: %v (%s)", rawPath, umountErr, strings.TrimSpace(string(out)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Mark disconnected
|
||||
if setErr := w.settings.SetDisconnected(path, true, stoppedStacks); setErr != nil {
|
||||
w.logger.Printf("[ERROR] [STORAGE] Failed to mark disconnected: %v", setErr)
|
||||
w.logger.Printf("[ERROR] [storage] Failed to mark disconnected: %v", setErr)
|
||||
}
|
||||
|
||||
// 5. Update in-memory state
|
||||
@@ -587,7 +587,7 @@ func (w *StorageWatchdog) SafeDisconnect(ctx context.Context, path string) (stop
|
||||
go w.pushHubReport()
|
||||
}
|
||||
|
||||
w.logger.Printf("[INFO] [STORAGE] Safe disconnect completed: %s — drive can be removed", path)
|
||||
w.logger.Printf("[INFO] [storage] Safe disconnect completed: %s — drive can be removed", path)
|
||||
return stoppedStacks, nil
|
||||
}
|
||||
|
||||
@@ -639,7 +639,7 @@ func (w *StorageWatchdog) Reconnect(ctx context.Context, path string) (stoppedSt
|
||||
|
||||
// Clear disconnected, preserve stopped stacks for restart UI
|
||||
if setErr := w.settings.SetDisconnected(path, false, filteredStacks); setErr != nil {
|
||||
w.logger.Printf("[ERROR] [STORAGE] Failed to clear disconnected: %v", setErr)
|
||||
w.logger.Printf("[ERROR] [storage] Failed to clear disconnected: %v", setErr)
|
||||
}
|
||||
|
||||
// Update in-memory state
|
||||
@@ -661,7 +661,7 @@ func (w *StorageWatchdog) Reconnect(ctx context.Context, path string) (stoppedSt
|
||||
go w.pushHubReport()
|
||||
}
|
||||
|
||||
w.logger.Printf("[INFO] [STORAGE] Reconnect completed: %s", path)
|
||||
w.logger.Printf("[INFO] [storage] Reconnect completed: %s", path)
|
||||
return filteredStacks, nil
|
||||
}
|
||||
|
||||
@@ -678,9 +678,9 @@ func (w *StorageWatchdog) RestartStoppedApps(path string) (started, failed []str
|
||||
}
|
||||
|
||||
for _, name := range stacks {
|
||||
w.logger.Printf("[INFO] [STORAGE] Starting stack %s (drive reconnected: %s)", name, path)
|
||||
w.logger.Printf("[INFO] [storage] Starting stack %s (drive reconnected: %s)", name, path)
|
||||
if err := w.stackProvider.StartStack(name); err != nil {
|
||||
w.logger.Printf("[ERROR] [STORAGE] Failed to start stack %s: %v", name, err)
|
||||
w.logger.Printf("[ERROR] [storage] Failed to start stack %s: %v", name, err)
|
||||
failed = append(failed, name)
|
||||
} else {
|
||||
started = append(started, name)
|
||||
@@ -689,7 +689,7 @@ func (w *StorageWatchdog) RestartStoppedApps(path string) (started, failed []str
|
||||
|
||||
// Clear stopped stacks list
|
||||
if err := w.settings.ClearStoppedStacks(path); err != nil {
|
||||
w.logger.Printf("[ERROR] [STORAGE] Failed to clear stopped stacks: %v", err)
|
||||
w.logger.Printf("[ERROR] [storage] Failed to clear stopped stacks: %v", err)
|
||||
}
|
||||
|
||||
return started, failed
|
||||
@@ -723,7 +723,7 @@ func (w *StorageWatchdog) SimulateDisconnect(ctx context.Context, path string) (
|
||||
if label == "" {
|
||||
label = sp.Path
|
||||
}
|
||||
w.logger.Printf("[INFO] [STORAGE] [DEBUG-SIM] Simulating disconnect: %s (%s)", path, label)
|
||||
w.logger.Printf("[INFO] [storage] (simulation) Simulating disconnect: %s (%s)", path, label)
|
||||
|
||||
// Mark as simulated so the watchdog skips probing this path
|
||||
w.simulatedMu.Lock()
|
||||
@@ -735,7 +735,7 @@ func (w *StorageWatchdog) SimulateDisconnect(ctx context.Context, path string) (
|
||||
|
||||
// Step 2: Mark disconnected in settings
|
||||
if err := w.settings.SetDisconnected(path, true, stoppedStacks); err != nil {
|
||||
w.logger.Printf("[ERROR] [STORAGE] [DEBUG-SIM] Failed to mark disconnected: %v", err)
|
||||
w.logger.Printf("[ERROR] [storage] (simulation) Failed to mark disconnected: %v", err)
|
||||
}
|
||||
|
||||
// Step 3: SKIPPED (no lazyUnmount — drive stays physically mounted)
|
||||
@@ -761,7 +761,7 @@ func (w *StorageWatchdog) SimulateDisconnect(ctx context.Context, path string) (
|
||||
go w.pushHubReport()
|
||||
}
|
||||
|
||||
w.logger.Printf("[INFO] [STORAGE] [DEBUG-SIM] Disconnect simulated: %s — %d stack(s) stopped", path, len(stoppedStacks))
|
||||
w.logger.Printf("[INFO] [storage] (simulation) Disconnect simulated: %s — %d stack(s) stopped", path, len(stoppedStacks))
|
||||
return stoppedStacks, nil
|
||||
}
|
||||
|
||||
@@ -780,7 +780,7 @@ func (w *StorageWatchdog) SimulateReconnect(ctx context.Context, path string) er
|
||||
if label == "" {
|
||||
label = sp.Path
|
||||
}
|
||||
w.logger.Printf("[INFO] [STORAGE] [DEBUG-SIM] Simulating reconnect: %s (%s)", path, label)
|
||||
w.logger.Printf("[INFO] [storage] (simulation) Simulating reconnect: %s (%s)", path, label)
|
||||
|
||||
// Remove from simulated set
|
||||
w.simulatedMu.Lock()
|
||||
@@ -801,7 +801,7 @@ func (w *StorageWatchdog) SimulateReconnect(ctx context.Context, path string) er
|
||||
|
||||
// Clear disconnected, preserve stopped stacks for restart UI
|
||||
if err := w.settings.SetDisconnected(path, false, filteredStacks); err != nil {
|
||||
w.logger.Printf("[ERROR] [STORAGE] [DEBUG-SIM] Failed to clear disconnected: %v", err)
|
||||
w.logger.Printf("[ERROR] [storage] (simulation) Failed to clear disconnected: %v", err)
|
||||
}
|
||||
|
||||
// Update in-memory state
|
||||
@@ -823,7 +823,7 @@ func (w *StorageWatchdog) SimulateReconnect(ctx context.Context, path string) er
|
||||
go w.pushHubReport()
|
||||
}
|
||||
|
||||
w.logger.Printf("[INFO] [STORAGE] [DEBUG-SIM] Reconnect simulated: %s", path)
|
||||
w.logger.Printf("[INFO] [storage] (simulation) Reconnect simulated: %s", path)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ func BuildReport(
|
||||
logger.Printf("[INFO] [report] Building system report")
|
||||
}
|
||||
if debug && logger != nil {
|
||||
logger.Printf("[DEBUG] BuildReport: starting — version=%s, storagePaths=%d", version, len(storagePaths))
|
||||
logger.Printf("[DEBUG] [report] BuildReport: starting — version=%s, storagePaths=%d", version, len(storagePaths))
|
||||
}
|
||||
|
||||
r := &Report{
|
||||
@@ -60,7 +60,7 @@ func BuildReport(
|
||||
h := sha256.Sum256(data)
|
||||
r.ConfigHash = hex.EncodeToString(h[:])
|
||||
if debug && logger != nil {
|
||||
logger.Printf("[DEBUG] BuildReport: configHash=%s (%d bytes)", r.ConfigHash[:12]+"...", len(data))
|
||||
logger.Printf("[DEBUG] [report] BuildReport: configHash=%s (%d bytes)", r.ConfigHash[:12]+"...", len(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,9 +126,9 @@ func BuildReport(
|
||||
}
|
||||
|
||||
if debug && logger != nil {
|
||||
logger.Printf("[DEBUG] BuildReport: system info collected — cpu=%.1f%%, mem=%d/%dMB, temp=%.1fC",
|
||||
logger.Printf("[DEBUG] [report] BuildReport: system info collected — cpu=%.1f%%, mem=%d/%dMB, temp=%.1fC",
|
||||
sysInfo.CPUPercent, sysInfo.UsedMemMB, sysInfo.TotalMemMB, sysInfo.TemperatureCelsius)
|
||||
logger.Printf("[DEBUG] BuildReport: storage entries=%d", len(r.Storage))
|
||||
logger.Printf("[DEBUG] [report] BuildReport: storage entries=%d", len(r.Storage))
|
||||
}
|
||||
|
||||
// Containers
|
||||
@@ -175,7 +175,7 @@ func BuildReport(
|
||||
}
|
||||
|
||||
if debug && logger != nil {
|
||||
logger.Printf("[DEBUG] BuildReport: complete — containers=%d, health=%s, deployed=%d, available=%d, app_telemetry=%d",
|
||||
logger.Printf("[DEBUG] [report] BuildReport: complete — containers=%d, health=%s, deployed=%d, available=%d, app_telemetry=%d",
|
||||
r.Containers.Total, r.Health.Status, len(r.Stacks.Deployed), len(r.Stacks.Available), len(r.AppTelemetry))
|
||||
}
|
||||
|
||||
|
||||
@@ -67,14 +67,14 @@ func BuildInfraBackup(
|
||||
if data, err := os.ReadFile(settingsPath); err == nil {
|
||||
ib.SettingsJSONB64 = base64.StdEncoding.EncodeToString(data)
|
||||
} else if !os.IsNotExist(err) {
|
||||
logger.Printf("[WARN] Infra backup: could not read settings.json: %v", err)
|
||||
logger.Printf("[WARN] [report] Infra backup: could not read settings.json: %v", err)
|
||||
}
|
||||
|
||||
// Read primary restic password (important but non-fatal)
|
||||
if data, err := os.ReadFile(resticPasswordFile); err == nil {
|
||||
ib.ResticPassword = base64.StdEncoding.EncodeToString(data)
|
||||
} else if !os.IsNotExist(err) {
|
||||
logger.Printf("[WARN] Infra backup: could not read restic password file: %v", err)
|
||||
logger.Printf("[WARN] [report] Infra backup: could not read restic password file: %v", err)
|
||||
}
|
||||
|
||||
// Read encryption key for app.yaml secrets (important but non-fatal)
|
||||
@@ -82,7 +82,7 @@ func BuildInfraBackup(
|
||||
if data, err := os.ReadFile(encryptionKeyFile); err == nil {
|
||||
ib.EncryptionKeyB64 = base64.StdEncoding.EncodeToString(data)
|
||||
} else if !os.IsNotExist(err) {
|
||||
logger.Printf("[WARN] Infra backup: could not read encryption key file: %v", err)
|
||||
logger.Printf("[WARN] [report] Infra backup: could not read encryption key file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,5 +103,6 @@ func BuildInfraBackup(
|
||||
ib.DeployedStacks = []InfraStack{}
|
||||
}
|
||||
|
||||
logger.Printf("[INFO] [report] InfraBackup built successfully (stacks=%d)", len(ib.DeployedStacks))
|
||||
return ib, nil
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func (p *Pusher) Push(report *Report) error {
|
||||
|
||||
url := p.hubURL + "/api/v1/report"
|
||||
if p.debug {
|
||||
p.logger.Printf("[DEBUG] Push: url=%s payload=%d bytes", url, len(data))
|
||||
p.logger.Printf("[DEBUG] [report] Push: url=%s payload=%d bytes", url, len(data))
|
||||
}
|
||||
|
||||
p.statusMu.Lock()
|
||||
@@ -106,7 +106,7 @@ func (p *Pusher) Push(report *Report) error {
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
p.logger.Printf("[INFO] Hub report pushed successfully (%d bytes)", len(data))
|
||||
p.logger.Printf("[INFO] [report] Hub report pushed successfully (%d bytes)", len(data))
|
||||
p.statusMu.Lock()
|
||||
p.status.LastSuccess = time.Now()
|
||||
p.status.LastError = ""
|
||||
@@ -151,7 +151,7 @@ func (p *Pusher) PushInfraBackup(data []byte) error {
|
||||
|
||||
url := p.hubURL + "/api/v1/infra-backup"
|
||||
if p.debug {
|
||||
p.logger.Printf("[DEBUG] PushInfraBackup: url=%s payload=%d bytes", url, len(data))
|
||||
p.logger.Printf("[DEBUG] [report] PushInfraBackup: url=%s payload=%d bytes", url, len(data))
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
@@ -179,12 +179,12 @@ func (p *Pusher) PushInfraBackup(data []byte) error {
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
p.logger.Printf("[INFO] Infra backup pushed to Hub (%d bytes)", len(data))
|
||||
p.logger.Printf("[INFO] [report] Infra backup pushed to Hub (%d bytes)", len(data))
|
||||
return nil
|
||||
}
|
||||
lastErr = fmt.Errorf("HTTP %d", resp.StatusCode)
|
||||
if p.debug {
|
||||
p.logger.Printf("[DEBUG] PushInfraBackup: attempt %d failed — HTTP %d", attempt+1, resp.StatusCode)
|
||||
p.logger.Printf("[DEBUG] [report] PushInfraBackup: attempt %d failed — HTTP %d", attempt+1, resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ func (p *Pusher) PushOnce(report *Report) error {
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
p.logger.Printf("[INFO] Hub push-once sent (%d bytes)", len(data))
|
||||
p.logger.Printf("[INFO] [report] Hub push-once sent (%d bytes)", len(data))
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("hub push-once: HTTP %d", resp.StatusCode)
|
||||
|
||||
@@ -17,7 +17,7 @@ func getBudapestLocation() *time.Location {
|
||||
budapestLocOnce.Do(func() {
|
||||
loc, err := time.LoadLocation("Europe/Budapest")
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Cannot load Europe/Budapest timezone: %v — using UTC", err)
|
||||
log.Printf("[ERROR] [scheduler] Cannot load Europe/Budapest timezone: %v — using UTC", err)
|
||||
loc = time.UTC
|
||||
}
|
||||
budapestLoc = loc
|
||||
@@ -60,7 +60,7 @@ func (s *Scheduler) SetDebug(on bool) {
|
||||
|
||||
func (s *Scheduler) dbg(format string, args ...interface{}) {
|
||||
if s.debug {
|
||||
s.logger.Printf("[DEBUG] [sched] "+format, args...)
|
||||
s.logger.Printf("[DEBUG] [scheduler] "+format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ func SaveState(dataDir string, state *UpdateState) error {
|
||||
func ClearState(dataDir string, logger *log.Logger) {
|
||||
path := filepath.Join(dataDir, stateFileName)
|
||||
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
||||
logger.Printf("[WARN] Failed to clear update state file: %v", err)
|
||||
logger.Printf("[WARN] [selfupdate] Failed to clear update state file: %v", err)
|
||||
return
|
||||
}
|
||||
logger.Printf("[INFO] [selfupdate] Update state cleared")
|
||||
|
||||
@@ -91,7 +91,7 @@ func (u *Updater) GetStatus() UpdateStatus {
|
||||
|
||||
state, err := LoadState(u.dataDir)
|
||||
if err != nil {
|
||||
u.logger.Printf("[WARN] Failed to load update state: %v", err)
|
||||
u.logger.Printf("[WARN] [selfupdate] Failed to load update state: %v", err)
|
||||
}
|
||||
|
||||
return UpdateStatus{
|
||||
@@ -123,7 +123,7 @@ func (u *Updater) CheckForUpdate() CheckResult {
|
||||
latestStr, err := u.queryRegistry()
|
||||
if err != nil {
|
||||
result.Error = fmt.Sprintf("Registry lekérdezés sikertelen: %v", err)
|
||||
u.logger.Printf("[WARN] Registry check failed: %v", err)
|
||||
u.logger.Printf("[WARN] [selfupdate] Registry check failed: %v", err)
|
||||
u.mu.Lock()
|
||||
u.lastCheck = &result
|
||||
u.mu.Unlock()
|
||||
@@ -342,7 +342,7 @@ func (u *Updater) TriggerUpdate(initiatedBy string) error {
|
||||
targetImage := fmt.Sprintf("%s:%s", u.cfg.Image, targetVersion)
|
||||
previousImage := fmt.Sprintf("%s:%s", u.cfg.Image, u.currentVer)
|
||||
|
||||
u.logger.Printf("[INFO] Starting self-update: %s → %s (initiated by: %s)", u.currentVer, targetVersion, initiatedBy)
|
||||
u.logger.Printf("[INFO] [selfupdate] Starting self-update: %s → %s (initiated by: %s)", u.currentVer, targetVersion, initiatedBy)
|
||||
u.dbg("TriggerUpdate: target=%s image=%s previousImage=%s", targetVersion, targetImage, previousImage)
|
||||
|
||||
go u.performUpdate(targetVersion, targetImage, previousImage, initiatedBy)
|
||||
@@ -370,13 +370,13 @@ func (u *Updater) performUpdate(targetVersion, targetImage, previousImage, initi
|
||||
InitiatedBy: initiatedBy,
|
||||
}
|
||||
if err := SaveState(u.dataDir, state); err != nil {
|
||||
u.logger.Printf("[ERROR] Failed to save update state: %v", err)
|
||||
u.logger.Printf("[ERROR] [selfupdate] Failed to save update state: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Docker pull
|
||||
u.dbg("performUpdate: step 2 — docker pull %s", targetImage)
|
||||
u.logger.Printf("[INFO] Pulling image: %s", targetImage)
|
||||
u.logger.Printf("[INFO] [selfupdate] Pulling image: %s", targetImage)
|
||||
pullStart := time.Now()
|
||||
pullOut, pullErr := runCommand("docker", "pull", targetImage)
|
||||
if pullErr != nil {
|
||||
@@ -384,10 +384,10 @@ func (u *Updater) performUpdate(targetVersion, targetImage, previousImage, initi
|
||||
state.Error = fmt.Sprintf("docker pull failed: %v — %s", pullErr, pullOut)
|
||||
state.CompletedAt = time.Now().UTC().Format(time.RFC3339)
|
||||
SaveState(u.dataDir, state)
|
||||
u.logger.Printf("[ERROR] Docker pull failed: %v — %s", pullErr, pullOut)
|
||||
u.logger.Printf("[ERROR] [selfupdate] Docker pull failed: %v — %s", pullErr, pullOut)
|
||||
return
|
||||
}
|
||||
u.logger.Printf("[INFO] Image pulled successfully: %s", targetImage)
|
||||
u.logger.Printf("[INFO] [selfupdate] Image pulled successfully: %s", targetImage)
|
||||
u.dbg("performUpdate: docker pull completed in %s", time.Since(pullStart).Round(time.Millisecond))
|
||||
|
||||
// 3. Update compose file (replace image tag)
|
||||
@@ -397,14 +397,14 @@ func (u *Updater) performUpdate(targetVersion, targetImage, previousImage, initi
|
||||
state.Error = fmt.Sprintf("compose update failed: %v", err)
|
||||
state.CompletedAt = time.Now().UTC().Format(time.RFC3339)
|
||||
SaveState(u.dataDir, state)
|
||||
u.logger.Printf("[ERROR] Compose file update failed: %v", err)
|
||||
u.logger.Printf("[ERROR] [selfupdate] Compose file update failed: %v", err)
|
||||
return
|
||||
}
|
||||
u.logger.Printf("[INFO] Compose file updated with new image: %s", targetImage)
|
||||
u.logger.Printf("[INFO] [selfupdate] Compose file updated with new image: %s", targetImage)
|
||||
|
||||
// 4. Docker compose up -d (this kills the current container)
|
||||
u.dbg("performUpdate: step 4 — docker compose up -d")
|
||||
u.logger.Printf("[INFO] Running docker compose up -d — container will restart")
|
||||
u.logger.Printf("[INFO] [selfupdate] Running docker compose up -d — container will restart")
|
||||
composeDir := strings.TrimSuffix(u.composePath, "/docker-compose.yml")
|
||||
upOut, upErr := runCommand("docker", "compose", "-f", u.composePath, "-p", "felhom-controller", "up", "-d")
|
||||
if upErr != nil {
|
||||
@@ -412,15 +412,15 @@ func (u *Updater) performUpdate(targetVersion, targetImage, previousImage, initi
|
||||
state.Error = fmt.Sprintf("docker compose up -d failed: %v — %s", upErr, upOut)
|
||||
state.CompletedAt = time.Now().UTC().Format(time.RFC3339)
|
||||
SaveState(u.dataDir, state)
|
||||
u.logger.Printf("[ERROR] docker compose up -d failed: %v — %s (dir: %s)", upErr, upOut, composeDir)
|
||||
u.logger.Printf("[ERROR] [selfupdate] docker compose up -d failed: %v — %s (dir: %s)", upErr, upOut, composeDir)
|
||||
return
|
||||
}
|
||||
|
||||
// If we're still alive after compose up -d, log it.
|
||||
// Normally this process should be killed when Docker replaces the container.
|
||||
u.logger.Printf("[WARN] Still running after docker compose up -d — expected to be replaced")
|
||||
u.logger.Printf("[WARN] [selfupdate] Still running after docker compose up -d — expected to be replaced")
|
||||
time.Sleep(30 * time.Second)
|
||||
u.logger.Printf("[WARN] Still alive 30s after docker compose up -d")
|
||||
u.logger.Printf("[WARN] [selfupdate] Still alive 30s after docker compose up -d")
|
||||
}
|
||||
|
||||
// updateComposeFile reads the compose file, replaces the image tag, and writes it back atomically.
|
||||
@@ -471,7 +471,7 @@ func (u *Updater) VerifyStartup() *UpdateState {
|
||||
u.dbg("VerifyStartup: checking update state in %s", u.dataDir)
|
||||
state, err := LoadState(u.dataDir)
|
||||
if err != nil {
|
||||
u.logger.Printf("[WARN] Failed to load update state on startup: %v — clearing", err)
|
||||
u.logger.Printf("[WARN] [selfupdate] Failed to load update state on startup: %v — clearing", err)
|
||||
ClearState(u.dataDir, u.logger)
|
||||
return nil
|
||||
}
|
||||
@@ -490,7 +490,7 @@ func (u *Updater) VerifyStartup() *UpdateState {
|
||||
state.Error = "Version parse error on startup verification"
|
||||
state.CompletedAt = time.Now().UTC().Format(time.RFC3339)
|
||||
SaveState(u.dataDir, state)
|
||||
u.logger.Printf("[WARN] Post-update startup: version parse error (current=%s, target=%s)", u.currentVer, state.TargetVersion)
|
||||
u.logger.Printf("[WARN] [selfupdate] Post-update startup: version parse error (current=%s, target=%s)", u.currentVer, state.TargetVersion)
|
||||
return state
|
||||
}
|
||||
|
||||
@@ -499,14 +499,14 @@ func (u *Updater) VerifyStartup() *UpdateState {
|
||||
state.Status = "success"
|
||||
state.CompletedAt = time.Now().UTC().Format(time.RFC3339)
|
||||
SaveState(u.dataDir, state)
|
||||
u.logger.Printf("[INFO] Post-update startup: update successful (%s → %s)", state.PreviousVersion, state.TargetVersion)
|
||||
u.logger.Printf("[INFO] [selfupdate] Post-update startup: update successful (%s → %s)", state.PreviousVersion, state.TargetVersion)
|
||||
} else {
|
||||
// Version mismatch — update may have failed
|
||||
state.Status = "failed"
|
||||
state.Error = fmt.Sprintf("Version mismatch: expected %s, running %s", state.TargetVersion, u.currentVer)
|
||||
state.CompletedAt = time.Now().UTC().Format(time.RFC3339)
|
||||
SaveState(u.dataDir, state)
|
||||
u.logger.Printf("[WARN] Post-update startup: version mismatch (expected %s, running %s)", state.TargetVersion, u.currentVer)
|
||||
u.logger.Printf("[WARN] [selfupdate] Post-update startup: version mismatch (expected %s, running %s)", state.TargetVersion, u.currentVer)
|
||||
}
|
||||
|
||||
return state
|
||||
|
||||
@@ -173,7 +173,7 @@ func Load(path string, logger *log.Logger) (*Settings, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
logger.Printf("[INFO] No settings.json found, using defaults")
|
||||
logger.Printf("[INFO] [settings] No settings.json found, using defaults")
|
||||
return s, nil
|
||||
}
|
||||
return nil, fmt.Errorf("reading settings file: %w", err)
|
||||
@@ -201,14 +201,14 @@ func (s *Settings) migrateResticToRsync() {
|
||||
prefs.CrossDrive.Method = "rsync"
|
||||
s.AppBackup[name] = prefs
|
||||
if s.log != nil {
|
||||
s.log.Printf("[INFO] Migrated cross-drive backup for %s from restic to rsync", name)
|
||||
s.log.Printf("[INFO] [settings] Migrated cross-drive backup for %s from restic to rsync", name)
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
if err := s.save(); err != nil && s.log != nil {
|
||||
s.log.Printf("[ERROR] Failed to save restic→rsync migration: %v", err)
|
||||
s.log.Printf("[ERROR] [settings] Failed to save restic→rsync migration: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -592,12 +592,12 @@ func (s *Settings) AutoDiscoverStoragePaths(discoveredPaths []string, fallbackHD
|
||||
|
||||
if len(s.StoragePaths) > 0 {
|
||||
if err := s.save(); err != nil {
|
||||
logger.Printf("[ERROR] Failed to save auto-discovered storage paths: %v", err)
|
||||
logger.Printf("[ERROR] [settings] Failed to save auto-discovered storage paths: %v", err)
|
||||
return
|
||||
}
|
||||
logger.Printf("[INFO] Auto-discovered %d storage path(s)", len(s.StoragePaths))
|
||||
logger.Printf("[INFO] [settings] Auto-discovered %d storage path(s)", len(s.StoragePaths))
|
||||
for _, sp := range s.StoragePaths {
|
||||
logger.Printf("[INFO] %s (%s) default=%v", sp.Path, sp.Label, sp.IsDefault)
|
||||
logger.Printf("[INFO] [settings] %s (%s) default=%v", sp.Path, sp.Label, sp.IsDefault)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -619,7 +619,7 @@ func (s *Settings) SetDisconnected(path string, disconnected bool, stoppedStacks
|
||||
s.log.Printf("[DEBUG] [settings] SetDisconnected path=%q disconnected=%v stopped_stacks=%d", path, disconnected, len(stoppedStacks))
|
||||
}
|
||||
if s.log != nil {
|
||||
s.log.Printf("[INFO] [settings] Node disconnected: %v", disconnected)
|
||||
s.log.Printf("[INFO] [settings] Storage path %s disconnected=%v", path, disconnected)
|
||||
}
|
||||
for i := range s.StoragePaths {
|
||||
if s.StoragePaths[i].Path == path {
|
||||
@@ -732,7 +732,7 @@ func (s *Settings) SetDecommissioned(path, migratedTo string) error {
|
||||
s.log.Printf("[DEBUG] [settings] SetDecommissioned path=%q migrated_to=%q", path, migratedTo)
|
||||
}
|
||||
if s.log != nil {
|
||||
s.log.Printf("[INFO] [settings] Node decommissioned")
|
||||
s.log.Printf("[INFO] [settings] Storage path %s decommissioned (migrated_to=%s)", path, migratedTo)
|
||||
}
|
||||
for i := range s.StoragePaths {
|
||||
if s.StoragePaths[i].Path == path {
|
||||
@@ -890,7 +890,7 @@ func (s *Settings) DrainPendingEvents() []PendingEvent {
|
||||
copy(events, s.PendingEvents)
|
||||
s.PendingEvents = nil
|
||||
if err := s.save(); err != nil {
|
||||
s.log.Printf("[ERROR] Failed to save after draining pending events: %v — restoring events", err)
|
||||
s.log.Printf("[ERROR] [settings] Failed to save after draining pending events: %v — restoring events", err)
|
||||
s.PendingEvents = events
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ func (s *SetupState) SetStep(step string) {
|
||||
s.Step = step
|
||||
s.mu.Unlock()
|
||||
if err := s.Save(); err != nil {
|
||||
log.Printf("[WARN] Failed to save setup step %q: %v", step, err)
|
||||
log.Printf("[WARN] [setup] Failed to save setup step %q: %v", step, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -161,12 +161,12 @@ func (m *Manager) DeployStack(req DeployRequest) (string, error) {
|
||||
reservedMB := m.cfg.System.ReservedMemoryMB
|
||||
totalMB, usedMB, memErr := system.GetMemoryMB()
|
||||
if memErr != nil {
|
||||
m.logger.Printf("[WARN] Cannot read system memory: %v — skipping memory check", memErr)
|
||||
m.logger.Printf("[WARN] [stacks] Cannot read system memory: %v — skipping memory check", memErr)
|
||||
} else {
|
||||
usableMB := totalMB - reservedMB
|
||||
newReqMB := ParseMemoryMB(meta.Resources.MemRequest)
|
||||
|
||||
m.logger.Printf("[INFO] Memory check: total=%dMB, reserved=%dMB, usable=%dMB, real_used=%dMB, new_req=%dMB, remaining=%dMB",
|
||||
m.logger.Printf("[INFO] [stacks] Memory check: total=%dMB, reserved=%dMB, usable=%dMB, real_used=%dMB, new_req=%dMB, remaining=%dMB",
|
||||
totalMB, reservedMB, usableMB, usedMB, newReqMB, usableMB-usedMB-newReqMB)
|
||||
|
||||
// Hard block: real used + new request exceeds usable memory
|
||||
@@ -309,7 +309,7 @@ func (m *Manager) DeployStack(req DeployRequest) (string, error) {
|
||||
for k := range env {
|
||||
envKeys = append(envKeys, k)
|
||||
}
|
||||
m.logger.Printf("[INFO] Deploying stack %s with %d env vars: [%s]", req.StackName, len(env), strings.Join(envKeys, ", "))
|
||||
m.logger.Printf("[INFO] [stacks] Deploying stack %s with %d env vars: [%s]", req.StackName, len(env), strings.Join(envKeys, ", "))
|
||||
|
||||
// Check which images are available locally before pulling
|
||||
if m.isDebug() {
|
||||
@@ -339,7 +339,7 @@ func (m *Manager) runComposeDeploy(name, stackDir string, env map[string]string,
|
||||
_, composeErr := m.composeExecWithEnv(stackDir, env, "up", "-d")
|
||||
|
||||
if composeErr != nil {
|
||||
m.logger.Printf("[ERROR] Stack %s deploy failed after %.1fs: %v", name, time.Since(start).Seconds(), composeErr)
|
||||
m.logger.Printf("[ERROR] [stacks] Stack %s deploy failed after %.1fs: %v", name, time.Since(start).Seconds(), composeErr)
|
||||
// Revert in-memory and disk state
|
||||
m.mu.Lock()
|
||||
if s, ok := m.stacks[name]; ok {
|
||||
@@ -357,7 +357,7 @@ func (m *Manager) runComposeDeploy(name, stackDir string, env map[string]string,
|
||||
return
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Stack %s deployed successfully (took %.1fs)", name, time.Since(start).Seconds())
|
||||
m.logger.Printf("[INFO] [stacks] Stack %s deployed successfully (took %.1fs)", name, time.Since(start).Seconds())
|
||||
|
||||
// Clear deploying flag
|
||||
m.mu.Lock()
|
||||
@@ -427,7 +427,7 @@ func (m *Manager) UpdateStackConfig(name string, values map[string]string) error
|
||||
return fmt.Errorf("restarting with new config: %w", err)
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Stack %s config updated and restarted", name)
|
||||
m.logger.Printf("[INFO] [stacks] Stack %s config updated and restarted", name)
|
||||
return m.RefreshStatus()
|
||||
}
|
||||
|
||||
@@ -503,13 +503,13 @@ func (m *Manager) UpdateOptionalConfig(stackName string, values map[string]strin
|
||||
changed := false
|
||||
for key, val := range values {
|
||||
if !allowed[key] {
|
||||
m.logger.Printf("[WARN] Ignoring non-optional env var: %s", key)
|
||||
m.logger.Printf("[WARN] [stacks] Ignoring non-optional env var: %s", key)
|
||||
continue
|
||||
}
|
||||
if appCfg.Env[key] != val {
|
||||
appCfg.Env[key] = val
|
||||
changed = true
|
||||
m.logger.Printf("[INFO] Updated optional config %s for %s", key, stackName)
|
||||
m.logger.Printf("[INFO] [stacks] Updated optional config %s for %s", key, stackName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,12 +522,12 @@ func (m *Manager) UpdateOptionalConfig(stackName string, values map[string]strin
|
||||
if err := SaveAppConfig(stackDir, appCfg, m.encKey, SensitiveEnvVars(&meta)); err != nil {
|
||||
return fmt.Errorf("saving app config: %w", err)
|
||||
}
|
||||
m.logger.Printf("[INFO] Saved updated app.yaml for %s", stackName)
|
||||
m.logger.Printf("[INFO] [stacks] Saved updated app.yaml for %s", stackName)
|
||||
|
||||
// If deployed, recreate containers to pick up new env vars
|
||||
// (docker compose restart does NOT pick up new env vars — must use up -d)
|
||||
if stack.Deployed {
|
||||
m.logger.Printf("[INFO] Restarting %s to apply new optional config", stackName)
|
||||
m.logger.Printf("[INFO] [stacks] Restarting %s to apply new optional config", stackName)
|
||||
env := m.stackEnv(stackDir)
|
||||
if _, err := m.composeExecCustomEnv(stackDir, env, "up", "-d"); err != nil {
|
||||
return fmt.Errorf("restart after config update: %w", err)
|
||||
@@ -591,7 +591,6 @@ func LoadAppConfig(stackDir string) *AppConfig {
|
||||
cfg := &AppConfig{}
|
||||
if err := yaml.Unmarshal(data, cfg); err != nil {
|
||||
log.Printf("[WARN] [stacks] LoadAppConfig: %v", err)
|
||||
log.Printf("[DEBUG] [stacks] LoadAppConfig: failed to parse %s: %v", path, err)
|
||||
return nil
|
||||
}
|
||||
return cfg
|
||||
@@ -618,7 +617,7 @@ func SaveAppConfig(stackDir string, cfg *AppConfig, encKey []byte, sensitiveVars
|
||||
continue
|
||||
} else {
|
||||
// H10 fix: log encryption failure — value will be saved in plaintext.
|
||||
log.Printf("[WARN] Failed to encrypt env var %q: %v — saving as plaintext", k, err)
|
||||
log.Printf("[WARN] [stacks] Failed to encrypt env var %q: %v — saving as plaintext", k, err)
|
||||
}
|
||||
}
|
||||
saveCfg.Env[k] = v
|
||||
@@ -763,12 +762,12 @@ func (m *Manager) InjectMissingFields(stackNames []string) {
|
||||
switch field.Type {
|
||||
case "secret":
|
||||
if field.Generate == "" {
|
||||
m.logger.Printf("[WARN] Stack %s: new secret field %s has no generator — skipping", name, field.EnvVar)
|
||||
m.logger.Printf("[WARN] [stacks] Stack %s: new secret field %s has no generator — skipping", name, field.EnvVar)
|
||||
continue
|
||||
}
|
||||
value, err := generateValue(field.Generate)
|
||||
if err != nil {
|
||||
m.logger.Printf("[ERROR] Stack %s: failed to generate %s: %v", name, field.EnvVar, err)
|
||||
m.logger.Printf("[ERROR] [stacks] Stack %s: failed to generate %s: %v", name, field.EnvVar, err)
|
||||
continue
|
||||
}
|
||||
appCfg.Env[field.EnvVar] = value
|
||||
@@ -791,7 +790,7 @@ func (m *Manager) InjectMissingFields(stackNames []string) {
|
||||
val = meta.Subdomain
|
||||
}
|
||||
if val == "" {
|
||||
m.logger.Printf("[WARN] Stack %s: new subdomain field %s has no default — skipping", name, field.EnvVar)
|
||||
m.logger.Printf("[WARN] [stacks] Stack %s: new subdomain field %s has no default — skipping", name, field.EnvVar)
|
||||
continue
|
||||
}
|
||||
appCfg.Env[field.EnvVar] = val
|
||||
@@ -801,16 +800,16 @@ func (m *Manager) InjectMissingFields(stackNames []string) {
|
||||
injected = append(injected, field.EnvVar)
|
||||
|
||||
default:
|
||||
m.logger.Printf("[WARN] Stack %s: new field %s (type=%s) requires manual configuration", name, field.EnvVar, field.Type)
|
||||
m.logger.Printf("[WARN] [stacks] Stack %s: new field %s (type=%s) requires manual configuration", name, field.EnvVar, field.Type)
|
||||
}
|
||||
}
|
||||
|
||||
if len(injected) > 0 {
|
||||
if err := SaveAppConfig(stackDir, appCfg, m.encKey, SensitiveEnvVars(&meta)); err != nil {
|
||||
m.logger.Printf("[ERROR] Stack %s: failed to save app.yaml after injection: %v", name, err)
|
||||
m.logger.Printf("[ERROR] [stacks] Stack %s: failed to save app.yaml after injection: %v", name, err)
|
||||
continue
|
||||
}
|
||||
m.logger.Printf("[SYNC] Stack %s: injected missing fields: %s", name, strings.Join(injected, ", "))
|
||||
m.logger.Printf("[INFO] [stacks] Stack %s: injected missing fields: %s", name, strings.Join(injected, ", "))
|
||||
}
|
||||
}
|
||||
m.logger.Printf("[INFO] [stacks] InjectMissingFields: processed %d stacks", count)
|
||||
|
||||
@@ -96,7 +96,7 @@ func NewManager(cfg *config.Config, logger *log.Logger) (*Manager, error) {
|
||||
return nil, fmt.Errorf("docker compose not found (tried 'docker compose' and 'docker-compose')")
|
||||
}
|
||||
|
||||
logger.Printf("[INFO] Using compose command: %s", composeCmd)
|
||||
logger.Printf("[INFO] [stacks] Using compose command: %s", composeCmd)
|
||||
|
||||
if err := os.MkdirAll(cfg.Paths.StacksDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("creating stacks directory %s: %w", cfg.Paths.StacksDir, err)
|
||||
@@ -176,14 +176,14 @@ func (m *Manager) MigrateEncryption() {
|
||||
m.logger.Printf("[DEBUG] [stacks] MigrateEncryption: stack %q needs migration — re-saving with encryption", s.Name)
|
||||
}
|
||||
if err := SaveAppConfig(stackDir, appCfg, m.encKey, sensitive); err != nil {
|
||||
m.logger.Printf("[WARN] Encryption migration failed for %s: %v", s.Name, err)
|
||||
m.logger.Printf("[WARN] [stacks] Encryption migration failed for %s: %v", s.Name, err)
|
||||
} else {
|
||||
migrated++
|
||||
}
|
||||
}
|
||||
}
|
||||
if migrated > 0 {
|
||||
m.logger.Printf("[INFO] Encrypted sensitive values in %d app.yaml file(s)", migrated)
|
||||
m.logger.Printf("[INFO] [stacks] Encrypted sensitive values in %d app.yaml file(s)", migrated)
|
||||
} else {
|
||||
m.logger.Printf("[INFO] [stacks] Encryption migration: no stacks needed migration")
|
||||
}
|
||||
@@ -316,7 +316,7 @@ func (m *Manager) ScanStacks() error {
|
||||
}
|
||||
}
|
||||
if orphanCount > 0 {
|
||||
m.logger.Printf("[INFO] Detected %d orphaned stack(s)", orphanCount)
|
||||
m.logger.Printf("[INFO] [stacks] Detected %d orphaned stack(s)", orphanCount)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,9 +326,8 @@ func (m *Manager) ScanStacks() error {
|
||||
deployedCount++
|
||||
}
|
||||
}
|
||||
m.logger.Printf("[INFO] Scanned stacks: %d found (%d deployed, %d available)",
|
||||
m.logger.Printf("[INFO] [stacks] ScanStacks complete: %d stacks found (%d deployed, %d available)",
|
||||
len(m.stacks), deployedCount, len(m.stacks)-deployedCount)
|
||||
m.logger.Printf("[INFO] [stacks] ScanStacks complete: %d stacks found", len(m.stacks))
|
||||
return m.refreshStatusLocked()
|
||||
}
|
||||
|
||||
@@ -628,7 +627,7 @@ func (m *Manager) StartStack(name string) error {
|
||||
m.logger.Printf("[DEBUG] [stacks] StartStack %s: current state=%s deployed=%v", name, stack.State, stack.Deployed)
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Starting stack: %s", name)
|
||||
m.logger.Printf("[INFO] [stacks] Starting stack: %s", name)
|
||||
start := time.Now()
|
||||
|
||||
dir := filepath.Dir(stack.ComposePath)
|
||||
@@ -639,11 +638,11 @@ func (m *Manager) StartStack(name string) error {
|
||||
}
|
||||
|
||||
if _, err := m.composeExecCustomEnv(dir, env, "up", "-d"); err != nil {
|
||||
m.logger.Printf("[ERROR] Stack %s start failed after %.1fs: %v", name, time.Since(start).Seconds(), err)
|
||||
m.logger.Printf("[ERROR] [stacks] Stack %s start failed after %.1fs: %v", name, time.Since(start).Seconds(), err)
|
||||
return fmt.Errorf("starting stack %s: %w", name, err)
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Stack %s started successfully (took %.1fs)", name, time.Since(start).Seconds())
|
||||
m.logger.Printf("[INFO] [stacks] Stack %s started successfully (took %.1fs)", name, time.Since(start).Seconds())
|
||||
m.logPostStartStatus(name, dir, env)
|
||||
|
||||
// Clear stale health probe so refreshStatus won't re-apply an old unhealthy override.
|
||||
@@ -671,16 +670,16 @@ func (m *Manager) StopStack(name string) error {
|
||||
m.logger.Printf("[DEBUG] [stacks] StopStack %s: current state=%s deployed=%v containers=%d", name, stack.State, stack.Deployed, len(stack.Containers))
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Stopping stack: %s", name)
|
||||
m.logger.Printf("[INFO] [stacks] Stopping stack: %s", name)
|
||||
start := time.Now()
|
||||
dir := filepath.Dir(stack.ComposePath)
|
||||
|
||||
if _, err := m.composeExec(dir, "down"); err != nil {
|
||||
m.logger.Printf("[ERROR] Stack %s stop failed after %.1fs: %v", name, time.Since(start).Seconds(), err)
|
||||
m.logger.Printf("[ERROR] [stacks] Stack %s stop failed after %.1fs: %v", name, time.Since(start).Seconds(), err)
|
||||
return fmt.Errorf("stopping stack %s: %w", name, err)
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Stack %s stopped successfully (took %.1fs)", name, time.Since(start).Seconds())
|
||||
m.logger.Printf("[INFO] [stacks] Stack %s stopped successfully (took %.1fs)", name, time.Since(start).Seconds())
|
||||
return m.RefreshStatus()
|
||||
}
|
||||
|
||||
@@ -694,7 +693,7 @@ func (m *Manager) RestartStack(name string) error {
|
||||
m.logger.Printf("[DEBUG] [stacks] RestartStack %s: current state=%s deployed=%v containers=%d", name, stack.State, stack.Deployed, len(stack.Containers))
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Restarting stack: %s", name)
|
||||
m.logger.Printf("[INFO] [stacks] Restarting stack: %s", name)
|
||||
start := time.Now()
|
||||
dir := filepath.Dir(stack.ComposePath)
|
||||
env := m.stackEnv(dir)
|
||||
@@ -704,11 +703,11 @@ func (m *Manager) RestartStack(name string) error {
|
||||
// picked up. Plain "docker compose restart" only sends SIGTERM+start
|
||||
// to existing containers without re-reading the compose file or env.
|
||||
if _, err := m.composeExecCustomEnv(dir, env, "up", "-d"); err != nil {
|
||||
m.logger.Printf("[ERROR] Stack %s restart failed after %.1fs: %v", name, time.Since(start).Seconds(), err)
|
||||
m.logger.Printf("[ERROR] [stacks] Stack %s restart failed after %.1fs: %v", name, time.Since(start).Seconds(), err)
|
||||
return fmt.Errorf("restarting stack %s: %w", name, err)
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Stack %s restarted successfully (took %.1fs)", name, time.Since(start).Seconds())
|
||||
m.logger.Printf("[INFO] [stacks] Stack %s restarted successfully (took %.1fs)", name, time.Since(start).Seconds())
|
||||
m.logPostStartStatus(name, dir, env)
|
||||
|
||||
// Clear stale health probe so refreshStatus won't re-apply an old unhealthy override.
|
||||
@@ -727,7 +726,7 @@ func (m *Manager) UpdateStack(name string) error {
|
||||
return fmt.Errorf("stack %q not found", name)
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Updating stack: %s", name)
|
||||
m.logger.Printf("[INFO] [stacks] Updating stack: %s", name)
|
||||
start := time.Now()
|
||||
dir := filepath.Dir(stack.ComposePath)
|
||||
env := m.stackEnv(dir)
|
||||
@@ -737,16 +736,16 @@ func (m *Manager) UpdateStack(name string) error {
|
||||
}
|
||||
|
||||
if _, err := m.composeExecCustomEnv(dir, env, "pull"); err != nil {
|
||||
m.logger.Printf("[ERROR] Stack %s update (pull) failed after %.1fs: %v", name, time.Since(start).Seconds(), err)
|
||||
m.logger.Printf("[ERROR] [stacks] Stack %s update (pull) failed after %.1fs: %v", name, time.Since(start).Seconds(), err)
|
||||
return fmt.Errorf("pulling images for %s: %w", name, err)
|
||||
}
|
||||
|
||||
if _, err := m.composeExecCustomEnv(dir, env, "up", "-d", "--remove-orphans"); err != nil {
|
||||
m.logger.Printf("[ERROR] Stack %s update (up) failed after %.1fs: %v", name, time.Since(start).Seconds(), err)
|
||||
m.logger.Printf("[ERROR] [stacks] Stack %s update (up) failed after %.1fs: %v", name, time.Since(start).Seconds(), err)
|
||||
return fmt.Errorf("recreating %s: %w", name, err)
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Stack %s updated successfully (took %.1fs)", name, time.Since(start).Seconds())
|
||||
m.logger.Printf("[INFO] [stacks] Stack %s updated successfully (took %.1fs)", name, time.Since(start).Seconds())
|
||||
m.logPostStartStatus(name, dir, env)
|
||||
return m.RefreshStatus()
|
||||
}
|
||||
@@ -765,12 +764,11 @@ func (m *Manager) GetLogs(name string, lines int) (string, error) {
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] [stacks] Fetching logs for stack %s (tail=%d)", name, lines)
|
||||
m.logger.Printf("[DEBUG] Fetching logs for %s (tail %d)", name, lines)
|
||||
|
||||
dir := filepath.Dir(stack.ComposePath)
|
||||
output, err := m.composeExec(dir, "logs", "--tail", fmt.Sprintf("%d", lines), "--no-color")
|
||||
if err != nil {
|
||||
m.logger.Printf("[WARN] Failed to fetch logs for %s: %v", name, err)
|
||||
m.logger.Printf("[WARN] [stacks] Failed to fetch logs for %s: %v", name, err)
|
||||
return "", fmt.Errorf("getting logs for %s: %w", name, err)
|
||||
}
|
||||
|
||||
@@ -860,13 +858,13 @@ func (m *Manager) composeExecCustomEnv(dir string, env []string, args ...string)
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
exitCode = exitErr.ExitCode()
|
||||
}
|
||||
m.logger.Printf("[ERROR] Command failed: %s %s (in %s) — exit code %d (took %.1fs)",
|
||||
m.logger.Printf("[ERROR] [stacks] Command failed: %s %s (in %s) — exit code %d (took %.1fs)",
|
||||
m.composeCmd, strings.Join(args, " "), dir, exitCode, elapsed.Seconds())
|
||||
if stdoutStr := truncateStr(stdout.String(), 500); stdoutStr != "" {
|
||||
m.logger.Printf("[ERROR] stdout: %s", stdoutStr)
|
||||
m.logger.Printf("[ERROR] [stacks] stdout: %s", stdoutStr)
|
||||
}
|
||||
if stderrStr := truncateStr(stderr.String(), 500); stderrStr != "" {
|
||||
m.logger.Printf("[ERROR] stderr: %s", stderrStr)
|
||||
m.logger.Printf("[ERROR] [stacks] stderr: %s", stderrStr)
|
||||
}
|
||||
return stdout.String(), fmt.Errorf("exit code %d\nstderr: %s", exitCode, truncateStr(stderr.String(), 500))
|
||||
}
|
||||
@@ -883,6 +881,7 @@ func (m *Manager) execCommand(name string, args ...string) (string, error) {
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
m.logger.Printf("[ERROR] [stacks] execCommand failed: %v", err)
|
||||
return "", fmt.Errorf("exec %s %s: %w\nstderr: %s", name, strings.Join(args, " "), err, stderr.String())
|
||||
}
|
||||
|
||||
@@ -913,20 +912,20 @@ func (m *Manager) logPostStartStatus(name, stackDir string, env []string) {
|
||||
|
||||
output, err := m.composeExecCustomEnv(stackDir, envCopy, "ps", "-a", "--format", "table {{.Name}}\t{{.Image}}\t{{.State}}\t{{.Status}}")
|
||||
if err != nil {
|
||||
m.logger.Printf("[WARN] Post-start status check failed for %s: %v", name, err)
|
||||
m.logger.Printf("[WARN] [stacks] Post-start status check failed for %s: %v", name, err)
|
||||
return
|
||||
}
|
||||
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
if len(lines) <= 1 {
|
||||
m.logger.Printf("[WARN] Post-start status for %s: no containers found", name)
|
||||
m.logger.Printf("[WARN] [stacks] Post-start status for %s: no containers found", name)
|
||||
return
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Stack %s post-start status:", name)
|
||||
m.logger.Printf("[INFO] [stacks] Stack %s post-start status:", name)
|
||||
// Skip header line
|
||||
for _, line := range lines[1:] {
|
||||
m.logger.Printf("[INFO] %s", line)
|
||||
m.logger.Printf("[INFO] [stacks] %s", line)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -962,7 +961,7 @@ func (m *Manager) checkLocalImages(name, stackDir string) {
|
||||
return
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Deploying stack %s — checking %d images...", name, len(images))
|
||||
m.logger.Printf("[INFO] [stacks] Deploying stack %s — checking %d images...", name, len(images))
|
||||
for _, img := range images {
|
||||
cmd := exec.Command("docker", "image", "inspect", img)
|
||||
if err := cmd.Run(); err != nil {
|
||||
@@ -1057,7 +1056,7 @@ func (m *Manager) getCatalogTemplateSlugs() map[string]bool {
|
||||
cacheDir := filepath.Join(m.cfg.Paths.DataDir, "catalog-cache", "templates")
|
||||
entries, err := os.ReadDir(cacheDir)
|
||||
if err != nil {
|
||||
m.logger.Printf("[WARN] Cannot read catalog cache for orphan detection: %v", err)
|
||||
m.logger.Printf("[WARN] [stacks] Cannot read catalog cache for orphan detection: %v", err)
|
||||
return nil
|
||||
}
|
||||
slugs := make(map[string]bool, len(entries))
|
||||
|
||||
@@ -373,7 +373,7 @@ func (o *MigrateOrchestrator) RunEnhancedMigration(
|
||||
// to the same drive the app now lives on — no redundancy, so we clear it.
|
||||
if strings.HasPrefix(cfg.DestinationPath, req.TargetPath) || cfg.DestinationPath == req.TargetPath {
|
||||
tier2WillClear = true
|
||||
o.Logger.Printf("[INFO] Migration %s: Tier 2 will be cleared (dest %s is under target %s)",
|
||||
o.Logger.Printf("[INFO] [storage] Migration %s: Tier 2 will be cleared (dest %s is under target %s)",
|
||||
req.StackName, cfg.DestinationPath, req.TargetPath)
|
||||
}
|
||||
}
|
||||
@@ -408,13 +408,13 @@ func (o *MigrateOrchestrator) RunEnhancedMigration(
|
||||
dstDBDumps := backup.AppDBDumpPath(req.TargetPath, req.StackName)
|
||||
if _, err := os.Stat(srcDBDumps); err == nil {
|
||||
if err := os.MkdirAll(filepath.Dir(dstDBDumps), 0755); err != nil {
|
||||
o.Logger.Printf("[WARN] Migration %s: failed to create DB dump dir: %v", req.StackName, err)
|
||||
o.Logger.Printf("[WARN] [storage] Migration %s: failed to create DB dump dir: %v", req.StackName, err)
|
||||
} else {
|
||||
cmd := exec.Command("rsync", "-a", srcDBDumps+"/", dstDBDumps+"/")
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
o.Logger.Printf("[WARN] Migration %s: DB dump copy failed: %v — %s", req.StackName, err, string(out))
|
||||
o.Logger.Printf("[WARN] [storage] Migration %s: DB dump copy failed: %v — %s", req.StackName, err, string(out))
|
||||
} else {
|
||||
o.Logger.Printf("[INFO] Migration %s: DB dumps copied to %s", req.StackName, dstDBDumps)
|
||||
o.Logger.Printf("[INFO] [storage] Migration %s: DB dumps copied to %s", req.StackName, dstDBDumps)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -422,9 +422,9 @@ func (o *MigrateOrchestrator) RunEnhancedMigration(
|
||||
// 2. Clear Tier 2 if conflict
|
||||
if tier2WillClear {
|
||||
if err := o.Sett.SetCrossDriveConfig(req.StackName, nil); err != nil {
|
||||
o.Logger.Printf("[WARN] Migration %s: failed to clear Tier 2 config: %v", req.StackName, err)
|
||||
o.Logger.Printf("[WARN] [storage] Migration %s: failed to clear Tier 2 config: %v", req.StackName, err)
|
||||
} else {
|
||||
o.Logger.Printf("[INFO] Migration %s: Tier 2 cross-drive config cleared (dest was on same drive)", req.StackName)
|
||||
o.Logger.Printf("[INFO] [storage] Migration %s: Tier 2 cross-drive config cleared (dest was on same drive)", req.StackName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,18 +443,18 @@ func (o *MigrateOrchestrator) RunEnhancedMigration(
|
||||
continue
|
||||
}
|
||||
if err := os.RemoveAll(srcPath); err != nil {
|
||||
o.Logger.Printf("[WARN] Migration %s: failed to delete stale data %s: %v", req.StackName, srcPath, err)
|
||||
o.Logger.Printf("[WARN] [storage] Migration %s: failed to delete stale data %s: %v", req.StackName, srcPath, err)
|
||||
} else {
|
||||
o.Logger.Printf("[INFO] Migration %s: deleted stale data %s", req.StackName, srcPath)
|
||||
o.Logger.Printf("[INFO] [storage] Migration %s: deleted stale data %s", req.StackName, srcPath)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete DB dumps from source
|
||||
if _, err := os.Stat(srcDBDumps); err == nil {
|
||||
if err := os.RemoveAll(srcDBDumps); err != nil {
|
||||
o.Logger.Printf("[WARN] Migration %s: failed to delete stale DB dumps %s: %v", req.StackName, srcDBDumps, err)
|
||||
o.Logger.Printf("[WARN] [storage] Migration %s: failed to delete stale DB dumps %s: %v", req.StackName, srcDBDumps, err)
|
||||
} else {
|
||||
o.Logger.Printf("[INFO] Migration %s: deleted stale DB dumps %s", req.StackName, srcDBDumps)
|
||||
o.Logger.Printf("[INFO] [storage] Migration %s: deleted stale DB dumps %s", req.StackName, srcDBDumps)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -469,7 +469,7 @@ func (o *MigrateOrchestrator) RunEnhancedMigration(
|
||||
}
|
||||
|
||||
if err := o.BackupTrigger.TryRunDriveBackup(context.Background(), req.TargetPath); err != nil {
|
||||
o.Logger.Printf("[WARN] Migration %s: post-migration backup failed: %v", req.StackName, err)
|
||||
o.Logger.Printf("[WARN] [storage] Migration %s: post-migration backup failed: %v", req.StackName, err)
|
||||
progress <- MigrateProgress{
|
||||
Step: "backing_up",
|
||||
Message: "Biztonsági mentés nem indítható (másik mentés fut)",
|
||||
@@ -477,7 +477,7 @@ func (o *MigrateOrchestrator) RunEnhancedMigration(
|
||||
ElapsedSeconds: int(time.Since(start).Seconds()),
|
||||
}
|
||||
} else {
|
||||
o.Logger.Printf("[INFO] Migration %s: post-migration backup completed for %s", req.StackName, req.TargetPath)
|
||||
o.Logger.Printf("[INFO] [storage] Migration %s: post-migration backup completed for %s", req.StackName, req.TargetPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,9 +91,9 @@ func (tx *migrationTx) add(desc string, undoFn func() error) {
|
||||
func (tx *migrationTx) rollback() {
|
||||
for i := len(tx.actions) - 1; i >= 0; i-- {
|
||||
a := tx.actions[i]
|
||||
tx.logger.Printf("[ROLLBACK] %s", a.description)
|
||||
tx.logger.Printf("[INFO] [storage] Rollback: %s", a.description)
|
||||
if err := a.undo(); err != nil {
|
||||
tx.logger.Printf("[ROLLBACK-ERROR] %s: %v", a.description, err)
|
||||
tx.logger.Printf("[ERROR] [storage] Rollback failed: %s: %v", a.description, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,6 +101,8 @@ func (tx *migrationTx) rollback() {
|
||||
// MigrateDrive performs a full drive migration, moving all apps from source to dest.
|
||||
func (dm *DriveMigrator) MigrateDrive(ctx context.Context, req DriveMigrateRequest, progress chan<- DriveMigrateProgress) error {
|
||||
start := time.Now()
|
||||
// TODO: debug should be driven by a dedicated Debug field, not Logger presence.
|
||||
// Currently always true when Logger is set (which is always in practice).
|
||||
debug := dm.Logger != nil
|
||||
|
||||
dbg := func(format string, args ...interface{}) {
|
||||
@@ -249,7 +251,7 @@ func (dm *DriveMigrator) MigrateDrive(ctx context.Context, req DriveMigrateReque
|
||||
}
|
||||
|
||||
dbg("estimated data: %s (%d bytes), free on dest: %s (%d bytes)", bytesHuman(totalBytes), totalBytes, bytesHuman(freeBytes), freeBytes)
|
||||
dm.Logger.Printf("[INFO] Drive migration: %s (%s) → %s (%s), %d apps, ~%s data",
|
||||
dm.Logger.Printf("[INFO] [storage] Drive migration: %s (%s) → %s (%s), %d apps, ~%s data",
|
||||
req.SourcePath, srcLabel, req.DestPath, dstLabel, len(appsToMigrate), bytesHuman(totalBytes))
|
||||
|
||||
tx := &migrationTx{logger: dm.Logger}
|
||||
@@ -261,7 +263,7 @@ func (dm *DriveMigrator) MigrateDrive(ctx context.Context, req DriveMigrateReque
|
||||
for _, app := range appsToMigrate {
|
||||
sendDetail("stopping", "Leállítás: "+app.DisplayName, app.Name, 5)
|
||||
if err := dm.StackProvider.StopStack(app.Name); err != nil {
|
||||
dm.Logger.Printf("[ERROR] Drive migration: failed to stop %s: %v", app.Name, err)
|
||||
dm.Logger.Printf("[ERROR] [storage] Drive migration: failed to stop %s: %v", app.Name, err)
|
||||
// Rollback: restart already stopped apps
|
||||
send("rolling_back", "Hiba a leállításnál, visszagörgetés...", 0)
|
||||
for _, name := range stoppedApps {
|
||||
@@ -274,7 +276,7 @@ func (dm *DriveMigrator) MigrateDrive(ctx context.Context, req DriveMigrateReque
|
||||
tx.add("Restart all stopped apps", func() error {
|
||||
for _, name := range stoppedApps {
|
||||
if err := dm.StackProvider.StartStack(name); err != nil {
|
||||
dm.Logger.Printf("[ROLLBACK-WARN] Failed to restart %s: %v", name, err)
|
||||
dm.Logger.Printf("[WARN] [storage] Rollback: failed to restart %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -365,7 +367,7 @@ func (dm *DriveMigrator) MigrateDrive(ctx context.Context, req DriveMigrateReque
|
||||
if _, err := os.Stat(destAppData); os.IsNotExist(err) {
|
||||
// appdata might not exist for all apps (SSD-only apps that share the drive)
|
||||
// Only warn, don't fail
|
||||
dm.Logger.Printf("[WARN] Drive migration: %s not found on destination (may be SSD-only)", destAppData)
|
||||
dm.Logger.Printf("[WARN] [storage] Drive migration: %s not found on destination (may be SSD-only)", destAppData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +379,7 @@ func (dm *DriveMigrator) MigrateDrive(ctx context.Context, req DriveMigrateReque
|
||||
for i, app := range appsToMigrate {
|
||||
// Guard: verify app still exists
|
||||
if !dm.StackProvider.StackExists(app.Name) {
|
||||
dm.Logger.Printf("[WARN] Drive migration: app %s no longer exists, skipping config update", app.Name)
|
||||
dm.Logger.Printf("[WARN] [storage] Drive migration: app %s no longer exists, skipping config update", app.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -386,7 +388,7 @@ func (dm *DriveMigrator) MigrateDrive(ctx context.Context, req DriveMigrateReque
|
||||
|
||||
oldPath := dm.StackProvider.GetStackHDDPath(app.Name)
|
||||
if err := dm.StackProvider.UpdateStackHDDPath(app.Name, req.DestPath); err != nil {
|
||||
dm.Logger.Printf("[ERROR] Drive migration: failed to update HDD_PATH for %s: %v", app.Name, err)
|
||||
dm.Logger.Printf("[ERROR] [storage] Drive migration: failed to update HDD_PATH for %s: %v", app.Name, err)
|
||||
send("rolling_back", "Konfiguráció frissítése sikertelen, visszagörgetés...", 0)
|
||||
// Rollback config changes
|
||||
for _, name := range configuredApps {
|
||||
@@ -424,7 +426,7 @@ func (dm *DriveMigrator) MigrateDrive(ctx context.Context, req DriveMigrateReque
|
||||
|
||||
// Mark source as decommissioned
|
||||
if err := dm.Sett.SetDecommissioned(req.SourcePath, req.DestPath); err != nil {
|
||||
dm.Logger.Printf("[WARN] Drive migration: failed to mark source as decommissioned: %v", err)
|
||||
dm.Logger.Printf("[WARN] [storage] Drive migration: failed to mark source as decommissioned: %v", err)
|
||||
}
|
||||
tx.add("Clear decommissioned on source", func() error {
|
||||
return dm.Sett.ClearDecommissioned(req.SourcePath)
|
||||
@@ -441,13 +443,13 @@ func (dm *DriveMigrator) MigrateDrive(ctx context.Context, req DriveMigrateReque
|
||||
// Apps that moved (source→dest) with Tier 2 pointing to dest: clear (no redundancy)
|
||||
appHDD := dm.StackProvider.GetStackHDDPath(name)
|
||||
if appHDD == req.DestPath && cfg.DestinationPath == req.DestPath {
|
||||
dm.Logger.Printf("[INFO] Drive migration: clearing Tier 2 for %s (dest same as app drive)", name)
|
||||
dm.Logger.Printf("[INFO] [storage] Drive migration: clearing Tier 2 for %s (dest same as app drive)", name)
|
||||
_ = dm.Sett.SetCrossDriveConfig(name, nil)
|
||||
continue
|
||||
}
|
||||
// Apps on OTHER drives with Tier 2 pointing to source: redirect to dest
|
||||
if cfg.DestinationPath == req.SourcePath {
|
||||
dm.Logger.Printf("[INFO] Drive migration: redirecting Tier 2 for %s from %s to %s", name, req.SourcePath, req.DestPath)
|
||||
dm.Logger.Printf("[INFO] [storage] Drive migration: redirecting Tier 2 for %s from %s to %s", name, req.SourcePath, req.DestPath)
|
||||
cfg.DestinationPath = req.DestPath
|
||||
_ = dm.Sett.SetCrossDriveConfig(name, cfg)
|
||||
}
|
||||
@@ -464,7 +466,7 @@ func (dm *DriveMigrator) MigrateDrive(ctx context.Context, req DriveMigrateReque
|
||||
sendDetail("starting", "Indítás: "+app.DisplayName, app.Name, pct)
|
||||
|
||||
if err := dm.StackProvider.StartStack(app.Name); err != nil {
|
||||
dm.Logger.Printf("[WARN] Drive migration: failed to start %s after migration: %v", app.Name, err)
|
||||
dm.Logger.Printf("[WARN] [storage] Drive migration: failed to start %s after migration: %v", app.Name, err)
|
||||
// Non-fatal — log but continue
|
||||
}
|
||||
}
|
||||
@@ -476,7 +478,7 @@ func (dm *DriveMigrator) MigrateDrive(ctx context.Context, req DriveMigrateReque
|
||||
|
||||
if dm.BackupTrigger != nil {
|
||||
if err := dm.BackupTrigger.TryRunDriveBackup(ctx, req.DestPath); err != nil {
|
||||
dm.Logger.Printf("[WARN] Drive migration: post-migration backup failed: %v", err)
|
||||
dm.Logger.Printf("[WARN] [storage] Drive migration: post-migration backup failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,7 +499,7 @@ func (dm *DriveMigrator) MigrateDrive(ctx context.Context, req DriveMigrateReque
|
||||
}
|
||||
|
||||
elapsed := time.Since(start)
|
||||
dm.Logger.Printf("[INFO] Drive migration complete: %s → %s, %d apps, %s elapsed",
|
||||
dm.Logger.Printf("[INFO] [storage] Drive migration complete: %s → %s, %d apps, %s elapsed",
|
||||
req.SourcePath, req.DestPath, len(appsToMigrate), elapsed.Round(time.Second))
|
||||
|
||||
// --- Step 10: Done ---
|
||||
|
||||
@@ -77,22 +77,22 @@ func maskRepoURL(url string) string {
|
||||
// Start begins the periodic sync loop. Call Stop() to terminate.
|
||||
func (s *Syncer) Start() {
|
||||
if s.cfg.Git.RepoURL == "" {
|
||||
s.logger.Println("[SYNC] Git repo URL is empty — sync disabled (manual mode)")
|
||||
s.logger.Println("[WARN] [sync] Git repo URL is empty — sync disabled (manual mode)")
|
||||
return
|
||||
}
|
||||
|
||||
interval, err := time.ParseDuration(s.cfg.Git.SyncInterval)
|
||||
if err != nil {
|
||||
s.logger.Printf("[SYNC] Invalid sync_interval %q, defaulting to 15m", s.cfg.Git.SyncInterval)
|
||||
s.logger.Printf("[WARN] [sync] Invalid sync_interval %q, defaulting to 15m", s.cfg.Git.SyncInterval)
|
||||
interval = 15 * time.Minute
|
||||
}
|
||||
|
||||
s.logger.Printf("[SYNC] Starting catalog sync (repo: %s, interval: %s)", s.cfg.Git.RepoURL, interval)
|
||||
s.logger.Printf("[INFO] [sync] Starting catalog sync (repo: %s, interval: %s)", s.cfg.Git.RepoURL, interval)
|
||||
|
||||
// Initial sync on startup
|
||||
go func() {
|
||||
result := s.doSync()
|
||||
s.logger.Printf("[SYNC] Initial sync: %s", result.Message)
|
||||
s.logger.Printf("[INFO] [sync] Initial sync: %s", result.Message)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
@@ -101,11 +101,11 @@ func (s *Syncer) Start() {
|
||||
for {
|
||||
select {
|
||||
case <-s.stopCh:
|
||||
s.logger.Println("[SYNC] Sync loop stopped")
|
||||
s.logger.Println("[INFO] [sync] Sync loop stopped")
|
||||
return
|
||||
case <-ticker.C:
|
||||
result := s.doSync()
|
||||
s.logger.Printf("[SYNC] Periodic sync: %s", result.Message)
|
||||
s.logger.Printf("[INFO] [sync] Periodic sync: %s", result.Message)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -207,14 +207,14 @@ func (s *Syncer) doSync() SyncResult {
|
||||
// Step 3: Trigger rescan if anything changed
|
||||
if len(newApps) > 0 || len(updated) > 0 {
|
||||
if err := s.rescanFn(); err != nil {
|
||||
s.logger.Printf("[SYNC] Rescan after sync failed: %v", err)
|
||||
s.logger.Printf("[WARN] [sync] Rescan after sync failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Inject missing deploy fields for updated stacks
|
||||
if len(updated) > 0 && s.postSyncHook != nil {
|
||||
if s.isDebug() {
|
||||
s.logger.Printf("[DEBUG] [SYNC] Post-sync hook: triggering missing field injection for %d stack(s): %v", len(updated), updated)
|
||||
s.logger.Printf("[DEBUG] [sync] Post-sync hook: triggering missing field injection for %d stack(s): %v", len(updated), updated)
|
||||
}
|
||||
s.postSyncHook(updated)
|
||||
}
|
||||
@@ -252,12 +252,12 @@ func (s *Syncer) gitCloneOrPull() error {
|
||||
gitDir := filepath.Join(s.cacheDir, ".git")
|
||||
if _, err := os.Stat(gitDir); os.IsNotExist(err) {
|
||||
// Clone
|
||||
s.logger.Printf("[SYNC] Cloning %s → %s", s.cfg.Git.RepoURL, s.cacheDir)
|
||||
s.logger.Printf("[INFO] [sync] Cloning %s → %s", s.cfg.Git.RepoURL, s.cacheDir)
|
||||
args := []string{"clone", "--depth", "1", "--branch", s.cfg.Git.Branch}
|
||||
repoURL := s.buildRepoURL()
|
||||
args = append(args, repoURL, s.cacheDir)
|
||||
if s.isDebug() {
|
||||
s.logger.Printf("[DEBUG] [SYNC] git clone URL: %s, branch: %s, cacheDir: %s", maskRepoURL(repoURL), s.cfg.Git.Branch, s.cacheDir)
|
||||
s.logger.Printf("[DEBUG] [sync] git clone URL: %s, branch: %s, cacheDir: %s", maskRepoURL(repoURL), s.cfg.Git.Branch, s.cacheDir)
|
||||
}
|
||||
return s.runGit(args...)
|
||||
}
|
||||
@@ -266,9 +266,9 @@ func (s *Syncer) gitCloneOrPull() error {
|
||||
s.removeGitLockFiles()
|
||||
|
||||
// Pull
|
||||
s.logger.Printf("[SYNC] Pulling latest from %s (branch: %s)", s.cfg.Git.RepoURL, s.cfg.Git.Branch)
|
||||
s.logger.Printf("[INFO] [sync] Pulling latest from %s (branch: %s)", s.cfg.Git.RepoURL, s.cfg.Git.Branch)
|
||||
if s.isDebug() {
|
||||
s.logger.Printf("[DEBUG] [SYNC] git fetch --depth 1 origin %s in %s", s.cfg.Git.Branch, s.cacheDir)
|
||||
s.logger.Printf("[DEBUG] [sync] git fetch --depth 1 origin %s in %s", s.cfg.Git.Branch, s.cacheDir)
|
||||
}
|
||||
if err := s.runGitInDir(s.cacheDir, "fetch", "--depth", "1", "origin", s.cfg.Git.Branch); err != nil {
|
||||
return fmt.Errorf("git fetch: %w", err)
|
||||
@@ -288,9 +288,9 @@ func (s *Syncer) removeGitLockFiles() {
|
||||
for _, name := range lockFiles {
|
||||
lockPath := filepath.Join(gitDir, name)
|
||||
if _, err := os.Stat(lockPath); err == nil {
|
||||
s.logger.Printf("[SYNC] Removing stale lock file: %s", lockPath)
|
||||
s.logger.Printf("[WARN] [sync] Removing stale lock file: %s", lockPath)
|
||||
if err := os.Remove(lockPath); err != nil {
|
||||
s.logger.Printf("[SYNC] Failed to remove lock file %s: %v", lockPath, err)
|
||||
s.logger.Printf("[WARN] [sync] Failed to remove lock file %s: %v", lockPath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -328,7 +328,7 @@ func (s *Syncer) copyTemplates() (newApps []string, updated []string, err error)
|
||||
if _, err := os.Stat(dstDir); os.IsNotExist(err) {
|
||||
isNew = true
|
||||
if err := os.MkdirAll(dstDir, 0755); err != nil {
|
||||
s.logger.Printf("[SYNC] Failed to create stack dir %s: %v", dstDir, err)
|
||||
s.logger.Printf("[WARN] [sync] Failed to create stack dir %s: %v", dstDir, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -343,7 +343,7 @@ func (s *Syncer) copyTemplates() (newApps []string, updated []string, err error)
|
||||
|
||||
if _, err := os.Stat(src); os.IsNotExist(err) {
|
||||
if s.isDebug() {
|
||||
s.logger.Printf("[DEBUG] [SYNC] %s/%s: source not found, skipping", appName, filename)
|
||||
s.logger.Printf("[DEBUG] [sync] %s/%s: source not found, skipping", appName, filename)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -355,12 +355,12 @@ func (s *Syncer) copyTemplates() (newApps []string, updated []string, err error)
|
||||
}
|
||||
if changed {
|
||||
anyChanged = true
|
||||
s.logger.Printf("[SYNC] Updated %s/%s", appName, filename)
|
||||
s.logger.Printf("[INFO] [sync] Updated %s/%s", appName, filename)
|
||||
if s.isDebug() {
|
||||
s.logFileHashes(appName, filename, src, dst)
|
||||
}
|
||||
} else if s.isDebug() {
|
||||
s.logger.Printf("[DEBUG] [SYNC] %s/%s: hash match, skipped", appName, filename)
|
||||
s.logger.Printf("[DEBUG] [sync] %s/%s: hash match, skipped", appName, filename)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,11 +383,11 @@ func (s *Syncer) logFileHashes(appName, filename, src, dst string) {
|
||||
srcHash := sha256.Sum256(srcData)
|
||||
dstData, err := os.ReadFile(dst)
|
||||
if err != nil {
|
||||
s.logger.Printf("[DEBUG] [SYNC] %s/%s: src=%s, dst=new file", appName, filename, hex.EncodeToString(srcHash[:8]))
|
||||
s.logger.Printf("[DEBUG] [sync] %s/%s: src=%s, dst=new file", appName, filename, hex.EncodeToString(srcHash[:8]))
|
||||
return
|
||||
}
|
||||
dstHash := sha256.Sum256(dstData)
|
||||
s.logger.Printf("[DEBUG] [SYNC] %s/%s: src=%s, dst=%s (changed)", appName, filename, hex.EncodeToString(srcHash[:8]), hex.EncodeToString(dstHash[:8]))
|
||||
s.logger.Printf("[DEBUG] [sync] %s/%s: src=%s, dst=%s (changed)", appName, filename, hex.EncodeToString(srcHash[:8]), hex.EncodeToString(dstHash[:8]))
|
||||
}
|
||||
|
||||
// copyIfChanged copies src to dst only if the content differs.
|
||||
@@ -430,7 +430,7 @@ func (s *Syncer) runGitInDir(dir string, args ...string) error {
|
||||
cmd.Stdout = io.Discard
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
s.logger.Printf("[SYNC] Running: git %s", maskRepoURL(strings.Join(args, " ")))
|
||||
s.logger.Printf("[DEBUG] [sync] Running: git %s", maskRepoURL(strings.Join(args, " ")))
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("git %s: %w\nstderr: %s", strings.Join(args, " "), err, stderr.String())
|
||||
|
||||
@@ -152,9 +152,6 @@ func (am *AlertManager) Refresh(report *monitor.HealthReport, cfg *config.Config
|
||||
am.mu.Lock()
|
||||
am.alerts = alerts
|
||||
am.mu.Unlock()
|
||||
|
||||
am.logger.Printf("[DEBUG] AlertManager refreshed: %d alerts (%d error, %d warning)",
|
||||
len(alerts), countLevel(alerts, "error"), countLevel(alerts, "warning"))
|
||||
}
|
||||
|
||||
// GetAlerts returns a copy of the current alerts, optionally excluding specific IDs.
|
||||
|
||||
@@ -140,7 +140,7 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
if attempt != nil && attempt.count >= loginMaxAttempts {
|
||||
s.loginAttemptMu.Unlock()
|
||||
s.logger.Printf("[WARN] Login rate limited for %s (%d attempts)", ip, attempt.count)
|
||||
s.logger.Printf("[WARN] [web] Login rate limited for %s (%d attempts)", ip, attempt.count)
|
||||
s.renderLogin(w, "Túl sok sikertelen próbálkozás, próbálja újra 1 perc múlva", "")
|
||||
return
|
||||
}
|
||||
@@ -148,7 +148,7 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
effectiveHash := s.effectivePasswordHash()
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(effectiveHash), []byte(password)); err != nil {
|
||||
s.logger.Printf("[WARN] Failed login from %s", r.RemoteAddr)
|
||||
s.logger.Printf("[WARN] [web] Failed login from %s", r.RemoteAddr)
|
||||
s.loginAttemptMu.Lock()
|
||||
if s.loginAttempts[ip] == nil {
|
||||
s.loginAttempts[ip] = &loginAttempt{}
|
||||
@@ -181,7 +181,7 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
Secure: isSecure,
|
||||
})
|
||||
|
||||
s.logger.Printf("[INFO] Login from %s", r.RemoteAddr)
|
||||
s.logger.Printf("[INFO] [web] Login from %s", r.RemoteAddr)
|
||||
|
||||
// Redirect to ?next= target if provided, otherwise to dashboard
|
||||
redirectTo := "/"
|
||||
@@ -260,9 +260,6 @@ func (s *Server) invalidateAllSessions() {
|
||||
s.sessions = make(map[string]*session)
|
||||
s.sessionsMu.Unlock()
|
||||
s.logger.Printf("[INFO] [web] All sessions invalidated (cleared %d)", count)
|
||||
if s.isDebug() {
|
||||
s.logger.Printf("[DEBUG] [web] invalidated all sessions (cleared %d)", count)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) cleanupSessions() {
|
||||
@@ -310,7 +307,7 @@ func (s *Server) renderLogin(w http.ResponseWriter, errorMsg, flashMsg string) {
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if err := s.tmpl.ExecuteTemplate(w, "login", data); err != nil {
|
||||
s.logger.Printf("[ERROR] Template error (login): %v", err)
|
||||
s.logger.Printf("[ERROR] [web] Template error (login): %v", err)
|
||||
http.Error(w, "Internal error", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,14 +95,14 @@ func (s *Server) apiExportEstimate(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
stackName := r.URL.Query().Get("stack")
|
||||
drive := r.URL.Query().Get("drive")
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiExportEstimate: stack=%q drive=%q", stackName, drive)
|
||||
s.logger.Printf("[DEBUG] [web] apiExportEstimate: stack=%q drive=%q", stackName, drive)
|
||||
if stackName == "" || drive == "" {
|
||||
jsonError(w, "Missing stack or drive parameter", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !s.isValidDrivePath(drive) {
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiExportEstimate: invalid drive path %q", drive)
|
||||
s.logger.Printf("[DEBUG] [web] apiExportEstimate: invalid drive path %q", drive)
|
||||
jsonError(w, "Invalid drive path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -110,12 +110,12 @@ func (s *Server) apiExportEstimate(w http.ResponseWriter, r *http.Request) {
|
||||
est, err := s.appExporter.EstimateExport(stackName, drive)
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERROR] [web] Export estimate failed for %s: %v", stackName, err)
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiExportEstimate error: %v", err)
|
||||
s.logger.Printf("[DEBUG] [web] apiExportEstimate error: %v", err)
|
||||
jsonError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiExportEstimate: total=%s free=%s fits=%v",
|
||||
s.logger.Printf("[DEBUG] [web] apiExportEstimate: total=%s free=%s fits=%v",
|
||||
est.TotalSizeHuman, est.DestFreeHuman, est.FitsOnDest)
|
||||
jsonResponse(w, map[string]interface{}{
|
||||
"ok": true,
|
||||
@@ -137,12 +137,12 @@ func (s *Server) apiExportStart(w http.ResponseWriter, r *http.Request) {
|
||||
StopApp bool `json:"stop_app"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiExportStart: invalid body: %v", err)
|
||||
s.logger.Printf("[DEBUG] [web] apiExportStart: invalid body: %v", err)
|
||||
jsonError(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiExportStart: stack=%q drive=%q encrypted=%v stopApp=%v",
|
||||
s.logger.Printf("[DEBUG] [web] apiExportStart: stack=%q drive=%q encrypted=%v stopApp=%v",
|
||||
req.StackName, req.DestDrive, req.Password != "", req.StopApp)
|
||||
|
||||
if req.StackName == "" || req.DestDrive == "" {
|
||||
@@ -151,7 +151,7 @@ func (s *Server) apiExportStart(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if !s.isValidDrivePath(req.DestDrive) {
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiExportStart: invalid drive path %q", req.DestDrive)
|
||||
s.logger.Printf("[DEBUG] [web] apiExportStart: invalid drive path %q", req.DestDrive)
|
||||
jsonError(w, "Invalid drive path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -164,12 +164,12 @@ func (s *Server) apiExportStart(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERROR] [web] Export start failed for %s: %v", req.StackName, err)
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiExportStart error: %v", err)
|
||||
s.logger.Printf("[DEBUG] [web] apiExportStart error: %v", err)
|
||||
jsonError(w, err.Error(), http.StatusConflict)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] Export started for %s to %s", req.StackName, req.DestDrive)
|
||||
s.logger.Printf("[INFO] [web] Export started for %s to %s", req.StackName, req.DestDrive)
|
||||
jsonResponse(w, map[string]interface{}{"ok": true})
|
||||
}
|
||||
|
||||
@@ -230,23 +230,23 @@ func (s *Server) apiExportManifest(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiExportManifest: path=%q hasPassword=%v", req.Path, req.Password != "")
|
||||
s.logger.Printf("[DEBUG] [web] apiExportManifest: path=%q hasPassword=%v", req.Path, req.Password != "")
|
||||
|
||||
// Security: validate path is within a registered exports directory
|
||||
if !s.isValidExportPath(req.Path) {
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiExportManifest: invalid path %q", req.Path)
|
||||
s.logger.Printf("[DEBUG] [web] apiExportManifest: invalid path %q", req.Path)
|
||||
jsonError(w, "Invalid bundle path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
encrypted, _ := appexport.IsEncryptedFAB(req.Path)
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiExportManifest: encrypted=%v", encrypted)
|
||||
s.logger.Printf("[DEBUG] [web] apiExportManifest: encrypted=%v", encrypted)
|
||||
|
||||
var manifest *appexport.Manifest
|
||||
var err error
|
||||
if encrypted {
|
||||
if req.Password == "" {
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiExportManifest: encrypted, needs password")
|
||||
s.logger.Printf("[DEBUG] [web] apiExportManifest: encrypted, needs password")
|
||||
jsonResponse(w, map[string]interface{}{
|
||||
"ok": true,
|
||||
"encrypted": true,
|
||||
@@ -260,12 +260,12 @@ func (s *Server) apiExportManifest(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiExportManifest: error: %v", err)
|
||||
s.logger.Printf("[DEBUG] [web] apiExportManifest: error: %v", err)
|
||||
jsonError(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiExportManifest: app=%s display=%s size=%d",
|
||||
s.logger.Printf("[DEBUG] [web] apiExportManifest: app=%s display=%s size=%d",
|
||||
manifest.AppName, manifest.DisplayName, manifest.TotalSizeBytes)
|
||||
jsonResponse(w, map[string]interface{}{
|
||||
"ok": true,
|
||||
@@ -285,12 +285,12 @@ func (s *Server) apiImportStart(w http.ResponseWriter, r *http.Request) {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiImportStart: invalid body: %v", err)
|
||||
s.logger.Printf("[DEBUG] [web] apiImportStart: invalid body: %v", err)
|
||||
jsonError(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiImportStart: path=%q hasPassword=%v", req.Path, req.Password != "")
|
||||
s.logger.Printf("[DEBUG] [web] apiImportStart: path=%q hasPassword=%v", req.Path, req.Password != "")
|
||||
|
||||
if req.Path == "" {
|
||||
jsonError(w, "Missing path", http.StatusBadRequest)
|
||||
@@ -298,7 +298,7 @@ func (s *Server) apiImportStart(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if !s.isValidExportPath(req.Path) {
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiImportStart: invalid path %q", req.Path)
|
||||
s.logger.Printf("[DEBUG] [web] apiImportStart: invalid path %q", req.Path)
|
||||
jsonError(w, "Invalid bundle path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -309,12 +309,12 @@ func (s *Server) apiImportStart(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERROR] [web] Import start failed for %s: %v", req.Path, err)
|
||||
s.logger.Printf("[DEBUG] [handler_export] apiImportStart error: %v", err)
|
||||
s.logger.Printf("[DEBUG] [web] apiImportStart error: %v", err)
|
||||
jsonError(w, err.Error(), http.StatusConflict)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] Import started from %s", req.Path)
|
||||
s.logger.Printf("[INFO] [web] Import started from %s", req.Path)
|
||||
jsonResponse(w, map[string]interface{}{"ok": true})
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ func (s *Server) apiRestoreSkip(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Println("[INFO] User skipped DR restore — entering normal mode")
|
||||
s.logger.Println("[INFO] [web] User skipped DR restore — entering normal mode")
|
||||
s.clearRestoreMode()
|
||||
|
||||
jsonResponse(w, map[string]interface{}{
|
||||
@@ -122,14 +122,14 @@ func (s *Server) apiRestoreSkip(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// executeAllRestores runs the restore for each pending app sequentially.
|
||||
func (s *Server) executeAllRestores() {
|
||||
s.logger.Println("[INFO] Starting DR restore for all apps")
|
||||
s.logger.Println("[INFO] [web] Starting DR restore for all apps")
|
||||
restoreStart := time.Now()
|
||||
|
||||
s.restoreMu.RLock()
|
||||
plan := s.restorePlan
|
||||
s.restoreMu.RUnlock()
|
||||
if plan == nil {
|
||||
s.logger.Println("[WARN] Restore plan cleared before execution could start")
|
||||
s.logger.Println("[WARN] [web] Restore plan cleared before execution could start")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ func (s *Server) executeAllRestores() {
|
||||
}
|
||||
|
||||
plan.UpdateApp(app.Name, "restoring", "")
|
||||
s.logger.Printf("[INFO] Restoring app %s (%s)", app.Name, app.DisplayName)
|
||||
s.logger.Printf("[INFO] [web] Restoring app %s (%s)", app.Name, app.DisplayName)
|
||||
appStart := time.Now()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
@@ -164,14 +164,14 @@ func (s *Server) executeAllRestores() {
|
||||
|
||||
if err != nil {
|
||||
plan.UpdateApp(app.Name, "failed", err.Error())
|
||||
s.logger.Printf("[ERROR] Restore failed for %s: %v", app.Name, err)
|
||||
s.logger.Printf("[ERROR] [web] Restore failed for %s: %v", app.Name, err)
|
||||
if s.isDebug() {
|
||||
s.logger.Printf("[DEBUG] [web] executeAllRestores: app=%s failed after %s", app.Name, time.Since(appStart))
|
||||
}
|
||||
failCount++
|
||||
} else {
|
||||
plan.UpdateApp(app.Name, "done", "")
|
||||
s.logger.Printf("[INFO] Restore completed for %s", app.Name)
|
||||
s.logger.Printf("[INFO] [web] Restore completed for %s", app.Name)
|
||||
if s.isDebug() {
|
||||
s.logger.Printf("[DEBUG] [web] executeAllRestores: app=%s completed in %s", app.Name, time.Since(appStart))
|
||||
}
|
||||
@@ -180,7 +180,7 @@ func (s *Server) executeAllRestores() {
|
||||
}
|
||||
|
||||
plan.SetStatus("done")
|
||||
s.logger.Println("[INFO] All app restores completed")
|
||||
s.logger.Println("[INFO] [web] All app restores completed")
|
||||
if s.isDebug() {
|
||||
s.logger.Printf("[DEBUG] [web] executeAllRestores: total=%d success=%d fail=%d elapsed=%s", pendingCount, successCount, failCount, time.Since(restoreStart))
|
||||
}
|
||||
@@ -193,7 +193,7 @@ func (s *Server) executeAllRestores() {
|
||||
// Re-scan stacks so dashboard picks up restored apps
|
||||
if s.stackMgr != nil {
|
||||
if err := s.stackMgr.ScanStacks(); err != nil {
|
||||
s.logger.Printf("[WARN] Post-restore stack scan failed: %v", err)
|
||||
s.logger.Printf("[WARN] [web] Post-restore stack scan failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -992,7 +992,7 @@ func (s *Server) settingsCrossBackupHandler(w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
}
|
||||
if !validDest {
|
||||
s.logger.Printf("[WARN] Cross-drive backup: rejected invalid dest path %q for %s", destPath, name)
|
||||
s.logger.Printf("[WARN] [web] Cross-drive backup: rejected invalid dest path %q for %s", destPath, name)
|
||||
http.Redirect(w, r, "/stacks/"+name+"/deploy?flash_error="+url.QueryEscape("Érvénytelen célútvonal: "+destPath), http.StatusFound)
|
||||
return
|
||||
}
|
||||
@@ -1016,12 +1016,12 @@ func (s *Server) settingsCrossBackupHandler(w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
|
||||
if err := s.settings.SetCrossDriveConfig(name, cfg); err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to save cross-drive config for %s: %v", name, err)
|
||||
s.logger.Printf("[ERROR] [web] Failed to save cross-drive config for %s: %v", name, err)
|
||||
http.Redirect(w, r, "/stacks/"+name+"/deploy?flash_error=Hiba+a+ment%C3%A9si+be%C3%A1ll%C3%ADt%C3%A1s+ment%C3%A9sakor", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] Cross-drive backup config saved for %s: dest=%s schedule=%s enabled=%v",
|
||||
s.logger.Printf("[INFO] [web] Cross-drive backup config saved for %s: dest=%s schedule=%s enabled=%v",
|
||||
name, destPath, schedule, enabled)
|
||||
|
||||
http.Redirect(w, r, "/stacks/"+name+"/deploy?flash=Ment%C3%A9si+be%C3%A1ll%C3%ADt%C3%A1s+mentve.", http.StatusFound)
|
||||
@@ -1047,11 +1047,11 @@ func (s *Server) backupRestoreHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[WARN] Restore requested: stack=%s, snapshot=%s from %s", stackName, snapshotID, r.RemoteAddr)
|
||||
s.logger.Printf("[WARN] [web] Restore requested: stack=%s, snapshot=%s from %s", stackName, snapshotID, r.RemoteAddr)
|
||||
|
||||
start := time.Now()
|
||||
if err := s.backupMgr.RestoreApp(stackName, snapshotID); err != nil {
|
||||
s.logger.Printf("[ERROR] Restore failed: %v", err)
|
||||
s.logger.Printf("[ERROR] [web] Restore failed: %v", err)
|
||||
if s.isDebug() {
|
||||
s.logger.Printf("[DEBUG] [web] backupRestoreHandler: stack=%s failed after %s", stackName, time.Since(start))
|
||||
}
|
||||
@@ -1223,7 +1223,7 @@ func (s *Server) settingsPasswordHandler(w http.ResponseWriter, r *http.Request)
|
||||
// Generate bcrypt hash
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(newPassword), 10)
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to hash new password: %v", err)
|
||||
s.logger.Printf("[ERROR] [web] Failed to hash new password: %v", err)
|
||||
data["PasswordError"] = "Belső hiba a jelszó mentésekor"
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
@@ -1231,13 +1231,13 @@ func (s *Server) settingsPasswordHandler(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// Save to settings.json
|
||||
if err := s.settings.SetPasswordHash(string(hash)); err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to save password to settings.json: %v", err)
|
||||
s.logger.Printf("[ERROR] [web] Failed to save password to settings.json: %v", err)
|
||||
data["PasswordError"] = "Belső hiba a jelszó mentésekor"
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] Password changed via settings page from %s", r.RemoteAddr)
|
||||
s.logger.Printf("[INFO] [web] Password changed via settings page from %s", r.RemoteAddr)
|
||||
|
||||
// Invalidate all sessions (force re-login)
|
||||
s.invalidateAllSessions()
|
||||
@@ -1297,20 +1297,20 @@ func (s *Server) settingsNotificationsHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
if err := s.settings.SetNotificationPrefs(prefs); err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to save notification prefs: %v", err)
|
||||
s.logger.Printf("[ERROR] [web] Failed to save notification prefs: %v", err)
|
||||
data := s.settingsData()
|
||||
data["NotificationError"] = "Hiba a beállítások mentésekor"
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] Notification preferences updated: email=%s, events=%v", email, enabledEvents)
|
||||
s.logger.Printf("[INFO] [web] Notification preferences updated: email=%s, events=%v", email, enabledEvents)
|
||||
|
||||
// Sync preferences to hub
|
||||
data := s.settingsData()
|
||||
if s.notifier != nil && s.notifier.IsEnabled() {
|
||||
if err := s.notifier.SyncPreferences(email, enabledEvents, cooldownHours); err != nil {
|
||||
s.logger.Printf("[WARN] Failed to sync preferences to hub: %v", err)
|
||||
s.logger.Printf("[WARN] [web] Failed to sync preferences to hub: %v", err)
|
||||
data["NotificationSuccess"] = fmt.Sprintf("Értesítési beállítások mentve (helyi). A központi szinkronizálás sikertelen: %v", err)
|
||||
} else {
|
||||
data["NotificationSuccess"] = "Értesítési beállítások mentve."
|
||||
@@ -1332,7 +1332,7 @@ func (s *Server) settingsNotificationsTestHandler(w http.ResponseWriter, r *http
|
||||
|
||||
err := s.notifier.SendTest()
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERROR] Test notification failed: %v", err)
|
||||
s.logger.Printf("[ERROR] [web] Test notification failed: %v", err)
|
||||
data["NotificationError"] = fmt.Sprintf("Teszt email küldése sikertelen: %v", err)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
@@ -1486,7 +1486,7 @@ func (s *Server) settingsStorageAddHandler(w http.ResponseWriter, r *http.Reques
|
||||
|
||||
// 5. Soft warning if not under /mnt/
|
||||
if !strings.HasPrefix(path, "/mnt/") {
|
||||
s.logger.Printf("[WARN] Storage path %s is not under /mnt/ — unusual but allowed", path)
|
||||
s.logger.Printf("[WARN] [web] Storage path %s is not under /mnt/ — unusual but allowed", path)
|
||||
}
|
||||
|
||||
sp := settings.StoragePath{
|
||||
@@ -1498,13 +1498,13 @@ func (s *Server) settingsStorageAddHandler(w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
|
||||
if err := s.settings.AddStoragePath(sp); err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to add storage path: %v", err)
|
||||
s.logger.Printf("[ERROR] [web] Failed to add storage path: %v", err)
|
||||
data["StorageError"] = "Hiba a mentés során."
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] Storage path added: %s (%s)", path, label)
|
||||
s.logger.Printf("[INFO] [web] Storage path added: %s (%s)", path, label)
|
||||
go s.SyncFileBrowserMounts()
|
||||
http.Redirect(w, r, "/settings?storage_msg=success&storage_detail="+url.QueryEscape("Adattároló sikeresen hozzáadva: "+path), http.StatusFound)
|
||||
}
|
||||
@@ -1549,7 +1549,7 @@ func (s *Server) settingsStorageRemoveHandler(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] Storage path removed: %s", path)
|
||||
s.logger.Printf("[INFO] [web] Storage path removed: %s", path)
|
||||
// Sync FileBrowser mounts after storage path removal
|
||||
go s.SyncFileBrowserMounts()
|
||||
http.Redirect(w, r, "/settings?storage_msg=success&storage_detail="+url.QueryEscape("Adattároló eltávolítva: "+path), http.StatusFound)
|
||||
@@ -1564,7 +1564,7 @@ func (s *Server) settingsStorageDefaultHandler(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
if err := s.settings.SetDefaultStoragePath(path); err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to set default storage path: %v", err)
|
||||
s.logger.Printf("[ERROR] [web] Failed to set default storage path: %v", err)
|
||||
http.Redirect(w, r, "/settings", http.StatusFound)
|
||||
return
|
||||
}
|
||||
@@ -1582,7 +1582,7 @@ func (s *Server) settingsStorageSchedulableHandler(w http.ResponseWriter, r *htt
|
||||
}
|
||||
|
||||
if err := s.settings.SetSchedulable(path, schedulable); err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to update schedulable: %v", err)
|
||||
s.logger.Printf("[ERROR] [web] Failed to update schedulable: %v", err)
|
||||
http.Redirect(w, r, "/settings", http.StatusFound)
|
||||
return
|
||||
}
|
||||
@@ -1607,14 +1607,14 @@ func (s *Server) settingsStorageLabelHandler(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
|
||||
if err := s.settings.SetStorageLabel(path, label); err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to set storage label: %v", err)
|
||||
s.logger.Printf("[ERROR] [web] Failed to set storage label: %v", err)
|
||||
data := s.settingsData()
|
||||
data["StorageError"] = "Hiba a megnevezés mentésekor."
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] Storage label updated: %s → %q", path, label)
|
||||
s.logger.Printf("[INFO] [web] Storage label updated: %s → %q", path, label)
|
||||
http.Redirect(w, r, "/settings?storage_msg=success&storage_detail="+url.QueryEscape("Megnevezés módosítva: "+label), http.StatusFound)
|
||||
}
|
||||
|
||||
@@ -1641,7 +1641,7 @@ func (s *Server) syncFileBrowserMounts(resetDBOnChange bool) {
|
||||
|
||||
// Check if FileBrowser stack exists
|
||||
if _, err := os.Stat(composePath); os.IsNotExist(err) {
|
||||
s.logger.Printf("[WARN] FileBrowser stack not found at %s — skipping mount sync", composePath)
|
||||
s.logger.Printf("[WARN] [web] FileBrowser stack not found at %s — skipping mount sync", composePath)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1651,7 +1651,7 @@ func (s *Server) syncFileBrowserMounts(resetDBOnChange bool) {
|
||||
// Use domain from controller config
|
||||
domain := s.cfg.Customer.Domain
|
||||
if domain == "" {
|
||||
s.logger.Printf("[WARN] Cannot sync FileBrowser mounts — customer domain not configured")
|
||||
s.logger.Printf("[WARN] [web] Cannot sync FileBrowser mounts — customer domain not configured")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1675,7 +1675,7 @@ func (s *Server) syncFileBrowserMounts(resetDBOnChange bool) {
|
||||
}
|
||||
|
||||
if err := os.WriteFile(configPath, []byte(fbConfig), 0644); err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to write FileBrowser config: %v", err)
|
||||
s.logger.Printf("[ERROR] [web] Failed to write FileBrowser config: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1687,7 +1687,7 @@ func (s *Server) syncFileBrowserMounts(resetDBOnChange bool) {
|
||||
// Generate and write compose (includes config.yaml mount)
|
||||
compose := generateFileBrowserCompose(domain, storageMounts)
|
||||
if err := os.WriteFile(composePath, []byte(compose), 0644); err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to write FileBrowser compose: %v", err)
|
||||
s.logger.Printf("[ERROR] [web] Failed to write FileBrowser compose: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1695,13 +1695,13 @@ func (s *Server) syncFileBrowserMounts(resetDBOnChange bool) {
|
||||
// nuke the data volume so FileBrowser re-reads config.yaml from scratch.
|
||||
// Normal operations skip this to preserve user accounts, permissions, and share links.
|
||||
if sourcesChanged && resetDBOnChange {
|
||||
s.logger.Printf("[INFO] FileBrowser sources changed — resetting database (restore mode)")
|
||||
s.logger.Printf("[INFO] [web] FileBrowser sources changed — resetting database (restore mode)")
|
||||
resetCtx, resetCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer resetCancel()
|
||||
stop := exec.CommandContext(resetCtx, "docker", "compose", "down", "-v")
|
||||
stop.Dir = stackDir
|
||||
if out, err := stop.CombinedOutput(); err != nil {
|
||||
s.logger.Printf("[WARN] FileBrowser down -v: %s — %v", strings.TrimSpace(string(out)), err)
|
||||
s.logger.Printf("[WARN] [web] FileBrowser down -v: %s — %v", strings.TrimSpace(string(out)), err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1711,9 +1711,9 @@ func (s *Server) syncFileBrowserMounts(resetDBOnChange bool) {
|
||||
cmd := exec.CommandContext(ctx, "docker", "compose", "up", "-d", "--force-recreate", "--remove-orphans")
|
||||
cmd.Dir = stackDir
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to recreate FileBrowser: %s — %v", string(out), err)
|
||||
s.logger.Printf("[ERROR] [web] Failed to recreate FileBrowser: %s — %v", string(out), err)
|
||||
} else {
|
||||
s.logger.Printf("[INFO] FileBrowser mounts synced — %d storage path(s), config updated", len(paths))
|
||||
s.logger.Printf("[INFO] [web] FileBrowser mounts synced — %d storage path(s), config updated", len(paths))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,11 +118,11 @@ func NewServer(cfg *config.Config, stackMgr *stacks.Manager, cpuCollector *syste
|
||||
|
||||
// Log auth source on startup
|
||||
if sett != nil && sett.GetPasswordHash() != "" {
|
||||
logger.Printf("[INFO] Auth: using password from settings.json")
|
||||
logger.Printf("[INFO] [web] Auth: using password from settings.json")
|
||||
} else if cfg.Web.PasswordHash != "" {
|
||||
logger.Printf("[INFO] Auth: using password from controller.yaml")
|
||||
logger.Printf("[INFO] [web] Auth: using password from controller.yaml")
|
||||
} else {
|
||||
logger.Printf("[INFO] Auth: no password configured — dashboard is open")
|
||||
logger.Printf("[INFO] [web] Auth: no password configured — dashboard is open")
|
||||
}
|
||||
|
||||
// Sync FileBrowser config on startup to ensure mounts and sources are current.
|
||||
@@ -416,7 +416,7 @@ func (s *Server) serveCatchAll(w http.ResponseWriter, r *http.Request, host stri
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
if err := s.tmpl.ExecuteTemplate(w, "catchall", data); err != nil {
|
||||
s.logger.Printf("[ERROR] Catch-all template error: %v", err)
|
||||
s.logger.Printf("[ERROR] [web] Catch-all template error: %v", err)
|
||||
http.Error(w, "Internal error", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
@@ -459,7 +459,7 @@ func (s *Server) primaryHDDPath() string {
|
||||
func (s *Server) render(w http.ResponseWriter, name string, data interface{}) {
|
||||
var buf bytes.Buffer
|
||||
if err := s.tmpl.ExecuteTemplate(&buf, name, data); err != nil {
|
||||
s.logger.Printf("[ERROR] Template error (%s): %v", name, err)
|
||||
s.logger.Printf("[ERROR] [web] Template error (%s): %v", name, err)
|
||||
http.Error(w, "Internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -477,7 +477,7 @@ func (s *Server) executeTemplate(w http.ResponseWriter, r *http.Request, name st
|
||||
data["CSRFToken"] = s.csrfToken(r)
|
||||
var buf bytes.Buffer
|
||||
if err := s.tmpl.ExecuteTemplate(&buf, name, data); err != nil {
|
||||
s.logger.Printf("[ERROR] Template error (%s): %v", name, err)
|
||||
s.logger.Printf("[ERROR] [web] Template error (%s): %v", name, err)
|
||||
http.Error(w, "Internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ func (s *Server) storageScanAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
result, err := storage.ScanDisks(s.logger, s.cfg.Logging.Level == "debug")
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERROR] storageScan: %v", err)
|
||||
s.logger.Printf("[ERROR] [web] storageScan: %v", err)
|
||||
jsonError(w, "Meghajtók keresése sikertelen: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -256,7 +256,7 @@ func (s *Server) storageInitAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] Storage init started: device=%s mountName=%s by %s", req.DevicePath, req.MountName, r.RemoteAddr)
|
||||
s.logger.Printf("[INFO] [web] Storage init started: device=%s mountName=%s by %s", req.DevicePath, req.MountName, r.RemoteAddr)
|
||||
|
||||
fmtReq := storage.FormatRequest{
|
||||
DevicePath: req.DevicePath,
|
||||
@@ -274,7 +274,7 @@ func (s *Server) storageInitAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if scanResult, scanErr := storage.ScanDisks(s.logger, s.cfg.Logging.Level == "debug"); scanErr == nil {
|
||||
for _, disk := range scanResult.AvailableDisks {
|
||||
if disk.Path == req.DevicePath && len(disk.Partitions) == 1 && disk.Partitions[0].FSType == "" {
|
||||
s.logger.Printf("[INFO] Disk %s has 1 empty partition (%s) — skipping repartition",
|
||||
s.logger.Printf("[INFO] [web] Disk %s has 1 empty partition (%s) — skipping repartition",
|
||||
req.DevicePath, disk.Partitions[0].Path)
|
||||
fmtReq.DevicePath = disk.Partitions[0].Path
|
||||
fmtReq.CreatePartition = false
|
||||
@@ -297,7 +297,7 @@ func (s *Server) storageInitAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
close(progressCh)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERROR] Storage init failed: %v", err)
|
||||
s.logger.Printf("[ERROR] [web] Storage init failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -314,9 +314,9 @@ func (s *Server) storageInitAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
AddedAt: time.Now().UTC().Format(time.RFC3339),
|
||||
}
|
||||
if err := s.settings.AddStoragePath(sp); err != nil {
|
||||
s.logger.Printf("[WARN] Failed to register storage path after init: %v", err)
|
||||
s.logger.Printf("[WARN] [web] Failed to register storage path after init: %v", err)
|
||||
} else {
|
||||
s.logger.Printf("[INFO] Storage path registered: %s (%s)", mountPath, label)
|
||||
s.logger.Printf("[INFO] [web] Storage path registered: %s (%s)", mountPath, label)
|
||||
// Sync FileBrowser mounts with new storage path
|
||||
s.SyncFileBrowserMounts()
|
||||
}
|
||||
@@ -505,7 +505,7 @@ func (s *Server) storageMigrateAPIHandler(w http.ResponseWriter, r *http.Request
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] Migration started: stack=%s from=%s to=%s by %s",
|
||||
s.logger.Printf("[INFO] [web] Migration started: stack=%s from=%s to=%s by %s",
|
||||
req.StackName, currentHDDPath, req.TargetPath, r.RemoteAddr)
|
||||
|
||||
migrReq := storage.MigrateRequest{
|
||||
@@ -551,9 +551,9 @@ func (s *Server) storageMigrateAPIHandler(w http.ResponseWriter, r *http.Request
|
||||
}()
|
||||
|
||||
if err := orch.RunEnhancedMigration(migrReq, stopFn, startFn, updateFn, opts, progressCh); err != nil {
|
||||
s.logger.Printf("[ERROR] Migration failed: stack=%s: %v", req.StackName, err)
|
||||
s.logger.Printf("[ERROR] [web] Migration failed: stack=%s: %v", req.StackName, err)
|
||||
} else {
|
||||
s.logger.Printf("[INFO] Migration complete: stack=%s → %s", req.StackName, req.TargetPath)
|
||||
s.logger.Printf("[INFO] [web] Migration complete: stack=%s → %s", req.StackName, req.TargetPath)
|
||||
// Sync FileBrowser mounts (storage paths may now have new app data)
|
||||
go s.SyncFileBrowserMounts()
|
||||
}
|
||||
@@ -879,7 +879,7 @@ func (s *Server) staleDataCleanupHandler(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// Safety: never delete protected top-level dirs
|
||||
if protected != nil && protected[cleanPath] {
|
||||
s.logger.Printf("[WARN] Refusing to delete protected HDD path: %s", cleanPath)
|
||||
s.logger.Printf("[WARN] [web] Refusing to delete protected HDD path: %s", cleanPath)
|
||||
errors = append(errors, fmt.Sprintf("Védett útvonal, nem törölhető: %s", cleanPath))
|
||||
continue
|
||||
}
|
||||
@@ -893,10 +893,10 @@ func (s *Server) staleDataCleanupHandler(w http.ResponseWriter, r *http.Request)
|
||||
size := dirSizeInt64(cleanPath)
|
||||
|
||||
if err := os.RemoveAll(cleanPath); err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to remove stale data %s: %v", cleanPath, err)
|
||||
s.logger.Printf("[ERROR] [web] Failed to remove stale data %s: %v", cleanPath, err)
|
||||
errors = append(errors, fmt.Sprintf("Törlés sikertelen: %s — %v", cleanPath, err))
|
||||
} else {
|
||||
s.logger.Printf("[INFO] Removed stale data: %s (%s) for stack %s", cleanPath, dirSizeBytesHuman(size), req.StackName)
|
||||
s.logger.Printf("[INFO] [web] Removed stale data: %s (%s) for stack %s", cleanPath, dirSizeBytesHuman(size), req.StackName)
|
||||
deleted = append(deleted, cleanPath)
|
||||
totalFreed += size
|
||||
}
|
||||
@@ -952,7 +952,7 @@ func (s *Server) storageAttachMountRawHandler(w http.ResponseWriter, r *http.Req
|
||||
rawPath, err := storage.MountRaw(req.DevicePath)
|
||||
if err != nil {
|
||||
s.diskJobMu.Unlock()
|
||||
s.logger.Printf("[ERROR] storageAttachMountRaw: %v", err)
|
||||
s.logger.Printf("[ERROR] [web] storageAttachMountRaw: %v", err)
|
||||
jsonError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -960,7 +960,7 @@ func (s *Server) storageAttachMountRawHandler(w http.ResponseWriter, r *http.Req
|
||||
s.activeRawMount = rawPath
|
||||
s.diskJobMu.Unlock()
|
||||
|
||||
s.logger.Printf("[INFO] Raw mount for attach: %s → %s", req.DevicePath, rawPath)
|
||||
s.logger.Printf("[INFO] [web] Raw mount for attach: %s → %s", req.DevicePath, rawPath)
|
||||
|
||||
jsonResponse(w, map[string]interface{}{
|
||||
"ok": true,
|
||||
@@ -1026,7 +1026,7 @@ func (s *Server) storageAttachMkdirHandler(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] Created directory for attach: %s", createdPath)
|
||||
s.logger.Printf("[INFO] [web] Created directory for attach: %s", createdPath)
|
||||
|
||||
jsonResponse(w, map[string]interface{}{
|
||||
"ok": true,
|
||||
@@ -1064,7 +1064,7 @@ func (s *Server) storageAttachAPIHandler(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] Storage attach started: device=%s mountName=%s subPath=%s by %s",
|
||||
s.logger.Printf("[INFO] [web] Storage attach started: device=%s mountName=%s subPath=%s by %s",
|
||||
req.DevicePath, req.MountName, req.SubPath, r.RemoteAddr)
|
||||
|
||||
attachReq := storage.AttachRequest{
|
||||
@@ -1089,7 +1089,7 @@ func (s *Server) storageAttachAPIHandler(w http.ResponseWriter, r *http.Request)
|
||||
close(progressCh)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERROR] Storage attach failed: %v", err)
|
||||
s.logger.Printf("[ERROR] [web] Storage attach failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1111,9 +1111,9 @@ func (s *Server) storageAttachAPIHandler(w http.ResponseWriter, r *http.Request)
|
||||
AddedAt: time.Now().UTC().Format(time.RFC3339),
|
||||
}
|
||||
if err := s.settings.AddStoragePath(sp); err != nil {
|
||||
s.logger.Printf("[WARN] Failed to register storage path after attach: %v", err)
|
||||
s.logger.Printf("[WARN] [web] Failed to register storage path after attach: %v", err)
|
||||
} else {
|
||||
s.logger.Printf("[INFO] Storage path registered: %s (%s)", mountPath, label)
|
||||
s.logger.Printf("[INFO] [web] Storage path registered: %s (%s)", mountPath, label)
|
||||
s.SyncFileBrowserMounts()
|
||||
}
|
||||
}()
|
||||
@@ -1169,9 +1169,9 @@ func (s *Server) storageAttachCancelHandler(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
if rawMount != "" {
|
||||
if err := storage.CleanupRawMount(rawMount); err != nil {
|
||||
s.logger.Printf("[WARN] Failed to cleanup raw mount %s: %v", rawMount, err)
|
||||
s.logger.Printf("[WARN] [web] Failed to cleanup raw mount %s: %v", rawMount, err)
|
||||
} else {
|
||||
s.logger.Printf("[INFO] Cleaned up raw mount: %s", rawMount)
|
||||
s.logger.Printf("[INFO] [web] Cleaned up raw mount: %s", rawMount)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1217,7 +1217,7 @@ func (s *Server) storageDisconnectHandler(w http.ResponseWriter, r *http.Request
|
||||
|
||||
stoppedStacks, err := s.storageWatchdog.SafeDisconnect(r.Context(), req.Path)
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERROR] Safe disconnect %s: %v", req.Path, err)
|
||||
s.logger.Printf("[ERROR] [web] Safe disconnect %s: %v", req.Path, err)
|
||||
jsonError(w, fmt.Sprintf("Leválasztás sikertelen: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -1260,7 +1260,7 @@ func (s *Server) storageReconnectHandler(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
stoppedStacks, err := s.storageWatchdog.Reconnect(r.Context(), req.Path)
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERROR] Reconnect %s: %v", req.Path, err)
|
||||
s.logger.Printf("[ERROR] [web] Reconnect %s: %v", req.Path, err)
|
||||
jsonError(w, fmt.Sprintf("Csatlakoztatás sikertelen: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -1482,7 +1482,7 @@ func (s *Server) driveMigrateAPIHandler(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] Drive migration started: %s → %s by %s", req.SourcePath, req.DestPath, r.RemoteAddr)
|
||||
s.logger.Printf("[INFO] [web] Drive migration started: %s → %s by %s", req.SourcePath, req.DestPath, r.RemoteAddr)
|
||||
|
||||
go func() {
|
||||
ctx := context.Background()
|
||||
@@ -1498,9 +1498,9 @@ func (s *Server) driveMigrateAPIHandler(w http.ResponseWriter, r *http.Request)
|
||||
DestPath: req.DestPath,
|
||||
}
|
||||
if err := s.driveMigrator.MigrateDrive(ctx, migrReq, progressCh); err != nil {
|
||||
s.logger.Printf("[ERROR] Drive migration failed: %v", err)
|
||||
s.logger.Printf("[ERROR] [web] Drive migration failed: %v", err)
|
||||
} else {
|
||||
s.logger.Printf("[INFO] Drive migration complete: %s → %s", req.SourcePath, req.DestPath)
|
||||
s.logger.Printf("[INFO] [web] Drive migration complete: %s → %s", req.SourcePath, req.DestPath)
|
||||
go s.SyncFileBrowserMounts()
|
||||
}
|
||||
close(progressCh)
|
||||
@@ -1580,12 +1580,12 @@ func (s *Server) decommissionRemoveHandler(w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
|
||||
if err := s.settings.RemoveStoragePath(req.Path); err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to remove decommissioned path %s: %v", req.Path, err)
|
||||
s.logger.Printf("[ERROR] [web] Failed to remove decommissioned path %s: %v", req.Path, err)
|
||||
jsonError(w, "Eltávolítás sikertelen: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] Decommissioned storage path removed: %s", req.Path)
|
||||
s.logger.Printf("[INFO] [web] Decommissioned storage path removed: %s", req.Path)
|
||||
|
||||
// For form submissions, redirect back to settings
|
||||
if r.Header.Get("Content-Type") != "application/json" {
|
||||
|
||||
Reference in New Issue
Block a user