#!/bin/bash # author: Remy van Elst - https://raymii.org # license: gnu gpl v3 # Start of configuration. NAME="" #array names must not contain spaces, only a-ZA-Z. declare -A urls # do not remove this line urls[webbed_sight]="https://vulpineawoo.github.io" urls[atheme_services]="http://irc.wppnx.pii.at" urls[irc.wppnx.pii.at]="http://irc.wppnx.pii.at" urls[foxes.are.allowed.org]="https://foxes.are.allowed.org" # The default status code. Can be overriden per URL lower in the script. defaultExpectedStatusCode=200 # Expected code for url, key must match urls[]. Only for URL's you consider UP, but for example require authentication declare -A statuscode # do not remove this line statuscode[gist.github.com]=302 # How many curl checks to run at the same time maxConcurrentCurls=12 # Max timeout of a check in seconds defaultTimeOut=3 # Start of script. Do not edit below # Exit immediately if a command exits with a non-zero status. set -e # patterns which match no files expand to a null string, otherwise later on we'd have *.status as a file... shopt -s nullglob # start of the function definitions # This function allows the script to execute all the curl calls in paralell. # Otherwise, if one would timeout or take long, the rest after that would be # slower. We're writing the status code to a file, reading that later on. Why? # Because an array cannot be filled via a subprocess (curl ... &) doRequest() { name="${1}" url="${2}" checkStartTimeMs="$(date +%s%3N)" # epoch in microseconds, but last chars stripped so it's milliseconds set +e # curl errors don't count for early exit, turn off e checkStatusCode="$(curl --max-time "${defaultTimeOut}" --silent --show-error --insecure --output /dev/null --write-out "%{http_code}" "$url" --stderr "${tempfolder}/FAIL/${name}.error")" set -e # turn e back on checkEndTimeMs="$(date +%s%3N)" timeCheckTook="$((checkEndTimeMs-checkStartTimeMs))" expectedStatusCode=${defaultExpectedStatusCode} # check if there is a specific status code for this domain if [[ -n "${statuscode[${name}]}" ]]; then expectedStatusCode="${statuscode[${name}]}" fi if [[ ${expectedStatusCode} -eq ${checkStatusCode} ]]; then echo "${timeCheckTook}" > "${tempfolder}/OK/${name}.duration" else echo "${checkStatusCode}" > "${tempfolder}/FAIL/${name}.status" fi # if the error file is empty, remove it. if [[ ! -s "${tempfolder}/FAIL/${name}.error" ]]; then rm "${tempfolder}/FAIL/${name}.error" fi } writeOkayChecks() { echo "" echo "
" echo "

Checks

" pushd "${tempfolder}/OK" >/dev/null 2>&1 okFiles=(*.duration) okCount=${#okFiles[@]} if [[ okCount -gt 0 ]]; then for filename in "${okFiles[@]}"; do if [[ -r $filename ]]; then value="$(cat $filename)" filenameWithoutExt=${filename%.*} echo '
' echo "${filenameWithoutExt}" echo '('${value} echo " ms)" echo "
" fi done fi popd >/dev/null 2>&1 } writeFailedChecks() { pushd "${tempfolder}/FAIL" >/dev/null 2>&1 failFiles=(*.status) failCount=${#failFiles[@]} if [[ failCount -gt 0 ]]; then echo '' for filename in "${failFiles[@]}"; do if [[ -r $filename ]]; then filenameWithoutExt=${filename%.*} if [[ -r "${filenameWithoutExt}.error" ]]; then curlError="$(cat "${filenameWithoutExt}.error" 2>/dev/null)" else curlError="Status code does not match expected code." fi status="$(cat ${filename})" echo "
" echo "${filenameWithoutExt}" echo "
"
        echo "Status: ${status}"
        echo "${curlError}"
        echo "
" fi done else echo '" fi popd >/dev/null 2>&1 } cleanupFailedCheckFiles() { pushd "${tempfolder}/FAIL" >/dev/null 2>&1 errorFiles=(*.error) errorCount=${#errorFiles[@]} for filename in "${errorFiles[@]}"; do if [[ -r $filename ]]; then rm "${filename}" fi done statusFiles=(*.status) statusCount=${#statusFiles[@]} for filename in "${statusFiles[@]}"; do if [[ -r $filename ]]; then rm "${filename}" fi done popd >/dev/null 2>&1 rmdir "${tempfolder}/FAIL" } cleanupOKCheckFiles() { pushd "${tempfolder}/OK" >/dev/null 2>&1 okFiles=(*.duration) okCount=${#okFiles[@]} for filename in "${okFiles[@]}"; do if [[ -r $filename ]]; then rm "${filename}" fi done popd >/dev/null 2>&1 rmdir "${tempfolder}/OK" } writeHeader() { echo '' echo '' echo "$NAME status" echo "" echo "
" echo "

$NAME status

" echo "
" echo "
" } writeFooter() { echo "
" echo "
" echo "

Info

" echo "

Last check: " date echo "
Total duration: ${runtime} ms" echo "
Monitoring script by Remy van Elst. License: GNU AGPLv3. " echo "Source code" echo "

" echo "
" echo "" } # script start # Total script duration timer start=$(date +%s%3N) tmpdir=$(mktemp -d) tempfolder=${tmpdir:-/tmp/statusmon/} # try to create folders, if it fails, stop the script. mkdir -p "${tempfolder}/OK" || exit 1 mkdir -p "${tempfolder}/FAIL" || exit 1 pushd "${tempfolder}" >/dev/null 2>&1 # Do the checks parallel for key in "${!urls[@]}" do value=${urls[$key]} if [[ "$(jobs | wc -l)" -ge ${maxConcurrentCurls} ]] ; then # run 12 curl commands at max parallel wait -n fi doRequest "$key" "$value" & done wait writeHeader # Failed checks, if any, go on top writeFailedChecks | sed 's/_/ /g' # Okay checks go below the failed checks writeOkayChecks | sed 's/_/ /g' # Cleanup the status files cleanupFailedCheckFiles cleanupOKCheckFiles # stop the total timer end=$(date +%s%3N) runtime=$((end-start)) writeFooter popd >/dev/null 2>&1 rmdir "${tempfolder}"