rsync backuping util
This commit is contained in:
1
bin/rsync-backup
Symbolic link
1
bin/rsync-backup
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../files/rsync-backup
|
||||||
147
files/rsync-backup
Executable file
147
files/rsync-backup
Executable file
@@ -0,0 +1,147 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shlex
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
|
VERSION = "1.0"
|
||||||
|
|
||||||
|
|
||||||
|
class RSB:
|
||||||
|
def __init__(self, options):
|
||||||
|
self.base_folder = options.base_folder
|
||||||
|
self.backup_source = options.backup_source
|
||||||
|
self.rsync_args = options.rsync_args
|
||||||
|
self.delete_older = options.del_older
|
||||||
|
self.keep_n = options.keep_n
|
||||||
|
self.delete_disk_percentage = options.del_used
|
||||||
|
|
||||||
|
if not self.backup_source.endswith("/"):
|
||||||
|
self.backup_source += "/"
|
||||||
|
self.current = os.path.join(self.base_folder, "current")
|
||||||
|
self.folder_format = "backup-%Y%m%d-%H%M%S"
|
||||||
|
os.makedirs(self.current, exist_ok=True)
|
||||||
|
self.clean_old()
|
||||||
|
self.make_backup()
|
||||||
|
self.make_hardlinks()
|
||||||
|
|
||||||
|
def diskused(self):
|
||||||
|
"""in percents"""
|
||||||
|
pcent = subprocess.check_output(
|
||||||
|
["df", "--output=pcent", self.base_folder]
|
||||||
|
).decode("utf8")
|
||||||
|
try:
|
||||||
|
pcent = int("".join([x for x in pcent if x.isdigit()]))
|
||||||
|
except Exception as e:
|
||||||
|
return 0
|
||||||
|
return pcent
|
||||||
|
|
||||||
|
def clean_old(self):
|
||||||
|
print("Cleaning old backups")
|
||||||
|
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
dirs = [
|
||||||
|
d
|
||||||
|
for d in sorted(os.listdir(self.base_folder))
|
||||||
|
if d.startswith("backup-")
|
||||||
|
and os.path.isdir(os.path.join(self.base_folder, d))
|
||||||
|
]
|
||||||
|
for i, d in enumerate(dirs):
|
||||||
|
if self.delete_older:
|
||||||
|
dir_date = datetime.datetime.strptime(d, self.folder_format)
|
||||||
|
dir_age = now - dir_date
|
||||||
|
is_old = dir_age.days > self.delete_older
|
||||||
|
else:
|
||||||
|
is_old = False
|
||||||
|
if self.delete_disk_percentage:
|
||||||
|
is_disk_full = self.diskused() > self.delete_disk_percentage
|
||||||
|
else:
|
||||||
|
is_disk_full = False
|
||||||
|
if self.keep_n:
|
||||||
|
not_kept = len(dirs) - i + 1 > self.keep_n
|
||||||
|
else:
|
||||||
|
not_kept = False
|
||||||
|
|
||||||
|
print(
|
||||||
|
"{}: {} -- old: {}, disk full: {}, keep n: {}".format(
|
||||||
|
i, d, is_old, is_disk_full, not_kept
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if any((is_old, is_disk_full, not_kept)):
|
||||||
|
print("Deleting {}".format(d))
|
||||||
|
cmd = ["rm", "-r", os.path.join(self.base_folder, d)]
|
||||||
|
subprocess.call(cmd, shell=False)
|
||||||
|
|
||||||
|
def make_hardlinks(self):
|
||||||
|
now = datetime.datetime.now().strftime(self.folder_format)
|
||||||
|
print("Creating new snapshot: {}".format(now))
|
||||||
|
tgt_dir = os.path.join(self.base_folder, now)
|
||||||
|
if os.path.exists(tgt_dir):
|
||||||
|
raise FileExistsError("Folder {} already exists".format(tgt_dir))
|
||||||
|
|
||||||
|
subprocess.call(["cp", "-la", self.current, tgt_dir], shell=False)
|
||||||
|
|
||||||
|
def make_backup(self):
|
||||||
|
print("Backing up")
|
||||||
|
command = [
|
||||||
|
"rsync",
|
||||||
|
*shlex.split(self.rsync_args),
|
||||||
|
self.backup_source,
|
||||||
|
self.current,
|
||||||
|
]
|
||||||
|
print(command)
|
||||||
|
subprocess.call(command, shell=False)
|
||||||
|
|
||||||
|
|
||||||
|
def get_opts():
|
||||||
|
|
||||||
|
parser = ArgumentParser(
|
||||||
|
description="Minimal incrementive backup utility using rsync and hard links.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--del-older",
|
||||||
|
action="store",
|
||||||
|
type=int,
|
||||||
|
dest="del_older",
|
||||||
|
default=None,
|
||||||
|
help="Delete old backups older than N days",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--del-disk-used",
|
||||||
|
action="store",
|
||||||
|
type=float,
|
||||||
|
dest="del_used",
|
||||||
|
default=99,
|
||||||
|
help="Delete old backups if disk is fuller than P percentage",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--keep-number",
|
||||||
|
action="store",
|
||||||
|
type=int,
|
||||||
|
dest="keep_n",
|
||||||
|
default=None,
|
||||||
|
help="Keep at most N old backups",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--rsync-args",
|
||||||
|
type=str,
|
||||||
|
dest="rsync_args",
|
||||||
|
default="-aP --del",
|
||||||
|
help="Rsync arguments. Add excludes here (example '-vxP --exclude folder/'). Defaults: '%(default)s'",
|
||||||
|
)
|
||||||
|
parser.add_argument("--version", action="version", version=VERSION)
|
||||||
|
parser.add_argument(
|
||||||
|
"backup_source", action="store", help="Source URL to backup with rsync"
|
||||||
|
)
|
||||||
|
parser.add_argument("base_folder", action="store", help="Local backup folder")
|
||||||
|
options = parser.parse_args()
|
||||||
|
return options
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
opts = get_opts()
|
||||||
|
RSB(opts)
|
||||||
Reference in New Issue
Block a user