Remote add.
parent
d19d8c0198
commit
00d79c8adf
|
@ -1,7 +1,7 @@
|
|||
import argparse
|
||||
import asyncio
|
||||
import os
|
||||
from . import user_service, sys_service
|
||||
from . import client, sys_service, user_service
|
||||
from .sys_service import rpc
|
||||
from .user_service import rpc
|
||||
del rpc
|
||||
|
@ -11,7 +11,11 @@ def main(args):
|
|||
# Parser
|
||||
parser = argparse.ArgumentParser("home-backup", description="Manage backup tools")
|
||||
sub_parser = parser.add_subparsers(dest="action")
|
||||
|
||||
parser_remote_add = sub_parser.add_parser("remote-add", help="Add remote for backups.")
|
||||
parser_remote_add.add_argument("--type", nargs=1, type=str, default=["borgbackup"], help="The remote type.\nDefault: borgbackup")
|
||||
parser_remote_add.add_argument("--target", nargs=1, type=str, default=None, help="The target of the remote (required by borgbackup).")
|
||||
parser_remote_add.add_argument("name", nargs=1, type=str, help="The name of the remote.")
|
||||
parser_remote_list = sub_parser.add_parser("remote-list", help="List remotes.")
|
||||
parser_remote_delete = sub_parser.add_parser("remote-delete", help="Delete remote.")
|
||||
|
||||
|
@ -38,6 +42,23 @@ def main(args):
|
|||
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":
|
||||
# Check remote type
|
||||
remote_type = result.type[0]
|
||||
if remote_type not in ("borgbackup",):
|
||||
raise ValueError("Unknown backup type %s." % repr(remote_type))
|
||||
|
||||
# Parse type
|
||||
info = {}
|
||||
if remote_type == "borgbackup":
|
||||
if result.target is None:
|
||||
raise ValueError("Target isn't set.")
|
||||
info["target"] = result.target[0]
|
||||
else:
|
||||
raise NotImplementedError("Type %s isn't supported well." % remote_type)
|
||||
|
||||
# Add remote
|
||||
client.run_command(client.remote_add_gen(name=result.name[0], rtype=remote_type, info=info))
|
||||
|
||||
# Not found action
|
||||
elif result.action is None:
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import asyncio
|
||||
from . import defaults, utils
|
||||
|
||||
#
|
||||
# Base connector
|
||||
#
|
||||
def run_command(async_func, user_path:str=None):
|
||||
async def runner():
|
||||
# Connect to user socket
|
||||
nonlocal user_path
|
||||
if user_path is None:
|
||||
user_path = defaults.USER_PATH
|
||||
if user_path is None:
|
||||
raise RuntimeError("User service socket path isn't set.")
|
||||
sock = utils.Connection()
|
||||
await sock.init(user_path)
|
||||
|
||||
# Run async function
|
||||
await async_func(sock)
|
||||
asyncio.run(runner())
|
||||
|
||||
|
||||
#
|
||||
# Remotes
|
||||
#
|
||||
def remote_add_gen(name:str, rtype:str, info):
|
||||
async def remote_add(con:utils.Connection):
|
||||
result = await con.call({"operation": "remote-add", "name": name, "type": rtype, "info": info})
|
||||
if result["status"] != "success":
|
||||
raise RuntimeError("Wasn't able to add remote.") # TODO: Show error
|
||||
return remote_add
|
|
@ -4,6 +4,7 @@ import configparser
|
|||
import io
|
||||
import os
|
||||
import time
|
||||
from .remotes import Remote
|
||||
from .. import utils
|
||||
|
||||
|
||||
|
@ -23,20 +24,14 @@ class Backup():
|
|||
|
||||
def __init__(self, name:str, periode:int=None, blocked:list=[]):
|
||||
# Check args
|
||||
if not isinstance(name, str):
|
||||
raise TypeError("Name has to be a string.")
|
||||
for i in filter(lambda x: not("a" <= x <= "z" or "A" <= x <= "Z" or "0" <= x <= "9" or x in ("_", "-")), i):
|
||||
raise ValueError("%s isn't a valid char in a backup name." % i)
|
||||
utils.valid_name_check(name)
|
||||
if periode is not None and not isinstance(periode, int):
|
||||
raise TypeError("Periode have to be an integer or null.")
|
||||
if periode is not None and periode < 0:
|
||||
raise ValueError("periode can't be negetive.")
|
||||
blocked = set(blocked)
|
||||
for i in blocked:
|
||||
if not isinstance(i, str):
|
||||
raise TypeError("Blocked have to be a list of strings.")
|
||||
for j in filter(lambda x: not("a" <= x <= "z" or "A" <= x <= "Z" or "0" <= x <= "9" or x in ("_", "-")), i):
|
||||
raise ValueError("%s isn't a valid char in a block name." % i)
|
||||
utils.valid_name_check(i)
|
||||
|
||||
# Set values
|
||||
self.name = name
|
||||
|
@ -52,31 +47,41 @@ class Backup():
|
|||
|
||||
|
||||
backups = []
|
||||
backups_lock = asyncio.Lock()
|
||||
remotes = {}
|
||||
config_lock = asyncio.Lock()
|
||||
if os.path.exists(CONFIG_FILE):
|
||||
def _parse_config():
|
||||
# Load config
|
||||
config = configparser.ConfigParser()
|
||||
config.read(CONFIG_FILE)
|
||||
|
||||
# Create backups
|
||||
backups = []
|
||||
for iID, i in filter(lambda x: x[0].startswith("BACKUP|"), config.items()):
|
||||
iID = iID[len("BACKUP|"):]
|
||||
periode = None
|
||||
if "periode" in i:
|
||||
periode = int(i["periode"])
|
||||
blocked = []
|
||||
if "blocked" in i:
|
||||
blocked = i["blocked"].split(",")
|
||||
backups.append(Backup(iID, periode=periode, blocked=blocked))
|
||||
return backups
|
||||
backups = _parse_config()
|
||||
remotes = {}
|
||||
for iID, i in filter(lambda x: x[0] != "DEFAULT", config.items()):
|
||||
if iID.startswith("BACKUP|"): # Parse backup config
|
||||
iID = iID[len("BACKUP|"):]
|
||||
periode = None
|
||||
if "periode" in i:
|
||||
periode = int(i["periode"])
|
||||
blocked = []
|
||||
if "blocked" in i:
|
||||
blocked = i["blocked"].split(",")
|
||||
backups.append(Backup(iID, periode=periode, blocked=blocked))
|
||||
elif 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))
|
||||
return backups, remotes
|
||||
backups, remotes = _parse_config()
|
||||
|
||||
|
||||
async def save_config():
|
||||
# Write config
|
||||
# Create config
|
||||
config = configparser.ConfigParser()
|
||||
|
||||
# Add backups
|
||||
for i in backups:
|
||||
if not isinstance(i, Backup):
|
||||
raise ValueError("backups contains a non backup config entry.")
|
||||
|
@ -86,12 +91,19 @@ async def save_config():
|
|||
if i.periode is not None:
|
||||
backup_data["periode"] = str(i.periode)
|
||||
config["BACKUP|%s" % i.name] = backup_data
|
||||
|
||||
|
||||
# Add remotes
|
||||
for i in remotes.values():
|
||||
if not isinstance(i, Remote):
|
||||
raise ValueError("remotes contains a non remote config entry.")
|
||||
config["REMOTE|%s" % i.name] = i.dump_config()
|
||||
|
||||
# Write data
|
||||
tmp = io.StringIO()
|
||||
config.write(tmp)
|
||||
to_write = tmp.getvalue()
|
||||
async with aiofile.AIOFile(CONFIG_FILE, "w") as f:
|
||||
await f.write(tmp.read())
|
||||
await f.write(to_write)
|
||||
await f.fsync()
|
||||
|
||||
|
||||
|
@ -120,7 +132,7 @@ class Timer():
|
|||
next_time = int(time.time())
|
||||
candiates = {}
|
||||
candidate_name = set()
|
||||
async with backups_lock:
|
||||
async with config_lock:
|
||||
for i in backups:
|
||||
if self.__latest < i.get_next_scedule(self.__latest, self.__zero) <= next_time:
|
||||
candiates[i.name] = i
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
from .. import utils
|
||||
|
||||
|
||||
class Remote():
|
||||
rtype:str
|
||||
name:str
|
||||
target:str
|
||||
|
||||
def __init__(self, rtype:str, name:str, target:str=None):
|
||||
# Check args
|
||||
if rtype not in ("borgbackup",):
|
||||
raise ValueError("rtype have to be borgbackup.")
|
||||
utils.valid_name_check(name)
|
||||
self.rtype = rtype
|
||||
self.name = name
|
||||
|
||||
# Check borg config
|
||||
if rtype == "borgbackup":
|
||||
if not isinstance(target, str):
|
||||
raise TypeError("target have to be a string.")
|
||||
self.target = target
|
||||
|
||||
def dump_config(self):
|
||||
if self.rtype == "borgbackup":
|
||||
return {"type": "borgbackup", "target":self.target}
|
||||
else:
|
||||
raise NotImplementedError("Unknown backup type %s to dump." % self.rtype)
|
||||
|
||||
@staticmethod
|
||||
def load_remote(name:str, config):
|
||||
config = dict(config.items())
|
||||
if config["type"] == "borgbackup":
|
||||
# Borg backup
|
||||
return Remote(rtype="borgbackup", name=name, target=config["target"])
|
||||
else:
|
||||
raise ValueError("Unknown backup type %s." % repr(config["type"]))
|
||||
|
||||
|
||||
# RPC implementations
|
||||
async def add_remote(data:dict):
|
||||
# Load config
|
||||
from . import config
|
||||
|
||||
# Load data
|
||||
name = data["name"]
|
||||
del data["name"]
|
||||
|
||||
rtype = data["type"]
|
||||
del data["type"]
|
||||
|
||||
info = data["info"]
|
||||
if not isinstance(info, dict):
|
||||
raise TypeError("info isn't a object.")
|
||||
info["type"] = rtype
|
||||
del data["info"]
|
||||
|
||||
for i in data.keys():
|
||||
raise ValueError("%s is an unknown option." % repr(i))
|
||||
|
||||
rem = Remote.load_remote(name, info)
|
||||
|
||||
# Set data
|
||||
async with config.config_lock:
|
||||
# Check
|
||||
if name in config.remotes:
|
||||
raise ValueError("'%s' remote already exists." % name)
|
||||
|
||||
# Set remote
|
||||
config.remotes[name] = rem
|
||||
await config.save_config()
|
||||
|
||||
# Return success
|
||||
return {"status": "success"}
|
|
@ -1,6 +1,6 @@
|
|||
import asyncio
|
||||
import os
|
||||
from . import config
|
||||
from . import config, remotes
|
||||
from .. import defaults, utils
|
||||
|
||||
|
||||
|
@ -33,9 +33,13 @@ def gen_callback_func(master:BackupManager):
|
|||
if "operation" not in data:
|
||||
raise ValueError("'operation' isn't set.")
|
||||
operation = data["operation"]
|
||||
del data["operation"]
|
||||
|
||||
# Run operation
|
||||
raise NotImplementedError()
|
||||
if operation == "remote-add":
|
||||
return await remotes.add_remote(data)
|
||||
else:
|
||||
raise NotImplementedError("%s isn't a supported operation." % repr(operation))
|
||||
return callback_func
|
||||
|
||||
|
||||
|
|
|
@ -87,5 +87,12 @@ class Connection():
|
|||
await self.__write.drain()
|
||||
|
||||
# Recive date and return
|
||||
size = _format_length.unpack(await self.__read.readexactly(_format_length.size))
|
||||
return json.loads((await self.__read.readexactly(size)).decode("UTF-8"))
|
||||
(size,) = _format_length.unpack(await self.__read.readexactly(_format_length.size))
|
||||
return json.loads((await self.__read.readexactly(size)).decode("UTF-8"))
|
||||
|
||||
|
||||
def valid_name_check(name:str):
|
||||
if not isinstance(name, str):
|
||||
raise TypeError("name has to be a string.")
|
||||
for i in filter(lambda x: not("a" <= x <= "z" or "A" <= x <= "Z" or "0" <= x <= "9" or x in ("_", "-")), name):
|
||||
raise ValueError("names can't contain %s." % repr(i))
|
Loading…
Reference in New Issue