Merge pull request #681 from usrpro/feat-unbound-dns

Unbound DNS as optional service
master
mergify[bot] 6 years ago committed by GitHub
commit e9217b8389
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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

@ -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"])

@ -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

@ -28,6 +28,16 @@ services:
{% endfor %} {% endfor %}
volumes: volumes:
- "{{ root }}/certs:/certs" - "{{ root }}/certs:/certs"
{% if resolver_enabled %}
resolver:
image: mailu/unbound:{{ version }}
env_file: {{ env }}
restart: always
networks:
default:
ipv4_address: {{ dns }}
{% endif %}
admin: admin:
image: mailu/admin:{{ version }} image: mailu/admin:{{ version }}
@ -58,6 +68,11 @@ services:
- "{{ root }}/overrides:/overrides" - "{{ root }}/overrides:/overrides"
depends_on: depends_on:
- front - front
{% if resolver_enabled %}
- resolver
dns:
- {{ dns }}
{% endif %}
# Optional services # Optional services
{% if antispam_enabled %} {% if antispam_enabled %}
@ -70,6 +85,11 @@ services:
- "{{ root }}/overrides/rspamd:/etc/rspamd/override.d" - "{{ root }}/overrides/rspamd:/etc/rspamd/override.d"
depends_on: depends_on:
- front - front
{% if resolver_enabled %}
- resolver
dns:
- {{ dns }}
{% endif %}
{% endif %} {% endif %}
{% if antivirus_enabled %} {% if antivirus_enabled %}
@ -78,6 +98,12 @@ services:
env_file: {{ env }} env_file: {{ env }}
volumes: volumes:
- "{{ root }}/filter:/data" - "{{ root }}/filter:/data"
{% if resolver_enabled %}
depends_on:
- resolver
dns:
- {{ dns }}
{% endif %}
{% endif %} {% endif %}
{% if webdav_enabled %} {% if webdav_enabled %}
@ -92,6 +118,12 @@ services:
fetchmail: fetchmail:
image: mailu/fetchmail:{{ version }} image: mailu/fetchmail:{{ version }}
env_file: {{ env }} env_file: {{ env }}
{% if resolver_enabled %}
depends_on:
- resolver
dns:
- {{ dns }}
{% endif %}
{% endif %} {% endif %}
# Webmail # Webmail
@ -104,3 +136,13 @@ services:
depends_on: depends_on:
- imap - imap
{% endif %} {% endif %}
{% if resolver_enabled %}
networks:
default:
driver: bridge
ipam:
driver: default
config:
- subnet: {{ subnet }}
{% endif %}

@ -25,6 +25,9 @@ SECRET_KEY={{ secret(16) }}
# PUBLIC_IPV4= {{ bind4 }} (default: 127.0.0.1) # PUBLIC_IPV4= {{ bind4 }} (default: 127.0.0.1)
# PUBLIC_IPV6= {{ bind6 }} (default: ::1) # PUBLIC_IPV6= {{ bind6 }} (default: ::1)
# Subnet
SUBNET={{ subnet }}
# Main mail domain # Main mail domain
DOMAIN={{ domain }} DOMAIN={{ domain }}

@ -28,6 +28,15 @@ services:
- "{{ root }}/certs:/certs" - "{{ root }}/certs:/certs"
deploy: deploy:
replicas: 1 replicas: 1
{% if resolver_enabled %}
resolver:
image: mailu/unbound:{{ version }}
env_file: {{ env }}
networks:
default:
ipv4_address: {{ dns }}
{% endif %}
admin: admin:
image: mailu/admin:{{ version }} image: mailu/admin:{{ version }}
@ -63,6 +72,10 @@ services:
- "{{ root }}/overrides:/overrides" - "{{ root }}/overrides:/overrides"
deploy: deploy:
replicas: 1 replicas: 1
{% if resolver_enabled %}
dns:
- {{ dns }}
{% endif %}
# Optional services # Optional services
{% if antispam_enabled %} {% if antispam_enabled %}
@ -77,6 +90,10 @@ services:
- "{{ root }}/overrides/rspamd:/etc/rspamd/override.d" - "{{ root }}/overrides/rspamd:/etc/rspamd/override.d"
deploy: deploy:
replicas: 1 replicas: 1
{% if resolver_enabled %}
dns:
- {{ dns }}
{% endif %}
{% endif %} {% endif %}
{% if antivirus_enabled %} {% if antivirus_enabled %}
@ -87,6 +104,10 @@ services:
- "{{ root }}/filter:/data" - "{{ root }}/filter:/data"
deploy: deploy:
replicas: 1 replicas: 1
{% if resolver_enabled %}
dns:
- {{ dns }}
{% endif %}
{% endif %} {% endif %}
{% if webdav_enabled %} {% if webdav_enabled %}
@ -107,6 +128,10 @@ services:
- "{{ root }}/data:/data" - "{{ root }}/data:/data"
deploy: deploy:
replicas: 1 replicas: 1
{% if resolver_enabled %}
dns:
- {{ dns }}
{% endif %}
{% endif %} {% endif %}
{% if webmail_type != 'none' %} {% if webmail_type != 'none' %}

@ -7,6 +7,7 @@ import jinja2
import uuid import uuid
import string import string
import random import random
import ipaddress
app = flask.Flask(__name__) app = flask.Flask(__name__)
@ -75,6 +76,7 @@ def build_app(path):
def submit(): def submit():
data = flask.request.form.copy() data = flask.request.form.copy()
data['uid'] = str(uuid.uuid4()) data['uid'] = str(uuid.uuid4())
data['dns'] = str(ipaddress.IPv4Network(data['subnet'])[-2])
db.set(data['uid'], json.dumps(data)) db.set(data['uid'], json.dumps(data))
return flask.redirect(flask.url_for('.setup', uid=data['uid'])) return flask.redirect(flask.url_for('.setup', uid=data['uid']))

@ -26,6 +26,19 @@ avoid generic all-interfaces addresses like <code>0.0.0.0</code> or <code>::</co
pattern="^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"> pattern="^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$">
</div> </div>
<div class="form-check form-check-inline">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" name="resolver_enabled" value="true">
Enable unbound resolver
</label>
</div>
<div class="form-group">
<label>Subnet</label>
<input class="form-control" type="text" name="subnet" required pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))$"
value="192.168.0.0/24">
</div>
<p>You server will be available under a main hostname but may expose multiple public <p>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. Every e-mail domain that points to this server must have one of the
hostnames in its <code>MX</code> record. Hostnames must be coma-separated.</p> hostnames in its <code>MX</code> record. Hostnames must be coma-separated.</p>

@ -3,9 +3,17 @@
and let users access their mailboxes. Mailu has some flexibility in the way and let users access their mailboxes. Mailu has some flexibility in the way
you expose it to the world.</p> you expose it to the world.</p>
<div class="form-check form-check-inline">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" name="resolver_enabled" value="true">
Enable unbound resolver
</label>
</div>
<div class="form-group"> <div class="form-group">
<label>Subnet</label> <label>Subnet</label>
<input class="form-control" type="text" name="subnet" required pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))$"> <input class="form-control" type="text" name="subnet" required pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))$"
value="192.168.0.0/24">
</div> </div>
<p>You server will be available under a main hostname but may expose multiple public <p>You server will be available under a main hostname but may expose multiple public

@ -6,6 +6,10 @@ services:
image: ${DOCKER_ORG:-mailu}/nginx:${VERSION:-local} image: ${DOCKER_ORG:-mailu}/nginx:${VERSION:-local}
build: ../core/nginx build: ../core/nginx
resolver:
image: ${DOCKER_ORG:-mailu}/unbound:${VERSION:-local}
build: ../services/unbound
imap: imap:
image: ${DOCKER_ORG:-mailu}/dovecot:${VERSION:-local} image: ${DOCKER_ORG:-mailu}/dovecot:${VERSION:-local}
build: ../core/dovecot build: ../core/dovecot

Loading…
Cancel
Save