├── .gitignore ├── LICENSE ├── README.md ├── arp-scan ├── README.md ├── actions_arp-scan.conf ├── arp-scan.sh └── install.sh ├── dotenvexample ├── folder_notify.sh ├── fortinet_firmware.sh ├── fortinet_fortiguard.sh ├── gitea_actions.yml ├── jellyfin_webhook_ntfy.handlebars ├── lidarr.sh ├── ntfy-fail2ban ├── README.md ├── install.sh └── ntfy-fail2ban.sh ├── ntfy.sh ├── opnsense.sh ├── paperless.sh ├── prowlarr.sh ├── qbittorrent.txt ├── radarr.sh ├── read_notify.sh ├── readarr.sh ├── sabnzbd.sh ├── sonarr.sh └── temperature.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, nickexyz 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ntfy-shellscripts 2 | 3 | A few scripts for the amazing ntfy project: (https://github.com/binwiederhier/ntfy) 4 | They are quick and dirty, but works for my use case. 5 | 6 | # Requirements 7 | - [ntfy.sh](https://ntfy.sh) 8 | - `curl` available on script host 9 | - For Sonarr and Radarr: 10 | - `jq` installed and available on your sonarr/radarr hosts. 11 | 12 | # Usage 13 | 1. Rename `dotenvexample` to `.env`: `mv dotenvexample .env` 14 | 2. Populate the variables for the scripts you would like to use. 15 | 3. If you are cherry picking particular scripts to move to a host, ensure the `.env` exists in the same directory as the script 16 | 17 | It is also possible to specify the path to the env file with the variable `NTFY_ENV` 18 | For example: `NTFY_ENV="/tmp/myenvfile" /opt/ntfy.sh` 19 | 20 | # Other 21 | ntfy.sh is a generic ntfy script. 22 | read_notify.sh and folder_notify.sh are two quick scripts that sends notifications when new files are added. 23 | There are a few other goodies, like qbittorrent.txt that gives an example for how to get ntfy running on QBT. 24 | gitea_actions.yml is an example for just [that](https://docs.gitea.com/usage/actions/overview). 25 | jellyfin_webhook_ntfy.handlebars is a pretty complete template for Jellyfin notifications. 26 | 27 | Feel free to use all the scripts as you see fit, and let me know if something isn't working, or if you want to improve something. 28 | -------------------------------------------------------------------------------- /arp-scan/README.md: -------------------------------------------------------------------------------- 1 | This is a new host notification script for OPNsense. 2 | See arp-scan.sh and install.sh for more information. 3 | 4 | If you want to run it on something other than OPNsense, that would probably work. 5 | -------------------------------------------------------------------------------- /arp-scan/actions_arp-scan.conf: -------------------------------------------------------------------------------- 1 | [run] 2 | command:/usr/local/opnsense/scripts/arp-scan/arp-scan.sh 3 | parameters:%s 4 | type:script 5 | message:Running arp-scan 6 | description:arp-scan - Notifications for new IP/MAC pairs 7 | -------------------------------------------------------------------------------- /arp-scan/arp-scan.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # If updating this script from an earlier version, you might need to remove the existing SQLite db. 4 | 5 | # This is a script that scans for new IP/MAC pairs from an OPNsense machine. 6 | # If found, it sends a notification through NTFY or Pushover and logs the new IP/MAC pair in OPNsense. 7 | # 8 | # It will also include the manufacturer of the MAC address, taken from this list: https://standards-oui.ieee.org/oui/oui.csv 9 | # When you run install.sh the csv will be downloaded and imported to the SQLite db. 10 | # If you want to update the OUI DB manually you can do so like this: 11 | # ./arp-scan.sh import-oui 12 | # 13 | # If you choose to use NTFY, there will be an action button to search the MAC prefix on dnschecker.org. 14 | 15 | # The idea is based on this script: https://gist.github.com/mimugmail/6cee79cdf97d49b1d6fc130e79dc3fa9 16 | 17 | # When a new pair is found, the script also checks if the "new" IP or MAC has been seen before. 18 | # In that case, the notification will include those IPs/MACs as well. 19 | 20 | # Run install.sh to install, or do it manually. 21 | # You will need to configure a Cron-job from the OPNsense webui after installing. (https://youropnsense/ui/cron) 22 | 23 | # Keep in mind that the sqlite db might grow large. 24 | # To avoid this you can purge old entries. This is optional. 25 | # When adding the cronjob in OPNsense, set "Parameters" to the number of days you want to keep old entries. 26 | # E.g. "30" for 30 days. 27 | # If you want to run the script standalone, you can set the number of days like this: 28 | # ./arp-scan.sh 30 29 | # 30 | # The cleanup will run every 10th time the script is run, and only entries that hasn't been seen in X days will be deleted. 31 | 32 | # These variables are needed in .env 33 | # If NTFY: 34 | # ntfy_username and ntfy_password or ntfy_token 35 | # opnsense_ntfy_topic 36 | # opnsense_ntfy_icon 37 | # ntfy_url 38 | # 39 | # If Pushover: 40 | # pushover_app_token 41 | # pushover_user_token 42 | 43 | # load env file from $NTFY_ENV or script dir 44 | SCRIPTPATH=${NTFY_ENV:-$(dirname "$0")/.env} 45 | [ -f ${SCRIPTPATH} ] && . "${SCRIPTPATH}" || echo "ENV missing: ${SCRIPTPATH}" 46 | 47 | oui_url="https://standards-oui.ieee.org/oui/oui.csv" 48 | our_path="/usr/local/opnsense/scripts/arp-scan/data" 49 | db_file="$our_path/arp-scan.db" 50 | 51 | lockfile="/tmp/arp-scan.lock" 52 | cleanup() { 53 | rm -f "$lockfile" 54 | } 55 | trap cleanup EXIT INT TERM 56 | 57 | if [ -e "$lockfile" ]; then 58 | echo "Another instance of the script is already running." 59 | exit 1 60 | fi 61 | 62 | touch "$lockfile" 63 | 64 | send_notification() { 65 | if [ -n "$pushover_app_token" ]; then 66 | curl -s -F "token=$pushover_app_token" -F "user=$pushover_user_token" -F "message=New IPv4/MAC pair seen: 67 | $1" https://api.pushover.net/1/messages 68 | else 69 | if [ -n "$ntfy_password" ] && [ -n "$ntfy_token" ]; then 70 | echo "Use ntfy_username and ntfy_password OR ntfy_token" 71 | exit 1 72 | elif [ -n "$ntfy_password" ]; then 73 | ntfy_auth="-u $ntfy_username:$ntfy_password" 74 | ntfy_header="" 75 | elif [ -n "$ntfy_token" ]; then 76 | ntfy_auth="" 77 | ntfy_header="Authorization: Bearer $ntfy_token" 78 | else 79 | ntfy_auth="" 80 | ntfy_header="" 81 | fi 82 | mac_prefix=$(echo $mac | tr -d ':.-' | cut -c -6 | tr '[:lower:]' '[:upper:]') 83 | curl -s $ntfy_auth \ 84 | -H "$ntfy_header" \ 85 | -H "tags:computer" \ 86 | -H "X-Title: OPNsense" \ 87 | -H "X-Icon: $opnsense_ntfy_icon" \ 88 | -H "Actions: view, Lookup MAC prefix, https://dnschecker.org/mac-lookup.php?query=$mac_prefix, clear=true;" \ 89 | -d "New IPv4/MAC pair seen: 90 | $1" --request POST "$ntfy_url/$opnsense_ntfy_topic" 91 | fi 92 | } 93 | 94 | # Set cleanup days if specified 95 | if [ -n "$1" ]; then 96 | cleanup_days="$1" 97 | fi 98 | 99 | # Create the SQLite database and tables if they do not exist 100 | sqlite3 "$db_file" <&2 142 | exit 1 143 | fi 144 | if [ ! -f /tmp/arp-scan-oui.csv ]; then 145 | echo "The file /tmp/arp-scan-oui.csv does not exist. Exiting..." 146 | exit 1 147 | fi 148 | 149 | sqlite3 "$db_file" < /dev/null ; 62 | then 63 | echo "You need sqlite installed for this script." 64 | exit 1 65 | fi 66 | 67 | check_lock() { 68 | if [ -f /tmp/folder_notify.lock ]; 69 | then 70 | echo "folder_notify.sh is already running, exiting..." 71 | exit 0 72 | fi 73 | 74 | trap "rm -f /tmp/folder_notify.lock ; rm -f /tmp/folder_notify_added.tmp ; rm -f /tmp/folder_notify_deleted.tmp ; exit" INT TERM EXIT 75 | touch /tmp/folder_notify.lock 76 | } 77 | 78 | check_folders() { 79 | 80 | sqlite3 "$dbpath" "CREATE TABLE IF NOT EXISTS folders (foldername CHAR NOT NULL PRIMARY KEY, files INT );" 81 | for dir in "${folder_path[@]}"; do 82 | if [ -d "$dir" ]; then 83 | foldername=$( echo $dir | tr / _ | sed 's/[^[:alnum:]_]//g' ) 84 | oldnr=$( sqlite3 "$dbpath" "SELECT files FROM folders WHERE foldername=\"$foldername\";" 2>/dev/null ) 85 | IFS=$'\n' 86 | newnr=$( find $dir -mindepth 1 -maxdepth "$folder_depth" -type f -printf '%P\n' | wc -l ) 87 | if [ -z "$oldnr" ]; then 88 | oldnr="0" 89 | fi 90 | if [ -z "$newnr" ]; then 91 | newnr="0" 92 | fi 93 | if [ "$newnr" -gt "$oldnr" ]; then 94 | chap=$( expr $newnr - $oldnr ) 95 | echo "$chap $push_type $push_added $dir" >> /tmp/folder_notify_added.tmp 96 | elif [ "$newnr" -lt "$oldnr" ]; then 97 | chap=$( expr $oldnr - $newnr ) 98 | echo "$chap $push_type $push_deleted $dir" >> /tmp/folder_notify_deleted.tmp 99 | fi 100 | unset IFS 101 | 102 | sqlite3 "$dbpath" "INSERT OR IGNORE INTO folders (foldername,files) VALUES (\"$foldername\",\"$newnr\"); UPDATE folders SET files = $newnr WHERE foldername=\"$foldername\"" 103 | fi 104 | done 105 | } 106 | 107 | check_push() { 108 | if [ -f "$push_file" ]; then 109 | if [ -n "$ntfy_url" ]; then 110 | if [[ -n $ntfy_password && -n $ntfy_token ]]; then 111 | echo "Use ntfy_username and ntfy_password OR ntfy_token" 112 | exit 1 113 | elif [ -n "$ntfy_password" ]; then 114 | ntfy_base64=$( echo -n "$ntfy_username:$ntfy_password" | base64 ) 115 | ntfy_auth="Authorization: Basic $ntfy_base64" 116 | elif [ -n "$ntfy_token" ]; then 117 | ntfy_auth="Authorization: Bearer $ntfy_token" 118 | else 119 | ntfy_auth="" 120 | fi 121 | curl -H "$ntfy_auth" -H tags:"$ntfy_tag" -H "X-Icon: $ntfy_icon" -H "X-Title: $ntfy_title" -d "$push_message" $ntfy_url > /dev/null 122 | fi 123 | if [ -n "$pushover_app_token" ]; then 124 | curl -s -F "token=$pushover_app_token" -F "user=$pushover_user_token" -F "message=$push_message" https://api.pushover.net/1/messages 125 | fi 126 | fi 127 | } 128 | 129 | check_push_added() { 130 | push_file="/tmp/folder_notify_added.tmp" 131 | push_message=$( cat /tmp/folder_notify_added.tmp 2>/dev/null ) 132 | ntfy_tag="$ntfy_added_tag" 133 | check_push 134 | } 135 | 136 | check_push_deleted() { 137 | push_file="/tmp/folder_notify_deleted.tmp" 138 | push_message=$( cat /tmp/folder_notify_deleted.tmp 2>/dev/null ) 139 | ntfy_tag="$ntfy_deleted_tag" 140 | check_push 141 | } 142 | 143 | cleanup() { 144 | fo_path=() 145 | for folder in "${folder_path[@]}"; do 146 | if [ -d "$folder" ]; then 147 | fo_path+=("$(echo $folder | tr / _ | sed 's/[^[:alnum:]_]//g')") 148 | fi 149 | done 150 | for name in $(sqlite3 "$dbpath" "SELECT foldername FROM folders"); do 151 | if [[ ! "${fo_path[*]}" =~ ${name} ]]; then 152 | sqlite3 "$dbpath" "DELETE FROM folders WHERE foldername = \"$name\"" 153 | fi 154 | done 155 | } 156 | 157 | 158 | # Create/vacuum DB 159 | sqlite3 "$dbpath" "VACUUM;" 160 | 161 | check_lock 162 | check_folders 163 | cleanup 164 | check_push_added 165 | check_push_deleted 166 | rm -f /tmp/folder_notify_added.tmp 2>/dev/null 167 | rm -f /tmp/folder_notify_deleted.tmp 2>/dev/null 168 | 169 | rm -f /tmp/folder_notify.lock 170 | 171 | exit 0 172 | -------------------------------------------------------------------------------- /fortinet_firmware.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This is a script that downloads the Fortinet firmware RSS and sends NTFY notifications based on keywords. 4 | 5 | rss_feed_url="https://support.fortinet.com/rss/firmware.xml" 6 | 7 | # load env file from $NTFY_ENV or 'env' in script dir 8 | SCRIPTPATH=${NTFY_ENV:-$(dirname "$0")/.env} 9 | [ -f ${SCRIPTPATH} ] && . "${SCRIPTPATH}" || echo "ENV missing: ${SCRIPTPATH}" 10 | 11 | if [[ -n $ntfy_password && -n $ntfy_token ]]; then 12 | echo "Use ntfy_username and ntfy_password OR ntfy_token" 13 | exit 1 14 | elif [ -n "$ntfy_password" ]; then 15 | ntfy_base64=$( echo -n "$ntfy_username:$ntfy_password" | base64 ) 16 | ntfy_auth="Authorization: Basic $ntfy_base64" 17 | elif [ -n "$ntfy_token" ]; then 18 | ntfy_auth="Authorization: Bearer $ntfy_token" 19 | else 20 | ntfy_auth="" 21 | fi 22 | 23 | send_ntfy() { 24 | curl \ 25 | -H "$ntfy_auth" \ 26 | -H "tags:arrow_up" \ 27 | -H "X-Title: Fortinet" \ 28 | -H "icon:https://www.fortinet.com/content/dam/fortinet/images/icons/fortinet-social-icon.jpg" \ 29 | -d "New version: $keyword $version" \ 30 | --request POST "$ntfy_url/$fortinet_firmware_ntfy_topic" > /dev/null 31 | } 32 | 33 | wget -qO- "$rss_feed_url" > /tmp/fortinet_firmware_rss_feed.xml 34 | 35 | for keyword in "${fortinet_firmware_keywords[@]}"; do 36 | seen_versions_file="fortinet_firmware_seen_versions_${keyword}.txt" 37 | touch "$fortinet_firmware_old_posts_path"/"$seen_versions_file" 38 | 39 | # Check new versions 40 | grep -oP "${keyword} \K\d+\.\d+\.\d+" /tmp/fortinet_firmware_rss_feed.xml | while read -r version; do 41 | if ! grep -qxF "$version" "$fortinet_firmware_old_posts_path"/"$seen_versions_file"; then 42 | send_ntfy 43 | echo "$version" >> "$fortinet_firmware_old_posts_path"/"$seen_versions_file" 44 | fi 45 | done 46 | done 47 | 48 | rm /tmp/fortinet_firmware_rss_feed.xml 49 | -------------------------------------------------------------------------------- /fortinet_fortiguard.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This is a script that downloads the FortiGuard Labs RSS and sends NTFY notifications. 4 | 5 | rss_feed_url="https://filestore.fortinet.com/fortiguard/rss/ir.xml" 6 | 7 | # load env file from $NTFY_ENV or 'env' in script dir 8 | SCRIPTPATH=${NTFY_ENV:-$(dirname "$0")/.env} 9 | [ -f ${SCRIPTPATH} ] && . "${SCRIPTPATH}" || echo "ENV missing: ${SCRIPTPATH}" 10 | 11 | if [[ -n $ntfy_password && -n $ntfy_token ]]; then 12 | echo "Use ntfy_username and ntfy_password OR ntfy_token" 13 | exit 1 14 | elif [ -n "$ntfy_password" ]; then 15 | ntfy_base64=$( echo -n "$ntfy_username:$ntfy_password" | base64 ) 16 | ntfy_auth="Authorization: Basic $ntfy_base64" 17 | elif [ -n "$ntfy_token" ]; then 18 | ntfy_auth="Authorization: Bearer $ntfy_token" 19 | else 20 | ntfy_auth="" 21 | fi 22 | 23 | send_ntfy() { 24 | curl \ 25 | -H "$ntfy_auth" \ 26 | -H "tags:warning" \ 27 | -H "X-Title: $title" \ 28 | -H "prio:default" \ 29 | -H "icon:https://www.fortinet.com/content/dam/fortinet/images/icons/fortinet-social-icon.jpg" \ 30 | -d "$description" \ 31 | -H "Actions: view, Open link, $link, clear=true" \ 32 | --request POST "$ntfy_url/$fortinet_fortiguard_ntfy_topic" > /dev/null 33 | } 34 | 35 | curl -s "$rss_feed_url" > /tmp/fortinet_fortiguard_feed.xml 36 | 37 | if [ ! -f "$fortinet_fortiguard_old_posts_path"/fortinet_fortiguard_old_posts.txt ]; then 38 | touch "$fortinet_fortiguard_old_posts_path"/fortinet_fortiguard_old_posts.txt 39 | fi 40 | 41 | # Extract items, then loop 42 | grep -oP '.*?' /tmp/fortinet_fortiguard_feed.xml | while IFS= read -r ITEM; do 43 | title=$(echo "$ITEM" | grep -oP '.*?' | sed 's/\(.*\)<\/title>/\1/' | sed 's/&/\&/g') 44 | description=$(echo "$ITEM" | grep -oP '<description>.*?</description>' | sed 's/<description>\(.*\)<\/description>/\1/' | sed 's/&/\&/g') 45 | link=$(echo "$ITEM" | grep -oP '<link>.*?</link>' | sed 's/<link>\(.*\)<\/link>/\1/') 46 | 47 | if grep -Fxq "$title" "$fortinet_fortiguard_old_posts_path"/fortinet_fortiguard_old_posts.txt; then 48 | # Post is old, skip it 49 | continue 50 | else 51 | echo "$title" >> "$fortinet_fortiguard_old_posts_path"/fortinet_fortiguard_old_posts.txt 52 | send_ntfy 53 | fi 54 | done 55 | 56 | rm /tmp/fortinet_fortiguard_feed.xml 57 | -------------------------------------------------------------------------------- /gitea_actions.yml: -------------------------------------------------------------------------------- 1 | # This is a simple Gitea Actions job that sends ntfy notifications at the start, and then if the job is a success or a failure. 2 | # It will only run when a tag is created with -example appended. I usually have "version_number-example". 3 | 4 | # The notification will look something like this: 5 | # Gitea Actions 6 | # Repo: nickexyz/my_repo 7 | # Branch: refs/tags/0.1-example 8 | # Arch: X64 9 | # Build 0.1-example by niclas 10 | # STARTED 11 | # Buttons: Open run, Open repo 12 | 13 | # You will probably need to change: 14 | # runs-on: ci-checker 15 | # As long as the tools you need and curl are available, any container image should work. 16 | 17 | # You also need to add some secrets: 18 | # NTFY_URL: The URL to your ntfy instance, including the topic. (https://ntfy.sh/my_topic) 19 | # NTFY_USR: Your ntfy user 20 | # NTFY_PWD: Your ntfy password. 21 | 22 | name: Example job with ntfy notifications 23 | on: 24 | push: 25 | tags: 26 | - '*-example' 27 | 28 | jobs: 29 | deploy-example: 30 | runs-on: ci-checker 31 | steps: 32 | - name: Job Started 33 | run: | 34 | echo -e "Repo: ${{ gitea.repository }} \ 35 | \nBranch: ${{ gitea.ref }} \ 36 | \nArch: ${{ runner.arch }} \ 37 | \nBuild ${{ gitea.ref_name }} by ${{ gitea.actor }} \ 38 | \nSTARTED" | \ 39 | curl -s \ 40 | -u "${{ secrets.NTFY_USR }}":"${{ secrets.NTFY_PWD }}" \ 41 | -H "tags:timer_clock" \ 42 | -H "X-Icon: https://raw.githubusercontent.com/go-gitea/gitea/main/\ 43 | public/assets/img/favicon.png" \ 44 | -H "Actions: view, Open run, \ 45 | ${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/\ 46 | ${{ gitea.run_number }}, clear=true; view, Open repo, \ 47 | ${{ gitea.server_url }}/${{ gitea.repository }}, clear=true;" \ 48 | -H "X-Title: Gitea Actions" \ 49 | -T- "${{ secrets.NTFY_URL }}" 50 | 51 | - name: Doing stuff 52 | run: | 53 | echo "Do something here..." 54 | 55 | - name: Job Succeeded 56 | if: success() 57 | run: | 58 | echo -e "Repo: ${{ gitea.repository }} \ 59 | \nBranch: ${{ gitea.ref }} \ 60 | \nArch: ${{ runner.arch }} \ 61 | \nBuild ${{ gitea.ref_name }} by ${{ gitea.actor }} \ 62 | \nSUCCEEDED" | \ 63 | curl -s \ 64 | -u "${{ secrets.NTFY_USR }}":"${{ secrets.NTFY_PWD }}" \ 65 | -H "tags:heavy_check_mark" \ 66 | -H "X-Icon: https://raw.githubusercontent.com/go-gitea/gitea/main/\ 67 | public/assets/img/favicon.png" \ 68 | -H "Actions: view, Open run, \ 69 | ${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/\ 70 | ${{ gitea.run_number }}, clear=true; view, Open repo, \ 71 | ${{ gitea.server_url }}/${{ gitea.repository }}, clear=true;" \ 72 | -H "X-Title: Gitea Actions" \ 73 | -T- "${{ secrets.NTFY_URL }}" 74 | 75 | - name: Job Failed 76 | if: failure() 77 | run: | 78 | echo -e "Repo: ${{ gitea.repository }} \ 79 | \nBranch: ${{ gitea.ref }} \ 80 | \nArch: ${{ runner.arch }} \ 81 | \nBuild ${{ gitea.ref_name }} by ${{ gitea.actor }} \ 82 | \nFAILED" | \ 83 | curl -s \ 84 | -u "${{ secrets.NTFY_USR }}":"${{ secrets.NTFY_PWD }}" \ 85 | -H "tags:x" \ 86 | -H "X-Icon: https://raw.githubusercontent.com/go-gitea/gitea/main/\ 87 | public/assets/img/favicon.png" \ 88 | -H "Actions: view, Open run, \ 89 | ${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/\ 90 | ${{ gitea.run_number }}, clear=true; view, Open repo, \ 91 | ${{ gitea.server_url }}/${{ gitea.repository }}, clear=true;" \ 92 | -H "X-Title: Gitea Actions" \ 93 | -T- "${{ secrets.NTFY_URL }}" 94 | -------------------------------------------------------------------------------- /jellyfin_webhook_ntfy.handlebars: -------------------------------------------------------------------------------- 1 | This is a ntfy.sh template for the Jellyfin Webhook plugin. 2 | 3 | I got most of the inspiration from the examples here: 4 | https://github.com/jellyfin/jellyfin-plugin-webhook/tree/master/Jellyfin.Plugin.Webhook/Templates 5 | 6 | I've tried most of the notifications, but let me know if something isn't working. 7 | I'm pretty sure AuthenticationSuccess and AuthenticationFailure doesn't work at all, since there isn't any mention of them on the Notifications page on Jellyfin. If anyone has any ideas, an issue or a PR would be awesome. 8 | 9 | I'm sure it's possible to slim down this template quite a bit, but this is what we got for the moment. :) 10 | 11 | To enable the notifications, install the Webhook plugin, then click "Add Generic Destination" on the settings page. 12 | Set the "Webook URL" to your ntfy server, for example: ntfy.sh 13 | Check the boxes you want notifications from. (Make sure they are checked on the "Notifications" page as well.) 14 | Click "Add Request Header" (If you need to login) and enter "Authorization" in the Key field. 15 | In the "Value" field you need to enter "Bearer" followed by an access token (see https://docs.ntfy.sh/publish/#access-tokens). 16 | The entered value should look something like this: "Bearer tk_<alphanumeric_stuff>". 17 | Alternatively, while not recommended, a base64 encoded string of your username:password, prepended by "Basic" can be used. 18 | See https://docs.ntfy.sh/publish/#username-password , the oneliner you need is probably: 19 | echo "Basic $(echo -n 'testuser:fakepassword' | base64)" 20 | 21 | Paste everything below this in the "Template" box, and click save. Do not forget to edit the "topic" field. 22 | 23 | { 24 | "topic": "jellyfin", 25 | "icon": "{{ServerUrl}}/web/favicon.png", 26 | {{#if_equals NotificationType 'Generic'}} 27 | "tags": ["octopus"], 28 | "title": "Jellyfin: Generic notification", 29 | "message": "{{Name}}", 30 | "actions": [ 31 | { 32 | "action": "view", 33 | "label": "Open Jellyfin", 34 | "url": "{{ServerUrl}}", 35 | "clear": true 36 | } 37 | ] 38 | {{/if_equals}} 39 | {{#if_equals NotificationType 'ItemAdded'}} 40 | "tags": ["partying_face","film_projector"], 41 | "attach": "{{ServerUrl}}/Items/{{ItemId}}/Images/Primary", 42 | {{~#if_exist Provider_imdb~}} 43 | "title": "Jellyfin: Item added", 44 | "actions": [ 45 | { 46 | "action": "view", 47 | "label": "Open Jellyfin", 48 | "url": "{{ServerUrl}}/web/index.html#!/details?id={{ItemId}}&serverId={{ServerId}}", 49 | "clear": true 50 | }, 51 | { 52 | "action": "view", 53 | "label": "IMDb", 54 | "url": "https://www.imdb.com/title/{{Provider_imdb}}/", 55 | "clear": true 56 | } 57 | ], 58 | {{~/if_exist~}} 59 | {{~#if_exist Provider_tvdb~}} 60 | {{~#if_equals ItemType 'Movie'~}} 61 | "title": "Jellyfin: Movie added", 62 | "actions": [ 63 | { 64 | "action": "view", 65 | "label": "Open Jellyfin", 66 | "url": "{{ServerUrl}}/web/index.html#!/details?id={{ItemId}}&serverId={{ServerId}}", 67 | "clear": true 68 | }, 69 | { 70 | "action": "view", 71 | "label": "tvdb", 72 | "url": "https://thetvdb.com/dereferrer/movie/{{Provider_tvdb}}", 73 | "clear": true 74 | } 75 | ], 76 | {{~else~}} 77 | {{~#if_equals ItemType 'Season'~}} 78 | "title": "Jellyfin: Season added", 79 | "actions": [ 80 | { 81 | "action": "view", 82 | "label": "Open Jellyfin", 83 | "url": "{{ServerUrl}}/web/index.html#!/details?id={{ItemId}}&serverId={{ServerId}}", 84 | "clear": true 85 | }, 86 | { 87 | "action": "view", 88 | "label": "tvdb", 89 | "url": "https://thetvdb.com/dereferrer/season/{{Provider_tvdb}}", 90 | "clear": true 91 | } 92 | ], 93 | {{~else~}} 94 | "title": "Jellyfin: Episode added", 95 | "actions": [ 96 | { 97 | "action": "view", 98 | "label": "Open Jellyfin", 99 | "url": "{{ServerUrl}}/web/index.html#!/details?id={{ItemId}}&serverId={{ServerId}}", 100 | "clear": true 101 | }, 102 | { 103 | "action": "view", 104 | "label": "tvdb", 105 | "url": "https://thetvdb.com/dereferrer/episode/{{Provider_tvdb}}", 106 | "clear": true 107 | } 108 | ], 109 | {{~/if_equals~}} 110 | {{~/if_equals~}} 111 | {{~/if_exist~}} 112 | {{~#if_exist Provider_tmdb~}} 113 | {{~#if_equals ItemType 'Movie'~}} 114 | "title": "Jellyfin: Movie added", 115 | "actions": [ 116 | { 117 | "action": "view", 118 | "label": "Open Jellyfin", 119 | "url": "{{ServerUrl}}/web/index.html#!/details?id={{ItemId}}&serverId={{ServerId}}", 120 | "clear": true 121 | }, 122 | { 123 | "action": "view", 124 | "label": "TMDb", 125 | "url": "https://www.themoviedb.org/movie/{{Provider_tmdb}}", 126 | "clear": true 127 | } 128 | ], 129 | {{~else~}} 130 | "title": "Jellyfin: Episode added", 131 | "actions": [ 132 | { 133 | "action": "view", 134 | "label": "Open Jellyfin", 135 | "url": "{{ServerUrl}}/web/index.html#!/details?id={{ItemId}}&serverId={{ServerId}}", 136 | "clear": true 137 | }, 138 | { 139 | "action": "view", 140 | "label": "TMDb", 141 | "url": "https://www.themoviedb.org/tv/{{Provider_tmdb}}", 142 | "clear": true 143 | } 144 | ], 145 | {{~/if_equals~}} 146 | {{~/if_exist~}} 147 | {{~#if_exist Provider_musicbrainzartist~}} 148 | "title": "Jellyfin: Music added", 149 | "actions": [ 150 | { 151 | "action": "view", 152 | "label": "Open Jellyfin", 153 | "url": "{{ServerUrl}}/web/index.html#!/details?id={{ItemId}}&serverId={{ServerId}}", 154 | "clear": true 155 | }, 156 | { 157 | "action": "view", 158 | "label": "MusicBrainz", 159 | "url": "https://musicbrainz.org/artist/{{Provider_musicbrainzartist}}", 160 | "clear": true 161 | } 162 | ], 163 | {{~/if_exist~}} 164 | {{#if_exist Provider_audiodbartist}} 165 | "title": "Jellyfin: Music added", 166 | "actions": [ 167 | { 168 | "action": "view", 169 | "label": "Open Jellyfin", 170 | "url": "{{ServerUrl}}/web/index.html#!/details?id={{ItemId}}&serverId={{ServerId}}", 171 | "clear": true 172 | }, 173 | { 174 | "action": "view", 175 | "label": "AudioDb", 176 | "url": "https://theaudiodb.com/artist/{{Provider_audiodbartist}}", 177 | "clear": true 178 | } 179 | ], 180 | {{~/if_exist~}} 181 | {{~#if_exist Provider_musicbrainztrack~}} 182 | "title": "Jellyfin: Music added", 183 | "actions": [ 184 | { 185 | "action": "view", 186 | "label": "Open Jellyfin", 187 | "url": "{{ServerUrl}}/web/index.html#!/details?id={{ItemId}}&serverId={{ServerId}}", 188 | "clear": true 189 | }, 190 | { 191 | "action": "view", 192 | "label": "MusicBrainz Track", 193 | "url": "https://musicbrainz.org/track/{{Provider_musicbrainztrack}}", 194 | "clear": true 195 | } 196 | ], 197 | {{~/if_exist~}} 198 | {{~#if_exist Provider_musicbrainzalbum~}} 199 | "title": "Jellyfin: Music album added", 200 | "actions": [ 201 | { 202 | "action": "view", 203 | "label": "Open Jellyfin", 204 | "url": "{{ServerUrl}}/web/index.html#!/details?id={{ItemId}}&serverId={{ServerId}}", 205 | "clear": true 206 | }, 207 | { 208 | "action": "view", 209 | "label": "MusicBrainz Album", 210 | "url": "https://musicbrainz.org/release/{{Provider_musicbrainzalbum}}", 211 | "clear": true 212 | } 213 | ], 214 | {{~/if_exist~}} 215 | {{#if_exist Provider_theaudiodbalbum}} 216 | "title": "Jellyfin: Music album added", 217 | "actions": [ 218 | { 219 | "action": "view", 220 | "label": "Open Jellyfin", 221 | "url": "{{ServerUrl}}/web/index.html#!/details?id={{ItemId}}&serverId={{ServerId}}", 222 | "clear": true 223 | }, 224 | { 225 | "action": "view", 226 | "label": "TADb Album", 227 | "url": "https://theaudiodb.com/album/{{Provider_theaudiodbalbum}}", 228 | "clear": true 229 | } 230 | ], 231 | {{~/if_exist~}} 232 | {{#if_equals ItemType 'Episode'}} 233 | "message": "{{SeriesName}} - S{{SeasonNumber00}}E{{EpisodeNumber00}} - {{Name}}" 234 | {{else}} 235 | {{#if_equals ItemType 'Season'}} 236 | "message": "{{SeriesName}} - {{Name}}" 237 | {{else}} 238 | "message": "{{Name}} - ({{Year}}) - {{RunTime}}" 239 | {{/if_equals}} 240 | {{/if_equals}} 241 | {{/if_equals}} 242 | {{#if_equals NotificationType 'PlaybackStart'}} 243 | "tags": ["octopus"], 244 | "title": "Jellyfin: Playback start", 245 | {{#if_equals ItemType 'Episode'}} 246 | "message": "User {{NotificationUsername}} started playing:\n{{SeriesName}} S{{SeasonNumber00}}E{{EpisodeNumber00}} {{Name}}", 247 | "actions": [ 248 | { 249 | "action": "view", 250 | "label": "Manage user", 251 | "url": "{{ServerUrl}}/web/index.html#!/useredit.html?userId={{UserId}}", 252 | "clear": true 253 | } 254 | ] 255 | {{else}} 256 | "message": "User {{NotificationUsername}} started playing:\n{{Name}} ({{Year}}) {{RunTime}}", 257 | "actions": [ 258 | { 259 | "action": "view", 260 | "label": "Manage user", 261 | "url": "{{ServerUrl}}/web/index.html#!/useredit.html?userId={{UserId}}", 262 | "clear": true 263 | } 264 | ] 265 | {{/if_equals}} 266 | {{/if_equals}} 267 | {{#if_equals NotificationType 'PlaybackProgress'}} 268 | "tags": ["octopus"], 269 | "title": "Jellyfin: Playback progress", 270 | {{#if_equals ItemType 'Episode'}} 271 | "message": "User {{NotificationUsername}} is playing:\n{{SeriesName}} S{{SeasonNumber00}}E{{EpisodeNumber00}} {{Name}}, at position {{PlaybackPosition}} of {{RunTime}}", 272 | "actions": [ 273 | { 274 | "action": "view", 275 | "label": "Manage user", 276 | "url": "{{ServerUrl}}/web/index.html#!/useredit.html?userId={{UserId}}", 277 | "clear": true 278 | } 279 | ] 280 | {{else}} 281 | "message": "User {{NotificationUsername}} is playing:\n{{Name}} ({{Year}}) at position {{PlaybackPosition}} of {{RunTime}}", 282 | "actions": [ 283 | { 284 | "action": "view", 285 | "label": "Manage user", 286 | "url": "{{ServerUrl}}/web/index.html#!/useredit.html?userId={{UserId}}", 287 | "clear": true 288 | } 289 | ] 290 | {{/if_equals}} 291 | {{/if_equals}} 292 | {{#if_equals NotificationType 'PlaybackStop'}} 293 | "tags": ["octopus"], 294 | "title": "Jellyfin: Playback stop", 295 | {{#if_equals ItemType 'Episode'}} 296 | "message": "User {{NotificationUsername}} stopped playing:\n{{SeriesName}} S{{SeasonNumber00}}E{{EpisodeNumber00}} {{Name}}", 297 | "actions": [ 298 | { 299 | "action": "view", 300 | "label": "Manage user", 301 | "url": "{{ServerUrl}}/web/index.html#!/useredit.html?userId={{UserId}}", 302 | "clear": true 303 | } 304 | ] 305 | {{else}} 306 | "message": "User {{NotificationUsername}} stopped playing:\n{{Name}} ({{Year}})", 307 | "actions": [ 308 | { 309 | "action": "view", 310 | "label": "Manage user", 311 | "url": "{{ServerUrl}}/web/index.html#!/useredit.html?userId={{UserId}}", 312 | "clear": true 313 | } 314 | ] 315 | {{/if_equals}} 316 | {{/if_equals}} 317 | {{#if_equals NotificationType 'SubtitleDownloadFailure'}} 318 | "tags": ["warning"], 319 | "title": "Jellyfin: Subtitle download failure", 320 | {{#if_equals ItemType 'Episode'}} 321 | "message": "Warning: Subtitle failed to download:\n{{SeriesName}} - S{{SeasonNumber00}}E{{EpisodeNumber00}} - {{Name}}" 322 | {{else}} 323 | "message": "Warning: Subtitle failed to download:\n{{Name}} - ({{Year}})" 324 | {{/if_equals}} 325 | {{/if_equals}} 326 | {{#if_equals NotificationType 'TaskCompleted'}} 327 | "tags": ["white_check_mark"], 328 | "title": "Jellyfin: Task completed", 329 | "message": "Task completed with status {{ResultStatus}}, check logs for details", 330 | "actions": [ 331 | { 332 | "action": "view", 333 | "label": "Open logs", 334 | "url": "{{ServerUrl}}/web/index.html#!/log.html", 335 | "clear": true 336 | } 337 | ] 338 | {{/if_equals}} 339 | {{#if_equals NotificationType 'PendingRestart'}} 340 | "tags": ["hourglass_flowing_sand"], 341 | "title": "Jellyfin: Server needs to be restarted", 342 | "message": "Check logs for details" 343 | {{/if_equals}} 344 | {{#if_equals NotificationType 'PluginInstallationCancelled'}} 345 | "tags": ["exclamation"], 346 | "title": "Jellyfin: Plugin installation cancelled", 347 | "message": "Check logs for details", 348 | "actions": [ 349 | { 350 | "action": "view", 351 | "label": "Open logs", 352 | "url": "{{ServerUrl}}/web/index.html#!/log.html", 353 | "clear": true 354 | } 355 | ] 356 | {{/if_equals}} 357 | {{#if_equals NotificationType 'PluginInstallationFailed'}} 358 | "tags": ["warning"], 359 | "title": "Jellyfin: Plugin installation failed", 360 | "message": "Check logs for details", 361 | "actions": [ 362 | { 363 | "action": "view", 364 | "label": "Open logs", 365 | "url": "{{ServerUrl}}/web/index.html#!/log.html", 366 | "clear": true 367 | } 368 | ] 369 | {{/if_equals}} 370 | {{#if_equals NotificationType 'PluginInstalled'}} 371 | "tags": ["white_check_mark"], 372 | "title": "Jellyfin: Plugin successfully installed", 373 | "message": "Server restart needed", 374 | "actions": [ 375 | { 376 | "action": "view", 377 | "label": "Open logs", 378 | "url": "{{ServerUrl}}/web/index.html#!/log.html", 379 | "clear": true 380 | } 381 | ] 382 | {{/if_equals}} 383 | {{#if_equals NotificationType 'PluginInstalling'}} 384 | "tags": ["hourglass_flowing_sand"], 385 | "title": "Jellyfin: Plugin is installing now", 386 | "message": "Check logs for details" 387 | {{/if_equals}} 388 | {{#if_equals NotificationType 'PluginUninstalled'}} 389 | "tags": ["white_check_mark"], 390 | "title": "Jellyfin: Plugin successfully uninstalled", 391 | "message": "Check logs for details", 392 | "actions": [ 393 | { 394 | "action": "view", 395 | "label": "Open logs", 396 | "url": "{{ServerUrl}}/web/index.html#!/log.html", 397 | "clear": true 398 | } 399 | ] 400 | {{/if_equals}} 401 | {{#if_equals NotificationType 'PluginUpdated'}} 402 | "tags": ["information_source"], 403 | "title": "Jellyfin: Plugin successfully updated", 404 | "message": "Check logs for details", 405 | "actions": [ 406 | { 407 | "action": "view", 408 | "label": "Open logs", 409 | "url": "{{ServerUrl}}/web/index.html#!/log.html", 410 | "clear": true 411 | } 412 | ] 413 | {{/if_equals}} 414 | {{#if_equals NotificationType 'SessionStart'}} 415 | "tags": ["information_source"], 416 | "title": "Jellyfin: User active", 417 | "message": "User {{NotificationUsername}} now active.\nClient: {{ClientName}}\nDevice: {{DeviceName}}", 418 | "actions": [ 419 | { 420 | "action": "view", 421 | "label": "Manage user", 422 | "url": "{{ServerUrl}}/web/index.html#!/useredit.html?userId={{UserId}}", 423 | "clear": true 424 | } 425 | ] 426 | {{/if_equals}} 427 | {{#if_equals NotificationType 'UserCreated'}} 428 | "tags": ["information_source"], 429 | "title": "Jellyfin: User created", 430 | "message": "User {{NotificationUsername}} was added successfully", 431 | "actions": [ 432 | { 433 | "action": "view", 434 | "label": "Manage user", 435 | "url": "{{ServerUrl}}/web/index.html#!/useredit.html?userId={{UserId}}", 436 | "clear": true 437 | } 438 | ] 439 | {{/if_equals}} 440 | {{#if_equals NotificationType 'UserDeleted'}} 441 | "tags": ["information_source"], 442 | "title": "Jellyfin: User deleted", 443 | "message": "User {{NotificationUsername}} was deleted", 444 | "actions": [ 445 | { 446 | "action": "view", 447 | "label": "Manage user", 448 | "url": "{{ServerUrl}}/web/index.html#!/useredit.html?userId={{UserId}}", 449 | "clear": true 450 | } 451 | ] 452 | {{/if_equals}} 453 | {{#if_equals NotificationType 'UserLockedOut'}} 454 | "tags": ["exclamation"], 455 | "title": "Jellyfin: User locked out", 456 | "message": "Warning: User {{NotificationUsername}} was locked out.", 457 | "actions": [ 458 | { 459 | "action": "view", 460 | "label": "Manage user", 461 | "url": "{{ServerUrl}}/web/index.html#!/useredit.html?userId={{UserId}}", 462 | "clear": true 463 | } 464 | ] 465 | {{/if_equals}} 466 | {{#if_equals NotificationType 'UserPasswordChanged'}} 467 | "tags": ["information_source"], 468 | "title": "Jellyfin: User password changed", 469 | "message": "User {{NotificationUsername}} password changed successfully", 470 | "actions": [ 471 | { 472 | "action": "view", 473 | "label": "Manage user", 474 | "url": "{{ServerUrl}}/web/index.html#!/useredit.html?userId={{UserId}}", 475 | "clear": true 476 | } 477 | ] 478 | {{/if_equals}} 479 | {{#if_equals NotificationType 'UserUpdated'}} 480 | "tags": ["information_source"], 481 | "title": "Jellyfin: User updated", 482 | "message": "User {{NotificationUsername}} was updated successfully", 483 | "actions": [ 484 | { 485 | "action": "view", 486 | "label": "Manage user", 487 | "url": "{{ServerUrl}}/web/index.html#!/useredit.html?userId={{UserId}}", 488 | "clear": true 489 | } 490 | ] 491 | {{/if_equals}} 492 | {{#if_equals NotificationType 'UserDataSaved'}} 493 | "tags": ["information_source"], 494 | "title": "Jellyfin: User data saved", 495 | "message": "User {{NotificationUsername}} data was saved", 496 | "actions": [ 497 | { 498 | "action": "view", 499 | "label": "Manage user", 500 | "url": "{{ServerUrl}}/web/index.html#!/useredit.html?userId={{UserId}}", 501 | "clear": true 502 | } 503 | ] 504 | {{/if_equals}} 505 | {{#if_equals NotificationType 'AuthenticationSuccess'}} 506 | "tags": ["white_check_mark"], 507 | "title": "Jellyfin: Authentication success", 508 | "message": "User {{NotificationUsername}} successfully logged on. Client: {{ClientName}} Device: {{DeviceName}}", 509 | "actions": [ 510 | { 511 | "action": "view", 512 | "label": "Manage user", 513 | "url": "{{ServerUrl}}/web/index.html#!/useredit.html?userId={{UserId}}", 514 | "clear": true 515 | } 516 | ] 517 | {{/if_equals}} 518 | {{#if_equals NotificationType 'AuthenticationFailure'}} 519 | "tags": ["exclamation"], 520 | "title": "Jellyfin: Authentication failed", 521 | "message": "Warning: User {{NotificationUsername}} failed to authenticate. Client: {{ClientName}} Device: {{DeviceName}}, check server logs for details", 522 | "actions": [ 523 | { 524 | "action": "view", 525 | "label": "Manage user", 526 | "url": "{{ServerUrl}}/web/index.html#!/useredit.html?userId={{UserId}}", 527 | "clear": true 528 | } 529 | ] 530 | {{/if_equals}} 531 | } 532 | -------------------------------------------------------------------------------- /lidarr.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # load env file from $NTFY_ENV or script dir 4 | SCRIPTPATH=${NTFY_ENV:-$(dirname "$0")/.env} 5 | [ -f ${SCRIPTPATH} ] && . "${SCRIPTPATH}" || echo "ENV missing: ${SCRIPTPATH}" 6 | 7 | if [[ -n $ntfy_password && -n $ntfy_token ]]; then 8 | echo "Use ntfy_username and ntfy_password OR ntfy_token" 9 | exit 1 10 | elif [ -n "$ntfy_password" ]; then 11 | ntfy_base64=$( echo -n "$ntfy_username:$ntfy_password" | base64 ) 12 | ntfy_auth="Authorization: Basic $ntfy_base64" 13 | elif [ -n "$ntfy_token" ]; then 14 | ntfy_auth="Authorization: Bearer $ntfy_token" 15 | else 16 | ntfy_auth="" 17 | fi 18 | 19 | ntfy_title=$lidarr_artist_name 20 | ntfy_message=" " 21 | if [ "$lidarr_eventtype" == "Test" ]; then 22 | ntfy_tag=information_source 23 | ntfy_title="Testing" 24 | elif [ "$lidarr_eventtype" == "AlbumDownload" ]; then 25 | ntfy_title+=" - " 26 | ntfy_title+=$lidarr_album_title 27 | ntfy_tag=musical_note 28 | elif [ "$lidarr_eventtype" == "ApplicationUpdate" ]; then 29 | ntfy_tag=arrow_up 30 | ntfy_message+="Lidarr updated from " 31 | ntfy_message+=$lidarr_update_previousversion 32 | ntfy_message+=" to " 33 | ntfy_message+=$lidarr_update_newversion 34 | # ntfy_message+=" - Lidarr message: " 35 | # ntfy_message+=$lidarr_update_message 36 | elif [ "$lidarr_eventtype" == "HealthIssue" ]; then 37 | ntfy_tag=warning 38 | ntfy_message+=$lidarr_health_issue_message 39 | else 40 | ntfy_tag=information_source 41 | fi 42 | 43 | if [ "$lidarr_eventtype" == "AlbumDownload" ]; then 44 | ntfy_post_data() 45 | { 46 | cat <<EOF 47 | { 48 | "topic": "$lidarr_ntfy_topic", 49 | "tags": ["$ntfy_tag"], 50 | "icon": "$lidarr_ntfy_icon", 51 | "title": "Lidarr: $lidarr_eventtype", 52 | "message": "$ntfy_title$ntfy_message", 53 | "actions": [ 54 | { 55 | "action": "view", 56 | "label": "MusicBrainz", 57 | "url": "https://musicbrainz.org/release-group/$lidarr_album_mbid", 58 | "clear": true 59 | } 60 | ] 61 | } 62 | EOF 63 | } 64 | else 65 | ntfy_post_data() 66 | { 67 | cat <<EOF 68 | { 69 | "topic": "$lidarr_ntfy_topic", 70 | "tags": ["$ntfy_tag"], 71 | "icon": "$lidarr_ntfy_icon", 72 | "title": "Lidarr: $lidarr_eventtype", 73 | "message": "$ntfy_title$ntfy_message" 74 | } 75 | EOF 76 | } 77 | fi 78 | 79 | curl -H "Accept: application/json" \ 80 | -H "Content-Type:application/json" \ 81 | -H "$ntfy_auth" -X POST --data "$(ntfy_post_data)" $ntfy_url 82 | -------------------------------------------------------------------------------- /ntfy-fail2ban/README.md: -------------------------------------------------------------------------------- 1 | This is a script that monitors a fail2ban logfile and sends a notification if an IP is banned. 2 | I created a small install script as well, that creates an user and a systemd service. 3 | 4 | There is also an action button to look up the IP at: https://bgp.he.net 5 | 6 | You will need to use these variables in .env: 7 | <pre> 8 | ntfy_username and ntfy_password OR ntfy_token 9 | ntfy_url 10 | fail2ban_ntfy_topic 11 | fail2ban_log_path 12 | </pre> 13 | -------------------------------------------------------------------------------- /ntfy-fail2ban/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Quick and dirty install script. 4 | 5 | install_location="/opt/scripts/ntfy-fail2ban" 6 | 7 | # Create user 8 | useradd -r -s /bin/false ntfy-fail2ban 9 | 10 | mkdir -p "$install_location" 11 | chown ntfy-fail2ban:ntfy-fail2ban "$install_location" 12 | chmod -R 700 "$install_location" 13 | 14 | cp ntfy-fail2ban.sh "$install_location"/ntfy-fail2ban.sh 15 | chown ntfy-fail2ban:ntfy-fail2ban "$install_location"/ntfy-fail2ban.sh 16 | chmod +x "$install_location"/ntfy-fail2ban.sh 17 | 18 | # Create the systemd service file 19 | cat <<EOF > /etc/systemd/system/ntfy-fail2ban.service 20 | [Unit] 21 | Description=Fail2Ban NTFY Service 22 | After=network.target 23 | 24 | [Service] 25 | User=ntfy-fail2ban 26 | Group=ntfy-fail2ban 27 | ExecStart=$install_location/ntfy-fail2ban.sh 28 | Restart=always 29 | 30 | [Install] 31 | WantedBy=multi-user.target 32 | EOF 33 | 34 | systemctl daemon-reload 35 | 36 | systemctl enable ntfy-fail2ban.service 37 | 38 | echo "Fail2Ban notification service installed and enabled, start with:" 39 | echo "systemctl start ntfy-fail2ban.service" 40 | echo 41 | echo "Do not forget to add the env file here: $install_location/.env" 42 | -------------------------------------------------------------------------------- /ntfy-fail2ban/ntfy-fail2ban.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # load env file from $NTFY_ENV or 'env' in script dir 4 | SCRIPTPATH=${NTFY_ENV:-$(dirname "$0")/.env} 5 | [ -f ${SCRIPTPATH} ] && . "${SCRIPTPATH}" || echo "ENV missing: ${SCRIPTPATH}" 6 | 7 | if [[ -n $ntfy_password && -n $ntfy_token ]]; then 8 | echo "Use ntfy_username and ntfy_password OR ntfy_token" 9 | exit 1 10 | elif [ -n "$ntfy_password" ]; then 11 | ntfy_base64=$( echo -n "$ntfy_username:$ntfy_password" | base64 ) 12 | ntfy_auth="Authorization: Basic $ntfy_base64" 13 | elif [ -n "$ntfy_token" ]; then 14 | ntfy_auth="Authorization: Bearer $ntfy_token" 15 | else 16 | ntfy_auth="" 17 | fi 18 | 19 | send_ntfy() { 20 | curl -s \ 21 | -H "$ntfy_auth" \ 22 | -H "tags: no_entry" \ 23 | -H "X-Priority: 3" \ 24 | -H "X-Title: Server: $HOSTNAME" \ 25 | -H "Actions: view, Lookup IP, https://bgp.he.net/ip/$ip_address, clear=true;" \ 26 | -d "Fail2Ban: $clean_line" \ 27 | --request POST "$ntfy_url/$fail2ban_ntfy_topic" > /dev/null 28 | } 29 | 30 | 31 | tail -n0 -F "$fail2ban_log_path" | while read LINE; do 32 | if echo "$LINE" | egrep "Ban"; then 33 | # Clean up the log message 34 | clean_line=$(echo "$LINE" | sed -n 's/.*\(\[[^][]*\].*\)/\1/p') 35 | ip_address=$(echo "$clean_line" | grep -oE '((1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])') 36 | send_ntfy 37 | fi 38 | done 39 | -------------------------------------------------------------------------------- /ntfy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # load env file from $NTFY_ENV or script dir 4 | SCRIPTPATH=${NTFY_ENV:-$(dirname "$0")/.env} 5 | [ -f ${SCRIPTPATH} ] && . "${SCRIPTPATH}" || echo "ENV missing: ${SCRIPTPATH}" 6 | 7 | help() 8 | { 9 | echo "Options:" 10 | echo "-t Your topic. (Optional, hostname will be used if omitted.)" 11 | echo "-m Your message." 12 | echo "-p Notification priority, 1-5, 5 is the highest. (Optional)" 13 | echo "-e Choose emoji. (https://ntfy.sh/docs/emojis/?h=emo)" 14 | echo "-i Icon URL" 15 | echo "-h Print this help." 16 | echo 17 | echo "If you want to show if the last command was successful or not, you can do something like this:" 18 | echo "yourcommand ; export le=$? ; /path/to/ntfy.sh" 19 | echo 20 | } 21 | 22 | 23 | while getopts "t:m:p:e:i:h" option; do 24 | case $option in 25 | t) ntfy_topic=${OPTARG};; 26 | m) ntfy_message=${OPTARG};; 27 | p) ntfy_prio=${OPTARG};; 28 | e) ntfy_emoji=${OPTARG};; 29 | i) ntfy_icon=${OPTARG};; 30 | h) help 31 | exit;; 32 | \?) 33 | echo "Error: Invalid option" 34 | exit;; 35 | esac 36 | done 37 | 38 | if [ -z "$ntfy_message" ]; then 39 | ntfy_message="Done" 40 | fi 41 | 42 | if [ "$ntfy_prio" == "1" ]; then 43 | ntfy_prio="min" 44 | ntfy_tag="white_small_square" 45 | elif [ "$ntfy_prio" == "2" ]; then 46 | ntfy_prio="low" 47 | ntfy_tag="computer" 48 | elif [ "$ntfy_prio" == "3" ]; then 49 | ntfy_prio="default" 50 | ntfy_tag="computer" 51 | elif [ "$ntfy_prio" == "4" ]; then 52 | ntfy_prio="high" 53 | ntfy_tag="warning" 54 | elif [ "$ntfy_prio" == "5" ]; then 55 | ntfy_prio="max" 56 | ntfy_tag="rotating_light" 57 | else 58 | ntfy_prio="default" 59 | ntfy_tag="computer" 60 | fi 61 | 62 | if [ -n "$ntfy_emoji" ]; then 63 | ntfy_tag="$ntfy_emoji" 64 | fi 65 | 66 | if [ -n "$le" ]; then 67 | if [ "$le" == "0" ]; then 68 | ntfy_tag="heavy_check_mark" 69 | else 70 | ntfy_tag="x" 71 | fi 72 | fi 73 | 74 | if [ -z "$ntfy_topic" ]; then 75 | ntfy_topic="$HOSTNAME" 76 | fi 77 | 78 | if [[ -n $ntfy_password && -n $ntfy_token ]]; then 79 | echo "Use ntfy_username and ntfy_password OR ntfy_token" 80 | exit 1 81 | elif [ -n "$ntfy_password" ]; then 82 | ntfy_base64=$( echo -n "$ntfy_username:$ntfy_password" | base64 ) 83 | ntfy_auth="Authorization: Basic $ntfy_base64" 84 | elif [ -n "$ntfy_token" ]; then 85 | ntfy_auth="Authorization: Bearer $ntfy_token" 86 | else 87 | ntfy_auth="" 88 | fi 89 | 90 | curl -s -H "$ntfy_auth" -H "tags:"$ntfy_tag -H "icon:"$ntfy_icon -H "prio:"$ntfy_prio -H "X-Title: $ntfy_topic" -d "$ntfy_message" "$ntfy_url/$ntfy_ntfy_topic" > /dev/null 91 | -------------------------------------------------------------------------------- /opnsense.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # load env file from $NTFY_ENV or script dir 4 | SCRIPTPATH=${NTFY_ENV:-$(dirname "$0")/.env} 5 | [ -f ${SCRIPTPATH} ] && . "${SCRIPTPATH}" || echo "ENV missing: ${SCRIPTPATH}" 6 | 7 | 8 | # This is kind of a not very good way to get ntfy notifications from Monit in OPNsense. 9 | # It works great, but isn't really done the correct way. We run this script as a test, so it really only works when something fails. 10 | # When the problem is cleared, no notification is sent. 11 | 12 | # Place this script here (Or wherever you want the script) after you have installed Monit: 13 | # /usr/local/opnsense/scripts/OPNsense/Monit/opnsense_ntfy.sh 14 | 15 | # In OPNsense Monit, add a ping service for example, with ping as test. 16 | # In Service Tests Settings, add an Execute action for condition "failed ping". 17 | # In Path, add your script: /usr/local/opnsense/scripts/OPNsense/Monit/opnsense_ntfy.sh 18 | 19 | # Also, keep in mind that if a ping check to your GW fails, there is a very good chance that the notification fails as well. :) 20 | 21 | # Uncomment the one you need. 22 | # No auth: 23 | curl -H "tags:warning" -H "X-Icon: $opnsense_ntfy_icon" -H "X-Title: $MONIT_HOST $MONIT_SERVICE" -d "$MONIT_DESCRIPTION" --request POST "$ntfy_url/$opnsense_ntfy_topic" 24 | # Token 25 | #curl -H "Authorization: Bearer $ntfy_token" -H "tags:warning" -H "X-Icon: $opnsense_ntfy_icon"" -H "X-Title: $MONIT_HOST $MONIT_SERVICE" -d "$MONIT_DESCRIPTION" --request POST "$ntfy_url/$opnsense_ntfy_topic" 26 | # User and password 27 | #curl -u $ntfy_username:$ntfy_password -H "tags:warning" -H "X-Icon: $opnsense_ntfy_icon" -H "X-Title: $MONIT_HOST $MONIT_SERVICE" -d "$MONIT_DESCRIPTION" --request POST "$ntfy_url/$opnsense_ntfy_topic" 28 | -------------------------------------------------------------------------------- /paperless.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # For more information how to use this script, see the documentation for Paperless-NGX 4 | # https://paperless-ngx.readthedocs.io/en/latest/advanced_usage.html?highlight=script#post-consumption-script 5 | 6 | DOCUMENT_ID=${1} 7 | DOCUMENT_FILE_NAME=${2} 8 | DOCUMENT_SOURCE_PATH=${3} 9 | DOCUMENT_THUMBNAIL_PATH=${4} 10 | DOCUMENT_DOWNLOAD_URL=${5} 11 | DOCUMENT_THUMBNAIL_URL=${6} 12 | DOCUMENT_CORRESPONDENT=${7} 13 | DOCUMENT_TAGS=${8} 14 | PAPERLESS_URL="https://paperless.example.com" 15 | NTFY_URL="https://ntfy.sh/mytopic" 16 | # Use NTFY_USER and NTFY_PASSWORD OR NTFY_TOKEN 17 | NTFY_USER="changeme" 18 | NTFY_PASSWORD="changeme" 19 | NTFY_TOKEN="" 20 | # Leave empty if you do not want an icon. 21 | ntfy_icon="https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/4df065d8d524870ec18e8fbf2fc488449939a044/src-ui/src/apple-touch-icon.png" 22 | 23 | if [[ -n $NTFY_PASSWORD && -n $NTFY_TOKEN ]]; then 24 | echo "Use NTFY_USER and NTFY_PASSWORD OR NTFY_TOKEN" 25 | exit 1 26 | elif [ -n "$NTFY_PASSWORD" ]; then 27 | NTFY_BASE64=$( echo -n "$NTFY_USER:$NTFY_PASSWORD" | base64 ) 28 | NTFY_AUTH="Authorization: Basic $NTFY_BASE64" 29 | elif [ -n "$NTFY_TOKEN" ]; then 30 | NTFY_AUTH="Authorization: Bearer $NTFY_TOKEN" 31 | else 32 | NTFY_AUTH="" 33 | fi 34 | 35 | curl -s -H "$NTFY_AUTH" -H tags:page_facing_up -H "X-Title: Paperless" \ 36 | -H "Actions: view, Open, $PAPERLESS_URL/documents/$DOCUMENT_ID, clear=true; view, Download, $PAPERLESS_URL/api/documents/$DOCUMENT_ID/download/, clear=false;" \ 37 | -d "Document ID ${DOCUMENT_ID} was imported. Name: ${DOCUMENT_FILE_NAME} Correspondent: ${DOCUMENT_CORRESPONDENT} Tags: ${DOCUMENT_TAGS}" -H "X-Icon: $ntfy_icon" "$NTFY_URL" > /dev/null 38 | -------------------------------------------------------------------------------- /prowlarr.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # load env file from $NTFY_ENV or script dir 4 | SCRIPTPATH=${NTFY_ENV:-$(dirname "$0")/.env} 5 | [ -f ${SCRIPTPATH} ] && . "${SCRIPTPATH}" || echo "ENV missing: ${SCRIPTPATH}" 6 | 7 | if [[ -n $ntfy_password && -n $ntfy_token ]]; then 8 | echo "Use ntfy_username and ntfy_password OR ntfy_token" 9 | exit 1 10 | elif [ -n "$ntfy_password" ]; then 11 | ntfy_base64=$( echo -n "$ntfy_username:$ntfy_password" | base64 ) 12 | ntfy_auth="Authorization: Basic $ntfy_base64" 13 | elif [ -n "$ntfy_token" ]; then 14 | ntfy_auth="Authorization: Bearer $ntfy_token" 15 | else 16 | ntfy_auth="" 17 | fi 18 | 19 | ntfy_title="$prowlarr_health_issue_type" 20 | ntfy_message=" " 21 | if [ "$prowlarr_eventtype" == "Test" ]; then 22 | ntfy_tag=information_source 23 | ntfy_title="Testing" 24 | elif [ "$prowlarr_eventtype" == "ApplicationUpdate" ]; then 25 | ntfy_tag=arrow_up 26 | ntfy_message+="Prowlarr updated from " 27 | ntfy_message+=$prowlarr_update_previousversion 28 | ntfy_message+=" to " 29 | ntfy_message+=$prowlarr_update_newversion 30 | # ntfy_message+=" - Prowlarr message: " 31 | # ntfy_message+=$prowlarr_update_message 32 | elif [ "$prowlarr_eventtype" == "HealthIssue" ]; then 33 | ntfy_tag=warning 34 | ntfy_message+=" - " 35 | ntfy_message+="$prowlarr_health_issue_message" 36 | fi 37 | 38 | curl \ 39 | -H "$ntfy_auth" \ 40 | -H "tags:"$ntfy_tag \ 41 | -H "X-Title: Prowlarr: $prowlarr_eventtype" \ 42 | -H "X-Icon: $prowlarr_ntfy_icon" \ 43 | -d "$ntfy_title""$ntfy_message" \ 44 | --request POST "$ntfy_url/$prowlarr_ntfy_topic" 45 | -------------------------------------------------------------------------------- /qbittorrent.txt: -------------------------------------------------------------------------------- 1 | You can enable ntfy notifications here in qBittorrent: 2 | Settings -> Downloads -> Run external program on torrent finished 3 | 4 | Enter the curl command below, or place it in a script. 5 | 6 | If you want to use an access token, replace: 7 | "-u USERNAME:PASSWORD" 8 | with: 9 | -H "Authorization: Bearer YOUR_TOKEN" 10 | 11 | curl -s -u USERNAME:PASSWORD -H "tags:arrow_down" -H "X-Icon: https://raw.githubusercontent.com/qbittorrent/qBittorrent/master/src/webui/www/public/images/qbittorrent32.png" -H "X-Title: qBittorrent" -d "Torrent Downloaded - %N" "https://ntfy.sh/mytopic" 12 | -------------------------------------------------------------------------------- /radarr.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # load env file from $NTFY_ENV or script dir 4 | SCRIPTPATH=${NTFY_ENV:-$(dirname "$0")/.env} 5 | [ -f ${SCRIPTPATH} ] && . "${SCRIPTPATH}" || echo "ENV missing: ${SCRIPTPATH}" 6 | 7 | if [[ -n $ntfy_password && -n $ntfy_token ]]; then 8 | echo "Use ntfy_username and ntfy_password OR ntfy_token" 9 | exit 1 10 | elif [ -n "$ntfy_password" ]; then 11 | ntfy_base64=$( echo -n "$ntfy_username:$ntfy_password" | base64 ) 12 | ntfy_auth="Authorization: Basic $ntfy_base64" 13 | elif [ -n "$ntfy_token" ]; then 14 | ntfy_auth="Authorization: Bearer $ntfy_token" 15 | else 16 | ntfy_auth="" 17 | fi 18 | 19 | ntfy_title=$radarr_movie_title 20 | ntfy_message=" " 21 | if [ "$radarr_eventtype" == "Test" ]; then 22 | ntfy_tag=information_source 23 | ntfy_title="Testing" 24 | elif [ "$radarr_eventtype" == "Grab" ]; then 25 | ntfy_tag=film_projector 26 | ntfy_message+=" (" 27 | ntfy_message+=$radarr_movie_year 28 | ntfy_message+=")" 29 | ntfy_message+=" [" 30 | ntfy_message+=$radarr_release_quality 31 | ntfy_message+="]" 32 | elif [ "$radarr_eventtype" == "Download" ]; then 33 | ntfy_tag=film_projector 34 | ntfy_message+=" (" 35 | ntfy_message+=$radarr_movie_year 36 | ntfy_message+=")" 37 | ntfy_message+=" [" 38 | ntfy_message+=$radarr_moviefile_quality 39 | ntfy_message+="]" 40 | elif [ "$radarr_eventtype" == "ApplicationUpdate" ]; then 41 | ntfy_tag=arrow_up 42 | ntfy_message+="Radarr updated from " 43 | ntfy_message+=$radarr_update_previousversion 44 | ntfy_message+=" to " 45 | ntfy_message+=$radarr_update_newversion 46 | # ntfy_message+=" - Radarr message: " 47 | # ntfy_message+=$radarr_update_message 48 | elif [ "$radarr_eventtype" == "HealthIssue" ]; then 49 | ntfy_tag=warning 50 | ntfy_message+=$radarr_health_issue_message 51 | else 52 | ntfy_tag=information_source 53 | fi 54 | 55 | if [[ "$radarr_eventtype" == "Download" || "$radarr_eventtype" == "Grab" ]]; then 56 | # Get the movie poster from Radarr 57 | response=$(curl -X GET -H "Content-Type: application/json" -H "X-Api-Key: $radarr_api_key" "$radarr_url/api/v3/movie/$radarr_movie_id") 58 | banner_image=$(echo "$response" | jq -r '.images[0].remoteUrl') 59 | ntfy_post_data() 60 | { 61 | cat <<EOF 62 | { 63 | "topic": "$radarr_ntfy_topic", 64 | "tags": ["$ntfy_tag"], 65 | "icon": "$radarr_ntfy_icon", 66 | "title": "Radarr: $radarr_eventtype", 67 | "attach": "$banner_image", 68 | "message": "$ntfy_title$ntfy_message", 69 | "actions": [ 70 | { 71 | "action": "view", 72 | "label": "IMDB", 73 | "url": "https://www.imdb.com/title/$radarr_movie_imdbid", 74 | "clear": true 75 | } 76 | ] 77 | } 78 | EOF 79 | } 80 | else 81 | ntfy_post_data() 82 | { 83 | cat <<EOF 84 | { 85 | "topic": "$radarr_ntfy_topic", 86 | "tags": ["$ntfy_tag"], 87 | "icon": "$radarr_ntfy_icon", 88 | "title": "Radarr: $radarr_eventtype", 89 | "message": "$ntfy_title$ntfy_message" 90 | } 91 | EOF 92 | } 93 | fi 94 | 95 | curl -H "Accept: application/json" \ 96 | -H "Content-Type:application/json" \ 97 | -H "$ntfy_auth" -X POST --data "$(ntfy_post_data)" $ntfy_url 98 | -------------------------------------------------------------------------------- /read_notify.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script looks in the directories that you specify with "library" 4 | # After that, it counts the .cbz files in all subdirectories, and inserts 5 | # the results in a sqlite database. If any new files has been added 6 | # since the last run, a push message will be sent. 7 | 8 | # I made it to send notifications when new chapters of comics/manga 9 | # have been added, but it can be used for anything I guess. 10 | 11 | # Be careful with special characters. Should work pretty good, 12 | # but I give no guarantees. 13 | 14 | # The file structure should be something like this: 15 | # "/path/one/manga name/chapter one.cbz" 16 | 17 | ###################################################################### 18 | # Config folder path, were to store the database. 19 | ###################################################################### 20 | config_path="/opt/read_notify" 21 | 22 | ###################################################################### 23 | # Library folder paths, you can add as many as you want. 24 | ###################################################################### 25 | library=( "/path/one" "/path/two" "/path/three" ) 26 | 27 | ###################################################################### 28 | # Do you want to be notified if there is a gap in the numbering? 29 | # Usually indicates a missing chapter/volume. 30 | # 31 | # When a file is added or deleted, it also checks for gaps. 32 | # 1 = On, 0 = Off 33 | # 34 | # To run a search for the whole library: 35 | # ./read_notify search_missing 36 | # 37 | # If a series is named like this: 38 | # Chapter 1.cbz 39 | # Chapter 2.cbz 40 | # Chapter 3.1.cbz 41 | # Chapter 3.2.cbz 42 | # The script will think that Chapter 3 is missing. 43 | # Simply create a ignore file, to manually ignore the "gap". 44 | # Chapter 1.cbz 45 | # Chapter 2.cbz 46 | # Chapter 3.ignore 47 | # Chapter 3.1.cbz 48 | # Chapter 3.2.cbz 49 | # 50 | # If you want to ignore the whole directory, create an .ignore_gaps file: 51 | # /path/to/manga/.ignore_gaps 52 | ###################################################################### 53 | find_missing="0" 54 | 55 | ###################################################################### 56 | # Notifications 57 | # Fill in the credentials for the service you want to use, or both. 58 | ###################################################################### 59 | 60 | # ntfy.sh 61 | ntfy_url="https://ntfy.sh/mytopic" 62 | ntfy_title="A title" 63 | ntfy_added_tag="heavy_plus_sign" 64 | ntfy_deleted_tag="heavy_minus_sign" 65 | # Use ntfy_username and ntfy_password OR ntfy_token 66 | ntfy_username="" 67 | ntfy_password="" 68 | ntfy_token="" 69 | # Leave empty if you do not want an icon. 70 | ntfy_icon="" 71 | 72 | # Pushover 73 | pushover_app_token="" 74 | pushover_user_token="" 75 | 76 | ###################################################################### 77 | # What should the notifications look like? 78 | # For example: "2 chapter(s) of foldername added" will be sent. 79 | ###################################################################### 80 | push_type="chapters(s) of" 81 | push_added="added" 82 | push_deleted="deleted" 83 | 84 | ###################################################################### 85 | # Script starts here 86 | ###################################################################### 87 | 88 | dbpath="$config_path/data.db" 89 | 90 | if ! sqlite3 --version &> /dev/null ; 91 | then 92 | echo "You need sqlite installed for this script." 93 | exit 1 94 | fi 95 | 96 | check_lock() { 97 | if [ -f /tmp/read_notify.lock ]; 98 | then 99 | echo "read_notify.sh is already running, exiting..." 100 | exit 0 101 | fi 102 | 103 | trap "rm -f /tmp/read_notify.lock ; rm -f /tmp/read_notify_added.tmp ; rm -f /tmp/read_notify_deleted.tmp ; exit" INT TERM EXIT 104 | touch /tmp/read_notify.lock 105 | } 106 | 107 | check_chapters() { 108 | 109 | for tbl in "${library[@]}"; do 110 | if [ -d "$tbl" ]; then 111 | # echo "$tbl" 112 | tbl_name=$( echo "$tbl" | tr / _ | sed 's/[^[:alnum:]_]//g' ) 113 | sqlite3 "$dbpath" "CREATE TABLE IF NOT EXISTS $tbl_name (name CHAR NOT NULL PRIMARY KEY, realname CHAR, chapters INT );" 114 | for di in "$tbl"/*/ ; do 115 | # Remove path and trailing slash 116 | dir=$( echo "$di" | sed "s|$tbl/||g" | sed 's:/*$::' ) 117 | 118 | name=$( echo "$dir" | tr -s '[:blank:]' '_' | sed 's/[^[:alnum:]_]//g' ) 119 | oldnr=$( sqlite3 "$dbpath" "SELECT chapters FROM $tbl_name WHERE name=\"$name\";" 2>/dev/null ) 120 | newnr=$( ls -1q "$tbl"/"$dir"/*.cbz | wc -l ) 121 | if [ -z "$oldnr" ]; then 122 | oldnr="0" 123 | fi 124 | if [ -z "$newnr" ]; then 125 | newnr="0" 126 | fi 127 | if [ "$newnr" -gt "$oldnr" ]; then 128 | chap=$( expr $newnr - $oldnr ) 129 | echo "$chap $push_type $dir $push_added" >> /tmp/read_notify_added.tmp 130 | if [[ "$find_missing" == "1" ]]; then 131 | find_gaps 132 | fi 133 | elif [ "$newnr" -lt "$oldnr" ]; then 134 | chap=$( expr $oldnr - $newnr ) 135 | echo "$chap $push_type $dir $push_deleted" >> /tmp/read_notify_deleted.tmp 136 | if [[ "$find_missing" == "1" ]]; then 137 | find_gaps 138 | fi 139 | fi 140 | 141 | sqlite3 "$dbpath" "INSERT OR IGNORE INTO $tbl_name (name,realname,chapters) VALUES (\"$name\",\"$dir\",\"$newnr\"); UPDATE $tbl_name SET chapters = $newnr WHERE name=\"$name\"" 142 | done 143 | fi 144 | done 145 | } 146 | 147 | check_push() { 148 | # Send push message 149 | if [ -f "$push_file" ]; then 150 | if [ -n "$ntfy_url" ]; then 151 | if [[ -n $ntfy_password && -n $ntfy_token ]]; then 152 | echo "Use ntfy_username and ntfy_password OR ntfy_token" 153 | exit 1 154 | elif [ -n "$ntfy_password" ]; then 155 | ntfy_base64=$( echo -n "$ntfy_username:$ntfy_password" | base64 ) 156 | ntfy_auth="Authorization: Basic $ntfy_base64" 157 | elif [ -n "$ntfy_token" ]; then 158 | ntfy_auth="Authorization: Bearer $ntfy_token" 159 | else 160 | ntfy_auth="" 161 | fi 162 | curl -H "$ntfy_auth" -H tags:"$ntfy_tag" -H "X-Icon: $ntfy_icon" -H "X-Title: $ntfy_title" -d "$push_message" $ntfy_url > /dev/null 163 | fi 164 | if [ -n "$pushover_app_token" ]; then 165 | curl -s -F "token=$pushover_app_token" -F "user=$pushover_user_token" -F "message=$push_message" https://api.pushover.net/1/messages 166 | fi 167 | fi 168 | } 169 | 170 | check_push_added() { 171 | push_file="/tmp/read_notify_added.tmp" 172 | # Replace _ with space. 173 | sed -i 's/_/ /g' /tmp/read_notify_added.tmp 2>/dev/null 174 | push_message=$( cat /tmp/read_notify_added.tmp 2>/dev/null ) 175 | ntfy_tag="$ntfy_added_tag" 176 | check_push 177 | } 178 | 179 | check_push_deleted() { 180 | push_file="/tmp/read_notify_deleted.tmp" 181 | # Replace _ with space. 182 | sed -i 's/_/ /g' /tmp/read_notify_added.tmp 2>/dev/null 183 | push_message=$( cat /tmp/read_notify_deleted.tmp 2>/dev/null ) 184 | ntfy_tag="$ntfy_deleted_tag" 185 | check_push 186 | } 187 | 188 | cleanup() { 189 | # Clean up tables 190 | libr_path=() 191 | for libr in "${library[@]}"; do 192 | if [ -d "$libr" ]; then 193 | libr_path+=("$(echo $libr | tr / _ | sed 's/[^[:alnum:]_]//g')") 194 | fi 195 | done 196 | for tbl in $(sqlite3 "$dbpath" ". tables"); do 197 | if [[ ! "${libr_path[*]}" =~ ${tbl} ]]; then 198 | sqlite3 "$dbpath" "DROP TABLE $tbl;" 199 | fi 200 | done 201 | 202 | # Clean up records 203 | for libr in "${library[@]}"; do 204 | if [ -d "$libr" ]; then 205 | readarray -d '\n' libr_name < <(find "$libr" -mindepth 1 -maxdepth 1 -type d -printf '%P\n') 206 | for arr_line in "${libr_name[@]}"; do 207 | libr_name_clean+=$( echo "$arr_line" | tr -s '[:blank:]' '_' | sed 's/[^[:alnum:]_]//g' ) 208 | done 209 | tbl_name=$(echo "$libr" | tr / _ | sed 's/[^[:alnum:]_]//g') 210 | for name in $(sqlite3 "$dbpath" "SELECT name FROM $tbl_name"); do 211 | if [[ ! "${libr_name_clean[*]}" =~ ${name} ]]; then 212 | realname=$( sqlite3 "$dbpath" "SELECT realname FROM $tbl_name WHERE name = \"$name\"" ) 213 | sqlite3 "$dbpath" "DELETE FROM $tbl_name WHERE name = \"$name\"" 214 | echo "$realname $push_deleted" >> /tmp/read_notify_deleted.tmp 215 | fi 216 | done 217 | fi 218 | done 219 | } 220 | 221 | find_gaps() { 222 | ls -1 "$tbl"/"$dir"/{*.cbz,*.ignore} > /tmp/read_notify_missing.first.tmp 2>/dev/null 223 | oIFS=$IFS 224 | while IFS="" read -r wholeline || [ -n "$p" ] ; do 225 | rm -f /tmp/read_notify_missing.work.tmp 2>/dev/null 226 | only_name=${wholeline##*/} 227 | # Echo line, split numbers and chars on different lines, remove everything except numbers, remove leading 0s. 228 | printf '%s\n' "$only_name" | grep -Eo '[[:alpha:]]+|[0-9]+' | grep -x '[0-9][0-9]*' | sed -e 's:^0*::' | sed '/^\s*$/d' >> /tmp/read_notify_missing.work.tmp 229 | number_last=$( cat /tmp/read_notify_missing.work.tmp | tail -n 1 ) 230 | number_lines=$( cat /tmp/read_notify_missing.work.tmp | wc -l ) 231 | # See if we can find the string Chapter, and grab the number from there. 232 | if echo "$only_name" | grep -q "[Cc][Hh][Aa][Pp][Tt][Ee][Rr]"; then 233 | # Check if Chapter is in the name more than once. The right one is probably the first in that case. 234 | if echo "$only_name" | grep -q '\('"Chapter"'\).*\1' ; then 235 | only_name_minus_chapter=$( echo "$only_name" | sed -E 's/(.*)Chapter/\1NOPE/' ) 236 | new_chapnr=$( echo "$only_name_minus_chapter" | sed -nr '/[Cc][Hh][Aa][Pp][Tt][Ee][Rr][\._ ][0-9]+/ s/.*[Cc][Hh][Aa][Pp][Tt][Ee][Rr][\._ ]+([0-9]+).*/\1/p' ) 237 | # If empty, use the old nr. 238 | if [ -z "$new_chapnr" ]; then 239 | echo "$number_last" >> /tmp/read_notify_missing.second.tmp 240 | else 241 | echo "$new_chapnr" >> /tmp/read_notify_missing.second.tmp 242 | fi 243 | else 244 | new_chapnr=$( echo "$only_name" | sed -nr '/[Cc][Hh][Aa][Pp][Tt][Ee][Rr][\._ ][0-9]+/ s/.*[Cc][Hh][Aa][Pp][Tt][Ee][Rr][\._ ]+([0-9]+).*/\1/p' ) 245 | if [ -z "$new_chapnr" ]; then 246 | echo "$number_last" >> /tmp/read_notify_missing.second.tmp 247 | else 248 | echo "$new_chapnr" >> /tmp/read_notify_missing.second.tmp 249 | fi 250 | fi 251 | else 252 | # Check if there are more than two numbers, which indicates that there are numbers in the chapter name. 253 | if [ "$number_lines" -gt "2" ]; then 254 | new_chapnr=$( echo "$only_name" | sed -nr '/[Cc][Hh][Aa][Pp][Tt][Ee][Rr][\. ][0-9]+/ s/.*[Cc][Hh][Aa][Pp][Tt][Ee][Rr][\. ]+([0-9]+).*/\1/p' ) 255 | # If empty, use the old nr. 256 | if [ -z "$new_chapnr" ]; then 257 | echo "$number_last" >> /tmp/read_notify_missing.second.tmp 258 | else 259 | echo "$new_chapnr" >> /tmp/read_notify_missing.second.tmp 260 | fi 261 | else 262 | echo "$number_last" >> /tmp/read_notify_missing.second.tmp 263 | fi 264 | fi 265 | done < /tmp/read_notify_missing.first.tmp 266 | IFS=$oIFS 267 | # Remove duplicates and sort 268 | if [ -f "/tmp/read_notify_missing.second.tmp" ]; then 269 | cat /tmp/read_notify_missing.second.tmp | awk '!seen[$0]++' | sort -V > /tmp/read_notify_missing.second.tmp.1 270 | mv /tmp/read_notify_missing.second.tmp.1 /tmp/read_notify_missing.second.tmp 271 | seq $(head -n1 /tmp/read_notify_missing.second.tmp) $(tail -n1 /tmp/read_notify_missing.second.tmp) | grep -vwFf /tmp/read_notify_missing.second.tmp - >> /tmp/read_notify_missing.tmp 272 | # Chapter 1 should exist, so check for that specifically. 273 | if ! grep -qcE '^1([^0-9]|$)' /tmp/read_notify_missing.second.tmp ; then 274 | echo "1" >> /tmp/read_notify_missing.tmp 275 | fi 276 | fi 277 | rm -f /tmp/read_notify_missing.first.tmp 278 | rm -f /tmp/read_notify_missing.second.tmp 279 | if [ -s "/tmp/read_notify_missing.tmp" ]; then 280 | if [ -f "/tmp/read_notify_added.tmp" ]; then 281 | echo "These $push_type are missing from $dir" >> /tmp/read_notify_added.tmp 282 | cat /tmp/read_notify_missing.tmp >> /tmp/read_notify_added.tmp 283 | else 284 | echo "These $push_type are missing from $dir" >> /tmp/read_notify_deleted.tmp 285 | cat /tmp/read_notify_missing.tmp >> /tmp/read_notify_deleted.tmp 286 | fi 287 | rm -f /tmp/read_notify_missing.tmp 288 | fi 289 | } 290 | 291 | 292 | check_lock 293 | 294 | if [[ "$1" == "search_missing" ]]; then 295 | for tbl in "${library[@]}"; do 296 | if [ -d "$tbl" ]; then 297 | for di in "$tbl"/*/ ; do 298 | # Remove path and trailing slash 299 | dir=$( echo "$di" | sed "s|$tbl/||g" | sed 's:/*$::' ) 300 | if [ ! -f "$dir".ignore_gaps ] ; then 301 | find_gaps 302 | fi 303 | done 304 | fi 305 | done 306 | check_push_deleted 307 | rm -f /tmp/read_notify_added.tmp 2>/dev/null 308 | rm -f /tmp/read_notify_deleted.tmp 2>/dev/null 309 | rm -f /tmp/read_notify.lock 310 | exit 0 311 | fi 312 | 313 | sqlite3 "$dbpath" "VACUUM;" 314 | 315 | check_chapters 316 | cleanup 317 | check_push_added 318 | check_push_deleted 319 | rm -f /tmp/read_notify_added.tmp 2>/dev/null 320 | rm -f /tmp/read_notify_deleted.tmp 2>/dev/null 321 | 322 | rm -f /tmp/read_notify.lock 323 | 324 | exit 0 325 | 326 | -------------------------------------------------------------------------------- /readarr.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # load env file from $NTFY_ENV or script dir 4 | SCRIPTPATH=${NTFY_ENV:-$(dirname "$0")/.env} 5 | [ -f ${SCRIPTPATH} ] && . "${SCRIPTPATH}" || echo "ENV missing: ${SCRIPTPATH}" 6 | 7 | if [[ -n $ntfy_password && -n $ntfy_token ]]; then 8 | echo "Use ntfy_username and ntfy_password OR ntfy_token" 9 | exit 1 10 | elif [ -n "$ntfy_password" ]; then 11 | ntfy_base64=$( echo -n "$ntfy_username:$ntfy_password" | base64 ) 12 | ntfy_auth="Authorization: Basic $ntfy_base64" 13 | elif [ -n "$ntfy_token" ]; then 14 | ntfy_auth="Authorization: Bearer $ntfy_token" 15 | else 16 | ntfy_auth="" 17 | fi 18 | 19 | ntfy_title=$readarr_author_name 20 | ntfy_message=" " 21 | if [ "$readarr_eventtype" == "Test" ]; then 22 | ntfy_tag=information_source 23 | ntfy_title="Testing" 24 | elif [ "$readarr_eventtype" == "Download" ]; then 25 | ntfy_title+=" - " 26 | ntfy_title+=$readarr_book_title 27 | ntfy_tag=headphones 28 | elif [ "$readarr_eventtype" == "ApplicationUpdate" ]; then 29 | ntfy_tag=arrow_up 30 | ntfy_message+="Readarr updated from " 31 | ntfy_message+=$readarr_update_previousversion 32 | ntfy_message+=" to " 33 | ntfy_message+=$readarr_update_newversion 34 | # ntfy_message+=" - Readarr message: " 35 | # ntfy_message+=$readarr_update_message 36 | elif [ "$readarr_eventtype" == "HealthIssue" ]; then 37 | ntfy_tag=warning 38 | ntfy_message+=$readarr_health_issue_message 39 | elif [ "$readarr_eventtype" == "Test" ]; then 40 | ntfy_tag=information_source 41 | fi 42 | 43 | if [ "$readarr_eventtype" == "Download" ]; then 44 | ntfy_post_data() 45 | { 46 | cat <<EOF 47 | { 48 | "topic": "$readarr_ntfy_topic", 49 | "tags": ["$ntfy_tag"], 50 | "icon": "$readarr_ntfy_icon", 51 | "title": "Readarr: $readarr_eventtype", 52 | "message": "$ntfy_title$ntfy_message", 53 | "actions": [ 54 | { 55 | "action": "view", 56 | "label": "Goodreads", 57 | "url": "https://www.goodreads.com/book/show/$readarr_book_grid", 58 | "clear": true 59 | } 60 | ] 61 | } 62 | EOF 63 | } 64 | else 65 | ntfy_post_data() 66 | { 67 | cat <<EOF 68 | { 69 | "topic": "$readarr_ntfy_topic", 70 | "tags": ["$ntfy_tag"], 71 | "icon": "$readarr_ntfy_icon", 72 | "title": "Readarr: $readarr_eventtype", 73 | "message": "$ntfy_title$ntfy_message" 74 | } 75 | EOF 76 | } 77 | fi 78 | 79 | curl -H "Accept: application/json" \ 80 | -H "Content-Type:application/json" \ 81 | -H "$ntfy_auth" -X POST --data "$(ntfy_post_data)" $ntfy_url 82 | -------------------------------------------------------------------------------- /sabnzbd.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # load env file from $NTFY_ENV or script dir 4 | SCRIPTPATH=${NTFY_ENV:-$(dirname "$0")/.env} 5 | [ -f ${SCRIPTPATH} ] && . "${SCRIPTPATH}" || echo "ENV missing: ${SCRIPTPATH}" 6 | 7 | if [[ -n $ntfy_password && -n $ntfy_token ]]; then 8 | echo "Use ntfy_username and ntfy_password OR ntfy_token" 9 | exit 1 10 | elif [ -n "$ntfy_password" ]; then 11 | ntfy_base64=$( echo -n "$ntfy_username:$ntfy_password" | base64 ) 12 | ntfy_auth="Authorization: Basic $ntfy_base64" 13 | elif [ -n "$ntfy_token" ]; then 14 | ntfy_auth="Authorization: Bearer $ntfy_token" 15 | else 16 | ntfy_auth="" 17 | fi 18 | 19 | if [ "$1" == "warning" ]; then 20 | ntfy_tag=warning 21 | elif [ "$1" == "error" ]; then 22 | ntfy_tag=rotating_light 23 | else 24 | ntfy_tag=information_source 25 | fi 26 | 27 | curl -H "$ntfy_auth" \ 28 | -H tags:$ntfy_tag \ 29 | -H "X-Title: $2" \ 30 | -H "X-Icon: $sabnzbd_ntfy_icon" \ 31 | -d "$3" \ 32 | --request POST "$ntfy_url/$sabnzbd_ntfy_topic" 33 | -------------------------------------------------------------------------------- /sonarr.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # load env file from $NTFY_ENV or script dir 4 | SCRIPTPATH=${NTFY_ENV:-$(dirname "$0")/.env} 5 | [ -f ${SCRIPTPATH} ] && . "${SCRIPTPATH}" || echo "ENV missing: ${SCRIPTPATH}" 6 | 7 | # Function to retrieve the banner image for a series from Sonarr API. Parameters are taken from env file 8 | # Outputs series banner_image URL for use in ntfy_post_data() 9 | get_banner_image() 10 | { 11 | headers_file=$(mktemp) 12 | response_file=$(mktemp) 13 | 14 | response_body=$(curl -s -D "$headers_file" -o "$response_file" -H "Content-Type: application/json" -H "X-Api-Key: $sonarr_api_key" "$sonarr_url/api/v3/series/$sonarr_series_id") 15 | response_code=$(grep HTTP/ "$headers_file" | awk '{print $2}') # get the status code from the headers 16 | if [[ $response_code -ne 200 ]]; then 17 | >&2 echo "Failed to retrieve banner image. Response code $response_code" 18 | exit 1 19 | fi 20 | banner_image=$(jq -r '.images[0].remoteUrl // empty' "$response_file") 21 | if [[ -z "$banner_image" ]]; then 22 | >&2 echo "Banner image not found" 23 | exit 2 24 | fi 25 | rm "$headers_file" "$response_file" 26 | } 27 | 28 | # Check if ntfy_username and ntfy_password OR ntfy_token are set. 29 | # If both are set, print an error message and exit with code 1 30 | # If only one is set, set the ntfy_auth variable accordingly 31 | if [[ -n $ntfy_password && -n $ntfy_token ]]; then 32 | >&2 echo "Use ntfy_username and ntfy_password OR ntfy_token" 33 | exit 3 34 | elif [ -n "$ntfy_password" ]; then 35 | ntfy_base64=$( echo -n "$ntfy_username:$ntfy_password" | base64 ) 36 | ntfy_auth="Authorization: Basic $ntfy_base64" 37 | elif [ -n "$ntfy_token" ]; then 38 | ntfy_auth="Authorization: Bearer $ntfy_token" 39 | else 40 | ntfy_auth="" 41 | fi 42 | 43 | # Sets the appropriate values for the notification title, message, and tag based on the Sonarr event type. 44 | ntfy_title="$sonarr_series_title" 45 | ntfy_message=" " 46 | if [ "$sonarr_eventtype" == "Test" ]; then 47 | ntfy_tag=information_source 48 | ntfy_title="Testing" 49 | elif [ "$sonarr_eventtype" == "Grab" ]; then 50 | get_banner_image # Call get_banner_image function to retrieve the banner image for the series 51 | ntfy_tag=tv 52 | ntfy_title="$ntfy_title - S$sonarr_release_seasonnumber:E$sonarr_release_episodenumbers" 53 | ntfy_message="- $sonarr_release_episodetitles [$sonarr_release_quality]" 54 | elif [ "$sonarr_eventtype" == "Download" ]; then 55 | get_banner_image # Call get_banner_image function to retrieve the banner image for the series 56 | ntfy_tag=tv 57 | ntfy_title="$ntfy_title - S$sonarr_episodefile_seasonnumber:E$sonarr_episodefile_episodenumbers" 58 | ntfy_message="- $sonarr_episodefile_episodetitles [$sonarr_episodefile_quality]" 59 | elif [ "$sonarr_eventtype" == "ApplicationUpdate" ]; then 60 | ntfy_tag=arrow_up 61 | ntfy_message="Sonarr updated from $sonarr_update_previousversion to $sonarr_update_newversion" 62 | elif [ "$sonarr_eventtype" == "HealthIssue" ]; then 63 | ntfy_tag=warning 64 | ntfy_message="$sonarr_health_issue_message" 65 | else 66 | ntfy_tag=information_source 67 | fi 68 | 69 | # Generates JSON data for sending to ntfy. 70 | # If the value of "sonarr_eventtype" is "Download", include additional fields for attaching a banner image and a link to the TVDB website. 71 | # Otherwise, generate JSON data without these additional fields. 72 | if [[ "$sonarr_eventtype" == "Download" || "$sonarr_eventtype" == "Grab" ]]; then 73 | ntfy_post_data() 74 | { 75 | cat <<EOF 76 | { 77 | "topic": "$sonarr_ntfy_topic", 78 | "tags": ["$ntfy_tag"], 79 | "icon": "$sonarr_ntfy_icon", 80 | "attach": "$banner_image", 81 | "title": "Sonarr: $sonarr_eventtype", 82 | "message": "$ntfy_title$ntfy_message", 83 | "actions": [ 84 | { 85 | "action": "view", 86 | "label": "TVDB", 87 | "url": "https://www.thetvdb.com/dereferrer/series/$sonarr_series_tvdbid", 88 | "clear": true 89 | } 90 | ] 91 | } 92 | EOF 93 | } 94 | else 95 | ntfy_post_data() 96 | { 97 | cat <<EOF 98 | { 99 | "topic": "$sonarr_ntfy_topic", 100 | "tags": ["$ntfy_tag"], 101 | "icon": "$sonarr_ntfy_icon", 102 | "title": "Sonarr: $sonarr_eventtype", 103 | "message": "$ntfy_title$ntfy_message" 104 | } 105 | EOF 106 | } 107 | fi 108 | 109 | #Sends a POST request to ntfy with the JSON data generated by ntfy_post_data. 110 | response=$(curl -s -o /dev/null -w "%{http_code}" -H "Accept: application/json" \ 111 | -H "Content-Type:application/json" \ 112 | -H "$ntfy_auth" -X POST --data "$(ntfy_post_data)" "$ntfy_url") 113 | 114 | # If the response code is 200, the notification was sent successfully. 115 | # Otherwise, print an error message and exit with code 4. 116 | if [[ "$response" -eq 200 ]]; then 117 | echo "Notification sent successfully" 118 | else 119 | >&2 echo "Failed to send notification. Response code: $response" 120 | exit 4 121 | fi 122 | -------------------------------------------------------------------------------- /temperature.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # load env file from $NTFY_ENV or 'env' in script dir 4 | SCRIPTPATH=${NTFY_ENV:-$(dirname "$0")/.env} 5 | [ -f ${SCRIPTPATH} ] && . "${SCRIPTPATH}" || echo "ENV missing: ${SCRIPTPATH}" 6 | 7 | # This script checks the current temperature of all available processors (CPU, GPU, etc.) 8 | # on a Linux host (in this case Raspbian) and if it is over 65 degrees Celsius, 9 | # sends a notification to the ntfy.sh topic defined in .env. 10 | 11 | ###################################################################### 12 | # Configurations 13 | ###################################################################### 14 | ntfy_url="$ntfy_url/MY_TOPIC" 15 | ntfy_title="Temperature Alert" 16 | ntfy_tag="thermometer" 17 | ntfy_icon="" 18 | 19 | # Temperature threshold in Celsius 20 | TEMP_THRESHOLD=65 21 | 22 | ###################################################################### 23 | # Script starts here 24 | ###################################################################### 25 | 26 | # Check if vcgencmd is installed, if not install it 27 | if ! command -v vcgencmd &> /dev/null; then 28 | echo "vcgencmd could not be found, installing..." 29 | sudo apt update && sudo apt install -y vcgencmd 30 | fi 31 | 32 | # Check if bc is installed, if not install it 33 | if ! command -v bc &> /dev/null; then 34 | echo "bc could not be found, installing..." 35 | sudo apt update && sudo apt install -y bc 36 | fi 37 | 38 | check_temperature() { 39 | local temp 40 | temp=$(vcgencmd measure_temp | grep -oP '\d+\.\d+') 41 | if (( $(echo "$temp > $TEMP_THRESHOLD" | bc -l) )); then 42 | echo "Temperature is above threshold: $temp°C" 43 | send_notification "$temp" 44 | else 45 | echo "Temperature is within safe limits: $temp°C" 46 | fi 47 | } 48 | 49 | send_notification() { 50 | local temp=$1 51 | local message="Warning: Processor temperature is $temp°C, which is above the threshold of $TEMP_THRESHOLD°C." 52 | 53 | if [ -n "$ntfy_url" ]; then 54 | curl -H "tags:$ntfy_tag" -H "X-Icon: $ntfy_icon" -H "X-Title: $ntfy_title" -d "$message" $ntfy_url > /dev/null 55 | fi 56 | } 57 | 58 | check_temperature 59 | 60 | exit 0 --------------------------------------------------------------------------------