chiark / gitweb /
check-bkp-status: New program writes reports about backups.
authorMark Wooding <mdw@distorted.org.uk>
Thu, 16 Jan 2014 10:06:33 +0000 (10:06 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Thu, 16 Jan 2014 10:15:41 +0000 (10:15 +0000)
Makefile.am
check-bkp-status.8 [new file with mode: 0644]
check-bkp-status.in [new file with mode: 0644]
debian/rsync-backup.install
rsync-backup.8

index 413d541b16792014ddd10e9a574f57647a8484c6..90618b8d1f930f98a4269c76756d3e6caef5655a 100644 (file)
@@ -99,6 +99,16 @@ update-bkp-index: update-bkp-index.in Makefile
                chmod +x update-bkp-index.new && \
                mv update-bkp-index.new update-bkp-index
 
+sbin_SCRIPTS           += check-bkp-status
+dist_man_MANS          += check-bkp-status.8
+CLEANFILES             += check-bkp-status
+EXTRA_DIST             += check-bkp-status.in
+check-bkp-status: check-bkp-status.in Makefile
+       $(SUBST) >check-bkp-status.new \
+                       $(srcdir)/check-bkp-status.in $(SUBSTVARS) && \
+               chmod +x check-bkp-status.new && \
+               mv check-bkp-status.new check-bkp-status
+
 bin_SCRIPTS            += fshash
 dist_man_MANS          += fshash.1
 CLEANFILES             += fshash
diff --git a/check-bkp-status.8 b/check-bkp-status.8
new file mode 100644 (file)
index 0000000..c76ed9c
--- /dev/null
@@ -0,0 +1,60 @@
+.ie t .ds o \(bu
+.el .ds o o
+.de hP
+.IP
+\h'-\w'\fB\\$1\ \fP'u'\fB\\$1\ \fP\c
+..
+.TH check-bkp-status 8 "16 January 2014" rsync-backup
+.SH NAME
+check-bkp-status \- write backup report
+.SH SYNOPSIS
+.B check-bkp-status
+.RB [ \-v ]
+.RB [ \-c
+.IR config-file ]
+.RB [ \-f
+.IR format ]
+.SH DESCRIPTION
+The
+.B check-bkp-status
+script writes to its standard output a report about the current backup
+status by checking the index database and logfiles written by
+.BR rsync-backup (8).
+.PP
+The following command-line options are recognized.
+.TP
+.B \-h
+Show a brief help message for the program, and exit successfully.
+.TP
+.B \-V
+Show
+.BR check-bkp-status 's
+version number and some choice pieces of build-time configuration, and
+exit successfully.
+.TP
+.BI "\-c " conf
+Read
+.I conf
+instead of the default configuration file (shown as
+.B conf
+in the
+.B \-V
+output).
+.TP
+.BI "\-f " format
+Write output in
+.IR format ,
+which may be
+.B txt
+for plain text output, suitable for email, or
+.B html
+for HTML output, suitable for use as a web page.
+.TP
+.B \-v
+Write information for all backups, rather than just those which failed.
+By default, if all backups are up-to-date, then no output is produced at
+all.
+.SH SEE ALSO
+.BR rsync-backup (8).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/check-bkp-status.in b/check-bkp-status.in
new file mode 100644 (file)
index 0000000..15afadd
--- /dev/null
@@ -0,0 +1,441 @@
+#! @BASH@
+###
+### Check that dumps have been made as expected.
+###
+### (c) 2013 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the `rsync-backup' program.
+###
+### rsync-backup is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### rsync-backup is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with rsync-backup; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+set -e
+
+quis=${0##*/}
+. @pkgdatadir@/lib.sh
+
+###--------------------------------------------------------------------------
+### Stubs for the configuration file.
+
+defhook () { :; }
+addhook () { :; }
+
+retain () { :; }
+snaptype () { :; }
+like () { :; }
+retry () { :; }
+user () { :; }
+
+###--------------------------------------------------------------------------
+### Output format switch.
+
+formats=:
+defformat () { formats=$formats$1:; }
+fmtstate=begin
+
+fmt () {
+  state=$1; shift
+
+  while :; do
+    ostate=$fmtstate
+    case $fmtstate in $state) break ;; esac
+    case "$fmtstate,$state" in
+      begin,end)
+       fmtstate=end
+       ;;
+      begin,*)
+       ${fmt}_header "$@"
+       fmtstate=hosttbl_begin
+       ;;
+      hosttbl_begin,hosttbl_*)
+       ${fmt}_hosttbl_begin "$@"
+       fmtstate=hosttbl_host
+       ;;
+      hosttbl_host,hosttbl_fs)
+       fmtstate=hosttbl_fs
+       ;;
+      hosttbl_fs,hosttbl_host)
+       ${fmt}_hosttbl_sep "$@"
+       fmtstate=hosttbl_host
+       ;;
+      hosttbl_begin,* | hosttbl_end,*)
+       fmtstate=logdump_begin
+       ;;
+      hosttbl_*,*)
+       ${fmt}_hosttbl_end "$@"
+       fmtstate=hosttbl_end
+       ;;
+      logdump_begin,logdump_*)
+       fmtstate=logdump_out
+       ;;
+      logdump_out,logdump_info | logdump_out,logdump_file)
+       fmtstate=$state
+       ;;
+      logdump_*,logdump_begin)
+       ${fmt}_logdump_end "$@"
+       fmtstate=logdump_begin
+       ;;
+      logdump_end,*)
+       fmtstate=footer
+       ;;
+      logdump_*,*)
+       fmtstate=logdump_end
+       ;;
+      footer,end)
+       ${fmt}_footer "$@"
+       fmtstate=end
+       ;;
+    esac
+    case $fmtstate in
+      "$ostate")
+       echo >&2 "$quis: FATAL!  NO PROGRESS IN FMT STATE MACHINE"
+       exit 9
+       ;;
+    esac
+  done
+
+  ${fmt}_$fmtstate "$@"
+}
+
+###--------------------------------------------------------------------------
+### Plain text output.
+
+defformat txt
+
+txt_header () { :; }
+txt_hosttbl_begin () { :; }
+txt_hosttbl_host () { echo "HOST $1"; }
+txt_hosttbl_fs () { printf "        %-23s %s\n" "$2" "$4"; }
+txt_hosttbl_sep () { echo; }
+txt_hosttbl_end () { :; }
+
+txt_logdump_begin () {
+  cat <<EOF
+
+-----------------------------------------------------------------------------
+Log tail for $1:$2
+
+EOF
+}
+
+txt_logdump_info () { echo "!!! $3"; }
+txt_logdump_file () { cat "$3"; }
+txt_logdump_end () { :; }
+
+txt_footer () { :; }
+txt_end () { :; }
+
+fmt=txt
+
+###--------------------------------------------------------------------------
+### HTML output.
+
+defformat html
+
+html_header () {
+  cat <<EOF
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+         "http://www.w3c.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <title>$now rsync-backup report</title>
+  <style type='text/css'><!--
+       body {
+         background: white;
+         color: black;
+         margin: 1ex 2em;
+       }
+
+       h1, h2 { font-family: sans-serif; font-weight: bold; }
+
+       h1 {
+         padding-bottom: 1ex;
+         border-bottom: solid medium black;
+         margin-bottom: 3ex;
+         font-size: 200%;
+       }
+
+       h2 {
+         border-top: solid thin black;
+         padding-top: 1ex;
+         margin-top: 3ex;
+         font-size: x-large;
+       }
+
+       h1 + h2 {
+         border-top: none;
+         padding-top: 0;
+       }
+
+       div.footer {
+         border-top: solid medium black;
+         margin-top: 3ex;
+         padding-top: 1ex;
+         font-size: small;
+         clear: both;
+         text-align: right;
+         font-style: italic;
+       }
+
+       table.hosttbl {
+         border-collapse: collapse;
+         display: inline-table;
+         margin: 1ex 1em;
+       }
+       table.hosttbl tr.fs td {
+         border: solid thin black;
+       }
+       table.hosttbl td { padding: 0.5ex 0.5em; }
+       table.hosttbl tr.host td {
+         padding-top: 2ex;
+         text-align: center;
+         font-weight: bold;
+         font-family: monospace;
+       }
+       table.hosttbl td.fs {
+         font-family: monospace;
+       }
+
+       table.hosttbl td.winning { background: #2f4; }
+       table.hosttbl td.failed { background: #f80; }
+       table.hosttbl td.broken { background: #f11; }
+       table.hosttbl td.never { background: #66f; }
+
+       pre {
+         clear: both;
+         margin: 3ex 2em;
+         padding: 1ex;
+         background: #eee;
+         border: solid thin black;
+         overflow-y: auto;
+       }
+
+       div.logdump-info {
+         margin: 3ex 2em;
+         padding: 1ex;
+         background: #f11;
+         border: solid thin black;
+       }
+  --></style>
+</head>
+<body>
+
+<h1><tt>rsync-backup</tt> report: $now</tt></h1>
+EOF
+}
+
+html_hosttbl_begin () {
+  cat <<EOF
+
+<h2>Overview</h2>
+EOF
+}
+
+html_hosttbl_host () {
+  cat <<EOF
+<table class=hosttbl>
+  <tr class=host><td colspan=2>$1</tc>
+EOF
+}
+
+html_hosttbl_fs () {
+  case $3 in
+    winning) link="" knil="" ;;
+    *) link="<a href='#log-$host:$fs'>" knil="</a>";;
+  esac
+  cat <<EOF
+  <tr class=fs>
+    <td class=fs>$2</td>
+    <td class=$3>$link$4$knil</td>
+EOF
+}
+
+html_hosttbl_sep () {
+  cat <<EOF
+</table>
+EOF
+}
+
+html_hosttbl_end () {
+  cat <<EOF
+</table>
+EOF
+}
+
+html_logdump_begin () {
+  cat <<EOF
+
+<h2><a name='log-$1:$2'>Log tail for <tt>$1:$2</tt></a></h2>
+EOF
+}
+
+html_logdump_info () {
+  cat <<EOF
+<div class=logdump-info>$3</div>
+EOF
+}
+
+html_logdump_file () {
+  cat <<EOF
+<pre class=logdump>
+EOF
+  cat "$3"
+  cat <<EOF
+</pre>
+EOF
+}
+
+html_logdump_end () { :; }
+
+html_footer () {
+  cat <<EOF
+
+<div class=footer>
+  Checked at $now $now_time.
+  <br><tt>rsync-backup</tt> $VERSION; &copy; 2014 Mark Wooding
+</div>
+
+</body>
+</html>
+EOF
+}
+
+html_end () { :; }
+
+###--------------------------------------------------------------------------
+### Main checking.
+
+INDEXDB=@pkglocalstatedir@/index.db
+
+host () { host=$1; }
+
+patterns=
+showhost=
+failures=
+
+backup () {
+  for fs in "$@"; do
+    case $fs in *:*) fs=${fs%%:*} ;; esac
+    matchp=nil
+    for p in "${patterns[@]}"; do
+      case $host:$fs in $p) matchp=t; break ;; esac
+    done
+    case $matchp in nil) return ;; esac
+    when=$(sqlite3 $INDEXDB \
+      "SELECT MAX(date) FROM idx WHERE host = '$host' AND fs = '$fs';")
+    case $when in
+      "")
+       class=never
+       info="NEVER"
+       show=t
+       win=nil
+       ;;
+      "$now")
+       class=winning
+       info="today"
+       show=$verbose
+       win=t
+       ;;
+      *)
+       jdn=$(julian "$when")
+       ago=$(( $now_jdn - $jdn ))
+       case $ago in 1 | -1) days=day ;; *) days=days ;; esac
+       case $ago in
+         -*) class=future; info="${ago#-} $days in the FUTURE" ;;
+         1 | 2) class=failed; info="$ago $days ago" ;;
+         *) class=broken; info="$ago $days ago" ;;
+       esac
+       show=t
+       win=nil
+       ;;
+    esac
+    case $show in
+      t)
+       case $showhost in
+         "$host") ;;
+         *)
+           fmt hosttbl_host $host
+           showhost=$host
+           ;;
+       esac
+       fmt hosttbl_fs $host $fs $class "$info"
+       case $win in
+         nil) failures="$failures $host:$fs" ;;
+       esac
+       ;;
+    esac
+  done
+}
+
+failure_logs () {
+  for fail in $failures; do
+    host=${fail%:*} fs=${fail##*:}
+    any=nil
+    for i in "$logdir/$host/$fs.$now#"*; do
+      if [ -f "$i" ]; then log=$i; any=t; fi
+    done
+    fmt logdump_begin $host $fs
+    case $any in
+      t) fmt logdump_file $host $fs "$log" ;;
+      nil) fmt logdump_info $host $fs "No log!  No backup attempted." ;;
+    esac
+  done
+}
+
+###--------------------------------------------------------------------------
+### Read the configuration and we're done.
+
+usage () {
+  echo "usage: $quis [-v] [-c CONF] [-f FORMAT] [PATTERNS...]"
+}
+
+version () {
+  echo "$quis, rsync-backup version $VERSION"
+}
+
+verbose=nil
+
+while getopts "hVc:f:v" opt; do
+  case "$opt" in
+    h) usage; exit 0 ;;
+    V) version; config; exit 0 ;;
+    c) conf=$OPTARG ;;
+    f)
+      case $formats in
+       *":$OPTARG:"*) ;;
+       *) echo >&2 "$0: unknown format \`$OPTARG'"; exit 1 ;;
+      esac
+      fmt=$OPTARG
+      ;;
+    v) verbose=t ;;
+    *) exit 1 ;;
+  esac
+done
+shift $((OPTIND - 1))
+case $# in
+  0) declare -a patterns=("*") ;;
+  *) declare -a patterns=("$@") ;;
+esac
+
+now=$(date +"%Y-%m-%d")
+now_time=$(date +"%H:%M:%S %z")
+now_jdn=$(julian $now)
+. "$conf"
+failure_logs
+fmt end
+
+###----- That's all, folks --------------------------------------------------
index 99ef749df9230a24d81b49a655b789d6705849de..58f467444ce02b0693c15879d3b15954e43f4379 100644 (file)
@@ -1,5 +1,7 @@
 /usr/sbin/rsync-backup
+/usr/sbin/check-bkp-status
 /usr/sbin/update-bkp-index
 /usr/share/rsync-backup/lib.sh
 /usr/share/man/man8/rsync-backup.8
+/usr/share/man/man8/check-bkp-status.8
 /usr/share/man/man8/update-bkp-index.8
index 04a489a0285f99c5ca72e894b654a6fb64315628..5ff20bcdbc2688035e81533e5e75af89712b2c39 100644 (file)
@@ -551,6 +551,7 @@ There is also a symbolic link
 .B last
 referring to the most recent backup of the filesystem.
 .SH SEE ALSO
+.BR check-bkp-status (8),
 .BR fshash (1),
 .BR lvm (8),
 .BR rfreezefs (8),