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> | |
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 |
299 | EOF |
300 | } | |
301 | ||
302 | html_hosttbl_begin () { | |
303 | cat <<EOF | |
304 | ||
305 | <h2>Overview</h2> | |
306 | EOF | |
307 | } | |
308 | ||
309 | html_hosttbl_host () { | |
310 | cat <<EOF | |
311 | <table class=hosttbl> | |
312 | <tr class=host><td colspan=2>$1</tc> | |
313 | EOF | |
314 | } | |
315 | ||
316 | html_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> | |
328 | EOF | |
329 | } | |
330 | ||
331 | html_hosttbl_sep () { | |
332 | cat <<EOF | |
333 | </table> | |
334 | EOF | |
335 | } | |
336 | ||
337 | html_hosttbl_end () { | |
338 | cat <<EOF | |
339 | </table> | |
340 | EOF | |
341 | } | |
342 | ||
343 | html_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 |
350 | EOF |
351 | } | |
352 | ||
353 | html_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 |
358 | EOF |
359 | } | |
360 | ||
361 | html_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 |
367 | EOF |
368 | cat "$3" | |
369 | cat <<EOF | |
370 | </pre> | |
371 | EOF | |
372 | } | |
373 | ||
374 | html_logdump_end () { :; } | |
375 | ||
376 | html_footer () { | |
377 | cat <<EOF | |
378 | ||
379 | <div class=footer> | |
3d7eec09 MW |
380 | Checked at $now $now_time<br> |
381 | <tt>rsync-backup</tt> $VERSION; © 2014 Mark Wooding | |
9f0350f9 MW |
382 | </div> |
383 | ||
384 | </body> | |
385 | </html> | |
386 | EOF | |
387 | } | |
388 | ||
389 | html_end () { :; } | |
390 | ||
391 | ###-------------------------------------------------------------------------- | |
392 | ### Main checking. | |
393 | ||
394 | INDEXDB=@pkglocalstatedir@/index.db | |
395 | ||
396 | host () { host=$1; } | |
397 | ||
398 | patterns= | |
399 | showhost= | |
400 | failures= | |
401 | ||
402 | backup () { | |
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 | ||
456 | failure_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 | ||
474 | usage () { | |
475 | echo "usage: $quis [-v] [-c CONF] [-f FORMAT] [PATTERNS...]" | |
476 | } | |
477 | ||
478 | version () { | |
479 | echo "$quis, rsync-backup version $VERSION" | |
480 | } | |
481 | ||
482 | verbose=nil | |
483 | ||
484 | while 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 | |
499 | done | |
500 | shift $((OPTIND - 1)) | |
501 | case $# in | |
502 | 0) declare -a patterns=("*") ;; | |
503 | *) declare -a patterns=("$@") ;; | |
504 | esac | |
505 | ||
506 | now=$(date +"%Y-%m-%d") | |
507 | now_time=$(date +"%H:%M:%S %z") | |
4064bd38 | 508 | now_dow=$(date +"%A") |
9f0350f9 MW |
509 | now_jdn=$(julian $now) |
510 | . "$conf" | |
511 | failure_logs | |
512 | fmt end | |
513 | ||
514 | ###----- That's all, folks -------------------------------------------------- |