diff --git a/.gitignore b/.gitignore index be22b7e..6817aaf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ config.json env +.vscode \ No newline at end of file diff --git a/backup.py b/backup.py index e69de29..6a08944 100644 --- a/backup.py +++ b/backup.py @@ -0,0 +1,151 @@ +import os +import sched +import time +import sys +from termcolor import colored, cprint +import getpass +import json +import datetime +import uuid +import logging +import logging.handlers +import multiprocessing + +syslog = True +debug = False +config_path = '/etc/python-backup.json' +Config = None + + +############### +# Helpers # +############### + + +def log(message): + if debug: + cprint('[DEBUG]: %s' % message, 'yellow') + + +def error(message): + cprint('[ERROR]: %s' % message, 'white', 'on_red') + + +def printHelp(): + cprint( + 'Please run backup.py like so:\n~$ python3 ./backup.py\t\t\tRun with default configuration file (/etc/python-backup.json)\n\ + or\n~$ python3 ./backup.py {parameter}\n\ + -h (--help)\t\t\tShow help\n\ + -v (--version)\t\t\tShow script version\n\ + -d (--debug)\t\t\tShow debug messages\n\ + -c (--config) [PATH]\t\tConfiguration file path\n\ + --single\t\t\tRun single backup - otherwise run in daemon mode\n', 'cyan') + + +def printVersion(): + cprint('Python-backup version 1.0\nBy Dominik Dancs | email> do@dancs.sk | web> https://do.dancs.sk', 'cyan') + exit(0) + + +def loadConfig(): + global config_path, Config, syslog + try: + Config = json.load(open(config_path)) + try: + Config['syslog'] + if isinstance(Config['syslog'], bool) and not Config['syslog']: + syslog = False + Config['unit-name'] + Config['to-backup'] + if not isinstance(Config['to-backup'], list): + raise Exception('Paths to backup must be a list!') + for location in Config['to-backup']: + location['path'] + location['name'] + Config['compression-level'] + if not isinstance(Config['compression-level'], int) or Config['compression-level'] < 0 or Config['compression-level'] > 9: + raise Exception( + 'Compression level must be a number between 0 - 9!') + Config['cpu-threads'] + if not isinstance(Config['cpu-threads'], int): + raise Exception('Number of CPU threads must be a number!') + if Config['cpu-threads'] < 1 or Config['cpu-threads'] > multiprocessing.cpu_count(): + raise Exception('Invalid number of CPU threads!') + Config['backup-locations'] + if not isinstance(Config['backup-locations'], list): + raise Exception('Backup locations must be a priority list!') + for location in Config['backup-locations']: + location['path'] + location['priority'] + if not isinstance(location['priority'], int) or location['priority'] < 0 or location['priority'] > 999: + raise Exception( + 'Location priority must be a number from 0 - 999') + except Exception as e: + if e == 'syslog': + error('Please specify unit name!') + if e == 'unit-name': + error('Please specify unit name!') + elif e == 'to-backup': + error('Please specify paths to backup!') + elif e == 'compression-level': + error('Please specify compression level (0-9)!') + elif e == 'cpu-threads': + error('Please specify number of available CPU threads!') + elif e == 'backup-locations': + error('Please specify backup locations!') + else: + error('Config file does not follow standards!') + log(e) + exit(1) + except Exception as e: + error('Unable to read config file!') + exit(1) + + +######################## +# Argument parsing # +######################## + + +# not enough parameters supplied - show help +if len(sys.argv) > 0: + i = 1 + while i < len(sys.argv): + arg = sys.argv[i] + i += 1 + if arg == '-h' or arg == '--help': + printHelp() + exit(0) + if arg == '-v' or arg == '--version': + printVersion() + exit(0) + if arg == '-d' or arg == '--debug': + debug = True + elif arg == '-c' or arg == '--config': + try: + config_path = sys.argv[i] + i += 1 + except: + error('No config file provided!') + printHelp() + exit(1) + else: + error('Unrecognized argument') + printHelp() + exit(1) + +loadConfig() + + +######################## +# Setup syslog # +######################## + + +if syslog: + logger = logging.getLogger('python-backup') + handler = logging.handlers.SysLogHandler(address='/dev/log') + handler.setFormatter(logging.Formatter( + '%(name)s: [%(levelname)s] %(message)s' + )) + logger.addHandler(handler) diff --git a/config.example.json b/config.example.json new file mode 100644 index 0000000..39c0797 --- /dev/null +++ b/config.example.json @@ -0,0 +1,28 @@ +{ + "unit-name": "vm-us-east-01", + "syslog": true, + "compression-level": 9, + "cpu-threads": 8, + "backup-locations": [ + { "priority": 0, "path": "/backup1" }, + { "priority": 1, "path": "/backup2" } + ], + "backup-format": "{path}/{}", + "to-backup": [{ + "path": "/home", + "name": "home" + }, + { + "path": "/data", + "name": "data" + }, + { + "path": "/root", + "name": "root" + }, + { + "path": "/opt", + "name": "opt" + } + ] +} \ No newline at end of file