-
Notifications
You must be signed in to change notification settings - Fork 0
/
btrfs_backup.py
125 lines (93 loc) · 3.39 KB
/
btrfs_backup.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#!/usr/bin/python
import os
import sys
import argparse
from os import path
def errorReturn(aMsg):
print(aMsg)
sys.exit(1)
def makeAbsolute(aPath):
ret = ""
if os.path.isabs(aPath): ret = aPath
else: ret = path.abspath(path.join(os.getcwd(), aPath))
if ret.endswith("/"): ret = ret[0:-1]
return ret
def getOptions():
parser = argparse.ArgumentParser()
parser.add_argument('--src', required=True, help='source dir of the btrfs subvolume')
parser.add_argument('--dest', required=True, help='destination dir to backup to. Note that in this dir a dir with SRC name is created')
parser.add_argument('--backup2backup', required=False, help='src by itself is a backup', action='store_true')
args = parser.parse_args()
return {
"src": makeAbsolute(args.src),
"dest": makeAbsolute(args.dest),
"backup2backup": args.backup2backup,
}
options = getOptions()
src = options["src"]
src_name = path.basename(src)
dest_base = options["dest"]
dest = path.join(dest_base, src_name)
src_snaps = None
if options["backup2backup"]:
src_snaps = src
else:
src_snaps = path.join(src, "../.snapshot_"+src_name)
if not path.isdir(dest):
print("Destination dir {} does not exist".format(dest))
print("should it created: y/n")
choose = input()
if choose == "y": os.mkdir(dest)
else: errorReturn("Dir {} required for backup".format(dest))
for iDir in [src, dest, src_snaps]:
if not os.path.isdir(iDir):
errorReturn("[ERROR] dir {} does not exists".format(iDir))
snapshots=[]
for idx, iSnap in enumerate(os.listdir(src_snaps)):
is_first_snap = idx==0
entry = {}
snap_src = path.join(src_snaps, iSnap)
snap_dest = path.join(dest, iSnap)
action="none"
is_dest = path.isdir(snap_dest)
if is_first_snap and not is_dest: action="full"
elif not is_dest: action="incremental"
entry["name"] = iSnap
entry["src"] = snap_src
entry["dest"] = snap_dest
entry["is_dest"] = is_dest
entry["action"] = action
if not is_first_snap:
entry["parent"] = snapshots[idx-1]["name"]
entry["parent_path"] = snapshots[idx-1]["src"]
else:
entry["parent"] = ""
entry["parent_path"] = ""
snapshots.append(entry)
print("SRC = {}".format(src))
print("DEST = {}".format(dest))
print("BASENAME = {}".format(src_name))
print("SRC_SNAP = {}".format(src_snaps))
print("SNAPSHOTS")
for iSnap in snapshots:
print(" {}: action={}; parent={} ".format(iSnap["name"], iSnap["action"], iSnap["parent"]))
#-------------------
#Execute
#-------------------
print("Are you sure y/n/dry")
choose=input()
is_run = choose == "y"
is_dryrun = choose == "dry"
if not is_run and not is_dryrun:
sys.exit(0)
for idx, iSnap in enumerate(snapshots):
action = iSnap["action"]
is_full = action == "full"
is_incr = action == "incremental"
command=""
if is_full: command="btrfs send -q {} | btrfs receive {}".format(iSnap["src"], dest)
elif is_incr: command="btrfs send -p {} {} | btrfs receive {}".format(iSnap["parent_path"], iSnap["src"], dest)
else: continue
print(command)
if is_dryrun: continue
elif is_run: os.system(command)