#!/usr/bin/env python
#
# RTBackup - Real-time backup tool
#
# Real-time backup script which periodically checks modification time of each
# file. It creates a copy of the file if its mtime is newer than stored in the
# database, with preserve the file and parent directories permissions.
#
# Requirements: SQLite 2.8.x
#
# This software may be freely redistributed under the terms of the GNU
# General Public License (GPL).
### config
file_dir = "/directory/you/want/to/backup"
backup_dir = "/directory/where/backup/will/be/stored"
backup_delay = "15m" # delay between backups (15 minutes)
date_format = "%Y-%m-%d_%H:%M:%S" # backup directory format (YY-MM-DD_HH:MM:SS)
db_filepath = "fileserver.db" # path to backup database
### code - do not change anything below here
__author__ = "Damian Pasternok <my_forename at pasternok.org>"
__version__ = "0.1"
__date__ = "2010/07/14"
__copyright__ = "Copyright (C) 2010 Damian Pasternok"
__license__ = "GPL"
import datetime
import os
import signal
import sqlite
import stat
import sys
import time
class DBsqlite:
def __init__(self, db_file):
self.__connection = sqlite.connect(db_file)
self.__cursor = self.__connection.cursor()
def db_query(self, query):
self.__cursor.execute(query)
self.__connection.commit()
def db_query_result(self):
return self.__cursor.fetchall()
def db_close(self):
self.__cursor.close()
self.__connection.close()
class FileBackup(DBsqlite):
def __init__(self, db_file):
# current file list
proc_find = os.popen("find \"%s\" -type f" % file_dir)
filelist = []
for i in proc_find.readlines():
filelist.append(i.rstrip())
tablename = os.path.splitext(db_file.upper())[0]
# first run
if not os.path.isfile(db_file):
# create new database and table with new result if doesn't exist
print "Creating new database %s..." % db_file
DBsqlite.__init__(self, db_file)
print "Creating new table %s..." % tablename
self.db_query("CREATE TABLE %s (relative_file_path TEXT, file_backup_date FLOAT)" % tablename)
else:
DBsqlite.__init__(self, db_file)
# common datetime for current backup
self.current_datetime = self.__current_datetime()
# check which files need update
for i in filelist:
self.db_query("SELECT * FROM %s WHERE relative_file_path=\"%s\""
% (tablename, FileOper().file_relative_path(i, file_dir)))
res = self.db_query_result()
current_relative_file_path = FileOper().file_relative_path(i, file_dir)
current_relative_file_dir = FileOper().file_dir(current_relative_file_path)
# create new record if file doesn't exist in the database
if not res:
print "Adding %s with timestamp %f..." % (current_relative_file_path,
FileOper().file_mtime(i))
self.db_query("INSERT INTO %s VALUES(\"%s\", %f)"
% (tablename, current_relative_file_path, FileOper().file_mtime(i)))
###
# then backup modified files
# [0][1] - file mtime (float)
if res and round(FileOper().file_mtime(i), 5) > round(res[0][1], 5):
# create directory tree if not exists
if not os.path.isdir(self.__backup_new_file_path(current_relative_file_dir)):
print "Creating directory %s..." % FileOper().file_relative_path(
self.__backup_new_file_path(current_relative_file_dir), backup_dir)
os.makedirs(self.__backup_new_file_path(current_relative_file_dir))
# do not override root dir permissions
if current_relative_file_dir != "/":
self.__keep_dir_permissions(current_relative_file_dir)
print "Copying %s into %s..." % (current_relative_file_path,
FileOper().file_relative_path(self.__backup_new_file_path(current_relative_file_path),
backup_dir))
os.popen("cp -ar \"%s\" \"%s\"" % (i, self.__backup_new_file_path(current_relative_file_path)))
# do not allow to modify the file copy
os.popen("chmod a-w \"%s\"" % self.__backup_new_file_path(current_relative_file_path))
print "Updating timestamp in database - old: %f, new: %f" % (res[0][1],
FileOper().file_mtime(i))
self.db_query("UPDATE %s SET file_backup_date=%f WHERE relative_file_path=\"%s\""
% (tablename, FileOper().file_mtime(i), FileOper().file_relative_path(i, file_dir)))
self.db_close()
def __current_datetime(self):
new_datetime = datetime.datetime.now()
print "New backup timestamp: %s [backup_delay=%s]" % (new_datetime, backup_delay)
return new_datetime.strftime(date_format)
def __backup_new_file_path(self, relative_file_dir):
backup_file_dir = backup_dir
backup_file_dir += "/"
backup_file_dir += self.current_datetime
backup_file_dir += relative_file_dir
return backup_file_dir
def __keep_dir_permissions(self, relative_file_dir):
dir_tree = FileOper().file_dir_tree(relative_file_dir)
if dir_tree:
for i in dir_tree:
full_file_dir = file_dir
full_file_dir += i
file_uid = os.stat(full_file_dir)[stat.ST_UID]
file_gid = os.stat(full_file_dir)[stat.ST_GID]
file_mode = os.stat(full_file_dir)[stat.ST_MODE]
file_mode_human = oct(file_mode)[-4:] # human readable file mode
full_backup_dir = backup_dir+"/" # (last four characters are permissions)
full_backup_dir += self.current_datetime
full_backup_dir += i
print "Setting up [UID=%d GID=%d MODE=%s (human: %s)] for %s..." % (file_uid,
file_gid, file_mode, file_mode_human, i)
os.chown(full_backup_dir, file_uid, file_gid)
os.chmod(full_backup_dir, file_mode)
class FileOper:
def file_relative_path(self, file_path, exclude):
return file_path.replace(exclude, '')
def file_dir(self, file_path):
return os.path.dirname(file_path)
def file_dir_tree(self, file_dir):
tree_elements = file_dir.split('/')
dir_tree = []
if len(tree_elements) > 1:
for i in range(1, len(tree_elements)):
tmp = ''
for j in range(1, i+1):
tmp += "/%s" % tree_elements[j]
dir_tree.append(tmp)
return dir_tree
else:
return 0
def file_mtime(self, file_path):
return os.path.getmtime(file_path)
def sigint(signum, frame):
print "\nCaught SIGINT. Bye."
sys.exit()
def main():
signal.signal(signal.SIGINT, sigint)
print "%s [PID=%d] is up and running..." % (sys.argv[0], os.getpid())
while True:
backup = FileBackup(db_filepath)
print
os.popen("sleep %s" % backup_delay)
if __name__ == "__main__":
main()