]> git.frykholm.com Git - butterbackup.git/commitdiff
Added backup pruning functionality. Reworked config files to use ini file syntax...
authorMikael Frykholm <mikael@frykholm.com>
Wed, 29 Aug 2012 13:29:25 +0000 (15:29 +0200)
committerMikael Frykholm <mikael@frykholm.com>
Wed, 29 Aug 2012 13:29:25 +0000 (15:29 +0200)
README.md
TODO
butterbackup.py

index 174a76c2dbad178c3ae435640a1f429b6254b1aa..63397f71e1a4d73d4dffb452099b83fa3432fea0 100644 (file)
--- a/README.md
+++ b/README.md
@@ -5,9 +5,10 @@ Butterbackup is a backup system which stores snapshots in btrfs on a central ser
 
 For now it is rather crude. To get started backing up some manual work is needed.
 <pre lang="bash"><code>
-Create /etc/Butterbackup
-# echo "--exclude /tmp --exclude /proc --exclude /sys --exclude /dev" > /etc/butterbackup/machine1.example.com
-# cp /etc/butterbackup/machine1.example.com /etc/butterbackup/machine2.example.com
+Create /etc/butterbackup
+# echo "--exclude /tmp --exclude /proc --exclude /sys --exclude /dev" > /etc/butterbackup/default.cfg
+# touch /etc/butterbackup/machine1.example.com
+# touch /etc/butterbackup/machine2.example.com
 # ssh-copy-id root@machine1.example.com
 # ssh-copy-id root@machine1.example.com
 # mkdir /mnt/data2
diff --git a/TODO b/TODO
index 0cb193c6e3dae284c10e04f2c8c6d95d44e667f9..17adadc9fd13c3722205341270a8acb183829724 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,2 +1,3 @@
 Make debian packages
-Make initial setup a bit nicer
\ No newline at end of file
+Make initial setup a bit nicer
+Make += work for inherted values. 
\ No newline at end of file
index ccf6229473a99c46fda0fd470a583a3034ae3482..7c80e8b344467f61e1a9738508ec09f020a311c0 100755 (executable)
@@ -4,54 +4,89 @@ import sys
 from subprocess import check_call, CalledProcessError
 import shlex
 import datetime
-class BackupRunner():
-    def __init__(self, config_dir, dest_dir):
-        self.config_dir = config_dir
-        self.dest_dir = dest_dir
-        if not os.path.exists(self.config_dir):
-            print("No config found", self.config_dir)
-            sys-exit(-1)
+import configparser
 
-    def run(self):
-        self.hosts = os.listdir(self.config_dir)
-        for host in self.hosts:
-            fp = open(os.path.join(self.config_dir, host),"r")
-            self.backup_host(host, fp)
-            fp.close()
+class Host():
+    def __init__(self, name, config):
+        self.name = name
+        self.config = config 
+        if not self.config.has_section('host'):
+            self.config.add_section('host')
+        self.store_dir = self.config['host']['store_dir']
+        self.host_dir = os.path.join(self.store_dir, name)
+        self.subvol_dir = os.path.join(self.host_dir, "latest")
+        self.keep = int(self.config.get("host", "keep", fallback=-1))
 
-    def backup_host(self, host, host_config):
-        host_dir = os.path.join(self.dest_dir, host)
-        subvol_dir = os.path.join(host_dir, "latest")
-        if not os.path.exists(host_dir):
+    def backup(self):
+        if not os.path.exists(self.host_dir):
             print("New host",host,".")
-            os.makedir(host_dir)
-        if not os.path.exists(subvol_dir):
+            os.makedir(self.host_dir)
+        if not os.path.exists(self.subvol_dir):
             try:
-                check_call(shlex.split("btrfs subvol create %s"% subvol_dir))
+                check_call(shlex.split("btrfs subvol create %s"% self.subvol_dir))
             except CalledProcessError as ex:
                 print("Failed to create subvol! Aborting backup.")
                 return() 
             
         command = ("rsync -a --acls --xattrs --whole-file --numeric-ids --delete --delete-excluded --human-readable --inplace ")
-        excludes = host_config.readline()[:-1]
+        excludes = " --exclude " + " --exclude ".join(self.config.get("host", "exclude").split(',')) #FIXME
         try:
-            check_call(shlex.split(command + excludes + " root@%s:/ "%(host) + subvol_dir))
+            print(command + excludes + " root@%s:/ "%(self.name) + self.subvol_dir)
+            check_call(shlex.split(command + excludes + " root@%s:/ "%(self.name) + self.subvol_dir))
         except CalledProcessError as ex:
-            if ex.returncode not in (12, 30, 255):
-                print("Rsync did not transfer anything from %s, skipping snapshot."%host)
+            if ex.returncode not in (12, 30):
+                print("Rsync did not transfer anything from %s, skipping snapshot."%self.name)
                 return()
         todays_date = datetime.datetime.now().date().strftime("%F")
-        if os.path.exists(os.path.join(host_dir, todays_date)):
+        if os.path.exists(os.path.join(self.host_dir, todays_date)):
             #There is a snapshot for today, removing it and creating a new one
             try:
-                check_call(shlex.split("btrfs subvol delete %s"%(os.path.join(host_dir, todays_date))))
+                check_call(shlex.split("btrfs subvol delete %s"%(os.path.join(self.host_dir, todays_date))))
             except CalledProcessError as ex: 
                 pass    
         try:
-            check_call(shlex.split("btrfs subvol snapshot -r %s %s"%(subvol_dir,os.path.join(host_dir, todays_date))))
+            check_call(shlex.split("btrfs subvol snapshot -r %s %s"%(self.subvol_dir,os.path.join(self.host_dir, todays_date))))
         except CalledProcessError as ex: 
             pass
 
+    def prune_snapshots(self):
+        if self.keep == -1:
+            print("No keep specified for %s, keeping all"%self.name)
+            return
+
+        snaps = sorted([snap for snap in os.listdir(self.host_dir) if not snap == "latest" ], reverse=True)
+        while len(snaps) > self.keep:
+            snap = snaps.pop()
+            try:
+                check_call(shlex.split("btrfs subvol delete %s"%(os.path.join(self.host_dir, snap))))
+            except CalledProcessError as ex: 
+                pass    
+
+class BackupRunner():
+    def __init__(self, config_dir, dest_dir):
+        self.config_dir = config_dir
+        self.dest_dir = dest_dir
+        if not os.path.exists(self.config_dir):
+            print("No config found", self.config_dir)
+            sys-exit(-1)
+
+    def run(self):
+        self.hosts = os.listdir(self.config_dir)
+
+        for host in self.hosts:
+            if host == 'default.cfg':
+                continue
+            try:
+                config = configparser.ConfigParser(strict=False)
+                config.read_file(open(os.path.join(self.config_dir, 'default.cfg'),'r'))
+                config.read(os.path.join(self.config_dir, host))
+            except BaseException as ex:
+                print("Config error for %s. Skipping host."%host)
+                continue
+            h = Host(host, config)
+            h.prune_snapshots()
+            h.backup()
+    
 if __name__ == "__main__":
     if os.geteuid() != 0:
         print("You need to be root. Otherwise all permissions will be lost.")