Blob


1 #!/bin/ksh
2 #
3 # Uptime Atomic v20231021
4 # https://got.high5.nl/?action=summary&path=uptimeatomic.git
5 #
6 export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin
7 CONFIGFILE="uptimeatomic.conf"
8 WORKDIR=$(dirname $0)
10 usage() {
11 echo "usage: ${0##*/} [-c checksfile] [-i incidentsfile] [-p pastincidentsfile] [-o htmlfile]" 1>&2
12 exit
13 }
15 date_rss() {
16 if [ -n "${1}" ]; then
17 date -ju '+%a, %d %b %Y %H:%M:%S %z' ${1}
18 else
19 date -ju '+%a, %d %b %Y %H:%M:%S %z'
20 fi
21 }
22 date_incident() {
23 if [ -n "${1}" ]; then
24 date -ju '+%F %H:%M %Z' ${1}
25 else
26 date -ju '+%F %H:%M %Z'
27 fi
28 }
30 get_element() {
31 echo "${2}" | awk -v col="${1}" -F',' '{gsub(/^[ \t]+|[ \t]+$/, "", $col); print $col}'
32 }
34 incidents() {
35 _date=${1}
36 _type=${2}
37 _description=${3}
38 _file=${4}
39 [[ ${_type} == "Incident" ]] && color="failed"
40 [[ ${_type} == "Maintenance" ]] && color="maint"
41 if [ ${_file} == "${PASTINCIDENTSFILE}" ]; then
42 echo "<p>$(date_incident ${_date}) - <b class='${color}'>${_type}</b><br />${_description}</p>" >> ${_PASTHTMLFILE}
43 else
44 echo "<p>$(date_incident ${_date}) - <b class='${color}'>${_type}</b><br />${_description}</p>" >> ${_HTMLFILE}
45 fi
46 if [ -n "${_RSSFILE}" ]; then
47 cat << EOF >> ${_RSSFILE}
48 <item>
49 <title>${_type}</title>
50 <link>${RSS_URL}/${RSSFILE}</link>
51 <guid>${RSS_URL}/${RSSFILE}?$(date -ju +%s $(echo "${_date}"))</guid>
52 <pubDate>$(date_rss "${_date}")</pubDate>
53 <description><![CDATA[ ${_description} ]]></description>
54 </item>
55 EOF
56 fi
57 }
59 notify() {
60 _name="${1}"
61 _status="${2}"
62 _priority="${3}"
64 if [ ${_priority} == "ko" ]; then
65 echo "${_status}." | mail -r "${TITLE} <${SENDER}>" -s "${_name} DOWN" ${RECIPIENT}
66 ${PUSHOVER} -c ${PUSHOVER_CONF} -t "${TITLE}" -m "${_name} DOWN ${_status}" -p 1 >/dev/null 2>&1
67 touch "${PUSHOVER_STATUS}/${_name}"
68 fi
69 if [ ${_priority} == "ok" ]; then
70 _seconds=$(expr $(date -ju +%s) - $(stat -r "${PUSHOVER_STATUS}/${_name}" | awk '{print $11}'))
71 _downtime=$(date -jur ${_seconds} +%H:%M:%S)
73 echo "${_status} - down for ${_downtime}" | mail -r "${TITLE} <${SENDER}>" -s "${_name} OK" ${RECIPIENT}
74 ${PUSHOVER} -c ${PUSHOVER_CONF} -t "${TITLE}" -m "${_name} OK ${_status} - down for ${_downtime}" >/dev/null 2>&1
75 rm -rf "${PUSHOVER_STATUS}/${_name}"
76 fi
77 }
79 check() {
80 _ctype="${1}"
81 _expectedcode="${2}"
82 _name="${3}"
83 _host="${4}"
84 _timeout="${5}"
85 IPv="${_ctype#(http|ping|port)}"
86 [[ -n "${_timeout}" ]] && TIMEOUT=${_timeout}
88 case "${_ctype}" in
89 http*)
90 statuscode="$(curl -${IPv}sSkLo /dev/null -H "${USERAGENT}" -m "${TIMEOUT}" -w "%{http_code}" "${_host}" 2> "${_TMP}/ko/${_name}.error")"
91 if [ "${statuscode}" -ne "${_expectedcode}" ]; then
92 if [ -s "${_TMP}/ko/${_name}.error" ]; then
93 sed -e 's,curl: ([0-9]*) ,,' "${_TMP}/ko/${_name}.error" > "${_TMP}/ko/${_name}.status"
94 else
95 echo "Status code: ${statuscode}" > "${_TMP}/ko/${_name}.status"
96 fi
97 else
98 echo "Status code: ${statuscode}" > "${_TMP}/ok/${_name}.status"
99 fi
100 ;;
101 ping*)
102 ping -${IPv}w "${TIMEOUT}" -c 3 "${_host}" >/dev/null 2>&1
103 statuscode=$?
104 if [ "${statuscode}" -ne "${_expectedcode}" ]; then
105 echo "Host unreachable" > "${_TMP}/ko/${_name}.status"
106 else
107 echo "Host reachable" > "${_TMP}/ok/${_name}.status"
108 fi
109 ;;
110 port*)
111 error="$(nc -${IPv}w "${TIMEOUT}" -zv ${_host} 2>&1)"
112 statuscode=$?
113 if [ "${statuscode}" -ne "${_expectedcode}" ]; then
114 echo "Connection refused" > "${_TMP}/ko/${_name}.status"
115 else
116 echo "Connection succeeded" > "${_TMP}/ok/${_name}.status"
117 fi
118 ;;
119 maint*)
120 echo "Maintenance" > "${_TMP}/maint/${_name}.status"
121 ;;
122 esac
125 process_status() {
126 _status_files=${1}
128 ls ${_TMP}/${_status_files}/*.status 2>/dev/null | sort -V | while read file; do
129 [ -e "${file}" ] || continue
130 _name="$(basename "${file%.status}")"
131 _status="$(cat "${file}")"
133 if [ ${_status_files} == "ko" ]; then
134 echo "<li>${_name} <span class='small failed'>(${_status})</span><span class='status failed'>Disrupted</span></li>" >> ${_HTMLFILE}
135 if [ ! -e "${PUSHOVER_STATUS}/${_name}" ]; then
136 notify "${_name}" "${_status}" "${_status_files}"
137 fi
138 fi
139 if [ ${_status_files} == "maint" ]; then
140 echo "<li>${_name} <span class='status maint'>Maintenance</span></li>" >> ${_HTMLFILE}
141 fi
142 if [ ${_status_files} == "ok" ]; then
143 echo "<li>${_name} <span class='status success'>Operational</span></li>" >> ${_HTMLFILE}
144 if [ -e "${PUSHOVER_STATUS}/${_name}" ]; then
145 notify "${_name}" "${_status}" "${_status_files}"
146 fi
147 fi
148 done
151 parse_file() {
152 _file=${1}
153 while IFS="$(printf '\n')" read -r line; do
154 _col1="$(get_element 1 "${line}")"
155 _col2="$(get_element 2 "${line}")"
156 _col3="$(get_element 3 "${line}")"
157 _col4="$(get_element 4 "${line}")"
158 _col5="$(get_element 5 "${line}")"
159 if [[ ${_file} == *".csv" ]]; then
160 check "${_col1}" "${_col2}" "${_col3}" "${_col4}" "${_col5}" &
161 else
162 incidents "${_col1}" "${_col2}" "${_col3}" "${_file}"
163 fi
164 done < "${_file}"
165 wait
168 cd ${WORKDIR}
169 if [ -s "${CONFIGFILE}" ]; then
170 . ${WORKDIR}/${CONFIGFILE}
171 else
172 echo "Config ${WORKDIR}/${CONFIGFILE} doesn't exist."
173 exit
174 fi
176 while getopts c:i:o:r:h arg; do
177 case ${arg} in
178 c) CHECKFILE=${OPTARG};;
179 i) INCIDENTSFILE=${OPTARG};;
180 p) PASTINCIDENTSFILE=${OPTARG};;
181 o) HTMLFILE=${OPTARG};;
182 r) RSSFILE=${OPTARG};;
183 h) usage;;
184 *) usage;;
185 esac
186 done
188 if [ ! -e "${CHECKFILE}" ]; then
189 echo "Checkfile ${WORKDIR}/${CHECKFILE} doesn't exist."
190 exit
191 fi
193 _TMP="$(mktemp -d)"
194 mkdir -p "${_TMP}/ok" "${_TMP}/ko" "${_TMP}/maint" || exit 1
195 _HTMLFILE="${_TMP}/${HTMLFILE}"
196 _PASTHTMLFILE="${_TMP}/${PASTHTMLFILE}"
197 [[ -n "${RSSFILE}" ]] && _RSSFILE="${_TMP}/${RSSFILE}"
199 parse_file "${CHECKFILE}"
202 # HTML
204 cat << EOF | tee ${_HTMLFILE} | tee ${_PASTHTMLFILE} >/dev/null
205 <!DOCTYPE html>
206 <html lang="en">
207 <head>
208 <meta charset="utf-8">
209 <meta http-equiv="refresh" content="${REFRESH}">
210 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
211 <title>${TITLE}</title>
212 <style>
213 body { font-family: segoe ui,Roboto,Oxygen-Sans,Ubuntu,Cantarell,helvetica neue,Verdana,sans-serif; }
214 h1 { margin-top: 30px; }
215 ul { padding: 0px; }
216 li { list-style: none; margin-bottom: 2px; padding: 5px; border-bottom: 1px solid #ddd; }
217 .container { max-width: 600px; width: 100%; margin: 15px auto; }
218 .panel { text-align: center; padding: 10px; border: 0px; border-radius: 5px; }
219 .failed-bg { color: white; background-color: #E25D6A; }
220 .success-bg { color: white; background-color: #52B86A; }
221 .maint-bg { color: white; background-color: #5DADE2; }
222 .failed { color: #E25D6A; }
223 .success { color: #52B86A; }
224 .maint { color: #5DADE2; }
225 .small { font-size: 80%; }
226 .status { float: right; }
227 @media (prefers-color-scheme: dark) {
228 a, a:link, a:visited { color: #ffe489; }
229 body, html { background-color: #111111; color: #888888; }
230 pre { background-color: #111111; color: #aaaaaa; }
231 li { border-bottom: 1px solid #333333; }
232 .clean { background-color: #111111; color: #aaaaaa; }
234 </style>
235 </head>
236 <body>
237 <div class='container'>
238 <h1>${HEADER}</h1>
239 EOF
242 # RSS
244 if [ -n "${_RSSFILE}" ]; then
245 cat << EOF > ${_RSSFILE}
246 <?xml version="1.0" encoding="utf-8"?>
247 <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
248 <channel>
249 <atom:link href="${RSS_URL}/${RSSFILE}" rel="self" type="application/rss+xml" />
250 <title>${TITLE}</title>
251 <description>${RSS_DESCRIPTION}</description>
252 <link>${RSS_URL}</link>
253 <lastBuildDate>$(date_rss)</lastBuildDate>
254 EOF
255 fi
257 _outage_count="$(find "${_TMP}/ko" -mindepth 1 | grep -c 'status$')"
258 _maint_count="$(find "${_TMP}/maint" -mindepth 1 | grep -c 'status$')"
259 if [ "${_outage_count}" -ne 0 ]; then
260 echo "<ul><li class='panel failed-bg'>${_outage_count} Outage(s)</li></ul>" >> ${_HTMLFILE}
261 fi
262 if [ "${_maint_count}" -ne 0 ]; then
263 echo "<ul><li class='panel maint-bg'>${_maint_count} Maintenance</li></ul>" >> ${_HTMLFILE}
264 fi
265 if [[ "${_outage_count}" -eq 0 && "${_maint_count}" -eq 0 ]]; then
266 echo "<ul><li class='panel success-bg'>All Systems Operational</li></ul>" >> ${_HTMLFILE}
267 fi
269 if [ -s "${INCIDENTSFILE}" ]; then
270 echo '<h2>Incidents / Maintenance</h2>' >> ${_HTMLFILE}
271 parse_file "${INCIDENTSFILE}"
272 fi
274 echo "<h1>Services</h1>" >> ${_HTMLFILE}
275 echo "<ul>" >> ${_HTMLFILE}
277 process_status "ko"
278 process_status "maint"
279 process_status "ok"
281 echo "</ul>" >> ${_HTMLFILE}
282 echo "<p class=small>Last check: $(date -ju '+%FT%T %Z')</p>" >> ${_HTMLFILE}
284 if [ -s "${PASTINCIDENTSFILE}" ]; then
285 echo '<h2>Past Incidents / Maintenance</h2>' >> ${_PASTHTMLFILE}
286 parse_file "${PASTINCIDENTSFILE}"
287 fi
290 # END HTML
292 cat << EOF | tee -a ${_HTMLFILE} | tee -a ${_PASTHTMLFILE} >/dev/null
293 <p class=small>
294 <a href="https://got.high5.nl/?action=summary&path=uptimeatomic.git">Uptime Atomic</a> loosely based on <a href="https://github.com/bderenzo/tinystatus">Tinystatus</a>
295 EOF
296 [[ -n "${_PASTHTMLFILE}" ]] && echo " - <a href='${PASTHTMLFILE}'>Past Incidents</a>" >> ${_HTMLFILE}
297 [[ -n "${_RSSFILE}" ]] && echo " - <a href='${RSS_URL}/${RSSFILE}'>RSS</a>" >> ${_HTMLFILE}
298 cat << EOF | tee -a ${_HTMLFILE} | tee -a ${_PASTHTMLFILE} >/dev/null
299 </p>
300 </div>
301 </body>
302 </html>
303 EOF
306 # END RSS
308 if [ -n "${_RSSFILE}" ]; then
309 cat << EOF >> ${_RSSFILE}
310 </channel>
311 </rss>
312 EOF
313 fi
315 if [[ -f "${HTMLDIR}/${RSSFILE}" ]]; then
316 _diff=$(diff "${_RSSFILE}" "${HTMLDIR}/${RSSFILE}" | wc -l)
317 if [ "${_diff}" -ne "4" ]; then
318 cp ${_RSSFILE} ${HTMLDIR}/${RSSFILE}
319 fi
320 elif [ -n "${RSSFILE}" ]; then
321 cp ${_RSSFILE} ${HTMLDIR}/${RSSFILE}
322 fi
324 cp ${_HTMLFILE} ${HTMLDIR}/${HTMLFILE}
325 cp ${_PASTHTMLFILE} ${HTMLDIR}/${PASTHTMLFILE}
326 rm -r "${_TMP}" 2>/dev/null