v0.9.0: Storage paths registry, per-app HDD_PATH resolution, storage management UI

- Fix backup toggles not appearing (read each app's own HDD_PATH from app.yaml)
- Storage paths registry in settings.json with auto-discovery from deployed apps
- Settings page "Adattárolók" section with disk usage, add/remove/default/schedulable
- Deploy page path field as dropdown of registered storage paths
- Health check storage monitoring (mount point, disk usage alerts)
- Mount-point validation utilities (Linux syscall + cross-platform stubs)
- Controller docker-compose mount changed to /mnt:/mnt:rw for multi-storage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-17 09:04:28 +01:00
parent 465dec443f
commit aca3b8680a
17 changed files with 963 additions and 33 deletions
+38 -2
View File
@@ -2,11 +2,13 @@ package monitor
import (
"fmt"
"os"
"os/exec"
"strings"
"time"
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
"gitea.dooplex.hu/admin/felhom-controller/internal/system"
)
@@ -20,13 +22,17 @@ type HealthReport struct {
}
// RunHealthCheck runs system checks and returns a diagnostic report.
func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector) *HealthReport {
func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, storagePaths []settings.StoragePath) *HealthReport {
report := &HealthReport{
Status: "ok",
Timestamp: time.Now(),
}
sysInfo := system.GetInfo(cfg.Paths.HDDPath, cpuCollector)
hddPath := cfg.Paths.HDDPath
if len(storagePaths) > 0 {
hddPath = storagePaths[0].Path
}
sysInfo := system.GetInfo(hddPath, cpuCollector)
// 1. Disk usage (SSD)
if sysInfo.DiskPercent > 0 {
@@ -88,6 +94,11 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector) *Heal
report.Issues = append(report.Issues, fmt.Sprintf("Protected container not running: %s", name))
}
// 7. Storage paths
storageIssues, storageWarnings := checkStoragePaths(storagePaths)
report.Issues = append(report.Issues, storageIssues...)
report.Warnings = append(report.Warnings, storageWarnings...)
// Determine status
if len(report.Issues) > 0 {
report.Status = "fail"
@@ -158,3 +169,28 @@ func checkProtectedContainers(protected []string) []string {
}
return missing
}
func checkStoragePaths(paths []settings.StoragePath) (issues, warnings []string) {
for _, sp := range paths {
// Path accessible?
if _, err := os.Stat(sp.Path); err != nil {
warnings = append(warnings, fmt.Sprintf("Storage path not accessible: %s", sp.Path))
continue
}
// Mount point check
if !system.IsMountPoint(sp.Path) {
issues = append(issues, fmt.Sprintf("Storage path %s is NOT a mount point — data writes to SSD!", sp.Path))
}
// Disk usage
if di := system.GetDiskUsage(sp.Path); di != nil {
if di.UsedPercent >= 95 {
issues = append(issues, fmt.Sprintf("Storage %s nearly full: %.0f%%", sp.Path, di.UsedPercent))
} else if di.UsedPercent >= 90 {
warnings = append(warnings, fmt.Sprintf("Storage %s usage high: %.0f%%", sp.Path, di.UsedPercent))
}
}
}
return
}