-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdbbackup
More file actions
executable file
·328 lines (279 loc) · 9.19 KB
/
dbbackup
File metadata and controls
executable file
·328 lines (279 loc) · 9.19 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
325
326
327
328
#!/usr/bin/env bash
# -*- mode: sh; sh-shell: bash; indent-tabs-mode: nil; tab-width: 2 -*-
# vim: ft=bash:et:ts=2:sts=2:sw=2
# code: language=bash insertSpaces=true tabSize=2
# shellcheck shell=bash
#
# Backup PostgreSQL and MySQL databases.
# FIXME mysql auth only possible via --config (default file not used)
# TODO error handling, delete partial backups on interrupt / error
# TODO add default variables for config files and expire days (optional system wide default config)
# TODO introduce an interactive mode or skip-if-exists
# cron example
#@daily root cronic dbbackup --expire 30 --backup-dir /backup/postgres --user postgres pgsql postgres
#@daily root cronic dbbackup --expire 30 --backup-dir /backup/mysql --config /root/.mylogin.cnf mysql
set -o errtrace
set -o errexit
set -o pipefail
set -o nounset
(( ${DEBUG_LVL:-0} >= 2 )) && set -o xtrace
IFS=$'\t\n\0'
# default values
typeset -r _BACKUP_DIR="/backup/db"
typeset -r _DATE=$(date -I)
typeset -r _USER
typeset -r _DB_USER_PGSQL="postgres"
typeset -r _DB_USER_MYSQL
# constats and variables
typeset -r _SCRIPT_FILE="$(basename "$0")"
typeset -r _USAGE="Usage: ${_SCRIPT_FILE} [OPTION..] DBMS DB.."
typeset _HELP
! IFS='' read -r -d '' _HELP <<EOF
$_USAGE
DBMS: pgsql
Backup all postgresql databases
Default db user: ${_DB_USER_PGSQL:--}
The --config option must point to a valid pgpass file
If not set the pgsql will use ~/.pgpass, example:
myhost:5432:mydb:myuser:mypassword
localhost:*:*:postgres:secret
DBMS: mysql
Backup all mysql databases
Default db user: ${_DB_USER_MYSQL:--}
The --config option must point to a valid 'mysqldump --defaults-file' file
If not set mysql will use ~/.mylogin.cnf, example (only pass mandatory):
[client]
user = myuser
password = "mypassword"
host = 127.0.0.1
Options:
-u | --db-user U the db user for the dbms connection (default: see DBMS)
--user U the host user that performs the backup
(default: ${_USER:-root})
-b | --backup-dir backup directory (default: ${_BACKUP_DIR})
the given directory must exist and be writeable
-c | --config F client config / credential file (default: see DBMS)
-x | --expire DAYS delete old DB backups for the given DBMS in the given dir
older than TODAY-DAYS with filename DBMS_DB_*.EXT
--full perform full dump (all databases, including users etc.)
requiring no specific db name list (ignored)
admin user required e.g. -u root or -u postgres
Authentication:
Passwords must be stored in a secure, DBMS specific config file (see above).
Using plain text passwords as command line arguments is not an option.
These fiels should be readable only to it's owner (mode 600).
EOF
typeset -r _USAGE
typeset -a _dbs
typeset _dbms
typeset _backup_dir
typeset _user
typeset _db_user
typeset _config_file
typeset _expire_days
typeset _backup_full=false
# print error and exit with given code
_exit() {
local _code="$1"
shift
printf "ERROR: %s\n" "$@" >&2
exit ${_code}
}
# parse args
_parse_args() {
# no arg at all?
[[ -z "${1:-}" ]] && printf "%s\n" "${_USAGE}" && exit 1
# read all args
while [[ -n "${1:-}" ]]; do
case "$1" in
pgsql|mysql)
[[ -n "${dbms:-}" ]] && _exit 1 "DBMS already set to [${dbms}], unable to set another [$1]."
_dbms="$1"
shift
;;
--backup-user)
[[ -z "${2:-}" ]] && _exit 1 "Missing value for arg [$1]."
_backup_user="$2"
shift 2
;;
-u|--db-user)
[[ -z "${2:-}" ]] && _exit 1 "Missing value for arg [$1]."
_db_user="$2"
shift 2
;;
--user)
[[ -z "${2:-}" ]] && _exit 1 "Missing value for arg [$1]."
_user="$2"
shift 2
;;
-c|--config)
[[ -z "${2:-}" ]] && _exit 1 "Missing value for arg [$1]."
_config_file="$2"
shift 2
;;
-b|--backup-dir)
[[ -z "${2:-}" ]] && _exit 1 "Missing value for arg [$1]."
_set_backup_dir "$2"
shift 2
;;
-x|--expire)
[[ -z "${2:-}" ]] && _exit 1 "Missing value for arg [$1]."
[[ ! "$2" =~ ^\ *[+-]?[0-9]+\ *$ ]] \
&& _exit 1 "Not a valid integer [$2]"
_expire_days="$2"
shift 2
;;
--full)
_backup_full=true
shift
;;
-h|--help)
printf "%s" "${_HELP}"
exit 0
;;
-*)
_exit 1 "Unknown arg [$1]"
;;
*)
[[ -z "${_dbms:-}" ]] && _exit 1 "Not a valid DBMS [$1]."
if [[ "${_dbs[@]}" =~ $1 ]]; then
printf "WARNING: DB [%s] already added, ignoring.\n" "$1"
else
_dbs+=($1)
fi
shift
;;
esac
done
# dbms or dbs unset?
[[ -z "${_dbms:-}" ]] && _exit 1 "No DBMS provided."
if [[ -z "${_dbs:-}" ]]; then
${_backup_full} || _exit 1 "No DB provided."
else
${_backup_full} && echo "WARNING: Ignoring specifically provided DB name(s) [${_dbs[@]}], full backup will be performed instead." >&2
fi
# set defaults where needed
[[ -z "${_backup_dir}" ]] && _set_backup_dir "${_BACKUP_DIR}"
[[ -z "${_user:-}" ]] && _user="${_USER:-}"
local _db_user_var="_DB_USER_${_dbms^^}"
[[ -z "${_db_user:-}" ]] && _db_user="${!_db_user_var:-}"
return 0
}
# set backup root directory (check if exists and writeable)
_set_backup_dir() {
local _dir="$1"
if [[ ! -d "${_dir}" || ! -w "${_dir}" ]]; then
_exit 1 "Backup dir not existing or missing write permissions [${_dir}]"
fi
_backup_dir="${_dir}"
return 0
}
# delete old backups
_delete_old() {
# no expire days set? then don't delete anything.
[[ -z "${_expire_days:-}" ]] && return 0
local _filename_pattern="$1"
printf ">> Deleting backups older than [%s] days in [%s] matching [%s]\n" \
"${_expire_days}" "${_backup_dir}" "${_filename_pattern}"
find "${_backup_dir}" -maxdepth 1 -regextype posix-extended -type f \
-ctime +${_expire_days} -regex "${_filename_pattern}" \
-exec rm -f {} \; -print
}
# backup a single postgresql db deleting old ones first
_backup_pgsql() {
local _db="$1"
local _outfile="${_dbms}_${_db}_${_DATE}.gz"
local _outfilepath="${_dir}/${_outfile}"
[[ -f "${_outfilepath}" ]] \
&& printf "WARNING: File exists [%s], will be owerridden .."\
"${_outfilepath}" >&2
# detele old db backups (if expire days provided)
_delete_old ".*\/${_dbms}_${_db}_[^/]*.gz"
# backup db
sudo ${_user:+-u${_user}} \
${_config_file:+env PGPASSFILE="${_config_file}"} \
pg_dump ${_db_user:+-U${_db_user}} -w -Fc ${_db} \
| gzip > "${_outfilepath}"
return 0
}
# backup all postgresql dbs
_backup_pgsql_full() {
local _outfile="${_dbms}_full_${_DATE}.gz"
local _outfilepath="${_dir}/${_outfile}"
[[ -f "${_outfilepath}" ]] \
&& printf "WARNING: File exists [${_outfilepath}], will be owerridden ..\n" >&2
# detele old db backups (if expire days provided)
_delete_old ".*\/${_dbms}_full_[^/]*.gz"
# backup dbs
sudo ${_user:+-u${_user}} \
${_config_file:+env PGPASSFILE="${_config_file}"} \
pg_dumpall ${_db_user:+-U${_db_user}} -w -c \
| gzip > "${_outfilepath}"
return 0
}
# backup a single mysql db deleting old ones first
_backup_mysql() {
local _db="$1"
local _outfile="${_dbms}_${_db}_${_DATE}.gz"
local _outfilepath="${_dir}/${_outfile}"
[[ -f "${_outfilepath}" ]] \
&& printf "WARNING: File exists [%s], will be overridden .." \
"${_outfilepath}" >&2
# detele old db backups (if expire days provided)
_delete_old ".*\/${_dbms}_${_db}_[^/]*.gz"
# backup db
sudo ${_user:+-u${_user}} \
mysqldump ${_config_file:+--defaults-file="${_config_file}"} \
${_db_user:+-u${_db_user}} \
--extended-insert --disable-keys --quick \
${_db} | gzip > "${_outfilepath}"
return 0
}
# backup all mysql dbs
_backup_mysql_full() {
local _outfile="${_dbms}_full_${_DATE}.gz"
local _outfilepath="${_dir}/${_outfile}"
[[ -f "${_outfilepath}" ]] \
&& echo "WARNING: File exists [${_outfilepath}], will be overridden .." >&2
# detele old db backups (if expire days provided)
_delete_old ".*\/${_dbms}_full_[^/]*.gz"
# backup dbs
sudo ${_user:+-u${_user}} \
mysqldump ${_config_file:+--defaults-file="${_config_file}"} \
${_db_user:+-u${_db_user}} \
--extended-insert --disable-keys --quick \
--all-databases --add-drop-database --flush-privileges \
--events --ignore-table=mysql.event \
| gzip > "${_outfilepath}"
return 0
}
_backup() {
local _dir="${_backup_dir}"
printf "> Sarting [%s] backup%s%s\n" \
"${_dbms}" "${_user:+ as [${_user}]}" \
"${_db_user:+ connecting as [${_db_user}]}"
if ${_backup_full}; then
printf ">> Creating full DB backup ..\n"
_backup_${_dbms}_full
printf ">> .. done\n"
else
for _db in "${_dbs[@]}"; do
local _outfile="${_dbms}_${_DATE}_${_db}.gz"
printf ">> Creating backup for DB [%s] ..\n" "${_db}"
_backup_${_dbms} "${_db}"
printf ">> .. done\n"
done
fi
printf "> Finished [%s] backup(s)" "${_dbms}"
return 0
}
# main function
_main() {
cd /
_parse_args "$@"
_backup
return 0
}
# run script
_main "$@"
exit 0