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
|
#!/usr/bin/env python
import os
import argparse
import sys
from datetime import (datetime,timezone)
from dateutil.tz import (tzlocal)
import subprocess
import json
from tempfile import TemporaryDirectory
import pathlib
import unshare
import signal
children = set()
def waitchildren():
while children:
waitpid, waitret = os.wait()
children.remove(waitpid)
def borg_lv(lv, target, archive_prefix, dry_run, **args):
vgp = lv.split('/')
lvn = vgp[-1]
snn = f'{lvn}_snap_{datetime.utcnow().strftime("%Y%m%dT%H%MZ")}'
sn = "/".join([*vgp[:-1], snn])
try:
subprocess.run(['lvcreate', f'-l100%FREE', '-s', '-n', snn, lv], stdin=subprocess.DEVNULL, check=True)
creation_time = None
with subprocess.Popen(['lvs', '--reportformat=json', '-olv_time', sn], stdout=subprocess.PIPE) as proc:
res = json.load(proc.stdout)
creation_time = datetime.strptime(res['report'][0]['lv'][0]['lv_time'], '%Y-%m-%d %H:%M:%S %z').astimezone(timezone.utc)
with TemporaryDirectory(prefix=f'{snn}_') as tmpdir:
child = os.fork()
if child == 0:
unshare.unshare(unshare.CLONE_NEWNS)
subprocess.run(['mount', '--make-rprivate', '/'], check=True)
chroot = pathlib.Path(tmpdir) / 'chroot'
upper = pathlib.Path(tmpdir) / 'upper'
work = pathlib.Path(tmpdir) / 'work'
for path in [chroot,upper,work]:
path.mkdir()
subprocess.run(['mount', '-t', 'overlay', 'overlay', '-o', f'lowerdir=/,upperdir={upper},workdir={work}', chroot], check=True)
bindMounts = ['nix', 'run', 'proc', 'dev', 'sys', pathlib.Path(os.path.expanduser('~')).relative_to('/')]
if 'SSH_AUTH_SOCK' in os.environ:
bindMounts.append(pathlib.Path(os.environ['SSH_AUTH_SOCK']).parent.relative_to('/'))
if 'BORG_CACHE_DIR' in os.environ:
bindMounts.append(pathlib.Path(os.environ['BORG_CACHE_DIR']).parent.relative_to('/'))
for bindMount in bindMounts:
(chroot / bindMount).mkdir(parents=True,exist_ok=True)
subprocess.run(['mount', '--bind', pathlib.Path('/') / bindMount, chroot / bindMount], check=True)
os.chroot(chroot)
os.chdir('/')
dir = pathlib.Path('/borg')
dir.mkdir(parents=True,exist_ok=True)
subprocess.run(['mount', f'/dev/{sn}', dir], check=True)
archive_lvn=lvn
if "/".join(vgp[:-1]) != archive_prefix.split('.')[-2]:
archive_lvn=f'{"_".join(vgp[:-1])}_{archive_lvn}'
archive=f'{target}::{archive_prefix}{archive_lvn}-{creation_time.strftime("%Y-%m-%dT%H:%M:%S")}'
env = os.environ.copy()
create_args = ['borg',
'create',
'--lock-wait=600',
'--one-file-system',
'--compression=auto,zstd,10',
'--chunker-params=10,23,16,4095',
f'--timestamp={creation_time.strftime("%Y-%m-%dT%H:%M:%S")}',
'--show-rc',
# '--progress',
'--list',
'--filter=AMEi-?',
'--stats',
'--patterns-from=.backup-vidhar',
'--exclude-caches',
'--keep-exclude-tags'
]
env['BORG_FILES_CACHE_SUFFIX'] = "_".join(vgp)
if dry_run:
create_args += ['--dry-run']
create_args += [archive]
subprocess.run(create_args, cwd=dir, check=True, env=env)
os._exit(0)
else:
children.add(child)
waitchildren()
finally:
waitchildren()
subprocess.run(['lvremove', '--force', sn], stdin=subprocess.DEVNULL, check=True)
def sigterm(signum, frame):
raise SystemExit(128 + signum)
def main():
signal.signal(signal.SIGTERM, sigterm)
parser = argparse.ArgumentParser()
parser.add_argument('lv', metavar='LV')
parser.add_argument('--target', metavar='REPO', default='borg.vidhar:.')
parser.add_argument('--archive-prefix', metavar='REPO', default='yggdrasil.niflheim.ymir.')
parser.add_argument('--dry-run', action='store_true')
args = parser.parse_args()
return borg_lv(**vars(args))
if __name__ == "__main__":
sys.exit(main())
|