-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbackitup.functions
More file actions
324 lines (279 loc) · 10.2 KB
/
Copy pathbackitup.functions
File metadata and controls
324 lines (279 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
#!/bin/bash
localhost=$(hostname --fqdn)
write_error () {
local exit_status=$1
local func_name=$2
local msg=$3
local level=$4
local msg_type=$5
[ $exit_status = 0 ] && return
if [ -z $func_name ]; then
func_name=$0
else
func_name=$0#$func_name
fi
case "$msg_type" in
cmd) log_msg="$level $func_name: '$msg' exited with status $exit_status" ;;
ssh-cmd) log_msg="$level $func_name: '[ssh -p $port $dest_host] $msg' exited with status $exit_status" ;;
msg) log_msg="$level $func_name: $msg" ;;
esac
[ "$level" = "FATAL" ] && log_msg+=" - ABORTING $name !!!"
echo -e $log_msg
logger -t $log_tag -- $log_msg
[ "$level" = "FATAL" ] && echo && exit
}
# some wrappers arround write_error
error_check () {
write_error "$@" 'cmd'
}
ssh_error_check () {
write_error "$@" 'ssh-cmd'
}
message () {
write_error -1 "$@" 'msg'
}
# check the keys given in config file
check_key () {
local valid_keys=( src_host dest_host bu_base_dir src_dir schedule verbose log_tag check_free_space \
minimal_disk_space mount_ro rsync_opts extra_rsync_opts exclude_path exclude_file global_exclude_file \
port )
local found=1
for string in "${valid_keys[@]}"; do
[ "$1" = "$string" ] && found=0
done
[ $found = 1 ] && message "" "Invalid key '$1' found in configuration file - ignoring this" "WARNING"
return $found
}
# check if configuration for a job is complete (not very elegant)
check_job_definition () {
local key=''
local needed_keys=( src_host dest_host bu_base_dir src_dir schedule verbose log_tag check_free_space \
minimal_disk_space mount_ro exclude_path port )
local needed_values=( "$src_host" "$dest_host" "$bu_base_dir" "$src_dir" "$schedule" "$verbose" "$log_tag" "$check_free_space" \
"$minimal_disk_space" "$mount_ro" "$exclude_path" "$port" )
local j=0
for key in "${needed_keys[@]}"; do
local value=$( echo ${needed_values[j]} | sed -e "s/''//g" -e 's/""//g' )
[ -z $value ] && message "" "Value for '$key' is missing or empty for '$name' " "FATAL"
((j++))
done
}
#execute a command locally or remote
execute_remote_or_local () {
local command=$1
local return_value=''
local exit_status=''
if [[ $dest_host == $localhost ]] ; then
#local
return_value=$(eval $command)
exit_status=$?
else
#remote
return_value=$(ssh -p$port $dest_host $command)
exit_status=$?
fi
echo -n $return_value
return $exit_status
}
# check for minimal free disk space
check_disk_space () {
# built the command strings to to get percentage of inodes space and inodes
local pipe="| tail -n1 | sed -e 's/.* \([0-9]\{1,3\}\)%.*/\1/'"
local kb_used_command="df $bu_base_dir $pipe"
local inode_used_command="df -i $bu_base_dir $pipe"
local hd_maxused=$((100-$minimal_disk_space))
$verbose && message "" "checking free space on backup disk" "INFO"
# get used space
local hd_kb_used=$(execute_remote_or_local "$kb_used_command")
ssh_error_check $? "check_disk_space" "$kb_used_command" "WARNING"
# get used inodes
local hd_inode_used=$(execute_remote_or_local "$inode_used_command")
ssh_error_check $? "check_disk_space" "$inode_used_command" "WARNING"
# compare to defined value
if [[ $hd_kb_used -ge $hd_maxused || $hd_inode_used -ge $hd_maxused ]] ; then
message "check_disk_space" "Not enough space left in $bu_base_dir on $dest_host for rsyncing backups!" "FATAL"
fi
}
# remount the backup disk
remount_bu_disk () {
# check correct usage
[ $# != 1 ] && message "remount_bu_disk" "Usage: remount_bu_disk <ro/rw> Call was: remount_bu_disk $(echo $@)" "FATAL"
local remount_state=$1
# get the mtab entry for the backup device
local mtab_line=$(execute_remote_or_local "mount | grep $bu_base_dir")
ssh_error_check $? "remount_bu_disk" "mount | grep $bu_base_dir" "FATAL"
# get the current mount state
local mount_state=$(echo $mtab_line | sed -e 's/.*(\(r[ow]\)).*/\1/')
# return on remount rw if device is allready rw
[[ $remount_state == 'rw' && $mount_state == 'rw' ]] && return
# get device name
local bu_device=$(echo $mtab_line | cut -d' ' -f1)
[ -z $bu_device ] && message "remount_bu_disk" "Could not determine backup-device on $dest_host" "FATAL"
# get file system
local fs_type=$(echo $mtab_line | cut -d' ' -f5)
[ -z $fs_type ] && message "remount_bu_disk" "Could not determine filesystem of $bu_device on $dest_host" "FATAL"
# built remount command
local remount_command="mount -t $fs_type -o remount,$remount_state $bu_device $bu_base_dir"
#remount
$verbose && message "" "remounting $bu_device on $dest_host $remount_state" "INFO"
execute_remote_or_local "$remount_command"
ssh_error_check $? "remount_bu_disk" "$remount_command" "FATAL"
#remount
return
}
# create the backup directory
create_backup_directory () {
execute_remote_or_local "test -d $bu_dir"
if [[ $? != 0 ]]; then
$verbose && message "" "creating needed directory $bu_dir" "INFO"
execute_remote_or_local "mkdir -p $bu_dir"
ssh_error_check $? "" "mkdir -p $bu_dir" "FATAL"
fi
}
# does what it says
cp_or_move_and_save_timestamp () {
local from=$1
local to=$2
local method=$3
if execute_remote_or_local "test -d $from" ; then
$verbose && message "" "$method $1 $2 (on $dest_host)" "INFO"
execute_remote_or_local "touch $timestamp -r $from"
execute_remote_or_local "$method $from $to"
ssh_error_check $? "cp_or_move_and_safe_timestamp" "$method $from $to" "FATAL"
execute_remote_or_local "touch $to -r $timestamp"
fi
}
# expand the schedule abbreviation to full name
schedule_name () {
case "$1" in
d) echo "daily";;
w) echo "weekly";;
m) echo "monthly";;
y) echo "yearly";;
*) message "" "Unknown schedule '$char'" "FATAL";;
esac
}
# define on which day a job interval runs
should_it_run? () {
if [[ $interval == "daily" ]]; then
echo true
elif [[ $interval == "weekly" && $(date +%w) = 0 ]]; then
echo true
elif [[ $interval == "monthly" && $(date +%d) == 01 ]]; then
echo true
elif [[ $interval == "yearly" && $(date +%j) == 001 ]]; then
echo true
else
echo false
fi
}
# the main rsync part
run_rsync () {
local dest_dir="$bu_dir/$interval.0"
local rsync_log_dir="/var/log/backup"
local rsync_log="$rsync_log_dir/$name.log"
! [ -d $rsync_log_dir ] && mkdir $rsync_log_dir
# built destination and source string for rsync
if [ $src_host != $localhost ]; then from="$src_host:$src_dir"; else from=$src_dir; fi
if [ $dest_host != $localhost ]; then to="$dest_host:$dest_dir"; else to=$dest_dir; fi
# built the rsync options
local exclude=''
[ -n $global_exclude_file ] && exclude+="--exclude-from=$exclude_path/$global_exclude_file "
[ -n $exclude_file ] && exclude+="--exclude-from=$exclude_path/$exclude_file "
# The rsync job
$verbose && message "" "starting rsync for '$name $interval'" "INFO"
echo -e "\n### $(date) " >> $rsync_log
rsync -avz --numeric-ids --rsh="ssh -p$port" --delete --delete-excluded \
$exclude $rsync_opts $extra_rsync_opts $from $to &>> $rsync_log
# check exit status: 0 = okay, 24 harmless (e.g. files being changed during run-time)
if [ $? = 24 -o $? = 0 ]; then
# save backup date
execute_remote_or_local "touch $dest_dir"
message "" "rsync for '$name $interval' finished successfully" "INFO"
else
message "" "rsync for $task $interval' finished with errors!" "CRITICAL"
fi
echo -e "### $(date) \n" >> $rsync_log
}
#the main rotation
rotate () {
local bu_base="$bu_dir/$interval"
# delete the oldest snapshot if necessary
local to_remove="$bu_base.$num"
if execute_remote_or_local "test -d $to_remove"; then
$verbose && message "" "removing $to_remove" "INFO"
execute_remote_or_local "rm -rf $to_remove"
ssh_error_check $? "rotate" "rm $to_remove" "FATAL"
fi
# shift all other snapshots (increment the name)
local old=$num
while (( $old >= 0 )); do
local new=$[ $old + 1 ]
cp_or_move_and_save_timestamp "$bu_base.$old" "$bu_base.$new" "mv"
let old=$old-1
done
# what to do for the newest snapshot?
local TO=$bu_base.0
local LAST=''
if [[ $interval == $smallest_interval ]]; then
# if we are on the smallest backup interval: cp smallest.1 to smallest.0
LAST=$bu_base.1
else
# if we are on any other backup interval: cp smaller.0 to this.0
LAST=$bu_dir/$last_interval.0
fi
if execute_remote_or_local "test -d $LAST" ; then
cp_or_move_and_save_timestamp "$LAST" "$TO" "cp -al"
fi
}
backup_and_rotate () {
local name=$name
# overwrite defaults if necessary
local bu_base_dir=${cfg_bu_base_dir[i]:-$default_bu_base_dir}
local bu_dir=$bu_base_dir/$name
local src_host=${cfg_src_host[i]:-$default_src_host}
local dest_host=${cfg_dest_host[i]:-$default_dest_host}
local src_dir=${cfg_src_dir[i]:-$default_src_dir}
local schedule=${cfg_schedule[i]:-$default_schedule}
local port=${cfg_port[i]:-$default_port}
local log_tag=${cfg_log_tag[i]:-$default_log_tag}
local verbose=${cfg_verbose[i]:-$default_verbose}
local exclude_path=${cfg_exlcude_path[i]:-$default_exclude_path}
local global_exclude_file=${cfg_global_exclude_file[i]:-$default_global_exclude_file}
local exclude_file=${cfg_exclude_file[i]:-$default_exclude_file}
local rsync_opts=${cfg_rsync_opts[i]:-$default_rsync_opts}
local extra_rsync_opts=${cfg_extra_rsync_opts[i]:-$default_extra_rsync_opts}
local check_free_space=${cfg_check_free_space[i]:-$default_check_free_space}
local mount_ro=${cfg_mount_ro[i]:-$default_mount_ro}
local minimal_disk_space=${cfg_minimal_disk_space[i]:-$default_minimal_disk_space}
check_job_definition
local timestamp="$bu_dir/.timestamp"
local smallest_interval=$(schedule_name ${schedule:0:1})
# run job on intervals defined in $schedule
did_run=false
for setting in $(echo $schedule | tr ":" "\n"); do
local interval=$(schedule_name ${setting:0:1})
local num=${setting:1}
if $(should_it_run?); then
# on the first loop do the disk_check and remount stuff
if ! $did_run; then
$verbose && message " " "Starting $name on $(date)" "INFO"
$check_free_space && check_disk_space
remount_bu_disk "rw"
create_backup_directory
fi
rotate
run_rsync
did_run=true
fi
local last_interval=$interval
done
# return if nothing there was nothing to do
! $did_run && return
# just to be sure
execute_remote_or_local "sync"
# remount disk ro if necessary
$mount_ro && remount_bu_disk "ro"
$verbose && message " " "Finished $name on $(date)" "INFO"
$verbose && echo
}