Я начал изучать волшебный язык 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
-
Реплики Сумок Лучший Выбор
19 Oct, 24 -
Как Я Написал Свой Vnc, А Потом Нет
19 Oct, 24 -
Заражение Веб-Сайтов Через Ftp
19 Oct, 24 -
Эволюция Логотипа
19 Oct, 24