]>
Commit | Line | Data |
---|---|---|
25a66962 MF |
1 | #!/usr/bin/env python3 |
2 | import os | |
3 | import sys | |
3fc75327 | 4 | from subprocess import check_call, CalledProcessError |
25a66962 MF |
5 | import shlex |
6 | import datetime | |
dcc08cd9 | 7 | import configparser |
25a66962 | 8 | |
dcc08cd9 MF |
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)) | |
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 ") | |
dcc08cd9 | 32 | excludes = " --exclude " + " --exclude ".join(self.config.get("host", "exclude").split(',')) #FIXME |
f0a474cc | 33 | try: |
dcc08cd9 MF |
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)) | |
3fc75327 | 36 | except CalledProcessError as ex: |
41690a07 MF |
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)) | |
f0a474cc MF |
41 | return() |
42 | todays_date = datetime.datetime.now().date().strftime("%F") | |
dcc08cd9 | 43 | if os.path.exists(os.path.join(self.host_dir, todays_date)): |
3fc75327 MF |
44 | #There is a snapshot for today, removing it and creating a new one |
45 | try: | |
dcc08cd9 | 46 | check_call(shlex.split("btrfs subvol delete %s"%(os.path.join(self.host_dir, todays_date)))) |
3fc75327 MF |
47 | except CalledProcessError as ex: |
48 | pass | |
f0a474cc | 49 | try: |
dcc08cd9 | 50 | check_call(shlex.split("btrfs subvol snapshot -r %s %s"%(self.subvol_dir,os.path.join(self.host_dir, todays_date)))) |
3fc75327 | 51 | except CalledProcessError as ex: |
f0a474cc | 52 | pass |
25a66962 | 53 | |
dcc08cd9 MF |
54 | def prune_snapshots(self): |
55 | if self.keep == -1: | |
56 | print("No keep specified for %s, keeping all"%self.name) | |
57 | return | |
5e361be5 MF |
58 | if not os.path.exists(self.host_dir): |
59 | print("New host, no pruning needed") | |
60 | return | |
dcc08cd9 MF |
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(): | |
5e361be5 | 70 | def __init__(self, config_dir): |
dcc08cd9 | 71 | self.config_dir = config_dir |
dcc08cd9 MF |
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): | |
77 | self.hosts = os.listdir(self.config_dir) | |
78 | ||
79 | for host in self.hosts: | |
80 | if host == 'default.cfg': | |
81 | continue | |
82 | try: | |
83 | config = configparser.ConfigParser(strict=False) | |
84 | config.read_file(open(os.path.join(self.config_dir, 'default.cfg'),'r')) | |
85 | config.read(os.path.join(self.config_dir, host)) | |
86 | except BaseException as ex: | |
87 | print("Config error for %s. Skipping host."%host) | |
88 | continue | |
89 | h = Host(host, config) | |
90 | h.prune_snapshots() | |
91 | h.backup() | |
92 | ||
25a66962 MF |
93 | if __name__ == "__main__": |
94 | if os.geteuid() != 0: | |
95 | print("You need to be root. Otherwise all permissions will be lost.") | |
96 | sys.exit(-1) | |
5e361be5 | 97 | br = BackupRunner("/etc/butterbackup") |
f0a474cc | 98 | br.run() |
25a66962 | 99 | sys.exit(0) |