Skip to content

Instantly share code, notes, and snippets.

@henri
Last active October 1, 2025 21:30
Show Gist options
  • Select an option

  • Save henri/a84ef7671c59b1db02799aada5fb31ed to your computer and use it in GitHub Desktop.

Select an option

Save henri/a84ef7671c59b1db02799aada5fb31ed to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
PATH=/bin:/usr/bin
#
# (C)Copyright 2025 Henri Shustak
#
# Licence : GNU GPLv3 or later
# https://www.gnu.org/licenses/gpl-3.0.html
#
#
# About : Check if a specific port is up or down on a specific host
#
# This script will send an email on port state changes.
# Port is now up (but was down) or is now down (but was up) will trigger such an email.
#
# It is easy to modify this script so that rather than sending a message via mailx
# some other action, script or notification takes place. Modify to make it work for you.
#
# Known issues :
# The script uptime reporting is jankey only calculates human readable output up to days.
# That could be improved greatly.
#
# Version History :
#
# version 1.0 - initial release
#
##
## Variables
##
# allow varable setting via export - set to false if you want to diable enviroment varable overides
export_varable_overide_allowed="false"
evoa=$export_varable_overide_allowed
# able to overwritten via export - base variables
if [[ -z $host_to_check ]] || [[ "$evoa" == "false" ]] ; then host_to_check=localhost ; fi
if [[ -z $local_port_to_check ]] || [[ "$evoa" == "false" ]] ; then local_port_to_check=8787 ; fi
if [[ -z $interval_check_seconds ]] || [[ "$evoa" == "false" ]] ; then interval_check_seconds=5 ; fi
# base varables
state_change_file_up="/tmp/port-$host_to_check:$local_port_to_check-$BASHPID-state.up"
state_change_file_down="/tmp/port-$host_to_check:$local_port_to_check-$BASHPID-state.down"
host=$(hostname)
nc_status_current="0" # port up
nc_status_previous="unset" # port not set
port_status_email_string=""
last_state_change_epoch=0
current_epoch=$(date +%s)
last_state_change_epoch=$current_epoch
monitor_script_start_epoch=$current_epoch
debug_status="true" # disable for less output
# email varables
if [[ -z $recipient ]] || [[ "$evoa" == "false" ]] ; then recipient="[email protected]" ; fi
if [[ -z $sender ]] || [[ "$evoa" == "false" ]] ; then sender="[email protected]" ;fi
##
## Functions
##
function clean_exit() {
rm -f $state_change_file_down
rm -f $state_change_file_up
}
function state_changed_up() {
rm -f $state_change_file_down
touch $state_change_file_up
if [[ "$debug_status" == "true" ]] ; then
echo "state changed : up"
fi
}
function state_changed_down() {
rm -f $state_change_file_up
touch $state_change_file_down
if [[ "$debug_status" == "true" ]] ; then
echo "state changed : down"
fi
}
function state_change_detected() {
# update the system uptime
uptime=$(uptime -p)
# setup the wording for the email
if [[ $nc_status_current == 0 ]] ; then
port_status_email_string="has"
else
port_status_email_string="lost"
fi
# calculate time since last state change
current_epoch=$(date +%s)
elapsed_seconds=$(( $current_epoch - $last_state_change_epoch ))
convert_seconds_to_human_readable_time
time_since_last_state_change=$human_readable_time
# calculate time since script started
elapsed_seconds=$(( $current_epoch - $monitor_script_start_epoch ))
convert_seconds_to_human_readable_time
monitor_script_uptime=$human_readable_time
# the position of this is important - in order to have the correct wording set
subject="$host $port_status_email_string connectivitiy to $host_to_check:$local_port_to_check"
# setup the wording for the email
if [[ $nc_status_current == 0 ]] ; then
port_status_email_string="up"
state_changed_up
else
port_status_email_string="down"
state_changed_down
fi
# send a custom email
mailx -s "$subject" -a "From:$host <$sender>" -r "$sender" "$recipient" \
<<EOF
sent from $host
host uptime : $uptime
script uptime : $monitor_script_uptime
monitored port state change report :
host : $host_to_check
port : $local_port_to_check
status : $port_status_email_string
time : ${time_since_last_state_change} (since last state change)
EOF
# update the previous status variables
nc_status_previous=$nc_status_current
last_state_change_epoch=$current_epoch
# report what happened with the eamil
return $?
}
function convert_seconds_to_human_readable_time () {
# make the human_readable_time variable have human readable formatting (days hours and miniutes or just seconds)
elapsed_miniutes="$(( elapsed_seconds / 60 ))"
elapsed_hours="$(( elapsed_miniutes / 60 ))"
elapsed_days="$(( elapsed_hours / 24 ))"
human_readable_time=""
if [[ ${elapsed_hours} -ge 1 ]] ; then
elapsed_miniutes="$(( elapsed_miniutes % 60 ))"
if [[ ${elapsed_days} -ge 1 ]] ; then
day_or_days="day" ; if [[ ${elapsed_days} -ge 2 ]] ; then day_or_days="days" ; fi
elapsed_hours="$(( elapsed_hours % 24 ))"
human_readable_time="${elapsed_days} ${day_or_days} "
fi
hour_or_hours="hour" ; if [[ ${elapsed_hours} -ge 2 ]] ; then hour_or_hours="hours" ; fi
human_readable_time="${human_readable_time}${elapsed_hours} ${hour_or_hours} "
fi
miniute_or_miniutes="miniute" ; if [[ ${elapsed_miniutes} -ge 2 ]] ; then miniute_or_miniutes="miniutes" ; fi
human_readable_time="${human_readable_time}${elapsed_miniutes} ${miniute_or_miniutes}"
if [[ ${elapsed_miniutes} == 0 ]] ; then
human_readable_time="${elapsed_seconds} seconds"
fi
}
##
## Logic
##
# set a trap to tidy up files
trap 'clean_exit' EXIT
# start the loop
while true ; do
nc -z $host_to_check $local_port_to_check 2> /dev/null
nc_status_current=$?
if [[ "$nc_status_previous" != "$nc_status_current" ]] ; then
# state change has occoured
state_change_detected
fi
# pause the loop
sleep $interval_check_seconds
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment