diff --git a/cmd/goupkeep/main.go b/cmd/goupkeep/main.go index 77cdadd..921a3c7 100644 --- a/cmd/goupkeep/main.go +++ b/cmd/goupkeep/main.go @@ -97,8 +97,6 @@ func main() { fmt.Printf("Database init error: %v\n", err) os.Exit(1) } - store.SetGlobal(s) - if *demo { seedDemoData(s) } @@ -117,15 +115,15 @@ func main() { fmt.Printf("Imported %d monitors and %d alerts from Uptime Kuma v%s\n", len(backup.Sites), len(backup.Alerts), kb.Version) } - monitor.InitHistoryFromStore() - monitor.StartEngine() + monitor.InitHistoryFromStore(s) + monitor.StartEngine(s) server.Start(server.ServerConfig{ Port: httpPort, EnableStatus: enableStatus, Title: statusTitle, ClusterKey: clusterKey, - }) + }, s) cluster.Start(cluster.Config{ Mode: clusterMode, @@ -133,10 +131,10 @@ func main() { SharedKey: clusterKey, }) - startSSHServer(*port) + startSSHServer(*port, s) if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) { - p := tea.NewProgram(tui.InitialModel(true), tea.WithAltScreen(), tea.WithMouseCellMotion()) + p := tea.NewProgram(tui.InitialModel(true, s), tea.WithAltScreen(), tea.WithMouseCellMotion()) if _, err := p.Run(); err != nil { fmt.Printf("Error: %v\n", err) } @@ -149,16 +147,16 @@ func main() { } } -func startSSHServer(port int) { +func startSSHServer(port int, db store.Store) { s, err := wish.NewServer( wish.WithAddress(fmt.Sprintf(":%d", port)), wish.WithHostKeyPath(".ssh/id_ed25519"), wish.WithPublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool { - return isKeyAllowed(key) + return isKeyAllowed(db, key) }), wish.WithMiddleware( bm.Middleware(func(s ssh.Session) (tea.Model, []tea.ProgramOption) { - return tui.InitialModel(false), []tea.ProgramOption{tea.WithAltScreen(), tea.WithMouseCellMotion()} + return tui.InitialModel(false, db), []tea.ProgramOption{tea.WithAltScreen(), tea.WithMouseCellMotion()} }), ), ) @@ -206,8 +204,8 @@ func seedDemoData(s store.Store) { s.AddSite(models.Site{Name: "SSH Server", Type: "port", Interval: 60, AlertID: alertID, Hostname: "10.0.0.1", Port: 22, Timeout: 5, ExpiryThreshold: 7}) } -func isKeyAllowed(incomingKey ssh.PublicKey) bool { - users, err := store.Get().GetAllUsers() +func isKeyAllowed(db store.Store, incomingKey ssh.PublicKey) bool { + users, err := db.GetAllUsers() if err != nil { return false } diff --git a/internal/monitor/history.go b/internal/monitor/history.go index 43ef0ee..dd3f375 100644 --- a/internal/monitor/history.go +++ b/internal/monitor/history.go @@ -20,11 +20,7 @@ var ( historyMu sync.RWMutex ) -func InitHistoryFromStore() { - s := store.Get() - if s == nil { - return - } +func InitHistoryFromStore(s store.Store) { all, err := s.LoadAllHistory(maxHistoryLen) if err != nil { AddLog("Failed to load check history: " + err.Error()) @@ -74,8 +70,8 @@ func RecordCheck(siteID int, latency time.Duration, isUp bool) { h.Statuses = h.Statuses[len(h.Statuses)-maxHistoryLen:] } - if s := store.Get(); s != nil { - go func() { _ = s.SaveCheck(siteID, latency.Nanoseconds(), isUp) }() + if db != nil { + go func() { _ = db.SaveCheck(siteID, latency.Nanoseconds(), isUp) }() } } diff --git a/internal/monitor/monitor.go b/internal/monitor/monitor.go index 27925ed..db11caf 100644 --- a/internal/monitor/monitor.go +++ b/internal/monitor/monitor.go @@ -55,6 +55,8 @@ var ( insecureSkipVerify bool + db store.Store + strictClient = &http.Client{ Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: false}}, } @@ -119,16 +121,11 @@ func RecordHeartbeat(token string) bool { return true } -func StartEngine() { +func StartEngine(s store.Store) { + db = s go func() { for { - s_instance := store.Get() - if s_instance == nil { - time.Sleep(1 * time.Second) - continue - } - - sites, err := s_instance.GetSites() + sites, err := db.GetSites() if err != nil { AddLog(fmt.Sprintf("Failed to load sites: %v", err)) time.Sleep(5 * time.Second) @@ -407,11 +404,10 @@ func handleStatusChange(site models.Site, rawStatus string, code int, latency ti } func triggerAlert(alertID int, title, message string) { - s_instance := store.Get() - if s_instance == nil { + if db == nil { return } - cfg, err := s_instance.GetAlert(alertID) + cfg, err := db.GetAlert(alertID) if err != nil { return } diff --git a/internal/server/server.go b/internal/server/server.go index bb97a88..b6a2b52 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -148,7 +148,7 @@ type ServerConfig struct { ClusterKey string // Shared Secret for Security } -func Start(cfg ServerConfig) { +func Start(cfg ServerConfig, s store.Store) { if cfg.ClusterKey == "" { fmt.Println("WARNING: No UPKEEP_CLUSTER_SECRET set. Cluster API endpoints are unauthenticated.") } @@ -185,7 +185,7 @@ func Start(cfg ServerConfig) { http.Error(w, "Unauthorized: UPKEEP_CLUSTER_SECRET required", 401) return } - data, err := store.Get().ExportData() + data, err := s.ExportData() if err != nil { log.Printf("Export failed: %v", err) http.Error(w, "Export failed", 500) @@ -209,7 +209,7 @@ func Start(cfg ServerConfig) { http.Error(w, "Invalid JSON", 400) return } - if err := store.Get().ImportData(data); err != nil { + if err := s.ImportData(data); err != nil { log.Printf("Import failed: %v", err) http.Error(w, "Import failed", 500) return @@ -234,7 +234,7 @@ func Start(cfg ServerConfig) { return } backup := importer.ConvertKuma(&kb) - if err := store.Get().ImportData(backup); err != nil { + if err := s.ImportData(backup); err != nil { log.Printf("Kuma import failed: %v", err) http.Error(w, "Import failed", 500) return diff --git a/internal/store/store.go b/internal/store/store.go index d119597..35afa0b 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -35,13 +35,3 @@ type Store interface { ExportData() (models.Backup, error) ImportData(data models.Backup) error } - -var Current Store - -func SetGlobal(s Store) { - Current = s -} - -func Get() Store { - return Current -} diff --git a/internal/tui/tab_alerts.go b/internal/tui/tab_alerts.go index 72bd19d..0d203f1 100644 --- a/internal/tui/tab_alerts.go +++ b/internal/tui/tab_alerts.go @@ -3,7 +3,6 @@ package tui import ( "fmt" "go-upkeep/internal/monitor" - "go-upkeep/internal/store" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/huh" @@ -278,11 +277,11 @@ func (m *Model) submitAlertForm() { } if m.editID > 0 { - if err := store.Get().UpdateAlert(m.editID, d.Name, d.AlertType, settings); err != nil { + if err := m.store.UpdateAlert(m.editID, d.Name, d.AlertType, settings); err != nil { monitor.AddLog("Update alert failed: " + err.Error()) } } else { - if err := store.Get().AddAlert(d.Name, d.AlertType, settings); err != nil { + if err := m.store.AddAlert(d.Name, d.AlertType, settings); err != nil { monitor.AddLog("Add alert failed: " + err.Error()) } } diff --git a/internal/tui/tab_sites.go b/internal/tui/tab_sites.go index f1a2aa0..1644a35 100644 --- a/internal/tui/tab_sites.go +++ b/internal/tui/tab_sites.go @@ -4,7 +4,6 @@ import ( "fmt" "go-upkeep/internal/models" "go-upkeep/internal/monitor" - "go-upkeep/internal/store" "net/url" "strconv" "strings" @@ -361,14 +360,12 @@ func (m *Model) initSiteHuhForm() tea.Cmd { } alertOpts := []huh.Option[string]{huh.NewOption("None", "0")} - if s := store.Get(); s != nil { - if alerts, err := s.GetAllAlerts(); err == nil { - for _, a := range alerts { - alertOpts = append(alertOpts, huh.NewOption( - fmt.Sprintf("%s (%s)", a.Name, a.Type), - strconv.Itoa(a.ID), - )) - } + if alerts, err := m.store.GetAllAlerts(); err == nil { + for _, a := range alerts { + alertOpts = append(alertOpts, huh.NewOption( + fmt.Sprintf("%s (%s)", a.Name, a.Type), + strconv.Itoa(a.ID), + )) } } @@ -560,12 +557,12 @@ func (m *Model) submitSiteForm() { } if m.editID > 0 { - if err := store.Get().UpdateSite(site); err != nil { + if err := m.store.UpdateSite(site); err != nil { monitor.AddLog("Update site failed: " + err.Error()) } monitor.UpdateSiteConfig(site) } else { - if err := store.Get().AddSite(site); err != nil { + if err := m.store.AddSite(site); err != nil { monitor.AddLog("Add site failed: " + err.Error()) } } diff --git a/internal/tui/tab_users.go b/internal/tui/tab_users.go index 77d4182..d82e5fb 100644 --- a/internal/tui/tab_users.go +++ b/internal/tui/tab_users.go @@ -3,7 +3,6 @@ package tui import ( "fmt" "go-upkeep/internal/monitor" - "go-upkeep/internal/store" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/huh" @@ -146,11 +145,11 @@ func (m *Model) initUserHuhForm() tea.Cmd { func (m *Model) submitUserForm() { d := m.userFormData if m.editID > 0 { - if err := store.Get().UpdateUser(m.editID, d.Username, d.PublicKey, d.Role); err != nil { + if err := m.store.UpdateUser(m.editID, d.Username, d.PublicKey, d.Role); err != nil { monitor.AddLog("Update user failed: " + err.Error()) } } else { - if err := store.Get().AddUser(d.Username, d.PublicKey, d.Role); err != nil { + if err := m.store.AddUser(d.Username, d.PublicKey, d.Role); err != nil { monitor.AddLog("Add user failed: " + err.Error()) } } diff --git a/internal/tui/tui.go b/internal/tui/tui.go index 533c993..4324c65 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -68,6 +68,7 @@ type Model struct { deleteTab int collapsed map[int]bool + store store.Store // harmonica animation state pulseSpring harmonica.Spring @@ -80,7 +81,7 @@ type Model struct { users []models.User } -func InitialModel(isAdmin bool) Model { +func InitialModel(isAdmin bool, s store.Store) Model { vpLogs := viewport.New(100, 20) vpLogs.SetContent("Waiting for logs...") z := zone.New() @@ -90,6 +91,7 @@ func InitialModel(isAdmin bool) Model { logViewport: vpLogs, maxTableRows: 5, isAdmin: isAdmin, + store: s, zones: z, pulseSpring: spring, collapsed: make(map[int]bool), @@ -107,25 +109,23 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if keyMsg, ok := msg.(tea.KeyMsg); ok { switch keyMsg.String() { case "y", "Y": - if s := store.Get(); s != nil { - switch m.deleteTab { - case 0: - if err := s.DeleteSite(m.deleteID); err != nil { - monitor.AddLog("Delete site failed: " + err.Error()) - } - monitor.RemoveSite(m.deleteID) - m.adjustCursor(len(m.sites) - 1) - case 1: - if err := s.DeleteAlert(m.deleteID); err != nil { - monitor.AddLog("Delete alert failed: " + err.Error()) - } - m.adjustCursor(len(m.alerts) - 1) - case 3: - if err := s.DeleteUser(m.deleteID); err != nil { - monitor.AddLog("Delete user failed: " + err.Error()) - } - m.adjustCursor(len(m.users) - 1) + switch m.deleteTab { + case 0: + if err := m.store.DeleteSite(m.deleteID); err != nil { + monitor.AddLog("Delete site failed: " + err.Error()) } + monitor.RemoveSite(m.deleteID) + m.adjustCursor(len(m.sites) - 1) + case 1: + if err := m.store.DeleteAlert(m.deleteID); err != nil { + monitor.AddLog("Delete alert failed: " + err.Error()) + } + m.adjustCursor(len(m.alerts) - 1) + case 3: + if err := m.store.DeleteUser(m.deleteID); err != nil { + monitor.AddLog("Delete user failed: " + err.Error()) + } + m.adjustCursor(len(m.users) - 1) } m.refreshData() m.state = stateDashboard @@ -319,9 +319,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { site := m.sites[m.cursor] monitor.ToggleSitePause(site.ID) site.Paused = !site.Paused - if s := store.Get(); s != nil { - _ = s.UpdateSitePaused(site.ID, site.Paused) - } + _ = m.store.UpdateSitePaused(site.ID, site.Paused) m.refreshData() } case "d", "backspace": @@ -470,23 +468,18 @@ func (m *Model) refreshData() { } ordered = append(ordered, ungrouped...) m.sites = ordered - if s := store.Get(); s != nil { - if alerts, err := s.GetAllAlerts(); err == nil { - m.alerts = alerts - } - if m.isAdmin { - if users, err := s.GetAllUsers(); err == nil { - m.users = users - } + if alerts, err := m.store.GetAllAlerts(); err == nil { + m.alerts = alerts + } + if m.isAdmin { + if users, err := m.store.GetAllUsers(); err == nil { + m.users = users } } m.logViewport.SetContent(strings.Join(monitor.GetLogs(), "\n")) } func (m *Model) submitForm() { - if store.Get() == nil { - return - } switch m.state { case stateFormSite: if m.siteFormData != nil {