We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Brainstorming for 1.10 / 2.0 roadmap
# Description
Now 1.9 has been released for some time, it is time to discuss what we want to work on for the next release.

- Release date?
- Scope 1.10 (normal release) or 2.0 (release with breaking changes)?  
- What do we want to work on?

## Ideas

Ideas from the last meeting:
- introduce snappymail.
- rootless containers
  - probably requires init container
- replacement of Socrates. ghostwheel42 will work on this (if he finds time).
- A security/keys page for Mailu admin interface. 
  - On this page you can configure/change/generate keys for all your Mail domains.
  - DNS overview 
    - Have some UI that tells you if DNS is correctly configured.
- Have a look at BuildX
  - means we can build for multiple platforms including ARM
  - means we remove the build arm script
  - means CI will be changed massively.
  - For ARM, we could maybe build once per week to make sure the build time for normal builts is not too long.
- autoconfiguration for email clients?
  - automx: https://rseichter.github.io/automx2/?
  - integrate whatever we choose with mailu (part of mailu)?
  - also good time to drop starttls for imap/pop/smtp(sending mail).
    - So only support direct SSL/TLS
    - Could be done via environment_variable. When not configured, then starttls supported. If configured (default for new deployments) then it is disabled.
    - Another idea is to disable starttls, and report a custom error (when you use explicit starttls) that you must switch to implicit SSL/TLS port 465.
- Full 2 factor authentication with xoath2 
  -  Too large in scope for this release. But preparations could be made.
  - Means we need autoconfiguration. Otherwise the email client will not use xoath2.
  - means using single sign on via identity provider (which mailu could be as well). This opens the door to use other identity providers in the future.

Feel free to suggest your own ideas

## Misc
For small non-critical issues/features I suggest we do not put it on the roadmap, but simply offer anyone the chance to pick these up and submit a PR if they want it included.

What are your thoughts? Please share your feedback.
Regardless the above wall of text, feel free to mention any feature/issue you would like included in the next release.

## My 2 cents
For release date we could maybe aim around August/September?
We are all very busy with our personal life now. This should give enough time to implement new features. Of course a decided date is not set in stone. It could still be moved forward/backwards. 

https://github.com/Mailu/Mailu/blob/master/design/mailu-directory-structure.md
This is what I was thinking about for the scope. Changing the directory structure must be part of a major release with breaking changes. Do we want to make this change for this release or postpone this?

Personally I'd like to check 
 - switching to SnappyMail 
 - rootless containers
 - BuildX. At least investigate what changes are required.

Feel free to suggest your own ideas. 


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `docs/conf.py`
Content:
```
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 #
4 
5 import os
6 
7 extensions = ['sphinx.ext.imgmath', 'sphinx.ext.viewcode', 'sphinx_rtd_theme']
8 templates_path = ['_templates']
9 source_suffix = '.rst'
10 master_doc = 'index'
11 project = 'Mailu'
12 copyright = '2018, Mailu authors'
13 author = 'Mailu authors'
14 version = release = os.environ.get('VERSION', 'master')
15 language = 'en'
16 exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'Dockerfile', 'docker-compose.yml']
17 pygments_style = 'sphinx'
18 todo_include_todos = False
19 html_theme = 'sphinx_rtd_theme'
20 html_title = 'Mailu, Docker based mail server'
21 html_static_path = []
22 htmlhelp_basename = 'Mailudoc'
23 
24 # Custom sidebar templates, must be a dictionary that maps document names
25 # to template names.
26 html_sidebars = {
27     '**': [
28         'relations.html', 
29         'searchbox.html',
30     ]
31 }
32 
33 # Theme options
34 html_context = {
35     'display_github': True,
36     'github_user': 'mailu',
37     'github_repo': 'mailu',
38     'github_version': version,
39     'stable_version': '1.9',
40     'versions': [
41         ('1.8', '/1.8/'),
42         ('1.9', '/1.9/'),
43         ('master', '/master/')
44     ],
45     'conf_py_path': '/docs/'
46 }
47
```

Path: `core/admin/mailu/configuration.py`
Content:
```
1 import os
2 
3 from datetime import timedelta
4 import ipaddress
5 
6 DEFAULT_CONFIG = {
7     # Specific to the admin UI
8     'DOCKER_SOCKET': 'unix:///var/run/docker.sock',
9     'BABEL_DEFAULT_LOCALE': 'en',
10     'BABEL_DEFAULT_TIMEZONE': 'UTC',
11     'BOOTSTRAP_SERVE_LOCAL': True,
12     'RATELIMIT_STORAGE_URL': '',
13     'DEBUG': False,
14     'DEBUG_PROFILER': False,
15     'DEBUG_TB_INTERCEPT_REDIRECTS': False,
16     'DEBUG_ASSETS': '',
17     'DOMAIN_REGISTRATION': False,
18     'TEMPLATES_AUTO_RELOAD': True,
19     'MEMORY_SESSIONS': False,
20     'FETCHMAIL_ENABLED': True,
21     'MAILU_VERSION': 'unknown',
22     # Database settings
23     'DB_FLAVOR': None,
24     'DB_USER': 'mailu',
25     'DB_PW': None,
26     'DB_HOST': 'database',
27     'DB_NAME': 'mailu',
28     'SQLITE_DATABASE_FILE': 'data/main.db',
29     'SQLALCHEMY_DATABASE_URI': 'sqlite:////data/main.db',
30     'SQLALCHEMY_DATABASE_URI_ROUNDCUBE': 'sqlite:////data/roundcube.db',
31     'SQLALCHEMY_TRACK_MODIFICATIONS': False,
32     # Statistics management
33     'INSTANCE_ID_PATH': '/data/instance',
34     'STATS_ENDPOINT': '19.{}.stats.mailu.io',
35     # Common configuration variables
36     'SECRET_KEY': 'changeMe',
37     'DOMAIN': 'mailu.io',
38     'HOSTNAMES': 'mail.mailu.io,alternative.mailu.io,yetanother.mailu.io',
39     'POSTMASTER': 'postmaster',
40     'WILDCARD_SENDERS': '',
41     'TLS_FLAVOR': 'cert',
42     'INBOUND_TLS_ENFORCE': False,
43     'DEFER_ON_TLS_ERROR': True,
44     'AUTH_RATELIMIT_IP': '5/hour',
45     'AUTH_RATELIMIT_IP_V4_MASK': 24,
46     'AUTH_RATELIMIT_IP_V6_MASK': 48,
47     'AUTH_RATELIMIT_USER': '50/day',
48     'AUTH_RATELIMIT_EXEMPTION': '',
49     'AUTH_RATELIMIT_EXEMPTION_LENGTH': 86400,
50     'DISABLE_STATISTICS': False,
51     # Mail settings
52     'DMARC_RUA': None,
53     'DMARC_RUF': None,
54     'WELCOME': False,
55     'WELCOME_SUBJECT': 'Dummy welcome topic',
56     'WELCOME_BODY': 'Dummy welcome body',
57     'DKIM_SELECTOR': 'dkim',
58     'DKIM_PATH': '/dkim/{domain}.{selector}.key',
59     'DEFAULT_QUOTA': 1000000000,
60     'MESSAGE_RATELIMIT': '200/day',
61     'MESSAGE_RATELIMIT_EXEMPTION': '',
62     'RECIPIENT_DELIMITER': '',
63     # Web settings
64     'SITENAME': 'Mailu',
65     'WEBSITE': 'https://mailu.io',
66     'ADMIN': 'none',
67     'WEB_ADMIN': '/admin',
68     'WEB_WEBMAIL': '/webmail',
69     'WEBMAIL': 'none',
70     'RECAPTCHA_PUBLIC_KEY': '',
71     'RECAPTCHA_PRIVATE_KEY': '',
72     'LOGO_URL': None,
73     'LOGO_BACKGROUND': None,
74     # Advanced settings
75     'API': False,
76     'WEB_API': '/api',
77     'API_TOKEN': None,
78     'LOG_LEVEL': 'WARNING',
79     'SESSION_KEY_BITS': 128,
80     'SESSION_TIMEOUT': 3600,
81     'PERMANENT_SESSION_LIFETIME': 30*24*3600,
82     'SESSION_COOKIE_SECURE': None,
83     'CREDENTIAL_ROUNDS': 12,
84     'TLS_PERMISSIVE': True,
85     'TZ': 'Etc/UTC',
86     'DEFAULT_SPAM_THRESHOLD': 80,
87     'PROXY_AUTH_WHITELIST': '',
88     'PROXY_AUTH_HEADER': 'X-Auth-Email',
89     'PROXY_AUTH_CREATE': False,
90     'PROXY_AUTH_LOGOUT_URL': None,
91     'SUBNET': '192.168.203.0/24',
92     'SUBNET6': None,
93 }
94 
95 class ConfigManager:
96     """ Naive configuration manager that uses environment only
97     """
98 
99     DB_TEMPLATES = {
100         'sqlite': 'sqlite:////{SQLITE_DATABASE_FILE}',
101         'postgresql': 'postgresql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}',
102         'mysql': 'mysql+mysqlconnector://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}',
103     }
104 
105     def __init__(self):
106         self.config = dict()
107 
108     def __get_env(self, key, value):
109         key_file = key + "_FILE"
110         if key_file in os.environ:
111             with open(os.environ.get(key_file)) as file:
112                 value_from_file = file.read()
113             return value_from_file.strip()
114         else:
115             return os.environ.get(key, value)
116 
117     def __coerce_value(self, value):
118         if isinstance(value, str) and value.lower() in ('true','yes'):
119             return True
120         elif isinstance(value, str) and value.lower() in ('false', 'no'):
121             return False
122         return value
123 
124     def init_app(self, app):
125         # get current app config
126         self.config.update(app.config)
127         # get environment variables
128         for key in os.environ:
129             if key.endswith('_ADDRESS'):
130                 self.config[key] = os.environ[key]
131 
132         self.config.update({
133             key: self.__coerce_value(self.__get_env(key, value))
134             for key, value in DEFAULT_CONFIG.items()
135         })
136 
137         # automatically set the sqlalchemy string
138         if self.config['DB_FLAVOR']:
139             template = self.DB_TEMPLATES[self.config['DB_FLAVOR']]
140             self.config['SQLALCHEMY_DATABASE_URI'] = template.format(**self.config)
141 
142         if not self.config.get('RATELIMIT_STORAGE_URL'):
143             self.config['RATELIMIT_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/2'
144 
145         self.config['SESSION_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/3'
146         self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
147         self.config['SESSION_COOKIE_HTTPONLY'] = True
148         if self.config['SESSION_COOKIE_SECURE'] is None:
149             self.config['SESSION_COOKIE_SECURE'] = self.config['TLS_FLAVOR'] != 'notls'
150         self.config['SESSION_PERMANENT'] = True
151         self.config['SESSION_TIMEOUT'] = int(self.config['SESSION_TIMEOUT'])
152         self.config['SESSION_KEY_BITS'] = int(self.config['SESSION_KEY_BITS'])
153         self.config['PERMANENT_SESSION_LIFETIME'] = int(self.config['PERMANENT_SESSION_LIFETIME'])
154         self.config['AUTH_RATELIMIT_IP_V4_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V4_MASK'])
155         self.config['AUTH_RATELIMIT_IP_V6_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V6_MASK'])
156         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)
157         self.config['MESSAGE_RATELIMIT_EXEMPTION'] = set([s for s in self.config['MESSAGE_RATELIMIT_EXEMPTION'].lower().replace(' ', '').split(',') if s])
158         hostnames = [host.strip() for host in self.config['HOSTNAMES'].split(',')]
159         self.config['HOSTNAMES'] = ','.join(hostnames)
160         self.config['HOSTNAME'] = hostnames[0]
161         self.config['DEFAULT_SPAM_THRESHOLD'] = int(self.config['DEFAULT_SPAM_THRESHOLD'])
162         self.config['PROXY_AUTH_WHITELIST'] = set(ipaddress.ip_network(cidr, False) for cidr in (cidr.strip() for cidr in self.config['PROXY_AUTH_WHITELIST'].split(',')) if cidr)
163         try:
164             self.config['MAILU_VERSION'] = open('/version', 'r').read()
165         except FileNotFoundError:
166             pass
167 
168         # update the app config
169         app.config.update(self.config)
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
@@ -31,7 +31,7 @@
     'SQLALCHEMY_TRACK_MODIFICATIONS': False,
     # Statistics management
     'INSTANCE_ID_PATH': '/data/instance',
-    'STATS_ENDPOINT': '19.{}.stats.mailu.io',
+    'STATS_ENDPOINT': '20.{}.stats.mailu.io',
     # Common configuration variables
     'SECRET_KEY': 'changeMe',
     'DOMAIN': 'mailu.io',
diff --git a/docs/conf.py b/docs/conf.py
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -9,7 +9,7 @@
 source_suffix = '.rst'
 master_doc = 'index'
 project = 'Mailu'
-copyright = '2018, Mailu authors'
+copyright = '2016, Mailu authors'
 author = 'Mailu authors'
 version = release = os.environ.get('VERSION', 'master')
 language = 'en'
@@ -25,7 +25,7 @@
 # to template names.
 html_sidebars = {
     '**': [
-        'relations.html', 
+        'relations.html',
         'searchbox.html',
     ]
 }
@@ -36,10 +36,10 @@
     'github_user': 'mailu',
     'github_repo': 'mailu',
     'github_version': version,
-    'stable_version': '1.9',
+    'stable_version': '2.0',
     'versions': [
-        ('1.8', '/1.8/'),
         ('1.9', '/1.9/'),
+        ('2.0', '/2.0/'),
         ('master', '/master/')
     ],
     'conf_py_path': '/docs/'

{"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@@ -31,7 +31,7 @@\n     'SQLALCHEMY_TRACK_MODIFICATIONS': False,\n     # Statistics management\n     'INSTANCE_ID_PATH': '/data/instance',\n-    'STATS_ENDPOINT': '19.{}.stats.mailu.io',\n+    'STATS_ENDPOINT': '20.{}.stats.mailu.io',\n     # Common configuration variables\n     'SECRET_KEY': 'changeMe',\n     'DOMAIN': 'mailu.io',\ndiff --git a/docs/conf.py b/docs/conf.py\n--- a/docs/conf.py\n+++ b/docs/conf.py\n@@ -9,7 +9,7 @@\n source_suffix = '.rst'\n master_doc = 'index'\n project = 'Mailu'\n-copyright = '2018, Mailu authors'\n+copyright = '2016, Mailu authors'\n author = 'Mailu authors'\n version = release = os.environ.get('VERSION', 'master')\n language = 'en'\n@@ -25,7 +25,7 @@\n # to template names.\n html_sidebars = {\n     '**': [\n-        'relations.html', \n+        'relations.html',\n         'searchbox.html',\n     ]\n }\n@@ -36,10 +36,10 @@\n     'github_user': 'mailu',\n     'github_repo': 'mailu',\n     'github_version': version,\n-    'stable_version': '1.9',\n+    'stable_version': '2.0',\n     'versions': [\n-        ('1.8', '/1.8/'),\n         ('1.9', '/1.9/'),\n+        ('2.0', '/2.0/'),\n         ('master', '/master/')\n     ],\n     'conf_py_path': '/docs/'\n", "issue": "Brainstorming for 1.10 / 2.0 roadmap\n# Description\r\nNow 1.9 has been released for some time, it is time to discuss what we want to work on for the next release.\r\n\r\n- Release date?\r\n- Scope 1.10 (normal release) or 2.0 (release with breaking changes)?  \r\n- What do we want to work on?\r\n\r\n## Ideas\r\n\r\nIdeas from the last meeting:\r\n- introduce snappymail.\r\n- rootless containers\r\n  - probably requires init container\r\n- replacement of Socrates. ghostwheel42 will work on this (if he finds time).\r\n- A security/keys page for Mailu admin interface. \r\n  - On this page you can configure/change/generate keys for all your Mail domains.\r\n  - DNS overview \r\n    - Have some UI that tells you if DNS is correctly configured.\r\n- Have a look at BuildX\r\n  - means we can build for multiple platforms including ARM\r\n  - means we remove the build arm script\r\n  - means CI will be changed massively.\r\n  - For ARM, we could maybe build once per week to make sure the build time for normal builts is not too long.\r\n- autoconfiguration for email clients?\r\n  - automx: https://rseichter.github.io/automx2/?\r\n  - integrate whatever we choose with mailu (part of mailu)?\r\n  - also good time to drop starttls for imap/pop/smtp(sending mail).\r\n    - So only support direct SSL/TLS\r\n    - Could be done via environment_variable. When not configured, then starttls supported. If configured (default for new deployments) then it is disabled.\r\n    - Another idea is to disable starttls, and report a custom error (when you use explicit starttls) that you must switch to implicit SSL/TLS port 465.\r\n- Full 2 factor authentication with xoath2 \r\n  -  Too large in scope for this release. But preparations could be made.\r\n  - Means we need autoconfiguration. Otherwise the email client will not use xoath2.\r\n  - means using single sign on via identity provider (which mailu could be as well). This opens the door to use other identity providers in the future.\r\n\r\nFeel free to suggest your own ideas\r\n\r\n## Misc\r\nFor small non-critical issues/features I suggest we do not put it on the roadmap, but simply offer anyone the chance to pick these up and submit a PR if they want it included.\r\n\r\nWhat are your thoughts? Please share your feedback.\r\nRegardless the above wall of text, feel free to mention any feature/issue you would like included in the next release.\r\n\r\n## My 2 cents\r\nFor release date we could maybe aim around August/September?\r\nWe are all very busy with our personal life now. This should give enough time to implement new features. Of course a decided date is not set in stone. It could still be moved forward/backwards. \r\n\r\nhttps://github.com/Mailu/Mailu/blob/master/design/mailu-directory-structure.md\r\nThis is what I was thinking about for the scope. Changing the directory structure must be part of a major release with breaking changes. Do we want to make this change for this release or postpone this?\r\n\r\nPersonally I'd like to check \r\n - switching to SnappyMail \r\n - rootless containers\r\n - BuildX. At least investigate what changes are required.\r\n\r\nFeel free to suggest your own ideas. \r\n\n", "before_files": [{"content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n\nimport os\n\nextensions = ['sphinx.ext.imgmath', 'sphinx.ext.viewcode', 'sphinx_rtd_theme']\ntemplates_path = ['_templates']\nsource_suffix = '.rst'\nmaster_doc = 'index'\nproject = 'Mailu'\ncopyright = '2018, Mailu authors'\nauthor = 'Mailu authors'\nversion = release = os.environ.get('VERSION', 'master')\nlanguage = 'en'\nexclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'Dockerfile', 'docker-compose.yml']\npygments_style = 'sphinx'\ntodo_include_todos = False\nhtml_theme = 'sphinx_rtd_theme'\nhtml_title = 'Mailu, Docker based mail server'\nhtml_static_path = []\nhtmlhelp_basename = 'Mailudoc'\n\n# Custom sidebar templates, must be a dictionary that maps document names\n# to template names.\nhtml_sidebars = {\n    '**': [\n        'relations.html', \n        'searchbox.html',\n    ]\n}\n\n# Theme options\nhtml_context = {\n    'display_github': True,\n    'github_user': 'mailu',\n    'github_repo': 'mailu',\n    'github_version': version,\n    'stable_version': '1.9',\n    'versions': [\n        ('1.8', '/1.8/'),\n        ('1.9', '/1.9/'),\n        ('master', '/master/')\n    ],\n    'conf_py_path': '/docs/'\n}\n", "path": "docs/conf.py"}, {"content": "import os\n\nfrom datetime import timedelta\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    'DEBUG': False,\n    'DEBUG_PROFILER': False,\n    'DEBUG_TB_INTERCEPT_REDIRECTS': False,\n    'DEBUG_ASSETS': '',\n    'DOMAIN_REGISTRATION': False,\n    'TEMPLATES_AUTO_RELOAD': True,\n    'MEMORY_SESSIONS': False,\n    'FETCHMAIL_ENABLED': True,\n    'MAILU_VERSION': 'unknown',\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_DATABASE_URI_ROUNDCUBE': 'sqlite:////data/roundcube.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': '5/hour',\n    'AUTH_RATELIMIT_IP_V4_MASK': 24,\n    'AUTH_RATELIMIT_IP_V6_MASK': 48,\n    'AUTH_RATELIMIT_USER': '50/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    'API': False,\n    'WEB_API': '/api',\n    'API_TOKEN': None,\n    'LOG_LEVEL': 'WARNING',\n    'SESSION_KEY_BITS': 128,\n    'SESSION_TIMEOUT': 3600,\n    'PERMANENT_SESSION_LIFETIME': 30*24*3600,\n    'SESSION_COOKIE_SECURE': None,\n    'CREDENTIAL_ROUNDS': 12,\n    'TLS_PERMISSIVE': True,\n    'TZ': 'Etc/UTC',\n    'DEFAULT_SPAM_THRESHOLD': 80,\n    'PROXY_AUTH_WHITELIST': '',\n    'PROXY_AUTH_HEADER': 'X-Auth-Email',\n    'PROXY_AUTH_CREATE': False,\n    'PROXY_AUTH_LOGOUT_URL': None,\n    'SUBNET': '192.168.203.0/24',\n    'SUBNET6': 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_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        for key in os.environ:\n            if key.endswith('_ADDRESS'):\n                self.config[key] = os.environ[key]\n\n        self.config.update({\n            key: self.__coerce_value(self.__get_env(key, value))\n            for key, value in DEFAULT_CONFIG.items()\n        })\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        if not self.config.get('RATELIMIT_STORAGE_URL'):\n            self.config['RATELIMIT_STORAGE_URL'] = f'redis://{self.config[\"REDIS_ADDRESS\"]}/2'\n\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        if self.config['SESSION_COOKIE_SECURE'] is None:\n            self.config['SESSION_COOKIE_SECURE'] = self.config['TLS_FLAVOR'] != 'notls'\n        self.config['SESSION_PERMANENT'] = True\n        self.config['SESSION_TIMEOUT'] = int(self.config['SESSION_TIMEOUT'])\n        self.config['SESSION_KEY_BITS'] = int(self.config['SESSION_KEY_BITS'])\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        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        hostnames = [host.strip() for host in self.config['HOSTNAMES'].split(',')]\n        self.config['HOSTNAMES'] = ','.join(hostnames)\n        self.config['HOSTNAME'] = hostnames[0]\n        self.config['DEFAULT_SPAM_THRESHOLD'] = int(self.config['DEFAULT_SPAM_THRESHOLD'])\n        self.config['PROXY_AUTH_WHITELIST'] = set(ipaddress.ip_network(cidr, False) for cidr in (cidr.strip() for cidr in self.config['PROXY_AUTH_WHITELIST'].split(',')) if cidr)\n        try:\n            self.config['MAILU_VERSION'] = open('/version', 'r').read()\n        except FileNotFoundError:\n            pass\n\n        # update the app config\n        app.config.update(self.config)\n", "path": "core/admin/mailu/configuration.py"}], "after_files": [{"content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n\nimport os\n\nextensions = ['sphinx.ext.imgmath', 'sphinx.ext.viewcode', 'sphinx_rtd_theme']\ntemplates_path = ['_templates']\nsource_suffix = '.rst'\nmaster_doc = 'index'\nproject = 'Mailu'\ncopyright = '2016, Mailu authors'\nauthor = 'Mailu authors'\nversion = release = os.environ.get('VERSION', 'master')\nlanguage = 'en'\nexclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'Dockerfile', 'docker-compose.yml']\npygments_style = 'sphinx'\ntodo_include_todos = False\nhtml_theme = 'sphinx_rtd_theme'\nhtml_title = 'Mailu, Docker based mail server'\nhtml_static_path = []\nhtmlhelp_basename = 'Mailudoc'\n\n# Custom sidebar templates, must be a dictionary that maps document names\n# to template names.\nhtml_sidebars = {\n    '**': [\n        'relations.html',\n        'searchbox.html',\n    ]\n}\n\n# Theme options\nhtml_context = {\n    'display_github': True,\n    'github_user': 'mailu',\n    'github_repo': 'mailu',\n    'github_version': version,\n    'stable_version': '2.0',\n    'versions': [\n        ('1.9', '/1.9/'),\n        ('2.0', '/2.0/'),\n        ('master', '/master/')\n    ],\n    'conf_py_path': '/docs/'\n}\n", "path": "docs/conf.py"}, {"content": "import os\n\nfrom datetime import timedelta\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    'DEBUG': False,\n    'DEBUG_PROFILER': False,\n    'DEBUG_TB_INTERCEPT_REDIRECTS': False,\n    'DEBUG_ASSETS': '',\n    'DOMAIN_REGISTRATION': False,\n    'TEMPLATES_AUTO_RELOAD': True,\n    'MEMORY_SESSIONS': False,\n    'FETCHMAIL_ENABLED': True,\n    'MAILU_VERSION': 'unknown',\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_DATABASE_URI_ROUNDCUBE': 'sqlite:////data/roundcube.db',\n    'SQLALCHEMY_TRACK_MODIFICATIONS': False,\n    # Statistics management\n    'INSTANCE_ID_PATH': '/data/instance',\n    'STATS_ENDPOINT': '20.{}.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': '5/hour',\n    'AUTH_RATELIMIT_IP_V4_MASK': 24,\n    'AUTH_RATELIMIT_IP_V6_MASK': 48,\n    'AUTH_RATELIMIT_USER': '50/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    'API': False,\n    'WEB_API': '/api',\n    'API_TOKEN': None,\n    'LOG_LEVEL': 'WARNING',\n    'SESSION_KEY_BITS': 128,\n    'SESSION_TIMEOUT': 3600,\n    'PERMANENT_SESSION_LIFETIME': 30*24*3600,\n    'SESSION_COOKIE_SECURE': None,\n    'CREDENTIAL_ROUNDS': 12,\n    'TLS_PERMISSIVE': True,\n    'TZ': 'Etc/UTC',\n    'DEFAULT_SPAM_THRESHOLD': 80,\n    'PROXY_AUTH_WHITELIST': '',\n    'PROXY_AUTH_HEADER': 'X-Auth-Email',\n    'PROXY_AUTH_CREATE': False,\n    'PROXY_AUTH_LOGOUT_URL': None,\n    'SUBNET': '192.168.203.0/24',\n    'SUBNET6': 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_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        for key in os.environ:\n            if key.endswith('_ADDRESS'):\n                self.config[key] = os.environ[key]\n\n        self.config.update({\n            key: self.__coerce_value(self.__get_env(key, value))\n            for key, value in DEFAULT_CONFIG.items()\n        })\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        if not self.config.get('RATELIMIT_STORAGE_URL'):\n            self.config['RATELIMIT_STORAGE_URL'] = f'redis://{self.config[\"REDIS_ADDRESS\"]}/2'\n\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        if self.config['SESSION_COOKIE_SECURE'] is None:\n            self.config['SESSION_COOKIE_SECURE'] = self.config['TLS_FLAVOR'] != 'notls'\n        self.config['SESSION_PERMANENT'] = True\n        self.config['SESSION_TIMEOUT'] = int(self.config['SESSION_TIMEOUT'])\n        self.config['SESSION_KEY_BITS'] = int(self.config['SESSION_KEY_BITS'])\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        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        hostnames = [host.strip() for host in self.config['HOSTNAMES'].split(',')]\n        self.config['HOSTNAMES'] = ','.join(hostnames)\n        self.config['HOSTNAME'] = hostnames[0]\n        self.config['DEFAULT_SPAM_THRESHOLD'] = int(self.config['DEFAULT_SPAM_THRESHOLD'])\n        self.config['PROXY_AUTH_WHITELIST'] = set(ipaddress.ip_network(cidr, False) for cidr in (cidr.strip() for cidr in self.config['PROXY_AUTH_WHITELIST'].split(',')) if cidr)\n        try:\n            self.config['MAILU_VERSION'] = open('/version', 'r').read()\n        except FileNotFoundError:\n            pass\n\n        # update the app config\n        app.config.update(self.config)\n", "path": "core/admin/mailu/configuration.py"}]}