Release 1.1.1.
[rsync-backup] / check-bkp-status.in
CommitLineData
9f0350f9
MW
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
26set -e
27
28quis=${0##*/}
29. @pkgdatadir@/lib.sh
30
31###--------------------------------------------------------------------------
32### Stubs for the configuration file.
33
34defhook () { :; }
35addhook () { :; }
36
37retain () { :; }
38snaptype () { :; }
39like () { :; }
40retry () { :; }
41user () { :; }
42
43###--------------------------------------------------------------------------
44### Output format switch.
45
46formats=:
47defformat () { formats=$formats$1:; }
48fmtstate=begin
49
50fmt () {
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
117defformat txt
118
119txt_header () { :; }
120txt_hosttbl_begin () { :; }
121txt_hosttbl_host () { echo "HOST $1"; }
122txt_hosttbl_fs () { printf " %-23s %s\n" "$2" "$4"; }
123txt_hosttbl_sep () { echo; }
124txt_hosttbl_end () { :; }
125
126txt_logdump_begin () {
127 cat <<EOF
128
129-----------------------------------------------------------------------------
130Log tail for $1:$2
131
132EOF
133}
134
135txt_logdump_info () { echo "!!! $3"; }
136txt_logdump_file () { cat "$3"; }
137txt_logdump_end () { :; }
138
139txt_footer () { :; }
140txt_end () { :; }
141
142fmt=txt
143
144###--------------------------------------------------------------------------
145### HTML output.
146
147defformat html
148
149html_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>
688e8fc3 156 <meta name=viewport content="width=device-width initial-scale=1.0">
9f0350f9
MW
157 <style type='text/css'><!--
158 body {
159 background: white;
160 color: black;
161 margin: 1ex 2em;
162 }
163
164 h1, h2 { font-family: sans-serif; font-weight: bold; }
165
166 h1 {
167 padding-bottom: 1ex;
168 border-bottom: solid medium black;
169 margin-bottom: 3ex;
170 font-size: 200%;
171 }
172
173 h2 {
174 border-top: solid thin black;
175 padding-top: 1ex;
176 margin-top: 3ex;
177 font-size: x-large;
178 }
179
180 h1 + h2 {
181 border-top: none;
182 padding-top: 0;
183 }
184
185 div.footer {
186 border-top: solid medium black;
187 margin-top: 3ex;
188 padding-top: 1ex;
189 font-size: small;
190 clear: both;
191 text-align: right;
192 font-style: italic;
193 }
194
195 table.hosttbl {
196 border-collapse: collapse;
197 display: inline-table;
198 margin: 1ex 1em;
199 }
200 table.hosttbl tr.fs td {
201 border: solid thin black;
202 }
203 table.hosttbl td { padding: 0.5ex 0.5em; }
204 table.hosttbl tr.host td {
205 padding-top: 2ex;
206 text-align: center;
207 font-weight: bold;
208 font-family: monospace;
209 }
210 table.hosttbl td.fs {
211 font-family: monospace;
212 }
213
214 table.hosttbl td.winning { background: #2f4; }
215 table.hosttbl td.failed { background: #f80; }
216 table.hosttbl td.broken { background: #f11; }
217 table.hosttbl td.never { background: #66f; }
218
219 pre {
220 clear: both;
221 margin: 3ex 2em;
222 padding: 1ex;
223 background: #eee;
224 border: solid thin black;
1093ae15 225 overflow: auto;
9f0350f9
MW
226 }
227
1093ae15
MW
228 pre.logdump { max-height: 120ex; }
229
9e224eb1
MW
230 .hide { display: none; }
231 a.expand-button {
232 float: right;
233 font-size: medium;
234 font-weight: initial;
235 padding-top: 1ex;
236 }
237
9f0350f9
MW
238 div.logdump-info {
239 margin: 3ex 2em;
240 padding: 1ex;
241 background: #f11;
242 border: solid thin black;
243 }
244 --></style>
9e224eb1
MW
245 <script type='text/javascript'><!--
246 var LAST_EXPAND = null;
247 function elt(id) { return document.getElementById(id); }
cc950b1c
MW
248 function elt_class_p(elt, cls) {
249 return elt.className.match('\\\\b' + cls + '\\\\b');
250 }
9e224eb1 251 function add_elt_class(elt, cls) {
cc950b1c 252 if (!elt_class_p(elt, cls)) elt.className += ' ' + cls;
9e224eb1
MW
253 }
254 function rm_elt_class(elt, cls) {
255 elt.className = elt.className.replace(
256 new RegExp('\\\\s*\\\\b' + cls + '\\\\b\\\\s*'), ' ');
257 }
258 function toggle_expand(ev, tag) {
259 var d = elt('logdump-' + tag);
260 var b = elt('expand-' + tag);
cc950b1c 261 if (elt_class_p(d, 'hide')) do_show(d, b);
9e224eb1
MW
262 else do_hide(d, b);
263 ev.preventDefault();
264 }
265 function do_show(d, b) {
266 rm_elt_class(d, 'hide');
267 b.textContent = '[hide]';
268 }
269 function do_hide(d, b) {
270 add_elt_class(d, 'hide');
271 b.textContent = '[show]';
afd4eb4b
MW
272 if (LAST_EXPAND !== null && d === LAST_EXPAND[0])
273 LAST_EXPAND = null;
9e224eb1
MW
274 }
275 function expand_log(tag) {
afd4eb4b 276 if (LAST_EXPAND !== null) do_hide(LAST_EXPAND[0], LAST_EXPAND[1]);
9e224eb1
MW
277 var d = elt('logdump-' + tag);
278 var b = elt('expand-' + tag);
b0c99c59
MW
279 if (elt_class_p(d, 'hide')) {
280 LAST_EXPAND = [d, b];
281 do_show(d, b);
282 }
9e224eb1
MW
283 }
284 function make_toggle_button(tag) {
285 document.write(
286 "<a class=expand-button id='expand-" + tag + "' href='#' " +
287 "onclick='toggle_expand(event, \"" + tag + "\")'>" +
288 "[show]" +
289 "</a>");
290 }
291 function hide_logdump(tag) {
292 add_elt_class(elt('logdump-' + tag), 'hide');
293 }
294 --></script>
9f0350f9
MW
295</head>
296<body>
297
4064bd38 298<h1><tt>rsync-backup</tt> report: $now_dow $now</tt></h1>
9f0350f9
MW
299EOF
300}
301
302html_hosttbl_begin () {
303 cat <<EOF
304
305<h2>Overview</h2>
306EOF
307}
308
309html_hosttbl_host () {
310 cat <<EOF
311<table class=hosttbl>
312 <tr class=host><td colspan=2>$1</tc>
313EOF
314}
315
316html_hosttbl_fs () {
317 case $3 in
318 winning) link="" knil="" ;;
9e224eb1
MW
319 *)
320 link="<a href='#log-$host:$fs' onclick='expand_log(\"$host:$fs\")'>"
321 knil="</a>"
322 ;;
9f0350f9
MW
323 esac
324 cat <<EOF
325 <tr class=fs>
326 <td class=fs>$2</td>
327 <td class=$3>$link$4$knil</td>
328EOF
329}
330
331html_hosttbl_sep () {
332 cat <<EOF
333</table>
334EOF
335}
336
337html_hosttbl_end () {
338 cat <<EOF
339</table>
340EOF
341}
342
343html_logdump_begin () {
344 cat <<EOF
345
9e224eb1
MW
346<h2 id='log-$1:$2'><a name='log-$1:$2'>Log tail for <tt>$1:$2</tt></a>
347<script type='text/javascript'><!--
348 make_toggle_button('$1:$2');
349--></script></h2>
9f0350f9
MW
350EOF
351}
352
353html_logdump_info () {
354 cat <<EOF
9e224eb1
MW
355<div id='logdump-$1:$2' class=logdump-info><script type='text/javascript'><!--
356 hide_logdump('$1:$2');
357--></script>$3</div>
9f0350f9
MW
358EOF
359}
360
361html_logdump_file () {
362 cat <<EOF
9e224eb1
MW
363<pre id='logdump-$1:$2' class=logdump>
364<script type='text/javascript'><!--
365 hide_logdump('$1:$2');
366--></script>$3</div>
9f0350f9
MW
367EOF
368 cat "$3"
369 cat <<EOF
370</pre>
371EOF
372}
373
374html_logdump_end () { :; }
375
376html_footer () {
377 cat <<EOF
378
379<div class=footer>
3d7eec09
MW
380 Checked at $now $now_time<br>
381 <tt>rsync-backup</tt> $VERSION; &copy; 2014 Mark Wooding
9f0350f9
MW
382</div>
383
384</body>
385</html>
386EOF
387}
388
389html_end () { :; }
390
391###--------------------------------------------------------------------------
392### Main checking.
393
394INDEXDB=@pkglocalstatedir@/index.db
395
396host () { host=$1; }
397
398patterns=
399showhost=
400failures=
401
402backup () {
403 for fs in "$@"; do
404 case $fs in *:*) fs=${fs%%:*} ;; esac
405 matchp=nil
406 for p in "${patterns[@]}"; do
407 case $host:$fs in $p) matchp=t; break ;; esac
408 done
409 case $matchp in nil) return ;; esac
fafa9288 410 when=$(sqlite3 -batch -list -separator \| -noheader $INDEXDB \
9f0350f9
MW
411 "SELECT MAX(date) FROM idx WHERE host = '$host' AND fs = '$fs';")
412 case $when in
413 "")
414 class=never
415 info="NEVER"
416 show=t
417 win=nil
418 ;;
419 "$now")
420 class=winning
421 info="today"
422 show=$verbose
423 win=t
424 ;;
425 *)
426 jdn=$(julian "$when")
427 ago=$(( $now_jdn - $jdn ))
428 case $ago in 1 | -1) days=day ;; *) days=days ;; esac
429 case $ago in
430 -*) class=future; info="${ago#-} $days in the FUTURE" ;;
431 1 | 2) class=failed; info="$ago $days ago" ;;
432 *) class=broken; info="$ago $days ago" ;;
433 esac
434 show=t
435 win=nil
436 ;;
437 esac
438 case $show in
439 t)
440 case $showhost in
441 "$host") ;;
442 *)
443 fmt hosttbl_host $host
444 showhost=$host
445 ;;
446 esac
447 fmt hosttbl_fs $host $fs $class "$info"
448 case $win in
449 nil) failures="$failures $host:$fs" ;;
450 esac
451 ;;
452 esac
453 done
454}
455
456failure_logs () {
457 for fail in $failures; do
458 host=${fail%:*} fs=${fail##*:}
459 any=nil
460 for i in "$logdir/$host/$fs.$now#"*; do
461 if [ -f "$i" ]; then log=$i; any=t; fi
462 done
463 fmt logdump_begin $host $fs
464 case $any in
465 t) fmt logdump_file $host $fs "$log" ;;
466 nil) fmt logdump_info $host $fs "No log! No backup attempted." ;;
467 esac
468 done
469}
470
471###--------------------------------------------------------------------------
472### Read the configuration and we're done.
473
474usage () {
475 echo "usage: $quis [-v] [-c CONF] [-f FORMAT] [PATTERNS...]"
476}
477
478version () {
479 echo "$quis, rsync-backup version $VERSION"
480}
481
482verbose=nil
483
484while getopts "hVc:f:v" opt; do
485 case "$opt" in
486 h) usage; exit 0 ;;
487 V) version; config; exit 0 ;;
488 c) conf=$OPTARG ;;
489 f)
490 case $formats in
491 *":$OPTARG:"*) ;;
492 *) echo >&2 "$0: unknown format \`$OPTARG'"; exit 1 ;;
493 esac
494 fmt=$OPTARG
495 ;;
496 v) verbose=t ;;
497 *) exit 1 ;;
498 esac
499done
500shift $((OPTIND - 1))
501case $# in
502 0) declare -a patterns=("*") ;;
503 *) declare -a patterns=("$@") ;;
504esac
505
506now=$(date +"%Y-%m-%d")
507now_time=$(date +"%H:%M:%S %z")
4064bd38 508now_dow=$(date +"%A")
9f0350f9
MW
509now_jdn=$(julian $now)
510. "$conf"
511failure_logs
512fmt end
513
514###----- That's all, folks --------------------------------------------------