Add backup.

master
Marko Semet 2020-04-16 22:21:29 +02:00
parent ae04fab683
commit 21bd8791cf
6 changed files with 163 additions and 28 deletions

View File

@ -12,6 +12,13 @@ def main(args):
parser = argparse.ArgumentParser("home-backup", description="Manage backup tools")
sub_parser = parser.add_subparsers(dest="action")
parser_backup_add = sub_parser.add_parser("backup-add", help="Add backup.")
parser_backup_add.add_argument("--type", nargs="?", type=str, default="borgbackup", help="The backup type.\nDefault: borgbackup")
parser_backup_add.add_argument("--remote", nargs="?", type=str, default=None, help="The remote of the backup (required by borgbackup).")
parser_backup_add.add_argument("name", nargs=1, type=str, help="The name of the backup.")
parser_backup_list = sub_parser.add_parser("backup-list", help="List all backups.")
parser_backup_delete = sub_parser.add_parser("backup-delete", help="Delete a backup.")
parser_remote_add = sub_parser.add_parser("remote-add", help="Add remote for backups.")
parser_remote_add.add_argument("--type", nargs="?", type=str, default="borgbackup", help="The remote type.\nDefault: borgbackup")
parser_remote_add.add_argument("--target", nargs="?", type=str, default=None, help="The target of the remote (required by borgbackup).")
@ -43,8 +50,27 @@ def main(args):
elif result.action == "user-server":
asyncio.run(user_service.rpc.run_deamon(fork=result.fork)) # TODO: Change default path of user and system socket
# Client actions
if result.action == "remote-add":
# Backup
if result.action == "backup-add":
# Check backup type
backup_type = result.type
if backup_type not in ("borgbackup",):
raise ValueError("Unknown backup type %s." & repr(backup_type))
# Parse type
info = {}
if backup_type == "borgbackup":
if result.remote is None:
raise ValueError("Remote is required for borg backup.")
info["remote"] = result.remote
else:
raise NotImplementedError("Type %s isn't supported well." % repr(backup_type))
# Add backup
client.run_command(client.backup_add_gen(name=result.name[0], btype=backup_type, info=info))
# Remote
elif result.action == "remote-add":
# Check remote type
remote_type = result.type
if remote_type not in ("borgbackup",):

View File

@ -19,6 +19,19 @@ def run_command(async_func, user_path:str=None):
await async_func(sock)
asyncio.run(runner())
#
# Backups
#
def backup_add_gen(name:str, btype:str, info):
async def backup_add(con:utils.Connection):
result = await con.call({"operation": "backup-add", "name": name, "type": btype, "info": info})
if result["status"] != "success":
if result["status"] == "fail-already-exists":
print("Backup %s already exists." % name)
exit(1)
else:
raise RuntimeError("Wasn't able to add remote.") # TODO: Show error
return backup_add
#
# Remotes

View File

@ -15,4 +15,4 @@ async def create_snapshot(name:str, user:str):
# List subvolumes
subvols = btrfs.list_path_subvolumes(mount, user_path)
print(repr(subvols))
raise
raise NotImplementedError()

View File

@ -1,15 +1,18 @@
import asyncio
from . import remotes
from .. import utils
class Backup():
name:str
btype:str
periode:int
blocked:set
to_backup:list
remote:remotes.Remote
def __init__(self, name:str, periode:int=None, blocked:list=[], to_backup:list=[]):
def __init__(self, name:str, btype:str="borgbackup", periode:int=None, blocked:list=[], to_backup:list=[], remote:remotes.Remote=None):
# Check args
utils.valid_name_check(name)
if periode is not None and not isinstance(periode, int):
@ -25,12 +28,29 @@ class Backup():
raise TypeError("to_backup have to be a string.")
if ":" in i:
raise ValueError(": isn't allowed a char.")
if not isinstance(btype, str):
raise TypeError("btype has to be a string.")
if btype not in ("borgbackup",):
raise ValueError("%s is an unknown backup tool." % repr(btype))
if remote is not None and not isinstance(remote, remotes.Remote):
raise TypeError("remote has to be an remote object.")
# Check type
if btype == "borgbackup":
if remote is None:
raise ValueError("remote is required for borg backup.")
if remote.rtype != "borgbackup":
raise ValueError("remote has to be an borg backup target.")
else:
raise NotImplementedError("%s isn't an implemented type." % btype)
# Set values
self.name = name
self.periode = periode
self.blocks = blocked
self.blocked = blocked
self.to_backup = to_backup
self.btype = btype
self.remote = remote
def get_next_scedule(self, latest, zero):
if self.periode is not None:
@ -41,38 +61,111 @@ class Backup():
def dump_config(self):
result = {}
result["type"] = self.btype
if self.blocked:
result["blocked"] = ",".join(self.blocked)
if self.periode is not None:
result["periode"] = str(self.periode)
if self.to_backup:
result["to_backup"] = ":".join(self.to_backup)
if self.remote:
result["remote"] = self.remote.name
return result
@staticmethod
def load_backup(name:str, config):
def load_backup(name:str, conf, remotes):
# Load informations
config = dict(config.items())
conf = dict(conf.items())
btype = conf["type"]
del conf["type"]
periode = None
if "periode" in config:
periode = int(config["periode"])
del config["periode"]
if "periode" in conf:
periode = int(conf["periode"])
del conf["periode"]
blocked = []
if "blocked" in config:
blocked = config["blocked"].split(",")
del config["blocked"]
if "blocked" in conf:
blocked = conf["blocked"].split(",")
del conf["blocked"]
to_backup = []
if "to_backup" in config:
to_backup = config["to_backup"].split(":")
del config["to_backup"]
if "to_backup" in conf:
to_backup = conf["to_backup"].split(":")
del conf["to_backup"]
remote = None
if "remote" in conf:
remote = conf["remote"]
if remote not in remotes.keys():
raise ValueError("Remote %s doesn't exists but is required." & remote)
remote = remotes[remote]
del conf["remote"]
# Generate backup
utils.check_empty_data_dict(config)
utils.check_empty_data_dict(conf)
return Backup(name=name, periode=periode, blocked=blocked, to_backup=to_backup)
return Backup(name=name, btype=btype, periode=periode, blocked=blocked, to_backup=to_backup, remote=remote)
async def run_backup(self, subvolumes:list):
print("Subvolumes: %s" % repr(subvolumes))
print("Subvolumes: %s" % repr(subvolumes))
# RPC implementations
async def add_backup(data):
# Import config
from . import config
# Load base values
name = data["name"]
del data["name"]
info = data["info"]
if not isinstance(info, dict):
raise TypeError("info has to be an object.")
del data["info"]
btype = data["type"]
del data["type"]
utils.check_empty_data_dict(data)
# Load info
periode = None
if "periode" in info:
periode = int(info["periode"])
del info["periode"]
blocked = []
if "blocked" in info:
blocked = info["blocked"]
del info["blocked"]
to_backup = []
if "to_backup" in info:
to_backup = info["to_backup"]
del info["to_backup"]
remote = None
if "remote" in info:
remote = info["remote"]
del info["remote"]
utils.check_empty_data_dict(info)
# Add backup
async with config.config_lock:
# Check if backup exists
for _ in filter(lambda x: x.name == name, config.backups):
return {"status": "fail-already-exists"}
# Search for remote
if remote is not None:
if remote not in config.remotes:
return {"status": "fail-remote-missing"}
remote = config.remotes[remote]
# Add backup
backup = Backup(name=name, btype=btype, periode=periode, blocked=blocked, to_backup=to_backup, remote=remote)
config.backups.append(backup)
await config.save_config()
return {"status": "success"}

View File

@ -26,18 +26,19 @@ if os.path.exists(CONFIG_FILE):
config = configparser.ConfigParser()
config.read(CONFIG_FILE)
backups = []
remotes = {}
for iID, i in filter(lambda x: x[0] != "DEFAULT", config.items()):
if iID.startswith("BACKUP|"): # Parse backup config
iID = iID[len("BACKUP|"):]
backups.append(Backup.load_backup(iID, i))
elif iID.startswith("REMOTE|"): # Parse remote config
if iID.startswith("REMOTE|"): # Parse remote config
iID = iID[len("REMOTE|"):]
tmp = Remote.load_remote(iID, i)
remotes[tmp.name] = tmp
else:
raise ValueError("Unknown config part %s." % repr(iID))
backups = []
for iID, i in filter(lambda x: x[0] != "DEFAULT", config.items()):
if iID.startswith("BACKUP|"): # Parse backup config
iID = iID[len("BACKUP|"):]
backups.append(Backup.load_backup(iID, i, remotes))
return backups, remotes
backups, remotes = _parse_config()

View File

@ -1,6 +1,6 @@
import asyncio
import os
from . import config, remotes
from . import backups, config, remotes
from .. import defaults, utils
@ -36,7 +36,9 @@ def gen_callback_func(master:BackupManager):
del data["operation"]
# Run operation
if operation == "remote-add":
if operation == "backup-add":
return await backups.add_backup(data)
elif operation == "remote-add":
return await remotes.add_remote(data)
elif operation == "remote-list":
return await remotes.remote_list(data)