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