Add Matrix notification

This commit is contained in:
Locria Cyber 2023-10-07 08:24:49 +00:00
parent ed440f582f
commit 2b7ba22bfa
Signed by: iacore
GPG key ID: F8C16E5157A63006
5 changed files with 140 additions and 29 deletions

View file

@ -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 {

View file

@ -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
View 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})
}

68
main.go
View file

@ -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
View 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)
}
}