mirror of
https://github.com/qdm12/ddns-updater.git
synced 2026-04-05 08:54:09 -04:00
Gotify support (#17)
* Added Gotify support and notifications * Readme update for Gotify
This commit is contained in:
53
pkg/admin/gotify.go
Normal file
53
pkg/admin/gotify.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/gotify/go-api-client/v2/auth"
|
||||
"github.com/gotify/go-api-client/v2/client"
|
||||
"github.com/gotify/go-api-client/v2/client/message"
|
||||
"github.com/gotify/go-api-client/v2/gotify"
|
||||
"github.com/gotify/go-api-client/v2/models"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Gotify contains the Gotify API client and the token for the application
|
||||
type Gotify struct {
|
||||
client *client.GotifyREST
|
||||
token string
|
||||
}
|
||||
|
||||
// NewGotify creates an API client with the token for the Gotify server
|
||||
func NewGotify(URL *url.URL, token string, httpClient *http.Client) (g *Gotify, err error) {
|
||||
if URL == nil {
|
||||
return &Gotify{}, fmt.Errorf("Gotify URL not provided")
|
||||
} else if token == "" {
|
||||
return &Gotify{}, fmt.Errorf("Gotify token not provided")
|
||||
}
|
||||
client := gotify.NewClient(URL, httpClient)
|
||||
_, err = client.Version.GetVersion(nil)
|
||||
if err != nil {
|
||||
return &Gotify{}, fmt.Errorf("cannot communicate with Gotify server: %w", err)
|
||||
}
|
||||
return &Gotify{client: client, token: token}, nil
|
||||
}
|
||||
|
||||
// Notify sends a notification to the Gotify server
|
||||
func (g *Gotify) Notify(title string, priority int, content string, args ...interface{}) {
|
||||
if g == nil || g.client == nil {
|
||||
return
|
||||
}
|
||||
content = fmt.Sprintf(content, args...)
|
||||
params := message.NewCreateMessageParams()
|
||||
params.Body = &models.MessageExternal{
|
||||
Title: title,
|
||||
Message: content,
|
||||
Priority: priority,
|
||||
}
|
||||
_, err := g.client.Message.CreateMessage(params, auth.TokenAuth(g.token))
|
||||
if err != nil {
|
||||
zap.S().Warn("cannot send message to Gotify: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ func init() {
|
||||
viper.SetDefault("logmode", "")
|
||||
viper.SetDefault("loglevel", "")
|
||||
viper.SetDefault("nodeid", "0")
|
||||
viper.SetDefault("gotifyurl", "")
|
||||
viper.SetDefault("gotifytoken", "")
|
||||
viper.BindEnv("listeningport")
|
||||
viper.BindEnv("rooturl")
|
||||
viper.BindEnv("delay")
|
||||
@@ -19,4 +21,6 @@ func init() {
|
||||
viper.BindEnv("logmode")
|
||||
viper.BindEnv("loglevel")
|
||||
viper.BindEnv("nodeid")
|
||||
viper.BindEnv("gotifyurl")
|
||||
viper.BindEnv("gotifytoken")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -107,6 +108,25 @@ func GetNodeID() int {
|
||||
return value
|
||||
}
|
||||
|
||||
// GetGotifyURL obtains the URL to the Gotify server
|
||||
func GetGotifyURL() (URL *url.URL) {
|
||||
s := viper.GetString("gotifyurl")
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
URL, err := url.Parse(s)
|
||||
if err != nil {
|
||||
zap.S().Fatalf("URL %s is not valid", s)
|
||||
}
|
||||
return URL
|
||||
}
|
||||
|
||||
// GetGotifyToken obtains the token for the app on the Gotify server
|
||||
func GetGotifyToken() (token string) {
|
||||
token = viper.GetString("gotifytoken")
|
||||
return token
|
||||
}
|
||||
|
||||
func stringInAny(s string, ss ...string) bool {
|
||||
for _, x := range ss {
|
||||
if s == x {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"text/template"
|
||||
|
||||
"ddns-updater/pkg/admin"
|
||||
"ddns-updater/pkg/models"
|
||||
"ddns-updater/pkg/network"
|
||||
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
|
||||
type healthcheckParamsType struct {
|
||||
recordsConfigs []models.RecordConfigType
|
||||
gotify *admin.Gotify
|
||||
}
|
||||
|
||||
type indexParamsType struct {
|
||||
@@ -27,9 +29,10 @@ type updateParamsType struct {
|
||||
}
|
||||
|
||||
// CreateRouter returns a router with all the necessary routes configured
|
||||
func CreateRouter(rootURL, dir string, forceCh chan struct{}, recordsConfigs []models.RecordConfigType) *httprouter.Router {
|
||||
func CreateRouter(rootURL, dir string, forceCh chan struct{}, recordsConfigs []models.RecordConfigType, gotify *admin.Gotify) *httprouter.Router {
|
||||
healthcheckParams := healthcheckParamsType{
|
||||
recordsConfigs: recordsConfigs,
|
||||
gotify: gotify,
|
||||
}
|
||||
indexParams := indexParamsType{
|
||||
dir: dir,
|
||||
@@ -62,17 +65,20 @@ func (params *healthcheckParamsType) get(w http.ResponseWriter, r *http.Request,
|
||||
clientIP, err := network.GetClientIP(r)
|
||||
if err != nil {
|
||||
zap.S().Infof("Cannot detect client IP: %s", err)
|
||||
params.gotify.Notify("DDNS Updater", 5, "Cannot detect client IP: %s", err)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if clientIP != "127.0.0.1" && clientIP != "::1" {
|
||||
zap.S().Infof("IP address %s tried to perform the healthcheck", clientIP)
|
||||
params.gotify.Notify("DDNS Updater", 5, "IP address %s tried to perform the healthcheck", clientIP)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
err = healthcheckHandler(params.recordsConfigs)
|
||||
if err != nil {
|
||||
zap.S().Warnf("Responded with error to healthcheck: %s", err)
|
||||
params.gotify.Notify("DDNS Updater", 4, "Responded with error to healthcheck: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package update
|
||||
|
||||
import (
|
||||
"ddns-updater/pkg/database"
|
||||
"ddns-updater/pkg/admin"
|
||||
"ddns-updater/pkg/models"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -14,6 +15,7 @@ func TriggerServer(
|
||||
recordsConfigs []models.RecordConfigType, // does not change size so no pointer needed
|
||||
httpClient *http.Client,
|
||||
sqlDb *database.DB,
|
||||
gotify *admin.Gotify,
|
||||
) {
|
||||
var chQuitArr, chForceArr []chan struct{}
|
||||
defer func() {
|
||||
@@ -31,9 +33,9 @@ func TriggerServer(
|
||||
chQuitArr = append(chQuitArr, make(chan struct{}))
|
||||
customDelay := recordsConfigs[i].Settings.Delay
|
||||
if customDelay > 0 {
|
||||
go periodicServer(&recordsConfigs[i], customDelay, httpClient, sqlDb, chForceArr[i], chQuitArr[i])
|
||||
go periodicServer(&recordsConfigs[i], customDelay, httpClient, sqlDb, chForceArr[i], chQuitArr[i], gotify)
|
||||
} else {
|
||||
go periodicServer(&recordsConfigs[i], delay, httpClient, sqlDb, chForceArr[i], chQuitArr[i])
|
||||
go periodicServer(&recordsConfigs[i], delay, httpClient, sqlDb, chForceArr[i], chQuitArr[i], gotify)
|
||||
}
|
||||
}
|
||||
// fan out channel signals
|
||||
@@ -58,6 +60,7 @@ func periodicServer(
|
||||
httpClient *http.Client,
|
||||
sqlDb *database.DB,
|
||||
chForce, chQuit chan struct{},
|
||||
gotify *admin.Gotify,
|
||||
) {
|
||||
ticker := time.NewTicker(delay * time.Second)
|
||||
defer func() {
|
||||
@@ -68,9 +71,9 @@ func periodicServer(
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
go update(recordConfig, httpClient, sqlDb)
|
||||
go update(recordConfig, httpClient, sqlDb, gotify)
|
||||
case <-chForce:
|
||||
go update(recordConfig, httpClient, sqlDb)
|
||||
go update(recordConfig, httpClient, sqlDb, gotify)
|
||||
case <-chQuit:
|
||||
recordConfig.IsUpdating.Lock() // wait for an eventual update to finish
|
||||
ticker.Stop()
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"ddns-updater/pkg/admin"
|
||||
"ddns-updater/pkg/database"
|
||||
"ddns-updater/pkg/models"
|
||||
"ddns-updater/pkg/network"
|
||||
@@ -32,6 +33,7 @@ func update(
|
||||
recordConfig *models.RecordConfigType,
|
||||
httpClient *http.Client,
|
||||
sqlDb *database.DB,
|
||||
gotify *admin.Gotify,
|
||||
) {
|
||||
var err error
|
||||
recordConfig.IsUpdating.Lock()
|
||||
@@ -44,6 +46,7 @@ func update(
|
||||
recordConfig.Status.SetCode(models.FAIL)
|
||||
recordConfig.Status.SetMessage("%s", err)
|
||||
zap.S().Warn(recordConfig)
|
||||
gotify.Notify("DDNS Updater", 5, recordConfig.String())
|
||||
return
|
||||
}
|
||||
// Note: empty IP means DNS provider provided
|
||||
@@ -123,6 +126,7 @@ func update(
|
||||
recordConfig.Status.SetCode(models.FAIL)
|
||||
recordConfig.Status.SetMessage("%s", err)
|
||||
zap.S().Warn(recordConfig)
|
||||
gotify.Notify("DDNS Updater", 5, recordConfig.String())
|
||||
return
|
||||
}
|
||||
if len(ips) > 0 && ip == ips[0] { // same IP
|
||||
@@ -132,6 +136,7 @@ func update(
|
||||
if err != nil {
|
||||
recordConfig.Status.SetCode(models.FAIL)
|
||||
recordConfig.Status.SetMessage("Cannot update database: %s", err)
|
||||
gotify.Notify("DDNS Updater", 4, "Cannot update database: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -140,10 +145,12 @@ func update(
|
||||
recordConfig.Status.SetMessage("")
|
||||
recordConfig.History.SetTSuccess(time.Now())
|
||||
recordConfig.History.PrependIP(ip)
|
||||
gotify.Notify("DDNS Updater", 1, "%s changed from %s to %s", recordConfig.Settings.BuildDomainName(), ips[0], ip)
|
||||
err = sqlDb.StoreNewIP(recordConfig.Settings.Domain, recordConfig.Settings.Host, ip)
|
||||
if err != nil {
|
||||
recordConfig.Status.SetCode(models.FAIL)
|
||||
recordConfig.Status.SetMessage("Cannot update database: %s", err)
|
||||
gotify.Notify("DDNS Updater", 4, "Cannot update database: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user