Резервное Копирование Базы Данных И Файлов Mysql На Удаленный Ftp — Python 3

Я начал изучать волшебный язык Python3 и решил опробовать его на своем маленьком VPS. На сервере есть Mysql, Apache, nginx. в общем простой стандартный набор, а еще там хостится два десятка клиентских сайтов.

Каждый день создается резервная копия всех баз и файлов домена с помощью замечательного скрипта #!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)

Метод берет файл или папку и создает архив с именем CURRENTDATE.zip или с вашим именем.

Если вы укажете add_to_file=True, файлы будут добавлены в существующий архив.



run_backup(self, mysql_user, mysql_pw, db)

Делаем резервную копию базы данных с помощью Linux-утилиты mysqldump, метод принимает ИМЯ ПОЛЬЗОВАТЕЛЯ, ПАРОЛЬ, ИМЯ БАЗЫ ДАННЫХ.



parse_options(self)

Разбираем переданные параметры, см.

пример ниже.



ftp_connect(self, host, username, password)

Откройте FTP-соединение, метод принимает HOST, USERNAME, PASSWORD с FTP-сервера.



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 дней удаляются.

Теги: #python3 #резервное копирование #linux #cron #python #разработка Linux

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.