feat: comprehensive INFO/WARN/ERROR logging across all controller modules

Add structured operational logging at INFO, WARN, and ERROR levels to
every controller module. Standardize custom prefixes ([GEO], [SCHED],
[SYNC]) to use [INFO/WARN/ERROR] [module] format. Fix misleveled logs
(WARN->ERROR for data loss scenarios, WARN->INFO for routine operations).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 19:58:27 +01:00
parent 95c821deb2
commit 8e61cd7ec4
44 changed files with 326 additions and 44 deletions
+46
View File
@@ -1,5 +1,51 @@
## Changelog ## Changelog
### v0.32.2 — Comprehensive INFO/WARN/ERROR logging across all modules (2026-02-26)
#### Added
- **stacks/manager.go**: INFO logs for status refresh container/stack counts, log fetching, encryption migration, ScanStacks completion
- **stacks/deploy.go**: INFO logs for config updates, InjectMissingFields summary; ERROR logs for SaveAppConfig failures; WARN for LoadAppConfig errors
- **stacks/delete.go**: INFO log for ParseComposeHDDMounts result count
- **stacks/metadata.go**: Fixed LoadMetadata error to use `log.Printf` instead of `fmt.Fprintf(os.Stderr)`
- **backup/backup.go**: WARN for perDriveRepoStats failures; INFO for drive stats, aggregate stats, dump file count, snapshot history save
- **backup/crossdrive.go**: INFO for cross-drive backup start/completion with success/fail counts; ERROR for rsync failures; INFO for DB dump copy counts
- **backup/restic.go**: INFO for Snapshot and Check success
- **backup/dbdump.go**: INFO for DiscoverDatabases count; INFO for DumpAll start/completion
- **backup/restore_drives_linux.go**: INFO for fstab entry additions
- **backup/local_infra.go**: INFO for backup version pruning with kept/removed counts
- **cloudflare/geosync.go**: Standardized all `[GEO]` prefixed logs to `[INFO]/[WARN]/[ERROR] [cloudflare]` format
- **scheduler/scheduler.go**: Standardized all `[SCHED]` prefixed logs to `[INFO]/[WARN]/[ERROR] [scheduler]` format
- **sync/sync.go**: INFO for catalog sync start/completion; ERROR for git/network failures; WARN for file copy errors (replaced `[SYNC]` prefix)
- **report/pusher.go**: WARN for Push and InfraBackup push failures
- **report/builder.go**: INFO for BuildReport start
- **monitor/healthcheck.go**: WARN for CPU/memory/disk/temperature threshold breaches; INFO for health check result status
- **system/mounts_linux.go**: WARN for unsafe backup destinations and storage path probe failures
- **settings/settings.go**: INFO for settings load/save, storage path add/remove, disconnect/decommission, pending events; ERROR for save failures
- **storage/attach_linux.go**: INFO for disk attach start/success; ERROR for attach failures
- **storage/scan_linux.go**: INFO for disk scan start/completion with count
- **storage/format_linux.go**: INFO for format start/success; ERROR for format failures
- **storage/migrate.go**: INFO for migration start/completion; ERROR for migration failures
- **integrations/manager.go**: ERROR for integration apply failures; WARN for context build and env load failures
- **integrations/lifecycle.go**: Added `[integrations]` module tag to all logs; upgraded re-apply failure from WARN to ERROR
- **integrations/onlyoffice_filebrowser.go**: ERROR for all Apply/Revoke error paths
- **integrations/onlyoffice_nextcloud.go**: ERROR for all Apply/Revoke error paths
- **selfupdate/updater.go**: INFO for up-to-date and update-available results; INFO/ERROR for compose file updates
- **selfupdate/state.go**: INFO for state cleared
- **assets/syncer.go**: ERROR for manifest save failures (previously silent); changed sync failure log from WARN to ERROR
- **appexport/restore.go**: INFO for import start
- **web/auth.go**: INFO for logout/session invalidation/session cleanup; WARN for unauthorized API requests
- **web/server.go**: WARN for 404 Not Found on unknown routes
- **web/handlers.go**: INFO for default storage path and schedulable state changes
- **web/handler_restore.go**: INFO for restore-all initiation
- **web/handler_export.go**: ERROR for export/import start failures
- **web/storage_handlers.go**: INFO for disk disconnect/reconnect/restart-apps completion
- **api/router.go**: ERROR for stack action failures, backup snapshot listing failures, metrics query failures
#### Changed
- **stacks/healthprobe.go**: Summary log now always prints — WARN when unhealthy, INFO when all ok (was debug-only for all-ok)
- **backup/restore.go**: Changed RestoreApp start log from `[WARN]` to `[INFO] [backup]`
- **backup/restore_app_linux.go**: Changed restoreUserData/restoreDBDumps failure logs from `[WARN]` to `[ERROR]` where data loss could occur
### v0.32.1 — Comprehensive debug logging across all modules (2026-02-26) ### v0.32.1 — Comprehensive debug logging across all modules (2026-02-26)
#### Added #### Added
+7 -2
View File
@@ -1145,9 +1145,14 @@ When `logging.level: "debug"` is set in `controller.yaml`, the controller expose
- **DebugCallbacks**: 7 closures wired from main.go for operations needing modules not on Server struct (hub push, infra backup, connectivity tests, telemetry preview). - **DebugCallbacks**: 7 closures wired from main.go for operations needing modules not on Server struct (hub push, infra backup, connectivity tests, telemetry preview).
- **Telemetry debug**: `GetTelemetryPreview` callback calls `report.BuildAppTelemetryForDebug()` (exported wrapper around the private `buildAppTelemetrySection()`). Result renders as a table with collapsible raw JSON. Available regardless of hub configuration. - **Telemetry debug**: `GetTelemetryPreview` callback calls `report.BuildAppTelemetryForDebug()` (exported wrapper around the private `buildAppTelemetrySection()`). Result renders as a table with collapsible raw JSON. Available regardless of hub configuration.
#### Per-Module Debug Logging #### Per-Module Logging
When `logging.level: "debug"`, every module emits detailed `[DEBUG] [module]` prefixed log lines. Each module with stateful debug (struct-based) exposes a `SetDebug(bool)` method, wired from `main.go`. Modules without a struct use package-level `DebugLogger` variables (e.g., `system.DebugLogger`). All modules emit structured log lines at `[INFO]`, `[WARN]`, and `[ERROR]` levels for operational events (state changes, completions, failures). When `logging.level: "debug"`, additional detailed `[DEBUG] [module]` prefixed log lines are emitted. Each module with stateful debug (struct-based) exposes a `SetDebug(bool)` method, wired from `main.go`. Modules without a struct use package-level `DebugLogger` variables (e.g., `system.DebugLogger`).
**Standard-level logging** (always active):
- `[INFO]` — Operational events: stack deploy/start/stop, backup completion, config changes, disk operations, sync results
- `[WARN]` — Degraded states: health threshold breaches, unsafe backup destinations, retryable failures, best-effort operation failures
- `[ERROR]` — Hard failures: data restore errors, integration apply failures, compose file update errors, disk format failures
| Module | Debug Field | Prefix | Key Areas | | Module | Debug Field | Prefix | Key Areas |
|--------|------------|--------|-----------| |--------|------------|--------|-----------|
+5
View File
@@ -456,6 +456,7 @@ func (r *Router) actionStack(w http.ResponseWriter, action, name string) {
} }
if err != nil { if err != nil {
r.logger.Printf("[ERROR] [api] %s failed for %s: %v", action, name, err)
status := http.StatusInternalServerError status := http.StatusInternalServerError
if strings.Contains(err.Error(), "protected") { if strings.Contains(err.Error(), "protected") {
status = http.StatusForbidden status = http.StatusForbidden
@@ -796,6 +797,7 @@ func (r *Router) backupSnapshots(w http.ResponseWriter, req *http.Request) {
snapshots, err := r.backupMgr.ListAllSnapshots(50) snapshots, err := r.backupMgr.ListAllSnapshots(50)
if err != nil { if err != nil {
r.logger.Printf("[ERROR] [api] Failed to list backup snapshots: %v", err)
writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()}) writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
return return
} }
@@ -833,6 +835,7 @@ func (r *Router) metricsSystem(w http.ResponseWriter, req *http.Request) {
samples, err := r.metricsStore.QuerySystemMetrics(from, to, resolution) samples, err := r.metricsStore.QuerySystemMetrics(from, to, resolution)
if err != nil { if err != nil {
r.logger.Printf("[ERROR] [api] Failed to query system metrics: %v", err)
writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()}) writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
return return
} }
@@ -869,6 +872,7 @@ func (r *Router) metricsContainerSummary(w http.ResponseWriter, _ *http.Request)
summary, err := r.metricsStore.QueryContainerSummary() summary, err := r.metricsStore.QueryContainerSummary()
if err != nil { if err != nil {
r.logger.Printf("[ERROR] [api] Failed to query container summary: %v", err)
writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()}) writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
return return
} }
@@ -887,6 +891,7 @@ func (r *Router) metricsContainer(w http.ResponseWriter, req *http.Request, name
samples, err := r.metricsStore.QueryContainerMetrics(name, from, to, resolution) samples, err := r.metricsStore.QueryContainerMetrics(name, from, to, resolution)
if err != nil { if err != nil {
r.logger.Printf("[ERROR] [api] Failed to query container metrics for %s: %v", name, err)
writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()}) writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
return return
} }
+1
View File
@@ -218,6 +218,7 @@ func (e *Exporter) StartImport(req ImportRequest) error {
func (e *Exporter) executeImport(req ImportRequest, job *Job) { func (e *Exporter) executeImport(req ImportRequest, job *Job) {
importStart := time.Now() importStart := time.Now()
e.logger.Printf("[INFO] [appexport] Import started for %s", filepath.Base(req.FABPath))
e.debugf("=== IMPORT START: path=%s encrypted=%v ===", req.FABPath, req.Password != "") e.debugf("=== IMPORT START: path=%s encrypted=%v ===", req.FABPath, req.Password != "")
defer func() { defer func() {
+6 -2
View File
@@ -218,7 +218,7 @@ func (s *Syncer) setError(err error) {
TotalBytes: s.status.TotalBytes, TotalBytes: s.status.TotalBytes,
} }
s.mu.Unlock() s.mu.Unlock()
s.logger.Printf("[WARN] Asset sync failed: %v", err) s.logger.Printf("[ERROR] [assets] Asset sync failed: %v", err)
} }
func (s *Syncer) fetchManifest(ctx context.Context) (*HubManifest, error) { func (s *Syncer) fetchManifest(ctx context.Context) (*HubManifest, error) {
@@ -316,14 +316,18 @@ func (s *Syncer) downloadFile(ctx context.Context, filename string) error {
func (s *Syncer) saveLocalManifest(manifest *HubManifest) { func (s *Syncer) saveLocalManifest(manifest *HubManifest) {
data, err := json.MarshalIndent(manifest, "", " ") data, err := json.MarshalIndent(manifest, "", " ")
if err != nil { if err != nil {
s.logger.Printf("[ERROR] [assets] Failed to save local manifest: %v", err)
return return
} }
path := filepath.Join(s.assetsDir, "manifest.json") path := filepath.Join(s.assetsDir, "manifest.json")
tmp := path + ".tmp" tmp := path + ".tmp"
if err := os.WriteFile(tmp, data, 0644); err != nil { if err := os.WriteFile(tmp, data, 0644); err != nil {
s.logger.Printf("[ERROR] [assets] Failed to save local manifest: %v", err)
return return
} }
os.Rename(tmp, path) if err := os.Rename(tmp, path); err != nil {
s.logger.Printf("[ERROR] [assets] Failed to save local manifest: %v", err)
}
} }
func fileSHA256(path string) (string, error) { func fileSHA256(path string) (string, error) {
+6
View File
@@ -856,6 +856,7 @@ func (m *Manager) perDriveRepoStats() []DriveRepoInfo {
} }
stats, err := m.restic.Stats(repoPath) stats, err := m.restic.Stats(repoPath)
if err != nil { if err != nil {
m.logger.Printf("[WARN] [backup] perDriveRepoStats: failed to get stats for %s: %v", drive, err)
continue continue
} }
infos = append(infos, DriveRepoInfo{ infos = append(infos, DriveRepoInfo{
@@ -866,6 +867,7 @@ func (m *Manager) perDriveRepoStats() []DriveRepoInfo {
LatestSnapshot: stats.LatestSnapshot, LatestSnapshot: stats.LatestSnapshot,
}) })
} }
m.logger.Printf("[INFO] [backup] perDriveRepoStats: collected stats for %d drives", len(infos))
return infos return infos
} }
@@ -918,6 +920,7 @@ func (m *Manager) aggregateRepoStats() *RepoStats {
if totalBytes > 0 { if totalBytes > 0 {
agg.TotalSize = humanizeBytes(totalBytes) agg.TotalSize = humanizeBytes(totalBytes)
} }
m.logger.Printf("[INFO] [backup] Aggregated repo stats: %d snapshots, total size %s", agg.SnapshotCount, agg.TotalSize)
return agg return agg
} }
@@ -932,6 +935,7 @@ func (m *Manager) listAllDumpFiles() []DumpFileInfo {
} }
} }
} }
m.logger.Printf("[INFO] [backup] Found %d DB dump files across drives", len(allFiles))
return allFiles return allFiles
} }
@@ -979,7 +983,9 @@ func (m *Manager) saveSnapshotHistory() {
} }
if err := os.Rename(tmp, m.snapshotHistoryFile); err != nil { 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] Could not rename snapshot history file: %v", err)
return
} }
m.logger.Printf("[INFO] [backup] Saved snapshot history (%d entries)", len(m.snapshotHistory))
} }
// loadSnapshotHistoryFromFile reads the persisted snapshot history from disk. // loadSnapshotHistoryFromFile reads the persisted snapshot history from disk.
+8
View File
@@ -203,6 +203,7 @@ func (r *CrossDriveRunner) RunAllScheduled(ctx context.Context, schedule string)
var errs []string var errs []string
var scheduled, skippedDisabled, skippedWrongSchedule int var scheduled, skippedDisabled, skippedWrongSchedule int
r.logger.Printf("[INFO] [backup] Cross-drive backup: starting scheduled run for %d configured app(s), schedule=%s", len(configs), schedule)
for stackName, cfg := range configs { for stackName, cfg := range configs {
if !cfg.Enabled { if !cfg.Enabled {
if r.debug { if r.debug {
@@ -240,6 +241,8 @@ func (r *CrossDriveRunner) RunAllScheduled(ctx context.Context, schedule string)
scheduled, skippedDisabled, skippedWrongSchedule, len(errs)) scheduled, skippedDisabled, skippedWrongSchedule, len(errs))
} }
r.logger.Printf("[INFO] [backup] Cross-drive backup complete: %d succeeded, %d failed", scheduled-len(errs), len(errs))
if len(errs) > 0 { if len(errs) > 0 {
return fmt.Errorf("cross-drive backup errors: %s", strings.Join(errs, "; ")) return fmt.Errorf("cross-drive backup errors: %s", strings.Join(errs, "; "))
} }
@@ -263,6 +266,7 @@ func (r *CrossDriveRunner) RunAllConfigured(ctx context.Context) error {
var errs []string var errs []string
var ran int var ran int
r.logger.Printf("[INFO] [backup] Cross-drive backup: starting all configured app(s), %d total", len(configs))
for stackName, cfg := range configs { for stackName, cfg := range configs {
if !cfg.Enabled { if !cfg.Enabled {
continue continue
@@ -281,6 +285,7 @@ func (r *CrossDriveRunner) RunAllConfigured(ctx context.Context) error {
if r.debug { if r.debug {
r.logger.Printf("[DEBUG] RunAllConfigured: done — %d ran, %d errors", ran, len(errs)) r.logger.Printf("[DEBUG] RunAllConfigured: done — %d ran, %d errors", ran, len(errs))
} }
r.logger.Printf("[INFO] [backup] Cross-drive backup complete: %d succeeded, %d failed", ran-len(errs), len(errs))
if len(errs) > 0 { if len(errs) > 0 {
return fmt.Errorf("cross-drive errors: %s", strings.Join(errs, "; ")) return fmt.Errorf("cross-drive errors: %s", strings.Join(errs, "; "))
} }
@@ -418,6 +423,7 @@ func (r *CrossDriveRunner) runRsyncBackup(ctx context.Context, stackName, destBa
if r.debug { if r.debug {
r.logger.Printf("[DEBUG] runRsyncBackup: rsync failed for %s: %s", srcMount, util.TruncateStr(strings.TrimSpace(string(out)), 500)) r.logger.Printf("[DEBUG] runRsyncBackup: rsync failed for %s: %s", srcMount, util.TruncateStr(strings.TrimSpace(string(out)), 500))
} }
r.logger.Printf("[ERROR] [backup] Rsync backup for %s failed: %v", stackName, err)
return fmt.Errorf("rsync failed for %s: %v (%s)", srcMount, err, strings.TrimSpace(string(out))) return fmt.Errorf("rsync failed for %s: %v (%s)", srcMount, err, strings.TrimSpace(string(out)))
} }
if r.debug { if r.debug {
@@ -452,6 +458,7 @@ func (r *CrossDriveRunner) runRsyncBackup(ctx context.Context, stackName, destBa
} }
} }
r.logger.Printf("[INFO] [backup] Rsync backup for %s to %s complete", stackName, destDir)
return nil return nil
} }
@@ -484,6 +491,7 @@ func (r *CrossDriveRunner) copyStackDBDumps(stackName, destDir string) error {
if copied > 0 { if copied > 0 {
r.logger.Printf("[DEBUG] Copied %d DB dump file(s) to %s", copied, destDir) 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 return nil
} }
+7
View File
@@ -135,6 +135,7 @@ func DiscoverDatabases(ctx context.Context, logger *log.Logger, debug bool) ([]D
logger.Printf("[DEBUG] DiscoverDatabases: found %d database(s), skipped %d non-DB container(s)", len(dbs), skipped) logger.Printf("[DEBUG] DiscoverDatabases: found %d database(s), skipped %d non-DB container(s)", len(dbs), skipped)
} }
logger.Printf("[INFO] [backup] Discovered %d databases", len(dbs))
return dbs, nil return dbs, nil
} }
@@ -143,11 +144,17 @@ func DumpAll(ctx context.Context, dbs []DiscoveredDB, dumpDir string, logger *lo
// Clean up old .tmp files (older than 1 hour) // Clean up old .tmp files (older than 1 hour)
cleanupTmpFiles(dumpDir, logger) cleanupTmpFiles(dumpDir, logger)
logger.Printf("[INFO] [backup] Starting DB dump for %d databases", len(dbs))
var results []DumpResult var results []DumpResult
var failed int
for _, db := range dbs { for _, db := range dbs {
result := DumpOne(ctx, db, dumpDir, logger, debug) result := DumpOne(ctx, db, dumpDir, logger, debug)
results = append(results, result) results = append(results, result)
if result.Error != nil {
failed++
}
} }
logger.Printf("[INFO] [backup] DB dump complete: %d succeeded, %d failed", len(results)-failed, failed)
return results return results
} }
@@ -187,6 +187,9 @@ func pruneLocalHistory(histDir string, maxKeep int, logger *log.Logger) {
logger.Printf("[DEBUG] Local infra history: pruned old version %s", ts) logger.Printf("[DEBUG] Local infra history: pruned old version %s", ts)
} }
} }
if logger != nil && toDelete > 0 {
logger.Printf("[INFO] [backup] Pruning old backup versions: kept %d, removed %d", maxKeep, toDelete)
}
} }
// sanitizeTimestamp converts an RFC3339 timestamp to a filename-safe format. // sanitizeTimestamp converts an RFC3339 timestamp to a filename-safe format.
+2
View File
@@ -207,6 +207,7 @@ func (r *ResticManager) Snapshot(repoPath string, paths []string, tags []string)
result.Duration, result.SnapshotID, result.FilesNew, result.FilesChanged, result.DataAdded) result.Duration, result.SnapshotID, result.FilesNew, result.FilesChanged, result.DataAdded)
} }
r.logger.Printf("[INFO] [backup] Restic snapshot complete for %s", repoPath)
return result, nil return result, nil
} }
@@ -264,6 +265,7 @@ func (r *ResticManager) Check(repoPath string) error {
if r.debug { if r.debug {
r.logger.Printf("[DEBUG] [restic] Check: repo=%s OK, completed in %s", repoPath, time.Since(start)) r.logger.Printf("[DEBUG] [restic] Check: repo=%s OK, completed in %s", repoPath, time.Since(start))
} }
r.logger.Printf("[INFO] [backup] Restic check passed for repo %s", repoPath)
return nil return nil
} }
+1 -1
View File
@@ -87,7 +87,7 @@ func (m *Manager) RestoreApp(stackName, snapshotID string) error {
m.logger.Printf("[DEBUG] RestoreApp: using repo=%s, %d restore path(s)", repoPath, len(restorePaths)) m.logger.Printf("[DEBUG] RestoreApp: using repo=%s, %d restore path(s)", repoPath, len(restorePaths))
} }
m.logger.Printf("[WARN] RESTORE starting: stack=%s, snapshot=%s, repo=%s, paths=%v, hasHDD=%v", m.logger.Printf("[INFO] [backup] Starting restore for %s (snapshot=%s, repo=%s, paths=%v, hasHDD=%v)",
stackName, snapshotID, repoPath, restorePaths, hasHDD) stackName, snapshotID, repoPath, restorePaths, hasHDD)
// Stop the app before restore // Stop the app before restore
@@ -156,14 +156,14 @@ func restoreUserData(ctx context.Context, app *RestorableApp, logger *log.Logger
if e.IsDir() { if e.IsDir() {
src = strings.TrimRight(src, "/") + "/" src = strings.TrimRight(src, "/") + "/"
if err := os.MkdirAll(dst, 0755); err != nil { if err := os.MkdirAll(dst, 0755); err != nil {
logger.Printf("[WARN] Cannot create %s: %v", dst, err) logger.Printf("[ERROR] [backup] Cannot create %s: %v", dst, err)
continue continue
} }
dst = strings.TrimRight(dst, "/") + "/" dst = strings.TrimRight(dst, "/") + "/"
logger.Printf("[DEBUG] [backup] restoreUserData: %s — rsync dir %s → %s", app.Name, src, dst) logger.Printf("[DEBUG] [backup] restoreUserData: %s — rsync dir %s → %s", app.Name, src, dst)
cmd := exec.CommandContext(ctx, "rsync", "-a", src, dst) cmd := exec.CommandContext(ctx, "rsync", "-a", src, dst)
if out, err := cmd.CombinedOutput(); err != nil { if out, err := cmd.CombinedOutput(); err != nil {
logger.Printf("[WARN] rsync data %s: %v (%s)", name, err, strings.TrimSpace(string(out))) logger.Printf("[ERROR] [backup] rsync data %s: %v (%s)", name, err, strings.TrimSpace(string(out)))
} else { } else {
restored++ restored++
} }
@@ -171,12 +171,12 @@ func restoreUserData(ctx context.Context, app *RestorableApp, logger *log.Logger
// Single file — copy directly // Single file — copy directly
data, err := os.ReadFile(src) data, err := os.ReadFile(src)
if err != nil { if err != nil {
logger.Printf("[WARN] Cannot read %s: %v", src, err) logger.Printf("[ERROR] [backup] Cannot read %s: %v", src, err)
continue continue
} }
logger.Printf("[DEBUG] [backup] restoreUserData: %s — copying file %s (%d bytes)", app.Name, name, len(data)) logger.Printf("[DEBUG] [backup] restoreUserData: %s — copying file %s (%d bytes)", app.Name, name, len(data))
if err := os.WriteFile(dst, data, 0644); err != nil { if err := os.WriteFile(dst, data, 0644); err != nil {
logger.Printf("[WARN] Cannot write %s: %v", dst, err) logger.Printf("[ERROR] [backup] Cannot write %s: %v", dst, err)
} else { } else {
restored++ restored++
} }
@@ -224,12 +224,12 @@ func restoreDBDumps(app *RestorableApp, logger *log.Logger) error {
dst := filepath.Join(destDir, e.Name()) dst := filepath.Join(destDir, e.Name())
data, err := os.ReadFile(src) data, err := os.ReadFile(src)
if err != nil { if err != nil {
logger.Printf("[WARN] Cannot read dump %s: %v", e.Name(), err) logger.Printf("[ERROR] [backup] Cannot read dump %s: %v", e.Name(), err)
continue continue
} }
logger.Printf("[DEBUG] [backup] restoreDBDumps: %s — copying %s (%d bytes)", app.Name, e.Name(), len(data)) logger.Printf("[DEBUG] [backup] restoreDBDumps: %s — copying %s (%d bytes)", app.Name, e.Name(), len(data))
if err := os.WriteFile(dst, data, 0644); err != nil { if err := os.WriteFile(dst, data, 0644); err != nil {
logger.Printf("[WARN] Cannot write dump %s: %v", e.Name(), err) logger.Printf("[ERROR] [backup] Cannot write dump %s: %v", e.Name(), err)
} else { } else {
copied++ copied++
} }
@@ -239,6 +239,7 @@ func mountRawAndBind(ctx context.Context, device string, dm DiskMount, logger *l
func addDRFstabEntries(dm DiskMount, logger *log.Logger) error { func addDRFstabEntries(dm DiskMount, logger *log.Logger) error {
const fstabPath = "/host-fstab" const fstabPath = "/host-fstab"
logger.Printf("[INFO] [backup] Adding fstab entries for disaster recovery (%s, UUID=%s)", dm.Label, dm.UUID)
logger.Printf("[DEBUG] [backup] addDRFstabEntries: checking fstab for %s (UUID=%s)", dm.Label, dm.UUID) logger.Printf("[DEBUG] [backup] addDRFstabEntries: checking fstab for %s (UUID=%s)", dm.Label, dm.UUID)
data, err := os.ReadFile(fstabPath) data, err := os.ReadFile(fstabPath)
@@ -257,20 +258,24 @@ func addDRFstabEntries(dm DiskMount, logger *log.Logger) error {
var additions strings.Builder var additions strings.Builder
additions.WriteString("\n# Restored by felhom-controller DR\n") additions.WriteString("\n# Restored by felhom-controller DR\n")
entryCount := 0
if dm.RawMount != "" { if dm.RawMount != "" {
// Raw mount entry // Raw mount entry
additions.WriteString(fmt.Sprintf("UUID=%s\t%s\t%s\t%s\t0 2\n", additions.WriteString(fmt.Sprintf("UUID=%s\t%s\t%s\t%s\t0 2\n",
dm.UUID, dm.RawMount, dm.FSType, dm.FstabOptions)) dm.UUID, dm.RawMount, dm.FSType, dm.FstabOptions))
entryCount++
} }
if dm.BindSubdir != "" && dm.RawMount != "" { if dm.BindSubdir != "" && dm.RawMount != "" {
// Bind mount entry // Bind mount entry
additions.WriteString(fmt.Sprintf("%s/%s\t%s\tnone\tbind,nofail\t0 0\n", additions.WriteString(fmt.Sprintf("%s/%s\t%s\tnone\tbind,nofail\t0 0\n",
dm.RawMount, dm.BindSubdir, dm.MountPoint)) dm.RawMount, dm.BindSubdir, dm.MountPoint))
entryCount++
} else if dm.RawMount == "" { } else if dm.RawMount == "" {
// Direct mount entry (no bind) // Direct mount entry (no bind)
additions.WriteString(fmt.Sprintf("UUID=%s\t%s\t%s\t%s\t0 2\n", additions.WriteString(fmt.Sprintf("UUID=%s\t%s\t%s\t%s\t0 2\n",
dm.UUID, dm.MountPoint, dm.FSType, dm.FstabOptions)) dm.UUID, dm.MountPoint, dm.FSType, dm.FstabOptions))
entryCount++
} }
newContent := content + additions.String() newContent := content + additions.String()
@@ -288,6 +293,7 @@ func addDRFstabEntries(dm DiskMount, logger *log.Logger) error {
} }
} }
logger.Printf("[INFO] [backup] Added %d fstab entries for %s", entryCount, dm.Label)
return nil return nil
} }
+16 -6
View File
@@ -68,6 +68,8 @@ func (g *GeoSyncManager) Sync(ctx context.Context) error {
g.mu.Unlock() g.mu.Unlock()
}() }()
g.logger.Printf("[INFO] [cloudflare] Geo-restriction sync starting")
geo := g.settings.GetGeoRestriction() geo := g.settings.GetGeoRestriction()
// If geo is nil or disabled, delete all felhom rules and return. // If geo is nil or disabled, delete all felhom rules and return.
@@ -75,7 +77,7 @@ func (g *GeoSyncManager) Sync(ctx context.Context) error {
return g.deleteAllRules(ctx, geo) return g.deleteAllRules(ctx, geo)
} }
g.logger.Printf("[GEO] Starting sync for domain %s (%d allowed countries, %d app overrides)", g.logger.Printf("[INFO] [cloudflare] Starting sync for domain %s (%d allowed countries, %d app overrides)",
g.domain, len(geo.AllowedCountries), len(geo.AppOverrides)) g.domain, len(geo.AllowedCountries), len(geo.AppOverrides))
// 1. Resolve zone ID (use cached value if available) // 1. Resolve zone ID (use cached value if available)
@@ -156,7 +158,9 @@ func (g *GeoSyncManager) Sync(ctx context.Context) error {
// 6. Save success state // 6. Save success state
g.saveError(zoneID, rulesetID, "") g.saveError(zoneID, rulesetID, "")
g.logger.Printf("[GEO] Sync completed successfully") // Count rules for summary
finalRules, _ := g.client.GetFelhomRules(ctx, zoneID, rulesetID)
g.logger.Printf("[INFO] [cloudflare] Geo-restriction sync complete (%d active rules)", len(finalRules))
return nil return nil
} }
@@ -184,7 +188,7 @@ func (g *GeoSyncManager) deleteAllRules(ctx context.Context, geo *settings.GeoRe
existing, err := g.client.GetFelhomRules(ctx, zoneID, rulesetID) existing, err := g.client.GetFelhomRules(ctx, zoneID, rulesetID)
if err != nil { if err != nil {
g.logger.Printf("[GEO] Warning: could not list rules for cleanup: %v", err) g.logger.Printf("[WARN] [cloudflare] Could not list rules for cleanup: %v", err)
return nil return nil
} }
@@ -195,14 +199,14 @@ func (g *GeoSyncManager) deleteAllRules(ctx context.Context, geo *settings.GeoRe
deleted := 0 deleted := 0
for _, r := range existing { for _, r := range existing {
if err := g.client.DeleteRule(ctx, zoneID, rulesetID, r.ID); err != nil { if err := g.client.DeleteRule(ctx, zoneID, rulesetID, r.ID); err != nil {
g.logger.Printf("[GEO] Warning: could not delete rule %s: %v", r.ID, err) g.logger.Printf("[ERROR] [cloudflare] Failed to delete WAF rule %s: %v", r.ID, err)
} else { } else {
deleted++ deleted++
} }
} }
if len(existing) > 0 { if len(existing) > 0 {
g.logger.Printf("[GEO] Deleted %d felhom-geo rules (feature disabled)", len(existing)) g.logger.Printf("[INFO] [cloudflare] Deleted %d felhom-geo rules (feature disabled)", len(existing))
if g.debug { if g.debug {
g.logger.Printf("[DEBUG] [cloudflare] deleteAllRules: successfully deleted %d/%d rules", deleted, len(existing)) g.logger.Printf("[DEBUG] [cloudflare] deleteAllRules: successfully deleted %d/%d rules", deleted, len(existing))
} }
@@ -296,6 +300,7 @@ func (g *GeoSyncManager) applyDiff(ctx context.Context, zoneID, rulesetID string
if err := g.client.UpdateRule(ctx, zoneID, rulesetID, ex.ID, r); err != nil { if err := g.client.UpdateRule(ctx, zoneID, rulesetID, ex.ID, r); err != nil {
return fmt.Errorf("update rule %q: %w", d.description, err) return fmt.Errorf("update rule %q: %w", d.description, err)
} }
g.logger.Printf("[INFO] [cloudflare] Updated WAF rule %s", d.description)
} else if g.debug { } else if g.debug {
g.logger.Printf("[DEBUG] [cloudflare] applyDiff: rule %q unchanged, skipping", d.description) g.logger.Printf("[DEBUG] [cloudflare] applyDiff: rule %q unchanged, skipping", d.description)
} }
@@ -308,6 +313,7 @@ func (g *GeoSyncManager) applyDiff(ctx context.Context, zoneID, rulesetID string
if _, err := g.client.CreateRule(ctx, zoneID, rulesetID, r); err != nil { if _, err := g.client.CreateRule(ctx, zoneID, rulesetID, r); err != nil {
return fmt.Errorf("create rule %q: %w", d.description, err) return fmt.Errorf("create rule %q: %w", d.description, err)
} }
g.logger.Printf("[INFO] [cloudflare] Created WAF rule %s", d.description)
} }
} }
@@ -320,6 +326,7 @@ func (g *GeoSyncManager) applyDiff(ctx context.Context, zoneID, rulesetID string
if err := g.client.DeleteRule(ctx, zoneID, rulesetID, ex.ID); err != nil { if err := g.client.DeleteRule(ctx, zoneID, rulesetID, ex.ID); err != nil {
return fmt.Errorf("delete rule %q: %w", ex.Description, err) return fmt.Errorf("delete rule %q: %w", ex.Description, err)
} }
g.logger.Printf("[INFO] [cloudflare] Deleted WAF rule %s", ex.Description)
} }
} }
@@ -328,7 +335,10 @@ func (g *GeoSyncManager) applyDiff(ctx context.Context, zoneID, rulesetID string
// saveError updates the sync state in settings. // saveError updates the sync state in settings.
func (g *GeoSyncManager) saveError(zoneID, rulesetID, errMsg string) { func (g *GeoSyncManager) saveError(zoneID, rulesetID, errMsg string) {
if errMsg != "" {
g.logger.Printf("[ERROR] [cloudflare] Geo-sync error: %v", errMsg)
}
if err := g.settings.SetGeoSyncState(zoneID, rulesetID, errMsg); err != nil { if err := g.settings.SetGeoSyncState(zoneID, rulesetID, errMsg); err != nil {
g.logger.Printf("[GEO] Warning: failed to save sync state: %v", err) g.logger.Printf("[WARN] [cloudflare] Failed to save sync state: %v", err)
} }
} }
+8
View File
@@ -63,6 +63,7 @@ func (c *Client) GetCustomRulesetID(ctx context.Context, zoneID string) (string,
path := fmt.Sprintf("/zones/%s/rulesets", zoneID) path := fmt.Sprintf("/zones/%s/rulesets", zoneID)
resp, err := c.do(ctx, "GET", path, nil) resp, err := c.do(ctx, "GET", path, nil)
if err != nil { if err != nil {
c.logger.Printf("[ERROR] [cloudflare] Failed to get custom ruleset: %v", err)
return "", fmt.Errorf("list rulesets: %w", err) return "", fmt.Errorf("list rulesets: %w", err)
} }
@@ -120,6 +121,7 @@ func (c *Client) GetRules(ctx context.Context, zoneID, rulesetID string) ([]rule
path := fmt.Sprintf("/zones/%s/rulesets/%s", zoneID, rulesetID) path := fmt.Sprintf("/zones/%s/rulesets/%s", zoneID, rulesetID)
resp, err := c.do(ctx, "GET", path, nil) resp, err := c.do(ctx, "GET", path, nil)
if err != nil { if err != nil {
c.logger.Printf("[WARN] [cloudflare] Failed to get WAF rules: %v", err)
return nil, fmt.Errorf("get ruleset: %w", err) return nil, fmt.Errorf("get ruleset: %w", err)
} }
@@ -165,9 +167,11 @@ func (c *Client) GetFelhomRules(ctx context.Context, zoneID, rulesetID string) (
// CreateRule adds a new rule to the ruleset. // CreateRule adds a new rule to the ruleset.
func (c *Client) CreateRule(ctx context.Context, zoneID, rulesetID string, r rule) (string, error) { func (c *Client) CreateRule(ctx context.Context, zoneID, rulesetID string, r rule) (string, error) {
c.logger.Printf("[INFO] [cloudflare] Creating WAF rule: %s", r.Description)
path := fmt.Sprintf("/zones/%s/rulesets/%s/rules", zoneID, rulesetID) path := fmt.Sprintf("/zones/%s/rulesets/%s/rules", zoneID, rulesetID)
resp, err := c.do(ctx, "POST", path, r) resp, err := c.do(ctx, "POST", path, r)
if err != nil { if err != nil {
c.logger.Printf("[ERROR] [cloudflare] Failed to create WAF rule: %v", err)
return "", fmt.Errorf("create rule: %w", err) return "", fmt.Errorf("create rule: %w", err)
} }
@@ -198,9 +202,11 @@ func (c *Client) CreateRule(ctx context.Context, zoneID, rulesetID string, r rul
// UpdateRule updates an existing rule in the ruleset. // UpdateRule updates an existing rule in the ruleset.
func (c *Client) UpdateRule(ctx context.Context, zoneID, rulesetID, ruleID string, r rule) error { func (c *Client) UpdateRule(ctx context.Context, zoneID, rulesetID, ruleID string, r rule) error {
c.logger.Printf("[INFO] [cloudflare] Updating WAF rule %s", ruleID)
path := fmt.Sprintf("/zones/%s/rulesets/%s/rules/%s", zoneID, rulesetID, ruleID) path := fmt.Sprintf("/zones/%s/rulesets/%s/rules/%s", zoneID, rulesetID, ruleID)
_, err := c.do(ctx, "PATCH", path, r) _, err := c.do(ctx, "PATCH", path, r)
if err != nil { if err != nil {
c.logger.Printf("[ERROR] [cloudflare] Failed to update WAF rule %s: %v", ruleID, err)
return fmt.Errorf("update rule %s: %w", 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("[CF] Updated rule %q (%s)", r.Description, ruleID)
@@ -216,9 +222,11 @@ func (c *Client) UpdateRule(ctx context.Context, zoneID, rulesetID, ruleID strin
// DeleteRule removes a rule from the ruleset. // DeleteRule removes a rule from the ruleset.
func (c *Client) DeleteRule(ctx context.Context, zoneID, rulesetID, ruleID string) error { func (c *Client) DeleteRule(ctx context.Context, zoneID, rulesetID, ruleID string) error {
c.logger.Printf("[INFO] [cloudflare] Deleting WAF rule %s", ruleID)
path := fmt.Sprintf("/zones/%s/rulesets/%s/rules/%s", zoneID, rulesetID, ruleID) path := fmt.Sprintf("/zones/%s/rulesets/%s/rules/%s", zoneID, rulesetID, ruleID)
_, err := c.do(ctx, "DELETE", path, nil) _, err := c.do(ctx, "DELETE", path, nil)
if err != nil { if err != nil {
c.logger.Printf("[ERROR] [cloudflare] Failed to delete WAF rule %s: %v", ruleID, err)
return fmt.Errorf("delete rule %s: %w", ruleID, err) return fmt.Errorf("delete rule %s: %w", ruleID, err)
} }
c.logger.Printf("[CF] Deleted rule %s", ruleID) c.logger.Printf("[CF] Deleted rule %s", ruleID)
+1
View File
@@ -52,6 +52,7 @@ func (c *Client) GetZoneID(ctx context.Context, domain string) (string, error) {
} }
} }
c.logger.Printf("[WARN] [cloudflare] No zone found for domain %s", domain)
return "", fmt.Errorf("no Cloudflare zone found for domain %q", domain) return "", fmt.Errorf("no Cloudflare zone found for domain %q", domain)
} }
@@ -41,7 +41,7 @@ func (m *Manager) OnStackStop(_ context.Context, stackName string) {
ac, err := m.buildApplyContext(provider, target) ac, err := m.buildApplyContext(provider, target)
if err != nil { if err != nil {
m.logger.Printf("[WARN] Cannot build context for integration %s revoke: %v", key, err) m.logger.Printf("[WARN] [integrations] Cannot build context for integration %s revoke: %v", key, err)
continue continue
} }
@@ -49,7 +49,7 @@ func (m *Manager) OnStackStop(_ context.Context, stackName string) {
m.logger.Printf("[DEBUG] [integrations] OnStackStop: revoking %s", key) m.logger.Printf("[DEBUG] [integrations] OnStackStop: revoking %s", key)
} }
if err := handler.Revoke(ac); err != nil { if err := handler.Revoke(ac); err != nil {
m.logger.Printf("[WARN] Integration revoke on stop failed for %s: %v", key, err) m.logger.Printf("[WARN] [integrations] Integration revoke on stop failed for %s: %v", key, err)
} }
if provider == stackName { if provider == stackName {
@@ -58,7 +58,7 @@ func (m *Manager) OnStackStop(_ context.Context, stackName string) {
state.Status = "target_unavailable" state.Status = "target_unavailable"
} }
_ = m.sett.SetIntegrationState(key, state) _ = m.sett.SetIntegrationState(key, state)
m.logger.Printf("[INFO] Integration %s suspended (stack %s stopped)", key, stackName) m.logger.Printf("[INFO] [integrations] Integration %s suspended (stack %s stopped)", key, stackName)
} }
} }
@@ -126,12 +126,12 @@ func (m *Manager) OnStackStart(_ context.Context, stackName string) {
ac, err := m.buildApplyContext(provider, target) ac, err := m.buildApplyContext(provider, target)
if err != nil { if err != nil {
m.logger.Printf("[WARN] Cannot re-apply integration %s on start: %v", key, err) m.logger.Printf("[WARN] [integrations] Cannot re-apply integration %s on start: %v", key, err)
continue continue
} }
if err := handler.Apply(ac); err != nil { if err := handler.Apply(ac); err != nil {
m.logger.Printf("[WARN] Integration re-apply on start failed for %s: %v", key, err) m.logger.Printf("[ERROR] [integrations] Integration re-apply on start failed for %s: %v", key, err)
state.Status = "error" state.Status = "error"
state.LastError = err.Error() state.LastError = err.Error()
_ = m.sett.SetIntegrationState(key, state) _ = m.sett.SetIntegrationState(key, state)
@@ -141,7 +141,7 @@ func (m *Manager) OnStackStart(_ context.Context, stackName string) {
state.Status = "active" state.Status = "active"
state.LastError = "" state.LastError = ""
_ = m.sett.SetIntegrationState(key, state) _ = m.sett.SetIntegrationState(key, state)
m.logger.Printf("[INFO] Integration %s re-activated (stack %s started)", key, stackName) m.logger.Printf("[INFO] [integrations] Integration %s re-activated (stack %s started)", key, stackName)
} }
} }
@@ -186,6 +186,6 @@ func (m *Manager) OnStackRemove(_ context.Context, stackName string) {
} }
_ = m.sett.RemoveIntegrationState(key) _ = m.sett.RemoveIntegrationState(key)
m.logger.Printf("[INFO] Integration %s removed (stack %s removed)", key, stackName) m.logger.Printf("[INFO] [integrations] Integration %s removed (stack %s removed)", key, stackName)
} }
} }
@@ -125,6 +125,7 @@ func (m *Manager) Toggle(ctx context.Context, provider, target string, enable bo
start := time.Now() start := time.Now()
if err := handler.Apply(ac); err != nil { if err := handler.Apply(ac); err != nil {
m.logger.Printf("[ERROR] [integrations] Integration %s apply failed: %v", key, err)
if m.isDebug() { if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] Toggle: Apply failed for %s in %v: %v", key, time.Since(start), err) m.logger.Printf("[DEBUG] [integrations] Toggle: Apply failed for %s in %v: %v", key, time.Since(start), err)
} }
@@ -210,6 +211,7 @@ func (m *Manager) ListForProvider(providerSlug string) []StatusInfo {
func (m *Manager) buildApplyContext(provider, target string) (*ApplyContext, error) { func (m *Manager) buildApplyContext(provider, target string) (*ApplyContext, error) {
provStack, ok := m.stacks.GetStack(provider) provStack, ok := m.stacks.GetStack(provider)
if !ok { if !ok {
m.logger.Printf("[WARN] [integrations] Failed to build context for %s:%s: provider stack not found", provider, target)
return nil, fmt.Errorf("szolgáltató stack %q nem található", provider) return nil, fmt.Errorf("szolgáltató stack %q nem található", provider)
} }
@@ -299,6 +301,7 @@ func (m *Manager) loadDecryptedEnv(s *stacks.Stack) map[string]string {
stackDir := filepath.Dir(s.ComposePath) stackDir := filepath.Dir(s.ComposePath)
cfg := stacks.LoadAppConfig(stackDir) cfg := stacks.LoadAppConfig(stackDir)
if cfg == nil { if cfg == nil {
m.logger.Printf("[WARN] [integrations] Failed to load env for %s: app config is nil", s.Name)
return nil return nil
} }
if m.encKey != nil { if m.encKey != nil {
@@ -13,6 +13,7 @@ type OnlyOfficeFileBrowserHandler struct{}
func (h *OnlyOfficeFileBrowserHandler) Apply(ac *ApplyContext) error { func (h *OnlyOfficeFileBrowserHandler) Apply(ac *ApplyContext) error {
jwtSecret := ac.ProviderEnv["JWT_SECRET"] jwtSecret := ac.ProviderEnv["JWT_SECRET"]
if jwtSecret == "" { if jwtSecret == "" {
ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-FileBrowser apply: JWT_SECRET not set")
return fmt.Errorf("OnlyOffice JWT_SECRET nincs beállítva — telepítsd újra az alkalmazást") return fmt.Errorf("OnlyOffice JWT_SECRET nincs beállítva — telepítsd újra az alkalmazást")
} }
@@ -21,6 +22,7 @@ func (h *OnlyOfficeFileBrowserHandler) Apply(ac *ApplyContext) error {
subdomain = ac.ProviderMeta.Subdomain subdomain = ac.ProviderMeta.Subdomain
} }
if subdomain == "" { if subdomain == "" {
ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-FileBrowser apply: subdomain unknown")
return fmt.Errorf("OnlyOffice aldomain nem ismert") return fmt.Errorf("OnlyOffice aldomain nem ismert")
} }
@@ -31,6 +33,7 @@ func (h *OnlyOfficeFileBrowserHandler) Apply(ac *ApplyContext) error {
configData, err := os.ReadFile(configPath) configData, err := os.ReadFile(configPath)
if err != nil { if err != nil {
ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-FileBrowser apply: %v", err)
return fmt.Errorf("FileBrowser config olvasási hiba: %w", err) return fmt.Errorf("FileBrowser config olvasási hiba: %w", err)
} }
@@ -47,10 +50,12 @@ func (h *OnlyOfficeFileBrowserHandler) Apply(ac *ApplyContext) error {
// Atomic write // Atomic write
tmpPath := configPath + ".tmp" tmpPath := configPath + ".tmp"
if err := os.WriteFile(tmpPath, []byte(configStr), 0644); err != nil { if err := os.WriteFile(tmpPath, []byte(configStr), 0644); err != nil {
ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-FileBrowser apply: %v", err)
return fmt.Errorf("config írási hiba: %w", err) return fmt.Errorf("config írási hiba: %w", err)
} }
if err := os.Rename(tmpPath, configPath); err != nil { if err := os.Rename(tmpPath, configPath); err != nil {
_ = os.Remove(tmpPath) _ = os.Remove(tmpPath)
ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-FileBrowser apply: %v", err)
return fmt.Errorf("config átnevezési hiba: %w", err) return fmt.Errorf("config átnevezési hiba: %w", err)
} }
@@ -67,6 +72,7 @@ func (h *OnlyOfficeFileBrowserHandler) Revoke(ac *ApplyContext) error {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil return nil
} }
ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-FileBrowser revoke: %v", err)
return fmt.Errorf("config olvasási hiba: %w", err) return fmt.Errorf("config olvasási hiba: %w", err)
} }
@@ -79,10 +85,12 @@ func (h *OnlyOfficeFileBrowserHandler) Revoke(ac *ApplyContext) error {
tmpPath := configPath + ".tmp" tmpPath := configPath + ".tmp"
if err := os.WriteFile(tmpPath, []byte(cleaned), 0644); err != nil { if err := os.WriteFile(tmpPath, []byte(cleaned), 0644); err != nil {
ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-FileBrowser revoke: %v", err)
return fmt.Errorf("config írási hiba: %w", err) return fmt.Errorf("config írási hiba: %w", err)
} }
if err := os.Rename(tmpPath, configPath); err != nil { if err := os.Rename(tmpPath, configPath); err != nil {
_ = os.Remove(tmpPath) _ = os.Remove(tmpPath)
ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-FileBrowser revoke: %v", err)
return fmt.Errorf("config átnevezési hiba: %w", err) return fmt.Errorf("config átnevezési hiba: %w", err)
} }
@@ -14,6 +14,7 @@ type OnlyOfficeNextcloudHandler struct{}
func (h *OnlyOfficeNextcloudHandler) Apply(ac *ApplyContext) error { func (h *OnlyOfficeNextcloudHandler) Apply(ac *ApplyContext) error {
jwtSecret := ac.ProviderEnv["JWT_SECRET"] jwtSecret := ac.ProviderEnv["JWT_SECRET"]
if jwtSecret == "" { if jwtSecret == "" {
ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-Nextcloud apply: JWT_SECRET not set")
return fmt.Errorf("OnlyOffice JWT_SECRET nincs beállítva") return fmt.Errorf("OnlyOffice JWT_SECRET nincs beállítva")
} }
@@ -22,6 +23,7 @@ func (h *OnlyOfficeNextcloudHandler) Apply(ac *ApplyContext) error {
subdomain = ac.ProviderMeta.Subdomain subdomain = ac.ProviderMeta.Subdomain
} }
if subdomain == "" { if subdomain == "" {
ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-Nextcloud apply: subdomain unknown")
return fmt.Errorf("OnlyOffice aldomain nem ismert") return fmt.Errorf("OnlyOffice aldomain nem ismert")
} }
@@ -72,6 +74,7 @@ func (h *OnlyOfficeNextcloudHandler) Apply(ac *ApplyContext) error {
ac.Logger.Printf("[DEBUG] Nextcloud occ: tolerated — %s", strings.TrimSpace(string(out))) ac.Logger.Printf("[DEBUG] Nextcloud occ: tolerated — %s", strings.TrimSpace(string(out)))
continue 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))) 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] Nextcloud occ %s: ok", strings.Join(cmd.args[7:], " "))
@@ -96,6 +99,7 @@ func (h *OnlyOfficeNextcloudHandler) Revoke(ac *ApplyContext) error {
ac.Logger.Printf("[DEBUG] Nextcloud occ app:disable skipped — %s", strings.TrimSpace(outStr)) ac.Logger.Printf("[DEBUG] Nextcloud occ app:disable skipped — %s", strings.TrimSpace(outStr))
return nil 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)) return fmt.Errorf("occ app:disable sikertelen: %v (kimenet: %s)", err, strings.TrimSpace(outStr))
} }
@@ -48,11 +48,17 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
if sysInfo.DiskPercent > 0 { if sysInfo.DiskPercent > 0 {
if sysInfo.DiskPercent >= float64(cfg.Monitoring.Thresholds.DiskCritPercent) { if sysInfo.DiskPercent >= float64(cfg.Monitoring.Thresholds.DiskCritPercent) {
report.Issues = append(report.Issues, fmt.Sprintf("SSD disk usage critical: %.0f%%", sysInfo.DiskPercent)) report.Issues = append(report.Issues, fmt.Sprintf("SSD disk usage critical: %.0f%%", sysInfo.DiskPercent))
if logger != nil {
logger.Printf("[WARN] [monitor] Disk (SSD) threshold breached: %.0f%% (limit: %d%%)", sysInfo.DiskPercent, cfg.Monitoring.Thresholds.DiskCritPercent)
}
if debug { if debug {
logger.Printf("[DEBUG] [HEALTH] SSD disk: CRITICAL (%.0f%% >= %d%%)", sysInfo.DiskPercent, cfg.Monitoring.Thresholds.DiskCritPercent) logger.Printf("[DEBUG] [HEALTH] SSD disk: CRITICAL (%.0f%% >= %d%%)", sysInfo.DiskPercent, cfg.Monitoring.Thresholds.DiskCritPercent)
} }
} else if sysInfo.DiskPercent >= float64(cfg.Monitoring.Thresholds.DiskWarnPercent) { } else if sysInfo.DiskPercent >= float64(cfg.Monitoring.Thresholds.DiskWarnPercent) {
report.Warnings = append(report.Warnings, fmt.Sprintf("SSD disk usage high: %.0f%%", sysInfo.DiskPercent)) report.Warnings = append(report.Warnings, fmt.Sprintf("SSD disk usage high: %.0f%%", sysInfo.DiskPercent))
if logger != nil {
logger.Printf("[WARN] [monitor] Disk (SSD) threshold breached: %.0f%% (limit: %d%%)", sysInfo.DiskPercent, cfg.Monitoring.Thresholds.DiskWarnPercent)
}
if debug { if debug {
logger.Printf("[DEBUG] [HEALTH] SSD disk: WARN (%.0f%% >= %d%%)", sysInfo.DiskPercent, cfg.Monitoring.Thresholds.DiskWarnPercent) logger.Printf("[DEBUG] [HEALTH] SSD disk: WARN (%.0f%% >= %d%%)", sysInfo.DiskPercent, cfg.Monitoring.Thresholds.DiskWarnPercent)
} }
@@ -68,8 +74,14 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
if sysInfo.HDDConfigured && sysInfo.HDDPercent > 0 { if sysInfo.HDDConfigured && sysInfo.HDDPercent > 0 {
if sysInfo.HDDPercent >= float64(cfg.Monitoring.Thresholds.DiskCritPercent) { if sysInfo.HDDPercent >= float64(cfg.Monitoring.Thresholds.DiskCritPercent) {
report.Issues = append(report.Issues, fmt.Sprintf("HDD disk usage critical: %.0f%%", sysInfo.HDDPercent)) report.Issues = append(report.Issues, fmt.Sprintf("HDD disk usage critical: %.0f%%", sysInfo.HDDPercent))
if logger != nil {
logger.Printf("[WARN] [monitor] Disk (HDD) threshold breached: %.0f%% (limit: %d%%)", sysInfo.HDDPercent, cfg.Monitoring.Thresholds.DiskCritPercent)
}
} else if sysInfo.HDDPercent >= float64(cfg.Monitoring.Thresholds.DiskWarnPercent) { } else if sysInfo.HDDPercent >= float64(cfg.Monitoring.Thresholds.DiskWarnPercent) {
report.Warnings = append(report.Warnings, fmt.Sprintf("HDD disk usage high: %.0f%%", sysInfo.HDDPercent)) report.Warnings = append(report.Warnings, fmt.Sprintf("HDD disk usage high: %.0f%%", sysInfo.HDDPercent))
if logger != nil {
logger.Printf("[WARN] [monitor] Disk (HDD) threshold breached: %.0f%% (limit: %d%%)", sysInfo.HDDPercent, cfg.Monitoring.Thresholds.DiskWarnPercent)
}
} }
} }
@@ -77,6 +89,9 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
if sysInfo.MemPercent > 0 { if sysInfo.MemPercent > 0 {
if sysInfo.MemPercent >= float64(cfg.Monitoring.Thresholds.MemoryWarnPercent) { if sysInfo.MemPercent >= float64(cfg.Monitoring.Thresholds.MemoryWarnPercent) {
report.Warnings = append(report.Warnings, fmt.Sprintf("Memory usage high: %.0f%%", sysInfo.MemPercent)) report.Warnings = append(report.Warnings, fmt.Sprintf("Memory usage high: %.0f%%", sysInfo.MemPercent))
if logger != nil {
logger.Printf("[WARN] [monitor] Memory threshold breached: %.0f%% (limit: %d%%)", sysInfo.MemPercent, cfg.Monitoring.Thresholds.MemoryWarnPercent)
}
if debug { if debug {
logger.Printf("[DEBUG] [HEALTH] Memory: WARN (%.0f%% >= %d%%)", sysInfo.MemPercent, cfg.Monitoring.Thresholds.MemoryWarnPercent) logger.Printf("[DEBUG] [HEALTH] Memory: WARN (%.0f%% >= %d%%)", sysInfo.MemPercent, cfg.Monitoring.Thresholds.MemoryWarnPercent)
} }
@@ -92,6 +107,9 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
if sysInfo.CPUPercent > 0 { if sysInfo.CPUPercent > 0 {
if sysInfo.CPUPercent >= float64(cfg.Monitoring.Thresholds.CPUWarnPercent) { if sysInfo.CPUPercent >= float64(cfg.Monitoring.Thresholds.CPUWarnPercent) {
report.Warnings = append(report.Warnings, fmt.Sprintf("CPU usage high: %.0f%%", sysInfo.CPUPercent)) report.Warnings = append(report.Warnings, fmt.Sprintf("CPU usage high: %.0f%%", sysInfo.CPUPercent))
if logger != nil {
logger.Printf("[WARN] [monitor] CPU threshold breached: %.0f%% (limit: %d%%)", sysInfo.CPUPercent, cfg.Monitoring.Thresholds.CPUWarnPercent)
}
if debug { if debug {
logger.Printf("[DEBUG] [HEALTH] CPU: WARN (%.0f%% >= %d%%)", sysInfo.CPUPercent, cfg.Monitoring.Thresholds.CPUWarnPercent) logger.Printf("[DEBUG] [HEALTH] CPU: WARN (%.0f%% >= %d%%)", sysInfo.CPUPercent, cfg.Monitoring.Thresholds.CPUWarnPercent)
} }
@@ -107,6 +125,9 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
if sysInfo.TemperatureCelsius > 0 { if sysInfo.TemperatureCelsius > 0 {
if sysInfo.TemperatureCelsius >= float64(cfg.Monitoring.Thresholds.TemperatureWarnCelsius) { if sysInfo.TemperatureCelsius >= float64(cfg.Monitoring.Thresholds.TemperatureWarnCelsius) {
report.Warnings = append(report.Warnings, fmt.Sprintf("Temperature high: %.0f°C (%s)", sysInfo.TemperatureCelsius, sysInfo.TemperatureSource)) report.Warnings = append(report.Warnings, fmt.Sprintf("Temperature high: %.0f°C (%s)", sysInfo.TemperatureCelsius, sysInfo.TemperatureSource))
if logger != nil {
logger.Printf("[WARN] [monitor] Temperature threshold breached: %.0f°C (limit: %d°C)", sysInfo.TemperatureCelsius, cfg.Monitoring.Thresholds.TemperatureWarnCelsius)
}
if debug { if debug {
logger.Printf("[DEBUG] [HEALTH] Temperature: WARN (%.0f°C >= %d°C)", sysInfo.TemperatureCelsius, cfg.Monitoring.Thresholds.TemperatureWarnCelsius) logger.Printf("[DEBUG] [HEALTH] Temperature: WARN (%.0f°C >= %d°C)", sysInfo.TemperatureCelsius, cfg.Monitoring.Thresholds.TemperatureWarnCelsius)
} }
@@ -159,6 +180,10 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
report.Status = "warn" report.Status = "warn"
} }
if logger != nil {
logger.Printf("[INFO] [monitor] Health check: status=%s", report.Status)
}
if debug { if debug {
logger.Printf("[DEBUG] [HEALTH] Final status: %s (issues=%d, warnings=%d, info=%d)", logger.Printf("[DEBUG] [HEALTH] Final status: %s (issues=%d, warnings=%d, info=%d)",
report.Status, len(report.Issues), len(report.Warnings), len(report.Info)) report.Status, len(report.Issues), len(report.Warnings), len(report.Info))
+3
View File
@@ -34,6 +34,9 @@ func BuildReport(
logger *log.Logger, logger *log.Logger,
) *Report { ) *Report {
debug := cfg.Logging.Level == "debug" debug := cfg.Logging.Level == "debug"
if logger != nil {
logger.Printf("[INFO] [report] Building system report")
}
if debug && logger != nil { if debug && logger != nil {
logger.Printf("[DEBUG] BuildReport: starting — version=%s, storagePaths=%d", version, len(storagePaths)) logger.Printf("[DEBUG] BuildReport: starting — version=%s, storagePaths=%d", version, len(storagePaths))
} }
+3
View File
@@ -125,6 +125,8 @@ func (p *Pusher) Push(report *Report) error {
lastErr = fmt.Errorf("HTTP %d", resp.StatusCode) lastErr = fmt.Errorf("HTTP %d", resp.StatusCode)
} }
p.logger.Printf("[WARN] [report] Push failed: %v", lastErr)
p.statusMu.Lock() p.statusMu.Lock()
p.status.LastError = lastErr.Error() p.status.LastError = lastErr.Error()
p.status.Consecutive++ p.status.Consecutive++
@@ -186,6 +188,7 @@ func (p *Pusher) PushInfraBackup(data []byte) error {
} }
} }
p.logger.Printf("[WARN] [report] InfraBackup push failed: %v", lastErr)
return fmt.Errorf("infra backup push failed after 3 attempts: %w", lastErr) return fmt.Errorf("infra backup push failed after 3 attempts: %w", lastErr)
} }
+14 -13
View File
@@ -75,7 +75,7 @@ func New(logger *log.Logger) *Scheduler {
// If the scheduler is already started, the job's goroutine is launched immediately. // If the scheduler is already started, the job's goroutine is launched immediately.
func (s *Scheduler) Every(name string, interval time.Duration, fn JobFunc) { func (s *Scheduler) Every(name string, interval time.Duration, fn JobFunc) {
if interval <= 0 { if interval <= 0 {
s.logger.Printf("[ERROR] Periodic job %s has invalid interval %s — job not registered", name, interval) s.logger.Printf("[ERROR] [scheduler] Periodic job %s has invalid interval %s — job not registered", name, interval)
return return
} }
@@ -88,7 +88,7 @@ func (s *Scheduler) Every(name string, interval time.Duration, fn JobFunc) {
Interval: interval, Interval: interval,
} }
s.jobs = append(s.jobs, job) s.jobs = append(s.jobs, job)
s.logger.Printf("[SCHED] Registered periodic job: %s (every %s)", name, interval) s.logger.Printf("[INFO] [scheduler] Registered periodic job: %s (every %s)", name, interval)
s.dbg("periodic job registered: name=%q interval=%s totalJobs=%d", name, interval, len(s.jobs)) s.dbg("periodic job registered: name=%q interval=%s totalJobs=%d", name, interval, len(s.jobs))
if s.started { if s.started {
@@ -105,7 +105,7 @@ func (s *Scheduler) Daily(name string, timeStr string, fn JobFunc) {
// Validate time format // Validate time format
if _, _, err := parseDailyTime(timeStr); err != nil { if _, _, err := parseDailyTime(timeStr); err != nil {
s.logger.Printf("[ERROR] Daily job %s has invalid schedule %q: %v — job not started", name, timeStr, err) s.logger.Printf("[ERROR] [scheduler] Daily job %s has invalid schedule %q: %v — job not started", name, timeStr, err)
return return
} }
@@ -117,7 +117,7 @@ func (s *Scheduler) Daily(name string, timeStr string, fn JobFunc) {
s.jobs = append(s.jobs, job) s.jobs = append(s.jobs, job)
nextRun := nextDailyRun(timeStr) nextRun := nextDailyRun(timeStr)
s.logger.Printf("[SCHED] Daily job %s scheduled for %s", name, nextRun.Format("2006-01-02 15:04 MST")) s.logger.Printf("[INFO] [scheduler] Daily job %s scheduled for %s", name, nextRun.Format("2006-01-02 15:04 MST"))
s.dbg("daily job registered: name=%q schedule=%q nextRun=%s totalJobs=%d", name, timeStr, nextRun.Format(time.RFC3339), len(s.jobs)) s.dbg("daily job registered: name=%q schedule=%q nextRun=%s totalJobs=%d", name, timeStr, nextRun.Format(time.RFC3339), len(s.jobs))
if s.started { if s.started {
@@ -131,7 +131,7 @@ func (s *Scheduler) Start(ctx context.Context) {
s.mu.Lock() s.mu.Lock()
if s.cancel != nil { if s.cancel != nil {
s.mu.Unlock() s.mu.Unlock()
s.logger.Println("[WARN] Scheduler already started — ignoring duplicate Start()") s.logger.Println("[WARN] [scheduler] Scheduler already started — ignoring duplicate Start()")
return return
} }
s.ctx, s.cancel = context.WithCancel(ctx) s.ctx, s.cancel = context.WithCancel(ctx)
@@ -147,7 +147,7 @@ func (s *Scheduler) Start(ctx context.Context) {
} }
} }
s.logger.Printf("[SCHED] Scheduler started with %d jobs", len(s.jobs)) s.logger.Printf("[INFO] [scheduler] Starting scheduler with %d jobs", len(s.jobs))
s.dbg("scheduler started: periodic=%d daily=%d", func() int { s.dbg("scheduler started: periodic=%d daily=%d", func() int {
n := 0 n := 0
for _, j := range s.jobs { for _, j := range s.jobs {
@@ -173,6 +173,7 @@ func (s *Scheduler) Stop() {
s.mu.Lock() s.mu.Lock()
cancel := s.cancel cancel := s.cancel
s.mu.Unlock() s.mu.Unlock()
s.logger.Printf("[INFO] [scheduler] Stopping scheduler")
if cancel != nil { if cancel != nil {
cancel() cancel()
} }
@@ -185,9 +186,9 @@ func (s *Scheduler) Stop() {
select { select {
case <-done: case <-done:
s.logger.Println("[SCHED] All jobs stopped") s.logger.Println("[INFO] [scheduler] All jobs stopped")
case <-time.After(30 * time.Second): case <-time.After(30 * time.Second):
s.logger.Println("[WARN] Scheduler stop timed out after 30s — some jobs may still be running") s.logger.Println("[WARN] [scheduler] Scheduler stop timed out after 30s — some jobs may still be running")
} }
} }
@@ -251,7 +252,7 @@ func (s *Scheduler) executeJob(job *Job, quiet bool) {
s.mu.Lock() s.mu.Lock()
if job.Running { if job.Running {
s.mu.Unlock() s.mu.Unlock()
s.logger.Printf("[WARN] Job %s still running, skipping", job.Name) s.logger.Printf("[WARN] [scheduler] Job %s still running, skipping", job.Name)
return return
} }
job.Running = true job.Running = true
@@ -270,12 +271,12 @@ func (s *Scheduler) executeJob(job *Job, quiet bool) {
job.LastErr = fmt.Errorf("panic: %v", r) job.LastErr = fmt.Errorf("panic: %v", r)
job.LastRun = time.Now() job.LastRun = time.Now()
s.mu.Unlock() s.mu.Unlock()
s.logger.Printf("[ERROR] Job %s panicked: %v", job.Name, r) s.logger.Printf("[ERROR] [scheduler] Job %s panicked: %v", job.Name, r)
} }
}() }()
if !quiet { if !quiet {
s.logger.Printf("[SCHED] Running job: %s", job.Name) s.logger.Printf("[INFO] [scheduler] Running job: %s", job.Name)
} }
s.dbg("job %s: execution starting", job.Name) s.dbg("job %s: execution starting", job.Name)
@@ -289,10 +290,10 @@ func (s *Scheduler) executeJob(job *Job, quiet bool) {
s.mu.Unlock() s.mu.Unlock()
if err != nil { if err != nil {
s.logger.Printf("[WARN] Job %s failed: %v (took %s)", job.Name, err, elapsed.Round(time.Millisecond)) s.logger.Printf("[ERROR] [scheduler] Job %s failed: %v (took %s)", job.Name, err, elapsed.Round(time.Millisecond))
s.dbg("job %s: failed after %s: %v", job.Name, elapsed.Round(time.Millisecond), err) s.dbg("job %s: failed after %s: %v", job.Name, elapsed.Round(time.Millisecond), err)
} else if !quiet { } else if !quiet {
s.logger.Printf("[SCHED] Job %s completed (took %s)", job.Name, elapsed.Round(time.Millisecond)) s.logger.Printf("[INFO] [scheduler] Job %s completed (took %s)", job.Name, elapsed.Round(time.Millisecond))
} }
s.dbg("job %s: finished in %s (err=%v)", job.Name, elapsed.Round(time.Millisecond), err) s.dbg("job %s: finished in %s (err=%v)", job.Name, elapsed.Round(time.Millisecond), err)
} }
+2
View File
@@ -67,5 +67,7 @@ func ClearState(dataDir string, logger *log.Logger) {
path := filepath.Join(dataDir, stateFileName) path := filepath.Join(dataDir, stateFileName)
if err := os.Remove(path); err != nil && !os.IsNotExist(err) { if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
logger.Printf("[WARN] Failed to clear update state file: %v", err) logger.Printf("[WARN] Failed to clear update state file: %v", err)
return
} }
logger.Printf("[INFO] [selfupdate] Update state cleared")
} }
@@ -144,6 +144,9 @@ func (u *Updater) CheckForUpdate() CheckResult {
cmp := latestVer.Compare(currentVer) cmp := latestVer.Compare(currentVer)
if cmp > 0 { if cmp > 0 {
result.UpdateAvailable = true result.UpdateAvailable = true
u.logger.Printf("[INFO] [selfupdate] Update available: %s → %s", u.currentVer, latestStr)
} else {
u.logger.Printf("[INFO] [selfupdate] Current version %s is up to date", u.currentVer)
} }
u.dbg("version comparison: current=%s (%d.%d.%d), latest=%s (%d.%d.%d), cmp=%d, updateAvailable=%v", u.dbg("version comparison: current=%s (%d.%d.%d), latest=%s (%d.%d.%d), cmp=%d, updateAvailable=%v",
@@ -422,8 +425,10 @@ func (u *Updater) performUpdate(targetVersion, targetImage, previousImage, initi
// updateComposeFile reads the compose file, replaces the image tag, and writes it back atomically. // updateComposeFile reads the compose file, replaces the image tag, and writes it back atomically.
func (u *Updater) updateComposeFile(newImage string) error { func (u *Updater) updateComposeFile(newImage string) error {
u.logger.Printf("[INFO] [selfupdate] Updating compose file")
data, err := os.ReadFile(u.composePath) data, err := os.ReadFile(u.composePath)
if err != nil { if err != nil {
u.logger.Printf("[ERROR] [selfupdate] Failed to update compose file: %v", err)
return fmt.Errorf("reading compose file: %w", err) return fmt.Errorf("reading compose file: %w", err)
} }
@@ -441,15 +446,18 @@ func (u *Updater) updateComposeFile(newImage string) error {
newData := re.ReplaceAll(data, []byte("${1}"+newImage)) newData := re.ReplaceAll(data, []byte("${1}"+newImage))
if bytes.Equal(data, newData) { if bytes.Equal(data, newData) {
u.logger.Printf("[ERROR] [selfupdate] Failed to update compose file: no image line found to replace")
return fmt.Errorf("no image line found to replace in compose file") return fmt.Errorf("no image line found to replace in compose file")
} }
// Atomic write: write to .tmp, then rename // Atomic write: write to .tmp, then rename
tmpPath := u.composePath + ".tmp" tmpPath := u.composePath + ".tmp"
if err := os.WriteFile(tmpPath, newData, 0644); err != nil { if err := os.WriteFile(tmpPath, newData, 0644); err != nil {
u.logger.Printf("[ERROR] [selfupdate] Failed to update compose file: %v", err)
return fmt.Errorf("writing temp compose file: %w", err) return fmt.Errorf("writing temp compose file: %w", err)
} }
if err := os.Rename(tmpPath, u.composePath); err != nil { if err := os.Rename(tmpPath, u.composePath); err != nil {
u.logger.Printf("[ERROR] [selfupdate] Failed to update compose file: %v", err)
return fmt.Errorf("renaming compose file: %w", err) return fmt.Errorf("renaming compose file: %w", err)
} }
+31 -1
View File
@@ -183,7 +183,7 @@ func Load(path string, logger *log.Logger) (*Settings, error) {
return nil, fmt.Errorf("parsing settings file: %w", err) return nil, fmt.Errorf("parsing settings file: %w", err)
} }
logger.Printf("[DEBUG] Settings loaded from %s", path) logger.Printf("[INFO] [settings] Loaded settings from %s", path)
if s.debug { if s.debug {
s.log.Printf("[DEBUG] [settings] loaded: storage_paths=%d integrations=%d pending_events=%d", s.log.Printf("[DEBUG] [settings] loaded: storage_paths=%d integrations=%d pending_events=%d",
len(s.StoragePaths), len(s.Integrations), len(s.PendingEvents)) len(s.StoragePaths), len(s.Integrations), len(s.PendingEvents))
@@ -218,27 +218,42 @@ func (s *Settings) migrateResticToRsync() {
func (s *Settings) save() error { func (s *Settings) save() error {
data, err := json.MarshalIndent(s, "", " ") data, err := json.MarshalIndent(s, "", " ")
if err != nil { if err != nil {
if s.log != nil {
s.log.Printf("[ERROR] [settings] Failed to save: %v", err)
}
return fmt.Errorf("marshaling settings: %w", err) return fmt.Errorf("marshaling settings: %w", err)
} }
tmpPath := s.path + ".tmp" tmpPath := s.path + ".tmp"
if err := os.MkdirAll(filepath.Dir(s.path), 0755); err != nil { if err := os.MkdirAll(filepath.Dir(s.path), 0755); err != nil {
if s.log != nil {
s.log.Printf("[ERROR] [settings] Failed to save: %v", err)
}
return fmt.Errorf("creating settings dir: %w", err) return fmt.Errorf("creating settings dir: %w", err)
} }
if err := os.WriteFile(tmpPath, data, 0644); err != nil { if err := os.WriteFile(tmpPath, data, 0644); err != nil {
os.Remove(tmpPath) // clean up partial file os.Remove(tmpPath) // clean up partial file
if s.log != nil {
s.log.Printf("[ERROR] [settings] Failed to save: %v", err)
}
return fmt.Errorf("writing tmp settings: %w", err) return fmt.Errorf("writing tmp settings: %w", err)
} }
if err := os.Rename(tmpPath, s.path); err != nil { if err := os.Rename(tmpPath, s.path); err != nil {
os.Remove(tmpPath) os.Remove(tmpPath)
if s.log != nil {
s.log.Printf("[ERROR] [settings] Failed to save: %v", err)
}
return fmt.Errorf("renaming settings file: %w", err) return fmt.Errorf("renaming settings file: %w", err)
} }
if s.debug { if s.debug {
s.log.Printf("[DEBUG] [settings] saved to %s (%d bytes)", s.path, len(data)) s.log.Printf("[DEBUG] [settings] saved to %s (%d bytes)", s.path, len(data))
} }
if s.log != nil {
s.log.Printf("[INFO] [settings] Settings saved")
}
return nil return nil
} }
@@ -461,6 +476,9 @@ func (s *Settings) AddStoragePath(sp StoragePath) error {
} }
} }
s.StoragePaths = append(s.StoragePaths, sp) s.StoragePaths = append(s.StoragePaths, sp)
if s.log != nil {
s.log.Printf("[INFO] [settings] Added storage path: %s", sp.Path)
}
return s.save() return s.save()
} }
@@ -478,6 +496,9 @@ func (s *Settings) RemoveStoragePath(path string) error {
} }
} }
s.StoragePaths = kept s.StoragePaths = kept
if s.log != nil {
s.log.Printf("[INFO] [settings] Removed storage path: %s", path)
}
return s.save() return s.save()
} }
@@ -597,6 +618,9 @@ func (s *Settings) SetDisconnected(path string, disconnected bool, stoppedStacks
if s.debug { if s.debug {
s.log.Printf("[DEBUG] [settings] SetDisconnected path=%q disconnected=%v stopped_stacks=%d", path, disconnected, len(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)
}
for i := range s.StoragePaths { for i := range s.StoragePaths {
if s.StoragePaths[i].Path == path { if s.StoragePaths[i].Path == path {
s.StoragePaths[i].Disconnected = disconnected s.StoragePaths[i].Disconnected = disconnected
@@ -707,6 +731,9 @@ func (s *Settings) SetDecommissioned(path, migratedTo string) error {
if s.debug { if s.debug {
s.log.Printf("[DEBUG] [settings] SetDecommissioned path=%q migrated_to=%q", path, migratedTo) s.log.Printf("[DEBUG] [settings] SetDecommissioned path=%q migrated_to=%q", path, migratedTo)
} }
if s.log != nil {
s.log.Printf("[INFO] [settings] Node decommissioned")
}
for i := range s.StoragePaths { for i := range s.StoragePaths {
if s.StoragePaths[i].Path == path { if s.StoragePaths[i].Path == path {
s.StoragePaths[i].Decommissioned = true s.StoragePaths[i].Decommissioned = true
@@ -842,6 +869,9 @@ func (s *Settings) AddPendingEvent(event PendingEvent) error {
if s.debug { if s.debug {
s.log.Printf("[DEBUG] [settings] AddPendingEvent type=%q severity=%q", event.EventType, event.Severity) s.log.Printf("[DEBUG] [settings] AddPendingEvent type=%q severity=%q", event.EventType, event.Severity)
} }
if s.log != nil {
s.log.Printf("[INFO] [settings] Added pending event: %s", event.EventType)
}
s.PendingEvents = append(s.PendingEvents, event) s.PendingEvents = append(s.PendingEvents, event)
return s.save() return s.save()
} }
+2
View File
@@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"context" "context"
"fmt" "fmt"
"log"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -557,6 +558,7 @@ func ParseComposeHDDMounts(composePath, hddPath string) []string {
} }
} }
log.Printf("[INFO] [stacks] ParseComposeHDDMounts: found %d HDD mounts for %s", len(mounts), composePath)
return mounts return mounts
} }
+10
View File
@@ -375,6 +375,7 @@ func (m *Manager) runComposeDeploy(name, stackDir string, env map[string]string,
// UpdateStackConfig updates non-locked fields for a deployed stack. // UpdateStackConfig updates non-locked fields for a deployed stack.
func (m *Manager) UpdateStackConfig(name string, values map[string]string) error { func (m *Manager) UpdateStackConfig(name string, values map[string]string) error {
m.logger.Printf("[INFO] [stacks] Updating config for stack %s", name)
if m.isDebug() { if m.isDebug() {
m.logger.Printf("[DEBUG] [stacks] UpdateStackConfig called: name=%q, %d values to update", name, len(values)) m.logger.Printf("[DEBUG] [stacks] UpdateStackConfig called: name=%q, %d values to update", name, len(values))
} }
@@ -457,6 +458,7 @@ func (m *Manager) GetDeployFields(name string) (*Metadata, *AppConfig, error) {
// UpdateOptionalConfig updates optional env vars in app.yaml and restarts the stack if deployed. // UpdateOptionalConfig updates optional env vars in app.yaml and restarts the stack if deployed.
// Only updates env vars that are listed in the metadata's optional_config sections. // Only updates env vars that are listed in the metadata's optional_config sections.
func (m *Manager) UpdateOptionalConfig(stackName string, values map[string]string) error { func (m *Manager) UpdateOptionalConfig(stackName string, values map[string]string) error {
m.logger.Printf("[INFO] [stacks] Updating optional config for stack %s", stackName)
if m.isDebug() { if m.isDebug() {
m.logger.Printf("[DEBUG] [stacks] UpdateOptionalConfig called: stack=%q, %d values provided", stackName, len(values)) m.logger.Printf("[DEBUG] [stacks] UpdateOptionalConfig called: stack=%q, %d values provided", stackName, len(values))
} }
@@ -588,6 +590,7 @@ func LoadAppConfig(stackDir string) *AppConfig {
} }
cfg := &AppConfig{} cfg := &AppConfig{}
if err := yaml.Unmarshal(data, cfg); err != nil { 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) log.Printf("[DEBUG] [stacks] LoadAppConfig: failed to parse %s: %v", path, err)
return nil return nil
} }
@@ -626,6 +629,7 @@ func SaveAppConfig(stackDir string, cfg *AppConfig, encKey []byte, sensitiveVars
data, err := yaml.Marshal(saveCfg) data, err := yaml.Marshal(saveCfg)
if err != nil { if err != nil {
log.Printf("[ERROR] [stacks] SaveAppConfig: failed to marshal config for %s: %v", stackDir, err)
return fmt.Errorf("marshaling app config: %w", err) return fmt.Errorf("marshaling app config: %w", err)
} }
path := filepath.Join(stackDir, "app.yaml") path := filepath.Join(stackDir, "app.yaml")
@@ -635,12 +639,15 @@ func SaveAppConfig(stackDir string, cfg *AppConfig, encKey []byte, sensitiveVars
// Atomic write: write to .tmp then rename (H04 fix) // Atomic write: write to .tmp then rename (H04 fix)
tmpPath := path + ".tmp" tmpPath := path + ".tmp"
if err := os.WriteFile(tmpPath, []byte(content), 0600); err != nil { if err := os.WriteFile(tmpPath, []byte(content), 0600); err != nil {
log.Printf("[ERROR] [stacks] SaveAppConfig: failed to save %s: %v", path, err)
return fmt.Errorf("writing %s: %w", tmpPath, err) return fmt.Errorf("writing %s: %w", tmpPath, err)
} }
if err := os.Rename(tmpPath, path); err != nil { if err := os.Rename(tmpPath, path); err != nil {
_ = os.Remove(tmpPath) _ = os.Remove(tmpPath)
log.Printf("[ERROR] [stacks] SaveAppConfig: failed to save %s: %v", path, err)
return fmt.Errorf("renaming %s to %s: %w", tmpPath, path, err) return fmt.Errorf("renaming %s to %s: %w", tmpPath, path, err)
} }
log.Printf("[INFO] [stacks] SaveAppConfig: saved config for %s", filepath.Base(stackDir))
return nil return nil
} }
@@ -724,11 +731,13 @@ func (m *Manager) InjectMissingFields(stackNames []string) {
m.logger.Printf("[DEBUG] [stacks] InjectMissingFields: checking %d stacks", len(stackNames)) m.logger.Printf("[DEBUG] [stacks] InjectMissingFields: checking %d stacks", len(stackNames))
} }
count := 0
for _, name := range stackNames { for _, name := range stackNames {
stack, ok := m.GetStack(name) stack, ok := m.GetStack(name)
if !ok { if !ok {
continue continue
} }
count++
stackDir := filepath.Dir(stack.ComposePath) stackDir := filepath.Dir(stack.ComposePath)
meta := LoadMetadata(stackDir) meta := LoadMetadata(stackDir)
@@ -804,6 +813,7 @@ func (m *Manager) InjectMissingFields(stackNames []string) {
m.logger.Printf("[SYNC] Stack %s: injected missing fields: %s", name, strings.Join(injected, ", ")) m.logger.Printf("[SYNC] Stack %s: injected missing fields: %s", name, strings.Join(injected, ", "))
} }
} }
m.logger.Printf("[INFO] [stacks] InjectMissingFields: processed %d stacks", count)
} }
func containsStr(slice []string, s string) bool { func containsStr(slice []string, s string) bool {
+3 -3
View File
@@ -126,9 +126,9 @@ func (m *Manager) RunHealthProbes() error {
// Summary log // Summary log
if failCount > 0 { if failCount > 0 {
m.logger.Printf("[INFO] Health probes: %d ok, %d unhealthy (of %d probed)", okCount, failCount, len(targets)) m.logger.Printf("[WARN] Health probes: %d ok, %d unhealthy (of %d probed)", okCount, failCount, len(targets))
} else if m.isDebug() { } else {
m.logger.Printf("[DEBUG] Health probes: %d ok (of %d probed)", okCount, len(targets)) m.logger.Printf("[INFO] Health probes: %d ok (of %d probed)", okCount, len(targets))
} }
return nil return nil
+6
View File
@@ -184,6 +184,8 @@ func (m *Manager) MigrateEncryption() {
} }
if migrated > 0 { if migrated > 0 {
m.logger.Printf("[INFO] Encrypted sensitive values in %d app.yaml file(s)", migrated) m.logger.Printf("[INFO] Encrypted sensitive values in %d app.yaml file(s)", migrated)
} else {
m.logger.Printf("[INFO] [stacks] Encryption migration: no stacks needed migration")
} }
} }
@@ -326,6 +328,7 @@ func (m *Manager) ScanStacks() error {
} }
m.logger.Printf("[INFO] Scanned stacks: %d found (%d deployed, %d available)", m.logger.Printf("[INFO] Scanned stacks: %d found (%d deployed, %d available)",
len(m.stacks), deployedCount, len(m.stacks)-deployedCount) len(m.stacks), deployedCount, len(m.stacks)-deployedCount)
m.logger.Printf("[INFO] [stacks] ScanStacks complete: %d stacks found", len(m.stacks))
return m.refreshStatusLocked() return m.refreshStatusLocked()
} }
@@ -370,6 +373,8 @@ func (m *Manager) refreshStatusLocked() error {
m.logger.Printf("[DEBUG] [stacks] refreshStatusLocked: docker ps returned %d containers across %d projects", totalContainers, len(projectContainers)) m.logger.Printf("[DEBUG] [stacks] refreshStatusLocked: docker ps returned %d containers across %d projects", totalContainers, len(projectContainers))
} }
m.logger.Printf("[INFO] [stacks] Status refresh: %d containers across %d stacks", totalContainers, len(m.stacks))
for name, stack := range m.stacks { for name, stack := range m.stacks {
containers, exists := projectContainers[name] containers, exists := projectContainers[name]
if !exists { if !exists {
@@ -759,6 +764,7 @@ func (m *Manager) GetLogs(name string, lines int) (string, error) {
lines = 1000 lines = 1000
} }
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) m.logger.Printf("[DEBUG] Fetching logs for %s (tail %d)", name, lines)
dir := filepath.Dir(stack.ComposePath) dir := filepath.Dir(stack.ComposePath)
+2 -2
View File
@@ -1,7 +1,7 @@
package stacks package stacks
import ( import (
"fmt" "log"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -126,7 +126,7 @@ func LoadMetadata(stackDir string) Metadata {
} }
if err := yaml.Unmarshal(data, &meta); err != nil { if err := yaml.Unmarshal(data, &meta); err != nil {
fmt.Fprintf(os.Stderr, "[ERROR] Failed to parse .felhom.yml in %s: %v\n", stackDir, err) log.Printf("[ERROR] [stacks] Failed to parse .felhom.yml in %s: %v", stackDir, err)
dirName := filepath.Base(stackDir) dirName := filepath.Base(stackDir)
meta.DisplayName = toTitleCase(strings.ReplaceAll(dirName, "-", " ")) meta.DisplayName = toTitleCase(strings.ReplaceAll(dirName, "-", " "))
meta.Slug = dirName meta.Slug = dirName
@@ -184,6 +184,9 @@ func FinalizeAttach(req AttachRequest, progress chan<- FormatProgress) (string,
errStr = err.Error() errStr = err.Error()
} }
progress <- FormatProgress{Step: "error", Message: msg, Error: errStr, Percent: 0} progress <- FormatProgress{Step: "error", Message: msg, Error: errStr, Percent: 0}
if req.Logger != nil {
req.Logger.Printf("[ERROR] [storage] Failed to attach disk: %v", err)
}
return fmt.Errorf("%s: %w", msg, err) return fmt.Errorf("%s: %w", msg, err)
} }
dbg := func(format string, args ...interface{}) { dbg := func(format string, args ...interface{}) {
@@ -193,6 +196,9 @@ func FinalizeAttach(req AttachRequest, progress chan<- FormatProgress) (string,
} }
mountPath := "/mnt/" + req.MountName mountPath := "/mnt/" + req.MountName
if req.Logger != nil {
req.Logger.Printf("[INFO] [storage] Attaching disk %s at %s", req.DevicePath, mountPath)
}
dbg("starting: device=%s mountName=%s subPath=%s", req.DevicePath, req.MountName, req.SubPath) dbg("starting: device=%s mountName=%s subPath=%s", req.DevicePath, req.MountName, req.SubPath)
// --- Step 1: Validate --- // --- Step 1: Validate ---
@@ -322,6 +328,9 @@ func FinalizeAttach(req AttachRequest, progress chan<- FormatProgress) (string,
} }
dbg("attach completed successfully: %s", mountPath) dbg("attach completed successfully: %s", mountPath)
if req.Logger != nil {
req.Logger.Printf("[INFO] [storage] Disk attached successfully")
}
send("done", "Meghajtó sikeresen csatolva: "+mountPath, 100) send("done", "Meghajtó sikeresen csatolva: "+mountPath, 100)
return mountPath, nil return mountPath, nil
@@ -27,6 +27,9 @@ func FormatAndMount(req FormatRequest, progress chan<- FormatProgress) (string,
errStr = err.Error() errStr = err.Error()
} }
progress <- FormatProgress{Step: "error", Message: msg, Error: errStr, Percent: 0} progress <- FormatProgress{Step: "error", Message: msg, Error: errStr, Percent: 0}
if req.Logger != nil {
req.Logger.Printf("[ERROR] [storage] Format failed: %v", err)
}
return fmt.Errorf("%s: %w", msg, err) return fmt.Errorf("%s: %w", msg, err)
} }
dbg := func(format string, args ...interface{}) { dbg := func(format string, args ...interface{}) {
@@ -36,6 +39,9 @@ func FormatAndMount(req FormatRequest, progress chan<- FormatProgress) (string,
} }
mountPath := "/mnt/" + req.MountName mountPath := "/mnt/" + req.MountName
if req.Logger != nil {
req.Logger.Printf("[INFO] [storage] Formatting disk %s as ext4", req.DevicePath)
}
dbg("starting: device=%s mountName=%s createPartition=%v", req.DevicePath, req.MountName, req.CreatePartition) dbg("starting: device=%s mountName=%s createPartition=%v", req.DevicePath, req.MountName, req.CreatePartition)
// --- Step 1: Validate --- // --- Step 1: Validate ---
@@ -216,6 +222,9 @@ func FormatAndMount(req FormatRequest, progress chan<- FormatProgress) (string,
} }
dbg("format and mount completed successfully: %s", mountPath) dbg("format and mount completed successfully: %s", mountPath)
if req.Logger != nil {
req.Logger.Printf("[INFO] [storage] Disk formatted and mounted at %s", mountPath)
}
send("done", "Meghajtó sikeresen inicializálva: "+mountPath, 100) send("done", "Meghajtó sikeresen inicializálva: "+mountPath, 100)
return mountPath, nil return mountPath, nil
+9
View File
@@ -82,6 +82,9 @@ func MigrateAppData(
Error: errStr, Error: errStr,
ElapsedSeconds: int(time.Since(start).Seconds()), ElapsedSeconds: int(time.Since(start).Seconds()),
} }
if req.Logger != nil {
req.Logger.Printf("[ERROR] [storage] Migration %s failed at %s: %v", req.StackName, step, err)
}
return fmt.Errorf("%s: %w", msg, err) return fmt.Errorf("%s: %w", msg, err)
} }
@@ -91,6 +94,9 @@ func MigrateAppData(
} }
} }
if req.Logger != nil {
req.Logger.Printf("[INFO] [storage] Starting migration: stack=%s from=%s to=%s", req.StackName, req.CurrentHDDPath, req.TargetPath)
}
dbg("starting migration: stack=%s from=%s to=%s mounts=%d", req.StackName, req.CurrentHDDPath, req.TargetPath, len(req.HDDMounts)) dbg("starting migration: stack=%s from=%s to=%s mounts=%d", req.StackName, req.CurrentHDDPath, req.TargetPath, len(req.HDDMounts))
// --- Step 1: Validate --- // --- Step 1: Validate ---
@@ -196,6 +202,9 @@ func MigrateAppData(
return fail("starting", "Alkalmazás indítása sikertelen az új tárolóról", err) return fail("starting", "Alkalmazás indítása sikertelen az új tárolóról", err)
} }
if req.Logger != nil {
req.Logger.Printf("[INFO] [storage] Migration completed: stack=%s elapsed=%ds", req.StackName, int(time.Since(start).Seconds()))
}
dbg("migration completed: stack=%s bytesCopied=%d elapsed=%ds", req.StackName, bytesCopied, int(time.Since(start).Seconds())) dbg("migration completed: stack=%s bytesCopied=%d elapsed=%ds", req.StackName, bytesCopied, int(time.Since(start).Seconds()))
send("done", send("done",
fmt.Sprintf("Áthelyezés kész! Az alkalmazás az új tárolóról fut. (Régi adat: %s, idő: %ds)", fmt.Sprintf("Áthelyezés kész! Az alkalmazás az új tárolóról fut. (Régi adat: %s, idő: %ds)",
@@ -246,6 +246,9 @@ func ScanDisks(logger *log.Logger, debug bool) (*ScanResult, error) {
} }
} }
if logger != nil {
logger.Printf("[INFO] [storage] Scanning disks")
}
dbg("starting disk scan") dbg("starting disk scan")
scanStart := time.Now() scanStart := time.Now()
@@ -341,6 +344,9 @@ func ScanDisks(logger *log.Logger, debug bool) (*ScanResult, error) {
enrichWithBlkid(result.AvailableDisks, logger, debug) enrichWithBlkid(result.AvailableDisks, logger, debug)
enrichWithBlkid(result.SystemDisks, logger, debug) enrichWithBlkid(result.SystemDisks, logger, debug)
if logger != nil {
logger.Printf("[INFO] [storage] Found %d disks", len(result.AvailableDisks)+len(result.SystemDisks))
}
dbg("disk scan completed in %s", time.Since(scanStart).Round(time.Millisecond)) dbg("disk scan completed in %s", time.Since(scanStart).Round(time.Millisecond))
return result, nil return result, nil
+7 -1
View File
@@ -178,8 +178,11 @@ func (s *Syncer) doSync() SyncResult {
result := SyncResult{OK: true} result := SyncResult{OK: true}
s.logger.Printf("[INFO] [sync] Starting catalog sync")
// Step 1: Clone or pull // Step 1: Clone or pull
if err := s.gitCloneOrPull(); err != nil { if err := s.gitCloneOrPull(); err != nil {
s.logger.Printf("[ERROR] [sync] Catalog sync failed: %v", err)
s.mu.Lock() s.mu.Lock()
s.lastErr = err s.lastErr = err
s.lastSync = time.Now() s.lastSync = time.Now()
@@ -190,6 +193,7 @@ func (s *Syncer) doSync() SyncResult {
// Step 2: Copy templates to stacks dir // Step 2: Copy templates to stacks dir
newApps, updated, err := s.copyTemplates() newApps, updated, err := s.copyTemplates()
if err != nil { if err != nil {
s.logger.Printf("[ERROR] [sync] Catalog sync failed: %v", err)
s.mu.Lock() s.mu.Lock()
s.lastErr = err s.lastErr = err
s.lastSync = time.Now() s.lastSync = time.Now()
@@ -229,6 +233,8 @@ func (s *Syncer) doSync() SyncResult {
result.Message = "Sablonok frissítve — " + strings.Join(parts, "; ") result.Message = "Sablonok frissítve — " + strings.Join(parts, "; ")
} }
s.logger.Printf("[INFO] [sync] Catalog sync complete")
s.mu.Lock() s.mu.Lock()
s.lastErr = nil s.lastErr = nil
s.lastSync = time.Now() s.lastSync = time.Now()
@@ -344,7 +350,7 @@ func (s *Syncer) copyTemplates() (newApps []string, updated []string, err error)
changed, err := copyIfChanged(src, dst) changed, err := copyIfChanged(src, dst)
if err != nil { if err != nil {
s.logger.Printf("[SYNC] Failed to copy %s/%s: %v", appName, filename, err) s.logger.Printf("[WARN] [sync] Failed to copy catalog file %s/%s: %v", appName, filename, err)
continue continue
} }
if changed { if changed {
@@ -5,6 +5,7 @@ package system
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"log"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -150,6 +151,7 @@ func CheckBackupDestination(path string) DestinationHealth {
h.Warning = "A cél tárhely (" + path + ") nem létezik!" h.Warning = "A cél tárhely (" + path + ") nem létezik!"
h.Blocked = true h.Blocked = true
h.Severity = "critical" h.Severity = "critical"
log.Printf("[WARN] [system] Backup destination %s is not safe: path does not exist", path)
debugf("[DEBUG] [system] CheckBackupDestination: path=%q — tier1 FAIL (not exists)", path) debugf("[DEBUG] [system] CheckBackupDestination: path=%q — tier1 FAIL (not exists)", path)
return h return h
} }
@@ -160,6 +162,7 @@ func CheckBackupDestination(path string) DestinationHealth {
h.Warning = "A cél tárhely (" + path + ") nem írható! Ellenőrizd a jogosultságokat." h.Warning = "A cél tárhely (" + path + ") nem írható! Ellenőrizd a jogosultságokat."
h.Blocked = true h.Blocked = true
h.Severity = "critical" h.Severity = "critical"
log.Printf("[WARN] [system] Backup destination %s is not safe: not writable", path)
debugf("[DEBUG] [system] CheckBackupDestination: path=%q — tier2 FAIL (not writable)", path) debugf("[DEBUG] [system] CheckBackupDestination: path=%q — tier2 FAIL (not writable)", path)
return h return h
} }
@@ -191,10 +194,12 @@ func CheckBackupDestination(path string) DestinationHealth {
h.Warning = fmt.Sprintf("A rendszermeghajtón csak %.1f GB szabad — legalább 10 GB szükséges a rendszer stabilitásához!", di.AvailGB) h.Warning = fmt.Sprintf("A rendszermeghajtón csak %.1f GB szabad — legalább 10 GB szükséges a rendszer stabilitásához!", di.AvailGB)
h.Blocked = true h.Blocked = true
h.Severity = "critical" h.Severity = "critical"
log.Printf("[WARN] [system] Backup destination %s is not safe: system drive low space (%.1f GB free)", path, di.AvailGB)
} else if di.UsedPercent >= 90 { } else if di.UsedPercent >= 90 {
h.Warning = fmt.Sprintf("A rendszermeghajtó %.0f%%-ban megtelt — maximum 90%% megengedett.", di.UsedPercent) h.Warning = fmt.Sprintf("A rendszermeghajtó %.0f%%-ban megtelt — maximum 90%% megengedett.", di.UsedPercent)
h.Blocked = true h.Blocked = true
h.Severity = "critical" h.Severity = "critical"
log.Printf("[WARN] [system] Backup destination %s is not safe: system drive %.0f%% full", path, di.UsedPercent)
} }
// If neither triggers, keep the Tier 3 system-drive warning // If neither triggers, keep the Tier 3 system-drive warning
} else { } else {
@@ -203,6 +208,7 @@ func CheckBackupDestination(path string) DestinationHealth {
h.Warning = fmt.Sprintf("A mentési meghajtó megtelt (%.0f%% használt)!", di.UsedPercent) h.Warning = fmt.Sprintf("A mentési meghajtó megtelt (%.0f%% használt)!", di.UsedPercent)
h.Blocked = true h.Blocked = true
h.Severity = "critical" h.Severity = "critical"
log.Printf("[WARN] [system] Backup destination %s is not safe: drive %.0f%% full", path, di.UsedPercent)
} else if di.UsedPercent >= 90 { } else if di.UsedPercent >= 90 {
h.Warning = fmt.Sprintf("A mentési meghajtó majdnem megtelt (%.0f%% használt).", di.UsedPercent) h.Warning = fmt.Sprintf("A mentési meghajtó majdnem megtelt (%.0f%% használt).", di.UsedPercent)
h.Severity = "warning" h.Severity = "warning"
@@ -273,6 +279,7 @@ func ProbeStoragePath(path string) ProbeResult {
// Quick check: does the path exist at all? // Quick check: does the path exist at all?
if _, err := os.Lstat(path); os.IsNotExist(err) { if _, err := os.Lstat(path); os.IsNotExist(err) {
log.Printf("[WARN] [system] Storage path %s probe failed: %v", path, err)
debugf("[DEBUG] [system] ProbeStoragePath: path=%q — not exists (%s)", path, time.Since(start).Round(time.Millisecond)) debugf("[DEBUG] [system] ProbeStoragePath: path=%q — not exists (%s)", path, time.Since(start).Round(time.Millisecond))
return ProbeResult{Status: ProbeDisconnected, Err: err} return ProbeResult{Status: ProbeDisconnected, Err: err}
} }
@@ -298,12 +305,15 @@ func ProbeStoragePath(path string) ProbeResult {
if strings.Contains(errStr, "transport endpoint") || if strings.Contains(errStr, "transport endpoint") ||
strings.Contains(errStr, "input/output error") || strings.Contains(errStr, "input/output error") ||
strings.Contains(errStr, "no such device") { strings.Contains(errStr, "no such device") {
log.Printf("[WARN] [system] Storage path %s probe failed: %v", path, res.err)
debugf("[DEBUG] [system] ProbeStoragePath: path=%q — disconnected: %v (%s)", path, res.err, elapsed) debugf("[DEBUG] [system] ProbeStoragePath: path=%q — disconnected: %v (%s)", path, res.err, elapsed)
return ProbeResult{Status: ProbeDisconnected, Err: res.err} return ProbeResult{Status: ProbeDisconnected, Err: res.err}
} }
log.Printf("[WARN] [system] Storage path %s probe failed: %v", path, res.err)
debugf("[DEBUG] [system] ProbeStoragePath: path=%q — disconnected (other error): %v (%s)", path, res.err, elapsed) debugf("[DEBUG] [system] ProbeStoragePath: path=%q — disconnected (other error): %v (%s)", path, res.err, elapsed)
return ProbeResult{Status: ProbeDisconnected, Err: res.err} return ProbeResult{Status: ProbeDisconnected, Err: res.err}
case <-time.After(3 * time.Second): case <-time.After(3 * time.Second):
log.Printf("[WARN] [system] Storage path %s probe failed: stat timed out after 3s", path)
debugf("[DEBUG] [system] ProbeStoragePath: path=%q — TIMEOUT (3s)", path) debugf("[DEBUG] [system] ProbeStoragePath: path=%q — TIMEOUT (3s)", path)
return ProbeResult{Status: ProbeTimeout, Err: fmt.Errorf("stat timed out after 3s")} return ProbeResult{Status: ProbeTimeout, Err: fmt.Errorf("stat timed out after 3s")}
} }
+6
View File
@@ -88,6 +88,7 @@ func (s *Server) RequireAuth(next http.Handler) http.Handler {
s.logger.Printf("[DEBUG] [web] auth: rejected %s %s from %s (%s)", r.Method, r.URL.Path, r.RemoteAddr, reason) s.logger.Printf("[DEBUG] [web] auth: rejected %s %s from %s (%s)", r.Method, r.URL.Path, r.RemoteAddr, reason)
} }
if strings.HasPrefix(r.URL.Path, "/api/") { if strings.HasPrefix(r.URL.Path, "/api/") {
s.logger.Printf("[WARN] [api] Unauthorized request to %s from %s", r.URL.Path, r.RemoteAddr)
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
fmt.Fprint(w, `{"ok":false,"error":"authentication required"}`) fmt.Fprint(w, `{"ok":false,"error":"authentication required"}`)
@@ -203,6 +204,7 @@ func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {
delete(s.sessions, cookie.Value) delete(s.sessions, cookie.Value)
s.sessionsMu.Unlock() s.sessionsMu.Unlock()
} }
s.logger.Printf("[INFO] [web] User logged out from %s", r.RemoteAddr)
http.SetCookie(w, &http.Cookie{Name: sessionCookieName, Value: "", Path: "/", MaxAge: -1}) http.SetCookie(w, &http.Cookie{Name: sessionCookieName, Value: "", Path: "/", MaxAge: -1})
http.Redirect(w, r, "/login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
} }
@@ -257,6 +259,7 @@ func (s *Server) invalidateAllSessions() {
count := len(s.sessions) count := len(s.sessions)
s.sessions = make(map[string]*session) s.sessions = make(map[string]*session)
s.sessionsMu.Unlock() s.sessionsMu.Unlock()
s.logger.Printf("[INFO] [web] All sessions invalidated (cleared %d)", count)
if s.isDebug() { if s.isDebug() {
s.logger.Printf("[DEBUG] [web] invalidated all sessions (cleared %d)", count) s.logger.Printf("[DEBUG] [web] invalidated all sessions (cleared %d)", count)
} }
@@ -281,6 +284,9 @@ func (s *Server) cleanupSessions() {
} }
remaining := len(s.sessions) remaining := len(s.sessions)
s.sessionsMu.Unlock() s.sessionsMu.Unlock()
if expired > 0 {
s.logger.Printf("[INFO] [web] Cleaned up %d expired sessions, %d remaining", expired, remaining)
}
if s.isDebug() && expired > 0 { if s.isDebug() && expired > 0 {
s.logger.Printf("[DEBUG] [web] session cleanup: expired=%d remaining=%d", expired, remaining) s.logger.Printf("[DEBUG] [web] session cleanup: expired=%d remaining=%d", expired, remaining)
} }
@@ -109,6 +109,7 @@ func (s *Server) apiExportEstimate(w http.ResponseWriter, r *http.Request) {
est, err := s.appExporter.EstimateExport(stackName, drive) est, err := s.appExporter.EstimateExport(stackName, drive)
if err != nil { 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] [handler_export] apiExportEstimate error: %v", err)
jsonError(w, err.Error(), http.StatusInternalServerError) jsonError(w, err.Error(), http.StatusInternalServerError)
return return
@@ -162,6 +163,7 @@ func (s *Server) apiExportStart(w http.ResponseWriter, r *http.Request) {
StopApp: req.StopApp, StopApp: req.StopApp,
}) })
if err != nil { 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] [handler_export] apiExportStart error: %v", err)
jsonError(w, err.Error(), http.StatusConflict) jsonError(w, err.Error(), http.StatusConflict)
return return
@@ -306,6 +308,7 @@ func (s *Server) apiImportStart(w http.ResponseWriter, r *http.Request) {
Password: req.Password, Password: req.Password,
}) })
if err != nil { 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] [handler_export] apiImportStart error: %v", err)
jsonError(w, err.Error(), http.StatusConflict) jsonError(w, err.Error(), http.StatusConflict)
return return
@@ -89,6 +89,7 @@ func (s *Server) apiRestoreAll(w http.ResponseWriter, r *http.Request) {
jsonError(w, "restore already in progress", http.StatusConflict) jsonError(w, "restore already in progress", http.StatusConflict)
return return
} }
s.logger.Printf("[INFO] [web] Restore-all initiated from %s", r.RemoteAddr)
go s.executeAllRestores() go s.executeAllRestores()
jsonResponse(w, map[string]interface{}{ jsonResponse(w, map[string]interface{}{
+2
View File
@@ -1568,6 +1568,7 @@ func (s *Server) settingsStorageDefaultHandler(w http.ResponseWriter, r *http.Re
http.Redirect(w, r, "/settings", http.StatusFound) http.Redirect(w, r, "/settings", http.StatusFound)
return return
} }
s.logger.Printf("[INFO] [web] Default storage path set to %s", path)
http.Redirect(w, r, "/settings?storage_msg=success&storage_detail="+url.QueryEscape("Alapértelmezett adattároló beállítva: "+path), http.StatusFound) http.Redirect(w, r, "/settings?storage_msg=success&storage_detail="+url.QueryEscape("Alapértelmezett adattároló beállítva: "+path), http.StatusFound)
} }
@@ -1585,6 +1586,7 @@ func (s *Server) settingsStorageSchedulableHandler(w http.ResponseWriter, r *htt
http.Redirect(w, r, "/settings", http.StatusFound) http.Redirect(w, r, "/settings", http.StatusFound)
return return
} }
s.logger.Printf("[INFO] [web] Storage schedulable updated: %s → %v", path, schedulable)
http.Redirect(w, r, "/settings?storage_msg=success&storage_detail="+url.QueryEscape("Adattároló állapot módosítva: "+path), http.StatusFound) http.Redirect(w, r, "/settings?storage_msg=success&storage_detail="+url.QueryEscape("Adattároló állapot módosítva: "+path), http.StatusFound)
} }
+1
View File
@@ -345,6 +345,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
s.debugPageHandler(w, r) s.debugPageHandler(w, r)
default: default:
s.logger.Printf("[WARN] [web] 404 Not Found: %s %s", r.Method, path)
http.NotFound(w, r) http.NotFound(w, r)
} }
} }
@@ -1222,6 +1222,7 @@ func (s *Server) storageDisconnectHandler(w http.ResponseWriter, r *http.Request
return return
} }
s.logger.Printf("[INFO] [web] Disk disconnect completed: %s (stopped %d stacks)", req.Path, len(stoppedStacks))
if s.isDebug() { if s.isDebug() {
s.logger.Printf("[DEBUG] [web] storageDisconnect: path=%s success, stopped %d stacks", req.Path, len(stoppedStacks)) s.logger.Printf("[DEBUG] [web] storageDisconnect: path=%s success, stopped %d stacks", req.Path, len(stoppedStacks))
} }
@@ -1264,6 +1265,7 @@ func (s *Server) storageReconnectHandler(w http.ResponseWriter, r *http.Request)
return return
} }
s.logger.Printf("[INFO] [web] Disk reconnect completed: %s", req.Path)
if s.isDebug() { if s.isDebug() {
s.logger.Printf("[DEBUG] [web] storageReconnect: path=%s success, previously stopped stacks=%v", req.Path, stoppedStacks) s.logger.Printf("[DEBUG] [web] storageReconnect: path=%s success, previously stopped stacks=%v", req.Path, stoppedStacks)
} }
@@ -1309,6 +1311,7 @@ func (s *Server) storageRestartAppsHandler(w http.ResponseWriter, r *http.Reques
} }
started, failed := s.storageWatchdog.RestartStoppedApps(req.Path) started, failed := s.storageWatchdog.RestartStoppedApps(req.Path)
s.logger.Printf("[INFO] [web] Restart apps for %s: started=%d failed=%d", req.Path, len(started), len(failed))
if s.isDebug() { if s.isDebug() {
s.logger.Printf("[DEBUG] [web] storageRestartApps: path=%s started=%v failed=%v", req.Path, started, failed) s.logger.Printf("[DEBUG] [web] storageRestartApps: path=%s started=%v failed=%v", req.Path, started, failed)
} }