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:
committed by
Antoine Nguyen
parent
45c777683c
commit
19ac9350d7
@@ -1,103 +0,0 @@
|
|||||||
[general]
|
|
||||||
# %(domain)s is the value specified when launching the installer
|
|
||||||
hostname = mail.%(domain)s
|
|
||||||
|
|
||||||
[certificate]
|
|
||||||
generate = true
|
|
||||||
# Choose between self-signed or letsencrypt
|
|
||||||
type = self-signed
|
|
||||||
|
|
||||||
[letsencrypt]
|
|
||||||
email = admin@example.com
|
|
||||||
|
|
||||||
[database]
|
|
||||||
# Select database engine : postgres or mysql
|
|
||||||
engine = postgres
|
|
||||||
#engine = mysql
|
|
||||||
host = 127.0.0.1
|
|
||||||
install = true
|
|
||||||
|
|
||||||
[postgres]
|
|
||||||
user = postgres
|
|
||||||
password =
|
|
||||||
|
|
||||||
[mysql]
|
|
||||||
user = root
|
|
||||||
password = $mysql_password
|
|
||||||
charset = utf8
|
|
||||||
collation = utf8_general_ci
|
|
||||||
|
|
||||||
[modoboa]
|
|
||||||
user = modoboa
|
|
||||||
home_dir = /srv/modoboa
|
|
||||||
venv_path = %(home_dir)s/env
|
|
||||||
instance_path = %(home_dir)s/instance
|
|
||||||
timezone = Europe/Paris
|
|
||||||
dbname = modoboa
|
|
||||||
dbuser = modoboa
|
|
||||||
dbpassword = $modoboa_password
|
|
||||||
# Extensions to install
|
|
||||||
# also available: modoboa-radicale modoboa-dmarc modoboa-imap-migration
|
|
||||||
extensions = modoboa-amavis modoboa-pdfcredentials modoboa-postfix-autoreply modoboa-sievefilters modoboa-stats modoboa-webmail modoboa-contacts
|
|
||||||
|
|
||||||
# Deploy Modoboa and enable development mode
|
|
||||||
devmode = false
|
|
||||||
|
|
||||||
[automx]
|
|
||||||
enabled = true
|
|
||||||
user = automx
|
|
||||||
config_dir = /etc
|
|
||||||
home_dir = /srv/automx
|
|
||||||
venv_path = %(home_dir)s/env
|
|
||||||
instance_path = %(home_dir)s/instance
|
|
||||||
|
|
||||||
[amavis]
|
|
||||||
enabled = true
|
|
||||||
user = amavis
|
|
||||||
max_servers = 1
|
|
||||||
|
|
||||||
dbname = amavis
|
|
||||||
dbuser = amavis
|
|
||||||
dbpassword = $amavis_password
|
|
||||||
|
|
||||||
[clamav]
|
|
||||||
enabled = true
|
|
||||||
user = clamav
|
|
||||||
|
|
||||||
[dovecot]
|
|
||||||
enabled = true
|
|
||||||
config_dir = /etc/dovecot
|
|
||||||
user = vmail
|
|
||||||
home_dir = /srv/vmail
|
|
||||||
mailboxes_owner = vmail
|
|
||||||
# Enable extra procotols (in addition to imap and lmtp)
|
|
||||||
# Example: pop3
|
|
||||||
extra_protocols =
|
|
||||||
# Replace localhost with your domain
|
|
||||||
postmaster_address = postmaster@localhost
|
|
||||||
|
|
||||||
[nginx]
|
|
||||||
enabled = true
|
|
||||||
config_dir = /etc/nginx
|
|
||||||
|
|
||||||
[razor]
|
|
||||||
enabled = true
|
|
||||||
config_dir = /etc/razor
|
|
||||||
|
|
||||||
[postfix]
|
|
||||||
enabled = true
|
|
||||||
config_dir = /etc/postfix
|
|
||||||
message_size_limit = 11534336
|
|
||||||
|
|
||||||
[spamassassin]
|
|
||||||
enabled = true
|
|
||||||
config_dir = /etc/mail/spamassassin
|
|
||||||
|
|
||||||
dbname = spamassassin
|
|
||||||
dbuser = spamassassin
|
|
||||||
dbpassword = $sa_password
|
|
||||||
|
|
||||||
[uwsgi]
|
|
||||||
enabled = true
|
|
||||||
config_dir = /etc/uwsgi
|
|
||||||
nb_processes = 2
|
|
||||||
362
modoboa_installer/config_dict_template.py
Normal file
362
modoboa_installer/config_dict_template.py
Normal 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",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -10,6 +10,12 @@ import shutil
|
|||||||
import string
|
import string
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
try:
|
||||||
|
import configparser
|
||||||
|
except ImportError:
|
||||||
|
import ConfigParser as configparser
|
||||||
|
|
||||||
|
from . import config_dict_template
|
||||||
|
|
||||||
|
|
||||||
ENV = {}
|
ENV = {}
|
||||||
@@ -139,23 +145,14 @@ def copy_from_template(template, dest, context):
|
|||||||
fp.write(ConfigFileTemplate(buf).substitute(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."""
|
"""Create a new installer config file if needed."""
|
||||||
if os.path.exists(dest):
|
if os.path.exists(dest):
|
||||||
return
|
return
|
||||||
printcolor(
|
printcolor(
|
||||||
"Configuration file {} not found, creating new one."
|
"Configuration file {} not found, creating new one."
|
||||||
.format(dest), YELLOW)
|
.format(dest), YELLOW)
|
||||||
with open("installer.cfg.template") as fp:
|
gen_config(dest, interactive)
|
||||||
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))
|
|
||||||
|
|
||||||
|
|
||||||
def has_colours(stream):
|
def has_colours(stream):
|
||||||
@@ -222,3 +219,67 @@ def random_key(l=16):
|
|||||||
key = "".join(random.sample(population * l, l))
|
key = "".join(random.sample(population * l, l))
|
||||||
if len(key) == l:
|
if len(key) == l:
|
||||||
return key
|
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)
|
||||||
|
|||||||
5
run.py
5
run.py
@@ -31,6 +31,9 @@ def main():
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--stop-after-configfile-check", action="store_true", default=False,
|
"--stop-after-configfile-check", action="store_true", default=False,
|
||||||
help="Check configuration, generate it if needed and exit")
|
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("domain", type=str,
|
parser.add_argument("domain", type=str,
|
||||||
help="The main domain of your future mail server")
|
help="The main domain of your future mail server")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -38,7 +41,7 @@ def main():
|
|||||||
if args.debug:
|
if args.debug:
|
||||||
utils.ENV["debug"] = True
|
utils.ENV["debug"] = True
|
||||||
utils.printcolor("Welcome to Modoboa installer!", utils.GREEN)
|
utils.printcolor("Welcome to Modoboa installer!", utils.GREEN)
|
||||||
utils.check_config_file(args.configfile)
|
utils.check_config_file(args.configfile, args.interactive)
|
||||||
if args.stop_after_configfile_check:
|
if args.stop_after_configfile_check:
|
||||||
return
|
return
|
||||||
config = configparser.SafeConfigParser()
|
config = configparser.SafeConfigParser()
|
||||||
|
|||||||
Reference in New Issue
Block a user