283 lines
10 KiB
Python
Executable File
283 lines
10 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
"""An installer for Modoboa."""
|
|
|
|
import argparse
|
|
import configparser
|
|
import datetime
|
|
import os
|
|
import sys
|
|
|
|
from modoboa_installer import checks
|
|
from modoboa_installer import compatibility_matrix
|
|
from modoboa_installer import constants
|
|
from modoboa_installer import package
|
|
from modoboa_installer import scripts
|
|
from modoboa_installer import ssl
|
|
from modoboa_installer import system
|
|
from modoboa_installer import utils
|
|
from modoboa_installer import disclaimers
|
|
|
|
|
|
PRIMARY_APPS = [
|
|
"fail2ban",
|
|
"modoboa",
|
|
"automx",
|
|
"radicale",
|
|
"uwsgi",
|
|
"nginx",
|
|
"postfix",
|
|
"dovecot"
|
|
]
|
|
|
|
|
|
def backup_system(config, args):
|
|
"""Launch backup procedure."""
|
|
disclaimers.backup_disclaimer()
|
|
backup_path = None
|
|
if args.silent_backup:
|
|
if not args.backup_path:
|
|
if config.has_option("backup", "default_path"):
|
|
path = config.get("backup", "default_path")
|
|
else:
|
|
path = constants.DEFAULT_BACKUP_DIRECTORY
|
|
date = datetime.datetime.now().strftime("%m_%d_%Y_%H_%M")
|
|
path = os.path.join(path, f"backup_{date}")
|
|
else:
|
|
path = args.backup_path
|
|
backup_path = utils.validate_backup_path(path, args.silent_backup)
|
|
if not backup_path:
|
|
utils.printcolor(f"Path provided: {path}", utils.BLUE)
|
|
return
|
|
else:
|
|
user_value = None
|
|
while not user_value or not backup_path:
|
|
utils.printcolor(
|
|
"Enter backup path (it must be an empty directory)",
|
|
utils.MAGENTA
|
|
)
|
|
utils.printcolor("CTRL+C to cancel", utils.MAGENTA)
|
|
user_value = utils.user_input("-> ")
|
|
if not user_value:
|
|
continue
|
|
backup_path = utils.validate_backup_path(user_value, args.silent_backup)
|
|
|
|
# Backup configuration file
|
|
utils.copy_file(args.configfile, backup_path)
|
|
# Backup applications
|
|
for app in PRIMARY_APPS:
|
|
if app == "dovecot" and args.no_mail:
|
|
utils.printcolor("Skipping mail backup", utils.BLUE)
|
|
continue
|
|
scripts.backup(app, config, backup_path)
|
|
|
|
|
|
def config_file_update_complete(backup_location):
|
|
utils.printcolor("Update complete. It seems successful.",
|
|
utils.BLUE)
|
|
if backup_location is not None:
|
|
utils.printcolor("You will find your old config file "
|
|
f"here: {backup_location}",
|
|
utils.BLUE)
|
|
|
|
|
|
def parser_setup(input_args):
|
|
parser = argparse.ArgumentParser()
|
|
versions = (
|
|
["latest"] + list(compatibility_matrix.COMPATIBILITY_MATRIX.keys())
|
|
)
|
|
parser.add_argument("--debug", action="store_true", default=False,
|
|
help="Enable debug output")
|
|
parser.add_argument("--force", action="store_true", default=False,
|
|
help="Force installation")
|
|
parser.add_argument("--configfile", default="installer.cfg",
|
|
help="Configuration file to use")
|
|
parser.add_argument(
|
|
"--version", default="latest", choices=versions,
|
|
help="Modoboa version to install")
|
|
parser.add_argument(
|
|
"--stop-after-configfile-check", action="store_true", default=False,
|
|
help="Check configuration, generate it if needed and exit")
|
|
parser.add_argument(
|
|
"--interactive", action="store_true", default=False,
|
|
help="Generate configuration file with user interaction")
|
|
parser.add_argument(
|
|
"--upgrade", action="store_true", default=False,
|
|
help="Run the installer in upgrade mode")
|
|
parser.add_argument(
|
|
"--beta", action="store_true", default=False,
|
|
help="Install latest beta release of Modoboa instead of the stable one")
|
|
parser.add_argument(
|
|
"--backup-path", type=str, metavar="path",
|
|
help="To use with --silent-backup, you must provide a valid path")
|
|
parser.add_argument(
|
|
"--backup", action="store_true", default=False,
|
|
help="Backing up interactively previously installed instance"
|
|
)
|
|
parser.add_argument(
|
|
"--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,
|
|
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(
|
|
"--skip-checks", action="store_true", default=False,
|
|
help="Skip the checks the installer performs initially")
|
|
parser.add_argument("domain", type=str,
|
|
help="The main domain of your future mail server")
|
|
return parser.parse_args(input_args)
|
|
|
|
|
|
def main(input_args):
|
|
"""Install process."""
|
|
args = parser_setup(input_args)
|
|
|
|
if args.debug:
|
|
utils.ENV["debug"] = True
|
|
|
|
# Restore prep
|
|
is_restoring = False
|
|
if args.restore is not None:
|
|
is_restoring = True
|
|
args.configfile = os.path.join(args.restore, args.configfile)
|
|
if not os.path.exists(args.configfile):
|
|
utils.error(
|
|
"Installer configuration file not found in backup!"
|
|
)
|
|
sys.exit(1)
|
|
|
|
utils.success("Welcome to Modoboa installer!\n")
|
|
|
|
# Checks
|
|
if not args.skip_checks:
|
|
utils.printcolor("Checking the installer...", utils.BLUE)
|
|
checks.handle()
|
|
utils.success("Checks complete\n")
|
|
|
|
is_config_file_available, outdate_config = utils.check_config_file(
|
|
args.configfile, args.interactive, args.upgrade, args.backup, is_restoring)
|
|
|
|
if not is_config_file_available and (
|
|
args.upgrade or args.backup or args.silent_backup):
|
|
utils.error("No config file found.")
|
|
return
|
|
|
|
# Check if config is outdated and ask user if it needs to be updated
|
|
if is_config_file_available and outdate_config:
|
|
answer = utils.user_input("It seems that your config file is outdated. "
|
|
"Would you like to update it? (Y/n) ")
|
|
if not answer or answer.lower().startswith("y"):
|
|
config_file_update_complete(utils.update_config(args.configfile))
|
|
if not args.stop_after_configfile_check:
|
|
answer = utils.user_input("Would you like to stop to review the updated config? (Y/n)")
|
|
if not answer or answer.lower().startswith("y"):
|
|
return
|
|
else:
|
|
utils.error("You might encounter unexpected errors ! "
|
|
"Make sure to update your config before opening an issue!")
|
|
|
|
if args.stop_after_configfile_check:
|
|
return
|
|
|
|
config = configparser.ConfigParser()
|
|
with open(args.configfile) as fp:
|
|
config.read_file(fp)
|
|
if not config.has_section("general"):
|
|
config.add_section("general")
|
|
config.set("general", "domain", args.domain)
|
|
config.set("dovecot", "domain", args.domain)
|
|
config.set("modoboa", "version", args.version)
|
|
config.set("modoboa", "install_beta", str(args.beta))
|
|
|
|
if config.get("antispam", "type") == "amavis":
|
|
antispam_apps = ["amavis", "opendkim"]
|
|
else:
|
|
antispam_apps = ["rspamd"]
|
|
|
|
if args.backup or args.silent_backup:
|
|
backup_system(config, args)
|
|
return
|
|
|
|
# Display disclaimer python 3 linux distribution
|
|
if args.upgrade:
|
|
disclaimers.upgrade_disclaimer(config)
|
|
elif args.restore:
|
|
disclaimers.restore_disclaimer()
|
|
scripts.restore_prep(args.restore)
|
|
else:
|
|
disclaimers.installation_disclaimer(args, config)
|
|
|
|
# Show concerned components
|
|
components = []
|
|
for section in config.sections():
|
|
if section in ["general", "antispam", "database", "mysql", "postgres",
|
|
"certificate", "letsencrypt", "backup"]:
|
|
continue
|
|
if (config.has_option(section, "enabled") and
|
|
not config.getboolean(section, "enabled")):
|
|
continue
|
|
incompatible_app_detected = not utils.check_app_compatibility(section, config)
|
|
if incompatible_app_detected:
|
|
sys.exit(0)
|
|
components.append(section)
|
|
utils.printcolor(" ".join(components), utils.YELLOW)
|
|
if not args.force:
|
|
answer = utils.user_input("Do you confirm? (Y/n) ")
|
|
if answer.lower().startswith("n"):
|
|
return
|
|
config.set("general", "force", str(args.force))
|
|
utils.printcolor(
|
|
"The process can be long, feel free to take a coffee "
|
|
"and come back later ;)", utils.BLUE)
|
|
utils.success("Starting...")
|
|
package.backend.prepare_system()
|
|
package.backend.install_many(["sudo", "wget"])
|
|
ssl_backend = ssl.get_backend(config)
|
|
if ssl_backend and not args.upgrade:
|
|
ssl_backend.generate_cert()
|
|
for appname in PRIMARY_APPS + antispam_apps:
|
|
scripts.install(appname, config, args.upgrade, args.restore)
|
|
system.restart_service("cron")
|
|
package.backend.restore_system()
|
|
hostname = config.get("general", "hostname")
|
|
if not args.restore:
|
|
utils.success(
|
|
f"Congratulations! You can enjoy Modoboa at https://{hostname} "
|
|
"(admin:password)"
|
|
)
|
|
else:
|
|
utils.success(
|
|
f"Restore complete! You can enjoy Modoboa at https://{hostname} "
|
|
"(same credentials as before)"
|
|
)
|
|
if config.getboolean("rspamd", "enabled"):
|
|
rspamd_password = config.get("rspamd", "password")
|
|
utils.success(
|
|
f"You can also enjoy rspamd at https://{hostname}/rspamd "
|
|
f"(password: {rspamd_password})"
|
|
)
|
|
utils.success(
|
|
"\n"
|
|
"Modoboa is a free software maintained by volunteers.\n"
|
|
"You like the project and want it to be sustainable?\n"
|
|
"Then don't wait anymore and go sponsor it here:\n"
|
|
)
|
|
utils.printcolor(
|
|
"https://github.com/sponsors/modoboa\n",
|
|
utils.YELLOW
|
|
)
|
|
utils.success(
|
|
"Thank you for your help :-)\n"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv[1:])
|