summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregor Kleen <gkleen@yggdrasil.li>2022-02-08 16:14:48 +0100
committerGregor Kleen <gkleen@yggdrasil.li>2022-02-08 16:14:48 +0100
commit42ada4b2a4602379367f7a1966b22d2b59c2cb84 (patch)
tree5fffa9dea406f0964231fd9e4fa81f7a9dd05945
parent0fc4c3f32491656343b64b6f07a484946c0f6e6a (diff)
downloadnixos-42ada4b2a4602379367f7a1966b22d2b59c2cb84.tar
nixos-42ada4b2a4602379367f7a1966b22d2b59c2cb84.tar.gz
nixos-42ada4b2a4602379367f7a1966b22d2b59c2cb84.tar.bz2
nixos-42ada4b2a4602379367f7a1966b22d2b59c2cb84.tar.xz
nixos-42ada4b2a4602379367f7a1966b22d2b59c2cb84.zip
ymir: ...
-rw-r--r--snap.py104
1 files changed, 104 insertions, 0 deletions
diff --git a/snap.py b/snap.py
new file mode 100644
index 00000000..cc75739b
--- /dev/null
+++ b/snap.py
@@ -0,0 +1,104 @@
1#! /usr/bin/env nix-shell
2#! nix-shell -i python -p "python3.withPackages (p: with p; [dateutil python-unshare])"
3
4import os
5import argparse
6import sys
7from datetime import (datetime,timezone)
8from dateutil.tz import (tzlocal)
9import subprocess
10import json
11
12from tempfile import TemporaryDirectory
13
14import pathlib
15
16import unshare
17
18
19def borg_lv(lv, size_percent, target, archive_prefix, **args):
20 vgp = lv.split('/')
21 lvn = vgp[-1]
22 snn = f'{lvn}_snap_{datetime.utcnow().strftime("%Y%m%dT%H%MZ")}'
23 sn = "/".join([*vgp[:-1], snn])
24
25 try:
26 subprocess.run(['lvcreate', f'-l{size_percent}%ORIGIN', '-s', '-n', snn, lv], stdin=subprocess.DEVNULL, check=True)
27
28 creation_time = None
29 with subprocess.Popen(['lvs', '--reportformat=json', '-olv_time', sn], stdout=subprocess.PIPE) as proc:
30 res = json.load(proc.stdout)
31 creation_time = datetime.strptime(res['report'][0]['lv'][0]['lv_time'], '%Y-%m-%d %H:%M:%S %z').astimezone(timezone.utc)
32
33 with TemporaryDirectory(prefix=f'{snn}_') as tmpdir:
34 child = os.fork()
35 if child == 0:
36 unshare.unshare(unshare.CLONE_NEWNS)
37 subprocess.run(['mount', '--make-rprivate', '/'], check=True)
38 chroot = pathlib.Path(tmpdir) / 'chroot'
39 upper = pathlib.Path(tmpdir) / 'upper'
40 work = pathlib.Path(tmpdir) / 'work'
41 for path in [chroot,upper,work]:
42 path.mkdir()
43 subprocess.run(['mount', '-t', 'overlay', 'overlay', '-o', f'lowerdir=/,upperdir={upper},workdir={work}', chroot], check=True)
44 bindMounts = ['nix', 'run', 'proc', 'dev', 'sys', pathlib.Path(os.path.expanduser('~')).relative_to('/')]
45 if 'SSH_AUTH_SOCK' in os.environ:
46 bindMounts.append(pathlib.Path(os.environ['SSH_AUTH_SOCK']).parent.relative_to('/'))
47 for bindMount in bindMounts:
48 (chroot / bindMount).mkdir(parents=True,exist_ok=True)
49 subprocess.run(['mount', '--bind', pathlib.Path('/') / bindMount, chroot / bindMount], check=True)
50 os.chroot(chroot)
51 os.chdir('/')
52 dir = pathlib.Path('/borg')
53 dir.mkdir(parents=True,exist_ok=True)
54 subprocess.run(['mount', f'/dev/{sn}', dir], check=True)
55
56 archive_lvn=lvn
57 if "/".join(vgp[:-1]) != archive_prefix.split('.')[-2]:
58 archive_lvn=f'{"_".join(vgp[:-1])}_{archive_lvn}'
59 archive=f'{target}::{archive_prefix}{archive_lvn}-{{utcnow}}'
60
61 env = os.environ.copy()
62 create_args = ['borg',
63 'create',
64 '--lock-wait=600',
65 '--one-file-system',
66 '--compression=auto,zstd,10',
67 '--chunker-params=10,23,16,4095',
68 f'--timestamp={creation_time.strftime("%Y-%m-%dT%H:%M:%S")}',
69 '--show-rc',
70 '--progress',
71 '--list',
72 '--filter=AMEi-x?',
73 '--stats',
74 '--patterns-from=.backup-vidhar',
75 '--exclude-caches',
76 '--keep-exclude-tags'
77 ]
78 env['BORG_FILES_CACHE_SUFFIX'] = "_".join(vgp)
79 create_args += [archive]
80 subprocess.run(create_args, cwd=dir, check=True, env=env)
81
82 os._exit(0)
83 else:
84 while True:
85 waitpid, waitret = os.wait()
86 if waitret != 0:
87 sys.exit(waitret)
88 if waitpid == child:
89 break
90 finally:
91 subprocess.run(['lvremove', '--force', sn], stdin=subprocess.DEVNULL, check=True)
92
93def main():
94 parser = argparse.ArgumentParser()
95 parser.add_argument('lv', metavar='LV')
96 parser.add_argument('--size-percent', metavar='PERCENT', type = float, default=10)
97 parser.add_argument('--target', metavar='REPO', default='borg.vidhar:.')
98 parser.add_argument('--archive-prefix', metavar='REPO', default='yggdrasil.niflheim.ymir.')
99 args = parser.parse_args()
100
101 return borg_lv(**vars(args))
102
103if __name__ == "__main__":
104 sys.exit(main())