Mobile push notifications for Bash and Powershell (for free!)

In this post I am going to show you how to get mobile-phone style push-notifications for your scripts. And what’s more it doesn’t need to cost you anything.

I have quite a few servers at home and it’s often useful to be able to monitor them and get alerts. There are plenty of monitoring products out there but they are more than a bit heavy-duty for my purposes.

Now, many guides online talk about setting up a mail server to handle notifications, especially on Linux. I really don’t like that option for a number of reasons:

  1. Running a mail server is just another point of failure in the monitoring-chain.
  2. A mail server is a potential (unnecessary) security-risk
  3. Since many ISP’s (wisely) block SMTP port 25 etc, being able to forward mail to an actual mail-provider like gmail isn’t going to be easy.
  4. Email is a poor notification method because emails tend to get lost in the noise of all the other emails.

So I had a look around for a push notification system and found a nice one from Notify17.
Better yet, they have a “free-forever” plan which gives you 75 free notifications each month. Sounds perfect for my home environment of 8 servers!

I also had some early issues which turned out to be my script rather than the service. When I first raised the issue I did so through the Notify17 Feedback system and got a reply within about 5 minutes! So a very responsive developer! Awesome.
I also found the documentation to be surprisingly good too!

Make sure that you sign-up and download the mobile app before starting! 🙂

POST Method

The main page gives you the syntax for triggering an alert under Linux using curl in a Bash script:

Linux

#!/usr/bin/env bash

curl -X POST \
    "https://hook.notify17.net/api/raw/RAW_API_KEY" \
    -F title="Instance setup: finished" \
    -F content="Instance ${HOSTNAME} has been set up correctly!"

Just replace “RAW_API_KEY” with your key found in your Notify17 account dashboard.
You can generate many different API keys for different purposes.

Powershell

We can easily adapt this for Powershell:

$TITLE = "Instance setup: finished"
$CONTENT = "Instance $(HOSTNAME) has been set up correctly!"

$Url = "https://hook.notify17.net/api/raw/RAW_API_KEY"
$Body = @{
    title = $TITLE
    content = $CONTENT
}
Invoke-RestMethod -Method 'Post' -Uri $url -Body $body

If all goes well you’ll get a HTTP 200 return code (success) in bash and Powershell will return:

  ok data
  -- ----
True queued

and your notifications will look something like these:

What’s clear here is that we have two parameters into which we can pass payload data:

  1. Title
  2. Content

We should probably keep the title short so it renders well on mobile devices.
However, we can probably put what we want in the content field. But if we want to pass multiple variables to the content field then we might want to use a template.

Templates allow you to pre-define the notification-layout and stipulate the place-holders for variables. This saves you having to put all that stuff in every script and then having to update scripts all the time.

Each template has its own unique URL and the ability to have trigger conditions on the content of payloads e.g. we could look for a payload of “CPU” and trigger if it was greater than a value of “95”.

Next, let’s work on a real world example to monitor CPU temperature on Linux.

Monitoring CPU temperature

First of all we need to figure out a way to measure CPU temperature via the Operating System.

There are a lot of ways to do this under Linux: some of them require the lm-sensors package to be installed and Raspberry Pi typically requires the vcgencmd command.
I, however, wanted something that I could run on any (Linux) system without having to modify it. As such, I settled on pulling the content of:

/sys/class/thermal/thermal_zone0/temp

However, if you just pull that data using cat, for example, then you get back a rather unfriendly number:

luis@pi-node4:~$ cat /sys/class/thermal/thermal_zone0/temp
37972

I then stole/borrowed a nice one-liner from the Internet that uses Perl to get us a better format that we can use:

luis@pi-node4:~$ (perl -e 'm/(\d+)/; $x=$1; s/\d+//; printf ("%.1f", ( $x / 1000))' -p /sys/class/thermal/thermal_zone0/temp)
37.5

As you can see this gives us a floating point number but I want an integer because later-on I’ll want to execute an arithmetical operational. So I’ll convert that floating point number by storing it into a variable called:

$currenttemperaturefloat

And in a second step convert that to an integer (whole number):

currenttemperaturefloat=$(/usr/bin/perl -e 'm/(\d+)/; $x=$1; s/\d+//; printf ("%.1f", ( $x / 1000))' -p /sys/class/thermal/thermal_zone0/temp)
currenttemperatureint=${currenttemperaturefloat%.*}

Looping the loop

Now we just need a simple piece of logic to check whether the value of $currenttemperatureint is equal to or greater than the threshold value we want.
In this case, I think that 70 degrees Celsius is a good threshold.

if [ $currenttemperatureint -ge 70 ]
then
		curl -X POST "https://hook.notify17.net/api/raw/API_RAW_KEY" -F title="$TITLE" -F content="$CONTENT"

fi

The entire basic script is below in 16 lines of actual code (excluding white-spaces, of course).
In it we are doing the following (in this order):

  1. Set our environment to bash
  2. Get and store the hostname
  3. Get the CPU temperature
  4. Convert the CPU temperature to an integer
  5. Set the payload for the body of the HTTP POST
  6. Set the payload for the content of the HTTP POST
  7. Check if temperature is equal or greater than 70 and if so run the curl command
  8. Optional: write out the date to a file so we can tell if the script ran
  9. Clean up our variables
#!/usr/bin/bash

currenthost=$(hostname)
currenttemperaturefloat=$(/usr/bin/perl -e 'm/(\d+)/; $x=$1; s/\d+//; printf ("%.1f", ( $x / 1000))' -p /sys/class/thermal/thermal_zone0/temp)
currenttemperatureint=${currenttemperaturefloat%.*}

TITLE="Temperature alert for $currenthost"
CONTENT="Temperature is currently $currenttemperatureint degrees Celcius"


if [ $currenttemperatureint -ge 70 ]
then
	curl -X POST "https://hook.notify17.net/api/raw/JV-TpZimZnUuOoemG_Fe4glyoPCX3JiITaKSdOxjNv0" -F title="$TITLE" -F content="$CONTENT"
	date >> /home/luis/execution.log

fi


unset currenthost
unset currenttemperaturefloat
unset currenttemperatureint
unset TITLE
unset CONTENT

Automating with cron

This script won’t be much use unless we can automate its execution and for that we’re going to use cron.
Nothing in the script requires elevated (root) privileges and so we can create a crontab for the current user:

crontab -e

I am going to run this every 30 minutes.
I would prefer an interval of 10-15 minutes but since this method is not stateful then we’ll get hammered by alerts if we trip the threshold. This is unhelpful and will easily chew through our free Notify17 credits.
See the Conclusion section at the end for more on this!

Your cron file should look something like this:

SHELL=/usr/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin

*/30* * * * /home/luis/temperature-alerts.sh

Now copy the script to a directory that the user has access to and then make sure to make the script file executable with

chmod +x temperature-alerts.sh

For initial testing you may want to modify your script to trigger on a much lower threshold; that way you can perform a positive-test to ensure that it’s actually working.
Also step 8 from the script allows us to see when/if cron has run the script.

To test the job I temporarily set my threshold to 40 degrees:

Conclusion & coming next

There you have it: a simple way to take any script output and put it into a payload notifying on your mobile phone.

Note that this is a first iteration to get something quick and dirty but that works.
While working on this method I was aware that being stateless was going to be a potential issue: after all, suppose that I check for temperature every 15 minutes and we hit 75 degrees:

First I’ll get one alert and then 15 minutes later at the next “polling” I’ll get another alert if the temperature is still over the threshold of 70 degrees.
Getting repeated alerts for the same issue isn’t helpful as mentioned earlier.

Moreover, we’re also missing a notification for when the temperature value goes back down below our threshold (so we know that all is now well).

For that we need to store the last temperature value and do some conditional logic to compare it to the current temperature value and take action on that basis.
The conditional logic would look something like this:

#!/bin/bash


if [ "$currenttemperatureint" -ge 70 -a "$lasttemperature" -lt 70 ]
then
		echo "NOTIFICATION: ABOVE THRESHOLD"

elif [ "$currenttemperatureint" -ge 70 -a "$lasttemperature" -ge 70 ]
then
		echo "NOTIFICATION: DO-NOTHING"

elif [ "$lasttemperature" -ge 70 -a "$currenttemperatureint" -lt 70  ]
then
		echo "NOTIFICATION: ABOVE THRESHOLD"

fi

Note that this is not a functional script (and that’s intentional) because I don’t really want to try to handle state using bash scripts and I think that writing something in code (e.g. Python) is again a bit heavy-duty for what we’re trying to achieve here.

So the next step is to look at how I can post this data up to something like Azure Event Grid and use Azure “serverless” functions to do the conditional logic and then post to the Notify17 web API.

You might think that learning Azure services is more work or heavier-duty but, in fact, the whole point of Cloud based services (as opposed to just sticking Virtual Machines in the infrastructure up there) is to allow cloud providers to do the hard work.

Leave a Reply

Your email address will not be published. Required fields are marked *