Add backup.
parent
ae04fab683
commit
21bd8791cf
|
@ -12,6 +12,13 @@ def main(args):
|
||||||
parser = argparse.ArgumentParser("home-backup", description="Manage backup tools")
|
parser = argparse.ArgumentParser("home-backup", description="Manage backup tools")
|
||||||
sub_parser = parser.add_subparsers(dest="action")
|
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 = 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("--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).")
|
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":
|
elif result.action == "user-server":
|
||||||
asyncio.run(user_service.rpc.run_deamon(fork=result.fork)) # TODO: Change default path of user and system socket
|
asyncio.run(user_service.rpc.run_deamon(fork=result.fork)) # TODO: Change default path of user and system socket
|
||||||
|
|
||||||
# Client actions
|
# Backup
|
||||||
if result.action == "remote-add":
|
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
|
# Check remote type
|
||||||
remote_type = result.type
|
remote_type = result.type
|
||||||
if remote_type not in ("borgbackup",):
|
if remote_type not in ("borgbackup",):
|
||||||
|
|
|
@ -19,6 +19,19 @@ def run_command(async_func, user_path:str=None):
|
||||||
await async_func(sock)
|
await async_func(sock)
|
||||||
asyncio.run(runner())
|
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
|
# Remotes
|
||||||
|
|
|
@ -15,4 +15,4 @@ async def create_snapshot(name:str, user:str):
|
||||||
# List subvolumes
|
# List subvolumes
|
||||||
subvols = btrfs.list_path_subvolumes(mount, user_path)
|
subvols = btrfs.list_path_subvolumes(mount, user_path)
|
||||||
print(repr(subvols))
|
print(repr(subvols))
|
||||||
raise
|
raise NotImplementedError()
|
|
@ -1,15 +1,18 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from . import remotes
|
||||||
from .. import utils
|
from .. import utils
|
||||||
|
|
||||||
|
|
||||||
class Backup():
|
class Backup():
|
||||||
name:str
|
name:str
|
||||||
|
btype:str
|
||||||
periode:int
|
periode:int
|
||||||
blocked:set
|
blocked:set
|
||||||
|
|
||||||
to_backup:list
|
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
|
# Check args
|
||||||
utils.valid_name_check(name)
|
utils.valid_name_check(name)
|
||||||
if periode is not None and not isinstance(periode, int):
|
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.")
|
raise TypeError("to_backup have to be a string.")
|
||||||
if ":" in i:
|
if ":" in i:
|
||||||
raise ValueError(": isn't allowed a char.")
|
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
|
# Set values
|
||||||
self.name = name
|
self.name = name
|
||||||
self.periode = periode
|
self.periode = periode
|
||||||
self.blocks = blocked
|
self.blocked = blocked
|
||||||
self.to_backup = to_backup
|
self.to_backup = to_backup
|
||||||
|
self.btype = btype
|
||||||
|
self.remote = remote
|
||||||
|
|
||||||
def get_next_scedule(self, latest, zero):
|
def get_next_scedule(self, latest, zero):
|
||||||
if self.periode is not None:
|
if self.periode is not None:
|
||||||
|
@ -41,38 +61,111 @@ class Backup():
|
||||||
|
|
||||||
def dump_config(self):
|
def dump_config(self):
|
||||||
result = {}
|
result = {}
|
||||||
|
result["type"] = self.btype
|
||||||
if self.blocked:
|
if self.blocked:
|
||||||
result["blocked"] = ",".join(self.blocked)
|
result["blocked"] = ",".join(self.blocked)
|
||||||
if self.periode is not None:
|
if self.periode is not None:
|
||||||
result["periode"] = str(self.periode)
|
result["periode"] = str(self.periode)
|
||||||
if self.to_backup:
|
if self.to_backup:
|
||||||
result["to_backup"] = ":".join(self.to_backup)
|
result["to_backup"] = ":".join(self.to_backup)
|
||||||
|
if self.remote:
|
||||||
|
result["remote"] = self.remote.name
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_backup(name:str, config):
|
def load_backup(name:str, conf, remotes):
|
||||||
# Load informations
|
# Load informations
|
||||||
config = dict(config.items())
|
conf = dict(conf.items())
|
||||||
|
|
||||||
|
btype = conf["type"]
|
||||||
|
del conf["type"]
|
||||||
|
|
||||||
periode = None
|
periode = None
|
||||||
if "periode" in config:
|
if "periode" in conf:
|
||||||
periode = int(config["periode"])
|
periode = int(conf["periode"])
|
||||||
del config["periode"]
|
del conf["periode"]
|
||||||
|
|
||||||
blocked = []
|
blocked = []
|
||||||
if "blocked" in config:
|
if "blocked" in conf:
|
||||||
blocked = config["blocked"].split(",")
|
blocked = conf["blocked"].split(",")
|
||||||
del config["blocked"]
|
del conf["blocked"]
|
||||||
|
|
||||||
to_backup = []
|
to_backup = []
|
||||||
if "to_backup" in config:
|
if "to_backup" in conf:
|
||||||
to_backup = config["to_backup"].split(":")
|
to_backup = conf["to_backup"].split(":")
|
||||||
del config["to_backup"]
|
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
|
# 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):
|
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"}
|
|
@ -26,18 +26,19 @@ if os.path.exists(CONFIG_FILE):
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read(CONFIG_FILE)
|
config.read(CONFIG_FILE)
|
||||||
|
|
||||||
backups = []
|
|
||||||
remotes = {}
|
remotes = {}
|
||||||
for iID, i in filter(lambda x: x[0] != "DEFAULT", config.items()):
|
for iID, i in filter(lambda x: x[0] != "DEFAULT", config.items()):
|
||||||
if iID.startswith("BACKUP|"): # Parse backup config
|
if iID.startswith("REMOTE|"): # Parse remote config
|
||||||
iID = iID[len("BACKUP|"):]
|
|
||||||
backups.append(Backup.load_backup(iID, i))
|
|
||||||
elif iID.startswith("REMOTE|"): # Parse remote config
|
|
||||||
iID = iID[len("REMOTE|"):]
|
iID = iID[len("REMOTE|"):]
|
||||||
tmp = Remote.load_remote(iID, i)
|
tmp = Remote.load_remote(iID, i)
|
||||||
remotes[tmp.name] = tmp
|
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
|
return backups, remotes
|
||||||
backups, remotes = _parse_config()
|
backups, remotes = _parse_config()
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
from . import config, remotes
|
from . import backups, config, remotes
|
||||||
from .. import defaults, utils
|
from .. import defaults, utils
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,7 +36,9 @@ def gen_callback_func(master:BackupManager):
|
||||||
del data["operation"]
|
del data["operation"]
|
||||||
|
|
||||||
# Run 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)
|
return await remotes.add_remote(data)
|
||||||
elif operation == "remote-list":
|
elif operation == "remote-list":
|
||||||
return await remotes.remote_list(data)
|
return await remotes.remote_list(data)
|
||||||
|
|
Loading…
Reference in New Issue