From 09337557f098fcd10cab57aa8e0fd1f413eea741 Mon Sep 17 00:00:00 2001 From: Mohammad Parvin Date: Sun, 2 Feb 2025 14:08:00 +0330 Subject: [PATCH 1/9] add notification todo --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 38870f3..21abb18 100644 --- a/README.md +++ b/README.md @@ -36,4 +36,5 @@ 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 \ No newline at end of file +- [ ] Add bash completion +- [ ] Add notification on Success/Failure \ No newline at end of file From 2dceafb25ee42bf28b62fbb31d2c309e7f2c5c92 Mon Sep 17 00:00:00 2001 From: Mohammad Parvin Date: Sun, 2 Feb 2025 14:12:40 +0330 Subject: [PATCH 2/9] add notification example --- examples/README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index deb5052..1622e63 100644 --- a/examples/README.md +++ b/examples/README.md @@ -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. @@ -25,3 +25,17 @@ 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 --telegram-chat-id ./my-backup-script.sh +``` From 39ec581e5f152ad85b6865d2ab6a74f0626823d1 Mon Sep 17 00:00:00 2001 From: Mohammad Parvin Date: Sun, 2 Feb 2025 17:43:11 +0330 Subject: [PATCH 3/9] notification tasks (desktop and telegram) are done --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 21abb18..daebfbd 100644 --- a/README.md +++ b/README.md @@ -37,4 +37,8 @@ All examples are in [examples](examples) directory. - [ ] Add `-l` flag to list all running jobs - [ ] Add more examples - [ ] Add bash completion -- [ ] Add notification on Success/Failure \ No newline at end of file +- [X] Add notification on Success/Failure +- [X] Add notification Desktop method +- [X] Add notification Telegram method +- [ ] Add notification Slack method +- [ ] Add notification Email method From 450d6dcbee8ef28b428d1b15f950773dec50a82c Mon Sep 17 00:00:00 2001 From: Mohammad Parvin Date: Sun, 2 Feb 2025 17:44:28 +0330 Subject: [PATCH 4/9] add notify-on, notify-method and telegram credentials --- cmd/root.go | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 207ec3a..120ab96 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -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 @@ -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 { @@ -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(¬ifyOn, "notify-on", "", "Notify on: failure, success, always") + rootCmd.Flags().StringVar(¬ifyMethod, "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" { @@ -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") @@ -87,7 +95,15 @@ func init() { tools.Ps() return } - tools.RunInfinitely(delayInt, args, verbose) + tools.RunInfinitely( + delayInt, + args, + verbose, + notifyOn, + notifyMethod, + telegramToken, + telegramChatID, + telegramCustomAPI, + ) } - } From afc415942f762fe0672f9052cb9da60cb19aefb1 Mon Sep 17 00:00:00 2001 From: Mohammad Parvin Date: Sun, 2 Feb 2025 17:45:18 +0330 Subject: [PATCH 5/9] add notifcation examples --- examples/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/examples/README.md b/examples/README.md index 1622e63..390daf3 100644 --- a/examples/README.md +++ b/examples/README.md @@ -39,3 +39,17 @@ Send a telegram message on command failure. ```bash run4ever -d 60 --notify-on failure --notify-method telegram --telegram-token --telegram-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 --slack-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 --email-from --email-password --email-smtp --email-port ./my-backup-script.sh +``` From 656a6a5e193c18355f18b45f893fd41e3842ff18 Mon Sep 17 00:00:00 2001 From: Mohammad Parvin Date: Sun, 2 Feb 2025 17:45:47 +0330 Subject: [PATCH 6/9] add beep for desktop notification --- go.mod | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 57b8f42..ba1fbbe 100644 --- a/go.mod +++ b/go.mod @@ -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 ) From 60570a3ac1a6e8f15b611ff818729070b5b66eb1 Mon Sep 17 00:00:00 2001 From: Mohammad Parvin Date: Sun, 2 Feb 2025 17:46:50 +0330 Subject: [PATCH 7/9] add beep for desktop notification --- go.sum | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/go.sum b/go.sum index 442875a..d2d9650 100644 --- a/go.sum +++ b/go.sum @@ -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= From 3c7d0a3b2fcddcde7aad9d1f05ed941d6f9f6acf Mon Sep 17 00:00:00 2001 From: Mohammad Parvin Date: Sun, 2 Feb 2025 17:50:16 +0330 Subject: [PATCH 8/9] add should notify function --- tools/runner.go | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/tools/runner.go b/tools/runner.go index bb7cb1b..75c6db1 100644 --- a/tools/runner.go +++ b/tools/runner.go @@ -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" +} \ No newline at end of file From 7f32ebadc53ccc10efedf7fc6994186ac63e210d Mon Sep 17 00:00:00 2001 From: Mohammad Parvin Date: Sun, 2 Feb 2025 17:51:40 +0330 Subject: [PATCH 9/9] add notify package with telegram and desktop support --- tools/notify.go | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tools/notify.go diff --git a/tools/notify.go b/tools/notify.go new file mode 100644 index 0000000..405a6ed --- /dev/null +++ b/tools/notify.go @@ -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 +} \ No newline at end of file