commit - /dev/null
commit + e20d6baf639b035ce416f106ffd196070cd3e372
blob - /dev/null
blob + ebf0db795e2a08b39cc7d6fdca8e5d8d25f327c4 (mode 644)
--- /dev/null
+++ LICENSE
+Copyright (c) 2021 Oliver Lowe <o@olowe.co>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
blob - /dev/null
blob + 6ec9128c06dcf0348aa57840072b871e618e595d (mode 644)
--- /dev/null
+++ README.md
+This repository contains the Go pushover package,
+the pover command-line utility,
+and example Icinga2 configuration and scripts to send notifications from Icinga2 using pover.
blob - /dev/null
blob + a4f6a780df90013537749f443140089eabd54784 (mode 644)
--- /dev/null
+++ cmd/pover/README.md
+## Tests
+Tests are run by building and running pover
+
+First, build pover:
+
+ go build
+
+Create a valid credentials file, then run test.sh
+
+ ./test.sh
blob - /dev/null
blob + c5667b15545813d8a6b4478c35bb5649d4340a65 (mode 644)
--- /dev/null
+++ cmd/pover/config.go
+package main
+
+import (
+ "os"
+ "bufio"
+ "strings"
+ "fmt"
+)
+
+type config struct {
+ user string
+ token string
+}
+
+func configFromFile(name string) (config, error) {
+ f, err := os.Open(name)
+ if err != nil {
+ f.Close()
+ return config{}, err
+ }
+ defer f.Close()
+ sc := bufio.NewScanner(f)
+ i := 0
+ c := config{}
+ for sc.Scan() {
+ i++
+ s := sc.Text()
+ // skip comments
+ if strings.HasPrefix(s, "#") {
+ continue
+ }
+ slice := strings.Split(s, " ")
+ if len(slice) > 2 {
+ return config{}, fmt.Errorf("%s:%d: too many values", f.Name(), i)
+ }
+ switch slice[0] {
+ case "user":
+ c.user = slice[1]
+ case "token":
+ c.token = slice[1]
+ default:
+ return config{}, fmt.Errorf("%s:%d: unknown key %s", f.Name(), i, slice[0])
+ }
+ }
+ if c.user == "" {
+ return config{}, fmt.Errorf("no user")
+ } else if c.token == "" {
+ return config{}, fmt.Errorf("no token")
+ }
+ return c, nil
+}
blob - /dev/null
blob + 1770264cf6764b635d4331c0e1d77ffb8a894978 (mode 644)
--- /dev/null
+++ cmd/pover/icinga2.conf
+object NotificationCommand "pushover" {
+ command = [ ConfigDir + "/scripts/pushover-icinga2.sh" ]
+ arguments = {
+ "-f" = "$pushover_config$"
+ }
+
+ env = {
+ HOSTADDRESS = "$address$"
+ HOSTDISPLAYNAME = "$host.display_name$"
+ LONGDATETIME = "$icinga.long_date_time$"
+ NOTIFICATIONAUTHORNAME = "$notification.author$"
+ NOTIFICATIONCOMMENT = "$notification.comment$"
+ NOTIFICATIONTYPE = "$notification.type$"
+ SERVICEDESC = "$service.name$"
+ SERVICEDISPLAYNAME = "$service.display_name$"
+ SERVICEOUTPUT = "$service.output$"
+ SERVICESTATE = "$service.state$"
+ }
+}
+
+object User "otl" {
+ display_name = "Oliver Lowe"
+ groups = [ "icingaadmins" ]
+ email = "otl@example.com"
+ vars.pushover_config = "/path/to/credentials/file"
+}
+
+apply Notification "olly-notification" to Service {
+ users = [ "otl" ]
+ command = "pushover"
+ /* Notify for every Service except for rdiff-backup services. */
+ assign where !match("rdiff-backup*", service.name)
+}
blob - /dev/null
blob + ce39ebe5e8a53724ab810944b803b3a5f46100b3 (mode 644)
--- /dev/null
+++ cmd/pover/pover.1
+.TH POVER 1
+.SH NAME
+pover \- push a notification to Pushover
+.SH SYNOPSIS
+.B pover
+[
+.B -d
+]
+[
+.B -f
+.I file
+]
+.SH DESCRIPTION
+.I Pover
+pushes a notification to Pushover using text read from standard input as the message body.
+The
+.B -d
+flag enables debugging output.
+The
+.B -f
+flag sets credentials to be read from
+.IR file .
+.PP
+Credentials must be present in a credentials file.
+A credentials file is a newline-delimited text file.
+Lines beginning with "#" are treated as comments and ignored.
+The recognised keys in the credentials file are:
+.TP
+.B user
+Pushover account User key.
+.TP
+.B token
+API token.
+.SH EXAMPLES
+An example credentials file
+.EX
+ # for pushover application "shell"
+ user abcd12345
+ token zxcvbnm98765
+.EE
+.PP
+Send a message "Hello world",
+reading credentials from a non-default path
+.EX
+ echo "Hello world" | pover -f /tmp/creds
+.EE
+.SH FILES
+.B $HOME/.config/pover
+.TP
+default credentials file
+.SH SOURCE
+.B github.com/ollytom/pover
blob - /dev/null
blob + d040cd863fe646a8b7440a699a252e0ee516143b (mode 644)
--- /dev/null
+++ cmd/pover/pover.go
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "io"
+ "io/ioutil"
+ "path/filepath"
+
+ "git.sr.ht/~otl/pushover"
+)
+
+const usage string = "usage: pover [-d] [-f file]"
+
+var debug *bool
+var configflag *string
+
+func init() {
+ debug = flag.Bool("d", false, "debug")
+ configflag = flag.String("f", "", "path to configuration file")
+ flag.Parse()
+}
+
+func main() {
+ var configpath string
+ configpath = *configflag
+ if *configflag == "" {
+ s, err := os.UserConfigDir()
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ configpath = filepath.Join(s, "pover")
+ }
+ config, err := configFromFile(configpath)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "load configuration: %v\n", err)
+ os.Exit(1)
+ }
+
+ lr := io.LimitReader(os.Stdin, pushover.MaxMsgLength)
+ b, err := ioutil.ReadAll(lr)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ if len(b) == pushover.MaxMsgLength {
+ fmt.Fprintf(os.Stderr, "max message length (%d) reached\n", pushover.MaxMsgLength)
+ }
+ if *debug {
+ fmt.Fprint(os.Stderr, string(b))
+ }
+
+ msg := pushover.Message{
+ User: config.user,
+ Token: config.token,
+ Message: string(b),
+ }
+ if err := pushover.Push(msg); err != nil {
+ fmt.Fprintf(os.Stderr, "push message: %v\n", err)
+ os.Exit(1)
+ }
+}
blob - /dev/null
blob + 1b4a447771810022d7edab07c1354ae8b73534a9 (mode 644)
--- /dev/null
+++ cmd/pover/pover.mdoc
+.Dd $Mdocdate$
+.Dt pover 1
+.Os
+.Sh NAME
+.Nm pover
+.Nd send a notification to Pushover
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl f Ar file
+.Op Fl t Ar title
+.Sh DESCRIPTION
+.Nm
+sends a notification to Pushover using text read from standard input as the message body.
+.Pp
+The options are:
+.Bl -tag -width Ds
+.It Fl d
+Write debugging output to standard error.
+.It Fl f Ar file
+Sets configuration to be read from
+.Ar file .
+.It Fl t Ar title
+Sets the message title to
+.Ar title .
+By default there is no title.
+.El
+.Pp
+Credentials must be present in a configuration file.
+A configuration file is a newline-delimited text file.
+Lines beginning with
+.Dq #
+are treated as comments and ignored.
+Configuration is a series of key-value pairs separated by whitespace,
+one per line.
+The recognised keys in the credentials file are:
+.Bl -tag -width Ds
+.It user
+Pushover account user key.
+.It token
+API token.
+.El
+.Sh EXIT STATUS
+.Ex
+.Sh EXAMPLES
+An example configuration file:
+.Pp
+.Bd -literal -offset indent -compact
+# for pushover application "shell"
+user abcd12345
+token zxcvbnm98765
+.Ed
+.Pp
+Send the current date as a notification:
+.Pp
+.Dl date | pover
+.Pp
+Send a hello world notification, reading configuration from
+.Pa /etc/pover :
+.Pp
+.Dl echo 'hello world' | pover -f /etc/pover
+.Sh FILES
+The default configuration file location is as returned from Go's os.UserConfigDir().
+.Bl -tag -width Ds
+.It Pa $HOME/.config/pover
+On Unix.
+.It Pa $HOME/Library/Application\ Support/pover
+On Darwin.
+.It Pa %AppData%\\\pover
+On Windows.
+.It Pa $home/lib/pover
+On Plan 9.
+.El
+.Sh SEE ALSO
+.Lk "Pushover Message API documentation" https://pushover.net/api
blob - /dev/null
blob + 368000d1915882c1f0102f46d516958cede7dc9f (mode 644)
--- /dev/null
+++ cmd/pover/pushover-icinga2.sh
+#!/bin/sh
+
+while getopt 'f:' flag
+do
+ case $flag in
+ f) config=$OPTARG ;;
+ *) echo "unknown flag" $flag ;;
+ esac
+done
+
+pover -f $config <<EOF
+$NOTIFICATIONTYPE
+
+$SERVICEDISPLAYNAME - $SERVICEDESC - on $HOSTDISPLAYNAME ($HOSTADDRESS) is $SERVICESTATE
+Time: $LONGDATETIME
+Output: $SERVICEOUTPUT
+Comments: [$NOTIFICATIONAUTHORNAME] $NOTIFICATIONCOMMENT
+EOF
blob - /dev/null
blob + c28e45c3d2e5e452bf9cb8e644b8e79a79eaba94 (mode 755)
--- /dev/null
+++ cmd/pover/test.sh
+#!/bin/sh
+
+if date -n | cmd/pover/pover
+then
+ echo 'date sent ok'
+else
+ echo 'date failed'
+fi
+
+if /bin/dd if=/dev/urandom of=/dev/stdout count=1024 | cmd/pover/pover
+then
+ echo 'max length message ok'
+else
+ echo 'max length message failed'
+fi
+
+# we expect pover to print a warning to standard error but send a truncated message anyway.
+if /bin/dd if=/dev/urandom of=/dev/stdout count=2048 | cmd/pover/pover
+then
+ echo 'too long message ok'
+else
+ echo 'too long message failed'
+fi
+
+if ! echo | cmd/pover/pover
+then
+ echo 'blank message ok'
+else
+ echo 'blank message failed'
+fi
+
+if ! cmd/pover/pover -f /dev/null
+then
+ echo "empty config ok"
+else
+ echo "empty config failed"
+fi
+
+badconfig=`mktemp`
+echo 'badconfig' > $badconfig
+if ! cmd/pover/pover -f $badconfig
+then
+ echo "bad config ok"
+else
+ echo "bad config failed"
+fi
+rm $badconfig
blob - /dev/null
blob + 42431be192c97fc0ec146dd88f0e9d6dd6875add (mode 644)
--- /dev/null
+++ go.mod
+module git.sr.ht/~otl/pushover
+
+go 1.16
blob - /dev/null
blob + 41119f9322acb87af84f93940a73f20f78e98dc2 (mode 644)
--- /dev/null
+++ pushover.go
+package pushover
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strings"
+)
+
+const apiurl = "https://api.pushover.net/1/messages.json"
+const MaxMsgLength = 1024
+const MaxTitleLength = 250
+
+// Message represents a message in the Pushover Message API.
+type Message struct {
+ User string
+ Token string
+ Title string
+ Message string
+ Priority int
+}
+
+type response struct {
+ Status int
+ Request string
+ Errors errors
+}
+
+type errors []string
+
+func (e errors) Error() string {
+ return strings.Join(e, ", ")
+}
+
+func (m *Message) validate() error {
+ nchar := strings.Count(m.Message, "")
+ if nchar > MaxMsgLength {
+ return fmt.Errorf("%d character message too long, allowed %d characters", nchar, MaxMsgLength)
+ }
+ nchar = strings.Count(m.Title, "")
+ if nchar > MaxTitleLength {
+ return fmt.Errorf("%d-character title too long, allowed %d characters", nchar, MaxTitleLength)
+ }
+ return nil
+}
+
+// Push sends the Message m to Pushover.
+func Push(m Message) error {
+ if err := m.validate(); err != nil {
+ return err
+ }
+ req := url.Values{}
+ req.Add("token", m.Token)
+ req.Add("user", m.User)
+ req.Add("title", m.Title)
+ req.Add("message", m.Message)
+ resp, err := http.PostForm(apiurl, req)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode == http.StatusOK {
+ return nil
+ }
+
+ var presp response
+ if err := json.NewDecoder(resp.Body).Decode(&presp); err != nil {
+ return fmt.Errorf("decode error response: %v", err)
+ }
+ return presp.Errors
+}