gpg encryption support

This commit is contained in:
Q
2023-10-22 10:34:45 +03:00
parent 71ce8d182f
commit 08a2e6b28b

View File

@@ -1,12 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import datetime import datetime
import subprocess
import os
import sys
import shlex
import json
import hashlib import hashlib
import json
import os
import shlex
import subprocess
import sys
from argparse import ArgumentParser from argparse import ArgumentParser
VERSION = "0.0.9" VERSION = "0.0.9"
@@ -23,13 +23,13 @@ class TB:
self.ssh_args = options.ssh_args self.ssh_args = options.ssh_args
self.delete_older = options.del_older self.delete_older = options.del_older
self.keep_n = options.keep_n self.keep_n = options.keep_n
self.do_gpg = options.gpg
self.delete_disk_percentage = options.del_used self.delete_disk_percentage = options.del_used
self.config_file = os.path.join(self.base_folder, "tar-backup.txt") self.config_file = os.path.join(self.base_folder, "tar-backup.txt")
self.lock_file = os.path.join(self.base_folder, "tar-backup.lock") self.lock_file = os.path.join(self.base_folder, "tar-backup.lock")
if not self.backup_source.endswith("/"): if not self.backup_source.endswith("/"):
self.backup_source += "/" self.backup_source += "/"
self.current = os.path.join(self.base_folder, "current")
self.folder_format = "backup-%Y%m%d-%H%M%S" self.folder_format = "backup-%Y%m%d-%H%M%S"
self.backup_folder = datetime.datetime.now().strftime(self.folder_format) self.backup_folder = datetime.datetime.now().strftime(self.folder_format)
os.makedirs(self.base_folder, exist_ok=True) os.makedirs(self.base_folder, exist_ok=True)
@@ -38,8 +38,8 @@ class TB:
self.write_config() self.write_config()
self.clean_old() self.clean_old()
success = self.make_backup() success = self.make_backup()
if success:
self.make_softlinks() self.make_softlinks(success)
self.lock_clean() self.lock_clean()
if not success: if not success:
@@ -48,9 +48,7 @@ class TB:
def diskused(self): def diskused(self):
"""in percents""" """in percents"""
pcent = subprocess.check_output( pcent = subprocess.check_output(["df", "--output=pcent", self.base_folder]).decode("utf8")
["df", "--output=pcent", self.base_folder]
).decode("utf8")
try: try:
pcent = int("".join([x for x in pcent if x.isdigit()])) pcent = int("".join([x for x in pcent if x.isdigit()]))
except Exception as e: except Exception as e:
@@ -70,8 +68,7 @@ class TB:
dirs = [ dirs = [
d d
for d in sorted(os.listdir(self.base_folder)) for d in sorted(os.listdir(self.base_folder))
if d.startswith("backup-") if d.startswith("backup-") and os.path.isdir(os.path.join(self.base_folder, d))
and os.path.isdir(os.path.join(self.base_folder, d))
] ]
for i, d in enumerate(dirs): for i, d in enumerate(dirs):
if self.delete_older: if self.delete_older:
@@ -89,23 +86,21 @@ class TB:
else: else:
not_kept = False not_kept = False
print( print("{}: {} -- old: {}, disk full: {}, keep n: {}".format(i, d, is_old, is_disk_full, not_kept))
"{}: {} -- old: {}, disk full: {}, keep n: {}".format(
i, d, is_old, is_disk_full, not_kept
)
)
if any((is_old, is_disk_full, not_kept)): if any((is_old, is_disk_full, not_kept)):
print("Deleting {}".format(d)) print("Deleting {}".format(d))
cmd = ["rm", "-rf", os.path.join(self.base_folder, d)] cmd = ["rm", "-rf", os.path.join(self.base_folder, d)]
subprocess.call(cmd, shell=False) subprocess.call(cmd, shell=False)
def make_softlinks(self): def make_softlinks(self, success):
target = "success" if success else "failed"
for folder in ("latest", target):
try: try:
os.remove(self.current) os.remove(os.path.join(self.base_folder, folder))
except Exception: except Exception:
pass pass
if os.path.exists(os.path.join(self.base_folder, self.backup_folder)): if os.path.exists(os.path.join(self.base_folder, self.backup_folder)):
os.symlink(self.backup_folder, self.current) os.symlink(self.backup_folder, os.path.join(self.base_folder, folder))
def make_backup(self): def make_backup(self):
if self.options.no_backup: if self.options.no_backup:
@@ -123,13 +118,9 @@ class TB:
else [] else []
) )
with open(os.path.join(self.base_folder, self.backup_folder, self.tar_file), "wb") as fp:
with open( with open(
os.path.join(self.base_folder, self.backup_folder, self.tar_file), "wb" os.path.join(self.base_folder, self.backup_folder, self.tar_file + ".log"),
) as fp:
with open(
os.path.join(
self.base_folder, self.backup_folder, self.tar_file + ".log"
),
"w", "w",
) as fp_log: ) as fp_log:
command = [ command = [
@@ -141,34 +132,43 @@ class TB:
print(command) print(command)
md5 = hashlib.md5() md5 = hashlib.md5()
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=fp_log) p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=fp_log)
if self.do_gpg:
pwpipe_r, pwpipe_w = os.pipe()
os.write(pwpipe_w, sys.stdin.buffer.read())
os.close(pwpipe_w)
gpg_command = "gpg -o - --passphrase-fd {} --batch --yes --symmetric".format(pwpipe_r)
gnp = subprocess.Popen(
shlex.split(gpg_command), stdin=p.stdout, stdout=subprocess.PIPE, pass_fds=[pwpipe_r]
)
reader = gnp
else:
reader = p
i = 1 i = 1
while True: while True:
chunk = p.stdout.read(1048576) chunk = reader.stdout.read(1048576)
if len(chunk) == 0: if len(chunk) == 0:
break break
print(f"{i} Mb", end="\r") print(f"{i} Mb", end="\r")
i += 1 i += 1
md5.update(chunk) md5.update(chunk)
fp.write(chunk) fp.write(chunk)
if self.do_gpg:
os.close(pwpipe_r)
exitcode = gnp.wait() + p.wait()
else:
exitcode = p.wait() exitcode = p.wait()
success = exitcode in (0, 2) success = exitcode in (0,)
print( print(f"\nWrote {os.path.join(self.base_folder, self.backup_folder,self.tar_file)}")
f"\nWrote {os.path.join(self.base_folder, self.backup_folder,self.tar_file)}" print(f"Log file {os.path.join(self.base_folder, self.backup_folder,self.tar_file+'.log')}")
)
print(
f"Log file {os.path.join(self.base_folder, self.backup_folder,self.tar_file+'.log')}"
)
with open( with open(
os.path.join(self.base_folder, self.backup_folder, self.tar_file + ".md5"), os.path.join(self.base_folder, self.backup_folder, self.tar_file + ".md5"),
"w", "w",
) as fp_md5: ) as fp_md5:
fp_md5.write(f"{md5.hexdigest()} {self.tar_file}\n") fp_md5.write(f"{md5.hexdigest()} {self.tar_file}\n")
if not success: if not success:
with open( with open(os.path.join(self.base_folder, self.backup_folder, self.tar_file + ".log")) as f:
os.path.join(
self.base_folder, self.backup_folder, self.tar_file + ".log"
)
) as f:
print(f.read(), end="") print(f.read(), end="")
print(f"Exit code: {exitcode}") print(f"Exit code: {exitcode}")
return success return success
@@ -280,12 +280,17 @@ def get_opts():
default=False, default=False,
help="Do not actually backup, only clean up.", help="Do not actually backup, only clean up.",
) )
parser.add_argument(
"--gpg",
dest="gpg",
action="store_true",
default=False,
help="Encrypt with GPG. Read passphrase from stdin. change --tar-file to match, e.g. .gpg suffix. You might want to remove -vv from tar-args.",
)
parser.add_argument("--version", action="version", version=VERSION) parser.add_argument("--version", action="version", version=VERSION)
parser.add_argument( parser.add_argument("backup_source", action="store", help="Source folder to backup with tar+ssh")
"backup_source", action="store", help="Source folder to backup with tar+ssh" parser.add_argument("base_folder", action="store", help="Local backup folder written to")
)
parser.add_argument("base_folder", action="store", help="Local backup folder")
options = parser.parse_args() options = parser.parse_args()
if "/" in options.tar_file: if "/" in options.tar_file:
parser.error("--tar-file must be a filename") parser.error("--tar-file must be a filename")