diff --git a/CHANGELOG.md b/CHANGELOG.md index d925b50d..dbbbf413 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,6 @@ v1.6.0 - unreleased - Feature: Add posibilty to run webmail on root ([#501](https://github.com/Mailu/Mailu/issues/501)) - Feature: Upgrade docker-compose.yml to version 3 ([#539](https://github.com/Mailu/Mailu/issues/539)) - Feature: Documentation to deploy mailu on a docker swarm ([#551](https://github.com/Mailu/Mailu/issues/551)) -- Feature: Add full-text search support ([#552](https://github.com/Mailu/Mailu/issues/552)) - Feature: Add optional Maildir-Compression ([#553](https://github.com/Mailu/Mailu/issues/553)) - Feature: Preserve rspamd history on container restart ([#561](https://github.com/Mailu/Mailu/issues/561)) - Feature: FAQ ([#564](https://github.com/Mailu/Mailu/issues/564), [#677](https://github.com/Mailu/Mailu/issues/677)) @@ -77,6 +76,9 @@ v1.6.0 - unreleased - Enhancement: Added regex validation for alias username ([#764](https://github.com/Mailu/Mailu/issues/764)) - Enhancement: Allow to disable aliases or users for a specific domain ([#799](https://github.com/Mailu/Mailu/issues/799)) - Enhancement: Update documentation +- Enhancement: Include favicon package ([#801](https://github.com/Mailu/Mailu/issues/801), ([#802](https://github.com/Mailu/Mailu/issues/802)) +- Enhancement: Add logging at critical places in python start.py scripts. Implement LOG_LEVEL to control verbosity ([#588](https://github.com/Mailu/Mailu/issues/588)) +- Enhancement: Mark message as seen when reporting as spam - Upstream: Update Roundcube - Upstream: Update Rainloop - Bug: Rainloop fails with "domain not allowed" ([#93](https://github.com/Mailu/Mailu/issues/93)) @@ -110,6 +112,9 @@ v1.6.0 - unreleased - Bug: Fix rainloop permissions ([#637](https://github.com/Mailu/Mailu/issues/637)) - Bug: Fix broken webmail and logo url in admin ([#792](https://github.com/Mailu/Mailu/issues/792)) - Bug: Don't allow negative values on domain creation/edit ([#799](https://github.com/Mailu/Mailu/issues/799)) +- Bug: Don't recursivly chown on mailboxes ([#776](https://github.com/Mailu/Mailu/issues/776)) +- Bug: Fix forced password input for user edit ([#745](https://github.com/Mailu/Mailu/issues/745)) +- Bug: Fetched accounts: Password field is of type "text" ([#789](https://github.com/Mailu/Mailu/issues/789)) v1.5.1 - 2017-11-21 ------------------- diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..856fa5a0 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,16 @@ +## What type of PR? + +(Feature, enhancement, bug-fix, documentation) + +## What does this PR do? + +### Related issue(s) +- Mention an issue like: #001 +- Auto close an issue like: closes #001 + +## Prerequistes +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. + +- [ ] In case of feature or enhancement: documentation updated accordingly +- [ ] Unless it's docs or a minor change: place entry in the [changelog](CHANGELOG.md), under the latest un-released version. diff --git a/core/admin/mailu/models.py b/core/admin/mailu/models.py index 3588b4ae..a69cf715 100644 --- a/core/admin/mailu/models.py +++ b/core/admin/mailu/models.py @@ -260,10 +260,19 @@ class Email(object): @classmethod def resolve_destination(cls, localpart, domain_name, ignore_forward_keep=False): + localpart_stripped = None + if os.environ.get('RECIPIENT_DELIMITER') in localpart: + localpart_stripped = localpart.rsplit(os.environ.get('RECIPIENT_DELIMITER'), 1)[0] + alias = Alias.resolve(localpart, domain_name) + if not alias and localpart_stripped: + alias = Alias.resolve(localpart_stripped, domain_name) if alias: return alias.destination + user = User.query.get('{}@{}'.format(localpart, domain_name)) + if not user and localpart_stripped: + user = User.query.get('{}@{}'.format(localpart_stripped, domain_name)) if user: if user.forward_enabled: destination = user.forward_destination diff --git a/core/admin/mailu/ui/forms.py b/core/admin/mailu/ui/forms.py index 04597872..e8302199 100644 --- a/core/admin/mailu/ui/forms.py +++ b/core/admin/mailu/ui/forms.py @@ -84,7 +84,7 @@ class RelayForm(flask_wtf.FlaskForm): class UserForm(flask_wtf.FlaskForm): localpart = fields.StringField(_('E-mail'), [validators.DataRequired(), validators.Regexp(LOCALPART_REGEX)]) - pw = fields.PasswordField(_('Password'), [validators.DataRequired()]) + pw = fields.PasswordField(_('Password')) pw2 = fields.PasswordField(_('Confirm password'), [validators.EqualTo('pw')]) quota_bytes = fields_.IntegerSliderField(_('Quota'), default=1000000000) enable_imap = fields.BooleanField(_('Allow IMAP access'), default=True) @@ -165,11 +165,11 @@ class FetchForm(flask_wtf.FlaskForm): protocol = fields.SelectField(_('Protocol'), choices=[ ('imap', 'IMAP'), ('pop3', 'POP3') ]) - host = fields.StringField(_('Hostname or IP')) - port = fields.IntegerField(_('TCP port')) + host = fields.StringField(_('Hostname or IP'), [validators.DataRequired()]) + port = fields.IntegerField(_('TCP port'), [validators.DataRequired(), validators.NumberRange(min=0, max=65535)]) tls = fields.BooleanField(_('Enable TLS')) - username = fields.StringField(_('Username')) - password = fields.StringField(_('Password')) + username = fields.StringField(_('Username'), [validators.DataRequired()]) + password = fields.PasswordField(_('Password')) keep = fields.BooleanField(_('Keep emails on the server')) submit = fields.SubmitField(_('Submit')) diff --git a/core/admin/mailu/ui/templates/base.html b/core/admin/mailu/ui/templates/base.html index 73fa7aa7..c27776e2 100644 --- a/core/admin/mailu/ui/templates/base.html +++ b/core/admin/mailu/ui/templates/base.html @@ -13,6 +13,13 @@ {% block head %} {{super()}} + + + + + + + {% block scripts %} {{super()}} diff --git a/core/admin/mailu/ui/views/fetches.py b/core/admin/mailu/ui/views/fetches.py index d9f55404..f2049fe9 100644 --- a/core/admin/mailu/ui/views/fetches.py +++ b/core/admin/mailu/ui/views/fetches.py @@ -3,6 +3,7 @@ from mailu.ui import ui, forms, access import flask import flask_login +import wtforms @ui.route('/fetch/list', methods=['GET', 'POST'], defaults={'user_email': None}) @@ -21,6 +22,7 @@ def fetch_create(user_email): user_email = user_email or flask_login.current_user.email user = models.User.query.get(user_email) or flask.abort(404) form = forms.FetchForm() + form.pw.validators = [wtforms.validators.DataRequired()] if form.validate_on_submit(): fetch = models.Fetch(user=user) form.populate_obj(fetch) @@ -38,6 +40,8 @@ def fetch_edit(fetch_id): fetch = models.Fetch.query.get(fetch_id) or flask.abort(404) form = forms.FetchForm(obj=fetch) if form.validate_on_submit(): + if not form.password.data: + form.password.data = fetch.password form.populate_obj(fetch) models.db.session.commit() flask.flash('Fetch configuration updated') diff --git a/core/admin/mailu/ui/views/users.py b/core/admin/mailu/ui/views/users.py index 42eb6c05..8830ff5b 100644 --- a/core/admin/mailu/ui/views/users.py +++ b/core/admin/mailu/ui/views/users.py @@ -23,6 +23,7 @@ def user_create(domain_name): return flask.redirect( flask.url_for('.user_list', domain_name=domain.name)) form = forms.UserForm() + form.pw.validators = [wtforms.validators.DataRequired()] if domain.max_quota_bytes: form.quota_bytes.validators = [ wtforms.validators.NumberRange(max=domain.max_quota_bytes)] @@ -54,7 +55,6 @@ def user_edit(user_email): # Create the form form = forms.UserForm(obj=user) wtforms_components.read_only(form.localpart) - form.pw.validators = [] form.localpart.validators = [] if max_quota_bytes: form.quota_bytes.validators = [ diff --git a/core/admin/requirements-prod.txt b/core/admin/requirements-prod.txt index a538c023..3679b63f 100644 --- a/core/admin/requirements-prod.txt +++ b/core/admin/requirements-prod.txt @@ -34,7 +34,7 @@ pyOpenSSL==18.0.0 python-dateutil==2.7.5 python-editor==1.0.3 pytz==2018.7 -PyYAML==3.13 +PyYAML==4.2b4 redis==3.0.1 six==1.11.0 SQLAlchemy==1.2.13 diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index 1d4f7b91..83d23b52 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -9,8 +9,9 @@ RUN pip3 install jinja2 RUN pip3 install tenacity # Image specific layers under this line RUN apk add --no-cache \ - dovecot dovecot-pigeonhole-plugin dovecot-fts-lucene rspamd-client bash \ - && pip3 install podop + dovecot dovecot-pigeonhole-plugin rspamd-client bash \ + && pip3 install podop \ + && mkdir /var/lib/dovecot COPY conf /conf COPY start.py /start.py diff --git a/core/dovecot/conf/dovecot.conf b/core/dovecot/conf/dovecot.conf index 83c78f16..b7cca76c 100644 --- a/core/dovecot/conf/dovecot.conf +++ b/core/dovecot/conf/dovecot.conf @@ -7,22 +7,6 @@ postmaster_address = {{ POSTMASTER }}@{{ DOMAIN }} hostname = {{ HOSTNAMES.split(",")[0] }} submission_host = {{ FRONT_ADDRESS }} -{% if DISABLE_FTS_LUCENE != 'true' %} -############### -# Full-text search -############### -mail_plugins = $mail_plugins fts fts_lucene - -plugin { - fts = lucene - - fts_autoindex = yes - fts_autoindex_exclude = \Junk - - fts_lucene = whitespace_chars=@. -} -{% endif %} - ############### # Mailboxes ############### diff --git a/core/dovecot/conf/report-spam.sieve b/core/dovecot/conf/report-spam.sieve index 108d6210..87fd515e 100644 --- a/core/dovecot/conf/report-spam.sieve +++ b/core/dovecot/conf/report-spam.sieve @@ -1,3 +1,5 @@ +require "imap4flags"; require "vnd.dovecot.execute"; +setflag "\\seen"; execute :pipe "spam"; diff --git a/core/dovecot/start.py b/core/dovecot/start.py index 8bf66efd..15e370de 100755 --- a/core/dovecot/start.py +++ b/core/dovecot/start.py @@ -6,23 +6,40 @@ import socket import glob import multiprocessing import tenacity +import logging as log +import sys from tenacity import retry from podop import run_server +log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) def start_podop(): os.setuid(8) - run_server(3 if "DEBUG" in os.environ else 0, "dovecot", "/tmp/podop.socket", [ + run_server(0, "dovecot", "/tmp/podop.socket", [ ("quota", "url", "http://admin/internal/dovecot/§"), ("auth", "url", "http://admin/internal/dovecot/§"), ("sieve", "url", "http://admin/internal/dovecot/§"), ]) -convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) +def convert(src, dst): + logger = log.getLogger("convert()") + logger.debug("Source: %s, Destination: %s", src, dst) + open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) + +@retry( + stop=tenacity.stop_after_attempt(100), + wait=tenacity.wait_random(min=2, max=5), + before=tenacity.before_log(log.getLogger("tenacity.retry"), log.DEBUG), + before_sleep=tenacity.before_sleep_log(log.getLogger("tenacity.retry"), log.INFO), + after=tenacity.after_log(log.getLogger("tenacity.retry"), log.DEBUG) + ) +def resolve(hostname): + logger = log.getLogger("resolve()") + logger.info(hostname) + return socket.gethostbyname(hostname) # Actual startup script -resolve = retry(socket.gethostbyname, stop=tenacity.stop_after_attempt(100), wait=tenacity.wait_random(min=2, max=5)) os.environ["FRONT_ADDRESS"] = resolve(os.environ.get("FRONT_ADDRESS", "front")) os.environ["REDIS_ADDRESS"] = resolve(os.environ.get("REDIS_ADDRESS", "redis")) if os.environ["WEBMAIL"] != "none": @@ -33,5 +50,6 @@ for dovecot_file in glob.glob("/conf/*.conf"): # Run Podop, then postfix multiprocessing.Process(target=start_podop).start() -os.system("chown -R mail:mail /mail /var/lib/dovecot /conf") +os.system("chown mail:mail /mail") +os.system("chown -R mail:mail /var/lib/dovecot /conf") os.execv("/usr/sbin/dovecot", ["dovecot", "-c", "/etc/dovecot/dovecot.conf", "-F"]) diff --git a/core/nginx/Dockerfile b/core/nginx/Dockerfile index 8b1a2bae..6afa8301 100644 --- a/core/nginx/Dockerfile +++ b/core/nginx/Dockerfile @@ -10,6 +10,7 @@ RUN apk add --no-cache certbot nginx nginx-mod-mail openssl curl \ && pip3 install idna requests watchdog COPY conf /conf +COPY static /static COPY *.py / EXPOSE 80/tcp 443/tcp 110/tcp 143/tcp 465/tcp 587/tcp 993/tcp 995/tcp 25/tcp 10025/tcp 10143/tcp diff --git a/core/nginx/conf/nginx.conf b/core/nginx/conf/nginx.conf index c90f7806..7107a351 100644 --- a/core/nginx/conf/nginx.conf +++ b/core/nginx/conf/nginx.conf @@ -38,6 +38,8 @@ http { {% if KUBERNETES_INGRESS != 'true' %} # Main HTTP server server { + # Favicon stuff + root /static; # Variables for proxifying set $admin {{ HOST_ADMIN }}; set $antispam {{ HOST_ANTISPAM }}; @@ -90,9 +92,9 @@ http { {% if WEB_WEBMAIL != '/' %} location / { {% if WEBROOT_REDIRECT %} - return 301 {{ WEBROOT_REDIRECT }}; + try_files $uri {{ WEBROOT_REDIRECT }}; {% else %} - return 404; + try_files $uri =404; {% endif %} } {% endif %} diff --git a/core/nginx/config.py b/core/nginx/config.py index 07b7ea32..79370508 100755 --- a/core/nginx/config.py +++ b/core/nginx/config.py @@ -2,11 +2,18 @@ import jinja2 import os - -convert = lambda src, dst, args: open(dst, "w").write(jinja2.Template(open(src).read()).render(**args)) +import logging as log +import sys args = os.environ.copy() +log.basicConfig(stream=sys.stderr, level=args.get("LOG_LEVEL", "WARNING")) + +def convert(src, dst, args): + logger = log.getLogger("convert()") + logger.debug("Source: %s, Destination: %s", src, dst) + open(dst, "w").write(jinja2.Template(open(src).read()).render(**args)) + # Get the first DNS server with open("/etc/resolv.conf") as handle: content = handle.read().split() diff --git a/core/nginx/static/android-chrome-192x192.png b/core/nginx/static/android-chrome-192x192.png new file mode 100644 index 00000000..86231db9 Binary files /dev/null and b/core/nginx/static/android-chrome-192x192.png differ diff --git a/core/nginx/static/android-chrome-512x512.png b/core/nginx/static/android-chrome-512x512.png new file mode 100644 index 00000000..f029c9fd Binary files /dev/null and b/core/nginx/static/android-chrome-512x512.png differ diff --git a/core/nginx/static/apple-touch-icon.png b/core/nginx/static/apple-touch-icon.png new file mode 100644 index 00000000..b4f92f33 Binary files /dev/null and b/core/nginx/static/apple-touch-icon.png differ diff --git a/core/nginx/static/browserconfig.xml b/core/nginx/static/browserconfig.xml new file mode 100644 index 00000000..5aecc916 --- /dev/null +++ b/core/nginx/static/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #00aba9 + + + diff --git a/core/nginx/static/favicon-16x16.png b/core/nginx/static/favicon-16x16.png new file mode 100644 index 00000000..ff35d4b0 Binary files /dev/null and b/core/nginx/static/favicon-16x16.png differ diff --git a/core/nginx/static/favicon-32x32.png b/core/nginx/static/favicon-32x32.png new file mode 100644 index 00000000..2b6b749b Binary files /dev/null and b/core/nginx/static/favicon-32x32.png differ diff --git a/core/nginx/static/favicon.ico b/core/nginx/static/favicon.ico new file mode 100644 index 00000000..f1f3fe03 Binary files /dev/null and b/core/nginx/static/favicon.ico differ diff --git a/core/nginx/static/mstile-150x150.png b/core/nginx/static/mstile-150x150.png new file mode 100644 index 00000000..04c609f7 Binary files /dev/null and b/core/nginx/static/mstile-150x150.png differ diff --git a/core/nginx/static/safari-pinned-tab.svg b/core/nginx/static/safari-pinned-tab.svg new file mode 100644 index 00000000..43cea812 --- /dev/null +++ b/core/nginx/static/safari-pinned-tab.svg @@ -0,0 +1,25 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/core/nginx/static/site.webmanifest b/core/nginx/static/site.webmanifest new file mode 100644 index 00000000..b20abb7c --- /dev/null +++ b/core/nginx/static/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/core/postfix/start.py b/core/postfix/start.py index 86e9a827..a06b3833 100755 --- a/core/postfix/start.py +++ b/core/postfix/start.py @@ -7,14 +7,18 @@ import glob import shutil import tenacity import multiprocessing +import logging as log +import sys from tenacity import retry from podop import run_server +log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) def start_podop(): os.setuid(100) - run_server(3 if "DEBUG" in os.environ else 0, "postfix", "/tmp/podop.socket", [ + # TODO: Remove verbosity setting from Podop? + run_server(0, "postfix", "/tmp/podop.socket", [ ("transport", "url", "http://admin/internal/postfix/transport/§"), ("alias", "url", "http://admin/internal/postfix/alias/§"), ("domain", "url", "http://admin/internal/postfix/domain/§"), @@ -23,11 +27,24 @@ def start_podop(): ("senderlogin", "url", "http://admin/internal/postfix/sender/login/§") ]) -convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) +def convert(src, dst): + logger = log.getLogger("convert()") + logger.debug("Source: %s, Destination: %s", src, dst) + open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) + +@retry( + stop=tenacity.stop_after_attempt(100), + wait=tenacity.wait_random(min=2, max=5), + before=tenacity.before_log(log.getLogger("tenacity.retry"), log.DEBUG), + before_sleep=tenacity.before_sleep_log(log.getLogger("tenacity.retry"), log.INFO), + after=tenacity.after_log(log.getLogger("tenacity.retry"), log.DEBUG) + ) +def resolve(hostname): + logger = log.getLogger("resolve()") + logger.info(hostname) + return socket.gethostbyname(hostname) # Actual startup script -resolve = retry(socket.gethostbyname, stop=tenacity.stop_after_attempt(100), wait=tenacity.wait_random(min=2, max=5)) - os.environ["FRONT_ADDRESS"] = resolve(os.environ.get("FRONT_ADDRESS", "front")) os.environ["HOST_ANTISPAM"] = os.environ.get("HOST_ANTISPAM", "antispam:11332") os.environ["HOST_LMTP"] = os.environ.get("HOST_LMTP", "imap:2525") diff --git a/docs/compose/.env b/docs/compose/.env index 836e9dbf..cf906b58 100644 --- a/docs/compose/.env +++ b/docs/compose/.env @@ -3,10 +3,6 @@ # these few settings must however be configured before starting the mail # server and require a restart upon change. -# Set this to `true` to disable full text search by lucene (value: true, false) -# This is a workaround for the bug in issue #751 (indexer-worker crashes) -DISABLE_FTS_LUCENE=false - ################################### # Common configuration variables ################################### @@ -151,3 +147,6 @@ REAL_IP_FROM= # choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) REJECT_UNLISTED_RECIPIENT= + +# Log level threshold in start.py (value: CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET) +LOG_LEVEL=WARNING diff --git a/docs/configuration.rst b/docs/configuration.rst index 2f44b293..ec114c97 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -71,6 +71,14 @@ Web settings The ``WEB_ADMIN`` contains the path to the main admin interface, while ``WEB_WEBMAIL`` contains the path to the Web email client. +The ``WEBROOT_REDIRECT`` redirects all non-found queries to the set path. +An empty ``WEBROOT_REDIRECT`` value disables redirecting and enables classic +behavior of a 404 result when not found. +All three options need a leading slash (``/``) to work. + + .. note:: ``WEBROOT_REDIRECT`` has to point to a valid path on the webserver. + This means it cannot point to any services which are not enabled. + For example, don't point it to ``/webmail`` when ``WEBMAIL=none`` Both ``SITENAME`` and ``WEBSITE`` are customization options for the panel menu in the admin interface, while ``SITENAME`` is a customization option for @@ -83,6 +91,13 @@ The ``PASSWORD_SCHEME`` is the password encryption scheme. You should use the default value, unless you are importing password from a separate system and want to keep using the old password encryption scheme. +The ``LOG_LEVEL`` setting is used by the python start-up scripts as a logging threshold. +Log messages equal or higher than this priority will be printed. +Can be one of: CRITICAL, ERROR, WARNING, INFO, DEBUG or NOTSET. +See the `python docs`_ for more information. + +.. _`python docs`: https://docs.python.org/3.6/library/logging.html#logging-levels + Infrastructure settings ----------------------- diff --git a/docs/faq.rst b/docs/faq.rst index cf1895ea..2669d9d1 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -204,6 +204,41 @@ correct syntax. The following file names will be taken as override configuration *Issue reference:* `206`_. +I want to integrate Nextcloud with Mailu +```````````````````````````````````````` + +First of all you have to install dependencies required to authenticate users via imap in Nextcloud + +.. code-block:: bash + + apt-get update \ + && apt-get install -y libc-client-dev libkrb5-dev \ + && rm -rf /var/lib/apt/lists/* \ + && docker-php-ext-configure imap --with-kerberos --with-imap-ssl \ + && docker-php-ext-install imap + +Next, you have to enable External user support from Nextcloud Apps interface + +In the end you need to configure additional user backends in Nextcloud’s configuration config/config.php using the following syntax: + +.. code-block:: bash + + array( + array( + 'class' => 'OC_User_IMAP', + 'arguments' => array( + '{imap.example.com:993/imap/ssl}', 'example.com' + ), + ), + ), + +If a domain name (e.g. example.com) is specified, then this makes sure that only users from this domain will be allowed to login. +After successfull login the domain part will be striped and the rest used as username in NextCloud. e.g. 'username@example.com' will be 'username' in NextCloud. + +*Issue reference:* `575`_. + .. _`Postfix`: http://www.postfix.org/postconf.5.html .. _`Dovecot`: https://wiki.dovecot.org/ConfigFile .. _`NGINX`: https://nginx.org/en/docs/ @@ -218,6 +253,7 @@ correct syntax. The following file names will be taken as override configuration .. _`747`: https://github.com/Mailu/Mailu/issues/747 .. _`520`: https://github.com/Mailu/Mailu/issues/520 .. _`591`: https://github.com/Mailu/Mailu/issues/591 +.. _`575`: https://github.com/Mailu/Mailu/issues/575 Technical issues ---------------- @@ -304,7 +340,7 @@ See also :ref:`external_certs`. *Issue reference:* `426`_, `615`_. How do I activate DKIM and DMARC? -``````````````````````` +````````````````````````````````` Go into the Domain Panel and choose the Domain you want to enable DKIM for. Click the first icon on the left side (domain details). Now click on the top right on the *"Regenerate Keys"* Button. @@ -367,7 +403,6 @@ We **strongly** advice against downgrading the TLS version and ciphers! *Issue reference:* `363`_, `698`_. - .. _`troubleshooting tag`: https://github.com/Mailu/Mailu/issues?utf8=%E2%9C%93&q=label%3Afaq%2Ftroubleshooting .. _`85`: https://github.com/Mailu/Mailu/issues/85 .. _`102`: https://github.com/Mailu/Mailu/issues/102 diff --git a/optional/clamav/start.py b/optional/clamav/start.py index d4701d2d..56e1bcfe 100755 --- a/optional/clamav/start.py +++ b/optional/clamav/start.py @@ -1,12 +1,21 @@ #!/usr/bin/python3 import os +import logging as log +import sys + +log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) +logger=log.getLogger(__name__) # Bootstrap the database if clamav is running for the first time -os.system("[ -f /data/main.cvd ] || freshclam") +if not os.path.isfile("/data/main.cvd"): + logger.info("Starting primary virus DB download") + os.system("freshclam") # Run the update daemon +logger.info("Starting the update daemon") os.system("freshclam -d -c 6") # Run clamav +logger.info("Starting clamav") os.system("clamd") diff --git a/services/rspamd/start.py b/services/rspamd/start.py index 0b3c48a8..744d4a9c 100755 --- a/services/rspamd/start.py +++ b/services/rspamd/start.py @@ -5,13 +5,31 @@ import os import socket import glob import tenacity +import logging as log +import sys + from tenacity import retry -convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) +log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) + +def convert(src, dst): + logger = log.getLogger("convert()") + logger.debug("Source: %s, Destination: %s", src, dst) + open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) + +@retry( + stop=tenacity.stop_after_attempt(100), + wait=tenacity.wait_random(min=2, max=5), + before=tenacity.before_log(log.getLogger("tenacity.retry"), log.DEBUG), + before_sleep=tenacity.before_sleep_log(log.getLogger("tenacity.retry"), log.INFO), + after=tenacity.after_log(log.getLogger("tenacity.retry"), log.DEBUG) + ) +def resolve(hostname): + logger = log.getLogger("resolve()") + logger.info(hostname) + return socket.gethostbyname(hostname) # Actual startup script -resolve = retry(socket.gethostbyname, stop=tenacity.stop_after_attempt(100), wait=tenacity.wait_random(min=2, max=5)) - os.environ["FRONT_ADDRESS"] = resolve(os.environ.get("FRONT_ADDRESS", "front")) if "HOST_REDIS" not in os.environ: os.environ["HOST_REDIS"] = "redis" diff --git a/services/unbound/start.py b/services/unbound/start.py index 6f494762..4dd5f3be 100755 --- a/services/unbound/start.py +++ b/services/unbound/start.py @@ -2,8 +2,16 @@ import jinja2 import os +import logging as log +import sys + +log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) + +def convert(src, dst): + logger = log.getLogger("convert()") + logger.debug("Source: %s, Destination: %s", src, dst) + open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) -convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) convert("/unbound.conf", "/etc/unbound/unbound.conf") os.execv("/usr/sbin/unbound", ["-c /etc/unbound/unbound.conf"]) diff --git a/setup/flavors/compose/mailu.env b/setup/flavors/compose/mailu.env index 6bdc5e21..2d2b8735 100644 --- a/setup/flavors/compose/mailu.env +++ b/setup/flavors/compose/mailu.env @@ -160,3 +160,6 @@ REAL_IP_FROM={{ real_ip_from }} # choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) REJECT_UNLISTED_RECIPIENT={{ reject_unlisted_recipient }} + +# Log level threshold in start.py (value: CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET) +LOG_LEVEL=WARNING diff --git a/webmails/rainloop/start.py b/webmails/rainloop/start.py index 4c116e09..e2b917bf 100755 --- a/webmails/rainloop/start.py +++ b/webmails/rainloop/start.py @@ -3,8 +3,15 @@ import jinja2 import os import shutil +import logging as log +import sys -convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) +log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) + +def convert(src, dst): + logger = log.getLogger("convert()") + logger.debug("Source: %s, Destination: %s", src, dst) + open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) # Actual startup script os.environ["FRONT_ADDRESS"] = os.environ.get("FRONT_ADDRESS", "front") diff --git a/webmails/roundcube/start.py b/webmails/roundcube/start.py index 3a0bd0bc..4effd965 100755 --- a/webmails/roundcube/start.py +++ b/webmails/roundcube/start.py @@ -2,8 +2,15 @@ import os import jinja2 +import logging as log +import sys -convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) +log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) + +def convert(src, dst): + logger = log.getLogger("convert()") + logger.debug("Source: %s, Destination: %s", src, dst) + open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) os.environ["MAX_FILESIZE"] = str(int(int(os.environ.get("MESSAGE_SIZE_LIMIT"))*0.66/1048576)) @@ -14,4 +21,4 @@ os.system("mkdir -p /data/gpg") os.system("chown -R www-data:www-data /data") # Run apache -os.execv("/usr/local/bin/apache2-foreground", ["apache2-foreground"]) \ No newline at end of file +os.execv("/usr/local/bin/apache2-foreground", ["apache2-foreground"])