We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
admin container on master branch fails with mysql
Thank you for opening an issue with Mailu. Please understand that issues are meant for bugs and enhancement-requests.
For **user-support questions**, reach out to us  on [matrix](https://matrix.to/#/#mailu:tedomum.net).

To be able to help you best, we need some more information.

## Before you open your issue
- [*] Check if no issue or pull-request for this already exists.
- [*] Check [documentation](https://mailu.io/master/) and [FAQ](https://mailu.io/master/faq.html). (Tip, use the search function on the documentation page)
- [*] You understand `Mailu` is made by volunteers in their **free time** — be conscise, civil and accept that delays can occur.
- [*] The title of the issue should be short and simple. It should contain specific terms related to the actual issue. Be specific while writing the title.

## Environment & Versions
### Environment
 - [*] docker-compose
 - [ ] kubernetes
 - [ ] docker swarm

### Versions
To find your version, get the image name of a mailu container and read  the version from the tag (example for version 1.7).
```
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}admin:${MAILU_VERSION:-master}
```

## Description
The last 2-3 admin builds of the master branch fail on mysql with this error:
ModuleNotFoundError: No module named 'MySQLdb'
This is not a production environment, just wanted to report.
My mysql server is as follows, but with the admin:1.9 image the setup works properly, so it isn't a mysql issue.
mysql  Ver 15.1 Distrib 10.8.4-MariaDB, for debian-linux-gnu (x86_64) using  EditLine wrapper


## Replication Steps
Steps for replicating your issue

## Expected behaviour

## Logs

````markdown
```
admin_1      | Traceback (most recent call last):
admin_1      |   File "/usr/lib/python3.9/site-packages/sqlalchemy/util/_collections.py", line 1008, in __call__
admin_1      |     return self.registry[key]
admin_1      | KeyError: <greenlet.greenlet object at 0x7f231b79da90 (otid=0x7f231b623340) current active started main>
admin_1      | During handling of the above exception, another exception occurred:
admin_1      | Traceback (most recent call last):
admin_1      |   File "/usr/lib/python3.9/site-packages/flask/app.py", line 2073, in wsgi_app
admin_1      |     response = self.full_dispatch_request()
admin_1      |   File "/usr/lib/python3.9/site-packages/flask/app.py", line 1518, in full_dispatch_request
admin_1      |     rv = self.handle_user_exception(e)
admin_1      |   File "/usr/lib/python3.9/site-packages/flask/app.py", line 1516, in full_dispatch_request
admin_1      |     rv = self.dispatch_request()
admin_1      |   File "/usr/lib/python3.9/site-packages/flask/app.py", line 1502, in dispatch_request
admin_1      |     return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
admin_1      |   File "/app/mailu/sso/views/base.py", line 49, in login
admin_1      |     return flask.render_template('login.html', form=form, fields=fields)
admin_1      |   File "/usr/lib/python3.9/site-packages/flask/templating.py", line 146, in render_template
admin_1      |     ctx.app.update_template_context(context)
admin_1      |   File "/usr/lib/python3.9/site-packages/flask/app.py", line 756, in update_template_context
admin_1      |     context.update(func())
admin_1      |   File "/app/mailu/__init__.py", line 54, in inject_defaults
admin_1      |     signup_domains = models.Domain.query.filter_by(signup_enabled=True).all()
admin_1      |   File "/usr/lib/python3.9/site-packages/flask_sqlalchemy/__init__.py", line 552, in __get__
admin_1      |     return type.query_class(mapper, session=self.sa.session())
admin_1      |   File "/usr/lib/python3.9/site-packages/sqlalchemy/orm/scoping.py", line 47, in __call__
admin_1      |     sess = self.registry()
admin_1      |   File "/usr/lib/python3.9/site-packages/sqlalchemy/util/_collections.py", line 1010, in __call__
admin_1      |     return self.registry.setdefault(key, self.createfunc())
admin_1      |   File "/usr/lib/python3.9/site-packages/sqlalchemy/orm/session.py", line 4172, in __call__
admin_1      |     return self.class_(**local_kw)
admin_1      |   File "/usr/lib/python3.9/site-packages/flask_sqlalchemy/__init__.py", line 176, in __init__
admin_1      |     bind = options.pop('bind', None) or db.engine
admin_1      |   File "/usr/lib/python3.9/site-packages/flask_sqlalchemy/__init__.py", line 998, in engine
admin_1      |     return self.get_engine()
admin_1      |   File "/usr/lib/python3.9/site-packages/flask_sqlalchemy/__init__.py", line 1017, in get_engine
admin_1      |     return connector.get_engine()
admin_1      |   File "/usr/lib/python3.9/site-packages/flask_sqlalchemy/__init__.py", line 594, in get_engine
admin_1      |     self._engine = rv = self._sa.create_engine(sa_url, options)
admin_1      |   File "/usr/lib/python3.9/site-packages/flask_sqlalchemy/__init__.py", line 1027, in create_engine
admin_1      |     return sqlalchemy.create_engine(sa_url, **engine_opts)
admin_1      |   File "<string>", line 2, in create_engine
admin_1      |   File "/usr/lib/python3.9/site-packages/sqlalchemy/util/deprecations.py", line 298, in warned
admin_1      |     return fn(*args, **kwargs)
admin_1      |   File "/usr/lib/python3.9/site-packages/sqlalchemy/engine/create.py", line 560, in create_engine
admin_1      |     dbapi = dialect_cls.dbapi(**dbapi_args)
admin_1      |   File "/usr/lib/python3.9/site-packages/sqlalchemy/dialects/mysql/mysqldb.py", line 167, in dbapi
admin_1      |     return __import__("MySQLdb")
admin_1      | ModuleNotFoundError: No module named 'MySQLdb'

```
````


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `core/admin/mailu/configuration.py`
Content:
```
1 import os
2 
3 from datetime import timedelta
4 from socrate import system
5 import ipaddress
6 
7 DEFAULT_CONFIG = {
8     # Specific to the admin UI
9     'DOCKER_SOCKET': 'unix:///var/run/docker.sock',
10     'BABEL_DEFAULT_LOCALE': 'en',
11     'BABEL_DEFAULT_TIMEZONE': 'UTC',
12     'BOOTSTRAP_SERVE_LOCAL': True,
13     'RATELIMIT_STORAGE_URL': '',
14     'QUOTA_STORAGE_URL': '',
15     'DEBUG': False,
16     'DOMAIN_REGISTRATION': False,
17     'TEMPLATES_AUTO_RELOAD': True,
18     'MEMORY_SESSIONS': False,
19     # Database settings
20     'DB_FLAVOR': None,
21     'DB_USER': 'mailu',
22     'DB_PW': None,
23     'DB_HOST': 'database',
24     'DB_NAME': 'mailu',
25     'SQLITE_DATABASE_FILE':'data/main.db',
26     'SQLALCHEMY_DATABASE_URI': 'sqlite:////data/main.db',
27     'SQLALCHEMY_TRACK_MODIFICATIONS': False,
28     # Statistics management
29     'INSTANCE_ID_PATH': '/data/instance',
30     'STATS_ENDPOINT': '19.{}.stats.mailu.io',
31     # Common configuration variables
32     'SECRET_KEY': 'changeMe',
33     'DOMAIN': 'mailu.io',
34     'HOSTNAMES': 'mail.mailu.io,alternative.mailu.io,yetanother.mailu.io',
35     'POSTMASTER': 'postmaster',
36     'WILDCARD_SENDERS': '',
37     'TLS_FLAVOR': 'cert',
38     'INBOUND_TLS_ENFORCE': False,
39     'DEFER_ON_TLS_ERROR': True,
40     'AUTH_RATELIMIT_IP': '60/hour',
41     'AUTH_RATELIMIT_IP_V4_MASK': 24,
42     'AUTH_RATELIMIT_IP_V6_MASK': 56,
43     'AUTH_RATELIMIT_USER': '100/day',
44     'AUTH_RATELIMIT_EXEMPTION': '',
45     'AUTH_RATELIMIT_EXEMPTION_LENGTH': 86400,
46     'DISABLE_STATISTICS': False,
47     # Mail settings
48     'DMARC_RUA': None,
49     'DMARC_RUF': None,
50     'WELCOME': False,
51     'WELCOME_SUBJECT': 'Dummy welcome topic',
52     'WELCOME_BODY': 'Dummy welcome body',
53     'DKIM_SELECTOR': 'dkim',
54     'DKIM_PATH': '/dkim/{domain}.{selector}.key',
55     'DEFAULT_QUOTA': 1000000000,
56     'MESSAGE_RATELIMIT': '200/day',
57     'MESSAGE_RATELIMIT_EXEMPTION': '',
58     'RECIPIENT_DELIMITER': '',
59     # Web settings
60     'SITENAME': 'Mailu',
61     'WEBSITE': 'https://mailu.io',
62     'ADMIN' : 'none',
63     'WEB_ADMIN': '/admin',
64     'WEB_WEBMAIL': '/webmail',
65     'WEBMAIL': 'none',
66     'RECAPTCHA_PUBLIC_KEY': '',
67     'RECAPTCHA_PRIVATE_KEY': '',
68     'LOGO_URL': None,
69     'LOGO_BACKGROUND': None,
70     # Advanced settings
71     'LOG_LEVEL': 'WARNING',
72     'SESSION_KEY_BITS': 128,
73     'SESSION_TIMEOUT': 3600,
74     'PERMANENT_SESSION_LIFETIME': 30*24*3600,
75     'SESSION_COOKIE_SECURE': True,
76     'CREDENTIAL_ROUNDS': 12,
77     'TZ': 'Etc/UTC',
78     # Host settings
79     'HOST_IMAP': 'imap',
80     'HOST_LMTP': 'imap:2525',
81     'HOST_POP3': 'imap',
82     'HOST_SMTP': 'smtp',
83     'HOST_AUTHSMTP': 'smtp',
84     'HOST_ADMIN': 'admin',
85     'HOST_WEBMAIL': 'webmail',
86     'HOST_WEBDAV': 'webdav:5232',
87     'HOST_REDIS': 'redis',
88     'HOST_FRONT': 'front',
89     'SUBNET': '192.168.203.0/24',
90     'SUBNET6': None,
91     'POD_ADDRESS_RANGE': None
92 }
93 
94 class ConfigManager:
95     """ Naive configuration manager that uses environment only
96     """
97 
98     DB_TEMPLATES = {
99         'sqlite': 'sqlite:////{SQLITE_DATABASE_FILE}',
100         'postgresql': 'postgresql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}',
101         'mysql': 'mysql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}'
102     }
103 
104     def __init__(self):
105         self.config = dict()
106 
107     def get_host_address(self, name):
108         # if MYSERVICE_ADDRESS is defined, use this
109         if f'{name}_ADDRESS' in os.environ:
110             return os.environ.get(f'{name}_ADDRESS')
111         # otherwise use the host name and resolve it
112         return system.resolve_address(self.config[f'HOST_{name}'])
113 
114     def resolve_hosts(self):
115         for key in ['IMAP', 'POP3', 'AUTHSMTP', 'SMTP', 'REDIS']:
116             self.config[f'{key}_ADDRESS'] = self.get_host_address(key)
117         if self.config['WEBMAIL'] != 'none':
118             self.config['WEBMAIL_ADDRESS'] = self.get_host_address('WEBMAIL')
119 
120     def __get_env(self, key, value):
121         key_file = key + "_FILE"
122         if key_file in os.environ:
123             with open(os.environ.get(key_file)) as file:
124                 value_from_file = file.read()
125             return value_from_file.strip()
126         else:
127             return os.environ.get(key, value)
128 
129     def __coerce_value(self, value):
130         if isinstance(value, str) and value.lower() in ('true','yes'):
131             return True
132         elif isinstance(value, str) and value.lower() in ('false', 'no'):
133             return False
134         return value
135 
136     def init_app(self, app):
137         # get current app config
138         self.config.update(app.config)
139         # get environment variables
140         self.config.update({
141             key: self.__coerce_value(self.__get_env(key, value))
142             for key, value in DEFAULT_CONFIG.items()
143         })
144         self.resolve_hosts()
145 
146         # automatically set the sqlalchemy string
147         if self.config['DB_FLAVOR']:
148             template = self.DB_TEMPLATES[self.config['DB_FLAVOR']]
149             self.config['SQLALCHEMY_DATABASE_URI'] = template.format(**self.config)
150 
151         self.config['RATELIMIT_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/2'
152         self.config['QUOTA_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/1'
153         self.config['SESSION_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/3'
154         self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
155         self.config['SESSION_COOKIE_HTTPONLY'] = True
156         self.config['SESSION_PERMANENT'] = True
157         self.config['SESSION_TIMEOUT'] = int(self.config['SESSION_TIMEOUT'])
158         self.config['PERMANENT_SESSION_LIFETIME'] = int(self.config['PERMANENT_SESSION_LIFETIME'])
159         self.config['AUTH_RATELIMIT_IP_V4_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V4_MASK'])
160         self.config['AUTH_RATELIMIT_IP_V6_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V6_MASK'])
161         hostnames = [host.strip() for host in self.config['HOSTNAMES'].split(',')]
162         self.config['AUTH_RATELIMIT_EXEMPTION'] = set(ipaddress.ip_network(cidr, False) for cidr in (cidr.strip() for cidr in self.config['AUTH_RATELIMIT_EXEMPTION'].split(',')) if cidr)
163         self.config['MESSAGE_RATELIMIT_EXEMPTION'] = set([s for s in self.config['MESSAGE_RATELIMIT_EXEMPTION'].lower().replace(' ', '').split(',') if s])
164         self.config['HOSTNAMES'] = ','.join(hostnames)
165         self.config['HOSTNAME'] = hostnames[0]
166 
167         # update the app config
168         app.config.update(self.config)
169 
170
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/core/admin/mailu/configuration.py b/core/admin/mailu/configuration.py
--- a/core/admin/mailu/configuration.py
+++ b/core/admin/mailu/configuration.py
@@ -98,7 +98,7 @@
     DB_TEMPLATES = {
         'sqlite': 'sqlite:////{SQLITE_DATABASE_FILE}',
         'postgresql': 'postgresql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}',
-        'mysql': 'mysql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}'
+        'mysql': 'mysql+mysqlconnector://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}'
     }
 
     def __init__(self):

{"golden_diff": "diff --git a/core/admin/mailu/configuration.py b/core/admin/mailu/configuration.py\n--- a/core/admin/mailu/configuration.py\n+++ b/core/admin/mailu/configuration.py\n@@ -98,7 +98,7 @@\n     DB_TEMPLATES = {\n         'sqlite': 'sqlite:////{SQLITE_DATABASE_FILE}',\n         'postgresql': 'postgresql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}',\n-        'mysql': 'mysql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}'\n+        'mysql': 'mysql+mysqlconnector://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}'\n     }\n \n     def __init__(self):\n", "issue": "admin container on master branch fails with mysql\nThank you for opening an issue with Mailu. Please understand that issues are meant for bugs and enhancement-requests.\r\nFor **user-support questions**, reach out to us  on [matrix](https://matrix.to/#/#mailu:tedomum.net).\r\n\r\nTo be able to help you best, we need some more information.\r\n\r\n## Before you open your issue\r\n- [*] Check if no issue or pull-request for this already exists.\r\n- [*] Check [documentation](https://mailu.io/master/) and [FAQ](https://mailu.io/master/faq.html). (Tip, use the search function on the documentation page)\r\n- [*] You understand `Mailu` is made by volunteers in their **free time** \u2014 be conscise, civil and accept that delays can occur.\r\n- [*] The title of the issue should be short and simple. It should contain specific terms related to the actual issue. Be specific while writing the title.\r\n\r\n## Environment & Versions\r\n### Environment\r\n - [*] docker-compose\r\n - [ ] kubernetes\r\n - [ ] docker swarm\r\n\r\n### Versions\r\nTo find your version, get the image name of a mailu container and read  the version from the tag (example for version 1.7).\r\n```\r\nimage: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}admin:${MAILU_VERSION:-master}\r\n```\r\n\r\n## Description\r\nThe last 2-3 admin builds of the master branch fail on mysql with this error:\r\nModuleNotFoundError: No module named 'MySQLdb'\r\nThis is not a production environment, just wanted to report.\r\nMy mysql server is as follows, but with the admin:1.9 image the setup works properly, so it isn't a mysql issue.\r\nmysql  Ver 15.1 Distrib 10.8.4-MariaDB, for debian-linux-gnu (x86_64) using  EditLine wrapper\r\n\r\n\r\n## Replication Steps\r\nSteps for replicating your issue\r\n\r\n## Expected behaviour\r\n\r\n## Logs\r\n\r\n````markdown\r\n```\r\nadmin_1      | Traceback (most recent call last):\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/sqlalchemy/util/_collections.py\", line 1008, in __call__\r\nadmin_1      |     return self.registry[key]\r\nadmin_1      | KeyError: <greenlet.greenlet object at 0x7f231b79da90 (otid=0x7f231b623340) current active started main>\r\nadmin_1      | During handling of the above exception, another exception occurred:\r\nadmin_1      | Traceback (most recent call last):\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/flask/app.py\", line 2073, in wsgi_app\r\nadmin_1      |     response = self.full_dispatch_request()\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/flask/app.py\", line 1518, in full_dispatch_request\r\nadmin_1      |     rv = self.handle_user_exception(e)\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/flask/app.py\", line 1516, in full_dispatch_request\r\nadmin_1      |     rv = self.dispatch_request()\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/flask/app.py\", line 1502, in dispatch_request\r\nadmin_1      |     return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)\r\nadmin_1      |   File \"/app/mailu/sso/views/base.py\", line 49, in login\r\nadmin_1      |     return flask.render_template('login.html', form=form, fields=fields)\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/flask/templating.py\", line 146, in render_template\r\nadmin_1      |     ctx.app.update_template_context(context)\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/flask/app.py\", line 756, in update_template_context\r\nadmin_1      |     context.update(func())\r\nadmin_1      |   File \"/app/mailu/__init__.py\", line 54, in inject_defaults\r\nadmin_1      |     signup_domains = models.Domain.query.filter_by(signup_enabled=True).all()\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/flask_sqlalchemy/__init__.py\", line 552, in __get__\r\nadmin_1      |     return type.query_class(mapper, session=self.sa.session())\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/sqlalchemy/orm/scoping.py\", line 47, in __call__\r\nadmin_1      |     sess = self.registry()\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/sqlalchemy/util/_collections.py\", line 1010, in __call__\r\nadmin_1      |     return self.registry.setdefault(key, self.createfunc())\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/sqlalchemy/orm/session.py\", line 4172, in __call__\r\nadmin_1      |     return self.class_(**local_kw)\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/flask_sqlalchemy/__init__.py\", line 176, in __init__\r\nadmin_1      |     bind = options.pop('bind', None) or db.engine\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/flask_sqlalchemy/__init__.py\", line 998, in engine\r\nadmin_1      |     return self.get_engine()\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/flask_sqlalchemy/__init__.py\", line 1017, in get_engine\r\nadmin_1      |     return connector.get_engine()\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/flask_sqlalchemy/__init__.py\", line 594, in get_engine\r\nadmin_1      |     self._engine = rv = self._sa.create_engine(sa_url, options)\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/flask_sqlalchemy/__init__.py\", line 1027, in create_engine\r\nadmin_1      |     return sqlalchemy.create_engine(sa_url, **engine_opts)\r\nadmin_1      |   File \"<string>\", line 2, in create_engine\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/sqlalchemy/util/deprecations.py\", line 298, in warned\r\nadmin_1      |     return fn(*args, **kwargs)\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/sqlalchemy/engine/create.py\", line 560, in create_engine\r\nadmin_1      |     dbapi = dialect_cls.dbapi(**dbapi_args)\r\nadmin_1      |   File \"/usr/lib/python3.9/site-packages/sqlalchemy/dialects/mysql/mysqldb.py\", line 167, in dbapi\r\nadmin_1      |     return __import__(\"MySQLdb\")\r\nadmin_1      | ModuleNotFoundError: No module named 'MySQLdb'\r\n\r\n```\r\n````\r\n\n", "before_files": [{"content": "import os\n\nfrom datetime import timedelta\nfrom socrate import system\nimport ipaddress\n\nDEFAULT_CONFIG = {\n    # Specific to the admin UI\n    'DOCKER_SOCKET': 'unix:///var/run/docker.sock',\n    'BABEL_DEFAULT_LOCALE': 'en',\n    'BABEL_DEFAULT_TIMEZONE': 'UTC',\n    'BOOTSTRAP_SERVE_LOCAL': True,\n    'RATELIMIT_STORAGE_URL': '',\n    'QUOTA_STORAGE_URL': '',\n    'DEBUG': False,\n    'DOMAIN_REGISTRATION': False,\n    'TEMPLATES_AUTO_RELOAD': True,\n    'MEMORY_SESSIONS': False,\n    # Database settings\n    'DB_FLAVOR': None,\n    'DB_USER': 'mailu',\n    'DB_PW': None,\n    'DB_HOST': 'database',\n    'DB_NAME': 'mailu',\n    'SQLITE_DATABASE_FILE':'data/main.db',\n    'SQLALCHEMY_DATABASE_URI': 'sqlite:////data/main.db',\n    'SQLALCHEMY_TRACK_MODIFICATIONS': False,\n    # Statistics management\n    'INSTANCE_ID_PATH': '/data/instance',\n    'STATS_ENDPOINT': '19.{}.stats.mailu.io',\n    # Common configuration variables\n    'SECRET_KEY': 'changeMe',\n    'DOMAIN': 'mailu.io',\n    'HOSTNAMES': 'mail.mailu.io,alternative.mailu.io,yetanother.mailu.io',\n    'POSTMASTER': 'postmaster',\n    'WILDCARD_SENDERS': '',\n    'TLS_FLAVOR': 'cert',\n    'INBOUND_TLS_ENFORCE': False,\n    'DEFER_ON_TLS_ERROR': True,\n    'AUTH_RATELIMIT_IP': '60/hour',\n    'AUTH_RATELIMIT_IP_V4_MASK': 24,\n    'AUTH_RATELIMIT_IP_V6_MASK': 56,\n    'AUTH_RATELIMIT_USER': '100/day',\n    'AUTH_RATELIMIT_EXEMPTION': '',\n    'AUTH_RATELIMIT_EXEMPTION_LENGTH': 86400,\n    'DISABLE_STATISTICS': False,\n    # Mail settings\n    'DMARC_RUA': None,\n    'DMARC_RUF': None,\n    'WELCOME': False,\n    'WELCOME_SUBJECT': 'Dummy welcome topic',\n    'WELCOME_BODY': 'Dummy welcome body',\n    'DKIM_SELECTOR': 'dkim',\n    'DKIM_PATH': '/dkim/{domain}.{selector}.key',\n    'DEFAULT_QUOTA': 1000000000,\n    'MESSAGE_RATELIMIT': '200/day',\n    'MESSAGE_RATELIMIT_EXEMPTION': '',\n    'RECIPIENT_DELIMITER': '',\n    # Web settings\n    'SITENAME': 'Mailu',\n    'WEBSITE': 'https://mailu.io',\n    'ADMIN' : 'none',\n    'WEB_ADMIN': '/admin',\n    'WEB_WEBMAIL': '/webmail',\n    'WEBMAIL': 'none',\n    'RECAPTCHA_PUBLIC_KEY': '',\n    'RECAPTCHA_PRIVATE_KEY': '',\n    'LOGO_URL': None,\n    'LOGO_BACKGROUND': None,\n    # Advanced settings\n    'LOG_LEVEL': 'WARNING',\n    'SESSION_KEY_BITS': 128,\n    'SESSION_TIMEOUT': 3600,\n    'PERMANENT_SESSION_LIFETIME': 30*24*3600,\n    'SESSION_COOKIE_SECURE': True,\n    'CREDENTIAL_ROUNDS': 12,\n    'TZ': 'Etc/UTC',\n    # Host settings\n    'HOST_IMAP': 'imap',\n    'HOST_LMTP': 'imap:2525',\n    'HOST_POP3': 'imap',\n    'HOST_SMTP': 'smtp',\n    'HOST_AUTHSMTP': 'smtp',\n    'HOST_ADMIN': 'admin',\n    'HOST_WEBMAIL': 'webmail',\n    'HOST_WEBDAV': 'webdav:5232',\n    'HOST_REDIS': 'redis',\n    'HOST_FRONT': 'front',\n    'SUBNET': '192.168.203.0/24',\n    'SUBNET6': None,\n    'POD_ADDRESS_RANGE': None\n}\n\nclass ConfigManager:\n    \"\"\" Naive configuration manager that uses environment only\n    \"\"\"\n\n    DB_TEMPLATES = {\n        'sqlite': 'sqlite:////{SQLITE_DATABASE_FILE}',\n        'postgresql': 'postgresql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}',\n        'mysql': 'mysql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}'\n    }\n\n    def __init__(self):\n        self.config = dict()\n\n    def get_host_address(self, name):\n        # if MYSERVICE_ADDRESS is defined, use this\n        if f'{name}_ADDRESS' in os.environ:\n            return os.environ.get(f'{name}_ADDRESS')\n        # otherwise use the host name and resolve it\n        return system.resolve_address(self.config[f'HOST_{name}'])\n\n    def resolve_hosts(self):\n        for key in ['IMAP', 'POP3', 'AUTHSMTP', 'SMTP', 'REDIS']:\n            self.config[f'{key}_ADDRESS'] = self.get_host_address(key)\n        if self.config['WEBMAIL'] != 'none':\n            self.config['WEBMAIL_ADDRESS'] = self.get_host_address('WEBMAIL')\n\n    def __get_env(self, key, value):\n        key_file = key + \"_FILE\"\n        if key_file in os.environ:\n            with open(os.environ.get(key_file)) as file:\n                value_from_file = file.read()\n            return value_from_file.strip()\n        else:\n            return os.environ.get(key, value)\n\n    def __coerce_value(self, value):\n        if isinstance(value, str) and value.lower() in ('true','yes'):\n            return True\n        elif isinstance(value, str) and value.lower() in ('false', 'no'):\n            return False\n        return value\n\n    def init_app(self, app):\n        # get current app config\n        self.config.update(app.config)\n        # get environment variables\n        self.config.update({\n            key: self.__coerce_value(self.__get_env(key, value))\n            for key, value in DEFAULT_CONFIG.items()\n        })\n        self.resolve_hosts()\n\n        # automatically set the sqlalchemy string\n        if self.config['DB_FLAVOR']:\n            template = self.DB_TEMPLATES[self.config['DB_FLAVOR']]\n            self.config['SQLALCHEMY_DATABASE_URI'] = template.format(**self.config)\n\n        self.config['RATELIMIT_STORAGE_URL'] = f'redis://{self.config[\"REDIS_ADDRESS\"]}/2'\n        self.config['QUOTA_STORAGE_URL'] = f'redis://{self.config[\"REDIS_ADDRESS\"]}/1'\n        self.config['SESSION_STORAGE_URL'] = f'redis://{self.config[\"REDIS_ADDRESS\"]}/3'\n        self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'\n        self.config['SESSION_COOKIE_HTTPONLY'] = True\n        self.config['SESSION_PERMANENT'] = True\n        self.config['SESSION_TIMEOUT'] = int(self.config['SESSION_TIMEOUT'])\n        self.config['PERMANENT_SESSION_LIFETIME'] = int(self.config['PERMANENT_SESSION_LIFETIME'])\n        self.config['AUTH_RATELIMIT_IP_V4_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V4_MASK'])\n        self.config['AUTH_RATELIMIT_IP_V6_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V6_MASK'])\n        hostnames = [host.strip() for host in self.config['HOSTNAMES'].split(',')]\n        self.config['AUTH_RATELIMIT_EXEMPTION'] = set(ipaddress.ip_network(cidr, False) for cidr in (cidr.strip() for cidr in self.config['AUTH_RATELIMIT_EXEMPTION'].split(',')) if cidr)\n        self.config['MESSAGE_RATELIMIT_EXEMPTION'] = set([s for s in self.config['MESSAGE_RATELIMIT_EXEMPTION'].lower().replace(' ', '').split(',') if s])\n        self.config['HOSTNAMES'] = ','.join(hostnames)\n        self.config['HOSTNAME'] = hostnames[0]\n\n        # update the app config\n        app.config.update(self.config)\n\n", "path": "core/admin/mailu/configuration.py"}], "after_files": [{"content": "import os\n\nfrom datetime import timedelta\nfrom socrate import system\nimport ipaddress\n\nDEFAULT_CONFIG = {\n    # Specific to the admin UI\n    'DOCKER_SOCKET': 'unix:///var/run/docker.sock',\n    'BABEL_DEFAULT_LOCALE': 'en',\n    'BABEL_DEFAULT_TIMEZONE': 'UTC',\n    'BOOTSTRAP_SERVE_LOCAL': True,\n    'RATELIMIT_STORAGE_URL': '',\n    'QUOTA_STORAGE_URL': '',\n    'DEBUG': False,\n    'DOMAIN_REGISTRATION': False,\n    'TEMPLATES_AUTO_RELOAD': True,\n    'MEMORY_SESSIONS': False,\n    # Database settings\n    'DB_FLAVOR': None,\n    'DB_USER': 'mailu',\n    'DB_PW': None,\n    'DB_HOST': 'database',\n    'DB_NAME': 'mailu',\n    'SQLITE_DATABASE_FILE':'data/main.db',\n    'SQLALCHEMY_DATABASE_URI': 'sqlite:////data/main.db',\n    'SQLALCHEMY_TRACK_MODIFICATIONS': False,\n    # Statistics management\n    'INSTANCE_ID_PATH': '/data/instance',\n    'STATS_ENDPOINT': '19.{}.stats.mailu.io',\n    # Common configuration variables\n    'SECRET_KEY': 'changeMe',\n    'DOMAIN': 'mailu.io',\n    'HOSTNAMES': 'mail.mailu.io,alternative.mailu.io,yetanother.mailu.io',\n    'POSTMASTER': 'postmaster',\n    'WILDCARD_SENDERS': '',\n    'TLS_FLAVOR': 'cert',\n    'INBOUND_TLS_ENFORCE': False,\n    'DEFER_ON_TLS_ERROR': True,\n    'AUTH_RATELIMIT_IP': '60/hour',\n    'AUTH_RATELIMIT_IP_V4_MASK': 24,\n    'AUTH_RATELIMIT_IP_V6_MASK': 56,\n    'AUTH_RATELIMIT_USER': '100/day',\n    'AUTH_RATELIMIT_EXEMPTION': '',\n    'AUTH_RATELIMIT_EXEMPTION_LENGTH': 86400,\n    'DISABLE_STATISTICS': False,\n    # Mail settings\n    'DMARC_RUA': None,\n    'DMARC_RUF': None,\n    'WELCOME': False,\n    'WELCOME_SUBJECT': 'Dummy welcome topic',\n    'WELCOME_BODY': 'Dummy welcome body',\n    'DKIM_SELECTOR': 'dkim',\n    'DKIM_PATH': '/dkim/{domain}.{selector}.key',\n    'DEFAULT_QUOTA': 1000000000,\n    'MESSAGE_RATELIMIT': '200/day',\n    'MESSAGE_RATELIMIT_EXEMPTION': '',\n    'RECIPIENT_DELIMITER': '',\n    # Web settings\n    'SITENAME': 'Mailu',\n    'WEBSITE': 'https://mailu.io',\n    'ADMIN' : 'none',\n    'WEB_ADMIN': '/admin',\n    'WEB_WEBMAIL': '/webmail',\n    'WEBMAIL': 'none',\n    'RECAPTCHA_PUBLIC_KEY': '',\n    'RECAPTCHA_PRIVATE_KEY': '',\n    'LOGO_URL': None,\n    'LOGO_BACKGROUND': None,\n    # Advanced settings\n    'LOG_LEVEL': 'WARNING',\n    'SESSION_KEY_BITS': 128,\n    'SESSION_TIMEOUT': 3600,\n    'PERMANENT_SESSION_LIFETIME': 30*24*3600,\n    'SESSION_COOKIE_SECURE': True,\n    'CREDENTIAL_ROUNDS': 12,\n    'TZ': 'Etc/UTC',\n    # Host settings\n    'HOST_IMAP': 'imap',\n    'HOST_LMTP': 'imap:2525',\n    'HOST_POP3': 'imap',\n    'HOST_SMTP': 'smtp',\n    'HOST_AUTHSMTP': 'smtp',\n    'HOST_ADMIN': 'admin',\n    'HOST_WEBMAIL': 'webmail',\n    'HOST_WEBDAV': 'webdav:5232',\n    'HOST_REDIS': 'redis',\n    'HOST_FRONT': 'front',\n    'SUBNET': '192.168.203.0/24',\n    'SUBNET6': None,\n    'POD_ADDRESS_RANGE': None\n}\n\nclass ConfigManager:\n    \"\"\" Naive configuration manager that uses environment only\n    \"\"\"\n\n    DB_TEMPLATES = {\n        'sqlite': 'sqlite:////{SQLITE_DATABASE_FILE}',\n        'postgresql': 'postgresql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}',\n        'mysql': 'mysql+mysqlconnector://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}'\n    }\n\n    def __init__(self):\n        self.config = dict()\n\n    def get_host_address(self, name):\n        # if MYSERVICE_ADDRESS is defined, use this\n        if f'{name}_ADDRESS' in os.environ:\n            return os.environ.get(f'{name}_ADDRESS')\n        # otherwise use the host name and resolve it\n        return system.resolve_address(self.config[f'HOST_{name}'])\n\n    def resolve_hosts(self):\n        for key in ['IMAP', 'POP3', 'AUTHSMTP', 'SMTP', 'REDIS']:\n            self.config[f'{key}_ADDRESS'] = self.get_host_address(key)\n        if self.config['WEBMAIL'] != 'none':\n            self.config['WEBMAIL_ADDRESS'] = self.get_host_address('WEBMAIL')\n\n    def __get_env(self, key, value):\n        key_file = key + \"_FILE\"\n        if key_file in os.environ:\n            with open(os.environ.get(key_file)) as file:\n                value_from_file = file.read()\n            return value_from_file.strip()\n        else:\n            return os.environ.get(key, value)\n\n    def __coerce_value(self, value):\n        if isinstance(value, str) and value.lower() in ('true','yes'):\n            return True\n        elif isinstance(value, str) and value.lower() in ('false', 'no'):\n            return False\n        return value\n\n    def init_app(self, app):\n        # get current app config\n        self.config.update(app.config)\n        # get environment variables\n        self.config.update({\n            key: self.__coerce_value(self.__get_env(key, value))\n            for key, value in DEFAULT_CONFIG.items()\n        })\n        self.resolve_hosts()\n\n        # automatically set the sqlalchemy string\n        if self.config['DB_FLAVOR']:\n            template = self.DB_TEMPLATES[self.config['DB_FLAVOR']]\n            self.config['SQLALCHEMY_DATABASE_URI'] = template.format(**self.config)\n\n        self.config['RATELIMIT_STORAGE_URL'] = f'redis://{self.config[\"REDIS_ADDRESS\"]}/2'\n        self.config['QUOTA_STORAGE_URL'] = f'redis://{self.config[\"REDIS_ADDRESS\"]}/1'\n        self.config['SESSION_STORAGE_URL'] = f'redis://{self.config[\"REDIS_ADDRESS\"]}/3'\n        self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'\n        self.config['SESSION_COOKIE_HTTPONLY'] = True\n        self.config['SESSION_PERMANENT'] = True\n        self.config['SESSION_TIMEOUT'] = int(self.config['SESSION_TIMEOUT'])\n        self.config['PERMANENT_SESSION_LIFETIME'] = int(self.config['PERMANENT_SESSION_LIFETIME'])\n        self.config['AUTH_RATELIMIT_IP_V4_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V4_MASK'])\n        self.config['AUTH_RATELIMIT_IP_V6_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V6_MASK'])\n        hostnames = [host.strip() for host in self.config['HOSTNAMES'].split(',')]\n        self.config['AUTH_RATELIMIT_EXEMPTION'] = set(ipaddress.ip_network(cidr, False) for cidr in (cidr.strip() for cidr in self.config['AUTH_RATELIMIT_EXEMPTION'].split(',')) if cidr)\n        self.config['MESSAGE_RATELIMIT_EXEMPTION'] = set([s for s in self.config['MESSAGE_RATELIMIT_EXEMPTION'].lower().replace(' ', '').split(',') if s])\n        self.config['HOSTNAMES'] = ','.join(hostnames)\n        self.config['HOSTNAME'] = hostnames[0]\n\n        # update the app config\n        app.config.update(self.config)\n\n", "path": "core/admin/mailu/configuration.py"}]}