chiark / gitweb /
check-bkp-status.in: Tweak auto-collapse of linked log sections.
[rsync-backup] / check-bkp-status.in
1 #! @BASH@
2 ###
3 ### Check that dumps have been made as expected.
4 ###
5 ### (c) 2013 Mark Wooding
6 ###
7
8 ###----- Licensing notice ---------------------------------------------------
9 ###
10 ### This file is part of the `rsync-backup' program.
11 ###
12 ### rsync-backup is free software; you can redistribute it and/or modify
13 ### it under the terms of the GNU General Public License as published by
14 ### the Free Software Foundation; either version 2 of the License, or
15 ### (at your option) any later version.
16 ###
17 ### rsync-backup is distributed in the hope that it will be useful,
18 ### but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 ### GNU General Public License for more details.
21 ###
22 ### You should have received a copy of the GNU General Public License
23 ### along with rsync-backup; if not, write to the Free Software Foundation,
24 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26 set -e
27
28 quis=${0##*/}
29 . @pkgdatadir@/lib.sh
30
31 ###--------------------------------------------------------------------------
32 ### Stubs for the configuration file.
33
34 defhook () { :; }
35 addhook () { :; }
36
37 retain () { :; }
38 snaptype () { :; }
39 like () { :; }
40 retry () { :; }
41 user () { :; }
42
43 ###--------------------------------------------------------------------------
44 ### Output format switch.
45
46 formats=:
47 defformat () { formats=$formats$1:; }
48 fmtstate=begin
49
50 fmt () {
51   state=$1; shift
52
53   while :; do
54     ostate=$fmtstate
55     case $fmtstate in $state) break ;; esac
56     case "$fmtstate,$state" in
57       begin,end)
58         fmtstate=end
59         ;;
60       begin,*)
61         ${fmt}_header "$@"
62         fmtstate=hosttbl_begin
63         ;;
64       hosttbl_begin,hosttbl_*)
65         ${fmt}_hosttbl_begin "$@"
66         fmtstate=hosttbl_host
67         ;;
68       hosttbl_host,hosttbl_fs)
69         fmtstate=hosttbl_fs
70         ;;
71       hosttbl_fs,hosttbl_host)
72         ${fmt}_hosttbl_sep "$@"
73         fmtstate=hosttbl_host
74         ;;
75       hosttbl_begin,* | hosttbl_end,*)
76         fmtstate=logdump_begin
77         ;;
78       hosttbl_*,*)
79         ${fmt}_hosttbl_end "$@"
80         fmtstate=hosttbl_end
81         ;;
82       logdump_begin,logdump_*)
83         fmtstate=logdump_out
84         ;;
85       logdump_out,logdump_info | logdump_out,logdump_file)
86         fmtstate=$state
87         ;;
88       logdump_*,logdump_begin)
89         ${fmt}_logdump_end "$@"
90         fmtstate=logdump_begin
91         ;;
92       logdump_end,*)
93         fmtstate=footer
94         ;;
95       logdump_*,*)
96         fmtstate=logdump_end
97         ;;
98       footer,end)
99         ${fmt}_footer "$@"
100         fmtstate=end
101         ;;
102     esac
103     case $fmtstate in
104       "$ostate")
105         echo >&2 "$quis: FATAL!  NO PROGRESS IN FMT STATE MACHINE"
106         exit 9
107         ;;
108     esac
109   done
110
111   ${fmt}_$fmtstate "$@"
112 }
113
114 ###--------------------------------------------------------------------------
115 ### Plain text output.
116
117 defformat txt
118
119 txt_header () { :; }
120 txt_hosttbl_begin () { :; }
121 txt_hosttbl_host () { echo "HOST $1"; }
122 txt_hosttbl_fs () { printf "        %-23s %s\n" "$2" "$4"; }
123 txt_hosttbl_sep () { echo; }
124 txt_hosttbl_end () { :; }
125
126 txt_logdump_begin () {
127   cat <<EOF
128
129 -----------------------------------------------------------------------------
130 Log tail for $1:$2
131
132 EOF
133 }
134
135 txt_logdump_info () { echo "!!! $3"; }
136 txt_logdump_file () { cat "$3"; }
137 txt_logdump_end () { :; }
138
139 txt_footer () { :; }
140 txt_end () { :; }
141
142 fmt=txt
143
144 ###--------------------------------------------------------------------------
145 ### HTML output.
146
147 defformat html
148
149 html_header () {
150   cat <<EOF
151 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
152           "http://www.w3c.org/TR/html4/strict.dtd">
153 <html>
154 <head>
155   <title>$now rsync-backup report</title>
156   <style type='text/css'><!--
157         body {
158           background: white;
159           color: black;
160           margin: 1ex 2em;
161         }
162
163         h1, h2 { font-family: sans-serif; font-weight: bold; }
164
165         h1 {
166           padding-bottom: 1ex;
167           border-bottom: solid medium black;
168           margin-bottom: 3ex;
169           font-size: 200%;
170         }
171
172         h2 {
173           border-top: solid thin black;
174           padding-top: 1ex;
175           margin-top: 3ex;
176           font-size: x-large;
177         }
178
179         h1 + h2 {
180           border-top: none;
181           padding-top: 0;
182         }
183
184         div.footer {
185           border-top: solid medium black;
186           margin-top: 3ex;
187           padding-top: 1ex;
188           font-size: small;
189           clear: both;
190           text-align: right;
191           font-style: italic;
192         }
193
194         table.hosttbl {
195           border-collapse: collapse;
196           display: inline-table;
197           margin: 1ex 1em;
198         }
199         table.hosttbl tr.fs td {
200           border: solid thin black;
201         }
202         table.hosttbl td { padding: 0.5ex 0.5em; }
203         table.hosttbl tr.host td {
204           padding-top: 2ex;
205           text-align: center;
206           font-weight: bold;
207           font-family: monospace;
208         }
209         table.hosttbl td.fs {
210           font-family: monospace;
211         }
212
213         table.hosttbl td.winning { background: #2f4; }
214         table.hosttbl td.failed { background: #f80; }
215         table.hosttbl td.broken { background: #f11; }
216         table.hosttbl td.never { background: #66f; }
217
218         pre {
219           clear: both;
220           margin: 3ex 2em;
221           padding: 1ex;
222           background: #eee;
223           border: solid thin black;
224           overflow-y: auto;
225         }
226
227         .hide { display: none; }
228         a.expand-button {
229           float: right;
230           font-size: medium;
231           font-weight: initial;
232           padding-top: 1ex;
233         }
234
235         div.logdump-info {
236           margin: 3ex 2em;
237           padding: 1ex;
238           background: #f11;
239           border: solid thin black;
240         }
241   --></style>
242   <script type='text/javascript'><!--
243         var LAST_EXPAND = null;
244         function elt(id) { return document.getElementById(id); }
245         function add_elt_class(elt, cls) {
246           if (!elt.className.match('\\\\b' + cls + '\\\\b'))
247             elt.className += ' ' + cls;
248         }
249         function rm_elt_class(elt, cls) {
250           elt.className = elt.className.replace(
251             new RegExp('\\\\s*\\\\b' + cls + '\\\\b\\\\s*'), ' ');
252         }
253         function toggle_expand(ev, tag) {
254           var d = elt('logdump-' + tag);
255           var b = elt('expand-' + tag);
256           if (d.className.match(/\bhide\b/)) do_show(d, b);
257           else do_hide(d, b);
258           ev.preventDefault();
259         }
260         function do_show(d, b) {
261           rm_elt_class(d, 'hide');
262           b.textContent = '[hide]';
263         }
264         function do_hide(d, b) {
265           add_elt_class(d, 'hide');
266           b.textContent = '[show]';
267           if (LAST_EXPAND !== null && d === LAST_EXPAND[0])
268             LAST_EXPAND = null;
269         }
270         function expand_log(tag) {
271           if (LAST_EXPAND !== null) do_hide(LAST_EXPAND[0], LAST_EXPAND[1]);
272           var d = elt('logdump-' + tag);
273           var b = elt('expand-' + tag);
274           LAST_EXPAND = [d, b];
275           do_show(d, b);
276         }
277         function make_toggle_button(tag) {
278           document.write(
279             "<a class=expand-button id='expand-" + tag + "' href='#' " +
280                "onclick='toggle_expand(event, \"" + tag + "\")'>" +
281             "[show]" +
282             "</a>");
283         }
284         function hide_logdump(tag) {
285           add_elt_class(elt('logdump-' + tag), 'hide');
286         }
287   --></script>
288 </head>
289 <body>
290
291 <h1><tt>rsync-backup</tt> report: $now_dow $now</tt></h1>
292 EOF
293 }
294
295 html_hosttbl_begin () {
296   cat <<EOF
297
298 <h2>Overview</h2>
299 EOF
300 }
301
302 html_hosttbl_host () {
303   cat <<EOF
304 <table class=hosttbl>
305   <tr class=host><td colspan=2>$1</tc>
306 EOF
307 }
308
309 html_hosttbl_fs () {
310   case $3 in
311     winning) link="" knil="" ;;
312     *)
313       link="<a href='#log-$host:$fs' onclick='expand_log(\"$host:$fs\")'>"
314       knil="</a>"
315       ;;
316   esac
317   cat <<EOF
318   <tr class=fs>
319     <td class=fs>$2</td>
320     <td class=$3>$link$4$knil</td>
321 EOF
322 }
323
324 html_hosttbl_sep () {
325   cat <<EOF
326 </table>
327 EOF
328 }
329
330 html_hosttbl_end () {
331   cat <<EOF
332 </table>
333 EOF
334 }
335
336 html_logdump_begin () {
337   cat <<EOF
338
339 <h2 id='log-$1:$2'><a name='log-$1:$2'>Log tail for <tt>$1:$2</tt></a>
340 <script type='text/javascript'><!--
341         make_toggle_button('$1:$2');
342 --></script></h2>
343 EOF
344 }
345
346 html_logdump_info () {
347   cat <<EOF
348 <div id='logdump-$1:$2' class=logdump-info><script type='text/javascript'><!--
349         hide_logdump('$1:$2');
350 --></script>$3</div>
351 EOF
352 }
353
354 html_logdump_file () {
355   cat <<EOF
356 <pre id='logdump-$1:$2' class=logdump>
357 <script type='text/javascript'><!--
358         hide_logdump('$1:$2');
359 --></script>$3</div>
360 EOF
361   cat "$3"
362   cat <<EOF
363 </pre>
364 EOF
365 }
366
367 html_logdump_end () { :; }
368
369 html_footer () {
370   cat <<EOF
371
372 <div class=footer>
373   Checked at $now $now_time.
374   <br><tt>rsync-backup</tt> $VERSION; &copy; 2014 Mark Wooding
375 </div>
376
377 </body>
378 </html>
379 EOF
380 }
381
382 html_end () { :; }
383
384 ###--------------------------------------------------------------------------
385 ### Main checking.
386
387 INDEXDB=@pkglocalstatedir@/index.db
388
389 host () { host=$1; }
390
391 patterns=
392 showhost=
393 failures=
394
395 backup () {
396   for fs in "$@"; do
397     case $fs in *:*) fs=${fs%%:*} ;; esac
398     matchp=nil
399     for p in "${patterns[@]}"; do
400       case $host:$fs in $p) matchp=t; break ;; esac
401     done
402     case $matchp in nil) return ;; esac
403     when=$(sqlite3 $INDEXDB \
404       "SELECT MAX(date) FROM idx WHERE host = '$host' AND fs = '$fs';")
405     case $when in
406       "")
407         class=never
408         info="NEVER"
409         show=t
410         win=nil
411         ;;
412       "$now")
413         class=winning
414         info="today"
415         show=$verbose
416         win=t
417         ;;
418       *)
419         jdn=$(julian "$when")
420         ago=$(( $now_jdn - $jdn ))
421         case $ago in 1 | -1) days=day ;; *) days=days ;; esac
422         case $ago in
423           -*) class=future; info="${ago#-} $days in the FUTURE" ;;
424           1 | 2) class=failed; info="$ago $days ago" ;;
425           *) class=broken; info="$ago $days ago" ;;
426         esac
427         show=t
428         win=nil
429         ;;
430     esac
431     case $show in
432       t)
433         case $showhost in
434           "$host") ;;
435           *)
436             fmt hosttbl_host $host
437             showhost=$host
438             ;;
439         esac
440         fmt hosttbl_fs $host $fs $class "$info"
441         case $win in
442           nil) failures="$failures $host:$fs" ;;
443         esac
444         ;;
445     esac
446   done
447 }
448
449 failure_logs () {
450   for fail in $failures; do
451     host=${fail%:*} fs=${fail##*:}
452     any=nil
453     for i in "$logdir/$host/$fs.$now#"*; do
454       if [ -f "$i" ]; then log=$i; any=t; fi
455     done
456     fmt logdump_begin $host $fs
457     case $any in
458       t) fmt logdump_file $host $fs "$log" ;;
459       nil) fmt logdump_info $host $fs "No log!  No backup attempted." ;;
460     esac
461   done
462 }
463
464 ###--------------------------------------------------------------------------
465 ### Read the configuration and we're done.
466
467 usage () {
468   echo "usage: $quis [-v] [-c CONF] [-f FORMAT] [PATTERNS...]"
469 }
470
471 version () {
472   echo "$quis, rsync-backup version $VERSION"
473 }
474
475 verbose=nil
476
477 while getopts "hVc:f:v" opt; do
478   case "$opt" in
479     h) usage; exit 0 ;;
480     V) version; config; exit 0 ;;
481     c) conf=$OPTARG ;;
482     f)
483       case $formats in
484         *":$OPTARG:"*) ;;
485         *) echo >&2 "$0: unknown format \`$OPTARG'"; exit 1 ;;
486       esac
487       fmt=$OPTARG
488       ;;
489     v) verbose=t ;;
490     *) exit 1 ;;
491   esac
492 done
493 shift $((OPTIND - 1))
494 case $# in
495   0) declare -a patterns=("*") ;;
496   *) declare -a patterns=("$@") ;;
497 esac
498
499 now=$(date +"%Y-%m-%d")
500 now_time=$(date +"%H:%M:%S %z")
501 now_dow=$(date +"%A")
502 now_jdn=$(julian $now)
503 . "$conf"
504 failure_logs
505 fmt end
506
507 ###----- That's all, folks --------------------------------------------------