diff --git a/.gitignore b/.gitignore index 5f868c1..6c5f7a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ raidcheck +.env +mdstat \ No newline at end of file diff --git a/filter_changed_states.go b/filter_changed_states.go index aca8b7f..4f09259 100644 --- a/filter_changed_states.go +++ b/filter_changed_states.go @@ -2,8 +2,6 @@ package main import ( "reflect" - - "git.tordarus.net/Tordarus/dockerhealth" ) func filterChanges(in <-chan RaidState) <-chan RaidState { @@ -14,17 +12,12 @@ func filterChanges(in <-chan RaidState) <-chan RaidState { currentStates := map[string]RaidState{} - first := true - for state := range in { - if oldState, ok := currentStates[state.Name]; !ok || !reflect.DeepEqual(oldState.DevicesUp, state.DevicesUp) { - dockerhealth.Healthy = first + oldState, ok := currentStates[state.Name] + if !ok || !reflect.DeepEqual(oldState.DevicesUp, state.DevicesUp) { currentStates[state.Name] = state out <- state - } else { - dockerhealth.Healthy = true } - first = false } }(in, out) diff --git a/go.mod b/go.mod index bdf6f54..5e35d3b 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ -module git.tordarus.net/Tordarus/raidcheck +module git.milar.in/milarin/raidcheck go 1.17 require ( - git.tordarus.net/Tordarus/adverr v0.2.0 - git.tordarus.net/Tordarus/dockerhealth v0.0.1 + git.milar.in/milarin/adverr v1.1.0 + git.milar.in/milarin/envvars/v2 v2.0.0 github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 ) diff --git a/go.sum b/go.sum index 83a6e6b..c3b1554 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ -git.tordarus.net/Tordarus/adverr v0.2.0 h1:kLYjR2/Vb2GHiSAMvAv+WPNaHR9BRphKanf8H/pCZdA= -git.tordarus.net/Tordarus/adverr v0.2.0/go.mod h1:XRf0+7nhOkIEr0gi9DUG4RvV2KaOFB0fYPDaR1KLenw= +git.milar.in/milarin/adverr v1.1.0 h1:jD9WnOvs40lfMhvqQ7cllOaRJNBMWr1f07/s9jAadp0= +git.milar.in/milarin/adverr v1.1.0/go.mod h1:joU9sBb7ySyNv4SpTXB0Z4o1mjXsArBw4N27wjgzj9E= +git.milar.in/milarin/envvars/v2 v2.0.0 h1:DWRQCWaHqzDD8NGpSgv5tYLuF9A/dVFPAtTvz3oiIqE= +git.milar.in/milarin/envvars/v2 v2.0.0/go.mod h1:HkdEi+gG2lJSmVq547bTlQV4qQ0hO333bE8IrE0B9yY= git.tordarus.net/Tordarus/dockerhealth v0.0.1 h1:rxkwmCW5PDe9gbnXap7d3n5rK1Qyr6xpmJPZFom/ZXc= git.tordarus.net/Tordarus/dockerhealth v0.0.1/go.mod h1:U0IPsBJHAjqWgNyehWwGfYqTwPJgqBNhzk/eBpaZsnE= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= diff --git a/main.go b/main.go index 6b37080..c7da792 100644 --- a/main.go +++ b/main.go @@ -3,56 +3,47 @@ package main import ( "fmt" "os" - "strconv" "time" - "git.tordarus.net/Tordarus/adverr" - "git.tordarus.net/Tordarus/dockerhealth" + "git.milar.in/milarin/adverr" + "git.milar.in/milarin/envvars/v2" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" ) var ( - interval = 1 * time.Minute - telegramToken string - chatID int64 + MdstatFile = envvars.String("MDSTAT_FILE", "/proc/mdstat") + PollInterval = envvars.Duration("POLL_INTERVAL", time.Minute) + BotToken = envvars.String("TELEGRAM_API_TOKEN", "") + ChatID = envvars.Int64("TELEGRAM_CHAT_ID", 0) ) func main() { - if i, ok := os.LookupEnv("POLL_INTERVAL"); ok { - if interval2, err := time.ParseDuration(i); err == nil { - interval = interval2 - } + if BotToken == "" { + fmt.Fprintln(os.Stderr, "$TELEGRAM_API_TOKEN not set") + os.Exit(1) } - if tt, ok := os.LookupEnv("TELEGRAM_API_TOKEN"); ok { - telegramToken = tt + if ChatID == 0 { + fmt.Fprintln(os.Stderr, "$TELEGRAM_CHAT_ID not set") + os.Exit(1) } - if id, ok := os.LookupEnv("TELEGRAM_CHAT_ID"); ok { - if chatId2, err := strconv.Atoi(id); err == nil { - chatID = int64(chatId2) - } - } - - bot, err := tgbotapi.NewBotAPI(telegramToken) + bot, err := tgbotapi.NewBotAPI(BotToken) if err != nil { adverr.Fatalln(err, 1) } - for state := range filterChanges(mdstat()) { + for state := range filterChanges(mdstat(MdstatFile, PollInterval)) { str := state.String() fmt.Println(str) - message := tgbotapi.NewMessage(chatID, fmt.Sprintf("Der Zustand des RAID Systems %s hat sich verändert:\n\n%s", state.Name, str)) + message := tgbotapi.NewMessage(ChatID, fmt.Sprintf("Der Zustand des RAID Systems %s hat sich verändert:\n\n%s", state.Name, str)) message.Entities = append(message.Entities, tgbotapi.MessageEntity{ Type: "code", Offset: len("Der Zustand des RAID Systems %s hat sich verändert:\n\n"), Length: len(str), }) - _, err := bot.Send(message) - if err != nil { + if _, err := bot.Send(message); err != nil { adverr.Println(err) } } - - dockerhealth.Healthy = false } diff --git a/mdstat.go b/mdstat.go index 119b5c8..87282cf 100644 --- a/mdstat.go +++ b/mdstat.go @@ -16,59 +16,74 @@ var ( deviceRegex = regexp.MustCompile(`^([\w\d]+)\[(\d+)\]$`) ) -func mdstat() <-chan RaidState { +func mdstat(file string, pollInterval time.Duration) <-chan RaidState { ch := make(chan RaidState, 4) - - go func(out chan<- RaidState) { - defer close(ch) - - for { - data, err := os.ReadFile("/proc/mdstat") - if err != nil { - panic(err) - } - lines := strings.Split(string(data), "\n") - - state := RaidState{} - for _, line := range lines { - if match := firstLineRegex.FindStringSubmatch(line); match != nil { - state.Name = match[1] - state.State = match[2] - state.Level = match[3] - - devices := strings.Split(match[4], " ") - state.Devices = make([]string, len(devices)) - for _, device := range devices { - if match := deviceRegex.FindStringSubmatch(device); match != nil { - deviceIndex, _ := strconv.Atoi(match[2]) - state.Devices[deviceIndex] = match[1] - } - } - } else if match := secondLineRegex.FindStringSubmatch(line); match != nil { - state.RegisteredDeviceCount, _ = strconv.Atoi(match[1]) - state.UsedDeviceCount, _ = strconv.Atoi(match[2]) - state.DevicesUp = map[string]bool{} - for i, rn := range match[3] { - state.DevicesUp[state.Devices[i]] = rn == 'U' - } - } else if match := actionLineRegex.FindStringSubmatch(line); match != nil { - state.Action = new(RaidAction) - state.Action.Name = match[1] - state.Action.Progress, _ = strconv.ParseFloat(match[2], 64) - state.Action.Duration = parseFinished(match[3], match[4]) - state.Action.DurationFormatted = state.Action.Duration.String() - state.Action.Finished = time.Now().Add(state.Action.Duration) - } - } - - out <- state - time.Sleep(time.Minute) - } - }(ch) - + go parseMdstatOutput(file, pollInterval, ch) return ch } +func parseMdstatOutput(file string, pollInterval time.Duration, out chan<- RaidState) { + defer close(out) + + ticker := time.NewTicker(pollInterval) + for range ticker.C { + data, err := os.ReadFile(file) + if err != nil { + panic(err) + } + + lines := strings.Split(string(data), "\n") + state := RaidState{} + for _, line := range lines { + parseLine(&state, line) + } + + out <- state + } +} + +func parseLine(state *RaidState, line string) { + if matches := firstLineRegex.FindStringSubmatch(line); matches != nil { + parseFirstLine(state, matches) + } else if matches := secondLineRegex.FindStringSubmatch(line); matches != nil { + parseSecondLine(state, matches) + } else if match := actionLineRegex.FindStringSubmatch(line); match != nil { + parseActionLine(state, matches) + } +} + +func parseFirstLine(state *RaidState, matches []string) { + state.Name = matches[1] + state.State = matches[2] + state.Level = matches[3] + + devices := strings.Split(matches[4], " ") + state.Devices = make([]string, len(devices)) + for deviceIndex, device := range devices { + if match := deviceRegex.FindStringSubmatch(device); match != nil { + state.Devices[deviceIndex] = match[1] + } + } +} + +func parseSecondLine(state *RaidState, matches []string) { + state.RegisteredDeviceCount, _ = strconv.Atoi(matches[1]) + state.UsedDeviceCount, _ = strconv.Atoi(matches[2]) + state.DevicesUp = map[string]bool{} + for i, rn := range matches[3] { + state.DevicesUp[state.Devices[i]] = rn == 'U' + } +} + +func parseActionLine(state *RaidState, matches []string) { + state.Action = new(RaidAction) + state.Action.Name = matches[1] + state.Action.Progress, _ = strconv.ParseFloat(matches[2], 64) + state.Action.Duration = parseFinished(matches[3], matches[4]) + state.Action.DurationFormatted = state.Action.Duration.String() + state.Action.Finished = time.Now().Add(state.Action.Duration) +} + func parseFinished(value, unit string) time.Duration { switch unit { case "sec":