Compare commits

...

12 Commits

Author SHA1 Message Date
mike cd14dc6158 log tweak 2024-04-21 02:07:40 -07:00
Mike e4d5e1b595 Update pyenv version and relock pipenv 2023-08-18 01:53:51 -07:00
mike 2e4638e448 Typo 2023-05-29 13:25:03 -07:00
mike 916064c153 Bump python version and relock pipenv 2023-05-20 18:24:25 -07:00
root ec894014c6 Uhm whoops 2023-03-27 20:27:35 -07:00
root d66688eb3c Remove annoying log emissions 2023-03-27 20:17:45 -07:00
root 5e30c2f7da Further upgrades to Logging 2023-03-27 20:12:37 -07:00
root c0769ad0b1 Working on logging 2023-03-27 19:59:45 -07:00
root 9d17178012 Adding dedicated Logger and Config classes 2023-03-27 19:40:52 -07:00
root 4f0b29cd3d Start a domain folder 2023-03-27 18:50:11 -07:00
root 9a2efa9f0a When consuming configs inside a dir, ignore nested dirs and files without valid YAML extensions 2023-03-27 18:44:27 -07:00
root 30bb98dff0 Update license info 2023-03-27 18:15:11 -07:00
8 changed files with 295 additions and 106 deletions

View File

@ -1 +1 @@
3.10.10
3.11.4

View File

@ -9,6 +9,6 @@ pyyaml = ">=5.4"
[dev-packages]
[requires]
python_version = "3.10.10"
python_version = "3.11.4"

79
Pipfile.lock generated
View File

@ -1,11 +1,11 @@
{
"_meta": {
"hash": {
"sha256": "cceb18d3baeb19edef3ba31b743720003102c4c3d9cddd6b595c664692a37384"
"sha256": "8c9a360e47ffd3442df22d4110e1463a62ea4dc7c0217a87a57b642b78c3d609"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.10.5"
"python_version": "3.11.4"
},
"sources": [
{
@ -18,42 +18,49 @@
"default": {
"pyyaml": {
"hashes": [
"sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
"sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
"sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
"sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
"sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
"sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
"sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
"sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
"sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
"sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
"sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
"sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
"sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
"sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
"sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
"sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
"sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
"sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
"sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
"sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
"sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
"sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
"sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
"sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
"sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
"sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
"sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
"sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
"sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
"sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
"sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
"sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc",
"sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741",
"sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206",
"sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27",
"sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595",
"sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62",
"sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98",
"sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696",
"sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d",
"sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867",
"sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47",
"sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486",
"sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6",
"sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3",
"sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007",
"sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938",
"sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c",
"sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735",
"sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d",
"sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba",
"sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8",
"sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5",
"sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd",
"sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3",
"sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0",
"sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515",
"sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c",
"sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c",
"sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924",
"sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34",
"sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43",
"sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859",
"sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673",
"sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a",
"sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab",
"sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa",
"sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c",
"sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585",
"sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d",
"sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"
],
"index": "pypi",
"version": "==6.0"
"version": "==6.0.1"
}
},
"develop": {}

View File

@ -5,6 +5,12 @@ This program functions somewhat similarly to a log rotator. It's purpose is to r
Suppose you have a third party backup program regularly dropping backup files into some directory. You could use this program to limit the number of files that remain in the directory at any given time.
# License
Copyright 2023 Mike Peralta; All rights reserved
Releasing to the public under the GNU GENERAL PUBLIC LICENSE v3 (See LICENSE file for more)
# Requirements
* Python 3

View File

@ -6,25 +6,32 @@ Mike's Backup Rotator
A simple script to help automatically rotate backup files
Copyright 2022 Mike Peralta; All rights reserved
Copyright 2023 Mike Peralta; All rights reserved
Released under the GNU GENERAL PUBLIC LICENSE v3 (See LICENSE file for more)
Releasing to the public under the GNU GENERAL PUBLIC LICENSE v3 (See LICENSE file for more)
"""
from domain.Logger import Logger
from domain.Config import Config
import datetime
import os
# import pprint
import shutil
import sys
import syslog
import time
import yaml
class BackupRotator:
def __init__(self):
def __init__(self, debug:bool = False):
self.__logger = Logger(name=type(self).__name__, debug=debug)
self.__config_helper = Config(logger=self.__logger)
self.__dry_run = False
self.__configs = []
@ -33,7 +40,7 @@ class BackupRotator:
def run(self, configs, dry_run: bool = False):
self.log("Begin")
self.info("Begin")
self.__dry_run = dry_run
self.__config_paths = configs
@ -47,7 +54,7 @@ class BackupRotator:
config = self.__configs[config_index]
#
self.log("Rotating for config " + str(config_index + 1) + " of " + str(len(self.__configs)), config["__path"])
self.info(f"Rotating for config {config_index + 1} of {len(self.__configs)} : {config['__path']}")
self._do_rotate(config)
@staticmethod
@ -57,37 +64,21 @@ class BackupRotator:
now_s = now.strftime("%b-%d-%Y %I:%M%p")
return str(now_s)
def log(self, s, o=None):
now = self.current_time()
to_log = "[" + now + "][Backup Rotator] " + str(s)
if o is not None:
to_log += " " + str(o)
syslog.syslog(to_log)
print(to_log)
def debug(self, s):
self.__logger.debug(s)
def info(self, s):
self.__logger.info(s)
def warn(self, s):
self.__logger.warn(s)
def error(self, s):
self.__logger.error(s)
def _consume_configs(self, paths: list=None):
assert paths is not None, "Config paths cannot be None"
assert len(paths) > 0, "Must provide at least one config file path"
configs = self.__config_helper.gather_valid_configs(paths=paths)
for config in configs:
self._consume_config(path=config)
# Use each config path
for path in paths:
# If this is a single path
if os.path.isfile(path):
self._consume_config(path)
# If this is a directory
elif os.path.isdir(path):
# Iterate over each file inside
for file_name in os.listdir(path):
self._consume_config(os.path.join(path, file_name))
def _consume_config(self, path: str):
# Open the file
@ -103,7 +94,7 @@ class BackupRotator:
# Consume to internal
self.__configs.append(config)
self.log("Consumed config from path:", path)
self.info(f"Consumed config from path: {path}")
def _do_rotate(self, config):
@ -111,7 +102,7 @@ class BackupRotator:
def _rotate_paths(self, config):
self.log("Begin rotating " + str(len(config["paths"])) + " paths")
self.info("Begin rotating " + str(len(config["paths"])) + " paths")
for path in config["paths"]:
self._rotate_path(config, path)
@ -119,7 +110,7 @@ class BackupRotator:
assert os.path.isdir(path), "Path should be a directory: {}".format(path)
self.log("Rotating path: {}".format(path))
self.info("Rotating path: {}".format(path))
found_any_rotation_keys = False
if "maximum-items" in config.keys():
@ -136,34 +127,34 @@ class BackupRotator:
assert os.path.isdir(path), "Path should be a directory: {}".format(path)
self.log("Rotating path for a maximum of {} items: {}".format(
self.info("Rotating path for a maximum of {} items: {}".format(
max_items, path
))
children = self._gather_rotation_candidates(config, path)
minimum_items = self._determine_minimum_items(config)
# Do we need to rotate anything out?
if len(children) < minimum_items:
self.log("Path only has {} items, which does not meet the minimum threshold of {} items. Won't rotate this path.".format(
self.info("Path only has {} items, which does not meet the minimum threshold of {} items. Won't rotate this path.".format(
len(children), minimum_items
))
return
elif len(children) <= max_items:
self.log("Path only has {} items, but needs more than {} for rotation; Won't rotate this path.".format(
self.info("Path only has {} items, but needs more than {} for rotation; Won't rotate this path.".format(
len(children), max_items
))
return
self.log("Found {} items to examine".format(len(children)))
self.info("Found {} items to examine".format(len(children)))
#
maximum_purge_count = len(children) - minimum_items
purge_count = len(children) - max_items
self.log("Want to purge {} items".format(purge_count))
self.info("Want to purge {} items".format(purge_count))
if purge_count > maximum_purge_count:
self.log("Reducing purge count from {} to {} items to respect minimum items setting ({})".format(
self.info("Reducing purge count from {} to {} items to respect minimum items setting ({})".format(
purge_count, maximum_purge_count, minimum_items
))
purge_count = maximum_purge_count
@ -174,7 +165,7 @@ class BackupRotator:
#
item_to_purge, item_ctime, item_age_seconds, item_age = self._pick_oldest_item(config, children)
children.remove(item_to_purge)
self.log("Found next item to purge: ({}) {} ({})".format(
self.info("Found next item to purge: ({}) {} ({})".format(
purge_index + 1,
os.path.basename(item_to_purge),
item_age
@ -184,7 +175,7 @@ class BackupRotator:
children_to_purge.append(item_to_purge)
#
self.log("Removing items")
self.info("Removing items")
for child_to_purge in children_to_purge:
child_basename = os.path.basename(child_to_purge)
self._remove_item(config, child_to_purge)
@ -193,19 +184,19 @@ class BackupRotator:
assert os.path.isdir(path), "Path should be a directory: {}".format(path)
self.log("Rotating path for max age of {} days: {}".format(max_age_days, path))
self.info("Rotating path for max age of {} days: {}".format(max_age_days, path))
children = self._gather_rotation_candidates(config, path)
minimum_items = self._determine_minimum_items(config)
# Do we need to rotate anything out?
if len(children) < minimum_items:
self.log("Path only has {} items, which does not meet the minimum threshold of {} items. Won't rotate this path.".format(
self.info("Path only has {} items, which does not meet the minimum threshold of {} items. Won't rotate this path.".format(
len(children), minimum_items
))
return
self.log("Examining {} items for deletion".format(len(children)))
self.info("Examining {} items for deletion".format(len(children)))
children_to_delete = []
for child in children:
@ -215,23 +206,23 @@ class BackupRotator:
child_basename = os.path.basename(child)
if age_days > max_age_days:
self.log("[Old enough ] {} ({})".format(
self.info("[Old enough ] {} ({})".format(
child_basename, age_formatted
))
children_to_delete.append(child)
else:
self.log("[Not Old enough] {} ({})".format(
self.info("[Not Old enough] {} ({})".format(
child_basename, age_formatted
))
if len(children_to_delete) > 0:
self.log("Removing old items ...")
self.info("Removing old items ...")
for child_to_delete in children_to_delete:
basename = os.path.basename(child_to_delete)
self._remove_item(config, child_to_delete)
else:
self.log("No old items to remove")
self.info("No old items to remove")
@staticmethod
def _gather_rotation_candidates(config, path):
@ -283,9 +274,7 @@ class BackupRotator:
if detection == "file":
ctime = os.path.getctime(item)
else:
raise AssertionError("Invalid value for \"date-detection\"; Should be one of {file}: {}".format(
detection
))
raise AssertionError(f"Invalid value for \"date-detection\"; Should be one of [file]: {detection}")
return ctime
@ -354,11 +343,11 @@ class BackupRotator:
raise Exception("Tried to remove a file, but this path isn't a file: " + str(file_path))
if self.__dry_run:
self.log("Won't purge file during global-level dry run: ", file_path)
self.info(f"Won't purge file during global-level dry run: {file_path}")
elif "dry-run" in config.keys() and config["dry-run"] is True:
self.log("Won't purge file during config-level dry run: ", file_path)
self.info(f"Won't purge file during config-level dry run: {file_path}")
else:
self.log("Purging file:", file_path)
self.info(f"Purging file: {file_path}")
os.remove(file_path)
def _remove_directory(self, config, dir_path):
@ -367,11 +356,11 @@ class BackupRotator:
raise Exception("Tried to remove a directory, but this path isn't a directory: " + str(dir_path))
if self.__dry_run:
self.log("Won't purge directory during global-level dry run: ", dir_path)
self.info(f"Won't purge directory during global-level dry run: {dir_path}")
elif "dry-run" in config.keys() and config["dry-run"] is True:
self.log("Won't purge directory during config-level dry run: ", dir_path)
self.info(f"Won't purge directory during config-level dry run: {dir_path}")
else:
self.log("Purging directory:", dir_path)
self.info(f"Purging directory: {dir_path}")
shutil.rmtree(dir_path)
@ -381,8 +370,8 @@ class BackupRotator:
if "minimum-items" in config.keys():
minimum_items = config["minimum-items"]
self.log("Won't delete anything unless a minimum of {} items were found".format(minimum_items))
self.info("Won't delete anything unless a minimum of {} items were found".format(minimum_items))
else:
self.log("No value found for \"minimum-items\"; Will not enforce minimum item constraint.")
self.info("No value found for \"minimum-items\"; Will not enforce minimum item constraint.")
return minimum_items

113
domain/Config.py Normal file
View File

@ -0,0 +1,113 @@
from domain.Logger import Logger
import os
class Config:
__DEFAULT_VALID_EXTENSIONS = [
"yaml",
"yml"
]
def __init__(self, logger):
self.__logger = logger
self.__valid_extensions = self.__DEFAULT_VALID_EXTENSIONS
def debug(self, s):
self.__logger.debug(f"[{type(self).__name__}] {s}")
def info(self, s):
self.__logger.info(f"[{type(self).__name__}] {s}")
def warn(self, s):
self.__logger.warn(f"[{type(self).__name__}] {s}")
def error(self, s):
self.__logger.error(f"[{type(self).__name__}] {s}")
@staticmethod
def get_dir_files_recursive(path: str):
files_paths = []
for dir_path, dirnames, filenames in os.walk(path):
for file_name in filenames:
file_path = os.path.join(dir_path, file_name)
files_paths.append(file_path)
# print("Uhm yeah", dir_path, "--", dirnames, "--", file_name)
# print("==>", file_path)
return files_paths
def gather_valid_configs(self, paths: list=None):
assert paths is not None, "Config paths cannot be None"
assert len(paths) > 0, "Must provide at least one config file path"
self.info("Gathering valid configs")
file_paths = []
configs = []
not_configs = []
# First gather all files that are potential configs
for path in paths:
self.info(f"Inspecting path: {path}")
if os.path.isfile(path):
self.debug(f"Path is a file; Adding directly to potential config candidates: {path}")
file_paths.append(path)
elif os.path.isdir(path):
self.debug(f"Path is a dir; Scanning recursively for potential config candidate files: {path}")
for file_path in Config.get_dir_files_recursive(path=path):
self.info(f"> Candidate file: {file_path}")
file_paths.append(file_path)
else:
raise AssertionError(f"Don't know how to handle path that isn't a file or dir: {path}")
# Now, filter for files with valid YAML extensions
for file_path in file_paths:
if self.check_file_extension(file_path=file_path, extensions=None):
configs.append(file_path)
else:
not_configs.append(file_path)
self.info("Filtered out non-config files:")
if len(not_configs) > 0:
for not_config in not_configs:
self.info(f"> {not_config}")
else:
self.info("> [none]")
self.info("Kept config-looking files:")
if len(configs) > 0:
for config in configs:
self.info(f"> {config}")
else:
self.info("> [none]")
return configs
def check_file_extension(self, file_path, extensions: list=None):
if extensions is None:
extensions = self.__valid_extensions
file_name, file_extension = os.path.splitext(file_path)
if len(file_extension) > 0 and file_extension[0] == ".":
file_extension = file_extension[1:]
file_extension = file_extension.lower()
for valid_extension in extensions:
#print(file_name, "---", file_extension, "---", valid_extension)
if file_extension == valid_extension:
return True
return False

64
domain/Logger.py Normal file
View File

@ -0,0 +1,64 @@
import logging
from logging.handlers import SysLogHandler
import sys
class Logger:
def __init__(self, name: str, debug: bool=False):
self.__name = name
self.__logger = logging.getLogger(self.__name)
if debug:
level = logging.DEBUG
else:
level = logging.INFO
self.__logger.setLevel(level)
formatter = logging.Formatter('[%(name)s][%(levelname)s] %(message)s')
formatter_full = logging.Formatter('[%(asctime)s][%(name)s][%(levelname)s] %(message)s')
# Console output / stream handler (STDOUT)
handler = logging.StreamHandler(
stream=sys.stdout
)
handler.setLevel(level)
handler.addFilter(lambda entry: entry.levelno <= logging.INFO)
handler.setFormatter(formatter_full)
self.__logger.addHandler(handler)
# Console output / stream handler (STDERR)
handler = logging.StreamHandler(
stream=sys.stderr
)
handler.setLevel(logging.WARNING)
handler.setFormatter(formatter_full)
self.__logger.addHandler(handler)
# Syslog handler
handler = SysLogHandler(
address="/dev/log"
)
handler.setLevel(level)
handler.setFormatter(formatter)
self.__logger.addHandler(handler)
# This is annoying inside cron
# self.debug("Test debug log")
# self.info("Test info log")
# self.warn("Test warn log")
# self.error("Test error log")
def debug(self, s):
self.__logger.debug(s)
def info(self, s):
self.__logger.info(s)
def warn(self, s):
self.__logger.warn(s)
def error(self, s):
self.__logger.error(s)

16
main.py
View File

@ -1,18 +1,26 @@
#!/usr/bin/env python3
from BackupRotator import BackupRotator
from domain.BackupRotator import BackupRotator
import argparse
#
def main():
parser = argparse.ArgumentParser(
description="Mike's Backup Rotator. Helps automatically remove old backup files or folders."
)
parser.add_argument(
"--debug", "--verbose",
dest="debug",
default=False,
action="store_true",
help="Verbose/Debug logging mode"
)
parser.add_argument(
"--config", "-c",
dest="config_files",
@ -31,7 +39,9 @@ def main():
args = parser.parse_args()
rotator = BackupRotator()
rotator = BackupRotator(
debug=args.debug
)
rotator.run(
configs=args.config_files,
dry_run=args.dry_run