Last active
December 5, 2024 16:29
-
-
Save lilydjwg/6c4f38d7eb8befb5099d6759941044e1 to your computer and use it in GitHub Desktop.
btrfs-autosnapshot
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
import os | |
import datetime | |
import subprocess | |
import logging | |
import tempfile | |
import contextlib | |
from pathlib import Path | |
DEV = '/dev/mapper/ssd' | |
SYSROOT = Path('root') | |
SNAPSHOT_ROOT = Path('snapshots') | |
CLONE_NEWNS = 0x20000 | |
logger = logging.getLogger(__name__) | |
def run_cmd(cmd): | |
logger.debug('running cmd: %r', cmd) | |
if cmd[0] == 'btrfs': | |
subprocess.check_call(cmd, stdout=subprocess.DEVNULL) | |
else: | |
subprocess.check_call(cmd) | |
def may_snapshot(path, name, path_name): | |
path = path.lstrip('/') | |
dst = SNAPSHOT_ROOT / path_name / name | |
if not dst.is_dir(): | |
dir = SNAPSHOT_ROOT / path_name | |
if not dir.exists(): | |
dir.mkdir() | |
src = SYSROOT / path | |
cmd = ['btrfs', 'subvolume', 'snapshot', '-r', | |
src, dst] | |
run_cmd(cmd) | |
def cleanup(path, path_name, nkeep, suffix): | |
snapshots = [x for x in (SNAPSHOT_ROOT / path_name).iterdir() | |
if x.name.endswith(suffix)] | |
snapshots.sort() | |
logger.info('known snapshots for path %r (%r): %r', | |
path, path_name, [str(x) for x in snapshots]) | |
locks = set() | |
lockfile = os.path.join(SNAPSHOT_ROOT, 'snapshot.lock') | |
try: | |
with open(lockfile) as f: | |
for l in f: | |
name, path, snap = l.split() | |
if path == path_name: | |
locks.add(snap) | |
except FileNotFoundError: | |
pass | |
to_remove = snapshots[:-nkeep] | |
for path in to_remove: | |
if path.name in locks: | |
continue | |
cmd = ['btrfs', 'subvolume', 'delete', path] | |
run_cmd(cmd) | |
@contextlib.contextmanager | |
def setup(): | |
if hasattr(os, 'unshare'): | |
os.unshare(os.CLONE_NEWNS) | |
else: | |
import ctypes | |
libc = ctypes.CDLL('libc.so.6', use_errno=True) | |
ret = libc.unshare(CLONE_NEWNS) | |
if ret != 0: | |
errno = ctypes.get_errno() | |
raise OSError(errno, 'unshare failed') | |
cmd = ['mount', '--make-rprivate', '/'] | |
run_cmd(cmd) | |
cwd = os.getcwd() | |
tmpdir = tempfile.mkdtemp(prefix='btrfs-snapshot-') | |
cmd = ['mount', '-o', 'compress=zstd,subvol=/', | |
DEV, tmpdir] | |
run_cmd(cmd) | |
os.chdir(tmpdir) | |
yield | |
os.chdir(cwd) | |
cmd = ['umount', tmpdir] | |
run_cmd(cmd) | |
os.rmdir(tmpdir) | |
def main(): | |
import argparse | |
parser = argparse.ArgumentParser( | |
description = 'automatically keep snapshots for btrfs', | |
) | |
parser.add_argument('-n', '--nkeep', type=int, | |
help='keep n snapshots') | |
parser.add_argument('-f', '--format', default='%Y-%m-%d-%H', | |
help='datetime format for snapshot names') | |
parser.add_argument('filesystem', nargs='+', | |
help='filesystems to do snapshots') | |
args = parser.parse_args() | |
suffix = '-auto' | |
dt = datetime.datetime.now() | |
name = '{dt}{suffix}'.format( | |
dt = dt.strftime(args.format), suffix = suffix) | |
with setup(): | |
for path in sorted(args.filesystem): | |
path_name = path.strip('/').replace('-', '--').replace('/', '-') | |
if not path_name: | |
path_name = '-' | |
may_snapshot(path, name, path_name) | |
if args.nkeep: | |
cleanup(path, path_name, args.nkeep, suffix) | |
if __name__ == '__main__': | |
import sys | |
sys.path.append('/home/lilydjwg/scripts/python/pylib') | |
from nicelogger import enable_pretty_logging | |
enable_pretty_logging('DEBUG') | |
# enable_pretty_logging('INFO') | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
yield
这样玩,学习了