diff --git a/services/unbound/Dockerfile b/services/unbound/Dockerfile new file mode 100644 index 00000000..1b84855c --- /dev/null +++ b/services/unbound/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3-alpine + +RUN apk add --no-cache unbound curl bind-tools \ + && pip3 install jinja2 \ + && curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache \ + && chown root:unbound /etc/unbound \ + && chmod 775 /etc/unbound \ + && apk del --no-cache curl \ + && /usr/sbin/unbound-anchor -a /etc/unbound/trusted-key.key | true + +COPY start.py /start.py +COPY unbound.conf /unbound.conf + +EXPOSE 53/udp 53/tcp + +CMD /start.py + +HEALTHCHECK CMD dig @127.0.0.1 || exit 1 diff --git a/services/unbound/start.py b/services/unbound/start.py new file mode 100755 index 00000000..82e017f7 --- /dev/null +++ b/services/unbound/start.py @@ -0,0 +1,9 @@ +#!/usr/local/bin/python3 + +import jinja2 +import os + +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/services/unbound/unbound.conf b/services/unbound/unbound.conf new file mode 100644 index 00000000..d54cbfbc --- /dev/null +++ b/services/unbound/unbound.conf @@ -0,0 +1,19 @@ +server: + verbosity: 1 + interface: 0.0.0.0 + interface: ::0 + logfile: /dev/stdout + do-ip4: yes + do-ip6: yes + do-udp: yes + do-tcp: yes + do-daemonize: no + access-control: {{ SUBNET }} allow + directory: "/etc/unbound" + username: root + auto-trust-anchor-file: trusted-key.key + root-hints: "/etc/unbound/root.hints" + hide-identity: yes + hide-version: yes + max-udp-size: 4096 + msg-buffer-size: 65552 diff --git a/setup/flavors/compose/docker-compose.yml b/setup/flavors/compose/docker-compose.yml index b01bb8fd..81b6bcb2 100644 --- a/setup/flavors/compose/docker-compose.yml +++ b/setup/flavors/compose/docker-compose.yml @@ -28,6 +28,16 @@ services: {% endfor %} volumes: - "{{ root }}/certs:/certs" + + {% if resolver_enabled %} + resolver: + image: mailu/unbound:{{ version }} + env_file: {{ env }} + restart: always + networks: + default: + ipv4_address: {{ dns }} + {% endif %} admin: image: mailu/admin:{{ version }} @@ -58,6 +68,11 @@ services: - "{{ root }}/overrides:/overrides" depends_on: - front + {% if resolver_enabled %} + - resolver + dns: + - {{ dns }} + {% endif %} # Optional services {% if antispam_enabled %} @@ -70,6 +85,11 @@ services: - "{{ root }}/overrides/rspamd:/etc/rspamd/override.d" depends_on: - front + {% if resolver_enabled %} + - resolver + dns: + - {{ dns }} + {% endif %} {% endif %} {% if antivirus_enabled %} @@ -78,6 +98,12 @@ services: env_file: {{ env }} volumes: - "{{ root }}/filter:/data" + {% if resolver_enabled %} + depends_on: + - resolver + dns: + - {{ dns }} + {% endif %} {% endif %} {% if webdav_enabled %} @@ -92,6 +118,12 @@ services: fetchmail: image: mailu/fetchmail:{{ version }} env_file: {{ env }} + {% if resolver_enabled %} + depends_on: + - resolver + dns: + - {{ dns }} + {% endif %} {% endif %} # Webmail @@ -104,3 +136,13 @@ services: depends_on: - imap {% endif %} + +{% if resolver_enabled %} +networks: + default: + driver: bridge + ipam: + driver: default + config: + - subnet: {{ subnet }} +{% endif %} diff --git a/setup/flavors/compose/mailu.env b/setup/flavors/compose/mailu.env index 9fc1197d..4a14de63 100644 --- a/setup/flavors/compose/mailu.env +++ b/setup/flavors/compose/mailu.env @@ -25,6 +25,9 @@ SECRET_KEY={{ secret(16) }} # PUBLIC_IPV4= {{ bind4 }} (default: 127.0.0.1) # PUBLIC_IPV6= {{ bind6 }} (default: ::1) +# Subnet +SUBNET={{ subnet }} + # Main mail domain DOMAIN={{ domain }} diff --git a/setup/flavors/stack/docker-compose.yml b/setup/flavors/stack/docker-compose.yml index f27b661f..b9537e94 100644 --- a/setup/flavors/stack/docker-compose.yml +++ b/setup/flavors/stack/docker-compose.yml @@ -28,6 +28,15 @@ services: - "{{ root }}/certs:/certs" deploy: replicas: 1 + + {% if resolver_enabled %} + resolver: + image: mailu/unbound:{{ version }} + env_file: {{ env }} + networks: + default: + ipv4_address: {{ dns }} + {% endif %} admin: image: mailu/admin:{{ version }} @@ -63,6 +72,10 @@ services: - "{{ root }}/overrides:/overrides" deploy: replicas: 1 + {% if resolver_enabled %} + dns: + - {{ dns }} + {% endif %} # Optional services {% if antispam_enabled %} @@ -77,6 +90,10 @@ services: - "{{ root }}/overrides/rspamd:/etc/rspamd/override.d" deploy: replicas: 1 + {% if resolver_enabled %} + dns: + - {{ dns }} + {% endif %} {% endif %} {% if antivirus_enabled %} @@ -87,6 +104,10 @@ services: - "{{ root }}/filter:/data" deploy: replicas: 1 + {% if resolver_enabled %} + dns: + - {{ dns }} + {% endif %} {% endif %} {% if webdav_enabled %} @@ -107,6 +128,10 @@ services: - "{{ root }}/data:/data" deploy: replicas: 1 + {% if resolver_enabled %} + dns: + - {{ dns }} + {% endif %} {% endif %} {% if webmail_type != 'none' %} diff --git a/setup/server.py b/setup/server.py index bfe5ef15..6f60c3c0 100644 --- a/setup/server.py +++ b/setup/server.py @@ -7,6 +7,7 @@ import jinja2 import uuid import string import random +import ipaddress app = flask.Flask(__name__) @@ -75,6 +76,7 @@ def build_app(path): def submit(): data = flask.request.form.copy() data['uid'] = str(uuid.uuid4()) + data['dns'] = str(ipaddress.IPv4Network(data['subnet'])[-2]) db.set(data['uid'], json.dumps(data)) return flask.redirect(flask.url_for('.setup', uid=data['uid'])) diff --git a/setup/templates/steps/compose/03_expose.html b/setup/templates/steps/compose/03_expose.html index df121c7d..783c2037 100644 --- a/setup/templates/steps/compose/03_expose.html +++ b/setup/templates/steps/compose/03_expose.html @@ -26,6 +26,19 @@ 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 hostnames in its MX record. Hostnames must be coma-separated.

diff --git a/setup/templates/steps/stack/03_expose.html b/setup/templates/steps/stack/03_expose.html index a9cffc1c..d47390be 100644 --- a/setup/templates/steps/stack/03_expose.html +++ b/setup/templates/steps/stack/03_expose.html @@ -3,9 +3,17 @@ and let users access their mailboxes. Mailu has some flexibility in the way you expose it to the world.

+
+ +
+
- +

You server will be available under a main hostname but may expose multiple public diff --git a/tests/build.yml b/tests/build.yml index 5f360ece..ed5b75fe 100644 --- a/tests/build.yml +++ b/tests/build.yml @@ -6,6 +6,10 @@ services: image: ${DOCKER_ORG:-mailu}/nginx:${VERSION:-local} build: ../core/nginx + resolver: + image: ${DOCKER_ORG:-mailu}/unbound:${VERSION:-local} + build: ../services/unbound + imap: image: ${DOCKER_ORG:-mailu}/dovecot:${VERSION:-local} build: ../core/dovecot