Commit | Line | Data |
---|---|---|
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 | ||
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; | |
1093ae15 | 224 | overflow: auto; |
9f0350f9 MW |
225 | } |
226 | ||
1093ae15 MW |
227 | pre.logdump { max-height: 120ex; } |
228 | ||
9e224eb1 MW |
229 | .hide { display: none; } |
230 | a.expand-button { | |
231 | float: right; | |
232 | font-size: medium; | |
233 | font-weight: initial; | |
234 | padding-top: 1ex; | |
235 | } | |
236 | ||
9f0350f9 MW |
237 | div.logdump-info { |
238 | margin: 3ex 2em; | |
239 | padding: 1ex; | |
240 | background: #f11; | |
241 | border: solid thin black; | |
242 | } | |
243 | --></style> | |
9e224eb1 MW |
244 | <script type='text/javascript'><!-- |
245 | var LAST_EXPAND = null; | |
246 | function elt(id) { return document.getElementById(id); } | |
cc950b1c MW |
247 | function elt_class_p(elt, cls) { |
248 | return elt.className.match('\\\\b' + cls + '\\\\b'); | |
249 | } | |
9e224eb1 | 250 | function add_elt_class(elt, cls) { |
cc950b1c | 251 | if (!elt_class_p(elt, cls)) elt.className += ' ' + cls; |
9e224eb1 MW |
252 | } |
253 | function rm_elt_class(elt, cls) { | |
254 | elt.className = elt.className.replace( | |
255 | new RegExp('\\\\s*\\\\b' + cls + '\\\\b\\\\s*'), ' '); | |
256 | } | |
257 | function toggle_expand(ev, tag) { | |
258 | var d = elt('logdump-' + tag); | |
259 | var b = elt('expand-' + tag); | |
cc950b1c | 260 | if (elt_class_p(d, 'hide')) do_show(d, b); |
9e224eb1 MW |
261 | else do_hide(d, b); |
262 | ev.preventDefault(); | |
263 | } | |
264 | function do_show(d, b) { | |
265 | rm_elt_class(d, 'hide'); | |
266 | b.textContent = '[hide]'; | |
267 | } | |
268 | function do_hide(d, b) { | |
269 | add_elt_class(d, 'hide'); | |
270 | b.textContent = '[show]'; | |
afd4eb4b MW |
271 | if (LAST_EXPAND !== null && d === LAST_EXPAND[0]) |
272 | LAST_EXPAND = null; | |
9e224eb1 MW |
273 | } |
274 | function expand_log(tag) { | |
afd4eb4b | 275 | if (LAST_EXPAND !== null) do_hide(LAST_EXPAND[0], LAST_EXPAND[1]); |
9e224eb1 MW |
276 | var d = elt('logdump-' + tag); |
277 | var b = elt('expand-' + tag); | |
278 | LAST_EXPAND = [d, b]; | |
279 | do_show(d, b); | |
280 | } | |
281 | function make_toggle_button(tag) { | |
282 | document.write( | |
283 | "<a class=expand-button id='expand-" + tag + "' href='#' " + | |
284 | "onclick='toggle_expand(event, \"" + tag + "\")'>" + | |
285 | "[show]" + | |
286 | "</a>"); | |
287 | } | |
288 | function hide_logdump(tag) { | |
289 | add_elt_class(elt('logdump-' + tag), 'hide'); | |
290 | } | |
291 | --></script> | |
9f0350f9 MW |
292 | </head> |
293 | <body> | |
294 | ||
4064bd38 | 295 | <h1><tt>rsync-backup</tt> report: $now_dow $now</tt></h1> |
9f0350f9 MW |
296 | EOF |
297 | } | |
298 | ||
299 | html_hosttbl_begin () { | |
300 | cat <<EOF | |
301 | ||
302 | <h2>Overview</h2> | |
303 | EOF | |
304 | } | |
305 | ||
306 | html_hosttbl_host () { | |
307 | cat <<EOF | |
308 | <table class=hosttbl> | |
309 | <tr class=host><td colspan=2>$1</tc> | |
310 | EOF | |
311 | } | |
312 | ||
313 | html_hosttbl_fs () { | |
314 | case $3 in | |
315 | winning) link="" knil="" ;; | |
9e224eb1 MW |
316 | *) |
317 | link="<a href='#log-$host:$fs' onclick='expand_log(\"$host:$fs\")'>" | |
318 | knil="</a>" | |
319 | ;; | |
9f0350f9 MW |
320 | esac |
321 | cat <<EOF | |
322 | <tr class=fs> | |
323 | <td class=fs>$2</td> | |
324 | <td class=$3>$link$4$knil</td> | |
325 | EOF | |
326 | } | |
327 | ||
328 | html_hosttbl_sep () { | |
329 | cat <<EOF | |
330 | </table> | |
331 | EOF | |
332 | } | |
333 | ||
334 | html_hosttbl_end () { | |
335 | cat <<EOF | |
336 | </table> | |
337 | EOF | |
338 | } | |
339 | ||
340 | html_logdump_begin () { | |
341 | cat <<EOF | |
342 | ||
9e224eb1 MW |
343 | <h2 id='log-$1:$2'><a name='log-$1:$2'>Log tail for <tt>$1:$2</tt></a> |
344 | <script type='text/javascript'><!-- | |
345 | make_toggle_button('$1:$2'); | |
346 | --></script></h2> | |
9f0350f9 MW |
347 | EOF |
348 | } | |
349 | ||
350 | html_logdump_info () { | |
351 | cat <<EOF | |
9e224eb1 MW |
352 | <div id='logdump-$1:$2' class=logdump-info><script type='text/javascript'><!-- |
353 | hide_logdump('$1:$2'); | |
354 | --></script>$3</div> | |
9f0350f9 MW |
355 | EOF |
356 | } | |
357 | ||
358 | html_logdump_file () { | |
359 | cat <<EOF | |
9e224eb1 MW |
360 | <pre id='logdump-$1:$2' class=logdump> |
361 | <script type='text/javascript'><!-- | |
362 | hide_logdump('$1:$2'); | |
363 | --></script>$3</div> | |
9f0350f9 MW |
364 | EOF |
365 | cat "$3" | |
366 | cat <<EOF | |
367 | </pre> | |
368 | EOF | |
369 | } | |
370 | ||
371 | html_logdump_end () { :; } | |
372 | ||
373 | html_footer () { | |
374 | cat <<EOF | |
375 | ||
376 | <div class=footer> | |
3d7eec09 MW |
377 | Checked at $now $now_time<br> |
378 | <tt>rsync-backup</tt> $VERSION; © 2014 Mark Wooding | |
9f0350f9 MW |
379 | </div> |
380 | ||
381 | </body> | |
382 | </html> | |
383 | EOF | |
384 | } | |
385 | ||
386 | html_end () { :; } | |
387 | ||
388 | ###-------------------------------------------------------------------------- | |
389 | ### Main checking. | |
390 | ||
391 | INDEXDB=@pkglocalstatedir@/index.db | |
392 | ||
393 | host () { host=$1; } | |
394 | ||
395 | patterns= | |
396 | showhost= | |
397 | failures= | |
398 | ||
399 | backup () { | |
400 | for fs in "$@"; do | |
401 | case $fs in *:*) fs=${fs%%:*} ;; esac | |
402 | matchp=nil | |
403 | for p in "${patterns[@]}"; do | |
404 | case $host:$fs in $p) matchp=t; break ;; esac | |
405 | done | |
406 | case $matchp in nil) return ;; esac | |
407 | when=$(sqlite3 $INDEXDB \ | |
408 | "SELECT MAX(date) FROM idx WHERE host = '$host' AND fs = '$fs';") | |
409 | case $when in | |
410 | "") | |
411 | class=never | |
412 | info="NEVER" | |
413 | show=t | |
414 | win=nil | |
415 | ;; | |
416 | "$now") | |
417 | class=winning | |
418 | info="today" | |
419 | show=$verbose | |
420 | win=t | |
421 | ;; | |
422 | *) | |
423 | jdn=$(julian "$when") | |
424 | ago=$(( $now_jdn - $jdn )) | |
425 | case $ago in 1 | -1) days=day ;; *) days=days ;; esac | |
426 | case $ago in | |
427 | -*) class=future; info="${ago#-} $days in the FUTURE" ;; | |
428 | 1 | 2) class=failed; info="$ago $days ago" ;; | |
429 | *) class=broken; info="$ago $days ago" ;; | |
430 | esac | |
431 | show=t | |
432 | win=nil | |
433 | ;; | |
434 | esac | |
435 | case $show in | |
436 | t) | |
437 | case $showhost in | |
438 | "$host") ;; | |
439 | *) | |
440 | fmt hosttbl_host $host | |
441 | showhost=$host | |
442 | ;; | |
443 | esac | |
444 | fmt hosttbl_fs $host $fs $class "$info" | |
445 | case $win in | |
446 | nil) failures="$failures $host:$fs" ;; | |
447 | esac | |
448 | ;; | |
449 | esac | |
450 | done | |
451 | } | |
452 | ||
453 | failure_logs () { | |
454 | for fail in $failures; do | |
455 | host=${fail%:*} fs=${fail##*:} | |
456 | any=nil | |
457 | for i in "$logdir/$host/$fs.$now#"*; do | |
458 | if [ -f "$i" ]; then log=$i; any=t; fi | |
459 | done | |
460 | fmt logdump_begin $host $fs | |
461 | case $any in | |
462 | t) fmt logdump_file $host $fs "$log" ;; | |
463 | nil) fmt logdump_info $host $fs "No log! No backup attempted." ;; | |
464 | esac | |
465 | done | |
466 | } | |
467 | ||
468 | ###-------------------------------------------------------------------------- | |
469 | ### Read the configuration and we're done. | |
470 | ||
471 | usage () { | |
472 | echo "usage: $quis [-v] [-c CONF] [-f FORMAT] [PATTERNS...]" | |
473 | } | |
474 | ||
475 | version () { | |
476 | echo "$quis, rsync-backup version $VERSION" | |
477 | } | |
478 | ||
479 | verbose=nil | |
480 | ||
481 | while getopts "hVc:f:v" opt; do | |
482 | case "$opt" in | |
483 | h) usage; exit 0 ;; | |
484 | V) version; config; exit 0 ;; | |
485 | c) conf=$OPTARG ;; | |
486 | f) | |
487 | case $formats in | |
488 | *":$OPTARG:"*) ;; | |
489 | *) echo >&2 "$0: unknown format \`$OPTARG'"; exit 1 ;; | |
490 | esac | |
491 | fmt=$OPTARG | |
492 | ;; | |
493 | v) verbose=t ;; | |
494 | *) exit 1 ;; | |
495 | esac | |
496 | done | |
497 | shift $((OPTIND - 1)) | |
498 | case $# in | |
499 | 0) declare -a patterns=("*") ;; | |
500 | *) declare -a patterns=("$@") ;; | |
501 | esac | |
502 | ||
503 | now=$(date +"%Y-%m-%d") | |
504 | now_time=$(date +"%H:%M:%S %z") | |
4064bd38 | 505 | now_dow=$(date +"%A") |
9f0350f9 MW |
506 | now_jdn=$(julian $now) |
507 | . "$conf" | |
508 | failure_logs | |
509 | fmt end | |
510 | ||
511 | ###----- That's all, folks -------------------------------------------------- |