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