Category: Docker

  • Where did all the vowels go?

    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