From c7ae18e5702cad646ccb732464d2c5f65cff9d28 Mon Sep 17 00:00:00 2001 From: Marcel Otte Date: Sun, 6 Jan 2019 16:58:12 +0100 Subject: [PATCH 1/4] Initial commit --- backive/__init__.py | 72 ++++++++++++++++++++++++++++++++++++++ backive/config/__init__.py | 0 backive/core/__init__.py | 0 setup.cfg | 0 setup.py | 0 5 files changed, 72 insertions(+) create mode 100644 backive/__init__.py create mode 100644 backive/config/__init__.py create mode 100644 backive/core/__init__.py create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/backive/__init__.py b/backive/__init__.py new file mode 100644 index 0000000..46083f1 --- /dev/null +++ b/backive/__init__.py @@ -0,0 +1,72 @@ +from ruamel.yaml import YAML +import logging +import sys +import signal +import os +import pwd + + +logging.basicConfig(format='%(asctime)s->%(name)s@%(levelname)s: %(message)s', filename=sys.stdout, level=logging.INFO) + +got_signal = False +disks_by_uuid = "/dev/disk/by-uuid" + + +class MBD: + def __init__(self): + self._config = None + # who are we? + uid = os.getuid() + # name? + user = pwd.getpwuid(uid).pw_name + try: + if uid == 0: + config_file = "/etc/mbd.yml" + else: + config_file = os.path.join(os.path.expanduser("~"), ".mbd", "mbd.yml") + pass + + with open(config_file, "r") as cfg: + self._config = YAML().load(cfg) + except Exception as e: + logging.error(e) + + def get_list_of_devices(self): + if os.path.exists(disks_by_uuid): + uuids = os.listdir(disks_by_uuid) + return uuids + return [] + + def get_backup_list(self): + defaults = self._config.get("defaults", None) + if defaults: + mount_root = defaults.get("mount_root", "/media") + else: + mount_root = "/media" + + devices = self._config.get("devices", dict()) + for d in devices: + if d in self.get_list_of_devices(): + # do backup to this device + # mount it + mount = os.path.join(mount_root, devices[d].get("mountname")) + os.system("mount {byuuid}/{uuid} {mount}".format( + byuuid=disks_by_uuid, uuid=d, mount=mount)) + last_backup_file = os.path.join(mount, devices[d].get("backup").get("target"), ".mdb.last") + if os.path.exists(last_backup_file): + with open(last_backup_file, "r") as f: + last_backup = f.read() + + + + + + +def signal_handler(signum, frame): + got_signal = True + + +signal.signal(signal.SIGHUP, signal_handler) + +if __name__ == "__main__": + pass diff --git a/backive/config/__init__.py b/backive/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backive/core/__init__.py b/backive/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..e69de29 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..e69de29 From 77e979886b32bc79e1e95bd06f5833dfc9c35d0f Mon Sep 17 00:00:00 2001 From: Marcel Otte Date: Sun, 6 Jan 2019 17:34:02 +0100 Subject: [PATCH 2/4] Adding class bodies --- backive/config/config.py | 27 +++++++++++++++++++++++++++ backive/core/backup.py | 14 ++++++++++++++ backive/core/device.py | 15 +++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 backive/config/config.py create mode 100644 backive/core/backup.py create mode 100644 backive/core/device.py diff --git a/backive/config/config.py b/backive/config/config.py new file mode 100644 index 0000000..8fec439 --- /dev/null +++ b/backive/config/config.py @@ -0,0 +1,27 @@ +import os +import pwd +from ruamel.yaml import YAML +import logging + + +class Config: + + def __init__(self): + pass + + def find_config(self): + # who are we? + uid = os.getuid() + # name? + user = pwd.getpwuid(uid).pw_name + try: + if uid == 0: + config_file = "/etc/mbd.yml" + else: + config_file = os.path.join(os.path.expanduser("~"), ".mbd", "mbd.yml") + pass + + with open(config_file, "r") as cfg: + self._config = YAML().load(cfg) + except Exception as e: + logging.error(e) diff --git a/backive/core/backup.py b/backive/core/backup.py new file mode 100644 index 0000000..12c6f54 --- /dev/null +++ b/backive/core/backup.py @@ -0,0 +1,14 @@ +import os + + +class Backup: + + config = {} + + def __init__(self): + pass + + def run(self): + pass + + diff --git a/backive/core/device.py b/backive/core/device.py new file mode 100644 index 0000000..fced780 --- /dev/null +++ b/backive/core/device.py @@ -0,0 +1,15 @@ +import os + + +class Device: + disks_by_uuid = "/dev/disk/by-uuid" + + def __init__(self): + pass + + @classmethod + def get_device_list(cls): + if os.path.exists(cls.disks_by_uuid): + uuids = os.listdir(cls.disks_by_uuid) + return uuids + return [] From ee1ae9364ac94eaf743ede5dbe9c3f91e2d86f7e Mon Sep 17 00:00:00 2001 From: Marcel Otte Date: Sun, 6 Jan 2019 21:29:21 +0100 Subject: [PATCH 3/4] Working on it... --- backive/config/config.py | 40 +++++++++++++++++++++++++++++++++++++--- backive/core/backup.py | 6 ++++-- backive/core/device.py | 19 ++++++++++++++++--- backive/core/tool.py | 20 ++++++++++++++++++++ backive/tools/rsync.py | 10 ++++++++++ setup.py | 23 +++++++++++++++++++++++ 6 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 backive/core/tool.py create mode 100644 backive/tools/rsync.py diff --git a/backive/config/config.py b/backive/config/config.py index 8fec439..520ae32 100644 --- a/backive/config/config.py +++ b/backive/config/config.py @@ -3,11 +3,14 @@ import pwd from ruamel.yaml import YAML import logging +from backive.core.backup import Backup +from backive.core.device import Device + class Config: def __init__(self): - pass + self._config = dict() def find_config(self): # who are we? @@ -16,12 +19,43 @@ class Config: user = pwd.getpwuid(uid).pw_name try: if uid == 0: - config_file = "/etc/mbd.yml" + config_file = "/etc/backive.yml" else: - config_file = os.path.join(os.path.expanduser("~"), ".mbd", "mbd.yml") + config_file = os.path.join(os.path.expanduser("~"), ".backive", "backive.yml") pass with open(config_file, "r") as cfg: self._config = YAML().load(cfg) except Exception as e: logging.error(e) + + def get_devices(self): + devices = [] + if self._config.get("devices", None): + data = self._config.get("devices") + for device in data: + devices.append( + Device.instance( + device, + data.get(device) + ) + ) + return devices + + def get_backups(self): + backups = [] + if self._config.get("backups", None): + data = self._config.get("backups") + for name in data: + backups.append( + Backup.instance( + name, + data.get(name) + ) + ) + return backups + + def get_globals(self): + if self._config.get("defaults", None): + return self._config.get("defaults") + return {} diff --git a/backive/core/backup.py b/backive/core/backup.py index 12c6f54..65dca4b 100644 --- a/backive/core/backup.py +++ b/backive/core/backup.py @@ -5,10 +5,12 @@ class Backup: config = {} - def __init__(self): + def __init__(self, name, cfg=None): pass def run(self): pass - + @classmethod + def instance(cls, name, cfg): + return Backup(name, cfg) diff --git a/backive/core/device.py b/backive/core/device.py index fced780..4de7cf9 100644 --- a/backive/core/device.py +++ b/backive/core/device.py @@ -1,15 +1,28 @@ import os +import backive.config.config as cfg class Device: disks_by_uuid = "/dev/disk/by-uuid" - def __init__(self): - pass + def __init__(self, uuid, config=None): + self.uuid = uuid + self.config = config @classmethod - def get_device_list(cls): + def instance(cls, uuid, config): + return Device(uuid, config) + + @classmethod + def get_list(cls): if os.path.exists(cls.disks_by_uuid): uuids = os.listdir(cls.disks_by_uuid) return uuids return [] + + def mount(self): + pass + + def unmount(self): + pass + diff --git a/backive/core/tool.py b/backive/core/tool.py new file mode 100644 index 0000000..8b11c4a --- /dev/null +++ b/backive/core/tool.py @@ -0,0 +1,20 @@ +import os + + +AVAILABLE_TOOLS = {} + +def register_tool(name): + def decorator(Cls): + AVAILABLE_TOOLS.update({name: Cls}) + return decorator + + +class Tool: + def __init__(self, options): + pass + + @classmethod + def instance(cls, name, options): + if name in AVAILABLE_TOOLS: + return AVAILABLE_TOOLS.get(name)(options) + return None diff --git a/backive/tools/rsync.py b/backive/tools/rsync.py new file mode 100644 index 0000000..37178c9 --- /dev/null +++ b/backive/tools/rsync.py @@ -0,0 +1,10 @@ +from backive.core.tool import AVAILABLE_TOOLS, register_tool + + +@register_tool("rsync") +class Rsync: + def __init__(self, options): + pass + + + diff --git a/setup.py b/setup.py index e69de29..f1fb221 100644 --- a/setup.py +++ b/setup.py @@ -0,0 +1,23 @@ +import setuptools +from setuptools import setup, find_packages + + +VERSION="0.1.0" + +setup_info = dict( + name="backive", + version=VERSION, + author="Marcel M. Otte", + author_email="qwc+backive@mmo.to", + url="tbd", + description="", + license="BSD", + classifiers=[ + ], + packages=find_packages(), + + ) + +setup(**setup_info) + + From d282f694850bd8114ee8515352e546b57efa10ba Mon Sep 17 00:00:00 2001 From: Marcel Otte Date: Sun, 6 Jan 2019 21:48:02 +0100 Subject: [PATCH 4/4] Added Readme --- Readme.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Readme.md diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..1ab1999 --- /dev/null +++ b/Readme.md @@ -0,0 +1,33 @@ +# backive + +The name comes from the combination of backup and archive - silly, I know. + +# Purpose + +I've a long-term backup strategy where I put some labeled hard-disk in a SATA docking station and run a backup routine. When done, this hard-disk goes back into some shelf in my attic or basement. When the time is come again to update the long-term backup the same procedure starts again. + +So now there are my backup routines, which are manually currently - **and that sucks.** + +So what this tool or service should do is the following: +- I am able to configure based on UUIDs of the partitions which devices are allowed for backup +- I can specify as much backup items as I want, which should include: + - Backup local and remote data (Linux machine and SSH required) + - Use the best tool available for the task (rsync, duplicity, whatever) + - Even be able to "backup" without a target device (choose another path on the system) + - (optional) Be able to run pre-backup commands (for databases maybe), remote too +- The service has to be able to automatically detect the presence of a hard-disk, mounting it, running the backup routine and unmounting +- Good logging about the process + + +What I currently see as optional: +- Notification about the finished process (got conky running anyway, where I see the disks) + +## Technical goals + +- systemd service +- udev rules for notifications about new drives +- Python package, as easy to install as possible + +# Current state + +In the very beginning...