From 42ada4b2a4602379367f7a1966b22d2b59c2cb84 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Tue, 8 Feb 2022 16:14:48 +0100 Subject: ymir: ... --- snap.py | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 snap.py diff --git a/snap.py b/snap.py new file mode 100644 index 00000000..cc75739b --- /dev/null +++ b/snap.py @@ -0,0 +1,104 @@ +#! /usr/bin/env nix-shell +#! nix-shell -i python -p "python3.withPackages (p: with p; [dateutil python-unshare])" + +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 + + +def borg_lv(lv, size_percent, target, archive_prefix, **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'-l{size_percent}%ORIGIN', '-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('/')) + 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}-{{utcnow}}' + + 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-x?', + '--stats', + '--patterns-from=.backup-vidhar', + '--exclude-caches', + '--keep-exclude-tags' + ] + env['BORG_FILES_CACHE_SUFFIX'] = "_".join(vgp) + create_args += [archive] + subprocess.run(create_args, cwd=dir, check=True, env=env) + + os._exit(0) + else: + while True: + waitpid, waitret = os.wait() + if waitret != 0: + sys.exit(waitret) + if waitpid == child: + break + finally: + subprocess.run(['lvremove', '--force', sn], stdin=subprocess.DEVNULL, check=True) + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('lv', metavar='LV') + parser.add_argument('--size-percent', metavar='PERCENT', type = float, default=10) + parser.add_argument('--target', metavar='REPO', default='borg.vidhar:.') + parser.add_argument('--archive-prefix', metavar='REPO', default='yggdrasil.niflheim.ymir.') + args = parser.parse_args() + + return borg_lv(**vars(args)) + +if __name__ == "__main__": + sys.exit(main()) -- cgit v1.2.3