-
Notifications
You must be signed in to change notification settings - Fork 0
/
Backup.py
executable file
·160 lines (135 loc) · 5.99 KB
/
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#!/usr/bin/python3
import os
import json
import subprocess
from argparse import ArgumentParser
class TapeManifest:
@staticmethod
def dump_manifest(manifest_dict):
return json.dumps(manifest_dict)
@staticmethod
def load_manifest(manifest_string):
return json.loads(manifest_string)
class Backup:
def __init__(self, label=None, tape_device=None):
self.label = label
self.tape_device = tape_device
self.sources = None
self.backup_size = 0
self.backup_manifest = []
self.tape_status = None
self.manifest = []
self.load_tape_manifest()
def initialize_tape(self):
if not os.path.isdir('/var/LTO-Backup'):
os.mkdir('/var/LTO-Backup')
manifest = []
self.write_manifest(manifest)
def write_manifest(self, manifest):
tape_manifest = TapeManifest()
payload = tape_manifest.dump_manifest(manifest)
with open(f'/var/LTO-Backup/{self.label}', 'w') as f:
f.write(payload)
def load_tape_manifest(self):
if not os.path.isfile(f'/var/LTO-Backup/{self.label}'):
self.initialize_tape()
with open(f'/var/LTO-Backup/{self.label}', 'r') as f:
data = f.read()
self.manifest = TapeManifest.load_manifest(data)
def status(self):
cmd = f'mt-gnu -f {self.tape_device} status'
subprocess.call(cmd, shell=True)
def rewind(self):
cmd = f'mt-gnu -f {self.tape_device} rewind'
subprocess.call(cmd, shell=True)
def set_tape_to_file_index(self, file_index):
cmd = f'mt-gnu -f {self.tape_device} asf {file_index}'
subprocess.call(cmd, shell=True)
self.status()
def set_tape_to_logical_end(self):
cmd = f'mt-gnu -f {self.tape_device} eom'
subprocess.call(cmd, shell=True)
self.status()
def eject(self):
cmd = f'mt-gnu -f {self.tape_device} eject'
subprocess.call(cmd, shell=True)
def backward_skip_file_marker(self, num):
cmd = f'mt-gnu -f {self.tape_device} bsfm {num}'
subprocess.call(cmd, shell=True)
def backup(self, sources):
self.load_tape_manifest()
print(json.dumps(self.manifest, indent=2))
self.set_tape_to_logical_end()
paths = ' '.join(x for x in sources)
this_manifest = {
'index': len(self.manifest) + 1,
'contents': sources
}
self.manifest.append(this_manifest)
cmd = f'size=`du -sc {paths} | tail -1 | '
cmd += 'awk {\'print $1\'}`'
cmd += f'; tar cf - {paths} | pv -w 100 | mbuffer -m 4G -P 100% | dd of={self.tape_device} bs=128k'
subprocess.call(cmd, shell=True)
self.write_manifest(self.manifest)
print(json.dumps(self.manifest, indent=2))
def restore(self, destination):
try:
cmd = 'mkdir -p {destination} && tar -b 256 -xvf {tape_device} -C {destination}/'.format(
tape_device=self.tape_device, destination=destination)
print(cmd)
subprocess.call(cmd, shell=True)
except Exception as error:
pass
if __name__ == '__main__':
from argparse import RawTextHelpFormatter
parser = ArgumentParser(formatter_class=RawTextHelpFormatter,
description='Copyright: (c) GPLv3 (20 june 2022)\n'
'Author: Robert Nagtegaal\n\n'
'Description:\n'
'A simple python script to write TAR archives to tape. You need to give a label '
'(name) for the tape. This label is used as filename for keeping a manifest. '
'Manifests are kept in /var/LTO-Backup. This program relies on mt-gnu.')
parser.add_argument('-l', '--label', type=str, metavar='label', required=True,
help='Set tape label (name of manifest in /var/LTO-Backup')
parser.add_argument('-d', '--device', type=str, metavar='device-file', default='/dev/nst0',
help='Set tape device (default: /dev/nst0)')
commands = parser.add_mutually_exclusive_group()
commands.add_argument('-E', '--eject', action='store_true', help='Eject tape')
commands.add_argument('-r', '--rewind', action='store_true', help='Rewind tape')
commands.add_argument('-e', '--end_of_logical_tape', action='store_true',
help='Set tape position after last archive')
commands.add_argument('-R', '--restore', type=str, default=None, metavar='target-dir',
help='Restore tape archive at current index to destination')
commands.add_argument('-s', '--status', action='store_true', help='Show drive status')
commands.add_argument('-b', '--backup_directory', nargs="*", metavar='dir',
help='Write contents of given directories to tape after the last archive')
commands.add_argument("-i", "--set_tape_to_index", type=int, metavar='<int>', default=None,
help="The tape is positioned at the beginning of the file at index <int>")
args = parser.parse_args()
label = args.label
backup = Backup(label=label, tape_device=args.device)
sources = args.backup_directory
restore = args.restore
eject = args.eject
rewind = args.rewind
end_of_logical_tape = args.end_of_logical_tape
status = args.status
tape_index = args.set_tape_to_index
if sources is not None:
backup.backup(sources)
elif restore is not None:
backup.restore(restore)
elif eject is True:
backup.eject()
elif rewind is True:
backup.rewind()
elif end_of_logical_tape is True:
backup.set_tape_to_logical_end()
elif status is True:
backup.status()
elif tape_index is not None:
if tape_index < 0:
raise Exception('Index cannot be negative')
backup.set_tape_to_file_index(tape_index)
else:
parser.print_help()