diff --git a/.mergify.yml b/.mergify.yml index ccf049d3..7c8b7b38 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -4,6 +4,8 @@ pull_request_rules: conditions: - -title~=(WIP|wip) - -label~=^(status/wip|status/blocked)$ + - -closed + - -merged actions: comment: message: | diff --git a/CHANGELOG.md b/CHANGELOG.md index cbd22a47..13c5a25f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ v1.6.1 - unreleased ------------------- - Enhancement: Make Unbound drop privileges after binding to port - Enhancement: Create an Authentication Token with IPv6 address restriction ([#829](https://github.com/Mailu/Mailu/issues/829)) +- Bug: Fix creating new fetched accounts +- Enhancement: Missing wildcard option in alias flask command ([#869](https://github.com/Mailu/Mailu/issues/869)) +- Bug: Fix poor performance if ANTIVIRUS is configured to none. v1.6.0 - 2019-01-18 ------------------- @@ -34,7 +37,6 @@ v1.6.0 - 2019-01-18 - Feature: Automated Releases ([#487](https://github.com/Mailu/Mailu/issues/487)) - Feature: Support for ARC ([#495](https://github.com/Mailu/Mailu/issues/495)) - 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 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)) @@ -85,6 +87,7 @@ v1.6.0 - 2019-01-18 - 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 +- Enhancement: Better support and document IPv6 ([#827](https://github.com/Mailu/Mailu/issues/827)) - Upstream: Update Roundcube - Upstream: Update Rainloop - Bug: Rainloop fails with "domain not allowed" ([#93](https://github.com/Mailu/Mailu/issues/93)) diff --git a/core/admin/Dockerfile b/core/admin/Dockerfile index 33c0bde7..4f887ff2 100644 --- a/core/admin/Dockerfile +++ b/core/admin/Dockerfile @@ -1,8 +1,9 @@ FROM alpine:3.8 # python3 shared with most images RUN apk add --no-cache \ - python3 py3-pip \ + python3 py3-pip git \ && pip3 install --upgrade pip +RUN pip3 install git+https://github.com/usrpro/MailuStart.git#egg=mailustart # Image specific layers under this line RUN mkdir -p /app WORKDIR /app diff --git a/core/admin/mailu/configuration.py b/core/admin/mailu/configuration.py index 8ccafce2..84fbbdde 100644 --- a/core/admin/mailu/configuration.py +++ b/core/admin/mailu/configuration.py @@ -1,5 +1,5 @@ import os - +from mailustart import resolve DEFAULT_CONFIG = { # Specific to the admin UI @@ -61,7 +61,6 @@ DEFAULT_CONFIG = { 'POD_ADDRESS_RANGE': None } - class ConfigManager(dict): """ Naive configuration manager that uses environment only """ @@ -75,6 +74,12 @@ class ConfigManager(dict): def __init__(self): self.config = dict() + def resolve_host(self): + self.config['HOST_IMAP'] = resolve(self.config['HOST_IMAP']) + self.config['HOST_POP3'] = resolve(self.config['HOST_POP3']) + self.config['HOST_AUTHSMTP'] = resolve(self.config['HOST_AUTHSMTP']) + self.config['HOST_SMTP'] = resolve(self.config['HOST_SMTP']) + def __coerce_value(self, value): if isinstance(value, str) and value.lower() in ('true','yes'): return True @@ -89,6 +94,7 @@ class ConfigManager(dict): key: self.__coerce_value(os.environ.get(key, value)) for key, value in DEFAULT_CONFIG.items() }) + self.resolve_host() # automatically set the sqlalchemy string if self.config['DB_FLAVOR']: diff --git a/core/admin/mailu/internal/nginx.py b/core/admin/mailu/internal/nginx.py index 460719f2..2dac4db1 100644 --- a/core/admin/mailu/internal/nginx.py +++ b/core/admin/mailu/internal/nginx.py @@ -2,7 +2,6 @@ from mailu import models from flask import current_app as app import re -import socket import urllib @@ -89,5 +88,4 @@ def get_server(protocol, authenticated=False): hostname, port = extract_host_port(app.config['HOST_AUTHSMTP'], 10025) else: hostname, port = extract_host_port(app.config['HOST_SMTP'], 25) - address = socket.gethostbyname(hostname) - return address, port + return hostname, port diff --git a/core/admin/mailu/manage.py b/core/admin/mailu/manage.py index e11644e7..3a8256ad 100644 --- a/core/admin/mailu/manage.py +++ b/core/admin/mailu/manage.py @@ -292,8 +292,9 @@ def alias_delete(email): @click.argument('localpart') @click.argument('domain_name') @click.argument('destination') +@click.option('-w', '--wildcard', is_flag=True) @flask_cli.with_appcontext -def alias(localpart, domain_name, destination): +def alias(localpart, domain_name, destination, wildcard=False): """ Create an alias """ domain = models.Domain.query.get(domain_name) @@ -303,6 +304,7 @@ def alias(localpart, domain_name, destination): alias = models.Alias( localpart=localpart, domain=domain, + wildcard=wildcard, destination=destination.split(','), email="%s@%s" % (localpart, domain_name) ) diff --git a/core/admin/mailu/ui/views/fetches.py b/core/admin/mailu/ui/views/fetches.py index f2049fe9..ec208af1 100644 --- a/core/admin/mailu/ui/views/fetches.py +++ b/core/admin/mailu/ui/views/fetches.py @@ -22,7 +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()] + form.password.validators = [wtforms.validators.DataRequired()] if form.validate_on_submit(): fetch = models.Fetch(user=user) form.populate_obj(fetch) diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index 83d23b52..02c9e49d 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -1,12 +1,10 @@ FROM alpine:3.8 # python3 shared with most images RUN apk add --no-cache \ - python3 py3-pip \ + python3 py3-pip git \ && pip3 install --upgrade pip # Shared layer between rspamd, postfix, dovecot, unbound and nginx -RUN pip3 install jinja2 -# Shared layer between rspamd, postfix, dovecot -RUN pip3 install tenacity +RUN pip3 install git+https://github.com/usrpro/MailuStart.git#egg=mailustart # Image specific layers under this line RUN apk add --no-cache \ dovecot dovecot-pigeonhole-plugin rspamd-client bash \ diff --git a/core/dovecot/start.py b/core/dovecot/start.py index 15e370de..53999bd6 100755 --- a/core/dovecot/start.py +++ b/core/dovecot/start.py @@ -1,47 +1,29 @@ #!/usr/bin/python3 -import jinja2 import os -import socket import glob import multiprocessing -import tenacity import logging as log import sys +from mailustart import resolve, convert -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) + url = "http://" + os.environ["ADMIN_ADDRESS"] + "/internal/dovecot/§" 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/§"), + ("quota", "url", url ), + ("auth", "url", url), + ("sieve", "url", url), ]) -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 os.environ["FRONT_ADDRESS"] = resolve(os.environ.get("FRONT_ADDRESS", "front")) os.environ["REDIS_ADDRESS"] = resolve(os.environ.get("REDIS_ADDRESS", "redis")) +os.environ["ADMIN_ADDRESS"] = resolve(os.environ.get("ADMIN_ADDRESS", "admin")) if os.environ["WEBMAIL"] != "none": os.environ["WEBMAIL_ADDRESS"] = resolve(os.environ.get("WEBMAIL_ADDRESS", "webmail")) diff --git a/core/nginx/Dockerfile b/core/nginx/Dockerfile index 6afa8301..9bc4c745 100644 --- a/core/nginx/Dockerfile +++ b/core/nginx/Dockerfile @@ -1,10 +1,10 @@ FROM alpine:3.8 # python3 shared with most images RUN apk add --no-cache \ - python3 py3-pip \ + python3 py3-pip git \ && pip3 install --upgrade pip # Shared layer between rspamd, postfix, dovecot, unbound and nginx -RUN pip3 install jinja2 +RUN pip3 install git+https://github.com/usrpro/MailuStart.git#egg=mailustart # Image specific layers under this line RUN apk add --no-cache certbot nginx nginx-mod-mail openssl curl \ && pip3 install idna requests watchdog diff --git a/core/nginx/config.py b/core/nginx/config.py index 79370508..78c76345 100755 --- a/core/nginx/config.py +++ b/core/nginx/config.py @@ -1,32 +1,27 @@ #!/usr/bin/python3 -import jinja2 import os import logging as log import sys +from mailustart import resolve, convert 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() args["RESOLVER"] = content[content.index("nameserver") + 1] -if "HOST_WEBMAIL" not in args: - args["HOST_WEBMAIL"] = "webmail" -if "HOST_ADMIN" not in args: - args["HOST_ADMIN"] = "admin" -if "HOST_WEBDAV" not in args: - args["HOST_WEBDAV"] = "webdav:5232" -if "HOST_ANTISPAM" not in args: - args["HOST_ANTISPAM"] = "antispam:11334" +args["HOST_ADMIN"] = resolve(args.get("HOST_ADMIN", "admin")) +args["HOST_ANTISPAM"] = resolve(args.get("HOST_ANTISPAM", "antispam:11334")) +args["HOST_WEBMAIL"] = args.get("HOST_WEBMAIL", "webmail") +if args["WEBMAIL"] != "none": + args["HOST_WEBMAIL"] = resolve(args.get("HOST_WEBMAIL")) +args["HOST_WEBDAV"] = args.get("HOST_WEBDAV", "webdav:5232") +if args["WEBDAV"] != "none": + args["HOST_WEBDAV"] = resolve(args.get("HOST_WEBDAV")) # TLS configuration cert_name = os.getenv("TLS_CERT_FILENAME", default="cert.pem") diff --git a/core/postfix/Dockerfile b/core/postfix/Dockerfile index ac9c8159..4b6c4aee 100644 --- a/core/postfix/Dockerfile +++ b/core/postfix/Dockerfile @@ -1,12 +1,10 @@ FROM alpine:3.8 # python3 shared with most images RUN apk add --no-cache \ - python3 py3-pip \ + python3 py3-pip git \ && pip3 install --upgrade pip # Shared layer between rspamd, postfix, dovecot, unbound and nginx -RUN pip3 install jinja2 -# Shared layer between rspamd, postfix, dovecot -RUN pip3 install tenacity +RUN pip3 install git+https://github.com/usrpro/MailuStart.git#egg=mailustart # Image specific layers under this line RUN apk add --no-cache postfix postfix-pcre rsyslog \ diff --git a/core/postfix/start.py b/core/postfix/start.py index a06b3833..95c97fde 100755 --- a/core/postfix/start.py +++ b/core/postfix/start.py @@ -1,53 +1,35 @@ #!/usr/bin/python3 -import jinja2 import os -import socket import glob import shutil -import tenacity import multiprocessing import logging as log import sys +from mailustart import resolve, convert -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) + url = "http://" + os.environ["ADMIN_ADDRESS"] + "/internal/postfix/" # 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/§"), - ("mailbox", "url", "http://admin/internal/postfix/mailbox/§"), - ("senderaccess", "url", "http://admin/internal/postfix/sender/access/§"), - ("senderlogin", "url", "http://admin/internal/postfix/sender/login/§") + ("transport", "url", url + "transport/§"), + ("alias", "url", url + "alias/§"), + ("domain", "url", url + "domain/§"), + ("mailbox", "url", url + "mailbox/§"), + ("senderaccess", "url", url + "sender/access/§"), + ("senderlogin", "url", url + "sender/login/§") ]) -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 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") +os.environ["ADMIN_ADDRESS"] = resolve(os.environ.get("ADMIN_ADDRESS", "admin")) +os.environ["HOST_ANTISPAM"] = resolve(os.environ.get("HOST_ANTISPAM", "antispam:11332")) +os.environ["HOST_LMTP"] = resolve(os.environ.get("HOST_LMTP", "imap:2525")) for postfix_file in glob.glob("/conf/*.cf"): convert(postfix_file, os.path.join("/etc/postfix", os.path.basename(postfix_file))) diff --git a/docs/cli.rst b/docs/cli.rst index 36899801..7328db0f 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -39,7 +39,7 @@ primary difference with simple `user` command is that password is being imported .. code-block:: bash - docker-compose run --rm admin flask mailu user-import --hash_scheme='SHA512-CRYPT' myuser example.net '$6$51ebe0cb9f1dab48effa2a0ad8660cb489b445936b9ffd812a0b8f46bca66dd549fea530ce' + docker-compose run --rm admin flask mailu user-import myuser example.net '$6$51ebe0cb9f1dab48effa2a0ad8660cb489b445936b9ffd812a0b8f46bca66dd549fea530ce' 'SHA512-CRYPT' user-delete ------------ diff --git a/docs/contributors/environment.rst b/docs/contributors/environment.rst index 66c1a400..e2e74d8f 100644 --- a/docs/contributors/environment.rst +++ b/docs/contributors/environment.rst @@ -227,12 +227,9 @@ trying to fix. When happy, you can approve the PR. When running into failures, m Additional commits `````````````````` -Sometimes users add new commits after ``bors try`` was run automatically. -In such cases, a reviewer will have to re-issue a ``bors try`` manually in order -to get the latest changes in the test image. The reviewer will have to be sure the -build finished successful before pulling the new images. - -Any previous reviews get dismissed automatically, whenever a new commit is done afterwards. +On every new commit ``bors try`` is run automatically. Past approvals get dismissed automatically. +When doing a subsequent review on the same PR, be sure to pull the latest image from docker hub +after Bors confirms a successful build. When bors try fails ``````````````````` diff --git a/docs/faq.rst b/docs/faq.rst index 4abd8874..2053bfe4 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -136,6 +136,49 @@ You're mail service will be reachable for IMAP, POP3, SMTP and Webmail at the ad *Issue reference:* `742`_, `747`_. +How to make IPv6 work? +`````````````````````` + +Docker currently does not expose the IPv6 ports properly, as it does not interface with ``ip6tables``. +Lets start with quoting everything that's wrong: + + Unfortunately, initially Docker was not created with IPv6 in mind. + It was added later and, while it has come a long way, is still not as usable as one would want. + Much discussion is still going on as to how IPv6 should be used in a containerized world; + See the various GitHub issues linked below: + + - Giving each container a publicly routable address means all ports (even unexposed / unpublished ports) are suddenly + reachable by everyone, if no additional filtering is done + (`docker/docker#21614 `_) + - By default, each container gets a random IPv6, making it impossible to do properly do DNS; + the alternative is to assign a specific IPv6 address to each container, + still an administrative hassle (`docker/docker#13481 `_) + - Published ports won't work on IPv6, unless you have the userland proxy enabled + (which, for now, is enabled by default in Docker) + - The userland proxy, however, seems to be on its way out + (`docker/docker#14856 `_) and has various issues, like: + + - It can use a lot of RAM (`docker/docker#11185 `_) + - Source IP addresses are rewritten, making it completely unusable for many purposes, e.g. mail servers + (`docker/docker#17666 `_), + (`docker/libnetwork#1099 `_). + + -- `Robbert Klarenbeek `_ (docker-ipv6nat author) + +So, how to make it work? Well, by using `docker-ipv6nat`_! This nifty container will set up ``ip6tables``, +just as Docker would do for IPv4. We know that nat-ing is not advised in IPv6, +however exposing all containers to public network neither. The choice is ultimately yous. + +Mailu `setup utility`_ generates a safe IPv6 ULA subnet by default. So when you run the following command, +Mailu will start to function on IPv6: + +.. code-block:: bash + + docker run -d --restart=always -v /var/run/docker.sock:/var/run/docker.sock:ro --privileged --net=host robbertkl/ipv6nat + +.. _`docker-ipv6nat`: https://github.com/robbertkl/docker-ipv6nat +.. _`setup utility`: https://setup.mailu.io + How does Mailu scale up? ```````````````````````` diff --git a/optional/traefik-certdumper/run.sh b/optional/traefik-certdumper/run.sh index 78d20a84..5d643670 100755 --- a/optional/traefik-certdumper/run.sh +++ b/optional/traefik-certdumper/run.sh @@ -4,20 +4,16 @@ function dump() { echo "$(date) Dumping certificates" bash dumpcerts.sh /traefik/acme.json /tmp/work/ || return - for crt_file in $(ls /tmp/work/certs/*); do - pem_file=$(echo $crt_file | sed 's/certs/pem/g' | sed 's/.crt/-public.pem/g') - echo "openssl x509 -inform PEM -in $crt_file > $pem_file" - openssl x509 -inform PEM -in $crt_file > $pem_file - done + # private-keys are rsa, we need pem though for key_file in $(ls /tmp/work/private/*); do pem_file=$(echo $key_file | sed 's/private/pem/g' | sed 's/.key/-private.pem/g') - echo "openssl rsa -in $key_file -text > $pem_file" openssl rsa -in $key_file -text > $pem_file done echo "$(date) Copying certificates" cp -v /tmp/work/pem/${DOMAIN}-private.pem /output/key.pem - cp -v /tmp/work/pem/${DOMAIN}-public.pem /output/cert.pem + # the .crt is a chained-pem, as common for letsencrypt + cp -v /tmp/work/certs/${DOMAIN}.crt /output/cert.pem } mkdir -p /tmp/work/pem /tmp/work/certs diff --git a/services/rspamd/Dockerfile b/services/rspamd/Dockerfile index 6d0cb5d0..32e93fac 100644 --- a/services/rspamd/Dockerfile +++ b/services/rspamd/Dockerfile @@ -1,12 +1,10 @@ FROM alpine:3.8 # python3 shared with most images RUN apk add --no-cache \ - python3 py3-pip \ + python3 py3-pip git \ && pip3 install --upgrade pip # Shared layer between rspamd, postfix, dovecot, unbound and nginx -RUN pip3 install jinja2 -# Shared layer between rspamd, postfix, dovecot -RUN pip3 install tenacity +RUN pip3 install git+https://github.com/usrpro/MailuStart.git#egg=mailustart # Image specific layers under this line RUN apk add --no-cache rspamd rspamd-controller rspamd-proxy rspamd-fuzzy ca-certificates curl diff --git a/services/rspamd/conf/antivirus.conf b/services/rspamd/conf/antivirus.conf index a72f79c1..7bf646b8 100644 --- a/services/rspamd/conf/antivirus.conf +++ b/services/rspamd/conf/antivirus.conf @@ -1,6 +1,8 @@ +{% if ANTIVIRUS == 'clamav' %} clamav { attachments_only = true; symbol = "CLAM_VIRUS"; type = "clamav"; servers = "antivirus:3310"; } +{% endif %} diff --git a/services/rspamd/start.py b/services/rspamd/start.py index 744d4a9c..3febed2b 100755 --- a/services/rspamd/start.py +++ b/services/rspamd/start.py @@ -1,34 +1,13 @@ #!/usr/bin/python3 -import jinja2 import os -import socket import glob -import tenacity import logging as log import sys - -from tenacity import retry +from mailustart import resolve, convert 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 os.environ["FRONT_ADDRESS"] = resolve(os.environ.get("FRONT_ADDRESS", "front")) diff --git a/services/unbound/Dockerfile b/services/unbound/Dockerfile index dbf8a3a9..9d40b691 100644 --- a/services/unbound/Dockerfile +++ b/services/unbound/Dockerfile @@ -1,10 +1,10 @@ FROM alpine:3.8 # python3 shared with most images RUN apk add --no-cache \ - python3 py3-pip \ + python3 py3-pip git \ && pip3 install --upgrade pip # Shared layer between rspamd, postfix, dovecot, unbound and nginx -RUN pip3 install jinja2 +RUN pip3 install git+https://github.com/usrpro/MailuStart.git#egg=mailustart # Image specific layers under this line RUN apk add --no-cache unbound curl bind-tools \ && curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache \ diff --git a/services/unbound/start.py b/services/unbound/start.py index 4dd5f3be..6216e783 100755 --- a/services/unbound/start.py +++ b/services/unbound/start.py @@ -1,17 +1,12 @@ #!/usr/bin/python3 -import jinja2 import os import logging as log import sys +from mailustart import convert 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("/unbound.conf", "/etc/unbound/unbound.conf") os.execv("/usr/sbin/unbound", ["-c /etc/unbound/unbound.conf"]) diff --git a/setup/flavors/compose/docker-compose.yml b/setup/flavors/compose/docker-compose.yml index 67408bee..a1d985e4 100644 --- a/setup/flavors/compose/docker-compose.yml +++ b/setup/flavors/compose/docker-compose.yml @@ -3,7 +3,7 @@ # Please read the documentation before attempting any change. # Generated for {{ flavor }} flavor -version: '3.6' +version: '2.2' services: @@ -160,8 +160,14 @@ services: networks: default: + {% if ipv6_enabled %} + enable_ipv6: true + {% endif %} driver: bridge ipam: driver: default config: - subnet: {{ subnet }} + {% if ipv6_enabled %} + - subnet: {{ subnet6 }} + {% endif %} diff --git a/setup/flavors/compose/mailu.env b/setup/flavors/compose/mailu.env index 5ccd8ace..bdd27853 100644 --- a/setup/flavors/compose/mailu.env +++ b/setup/flavors/compose/mailu.env @@ -27,6 +27,9 @@ SECRET_KEY={{ secret(16) }} # Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!) SUBNET={{ subnet }} +{% if ipv6_enabled %} +SUBNET6={{ subnet6 }} +{% endif %} # Main mail domain DOMAIN={{ domain }} @@ -116,7 +119,11 @@ WEBROOT_REDIRECT=/webmail WEB_ADMIN={{ admin_path }} # Path to the webmail if enabled +{% if webmail_type != 'none' and webmail_path == '' %} +WEB_WEBMAIL=/ +{% else %} WEB_WEBMAIL={{ webmail_path }} +{% endif %} # Website name SITENAME={{ site_name }} diff --git a/setup/flavors/compose/setup.html b/setup/flavors/compose/setup.html index 46369577..b29e726a 100644 --- a/setup/flavors/compose/setup.html +++ b/setup/flavors/compose/setup.html @@ -44,8 +44,13 @@ Before you can use Mailu, you must create the primary administrator user account one of the hostnames {{ hostnames.split(',')[0] }}{{ admin_path }}. {% else %} -http://127.0.0.1:8080 (only directly from the host running docker). +http://127.0.0.1:8080/ui (only directly from the host running docker). +If you run mailu on a remote server, and wish to access the admin interface via a SSH tunnel, you can create a port-forward from your local machine to your server like +
ssh -L 127.0.0.1:8080:127.0.0.1:8080 <user>@<server>
+
+And access the above URL from your local machine. +
{% endif %} -And choose the "Update password" option in the left menu. +Also, choose the "Update password" option in the left menu.

{% endcall %} diff --git a/setup/flavors/stack/setup.html b/setup/flavors/stack/setup.html index 81470b5b..9d51a949 100644 --- a/setup/flavors/stack/setup.html +++ b/setup/flavors/stack/setup.html @@ -50,11 +50,16 @@ Before you can use Mailu, you must create the primary administrator user account

Login to the admin interface to change the password for a safe one, at {% if admin_enabled %} -one of the hostnames +one of the hostnames {{ hostnames.split(',')[0] }}{{ admin_path }}. {% else %} -http://127.0.0.1:8080 (only directly from the host running docker). +http://127.0.0.1:8080/ui (only directly from the host running docker). +If you run mailu on a remote server, and wish to access the admin interface via a SSH tunnel, you can create a port-forward from your local machine to your server like +

ssh -L 127.0.0.1:8080:127.0.0.1:8080 <user>@<server>
+
+And access the above URL from your local machine. +
{% endif %} -And choose the "Update password" option in the left menu. +Also, choose the "Update password" option in the left menu.

{% endcall %} diff --git a/setup/server.py b/setup/server.py index 556d4b3a..4dfd49ae 100644 --- a/setup/server.py +++ b/setup/server.py @@ -9,6 +9,7 @@ import string import random import ipaddress import hashlib +import time version = os.getenv("this_version", "master") @@ -33,6 +34,17 @@ def secret(length=16): for _ in range(length) ) +#Original copied from https://github.com/andrewlkho/ulagen +def random_ipv6_subnet(): + eui64 = uuid.getnode() >> 24 << 48 | 0xfffe000000 | uuid.getnode() & 0xffffff + eui64_canon = "-".join([format(eui64, "02X")[i:i+2] for i in range(0, 18, 2)]) + + h = hashlib.sha1() + h.update((eui64_canon + str(time.time() - time.mktime((1900, 1, 1, 0, 0, 0, 0, 1, -1)))).encode('utf-8')) + globalid = h.hexdigest()[0:10] + + prefix = ":".join(("fd" + globalid[0:2], globalid[2:6], globalid[6:10])) + return prefix def build_app(path): @@ -69,8 +81,9 @@ def build_app(path): @root_bp.route("/submit_flavor", methods=["POST"]) def submit_flavor(): data = flask.request.form.copy() + subnet6 = random_ipv6_subnet() steps = sorted(os.listdir(os.path.join(path, "templates", "steps", data["flavor"]))) - return flask.render_template('wizard.html', flavor=data["flavor"], steps=steps) + return flask.render_template('wizard.html', flavor=data["flavor"], steps=steps, subnet6=subnet6) @prefix_bp.route("/submit", methods=["POST"]) @root_bp.route("/submit", methods=["POST"]) diff --git a/setup/static/render.js b/setup/static/render.js index 23afcbec..e501fffb 100644 --- a/setup/static/render.js +++ b/setup/static/render.js @@ -86,3 +86,16 @@ $(document).ready(function() { } }); }); + +$(document).ready(function() { + if ($('#enable_ipv6').prop('checked')) { + $("#ipv6").show(); + } + $("#enable_ipv6").change(function() { + if ($(this).is(":checked")) { + $("#ipv6").show(); + } else { + $("#ipv6").hide(); + } + }); +}); diff --git a/setup/templates/steps/compose/03_expose.html b/setup/templates/steps/compose/03_expose.html index 837b7bba..c909fc9b 100644 --- a/setup/templates/steps/compose/03_expose.html +++ b/setup/templates/steps/compose/03_expose.html @@ -18,13 +18,26 @@ avoid generic all-interfaces addresses like 0.0.0.0 or :: + + -
+
+ +
+ +

The unbound resolver enables Mailu to do DNSsec verification, DNS root lookups and caching. This also helps the antispam service not to get blocked by the public or ISP DNS servers.

@@ -34,12 +47,6 @@ avoid generic all-interfaces addresses like 0.0.0.0 or ::
-

-
- - -

You server will be available under a main hostname but may expose multiple public hostnames. Every e-mail domain that points to this server must have one of the diff --git a/tests/compose/filters/mailu.env b/tests/compose/filters/mailu.env index 25c7c133..e165fee2 100644 --- a/tests/compose/filters/mailu.env +++ b/tests/compose/filters/mailu.env @@ -60,7 +60,7 @@ WEBMAIL=none WEBDAV=none # Antivirus solution (value: clamav, none) -#ANTIVIRUS=clamav +ANTIVIRUS=clamav #Antispam solution ANTISPAM=none diff --git a/webmails/rainloop/Dockerfile b/webmails/rainloop/Dockerfile index 224fe457..c20f1975 100644 --- a/webmails/rainloop/Dockerfile +++ b/webmails/rainloop/Dockerfile @@ -1,7 +1,7 @@ FROM php:7.2-apache #Shared layer between rainloop and roundcube RUN apt-get update && apt-get install -y \ - python3 curl \ + python3 curl python3-pip git \ && rm -rf /var/lib/apt/lists \ && echo "ServerSignature Off" >> /etc/apache2/apache2.conf @@ -21,6 +21,8 @@ RUN apt-get update && apt-get install -y \ && chown -R www-data: * \ && apt-get purge -y unzip \ && rm -rf /var/lib/apt/lists + + RUN pip3 install git+https://github.com/usrpro/MailuStart.git#egg=mailustart COPY include.php /var/www/html/include.php COPY php.ini /php.ini diff --git a/webmails/rainloop/start.py b/webmails/rainloop/start.py index e2b917bf..495eb376 100755 --- a/webmails/rainloop/start.py +++ b/webmails/rainloop/start.py @@ -1,21 +1,16 @@ #!/usr/bin/python3 -import jinja2 import os import shutil import logging as log import sys +from mailustart import resolve, convert 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") -os.environ["IMAP_ADDRESS"] = os.environ.get("IMAP_ADDRESS", "imap") +os.environ["FRONT_ADDRESS"] = resolve(os.environ.get("FRONT_ADDRESS", "front")) +os.environ["IMAP_ADDRESS"] = resolve(os.environ.get("IMAP_ADDRESS", "imap")) os.environ["MAX_FILESIZE"] = str(int(int(os.environ.get("MESSAGE_SIZE_LIMIT"))*0.66/1048576)) diff --git a/webmails/roundcube/Dockerfile b/webmails/roundcube/Dockerfile index 1c5d82c4..cecf009b 100644 --- a/webmails/roundcube/Dockerfile +++ b/webmails/roundcube/Dockerfile @@ -1,7 +1,7 @@ FROM php:7.2-apache #Shared layer between rainloop and roundcube RUN apt-get update && apt-get install -y \ - python3 curl \ + python3 curl python3-pip git \ && rm -rf /var/lib/apt/lists \ && echo "ServerSignature Off" >> /etc/apache2/apache2.conf @@ -23,6 +23,8 @@ RUN apt-get update && apt-get install -y \ && chown -R www-data: logs temp \ && rm -rf /var/lib/apt/lists + RUN pip3 install git+https://github.com/usrpro/MailuStart.git#egg=mailustart + COPY php.ini /php.ini COPY config.inc.php /var/www/html/config/ COPY start.py /start.py diff --git a/webmails/roundcube/start.py b/webmails/roundcube/start.py index 4effd965..c0e52883 100755 --- a/webmails/roundcube/start.py +++ b/webmails/roundcube/start.py @@ -1,17 +1,12 @@ #!/usr/bin/python3 import os -import jinja2 import logging as log import sys +from mailustart import convert 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)) convert("/php.ini", "/usr/local/etc/php/conf.d/roundcube.ini")