mirror of
https://github.com/Mailu/Mailu.git
synced 2025-04-15 11:47:04 +02:00
3729: Allow setting collation via env variable and add uvloop r=mergify[bot] a=Grennith ## What type of PR? Enhancement / Bugfix ## What does this PR do? As of https://github.com/Mailu/Mailu/pull/3701, the collation shall be set by overwriting `SQLALCHEMY_DATABASE_URI` to contain the collation of the related DB. However, this is currently not possible in the Helm chart of Mailu at all. It's statically set there and would also require not setting DB_NAME etc. to not have it overwritten, see https://github.com/Mailu/Mailu/blob/master/core/admin/mailu/configuration.py#L144 Additionally, uvloop is added to the prod requirements of which postfix-mta-sts-resolver makes use of. ### Related issue(s) - Mention an issue like: #3449 ## Prerequisites Before we can consider review and merge, please make sure the following list is done and checked. If an entry in not applicable, you can check it or remove it from the list. - [ x ] In case of feature or enhancement: documentation updated accordingly - [ ] Unless it's docs or a minor change: add [changelog](https://mailu.io/master/contributors/workflow.html#changelog) entry file. Co-authored-by: Till Skrodzki <till@mueskro.de>
175 lines
6.5 KiB
Python
175 lines
6.5 KiB
Python
import os
|
|
|
|
from datetime import timedelta
|
|
import ipaddress
|
|
|
|
DEFAULT_CONFIG = {
|
|
# Specific to the admin UI
|
|
'DOCKER_SOCKET': 'unix:///var/run/docker.sock',
|
|
'BABEL_DEFAULT_LOCALE': 'en',
|
|
'BABEL_DEFAULT_TIMEZONE': 'UTC',
|
|
'BOOTSTRAP_SERVE_LOCAL': True,
|
|
'RATELIMIT_STORAGE_URL': '',
|
|
'DEBUG': False,
|
|
'DEBUG_PROFILER': False,
|
|
'DEBUG_TB_INTERCEPT_REDIRECTS': False,
|
|
'DEBUG_ASSETS': '',
|
|
'DOMAIN_REGISTRATION': False,
|
|
'TEMPLATES_AUTO_RELOAD': True,
|
|
'MEMORY_SESSIONS': False,
|
|
'FETCHMAIL_ENABLED': True,
|
|
'MAILU_VERSION': 'unknown',
|
|
# Database settings
|
|
'DB_FLAVOR': None,
|
|
'DB_USER': 'mailu',
|
|
'DB_PW': None,
|
|
'DB_HOST': 'database',
|
|
'DB_NAME': 'mailu',
|
|
'DB_APPENDIX': '',
|
|
'SQLITE_DATABASE_FILE': 'data/main.db',
|
|
'SQLALCHEMY_DATABASE_URI': 'sqlite:////data/main.db',
|
|
'SQLALCHEMY_DATABASE_URI_ROUNDCUBE': 'sqlite:////data/roundcube.db',
|
|
'SQLALCHEMY_TRACK_MODIFICATIONS': False,
|
|
# Statistics management
|
|
'INSTANCE_ID_PATH': '/data/instance',
|
|
'STATS_ENDPOINT': '202406.{}.stats.mailu.io',
|
|
# Common configuration variables
|
|
'SECRET_KEY': 'changeMe',
|
|
'DOMAIN': 'mailu.io',
|
|
'HOSTNAMES': 'mail.mailu.io,alternative.mailu.io,yetanother.mailu.io',
|
|
'POSTMASTER': 'postmaster',
|
|
'WILDCARD_SENDERS': '',
|
|
'TLS_FLAVOR': 'cert',
|
|
'INBOUND_TLS_ENFORCE': False,
|
|
'DEFER_ON_TLS_ERROR': True,
|
|
'AUTH_RATELIMIT_IP': '5/hour',
|
|
'AUTH_RATELIMIT_IP_V4_MASK': 24,
|
|
'AUTH_RATELIMIT_IP_V6_MASK': 48,
|
|
'AUTH_RATELIMIT_USER': '50/day',
|
|
'AUTH_RATELIMIT_EXEMPTION': '',
|
|
'AUTH_RATELIMIT_EXEMPTION_LENGTH': 86400,
|
|
'DISABLE_STATISTICS': False,
|
|
# Mail settings
|
|
'DMARC_RUA': None,
|
|
'DMARC_RUF': None,
|
|
'WELCOME': False,
|
|
'WELCOME_SUBJECT': 'Dummy welcome topic',
|
|
'WELCOME_BODY': 'Dummy welcome body',
|
|
'DKIM_SELECTOR': 'dkim',
|
|
'DKIM_PATH': '/dkim/{domain}.{selector}.key',
|
|
'DEFAULT_QUOTA': 1000000000,
|
|
'MESSAGE_RATELIMIT': '200/day',
|
|
'MESSAGE_RATELIMIT_EXEMPTION': '',
|
|
'RECIPIENT_DELIMITER': '',
|
|
# Web settings
|
|
'SITENAME': 'Mailu',
|
|
'WEBSITE': 'https://mailu.io',
|
|
'ADMIN': 'none',
|
|
'WEB_ADMIN': '/admin',
|
|
'WEB_WEBMAIL': '/webmail',
|
|
'WEBMAIL': 'none',
|
|
'RECAPTCHA_PUBLIC_KEY': '',
|
|
'RECAPTCHA_PRIVATE_KEY': '',
|
|
'LOGO_URL': None,
|
|
'LOGO_BACKGROUND': None,
|
|
# Advanced settings
|
|
'AUTH_REQUIRE_TOKENS': False,
|
|
'API': False,
|
|
'WEB_API': '/api',
|
|
'API_TOKEN': None,
|
|
'FULL_TEXT_SEARCH': 'en',
|
|
'FULL_TEXT_SEARCH_ATTACHMENTS': False,
|
|
'LOG_LEVEL': 'INFO',
|
|
'SESSION_KEY_BITS': 128,
|
|
'SESSION_TIMEOUT': 3600,
|
|
'PERMANENT_SESSION_LIFETIME': 30*24*3600,
|
|
'SESSION_COOKIE_SECURE': None,
|
|
'CREDENTIAL_ROUNDS': 13,
|
|
'TLS_PERMISSIVE': True,
|
|
'TZ': 'Etc/UTC',
|
|
'DEFAULT_SPAM_THRESHOLD': 80,
|
|
'PORTS': '25,80,443,465,993,995,4190',
|
|
'PROXY_AUTH_WHITELIST': '',
|
|
'PROXY_AUTH_HEADER': 'X-Auth-Email',
|
|
'PROXY_AUTH_CREATE': False,
|
|
'PROXY_AUTH_LOGOUT_URL': None,
|
|
'SUBNET': '192.168.203.0/24',
|
|
'SUBNET6': None,
|
|
}
|
|
|
|
class ConfigManager:
|
|
""" Naive configuration manager that uses environment only
|
|
"""
|
|
|
|
DB_TEMPLATES = {
|
|
'sqlite': 'sqlite:////{SQLITE_DATABASE_FILE}',
|
|
'postgresql': 'postgresql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}{DB_APPENDIX}',
|
|
'mysql': 'mysql+mysqlconnector://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}{DB_APPENDIX}',
|
|
}
|
|
|
|
def __init__(self):
|
|
self.config = dict()
|
|
|
|
def __get_env(self, key, value):
|
|
key_file = key + "_FILE"
|
|
if key_file in os.environ:
|
|
with open(os.environ.get(key_file)) as file:
|
|
value_from_file = file.read()
|
|
return value_from_file.strip()
|
|
else:
|
|
return os.environ.get(key, value)
|
|
|
|
def __coerce_value(self, value):
|
|
if isinstance(value, str) and value.lower() in ('true','yes'):
|
|
return True
|
|
elif isinstance(value, str) and value.lower() in ('false', 'no'):
|
|
return False
|
|
return value
|
|
|
|
def init_app(self, app):
|
|
# get current app config
|
|
self.config.update(app.config)
|
|
# get environment variables
|
|
for key in os.environ:
|
|
if key.endswith('_ADDRESS'):
|
|
self.config[key] = os.environ[key]
|
|
|
|
self.config.update({
|
|
key: self.__coerce_value(self.__get_env(key, value))
|
|
for key, value in DEFAULT_CONFIG.items()
|
|
})
|
|
|
|
# automatically set the sqlalchemy string
|
|
if self.config['DB_FLAVOR']:
|
|
template = self.DB_TEMPLATES[self.config['DB_FLAVOR']]
|
|
self.config['SQLALCHEMY_DATABASE_URI'] = template.format(**self.config)
|
|
|
|
if not self.config.get('RATELIMIT_STORAGE_URL'):
|
|
self.config['RATELIMIT_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/2'
|
|
|
|
self.config['SESSION_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/3'
|
|
self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
|
|
self.config['SESSION_COOKIE_HTTPONLY'] = True
|
|
if self.config['SESSION_COOKIE_SECURE'] is None:
|
|
self.config['SESSION_COOKIE_SECURE'] = self.config['TLS_FLAVOR'] != 'notls'
|
|
self.config['SESSION_PERMANENT'] = True
|
|
self.config['SESSION_TIMEOUT'] = int(self.config['SESSION_TIMEOUT'])
|
|
self.config['SESSION_KEY_BITS'] = int(self.config['SESSION_KEY_BITS'])
|
|
self.config['PERMANENT_SESSION_LIFETIME'] = int(self.config['PERMANENT_SESSION_LIFETIME'])
|
|
self.config['AUTH_RATELIMIT_IP_V4_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V4_MASK'])
|
|
self.config['AUTH_RATELIMIT_IP_V6_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V6_MASK'])
|
|
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)
|
|
self.config['MESSAGE_RATELIMIT_EXEMPTION'] = set([s for s in self.config['MESSAGE_RATELIMIT_EXEMPTION'].lower().replace(' ', '').split(',') if s])
|
|
hostnames = [host.strip() for host in self.config['HOSTNAMES'].split(',')]
|
|
self.config['HOSTNAMES'] = ','.join(hostnames)
|
|
self.config['HOSTNAME'] = hostnames[0]
|
|
self.config['DEFAULT_SPAM_THRESHOLD'] = int(self.config['DEFAULT_SPAM_THRESHOLD'])
|
|
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)
|
|
try:
|
|
self.config['MAILU_VERSION'] = open('/version', 'r').read()
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
# update the app config
|
|
app.config.update(self.config)
|