gpg encryption support
This commit is contained in:
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user