Bluebells in Smyrton Wood, reused from https://www.geograph.org.uk/photo/3977770

Where did all the vowels go?

Written by

in

, ,

Back in the early days of smartphones, Apple took the branding path of adding a lowercase ‘i’ to the front of everything. iPods, iPads, iPhones, iMacs – there became a proliferation of additional vowels for no good reason I could ever quite fathom.

It felt like an attempt at a bit of counter-culture, as everyone else was on a disemvoweling run – remember Flickr, twttr (oh the heady days!), Tumblr, and Scribd?

Anyway, the reason why I’m on this current reminisce, is because I was looking for a way to receive Nagios notifications on my phone nice & easily, something like an SMS (acronym, not disemvoweling in this case) – but without the associated costs, or a mobile app. I honestly toyed for a while with the idea of writing my own Android app, and learning yet another set of programming languages and protocols – and then I randomly came across ntfy.sh – and everything was suddenly SO. MUCH. SIMPLER!

The docker-compose.yaml file for setting up my own personal ntfy.sh server is insanely simple:

services:
  ntfy:
    container_name: ntfy
    hostname: ntfy
    image: binwiederhier/ntfy
    command:
      - serve
    environment:
      - TZ=Pacific/Auckland
    volumes:
      - ./var-cache-ntfy:/var/cache/ntfy
      - ./etc-ntfy:/etc/ntfy
    ports:
      - 8000:80
    restart: unless-stopped

And that was it done. The ntfy.sh Android app is small, and does exactly what I need (aforementioned iOS devices have their own one – but I can’t comment on that version) – which is to alert me to new notifications as soon as they arrive.

The next step was to write a Bash script to handle Nagios notifications and feed them out to my new ntfy.sh server. This one’s a bit bigger than others I’ve shared so far, because it’s doing a few things to make things pretty at the receiving end:

#!/usr/bin/env bash

# notify_via_ntfy.sh — Nagios script for sending alerts to Nagios
# Nagios macros available; https://assets.nagios.com/downloads/nagioscore/docs/nagioscore/3/en/macrolist.html
#
# -----------------------------------------------------------------------------
# Description:
#   This script takes a generated notification from Nagios, and sends it to a
#   ntfy.sh endpoint, while auto-tagging with tags, emoji, and priority based
#	on the values passed.
#
# Example usage:
#   ./notify_via_ntfy.sh \
#	 --notification-type "TEST" \
#	 --state "TEST" \
#	 --host "Example hostname" \
#	 --service "Test service description"
#	 --endpoint "http://127.0.0.1:8000/nagiosNotifications" \
#	 --output "Test output" \
#	 --timestamp "1970-01-01T00:00:00+0"
# 
# Example commands.cfg definition
# 	define command {
# 		command_name	notify-host-by-ntfy
# 		command_line	$USER2$/notify_via_ntfy.sh \
# 					--notification-type "$NOTIFICATIONTYPE$" \
# 					--state "$HOSTSTATE$" \
# 					--host "$HOSTNAME$" \
# 					--endpoint "$USER3$" \
# 					--output "$HOSTOUTPUT$ $LONGHOSTOUTPUT$" \
# 					--timestamp "$LONGDATETIME$"
# 	}
# 	define command {
# 		command_name	notify-service-by-ntfy
# 		command_line	$USER2$/notify_via_ntfy.sh \
# 					--notification-type "$NOTIFICATIONTYPE$" \
# 					--state "$SERVICESTATE$" \
# 					--host "$HOSTNAME$" \
# 					--service "$SERVICEDESC$" \
# 					--endpoint "$USER3$" \
# 					--output "$SERVICEOUTPUT$ $LONGSERVICEOUTPUT$" \
# 					--timestamp "$LONGDATETIME$"
# 	}
#
# -----------------------------------------------------------------------------
# Requirements:
#   • bash 4.x or later (uses associative arrays)
#   • curl
#
# -----------------------------------------------------------------------------
# Author:		Phil Tanner <phil@philtanner.com>
# Created:		2026-08
# Last updated: 	2026-08-21
#
# Licence: (MIT licence: https://mit-license.org/)
# Copyright © 2026 Phil Tanner
#
# Permission is hereby granted, free of charge,  to any person obtaining a copy
# of this software and associated documentation files (the “Software”), to deal
# in the Software without restriction,  including without limitation the rights
# to use, copy,  modify, merge,  publish, distribute,  sublicense,  and/or sell 
# copies of  the  Software,  and to  permit  persons to  whom  the  Software is
# furnished to do so, subject to the following conditions:
#
# The above  copyright notice  and this permission notice shall  be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED “AS IS”,  WITHOUT  WARRANTY OF ANY KIND,  EXPRESS OR
# IMPLIED,  INCLUDING  BUT NOT  LIMITED TO THE  WARRANTIES OF  MERCHANTABILITY,
# FITNESS FOR  A PARTICULAR PURPOSE  AND NONINFRINGEMENT.  IN  NO  EVENT  SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE  FOR ANY  CLAIM,  DAMAGES OR OTHER
# LIABILITY,  WHETHER IN  AN ACTION  OF CONTRACT,  TORT  OR OTHERWISE,  ARISING
# FROM,  OUT  OF  OR  IN  CONNECTION  WITH  THE  SOFTWARE OR THE  USE  OR OTHER
# DEALINGS IN THE SOFTWARE.
#
# -----------------------------------------------------------------------------
# Version: 1.0.0
# -----------------------------------------------------------------------------
#
# -----------------------------------------------------------------------------
# TODO:
#   • nothing \o/
# 
# -----------------------------------------------------------------------------

set -euo pipefail
# Default allow spaces in filenames/paths without quoting (just in case)
IFS=$'\n\t'


# Standard Nagios plugin return codes.
NAGIOS_STATUS_OK=0
NAGIOS_STATUS_WARNING=1
NAGIOS_STATUS_CRITICAL=2
NAGIOS_STATUS_UNKNOWN=3

# Variables for the outcome
NOTIFICATIONTYPE=""
HOSTNAME=""
STATE=""
ENDPOINT=""
ALERTTYPE="host"
OUTPUT=""
TIMESTAMP=""
SERVICE=""
BODY=""

usage()
{
	echo "NAME"
	echo "	$0"
	echo "SYNOPSIS"
	echo "	$0 "
	echo "DESCRIPTION"
	echo "	This script is a wrapper to send Nagios alerts to a"
	echo "	ntfy.sh endpoint, designed to be configured for host"
	echo "	or service level alerts, and have some standardised"
	echo "	emoji/tags based on the values passed."
	echo "	The options are as follows:"
	echo "		--notification-type <type>"
	echo "			The type of notification. This should be the"
	echo "			value held by the \$NOTIFICATIONTYPE\$ value."
	echo "			Valid options:"
	echo "			https://assets.nagios.com/downloads/nagioscore/docs/nagioscore/3/en/macrolist.html#notificationtype"
	echo "		--host <name>"
	echo "			The name of the host as the source of the"
	echo "			notification. Needed for host and service"
	echo "			notification types."
	echo "		--state <state>"
	echo "			The state of the host, or the service. Valid"
	echo "			options can be either a service state:"
	echo "			https://assets.nagios.com/downloads/nagioscore/docs/nagioscore/3/en/macrolist.html#servicestate"
	echo "			Or a host state:"
	echo "			https://assets.nagios.com/downloads/nagioscore/docs/nagioscore/3/en/macrolist.html#hoststate"
	echo "			depending upon the value of [type] argument"
	echo "		--endpoint <uri>"
	echo "			The ntfy.sh server and topic you want to send"
	echo "			notifications to."
	echo "		--output '<content>'"
	echo "			The output from the Nagios check. This will be"
	echo "			in the body of the ntfy message. Usually this"
	echo "			would expect to be either one of the Nagios macros"
	echo "			\$HOSTOUTPUT\$ or \$SERVICEOUTPUT\$"
	echo "		--timestamp '<timestamp>'"
	echo "			You can use the \$LONGDATETIME\$ macro here"
	echo "		--service '<name>'"
	echo "			The name of the service generating the notification"
	echo "			NOTE: Optional."
	echo "			  If not defined, the script assumes this is a"
	echo "			  notification about a host, NOT about a service."
	exit "${1:-$NAGIOS_STATUS_UNKNOWN}"
}

dedupe_csv()
{
	local csv="$1"
	local item

	local -a items=()
	local -A seen=()
	local -a unique=()

	IFS=',' read -ra items <<< "$csv"

	for item in "${items[@]}"; do
		if [[ -z "${seen[$item]+x}" ]]; then
			seen[$item]=1
			unique+=("$item")
		fi
	done

	IFS=','
	printf '%s' "${unique[*]}"
}

require_arg()
{
	if [[ $# -lt 2 || -z "$2" ]]; then
		echo "UNKNOWN - Option $1 requires an argument"
		exit $NAGIOS_STATUS_UNKNOWN
	fi
}

while [[ $# -gt 0 ]]; do
	case "$1" in
		--notification-type)
			require_arg "$@"
			NOTIFICATIONTYPE="$2"
			shift 2
			;;
		--host)
			require_arg "$@"
			HOSTNAME="$2"
			shift 2
			;;
		--state)
			require_arg "$@"
			STATE="$2"
			shift 2
			;;
		--endpoint)
			require_arg "$@"
			ENDPOINT="$2"
			shift 2
			;;
		--output)
			require_arg "$@"
			OUTPUT="$2"
			shift 2
			;;
		--timestamp)
			require_arg "$@"
			TIMESTAMP="$2"
			shift 2
			;;
		--service)
			SERVICE="$2"
			ALERTTYPE="service"
			shift 2
			;;
		--help|-h)
			usage "$NAGIOS_STATUS_OK"
			;;
		*)
			echo "UNKNOWN - Unknown argument: $1"
			usage
			;;
	esac
done

# Sense-check our variables
if [[ -z "$NOTIFICATIONTYPE" ]]; then
	echo "UNKNOWN - Notification type not specified"
	exit $NAGIOS_STATUS_UNKNOWN
fi
if [[ -z "$HOSTNAME" ]]; then
	echo "UNKNOWN - Host not specified"
	exit $NAGIOS_STATUS_UNKNOWN
fi
if [[ -z "$STATE" ]]; then
	echo "UNKNOWN - State not specified"
	exit $NAGIOS_STATUS_UNKNOWN
fi
if [[ -z "$ENDPOINT" ]]; then
	echo "UNKNOWN - Endpoint not specified"
	exit $NAGIOS_STATUS_UNKNOWN
fi
if [[ "$ALERTTYPE" == "service" && -z "$SERVICE" ]]; then
	echo "UNKNOWN - Service not specified"
	exit $NAGIOS_STATUS_UNKNOWN
fi


if [[ "${ALERTTYPE}" = "host" ]]; then
	TAGS="nagios,computer"

	# Host states: 	"UP", "DOWN", or "UNREACHABLE". 
	case $STATE in
		UP)
			TAGS="${TAGS},white_check_mark"
			;;
		DOWN)
			TAGS="${TAGS},x"
			;;
		UNREACHABLE)
			TAGS="${TAGS},grey_question"
			;;
		TEST)
			TAGS="${TAGS},hammer_and_wrench"
			;;
		*)
			echo "UNKNOWN - state not specified, or unknown value. Check if it is a host state."
			echo "https://assets.nagios.com/downloads/nagioscore/docs/nagioscore/3/en/macrolist.html#hoststate"
			exit $NAGIOS_STATUS_CRITICAL
			;;
	esac
	TITLE="${NOTIFICATIONTYPE} '${HOSTNAME}' ${STATE}"

elif [[ "${ALERTTYPE}" = "service" ]]; then
	TAGS="nagios,gear"

	# Service states: "OK", "WARNING", "UNKNOWN", or "CRITICAL"
	case $STATE in
		OK)
			TAGS="${TAGS},white_check_mark"
			;;
		WARNING)
			TAGS="${TAGS},warning"
			;;
		UNKNOWN)
			TAGS="${TAGS},grey_question"
			;;
		CRITICAL)
			TAGS="${TAGS},x"
			;;
		TEST)
			TAGS="${TAGS},hammer_and_wrench"
			;;
		*)
			echo "UNKNOWN - state not specified, or unknown value. Check if it is a service state."
			echo "https://assets.nagios.com/downloads/nagioscore/docs/nagioscore/3/en/macrolist.html#servicestate"
			exit $NAGIOS_STATUS_CRITICAL
			;;
	esac

	TITLE="${NOTIFICATIONTYPE} '${HOSTNAME}:${SERVICE}' ${STATE}"
	BODY="  

**Service:**  
${SERVICE}"

fi


# https://assets.nagios.com/downloads/nagioscore/docs/nagioscore/3/en/macrolist.html#notificationtype
case $NOTIFICATIONTYPE in
	PROBLEM)
		STATUS_ICON="arrow_heading_down"
		PRIORITY=5
		;;
	RECOVERY)
		STATUS_ICON="arrow_heading_up"
		PRIORITY=4
		;;
	ACKNOWLEDGEMENT)
		STATUS_ICON="raising_hand"
		PRIORITY=2
		;;
	FLAPPINGSTART)
		STATUS_ICON="arrow_up_down"
		PRIORITY=3
		;;
	FLAPPINGSTOP)
		STATUS_ICON="ballot_box_with_check"
		PRIORITY=3
		;;
	FLAPPINGDISABLED)
		STATUS_ICON="no_entry_sign"
		PRIORITY=2
		;;
	DOWNTIMESTART)
		STATUS_ICON="clock10"
		PRIORITY=1
		;;
	DOWNTIMEEND)
		STATUS_ICON="clock2"
		PRIORITY=1
		;;
	DOWNTIMECANCELLED)
		STATUS_ICON="clock6"
		PRIORITY=1
		;;
	TEST)
		STATUS_ICON="hammer_and_wrench"
		PRIORITY=1
		;;

	*)
		echo "UNKNOWN - notification type not specified, or unknown value"
		exit $NAGIOS_STATUS_CRITICAL
		;;
esac
TAGS="${TAGS},${STATUS_ICON}"

# Remove any doubled up tags
TAGS=$(dedupe_csv "$TAGS")

BODY="**Additional Info:**  
${OUTPUT}  

**Notification Type:**  
${NOTIFICATIONTYPE}  

**Hostname:**  
${HOSTNAME}  

**Status:**  
${STATE}${BODY}  

**Date:**  
${TIMESTAMP}"

# Send our notification out to our ntfy.sh endpoint
CURL_ERROR=$(mktemp)
if HTTP_STATUS=$(
	/usr/bin/curl \
		--write-out '%{http_code}' \
		--output /dev/null \
		--silent \
		--show-error \
		-w '%{http_code}' \
		-H "Title: ${TITLE}" \
		-H "Priority: ${PRIORITY}" \
		-H "X-Tags: ${TAGS}" \
		-H "X-Markdown: yes" \
		-d "${BODY}" \
		"$ENDPOINT" \
		2>"$CURL_ERROR"
); then

	if [[ "$HTTP_STATUS" =~ ^2[0-9][0-9]$ ]]; then
		rm -f "$CURL_ERROR"
		exit $NAGIOS_STATUS_OK
	else
		echo "UNKNOWN - curl failed: $(<"$CURL_ERROR")"
		rm -f "$CURL_ERROR"
		exit $NAGIOS_STATUS_UNKNOWN
	fi

else
	echo "UNKNOWN - curl failed to contact ntfy"
	exit $NAGIOS_STATUS_UNKNOWN
fi

rm -f "$CURL_ERROR"

# If we've gotten here, we did something wrong since we should've handled everything above
echo 'Script failed, manual intervention required.'
exit $NAGIOS_STATUS_UNKNOWN

In the comments at the top of the bash script, you can see the definitions that need to be added to the Nagios commands.cfg file. I have also stored the URL for my ntfy.sh server as $USER3$ in my resource.cfg file (and $USER2$ points to my custom scripts location for Nagios – which I think is the default).

Now I can receive Nagios alerts direct to my mobile phone, pretty much instantaneously. And find out whether or not the Pentagon has just ordered a bucket load of pizza….

Fediverse reactions

Comments

One response to “Where did all the vowels go?”

  1. 64 Islands Aroha Cooperative avatar

Leave a Reply

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