#! /usr/bin/env python
import sys
import os
import getopt
import zipfile
import glob
from time import localtime, strftime, mktime
from ConfigParser import *
try:
import MySQLdb as mydb
except ImportError, err:
print str(err) + '. Please install python-mysqldb. (http://sourceforge.net/projects/mysql-python) '
sys.exit(-1)
def license():
print """
# Copyright (C) 2008 Tilo Werner \n \
# \n \
# backupdb is free software; you can redistribute it and/or modify \n \
# it under the terms of the GNU General Public License as published by \n \
# the Free Software Foundation; either version 2 of the License, or \n \
# (at your option) any later version. \n \
# \n \
# backupdb is distributed in the hope that it will be useful, \n \
# but WITHOUT ANY WARRANTY; without even the implied warranty of \n \
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the \n \
# GNU General Public License for more details. \n \
# \n \
# You should have received a copy of the GNU General Public License \n \
# along with backupdb; if not, write to the Free Software \n \
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
# USA \n """
sys.exit(0)
def params():
try:
opts, extraparams = getopt.getopt(sys.argv[1:], "h:u:p:d:?vinf:",
["host=", "user=", "password=","dbprefix=","help",
"license","verbose","include-system-dbs=","noquestions",
"file=", 'createconfigfile='])
except getopt.GetoptError, err:
print str(err)
help()
sys.exit(-1)
# initial parameters
param = {'host':'localhost','user':'root','passwd':'','dbprefix':'','file':'','outputdir':'./', \
'verbose':False,'includesystemdb':False,'noquestion':False}
for o,p in opts:
if o in ['-f', '--file']:
if p in ['?', 'help']:
print 'Configfile syntax:\n'+helpfile()
sys.exit(0)
if not os.path.isfile(p) and p:
quest = raw_input('No config file given, create one ? [Y/N] ')
if quest.lower() == 'y':
conffile = open(p,'w')
warning = '#This is just an example default section, please modify it!\n\n'
conffile.write(warning+helpfile().replace('\t','').replace('\\',''))
conffile.close()
print 'Initial configfile \"'+p+'\" written. Please modiy it.'
sys.exit(0)
else:
param['file'] = p
return param
if o in ['-h','--host']:
param['host'] = p
if o in ['-u','--user']:
param['user'] = p
if o in ['-p', '--password']:
param['passwd'] = p
if o in ['-d', '--dbprefix']:
param['dbprefix'] = p
if o in ['-v', '--verbose']:
param['verbose'] = True
if o in ['-i', '--include-system-dbs']:
param['includesystemdb'] = True
if o in ['-n', '--noquestions']:
param['noquestion'] = True
if o in ['-?', '--help']:
help()
if o in ['--license']:
license()
try:
if os.path.isdir(extraparams[0]):
param['outputdir'] = str(extraparams[0])
else:
print 'Please enter a valid destination directory'
help()
sys.exit(-1)
except IndexError:
print 'Wrong syntax'
help()
sys.exit(-1)
return param
def help():
print '\nbackupdb.py <options> destination directory\n'
print \
'\t -h --host \t\t Hostname (Default: localhost)\n\
\t -u --user \t\t Username (Default: root)\n\
\t -p --password \t\t Password (Default: empty)\n\
\t -f --file \t\t Configfile, if non-existing is given, you will be ask to create one\n\
\t\t\t\t For syntax type: '+sys.argv[0]+' -f/--file followed by ? or help\n\
\t -n --noquestions \t I will not ask about anything, but take defaults \n\
\t -i --include-system-dbs Include System Databases (Default exclude) \n\
\t -v --verbose \t\t Mysqldump will speak to you (Default: nonverbose)\n\
\t -? --help \t\t Prints what you see \n\
\t --license \t\t Prints the license'
sys.exit(0)
def helpfile():
syntax = '\
[DEFAULT]\n\
host = localhost\n\
user = root\n\
passwd = None \n\
maxdelcount = 5\n\
maxdeltime = 10\n\
dbprefix = Off\n\
verbose = False\n\
includesystemdb = 0\n\
outputdir = ./\n\n\
\#Uncomment the following and adapt it to your needs\n\n\
\#[Your special context ...]\n\
\#host = Your special Option\n\
\#user = Your special Option\n\
\#... overrides the defaults\n'
return syntax
def error():
if errorList:
print '\nThe following problems occured:\n'
for each in errorList:
print each+'\n'
sys.exit(-1)
sys.exit(0)
def testlist(list,value):
try:
list.index(value)
return True
except:
return False
def archive(argDict):
_host = argDict['host']
_outputdir = os.path.normpath(argDict['outputdir'])
#_compression = argDict['compression']
_compression = 8
# try:
# currentdumps = argDict['currentdumps']#
# except KeyError:
# currentdumps = ''
# pass
currenttime = strftime('%Y-%m-%d_%H-%M-%S',starttime)
archivefile = _outputdir+'/'+_host+'/'+_host+'_'+currenttime+'.zip'
sqlfilelist = list()
sqldellist = list()
for each in glob.glob(_outputdir+'/'+_host+'/*.sql'):
if not each in currentdumps:
sqlfilelist.append(each)
if sqlfilelist:
try:
archivefileobject = zipfile.ZipFile(archivefile,'w',_compression)
for each in sqlfilelist:
print 'Archiving '+each+' to '+archivefile
archivefileobject.write(each, os.path.basename(each))
sqldellist.append(each)
archivefileobject.close()
if not sqldellist:
errorList.append('No files found for archiving '+archivefile)
os.remove(archivefile)
elif not zipfile.is_zipfile(archivefile):
errorList.append('Error creating zip archive '+archivefile)
os.remove(archivefile)
else:
for each in sqldellist:
os.remove(each)
except error, err:
errorList.append(str(err))
pass
def clear(argDict):
_host = argDict['host']
_outputdir = os.path.normpath(argDict['outputdir'])
zipfile = _outputdir+'/'+_host+'/*.zip'
if argDict['maxdelcount'] not in negativelist:
_delcount = int(argDict['maxdelcount'])
else:
_delcount = 0
if argDict['maxdeltime'] not in negativelist:
#_deltime = 24*3600*int(argDict['maxdeltime'])
_deltime = int(argDict['maxdeltime'])
else:
_deltime = 0
# If both are false return with nothing done
if not _delcount and not _deltime:
return
try:
filelist = list()
dellist = list()
for each in glob.glob(zipfile):
filelist.append((os.stat(each)[8],each))
del each
# Sorting list form oldest to newest
filelist.sort()
# If the oldest file in list is younger then the _specified_ min. backup timeframe, do nothing.
if (filelist[0][0] >= (mktime(starttime)-_deltime)) and _deltime:
return
# While more files in directory as specified as minimum file count
while len(filelist) > _delcount:
# Mark oldest file(s) for deletion
if filelist[0][0] <= (int(mktime(starttime)-_deltime)):
dellist.append(filelist[0])
filelist.remove(filelist[0])
else:
break
if dellist:
print 'Deleting expired archives'
for each in dellist:
os.remove(each[1])
except os.error, err:
errorList.append('Error clearing the backupdir: '+_outputdir+'\n'+str(err))
def dump(argDict):
_host = argDict['host']
_user = argDict['user']
_passwd = argDict['passwd']
_dbprefix = argDict['dbprefix']
_file = argDict['file']
_outputdir = os.path.normpath(argDict['outputdir'])
currenttime = strftime('%Y-%m-%d_%H-%M',starttime)
if str(argDict['verbose']).lower() in positivelist:
_verbose = '-v'
else:
_verbose = ''
if str(argDict['includesystemdb']).lower() in positivelist:
_includesystemdb = True
else:
_includesystemdb = False
if str(argDict['noquestion']).lower() in positivelist:
_noquestion = True
else:
_noquestion = False
if str(argDict['passwd']).lower() in negativelist:
_passwd = ''
else:
_passwd = argDict['passwd']
if str(argDict['dbprefix']).lower() in negativelist:
_dbprefix = ''
else:
_dbprefix = argDict['dbprefix']
try:
dbs = list()
conn = mydb.connect(host=_host, user=_user, passwd=_passwd)
cursor = conn.cursor()
cursor.execute('show databases')
for each in cursor.fetchall():
dbs.append(each[0])
del cursor
conn.close()
if not _includesystemdb:
try:
dbs.remove('information_schema')
dbs.remove('mysql')
except ValueError:
pass
print 'Processing Host: '+_host
for each in dbs:
if each.startswith(_dbprefix):
outputfile = _outputdir+'/'+_host+'/'+each+'_'+currenttime+'.sql'
if not os.path.isdir(_outputdir+'/'+_host):
os.makedirs(_outputdir+'/'+_host)
print 'Dumping Database: '+each+' from '+_host+' to '+outputfile
if _passwd:
#print 'mysqldump '+_verbose+' -h '+_host+' -u '+ _user+' --password='+_passwd+' '+each+' > ' +outputfile
open(outputfile, 'w').close()
#os.system('mysqldump '+_verbose+' -h '+_host+' -u '+ _user+' --password='+_passwd+' '+each+' > '+outputfile)
else:
#print 'mysqldump '+_verbose+' -h '+_host+' -u '+ _user+' '+each+' > '+_outputfile
open(outputfile, 'w').close()
#os.system('mysqldump '+_verbose+' -h '+_host+' -u '+ _user+' '+each+' > '+outputfile)
currentdump.append(each+'_'+currenttime+'.sql')
if currentdump:
argDict['currentdumps'] = currentdump
except mydb.OperationalError, err:
errorList.append('MySQL connect error for: ' +_user+'@'+_host+'\n'+str(err))
pass
if __name__ == "__main__":
errorList = list()
argDict = params()
starttime = localtime()
_file = argDict['file']
_dbprefix = argDict['dbprefix']
_includesystemdb = argDict['includesystemdb']
_noquestion = argDict['noquestion']
positivelist = ['true','on','1']
negativelist = ['false','off','0','null','none', '']
currentdump = list()
print ''
if os.path.isfile(_file):
try:
config = ConfigParser()
config.read(_file)
for i in config.sections():
archive(argDict)
configdict = dict(config.items(i))
argDict['configsection'] = i
for j,k in configdict.iteritems():
argDict[j] = k
dump(argDict)
clear(argDict)
except ConfigParser, err:
print str(err)
error()
elif not _noquestion:
if not _dbprefix:
if _includesystemdb:
quest = raw_input('No DB-Prefix given. Do you want to dump all databases, including system dbs? [Y/N] ')
else:
quest = raw_input('No DB-Prefix given. Do you want to dump all non-system databases? [Y/N] ')
if quest.lower() == 'n':
sys.exit(1)
dump(argDict)
error()