Skip to content

Commit

Permalink
Merge pull request #1 from MParvin/add-notification
Browse files Browse the repository at this point in the history
Add notification
  • Loading branch information
MParvin authored Feb 2, 2025
2 parents 67272b6 + 7f32eba commit 1d6bb04
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 17 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,9 @@ All examples are in [examples](examples) directory.
- [ ] Add `-g` flag to run command in background
- [ ] Add `-l` flag to list all running jobs
- [ ] Add more examples
- [ ] Add bash completion
- [ ] Add bash completion
- [X] Add notification on Success/Failure
- [X] Add notification Desktop method
- [X] Add notification Telegram method
- [ ] Add notification Slack method
- [ ] Add notification Email method
34 changes: 25 additions & 9 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ import (
"github.com/spf13/cobra"
)

var (
notifyOn string
notifyMethod string
telegramToken string
telegramChatID string
telegramCustomAPI string
)

var delay string

// rootCmd represents the base command when called without any subcommands
Expand Down Expand Up @@ -40,12 +48,9 @@ You can also enable verbose mode by using the -v flag. This will cause run4ever
return nil
},
DisableFlagParsing: false,
// BashCompletionFunction: bashCompletionFunc,
Run: func(cmd *cobra.Command, args []string) {},
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
Expand All @@ -58,6 +63,12 @@ func init() {
rootCmd.Flags().BoolP("verbose", "v", false, "Verbose mode")
rootCmd.Flags().SetInterspersed(false)
rootCmd.Flags().BoolP("ps", "", false, "Running PIDs")
rootCmd.Flags().StringVar(&notifyOn, "notify-on", "", "Notify on: failure, success, always")
rootCmd.Flags().StringVar(&notifyMethod, "notify-method", "desktop", "Notification method: desktop, telegram")
rootCmd.Flags().StringVar(&telegramToken, "telegram-token", "", "Telegram bot token (required for Telegram notifications)")
rootCmd.Flags().StringVar(&telegramChatID, "telegram-chat-id", "", "Telegram chat ID (required for Telegram notifications)")
rootCmd.Flags().StringVar(&telegramCustomAPI, "telegram-custom-api", "", "Telegram custom API URL (optional)")


rootCmd.PreRun = func(cmd *cobra.Command, args []string) {
if len(args) == 0 && rootCmd.Flags().Lookup("ps").Value.String() == "false" {
Expand All @@ -72,10 +83,7 @@ func init() {
}

verbose := false
verbose, err = cmd.Flags().GetBool("verbose")
if err != nil {
log.Fatal("Error getting verbose flag")
}
verbose, _ = cmd.Flags().GetBool("verbose")

if verbose {
fmt.Println("run4ever called")
Expand All @@ -87,7 +95,15 @@ func init() {
tools.Ps()
return
}
tools.RunInfinitely(delayInt, args, verbose)
tools.RunInfinitely(
delayInt,
args,
verbose,
notifyOn,
notifyMethod,
telegramToken,
telegramChatID,
telegramCustomAPI,
)
}

}
30 changes: 29 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Setup a SOCKS proxy on port 1080, and If the command fails, it will be restarte

Run the script `my-backup-script.sh` every hour.
```bash
./run4ever -d 3600 my-backup-script.sh
./run4ever -d 3600 ./my-backup-script.sh
```

Monitor the status of a service every minute.
Expand All @@ -25,3 +25,31 @@ Run `rsync` command every 5 minutes.
```bash
run4ever -d 300 rsync -avz --delete /home/user/ /mnt/backup
```

## Notifications

### Desktop Notification
Show desktop notification on command failure.
```bash
run4ever -d 30 --notify-on failure --notify-method desktop ./my-backup-script.sh
```

### Telegram Notification
Send a telegram message on command failure.
```bash
run4ever -d 60 --notify-on failure --notify-method telegram --telegram-token <token> --telegram-chat-id <chat-id> ./my-backup-script.sh
```

TODO:
### Slack Notification
Send a slack message on command success.
```bash
run4ever -d 60 --notify-on success --notify-method slack --slack-token <token> --slack-channel <channel> ./my-backup-script.sh
```

TODO:
### Email Notification
Send an email on command failure.
```bash
run4ever -d 60 --notify-on failure --notify-method email --email-to <to> --email-from <from> --email-password <password> --email-smtp <smtp> --email-port <port> ./my-backup-script.sh
```
10 changes: 9 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@ module github.com/mparvin/run4ever

go 1.19

require github.com/spf13/cobra v1.6.1
require (
github.com/gen2brain/beeep v0.0.0-20240516210008-9c006672e7f4
github.com/spf13/cobra v1.6.1
)

require (
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
golang.org/x/sys v0.6.0 // indirect
)
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/gen2brain/beeep v0.0.0-20240516210008-9c006672e7f4 h1:ygs9POGDQpQGLJPlq4+0LBUmMBNox1N4JSpw+OETcvI=
github.com/gen2brain/beeep v0.0.0-20240516210008-9c006672e7f4/go.mod h1:0W7dI87PvXJ1Sjs0QPvWXKcQmNERY77e8l7GFhZB/s4=
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE=
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk=
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
62 changes: 62 additions & 0 deletions tools/notify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package tools

import (
"fmt"
"github.com/gen2brain/beeep"
"net/http"
"net/url"
"strings"
)

func SendDesktopNotification(title, message string, verbose bool) error {
if verbose {
fmt.Println("Sending desktop notification")
fmt.Println("Title: ", title)
fmt.Println("Message: ", message)
}
return beeep.Notify(title, message, "")
}

func SendTelegramNotification(token, chatID, message string, telegramCustomAPI string, verbose bool) error {
baseURL := "https://api.telegram.org"
if telegramCustomAPI != "" {
customAPI := telegramCustomAPI
if !strings.HasPrefix(customAPI, "http://") &&
!strings.HasPrefix(customAPI, "https://") {
customAPI = "https://" + customAPI
}
baseURL = customAPI
}

if verbose {
fmt.Println("Sending Telegram notification")
maskedToken := fmt.Sprintf("********%s", token[3:])
fmt.Println("Token: ", maskedToken)
fmt.Println("Chat ID: ", chatID)
fmt.Println("Message: ", message)
fmt.Println("Using API URL: ", baseURL)
}

apiURL := fmt.Sprintf("%s/bot%s/sendMessage", baseURL, token)
params := url.Values{}
params.Add("chat_id", chatID)
params.Add("text", message)

if verbose {
fmt.Println("Sending request to: ", apiURL)
}
resp, err := http.PostForm(apiURL, params)
if err != nil {
return err
}
defer resp.Body.Close()

if verbose {
fmt.Println("Telegram response: ", resp.Status)
}

if resp.StatusCode != 200 {
return fmt.Errorf("Telegram response: %s", resp.Status)
}
return nil
}
47 changes: 42 additions & 5 deletions tools/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,62 @@ import (
"time"
)

func RunInfinitely(delayInt int, args []string, verbose bool) {
func RunInfinitely(delayInt int, args []string, verbose bool , notifyOn string, notifyMethod string, telegramToken string, telegramChatID string, telegramCustomAPI string) {
for {
exitStatus := 0

cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
err := cmd.Run()

if err != nil {
if verbose {
fmt.Println(err)
}
time.Sleep(time.Duration(delayInt) * time.Second)
continue
exitStatus = cmd.ProcessState.ExitCode()
}

if ShouldNotify(notifyOn, exitStatus) {
title := "run4ever: Task " + StatusToString(exitStatus)
message := fmt.Sprintf("Command %s exited with status %d", args[0], exitStatus)

switch notifyMethod {
case "desktop":
if err := SendDesktopNotification(title, message, verbose); err != nil && verbose {
fmt.Println("Error sending desktop notification: ", err)
}
case "telegram":
if err := SendTelegramNotification(telegramToken, telegramChatID, message, telegramCustomAPI ,verbose); err != nil && verbose {
fmt.Println("Error sending Telegram notification: ", err)
}
}
}
if verbose {
fmt.Printf("Command %s exited", args[0])
fmt.Print("Sleeping for ", delayInt, " seconds")
fmt.Printf("Command `%s` exited with status %d\n", args[0], exitStatus)
fmt.Printf("Sleeping for %d seconds\n", delayInt)
}
time.Sleep(time.Duration(delayInt) * time.Second)
}
}

func ShouldNotify(notifyOn string, exitStatus int) bool {
switch notifyOn {
case "always":
return true
case "success":
return exitStatus == 0
case "failure":
return exitStatus != 0
default:
return false
}
}

func StatusToString(exitStatus int) string {
if exitStatus == 0 {
return "Success"
}
return "Failure"
}

0 comments on commit 1d6bb04

Please sign in to comment.