diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aae5376 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +config.json +logs diff --git a/README.md b/README.md new file mode 100644 index 0000000..0949ad4 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# canaryBot +Bot to check if services are alive. +Current bots: +- Matrix (Riot) + + +#### Config +File `config.json` +```json +{ + "matrix": { + "room_id": "!zzzzz:mmmmmm.ooo", + "user": "aaaaa", + "password": "xxxxx", + "server": "https://sssss.ooo" + }, + "services": [{ + "name": "name01", + "url": "http://127.0.0.1:80", + "statusCode": 200 + }, + { + "name": "service02", + "url": "http://127.0.0.1:7000/api", + "statusCode": 200 + } + ], + "sleepTime": 30, + "retry": 5 +} +``` + +- sleepTime: time between each request to the services +- retry: after X times failing, restart the counter + +### Run +``` +./canaryBot +``` diff --git a/canaryBot b/canaryBot new file mode 100755 index 0000000..af6ec5b Binary files /dev/null and b/canaryBot differ diff --git a/checker.go b/checker.go new file mode 100644 index 0000000..53472a4 --- /dev/null +++ b/checker.go @@ -0,0 +1,54 @@ +package main + +import ( + "log" + "net/http" + "strconv" + "time" +) + +func checker(services []Service) { + log.Println("serverChecker started") + log.Println(services) + + ticker := time.NewTicker(time.Second * time.Duration(int64(config.SleepTime))) + for _ = range ticker.C { + for k, service := range services { + resp, err := checkUrl(service.Url) + if err != nil { + if service.Counter == 0 { + msg := "⚠️ Service " + service.Name + ", with url " + service.Url + " is not responding" + matrixSendMsg(msg) + } + services[k].Counter = services[k].Counter + 1 + } else if resp.StatusCode != service.StatusCode { + if service.Counter == 0 { + msg := "⚠️ Service " + service.Name + ", with url " + service.Url + " has returned a non expected StatusCode: " + strconv.Itoa(resp.StatusCode) + ", expected: " + strconv.Itoa(service.StatusCode) + matrixSendMsg(msg) + } + services[k].Counter = services[k].Counter + 1 + } else { + if services[k].Counter != 0 { + msg := "✔️ Service " + service.Name + ", with url " + service.Url + " is alive again" + matrixSendMsg(msg) + } + services[k].Counter = 0 + } + + if services[k].Counter > config.Retry { + msg := "⚠️ Service " + services[k].Name + ", with url " + services[k].Url + " is still failing" + matrixSendNotice(msg) + services[k].Counter = 1 + } + } + } +} + +func checkUrl(url string) (*http.Response, error) { + timeout := time.Duration(1 * time.Second) + client := http.Client{ + Timeout: timeout, + } + resp, err := client.Get(url) + return resp, err +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..4ba316b --- /dev/null +++ b/config.go @@ -0,0 +1,37 @@ +package main + +import ( + "encoding/json" + "io/ioutil" +) + +//MatrixConfig stores the matrix data config +type MatrixConfig struct { + RoomId string `json:"room_id"` + User string `json:"user"` + Password string `json:"password"` + Server string `json:"server"` +} +type Service struct { + Name string `json:"name"` + Url string `json:"url"` + StatusCode int `json:"statusCode"` + Counter int +} +type Config struct { + Matrix MatrixConfig `json:"matrix"` + Services []Service `json:"services"` + SleepTime int `json:"sleepTime"` + Retry int `json:"retry"` +} + +var config Config + +func readConfig() { + file, e := ioutil.ReadFile("config.json") + if e != nil { + panic(e) + } + content := string(file) + json.Unmarshal([]byte(content), &config) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..85734c4 --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module github.com/arnaucube/canaryBot + +require ( + github.com/fatih/color v1.7.0 + github.com/mattn/go-colorable v0.0.9 // indirect + github.com/mattn/go-isatty v0.0.4 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..993e4bb --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= diff --git a/log.go b/log.go new file mode 100644 index 0000000..d12c469 --- /dev/null +++ b/log.go @@ -0,0 +1,19 @@ +package main + +import ( + "io" + "log" + "os" + "time" +) + +func savelog() { + timeS := time.Now().String() + _ = os.Mkdir("logs", os.ModePerm) + logFile, err := os.OpenFile("logs/log-"+timeS+".log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666) + if err != nil { + panic(err) + } + mw := io.MultiWriter(os.Stdout, logFile) + log.SetOutput(mw) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..3595050 --- /dev/null +++ b/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "fmt" + + "github.com/fatih/color" +) + +const version = "0.0.1-dev" + +func main() { + savelog() + + color.Green("canaryBot") + color.Yellow("version: " + version) + + color.Magenta("------matrix------") + readConfig() + loginMatrix() + fmt.Print("matrix token: ") + color.Cyan(matrixToken.AccessToken) + fmt.Println("") + matrixSendMsg("canaryBot started") + + color.Magenta("------starting to listen------") + + checker(config.Services) +} diff --git a/matrix.go b/matrix.go new file mode 100644 index 0000000..4c26edc --- /dev/null +++ b/matrix.go @@ -0,0 +1,93 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "math/rand" + "net/http" + "strconv" + "strings" + "time" + + "github.com/fatih/color" +) + +//MatrixToken stores the token data from matrix +type MatrixToken struct { + AccessToken string `json:"access_token"` + Server string `json:"server"` + UserId string `json:"user_id"` + DeviceId string `json:"device_id"` +} + +var matrixToken MatrixToken + +var defaultTimeFormat = "15:04:05" + +func loginMatrix() { + url := config.Matrix.Server + "/_matrix/client/r0/login" + jsonStr := `{ + "type":"m.login.password", + "user":"` + config.Matrix.User + `", + "password":"` + config.Matrix.Password + `" + }` + b := strings.NewReader(jsonStr) + req, _ := http.NewRequest("POST", url, b) + req.Header.Set("Content-Type", "application/json") + res, err := http.DefaultClient.Do(req) + if err != nil { + log.Println(err) + } + defer res.Body.Close() + body, _ := ioutil.ReadAll(res.Body) + + json.Unmarshal([]byte(body), &matrixToken) + +} +func matrixSendMsg(msg string) { + txnId := strconv.Itoa(rand.Int()) + url := config.Matrix.Server + "/_matrix/client/r0/rooms/" + config.Matrix.RoomId + "/send/m.room.message/" + txnId + "?access_token=" + matrixToken.AccessToken + jsonStr := `{ + "body":"` + time.Now().Format(defaultTimeFormat) + `: ` + msg + `", + "msgtype":"m.text" + }` + b := strings.NewReader(jsonStr) + req, _ := http.NewRequest("PUT", url, b) + req.Header.Set("Content-Type", "application/json") + res, err := http.DefaultClient.Do(req) + if err != nil { + log.Println(err) + } + defer res.Body.Close() + body, _ := ioutil.ReadAll(res.Body) + + fmt.Println(string(body)) + fmt.Print("msg sent to Matrix: ") + color.Green(msg) + +} + +func matrixSendNotice(msg string) { + txnId := strconv.Itoa(rand.Int()) + url := config.Matrix.Server + "/_matrix/client/r0/rooms/" + config.Matrix.RoomId + "/send/m.room.message/" + txnId + "?access_token=" + matrixToken.AccessToken + jsonStr := `{ + "body":"` + time.Now().Format(defaultTimeFormat) + `: ` + msg + `", + "msgtype":"m.notice" + }` + b := strings.NewReader(jsonStr) + req, _ := http.NewRequest("PUT", url, b) + req.Header.Set("Content-Type", "application/json") + res, err := http.DefaultClient.Do(req) + if err != nil { + log.Println(err) + } + defer res.Body.Close() + body, _ := ioutil.ReadAll(res.Body) + + fmt.Println(string(body)) + fmt.Print("msg sent to Matrix: ") + color.Green(msg) + +}