forked from qubitstream/phpmyadmin_sql_backup
-
Notifications
You must be signed in to change notification settings - Fork 0
/
phpmyadmin_sql_backup.py
163 lines (134 loc) · 7.38 KB
/
phpmyadmin_sql_backup.py
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
#!/usr/bin/env python3
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################
#
# A Python script to automate the download of SQL dump backups
# via a phpMyAdmin web interface.
#
# tested on Python 3.4+
# requires: grab (http://grablib.org/)
#
# Christoph Haunschmidt, started 2016-03
import argparse
import datetime
import os
import re
import sys
import grab
__version__ = '2019-05-07.1'
CONTENT_DISPOSITION_FILENAME_RE = re.compile(r'^.*filename="(?P<filename>[^"]+)".*$')
DEFAULT_PREFIX_FORMAT = r'%Y-%m-%d--%H-%M-%S-UTC_'
def is_login_successful(g):
return g.doc.text_search("frame_content") or g.doc.text_search("index.php?route=/server/export")
def open_frame_if_phpmyadmin_3(g):
frame_url_selector = g.doc.select("id('frame_content')/@src")
if frame_url_selector.exists():
g.go(frame_url_selector.text())
def download_sql_backup(url, user, password, dry_run=False, overwrite_existing=False, prepend_date=True, basename=None,
output_directory=os.getcwd(), exclude_dbs=None, compression='none', prefix_format=None,
timeout=60, http_auth=None, server_name=None, **kwargs):
prefix_format = prefix_format or DEFAULT_PREFIX_FORMAT
exclude_dbs = exclude_dbs.split(',') or []
encoding = '' if compression == 'gzip' else 'gzip'
g = grab.Grab(encoding=encoding, timeout=timeout)
if http_auth:
g.setup(userpwd=http_auth)
g.go(url)
g.doc.set_input_by_id('input_username', user)
g.doc.set_input_by_id('input_password', password)
if server_name:
g.doc.set_input_by_id('input_servername', server_name)
g.submit()
if not is_login_successful(g):
raise ValueError('Could not login - did you provide the correct username / password?')
open_frame_if_phpmyadmin_3(g)
export_url = g.doc.select("id('topmenu')//a[contains(@href,'index.php?route=/server/export')]/@href").text()
g.go(export_url)
dbs_available = [option.attrib['value'] for option in g.doc.form.inputs['db_select[]']]
dbs_to_dump = [db_name for db_name in dbs_available if db_name not in exclude_dbs]
if not dbs_to_dump:
print('Warning: no databases to dump (databases available: "{}")'.format('", "'.join(dbs_available)),
file=sys.stderr)
file_response = g.submit(
extra_post=[('db_select[]', db_name) for db_name in dbs_to_dump] + [('compression', compression)])
re_match = CONTENT_DISPOSITION_FILENAME_RE.match(g.doc.headers['Content-Disposition'])
if not re_match:
raise ValueError(
'Could not determine SQL backup filename from {}'.format(g.doc.headers['Content-Disposition']))
content_filename = re_match.group('filename')
filename = content_filename if basename is None else basename + os.path.splitext(content_filename)[1]
if prepend_date:
prefix = datetime.datetime.utcnow().strftime(prefix_format)
filename = prefix + filename
out_filename = os.path.join(output_directory, filename)
if os.path.isfile(out_filename) and not overwrite_existing:
basename, ext = os.path.splitext(out_filename)
n = 1
print('File {} already exists, to overwrite it use --overwrite-existing'.format(out_filename), file=sys.stderr)
while True:
alternate_out_filename = '{}_({}){}'.format(basename, n, ext)
if not os.path.isfile(alternate_out_filename):
out_filename = alternate_out_filename
break
n += 1
if not dry_run:
file_response.save(out_filename)
return out_filename
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Automates the download of SQL dump backups via a phpMyAdmin web interface.',
epilog='Written by Christoph Haunschmidt et al., version: {}'.format(__version__))
parser.add_argument('url', metavar='URL', help='phpMyAdmin login page url')
parser.add_argument('user', metavar='USERNAME', help='phpMyAdmin login username')
parser.add_argument('password', metavar='PASSWORD', help='phpMyAdmin login password')
parser.add_argument('-o', '--output-directory', default=os.getcwd(),
help='output directory for the SQL dump file (default: the current working directory)')
parser.add_argument('-p', '--prepend-date', action='store_true', default=False,
help='prepend current UTC date & time to the filename; '
'see the --prefix-format option for custom formatting')
parser.add_argument('-e', '--exclude-dbs', default='',
help='comma-separated list of database names to exclude from the dump')
parser.add_argument('-s', '--server-name', default=None,
help='mysql server hostname to supply if enabled as field on login page')
parser.add_argument('--compression', default='none', choices=['none', 'zip', 'gzip'],
help='compression method for the output file - must be supported by the server (default: %(default)s)')
parser.add_argument('--basename', default=None,
help='the desired basename (without extension) of the SQL dump file (default: the name given '
'by phpMyAdmin); you can also set an empty basename "" in combination with '
'--prepend-date and --prefix-format')
parser.add_argument('--timeout', type=int, default=60,
help='timeout in seconds for the requests (default: %(default)s)')
parser.add_argument('--overwrite-existing', action='store_true', default=False,
help='overwrite existing SQL dump files (instead of appending a number to the name)')
parser.add_argument('--prefix-format', default='',
help=str('the prefix format for --prepend-date (default: "{}"); in Python\'s strftime format. '
'Must be used with --prepend-date to be in effect'.format(
DEFAULT_PREFIX_FORMAT.replace('%', '%%'))))
parser.add_argument('--dry-run', action='store_true', default=False,
help='dry run, do not actually download any file')
parser.add_argument('--http-auth', default=None,
help='Basic HTTP authentication, using format "username:password"')
args = parser.parse_args()
if args.prefix_format and not args.prepend_date:
print('Error: --prefix-format given without --prepend-date', file=sys.stderr)
sys.exit(2)
try:
dump_fn = download_sql_backup(**vars(args))
except Exception as e:
print('Error: {}'.format(e), file=sys.stderr)
sys.exit(1)
print('{} saved SQL dump to: {}'.format(('Would have' if args.dry_run else 'Successfully'), dump_fn),
file=sys.stdout)