rsync wrapper for pushing incremental backups
Révision | 7c0782056095c3f700d78b8b3cd6c3e8d5b8d728 (tree) |
---|---|
l'heure | 2019-11-06 14:01:48 |
Auteur | Frank Tobin <ftobin@neve...> |
Commiter | Frank Tobin |
initial commit
@@ -0,0 +1,78 @@ | ||
1 | +#!/usr/bin/env python3 | |
2 | + | |
3 | +""" | |
4 | +rsnappush: Perform file-based incremental rsync push backup with hard-links | |
5 | + | |
6 | +Author: Frank Tobin | |
7 | +Email: ftobin@neverending.org | |
8 | +License: Eclipse Public License 2.0 (https://opensource.org/licenses/EPL-2.0) | |
9 | +""" | |
10 | + | |
11 | +import os | |
12 | +import re | |
13 | +import sys | |
14 | +import time | |
15 | +import argparse | |
16 | +import subprocess | |
17 | + | |
18 | +parser = argparse.ArgumentParser(description="Perform file-based incremental rsync push backup with hard-links") | |
19 | + | |
20 | +parser.add_argument("source", metavar="SOURCE_PATH", help="directory to backup") | |
21 | +parser.add_argument("dest", metavar="[ACCOUNT:]DEST_PATH", help="Where to store the backups. ACCOUNT can be anything ssh will accept. Example: user@host:backups/") | |
22 | + | |
23 | +parser.add_argument("-r", "--rsync-opt", action="append", default=[], | |
24 | + help="pass-thru options to rsync. Use '=' syntax, and include prefixing dashes. Example: --rsync-opt=--partial-dir=/home/user/rsync-partial") | |
25 | +parser.add_argument("--quiet", "-q", action="store_true", default=False, | |
26 | + help="emit less output") | |
27 | +parser.add_argument("--backup-prefix", default="backup-", | |
28 | + help="Prefix used in directory snapshots; default: 'backup-'") | |
29 | +parser.add_argument("--compare-n-backups", type=int, default=20, | |
30 | + help="How many previous backups to consider hard-linking against; default: 20 (rsync max)") | |
31 | + | |
32 | +args = parser.parse_args() | |
33 | + | |
34 | +match = re.search("(.+)?:(.*)", args.dest) | |
35 | + | |
36 | +if match: | |
37 | + dest_account = match.group(1) | |
38 | + dest_path = match.group(2) | |
39 | +else: | |
40 | + dest_account = None | |
41 | + dest_path = args.dest | |
42 | + | |
43 | +mkdir_cmd = ["mkdir", "-p", dest_path] | |
44 | +if dest_account: | |
45 | + mkdir_cmd = ['ssh', dest_account] + mkdir_cmd | |
46 | +subprocess.run(mkdir_cmd, check=True) | |
47 | + | |
48 | +# check what backups already exist | |
49 | +check_backups_cmd = ["ls", f"{dest_path}/"] | |
50 | +if dest_account: | |
51 | + check_backups_cmd = ['ssh', dest_account] + check_backups_cmd | |
52 | +proc = subprocess.run(check_backups_cmd, | |
53 | + stdout=subprocess.PIPE, encoding="utf-8", check=True) | |
54 | + | |
55 | +link_dests = proc.stdout.splitlines() | |
56 | +link_dests = list(filter(lambda x: re.search(args.backup_prefix, x), link_dests)) | |
57 | +link_dests = link_dests[-args.compare_n_backups:] | |
58 | +link_dest_args = list(map(lambda x: f"--link-dest=../{x}", link_dests)) | |
59 | + | |
60 | +my_dest = time.strftime(f'{args.backup_prefix}%Y%m%d-%H:%M') | |
61 | + | |
62 | +cmd = ["rsync", "--human-readable", "-a", "--hard-links", | |
63 | + "--delete", "--compress-level", "9", | |
64 | + "-e", "ssh" | |
65 | +] | |
66 | + | |
67 | +if not args.quiet: | |
68 | + cmd.extend(["-v", "--progress"]) | |
69 | + | |
70 | +cmd.extend(args.rsync_opt) | |
71 | +cmd.extend(link_dest_args) | |
72 | + | |
73 | +cmd.extend([os.path.normpath(f"{args.source}") + "/", # just look inside the directory | |
74 | + os.path.join(args.dest, my_dest)]) | |
75 | + | |
76 | +if not args.quiet: | |
77 | + print(*cmd) | |
78 | +os.execvp(cmd[0], cmd) |
@@ -0,0 +1,12 @@ | ||
1 | +#!/usr/bin/python3 | |
2 | + | |
3 | +import distutils.core | |
4 | + | |
5 | +distutils.core.setup(name='rsnappush', | |
6 | + version='1.0', | |
7 | + license = "EPL 2.0", | |
8 | + author = "Frank Tobin", | |
9 | + author_email = "ftobin@neverending.org", | |
10 | + platforms = "POSIX", | |
11 | + scripts=["rsnappush"] | |
12 | +) |