Создаёт копию всех данных в каталоге /var/www/vhosts/
по понедельникам, и “инкрементальный” бекап только файлов, изменившихся за последние 24 часа.
Упаковывает и сжимает в архив tar.bz2
.
На каждый день создаётся отдельная директория, хранятся 4 полных бекапа и 7 – ежедневных.
Для бекапа баз MySQL – второй скрипт: Python: скрипт бекапа баз данных MySQL/MariaDB.
Структура каталогов в директории бекапов выглядит так:
[simterm]
# tree -L 3 -d /home/setevoy/backups/ /home/setevoy/backups/ ├── freeproxy │ ├── daily │ │ ├── 2014-10-13-files │ │ └── 2014-10-14-files │ └── weekly │ └── 2014-10-13-files ├── hudeem │ ├── daily │ │ ├── 2014-10-13-files │ │ └── 2014-10-14-files │ └── weekly │ └── 2014-10-13-files ├── profy │ ├── daily │ │ ├── 2014-10-13-files │ │ └── 2014-10-14-files │ └── weekly │ └── 2014-10-13-files ├── rtfm │ ├── daily │ │ ├── 2014-10-13-files │ │ └── 2014-10-14-files │ └── weekly │ └── 2014-10-13-files ├── domain │ ├── daily │ │ ├── 2014-10-13-files │ │ └── 2014-10-14-files │ └── weekly │ └── 2014-10-13-files ├── worlddesign │ ├── daily │ │ ├── 2014-10-13-files │ │ └── 2014-10-14-files │ └── weekly │ └── 2014-10-13-files └── zabbix ├── daily │ ├── 2014-10-13-files │ └── 2014-10-14-files └── weekly └── 2014-10-13-files
[/simterm]
И в каждой директории – хранится архив с содержимым сайта за конкретную дату:
[simterm]
# ls -lh /home/setevoy/backups/rtfm/weekly/2014-10-13-files/ total 168M -rw-r--r-- 1 root root 168M Oct 13 23:09 rtfm.co.ua.tar.bz2
[/simterm]
Скрипт делался под конкретную структуру директорий виртуалхостов:
[simterm]
# tree -L 2 -d /var/www/vhosts/ /var/www/vhosts/ ├── freeproxy │ └── freeproxy.in.ua ├── hudeem │ └── hudeem.kiev.ua ├── profy │ └── profy.kiev.ua ├── rtfm │ └── rtfm.co.ua ├── domain │ ├── forum.domain.kiev.ua │ ├── postfixadmin.domain.org.ua │ ├── domain.org.ua │ └── webmail.domain.org.ua ├── worlddesign │ └── worlddesign.org.ua └── zabbix └── zabbix.domain.org.ua
[/simterm]
Вызывается по крону, каждый день в 4 утра (т.к. нагрузка на CPU во время сжатия большая):
[simterm]
# crontab -l 00 */12 * * * /usr/bin/sa-learn --spam /var/vmail/domain.kiev.ua/[email protected]/.Junk/ 00 */12 * * * /usr/bin/sa-learn --ham /var/vmail/domain.kiev.ua/[email protected]/cur/ 0 04 * * * /home/setevoy/opt/files_backup.py
[/simterm]
После каждого выполнения – отправляет письмо с уведомлением о выполнении и размере занятого дискового пространства:
[simterm]
# cat /var/vmail/domain.org.ua/[email protected]/cur/1413272219.M851594P3122.venti.domain.org.ua,S=829,W=855:2,a Return-path: <[email protected]> Envelope-to: [email protected] Delivery-date: Tue, 14 Oct 2014 10:36:59 +0300 Received: from [127.0.0.1] (helo=venti.domain.org.ua) by mx0.domain.org.ua with esmtp (Exim 4.72) (envelope-from <[email protected]>) id 1XdwfD-0000oG-Dr for [email protected]; Tue, 14 Oct 2014 10:36:59 +0300 Content-Type: multipart/mixed; boundary="===============8677965755135430447==" MIME-Version: 1.0 Subject: daily backup report From: Backup Manager <[email protected]> To: root <[email protected]> --===============8677965755135430447== Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit daily backup on venti.domain.org.ua finished. Total used disk space: 341 MB. --===============8677965755135430447==--
[/simterm]
В самом скрипте все функции документированы:
[simterm]
# pydoc /home/setevoy/opt/files_backup.py | head -n 30 Help on module files_backup: NAME files_backup - Create full (at Monday) or incremental backup files [VHOSTSPATH]. FILE /home/setevoy/opt/files_backup.py DESCRIPTION Creation date: 13.10.2014 Last modify date: 14.10.2014 FUNCTIONS arch_users_list(path) Call with VHOSTSPATH as argument. See 'run_backup()' help. arch_users_path(path, userlist) Call with VHOSTSPATH and userlist[] from 'arch_users_list()'. See 'run_backup()' help. arch_vhosts_names(vhosts_paths) Call with vhosts_paths[] from 'arch_users_path()'. See 'run_backup()' help. back_dir_create(user, day, bkptype) Creates new directory for each day; depending on data recieved from 'dir_bkptype()' can create 'weekly' or 'daily' directory.
[/simterm]
И сам скрипт:
#!/usr/bin/env python ''' Create full (at Monday) or incremental backup files [VHOSTSPATH]. Creation date: 13.10.2014 Last modify date: 14.10.2014 ''' import tarfile import os import time import sys import heapq import shutil import logging import smtplib import mimetypes import email import email.mime.application import socket VHOSTSPATH = '/var/www/vhosts/' BACKDIR = '/home/setevoy/backups/' LOGPATH = '/var/log/backup.log' # os.environ не сработает через cron # HOST = os.environ['HOSTNAME'] HOST = socket.gethostname() logging.basicConfig(format = '%(filename)s - %(levelname)-3s [%(asctime)s] %(message)s ', filename=LOGPATH, level=logging.DEBUG) def dir_bkptype(day): '''On Sunday - create full weekly backup with 'full_backup()'; other days - incremental backup with inc_backup()'; also used in 'back_dir_create()' to create BACKDIR/weekly or daily; also used in 'clear_old_dirs()' for deleting old directories.''' if day == 'Mon': bkptype = 'weekly' else: bkptype = 'daily' logging.info('Today is %s, will use backuptype %s.' % (day, bkptype)) return(bkptype) def back_dir_create(user, bkptype): '''Creates new directory for each day; depending on data recieved from 'dir_bkptype()' can create 'weekly' or 'daily' directory.''' dirname = os.path.join(BACKDIR, user, bkptype, '%s-files/' % time.strftime('%Y-%m-%d')) if not os.path.exists(dirname): os.makedirs(dirname) logging.info('Directory %s created' % dirname) return(dirname) def arch_users_list(path): '''Call with VHOSTSPATH as argument. See 'run_backup()' help.''' userlist = [] for user in os.listdir(path): if not user == '.htpasswd': userlist.append(user) return(userlist) def arch_users_path(path, userlist): '''Call with VHOSTSPATH and userlist[] from 'arch_users_list()'. See 'run_backup()' help.''' vhosts_paths = [] for user in userlist: userdirpath = os.path.join(path, user) vhosts_paths.append(userdirpath) return(vhosts_paths) def arch_vhosts_names(vhosts_paths): '''Call with vhosts_paths[] from 'arch_users_path()'. See 'run_backup()' help.''' vhosts_names = [] for hostname in os.listdir(vhosts_paths): hostnamepath = os.path.join(vhosts_paths, hostname) if os.path.isdir(hostnamepath): vhosts_names.append(hostname) return(vhosts_names) def inc_backup(dir_to_backup, backupname): '''Incremental backup. Call with [hostname] as dir_to_backup, and [archname] as backupname in 'run_backup()'. Walks through files and directories in [hostname], looks for data, modified in last 86400 seconds (24 hours).''' now = time.time() cutoff = 86400 logging.info('Creating archive %s' % backupname) out = tarfile.open(backupname, mode='w:bz2') for root, dirs, files in os.walk(dir_to_backup, followlinks=True): for file in files: file_to_backup = os.path.join(root, file) try: filemodtime = os.stat(file_to_backup).st_mtime if now - filemodtime < cutoff: logging.debug('Adding file: %s' % file_to_backup) out.add(file_to_backup) logging.debug('File modified: %s' % time.ctime(os.path.getmtime(file_to_backup))) except OSError as error: logging.critical('ERROR: %s' % error) logging.info('Done.') out.close() def full_backup(dir_to_backup, backupname): '''Full backup. Call with [hostname] as dir_to_backup, and [archname] as backupname in 'run_backup()'. Add all files and directories in [hostname] to [archname].''' logging.info('Creating archive %s' % backupname) out = tarfile.open(backupname, mode='w:bz2') try: logging.info('Adding directory: %s' % dir_to_backup) out.add(dir_to_backup) except OSError as error: logging.critical('ERROR: %s' % error) logging.info('Done.') out.close() def clear_old_dirs(user, day, count, host, bkptype): '''Deletes old directories. Depending on [count] - can store 7 (for daily) or 4 (for weekly) copies, including today's archive.''' dirname = os.path.join(BACKDIR, user, bkptype) last = heapq.nlargest(count, os.listdir(dirname)) for i in last: logging.info('Saving previous data backups: %s/%s' % (host, i)) dir_to_check = [] for dir in os.listdir(dirname): if dir not in last: logging.info('Deleting old data backups: %s/%s' % (host, dir)) dit_to_del = os.path.join(dirname, dir) shutil.rmtree(dit_to_del) def get_size(path): '''Calculate all files size in [BACKDIR] directory. 1048576 - for Megabytes, 1024 - for Kilobytes.''' total_size = 0 for root, dirs, files in os.walk(path): for file in files: fp = os.path.join(root, file) total_size += os.path.getsize(fp) return(total_size / 1048576) def sendmail(bkptype): '''Send email report to [to] with used disk space information.''' total_size = get_size(BACKDIR) sender = '[email protected]' to = ['[email protected]'] msg = email.mime.Multipart.MIMEMultipart() msg['Subject'] = ('%s backup report' % bkptype) msg['From'] = 'Backup Manager <[email protected]>' msg['To'] = 'root <[email protected]>' body = email.mime.Text.MIMEText(""" %s backup on %s finished. Total used disk space: %s MB. """ % (bkptype, HOST, total_size)) msg.attach(body) smtpconnect = smtplib.SMTP('localhost:25') #smtpconnect.set_debuglevel(1) smtpconnect.sendmail(sender, to, msg.as_string()) smtpconnect.quit() logging.info('Email sent from ['%s'] to %s.' % (sender, to)) def run_backup(): '''userlist - data from 'arch_users_list()' in list: ['rtfm', 'profy', 'freeproxy', 'worlddesign', 'domain', 'zabbix', 'hudeem']; userpaths - data from 'arch_users_path()' in list: ['/var/www/vhosts/rtfm', '/var/www/vhosts/profy', ... '/var/www/vhosts/hudeem']; curday - return week day, like Sun, Mon etc; bkptype - backup type from 'dir_bkptype()' like 'weekly' or 'daily'; backup_dir - directory, created by 'back_dir_create()' like: /home/setevoy/backups/temp_new_test/hudeem/daily/2014-10-13-files/; virtual_hosts - data from arch_vhosts_names like: ['hudeem.kiev.ua'].''' userlist = arch_users_list(VHOSTSPATH) userpaths = arch_users_path(VHOSTSPATH, userlist) curday = time.strftime('%a') bkptype = dir_bkptype(curday) for hostdir, username in zip(userpaths, userlist): os.chdir(hostdir) logging.info('Working in: %s and user %s' % (os.getcwd(), username)) backup_dir = back_dir_create(username, bkptype) if backup_dir: virtual_hosts = arch_vhosts_names(hostdir) for host in virtual_hosts: archname = (backup_dir + host + '.tar.bz2') # если используется вместе со скриптом бекапа MySQL баз # то увеличить х2 (4 = 8, 7 = 14) # т.к. там же находятся каталоги с бекапами баз if bkptype == 'weekly': clear_old_dirs(username, curday, 4, host, bkptype) full_backup(host, archname) else: clear_old_dirs(username, curday, 7, host, bkptype) inc_backup(host, archname) else: logging.info('Directory already present, skip.') sendmail(bkptype) if __name__ == "__main__": starttime = time.strftime('%Y-%m-%d %H:%M:%S') logging.info('Backup started at: %s' % starttime) run_backup() finishtime = time.strftime('%Y-%m-%d %H:%M:%S') logging.info('Backup finished at: %s' % finishtime) logging.info('Total used size in %s - %s MB' % (BACKDIR, get_size(BACKDIR)))
Лог выглядит так:
[simterm]
files_backup.py - INFO [2014-10-13 23:06:29,145] Backup started at: 2014-10-13 23:06:29 files_backup.py - INFO [2014-10-13 23:06:29,147] Today is Mon, will use backuptype weekly. files_backup.py - INFO [2014-10-13 23:06:29,147] Working in: /var/www/vhosts/rtfm and user rtfm files_backup.py - INFO [2014-10-13 23:06:29,147] Directory /home/setevoy/backups/rtfm/weekly/2014-10-13-files/ created files_backup.py - INFO [2014-10-13 23:06:29,147] Saving previous data backups: rtfm.co.ua/2014-10-13-files files_backup.py - INFO [2014-10-13 23:06:29,148] Creating archive /home/setevoy/backups/rtfm/weekly/2014-10-13-files/rtfm.co.ua.tar.bz2 files_backup.py - INFO [2014-10-13 23:06:29,149] Adding directory: rtfm.co.ua files_backup.py - INFO [2014-10-13 23:09:54,873] Done.
[/simterm]
Скрипт писался больше ради практики в Python, что бы совсем его не забыть, т.к. в работе писат ьприходится редко.