Thomas Waldmann avatar Thomas Waldmann committed 7e56be4 Draft

add a long running daemon, add fs and smb utils

Comments (0)

Files changed (3)

+# -*- coding: ascii
+"""
+filesystem tools: mounting, etc.
+
+Note: bind mounting to /rootfs is a trick to get the REAL stuff from the
+      disk, but not the (virtual) filesystem files mounted on top of that.
+      It also makes special excludes for /dev /proc /sys unneccessary if one
+      wants to backup the root filesystem.
+
+author: Thomas Waldmann
+license: BSD
+"""
+
+import os
+import errno
+from subprocess import check_call as cmd
+
+
+def make_dir(path):
+    """make sure directory <path> exists"""
+    try:
+        # make sure that dst_path exists
+        os.mkdir(path)
+    except OSError as err:
+        # no problem if it already was there
+        if err.errno != errno.EEXIST:
+            raise
+
+
+def mount(src, mountpoint):
+    """mount src on mountpoint"""
+    cmd("mount %(src)s %(mountpoint)s" % dict(src=src, mountpoint=mountpoint),
+        shell=True)
+    # TODO: check if mount succeeded with os.path.ismount(mountpoint)
+
+
+def umount(mountpoint):
+    """umount mountpoint"""
+    cmd("umount %(mountpoint)s" % dict(mountpoint=mountpoint),
+        shell=True)
+
+
+def _process_pathes(command, pathes, dst_path, root_path="/", **kw):
+    """process command on all pathes below dst_path directory"""
+    prefix_len = len(os.path.commonprefix(pathes))
+    for path in pathes:
+        dst = os.path.join(dst_path, path[prefix_len:])
+        make_dir(dst)
+        cmd(command % dict(src=path, dst=dst), shell=True)
+
+
+def mount_pathes(pathes, dst_path, root_path="/", **kw):
+    """bind-mount all pathes below dst_path directory"""
+    _process_pathes("mount --bind %(src)s %(dst)s", pathes, dst_path, root_path, **kw)
+
+
+def umount_pathes(pathes, dst_path, root_path="/", **kw):
+    """umount all pathes in reverse given order"""
+    reversed_pathes = list(reversed(pathes)) # we MUST have a list, not iterator
+    _process_pathes("umount %(dst)s", reversed_pathes, dst_path, root_path, **kw)
+

rsync_backup_daemon.py

+# -*- coding: ascii
+"""
+daemon process based backup for interactive backups
+
+scenario:
+* linux based server running this script as daemon process
+* windows PC with smb network message service for notificiations
+* windows PC triggers backup by creating SIGNAL_FILE
+* server notifies PC about start of backup
+* server notifies PC about end of backup
+
+author: Thomas Waldmann
+license: BSD
+"""
+
+import os
+import time
+import subprocess
+
+from rsync_backup import backup, cmd
+from fs import mount_pathes, umount_pathes, mount, umount
+from smb import send_msg
+
+SIGNAL_FILE = "BACKUP.SIG"
+LOG_FILE = "BACKUP-LOG.TXT"
+
+BACKUP_DEV = "-L BACKUP"  # anything acceptable as 1st mount param
+BACKUP_MOUNTPOINT = "/mnt/backup"
+
+PATHES = ["/bin", "/etc", ]
+ROOTFS = "/rootfs"
+
+SRC = ROOTFS
+DST = BACKUP_MOUNTPOINT
+GENERATIONS = 3
+
+MSG_HOST = "gw-pc" # smb hostname for notifications
+
+
+def pre_backup():
+    """do stuff before the backup begins"""
+    send_msg("backup starts in 60s...", MSG_HOST)
+    time.sleep(60)
+    mount(BACKUP_DEV, BACKUP_MOUNTPOINT)
+    mount_pathes(PATHES, ROOTFS)
+
+
+def post_backup():
+    """do stuff after the backup finished"""
+    umount_pathes(PATHES, ROOTFS)
+    umount(BACKUP_MOUNTPOINT)
+    send_msg("backup ended", MSG_HOST)
+
+
+def do_backup():
+    with open(LOG_FILE, "a") as logfile:
+        try:
+            pre_backup()
+            backup(SRC, DST, GENERATIONS, stdout=logfile, stderr=subprocess.STDOUT)
+            post_backup()
+        except Exception as err:
+            send_msg("backup crashed!", MSG_HOST)
+            #logfile.write("Backup crashed, Exception:\n")
+            #logfile.write(str(err))
+            raise
+
+
+def daemon():
+    """poll whether SIGNAL_FILE to exist, if so: start backup, repeat"""
+    while True:
+        if os.path.exists(SIGNAL_FILE):
+            os.remove(SIGNAL_FILE)
+            do_backup()
+        time.sleep(10)
+
+
+if __name__ == "__main__":
+    daemon()
+# -*- coding: ascii
+"""
+sending smb network messages
+
+author: Thomas Waldmann
+license: BSD
+"""
+
+from subprocess import check_call as cmd
+
+
+SENDMSG = """echo "%(msg)s" | smbclient -U%(user)s%%%(password)s -M %(hostname)s"""
+
+
+def send_msg(msg, hostname, user=None, password=None, unattended=True):
+    """use smbclient to send a msg to hostname
+    
+    Note: do NOT give any user input to any parameter, it is unsafe.
+    """
+    if unattended:
+        # it seems like a dummy is OK, but if one does not give user and
+        # password on the commandline, it will ask for it interactively:
+        if user is None:
+            user = "server" # it will say "message from <user> ..." in the popup
+        if password is None:
+            password = user
+    cmd(SENDMSG % dict(msg=msg, hostname=hostname, user=user, password=password),
+        shell=True)
+
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.