blob: 99c9a6363ecfab1e34b6fa1034c3e935c0fdb49a (
plain)
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
|
#!/usr/bin/env zsh
self_name=$0
# some hardcoded defaults
default_cfg='/etc/backup.zsh.cfg'
default_postfix=$(date +%F-%H%M)
default_ftp_port='21'
default_ssh_port='22'
# echo to stderr
function err
{
[[ -n $1 ]] && printf "%s\n" $1 >&2
}
# standard configuration error message to stderr
function cfg_err
{
[[ -n $1 ]] && printf "%s is not set in configuration, but is required by %s to work.\n" $1 $self_name >&2
}
# print help
function usage
{
printf "usage: %s [--help|--conf /path/to/config]\n" $self_name
printf " --help -h show this message\n"
printf " --conf -c use config from the specified path\n\n"
printf "Default config path %s will be used if invoked without options\n" $default_cfg
}
# read the configuration file and spit out some exceptions if stuff is missing
function apply_config
{
# testing remote settings only needed for non-local backups
function test_remote_settings
{
if [[ -z $remote_host ]]; then
cfg_err 'remote_host'
return 5
fi
if [[ -z $remote_user ]]; then
cfg_err 'remote_user'
return 5
fi
if [[ -z $remote_pass ]]; then
cfg_err 'remote_pass'
return 5
fi
if ! (( port )); then
err 'remote_port is not a numeric value.'
return 5
fi
}
# import contents of the config (including functions if present)
source $cfg || { err "Config file $cfg is unreadable or does not exist"; return 15 }
hostname=${local_host:-$HOST}
postfix=${outfile_postfix:-$default_postfix}
# do the tests
if [[ -z $source_dirs ]]; then
cfg_err 'source_dirs'
return 5
fi
# set defaults and / or fail to run if something is missing
local exit_code
case $protocol in
('ftp'|'ftps') port=${remote_port:-$default_ftp_port}; test_remote_settings; exit_code=$?; (( exit_code )) && return $exit_code;;
('sftp'|'ssh') port=${remote_port:-$default_ssh_port}; test_remote_settings; exit_code=$?; (( exit_code )) && return $exit_code;;
('local') unset remote_port;;
(*) cfg_err 'protocol'; return 5;;
esac
unset exit_code
# set variables for tar command
case $compress_format in
('xz') compress_flag='J' ;;
('bz2') compress_flag='j' ;;
('gz') compress_flag='z' ;;
('') unset compress_flag ;;
(*) err "$compress_format is not a valid value for the compression format option."; return 5;;
esac
if [[ -n $exclude_list ]]; then
if [[ -r $exclude_list ]]; then
exclude_option='-X'
else
err "Exclusion list $exclude_list is either unreadable or does not exist. Proceeding without it."
fi
fi
}
# generate the full backup path
function generate_fullpath
{
local backup_type
# increment or full backup
if [[ -s $snapshot_file ]]; then
backup_type='incr'
else
backup_type='full'
fi
if [[ -z $backup_filename ]]; then
outfile="${backup_dir}${backup_dir:+/}${hostname}-${src_basename}_${postfix}_${backup_type}${gnupg_key:+.gpg.}.t${compress_format:-ar}"
else
outfile=$backup_filename
fi
}
# compress to stdout
function compress
{
# snapshot file is per directory so we cannot test it within apply_config()
if [[ -n $snapshot_file ]]; then
if printf '' >> $snapshot_file; then
snapshot_option='-g'
else
err "Snapshot file $snapshot_file cannot be written. Proceeding with full backup."
fi
fi
# do the magic and spit to stdout
tar -C $src_basedir -c$compress_flag $snapshot_option $snapshot_file $exclude_option $exclude_list --ignore-failed-read $src_basename
}
# store to local or remote
function store
{
# take from stdin and do the magic
case $protocol in
('local') dd of=$outfile ;;
('ssh') ssh -p$port $remote_user@$remote_host "dd of=$outfile" ;;
('sftp'|'ftp'|'ftps') curl -ksS -T - $protocol://$remote_host:$port/$outfile -u $remote_user:$remote_pass ;;
esac
}
# encrypt asymmetrically via gpg
function encrypt
{
gpg -r $gnupg_key -e -
}
# self explanatory, using case statement, so one dash multiple opts is not supported
function parse_opts
{
while [[ -n $1 ]]; do
case $1 in
('--help'|'-h') usage; exit 0;;
('--conf'|'-c') shift; opt_cfg=$1; return 0;;
(*) err "unknown parameter $1"; exit 127;;
esac
done
}
# the main logic
function main
{
# parse options
parse_opts $@
# set default config if $opt_cfg is not defined via an option
cfg=${opt_cfg:-$default_cfg}
# run config tests and fill in defaults
apply_config
# fail in case something goes wrong
local exit_code=$?
(( exit_code )) && return $exit_code
unset exit_code
# run backups per directory
for i in $source_dirs; do
# prepare the set of variables
unset src_basename src_basedir outfile
IFS=':' read source_dir snapshot_file <<< $i
src_basename=${source_dir:t}
src_basedir=${source_dir:h}
# generate the backups path
generate_fullpath
err "Creating a backup of $source_dir via $protocol to store it in $outfile."
# pipe magic into magic
if [[ -n $gnupg_key ]]; then
compress | encrypt | store
else
compress | store
fi
done
return 0
}
main $@
|