Skip to content

Commit

Permalink
notifier: start to implemtn sms-modem and email backends
Browse files Browse the repository at this point in the history
  • Loading branch information
equinox0815 committed Oct 16, 2023
1 parent c0acfe1 commit 2f73907
Show file tree
Hide file tree
Showing 8 changed files with 318 additions and 12 deletions.
2 changes: 1 addition & 1 deletion cmd/whawty-alerts/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ type WebConfig struct {

type Config struct {
Store store.Config `yaml:"store"`
Notifier notifier.Config `yaml:"notifer"`
Notifier notifier.Config `yaml:"notifier"`
Web WebConfig `yaml:"web"`
}

Expand Down
12 changes: 12 additions & 0 deletions contrib/sample-cfg.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
store:
path: ./contrib/test.db
notifier:
backends:
- name: mail-foo
email:
from: [email protected]
smarthost: mailrelay.example.com
- name: sms-bar
smsModem:
device: /dev/ttyUSB0
baudrate: 115200
timeout: 10s
pin: 1234
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,12 @@ require (
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 // indirect
github.com/spreadspace/tlsconfig v0.0.0-20230726215100-56bbcafa5d60 // indirect
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/urfave/cli v1.22.14 // indirect
github.com/warthog618/modem v0.4.0 // indirect
github.com/warthog618/sms v0.3.0 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.9.0 // indirect
Expand Down
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
Expand Down Expand Up @@ -325,13 +326,19 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
github.com/warthog618/modem v0.4.0 h1:QmNRbVopcJYTEalWePSgKd5rhpQW5x/yptojTFVdjjg=
github.com/warthog618/modem v0.4.0/go.mod h1:9b3nNrk7JZRskP+TpHQppfz5QxRKkQGdgeq2Fi0QHcI=
github.com/warthog618/sms v0.3.0 h1:LYAb5ngmu2qjNExgji3B7xi2tIZ9+DsuE9pC5xs4wwc=
github.com/warthog618/sms v0.3.0/go.mod h1:+bYZGeBxu003sxD5xhzsrIPBAjPBzTABsRTwSpd7ld4=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand Down Expand Up @@ -476,6 +483,7 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down Expand Up @@ -645,6 +653,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down
69 changes: 69 additions & 0 deletions notifier/backend_email.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// Copyright (c) 2023 whawty contributors (see AUTHORS file)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// - Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// - Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// - Neither the name of whawty.alerts nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

package notifier

import (
"context"
"fmt"
"log"

"github.com/whawty/alerts/store"
)

type EMailBackend struct {
infoLog *log.Logger
dbgLog *log.Logger
name string
conf *NotifierBackendConfigEMail
// TODO: add client config
}

func NewEMailBackend(name string, conf *NotifierBackendConfigEMail, infoLog, dbgLog *log.Logger) *EMailBackend {
return &EMailBackend{name: name, conf: conf, infoLog: infoLog, dbgLog: dbgLog}
}

func (emb *EMailBackend) Init() (err error) {
return fmt.Errorf("not yet implemented!")
}

func (smb *EMailBackend) Ready() bool {
// TODO: implement this
return false
}

func (emb *EMailBackend) Notify(ctx context.Context, target NotifierTarget, alert *store.Alert) error {
return fmt.Errorf("not yet implemented!")
}

func (emb *EMailBackend) Close() error {
// TODO: close client?
return nil
}
129 changes: 129 additions & 0 deletions notifier/backend_smsmodem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//
// Copyright (c) 2023 whawty contributors (see AUTHORS file)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// - Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// - Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// - Neither the name of whawty.alerts nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

package notifier

import (
"context"
"fmt"
"io"
"log"
"time"

"github.com/warthog618/modem/at"
"github.com/warthog618/modem/gsm"
"github.com/warthog618/modem/serial"
"github.com/whawty/alerts/store"
)

type SMSModemBackend struct {
infoLog *log.Logger
dbgLog *log.Logger
name string
conf *NotifierBackendConfigSMSModem
modem io.ReadWriteCloser
sms *gsm.GSM
}

func NewSMSModemBackend(name string, conf *NotifierBackendConfigSMSModem, infoLog, dbgLog *log.Logger) *SMSModemBackend {
if conf.Timeout <= 0 {
conf.Timeout = 5 * time.Second
}
return &SMSModemBackend{name: name, conf: conf, infoLog: infoLog, dbgLog: dbgLog}
}

func (smb *SMSModemBackend) Init() (err error) {
smb.modem, err = serial.New(serial.WithPort(smb.conf.Device), serial.WithBaud(smb.conf.Baudrate))
if err != nil {
smb.modem = nil
return
}

a := at.New(smb.modem, at.WithTimeout(smb.conf.Timeout))
if smb.conf.Pin != nil {
var resp []string
resp, err = a.Command(fmt.Sprintf("+CPIN=%d", smb.conf.Pin))
if err != nil {
smb.modem.Close()
smb.modem = nil
return
}
smb.dbgLog.Printf("SMSModem(%s): enter pin code respone: %v", smb.name, resp)
}

smb.sms = gsm.New(a)
err = smb.sms.Init()
if err != nil {
smb.modem.Close()
smb.modem = nil
smb.sms = nil
return
}

err = smb.sms.StartMessageRx(
func(msg gsm.Message) {
smb.infoLog.Printf("SMSModem(%s): got SMS from '%s': %s", smb.name, msg.Number, msg.Message)
},
func(err error) {
smb.infoLog.Printf("SMSModem(%s): got SMS rx error: %v", smb.name, err)
})

if err != nil {
smb.modem.Close()
smb.modem = nil
smb.sms = nil
return
}
return nil
}

func (smb *SMSModemBackend) Ready() bool {
return smb.modem != nil && smb.sms != nil
}

func (smb *SMSModemBackend) Notify(ctx context.Context, target NotifierTarget, alert *store.Alert) error {
// TODO: improve alert formatting
message := fmt.Sprintf("%s / %s / %s", alert.State, alert.Severity, alert.Name)

resp, err := smb.sms.SendLongMessage(target.SMS.Number, message)
if err != nil {
return err
}
smb.dbgLog.Printf("SMSModem(%s): send sms response: %v", smb.name, resp)
return nil
}

func (smb *SMSModemBackend) Close() error {
smb.sms.StopMessageRx()
smb.modem.Close()
smb.modem = nil
smb.sms = nil
return nil
}
54 changes: 47 additions & 7 deletions notifier/notifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ import (
)

type Notifier struct {
conf *Config
store *store.Store
infoLog *log.Logger
dbgLog *log.Logger
ctx context.Context
cancel context.CancelFunc
conf *Config
store *store.Store
infoLog *log.Logger
dbgLog *log.Logger
ctx context.Context
cancel context.CancelFunc
backends map[string]NotifierBackend
}

func (n *Notifier) Close() error {
Expand All @@ -67,8 +68,47 @@ func NewNotifier(conf *Config, st *store.Store, infoLog, dbgLog *log.Logger) (n
n.conf.Interval = 1 * time.Minute
}

n.backends = make(map[string]NotifierBackend)
for idx, backend := range n.conf.Backends {
if backend.Name == "" {
infoLog.Printf("notifier: ignoring unnamed backend at index %d", idx)
continue // make this an permanent error??
}
if _, ok := n.backends[backend.Name]; ok {
infoLog.Printf("notifier: ignoring duplicate backend name at index %d", idx)
continue // make this an permanent error??
}

var b NotifierBackend
cnt := 0
if backend.EMail != nil {
b = NewEMailBackend(backend.Name, backend.EMail, infoLog, dbgLog)
cnt = cnt + 1
}
if backend.SMSModem != nil {
b = NewSMSModemBackend(backend.Name, backend.SMSModem, infoLog, dbgLog)
cnt = cnt + 1
}
if cnt == 0 {
infoLog.Printf("notifier: no valid backend config found for backend '%s'", backend.Name)
continue
}
if cnt > 1 {
infoLog.Printf("notifier: ambiguous backend config '%s'", backend.Name)
continue
}
n.backends[backend.Name] = b

if err := b.Init(); err != nil {
infoLog.Printf("notifier: failed to initialize backend '%s': %v", backend.Name, err)
} else {
infoLog.Printf("notifier: backend '%s' successfully initialized", backend.Name)
}
}

// TODO: start go-routine to re-initialize failed backends
// TODO: start go-routine to handle notfications

infoLog.Printf("notifier: started with evaluation interval %s", conf.Interval.String())
infoLog.Printf("notifier: started with %d backends and evaluation interval %s", len(n.backends), conf.Interval.String())
return
}
Loading

0 comments on commit 2f73907

Please sign in to comment.