Files
nasmon/internal/config/config.go
T
a.gritsenkoandClaude Opus 5.5 30dbb00dd1
build / build (push) Successful in 1m43s
Shares of other services and RepoISO integration
- include SMB drop-ins from /etc/samba/smb.conf.d into the generated smb.conf
- keep smbd/nfs-server running while other services (/etc/exports.d, drop-ins) share,
  instead of disabling them when nasmon has no shares of its own
- list those shares read-only on /shares
- settings: RepoISO URL; poll its /api/integration/status, dashboard card, menu link,
  alerts for portal down, stale worker, failed jobs, unmounted publications

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 15:06:17 +03:00

174 lines
4.7 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
}
// Integrations links nasmon with other services on the same host.
type Integrations struct {
// RepoISOURL is the RepoISO portal base URL nasmon polls for health
// (e.g. http://127.0.0.1); empty disables the integration.
RepoISOURL string `json:"repoiso_url"`
}
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"`
Integrations Integrations `json:"integrations"`
}
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")
}
u := strings.TrimRight(strings.TrimSpace(s.Integrations.RepoISOURL), "/")
if u != "" && !strings.HasPrefix(u, "http://") && !strings.HasPrefix(u, "https://") {
return errors.New("RepoISO URL must start with http:// or https://")
}
s.Integrations.RepoISOURL = u
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
}