Files
uptop/internal/config/export.go
T
lerko bd561d9a5e
CI / test (pull_request) Successful in 4m23s
CI / lint (pull_request) Successful in 1m11s
fix(security): phase 3 medium reliability and hardening
- Fail hard on critical migration errors (ignore only "already exists")
- Cache SSH user keys with 30s TTL (avoid DB query per auth attempt)
- Configure DB connection pooling (25 open, 5 idle, 5m lifetime)
- Enable SQLite WAL mode for concurrent read/write
- Optimize check history pruning (only prune above 1100 rows)
- Add security headers: X-Content-Type-Options, X-Frame-Options, CSP, Referrer-Policy
- Add CORS policy on /status/json via UPTOP_CORS_ORIGIN env var
- Add HTTP request logging middleware (method, path, status, duration, IP)
- Fix config file permissions from 0644 to 0600
- Pin Docker images: golang:1.24-alpine3.21, alpine:3.21
- Fix Docker CI tag pattern for CalVer (was semver)
- Pass build args (VERSION, COMMIT, BUILD_DATE) to Docker build
2026-05-26 16:57:03 -04:00

160 lines
3.3 KiB
Go

package config
import (
"fmt"
"os"
"sort"
"gitea.lerkolabs.com/lerko/uptop/internal/models"
"gitea.lerkolabs.com/lerko/uptop/internal/store"
"gopkg.in/yaml.v3"
)
func Export(s store.Store) (*File, error) {
dbAlerts, err := s.GetAllAlerts()
if err != nil {
return nil, fmt.Errorf("load alerts: %w", err)
}
dbSites, err := s.GetSites()
if err != nil {
return nil, fmt.Errorf("load sites: %w", err)
}
alertIDToName := make(map[int]string, len(dbAlerts))
var yamlAlerts []Alert
for _, a := range dbAlerts {
alertIDToName[a.ID] = a.Name
yamlAlerts = append(yamlAlerts, Alert{
Name: a.Name,
Type: a.Type,
Settings: a.Settings,
})
}
groups := make(map[int]models.Site)
children := make(map[int][]models.Site)
var topLevel []models.Site
for _, s := range dbSites {
switch {
case s.Type == "group":
groups[s.ID] = s
case s.ParentID > 0:
children[s.ParentID] = append(children[s.ParentID], s)
default:
topLevel = append(topLevel, s)
}
}
var yamlMonitors []Monitor
groupIDs := make([]int, 0, len(groups))
for id := range groups {
groupIDs = append(groupIDs, id)
}
sort.Ints(groupIDs)
for _, gid := range groupIDs {
g := groups[gid]
ym := siteToMonitor(g, alertIDToName)
kids := children[gid]
sort.Slice(kids, func(i, j int) bool { return kids[i].ID < kids[j].ID })
for _, child := range kids {
ym.Monitors = append(ym.Monitors, siteToMonitor(child, alertIDToName))
}
yamlMonitors = append(yamlMonitors, ym)
}
sort.Slice(topLevel, func(i, j int) bool { return topLevel[i].ID < topLevel[j].ID })
for _, s := range topLevel {
yamlMonitors = append(yamlMonitors, siteToMonitor(s, alertIDToName))
}
return &File{Alerts: yamlAlerts, Monitors: yamlMonitors}, nil
}
func siteToMonitor(s models.Site, alertIDToName map[int]string) Monitor {
m := Monitor{
Name: s.Name,
Type: s.Type,
Interval: s.Interval,
}
if s.AlertID > 0 {
if name, ok := alertIDToName[s.AlertID]; ok {
m.Alert = name
}
}
if s.URL != "" {
m.URL = s.URL
}
if s.Hostname != "" {
m.Hostname = s.Hostname
}
if s.Port != 0 {
m.Port = s.Port
}
if s.Timeout != 0 {
m.Timeout = s.Timeout
}
if s.Description != "" {
m.Description = s.Description
}
if s.DNSResolveType != "" {
m.DNSResolveType = s.DNSResolveType
}
if s.DNSServer != "" {
m.DNSServer = s.DNSServer
}
if s.Method != "" && s.Method != "GET" {
m.Method = s.Method
}
if s.AcceptedCodes != "" && s.AcceptedCodes != "200-299" {
m.AcceptedCodes = s.AcceptedCodes
}
if s.ExpiryThreshold != 0 && s.ExpiryThreshold != 7 {
m.ExpiryThreshold = s.ExpiryThreshold
}
if s.MaxRetries != 0 {
m.MaxRetries = s.MaxRetries
}
m.CheckSSL = s.CheckSSL
m.IgnoreTLS = s.IgnoreTLS
m.Paused = s.Paused
if s.Regions != "" {
m.Regions = s.Regions
}
return m
}
func WriteFile(f *File, path string) error {
data, err := yaml.Marshal(f)
if err != nil {
return fmt.Errorf("marshal yaml: %w", err)
}
if path == "-" || path == "" {
_, err = os.Stdout.Write(data)
return err
}
return os.WriteFile(path, data, 0600)
}
func LoadFile(path string) (*File, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read %s: %w", path, err)
}
var f File
if err := yaml.Unmarshal(data, &f); err != nil {
return nil, fmt.Errorf("parse %s: %w", path, err)
}
return &f, nil
}