Add configuration wizard (#158)

* add --interactive option refs #133

* delete uneeded template as config is a dict now

* minor changes after code review
This commit is contained in:
Yohann Rebattu
2017-10-08 11:29:34 +02:00
committed by Antoine Nguyen
parent 45c777683c
commit 19ac9350d7
4 changed files with 438 additions and 115 deletions

View File

@@ -0,0 +1,362 @@
import random
import string
def make_password(length=16):
"""Create a random password."""
return "".join(
random.SystemRandom().choice(
string.ascii_letters + string.digits) for _ in range(length))
# Validators should return a tuple bool, error message
def is_email(user_input):
"""Return True in input is a valid email"""
return "@" in user_input, "Please enter a valid email"
ConfigDictTemplate = [
{
"name": "general",
"values": [
{
"option": "hostname",
"default": "mail.%(domain)s",
}
]
},
{
"name": "certificate",
"values": [
{
"option": "generate",
"default": "true",
},
{
"option": "type",
"default": "self-signed",
"customizable": True,
"question": "Please choose your certificate type",
"values": ["self-signed", "letsencrypt"],
}
],
},
{
"name": "letsencrypt",
"if": "certificate.type=letsencrypt",
"values": [
{
"option": "email",
"default": "admin@example.com",
"question": (
"Please enter the mail you wish to use for "
"letsencrypt"),
"customizable": True,
"validators": [is_email]
}
]
},
{
"name": "database",
"values": [
{
"option": "engine",
"default": "postgres",
"customizable": True,
"question": "Please choose your database engine",
"values": ["postgres", "mysql"],
},
{
"option": "host",
"default": "127.0.0.1",
},
{
"option": "install",
"default": "true",
}
]
},
{
"name": "postgres",
"if": "database.engine=postgres",
"values": [
{
"option": "user",
"default": "postgres",
},
{
"option": "password",
"default": "",
"customizable": True,
"question": "Please enter postgres password",
},
]
},
{
"name": "mysql",
"if": "database.engine=mysql",
"values": [
{
"option": "user",
"default": "root",
},
{
"option": "password",
"default": make_password,
"customizable": True,
"question": "Please enter mysql root password"
},
{
"option": "charset",
"default": "utf8",
},
{
"option": "collation",
"default": "utf8_general_ci",
}
]
},
{
"name": "modoboa",
"values": [
{
"option": "user",
"default": "modoboa",
},
{
"option": "home_dir",
"default": "/srv/modoboa",
},
{
"option": "venv_path",
"default": "%(home_dir)s/instance",
},
{
"option": "instance_path",
"default": "%(home_dir)s/env",
},
{
"option": "timezone",
"default": "Europe/Paris",
},
{
"option": "dbname",
"default": "modoboa",
},
{
"option": "dbuser",
"default": "modoboa",
},
{
"option": "dbpassword",
"default": make_password,
"customizable": True,
"question": "Please enter Modoboa db password",
},
{
"option": "extensions",
"default": (
"modoboa-amavis modoboa-pdfcredentials "
"modoboa-postfix-autoreply modoboa-sievefilters "
"modoboa-stats modoboa-webmail modoboa-contacts"),
},
{
"option": "devmod",
"default": "false",
},
]
},
{
"name": "automx",
"values": [
{
"option": "enabled",
"default": "true",
},
{
"option": "user",
"default": "automx",
},
{
"option": "config_dir",
"default": "/etc",
},
{
"option": "home_dir",
"default": "/srv/automx",
},
{
"option": "venv_path",
"default": "%(home_dir)s/env",
},
{
"option": "instance_path",
"default": "%(home_dir)s/instance",
},
]
},
{
"name": "amavis",
"values": [
{
"option": "enabled",
"default": "true",
},
{
"option": "user",
"default": "amavis",
},
{
"option": "max_servers",
"default": "1",
},
{
"option": "dbname",
"default": "amavis",
},
{
"option": "dbuser",
"default": "amavis",
},
{
"option": "dbpassword",
"default": make_password,
"customizable": True,
"question": "Please enter amavis db password"
},
],
},
{
"name": "clamav",
"values": [
{
"option": "enabled",
"default": "true",
},
{
"option": "user",
"default": "clamav",
},
]
},
{
"name": "dovecot",
"values": [
{
"option": "enabled",
"default": "true",
},
{
"option": "config_dir",
"default": "/etc/dovecot",
},
{
"option": "user",
"default": "vmail",
},
{
"option": "home_dir",
"default": "/srv/vmail",
},
{
"option": "mailboxes_owner",
"default": "vmail",
},
{
"option": "extra_protocols",
"default": "",
},
{
"option": "postmaster_address",
"default": "postmaster@%(domain)s",
},
]
},
{
"name": "nginx",
"values": [
{
"option": "enabled",
"default": "true",
},
{
"option": "config_dir",
"default": "/etc/nginx",
},
],
},
{
"name": "razor",
"values": [
{
"option": "enabled",
"default": "true",
},
{
"option": "config_dir",
"default": "/etc/razor",
},
]
},
{
"name": "postfix",
"values": [
{
"option": "enabled",
"default": "true",
},
{
"option": "config_dir",
"default": "/etc/postfix",
},
{
"option": "message_size_limit",
"default": "11534336",
},
]
},
{
"name": "spamassassin",
"values": [
{
"option": "enabled",
"default": "true",
},
{
"option": "config_dir",
"default": "/etc/mail/spamassassin",
},
{
"option": "dbname",
"default": "spamassassin",
},
{
"option": "dbuser",
"default": "spamassassin",
},
{
"option": "dbpassword",
"default": make_password,
"customizable": True,
"question": "Please enter spamassassin db password"
},
]
},
{
"name": "uwsgi",
"values": [
{
"option": "enabled",
"default": "true",
},
{
"option": "config_dir",
"default": "/etc/uwsgi",
},
{
"option": "nb_processes",
"default": "2",
},
]
},
]

View File

@@ -10,6 +10,12 @@ import shutil
import string
import subprocess
import sys
try:
import configparser
except ImportError:
import ConfigParser as configparser
from . import config_dict_template
ENV = {}
@@ -139,23 +145,14 @@ def copy_from_template(template, dest, context):
fp.write(ConfigFileTemplate(buf).substitute(context))
def check_config_file(dest):
def check_config_file(dest, interactive=False):
"""Create a new installer config file if needed."""
if os.path.exists(dest):
return
printcolor(
"Configuration file {} not found, creating new one."
.format(dest), YELLOW)
with open("installer.cfg.template") as fp:
buf = fp.read()
context = {
"mysql_password": make_password(),
"modoboa_password": make_password(),
"amavis_password": make_password(),
"sa_password": make_password()
}
with open(dest, "w") as fp:
fp.write(string.Template(buf).substitute(context))
gen_config(dest, interactive)
def has_colours(stream):
@@ -222,3 +219,67 @@ def random_key(l=16):
key = "".join(random.sample(population * l, l))
if len(key) == l:
return key
def validate(value, config_entry):
if value is None:
return False
if "values" not in config_entry and "validators" not in config_entry:
return True
if "values" in config_entry:
try:
value = int(value)
except ValueError:
return False
return value >= 0 and value < len(config_entry["values"])
if "validators" in config_entry:
for validator in config_entry["validators"]:
valide, message = validator(value)
if not valide:
printcolor(message, MAGENTA)
return False
return True
def get_entry_value(entry, interactive):
if callable(entry["default"]):
default_value = entry["default"]()
else:
default_value = entry["default"]
user_value = None
if entry.get("customizable") and interactive:
while (user_value != '' and not validate(user_value, entry)):
print(entry.get("question"))
if entry.get("values"):
print("Please choose from the list")
values = entry.get("values")
for index, value in enumerate(values):
print("{} {}".format(index, value))
print("default is <{}>".format(default_value))
user_value = user_input("->")
if entry.get("values") and user_value != '':
user_value = values[int(user_value)]
return user_value if user_value else default_value
def gen_config(dest, interactive=False):
"""Create config file from dict template"""
tpl_dict = config_dict_template.ConfigDictTemplate
config = configparser.ConfigParser()
# only ask about options we need, else still generate default
for section in tpl_dict:
if "if" in section:
config_key, value = section.get("if").split("=")
section_name, option = config_key.split(".")
interactive_section = (
config.get(section_name, option) == value and interactive)
else:
interactive_section = interactive
config.add_section(section["name"])
for config_entry in section["values"]:
value = get_entry_value(config_entry, interactive_section)
config.set(section["name"], config_entry["option"], value)
with open(dest, "w") as configfile:
config.write(configfile)