mysqlデータベースとファイルをリモートFTPにバックアップする-Python 3

彼はPython3の魔法の言語を学び始め、小さなVPSで試してみることにしました。



サーバーには、Mysql、Apache、nginxなどがあり、一般的には単純な標準セットで、そこにはホストされた20のクライアントサイトがあります。



毎日、知覚可能なスクリプト#!Bin / bashを使用して、すべてのデータベースとドメインファイルのバックアップコピーが作成されます。



Python 3を使用することにしました...コードは次のとおりです。



#!/usr/bin/env python3 import subprocess import datetime import optparse import zipfile import os import ftplib class ReturnCode(Exception): pass class NotExist(Exception): pass class RequiredOpts(Exception): pass class BackupUtils: __current_date = str(datetime.datetime.now().strftime('%d_%m_%Y')) def __init__(self): self.ftp = None def to_zip(self, file, filename=__current_date + '.zip', append_to_file=False): """ :param file: file or folder for added to archive :param filename: output archive filename :param append_to_file: if False, will be create new file, True for append in exist file :type append_to_file: False :type filename: str :type file: str :return True """ param_zip = 'a' if append_to_file else 'w' try: with zipfile.ZipFile(filename, param_zip) as zip_file: if os.path.isfile(file): zip_file.write(file) else: self.add_folder_to_zip(zip_file, file) return True except IOError as error: print('Cannot create zip file, error: {}'.format(error)) return False def add_folder_to_zip(self, zip_file, folder): """ :type folder: str :type zip_file: file """ for file in os.listdir(folder): full_path = os.path.join(folder, file) if os.path.isfile(full_path): zip_file.write(full_path) elif os.path.isdir(full_path): self.add_folder_to_zip(zip_file, full_path) def run_backup(self, mysql_user, mysql_pw, db): """ :type db: str :type mysql_pw: str :type mysql_user: str :return string - dump filename """ try: dump = 'dump_' + db + '_' + self.__current_date + '.sql' # return dump p = subprocess.Popen( 'mysqldump -u' + mysql_user + ' -p' + mysql_pw + ' --databases ' + db + ' > ' + dump, shell=True) # Wait for completion p.communicate() # Check for errors if p.returncode != 0: raise ReturnCode print('Backup done for', db) return dump except: print('Backup failed for ', db) def parse_options(self): parser = optparse.OptionParser(usage="""\ %prog -u USERNAME -p PASSWORD -d DATABASE -D /path/for/domain/ -f BACKUP_FILE_NAME Required Username, Password, Database name and path for Domain folder If you want copy backup to remote ftp, use options: %prog -u USERNAME -p PASSWORD -d DATABASE -D /path/for/domain/ -f BACKUP_FILE_NAME --ftp-host HOST --ftp-user USERNAME --ftp-password PASSWORD --ftp-folder FOLDER If you want delete archives from ftp, add options: --ftp-delete-old --ftp-delete-day N (not required, 3 days default) """, conflict_handler="resolve") parser.add_option("-u", "--username", dest="username", help=("Username of database " "[default: %default]")) parser.add_option("-p", "--password", dest="password", help=("Password of database " "[default: %default]")) parser.add_option("-d", "--database", dest="database", help=("Database name " "[default: %default]")) parser.add_option("-D", "--domain", dest="domain", help=("Domain folder for backup " "[default: %default]")) parser.add_option("-f", "--filename", dest="filename", help=("Backup file name " "[default: %default]")) parser.add_option("--ftp-host", dest="host", help=("Ftp host " "[default: %default]")) parser.add_option("--ftp-user", dest="ftpuser", help=("Ftp username " "[default: %default]")) parser.add_option("--ftp-password", dest="ftppassword", help=("Ftp password " "[default: %default]")) parser.add_option("--ftp-folder", dest="folder", help=("Ftp upload folder " "[default: %default]")) parser.add_option("--ftp-delete-old", dest="ftpdelete", action='store_true', help=("Delete files from ftp older 3 days " "[default: %default]")) parser.add_option("--ftp-delete-day", dest="ftpdeleteday", type='int', help=("Delete files from ftp older N days " "[default: %default]")) parser.set_defaults(username='root', filename=self.__current_date + '.zip', folder='.', ftpdelete=False, ftpdeleteday=3) return parser.parse_args() def ftp_connect(self, host, username, password): """ :param host: remote host name :param username: username for remote host :param password: password for remote host :type host: str :type username: str :type password: str :return object self.ftp """ try: self.ftp = ftplib.FTP(host=host, user=username, passwd=password) return self.ftp except ftplib.error_perm as error: print('Is there something wrong: {}'.format(error)) except: print('Cannot connected to ftp: ', host) return False def ftp_disconnect(self): """ :return: True """ try: self.ftp.close() self.ftp = None return True except: return False def upload_file_to_ftp(self, filename, folder='.'): """ :param filename: upload file name :param folder: special folder - / default :type filename: str :type folder: str :return True """ try: self.ftp.cwd(folder) self.ftp.dir() with open(filename, 'rb') as f: self.ftp.storbinary('STOR %s' % filename, f) return True except ftplib.all_errors as error: print('Is there something wrong: {}'.format(error)) return False def remove_old_files_from_ftp(self, folder='.', day=3): """ :param folder: special folder - / default :param day: count of day :type folder: str :type day: int :return True """ try: self.ftp.cwd(folder) facts = self.ftp.mlsd() i = 0 for fact in facts: modify = fact[1]['modify'][:8] if (int(datetime.datetime.now().strftime('%Y%m%d')) - int(modify)) > int(day): # if we cannot change directory - is file try: self.ftp.cwd(fact[0]) except: self.ftp.delete(fact[0]) i += 1 print('Deleted {} files'.format(str(i))) return True except ftplib.all_errors as error: print('Is there something wrong: {}'.format(error)) return False except TypeError: print('Day is not number, use 1 or 2,3,n') return False
      
      





いくつかのメソッドを持つ単純なクラスを作成しました:



 to_zip(self, file, filename=__current_date + '.zip', append_to_file=False)
      
      





このメソッドはファイルまたはフォルダーを受け入れ、CURRENT.zipという名前または自分の名前のアーカイブを作成します。append_to_file= Trueを渡すと、ファイルは既存のアーカイブに追加されます



 run_backup(self, mysql_user, mysql_pw, db)
      
      





Linux mysqldumpユーティリティを使用して、データベースのバックアップコピーを作成します。メソッドは、USER NAME、PASSWORD、BASE NAMEを受け入れます



 parse_options(self)
      
      





Parsimはオプションを渡しました。以下の例で詳しく説明します...



 ftp_connect(self, host, username, password)
      
      





FTP接続を開きます。メソッドはFTPサーバーからHOST、USER NAME、PASSWORDを受け入れます



 ftp_disconnect(self)
      
      





明確でない名前の明確でないメソッド)



 upload_file_to_ftp(self, filename, folder='.')
      
      





このメソッドは、ファイル名とオプションでフォルダを受け入れます。ファイルのみがコピーされます。



 remove_old_files_from_ftp(self, folder='.', day=3)
      
      





指定したフォルダーからN日より古いすべてのファイルを削除します。メソッドはそれぞれFOLDERとDAYSを受け入れます



そして今、このクラスをどのように使用するかの例:



 def main(): backup_utils = BackupUtils() opts, args = backup_utils.parse_options() # required Username, password, database name and path for domain folder try: if opts.username is None or opts.password is None or opts.database is None or opts.domain is None: raise RequiredOpts except RequiredOpts: print('Use -h or --help option') exit() # create sql dump backup_database = backup_utils.run_backup(opts.username, opts.password, opts.database) # dump archive filename dump_archive = 'dump_' + opts.filename if '.zip' in opts.filename else 'dump_' + opts.filename + '.zip' if backup_database: # add sql dump to zip "dump_filename.zip" backup_utils.to_zip(backup_database, dump_archive) # remove sql dump os.remove(backup_database) # find domain name in path - site.com try: i = opts.domain.index('.') if opts.domain[:-1] != '/': opts.domain += '/' left = opts.domain.rindex('/', 0, i) right = opts.domain.index('/', i) domain = opts.domain[left + 1:right] except: domain = '' # backup file name backup_archive = 'backup_' + domain + '_' + opts.filename if '.zip' in opts.filename else 'backup_' + domain + '_' + opts.filename + '.zip' # check if path exist try: if not os.path.isdir(opts.domain) and not os.path.isfile(opts.domain): raise NotExist except NotExist: print('{} No such file or directory'.format(opts.domain)) exit() # create domain folder archive backup_utils.to_zip(opts.domain, backup_archive) if os.path.isfile(dump_archive): # add dump archive to domain archive backup_utils.to_zip(dump_archive, backup_archive, True) # remove dump zip file os.remove(dump_archive) # upload backup to ftp if opts.host and opts.ftpuser and opts.ftppassword and backup_utils.ftp_connect(opts.host, opts.ftpuser, opts.ftppassword) is not None: backup_utils.upload_file_to_ftp(backup_archive, folder=opts.folder) backup_utils.ftp_disconnect() # remove local backup archive os.remove(backup_archive) # delete files from ftp older N days if opts.ftpdelete and backup_utils.ftp_connect(opts.host, opts.ftpuser, opts.ftppassword) is not None: backup_utils.remove_old_files_from_ftp(folder=opts.folder, day=opts.ftpdeleteday) backup_utils.ftp_disconnect() if __name__ == "__main__": main()
      
      







最後に、コマンドをcronに追加します。



backup.py -p PASSWORD FOR DB -d NAME FO DB -D /PATH/FOR/WEB/SITE.COM/HTML/ --ftp-host FTP HOST NAME --ftp-user FTP USER --ftp-password FTP PASSWORD --ftp-delete-old --ftp-delete-day DAYS --ftp-folder FTP FOLDER







それだけです! 毎日、データベースとプロジェクトファイルのバックアップコピーが作成され、ftpにコピーされます。ftpサーバーを圧倒しないように、3日より古いコピーはすべて削除されます。



All Articles