summaryrefslogtreecommitdiff
path: root/backup.zsh
blob: 77a89803be2e774a317a6521d490786a2d74453b (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
#!/usr/bin/env zsh
self_name=$0
default_cfg='/usr/local/etc/backup.cfg'
default_postfix=$(date +%F-%H%M)
default_ftp_port='21'
default_ssh_port='22'

function err
{
	[[ -n $1 ]] && echo $1 >&2
}

function cfg_err
{
	[[ -n $1 ]] && echo "$1 is not set in configuration, but is required by $0 to work." >&2
}

function usage
{
	echo "usage: $self_name [--help|--config]
	--help    -h - show this message
	--config  -c - use config from the specified path

	Default config path /usr/local/etc/backup.cfg will be used if invoked without options"
}

# function to read the configuration file and spit out some exceptions if stuff is missing
function apply_config
{
	source $cfg || err 15 'Config file does not exist'
	if [[ -z $source_dirs ]]; then
		cfg_err 'Backup source'
		exit 5
	fi
	if [[ -z $remote_host ]]; then
		cfg_err 'Remote host'
		exit 5
	fi
	if [[ -z $backup_dir && $protocol != 'ssh' ]]; then
		cfg_err 'Target directory'
		exit 5
	fi
	if [[ -z $local_host ]]; then
		local_host=$HOST
	fi
	# date postfix
	if [[ -z $outfile_postfix ]]; then
		postfix=$default_postfix
	else
		postfix=$outfile_postfix
	fi
	case $protocol in
		('ftp'|'ftps') port=${remote_port:-$default_ftp_port};;
		('sftp'|'ssh') port=${remote_port:-$default_ssh_port};;
		('local') unset remote_port;;
		(*) cfg_err 'Backup protocol'; exit 5;;
	esac
	if [[ ! $remote_port =~ ^[0-9]+$ ]]; then
		err 'Remote port is not a numeric value.'
	fi
	case $compress_format in
		('xz') compress_flag='J' ;;
		('bz2') compress_flag='j' ;;
		('gz') compress_flag='z' ;;
		('') unset compress_flag; unset compress_format ;;
		(*) err "$compress_format is not a valid value for the compression format option."; exit 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."
		fi
	fi
	if [[ -n $snap_file ]]; then
		if printf '' >> $snap_file; then
			snapshot_option='-g'
		else
			err "Snapshot file $snap_file cannot be written."
		fi
	fi
}

# generate the full backup path
function generate_fullpath
{
	local backup_type
	# increment or full backup
	if [[ -s $snap_file ]]; then
		backup_type='incr'
	else
		backup_type='full'
	fi
	if [[ -z $backup_filename ]]; then
		outfile="$backup_dir/${local_host}-${src_basename}_${postfix}_${backup_type}.t${compress_format:-'ar'}"
	else
		outfile=$backup_filename
	fi
}

function compress # compress to stdout
{
	tar cf$compress_flag - -C $src_basedir $src_basename $snapshot_option $snapshot_file $exclude_option $exclude_list --ignore-failed-read
}

function store # store to local or remote
{
	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
}

function parse_opts
{
	while [[ -n $1 ]]; do
		case $1 in
			('--help'|'-h') usage; return 0;;
			('--config'|'-c') shift; opt_cfg=$1; shift;;
		esac
	done
}

function main
{
	parse_opts
	if [[ -z $opt_cfg ]]; then
		cfg=$default_cfg
	else
		cfg=$opt_cfg
	fi
	apply_config
	for i in $source_dirs; do
		unset src_basename src_basedir outfile
		IFS=':' read source_dir snap_file <<< $i
		src_basename=${source_dir:t}
		src_basedir=${source_dir:h}
		generate_fullpath
		compress | store
	done
	return 0
}

main $@