]> git.frykholm.com Git - butterbackup.git/blob - butterbackup.py
26822276a29d6449bb03069493c3be4d85ec498e
[butterbackup.git] / butterbackup.py
1 #!/usr/bin/env python3
2 import os
3 import sys
4 from subprocess import check_call, CalledProcessError
5 import shlex
6 import datetime
7 import configparser
8
9 class Host():
10 def __init__(self, name, config):
11 self.name = name
12 self.config = config
13 if not self.config.has_section('host'):
14 self.config.add_section('host')
15 self.store_dir = self.config['host']['store_dir']
16 self.host_dir = os.path.join(self.store_dir, name)
17 self.subvol_dir = os.path.join(self.host_dir, "latest")
18 self.keep = int(self.config.get("host", "keep", fallback=-1))
19
20 def backup(self):
21 if not os.path.exists(self.host_dir):
22 print("New host",self.name,".")
23 os.makedirs(self.host_dir)
24 if not os.path.exists(self.subvol_dir):
25 try:
26 check_call(shlex.split("btrfs subvol create %s"% self.subvol_dir))
27 except CalledProcessError as ex:
28 print("Failed to create subvol! Aborting backup.")
29 return()
30
31 command = ("rsync -a --acls --xattrs --whole-file --numeric-ids --delete --delete-excluded --human-readable --inplace ")
32 excludes = " --exclude " + " --exclude ".join(self.config.get("host", "exclude").split(',')) #FIXME
33 try:
34 print(command + excludes + " root@%s:/ "%(self.name) + self.subvol_dir)
35 check_call(shlex.split(command + excludes + " root@%s:/ "%(self.name) + self.subvol_dir))
36 except CalledProcessError as ex:
37 if ex.returncode in (24,):
38 pass
39 else:
40 print("Rsync error from %s, skipping snapshot. Rsync exit value=%s"%(self.name, ex.returncode))
41 return()
42 todays_date = datetime.datetime.now().date().strftime("%F")
43 if os.path.exists(os.path.join(self.host_dir, todays_date)):
44 #There is a snapshot for today, removing it and creating a new one
45 try:
46 check_call(shlex.split("btrfs subvol delete %s"%(os.path.join(self.host_dir, todays_date))))
47 except CalledProcessError as ex:
48 pass
49 try:
50 check_call(shlex.split("btrfs subvol snapshot -r %s %s"%(self.subvol_dir,os.path.join(self.host_dir, todays_date))))
51 except CalledProcessError as ex:
52 pass
53
54 def prune_snapshots(self):
55 if self.keep == -1:
56 print("No keep specified for %s, keeping all"%self.name)
57 return
58 if not os.path.exists(self.host_dir):
59 print("New host, no pruning needed")
60 return
61 snaps = sorted([snap for snap in os.listdir(self.host_dir) if not snap == "latest" ], reverse=True)
62 while len(snaps) > self.keep:
63 snap = snaps.pop()
64 try:
65 check_call(shlex.split("btrfs subvol delete %s"%(os.path.join(self.host_dir, snap))))
66 except CalledProcessError as ex:
67 pass
68
69 class BackupRunner():
70 def __init__(self, config_dir):
71 self.config_dir = config_dir
72 if not os.path.exists(self.config_dir):
73 print("No config found", self.config_dir)
74 sys-exit(-1)
75
76 def run(self, hostlist=None):
77 self.hosts = hostlist or os.listdir(self.config_dir)
78
79 for host in self.hosts:
80 if host == 'default.cfg':
81 continue
82 try:
83 configfile = os.path.join(self.config_dir, host)
84
85 if not os.path.exists(configfile):
86 # Trigger logging in the except clause
87 raise BaseException()
88
89 config = configparser.ConfigParser(strict=False)
90 config.read_file(open(os.path.join(self.config_dir, 'default.cfg'),'r'))
91 config.read(configfile)
92 except BaseException as ex:
93 print("Config error for %s. Skipping host."%host)
94 continue
95 h = Host(host, config)
96 h.prune_snapshots()
97 h.backup()
98
99 if __name__ == "__main__":
100 if os.geteuid() != 0:
101 print("You need to be root. Otherwise all permissions will be lost.")
102 sys.exit(-1)
103 br = BackupRunner("/etc/butterbackup")
104
105 hostlist = sys.argv[1:]
106 br.run(hostlist=hostlist)
107
108 sys.exit(0)
109