ae141c62ba
generateToken() now returns (string, error) instead of panicking on crypto/rand failure. All json.Unmarshal calls for alert settings now check and propagate errors instead of silently ignoring them. Adds Close() to Store interface for graceful shutdown support. Skips malformed notification entries during Kuma import.
189 lines
4.8 KiB
Go
189 lines
4.8 KiB
Go
package importer
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"go-upkeep/internal/models"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
type KumaBackup struct {
|
|
Version string `json:"version"`
|
|
MonitorList []KumaMonitor `json:"monitorList"`
|
|
NotificationList []KumaNotifEntry `json:"notificationList"`
|
|
}
|
|
|
|
type KumaMonitor struct {
|
|
ID int `json:"id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
URL string `json:"url"`
|
|
Hostname string `json:"hostname"`
|
|
Port int `json:"port"`
|
|
Type string `json:"type"`
|
|
Interval int `json:"interval"`
|
|
Timeout int `json:"timeout"`
|
|
MaxRetries int `json:"maxretries"`
|
|
Method string `json:"method"`
|
|
AcceptedCodes []string `json:"accepted_statuscodes"`
|
|
IgnoreTLS bool `json:"ignoreTls"`
|
|
Parent int `json:"parent"`
|
|
Active bool `json:"active"`
|
|
DNSResolveType string `json:"dns_resolve_type"`
|
|
DNSResolveServer string `json:"dns_resolve_server"`
|
|
NotificationIDs map[string]bool `json:"notificationIDList"`
|
|
ExpiryNotif bool `json:"expiryNotification"`
|
|
Tags []json.RawMessage `json:"tags"`
|
|
}
|
|
|
|
type KumaNotifEntry struct {
|
|
ID int `json:"id"`
|
|
Name string `json:"name"`
|
|
Config string `json:"config"`
|
|
}
|
|
|
|
type KumaNotifConfig struct {
|
|
Type string `json:"type"`
|
|
URL string `json:"ntfyserverurl"`
|
|
Topic string `json:"ntfytopic"`
|
|
Priority int `json:"ntfyPriority"`
|
|
AuthMode string `json:"ntfyAuthenticationMethod"`
|
|
Username string `json:"ntfyusername"`
|
|
Password string `json:"ntfypassword"`
|
|
}
|
|
|
|
func LoadKumaFile(path string) (*KumaBackup, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read file: %w", err)
|
|
}
|
|
var backup KumaBackup
|
|
if err := json.Unmarshal(data, &backup); err != nil {
|
|
return nil, fmt.Errorf("parse JSON: %w", err)
|
|
}
|
|
return &backup, nil
|
|
}
|
|
|
|
func ConvertKuma(kb *KumaBackup) models.Backup {
|
|
alertMap := convertKumaNotifications(kb.NotificationList)
|
|
|
|
var alerts []models.AlertConfig
|
|
for _, a := range alertMap {
|
|
alerts = append(alerts, a)
|
|
}
|
|
|
|
kumaToUpkeepAlert := make(map[int]int)
|
|
for _, n := range kb.NotificationList {
|
|
if a, ok := alertMap[n.ID]; ok {
|
|
kumaToUpkeepAlert[n.ID] = a.ID
|
|
}
|
|
}
|
|
|
|
var sites []models.Site
|
|
for _, m := range kb.MonitorList {
|
|
site := convertKumaMonitor(m, kumaToUpkeepAlert)
|
|
sites = append(sites, site)
|
|
}
|
|
|
|
return models.Backup{
|
|
Sites: sites,
|
|
Alerts: alerts,
|
|
}
|
|
}
|
|
|
|
func convertKumaNotifications(entries []KumaNotifEntry) map[int]models.AlertConfig {
|
|
result := make(map[int]models.AlertConfig)
|
|
for _, entry := range entries {
|
|
var cfg KumaNotifConfig
|
|
if err := json.Unmarshal([]byte(entry.Config), &cfg); err != nil {
|
|
continue
|
|
}
|
|
|
|
alert := models.AlertConfig{
|
|
ID: entry.ID,
|
|
Name: entry.Name,
|
|
Settings: make(map[string]string),
|
|
}
|
|
|
|
switch cfg.Type {
|
|
case "ntfy":
|
|
alert.Type = "ntfy"
|
|
alert.Settings["url"] = strings.TrimRight(cfg.URL, "/")
|
|
alert.Settings["topic"] = cfg.Topic
|
|
alert.Settings["priority"] = fmt.Sprintf("%d", cfg.Priority)
|
|
if cfg.AuthMode == "usernamePassword" {
|
|
alert.Settings["username"] = cfg.Username
|
|
alert.Settings["password"] = cfg.Password
|
|
}
|
|
case "discord":
|
|
alert.Type = "discord"
|
|
alert.Settings["url"] = cfg.URL
|
|
case "slack":
|
|
alert.Type = "slack"
|
|
alert.Settings["url"] = cfg.URL
|
|
default:
|
|
alert.Type = "webhook"
|
|
alert.Settings["url"] = cfg.URL
|
|
}
|
|
|
|
result[entry.ID] = alert
|
|
}
|
|
return result
|
|
}
|
|
|
|
func convertKumaMonitor(m KumaMonitor, alertMap map[int]int) models.Site {
|
|
site := models.Site{
|
|
ID: m.ID,
|
|
Name: m.Name,
|
|
Description: m.Description,
|
|
Type: m.Type,
|
|
Interval: m.Interval,
|
|
Timeout: m.Timeout,
|
|
MaxRetries: m.MaxRetries,
|
|
Method: m.Method,
|
|
Hostname: m.Hostname,
|
|
Port: m.Port,
|
|
IgnoreTLS: m.IgnoreTLS,
|
|
ParentID: m.Parent,
|
|
}
|
|
|
|
if len(m.AcceptedCodes) > 0 {
|
|
site.AcceptedCodes = strings.Join(m.AcceptedCodes, ",")
|
|
}
|
|
|
|
site.DNSResolveType = m.DNSResolveType
|
|
site.DNSServer = m.DNSResolveServer
|
|
|
|
switch m.Type {
|
|
case "http":
|
|
site.URL = m.URL
|
|
site.CheckSSL = m.ExpiryNotif
|
|
case "ping":
|
|
if m.Hostname != "" {
|
|
site.Hostname = m.Hostname
|
|
}
|
|
case "port":
|
|
if m.Hostname != "" {
|
|
site.Hostname = m.Hostname
|
|
}
|
|
case "dns":
|
|
if m.Hostname != "" {
|
|
site.Hostname = m.Hostname
|
|
}
|
|
case "group":
|
|
// groups are organizational only
|
|
}
|
|
|
|
for nidStr := range m.NotificationIDs {
|
|
var nid int
|
|
fmt.Sscanf(nidStr, "%d", &nid)
|
|
if upkeepID, ok := alertMap[nid]; ok {
|
|
site.AlertID = upkeepID
|
|
break
|
|
}
|
|
}
|
|
|
|
return site
|
|
}
|