We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Discussion of 1.9 roadmap
As proposed in the last project meeting (#1582 (comment)), we are experimenting with a discussion driven model for determining the roadmap of the next release. This follows the existing practice of the succesful community run project Gitea (e.g go-gitea/gitea#14477).

During last meeting someone made the remark that with everything in the pipeline we already have sufficient work for a 1.9 release. I agree. We have this special situation due to the 1.8rc where we had a release candidate for over 7 months. This caused many PRs to be merged, but not included in 1.8. These are now waiting to be made available via a 1.9 release. This also caused a lot of already submitted PRs not be reviewed yet. We wanted to focus on getting 1.8 out of the door.

There are new PRs waiting for review for
- Updating the interface to AdminLTE3 #1800. This is not only a security update, but also adds a lot of functionality such as
  - a language selector
  - more modern look&feel of interface elements
  - filtering and column ordering for tables.
- Multiple PRs for increasing the overall security of mailu #1922, #1916, #1902. 
  - For these PRs we (I?) will also introduce a brand new security documentation page where you can find all documentation on what security measures are in place, how it works and how it can be tweaked. 
- updated polish translations #1751 and completely new Hebrew translation #1873.

We have already merged PRs on master for
- Completely new CLI configuration import/export which really exports/imports the complete mailu configuration database. With this it is possible to migrate the mailu config to another mailu instance. You can also use it to easily add new configuration (e.g. import users with specific settings) https://mailu.io/master/cli.html#config-export
- single sign on for webmail. Webmail uses the admin web interface for authenticating users
- various security related enhancements
- All other already merged PRs which were not included in 1.8. See all newsfragments for the details https://github.com/Mailu/Mailu/tree/master/towncrier/newsfragments

IMHO All those features together are sufficient to release a new version.


I suggest we review all open PRs and then check what issues really need to be included as well for 1.9 and include these on the roadmap. Examples are:
- I think a good example is the new SSO #1929. When you are redirected to the login page, it does not show that the login is for the webmail. You have the feeling that you are redirected to the wrong page. See the demo site for an example (https://test.mailu.io/webmail/). Of course this is not production worthy. 
- Incorrect documentation
  - E.g.the documentation on translations #1869. We have no weblate instance anymore for web-based translations. The documentation must be updated as it is incorrect now. You can only do translations manually with a tool such as poedit and send a PR for getting your new translation files merged .
  - documentation on development environment for the admin interface #1577. The faulty documentation should be removed or directly updated with the correct steps.

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 1.9.




--- 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 = None
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.8',
40     'versions': [
41         ('1.5', '/1.5/'),
42         ('1.6', '/1.6/'),
43         ('1.7', '/1.7/'),
44         ('1.8', '/1.8/'),
45         ('master', '/master/')
46     ],
47     'conf_py_path': '/docs/'
48 }
49
```

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': '18.{}.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
@@ -27,7 +27,7 @@
     'SQLALCHEMY_TRACK_MODIFICATIONS': False,
     # Statistics management
     'INSTANCE_ID_PATH': '/data/instance',
-    'STATS_ENDPOINT': '18.{}.stats.mailu.io',
+    'STATS_ENDPOINT': '19.{}.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
@@ -36,12 +36,10 @@
     'github_user': 'mailu',
     'github_repo': 'mailu',
     'github_version': version,
-    'stable_version': '1.8',
+    'stable_version': '1.9',
     'versions': [
-        ('1.5', '/1.5/'),
-        ('1.6', '/1.6/'),
-        ('1.7', '/1.7/'),
         ('1.8', '/1.8/'),
+        ('1.9', '/1.9/'),
         ('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@@ -27,7 +27,7 @@\n     'SQLALCHEMY_TRACK_MODIFICATIONS': False,\n     # Statistics management\n     'INSTANCE_ID_PATH': '/data/instance',\n-    'STATS_ENDPOINT': '18.{}.stats.mailu.io',\n+    'STATS_ENDPOINT': '19.{}.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@@ -36,12 +36,10 @@\n     'github_user': 'mailu',\n     'github_repo': 'mailu',\n     'github_version': version,\n-    'stable_version': '1.8',\n+    'stable_version': '1.9',\n     'versions': [\n-        ('1.5', '/1.5/'),\n-        ('1.6', '/1.6/'),\n-        ('1.7', '/1.7/'),\n         ('1.8', '/1.8/'),\n+        ('1.9', '/1.9/'),\n         ('master', '/master/')\n     ],\n     'conf_py_path': '/docs/'\n", "issue": "Discussion of 1.9 roadmap\nAs proposed in the last project meeting (#1582 (comment)), we are experimenting with a discussion driven model for determining the roadmap of the next release. This follows the existing practice of the succesful community run project Gitea (e.g go-gitea/gitea#14477).\r\n\r\nDuring last meeting someone made the remark that with everything in the pipeline we already have sufficient work for a 1.9 release. I agree. We have this special situation due to the 1.8rc where we had a release candidate for over 7 months. This caused many PRs to be merged, but not included in 1.8. These are now waiting to be made available via a 1.9 release. This also caused a lot of already submitted PRs not be reviewed yet. We wanted to focus on getting 1.8 out of the door.\r\n\r\nThere are new PRs waiting for review for\r\n- Updating the interface to AdminLTE3 #1800. This is not only a security update, but also adds a lot of functionality such as\r\n  - a language selector\r\n  - more modern look&feel of interface elements\r\n  - filtering and column ordering for tables.\r\n- Multiple PRs for increasing the overall security of mailu #1922, #1916, #1902. \r\n  - For these PRs we (I?) will also introduce a brand new security documentation page where you can find all documentation on what security measures are in place, how it works and how it can be tweaked. \r\n- updated polish translations #1751 and completely new Hebrew translation #1873.\r\n\r\nWe have already merged PRs on master for\r\n- Completely new CLI configuration import/export which really exports/imports the complete mailu configuration database. With this it is possible to migrate the mailu config to another mailu instance. You can also use it to easily add new configuration (e.g. import users with specific settings) https://mailu.io/master/cli.html#config-export\r\n- single sign on for webmail. Webmail uses the admin web interface for authenticating users\r\n- various security related enhancements\r\n- All other already merged PRs which were not included in 1.8. See all newsfragments for the details https://github.com/Mailu/Mailu/tree/master/towncrier/newsfragments\r\n\r\nIMHO All those features together are sufficient to release a new version.\r\n\r\n\r\nI suggest we review all open PRs and then check what issues really need to be included as well for 1.9 and include these on the roadmap. Examples are:\r\n- I think a good example is the new SSO #1929. When you are redirected to the login page, it does not show that the login is for the webmail. You have the feeling that you are redirected to the wrong page. See the demo site for an example (https://test.mailu.io/webmail/). Of course this is not production worthy. \r\n- Incorrect documentation\r\n  - E.g.the documentation on translations #1869. We have no weblate instance anymore for web-based translations. The documentation must be updated as it is incorrect now. You can only do translations manually with a tool such as poedit and send a PR for getting your new translation files merged .\r\n  - documentation on development environment for the admin interface #1577. The faulty documentation should be removed or directly updated with the correct steps.\r\n\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 1.9.\r\n\r\n\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 = None\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.8',\n    'versions': [\n        ('1.5', '/1.5/'),\n        ('1.6', '/1.6/'),\n        ('1.7', '/1.7/'),\n        ('1.8', '/1.8/'),\n        ('master', '/master/')\n    ],\n    'conf_py_path': '/docs/'\n}\n", "path": "docs/conf.py"}, {"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': '18.{}.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": "#!/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 = None\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\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"}]}