Skip to content
Permalink
Browse files

Merge pull request #261 from modoboa/feature/upgrade_mode

Installer upgrade mode.
  • Loading branch information...
tonioo committed Mar 19, 2019
2 parents cac8c35 + 503145e commit 6c28be4cca9886072bf0b9097d9e75484985da79
@@ -42,6 +42,8 @@ The following components are installed by the installer:
* Dovecot
* Amavis (with SpamAssassin and ClamAV)
* automx (autoconfiguration service)
* OpenDKIM
* Radicale (CalDAV and CardDAV server)

If you want to customize configuration before running the installer,
run the following command::
@@ -66,6 +68,22 @@ a previous one using the ``--version`` option::
If you want more information about the installation process, add the
``--debug`` option to your command line.

Upgrade mode
------------

An experimental upgrade mode is available.

.. note::

You must keep the original configuration file, ie the one used for
the installation. Otherwise, you won't be able to use this mode.

You can activate it as follows::

$ sudo ./run.py --upgrade <your domain>

It will automatically install latest versions of modoboa and its plugins.

Change the generated hostname
-----------------------------

@@ -6,7 +6,7 @@
from .. import utils


def install(appname, config):
def install(appname, config, upgrade):
"""Install an application."""
if (config.has_option(appname, "enabled") and
not config.getboolean(appname, "enabled")):
@@ -19,7 +19,7 @@ def install(appname, config):
print("Unknown application {}".format(appname))
sys.exit(1)
try:
getattr(script, appname.capitalize())(config).run()
getattr(script, appname.capitalize())(config, upgrade).run()
except utils.FatalError as inst:
utils.printcolor(u"{}".format(inst), utils.RED)
sys.exit(1)
@@ -85,5 +85,5 @@ def post_run(self):
"""Additional tasks."""
with open("/etc/mailname", "w") as fp:
fp.write("{}\n".format(self.config.get("general", "hostname")))
install("spamassassin", self.config)
install("clamav", self.config)
install("spamassassin", self.config, self.upgrade)
install("clamav", self.config, self.upgrade)
@@ -24,11 +24,11 @@ class Automx(base.Installer):
}
with_user = True

def __init__(self, config):
def __init__(self, *args, **kwargs):
"""Get configuration."""
super(Automx, self).__init__(config)
self.venv_path = config.get("automx", "venv_path")
self.instance_path = config.get("automx", "instance_path")
super(Automx, self).__init__(*args, **kwargs)
self.venv_path = self.config.get("automx", "venv_path")
self.instance_path = self.config.get("automx", "instance_path")

def get_template_context(self):
"""Additional variables."""
@@ -19,9 +19,10 @@ class Installer(object):
with_db = False
config_files = []

def __init__(self, config):
def __init__(self, config, upgrade):
"""Get configuration."""
self.config = config
self.upgrade = upgrade
if self.config.has_section(self.appname):
self.app_config = dict(self.config.items(self.appname))
self.dbengine = self.config.get("database", "engine")
@@ -67,8 +68,8 @@ def setup_database(self):
self.backend.load_sql_file(
self.dbname, self.dbuser, self.dbpasswd, schema)

def create_user(self):
"""Create a system user."""
def setup_user(self):
"""Setup a system user."""
if not self.with_user:
return
self.user = self.config.get(self.appname, "user")
@@ -143,8 +144,9 @@ def restart_daemon(self):
def run(self):
"""Run the installer."""
self.install_packages()
self.create_user()
self.setup_database()
self.setup_user()
if not self.upgrade:
self.setup_database()
self.install_config_files()
self.post_run()
self.restart_daemon()
@@ -36,13 +36,13 @@ class Modoboa(base.Installer):
with_db = True
with_user = True

def __init__(self, config):
def __init__(self, *args, **kwargs):
"""Get configuration."""
super(Modoboa, self).__init__(config)
self.venv_path = config.get("modoboa", "venv_path")
self.instance_path = config.get("modoboa", "instance_path")
self.extensions = config.get("modoboa", "extensions").split()
self.devmode = config.getboolean("modoboa", "devmode")
super(Modoboa, self).__init__(*args, **kwargs)
self.venv_path = self.config.get("modoboa", "venv_path")
self.instance_path = self.config.get("modoboa", "instance_path")
self.extensions = self.config.get("modoboa", "extensions").split()
self.devmode = self.config.getboolean("modoboa", "devmode")
# Sanity check for amavis
self.amavis_enabled = False
if "modoboa-amavis" in self.extensions:
@@ -87,7 +87,8 @@ def _setup_venv(self):
packages.append(extension)
# Temp fix for https://github.com/modoboa/modoboa-installer/issues/197
python.install_package(
modoboa_package, self.venv_path, binary=False, sudo_user=self.user)
modoboa_package, self.venv_path,
upgrade=self.upgrade, binary=False, sudo_user=self.user)
if self.dbengine == "postgres":
packages.append("psycopg2-binary")
else:
@@ -99,18 +100,23 @@ def _setup_venv(self):
# Temp. fix
packages += [
"https://github.com/modoboa/caldav/tarball/master#egg=caldav"]
python.install_packages(packages, self.venv_path, sudo_user=self.user)
python.install_packages(
packages, self.venv_path, upgrade=self.upgrade, sudo_user=self.user)
if self.devmode:
# FIXME: use dev-requirements instead
python.install_packages(
["django-bower", "django-debug-toolbar"], self.venv_path,
sudo_user=self.user)
upgrade=self.upgrade, sudo_user=self.user)

def _deploy_instance(self):
"""Deploy Modoboa."""
target = os.path.join(self.home_dir, "instance")
if os.path.exists(target):
if not self.config.getboolean("general", "force"):
condition = (
not self.upgrade and
not self.config.getboolean("general", "force")
)
if condition:
utils.printcolor(
"Target directory for Modoboa deployment ({}) already "
"exists. If you choose to continue, it will be removed."
@@ -239,4 +245,5 @@ def post_run(self):
"""Additional tasks."""
self._setup_venv()
self._deploy_instance()
self.apply_settings()
if not self.upgrade:
self.apply_settings()
@@ -25,7 +25,8 @@ def get_template_context(self, app):
context.update({
"app_instance_path": (
self.config.get(app, "instance_path")),
"uwsgi_socket_path": Uwsgi(self.config).get_socket_path(app)
"uwsgi_socket_path": (
Uwsgi(self.config, self.upgrade).get_socket_path(app))
})
return context

@@ -97,4 +97,4 @@ def post_run(self):
utils.exec_cmd("postalias {}".format(aliases_file))

# Postwhite
install("postwhite", self.config)
install("postwhite", self.config, self.upgrade)
@@ -22,10 +22,10 @@ class Radicale(base.Installer):
}
with_user = True

def __init__(self, config):
def __init__(self, *args, **kwargs):
"""Get configuration."""
super(Radicale, self).__init__(config)
self.venv_path = config.get("radicale", "venv_path")
super(Radicale, self).__init__(*args, **kwargs)
self.venv_path = self.config.get("radicale", "venv_path")

def _setup_venv(self):
"""Prepare a dedicated virtualenv."""
@@ -61,7 +61,7 @@ def post_run(self):
"pyzor --homedir {} discover".format(pw[5]),
sudo_user=amavis_user, login=False
)
install("razor", self.config)
install("razor", self.config, self.upgrade)
if utils.dist_name() in ["debian", "ubuntu"]:
utils.exec_cmd(
"perl -pi -e 's/^CRON=0/CRON=1/' /etc/cron.daily/spamassassin")
@@ -43,7 +43,7 @@ def __init__(self, *args, **kwargs):
return
raise RuntimeError("Cannot find a directory to store certificate")

def create(self):
def generate_cert(self):
"""Create a certificate."""
if not self.overwrite_existing_certificate():
return
@@ -61,26 +61,30 @@ def create(self):
class LetsEncryptCertificate(CertificateBackend):
"""Create a certificate using letsencrypt."""

def create(self):
def __init__(self, *args, **kwargs):
"""Update config."""
super(LetsEncryptCertificate, self).__init__(*args, **kwargs)
self.hostname = self.config.get("general", "hostname")
self.config.set("general", "tls_cert_file", (
"/etc/letsencrypt/live/{}/fullchain.pem".format(self.hostname)))
self.config.set("general", "tls_key_file", (
"/etc/letsencrypt/live/{}/privkey.pem".format(self.hostname)))

def generate_cert(self):
"""Create a certificate."""
utils.printcolor(
"Generating new certificate using letsencrypt", utils.YELLOW)
hostname = self.config.get("general", "hostname")
utils.exec_cmd(
"wget https://dl.eff.org/certbot-auto; chmod a+x certbot-auto",
cwd="/opt")
utils.exec_cmd(
"/opt/certbot-auto certonly -n --standalone -d {} "
"-m {} --agree-tos".format(
hostname, self.config.get("letsencrypt", "email")))
self.config.set("general", "tls_cert_file", (
"/etc/letsencrypt/live/{}/fullchain.pem".format(hostname)))
self.config.set("general", "tls_key_file", (
"/etc/letsencrypt/live/{}/privkey.pem".format(hostname)))
self.hostname, self.config.get("letsencrypt", "email")))
with open("/etc/cron.d/letsencrypt", "w") as fp:
fp.write("0 */12 * * * root /opt/certbot-auto renew "
"--quiet --no-self-upgrade --force-renewal\n")
cfg_file = "/etc/letsencrypt/renewal/{}.conf".format(hostname)
cfg_file = "/etc/letsencrypt/renewal/{}.conf".format(self.hostname)
pattern = "s/authenticator = standalone/authenticator = nginx/"
utils.exec_cmd("perl -pi -e '{}' {}".format(pattern, cfg_file))

@@ -145,10 +145,15 @@ def copy_from_template(template, dest, context):
fp.write(ConfigFileTemplate(buf).substitute(context))


def check_config_file(dest, interactive=False):
def check_config_file(dest, interactive=False, upgrade=False):
"""Create a new installer config file if needed."""
if os.path.exists(dest):
return
if upgrade:
printcolor(
"You cannot upgrade an existing installation without a "
"configuration file.", RED)
sys.exit(1)
printcolor(
"Configuration file {} not found, creating new one."
.format(dest), YELLOW)
77 run.py
@@ -17,6 +17,34 @@
from modoboa_installer import utils


def installation_disclaimer(args, config):
"""Display installation disclaimer."""
hostname = config.get("general", "hostname")
utils.printcolor(
"Warning:\n"
"Before you start the installation, please make sure the following "
"DNS records exist for domain '{}':\n"
" {} IN A <IP ADDRESS OF YOUR SERVER>\n"
" IN MX {}.\n".format(
args.domain,
hostname.replace(".{}".format(args.domain), ""),
hostname
),
utils.CYAN
)
utils.printcolor(
"Your mail server will be installed with the following components:",
utils.BLUE)


def upgrade_disclaimer(config):
"""Display upgrade disclaimer."""
utils.printcolor(
"Your mail server is about to be upgraded and the following components"
" will be impacted:", utils.BLUE
)


def main(input_args):
"""Install process."""
parser = argparse.ArgumentParser()
@@ -38,14 +66,17 @@ def main(input_args):
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("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
utils.printcolor("Welcome to Modoboa installer!\n", utils.GREEN)
utils.check_config_file(args.configfile, args.interactive)
utils.check_config_file(args.configfile, args.interactive, args.upgrade)
if args.stop_after_configfile_check:
return
config = configparser.SafeConfigParser()
@@ -56,22 +87,12 @@ def main(input_args):
config.set("general", "domain", args.domain)
config.set("dovecot", "domain", args.domain)
config.set("modoboa", "version", args.version)
hostname = config.get("general", "hostname")
utils.printcolor(
"Warning:\n"
"Before you start the installation, please make sure the following "
"DNS records exist for domain '{}':\n"
" {} IN A <IP ADDRESS OF YOUR SERVER>\n"
" IN MX {}.\n".format(
args.domain,
hostname.replace(".{}".format(args.domain), ""),
hostname
),
utils.CYAN
)
utils.printcolor(
"Your mail server will be installed with the following components:",
utils.BLUE)
# Display disclaimer
if not args.upgrade:
installation_disclaimer(args, config)
else:
upgrade_disclaimer(config)
# Show concerned components
components = []
for section in config.sections():
if section in ["general", "database", "mysql", "postgres",
@@ -93,17 +114,17 @@ def main(input_args):
utils.printcolor("Starting...", utils.GREEN)
package.backend.install_many(["sudo", "wget"])
ssl_backend = ssl.get_backend(config)
if ssl_backend:
ssl_backend.create()
scripts.install("amavis", config)
scripts.install("modoboa", config)
scripts.install("automx", config)
scripts.install("radicale", config)
scripts.install("uwsgi", config)
scripts.install("nginx", config)
scripts.install("opendkim", config)
scripts.install("postfix", config)
scripts.install("dovecot", 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)
system.restart_service("cron")
utils.printcolor(
"Congratulations! You can enjoy Modoboa at https://{} (admin:password)"
Oops, something went wrong.

0 comments on commit 6c28be4

Please sign in to comment.
You can’t perform that action at this time.