]> arthur.barton.de Git - backup-script.git/blob - bin/backup-audit
backup-status: Ignore disabled jobs when listing jobs with errors
[backup-script.git] / bin / backup-audit
1 #!/bin/bash
2 #
3 # backup-script system for cloning systems using rsync
4 # Copyright (c)2008-2016 Alexander Barton, alex@barton.de
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 # Please read the file COPYING, README and AUTHORS for more information.
11 #
12
13 NAME=$(basename "$0")
14
15 VERBOSE=0
16 QUIET=0
17
18 export LC_ALL=C
19
20 # Default settings, can be overwritten in backup-script.conf:
21 [ -d "/usr/local/etc/backup-script.d" ] \
22         && conf_d="/usr/local/etc/backup-script.d" \
23         || conf_d="/etc/backup-script.d"
24
25 default_backup_type="rsync"
26 default_files="running-config"
27 default_generations=0
28 default_target="/var/backups"
29
30 # Search configuration file (last one is used as default!)
31 for conf in \
32         "/usr/local/etc/backup-script.conf" \
33         "/etc/backup-script.conf" \
34         "${conf_d}/backup-script.conf" \
35         "/usr/local/etc/backup-script.conf" \
36 ; do
37         if [ -r "$conf" ]; then
38                 # shellcheck source=/dev/null
39                 source "$conf"
40                 break
41         fi
42 done
43
44 Usage() {
45         echo "Usage: $NAME [-q|--quiet] [-v|--verbose] [<job> [<job> [...]]]"
46         echo "       $NAME <-d|--dirs> <dir1> <dir2>"
47         echo
48         echo "  -d, --dirs      Compare two backup directories (not jobs)."
49         echo "  -q, --quiet     Quite mode, only list jobs with changes or errors."
50         echo "  -v, --verbose   Verbose mode, show all checks that are run."
51         echo
52         echo "When no <job> is given, all defined jobs are checked."
53         echo
54         exit 2
55 }
56
57 BeginDiff() {
58         echo "Differences in $*:"
59 }
60
61 PipeDiff() {
62         local line
63         IFS=
64         while read -r line; do
65                 echo -e "     | $line"
66         done
67 }
68
69 EndDiff() {
70         :
71 }
72
73 ListDirectory() {
74         local base_dir="$1"
75         local dir_name="$2"
76
77         local exclude
78
79         exclude='total '
80         if [[ "$dir_name" == "/" ]]; then
81                 exclude="$exclude"'| \.stamp$| dev$| etc$| proc$| root$| run$| sys$| tmp$'
82                 exclude="$exclude"'| data$| net$| srv$'
83                 exclude="$exclude"'| [[:alnum:]_-]+\.log(\.[[:alnum:]]+|)$'
84         fi
85
86         # shellcheck disable=SC2012
87         ls -Al "$base_dir$dir_name" 2>/dev/null \
88                 | egrep -v "($exclude)" | awk '!($2="")' | column -t
89 }
90
91 HandleSystem() {
92         local fname="$1"
93
94         # Set global defaults
95         local backup_type="$default_backup_type"
96         local files="$default_files"
97         local generations="$default_generations"
98         local local=0
99         local system="$fname"
100         local target="$default_target"
101
102         # Read in system configuration file
103         # shellcheck source=/dev/null
104         source "$f"
105
106         target="$target/$(basename "$f")"
107
108         [[ -d "$target" ]] || return 0
109
110         # System name
111         [[ "$system" == "$fname" ]] \
112                 && systxt="\"$system\"" \
113                 || systxt="\"$fname\" [\"$system\"]"
114         [[ "$local" -eq 0 ]] \
115                 && echo "Checking $systxt ..." \
116                 || echo "Checking $systxt (local system) ..."
117
118         # Check if job is disabled
119         if [[ "$backup_type" == "disabled" ]]; then
120                 echo "Job is DISABLED and will be skipped."
121                 echo; return 0
122         fi
123
124         if [ $generations -lt 1 ]; then
125                 echo "No generations configured, nothing to compare, skipping system!"
126                 echo; return 1
127         fi
128
129         local latest_d="$target/latest"
130         if [[ ! -d "$latest_d" || ! -r "$latest_d/.stamp" ]]; then
131                 echo "Failed to access latest backup generation in \"$latest_d\", skipping system!"
132                 echo; return 1
133         fi
134         echo "Found latest generation in \"$latest_d\"."
135
136         declare -i code=-1
137         # shellcheck source=/dev/null
138         source "$latest_d/.stamp"
139
140         if [[ $code -ne 0 && $code -ne 24 ]]; then
141                 echo "Last backup generation has errors, skipping system!"
142                 echo; return 1
143         fi
144
145         # Search previous generation without errors
146         local previous_d=""
147         # shellcheck disable=SC2045
148         for d in $(ls -1dt "$target/"[0-9]*-[0-9]* 2>/dev/null); do
149                 [[ -d "$d" && -r "$d/.stamp" ]] || return 0
150
151                 declare -i code=-1
152                 # shellcheck source=/dev/null
153                 source "$d/.stamp"
154
155                 if [[ $code -eq 0 || $code -eq 24 ]]; then
156                         previous_d="$d"
157                         break
158                 fi
159         done
160         if [[ -z "$previous_d" || ! -d "$previous_d" || ! -r "$previous_d/.stamp" ]]; then
161                 echo "Failed to find previous successfull backup generation, skipping system!"
162                 echo; return 1
163         fi
164         echo "Comparing with generation in $previous_d ..."
165
166         DiffGenerations "$backup_type" "$previous_d" "$latest_d" "$files"
167         return_code=$?
168
169         echo
170         return $return_code
171 }
172
173 DiffGenerations() {
174         local backup_type="$1"
175         local gen1_d="$2"
176         local gen2_d="$3"
177         local files="$4"
178
179         local return_code=0
180
181         if [[ "$backup_type" == "rsync" ]]; then
182                 # rsync Backup Type
183
184                 for file in \
185                         /etc/passwd \
186                         /etc/shadow \
187                         /etc/group \
188                         /etc/gshadow \
189                         \
190                         /boot/grub/grub.cfg \
191                         /etc/aliases \
192                         /etc/bash.bashrc \
193                         /etc/crontab \
194                         /etc/environment \
195                         /etc/fstab \
196                         /etc/hostname \
197                         /etc/hosts \
198                         /etc/hosts.allow \
199                         /etc/hosts.deny \
200                         /etc/inittab \
201                         /etc/ld.so.conf \
202                         /etc/login.defs \
203                         /etc/machine-id \
204                         /etc/modules \
205                         /etc/network/interfaces \
206                         /etc/networks \
207                         /etc/nsswitch.conf \
208                         /etc/profile \
209                         /etc/rc.local \
210                         /etc/resolv.conf \
211                         /etc/services \
212                         /etc/shells \
213                         /etc/ssh/sshd_config \
214                         /etc/sshd_config \
215                         /etc/sudoers \
216                         /etc/sysctl.conf \
217                 ; do
218                         [[ -r "${gen1_d}${file}" ]] || continue
219
220                         [[ $VERBOSE -ne 0 ]] && echo "Checking \"$file\" ..."
221                         if ! diff -U 3 "${gen1_d}${file}" "${gen2_d}${file}" >"$tmp_diff"; then
222                                 BeginDiff "\"$file\""
223                                 tail -n +3 "$tmp_diff" | PipeDiff
224                                 EndDiff
225                                 return_code=1
226                         fi
227                 done
228
229                 for dir in \
230                         / \
231                         /etc/cron.d/ \
232                         /etc/cron.daily/ \
233                         /etc/cron.hourly/ \
234                         /etc/cron.monthly/ \
235                         /etc/cron.weekly/ \
236                         /etc/sudoers.d/ \
237                         /var/log/dumps/ \
238                 ; do
239                         [[ ! -d "${gen1_d}${dir}" ]] && continue
240                         [[ ! -d "${gen2_d}${dir}" ]] && continue
241
242                         # Make sure that this is a system root; comparing other
243                         # root folders results in misleading output ...
244                         [[ "$dir" == "/" && ! -d "${gen1_d}${dir}/etc" ]] && continue
245
246                         [[ $VERBOSE -ne 0 ]] && echo "Checking \"$dir\" ..."
247                         ListDirectory "${gen1_d}" "${dir}" >"$tmp_1"
248                         ListDirectory "${gen2_d}" "${dir}" >"$tmp_2"
249                         if ! diff -U 0 "$tmp_1" "$tmp_2" >"$tmp_diff"; then
250                                 BeginDiff "\"$dir\" directory"
251                                 tail -n +3 "$tmp_diff" | egrep -v '^@@ ' | PipeDiff
252                                 EndDiff
253                                 return_code=1
254                         fi
255                 done
256
257                 if [[ -d "${gen1_d}/var/lib/dpkg/info" && -d "${gen2_d}/var/lib/dpkg/info" ]]; then
258                         [[ $VERBOSE -ne 0 ]] && echo "Checking list of installed packages ..."
259                         chroot "${gen1_d}" dpkg --get-selections >"$tmp_1" || return 2
260                         chroot "${gen2_d}" dpkg --get-selections >"$tmp_2" || return 2
261                         if ! diff -U 0 "$tmp_1" "$tmp_2" >"$tmp_diff"; then
262                                 BeginDiff "list of installed packages"
263                                 tail -n +3 "$tmp_diff" | grep -v '^@@ ' | PipeDiff
264                                 EndDiff
265                                 return_code=1
266                         fi
267                 fi
268         elif [[ "$backup_type" == "scp" ]]; then
269                 # scp Backup type
270                 file=$(basename "$files")
271                 [[ $VERBOSE -ne 0 ]] && echo "Checking \"$file\" ..."
272                 if ! diff -U 3 "${gen1_d}/${file}" "${gen2_d}/${file}" >"$tmp_diff"; then
273                         BeginDiff "\"$file\""
274                         tail -n +3 "$tmp_diff" | PipeDiff
275                         EndDiff
276                         return_code=1
277                 fi
278         else
279                 echo "Backup type \"$backup_type\" undefined, \"$system\" skipped!"
280                 echo; return 2
281         fi
282
283         return $return_code
284 }
285
286 MkTempFiles() {
287         tmp_1=$(mktemp "/tmp/$NAME.XXXXXX") || exit 1
288         tmp_2=$(mktemp "/tmp/$NAME.XXXXXX") || exit 1
289         tmp_diff=$(mktemp "/tmp/$NAME.XXXXXX") || exit 1
290         tmp_out=$(mktemp "/tmp/$NAME.XXXXXX") || exit 1
291 }
292
293 CleanUp() {
294         rm -f "$tmp_1" "$tmp_2" "$tmp_diff" "$tmp_out"
295 }
296
297 while [[ $# -gt 0 ]]; do
298         case "$1" in
299           "-d"|"--dirs")
300                 shift
301                 [[ $# -eq 2 ]] || Usage
302                 MkTempFiles
303                 DiffGenerations "$default_backup_type" "$1" "$2" "$default_files"
304                 return_code=$?
305                 CleanUp
306                 exit $return_code
307                 ;;
308           "-q"|"--quiet")
309                 QUIET=1; shift
310                 ;;
311           "-v"|"--verbose")
312                 VERBOSE=1; shift
313                 ;;
314           "-"*)
315                 Usage
316                 ;;
317           *)
318                 break
319         esac
320 done
321
322 if [[ $# -ge 1 ]]; then
323         for s in "$@"; do
324                 if [ ! -r "${conf_d}/$s" ]; then
325                         echo "$NAME: Can' read \"${conf_d}/$s\"!"
326                         exit 1
327                 fi
328                 sys+=("${conf_d}/$s")
329         done
330 else
331         sys=("${conf_d}/"*)
332 fi
333
334 MkTempFiles
335 for f in "${sys[@]}"; do
336         [[ -r "$f" && -f "$f" ]] || continue
337
338         fname=$(basename "$f")
339         case "$fname" in
340                 "backup-script.conf"|*.sh)
341                         continue
342                         ;;
343         esac
344
345         HandleSystem "$fname" >"$tmp_out" 2>&1; result=$?
346         [[ $QUIET -eq 0 || $result -ne 0 ]] && cat "$tmp_out"
347 done
348 CleanUp
349
350 # -eof-