Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70251ea215 | |||
| 87ac211e03 | |||
| eb8380fa5b | |||
| a6491fd87f | |||
| 4ab9ba97ca | |||
| e2aa247c2d | |||
|
|
f75b2fb631 | ||
|
|
0739224fc4 | ||
|
|
fc83a7d829 | ||
|
|
2b1ed25148 | ||
|
|
a4551870aa | ||
|
|
6869b3096b | ||
|
|
1c39af464d | ||
|
|
f02a183694 | ||
|
|
cd80fda12e | ||
|
|
8c7138d69b | ||
|
|
28f8e69ebb | ||
|
|
09934c460c | ||
|
|
18f4f0bed8 | ||
|
|
e2ce62cbd0 | ||
|
|
a36fc369f9 | ||
|
|
c91953b5df | ||
|
|
790e32cfb8 | ||
|
|
bbcf3edb10 | ||
|
|
e4b63cb043 | ||
|
|
a7a27625cc | ||
|
|
57188345f0 | ||
|
|
4dd66e4964 | ||
|
|
cfff8953e5 | ||
|
|
9bf6f84c05 | ||
|
|
cd0bc5562a | ||
|
|
eb4e65be64 | ||
|
|
5d26c9a097 | ||
|
|
f0e56bb80e | ||
|
|
465ccda95f | ||
|
|
e14fcd1b8a | ||
|
|
4d62861b78 | ||
|
|
550470d604 | ||
|
|
43b64229d1 | ||
|
|
ad6bc4060d | ||
|
|
53da190863 | ||
|
|
6674af8881 | ||
|
|
c24cabc305 | ||
|
|
ce4db065c6 | ||
|
|
3e6f0d7842 | ||
|
|
11f145da8c | ||
|
|
6a3236200d | ||
|
|
e17c587b78 | ||
|
|
9caab592ed | ||
|
|
6afde67525 | ||
|
|
564f5ad070 | ||
|
|
fb74adf1dc | ||
|
|
91bb76ecd3 | ||
|
|
f7dfee4c60 | ||
|
|
dbe0cfe129 | ||
|
|
fdeb302eff | ||
|
|
8eeb9b22b4 | ||
|
|
b362940388 | ||
|
|
ce01389669 | ||
|
|
37ba915e7b | ||
|
|
6c47dfb997 | ||
|
|
4b6e48d826 | ||
|
|
2f7dbe92bf | ||
|
|
0ff9d3c953 | ||
|
|
cce0de24e9 | ||
|
|
2b144bc574 | ||
|
|
449af8eb0b | ||
|
|
e03097b505 | ||
|
|
c157088385 | ||
|
|
4ce3a04287 | ||
|
|
8854d76bcc | ||
|
|
13ffb85912 | ||
|
|
f5b78ffff0 | ||
|
|
46dc9b0b16 | ||
|
|
820f6dee82 | ||
|
|
92c21613ff | ||
|
|
017a2611cc |
27
.travis.yml
27
.travis.yml
@@ -1,8 +1,29 @@
|
|||||||
|
sudo: required
|
||||||
|
services: docker
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- docker-ce
|
||||||
|
|
||||||
|
env:
|
||||||
|
- MAILU_VERSION=$TRAVIS_BRANCH
|
||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
- "3.6"
|
- "3.6"
|
||||||
install:
|
install:
|
||||||
- pip install -r docs/requirements.txt
|
- sudo curl -L https://github.com/docker/compose/releases/download/1.23.0-rc3/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
|
||||||
|
- sudo chmod +x /usr/local/bin/docker-compose
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- docker-compose -v
|
||||||
|
- docker-compose -f tests/build.yml build
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- sphinx-versioning build -b -B 1.5 -r 1.5 -w '^[0-9.]*$' -w master -W '^$' docs/ build/
|
- /bin/true
|
||||||
- python docs/conf.py build $DEPLOY_HOST $DEPLOY_USERNAME $DEPLOY_PASSWORD $DEPLOY_REMOTEDIR
|
|
||||||
|
deploy:
|
||||||
|
provider: script
|
||||||
|
script: bash tests/deploy.sh
|
||||||
|
on:
|
||||||
|
all_branches: true
|
||||||
|
condition: -n $DOCKER_UN
|
||||||
|
|||||||
26
build.sh
Executable file
26
build.sh
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
TAG="1.5-$(date +%Y%m%d)"
|
||||||
|
|
||||||
|
build() {
|
||||||
|
image=$1
|
||||||
|
echo "[INFO] Building ${image}"
|
||||||
|
docker build -t ${image} .
|
||||||
|
docker push ${image}
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
find . -name Dockerfile | while read i; do
|
||||||
|
dir=$(dirname $i)
|
||||||
|
pushd ${dir} >/dev/null
|
||||||
|
image="genunix/mailu-$(basename $dir):${TAG}"
|
||||||
|
build ${image}
|
||||||
|
popd >/dev/null
|
||||||
|
done
|
||||||
|
else
|
||||||
|
dir=$(basename $1)
|
||||||
|
image="genunix/mailu-$(basename $dir):${TAG}"
|
||||||
|
pushd "$1" >/dev/null
|
||||||
|
build "$image"
|
||||||
|
popd >/dev/null
|
||||||
|
fi
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
FROM python:3-alpine
|
FROM python:3-alpine3.13
|
||||||
|
|
||||||
RUN mkdir -p /app
|
RUN mkdir -p /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY requirements-prod.txt requirements.txt
|
COPY requirements-prod.txt requirements.txt
|
||||||
RUN apk --update add --virtual build-dep openssl-dev libffi-dev python-dev build-base \
|
RUN apk add --no-cache openssl \
|
||||||
|
&& apk add --no-cache --virtual build-dep openssl-dev libffi-dev python3-dev build-base \
|
||||||
&& pip install -r requirements.txt \
|
&& pip install -r requirements.txt \
|
||||||
&& apk del build-dep
|
&& apk del build-dep
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import docker
|
|||||||
import socket
|
import socket
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from werkzeug.contrib import fixers
|
||||||
|
|
||||||
# Create application
|
# Create application
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
@@ -115,4 +117,4 @@ class PrefixMiddleware(object):
|
|||||||
environ['SCRIPT_NAME'] = prefix
|
environ['SCRIPT_NAME'] = prefix
|
||||||
return self.app(environ, start_response)
|
return self.app(environ, start_response)
|
||||||
|
|
||||||
app.wsgi_app = PrefixMiddleware(app.wsgi_app)
|
app.wsgi_app = PrefixMiddleware(fixers.ProxyFix(app.wsgi_app))
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
FROM alpine:edge
|
FROM alpine:3.13
|
||||||
|
|
||||||
RUN echo "@testing http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories \
|
RUN echo "@testing http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories \
|
||||||
&& apk add --no-cache \
|
&& apk add --no-cache \
|
||||||
dovecot dovecot-sqlite dovecot-pigeonhole-plugin dovecot-pigeonhole-plugin-extdata \
|
dovecot dovecot-sqlite dovecot-pigeonhole-plugin \
|
||||||
rspamd-client@testing python py-jinja2
|
rspamd-client python3 py3-jinja2
|
||||||
|
|
||||||
COPY conf /conf
|
COPY conf /conf
|
||||||
COPY sieve /var/lib/dovecot
|
COPY sieve /var/lib/dovecot
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ namespace inbox {
|
|||||||
###############
|
###############
|
||||||
auth_mechanisms = plain login
|
auth_mechanisms = plain login
|
||||||
disable_plaintext_auth = no
|
disable_plaintext_auth = no
|
||||||
|
ssl_protocols = !SSLv3
|
||||||
|
|
||||||
passdb {
|
passdb {
|
||||||
driver = sql
|
driver = sql
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
require "vnd.dovecot.execute";
|
require ["vnd.dovecot.execute", "copy", "imapsieve", "environment", "variables"];
|
||||||
|
|
||||||
|
if environment :matches "imap.mailbox" "*" {
|
||||||
|
set "mailbox" "${1}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if string "${mailbox}" "Trash" {
|
||||||
|
stop;
|
||||||
|
}
|
||||||
|
|
||||||
execute :pipe "mailtrain" "ham";
|
execute :pipe "mailtrain" "ham";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python3
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
import os
|
import os
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
FROM alpine:edge
|
FROM alpine:3.13
|
||||||
|
|
||||||
RUN apk add --no-cache nginx nginx-mod-mail python py-jinja2 certbot openssl
|
RUN apk add --no-cache nginx nginx-mod-mail python3 py3-jinja2 certbot openssl
|
||||||
|
# added to fix #522
|
||||||
|
RUN apk add --no-cache py-requests-toolbelt py-pip
|
||||||
|
RUN pip install "idna<2.7"
|
||||||
|
|
||||||
COPY conf /conf
|
COPY conf /conf
|
||||||
COPY *.py /
|
COPY *.py /
|
||||||
|
|||||||
13
core/nginx/conf/dhparam.pem
Normal file
13
core/nginx/conf/dhparam.pem
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
-----BEGIN DH PARAMETERS-----
|
||||||
|
MIICCAKCAgEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
|
||||||
|
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
|
||||||
|
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
|
||||||
|
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
|
||||||
|
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
|
||||||
|
ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3
|
||||||
|
7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32
|
||||||
|
nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZp4e
|
||||||
|
8W5vUsMWTfT7eTDp5OWIV7asfV9C1p9tGHdjzx1VA0AEh/VbpX4xzHpxNciG77Qx
|
||||||
|
iu1qHgEtnmgyqQdgCpGBMMRtx3j5ca0AOAkpmaMzy4t6Gh25PXFAADwqTs6p+Y0K
|
||||||
|
zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eZV9q//////////8CAQI=
|
||||||
|
-----END DH PARAMETERS-----
|
||||||
@@ -20,6 +20,12 @@ http {
|
|||||||
absolute_redirect off;
|
absolute_redirect off;
|
||||||
resolver {{ RESOLVER }} valid=30s;
|
resolver {{ RESOLVER }} valid=30s;
|
||||||
|
|
||||||
|
# Header maps
|
||||||
|
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
|
||||||
|
default $http_x_forwarded_proto;
|
||||||
|
'' $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
# Main HTTP server
|
# Main HTTP server
|
||||||
server {
|
server {
|
||||||
# Variables for proxifying
|
# Variables for proxifying
|
||||||
@@ -65,13 +71,14 @@ http {
|
|||||||
# Actual logic
|
# Actual logic
|
||||||
{% if WEBMAIL != 'none' %}
|
{% if WEBMAIL != 'none' %}
|
||||||
location / {
|
location / {
|
||||||
return 301 $scheme://$host/webmail/;
|
return 301 {{ WEB_WEBMAIL }};
|
||||||
}
|
}
|
||||||
|
|
||||||
location {{ WEB_WEBMAIL }} {
|
location {{ WEB_WEBMAIL }} {
|
||||||
rewrite ^({{ WEB_WEBMAIL }})$ $1/ permanent;
|
rewrite ^({{ WEB_WEBMAIL }})$ $1/ permanent;
|
||||||
rewrite ^{{ WEB_WEBMAIL }}/(.*) /$1 break;
|
rewrite ^{{ WEB_WEBMAIL }}/(.*) /$1 break;
|
||||||
proxy_set_header Host $host;
|
include /etc/nginx/proxy.conf;
|
||||||
|
client_max_body_size 30M;
|
||||||
proxy_pass http://$webmail;
|
proxy_pass http://$webmail;
|
||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -83,8 +90,8 @@ http {
|
|||||||
|
|
||||||
location ~ {{ WEB_ADMIN }}/(ui|static) {
|
location ~ {{ WEB_ADMIN }}/(ui|static) {
|
||||||
rewrite ^{{ WEB_ADMIN }}/(.*) /$1 break;
|
rewrite ^{{ WEB_ADMIN }}/(.*) /$1 break;
|
||||||
|
include /etc/nginx/proxy.conf;
|
||||||
proxy_set_header X-Forwarded-Prefix {{ WEB_ADMIN }};
|
proxy_set_header X-Forwarded-Prefix {{ WEB_ADMIN }};
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_pass http://$admin;
|
proxy_pass http://$admin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,9 +109,15 @@ http {
|
|||||||
rewrite ^/webdav/(.*) /$1 break;
|
rewrite ^/webdav/(.*) /$1 break;
|
||||||
auth_request /internal/auth/basic;
|
auth_request /internal/auth/basic;
|
||||||
auth_request_set $user $upstream_http_x_user;
|
auth_request_set $user $upstream_http_x_user;
|
||||||
|
include /etc/nginx/proxy.conf;
|
||||||
proxy_set_header X-Remote-User $user;
|
proxy_set_header X-Remote-User $user;
|
||||||
|
proxy_set_header X-Script-Name /webdav;
|
||||||
proxy_pass http://$webdav;
|
proxy_pass http://$webdav;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location ~ ^/.well-known/(carddav|caldav) {
|
||||||
|
return 301 /webdav/;
|
||||||
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
5
core/nginx/conf/proxy.conf
Normal file
5
core/nginx/conf/proxy.conf
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Default proxy setup
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
||||||
@@ -4,4 +4,4 @@ ssl_prefer_server_ciphers on;
|
|||||||
ssl_session_timeout 10m;
|
ssl_session_timeout 10m;
|
||||||
ssl_certificate {{ TLS[0] }};
|
ssl_certificate {{ TLS[0] }};
|
||||||
ssl_certificate_key {{ TLS[1] }};
|
ssl_certificate_key {{ TLS[1] }};
|
||||||
ssl_dhparam /certs/dhparam.pem;
|
ssl_dhparam /conf/dhparam.pem;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python3
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
import os
|
import os
|
||||||
@@ -29,5 +29,7 @@ if args["TLS"] and not all(os.path.exists(file_path) for file_path in args["TLS"
|
|||||||
|
|
||||||
# Build final configuration paths
|
# Build final configuration paths
|
||||||
convert("/conf/tls.conf", "/etc/nginx/tls.conf", args)
|
convert("/conf/tls.conf", "/etc/nginx/tls.conf", args)
|
||||||
|
convert("/conf/proxy.conf", "/etc/nginx/proxy.conf", args)
|
||||||
convert("/conf/nginx.conf", "/etc/nginx/nginx.conf", args)
|
convert("/conf/nginx.conf", "/etc/nginx/nginx.conf", args)
|
||||||
os.system("nginx -s reload")
|
if os.path.exists("/var/run/nginx.pid"):
|
||||||
|
os.system("nginx -s reload")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python3
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python3
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
# Check if a stale pid file exists
|
||||||
# Actual startup script
|
if os.path.exists("/var/run/nginx.pid"):
|
||||||
if not os.path.exists("/certs/dhparam.pem") and os.environ["TLS_FLAVOR"] != "notls":
|
os.remove("/var/run/nginx.pid")
|
||||||
os.system("openssl dhparam -out /certs/dhparam.pem 4096")
|
|
||||||
|
|
||||||
if os.environ["TLS_FLAVOR"] == "letsencrypt":
|
if os.environ["TLS_FLAVOR"] == "letsencrypt":
|
||||||
subprocess.Popen(["/letsencrypt.py"])
|
subprocess.Popen(["/letsencrypt.py"])
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# This is an idle image to dynamically replace any component if disabled.
|
# This is an idle image to dynamically replace any component if disabled.
|
||||||
|
|
||||||
FROM alpine
|
FROM alpine:3.13
|
||||||
|
|
||||||
CMD sleep 1000000d
|
CMD sleep 1000000d
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
FROM alpine
|
FROM alpine:3.13
|
||||||
|
|
||||||
RUN apk add --no-cache postfix postfix-sqlite postfix-pcre rsyslog python py-jinja2
|
RUN apk add --no-cache bash postfix postfix-sqlite postfix-pcre rsyslog
|
||||||
|
|
||||||
COPY conf /conf
|
COPY conf /etc/postfix
|
||||||
COPY start.py /start.py
|
COPY rsyslog.conf /etc/rsyslog.conf
|
||||||
|
|
||||||
CMD /start.py
|
COPY start.sh /start.sh
|
||||||
|
|
||||||
|
CMD ["/start.sh"]
|
||||||
|
|||||||
@@ -4,12 +4,9 @@
|
|||||||
|
|
||||||
# Main domain and hostname
|
# Main domain and hostname
|
||||||
mydomain = {{ DOMAIN }}
|
mydomain = {{ DOMAIN }}
|
||||||
myhostname = {{ HOSTNAMES.split(",")[0] }}
|
myhostname = {{ HOSTNAME }}
|
||||||
myorigin = $mydomain
|
myorigin = $mydomain
|
||||||
|
|
||||||
# Queue location
|
|
||||||
queue_directory = /queue
|
|
||||||
|
|
||||||
# Message size limit
|
# Message size limit
|
||||||
message_size_limit = {{ MESSAGE_SIZE_LIMIT }}
|
message_size_limit = {{ MESSAGE_SIZE_LIMIT }}
|
||||||
|
|
||||||
@@ -28,12 +25,6 @@ mydestination =
|
|||||||
# Relayhost if any is configured
|
# Relayhost if any is configured
|
||||||
relayhost = {{ RELAYHOST }}
|
relayhost = {{ RELAYHOST }}
|
||||||
|
|
||||||
# Recipient delimiter for extended addresses
|
|
||||||
recipient_delimiter = {{ RECIPIENT_DELIMITER }}
|
|
||||||
|
|
||||||
# Only the front server is allowed to perform xclient
|
|
||||||
smtpd_authorized_xclient_hosts={{ FRONT_ADDRESS }}
|
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# TLS
|
# TLS
|
||||||
###############
|
###############
|
||||||
@@ -41,14 +32,47 @@ smtpd_authorized_xclient_hosts={{ FRONT_ADDRESS }}
|
|||||||
# General TLS configuration
|
# General TLS configuration
|
||||||
tls_high_cipherlist = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA
|
tls_high_cipherlist = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA
|
||||||
tls_preempt_cipherlist = yes
|
tls_preempt_cipherlist = yes
|
||||||
tls_ssl_options = NO_COMPRESSION
|
|
||||||
|
# Only one key/certificate pair is used, SNI not being supported by all
|
||||||
|
# services and not a strong requirement. Also, TLS is enforced for submission
|
||||||
|
# and smtps in master.cf.
|
||||||
|
smtpd_tls_security_level = may
|
||||||
|
smtpd_tls_cert_file=/certs/cert.pem
|
||||||
|
smtpd_tls_key_file=/certs/key.pem
|
||||||
|
smtpd_tls_session_cache_database = lmdb:${data_directory}/smtpd_scache
|
||||||
|
|
||||||
|
# Server-side TLS is hardened, it should be up to the client to update his or
|
||||||
|
# her TLS stack in order to connect to the mail server. Hardening is based on
|
||||||
|
# https://bettercrypto.org/static/applied-crypto-hardening.pdf
|
||||||
|
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3
|
||||||
|
smtpd_tls_protocols = !SSLv2, !SSLv3
|
||||||
|
smtpd_tls_ciphers = high
|
||||||
|
smtpd_tls_mandatory_ciphers = high
|
||||||
|
|
||||||
|
|
||||||
# Outgoing TLS is more flexible because 1. not all receiving servers will
|
# Outgoing TLS is more flexible because 1. not all receiving servers will
|
||||||
# support TLS, 2. not all will have and up-to-date TLS stack.
|
# support TLS, 2. not all will have and up-to-date TLS stack.
|
||||||
smtp_tls_security_level = may
|
smtp_tls_security_level = may
|
||||||
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3
|
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3
|
||||||
smtp_tls_protocols =!SSLv2,!SSLv3
|
smtp_tls_protocols =!SSLv2,!SSLv3
|
||||||
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
|
smtp_tls_session_cache_database = lmdb:${data_directory}/smtp_scache
|
||||||
|
|
||||||
|
# General TLS hardening
|
||||||
|
tls_ssl_options = NO_COMPRESSION
|
||||||
|
tls_preempt_cipherlist = yes
|
||||||
|
|
||||||
|
###############
|
||||||
|
# SASL
|
||||||
|
###############
|
||||||
|
|
||||||
|
smtpd_sasl_local_domain = $myhostname
|
||||||
|
|
||||||
|
# Authentication is done against dovecot, which acts as the main authention
|
||||||
|
# source
|
||||||
|
smtpd_sasl_type = dovecot
|
||||||
|
smtpd_sasl_path = inet:imap:2102
|
||||||
|
smtpd_sasl_auth_enable = yes
|
||||||
|
smtpd_sasl_security_options = noanonymous
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Virtual
|
# Virtual
|
||||||
@@ -60,8 +84,7 @@ virtual_alias_maps = ${sql}sqlite-virtual_alias_maps.cf
|
|||||||
virtual_mailbox_domains = ${sql}sqlite-virtual_mailbox_domains.cf
|
virtual_mailbox_domains = ${sql}sqlite-virtual_mailbox_domains.cf
|
||||||
virtual_mailbox_maps = $virtual_alias_maps
|
virtual_mailbox_maps = $virtual_alias_maps
|
||||||
|
|
||||||
# Mails are transported if required, then forwarded to Dovecot for delivery
|
# Mails are forwarded to Dovecot for delivery
|
||||||
transport_maps = ${sql}sqlite-transport.cf
|
|
||||||
virtual_transport = lmtp:inet:imap:2525
|
virtual_transport = lmtp:inet:imap:2525
|
||||||
|
|
||||||
# In order to prevent Postfix from running DNS query, enforce the use of the
|
# In order to prevent Postfix from running DNS query, enforce the use of the
|
||||||
@@ -78,22 +101,31 @@ smtpd_delay_reject = yes
|
|||||||
# Allowed senders are: the user or one of the alias destinations
|
# Allowed senders are: the user or one of the alias destinations
|
||||||
smtpd_sender_login_maps = $virtual_alias_maps
|
smtpd_sender_login_maps = $virtual_alias_maps
|
||||||
|
|
||||||
# Restrictions for incoming SMTP, other restrictions are applied in master.cf
|
# Helo restrictions are specified for smtp only in master.cf
|
||||||
smtpd_helo_required = yes
|
smtpd_helo_required = yes
|
||||||
|
|
||||||
smtpd_recipient_restrictions =
|
# Sender restrictions
|
||||||
|
smtpd_sender_restrictions =
|
||||||
permit_mynetworks,
|
permit_mynetworks,
|
||||||
check_sender_access ${sql}sqlite-reject-spoofed.cf,
|
|
||||||
reject_non_fqdn_sender,
|
reject_non_fqdn_sender,
|
||||||
reject_unknown_sender_domain,
|
reject_unknown_sender_domain,
|
||||||
|
reject_unlisted_sender,
|
||||||
|
reject_sender_login_mismatch,
|
||||||
|
permit
|
||||||
|
|
||||||
|
# Recipient restrictions:
|
||||||
|
smtpd_recipient_restrictions =
|
||||||
|
reject_unauth_pipelining,
|
||||||
|
reject_non_fqdn_recipient,
|
||||||
reject_unknown_recipient_domain,
|
reject_unknown_recipient_domain,
|
||||||
|
permit_mynetworks,
|
||||||
permit
|
permit
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Milter
|
# Milter
|
||||||
###############
|
###############
|
||||||
|
|
||||||
smtpd_milters = inet:antispam:11332
|
smtpd_milters = inet:milter:9900
|
||||||
milter_protocol = 6
|
milter_protocol = 6
|
||||||
milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
|
milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
|
||||||
milter_default_action = tempfail
|
milter_default_action = tempfail
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
# service type private unpriv chroot wakeup maxproc command + args
|
# service type private unpriv chroot wakeup maxproc command + args
|
||||||
# (yes) (yes) (yes) (never) (100)
|
# (yes) (yes) (yes) (never) (100)
|
||||||
|
|
||||||
# Exposed SMTP service
|
# Exposed SMTP services
|
||||||
smtp inet n - n - - smtpd
|
smtp inet n - n - - smtpd
|
||||||
|
-o smtpd_helo_restrictions=permit_mynetworks,permit
|
||||||
# Internal SMTP service
|
submission inet n - n - - smtpd
|
||||||
10025 inet n - n - - smtpd
|
-o smtpd_tls_security_level=encrypt
|
||||||
-o smtpd_sasl_auth_enable=yes
|
-o smtpd_sasl_auth_enable=yes
|
||||||
-o smtpd_recipient_restrictions=reject_unlisted_sender,reject_sender_login_mismatch,permit
|
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
|
||||||
-o cleanup_service_name=outclean
|
-o cleanup_service_name=outclean
|
||||||
|
smtps inet n - n - - smtpd
|
||||||
|
-o smtpd_tls_security_level=encrypt
|
||||||
|
-o smtpd_sasl_auth_enable=yes
|
||||||
|
-o smtpd_tls_wrappermode=yes
|
||||||
|
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
|
||||||
|
-o cleanup_service_name=outclean
|
||||||
|
|
||||||
|
# Additional services
|
||||||
outclean unix n - n - 0 cleanup
|
outclean unix n - n - 0 cleanup
|
||||||
-o header_checks=pcre:/etc/postfix/outclean_header_filter.cf
|
-o header_checks=pcre:/etc/postfix/outclean_header_filter
|
||||||
|
|
||||||
# Internal postfix services
|
# Internal postfix services
|
||||||
pickup unix n - n 60 1 pickup
|
pickup unix n - n 60 1 pickup
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
# This configuration was copied from Mailinabox. The original version is available at:
|
|
||||||
# https://raw.githubusercontent.com/mail-in-a-box/mailinabox/master/conf/postfix_outgoing_mail_header_filters
|
|
||||||
|
|
||||||
# Remove the first line of the Received: header. Note that we cannot fully remove the Received: header
|
|
||||||
# because OpenDKIM requires that a header be present when signing outbound mail. The first line is
|
|
||||||
# where the user's home IP address would be.
|
|
||||||
/^\s*Received:[^\n]*(.*)/ REPLACE Received: from authenticated-user (PRIMARY_HOSTNAME [PUBLIC_IP])$1
|
|
||||||
|
|
||||||
# Remove other typically private information.
|
|
||||||
/^\s*User-Agent:/ IGNORE
|
|
||||||
/^\s*X-Enigmail:/ IGNORE
|
|
||||||
/^\s*X-Mailer:/ IGNORE
|
|
||||||
/^\s*X-Originating-IP:/ IGNORE
|
|
||||||
/^\s*X-Pgp-Agent:/ IGNORE
|
|
||||||
|
|
||||||
# The Mime-Version header can leak the user agent too, e.g. in Mime-Version: 1.0 (Mac OS X Mail 8.1 \(2010.6\)).
|
|
||||||
/^\s*(Mime-Version:\s*[0-9\.]+)\s.+/ REPLACE $1
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
$ModLoad imuxsock
|
|
||||||
$template noTimestampFormat,"%syslogtag%%msg%\n"
|
|
||||||
$ActionFileDefaultTemplate noTimestampFormat
|
|
||||||
*.*;auth,authpriv.none /dev/stdout
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
dbpath = /data/main.db
|
|
||||||
query =
|
|
||||||
SELECT 'REJECT' FROM domain WHERE name='%s'
|
|
||||||
UNION
|
|
||||||
SELECT 'REJECT' FROM alternative WHERE name='%s'
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
dbpath = /data/main.db
|
|
||||||
query =
|
|
||||||
SELECT 'smtp:['||smtp||']' FROM relay WHERE name='%s'
|
|
||||||
@@ -4,9 +4,7 @@ query =
|
|||||||
FROM
|
FROM
|
||||||
(SELECT destination, email, wildcard, localpart FROM alias
|
(SELECT destination, email, wildcard, localpart FROM alias
|
||||||
UNION
|
UNION
|
||||||
SELECT (CASE WHEN forward_enabled=1 THEN (CASE WHEN forward_keep=1 THEN email||',' ELSE '' END)||forward_destination ELSE email END) AS destination, email, 0 as wildcard, localpart FROM user
|
SELECT email||(CASE WHEN forward_enabled=1 THEN ','||forward_destination ELSE '' END) AS destination, email, 0 as wildcard, localpart FROM user)
|
||||||
UNION
|
|
||||||
SELECT '@'||domain_name as destination, '@'||name as email, 0 as wildcard, '' as localpart FROM alternative)
|
|
||||||
WHERE
|
WHERE
|
||||||
(
|
(
|
||||||
wildcard = 0
|
wildcard = 0
|
||||||
|
|||||||
@@ -1,5 +1,2 @@
|
|||||||
dbpath = /data/main.db
|
dbpath = /data/main.db
|
||||||
query =
|
query = SELECT name FROM domain WHERE name='%s'
|
||||||
SELECT name FROM domain WHERE name='%s'
|
|
||||||
UNION
|
|
||||||
SELECT name FROM alternative WHERE name='%s'
|
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
import jinja2
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import glob
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ))
|
|
||||||
|
|
||||||
# Actual startup script
|
|
||||||
os.environ["FRONT_ADDRESS"] = socket.gethostbyname("front")
|
|
||||||
|
|
||||||
for postfix_file in glob.glob("/conf/*.cf"):
|
|
||||||
convert(postfix_file, os.path.join("/etc/postfix", os.path.basename(postfix_file)))
|
|
||||||
|
|
||||||
if os.path.exists("/overrides/postfix.cf"):
|
|
||||||
for line in open("/overrides/postfix.cf").read().strip().split("\n"):
|
|
||||||
os.system('postconf -e "{}"'.format(line))
|
|
||||||
|
|
||||||
if os.path.exists("/overrides/postfix.master"):
|
|
||||||
for line in open("/overrides/postfix.master").read().strip().split("\n"):
|
|
||||||
os.system('postconf -Me "{}"'.format(line))
|
|
||||||
|
|
||||||
for map_file in glob.glob("/overrides/*.map"):
|
|
||||||
destination = os.path.join("/etc/postfix", os.path.basename(map_file))
|
|
||||||
shutil.copyfile(map_file, destination)
|
|
||||||
os.system("postmap {}".format(destination))
|
|
||||||
os.remove(destination)
|
|
||||||
|
|
||||||
convert("/conf/rsyslog.conf", "/etc/rsyslog.conf")
|
|
||||||
|
|
||||||
# Run postfix
|
|
||||||
if os.path.exists("/var/run/rsyslogd.pid"):
|
|
||||||
os.remove("/var/run/rsyslogd.pid")
|
|
||||||
os.system("/usr/lib/postfix/post-install meta_directory=/etc/postfix create-missing")
|
|
||||||
os.system("/usr/lib/postfix/master &")
|
|
||||||
os.execv("/usr/sbin/rsyslogd", ["rsyslogd", "-n"])
|
|
||||||
20
docs/Dockerfile
Normal file
20
docs/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
FROM python:3-alpine
|
||||||
|
|
||||||
|
COPY requirements.txt /requirements.txt
|
||||||
|
|
||||||
|
ARG version=master
|
||||||
|
ENV VERSION=$version
|
||||||
|
|
||||||
|
RUN pip install -r /requirements.txt \
|
||||||
|
&& apk add --no-cache nginx curl \
|
||||||
|
&& mkdir /run/nginx
|
||||||
|
|
||||||
|
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY . /docs
|
||||||
|
|
||||||
|
RUN mkdir -p /build/$VERSION \
|
||||||
|
&& sphinx-build /docs /build/$VERSION
|
||||||
|
|
||||||
|
EXPOSE 80/tcp
|
||||||
|
|
||||||
|
CMD nginx -g "daemon off;"
|
||||||
9
docs/_templates/layout.html
vendored
9
docs/_templates/layout.html
vendored
@@ -1,2 +1,9 @@
|
|||||||
{% set version=github_version %}
|
|
||||||
{% extends "!layout.html" %}
|
{% extends "!layout.html" %}
|
||||||
|
{% block document %}
|
||||||
|
{% if version != stable_version %}
|
||||||
|
<div class="wy-alert info">
|
||||||
|
<p>You are currently browsing documentation for the <b>{{ version }}</b> branch. Documentation for the stable <b>{{ stable_version }}</b> branch can be found <a href="/{{ stable_version }}/">here</a>.</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{{ super() }}
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
16
docs/_templates/versions.html
vendored
Normal file
16
docs/_templates/versions.html
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<div class="rst-versions" data-toggle="rst-versions" role="note" aria-label="versions">
|
||||||
|
<span class="rst-current-version" data-toggle="rst-current-version">
|
||||||
|
<span class="fa fa-book"> Versions</span>
|
||||||
|
v: {{ version }}
|
||||||
|
<span class="fa fa-caret-down"></span>
|
||||||
|
</span>
|
||||||
|
<div class="rst-other-versions">
|
||||||
|
<dl>
|
||||||
|
<dt>{{ _('Versions') }}</dt>
|
||||||
|
{% for slug, url in versions %}
|
||||||
|
<dd><a href="{{ url }}">{{ slug }}</a></dd>
|
||||||
|
{% endfor %}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ user_delete
|
|||||||
config_update
|
config_update
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
This command sole purpose is for importing users/aliases in bulk and synchronizing DB entries with external YAML template:
|
The sole purpose of this command is for importing users/aliases in bulk and synchronizing DB entries with external YAML template:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
ROOT=/mailu
|
ROOT=/mailu
|
||||||
|
|
||||||
# Mailu version to run (1.0, 1.1, etc. or master)
|
# Mailu version to run (1.0, 1.1, etc. or master)
|
||||||
VERSION=master
|
VERSION=1.5
|
||||||
|
|
||||||
# Set to a randomly generated 16 bytes string
|
# Set to a randomly generated 16 bytes string
|
||||||
SECRET_KEY=ChangeMeChangeMe
|
SECRET_KEY=ChangeMeChangeMe
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ make sure that you either:
|
|||||||
|
|
||||||
If you chose to create a dedicated partition, simply mount it to
|
If you chose to create a dedicated partition, simply mount it to
|
||||||
``/var/lib/docker``. You could also create a separate partition (*ext4* is a
|
``/var/lib/docker``. You could also create a separate partition (*ext4* is a
|
||||||
sane default) ans mount it to ``/mailu`` for storing e-mail data.
|
sane default) and mount it to ``/mailu`` for storing e-mail data.
|
||||||
|
|
||||||
Docker supports *AUFS* over *ext4* and *btrfs* as stable storage drivers.
|
Docker supports *AUFS* over *ext4* and *btrfs* as stable storage drivers.
|
||||||
Other filesystems are supported such as *OverlayFS*. If you know what you are
|
Other filesystems are supported such as *OverlayFS*. If you know what you are
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ for the ``VERSION_TAG`` branch, use:
|
|||||||
Then open the ``.env`` file to setup the mail server. Modify the ``ROOT`` setting
|
Then open the ``.env`` file to setup the mail server. Modify the ``ROOT`` setting
|
||||||
to match your setup directory if different from ``/mailu``.
|
to match your setup directory if different from ``/mailu``.
|
||||||
|
|
||||||
Mdify the ``VERSION`` configuration in the ``.env`` file to reflect the version you picked.
|
Modify the ``VERSION`` configuration in the ``.env`` file to reflect the version you picked.
|
||||||
|
|
||||||
Set the common configuration values
|
Set the common configuration values
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
@@ -63,11 +63,11 @@ default configuration.
|
|||||||
A Webmail is a Web interface exposing an email client. Mailu webmails are
|
A Webmail is a Web interface exposing an email client. Mailu webmails are
|
||||||
bound to the internal IMAP and SMTP server for users to access their mailbox through
|
bound to the internal IMAP and SMTP server for users to access their mailbox through
|
||||||
the Web. By exposing a complex application such as a Webmail, you should be aware of
|
the Web. By exposing a complex application such as a Webmail, you should be aware of
|
||||||
the security implications such an increase of attack surface. The ``WEBMAIL``
|
the security implications caused by such an increase of attack surface. The ``WEBMAIL``
|
||||||
configuration option must be one of the following:
|
configuration option must be one of the following:
|
||||||
|
|
||||||
- ``none`` is the default value, no Webmail service will be exposed;
|
- ``none`` is the default value, no Webmail service will be exposed;
|
||||||
- ``roundcube`` will run the popular Roundcube Webmail ;
|
- ``roundcube`` will run the popular Roundcube Webmail;
|
||||||
- ``rainloop`` will run the popular Rainloop Webmail.
|
- ``rainloop`` will run the popular Rainloop Webmail.
|
||||||
|
|
||||||
The administration interface is not exposed on the public address by default,
|
The administration interface is not exposed on the public address by default,
|
||||||
@@ -90,7 +90,7 @@ setting. The configuration option must be one of the following:
|
|||||||
- ``none`` disables antivirus checks;
|
- ``none`` disables antivirus checks;
|
||||||
- ``clamav`` is the default values, the popular ClamAV antivirus is enabled.
|
- ``clamav`` is the default values, the popular ClamAV antivirus is enabled.
|
||||||
|
|
||||||
Make sure that you have at least 1GB or memory for ClamAV to load its signature
|
Make sure that you have at least 1GB of memory for ClamAV to load its signature
|
||||||
database.
|
database.
|
||||||
|
|
||||||
Finish setting up TLS
|
Finish setting up TLS
|
||||||
|
|||||||
39
docs/conf.py
39
docs/conf.py
@@ -2,19 +2,22 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
extensions = ['sphinx.ext.imgmath', 'sphinx.ext.viewcode']
|
extensions = ['sphinx.ext.imgmath', 'sphinx.ext.viewcode']
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
source_suffix = '.rst'
|
source_suffix = '.rst'
|
||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
project = 'Mailu'
|
project = 'Mailu'
|
||||||
copyright = '2017, Mailu authors'
|
copyright = '2018, Mailu authors'
|
||||||
author = 'Mailu authors'
|
author = 'Mailu authors'
|
||||||
version = release = 'latest'
|
version = release = os.environ.get('VERSION', 'master')
|
||||||
language = None
|
language = None
|
||||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'Dockerfile', 'docker-compose.yml']
|
||||||
pygments_style = 'sphinx'
|
pygments_style = 'sphinx'
|
||||||
todo_include_todos = False
|
todo_include_todos = False
|
||||||
html_theme = 'sphinx_rtd_theme'
|
html_theme = 'sphinx_rtd_theme'
|
||||||
|
html_title = 'Mailu, Docker based mail server'
|
||||||
html_static_path = []
|
html_static_path = []
|
||||||
htmlhelp_basename = 'Mailudoc'
|
htmlhelp_basename = 'Mailudoc'
|
||||||
|
|
||||||
@@ -22,7 +25,7 @@ htmlhelp_basename = 'Mailudoc'
|
|||||||
# to template names.
|
# to template names.
|
||||||
html_sidebars = {
|
html_sidebars = {
|
||||||
'**': [
|
'**': [
|
||||||
'relations.html', # needs 'show_related': True theme option to display
|
'relations.html',
|
||||||
'searchbox.html',
|
'searchbox.html',
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -32,27 +35,11 @@ html_context = {
|
|||||||
'display_github': True,
|
'display_github': True,
|
||||||
'github_user': 'mailu',
|
'github_user': 'mailu',
|
||||||
'github_repo': 'mailu',
|
'github_repo': 'mailu',
|
||||||
'github_version': 'master',
|
'github_version': version,
|
||||||
|
'stable_version': '1.5',
|
||||||
|
'versions': [
|
||||||
|
('1.5', '/1.5/'),
|
||||||
|
('master', '/master/')
|
||||||
|
],
|
||||||
'conf_py_path': '/docs/'
|
'conf_py_path': '/docs/'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Upload function when the script is called directly
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import os, sys, paramiko
|
|
||||||
build_dir, hostname, username, password, dest_dir = sys.argv[1:]
|
|
||||||
transport = paramiko.Transport((hostname, 22))
|
|
||||||
transport.connect(username=username, password=password)
|
|
||||||
sftp = paramiko.SFTPClient.from_transport(transport)
|
|
||||||
os.chdir(build_dir)
|
|
||||||
for dirpath, dirnames, filenames in os.walk("."):
|
|
||||||
remote_path = os.path.join(dest_dir, dirpath)
|
|
||||||
try:
|
|
||||||
sftp.mkdir(remote_path)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
for filename in filenames:
|
|
||||||
sftp.put(
|
|
||||||
os.path.join(dirpath, filename),
|
|
||||||
os.path.join(remote_path, filename)
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ like ``localpart+custom@domain.tld`` to deliver mail to ``localpart@domain.tld``
|
|||||||
This is useful to provide external parties with different email addresses and
|
This is useful to provide external parties with different email addresses and
|
||||||
later classify incoming mail based on the custom part.
|
later classify incoming mail based on the custom part.
|
||||||
|
|
||||||
The ``DMAR_RUA`` and ``DMARC_RUF`` are DMARC protocol specific values. They hold
|
The ``DMARC_RUA`` and ``DMARC_RUF`` are DMARC protocol specific values. They hold
|
||||||
the localpart for DMARC rua and ruf email addresses.
|
the localpart for DMARC rua and ruf email addresses.
|
||||||
|
|
||||||
Web settings
|
Web settings
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Set that name in the ``HOSTNAME`` configuration entry. Then depending on your do
|
|||||||
|
|
||||||
Also, ``a.b.c.d`` should be set in your ``BIND_INTERFACE`` configuration unless your server is in a DMZ and you are using port forwards to expose the services.
|
Also, ``a.b.c.d`` should be set in your ``BIND_INTERFACE`` configuration unless your server is in a DMZ and you are using port forwards to expose the services.
|
||||||
|
|
||||||
Finally, make sure that you have a proper TLS certificate for you mail server hostname and install it according to the instructions in the [[Setup Guide]].
|
Finally, make sure that you have a proper TLS certificate for your mail server hostname and install it according to the instructions in the [[Setup Guide]].
|
||||||
|
|
||||||
MX entries
|
MX entries
|
||||||
----------
|
----------
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ Main features include:
|
|||||||
|
|
||||||
- **Standard email server**, IMAP and IMAP+, SMTP and Submission
|
- **Standard email server**, IMAP and IMAP+, SMTP and Submission
|
||||||
- **Advanced email features**, aliases, domain aliases, custom routing
|
- **Advanced email features**, aliases, domain aliases, custom routing
|
||||||
- **Web access**, multiple Webmails and adminitration interface
|
- **Web access**, multiple Webmails and administration interface
|
||||||
- **User features**, aliases, auto-reply, auto-forward, fetched accounts
|
- **User features**, aliases, auto-reply, auto-forward, fetched accounts
|
||||||
- **Admin features**, global admins, announcements, per-domain delegation, quotas
|
- **Admin features**, global admins, announcements, per-domain delegation, quotas
|
||||||
- **Security**, enforced TLS, Letsencrypt!, outgoing DKIM, anti-virus scanner
|
- **Security**, enforced TLS, Letsencrypt!, outgoing DKIM, anti-virus scanner
|
||||||
|
|||||||
@@ -23,4 +23,4 @@ Using the resource configurations is simple:
|
|||||||
1. ``kubectl apply -f kubernetes-nginx-ingress-controller.yaml`` to configure an ingress controller with the proper settings. (If you have one set up already you may need to port the configuration to your own ingress).
|
1. ``kubectl apply -f kubernetes-nginx-ingress-controller.yaml`` to configure an ingress controller with the proper settings. (If you have one set up already you may need to port the configuration to your own ingress).
|
||||||
2. ``kubectl apply -f kubernetes-mailu.yaml`` to create the resources required to run Mailu.
|
2. ``kubectl apply -f kubernetes-mailu.yaml`` to create the resources required to run Mailu.
|
||||||
|
|
||||||
Based on the configuration, your Mailu instance should be available at ``mail.<hostname>.tld/admin`` (note that visiting just ``mail.<hostname>.tld`` will likely result in a 404.
|
Based on the configuration, your Mailu instance should be available at ``mail.<hostname>.tld/admin`` (note that visiting just ``mail.<hostname>.tld`` will likely result in a 404 error).
|
||||||
|
|||||||
@@ -20,25 +20,25 @@ simply pull the latest images and recreate the containers :
|
|||||||
Monitoring the mail server
|
Monitoring the mail server
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
Logs are managed by Docker directly. You can easily read your logs using :
|
Logs are managed by Docker directly. You can easily read your logs using:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
docker-compose logs
|
docker-compose logs
|
||||||
|
|
||||||
Docker is able to forward logs to multiple log engines. Read the following documentation or details: https://docs.docker.com/engine/admin/logging/overview/.
|
Docker is able to forward logs to multiple log engines. Read the following documentation for details: https://docs.docker.com/engine/admin/logging/overview/.
|
||||||
|
|
||||||
Migrating an instance
|
Migrating an instance
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
The SMTP protocol has an embedded retry mechanism and multiple MX that can serve a single domain, so that most migration process or maintenance process do not require any specific care.
|
The SMTP protocol has an embedded retry mechanism and multiple MX that can serve a single domain, so that most migration processes or maintenance processes do not require any specific care.
|
||||||
|
|
||||||
Mailu relys heavily on files for storing everything, which helps the migration process, that can be performed based on file synchronization.
|
Mailu relies heavily on files for storing everything, which helps the migration process, that can be performed based on file synchronization.
|
||||||
|
|
||||||
The suggested migration process consists in setting up a new backup server that drops incoming emails (Mailu not started), synchronizing both servers, stopping the main server and launching the backup server. Then, the backup server is switched as a main MX and the old server is deleted.
|
The suggested migration process consists of setting up a new backup server that drops incoming emails (Mailu not started), synchronizing both servers, stopping the main server and launching the backup server. Then, the backup server is switched as a main MX and the old server is deleted.
|
||||||
|
|
||||||
1. Prepare your new server, copy your ``docker-compose.yml``, ``.env`` and basic configuration files to the server, so that it is ready to start configuration Mailu, *do not start Mailu*
|
1. Prepare your new server, copy your ``docker-compose.yml``, ``.env`` and basic configuration files to the server, so that it is ready to start configuration Mailu, *do not start Mailu*
|
||||||
2. Setup your DNS so that the backup server is an additional, deprioritized MX for the domain; this can be complex if you serve many domains, in which case you can simply accept that some remote MX will retry for a couple minutes, skip this step
|
2. Setup your DNS so that the backup server is an additional, deprioritized MX for the domain; this can be complex if you serve many domains, in which case you can simply accept that some remote MX will retry for a couple of minutes, skip this step
|
||||||
3. While your DNS TTL expires and your modification propagates, start *rsyncing* your Mailu directory (``data``, ``dkim``, ``mail``, etc.) to the new server, repeat until there are only a couple files synchronized
|
3. While your DNS TTL expires and your modification propagates, start *rsyncing* your Mailu directory (``data``, ``dkim``, ``mail``, etc.) to the new server, repeat until there are only a couple files synchronized
|
||||||
4. Stop Mailu on the old server and run a final ``rsync`` while no process is writing to the files
|
4. Stop Mailu on the old server and run a final ``rsync`` while no process is writing to the files
|
||||||
5. Start Mailu on the new server, and production should be back to normal
|
5. Start Mailu on the new server, and production should be back to normal
|
||||||
|
|||||||
5
docs/nginx.conf
Normal file
5
docs/nginx.conf
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
root /build;
|
||||||
|
}
|
||||||
@@ -2,5 +2,3 @@ recommonmark
|
|||||||
Sphinx
|
Sphinx
|
||||||
sphinx-autobuild
|
sphinx-autobuild
|
||||||
sphinx-rtd-theme
|
sphinx-rtd-theme
|
||||||
sphinxcontrib-versioning
|
|
||||||
paramiko
|
|
||||||
|
|||||||
@@ -141,4 +141,4 @@ Disable completely Mailu reverse proxy
|
|||||||
|
|
||||||
You can simply disable Mailu reverse proxy by removing the ``front`` section from the ``docker-compose.yml`` and use your own means to reverse proxy requests to the proper containers.
|
You can simply disable Mailu reverse proxy by removing the ``front`` section from the ``docker-compose.yml`` and use your own means to reverse proxy requests to the proper containers.
|
||||||
|
|
||||||
Be careful with this method as resolving container addresses outside the Docker Compose structure is a tricky task: there is no guaranty that addresses will remain after a restart and you are almost certain that addresses will change after every upgrade (and whenever containers are recreated).
|
Be careful with this method as resolving container addresses outside the Docker Compose structure is a tricky task: there is no guarantee that addresses will remain after a restart and you are almost certain that addresses will change after every upgrade (and whenever containers are recreated).
|
||||||
|
|||||||
@@ -33,9 +33,8 @@ Pick a Mailu version
|
|||||||
|
|
||||||
Mailu is shipped in multiple versions.
|
Mailu is shipped in multiple versions.
|
||||||
|
|
||||||
- ``stable`` is the default version and features a stable server, it gets bugfixes
|
- ``1.5`` features the most recent stable version for Mailu. This is the
|
||||||
and patches but features are included every month or so after a couple weeks of
|
recommended build for new setups, old setups should migrate when possible.
|
||||||
testing. This is the default setting and should match most requirements.
|
|
||||||
|
|
||||||
- ``1.0``, ``1.1``, and other version branches feature old versions of Mailu
|
- ``1.0``, ``1.1``, and other version branches feature old versions of Mailu
|
||||||
they will not receive any more patches (except for the stable one) and you should
|
they will not receive any more patches (except for the stable one) and you should
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM alpine
|
FROM alpine:3.13
|
||||||
|
|
||||||
RUN apk add --no-cache clamav rsyslog wget clamav-libunrar
|
RUN apk add --no-cache clamav rsyslog wget clamav-libunrar
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM python:alpine
|
FROM python:alpine3.13
|
||||||
|
|
||||||
RUN apk add --no-cache fetchmail ca-certificates
|
RUN apk add --no-cache fetchmail ca-certificates
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
FROM alpine:edge
|
FROM alpine:3.13
|
||||||
|
|
||||||
RUN apk add --no-cache python py-jinja2 rspamd rspamd-controller rspamd-proxy ca-certificates
|
RUN apk add --no-cache python3 py3-jinja2 rspamd rspamd-controller rspamd-proxy ca-certificates
|
||||||
|
|
||||||
RUN mkdir /run/rspamd
|
RUN mkdir /run/rspamd
|
||||||
|
|
||||||
COPY conf/ /conf
|
COPY conf/ /conf
|
||||||
COPY start.py /start.py
|
COPY start.py /start.py
|
||||||
|
|
||||||
|
# Temporary fix to remove references to rspamd-fuzzy for now
|
||||||
|
RUN sed -i '/fuzzy/,$d' /etc/rspamd/rspamd.conf
|
||||||
|
|
||||||
CMD /start.py
|
CMD /start.py
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
type = "controller";
|
||||||
bind_socket = "*:11334";
|
bind_socket = "*:11334";
|
||||||
password = "mailu";
|
password = "mailu";
|
||||||
secure_ip = "{{ FRONT_ADDRESS }}";
|
secure_ip = "{{ FRONT_ADDRESS }}";
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
type = "normal";
|
||||||
enabled = false;
|
enabled = false;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
type = "proxy";
|
||||||
bind_socket = "*:11332";
|
bind_socket = "*:11332";
|
||||||
upstream "local" {
|
upstream "local" {
|
||||||
default = yes;
|
default = yes;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python3
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
import os
|
import os
|
||||||
|
|||||||
54
tests/build.yml
Normal file
54
tests/build.yml
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
front:
|
||||||
|
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}nginx:${MAILU_VERSION:-local}
|
||||||
|
build: ../core/nginx
|
||||||
|
|
||||||
|
imap:
|
||||||
|
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}dovecot:${MAILU_VERSION:-local}
|
||||||
|
build: ../core/dovecot
|
||||||
|
|
||||||
|
smtp:
|
||||||
|
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}postfix:${MAILU_VERSION:-local}
|
||||||
|
build: ../core/postfix
|
||||||
|
|
||||||
|
antispam:
|
||||||
|
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}rspamd:${MAILU_VERSION:-local}
|
||||||
|
build: ../services/rspamd
|
||||||
|
|
||||||
|
antivirus:
|
||||||
|
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}clamav:${MAILU_VERSION:-local}
|
||||||
|
build: ../optional/clamav
|
||||||
|
|
||||||
|
webdav:
|
||||||
|
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}radicale:${MAILU_VERSION:-local}
|
||||||
|
build: ../optional/radicale
|
||||||
|
|
||||||
|
admin:
|
||||||
|
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}admin:${MAILU_VERSION:-local}
|
||||||
|
build: ../core/admin
|
||||||
|
|
||||||
|
roundcube:
|
||||||
|
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}roundcube:${MAILU_VERSION:-local}
|
||||||
|
build: ../webmails/roundcube
|
||||||
|
|
||||||
|
rainloop:
|
||||||
|
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}rainloop:${MAILU_VERSION:-local}
|
||||||
|
build: ../webmails/rainloop
|
||||||
|
|
||||||
|
fetchmail:
|
||||||
|
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}fetchmail:${MAILU_VERSION:-local}
|
||||||
|
build: ../services/fetchmail
|
||||||
|
|
||||||
|
none:
|
||||||
|
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}none:${MAILU_VERSION:-local}
|
||||||
|
build: ../core/none
|
||||||
|
|
||||||
|
docs:
|
||||||
|
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}docs:${MAILU_VERSION:-local}
|
||||||
|
build:
|
||||||
|
context: ../docs
|
||||||
|
args:
|
||||||
|
version: ${MAILU_VERSION:-local}
|
||||||
4
tests/deploy.sh
Executable file
4
tests/deploy.sh
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
docker login -u $DOCKER_UN -p $DOCKER_PW
|
||||||
|
docker-compose -f tests/build.yml push
|
||||||
@@ -4,7 +4,7 @@ RUN apt-get update && apt-get install -y \
|
|||||||
libfreetype6-dev \
|
libfreetype6-dev \
|
||||||
libjpeg62-turbo-dev \
|
libjpeg62-turbo-dev \
|
||||||
libmcrypt-dev \
|
libmcrypt-dev \
|
||||||
libpng12-dev \
|
libpng-dev \
|
||||||
&& docker-php-ext-install pdo_mysql mcrypt
|
&& docker-php-ext-install pdo_mysql mcrypt
|
||||||
|
|
||||||
ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.3.3/roundcubemail-1.3.3-complete.tar.gz
|
ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.3.3/roundcubemail-1.3.3-complete.tar.gz
|
||||||
@@ -19,7 +19,8 @@ RUN rm -rf /var/www/html/ \
|
|||||||
&& mv roundcubemail-* html \
|
&& mv roundcubemail-* html \
|
||||||
&& cd html \
|
&& cd html \
|
||||||
&& rm -rf CHANGELOG INSTALL LICENSE README.md UPGRADING composer.json-dist installer \
|
&& rm -rf CHANGELOG INSTALL LICENSE README.md UPGRADING composer.json-dist installer \
|
||||||
&& chown -R www-data: logs
|
&& chown -R www-data: logs \
|
||||||
|
&& sed -i 's/mod_php5.c/mod_php7.c/g' /var/www/html/.htaccess
|
||||||
|
|
||||||
COPY config.inc.php /var/www/html/config/
|
COPY config.inc.php /var/www/html/config/
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ $config = array();
|
|||||||
|
|
||||||
// Generals
|
// Generals
|
||||||
$config['db_dsnw'] = 'sqlite:////data/roundcube.db';
|
$config['db_dsnw'] = 'sqlite:////data/roundcube.db';
|
||||||
|
$config['temp_dir'] = '/tmp/';
|
||||||
$config['des_key'] = getenv('SECRET_KEY');
|
$config['des_key'] = getenv('SECRET_KEY');
|
||||||
$config['identities_level'] = 3;
|
$config['identities_level'] = 3;
|
||||||
$config['reply_all_mode'] = 1;
|
$config['reply_all_mode'] = 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user