diff --git a/CHANGELOG.md b/CHANGELOG.md index f34ea3cc..96426b67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ v1.6.1 - unreleased - Enhancement: Make Unbound drop privileges after binding to port - Enhancement: Stop using static assets, but build them using Webpack - Enhancement: Create an Authentication Token with IPv6 address restriction ([#829](https://github.com/Mailu/Mailu/issues/829)) +- Enhancement: Automatically create admin user on container startup if given appropriate environment variables - Enhancement: Missing wildcard option in alias flask command ([#869](https://github.com/Mailu/Mailu/issues/869)) - Bug: Fix creating new fetched accounts - Bug: Fix poor performance if ANTIVIRUS is configured to none. diff --git a/core/admin/mailu/manage.py b/core/admin/mailu/manage.py index e113abfe..819fe410 100644 --- a/core/admin/mailu/manage.py +++ b/core/admin/mailu/manage.py @@ -42,22 +42,44 @@ def advertise(): @click.argument('localpart') @click.argument('domain_name') @click.argument('password') +@click.option('-m', '--mode') @flask_cli.with_appcontext -def admin(localpart, domain_name, password): +def admin(localpart, domain_name, password, mode='create'): """ Create an admin user + 'mode' can be: + - 'create' (default) Will try to create user and will raise an exception if present + - 'ifmissing': if user exists, nothing happens, else it will be created + - 'update': user is created or, if it exists, its password gets updated """ domain = models.Domain.query.get(domain_name) if not domain: domain = models.Domain(name=domain_name) db.session.add(domain) - user = models.User( - localpart=localpart, - domain=domain, - global_admin=True - ) - user.set_password(password) - db.session.add(user) - db.session.commit() + + user = None + if mode == 'ifmissing' or mode == 'update': + email = '{}@{}'.format(localpart, domain_name) + user = models.User.query.get(email) + + if user and mode == 'ifmissing': + print('user %s exists, not updating' % email) + return + + if not user: + user = models.User( + localpart=localpart, + domain=domain, + global_admin=True + ) + user.set_password(password) + db.session.add(user) + db.session.commit() + print("created admin user") + elif mode == 'update': + user.set_password(password) + db.session.commit() + print("updated admin password") + @mailu.command() diff --git a/core/admin/start.py b/core/admin/start.py index bf0dc38f..d8165ff3 100755 --- a/core/admin/start.py +++ b/core/admin/start.py @@ -4,4 +4,13 @@ import os os.system("flask mailu advertise") os.system("flask db upgrade") + +account = os.environ.get("INITIAL_ADMIN_ACCOUNT") +domain = os.environ.get("INITIAL_ADMIN_DOMAIN") +password = os.environ.get("INITIAL_ADMIN_PW") + +if account is not None and domain is not None and password is not None: + mode = os.environ.get("INITIAL_ADMIN_MODE", default="ifmissing") + os.system("flask mailu admin %s %s '%s' --mode %s" % (account, domain, password, mode)) + os.system("gunicorn -w 4 -b :80 --access-logfile - --error-logfile - --preload 'mailu:create_app()'") diff --git a/docs/compose/setup.rst b/docs/compose/setup.rst index c1a620e6..2352f16e 100644 --- a/docs/compose/setup.rst +++ b/docs/compose/setup.rst @@ -96,7 +96,13 @@ You may now start Mailu. Move the to the Mailu directory and run: docker-compose up -d -Finally, you must create the initial admin user account: +Finally, you need an admin user account. + +You can have the system create it automatically: +use the environment variables ``INITIAL_ACCOUNT*`` as described in :ref:`admin_account` +You should set ``INITIAL_ADMIN_MODE`` also to either ``update`` or ``ifmissing``. Leaving it with the default value could cause errors when restarting the system. + +Else, if you don't go with the automatic way, you need to manually create the admin account now: .. code-block:: bash diff --git a/docs/configuration.rst b/docs/configuration.rst index 7b84d6fc..2116888b 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -92,6 +92,31 @@ Both ``SITENAME`` and ``WEBSITE`` are customization options for the panel menu in the admin interface, while ``SITENAME`` is a customization option for every Web interface. +.. _admin_account: + +Admin account - automatic creation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +For administrative tasks, an admin user account will be needed. You can create it manually, +after deploying the system, or automatically. +To create it manually, follow the specific deployment method documentation. + +To have the account created automatically, you just need to define a few environment variables: + +.. code-block:: bash + + INITIAL_ADMIN_ACCOUNT = ``root`` The first part of the e-mail address (ROOT@example.com) + INITIAL_ADMIN_DOMAIN = ``example.com`` the domain appendix. Most probably identical to the DOMAIN variable + INITIAL_ADMIN_PW = ``password`` the chosen password for the user + +Also, environment variable ``INITIAL_ADMIN_MODE`` defines how the code should behave when it will +try to create the admin user: + +- ``create`` (default) Will try to create user and will raise an exception if present +- ``ifmissing``: if user exists, nothing happens, else it will be created +- ``update``: user is created or, if it exists, its password gets updated + +Depending on your particular deployment you most probably will want to change the default. + Advanced settings ----------------- diff --git a/docs/kubernetes/mailu/index.rst b/docs/kubernetes/mailu/index.rst index 0711abbd..ab5f3ca4 100644 --- a/docs/kubernetes/mailu/index.rst +++ b/docs/kubernetes/mailu/index.rst @@ -117,7 +117,17 @@ Create the first admin account ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When the cluster is online you need to create you master user to access https://mail.example.com/admin -Enter the main ``admin`` pod to create the root account: + +You can create it now manually, or have the system create it automatically. + +If you want the system to create the admin user account automatically, see :ref:`admin_account` +about the environment variables needed (``INITIAL_ADMIN_*``). +Also, important, taking into consideration that a pod in Kubernetes can be stopped/rescheduled at +any time, you should set ``INITIAL_ADMIN_MODE`` to either ``update`` or ``ifmissing`` - depending on what you +want to happen to its password. + + +To create the admin user account manually, enter the main ``admin`` pod: .. code-block:: bash @@ -131,13 +141,14 @@ And in the pod run the following command. The command uses following entries: flask mailu admin root example.com password - ``admin`` Make it an admin user -- ``root`` The first part of the e-mail adres (ROOT@example.com) +- ``root`` The first part of the e-mail address (ROOT@example.com) - ``example.com`` the domain appendix - ``password`` the chosen password for the user Now you should be able to login on the mail account: https://mail.example.com/admin + Adaptations ----------- diff --git a/tests/compose/core/00_create_users.sh b/tests/compose/core/00_create_users.sh index eb214f64..d2ee5c20 100755 --- a/tests/compose/core/00_create_users.sh +++ b/tests/compose/core/00_create_users.sh @@ -1,5 +1,11 @@ -echo "Creating users ..." -docker-compose -f tests/compose/core/docker-compose.yml exec admin flask mailu admin admin mailu.io password || exit 1 +echo "Users tests ..." +# Should fail, admin is already auto-created +docker-compose -f tests/compose/core/docker-compose.yml exec admin flask mailu admin admin mailu.io 'FooBar' && exit 1 +echo "The above error was intended!" +# Should not fail, but does nothing; ifmissing mode +docker-compose -f tests/compose/core/docker-compose.yml exec admin flask mailu admin admin mailu.io 'FooBar' --mode=ifmissing || exit 1 +# Should not fail and update the password; update mode +docker-compose -f tests/compose/core/docker-compose.yml exec admin flask mailu admin admin mailu.io 'password' --mode=update || exit 1 docker-compose -f tests/compose/core/docker-compose.yml exec admin flask mailu user user mailu.io 'password' 'SHA512-CRYPT' || exit 1 docker-compose -f tests/compose/core/docker-compose.yml exec admin flask mailu user 'user/with/slash' mailu.io 'password' 'SHA512-CRYPT' || exit 1 -echo "Admin and user successfully created!" +echo "User testing succesfull!" diff --git a/tests/compose/core/mailu.env b/tests/compose/core/mailu.env index d77f3a2d..dd7bd25f 100644 --- a/tests/compose/core/mailu.env +++ b/tests/compose/core/mailu.env @@ -140,3 +140,8 @@ REAL_IP_FROM= # choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) REJECT_UNLISTED_RECIPIENT= + +# Test for initial admin create +INITIAL_ADMIN_ACCOUNT=admin +INITIAL_ADMIN_DOMAIN=mailu.io +INITIAL_ADMIN_PW=FooBar \ No newline at end of file