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