kiss server monitoring tool with email alerts
go
monitoring
1package main
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "os"
8 "os/signal"
9 "path"
10 "runtime/debug"
11 "strings"
12 "syscall"
13 "time"
14
15 "pkg.rbrt.fr/servmon/internal/alert"
16 "pkg.rbrt.fr/servmon/internal/config"
17 "pkg.rbrt.fr/servmon/internal/monitor"
18
19 "github.com/spf13/cobra"
20)
21
22var (
23 flagConfig = "config"
24 cfgFile string
25)
26
27func main() {
28 homeDir, err := os.UserHomeDir()
29 if err != nil {
30 fmt.Fprintf(os.Stderr, "error getting user home directory: %v", err)
31 os.Exit(1)
32 }
33
34 version, err := getVersion()
35 if err != nil {
36 fmt.Fprintf(os.Stderr, "error getting version: %v", err)
37 version = "unknown"
38 }
39
40 rootCmd := &cobra.Command{
41 Use: "servmon",
42 Short: "Server monitoring tool with email alerts",
43 Version: version,
44 RunE: func(cmd *cobra.Command, args []string) error {
45 cfgPath, err := cmd.Flags().GetString(flagConfig)
46 if err != nil {
47 return fmt.Errorf("error getting flag %s: %v", flagConfig, err)
48 }
49
50 if _, err := os.Stat(cfgPath); os.IsNotExist(err) {
51 cfg := config.Default()
52 if err := cfg.Save(cfgFile); err != nil {
53 return err
54 }
55
56 cmd.Println("✓ Configuration file generated at", cfgFile)
57 cmd.Println("Please edit the configuration file and restart servmon")
58 return nil
59 } else if err != nil {
60 return fmt.Errorf("error checking config file: %v", err)
61 }
62
63 cfg, err := config.Load(cfgPath)
64 if err != nil {
65 return err
66 }
67
68 // Show current system status
69 if status, err := monitor.GetCurrentStatus(); err == nil {
70 cmd.Println()
71 cmd.Println(status)
72 cmd.Println()
73 }
74
75 // Set up signal handling for graceful shutdown
76 sigChan := make(chan os.Signal, 1)
77 signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
78
79 // Create context for graceful shutdown
80 ctx, cancel := context.WithCancel(context.Background())
81 defer cancel()
82
83 // Initialize alerter
84 emailAlerter := alert.NewEmailAlerter(alert.EmailConfig{
85 SMTPServer: cfg.Email.SMTPServer,
86 SMTPPort: cfg.Email.SMTPPort,
87 From: cfg.Email.From,
88 To: cfg.Email.To,
89 Username: cfg.Email.Username,
90 Password: cfg.Email.Password,
91 })
92
93 // Check for recent reboot and send notification if needed
94 if err := monitor.CheckRebootAndNotify(ctx, cfg, emailAlerter); err != nil {
95 cmd.Printf("Warning: Failed to check reboot status: %v\n", err)
96 }
97
98 // Create monitor with alerter
99 mon := monitor.New(cfg, emailAlerter)
100
101 // Start monitoring
102 mon.Start(ctx)
103
104 cmd.Println()
105 cmd.Println("✓ ServMon started successfully. Monitoring active.")
106 cmd.Println("Monitoring in progress... Press Ctrl+C to stop.")
107 cmd.Println()
108
109 // Send startup notification
110 go func() {
111 hostname, err := os.Hostname()
112 if err != nil {
113 hostname = "unknown"
114 }
115
116 startupAlert := alert.NewAlert(
117 alert.LevelInfo,
118 fmt.Sprintf("Monitoring Started on %s", hostname),
119 "ServMon has started successfully and is now actively monitoring your system.",
120 )
121
122 startupAlert.WithMetadata("cpu_threshold", fmt.Sprintf("%.1f%%", cfg.AlertThresholds.CPU.Threshold))
123 startupAlert.WithMetadata("memory_threshold", fmt.Sprintf("%.1f%%", cfg.AlertThresholds.Memory.Threshold))
124 startupAlert.WithMetadata("disk_paths", cfg.GetDiskPaths())
125
126 if cfg.AlertThresholds.HTTP.URL != "" {
127 startupAlert.WithMetadata("http_endpoint", cfg.AlertThresholds.HTTP.URL)
128 }
129
130 if err := emailAlerter.Send(ctx, startupAlert); err != nil {
131 cmd.Printf("Warning: Failed to send startup notification: %v\n", err)
132 } else {
133 cmd.Println("✓ Startup notification sent successfully")
134 }
135 }()
136
137 // Wait for shutdown signal
138 sig := <-sigChan
139 cmd.Printf("Received signal %v, shutting down gracefully...\n", sig)
140 cancel()
141
142 // Give goroutines time to clean up
143 time.Sleep(1 * time.Second)
144 cmd.Println("✓ ServMon stopped successfully")
145 return nil
146 },
147 }
148
149 rootCmd.CompletionOptions.DisableDefaultCmd = true
150 rootCmd.PersistentFlags().StringVar(&cfgFile, flagConfig, path.Join(homeDir, ".servmon.yaml"), "config file")
151
152 if err := rootCmd.Execute(); err != nil {
153 fmt.Fprint(os.Stderr, err)
154 os.Exit(1)
155 }
156}
157
158func getVersion() (string, error) {
159 version, ok := debug.ReadBuildInfo()
160 if !ok {
161 return "", errors.New("failed to get version")
162 }
163
164 return strings.TrimSpace(version.Main.Version), nil
165}