summaryrefslogtreecommitdiff
path: root/snap.py
blob: cc75739b9b328c7fd2c5375b4417099b97dbcc26 (plain)
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
#! /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())