Last active
October 1, 2025 21:30
-
-
Save henri/a84ef7671c59b1db02799aada5fb31ed to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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