Files
a.gritsenkoandClaude Opus 5.5 42cfb43e2e
build / build (push) Successful in 1m5s
Remove the RepoISO integration; keep generic support for shares of other services
RepoISO now manages the NAS itself, so nasmon no longer polls it (settings field, dashboard card,
menu link and repoiso:* alerts are gone). Drop-in SMB includes, read-only listing and keeping
smbd/nfs-server running for other software's shares stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 19:05:16 +03:00

161 lines
4.1 KiB
Go

// Package config holds nasmon settings, persisted as a JSON file that is
// editable both by hand and through the web UI.
package config
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
)
type SMTP struct {
Host string `json:"host"`
Port int `json:"port"`
Security string `json:"security"` // starttls | tls | none
User string `json:"user"`
Pass string `json:"pass"`
From string `json:"from"`
To []string `json:"to"`
SubjectPrefix string `json:"subject_prefix"`
}
type Thresholds struct {
DiskTempC int `json:"disk_temp_c"`
CPUTempC int `json:"cpu_temp_c"`
PoolCapacityPct int `json:"pool_capacity_pct"`
ScrubMaxAgeDays int `json:"scrub_max_age_days"`
RootFSFreePct int `json:"rootfs_free_pct"`
NVMeUsedPct int `json:"nvme_used_pct"`
NVMeSparePct int `json:"nvme_spare_pct"`
}
type SelfTests struct {
Enabled bool `json:"enabled"`
ShortDays int `json:"short_days"`
LongDays int `json:"long_days"`
Hour int `json:"hour"` // local hour of day when tests may be started
}
type Settings struct {
SMTP SMTP `json:"smtp"`
AlertsEnabled bool `json:"alerts_enabled"`
NotifyResolved bool `json:"notify_resolved"`
RemindHours int `json:"remind_hours"`
SmartIntervalMin int `json:"smart_interval_min"`
SmartSkipStandby bool `json:"smart_skip_standby"`
Thresholds Thresholds `json:"thresholds"`
SelfTests SelfTests `json:"self_tests"`
}
func Defaults() Settings {
return Settings{
SMTP: SMTP{Port: 587, Security: "starttls", SubjectPrefix: "[nasmon]"},
AlertsEnabled: true,
NotifyResolved: true,
RemindHours: 24,
SmartIntervalMin: 30,
SmartSkipStandby: true,
Thresholds: Thresholds{
DiskTempC: 50,
CPUTempC: 80,
PoolCapacityPct: 85,
ScrubMaxAgeDays: 45,
RootFSFreePct: 10,
NVMeUsedPct: 90,
NVMeSparePct: 10,
},
SelfTests: SelfTests{Enabled: false, ShortDays: 7, LongDays: 30, Hour: 3},
}
}
// Validate normalises and checks values coming from the web form.
func (s *Settings) Validate() error {
s.SMTP.Security = strings.ToLower(strings.TrimSpace(s.SMTP.Security))
switch s.SMTP.Security {
case "starttls", "tls", "none":
default:
return fmt.Errorf("smtp security must be starttls, tls or none")
}
if s.SMTP.Port <= 0 || s.SMTP.Port > 65535 {
return errors.New("smtp port out of range")
}
var to []string
for _, a := range s.SMTP.To {
if a = strings.TrimSpace(a); a != "" {
to = append(to, a)
}
}
s.SMTP.To = to
if s.RemindHours < 0 {
return errors.New("remind hours must be >= 0")
}
if s.SmartIntervalMin < 1 {
return errors.New("SMART interval must be >= 1 minute")
}
if s.SelfTests.Hour < 0 || s.SelfTests.Hour > 23 {
return errors.New("self-test hour must be 0..23")
}
if s.SelfTests.ShortDays < 0 || s.SelfTests.LongDays < 0 {
return errors.New("self-test periods must be >= 0")
}
return nil
}
// Store is a thread-safe holder for the current settings backed by a file.
type Store struct {
path string
mu sync.RWMutex
cur Settings
}
func Load(path string) (*Store, error) {
st := &Store{path: path, cur: Defaults()}
b, err := os.ReadFile(path)
switch {
case errors.Is(err, os.ErrNotExist):
return st, nil
case err != nil:
return nil, err
}
if err := json.Unmarshal(b, &st.cur); err != nil {
return nil, fmt.Errorf("parse %s: %w", path, err)
}
return st, nil
}
func (st *Store) Get() Settings {
st.mu.RLock()
defer st.mu.RUnlock()
s := st.cur
s.SMTP.To = append([]string(nil), s.SMTP.To...)
return s
}
func (st *Store) Save(s Settings) error {
if err := s.Validate(); err != nil {
return err
}
b, err := json.MarshalIndent(s, "", " ")
if err != nil {
return err
}
st.mu.Lock()
defer st.mu.Unlock()
if err := os.MkdirAll(filepath.Dir(st.path), 0o755); err != nil {
return err
}
tmp := st.path + ".tmp"
if err := os.WriteFile(tmp, append(b, '\n'), 0o600); err != nil {
return err
}
if err := os.Rename(tmp, st.path); err != nil {
return err
}
st.cur = s
return nil
}