2023-05-30 15:04:46 +02:00
|
|
|
#!/bin/ksh
|
|
|
|
#
|
2023-06-03 17:09:17 +02:00
|
|
|
# Uptime Atomic v20230603
|
2023-05-31 10:45:22 +02:00
|
|
|
# https://git.high5.nl/uptimeatomic/
|
2023-05-30 22:13:46 +02:00
|
|
|
#
|
2023-05-30 15:04:46 +02:00
|
|
|
export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin
|
2023-06-03 18:04:03 +02:00
|
|
|
CONFIGFILE="uptimeatomic.conf"
|
2023-06-03 20:07:04 +02:00
|
|
|
WORKDIR=$(cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P)
|
2023-05-30 15:04:46 +02:00
|
|
|
|
2023-05-30 19:54:49 +02:00
|
|
|
usage() {
|
2023-05-30 22:06:53 +02:00
|
|
|
echo "usage: ${0##*/} [-c checksfile] [-i incidentsfile] [-p pastincidentsfile] [-o htmlfile]" 1>&2
|
2023-05-30 19:54:49 +02:00
|
|
|
exit
|
|
|
|
}
|
|
|
|
|
2023-06-03 17:09:17 +02:00
|
|
|
date_rss() {
|
|
|
|
if [ -n "${1}" ]; then
|
2023-06-03 20:41:49 +02:00
|
|
|
date -uj '+%a, %d %b %Y %H:%M:%S %z' ${1}
|
2023-06-03 17:09:17 +02:00
|
|
|
else
|
2023-06-03 17:39:48 +02:00
|
|
|
date -uj '+%a, %d %b %Y %H:%M:%S %z'
|
2023-06-03 17:09:17 +02:00
|
|
|
fi
|
|
|
|
}
|
|
|
|
date_incident() {
|
|
|
|
if [ -n "${1}" ]; then
|
2023-06-03 20:41:49 +02:00
|
|
|
date -uj '+%F %H:%M %Z' ${1}
|
2023-06-03 17:09:17 +02:00
|
|
|
else
|
2023-06-03 17:39:48 +02:00
|
|
|
date -uj '+%F %H:%M %Z'
|
2023-06-03 17:09:17 +02:00
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
2023-05-30 15:04:46 +02:00
|
|
|
get_element() {
|
|
|
|
echo "${2}" | awk -v col="${1}" -F',' '{gsub(/^[ \t]+|[ \t]+$/, "", $col); print $col}'
|
|
|
|
}
|
|
|
|
|
2023-06-03 17:09:17 +02:00
|
|
|
incidents() {
|
|
|
|
_date=${1}
|
|
|
|
_type=${2}
|
|
|
|
_description=${3}
|
|
|
|
[[ ${_type} == "Incident" ]] && color="failed"
|
|
|
|
[[ ${_type} == "Maintenance" ]] && color="maint"
|
|
|
|
echo "<p>$(date_incident ${_date}) - <b class='${color}'>${_type}</b><br />${_description}</p>" >> ${_HTMLFILE}
|
2023-06-03 20:14:15 +02:00
|
|
|
if [ -n "${_RSSFILE}" ]; then
|
|
|
|
cat << EOF >> ${_RSSFILE}
|
2023-06-03 17:09:17 +02:00
|
|
|
<item>
|
|
|
|
<title>${_type}</title>
|
|
|
|
<link>${RSS_URL}/rss.xml</link>
|
|
|
|
<guid>${RSS_URL}/rss.xml?$(date -j '+%s' $(echo "${_date}"))</guid>
|
|
|
|
<pubDate>$(date_rss "${_date}")</pubDate>
|
|
|
|
<description><![CDATA[ ${_description} ]]></description>
|
|
|
|
</item>
|
|
|
|
EOF
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
2023-05-30 15:04:46 +02:00
|
|
|
notify() {
|
2023-06-03 17:09:17 +02:00
|
|
|
_name="${1}"
|
|
|
|
_status="${2}"
|
|
|
|
_priority="${3}"
|
|
|
|
|
|
|
|
if [ ${_priority} == "ko" ]; then
|
|
|
|
echo "${_status}." | mail -r "${TITLE} <${SENDER}>" -s "${_name} DOWN" ${RECIPIENT}
|
|
|
|
${PUSHOVER} -c ${PUSHOVER_CONF} -t "${TITLE}" -m "${_name} DOWN ${_status}." -p 1 >/dev/null 2>&1
|
|
|
|
touch "${PUSHOVER_STATUS}/${_name}"
|
2023-05-30 15:04:46 +02:00
|
|
|
fi
|
2023-06-03 17:09:17 +02:00
|
|
|
if [ ${_priority} == "ok" ]; then
|
|
|
|
_seconds=$(expr $(date +%s) - $(stat -r "${PUSHOVER_STATUS}/${_name}" | awk '{print $11}'))
|
2023-05-30 15:04:46 +02:00
|
|
|
_downtime=$(date -r${_seconds} -u +%H:%M:%S)
|
|
|
|
|
2023-06-03 17:09:17 +02:00
|
|
|
echo "${_status} - down for ${_downtime}" | mail -r "${TITLE} <${SENDER}>" -s "${_name} OK" ${RECIPIENT}
|
|
|
|
${PUSHOVER} -c ${PUSHOVER_CONF} -t "${TITLE}" -m "${_name} OK ${_status} - down for ${_downtime}" >/dev/null 2>&1
|
|
|
|
rm -rf "${PUSHOVER_STATUS}/${_name}"
|
2023-05-30 15:04:46 +02:00
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
2023-05-30 22:13:46 +02:00
|
|
|
check() {
|
2023-06-03 17:09:17 +02:00
|
|
|
_ctype="${1}"
|
|
|
|
_expectedcode="${2}"
|
|
|
|
_name="${3}"
|
|
|
|
_host="${4}"
|
|
|
|
_timeout="${5}"
|
|
|
|
IPv="${_ctype#(http|ping|port)}"
|
|
|
|
[[ -n "${_timeout}" ]] && TIMEOUT=${_timeout}
|
|
|
|
|
|
|
|
case "${_ctype}" in
|
2023-05-30 15:04:46 +02:00
|
|
|
http*)
|
2023-06-03 17:09:17 +02:00
|
|
|
statuscode="$(curl -${IPv}sSkLo /dev/null -H "${USERAGENT}" -m "${TIMEOUT}" -w "%{http_code}" "${_host}" 2> "${_TMP}/ko/${_name}.error")"
|
|
|
|
if [ "${statuscode}" -ne "${_expectedcode}" ]; then
|
|
|
|
if [ -s "${_TMP}/ko/${_name}.error" ]; then
|
|
|
|
sed -e 's,curl: ([0-9]*) ,,' "${_TMP}/ko/${_name}.error" > "${_TMP}/ko/${_name}.status"
|
2023-05-30 15:04:46 +02:00
|
|
|
else
|
2023-06-03 17:09:17 +02:00
|
|
|
echo "Status code: ${statuscode}" > "${_TMP}/ko/${_name}.status"
|
2023-05-30 15:04:46 +02:00
|
|
|
fi
|
|
|
|
else
|
2023-06-03 17:09:17 +02:00
|
|
|
echo "Status code: ${statuscode}" > "${_TMP}/ok/${_name}.status"
|
2023-05-30 15:04:46 +02:00
|
|
|
fi
|
|
|
|
;;
|
|
|
|
ping*)
|
2023-06-03 17:09:17 +02:00
|
|
|
ping -${IPv}w "${TIMEOUT}" -c 1 "${_host}" >/dev/null 2>&1
|
2023-05-30 15:04:46 +02:00
|
|
|
statuscode=$?
|
2023-06-03 17:09:17 +02:00
|
|
|
if [ "${statuscode}" -ne "${_expectedcode}" ]; then
|
|
|
|
echo "Host unreachable" > "${_TMP}/ko/${_name}.status"
|
2023-05-30 15:04:46 +02:00
|
|
|
else
|
2023-06-03 17:09:17 +02:00
|
|
|
echo "Host reachable" > "${_TMP}/ok/${_name}.status"
|
2023-05-30 15:04:46 +02:00
|
|
|
fi
|
|
|
|
;;
|
|
|
|
port*)
|
2023-06-03 17:09:17 +02:00
|
|
|
error="$(nc -${IPv}w "${TIMEOUT}" -zv ${_host} 2>&1)"
|
2023-05-30 15:04:46 +02:00
|
|
|
statuscode=$?
|
2023-06-03 17:09:17 +02:00
|
|
|
if [ "${statuscode}" -ne "${_expectedcode}" ]; then
|
|
|
|
echo "Connection refused" > "${_TMP}/ko/${_name}.status"
|
2023-05-30 15:04:46 +02:00
|
|
|
else
|
2023-06-03 17:09:17 +02:00
|
|
|
echo "Connection succeeded" > "${_TMP}/ok/${_name}.status"
|
2023-05-30 15:04:46 +02:00
|
|
|
fi
|
|
|
|
;;
|
|
|
|
maint*)
|
2023-06-03 17:09:17 +02:00
|
|
|
echo "Maintenance" > "${_TMP}/maint/${_name}.status"
|
2023-05-30 15:04:46 +02:00
|
|
|
;;
|
|
|
|
esac
|
|
|
|
}
|
|
|
|
|
2023-06-03 18:04:03 +02:00
|
|
|
process_status() {
|
|
|
|
_status_files=${1}
|
2023-06-03 17:09:17 +02:00
|
|
|
|
2023-06-03 18:04:03 +02:00
|
|
|
ls ${_TMP}/${_status_files}/*.status 2>/dev/null | sort -V | while read file; do
|
2023-06-03 17:09:17 +02:00
|
|
|
[ -e "${file}" ] || continue
|
|
|
|
_name="$(basename "${file%.status}")"
|
|
|
|
_status="$(cat "${file}")"
|
|
|
|
|
2023-06-03 18:04:03 +02:00
|
|
|
if [ ${_status_files} == "ko" ]; then
|
2023-06-03 17:09:17 +02:00
|
|
|
echo "<li>${_name} <span class='small failed'>(${_status})</span><span class='status failed'>Disrupted</span></li>" >> ${_HTMLFILE}
|
|
|
|
if [ ! -e "${PUSHOVER_STATUS}/${_name}" ]; then
|
2023-06-03 18:04:03 +02:00
|
|
|
notify "${_name}" "${status}" "${_status_files}"
|
2023-06-03 17:09:17 +02:00
|
|
|
fi
|
|
|
|
fi
|
2023-06-03 18:04:03 +02:00
|
|
|
if [ ${_status_files} == "maint" ]; then
|
2023-06-03 17:09:17 +02:00
|
|
|
echo "<li>${_name} <span class='status maint'>Maintenance</span></li>" >> ${_HTMLFILE}
|
|
|
|
fi
|
2023-06-03 18:04:03 +02:00
|
|
|
if [ ${_status_files} == "ok" ]; then
|
2023-06-03 17:09:17 +02:00
|
|
|
echo "<li>${_name} <span class='status success'>Operational</span></li>" >> ${_HTMLFILE}
|
|
|
|
if [ -e "${PUSHOVER_STATUS}/${_name}" ]; then
|
2023-06-03 18:04:03 +02:00
|
|
|
notify "${_name}" "${_status}" "${_status_files}"
|
2023-06-03 17:09:17 +02:00
|
|
|
fi
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
}
|
|
|
|
|
|
|
|
parse_file() {
|
|
|
|
_file=${1}
|
|
|
|
while IFS="$(printf '\n')" read -r line; do
|
|
|
|
_col1="$(get_element 1 "${line}")"
|
|
|
|
_col2="$(get_element 2 "${line}")"
|
|
|
|
_col3="$(get_element 3 "${line}")"
|
|
|
|
_col4="$(get_element 4 "${line}")"
|
|
|
|
_col5="$(get_element 5 "${line}")"
|
|
|
|
if [[ ${_file} == *".csv" ]]; then
|
|
|
|
check "${_col1}" "${_col2}" "${_col3}" "${_col4}" "${_col5}" &
|
|
|
|
else
|
|
|
|
incidents "${_col1}" "${_col2}" "${_col3}"
|
|
|
|
fi
|
|
|
|
done < "${_file}"
|
|
|
|
wait
|
|
|
|
}
|
|
|
|
|
2023-06-03 18:08:59 +02:00
|
|
|
cd ${WORKDIR}
|
2023-06-03 18:11:07 +02:00
|
|
|
if [ -e "${CONFIGFILE}" ]; then
|
|
|
|
. ${WORKDIR}/${CONFIGFILE}
|
|
|
|
else
|
2023-06-03 18:08:59 +02:00
|
|
|
echo "Configfile ${WORKDIR}/${CONFIGFILE} doesn't exist."
|
|
|
|
exit
|
|
|
|
fi
|
|
|
|
|
2023-06-03 17:09:17 +02:00
|
|
|
while getopts c:i:o:r:h arg; do
|
2023-05-30 15:04:46 +02:00
|
|
|
case ${arg} in
|
|
|
|
c) CHECKFILE=${OPTARG};;
|
|
|
|
i) INCIDENTSFILE=${OPTARG};;
|
2023-05-30 22:06:53 +02:00
|
|
|
p) PASTINCIDENTSFILE=${OPTARG};;
|
2023-05-30 15:04:46 +02:00
|
|
|
o) HTMLFILE=${OPTARG};;
|
2023-06-03 17:09:17 +02:00
|
|
|
r) RSS_FILE=${OPTARG};;
|
2023-05-30 19:54:49 +02:00
|
|
|
h) usage;;
|
2023-05-30 15:04:46 +02:00
|
|
|
*) usage;;
|
|
|
|
esac
|
|
|
|
done
|
|
|
|
|
|
|
|
if [ ! -e "${CHECKFILE}" ]; then
|
|
|
|
echo "Checkfile ${WORKDIR}/${CHECKFILE} doesn't exist."
|
|
|
|
exit
|
|
|
|
fi
|
|
|
|
|
2023-06-03 17:09:17 +02:00
|
|
|
_TMP="$(mktemp -d)"
|
|
|
|
mkdir -p "${_TMP}/ok" "${_TMP}/ko" "${_TMP}/maint" || exit 1
|
|
|
|
_HTMLFILE="${_TMP}/${HTMLFILE}"
|
2023-06-03 20:22:33 +02:00
|
|
|
[[ -n "${RSS_FILE}" ]] && _RSSFILE="${HTMLDIR}/${RSS_FILE}"
|
2023-06-03 17:09:17 +02:00
|
|
|
|
|
|
|
parse_file "${CHECKFILE}"
|
|
|
|
|
|
|
|
#
|
|
|
|
# HTML
|
|
|
|
#
|
|
|
|
cat << EOF > ${_HTMLFILE}
|
2023-05-30 22:06:53 +02:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html lang="en">
|
|
|
|
<head>
|
|
|
|
<meta charset="utf-8">
|
|
|
|
<meta http-equiv="refresh" content="${REFRESH}">
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
|
|
<title>${TITLE}</title>
|
|
|
|
<style>
|
2023-05-30 15:04:46 +02:00
|
|
|
body { font-family: segoe ui,Roboto,Oxygen-Sans,Ubuntu,Cantarell,helvetica neue,Verdana,sans-serif; }
|
|
|
|
h1 { margin-top: 30px; }
|
|
|
|
ul { padding: 0px; }
|
|
|
|
li { list-style: none; margin-bottom: 2px; padding: 5px; border-bottom: 1px solid #ddd; }
|
|
|
|
.container { max-width: 600px; width: 100%; margin: 15px auto; }
|
|
|
|
.panel { text-align: center; padding: 10px; border: 0px; border-radius: 5px; }
|
|
|
|
.failed-bg { color: white; background-color: #E25D6A; }
|
|
|
|
.success-bg { color: white; background-color: #52B86A; }
|
|
|
|
.maint-bg { color: white; background-color: #5DADE2; }
|
|
|
|
.failed { color: #E25D6A; }
|
|
|
|
.success { color: #52B86A; }
|
|
|
|
.maint { color: #5DADE2; }
|
|
|
|
.small { font-size: 80%; }
|
|
|
|
.status { float: right; }
|
2023-05-30 22:06:53 +02:00
|
|
|
</style>
|
|
|
|
</head>
|
2023-05-30 15:04:46 +02:00
|
|
|
<body>
|
|
|
|
<div class='container'>
|
|
|
|
<h1>${HEADER}</h1>
|
|
|
|
EOF
|
2023-06-03 17:09:17 +02:00
|
|
|
|
|
|
|
#
|
|
|
|
# RSS
|
|
|
|
#
|
2023-06-03 20:14:15 +02:00
|
|
|
if [ -n "${_RSSFILE}" ]; then
|
|
|
|
cat << EOF > ${_RSSFILE}
|
2023-06-03 17:09:17 +02:00
|
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
|
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
|
|
|
<channel>
|
2023-06-03 17:15:48 +02:00
|
|
|
<atom:link href="${RSS_URL}/rss.xml" rel="self" type="application/rss+xml" />
|
2023-06-03 17:09:17 +02:00
|
|
|
<title>${TITLE}</title>
|
|
|
|
<description>${RSS_DESCRIPTION}</description>
|
|
|
|
<link>${RSS_URL}</link>
|
|
|
|
<lastBuildDate>$(date_rss)</lastBuildDate>
|
|
|
|
EOF
|
|
|
|
fi
|
|
|
|
|
|
|
|
_outage_count="$(find "${_TMP}/ko" -mindepth 1 | grep -c 'status$')"
|
|
|
|
_maint_count="$(find "${_TMP}/maint" -mindepth 1 | grep -c 'status$')"
|
2023-05-30 15:04:46 +02:00
|
|
|
if [ "${_outage_count}" -ne 0 ]; then
|
2023-06-03 17:09:17 +02:00
|
|
|
echo "<ul><li class='panel failed-bg'>${_outage_count} Outage(s)</li></ul>" >> ${_HTMLFILE}
|
2023-05-30 15:04:46 +02:00
|
|
|
fi
|
|
|
|
if [ "${_maint_count}" -ne 0 ]; then
|
2023-06-03 17:09:17 +02:00
|
|
|
echo "<ul><li class='panel maint-bg'>${_maint_count} Maintenance</li></ul>" >> ${_HTMLFILE}
|
2023-05-30 15:04:46 +02:00
|
|
|
fi
|
|
|
|
if [[ "${_outage_count}" -eq 0 && "${_maint_count}" -eq 0 ]]; then
|
2023-06-03 17:09:17 +02:00
|
|
|
echo "<ul><li class='panel success-bg'>All Systems Operational</li></ul>" >> ${_HTMLFILE}
|
2023-05-30 15:04:46 +02:00
|
|
|
fi
|
2023-06-03 17:09:17 +02:00
|
|
|
|
2023-05-30 22:08:54 +02:00
|
|
|
if [ -s "${INCIDENTSFILE}" ]; then
|
2023-06-03 17:09:17 +02:00
|
|
|
echo '<h2>Incidents / Maintenance</h2>' >> ${_HTMLFILE}
|
|
|
|
parse_file "${INCIDENTSFILE}"
|
2023-05-30 15:04:46 +02:00
|
|
|
fi
|
2023-06-03 17:09:17 +02:00
|
|
|
|
|
|
|
echo "<h1>Services</h1>" >> ${_HTMLFILE}
|
|
|
|
echo "<ul>" >> ${_HTMLFILE}
|
|
|
|
|
2023-06-03 18:04:03 +02:00
|
|
|
process_status "ko"
|
|
|
|
process_status "maint"
|
|
|
|
process_status "ok"
|
2023-06-03 17:09:17 +02:00
|
|
|
|
|
|
|
echo "</ul>" >> ${_HTMLFILE}
|
2023-06-03 17:39:48 +02:00
|
|
|
echo "<p class=small>Last check: $(date -u '+%FT%T %Z')</p>" >> ${_HTMLFILE}
|
2023-06-03 17:09:17 +02:00
|
|
|
|
2023-05-30 22:06:53 +02:00
|
|
|
if [ -s "${PASTINCIDENTSFILE}" ]; then
|
2023-06-03 17:09:17 +02:00
|
|
|
echo '<h3>Past Incidents / Maintenance</h3>' >> ${_HTMLFILE}
|
|
|
|
parse_file "${PASTINCIDENTSFILE}"
|
2023-05-30 22:06:53 +02:00
|
|
|
fi
|
2023-06-03 17:09:17 +02:00
|
|
|
|
|
|
|
#
|
|
|
|
# END HTML
|
|
|
|
#
|
|
|
|
cat << EOF >> ${_HTMLFILE}
|
2023-06-03 20:30:06 +02:00
|
|
|
<p class=small>
|
|
|
|
<a href="https://git.high5.nl/uptimeatomic/">Uptime Atomic</a> loosely based on <a href="https://github.com/bderenzo/tinystatus">Tinystatus</a>
|
|
|
|
EOF
|
|
|
|
[[ -n "${_RSSFILE}" ]] && echo " - <a href='${RSS_URL}/${RSS_FILE}'>RSS</a>" >> ${_HTMLFILE}
|
|
|
|
cat << EOF >> ${_HTMLFILE}
|
|
|
|
</p>
|
2023-05-30 15:04:46 +02:00
|
|
|
</div>
|
2023-05-30 22:06:53 +02:00
|
|
|
</body>
|
|
|
|
</html>
|
2023-05-30 15:04:46 +02:00
|
|
|
EOF
|
|
|
|
|
2023-06-03 17:09:17 +02:00
|
|
|
#
|
|
|
|
# END RSS
|
|
|
|
#
|
2023-06-03 20:14:15 +02:00
|
|
|
if [ -n "${_RSSFILE}" ]; then
|
|
|
|
cat << EOF >> ${_RSSFILE}
|
2023-06-03 17:09:17 +02:00
|
|
|
</channel>
|
|
|
|
</rss>
|
|
|
|
EOF
|
|
|
|
fi
|
|
|
|
|
|
|
|
cp ${_HTMLFILE} ${HTMLDIR}/${HTMLFILE}
|
|
|
|
rm -r "${_TMP}" 2>/dev/null
|