Add Matrix notification
This commit is contained in:
parent
ed440f582f
commit
2b7ba22bfa
5 changed files with 140 additions and 29 deletions
|
@ -16,12 +16,6 @@ type Config struct {
|
|||
Matrix MatrixConfig
|
||||
}
|
||||
|
||||
type MatrixConfig struct {
|
||||
UserId string
|
||||
AccessToken string
|
||||
RoomId string
|
||||
}
|
||||
|
||||
func LoadConfigFromTOML(configPath string) (*Config, error) {
|
||||
file, err := os.Open(configPath)
|
||||
if err != nil {
|
||||
|
|
|
@ -14,13 +14,13 @@ import (
|
|||
|
||||
"github.com/oklog/ulid/v2"
|
||||
|
||||
"git.exozy.me/exozyme/status/scanner"
|
||||
. "git.exozy.me/exozyme/status/scanner"
|
||||
)
|
||||
|
||||
type Row struct {
|
||||
time time.Time
|
||||
kind EventType
|
||||
data scanner.ServiceStatus
|
||||
data ServiceStatus
|
||||
}
|
||||
|
||||
type EventType int
|
||||
|
@ -48,7 +48,8 @@ type Database interface {
|
|||
QueryAllAfter(cutoff time.Time) ([]Row, error)
|
||||
|
||||
// store service status records with timestamp
|
||||
StoreServiceStatusBatch(entries scanner.ServiceStatusBatch, when time.Time) error
|
||||
// returns non-duplicate rows
|
||||
StoreServiceStatusBatch(entries ServiceStatusBatch, when time.Time) ([]ServiceStatus, error)
|
||||
|
||||
// store self up/down event with timestamp
|
||||
//
|
||||
|
@ -59,10 +60,10 @@ type Database interface {
|
|||
type Span struct {
|
||||
start time.Time
|
||||
stop time.Time
|
||||
status scanner.ServiceStatus
|
||||
status ServiceStatus
|
||||
}
|
||||
|
||||
func append_close_span(res *[]Span, rows []Row, start_i, stop_i int, status scanner.ServiceStatus) {
|
||||
func append_close_span(res *[]Span, rows []Row, start_i, stop_i int, status ServiceStatus) {
|
||||
var stop_time time.Time
|
||||
if stop_i < 0 {
|
||||
stop_time = time.Now()
|
||||
|
@ -78,7 +79,7 @@ func append_close_span(res *[]Span, rows []Row, start_i, stop_i int, status scan
|
|||
func CalculateServiceStatusSpans(rows []Row, service_url string) []Span {
|
||||
res := []Span{}
|
||||
start_i := -1
|
||||
var status scanner.ServiceStatus
|
||||
var status ServiceStatus
|
||||
|
||||
for i, v := range rows {
|
||||
switch v.kind {
|
||||
|
@ -210,7 +211,9 @@ func (db *dumbDatabase) QueryAllAfter(cutoff time.Time) ([]Row, error) {
|
|||
return db.rows[start:], nil
|
||||
}
|
||||
|
||||
func (db *dumbDatabase) StoreServiceStatusBatch(entries scanner.ServiceStatusBatch, when time.Time) error {
|
||||
func (db *dumbDatabase) StoreServiceStatusBatch(entries ServiceStatusBatch, when time.Time) ([]ServiceStatus, error) {
|
||||
non_duplicates := []ServiceStatus{}
|
||||
|
||||
db.mutex.Lock()
|
||||
defer db.mutex.Unlock()
|
||||
|
||||
|
@ -235,7 +238,7 @@ func (db *dumbDatabase) StoreServiceStatusBatch(entries scanner.ServiceStatusBat
|
|||
db.rows = append(db.rows, new_row)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return non_duplicates, nil
|
||||
}
|
||||
|
||||
const max_rows = 10000
|
||||
|
@ -247,7 +250,7 @@ func (db *dumbDatabase) StoreSelfStatus(status EventType, when time.Time) error
|
|||
if status != EventSelfUp && status != EventSelfDown {
|
||||
log.Panicf("(StoreSelfStatus) invalid value: %v", status)
|
||||
}
|
||||
var placeholder scanner.ServiceStatus
|
||||
var placeholder ServiceStatus
|
||||
new_row := Row{when, EventType(status), placeholder}
|
||||
db.rows = append(db.rows, new_row)
|
||||
if len(db.rows) > max_rows {
|
||||
|
|
61
core/matrix.go
Normal file
61
core/matrix.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
m "maunium.net/go/mautrix"
|
||||
. "maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
type MatrixConfig struct {
|
||||
UserId string
|
||||
// The AccessToken can be got by logging in with any Matrix client.
|
||||
// In element.io, find it under Settings -> About & Help -> Advanced.
|
||||
AccessToken string
|
||||
RoomId string
|
||||
}
|
||||
|
||||
type MatrixClient struct {
|
||||
inner m.Client
|
||||
config MatrixConfig
|
||||
// Resolved/opaque Room Id
|
||||
room_id RoomID
|
||||
}
|
||||
|
||||
func NewMatrixClient(config MatrixConfig) (*MatrixClient, error) {
|
||||
if config.AccessToken == "" {
|
||||
return nil, fmt.Errorf("no access token")
|
||||
}
|
||||
|
||||
user_id := UserID(config.UserId)
|
||||
client, err := m.NewClient(user_id.Homeserver(), user_id, config.AccessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var opaque_room_id RoomID
|
||||
if strings.HasPrefix(config.RoomId, "!") {
|
||||
opaque_room_id = RoomID(config.RoomId)
|
||||
} else {
|
||||
resp, err := client.ResolveAlias(RoomAlias(config.RoomId))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opaque_room_id = resp.RoomID
|
||||
}
|
||||
|
||||
return &MatrixClient{
|
||||
*client,
|
||||
config,
|
||||
opaque_room_id,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (client *MatrixClient) SendNotice(message string) (*m.RespSendEvent, error) {
|
||||
return client.inner.SendNotice(client.room_id, message)
|
||||
}
|
||||
|
||||
func (client *MatrixClient) RedactEvent(eventId EventID, reason string) (*m.RespSendEvent, error) {
|
||||
return client.inner.RedactEvent(client.room_id, eventId, m.ReqRedact{Reason: reason})
|
||||
}
|
66
main.go
66
main.go
|
@ -3,6 +3,8 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net"
|
||||
|
@ -59,9 +61,18 @@ func rootPage(w http.ResponseWriter, r *http.Request) {
|
|||
// get service status'
|
||||
timeout := time.Second
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
data := scanner.CheckServiceBatch(ctx, config.Service)
|
||||
dbm.StoreServiceStatusBatch(data, time.Now())
|
||||
cancel()
|
||||
non_duplicates, err := dbm.StoreServiceStatusBatch(data, time.Now())
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
err = notify(non_duplicates)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
// don't uncomment this in production. verbose
|
||||
// log.Printf("%v", data)
|
||||
|
@ -69,20 +80,15 @@ func rootPage(w http.ResponseWriter, r *http.Request) {
|
|||
parsedTemplate, err := parseAndRenderTemplate("templates/index.html", data)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte(parsedTemplate))
|
||||
}
|
||||
|
||||
func removeFile(path string) {
|
||||
err := os.Remove(path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
var config *core.Config
|
||||
var dbm core.Database
|
||||
var client *core.MatrixClient
|
||||
|
||||
func cleanup() {
|
||||
log.Println("Cleaning up")
|
||||
|
@ -102,6 +108,11 @@ func main() {
|
|||
log.Panic(err)
|
||||
}
|
||||
|
||||
client, err = core.NewMatrixClient(config.Matrix)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
http.HandleFunc("/", rootPage)
|
||||
|
||||
// serve public folder from embedfs
|
||||
|
@ -151,7 +162,7 @@ func main() {
|
|||
}
|
||||
|
||||
// log startup and register cleanup (on SIGTERM or panic)
|
||||
ch_sigterm := make(chan os.Signal)
|
||||
ch_sigterm := make(chan os.Signal, 1)
|
||||
os_signal.Notify(ch_sigterm, syscall.SIGTERM, syscall.SIGINT)
|
||||
|
||||
log.Print("Starting up")
|
||||
|
@ -173,7 +184,11 @@ func main() {
|
|||
|
||||
go func() {
|
||||
for {
|
||||
routineCheck(5 * time.Minute)
|
||||
err := routineCheck(5 * time.Minute)
|
||||
if err != nil {
|
||||
log.Printf("Error in routineCheck: %v", err)
|
||||
// idea: maybe should delay here
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -185,12 +200,37 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
func routineCheck(timeout time.Duration) {
|
||||
func routineCheck(timeout time.Duration) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
data := scanner.CheckServiceBatch(ctx, config.Service)
|
||||
dbm.StoreServiceStatusBatch(data, time.Now())
|
||||
non_duplicates, err := dbm.StoreServiceStatusBatch(data, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = notify(non_duplicates)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// log.Printf("Routine check: %v", data)
|
||||
<-ctx.Done()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func notify(updates []scanner.ServiceStatus) error {
|
||||
final_err := error(nil)
|
||||
for _, v := range updates {
|
||||
statusString := "???"
|
||||
if v.Ok {
|
||||
statusString = "up"
|
||||
} else {
|
||||
statusString = "down"
|
||||
|
||||
}
|
||||
_, err := client.SendNotice(fmt.Sprintf("%s is %s!", v.Name, statusString))
|
||||
final_err = errors.Join(final_err, err)
|
||||
}
|
||||
return final_err
|
||||
}
|
||||
|
|
13
util.go
Normal file
13
util.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func removeFile(path string) {
|
||||
err := os.Remove(path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue