From 56ed214fb5e4ff039384c2e448667c52ba538aad Mon Sep 17 00:00:00 2001 From: Spitap Date: Tue, 19 Jul 2022 19:06:53 +0200 Subject: [PATCH 01/42] Starting work on backup system --- modoboa_installer/scripts/__init__.py | 7 +- modoboa_installer/scripts/backup.py | 114 ++++++++++++++++++++++++++ modoboa_installer/utils.py | 11 ++- run.py | 24 ++++-- 4 files changed, 147 insertions(+), 9 deletions(-) create mode 100644 modoboa_installer/scripts/backup.py diff --git a/modoboa_installer/scripts/__init__.py b/modoboa_installer/scripts/__init__.py index 3edfa66..b101966 100644 --- a/modoboa_installer/scripts/__init__.py +++ b/modoboa_installer/scripts/__init__.py @@ -6,11 +6,16 @@ import sys from .. import utils -def install(appname, config, upgrade): +def install(appname, config, upgrade, backup): """Install an application.""" if (config.has_option(appname, "enabled") and not config.getboolean(appname, "enabled")): return + if backup: + utils.printcolor("Starting up backup...", utils.MAGENTA) + script = importlib.import_module("modoboa_installer.backup") + getattr(script, Backup())(config).run() + return utils.printcolor("Installing {}".format(appname), utils.MAGENTA) try: script = importlib.import_module( diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py new file mode 100644 index 0000000..ba2b476 --- /dev/null +++ b/modoboa_installer/scripts/backup.py @@ -0,0 +1,114 @@ +"""Backup script for pre-installed instance""" + +import shutil +import utils +import os + +#TODO: have version of each modoboa componenent saved into the config file to restore the same version + +class Backup(): + + + def __init__(self, config): + self.config = config + self.destinationPath = "" + self.BACKUPDIRECTORY = ["mails", "custom", "databases"] + + + def preparePath(self): + for dir in self.BACKUPDIRECTORY: + os.mkdir(self.destinationPath + dir) + + + def validatePath(self, path): + """Check basic condition for backup directory""" + + if os.path.isfile(path): + print("Error, you provided a file instead of a directory!") + return False + + if not os.path.exists(path): + createDir = input(f"\"{path}\" doesn't exists, would you like to create it ? [Y/n]\n").lower() + + if createDir == "y" or createDir == "yes": + os.mkdir(path) + else: + return False + + if len(os.listdir(path)) != 0: + delDir = input("Warning : backup folder is not empty, it will be purged if you continue... [Y/n]").lower() + if delDir == "y" or delDir == "yes": + shutil.rmtree(path) + else: + return False + + self.destinationPath = path + + if self.destinationPath[-1] != "/": + self.destinationPath += "/" + + self.preparePath() + return True + + + def setPath(self): + """Setup backup directory""" + user_value = None + while (user_value != '' and not self.validatePath(user_value)): + print("Enter backup path, please provide an empty folder.") + user_value = utils.user_input("-> ") + + + def backupConfigFile(self): + utils.copy_file("installer.cfg", self.destinationPath) + + + def backupMails(self): + + utils.printcolor("Backing up mails", utils.MAGENTA) + + home_path = self.config.get("dovecot", "home_dir") + + if not os.path.exists(home_path) or os.path.isfile(home_path): + utils.printcolor("Error backing up Email, provided path " + f" ({home_path}) seems not right...", utils.RED) + + else: + shutil.copytree(home_path, self.destinationPath+"mails/") + utils.printcolor("Mail backup complete!", utils.GREEN) + + + def backupCustomConfig(self): + """Custom config : + - Amavis : /etc/amavis/conf.d/99-custom + - Postscreen : /etc/postfix/custom_whitelist.cidr + Feel free to suggest to add others!""" + utils.printcolor("Backing up some custom configuration...", utils.MAGENTA) + + custom_path = self.destinationPath+"custom/" + + """AMAVIS""" + amavis_custom = "/etc/amavis/conf.d/99-custom" + if os.path.isfile(amavis_custom): + utils.copy_file(amavis_custom, custom_path) + utils.printcolor("Amavis custom configuration saved!", utils.GREEN) + + """POSTSCREEN""" + postscreen_custom = "/etc/postfix/custom_whitelist.cidr" + if os.path.isfile(postscreen_custom): + utils.copy_file(postscreen_custom, custom_path) + utils.printcolor("Postscreen whitelist custom configuration saved!", utils.GREEN) + + + def backupDBs(self): + """Backing up databases""" + + + def run(self): + self.setPath() + self.backupConfigFile() + self.backupMails() + self.backupCustomConfig() + self.backupDBs() + + diff --git a/modoboa_installer/utils.py b/modoboa_installer/utils.py index 8a793af..627f519 100644 --- a/modoboa_installer/utils.py +++ b/modoboa_installer/utils.py @@ -163,19 +163,26 @@ def copy_from_template(template, dest, context): fp.write(ConfigFileTemplate(buf).substitute(context)) -def check_config_file(dest, interactive=False, upgrade=False): +def check_config_file(dest, interactive=False, upgrade=False, backup=False): """Create a new installer config file if needed.""" + isPresent = True if os.path.exists(dest): - return + return isPresent if upgrade: printcolor( "You cannot upgrade an existing installation without a " "configuration file.", RED) sys.exit(1) + elif backup: + isPresent = False + printcolor( + "Your configuration file hasn't been found. A new one will be generated. " + "Please edit it with correct password for the databases !", RED) printcolor( "Configuration file {} not found, creating new one." .format(dest), YELLOW) gen_config(dest, interactive) + return isPresent def has_colours(stream): diff --git a/run.py b/run.py index 8bfeaf2..784d667 100755 --- a/run.py +++ b/run.py @@ -44,6 +44,13 @@ def upgrade_disclaimer(config): " will be impacted:", utils.BLUE ) +def backup_disclamer(): + """Display backup disclamer. """ + utils.printcolor( + "Your mail server will be backed up (messages and databases) locally." + " !! You should really transfer the backup somewhere else..." + " Custom configuration (like to postfix) won't be saved.", utils.BLUE) + def main(input_args): """Install process.""" @@ -51,6 +58,8 @@ def main(input_args): versions = ( ["latest"] + list(compatibility_matrix.COMPATIBILITY_MATRIX.keys()) ) + parser.add_argument("--backup", action="store_true", default=False, + help="Backing up previously installed instance") parser.add_argument("--debug", action="store_true", default=False, help="Enable debug output") parser.add_argument("--force", action="store_true", default=False, @@ -79,8 +88,8 @@ def main(input_args): if args.debug: utils.ENV["debug"] = True utils.printcolor("Welcome to Modoboa installer!\n", utils.GREEN) - utils.check_config_file(args.configfile, args.interactive, args.upgrade) - if args.stop_after_configfile_check: + wasConfigFileAlreadyThere = utils.check_config_file(args.configfile, args.interactive, args.upgrade, args.backup) + if args.stop_after_configfile_check or (not wasConfigFileAlreadyThere and args.backup): return config = configparser.ConfigParser() with open(args.configfile) as fp: @@ -91,11 +100,14 @@ def main(input_args): config.set("dovecot", "domain", args.domain) config.set("modoboa", "version", args.version) config.set("modoboa", "install_beta", str(args.beta)) - # Display disclaimerpython 3 linux distribution - if not args.upgrade: - installation_disclaimer(args, config) - else: + # Display disclaimer python 3 linux distribution + if args.upgrade: upgrade_disclaimer(config) + elif args.backup: + backup_disclamer() + else: + installation_disclaimer(args, config) + # Show concerned components components = [] for section in config.sections(): From 27b9de6755c077c53662085a34019a0fa0310943 Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 21 Jul 2022 13:48:44 +0200 Subject: [PATCH 02/42] database backup --- README.rst | 15 ++++++++ modoboa_installer/database.py | 12 ++++++ modoboa_installer/scripts/__init__.py | 2 +- modoboa_installer/scripts/backup.py | 54 +++++++++++++++++++++++++-- 4 files changed, 78 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index dd8424e..bc08e66 100644 --- a/README.rst +++ b/README.rst @@ -92,6 +92,21 @@ You can activate it as follows:: It will automatically install latest versions of modoboa and its plugins. +Backup mode +------------ + +An experimental backup mode is available. + +.. note:: + + You must keep the original configuration file, ie the one used for + the installation. Otherwise, you will need to recreate it manually with the right informations !. + +You can start the process as follows:: + $ sudo ./run.py --backup +.. note:: + Then follow the step on the console + Change the generated hostname ----------------------------- diff --git a/modoboa_installer/database.py b/modoboa_installer/database.py index 41b79df..1878cb2 100644 --- a/modoboa_installer/database.py +++ b/modoboa_installer/database.py @@ -141,6 +141,13 @@ class PostgreSQL(Database): self.dbhost, dbname, dbuser, path) utils.exec_cmd(cmd, sudo_user=self.dbuser) + def dumpDatabase(self, dbname, dbuser, dbpassword, path): + """Dump DB to SQL file""" + self._setup_pgpass(dbname, dbuser, dbpassword) + cmd = "pg_dump -h {} -d {} -U {} -O -w > {}".format( + self.dbhost, dbname, dbuser, path) + utils.exec_cmd(cmd, sudo_user=self.dbuser) + class MySQL(Database): @@ -250,6 +257,11 @@ class MySQL(Database): "mysql -h {} -u {} -p{} {} < {}".format( self.dbhost, dbuser, dbpassword, dbname, path) ) + def dumpDatabase(self, dbname, dbuser, dbpassword, path): + """Dump DB to SQL file""" + cmd = "mysqldump -h {} -u {} -p{} {} > {}".format( + self.dbhost, dbuser, dbpassword, dbname, path) + utils.exec_cmd(cmd, sudo_user=self.dbuser) def get_backend(config): diff --git a/modoboa_installer/scripts/__init__.py b/modoboa_installer/scripts/__init__.py index b101966..8f526d5 100644 --- a/modoboa_installer/scripts/__init__.py +++ b/modoboa_installer/scripts/__init__.py @@ -14,7 +14,7 @@ def install(appname, config, upgrade, backup): if backup: utils.printcolor("Starting up backup...", utils.MAGENTA) script = importlib.import_module("modoboa_installer.backup") - getattr(script, Backup())(config).run() + getattr(script, "BACKUP"())(config).run() return utils.printcolor("Installing {}".format(appname), utils.MAGENTA) try: diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index ba2b476..5fa1af8 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -3,16 +3,30 @@ import shutil import utils import os +from .. import database -#TODO: have version of each modoboa componenent saved into the config file to restore the same version +#TODO: have version of each modoboa componenents saved into the config file to restore the same version class Backup(): +#Backup structure ( {optional} ): +#{{backup_folder}} +#|| +#||--> installer.cfg +#||--> custom +# |--> { (copy of) /etc/amavis/conf.d/99-custom } +# |--> { (copy of) /etc/postfix/custom_whitelist.cidr } +#||--> databases +# |--> modoboa.sql +# |--> { amavis.sql } +# |--> { spamassassin.sql } +#||--> mails +# |--> vmails def __init__(self, config): self.config = config self.destinationPath = "" - self.BACKUPDIRECTORY = ["mails", "custom", "databases"] + self.BACKUPDIRECTORY = ["mails/", "custom/", "databases/"] def preparePath(self): @@ -56,6 +70,7 @@ class Backup(): user_value = None while (user_value != '' and not self.validatePath(user_value)): print("Enter backup path, please provide an empty folder.") + print("CTRL+C to cancel") user_value = utils.user_input("-> ") @@ -74,7 +89,7 @@ class Backup(): f" ({home_path}) seems not right...", utils.RED) else: - shutil.copytree(home_path, self.destinationPath+"mails/") + shutil.copytree(home_path, self.destinationPath + self.BACKUPDIRECTORY[0]) utils.printcolor("Mail backup complete!", utils.GREEN) @@ -85,7 +100,7 @@ class Backup(): Feel free to suggest to add others!""" utils.printcolor("Backing up some custom configuration...", utils.MAGENTA) - custom_path = self.destinationPath+"custom/" + custom_path = self.destinationPath + self.BACKUPDIRECTORY[1] """AMAVIS""" amavis_custom = "/etc/amavis/conf.d/99-custom" @@ -103,6 +118,36 @@ class Backup(): def backupDBs(self): """Backing up databases""" + utils.printcolor("Backing up databases...", utils.MAGENTA) + + dump_path = self.destinationPath + self.BACKUPDIRECTORY[2] + backend = database.get_backend(self.config) + + """Modoboa""" + dbname = self.config.get("modoboa", "dbname") + dbuser = self.config.get("modoboa", "dbuser") + dbpasswd = self.config.get("modoboa", "dbpassword") + backend.dumpDatabase(dbname, dbuser, dbpasswd, dump_path+"modoboa.sql") + + """Amavis""" + if (self.config.has_option("amavis", "enabled") and + not self.config.getboolean("amavis", "enabled")): + dbname = self.config.get("amavis", "dbname") + dbuser = self.config.get("amavis", "dbuser") + dbpasswd = self.config.get("amavis", "dbpassword") + backend.dumpDatabase(dbname, dbuser, dbpasswd, dump_path+"amavis.sql") + + """SpamAssassin""" + if (self.config.has_option("spamassassin", "enabled") and + not self.config.getboolean("spamassassin", "enabled")): + dbname = self.config.get("spamassassin", "dbname") + dbuser = self.config.get("spamassassin", "dbuser") + dbpasswd = self.config.get("spamassassin", "dbpassword") + backend.dumpDatabase(dbname, dbuser, dbpasswd, dump_path+"spamassassin.sql") + + def backupCompletion(self): + utils.printcolor("Backup process done, your backup is availible here:" + f"--> {self.destinationPath}", utils.GREEN) def run(self): self.setPath() @@ -110,5 +155,6 @@ class Backup(): self.backupMails() self.backupCustomConfig() self.backupDBs() + self.backupCompletion() From 9917d8023ec370caa1021937f0c7bb15d2371e49 Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 21 Jul 2022 14:02:41 +0200 Subject: [PATCH 03/42] Edited README, fix backup run process --- README.rst | 7 ++++--- modoboa_installer/scripts/__init__.py | 7 +------ run.py | 5 ++++- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index bc08e66..3313e76 100644 --- a/README.rst +++ b/README.rst @@ -100,12 +100,13 @@ An experimental backup mode is available. .. note:: You must keep the original configuration file, ie the one used for - the installation. Otherwise, you will need to recreate it manually with the right informations !. + the installation. Otherwise, you will need to recreate it manually with the right informations ! You can start the process as follows:: + $ sudo ./run.py --backup -.. note:: - Then follow the step on the console + + Then follow the step on the console Change the generated hostname ----------------------------- diff --git a/modoboa_installer/scripts/__init__.py b/modoboa_installer/scripts/__init__.py index 8f526d5..3edfa66 100644 --- a/modoboa_installer/scripts/__init__.py +++ b/modoboa_installer/scripts/__init__.py @@ -6,16 +6,11 @@ import sys from .. import utils -def install(appname, config, upgrade, backup): +def install(appname, config, upgrade): """Install an application.""" if (config.has_option(appname, "enabled") and not config.getboolean(appname, "enabled")): return - if backup: - utils.printcolor("Starting up backup...", utils.MAGENTA) - script = importlib.import_module("modoboa_installer.backup") - getattr(script, "BACKUP"())(config).run() - return utils.printcolor("Installing {}".format(appname), utils.MAGENTA) try: script = importlib.import_module( diff --git a/run.py b/run.py index 784d667..f09022d 100755 --- a/run.py +++ b/run.py @@ -15,7 +15,7 @@ from modoboa_installer import scripts from modoboa_installer import ssl from modoboa_installer import system from modoboa_installer import utils - +from modoboa_installer.scripts import backup def installation_disclaimer(args, config): """Display installation disclaimer.""" @@ -105,6 +105,9 @@ def main(input_args): upgrade_disclaimer(config) elif args.backup: backup_disclamer() + backupProcess = backup.Backup(config) + backupProcess.run() + return else: installation_disclaimer(args, config) From 070efd61c4b767ffa8c347c9d18271223317604d Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 21 Jul 2022 14:08:39 +0200 Subject: [PATCH 04/42] Fix import --- modoboa_installer/scripts/__init__.py | 13 +++++++++++++ modoboa_installer/scripts/backup.py | 5 +++-- run.py | 4 +--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/modoboa_installer/scripts/__init__.py b/modoboa_installer/scripts/__init__.py index 3edfa66..43dab70 100644 --- a/modoboa_installer/scripts/__init__.py +++ b/modoboa_installer/scripts/__init__.py @@ -23,3 +23,16 @@ def install(appname, config, upgrade): except utils.FatalError as inst: utils.printcolor(u"{}".format(inst), utils.RED) sys.exit(1) + +def backup(config): + """Backup instance""" + try: + script = importlib.import_module( + "modoboa_installer.scripts.backup") + except ImportError: + print("Error importing backup") + try: + getattr(script, "BACKUP"())(config).run() + except utils.FatalError as inst: + utils.printcolor(u"{}".format(inst), utils.RED) + sys.exit(1) diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index 5fa1af8..588ca99 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -1,9 +1,10 @@ """Backup script for pre-installed instance""" -import shutil -import utils import os +import shutil + from .. import database +from .. import utils #TODO: have version of each modoboa componenents saved into the config file to restore the same version diff --git a/run.py b/run.py index f09022d..213b89a 100755 --- a/run.py +++ b/run.py @@ -15,7 +15,6 @@ from modoboa_installer import scripts from modoboa_installer import ssl from modoboa_installer import system from modoboa_installer import utils -from modoboa_installer.scripts import backup def installation_disclaimer(args, config): """Display installation disclaimer.""" @@ -105,8 +104,7 @@ def main(input_args): upgrade_disclaimer(config) elif args.backup: backup_disclamer() - backupProcess = backup.Backup(config) - backupProcess.run() + scripts.backup(config) return else: installation_disclaimer(args, config) From 304e25fa3c3da042a0803fe863829a23263a5d8e Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 21 Jul 2022 14:10:57 +0200 Subject: [PATCH 05/42] Fix getattr --- modoboa_installer/scripts/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa_installer/scripts/__init__.py b/modoboa_installer/scripts/__init__.py index 43dab70..c876ffa 100644 --- a/modoboa_installer/scripts/__init__.py +++ b/modoboa_installer/scripts/__init__.py @@ -32,7 +32,7 @@ def backup(config): except ImportError: print("Error importing backup") try: - getattr(script, "BACKUP"())(config).run() + getattr(script, "BACKUP")(config).run() except utils.FatalError as inst: utils.printcolor(u"{}".format(inst), utils.RED) sys.exit(1) From dc84a7952801deeb4b863cd501ce7171120b8d31 Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 21 Jul 2022 14:12:35 +0200 Subject: [PATCH 06/42] Note : capitalize affects only first letter --- modoboa_installer/scripts/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa_installer/scripts/__init__.py b/modoboa_installer/scripts/__init__.py index c876ffa..d60b2ca 100644 --- a/modoboa_installer/scripts/__init__.py +++ b/modoboa_installer/scripts/__init__.py @@ -32,7 +32,7 @@ def backup(config): except ImportError: print("Error importing backup") try: - getattr(script, "BACKUP")(config).run() + getattr(script, "Backup")(config).run() except utils.FatalError as inst: utils.printcolor(u"{}".format(inst), utils.RED) sys.exit(1) From 568c4a65a08fab3bdf1e7b3dc190eaf86204f531 Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 21 Jul 2022 16:51:32 +0200 Subject: [PATCH 07/42] fix none-type passed to os.path --- modoboa_installer/scripts/backup.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index 588ca99..0dcae30 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -32,14 +32,17 @@ class Backup(): def preparePath(self): for dir in self.BACKUPDIRECTORY: - os.mkdir(self.destinationPath + dir) + utils.mkdir(self.destinationPath + dir) def validatePath(self, path): """Check basic condition for backup directory""" - - if os.path.isfile(path): - print("Error, you provided a file instead of a directory!") + try: + if os.path.isfile(path): + print("Error, you provided a file instead of a directory!") + return False + except: + print("Provided path is not recognized...") return False if not os.path.exists(path): @@ -69,7 +72,7 @@ class Backup(): def setPath(self): """Setup backup directory""" user_value = None - while (user_value != '' and not self.validatePath(user_value)): + while (user_value != '' and user_value != None and not self.validatePath(user_value)): print("Enter backup path, please provide an empty folder.") print("CTRL+C to cancel") user_value = utils.user_input("-> ") From 6f604a5fec371d27d2383c95b4802cfedca79f07 Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 21 Jul 2022 16:53:56 +0200 Subject: [PATCH 08/42] Fix loop logic --- modoboa_installer/scripts/backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index 0dcae30..a913919 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -72,7 +72,7 @@ class Backup(): def setPath(self): """Setup backup directory""" user_value = None - while (user_value != '' and user_value != None and not self.validatePath(user_value)): + while (user_value == '' or user_value == None or not self.validatePath(user_value)): print("Enter backup path, please provide an empty folder.") print("CTRL+C to cancel") user_value = utils.user_input("-> ") From 8d02d2a9fb06acb9ef9590b6ee5fd6b6c9ba8f11 Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 21 Jul 2022 17:09:23 +0200 Subject: [PATCH 09/42] added safe mkdir in utils, use utils.mkdir_safe() in backup --- modoboa_installer/scripts/backup.py | 8 ++++++-- modoboa_installer/utils.py | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index a913919..87de10f 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -1,7 +1,9 @@ """Backup script for pre-installed instance""" import os +import pwd import shutil +import stat from .. import database from .. import utils @@ -31,8 +33,9 @@ class Backup(): def preparePath(self): + pw = pwd.getpwnam("root") for dir in self.BACKUPDIRECTORY: - utils.mkdir(self.destinationPath + dir) + utils.mkdir_safe(self.destinationPath + dir, stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3]) def validatePath(self, path): @@ -49,7 +52,8 @@ class Backup(): createDir = input(f"\"{path}\" doesn't exists, would you like to create it ? [Y/n]\n").lower() if createDir == "y" or createDir == "yes": - os.mkdir(path) + pw = pwd.getpwnam("root") + utils.mkdir_safe(path, stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3]) else: return False diff --git a/modoboa_installer/utils.py b/modoboa_installer/utils.py index 627f519..78d9d67 100644 --- a/modoboa_installer/utils.py +++ b/modoboa_installer/utils.py @@ -106,6 +106,11 @@ def mkdir(path, mode, uid, gid): os.chmod(path, mode) os.chown(path, uid, gid) +def mkdir_safe(path, mode, uid, gid): + """Create a directory. Safe way (-p)""" + if not os.path.exists(path): + os.makedirs(os.path.abspath(path), mode) + mkdir(path, mode, uid, gid) def make_password(length=16): """Create a random password.""" From 1f9d69c37c28375774085293a417538deb66e87d Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 21 Jul 2022 17:21:59 +0200 Subject: [PATCH 10/42] Fix copy issue --- modoboa_installer/scripts/backup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index 87de10f..5cbdc66 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -97,7 +97,12 @@ class Backup(): f" ({home_path}) seems not right...", utils.RED) else: - shutil.copytree(home_path, self.destinationPath + self.BACKUPDIRECTORY[0]) + dst = self.destinationPath + self.BACKUPDIRECTORY[0] + "vmail/" + + if os.path.exists(dst): + shutil.rmtree(dst) + + shutil.copytree(home_path, dst) utils.printcolor("Mail backup complete!", utils.GREEN) From 54185a7c5aa334c46924a037f018821c39596462 Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 21 Jul 2022 17:26:40 +0200 Subject: [PATCH 11/42] Fix database backup logic issue --- modoboa_installer/scripts/backup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index 5cbdc66..64acf36 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -144,7 +144,7 @@ class Backup(): """Amavis""" if (self.config.has_option("amavis", "enabled") and - not self.config.getboolean("amavis", "enabled")): + self.config.getboolean("amavis", "enabled")): dbname = self.config.get("amavis", "dbname") dbuser = self.config.get("amavis", "dbuser") dbpasswd = self.config.get("amavis", "dbpassword") @@ -152,7 +152,7 @@ class Backup(): """SpamAssassin""" if (self.config.has_option("spamassassin", "enabled") and - not self.config.getboolean("spamassassin", "enabled")): + self.config.getboolean("spamassassin", "enabled")): dbname = self.config.get("spamassassin", "dbname") dbuser = self.config.get("spamassassin", "dbuser") dbpasswd = self.config.get("spamassassin", "dbpassword") From 74de6a9bb1e4ee84abb23f2f1780cacd9c6c767f Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 21 Jul 2022 17:31:56 +0200 Subject: [PATCH 12/42] Reset pgpass before trying to backup secondary dbs --- modoboa_installer/database.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modoboa_installer/database.py b/modoboa_installer/database.py index 1878cb2..b589954 100644 --- a/modoboa_installer/database.py +++ b/modoboa_installer/database.py @@ -143,6 +143,7 @@ class PostgreSQL(Database): def dumpDatabase(self, dbname, dbuser, dbpassword, path): """Dump DB to SQL file""" + self._pgpass_done = False #Reset pgpass since we backup multiple db (different secret set) self._setup_pgpass(dbname, dbuser, dbpassword) cmd = "pg_dump -h {} -d {} -U {} -O -w > {}".format( self.dbhost, dbname, dbuser, path) From 5318fa279bcfdb7f930e7ed12ddd737cb77e6ae0 Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 21 Jul 2022 18:00:50 +0200 Subject: [PATCH 13/42] bash option --- modoboa_installer/scripts/__init__.py | 4 ++-- modoboa_installer/scripts/backup.py | 29 ++++++++++++++++++--------- run.py | 5 ++++- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/modoboa_installer/scripts/__init__.py b/modoboa_installer/scripts/__init__.py index d60b2ca..93043b3 100644 --- a/modoboa_installer/scripts/__init__.py +++ b/modoboa_installer/scripts/__init__.py @@ -24,7 +24,7 @@ def install(appname, config, upgrade): utils.printcolor(u"{}".format(inst), utils.RED) sys.exit(1) -def backup(config): +def backup(config, isBash): """Backup instance""" try: script = importlib.import_module( @@ -32,7 +32,7 @@ def backup(config): except ImportError: print("Error importing backup") try: - getattr(script, "Backup")(config).run() + getattr(script, "Backup")(config, isBash).run() except utils.FatalError as inst: utils.printcolor(u"{}".format(inst), utils.RED) sys.exit(1) diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index 64acf36..14c8dff 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -4,6 +4,7 @@ import os import pwd import shutil import stat +import datetime from .. import database from .. import utils @@ -26,8 +27,9 @@ class Backup(): #||--> mails # |--> vmails - def __init__(self, config): + def __init__(self, config, isBash): self.config = config + self.isBash = isBash self.destinationPath = "" self.BACKUPDIRECTORY = ["mails/", "custom/", "databases/"] @@ -49,17 +51,19 @@ class Backup(): return False if not os.path.exists(path): - createDir = input(f"\"{path}\" doesn't exists, would you like to create it ? [Y/n]\n").lower() + if not self.isBash: + createDir = input(f"\"{path}\" doesn't exists, would you like to create it ? [Y/n]\n").lower() - if createDir == "y" or createDir == "yes": + if self.isBash or (not self.isBash and (createDir == "y" or createDir == "yes")): pw = pwd.getpwnam("root") utils.mkdir_safe(path, stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3]) else: return False if len(os.listdir(path)) != 0: - delDir = input("Warning : backup folder is not empty, it will be purged if you continue... [Y/n]").lower() - if delDir == "y" or delDir == "yes": + if not self.isBash: + delDir = input("Warning : backup folder is not empty, it will be purged if you continue... [Y/n]\n").lower() + if self.isBash or (not self.isBash and (delDir == "y" or delDir == "yes")): shutil.rmtree(path) else: return False @@ -75,11 +79,16 @@ class Backup(): def setPath(self): """Setup backup directory""" - user_value = None - while (user_value == '' or user_value == None or not self.validatePath(user_value)): - print("Enter backup path, please provide an empty folder.") - print("CTRL+C to cancel") - user_value = utils.user_input("-> ") + if self.isBash: + date = datetime.datetime.now().strftime("%m_%d_%Y_%H_%M") + path = f"/modoboa_backup/backup_{date}/" + self.validatePath(path) + else: + user_value = None + while (user_value == '' or user_value == None or not self.validatePath(user_value)): + print("Enter backup path, please provide an empty folder.") + print("CTRL+C to cancel") + user_value = utils.user_input("-> ") def backupConfigFile(self): diff --git a/run.py b/run.py index 213b89a..7fbf7fd 100755 --- a/run.py +++ b/run.py @@ -80,6 +80,9 @@ def main(input_args): parser.add_argument( "--beta", action="store_true", default=False, help="Install latest beta release of Modoboa instead of the stable one") + parser.add_argument( + "--bash", action="store_true", default=False, + help="(backup only) - For script usage, No interaction will be required") parser.add_argument("domain", type=str, help="The main domain of your future mail server") args = parser.parse_args(input_args) @@ -104,7 +107,7 @@ def main(input_args): upgrade_disclaimer(config) elif args.backup: backup_disclamer() - scripts.backup(config) + scripts.backup(config, args.bash) return else: installation_disclaimer(args, config) From 579faccfa53e5a56cd7ce0106fbd4c6e4cbd28b1 Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 21 Jul 2022 19:00:32 +0200 Subject: [PATCH 14/42] added an automatic bash option (no path provided) or a path provided bash (for cron job) --- modoboa_installer/scripts/__init__.py | 4 ++-- modoboa_installer/scripts/backup.py | 23 +++++++++++++++++----- run.py | 28 ++++++++++++++++++++++++--- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/modoboa_installer/scripts/__init__.py b/modoboa_installer/scripts/__init__.py index 93043b3..cb32010 100644 --- a/modoboa_installer/scripts/__init__.py +++ b/modoboa_installer/scripts/__init__.py @@ -24,7 +24,7 @@ def install(appname, config, upgrade): utils.printcolor(u"{}".format(inst), utils.RED) sys.exit(1) -def backup(config, isBash): +def backup(config, bashArg): """Backup instance""" try: script = importlib.import_module( @@ -32,7 +32,7 @@ def backup(config, isBash): except ImportError: print("Error importing backup") try: - getattr(script, "Backup")(config, isBash).run() + getattr(script, "Backup")(config, bashArg).run() except utils.FatalError as inst: utils.printcolor(u"{}".format(inst), utils.RED) sys.exit(1) diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index 14c8dff..dc9cabb 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -5,6 +5,7 @@ import pwd import shutil import stat import datetime +import sys from .. import database from .. import utils @@ -27,12 +28,17 @@ class Backup(): #||--> mails # |--> vmails - def __init__(self, config, isBash): + def __init__(self, config, bashArg): self.config = config - self.isBash = isBash self.destinationPath = "" self.BACKUPDIRECTORY = ["mails/", "custom/", "databases/"] + self.isBash = False + self.bash = "" + if bashArg != "NOBASH": + self.isBash = True + self.bash = bashArg + def preparePath(self): pw = pwd.getpwnam("root") @@ -80,9 +86,16 @@ class Backup(): def setPath(self): """Setup backup directory""" if self.isBash: - date = datetime.datetime.now().strftime("%m_%d_%Y_%H_%M") - path = f"/modoboa_backup/backup_{date}/" - self.validatePath(path) + if self.bash == "TRUE": + date = datetime.datetime.now().strftime("%m_%d_%Y_%H_%M") + path = f"/modoboa_backup/backup_{date}/" + self.validatePath(path) + else : + validate = self.validatePath(self.bash) + if not validate: + print("provided bash is not right, exiting...") + print(f"Path provided : {self.bash}") + sys.exit(1) else: user_value = None while (user_value == '' or user_value == None or not self.validatePath(user_value)): diff --git a/run.py b/run.py index 7fbf7fd..fdfa2e3 100755 --- a/run.py +++ b/run.py @@ -81,18 +81,35 @@ def main(input_args): "--beta", action="store_true", default=False, help="Install latest beta release of Modoboa instead of the stable one") parser.add_argument( - "--bash", action="store_true", default=False, - help="(backup only) - For script usage, No interaction will be required") + "--bash", type=str, nargs=1, metavar="path", + help="(backup only) - For script usage, No interaction will be required, you must provide a path") + parser.add_argument( + "--sbash", action="store_true", default=False, + help="same as --bash but backup will be at /modoboa_backup/Backup_M_Y_d_H_M") parser.add_argument("domain", type=str, help="The main domain of your future mail server") args = parser.parse_args(input_args) if args.debug: utils.ENV["debug"] = True + + if not args.backup and (args.bash != None or args.sbash): + utils.printcolor("You provided --bash or --sbash without --backup, " + "if you want to do a backup, please provide --backup!", utils.RED) + return + elif args.bash != None and args.sbash : + utils.printcolor("You provided --bash PATH and --sbash at the same time. " + "Please provided only one!", utils.RED) + return + elif args.bash == "TRUE": + utils.printcolor("You can't pick *TRUE* as backup directory !", utils.RED) + utils.printcolor("Welcome to Modoboa installer!\n", utils.GREEN) wasConfigFileAlreadyThere = utils.check_config_file(args.configfile, args.interactive, args.upgrade, args.backup) + if args.stop_after_configfile_check or (not wasConfigFileAlreadyThere and args.backup): return + config = configparser.ConfigParser() with open(args.configfile) as fp: config.read_file(fp) @@ -107,7 +124,12 @@ def main(input_args): upgrade_disclaimer(config) elif args.backup: backup_disclamer() - scripts.backup(config, args.bash) + bashArg = "NOBASH" + if args.bash != None: + bashArg = args.bash + elif args.sbash: + bashArg = "TRUE" + scripts.backup(config, bashArg) return else: installation_disclaimer(args, config) From db6457c5f526a93c2b07153e606a23f18d60bcbe Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 21 Jul 2022 19:07:18 +0200 Subject: [PATCH 15/42] better path handling --- modoboa_installer/scripts/backup.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index dc9cabb..52a8329 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -48,15 +48,21 @@ class Backup(): def validatePath(self, path): """Check basic condition for backup directory""" - try: - if os.path.isfile(path): - print("Error, you provided a file instead of a directory!") - return False + + if path[-1] != "/": + path += "/" + + try : + pathExists = os.path.exists(path) except: print("Provided path is not recognized...") return False + + if pathExists and os.path.isfile(path): + print("Error, you provided a file instead of a directory!") + return False - if not os.path.exists(path): + if not pathExists: if not self.isBash: createDir = input(f"\"{path}\" doesn't exists, would you like to create it ? [Y/n]\n").lower() @@ -76,9 +82,6 @@ class Backup(): self.destinationPath = path - if self.destinationPath[-1] != "/": - self.destinationPath += "/" - self.preparePath() return True From 9e1c18cd6b19d267f126de1e984e0e992005696a Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 21 Jul 2022 19:09:53 +0200 Subject: [PATCH 16/42] Fix argument passed as list instead of string --- run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run.py b/run.py index fdfa2e3..5d94e9b 100755 --- a/run.py +++ b/run.py @@ -81,7 +81,7 @@ def main(input_args): "--beta", action="store_true", default=False, help="Install latest beta release of Modoboa instead of the stable one") parser.add_argument( - "--bash", type=str, nargs=1, metavar="path", + "--bash", type=str, metavar="path", help="(backup only) - For script usage, No interaction will be required, you must provide a path") parser.add_argument( "--sbash", action="store_true", default=False, From 632c26596e62e6219a485dfb1565cc369c23be87 Mon Sep 17 00:00:00 2001 From: Spitap Date: Mon, 25 Jul 2022 21:52:15 +0200 Subject: [PATCH 17/42] Update backup readme --- README.rst | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 3313e76..c37a00f 100644 --- a/README.rst +++ b/README.rst @@ -97,17 +97,35 @@ Backup mode An experimental backup mode is available. -.. note:: +.. warning:: - You must keep the original configuration file, ie the one used for - the installation. Otherwise, you will need to recreate it manually with the right informations ! + You must keep the original configuration file, i.e. the one used for + the installation. Otherwise, you will need to recreate it manually with the right information ! You can start the process as follows:: $ sudo ./run.py --backup - Then follow the step on the console +Then follow the step on the console. +There are also two non-interactive mode: + +1. Silent batch mode + +Command:: + + $ sudo ./run.py --backup --sbatch + +This mode is the silent batch mode, when executed, it will create /modoboa_backup/backup{time} and each time you execute it, it will create a new backup directory with current time. + +2. Path batch mode + +Command:: + + $ sudo ./run.py --backup --batch /path/of/backup/directory + +This mode is the same as silent batch mode, but you provide the path to the backup directory you want. + Change the generated hostname ----------------------------- From 20970557de951c91b2af969eef52f221e58d60dd Mon Sep 17 00:00:00 2001 From: Spitap Date: Mon, 25 Jul 2022 22:05:35 +0200 Subject: [PATCH 18/42] Allow to disable mail backup --- README.rst | 7 +++++++ modoboa_installer/scripts/__init__.py | 4 ++-- modoboa_installer/scripts/backup.py | 8 ++++++-- run.py | 7 +++++-- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index c37a00f..be0c9b6 100644 --- a/README.rst +++ b/README.rst @@ -125,7 +125,14 @@ Command:: $ sudo ./run.py --backup --batch /path/of/backup/directory This mode is the same as silent batch mode, but you provide the path to the backup directory you want. + + +If you want to disable mail backup:: + + $ sudo ./run.py --backup --no-mail +This can be useful for larger instance + Change the generated hostname ----------------------------- diff --git a/modoboa_installer/scripts/__init__.py b/modoboa_installer/scripts/__init__.py index cb32010..3b8fd1a 100644 --- a/modoboa_installer/scripts/__init__.py +++ b/modoboa_installer/scripts/__init__.py @@ -24,7 +24,7 @@ def install(appname, config, upgrade): utils.printcolor(u"{}".format(inst), utils.RED) sys.exit(1) -def backup(config, bashArg): +def backup(config, bashArg, nomail): """Backup instance""" try: script = importlib.import_module( @@ -32,7 +32,7 @@ def backup(config, bashArg): except ImportError: print("Error importing backup") try: - getattr(script, "Backup")(config, bashArg).run() + getattr(script, "Backup")(config, bashArg, nomail).run() except utils.FatalError as inst: utils.printcolor(u"{}".format(inst), utils.RED) sys.exit(1) diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index 52a8329..39387c2 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -28,11 +28,11 @@ class Backup(): #||--> mails # |--> vmails - def __init__(self, config, bashArg): + def __init__(self, config, bashArg, nomail): self.config = config self.destinationPath = "" self.BACKUPDIRECTORY = ["mails/", "custom/", "databases/"] - + self.nomail = nomail self.isBash = False self.bash = "" if bashArg != "NOBASH": @@ -113,6 +113,10 @@ class Backup(): def backupMails(self): + if self.nomail: + utils.printcolor("Skipping mail backup, no-mail argument provided", utils.MAGENTA) + return + utils.printcolor("Backing up mails", utils.MAGENTA) home_path = self.config.get("dovecot", "home_dir") diff --git a/run.py b/run.py index 5d94e9b..ec67264 100755 --- a/run.py +++ b/run.py @@ -86,6 +86,9 @@ def main(input_args): parser.add_argument( "--sbash", action="store_true", default=False, help="same as --bash but backup will be at /modoboa_backup/Backup_M_Y_d_H_M") + parser.add_argument( + "--no-mail", action="store_true", default=False, + help="Disable mail backup (save space)") parser.add_argument("domain", type=str, help="The main domain of your future mail server") args = parser.parse_args(input_args) @@ -93,7 +96,7 @@ def main(input_args): if args.debug: utils.ENV["debug"] = True - if not args.backup and (args.bash != None or args.sbash): + if not args.backup and (args.bash != None or args.sbash or args.no_mail): utils.printcolor("You provided --bash or --sbash without --backup, " "if you want to do a backup, please provide --backup!", utils.RED) return @@ -129,7 +132,7 @@ def main(input_args): bashArg = args.bash elif args.sbash: bashArg = "TRUE" - scripts.backup(config, bashArg) + scripts.backup(config, bashArg, args.no_mail) return else: installation_disclaimer(args, config) From 37bc21dfd34d1445d8d4b3b00744e231edc346c9 Mon Sep 17 00:00:00 2001 From: Spitap Date: Tue, 26 Jul 2022 10:36:08 +0200 Subject: [PATCH 19/42] Backup postewhite.conf instead of custom whitelist Postwhite.conf contains a custom host list --- modoboa_installer/scripts/backup.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index 39387c2..a3717b7 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -138,7 +138,7 @@ class Backup(): def backupCustomConfig(self): """Custom config : - Amavis : /etc/amavis/conf.d/99-custom - - Postscreen : /etc/postfix/custom_whitelist.cidr + - Postwhite : /etc/postwhite.conf Feel free to suggest to add others!""" utils.printcolor("Backing up some custom configuration...", utils.MAGENTA) @@ -150,11 +150,11 @@ class Backup(): utils.copy_file(amavis_custom, custom_path) utils.printcolor("Amavis custom configuration saved!", utils.GREEN) - """POSTSCREEN""" - postscreen_custom = "/etc/postfix/custom_whitelist.cidr" - if os.path.isfile(postscreen_custom): - utils.copy_file(postscreen_custom, custom_path) - utils.printcolor("Postscreen whitelist custom configuration saved!", utils.GREEN) + """POSTWHITE""" + postswhite_custom = "/etc/postwhite.conf" + if os.path.isfile(postswhite_custom): + utils.copy_file(postswhite_custom, custom_path) + utils.printcolor("Postwhite configuration saved!", utils.GREEN) def backupDBs(self): From 439ffb94c44dec6b067e80e3aeea608a17aca796 Mon Sep 17 00:00:00 2001 From: Spitap Date: Mon, 25 Jul 2022 18:54:47 +0200 Subject: [PATCH 20/42] initial commit --- modoboa_installer/scripts/__init__.py | 10 +++-- modoboa_installer/scripts/base.py | 3 +- modoboa_installer/utils.py | 8 +++- run.py | 57 ++++++++++++++++++++------- 4 files changed, 58 insertions(+), 20 deletions(-) diff --git a/modoboa_installer/scripts/__init__.py b/modoboa_installer/scripts/__init__.py index 3b8fd1a..219d255 100644 --- a/modoboa_installer/scripts/__init__.py +++ b/modoboa_installer/scripts/__init__.py @@ -6,12 +6,16 @@ import sys from .. import utils -def install(appname, config, upgrade): +def install(appname, config, upgrade, restore): """Install an application.""" if (config.has_option(appname, "enabled") and not config.getboolean(appname, "enabled")): return - utils.printcolor("Installing {}".format(appname), utils.MAGENTA) + + if not restore: + utils.printcolor("Installing {}".format(appname), utils.MAGENTA) + else: + utils.printcolor("Restoring {}".format(appname), utils.MAGENTA) try: script = importlib.import_module( "modoboa_installer.scripts.{}".format(appname)) @@ -19,7 +23,7 @@ def install(appname, config, upgrade): print("Unknown application {}".format(appname)) sys.exit(1) try: - getattr(script, appname.capitalize())(config, upgrade).run() + getattr(script, appname.capitalize())(config, upgrade, restore).run() except utils.FatalError as inst: utils.printcolor(u"{}".format(inst), utils.RED) sys.exit(1) diff --git a/modoboa_installer/scripts/base.py b/modoboa_installer/scripts/base.py index 8f61e5d..d56fdb0 100644 --- a/modoboa_installer/scripts/base.py +++ b/modoboa_installer/scripts/base.py @@ -20,10 +20,11 @@ class Installer(object): with_db = False config_files = [] - def __init__(self, config, upgrade): + def __init__(self, config, upgrade, restore): """Get configuration.""" self.config = config self.upgrade = upgrade + self.restore = restore if self.config.has_section(self.appname): self.app_config = dict(self.config.items(self.appname)) self.dbengine = self.config.get("database", "engine") diff --git a/modoboa_installer/utils.py b/modoboa_installer/utils.py index 78d9d67..48260d2 100644 --- a/modoboa_installer/utils.py +++ b/modoboa_installer/utils.py @@ -168,7 +168,7 @@ def copy_from_template(template, dest, context): fp.write(ConfigFileTemplate(buf).substitute(context)) -def check_config_file(dest, interactive=False, upgrade=False, backup=False): +def check_config_file(dest, interactive=False, upgrade=False, backup=False, restore=False): """Create a new installer config file if needed.""" isPresent = True if os.path.exists(dest): @@ -183,6 +183,12 @@ def check_config_file(dest, interactive=False, upgrade=False, backup=False): printcolor( "Your configuration file hasn't been found. A new one will be generated. " "Please edit it with correct password for the databases !", RED) + elif restore: + printcolor( + "You cannot restore an existing installation without a " + f"configuration file. (file : {dest} has not been found...", RED) + sys.exit(1) + printcolor( "Configuration file {} not found, creating new one." .format(dest), YELLOW) diff --git a/run.py b/run.py index ec67264..901bfdc 100755 --- a/run.py +++ b/run.py @@ -3,6 +3,8 @@ """An installer for Modoboa.""" import argparse +from ast import parse +from ctypes import util try: import configparser except ImportError: @@ -50,6 +52,12 @@ def backup_disclamer(): " !! You should really transfer the backup somewhere else..." " Custom configuration (like to postfix) won't be saved.", utils.BLUE) +def restore_disclamer(path): + """Display restore disclamer. """ + utils.printcolor( + "You are about to restore a previous installation of Modoboa." + "Is a new version has been released in between, please update your database !", + utils.BLUE) def main(input_args): """Install process.""" @@ -89,6 +97,10 @@ def main(input_args): parser.add_argument( "--no-mail", action="store_true", default=False, help="Disable mail backup (save space)") + parser.add_argument( + "--restore", type=str, metavar="path", + help="Restore a previously backup up modoboa instance on a NEW machine. You Must provide backup directory" + ) parser.add_argument("domain", type=str, help="The main domain of your future mail server") args = parser.parse_args(input_args) @@ -106,9 +118,17 @@ def main(input_args): return elif args.bash == "TRUE": utils.printcolor("You can't pick *TRUE* as backup directory !", utils.RED) + + #Restore prep + isRestoring = False + if args.restore != None: + isRestoring = True + if args.restore[-1] != "/": + args.restore += "/" + args.configfile = args.restore + "installer.cfg" utils.printcolor("Welcome to Modoboa installer!\n", utils.GREEN) - wasConfigFileAlreadyThere = utils.check_config_file(args.configfile, args.interactive, args.upgrade, args.backup) + wasConfigFileAlreadyThere = utils.check_config_file(args.configfile, args.interactive, args.upgrade, args.backup, isRestoring) if args.stop_after_configfile_check or (not wasConfigFileAlreadyThere and args.backup): return @@ -134,6 +154,8 @@ def main(input_args): bashArg = "TRUE" scripts.backup(config, bashArg, args.no_mail) return + elif args.restore: + restore_disclamer() else: installation_disclaimer(args, config) @@ -162,22 +184,27 @@ def main(input_args): ssl_backend = ssl.get_backend(config) if ssl_backend and not args.upgrade: ssl_backend.generate_cert() - scripts.install("amavis", config, args.upgrade) - scripts.install("modoboa", config, args.upgrade) - scripts.install("automx", config, args.upgrade) - scripts.install("radicale", config, args.upgrade) - scripts.install("uwsgi", config, args.upgrade) - scripts.install("nginx", config, args.upgrade) - scripts.install("opendkim", config, args.upgrade) - scripts.install("postfix", config, args.upgrade) - scripts.install("dovecot", config, args.upgrade) + scripts.install("amavis", config, args.upgrade, args.restore) + scripts.install("modoboa", config, args.upgrade, args.restore) + scripts.install("automx", config, args.upgrade, args.restore) + scripts.install("radicale", config, args.upgrade, args.restore) + scripts.install("uwsgi", config, args.upgrade, args.restore) + scripts.install("nginx", config, args.upgrade, args.restore) + scripts.install("opendkim", config, args.upgrade, args.restore) + scripts.install("postfix", config, args.upgrade, args.restore) + scripts.install("dovecot", config, args.upgrade, args.restore) system.restart_service("cron") package.backend.restore_system() - utils.printcolor( - "Congratulations! You can enjoy Modoboa at https://{} (admin:password)" - .format(config.get("general", "hostname")), - utils.GREEN) - + if not args.restore: + utils.printcolor( + "Congratulations! You can enjoy Modoboa at https://{} (admin:password)" + .format(config.get("general", "hostname")), + utils.GREEN) + else: + utils.printcolor( + "Resotre complete! You can enjoy Modoboa at https://{} (same credentials as before)" + .format(config.get("general", "hostname")), + utils.GREEN) if __name__ == "__main__": main(sys.argv[1:]) From 15768c429e6eccd42a9eed21fe0845d252099ce3 Mon Sep 17 00:00:00 2001 From: Spitap Date: Tue, 26 Jul 2022 12:07:42 +0200 Subject: [PATCH 21/42] Restore workflow done --- modoboa_installer/scripts/__init__.py | 13 ++++++++++ modoboa_installer/scripts/amavis.py | 13 +++++++++- modoboa_installer/scripts/dovecot.py | 7 ++++++ modoboa_installer/scripts/modoboa.py | 13 ++++++++++ modoboa_installer/scripts/postwhite.py | 7 +++++- modoboa_installer/scripts/restore.py | 29 +++++++++++++++++++++++ modoboa_installer/scripts/spamassassin.py | 8 +++++++ run.py | 1 + 8 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 modoboa_installer/scripts/restore.py diff --git a/modoboa_installer/scripts/__init__.py b/modoboa_installer/scripts/__init__.py index 219d255..fa87c54 100644 --- a/modoboa_installer/scripts/__init__.py +++ b/modoboa_installer/scripts/__init__.py @@ -40,3 +40,16 @@ def backup(config, bashArg, nomail): except utils.FatalError as inst: utils.printcolor(u"{}".format(inst), utils.RED) sys.exit(1) + +def restore(restore): + """Restore instance""" + try: + script = importlib.import_module( + "modoboa_installer.scripts.restore") + except ImportError: + print("Error importing restore") + try: + getattr(script, "Restore")(restore) + except utils.FatalError as inst: + utils.printcolor(u"{}".format(inst), utils.RED) + sys.exit(1) \ No newline at end of file diff --git a/modoboa_installer/scripts/amavis.py b/modoboa_installer/scripts/amavis.py index 4fdc187..d679c98 100644 --- a/modoboa_installer/scripts/amavis.py +++ b/modoboa_installer/scripts/amavis.py @@ -1,7 +1,6 @@ """Amavis related functions.""" import os -import platform from .. import package from .. import utils @@ -43,6 +42,10 @@ class Amavis(base.Installer): def get_config_files(self): """Return appropriate config files.""" if package.backend.FORMAT == "deb": + amavisCustomConf = self.restore + "custom/99-custom" + if self.restore and os.path.isfile(amavisCustomConf): + utils.printcolor("Restoring custom Amavis configuration", utils.MAGENTA) + utils.copy_file(amavisCustomConf, self.config_dir) return [ "conf.d/05-node_id", "conf.d/15-content_filter_mode", "conf.d/50-user"] @@ -70,6 +73,14 @@ class Amavis(base.Installer): def get_sql_schema_path(self): """Return schema path.""" + if self.restore: + utils.printcolor("Trying to restore amavis database from backup", utils.MAGENTA) + amavisDbBackupPath = self.restore + "databases/amavis.sql" + if os.path.isfile(amavisDbBackupPath): + utils.printcolor("Amavis database backup found ! Restoring...", utils.GREEN) + return amavisDbBackupPath + utils.printcolor("Amavis database backup not found, creating empty database", utils.RED) + version = package.backend.get_installed_version("amavisd-new") if version is None: # Fallback to amavis... diff --git a/modoboa_installer/scripts/dovecot.py b/modoboa_installer/scripts/dovecot.py index 3830216..c5a3e45 100644 --- a/modoboa_installer/scripts/dovecot.py +++ b/modoboa_installer/scripts/dovecot.py @@ -3,6 +3,7 @@ import glob import os import pwd +import shutil from .. import database from .. import package @@ -87,6 +88,12 @@ class Dovecot(base.Installer): def post_run(self): """Additional tasks.""" + if self.restore and len(os.listdir(self.restore + "mails")) > 0: + utils.printcolor("Copying mail backup over dovecot directory", utils.MAGENTA) + shutil.copytree(self.restore+"mails/vmails", self.home_dir, dirs_exist_ok=True) + elif self.restore: + utils.printcolor("It seems that mails were not backed up, skipping mail restoration.", utils.MAGENTA) + if self.dbengine == "postgres": dbname = self.config.get("modoboa", "dbname") dbuser = self.config.get("modoboa", "dbuser") diff --git a/modoboa_installer/scripts/modoboa.py b/modoboa_installer/scripts/modoboa.py index adfd9e4..7caa571 100644 --- a/modoboa_installer/scripts/modoboa.py +++ b/modoboa_installer/scripts/modoboa.py @@ -1,5 +1,6 @@ """Modoboa related tasks.""" +from genericpath import isfile import json import os import pwd @@ -176,6 +177,18 @@ class Modoboa(base.Installer): self.backend.grant_access( self.config.get("amavis", "dbname"), self.dbuser) + + def get_sql_schema_path(self): + if self.restore: + utils.printcolor("Trying to restore modoboa database from backup", utils.MAGENTA) + modoboaDbBackupPath = self.restore + "databases/modoboa.sql" + if os.path.isfile(modoboaDbBackupPath): + utils.printcolor("Modoboa database backup found ! Restoring...", utils.GREEN) + return modoboaDbBackupPath + utils.printcolor("Modoboa database backup not found, creating empty database", utils.RED) + + return super().get_sql_schema_path()() + def get_packages(self): """Include extra packages if needed.""" packages = super(Modoboa, self).get_packages() diff --git a/modoboa_installer/scripts/postwhite.py b/modoboa_installer/scripts/postwhite.py index 239d2d3..c534205 100644 --- a/modoboa_installer/scripts/postwhite.py +++ b/modoboa_installer/scripts/postwhite.py @@ -46,6 +46,11 @@ class Postwhite(base.Installer): self.install_from_archive(SPF_TOOLS_REPOSITORY, install_dir) postw_dir = self.install_from_archive( POSTWHITE_REPOSITORY, install_dir) - utils.copy_file(os.path.join(postw_dir, "postwhite.conf"), "/etc") + postwhiteBackupConf = self.restore+"custom/postwhite.conf" + if self.restore and os.path.isfile(postwhiteBackupConf): + utils.printcolor("Restoring postwhite.conf backup.", utils.MAGENTA) + utils.copy_file(postwhiteBackupConf, "/etc") + else: + utils.copy_file(os.path.join(postw_dir, "postwhite.conf"), "/etc") postw_bin = os.path.join(postw_dir, "postwhite") utils.exec_cmd("{} /etc/postwhite.conf".format(postw_bin)) diff --git a/modoboa_installer/scripts/restore.py b/modoboa_installer/scripts/restore.py new file mode 100644 index 0000000..5a5e690 --- /dev/null +++ b/modoboa_installer/scripts/restore.py @@ -0,0 +1,29 @@ +from ctypes import util +import os +import sys +from .. import utils + +class Restore: + def __init__(self, restore): + """Restoring pre-check (backup integriety)""" + """REQUIRED : modoboa.sql""" + """OPTIONAL : mails/, custom/, amavis.sql, spamassassin.sql""" + """Only checking required""" + + try: + if not os.path.isdir(restore): + utils.printcolor("Provided path is not a directory !", utils.RED) + sys.exit(1) + except: + utils.printcolor("Provided path is not right...", utils.RED) + sys.exit(1) + + try: + if not os.path.isfile(restore+"databases/modoboa.sql"): + utils.printcolor(restore+"databases/modoboa.sql not found, please check your backup", utils.RED) + sys.exit(1) + except: + utils.printcolor(restore+"databases/modoboa.sql not found, please check your backup", utils.RED) + sys.exit(1) + + #Everything seems allright here, proceding... \ No newline at end of file diff --git a/modoboa_installer/scripts/spamassassin.py b/modoboa_installer/scripts/spamassassin.py index 49186f4..4466e79 100644 --- a/modoboa_installer/scripts/spamassassin.py +++ b/modoboa_installer/scripts/spamassassin.py @@ -25,6 +25,14 @@ class Spamassassin(base.Installer): def get_sql_schema_path(self): """Return SQL schema.""" + if self.restore: + utils.printcolor("Trying to restore spamassassin database from backup", utils.MAGENTA) + amavisDbBackupPath = self.restore + "databases/spamassassin.sql" + if os.path.isfile(amavisDbBackupPath): + utils.printcolor("Spamassassin database backup found ! Restoring...", utils.GREEN) + return amavisDbBackupPath + utils.printcolor("Spamassassin database backup not found, creating empty database", utils.RED) + if self.dbengine == "postgres": fname = "bayes_pg.sql" else: diff --git a/run.py b/run.py index 901bfdc..6fd95dc 100755 --- a/run.py +++ b/run.py @@ -156,6 +156,7 @@ def main(input_args): return elif args.restore: restore_disclamer() + scripts.restore(restore) else: installation_disclaimer(args, config) From 4a00590354df5b21793b75734c11dbfdca6ffa6a Mon Sep 17 00:00:00 2001 From: Spitap Date: Tue, 26 Jul 2022 16:20:03 +0200 Subject: [PATCH 22/42] fixed restore disclamer --- modoboa_installer/scripts/restore.py | 1 - run.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/modoboa_installer/scripts/restore.py b/modoboa_installer/scripts/restore.py index 5a5e690..ac814b7 100644 --- a/modoboa_installer/scripts/restore.py +++ b/modoboa_installer/scripts/restore.py @@ -1,4 +1,3 @@ -from ctypes import util import os import sys from .. import utils diff --git a/run.py b/run.py index 6fd95dc..8359a8a 100755 --- a/run.py +++ b/run.py @@ -52,7 +52,7 @@ def backup_disclamer(): " !! You should really transfer the backup somewhere else..." " Custom configuration (like to postfix) won't be saved.", utils.BLUE) -def restore_disclamer(path): +def restore_disclamer(): """Display restore disclamer. """ utils.printcolor( "You are about to restore a previous installation of Modoboa." @@ -156,7 +156,7 @@ def main(input_args): return elif args.restore: restore_disclamer() - scripts.restore(restore) + scripts.restore(args.restore) else: installation_disclaimer(args, config) From e7b6104195b21bd6fc990bdc70981ada21a238a1 Mon Sep 17 00:00:00 2001 From: Spitap Date: Tue, 26 Jul 2022 16:39:41 +0200 Subject: [PATCH 23/42] fixed install within class --- modoboa_installer/scripts/amavis.py | 4 ++-- modoboa_installer/scripts/spamassassin.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modoboa_installer/scripts/amavis.py b/modoboa_installer/scripts/amavis.py index d679c98..f6afaca 100644 --- a/modoboa_installer/scripts/amavis.py +++ b/modoboa_installer/scripts/amavis.py @@ -104,5 +104,5 @@ class Amavis(base.Installer): def post_run(self): """Additional tasks.""" - install("spamassassin", self.config, self.upgrade) - install("clamav", self.config, self.upgrade) + install("spamassassin", self.config, self.upgrade, self.restore) + install("clamav", self.config, self.upgrade, self.restore) diff --git a/modoboa_installer/scripts/spamassassin.py b/modoboa_installer/scripts/spamassassin.py index 4466e79..bd4a4d7 100644 --- a/modoboa_installer/scripts/spamassassin.py +++ b/modoboa_installer/scripts/spamassassin.py @@ -69,7 +69,7 @@ class Spamassassin(base.Installer): "pyzor --homedir {} discover".format(pw[5]), sudo_user=amavis_user, login=False ) - install("razor", self.config, self.upgrade) + install("razor", self.config, self.upgrade, self.restore) if utils.dist_name() in ["debian", "ubuntu"]: utils.exec_cmd( "perl -pi -e 's/^CRON=0/CRON=1/' /etc/cron.daily/spamassassin") From 4a7222bd2481a948b7535101608f78370fd567f4 Mon Sep 17 00:00:00 2001 From: Spitap Date: Tue, 26 Jul 2022 16:53:24 +0200 Subject: [PATCH 24/42] Fixed nginx call to uwsgi --- modoboa_installer/scripts/nginx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa_installer/scripts/nginx.py b/modoboa_installer/scripts/nginx.py index bfa0850..3537842 100644 --- a/modoboa_installer/scripts/nginx.py +++ b/modoboa_installer/scripts/nginx.py @@ -26,7 +26,7 @@ class Nginx(base.Installer): "app_instance_path": ( self.config.get(app, "instance_path")), "uwsgi_socket_path": ( - Uwsgi(self.config, self.upgrade).get_socket_path(app)) + Uwsgi(self.config, self.upgrade, self.restore).get_socket_path(app)) }) return context From 2077c94b52c94e55328da3421cf3f378cada0e8b Mon Sep 17 00:00:00 2001 From: Spitap Date: Tue, 26 Jul 2022 17:05:00 +0200 Subject: [PATCH 25/42] Fix amavis config file not copied to right location --- modoboa_installer/scripts/amavis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa_installer/scripts/amavis.py b/modoboa_installer/scripts/amavis.py index f6afaca..7220d4f 100644 --- a/modoboa_installer/scripts/amavis.py +++ b/modoboa_installer/scripts/amavis.py @@ -45,7 +45,7 @@ class Amavis(base.Installer): amavisCustomConf = self.restore + "custom/99-custom" if self.restore and os.path.isfile(amavisCustomConf): utils.printcolor("Restoring custom Amavis configuration", utils.MAGENTA) - utils.copy_file(amavisCustomConf, self.config_dir) + utils.copy_file(amavisCustomConf, self.config_dir+"/conf.d") return [ "conf.d/05-node_id", "conf.d/15-content_filter_mode", "conf.d/50-user"] From ee2ccf06474fb76c5b8fb6ae5b233eeed78319b3 Mon Sep 17 00:00:00 2001 From: Spitap Date: Wed, 27 Jul 2022 14:35:48 +0200 Subject: [PATCH 26/42] Fixed postfix install, added restore to readme --- README.rst | 11 +++++++++++ modoboa_installer/scripts/postfix.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index be0c9b6..a3a5412 100644 --- a/README.rst +++ b/README.rst @@ -133,6 +133,17 @@ If you want to disable mail backup:: This can be useful for larger instance +Restore mode +------------ + +An experimental restore mode is available. + +You can start the process as follows:: + + $ sudo ./run.py --restore /path/to/backup/directory/ + +Then wait for the process to finish + Change the generated hostname ----------------------------- diff --git a/modoboa_installer/scripts/postfix.py b/modoboa_installer/scripts/postfix.py index 607a905..fc1a8f2 100644 --- a/modoboa_installer/scripts/postfix.py +++ b/modoboa_installer/scripts/postfix.py @@ -97,4 +97,4 @@ class Postfix(base.Installer): utils.exec_cmd("postalias {}".format(aliases_file)) # Postwhite - install("postwhite", self.config, self.upgrade) + install("postwhite", self.config, self.upgrade, self.restore) From 563979a7ddc8bb5641f2349d59199e220c04c402 Mon Sep 17 00:00:00 2001 From: Spitap Date: Wed, 27 Jul 2022 15:51:22 +0200 Subject: [PATCH 27/42] fixed mail backup/restore --- modoboa_installer/scripts/backup.py | 4 ++-- modoboa_installer/scripts/dovecot.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index a3717b7..c2d4b99 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -31,7 +31,7 @@ class Backup(): def __init__(self, config, bashArg, nomail): self.config = config self.destinationPath = "" - self.BACKUPDIRECTORY = ["mails/", "custom/", "databases/"] + self.BACKUPDIRECTORY = ["custom/", "databases/"] self.nomail = nomail self.isBash = False self.bash = "" @@ -126,7 +126,7 @@ class Backup(): f" ({home_path}) seems not right...", utils.RED) else: - dst = self.destinationPath + self.BACKUPDIRECTORY[0] + "vmail/" + dst = self.destinationPath + "mails/" if os.path.exists(dst): shutil.rmtree(dst) diff --git a/modoboa_installer/scripts/dovecot.py b/modoboa_installer/scripts/dovecot.py index c5a3e45..77b6303 100644 --- a/modoboa_installer/scripts/dovecot.py +++ b/modoboa_installer/scripts/dovecot.py @@ -90,7 +90,11 @@ class Dovecot(base.Installer): """Additional tasks.""" if self.restore and len(os.listdir(self.restore + "mails")) > 0: utils.printcolor("Copying mail backup over dovecot directory", utils.MAGENTA) - shutil.copytree(self.restore+"mails/vmails", self.home_dir, dirs_exist_ok=True) + + if os.path.exists(self.home_dir): + shutil.rmtree(self.home_dir) + + shutil.copytree(self.restore+"mails/", self.home_dir) elif self.restore: utils.printcolor("It seems that mails were not backed up, skipping mail restoration.", utils.MAGENTA) From 70faa1c5cb3f1e156740d0789c66e0f72c929a08 Mon Sep 17 00:00:00 2001 From: Spitap Date: Wed, 27 Jul 2022 15:58:41 +0200 Subject: [PATCH 28/42] Fixed backupdir index --- modoboa_installer/scripts/backup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index c2d4b99..84e052f 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -142,7 +142,7 @@ class Backup(): Feel free to suggest to add others!""" utils.printcolor("Backing up some custom configuration...", utils.MAGENTA) - custom_path = self.destinationPath + self.BACKUPDIRECTORY[1] + custom_path = self.destinationPath + self.BACKUPDIRECTORY[0] """AMAVIS""" amavis_custom = "/etc/amavis/conf.d/99-custom" @@ -162,7 +162,7 @@ class Backup(): utils.printcolor("Backing up databases...", utils.MAGENTA) - dump_path = self.destinationPath + self.BACKUPDIRECTORY[2] + dump_path = self.destinationPath + self.BACKUPDIRECTORY[1] backend = database.get_backend(self.config) """Modoboa""" From e546d2cb23f36385d379aee8efe29a72c4981b23 Mon Sep 17 00:00:00 2001 From: Spitap Date: Wed, 27 Jul 2022 16:32:59 +0200 Subject: [PATCH 29/42] Better UX --- modoboa_installer/scripts/__init__.py | 5 +---- modoboa_installer/scripts/dovecot.py | 2 +- modoboa_installer/scripts/postwhite.py | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/modoboa_installer/scripts/__init__.py b/modoboa_installer/scripts/__init__.py index fa87c54..097b369 100644 --- a/modoboa_installer/scripts/__init__.py +++ b/modoboa_installer/scripts/__init__.py @@ -12,10 +12,7 @@ def install(appname, config, upgrade, restore): not config.getboolean(appname, "enabled")): return - if not restore: - utils.printcolor("Installing {}".format(appname), utils.MAGENTA) - else: - utils.printcolor("Restoring {}".format(appname), utils.MAGENTA) + utils.printcolor("Installing {}".format(appname), utils.MAGENTA) try: script = importlib.import_module( "modoboa_installer.scripts.{}".format(appname)) diff --git a/modoboa_installer/scripts/dovecot.py b/modoboa_installer/scripts/dovecot.py index 77b6303..cb3e05a 100644 --- a/modoboa_installer/scripts/dovecot.py +++ b/modoboa_installer/scripts/dovecot.py @@ -89,7 +89,7 @@ class Dovecot(base.Installer): def post_run(self): """Additional tasks.""" if self.restore and len(os.listdir(self.restore + "mails")) > 0: - utils.printcolor("Copying mail backup over dovecot directory", utils.MAGENTA) + utils.printcolor("Copying mail backup over dovecot directory", utils.GREEN) if os.path.exists(self.home_dir): shutil.rmtree(self.home_dir) diff --git a/modoboa_installer/scripts/postwhite.py b/modoboa_installer/scripts/postwhite.py index c534205..26ab378 100644 --- a/modoboa_installer/scripts/postwhite.py +++ b/modoboa_installer/scripts/postwhite.py @@ -48,7 +48,7 @@ class Postwhite(base.Installer): POSTWHITE_REPOSITORY, install_dir) postwhiteBackupConf = self.restore+"custom/postwhite.conf" if self.restore and os.path.isfile(postwhiteBackupConf): - utils.printcolor("Restoring postwhite.conf backup.", utils.MAGENTA) + utils.printcolor("Restoring postwhite.conf backup.", utils.GREEN) utils.copy_file(postwhiteBackupConf, "/etc") else: utils.copy_file(os.path.join(postw_dir, "postwhite.conf"), "/etc") From 53e3e3ec5853fe19de26e8de2d00adc8e5ee909d Mon Sep 17 00:00:00 2001 From: Spitap Date: Fri, 5 Aug 2022 15:20:11 +0200 Subject: [PATCH 30/42] Better UX, use of os to concatenate path --- modoboa_installer/scripts/amavis.py | 8 ++++---- modoboa_installer/scripts/backup.py | 19 ++++++++----------- modoboa_installer/scripts/dovecot.py | 10 ++++++++-- modoboa_installer/scripts/modoboa.py | 3 +-- modoboa_installer/scripts/postwhite.py | 4 ++-- modoboa_installer/scripts/restore.py | 7 ++++--- modoboa_installer/scripts/spamassassin.py | 2 +- run.py | 14 +++++++------- 8 files changed, 35 insertions(+), 32 deletions(-) diff --git a/modoboa_installer/scripts/amavis.py b/modoboa_installer/scripts/amavis.py index 7220d4f..3f19596 100644 --- a/modoboa_installer/scripts/amavis.py +++ b/modoboa_installer/scripts/amavis.py @@ -42,10 +42,10 @@ class Amavis(base.Installer): def get_config_files(self): """Return appropriate config files.""" if package.backend.FORMAT == "deb": - amavisCustomConf = self.restore + "custom/99-custom" + amavisCustomConf = os.path.join(self.restore, "custom/99-custom") if self.restore and os.path.isfile(amavisCustomConf): - utils.printcolor("Restoring custom Amavis configuration", utils.MAGENTA) - utils.copy_file(amavisCustomConf, self.config_dir+"/conf.d") + utils.copy_file(amavisCustomConf, os.path.join(self.config_dir, "/conf.d")) + utils.printcolor("Custom amavis configuration restored", utils.GREEN) return [ "conf.d/05-node_id", "conf.d/15-content_filter_mode", "conf.d/50-user"] @@ -75,7 +75,7 @@ class Amavis(base.Installer): """Return schema path.""" if self.restore: utils.printcolor("Trying to restore amavis database from backup", utils.MAGENTA) - amavisDbBackupPath = self.restore + "databases/amavis.sql" + amavisDbBackupPath = os.path.join(self.restore, "databases/amavis.sql") if os.path.isfile(amavisDbBackupPath): utils.printcolor("Amavis database backup found ! Restoring...", utils.GREEN) return amavisDbBackupPath diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index 84e052f..40cf20f 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -43,15 +43,12 @@ class Backup(): def preparePath(self): pw = pwd.getpwnam("root") for dir in self.BACKUPDIRECTORY: - utils.mkdir_safe(self.destinationPath + dir, stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3]) + utils.mkdir_safe(os.path.join(self.destinationPath,dir), + stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3]) def validatePath(self, path): """Check basic condition for backup directory""" - - if path[-1] != "/": - path += "/" - try : pathExists = os.path.exists(path) except: @@ -126,7 +123,7 @@ class Backup(): f" ({home_path}) seems not right...", utils.RED) else: - dst = self.destinationPath + "mails/" + dst = os.path.join(self.destinationPath, "mails/") if os.path.exists(dst): shutil.rmtree(dst) @@ -142,7 +139,7 @@ class Backup(): Feel free to suggest to add others!""" utils.printcolor("Backing up some custom configuration...", utils.MAGENTA) - custom_path = self.destinationPath + self.BACKUPDIRECTORY[0] + custom_path = os.path.join(self.destinationPath, self.BACKUPDIRECTORY[0]) """AMAVIS""" amavis_custom = "/etc/amavis/conf.d/99-custom" @@ -162,14 +159,14 @@ class Backup(): utils.printcolor("Backing up databases...", utils.MAGENTA) - dump_path = self.destinationPath + self.BACKUPDIRECTORY[1] + dump_path = os.path.join(self.destinationPath, self.BACKUPDIRECTORY[1]) backend = database.get_backend(self.config) """Modoboa""" dbname = self.config.get("modoboa", "dbname") dbuser = self.config.get("modoboa", "dbuser") dbpasswd = self.config.get("modoboa", "dbpassword") - backend.dumpDatabase(dbname, dbuser, dbpasswd, dump_path+"modoboa.sql") + backend.dumpDatabase(dbname, dbuser, dbpasswd, os.path.join(dump_path,"modoboa.sql")) """Amavis""" if (self.config.has_option("amavis", "enabled") and @@ -177,7 +174,7 @@ class Backup(): dbname = self.config.get("amavis", "dbname") dbuser = self.config.get("amavis", "dbuser") dbpasswd = self.config.get("amavis", "dbpassword") - backend.dumpDatabase(dbname, dbuser, dbpasswd, dump_path+"amavis.sql") + backend.dumpDatabase(dbname, dbuser, dbpasswd, os.path.join(dump_path,"amavis.sql")) """SpamAssassin""" if (self.config.has_option("spamassassin", "enabled") and @@ -185,7 +182,7 @@ class Backup(): dbname = self.config.get("spamassassin", "dbname") dbuser = self.config.get("spamassassin", "dbuser") dbpasswd = self.config.get("spamassassin", "dbpassword") - backend.dumpDatabase(dbname, dbuser, dbpasswd, dump_path+"spamassassin.sql") + backend.dumpDatabase(dbname, dbuser, dbpasswd, os.path.join(dump_path,"spamassassin.sql")) def backupCompletion(self): utils.printcolor("Backup process done, your backup is availible here:" diff --git a/modoboa_installer/scripts/dovecot.py b/modoboa_installer/scripts/dovecot.py index cb3e05a..2024bcb 100644 --- a/modoboa_installer/scripts/dovecot.py +++ b/modoboa_installer/scripts/dovecot.py @@ -88,13 +88,19 @@ class Dovecot(base.Installer): def post_run(self): """Additional tasks.""" - if self.restore and len(os.listdir(self.restore + "mails")) > 0: + mail_dir = os.path.join(self.restore, "mails/") + if self.restore and len(os.listdir(mail_dir)) > 0: utils.printcolor("Copying mail backup over dovecot directory", utils.GREEN) if os.path.exists(self.home_dir): shutil.rmtree(self.home_dir) - shutil.copytree(self.restore+"mails/", self.home_dir) + shutil.copytree(mail_dir, self.home_dir) + #Resetting permission for vmail + for dirpath, dirnames, filenames in os.walk(self.home_dir): + shutil.chown(dirpath, self.user, self.user) + for filename in filenames: + shutil.chown(os.path.join(dirpath, filename), self.user, self.user) elif self.restore: utils.printcolor("It seems that mails were not backed up, skipping mail restoration.", utils.MAGENTA) diff --git a/modoboa_installer/scripts/modoboa.py b/modoboa_installer/scripts/modoboa.py index 7caa571..dadbf4f 100644 --- a/modoboa_installer/scripts/modoboa.py +++ b/modoboa_installer/scripts/modoboa.py @@ -1,6 +1,5 @@ """Modoboa related tasks.""" -from genericpath import isfile import json import os import pwd @@ -181,7 +180,7 @@ class Modoboa(base.Installer): def get_sql_schema_path(self): if self.restore: utils.printcolor("Trying to restore modoboa database from backup", utils.MAGENTA) - modoboaDbBackupPath = self.restore + "databases/modoboa.sql" + modoboaDbBackupPath = os.path.join(self.restore, "databases/modoboa.sql") if os.path.isfile(modoboaDbBackupPath): utils.printcolor("Modoboa database backup found ! Restoring...", utils.GREEN) return modoboaDbBackupPath diff --git a/modoboa_installer/scripts/postwhite.py b/modoboa_installer/scripts/postwhite.py index 26ab378..49c4155 100644 --- a/modoboa_installer/scripts/postwhite.py +++ b/modoboa_installer/scripts/postwhite.py @@ -46,10 +46,10 @@ class Postwhite(base.Installer): self.install_from_archive(SPF_TOOLS_REPOSITORY, install_dir) postw_dir = self.install_from_archive( POSTWHITE_REPOSITORY, install_dir) - postwhiteBackupConf = self.restore+"custom/postwhite.conf" + postwhiteBackupConf = os.path.join(self.restore, "custom/postwhite.conf") if self.restore and os.path.isfile(postwhiteBackupConf): - utils.printcolor("Restoring postwhite.conf backup.", utils.GREEN) utils.copy_file(postwhiteBackupConf, "/etc") + utils.printcolor("postwhite.conf restored from backup", utils.GREEN) else: utils.copy_file(os.path.join(postw_dir, "postwhite.conf"), "/etc") postw_bin = os.path.join(postw_dir, "postwhite") diff --git a/modoboa_installer/scripts/restore.py b/modoboa_installer/scripts/restore.py index ac814b7..028abe7 100644 --- a/modoboa_installer/scripts/restore.py +++ b/modoboa_installer/scripts/restore.py @@ -18,11 +18,12 @@ class Restore: sys.exit(1) try: - if not os.path.isfile(restore+"databases/modoboa.sql"): - utils.printcolor(restore+"databases/modoboa.sql not found, please check your backup", utils.RED) + modobasql_file = os.path.join(restore, "databases/modoboa.sql") + if not os.path.isfile(modobasql_file): + utils.printcolor(modobasql_file +" not found, please check your backup", utils.RED) sys.exit(1) except: - utils.printcolor(restore+"databases/modoboa.sql not found, please check your backup", utils.RED) + utils.printcolor(modobasql_file + " not found, please check your backup", utils.RED) sys.exit(1) #Everything seems allright here, proceding... \ No newline at end of file diff --git a/modoboa_installer/scripts/spamassassin.py b/modoboa_installer/scripts/spamassassin.py index bd4a4d7..adf1242 100644 --- a/modoboa_installer/scripts/spamassassin.py +++ b/modoboa_installer/scripts/spamassassin.py @@ -27,7 +27,7 @@ class Spamassassin(base.Installer): """Return SQL schema.""" if self.restore: utils.printcolor("Trying to restore spamassassin database from backup", utils.MAGENTA) - amavisDbBackupPath = self.restore + "databases/spamassassin.sql" + amavisDbBackupPath = os.path.join(self.restore, "databases/spamassassin.sql") if os.path.isfile(amavisDbBackupPath): utils.printcolor("Spamassassin database backup found ! Restoring...", utils.GREEN) return amavisDbBackupPath diff --git a/run.py b/run.py index 8359a8a..31608d8 100755 --- a/run.py +++ b/run.py @@ -3,8 +3,7 @@ """An installer for Modoboa.""" import argparse -from ast import parse -from ctypes import util +import os try: import configparser except ImportError: @@ -56,7 +55,7 @@ def restore_disclamer(): """Display restore disclamer. """ utils.printcolor( "You are about to restore a previous installation of Modoboa." - "Is a new version has been released in between, please update your database !", + "If a new version has been released in between, please update your database !", utils.BLUE) def main(input_args): @@ -123,10 +122,11 @@ def main(input_args): isRestoring = False if args.restore != None: isRestoring = True - if args.restore[-1] != "/": - args.restore += "/" - args.configfile = args.restore + "installer.cfg" - + args.configfile = os.path.join(args.restore, "installer.cfg") + if not os.path.exists(args.configfile): + utils.printcolor("installer.cfg from backup not found!", utils.RED) + sys.exit(1) + utils.printcolor("Welcome to Modoboa installer!\n", utils.GREEN) wasConfigFileAlreadyThere = utils.check_config_file(args.configfile, args.interactive, args.upgrade, args.backup, isRestoring) From b0d56b3989382aa6075a9719b1f9e8d50842b6dd Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 15 Sep 2022 11:32:57 +0200 Subject: [PATCH 31/42] PEP formating --- modoboa_installer/scripts/__init__.py | 6 +- modoboa_installer/scripts/amavis.py | 20 +++-- modoboa_installer/scripts/backup.py | 91 ++++++++++++----------- modoboa_installer/scripts/dovecot.py | 13 ++-- modoboa_installer/scripts/modoboa.py | 13 ++-- modoboa_installer/scripts/postwhite.py | 6 +- modoboa_installer/scripts/restore.py | 12 ++- modoboa_installer/scripts/spamassassin.py | 12 ++- modoboa_installer/utils.py | 4 +- run.py | 29 +++++--- 10 files changed, 120 insertions(+), 86 deletions(-) diff --git a/modoboa_installer/scripts/__init__.py b/modoboa_installer/scripts/__init__.py index 097b369..15201c4 100644 --- a/modoboa_installer/scripts/__init__.py +++ b/modoboa_installer/scripts/__init__.py @@ -25,11 +25,12 @@ def install(appname, config, upgrade, restore): utils.printcolor(u"{}".format(inst), utils.RED) sys.exit(1) + def backup(config, bashArg, nomail): """Backup instance""" try: script = importlib.import_module( - "modoboa_installer.scripts.backup") + "modoboa_installer.scripts.backup") except ImportError: print("Error importing backup") try: @@ -38,6 +39,7 @@ def backup(config, bashArg, nomail): utils.printcolor(u"{}".format(inst), utils.RED) sys.exit(1) + def restore(restore): """Restore instance""" try: @@ -49,4 +51,4 @@ def restore(restore): getattr(script, "Restore")(restore) except utils.FatalError as inst: utils.printcolor(u"{}".format(inst), utils.RED) - sys.exit(1) \ No newline at end of file + sys.exit(1) diff --git a/modoboa_installer/scripts/amavis.py b/modoboa_installer/scripts/amavis.py index 3f19596..3b15c41 100644 --- a/modoboa_installer/scripts/amavis.py +++ b/modoboa_installer/scripts/amavis.py @@ -44,8 +44,10 @@ class Amavis(base.Installer): if package.backend.FORMAT == "deb": amavisCustomConf = os.path.join(self.restore, "custom/99-custom") if self.restore and os.path.isfile(amavisCustomConf): - utils.copy_file(amavisCustomConf, os.path.join(self.config_dir, "/conf.d")) - utils.printcolor("Custom amavis configuration restored", utils.GREEN) + utils.copy_file(amavisCustomConf, os.path.join( + self.config_dir, "/conf.d")) + utils.printcolor( + "Custom amavis configuration restored", utils.GREEN) return [ "conf.d/05-node_id", "conf.d/15-content_filter_mode", "conf.d/50-user"] @@ -74,12 +76,16 @@ class Amavis(base.Installer): def get_sql_schema_path(self): """Return schema path.""" if self.restore: - utils.printcolor("Trying to restore amavis database from backup", utils.MAGENTA) - amavisDbBackupPath = os.path.join(self.restore, "databases/amavis.sql") + utils.printcolor( + "Trying to restore amavis database from backup", utils.MAGENTA) + amavisDbBackupPath = os.path.join( + self.restore, "databases/amavis.sql") if os.path.isfile(amavisDbBackupPath): - utils.printcolor("Amavis database backup found ! Restoring...", utils.GREEN) + utils.printcolor( + "Amavis database backup found ! Restoring...", utils.GREEN) return amavisDbBackupPath - utils.printcolor("Amavis database backup not found, creating empty database", utils.RED) + utils.printcolor( + "Amavis database backup not found, creating empty database", utils.RED) version = package.backend.get_installed_version("amavisd-new") if version is None: @@ -94,7 +100,7 @@ class Amavis(base.Installer): path = self.get_file_path( "amavis_{}_{}.sql".format(self.dbengine, version)) if not os.path.exists(path): - raise utils.FatalError("Failed to find amavis database schema") + raise utils.FatalError("Failed to find amavis database schema") return path def pre_run(self): diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index 40cf20f..7984372 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -10,23 +10,24 @@ import sys from .. import database from .. import utils -#TODO: have version of each modoboa componenents saved into the config file to restore the same version +# TODO: have version of each modoboa componenents saved into the config file to restore the same version + class Backup(): -#Backup structure ( {optional} ): -#{{backup_folder}} -#|| -#||--> installer.cfg -#||--> custom -# |--> { (copy of) /etc/amavis/conf.d/99-custom } -# |--> { (copy of) /etc/postfix/custom_whitelist.cidr } -#||--> databases -# |--> modoboa.sql -# |--> { amavis.sql } -# |--> { spamassassin.sql } -#||--> mails -# |--> vmails + # Backup structure ( {optional} ): + # {{backup_folder}} + # || + # ||--> installer.cfg + # ||--> custom + # |--> { (copy of) /etc/amavis/conf.d/99-custom } + # |--> { (copy of) /etc/postfix/custom_whitelist.cidr } + # ||--> databases + # |--> modoboa.sql + # |--> { amavis.sql } + # |--> { spamassassin.sql } + # ||--> mails + # |--> vmails def __init__(self, config, bashArg, nomail): self.config = config @@ -39,39 +40,40 @@ class Backup(): self.isBash = True self.bash = bashArg - def preparePath(self): pw = pwd.getpwnam("root") for dir in self.BACKUPDIRECTORY: - utils.mkdir_safe(os.path.join(self.destinationPath,dir), - stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3]) - + utils.mkdir_safe(os.path.join(self.destinationPath, dir), + stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3]) def validatePath(self, path): """Check basic condition for backup directory""" - try : + try: pathExists = os.path.exists(path) except: print("Provided path is not recognized...") return False - + if pathExists and os.path.isfile(path): - print("Error, you provided a file instead of a directory!") - return False + print("Error, you provided a file instead of a directory!") + return False if not pathExists: if not self.isBash: - createDir = input(f"\"{path}\" doesn't exists, would you like to create it ? [Y/n]\n").lower() + createDir = input( + f"\"{path}\" doesn't exists, would you like to create it ? [Y/n]\n").lower() if self.isBash or (not self.isBash and (createDir == "y" or createDir == "yes")): pw = pwd.getpwnam("root") - utils.mkdir_safe(path, stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3]) + utils.mkdir_safe(path, stat.S_IRWXU | + stat.S_IRWXG, pw[2], pw[3]) else: return False if len(os.listdir(path)) != 0: if not self.isBash: - delDir = input("Warning : backup folder is not empty, it will be purged if you continue... [Y/n]\n").lower() + delDir = input( + "Warning : backup folder is not empty, it will be purged if you continue... [Y/n]\n").lower() if self.isBash or (not self.isBash and (delDir == "y" or delDir == "yes")): shutil.rmtree(path) else: @@ -82,7 +84,6 @@ class Backup(): self.preparePath() return True - def setPath(self): """Setup backup directory""" if self.isBash: @@ -90,7 +91,7 @@ class Backup(): date = datetime.datetime.now().strftime("%m_%d_%Y_%H_%M") path = f"/modoboa_backup/backup_{date}/" self.validatePath(path) - else : + else: validate = self.validatePath(self.bash) if not validate: print("provided bash is not right, exiting...") @@ -102,16 +103,15 @@ class Backup(): print("Enter backup path, please provide an empty folder.") print("CTRL+C to cancel") user_value = utils.user_input("-> ") - def backupConfigFile(self): utils.copy_file("installer.cfg", self.destinationPath) - def backupMails(self): if self.nomail: - utils.printcolor("Skipping mail backup, no-mail argument provided", utils.MAGENTA) + utils.printcolor( + "Skipping mail backup, no-mail argument provided", utils.MAGENTA) return utils.printcolor("Backing up mails", utils.MAGENTA) @@ -120,26 +120,27 @@ class Backup(): if not os.path.exists(home_path) or os.path.isfile(home_path): utils.printcolor("Error backing up Email, provided path " - f" ({home_path}) seems not right...", utils.RED) - + f" ({home_path}) seems not right...", utils.RED) + else: dst = os.path.join(self.destinationPath, "mails/") if os.path.exists(dst): shutil.rmtree(dst) - + shutil.copytree(home_path, dst) utils.printcolor("Mail backup complete!", utils.GREEN) - def backupCustomConfig(self): """Custom config : - Amavis : /etc/amavis/conf.d/99-custom - Postwhite : /etc/postwhite.conf Feel free to suggest to add others!""" - utils.printcolor("Backing up some custom configuration...", utils.MAGENTA) + utils.printcolor( + "Backing up some custom configuration...", utils.MAGENTA) - custom_path = os.path.join(self.destinationPath, self.BACKUPDIRECTORY[0]) + custom_path = os.path.join( + self.destinationPath, self.BACKUPDIRECTORY[0]) """AMAVIS""" amavis_custom = "/etc/amavis/conf.d/99-custom" @@ -153,7 +154,6 @@ class Backup(): utils.copy_file(postswhite_custom, custom_path) utils.printcolor("Postwhite configuration saved!", utils.GREEN) - def backupDBs(self): """Backing up databases""" @@ -166,27 +166,30 @@ class Backup(): dbname = self.config.get("modoboa", "dbname") dbuser = self.config.get("modoboa", "dbuser") dbpasswd = self.config.get("modoboa", "dbpassword") - backend.dumpDatabase(dbname, dbuser, dbpasswd, os.path.join(dump_path,"modoboa.sql")) + backend.dumpDatabase(dbname, dbuser, dbpasswd, + os.path.join(dump_path, "modoboa.sql")) """Amavis""" if (self.config.has_option("amavis", "enabled") and - self.config.getboolean("amavis", "enabled")): + self.config.getboolean("amavis", "enabled")): dbname = self.config.get("amavis", "dbname") dbuser = self.config.get("amavis", "dbuser") dbpasswd = self.config.get("amavis", "dbpassword") - backend.dumpDatabase(dbname, dbuser, dbpasswd, os.path.join(dump_path,"amavis.sql")) + backend.dumpDatabase(dbname, dbuser, dbpasswd, + os.path.join(dump_path, "amavis.sql")) """SpamAssassin""" if (self.config.has_option("spamassassin", "enabled") and - self.config.getboolean("spamassassin", "enabled")): + self.config.getboolean("spamassassin", "enabled")): dbname = self.config.get("spamassassin", "dbname") dbuser = self.config.get("spamassassin", "dbuser") dbpasswd = self.config.get("spamassassin", "dbpassword") - backend.dumpDatabase(dbname, dbuser, dbpasswd, os.path.join(dump_path,"spamassassin.sql")) + backend.dumpDatabase(dbname, dbuser, dbpasswd, + os.path.join(dump_path, "spamassassin.sql")) def backupCompletion(self): utils.printcolor("Backup process done, your backup is availible here:" - f"--> {self.destinationPath}", utils.GREEN) + f"--> {self.destinationPath}", utils.GREEN) def run(self): self.setPath() @@ -195,5 +198,3 @@ class Backup(): self.backupCustomConfig() self.backupDBs() self.backupCompletion() - - diff --git a/modoboa_installer/scripts/dovecot.py b/modoboa_installer/scripts/dovecot.py index 2024bcb..241ee7e 100644 --- a/modoboa_installer/scripts/dovecot.py +++ b/modoboa_installer/scripts/dovecot.py @@ -90,19 +90,22 @@ class Dovecot(base.Installer): """Additional tasks.""" mail_dir = os.path.join(self.restore, "mails/") if self.restore and len(os.listdir(mail_dir)) > 0: - utils.printcolor("Copying mail backup over dovecot directory", utils.GREEN) - + utils.printcolor( + "Copying mail backup over dovecot directory", utils.GREEN) + if os.path.exists(self.home_dir): shutil.rmtree(self.home_dir) shutil.copytree(mail_dir, self.home_dir) - #Resetting permission for vmail + # Resetting permission for vmail for dirpath, dirnames, filenames in os.walk(self.home_dir): shutil.chown(dirpath, self.user, self.user) for filename in filenames: - shutil.chown(os.path.join(dirpath, filename), self.user, self.user) + shutil.chown(os.path.join(dirpath, filename), + self.user, self.user) elif self.restore: - utils.printcolor("It seems that mails were not backed up, skipping mail restoration.", utils.MAGENTA) + utils.printcolor( + "It seems that mails were not backed up, skipping mail restoration.", utils.MAGENTA) if self.dbengine == "postgres": dbname = self.config.get("modoboa", "dbname") diff --git a/modoboa_installer/scripts/modoboa.py b/modoboa_installer/scripts/modoboa.py index dadbf4f..3f1f164 100644 --- a/modoboa_installer/scripts/modoboa.py +++ b/modoboa_installer/scripts/modoboa.py @@ -176,15 +176,18 @@ class Modoboa(base.Installer): self.backend.grant_access( self.config.get("amavis", "dbname"), self.dbuser) - def get_sql_schema_path(self): if self.restore: - utils.printcolor("Trying to restore modoboa database from backup", utils.MAGENTA) - modoboaDbBackupPath = os.path.join(self.restore, "databases/modoboa.sql") + utils.printcolor( + "Trying to restore modoboa database from backup", utils.MAGENTA) + modoboaDbBackupPath = os.path.join( + self.restore, "databases/modoboa.sql") if os.path.isfile(modoboaDbBackupPath): - utils.printcolor("Modoboa database backup found ! Restoring...", utils.GREEN) + utils.printcolor( + "Modoboa database backup found ! Restoring...", utils.GREEN) return modoboaDbBackupPath - utils.printcolor("Modoboa database backup not found, creating empty database", utils.RED) + utils.printcolor( + "Modoboa database backup not found, creating empty database", utils.RED) return super().get_sql_schema_path()() diff --git a/modoboa_installer/scripts/postwhite.py b/modoboa_installer/scripts/postwhite.py index 49c4155..d6e6a67 100644 --- a/modoboa_installer/scripts/postwhite.py +++ b/modoboa_installer/scripts/postwhite.py @@ -46,10 +46,12 @@ class Postwhite(base.Installer): self.install_from_archive(SPF_TOOLS_REPOSITORY, install_dir) postw_dir = self.install_from_archive( POSTWHITE_REPOSITORY, install_dir) - postwhiteBackupConf = os.path.join(self.restore, "custom/postwhite.conf") + postwhiteBackupConf = os.path.join( + self.restore, "custom/postwhite.conf") if self.restore and os.path.isfile(postwhiteBackupConf): utils.copy_file(postwhiteBackupConf, "/etc") - utils.printcolor("postwhite.conf restored from backup", utils.GREEN) + utils.printcolor( + "postwhite.conf restored from backup", utils.GREEN) else: utils.copy_file(os.path.join(postw_dir, "postwhite.conf"), "/etc") postw_bin = os.path.join(postw_dir, "postwhite") diff --git a/modoboa_installer/scripts/restore.py b/modoboa_installer/scripts/restore.py index 028abe7..7f88520 100644 --- a/modoboa_installer/scripts/restore.py +++ b/modoboa_installer/scripts/restore.py @@ -2,6 +2,7 @@ import os import sys from .. import utils + class Restore: def __init__(self, restore): """Restoring pre-check (backup integriety)""" @@ -11,7 +12,8 @@ class Restore: try: if not os.path.isdir(restore): - utils.printcolor("Provided path is not a directory !", utils.RED) + utils.printcolor( + "Provided path is not a directory !", utils.RED) sys.exit(1) except: utils.printcolor("Provided path is not right...", utils.RED) @@ -20,10 +22,12 @@ class Restore: try: modobasql_file = os.path.join(restore, "databases/modoboa.sql") if not os.path.isfile(modobasql_file): - utils.printcolor(modobasql_file +" not found, please check your backup", utils.RED) + utils.printcolor( + modobasql_file + " not found, please check your backup", utils.RED) sys.exit(1) except: - utils.printcolor(modobasql_file + " not found, please check your backup", utils.RED) + utils.printcolor(modobasql_file + + " not found, please check your backup", utils.RED) sys.exit(1) - #Everything seems allright here, proceding... \ No newline at end of file + # Everything seems allright here, proceding... diff --git a/modoboa_installer/scripts/spamassassin.py b/modoboa_installer/scripts/spamassassin.py index adf1242..00843ce 100644 --- a/modoboa_installer/scripts/spamassassin.py +++ b/modoboa_installer/scripts/spamassassin.py @@ -26,12 +26,16 @@ class Spamassassin(base.Installer): def get_sql_schema_path(self): """Return SQL schema.""" if self.restore: - utils.printcolor("Trying to restore spamassassin database from backup", utils.MAGENTA) - amavisDbBackupPath = os.path.join(self.restore, "databases/spamassassin.sql") + utils.printcolor( + "Trying to restore spamassassin database from backup", utils.MAGENTA) + amavisDbBackupPath = os.path.join( + self.restore, "databases/spamassassin.sql") if os.path.isfile(amavisDbBackupPath): - utils.printcolor("Spamassassin database backup found ! Restoring...", utils.GREEN) + utils.printcolor( + "Spamassassin database backup found ! Restoring...", utils.GREEN) return amavisDbBackupPath - utils.printcolor("Spamassassin database backup not found, creating empty database", utils.RED) + utils.printcolor( + "Spamassassin database backup not found, creating empty database", utils.RED) if self.dbengine == "postgres": fname = "bayes_pg.sql" diff --git a/modoboa_installer/utils.py b/modoboa_installer/utils.py index 48260d2..babea78 100644 --- a/modoboa_installer/utils.py +++ b/modoboa_installer/utils.py @@ -106,12 +106,14 @@ def mkdir(path, mode, uid, gid): os.chmod(path, mode) os.chown(path, uid, gid) + def mkdir_safe(path, mode, uid, gid): """Create a directory. Safe way (-p)""" if not os.path.exists(path): os.makedirs(os.path.abspath(path), mode) mkdir(path, mode, uid, gid) + def make_password(length=16): """Create a random password.""" return "".join( @@ -188,7 +190,7 @@ def check_config_file(dest, interactive=False, upgrade=False, backup=False, rest "You cannot restore an existing installation without a " f"configuration file. (file : {dest} has not been found...", RED) sys.exit(1) - + printcolor( "Configuration file {} not found, creating new one." .format(dest), YELLOW) diff --git a/run.py b/run.py index 31608d8..33939ac 100755 --- a/run.py +++ b/run.py @@ -17,6 +17,7 @@ from modoboa_installer import ssl from modoboa_installer import system from modoboa_installer import utils + def installation_disclaimer(args, config): """Display installation disclaimer.""" hostname = config.get("general", "hostname") @@ -44,6 +45,7 @@ def upgrade_disclaimer(config): " will be impacted:", utils.BLUE ) + def backup_disclamer(): """Display backup disclamer. """ utils.printcolor( @@ -51,6 +53,7 @@ def backup_disclamer(): " !! You should really transfer the backup somewhere else..." " Custom configuration (like to postfix) won't be saved.", utils.BLUE) + def restore_disclamer(): """Display restore disclamer. """ utils.printcolor( @@ -58,6 +61,7 @@ def restore_disclamer(): "If a new version has been released in between, please update your database !", utils.BLUE) + def main(input_args): """Install process.""" parser = argparse.ArgumentParser() @@ -96,7 +100,7 @@ def main(input_args): parser.add_argument( "--no-mail", action="store_true", default=False, help="Disable mail backup (save space)") - parser.add_argument( + parser.add_argument( "--restore", type=str, metavar="path", help="Restore a previously backup up modoboa instance on a NEW machine. You Must provide backup directory" ) @@ -109,16 +113,17 @@ def main(input_args): if not args.backup and (args.bash != None or args.sbash or args.no_mail): utils.printcolor("You provided --bash or --sbash without --backup, " - "if you want to do a backup, please provide --backup!", utils.RED) + "if you want to do a backup, please provide --backup!", utils.RED) return - elif args.bash != None and args.sbash : + elif args.bash != None and args.sbash: utils.printcolor("You provided --bash PATH and --sbash at the same time. " - "Please provided only one!", utils.RED) + "Please provided only one!", utils.RED) return elif args.bash == "TRUE": - utils.printcolor("You can't pick *TRUE* as backup directory !", utils.RED) - - #Restore prep + utils.printcolor( + "You can't pick *TRUE* as backup directory !", utils.RED) + + # Restore prep isRestoring = False if args.restore != None: isRestoring = True @@ -126,10 +131,11 @@ def main(input_args): if not os.path.exists(args.configfile): utils.printcolor("installer.cfg from backup not found!", utils.RED) sys.exit(1) - + utils.printcolor("Welcome to Modoboa installer!\n", utils.GREEN) - wasConfigFileAlreadyThere = utils.check_config_file(args.configfile, args.interactive, args.upgrade, args.backup, isRestoring) - + wasConfigFileAlreadyThere = utils.check_config_file( + args.configfile, args.interactive, args.upgrade, args.backup, isRestoring) + if args.stop_after_configfile_check or (not wasConfigFileAlreadyThere and args.backup): return @@ -159,7 +165,7 @@ def main(input_args): scripts.restore(args.restore) else: installation_disclaimer(args, config) - + # Show concerned components components = [] for section in config.sections(): @@ -207,5 +213,6 @@ def main(input_args): .format(config.get("general", "hostname")), utils.GREEN) + if __name__ == "__main__": main(sys.argv[1:]) From f3811b4b39c2f9e11e29d80895968402a17c42be Mon Sep 17 00:00:00 2001 From: Spitap Date: Mon, 19 Sep 2022 14:59:43 +0200 Subject: [PATCH 32/42] refactoring --- modoboa_installer/config_dict_template.py | 9 ++ modoboa_installer/database.py | 12 +- modoboa_installer/scripts/__init__.py | 27 ++-- modoboa_installer/scripts/amavis.py | 6 +- modoboa_installer/scripts/backup.py | 189 ++++++++++------------ modoboa_installer/scripts/base.py | 4 + modoboa_installer/scripts/restore.py | 33 ++-- run.py | 66 ++++---- 8 files changed, 161 insertions(+), 185 deletions(-) diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index fe04277..43bf7c6 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -439,4 +439,13 @@ ConfigDictTemplate = [ ] }, + { + "name": "backup", + "values": [ + { + "option": "default_path", + "default": "./modoboa_backup/" + } + ] + } ] diff --git a/modoboa_installer/database.py b/modoboa_installer/database.py index b589954..bb0ec2c 100644 --- a/modoboa_installer/database.py +++ b/modoboa_installer/database.py @@ -141,9 +141,10 @@ class PostgreSQL(Database): self.dbhost, dbname, dbuser, path) utils.exec_cmd(cmd, sudo_user=self.dbuser) - def dumpDatabase(self, dbname, dbuser, dbpassword, path): - """Dump DB to SQL file""" - self._pgpass_done = False #Reset pgpass since we backup multiple db (different secret set) + def dump_database(self, dbname, dbuser, dbpassword, path): + """Dump DB to SQL file.""" + # Reset pgpass since we backup multiple db (different secret set) + self._pgpass_done = False self._setup_pgpass(dbname, dbuser, dbpassword) cmd = "pg_dump -h {} -d {} -U {} -O -w > {}".format( self.dbhost, dbname, dbuser, path) @@ -258,8 +259,9 @@ class MySQL(Database): "mysql -h {} -u {} -p{} {} < {}".format( self.dbhost, dbuser, dbpassword, dbname, path) ) - def dumpDatabase(self, dbname, dbuser, dbpassword, path): - """Dump DB to SQL file""" + + def dump_database(self, dbname, dbuser, dbpassword, path): + """Dump DB to SQL file.""" cmd = "mysqldump -h {} -u {} -p{} {} > {}".format( self.dbhost, dbuser, dbpassword, dbname, path) utils.exec_cmd(cmd, sudo_user=self.dbuser) diff --git a/modoboa_installer/scripts/__init__.py b/modoboa_installer/scripts/__init__.py index 15201c4..94efdcb 100644 --- a/modoboa_installer/scripts/__init__.py +++ b/modoboa_installer/scripts/__init__.py @@ -26,29 +26,20 @@ def install(appname, config, upgrade, restore): sys.exit(1) -def backup(config, bashArg, nomail): +def backup(config, silent_backup, backup_path, nomail): """Backup instance""" + script = importlib.import_module( + "modoboa_installer.scripts.backup") try: - script = importlib.import_module( - "modoboa_installer.scripts.backup") - except ImportError: - print("Error importing backup") - try: - getattr(script, "Backup")(config, bashArg, nomail).run() + getattr(script, "Backup")( + config, silent_backup, backup_path, nomail).run() except utils.FatalError as inst: utils.printcolor(u"{}".format(inst), utils.RED) sys.exit(1) -def restore(restore): +def restore_prep(restore): """Restore instance""" - try: - script = importlib.import_module( - "modoboa_installer.scripts.restore") - except ImportError: - print("Error importing restore") - try: - getattr(script, "Restore")(restore) - except utils.FatalError as inst: - utils.printcolor(u"{}".format(inst), utils.RED) - sys.exit(1) + script = importlib.import_module( + "modoboa_installer.scripts.restore") + getattr(script, "Restore")(restore) diff --git a/modoboa_installer/scripts/amavis.py b/modoboa_installer/scripts/amavis.py index 3b15c41..23a508d 100644 --- a/modoboa_installer/scripts/amavis.py +++ b/modoboa_installer/scripts/amavis.py @@ -78,12 +78,12 @@ class Amavis(base.Installer): if self.restore: utils.printcolor( "Trying to restore amavis database from backup", utils.MAGENTA) - amavisDbBackupPath = os.path.join( + amavis_db_backup_path = os.path.join( self.restore, "databases/amavis.sql") - if os.path.isfile(amavisDbBackupPath): + if os.path.isfile(amavis_db_backup_path): utils.printcolor( "Amavis database backup found ! Restoring...", utils.GREEN) - return amavisDbBackupPath + return amavis_db_backup_path utils.printcolor( "Amavis database backup not found, creating empty database", utils.RED) diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index 7984372..84071f9 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -1,4 +1,4 @@ -"""Backup script for pre-installed instance""" +"""Backup script for pre-installed instance.""" import os import pwd @@ -10,60 +10,52 @@ import sys from .. import database from .. import utils -# TODO: have version of each modoboa componenents saved into the config file to restore the same version +class Backup: + """ + Backup structure ( {optional} ): + {{backup_folder}} + || + ||--> installer.cfg + ||--> custom + |--> { (copy of) /etc/amavis/conf.d/99-custom } + |--> { (copy of) /etc/postfix/custom_whitelist.cidr } + ||--> databases + |--> modoboa.sql + |--> { amavis.sql } + |--> { spamassassin.sql } + ||--> mails + |--> vmails + """ -class Backup(): - - # Backup structure ( {optional} ): - # {{backup_folder}} - # || - # ||--> installer.cfg - # ||--> custom - # |--> { (copy of) /etc/amavis/conf.d/99-custom } - # |--> { (copy of) /etc/postfix/custom_whitelist.cidr } - # ||--> databases - # |--> modoboa.sql - # |--> { amavis.sql } - # |--> { spamassassin.sql } - # ||--> mails - # |--> vmails - - def __init__(self, config, bashArg, nomail): + def __init__(self, config, silent_backup, backup_path, nomail): self.config = config - self.destinationPath = "" - self.BACKUPDIRECTORY = ["custom/", "databases/"] + self.destinationPath = backup_path self.nomail = nomail - self.isBash = False - self.bash = "" - if bashArg != "NOBASH": - self.isBash = True - self.bash = bashArg + self.silent_backup = silent_backup def preparePath(self): pw = pwd.getpwnam("root") - for dir in self.BACKUPDIRECTORY: + for dir in ["custom/", "databases/"]: utils.mkdir_safe(os.path.join(self.destinationPath, dir), stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3]) def validatePath(self, path): - """Check basic condition for backup directory""" - try: - pathExists = os.path.exists(path) - except: - print("Provided path is not recognized...") + """Check basic condition for backup directory.""" + + path_exists = os.path.exists(path) + + if path_exists and os.path.isfile(path): + utils.printcolor( + "Error, you provided a file instead of a directory!", utils.RED) return False - if pathExists and os.path.isfile(path): - print("Error, you provided a file instead of a directory!") - return False - - if not pathExists: - if not self.isBash: + if not path_exists: + if not self.silent_backup: createDir = input( f"\"{path}\" doesn't exists, would you like to create it ? [Y/n]\n").lower() - if self.isBash or (not self.isBash and (createDir == "y" or createDir == "yes")): + if self.silent_backup or (not self.silent_backup and (createDir == "y" or createDir == "yes")): pw = pwd.getpwnam("root") utils.mkdir_safe(path, stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3]) @@ -71,10 +63,10 @@ class Backup(): return False if len(os.listdir(path)) != 0: - if not self.isBash: + if not self.silent_backup: delDir = input( "Warning : backup folder is not empty, it will be purged if you continue... [Y/n]\n").lower() - if self.isBash or (not self.isBash and (delDir == "y" or delDir == "yes")): + if self.silent_backup or (not self.silent_backup and (delDir == "y" or delDir == "yes")): shutil.rmtree(path) else: return False @@ -84,31 +76,30 @@ class Backup(): self.preparePath() return True - def setPath(self): - """Setup backup directory""" - if self.isBash: - if self.bash == "TRUE": + def set_path(self): + """Setup backup directory.""" + if self.silent_backup: + if self.destinationPath is None: date = datetime.datetime.now().strftime("%m_%d_%Y_%H_%M") - path = f"/modoboa_backup/backup_{date}/" + path = f"./modoboa_backup/backup_{date}/" self.validatePath(path) else: - validate = self.validatePath(self.bash) - if not validate: - print("provided bash is not right, exiting...") - print(f"Path provided : {self.bash}") + if not self.validatePath(self.destinationPath): + utils.printcolor( + f"Path provided : {self.destinationPath}", utils.BLUE) sys.exit(1) else: user_value = None - while (user_value == '' or user_value == None or not self.validatePath(user_value)): - print("Enter backup path, please provide an empty folder.") - print("CTRL+C to cancel") + while user_value == "" or user_value is None or not self.validatePath(user_value): + utils.printcolor( + "Enter backup path, please provide an empty folder.", utils.MAGENTA) + utils.printcolor("CTRL+C to cancel", utils.MAGENTA) user_value = utils.user_input("-> ") - def backupConfigFile(self): + def config_file_backup(self): utils.copy_file("installer.cfg", self.destinationPath) - def backupMails(self): - + def mail_backup(self): if self.nomail: utils.printcolor( "Skipping mail backup, no-mail argument provided", utils.MAGENTA) @@ -131,7 +122,7 @@ class Backup(): shutil.copytree(home_path, dst) utils.printcolor("Mail backup complete!", utils.GREEN) - def backupCustomConfig(self): + def custom_config_backup(self): """Custom config : - Amavis : /etc/amavis/conf.d/99-custom - Postwhite : /etc/postwhite.conf @@ -140,61 +131,57 @@ class Backup(): "Backing up some custom configuration...", utils.MAGENTA) custom_path = os.path.join( - self.destinationPath, self.BACKUPDIRECTORY[0]) + self.destinationPath, "custom") - """AMAVIS""" - amavis_custom = "/etc/amavis/conf.d/99-custom" - if os.path.isfile(amavis_custom): - utils.copy_file(amavis_custom, custom_path) - utils.printcolor("Amavis custom configuration saved!", utils.GREEN) + # AMAVIS + if (self.config.has_option("amavis", "enabled") and + self.config.getboolean("amavis", "enabled")): + amavis_custom = "/etc/amavis/conf.d/99-custom" + if os.path.isfile(amavis_custom): + utils.copy_file(amavis_custom, custom_path) + utils.printcolor( + "Amavis custom configuration saved!", utils.GREEN) - """POSTWHITE""" - postswhite_custom = "/etc/postwhite.conf" - if os.path.isfile(postswhite_custom): - utils.copy_file(postswhite_custom, custom_path) - utils.printcolor("Postwhite configuration saved!", utils.GREEN) + # POSTWHITE + if (self.config.has_option("postwhite", "enabled") and + self.config.getboolean("postwhite", "enabled")): + postswhite_custom = os.path.join(self.config.get( + "postwhite", "config_dir", "postwhite.conf")) + if os.path.isfile(postswhite_custom): + utils.copy_file(postswhite_custom, custom_path) + utils.printcolor( + "Postwhite configuration saved!", utils.GREEN) - def backupDBs(self): + def database_backup(self): """Backing up databases""" utils.printcolor("Backing up databases...", utils.MAGENTA) - dump_path = os.path.join(self.destinationPath, self.BACKUPDIRECTORY[1]) + self.database_dump("modoboa") + self.database_dump("amavis") + self.database_dump("spamassassin") + + def database_dump(self, app_name): + + dump_path = os.path.join(self.destinationPath, "backup") backend = database.get_backend(self.config) - """Modoboa""" - dbname = self.config.get("modoboa", "dbname") - dbuser = self.config.get("modoboa", "dbuser") - dbpasswd = self.config.get("modoboa", "dbpassword") - backend.dumpDatabase(dbname, dbuser, dbpasswd, - os.path.join(dump_path, "modoboa.sql")) + if app_name == "modoboa" or (self.config.has_option(app_name, "enabled") and + self.config.getboolean(app_name, "enabled")): + dbname = self.config.get(app_name, "dbname") + dbuser = self.config.get(app_name, "dbuser") + dbpasswd = self.config.get(app_name, "dbpassword") + backend.dump_database(dbname, dbuser, dbpasswd, + os.path.join(dump_path, f"{app_name}.sql")) - """Amavis""" - if (self.config.has_option("amavis", "enabled") and - self.config.getboolean("amavis", "enabled")): - dbname = self.config.get("amavis", "dbname") - dbuser = self.config.get("amavis", "dbuser") - dbpasswd = self.config.get("amavis", "dbpassword") - backend.dumpDatabase(dbname, dbuser, dbpasswd, - os.path.join(dump_path, "amavis.sql")) - - """SpamAssassin""" - if (self.config.has_option("spamassassin", "enabled") and - self.config.getboolean("spamassassin", "enabled")): - dbname = self.config.get("spamassassin", "dbname") - dbuser = self.config.get("spamassassin", "dbuser") - dbpasswd = self.config.get("spamassassin", "dbpassword") - backend.dumpDatabase(dbname, dbuser, dbpasswd, - os.path.join(dump_path, "spamassassin.sql")) - - def backupCompletion(self): + def backup_completed(self): utils.printcolor("Backup process done, your backup is availible here:" f"--> {self.destinationPath}", utils.GREEN) def run(self): - self.setPath() - self.backupConfigFile() - self.backupMails() - self.backupCustomConfig() - self.backupDBs() - self.backupCompletion() + self.set_path() + self.config_file_backup() + self.mail_backup() + self.custom_config_backup() + self.database_backup() + self.backup_completed() diff --git a/modoboa_installer/scripts/base.py b/modoboa_installer/scripts/base.py index d56fdb0..67e3f0b 100644 --- a/modoboa_installer/scripts/base.py +++ b/modoboa_installer/scripts/base.py @@ -161,6 +161,10 @@ class Installer(object): """Tasks to execute before the installer starts.""" pass + def restore(self): + """Tasks to execute to restore files/databases.""" + pass + def post_run(self): """Additionnal tasks.""" pass diff --git a/modoboa_installer/scripts/restore.py b/modoboa_installer/scripts/restore.py index 7f88520..a25d364 100644 --- a/modoboa_installer/scripts/restore.py +++ b/modoboa_installer/scripts/restore.py @@ -5,29 +5,22 @@ from .. import utils class Restore: def __init__(self, restore): - """Restoring pre-check (backup integriety)""" - """REQUIRED : modoboa.sql""" - """OPTIONAL : mails/, custom/, amavis.sql, spamassassin.sql""" - """Only checking required""" + """ + Restoring pre-check (backup integriety) + REQUIRED : modoboa.sql + OPTIONAL : mails/, custom/, amavis.sql, spamassassin.sql + Only checking required + """ - try: - if not os.path.isdir(restore): - utils.printcolor( - "Provided path is not a directory !", utils.RED) - sys.exit(1) - except: - utils.printcolor("Provided path is not right...", utils.RED) + if not os.path.isdir(restore): + utils.printcolor( + "Provided path is not a directory !", utils.RED) sys.exit(1) - try: - modobasql_file = os.path.join(restore, "databases/modoboa.sql") - if not os.path.isfile(modobasql_file): - utils.printcolor( - modobasql_file + " not found, please check your backup", utils.RED) - sys.exit(1) - except: - utils.printcolor(modobasql_file + - " not found, please check your backup", utils.RED) + modobasql_file = os.path.join(restore, "databases/modoboa.sql") + if not os.path.isfile(modobasql_file): + utils.printcolor( + modobasql_file + " not found, please check your backup", utils.RED) sys.exit(1) # Everything seems allright here, proceding... diff --git a/run.py b/run.py index 33939ac..19b9bd4 100755 --- a/run.py +++ b/run.py @@ -46,7 +46,7 @@ def upgrade_disclaimer(config): ) -def backup_disclamer(): +def backup_disclaimer(): """Display backup disclamer. """ utils.printcolor( "Your mail server will be backed up (messages and databases) locally." @@ -54,7 +54,7 @@ def backup_disclamer(): " Custom configuration (like to postfix) won't be saved.", utils.BLUE) -def restore_disclamer(): +def restore_disclaimer(): """Display restore disclamer. """ utils.printcolor( "You are about to restore a previous installation of Modoboa." @@ -69,7 +69,7 @@ def main(input_args): ["latest"] + list(compatibility_matrix.COMPATIBILITY_MATRIX.keys()) ) parser.add_argument("--backup", action="store_true", default=False, - help="Backing up previously installed instance") + help="Backing up interactively previously installed instance") parser.add_argument("--debug", action="store_true", default=False, help="Enable debug output") parser.add_argument("--force", action="store_true", default=False, @@ -92,17 +92,19 @@ def main(input_args): "--beta", action="store_true", default=False, help="Install latest beta release of Modoboa instead of the stable one") parser.add_argument( - "--bash", type=str, metavar="path", - help="(backup only) - For script usage, No interaction will be required, you must provide a path") + "--backup-path", type=str, metavar="path", + help="To use with --silent-backup, you must provide a valid path") parser.add_argument( - "--sbash", action="store_true", default=False, - help="same as --bash but backup will be at /modoboa_backup/Backup_M_Y_d_H_M") + "--silent-backup", action="store_true", default=False, + help="For script usage, do not require user interaction " + "backup will be saved at ./modoboa_backup/Backup_M_Y_d_H_M if --backup-path is not provided") parser.add_argument( - "--no-mail", action="store_true", default=False, + "--no-mail-backup", action="store_true", default=False, help="Disable mail backup (save space)") parser.add_argument( "--restore", type=str, metavar="path", - help="Restore a previously backup up modoboa instance on a NEW machine. You Must provide backup directory" + help="Restore a previously backup up modoboa instance on a NEW machine. " + "You MUST provide backup directory" ) parser.add_argument("domain", type=str, help="The main domain of your future mail server") @@ -111,32 +113,24 @@ def main(input_args): if args.debug: utils.ENV["debug"] = True - if not args.backup and (args.bash != None or args.sbash or args.no_mail): - utils.printcolor("You provided --bash or --sbash without --backup, " - "if you want to do a backup, please provide --backup!", utils.RED) - return - elif args.bash != None and args.sbash: - utils.printcolor("You provided --bash PATH and --sbash at the same time. " - "Please provided only one!", utils.RED) - return - elif args.bash == "TRUE": - utils.printcolor( - "You can't pick *TRUE* as backup directory !", utils.RED) - # Restore prep - isRestoring = False - if args.restore != None: - isRestoring = True + is_restoring = False + if args.restore is not None: + is_restoring = True args.configfile = os.path.join(args.restore, "installer.cfg") if not os.path.exists(args.configfile): utils.printcolor("installer.cfg from backup not found!", utils.RED) sys.exit(1) utils.printcolor("Welcome to Modoboa installer!\n", utils.GREEN) - wasConfigFileAlreadyThere = utils.check_config_file( - args.configfile, args.interactive, args.upgrade, args.backup, isRestoring) + is_config_file_availible = utils.check_config_file( + args.configfile, args.interactive, args.upgrade, args.backup, is_restoring) - if args.stop_after_configfile_check or (not wasConfigFileAlreadyThere and args.backup): + if is_config_file_availible and args.backup: + utils.printcolor("No config file found,", utils.RED) + return + + if args.stop_after_configfile_check: return config = configparser.ConfigParser() @@ -151,18 +145,14 @@ def main(input_args): # Display disclaimer python 3 linux distribution if args.upgrade: upgrade_disclaimer(config) - elif args.backup: - backup_disclamer() - bashArg = "NOBASH" - if args.bash != None: - bashArg = args.bash - elif args.sbash: - bashArg = "TRUE" - scripts.backup(config, bashArg, args.no_mail) + elif args.backup or args.silent_backup: + backup_disclaimer() + scripts.backup(config, args.silent_backup, + args.backup_path, args.no_mail) return elif args.restore: - restore_disclamer() - scripts.restore(args.restore) + restore_disclaimer() + scripts.restore_prep(args.restore) else: installation_disclaimer(args, config) @@ -209,7 +199,7 @@ def main(input_args): utils.GREEN) else: utils.printcolor( - "Resotre complete! You can enjoy Modoboa at https://{} (same credentials as before)" + "Restore complete! You can enjoy Modoboa at https://{} (same credentials as before)" .format(config.get("general", "hostname")), utils.GREEN) From d75d83f202811a00a948d56211ab480f158e5b23 Mon Sep 17 00:00:00 2001 From: Spitap Date: Mon, 19 Sep 2022 15:13:44 +0200 Subject: [PATCH 33/42] more refactoring --- modoboa_installer/scripts/amavis.py | 13 +++--- modoboa_installer/scripts/backup.py | 49 +++++++++++------------ modoboa_installer/scripts/base.py | 4 -- modoboa_installer/scripts/dovecot.py | 2 +- modoboa_installer/scripts/modoboa.py | 10 ++--- modoboa_installer/scripts/postwhite.py | 6 +-- modoboa_installer/scripts/spamassassin.py | 10 ++--- 7 files changed, 44 insertions(+), 50 deletions(-) diff --git a/modoboa_installer/scripts/amavis.py b/modoboa_installer/scripts/amavis.py index 23a508d..2e5b696 100644 --- a/modoboa_installer/scripts/amavis.py +++ b/modoboa_installer/scripts/amavis.py @@ -42,12 +42,13 @@ class Amavis(base.Installer): def get_config_files(self): """Return appropriate config files.""" if package.backend.FORMAT == "deb": - amavisCustomConf = os.path.join(self.restore, "custom/99-custom") - if self.restore and os.path.isfile(amavisCustomConf): - utils.copy_file(amavisCustomConf, os.path.join( + amavis_custom_configuration = os.path.join( + self.restore, "custom/99-custom") + if self.restore and os.path.isfile(amavis_custom_configuration): + utils.copy_file(amavis_custom_configuration, os.path.join( self.config_dir, "/conf.d")) utils.printcolor( - "Custom amavis configuration restored", utils.GREEN) + "Custom amavis configuration restored.", utils.GREEN) return [ "conf.d/05-node_id", "conf.d/15-content_filter_mode", "conf.d/50-user"] @@ -77,7 +78,7 @@ class Amavis(base.Installer): """Return schema path.""" if self.restore: utils.printcolor( - "Trying to restore amavis database from backup", utils.MAGENTA) + "Trying to restore amavis database from backup.", utils.MAGENTA) amavis_db_backup_path = os.path.join( self.restore, "databases/amavis.sql") if os.path.isfile(amavis_db_backup_path): @@ -85,7 +86,7 @@ class Amavis(base.Installer): "Amavis database backup found ! Restoring...", utils.GREEN) return amavis_db_backup_path utils.printcolor( - "Amavis database backup not found, creating empty database", utils.RED) + "Amavis database backup not found, creating empty database.", utils.RED) version = package.backend.get_installed_version("amavisd-new") if version is None: diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index 84071f9..0b3aa9e 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -30,17 +30,11 @@ class Backup: def __init__(self, config, silent_backup, backup_path, nomail): self.config = config - self.destinationPath = backup_path + self.backup_path = backup_path self.nomail = nomail self.silent_backup = silent_backup - def preparePath(self): - pw = pwd.getpwnam("root") - for dir in ["custom/", "databases/"]: - utils.mkdir_safe(os.path.join(self.destinationPath, dir), - stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3]) - - def validatePath(self, path): + def validate_path(self, path): """Check basic condition for backup directory.""" path_exists = os.path.exists(path) @@ -52,10 +46,10 @@ class Backup: if not path_exists: if not self.silent_backup: - createDir = input( + create_dir = input( f"\"{path}\" doesn't exists, would you like to create it ? [Y/n]\n").lower() - if self.silent_backup or (not self.silent_backup and (createDir == "y" or createDir == "yes")): + if self.silent_backup or (not self.silent_backup and (create_dir == "y" or create_dir == "yes")): pw = pwd.getpwnam("root") utils.mkdir_safe(path, stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3]) @@ -64,40 +58,44 @@ class Backup: if len(os.listdir(path)) != 0: if not self.silent_backup: - delDir = input( + delete_dir = input( "Warning : backup folder is not empty, it will be purged if you continue... [Y/n]\n").lower() - if self.silent_backup or (not self.silent_backup and (delDir == "y" or delDir == "yes")): + if self.silent_backup or (not self.silent_backup and (delete_dir == "y" or delete_dir == "yes")): shutil.rmtree(path) else: return False - self.destinationPath = path + self.backup_path = path + + pw = pwd.getpwnam("root") + for dir in ["custom/", "databases/"]: + utils.mkdir_safe(os.path.join(self.backup_path, dir), + stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3]) - self.preparePath() return True def set_path(self): """Setup backup directory.""" if self.silent_backup: - if self.destinationPath is None: + if self.backup_path is None: date = datetime.datetime.now().strftime("%m_%d_%Y_%H_%M") path = f"./modoboa_backup/backup_{date}/" - self.validatePath(path) + self.validate_path(path) else: - if not self.validatePath(self.destinationPath): + if not self.validate_path(self.backup_path): utils.printcolor( - f"Path provided : {self.destinationPath}", utils.BLUE) + f"Path provided : {self.backup_path}", utils.BLUE) sys.exit(1) else: user_value = None - while user_value == "" or user_value is None or not self.validatePath(user_value): + while user_value == "" or user_value is None or not self.validate_path(user_value): utils.printcolor( "Enter backup path, please provide an empty folder.", utils.MAGENTA) utils.printcolor("CTRL+C to cancel", utils.MAGENTA) user_value = utils.user_input("-> ") def config_file_backup(self): - utils.copy_file("installer.cfg", self.destinationPath) + utils.copy_file("installer.cfg", self.backup_path) def mail_backup(self): if self.nomail: @@ -114,7 +112,7 @@ class Backup: f" ({home_path}) seems not right...", utils.RED) else: - dst = os.path.join(self.destinationPath, "mails/") + dst = os.path.join(self.backup_path, "mails/") if os.path.exists(dst): shutil.rmtree(dst) @@ -131,7 +129,7 @@ class Backup: "Backing up some custom configuration...", utils.MAGENTA) custom_path = os.path.join( - self.destinationPath, "custom") + self.backup_path, "custom") # AMAVIS if (self.config.has_option("amavis", "enabled") and @@ -145,8 +143,7 @@ class Backup: # POSTWHITE if (self.config.has_option("postwhite", "enabled") and self.config.getboolean("postwhite", "enabled")): - postswhite_custom = os.path.join(self.config.get( - "postwhite", "config_dir", "postwhite.conf")) + postswhite_custom = "/etc/postwhite.conf" if os.path.isfile(postswhite_custom): utils.copy_file(postswhite_custom, custom_path) utils.printcolor( @@ -163,7 +160,7 @@ class Backup: def database_dump(self, app_name): - dump_path = os.path.join(self.destinationPath, "backup") + dump_path = os.path.join(self.backup_path, "backup") backend = database.get_backend(self.config) if app_name == "modoboa" or (self.config.has_option(app_name, "enabled") and @@ -176,7 +173,7 @@ class Backup: def backup_completed(self): utils.printcolor("Backup process done, your backup is availible here:" - f"--> {self.destinationPath}", utils.GREEN) + f"--> {self.backup_path}", utils.GREEN) def run(self): self.set_path() diff --git a/modoboa_installer/scripts/base.py b/modoboa_installer/scripts/base.py index 67e3f0b..d56fdb0 100644 --- a/modoboa_installer/scripts/base.py +++ b/modoboa_installer/scripts/base.py @@ -161,10 +161,6 @@ class Installer(object): """Tasks to execute before the installer starts.""" pass - def restore(self): - """Tasks to execute to restore files/databases.""" - pass - def post_run(self): """Additionnal tasks.""" pass diff --git a/modoboa_installer/scripts/dovecot.py b/modoboa_installer/scripts/dovecot.py index 241ee7e..cc353ed 100644 --- a/modoboa_installer/scripts/dovecot.py +++ b/modoboa_installer/scripts/dovecot.py @@ -91,7 +91,7 @@ class Dovecot(base.Installer): mail_dir = os.path.join(self.restore, "mails/") if self.restore and len(os.listdir(mail_dir)) > 0: utils.printcolor( - "Copying mail backup over dovecot directory", utils.GREEN) + "Copying mail backup over dovecot directory.", utils.GREEN) if os.path.exists(self.home_dir): shutil.rmtree(self.home_dir) diff --git a/modoboa_installer/scripts/modoboa.py b/modoboa_installer/scripts/modoboa.py index 3f1f164..d9e7ad0 100644 --- a/modoboa_installer/scripts/modoboa.py +++ b/modoboa_installer/scripts/modoboa.py @@ -179,15 +179,15 @@ class Modoboa(base.Installer): def get_sql_schema_path(self): if self.restore: utils.printcolor( - "Trying to restore modoboa database from backup", utils.MAGENTA) - modoboaDbBackupPath = os.path.join( + "Trying to restore modoboa database from backup.", utils.MAGENTA) + modoboa_database_backup_path = os.path.join( self.restore, "databases/modoboa.sql") - if os.path.isfile(modoboaDbBackupPath): + if os.path.isfile(modoboa_database_backup_path): utils.printcolor( "Modoboa database backup found ! Restoring...", utils.GREEN) - return modoboaDbBackupPath + return modoboa_database_backup_path utils.printcolor( - "Modoboa database backup not found, creating empty database", utils.RED) + "Modoboa database backup not found, creating empty database.", utils.RED) return super().get_sql_schema_path()() diff --git a/modoboa_installer/scripts/postwhite.py b/modoboa_installer/scripts/postwhite.py index d6e6a67..040a212 100644 --- a/modoboa_installer/scripts/postwhite.py +++ b/modoboa_installer/scripts/postwhite.py @@ -46,10 +46,10 @@ class Postwhite(base.Installer): self.install_from_archive(SPF_TOOLS_REPOSITORY, install_dir) postw_dir = self.install_from_archive( POSTWHITE_REPOSITORY, install_dir) - postwhiteBackupConf = os.path.join( + postwhite_backup_configuration = os.path.join( self.restore, "custom/postwhite.conf") - if self.restore and os.path.isfile(postwhiteBackupConf): - utils.copy_file(postwhiteBackupConf, "/etc") + if self.restore and os.path.isfile(postwhite_backup_configuration): + utils.copy_file(postwhite_backup_configuration, "/etc") utils.printcolor( "postwhite.conf restored from backup", utils.GREEN) else: diff --git a/modoboa_installer/scripts/spamassassin.py b/modoboa_installer/scripts/spamassassin.py index 00843ce..150f238 100644 --- a/modoboa_installer/scripts/spamassassin.py +++ b/modoboa_installer/scripts/spamassassin.py @@ -27,15 +27,15 @@ class Spamassassin(base.Installer): """Return SQL schema.""" if self.restore: utils.printcolor( - "Trying to restore spamassassin database from backup", utils.MAGENTA) - amavisDbBackupPath = os.path.join( + "Trying to restore spamassassin database from backup.", utils.MAGENTA) + sa_database_backup_path = os.path.join( self.restore, "databases/spamassassin.sql") - if os.path.isfile(amavisDbBackupPath): + if os.path.isfile(sa_database_backup_path): utils.printcolor( "Spamassassin database backup found ! Restoring...", utils.GREEN) - return amavisDbBackupPath + return sa_database_backup_path utils.printcolor( - "Spamassassin database backup not found, creating empty database", utils.RED) + "Spamassassin database backup not found, creating empty database.", utils.RED) if self.dbengine == "postgres": fname = "bayes_pg.sql" From 6b096a7470f07c77cf2ad34c7d5a0a9f518ea9c8 Mon Sep 17 00:00:00 2001 From: Spitap Date: Mon, 19 Sep 2022 15:50:03 +0200 Subject: [PATCH 34/42] Simplified db dumps restore --- modoboa_installer/scripts/amavis.py | 13 +++---------- modoboa_installer/scripts/base.py | 14 ++++++++++++++ modoboa_installer/scripts/modoboa.py | 13 +++---------- modoboa_installer/scripts/spamassassin.py | 14 +++----------- 4 files changed, 23 insertions(+), 31 deletions(-) diff --git a/modoboa_installer/scripts/amavis.py b/modoboa_installer/scripts/amavis.py index 2e5b696..14e4b1f 100644 --- a/modoboa_installer/scripts/amavis.py +++ b/modoboa_installer/scripts/amavis.py @@ -77,16 +77,9 @@ class Amavis(base.Installer): def get_sql_schema_path(self): """Return schema path.""" if self.restore: - utils.printcolor( - "Trying to restore amavis database from backup.", utils.MAGENTA) - amavis_db_backup_path = os.path.join( - self.restore, "databases/amavis.sql") - if os.path.isfile(amavis_db_backup_path): - utils.printcolor( - "Amavis database backup found ! Restoring...", utils.GREEN) - return amavis_db_backup_path - utils.printcolor( - "Amavis database backup not found, creating empty database.", utils.RED) + db_dump_path = self._restore_database_dump("amavis") + if db_dump_path is not None: + return db_dump_path version = package.backend.get_installed_version("amavisd-new") if version is None: diff --git a/modoboa_installer/scripts/base.py b/modoboa_installer/scripts/base.py index 43e7939..3a1505b 100644 --- a/modoboa_installer/scripts/base.py +++ b/modoboa_installer/scripts/base.py @@ -160,6 +160,20 @@ class Installer(object): self.post_run() self.restart_daemon() + def _restore_database_dump(self, app_name): + """Restore database dump from a dump.""" + + utils.printcolor( + f"Trying to restore {app_name} database from backup.", utils.MAGENTA) + database_backup_path = os.path.join( + self.restore, f"databases/{app_name}.sql") + if os.path.isfile(database_backup_path): + utils.printcolor( + f"{app_name.capitalize()} database backup found ! Restoring...", utils.GREEN) + return database_backup_path + utils.printcolor( + f"{app_name.capitalize()} database backup not found, creating empty database.", utils.RED) + def pre_run(self): """Tasks to execute before the installer starts.""" pass diff --git a/modoboa_installer/scripts/modoboa.py b/modoboa_installer/scripts/modoboa.py index c14888b..299349e 100644 --- a/modoboa_installer/scripts/modoboa.py +++ b/modoboa_installer/scripts/modoboa.py @@ -180,16 +180,9 @@ class Modoboa(base.Installer): def get_sql_schema_path(self): if self.restore: - utils.printcolor( - "Trying to restore modoboa database from backup.", utils.MAGENTA) - modoboa_database_backup_path = os.path.join( - self.restore, "databases/modoboa.sql") - if os.path.isfile(modoboa_database_backup_path): - utils.printcolor( - "Modoboa database backup found ! Restoring...", utils.GREEN) - return modoboa_database_backup_path - utils.printcolor( - "Modoboa database backup not found, creating empty database.", utils.RED) + db_dump_path = self._restore_database_dump("modoboa") + if db_dump_path is not None: + return db_dump_path return super().get_sql_schema_path()() diff --git a/modoboa_installer/scripts/spamassassin.py b/modoboa_installer/scripts/spamassassin.py index 9f6e45d..e5fd3a3 100644 --- a/modoboa_installer/scripts/spamassassin.py +++ b/modoboa_installer/scripts/spamassassin.py @@ -26,17 +26,9 @@ class Spamassassin(base.Installer): def get_sql_schema_path(self): """Return SQL schema.""" if self.restore: - utils.printcolor( - "Trying to restore spamassassin database from backup.", utils.MAGENTA) - sa_database_backup_path = os.path.join( - self.restore, "databases/spamassassin.sql") - if os.path.isfile(sa_database_backup_path): - utils.printcolor( - "Spamassassin database backup found ! Restoring...", utils.GREEN) - return sa_database_backup_path - utils.printcolor( - "Spamassassin database backup not found, creating empty database.", utils.RED) - + db_dump_path = self._restore_database_dump("spamassassin") + if db_dump_path is not None: + return db_dump_path if self.dbengine == "postgres": fname = "bayes_pg.sql" else: From 5bed9655ea2e75873846d562c4c8aefd00572a9a Mon Sep 17 00:00:00 2001 From: Spitap Date: Mon, 19 Sep 2022 15:53:19 +0200 Subject: [PATCH 35/42] fixed typo --- run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run.py b/run.py index 19b9bd4..41866c5 100755 --- a/run.py +++ b/run.py @@ -148,7 +148,7 @@ def main(input_args): elif args.backup or args.silent_backup: backup_disclaimer() scripts.backup(config, args.silent_backup, - args.backup_path, args.no_mail) + args.backup_path, args.no_mail_backup) return elif args.restore: restore_disclaimer() From a192cbcbd0ab2b6169a66951fe0a44d32f210a33 Mon Sep 17 00:00:00 2001 From: Spitap Date: Mon, 19 Sep 2022 16:40:25 +0200 Subject: [PATCH 36/42] Updated doc, default path on conf file --- README.rst | 16 ++++++++-------- modoboa_installer/scripts/backup.py | 7 ++++--- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index a3a5412..8038ba7 100644 --- a/README.rst +++ b/README.rst @@ -108,30 +108,30 @@ You can start the process as follows:: Then follow the step on the console. -There are also two non-interactive mode: +There are also a non-interactive mode: -1. Silent batch mode +1. Silent mode Command:: - $ sudo ./run.py --backup --sbatch + $ sudo ./run.py --silent-backup -This mode is the silent batch mode, when executed, it will create /modoboa_backup/backup{time} and each time you execute it, it will create a new backup directory with current time. +This mode is the silent batch mode, when executed, it will create /modoboa_backup/ and each time you execute it, it will create a new backup directory with current time. -2. Path batch mode +You can supply a custom path. Command:: - $ sudo ./run.py --backup --batch /path/of/backup/directory + $ sudo ./run.py --silent-backup --backup-path /path/of/backup/directory This mode is the same as silent batch mode, but you provide the path to the backup directory you want. If you want to disable mail backup:: - $ sudo ./run.py --backup --no-mail + $ sudo ./run.py {--backup|--silent-backup} --no-mail-backup -This can be useful for larger instance +This can be useful for larger instance. Restore mode ------------ diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index 0b3aa9e..54d9f5a 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -4,7 +4,6 @@ import os import pwd import shutil import stat -import datetime import sys from .. import database @@ -78,8 +77,10 @@ class Backup: """Setup backup directory.""" if self.silent_backup: if self.backup_path is None: - date = datetime.datetime.now().strftime("%m_%d_%Y_%H_%M") - path = f"./modoboa_backup/backup_{date}/" + if self.config.has_option("backup", "default_path"): + path = self.config.get("backup", "default_path") + else: + path = f"./modoboa_backup/" self.validate_path(path) else: if not self.validate_path(self.backup_path): From 6726f5b1a232d30438b6a5ea175141523d1eb6c4 Mon Sep 17 00:00:00 2001 From: Spitap Date: Mon, 26 Sep 2022 13:39:28 +0200 Subject: [PATCH 37/42] Improved path generation, path mistake proofing --- README.rst | 2 +- modoboa_installer/scripts/backup.py | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 8038ba7..e2af8c7 100644 --- a/README.rst +++ b/README.rst @@ -116,7 +116,7 @@ Command:: $ sudo ./run.py --silent-backup -This mode is the silent batch mode, when executed, it will create /modoboa_backup/ and each time you execute it, it will create a new backup directory with current time. +This mode is the silent batch mode, when executed, it will create /modoboa_backup/ and each time you execute it, it will create a new backup directory with current date and time. You can supply a custom path. diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index 54d9f5a..9c7e53a 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -5,6 +5,7 @@ import pwd import shutil import stat import sys +import datetime from .. import database from .. import utils @@ -53,15 +54,29 @@ class Backup: utils.mkdir_safe(path, stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3]) else: + utils.printcolor( + "Error, backup dir not present.", utils.RED + ) return False if len(os.listdir(path)) != 0: if not self.silent_backup: delete_dir = input( "Warning : backup folder is not empty, it will be purged if you continue... [Y/n]\n").lower() + if self.silent_backup or (not self.silent_backup and (delete_dir == "y" or delete_dir == "yes")): - shutil.rmtree(path) + try: + os.remove(os.path.join(path, "installer.cfg")) + except FileNotFoundError: + pass + + shutil.rmtree(os.path.join(path, "custom"),ignore_errors=False) + shutil.rmtree(os.path.join(path, "mails"), ignore_errors=False) + shutil.rmtree(os.path.join(path, "databases"), ignore_errors=False) else: + utils.printcolor( + "Error, backup dir not clean.", utils.RED + ) return False self.backup_path = path @@ -70,7 +85,6 @@ class Backup: for dir in ["custom/", "databases/"]: utils.mkdir_safe(os.path.join(self.backup_path, dir), stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3]) - return True def set_path(self): @@ -81,6 +95,8 @@ class Backup: path = self.config.get("backup", "default_path") else: path = f"./modoboa_backup/" + date = datetime.datetime.now().strftime("%m_%d_%Y_%H_%M") + path = os.path.join(path, f"backup_{date}") self.validate_path(path) else: if not self.validate_path(self.backup_path): From e34eb4b3372087844e444b23fe5043841f37d1c2 Mon Sep 17 00:00:00 2001 From: Spitap Date: Tue, 25 Oct 2022 13:59:28 +0200 Subject: [PATCH 38/42] fix database path --- modoboa_installer/scripts/backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index 9c7e53a..b6b9943 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -177,7 +177,7 @@ class Backup: def database_dump(self, app_name): - dump_path = os.path.join(self.backup_path, "backup") + dump_path = os.path.join(self.backup_path, "databases") backend = database.get_backend(self.config) if app_name == "modoboa" or (self.config.has_option(app_name, "enabled") and From 4c1f8710b5600799027e4908470655a9478ad4a5 Mon Sep 17 00:00:00 2001 From: Spitap Date: Tue, 25 Oct 2022 16:04:55 +0200 Subject: [PATCH 39/42] Added dkim key backup --- modoboa_installer/scripts/backup.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index b6b9943..64b3add 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -20,6 +20,8 @@ class Backup: ||--> custom |--> { (copy of) /etc/amavis/conf.d/99-custom } |--> { (copy of) /etc/postfix/custom_whitelist.cidr } + |--> { (copy of) dkim folder } + |--> {dkim.pem}... ||--> databases |--> modoboa.sql |--> { amavis.sql } @@ -63,16 +65,18 @@ class Backup: if not self.silent_backup: delete_dir = input( "Warning : backup folder is not empty, it will be purged if you continue... [Y/n]\n").lower() - + if self.silent_backup or (not self.silent_backup and (delete_dir == "y" or delete_dir == "yes")): try: os.remove(os.path.join(path, "installer.cfg")) except FileNotFoundError: pass - - shutil.rmtree(os.path.join(path, "custom"),ignore_errors=False) + + shutil.rmtree(os.path.join(path, "custom"), + ignore_errors=False) shutil.rmtree(os.path.join(path, "mails"), ignore_errors=False) - shutil.rmtree(os.path.join(path, "databases"), ignore_errors=False) + shutil.rmtree(os.path.join(path, "databases"), + ignore_errors=False) else: utils.printcolor( "Error, backup dir not clean.", utils.RED @@ -139,6 +143,7 @@ class Backup: def custom_config_backup(self): """Custom config : + - DKIM keys: {{keys_storage_dir}} - Amavis : /etc/amavis/conf.d/99-custom - Postwhite : /etc/postwhite.conf Feel free to suggest to add others!""" @@ -148,6 +153,15 @@ class Backup: custom_path = os.path.join( self.backup_path, "custom") + # DKIM Key + if (self.config.has_option("opendkim", "enabled") and + self.config.getboolean("opendkim", "enabled")): + dkim_keys = self.config.get( + "opendkim", "keys_storage_dir", fallback="/var/lib/dkim") + shutil.copytree(dkim_keys, os.path.join(custom_path, "dkim")) + utils.printcolor( + "DKIM keys saved!", utils.GREEN) + # AMAVIS if (self.config.has_option("amavis", "enabled") and self.config.getboolean("amavis", "enabled")): From 5c9d5c9a0319a9e94b9abd1906a09693cc7615d6 Mon Sep 17 00:00:00 2001 From: Spitap Date: Tue, 25 Oct 2022 16:58:57 +0200 Subject: [PATCH 40/42] DKIM keys restore, Radicale backup/restore, fixes --- modoboa_installer/scripts/amavis.py | 15 ++++++++------- modoboa_installer/scripts/backup.py | 19 ++++++++++++++++--- modoboa_installer/scripts/dovecot.py | 4 ++-- modoboa_installer/scripts/modoboa.py | 2 +- modoboa_installer/scripts/opendkim.py | 12 ++++++++++++ modoboa_installer/scripts/postwhite.py | 18 ++++++++++-------- modoboa_installer/scripts/radicale.py | 12 ++++++++++++ modoboa_installer/scripts/spamassassin.py | 2 +- 8 files changed, 62 insertions(+), 22 deletions(-) diff --git a/modoboa_installer/scripts/amavis.py b/modoboa_installer/scripts/amavis.py index 14e4b1f..8988695 100644 --- a/modoboa_installer/scripts/amavis.py +++ b/modoboa_installer/scripts/amavis.py @@ -42,13 +42,14 @@ class Amavis(base.Installer): def get_config_files(self): """Return appropriate config files.""" if package.backend.FORMAT == "deb": - amavis_custom_configuration = os.path.join( - self.restore, "custom/99-custom") - if self.restore and os.path.isfile(amavis_custom_configuration): - utils.copy_file(amavis_custom_configuration, os.path.join( - self.config_dir, "/conf.d")) - utils.printcolor( - "Custom amavis configuration restored.", utils.GREEN) + if self.restore is not None: + amavis_custom_configuration = os.path.join( + self.restore, "custom/99-custom") + if os.path.isfile(amavis_custom_configuration): + utils.copy_file(amavis_custom_configuration, os.path.join( + self.config_dir, "conf.d")) + utils.printcolor( + "Custom amavis configuration restored.", utils.GREEN) return [ "conf.d/05-node_id", "conf.d/15-content_filter_mode", "conf.d/50-user"] diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index 64b3add..a434a94 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -22,6 +22,7 @@ class Backup: |--> { (copy of) /etc/postfix/custom_whitelist.cidr } |--> { (copy of) dkim folder } |--> {dkim.pem}... + |--> { (copy of) radicale home_dir } ||--> databases |--> modoboa.sql |--> { amavis.sql } @@ -144,6 +145,7 @@ class Backup: def custom_config_backup(self): """Custom config : - DKIM keys: {{keys_storage_dir}} + - Radicale collection (calendat, contacts): {{home_dir}} - Amavis : /etc/amavis/conf.d/99-custom - Postwhite : /etc/postwhite.conf Feel free to suggest to add others!""" @@ -158,9 +160,20 @@ class Backup: self.config.getboolean("opendkim", "enabled")): dkim_keys = self.config.get( "opendkim", "keys_storage_dir", fallback="/var/lib/dkim") - shutil.copytree(dkim_keys, os.path.join(custom_path, "dkim")) - utils.printcolor( - "DKIM keys saved!", utils.GREEN) + if os.path.isdir(dkim_keys): + shutil.copytree(dkim_keys, os.path.join(custom_path, "dkim")) + utils.printcolor( + "DKIM keys saved!", utils.GREEN) + + # Radicale Collections + if (self.config.has_option("radicale", "enabled") and + self.config.getboolean("radicale", "enabled")): + radicale_backup = os.path.join(self.config.get( + "radicale", "home_dir", fallback="/srv/radicale"), "collections") + if os.path.isdir(radicale_backup): + shutil.copytree(radicale_backup, os.path.join( + custom_path, "radicale")) + utils.printcolor("Radicale files saved", utils.GREEN) # AMAVIS if (self.config.has_option("amavis", "enabled") and diff --git a/modoboa_installer/scripts/dovecot.py b/modoboa_installer/scripts/dovecot.py index 3bb2518..03cda78 100644 --- a/modoboa_installer/scripts/dovecot.py +++ b/modoboa_installer/scripts/dovecot.py @@ -89,7 +89,7 @@ class Dovecot(base.Installer): def post_run(self): """Additional tasks.""" mail_dir = os.path.join(self.restore, "mails/") - if self.restore and len(os.listdir(mail_dir)) > 0: + if self.restore is not None and len(os.listdir(mail_dir)) > 0: utils.printcolor( "Copying mail backup over dovecot directory.", utils.GREEN) @@ -103,7 +103,7 @@ class Dovecot(base.Installer): for filename in filenames: shutil.chown(os.path.join(dirpath, filename), self.user, self.user) - elif self.restore: + elif self.restore is not None: utils.printcolor( "It seems that mails were not backed up, skipping mail restoration.", utils.MAGENTA) diff --git a/modoboa_installer/scripts/modoboa.py b/modoboa_installer/scripts/modoboa.py index 8821016..9719782 100644 --- a/modoboa_installer/scripts/modoboa.py +++ b/modoboa_installer/scripts/modoboa.py @@ -193,7 +193,7 @@ class Modoboa(base.Installer): self.config.get("amavis", "dbname"), self.dbuser) def get_sql_schema_path(self): - if self.restore: + if self.restore is not None: db_dump_path = self._restore_database_dump("modoboa") if db_dump_path is not None: return db_dump_path diff --git a/modoboa_installer/scripts/opendkim.py b/modoboa_installer/scripts/opendkim.py index 9875a93..1dec655 100644 --- a/modoboa_installer/scripts/opendkim.py +++ b/modoboa_installer/scripts/opendkim.py @@ -46,6 +46,18 @@ class Opendkim(base.Installer): stat.S_IROTH | stat.S_IXOTH, target[1], target[2] ) + # Restore dkim keys from backup if restoring + if self.restore is not None: + dkim_keys_backup = os.path.join( + self.restore, "custom/dkim") + if os.path.isdir(dkim_keys_backup): + for file in os.listdir(dkim_keys_backup): + file_path = os.path.join(dkim_keys_backup, file) + if os.path.isfile(file_path): + utils.copy_file(file_path, self.config.get( + "opendkim", "keys_storage_dir", fallback="/var/lib/dkim")) + utils.printcolor( + "DKIM keys restored from backup", utils.GREEN) super(Opendkim, self).install_config_files() def get_template_context(self): diff --git a/modoboa_installer/scripts/postwhite.py b/modoboa_installer/scripts/postwhite.py index 4a36182..ec54639 100644 --- a/modoboa_installer/scripts/postwhite.py +++ b/modoboa_installer/scripts/postwhite.py @@ -47,13 +47,15 @@ class Postwhite(base.Installer): self.install_from_archive(SPF_TOOLS_REPOSITORY, install_dir) postw_dir = self.install_from_archive( POSTWHITE_REPOSITORY, install_dir) - postwhite_backup_configuration = os.path.join( - self.restore, "custom/postwhite.conf") - if self.restore and os.path.isfile(postwhite_backup_configuration): - utils.copy_file(postwhite_backup_configuration, "/etc") - utils.printcolor( - "postwhite.conf restored from backup", utils.GREEN) - else: - utils.copy_file(os.path.join(postw_dir, "postwhite.conf"), "/etc") + # Attempt to restore config file from backup + if self.restore is not None: + postwhite_backup_configuration = os.path.join( + self.restore, "custom/postwhite.conf") + if os.path.isfile(postwhite_backup_configuration): + utils.copy_file(postwhite_backup_configuration, self.config_dir) + utils.printcolor( + "postwhite.conf restored from backup", utils.GREEN) + else: + utils.copy_file(os.path.join(postw_dir, "postwhite.conf"), self.config_dir) postw_bin = os.path.join(postw_dir, "postwhite") utils.exec_cmd("{} /etc/postwhite.conf".format(postw_bin)) diff --git a/modoboa_installer/scripts/radicale.py b/modoboa_installer/scripts/radicale.py index 889403f..df6e38b 100644 --- a/modoboa_installer/scripts/radicale.py +++ b/modoboa_installer/scripts/radicale.py @@ -1,6 +1,7 @@ """Radicale related tasks.""" import os +import shutil import stat from .. import package @@ -70,6 +71,17 @@ class Radicale(base.Installer): stat.S_IROTH | stat.S_IXOTH, 0, 0 ) + # Attempt to restore radicale collections from backup + if self.restore is not None: + radicale_backup = os.path.join( + self.restore, "custom/radicale") + if os.path.isdir(radicale_backup): + restore_target = os.path.join(self.home_dir, "collections") + if os.path.isdir(restore_target): + shutil.rmtree(restore_target) + shutil.copytree(radicale_backup, restore_target) + utils.printcolor( + "Radicale collections restored from backup", utils.GREEN) super(Radicale, self).install_config_files() def post_run(self): diff --git a/modoboa_installer/scripts/spamassassin.py b/modoboa_installer/scripts/spamassassin.py index e5fd3a3..f47ebd8 100644 --- a/modoboa_installer/scripts/spamassassin.py +++ b/modoboa_installer/scripts/spamassassin.py @@ -25,7 +25,7 @@ class Spamassassin(base.Installer): def get_sql_schema_path(self): """Return SQL schema.""" - if self.restore: + if self.restore is not None: db_dump_path = self._restore_database_dump("spamassassin") if db_dump_path is not None: return db_dump_path From 554611b36603115f5f42f59874ac2bf36b181ed2 Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 3 Nov 2022 10:54:06 +0100 Subject: [PATCH 41/42] review fix --- modoboa_installer/config_dict_template.py | 6 ++++-- modoboa_installer/constants.py | 1 + modoboa_installer/scripts/backup.py | 17 +++++++++-------- modoboa_installer/scripts/restore.py | 6 +++--- modoboa_installer/utils.py | 4 ++-- run.py | 4 ++-- 6 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 modoboa_installer/constants.py diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index 43bf7c6..e55ea92 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -1,6 +1,8 @@ import random import string +from constants import DEFAULT_BACKUP_DIRECTORY + def make_password(length=16): """Create a random password.""" @@ -441,10 +443,10 @@ ConfigDictTemplate = [ }, { "name": "backup", - "values": [ + "values": [ { "option": "default_path", - "default": "./modoboa_backup/" + "default": DEFAULT_BACKUP_DIRECTORY } ] } diff --git a/modoboa_installer/constants.py b/modoboa_installer/constants.py new file mode 100644 index 0000000..7df5082 --- /dev/null +++ b/modoboa_installer/constants.py @@ -0,0 +1 @@ +DEFAULT_BACKUP_DIRECTORY = "./modoboa_backup/" diff --git a/modoboa_installer/scripts/backup.py b/modoboa_installer/scripts/backup.py index a434a94..ffa4fda 100644 --- a/modoboa_installer/scripts/backup.py +++ b/modoboa_installer/scripts/backup.py @@ -9,18 +9,19 @@ import datetime from .. import database from .. import utils +from ..constants import DEFAULT_BACKUP_DIRECTORY class Backup: """ Backup structure ( {optional} ): - {{backup_folder}} + {{backup_directory}} || ||--> installer.cfg ||--> custom |--> { (copy of) /etc/amavis/conf.d/99-custom } |--> { (copy of) /etc/postfix/custom_whitelist.cidr } - |--> { (copy of) dkim folder } + |--> { (copy of) dkim directory } |--> {dkim.pem}... |--> { (copy of) radicale home_dir } ||--> databases @@ -50,22 +51,22 @@ class Backup: if not path_exists: if not self.silent_backup: create_dir = input( - f"\"{path}\" doesn't exists, would you like to create it ? [Y/n]\n").lower() + f"\"{path}\" doesn't exists, would you like to create it? [Y/n]\n").lower() - if self.silent_backup or (not self.silent_backup and (create_dir == "y" or create_dir == "yes")): + if self.silent_backup or (not self.silent_backup and create_dir.startswith("y")): pw = pwd.getpwnam("root") utils.mkdir_safe(path, stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3]) else: utils.printcolor( - "Error, backup dir not present.", utils.RED + "Error, backup directory not present.", utils.RED ) return False if len(os.listdir(path)) != 0: if not self.silent_backup: delete_dir = input( - "Warning : backup folder is not empty, it will be purged if you continue... [Y/n]\n").lower() + "Warning : backup directory is not empty, it will be purged if you continue... [Y/n]\n").lower() if self.silent_backup or (not self.silent_backup and (delete_dir == "y" or delete_dir == "yes")): try: @@ -99,7 +100,7 @@ class Backup: if self.config.has_option("backup", "default_path"): path = self.config.get("backup", "default_path") else: - path = f"./modoboa_backup/" + path = DEFAULT_BACKUP_DIRECTORY date = datetime.datetime.now().strftime("%m_%d_%Y_%H_%M") path = os.path.join(path, f"backup_{date}") self.validate_path(path) @@ -112,7 +113,7 @@ class Backup: user_value = None while user_value == "" or user_value is None or not self.validate_path(user_value): utils.printcolor( - "Enter backup path, please provide an empty folder.", utils.MAGENTA) + "Enter backup path (it must be an empty directory)", utils.MAGENTA) utils.printcolor("CTRL+C to cancel", utils.MAGENTA) user_value = utils.user_input("-> ") diff --git a/modoboa_installer/scripts/restore.py b/modoboa_installer/scripts/restore.py index a25d364..d5076e9 100644 --- a/modoboa_installer/scripts/restore.py +++ b/modoboa_installer/scripts/restore.py @@ -17,10 +17,10 @@ class Restore: "Provided path is not a directory !", utils.RED) sys.exit(1) - modobasql_file = os.path.join(restore, "databases/modoboa.sql") - if not os.path.isfile(modobasql_file): + modoba_sql_file = os.path.join(restore, "databases/modoboa.sql") + if not os.path.isfile(modoba_sql_file): utils.printcolor( - modobasql_file + " not found, please check your backup", utils.RED) + modoba_sql_file + " not found, please check your backup", utils.RED) sys.exit(1) # Everything seems allright here, proceding... diff --git a/modoboa_installer/utils.py b/modoboa_installer/utils.py index babea78..1a8f18d 100644 --- a/modoboa_installer/utils.py +++ b/modoboa_installer/utils.py @@ -172,9 +172,9 @@ def copy_from_template(template, dest, context): def check_config_file(dest, interactive=False, upgrade=False, backup=False, restore=False): """Create a new installer config file if needed.""" - isPresent = True + is_present = True if os.path.exists(dest): - return isPresent + return is_present if upgrade: printcolor( "You cannot upgrade an existing installation without a " diff --git a/run.py b/run.py index 41866c5..c07040e 100755 --- a/run.py +++ b/run.py @@ -123,10 +123,10 @@ def main(input_args): sys.exit(1) utils.printcolor("Welcome to Modoboa installer!\n", utils.GREEN) - is_config_file_availible = utils.check_config_file( + is_config_file_available = utils.check_config_file( args.configfile, args.interactive, args.upgrade, args.backup, is_restoring) - if is_config_file_availible and args.backup: + if is_config_file_available and args.backup: utils.printcolor("No config file found,", utils.RED) return From 8dd0b7d4975e9fdbc2f7ae50622072174441b4ed Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 3 Nov 2022 10:57:03 +0100 Subject: [PATCH 42/42] Last camelCase --- modoboa_installer/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modoboa_installer/utils.py b/modoboa_installer/utils.py index 1a8f18d..cf19ff9 100644 --- a/modoboa_installer/utils.py +++ b/modoboa_installer/utils.py @@ -181,7 +181,7 @@ def check_config_file(dest, interactive=False, upgrade=False, backup=False, rest "configuration file.", RED) sys.exit(1) elif backup: - isPresent = False + is_present = False printcolor( "Your configuration file hasn't been found. A new one will be generated. " "Please edit it with correct password for the databases !", RED) @@ -195,7 +195,7 @@ def check_config_file(dest, interactive=False, upgrade=False, backup=False, rest "Configuration file {} not found, creating new one." .format(dest), YELLOW) gen_config(dest, interactive) - return isPresent + return is_present def has_colours(stream):