Merge branch 'master' into feat-abstract-db

master
kaiyou 6 years ago committed by GitHub
commit 00b5ae11db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,10 @@
rules:
default: null
branches:
master:
protection:
required_status_checks:
contexts:
- continuous-integration/travis-ci
required_pull_request_reviews:
required_approving_review_count: 2

@ -8,4 +8,15 @@ env:
- VERSION=$TRAVIS_BRANCH - VERSION=$TRAVIS_BRANCH
script: script:
- docker-compose -f tests/build.yml -p Mailu build # Default to mailu for DOCKER_ORG
- if [ -z "$DOCKER_ORG" ]; then export DOCKER_ORG="mailu"; fi
- docker-compose -f tests/build.yml build
- tests/compose/test-script.sh
deploy:
provider: script
script: bash tests/deploy.sh
on:
all_branches: true
condition: -n $DOCKER_UN

@ -17,5 +17,6 @@ COPY start.sh /start.sh
RUN pybabel compile -d mailu/translations RUN pybabel compile -d mailu/translations
EXPOSE 80/tcp EXPOSE 80/tcp
VOLUME ["/data"]
CMD ["/start.sh"] CMD ["/start.sh"]

@ -3,11 +3,13 @@ FROM alpine:3.8
RUN apk add --no-cache \ RUN apk add --no-cache \
dovecot dovecot-pigeonhole-plugin dovecot-fts-lucene rspamd-client \ dovecot dovecot-pigeonhole-plugin dovecot-fts-lucene rspamd-client \
python3 py3-pip \ python3 py3-pip \
&& pip3 install jinja2 podop && pip3 install --upgrade pip \
&& pip3 install jinja2 podop tenacity
COPY conf /conf COPY conf /conf
COPY start.py /start.py COPY start.py /start.py
EXPOSE 110/tcp 143/tcp 993/tcp 4190/tcp 2525/tcp EXPOSE 110/tcp 143/tcp 993/tcp 4190/tcp 2525/tcp
VOLUME ["/data", "/mail"]
CMD /start.py CMD /start.py

@ -5,7 +5,9 @@ import os
import socket import socket
import glob import glob
import multiprocessing import multiprocessing
import tenacity
from tenacity import retry
from podop import run_server from podop import run_server
@ -19,8 +21,15 @@ def start_podop():
convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ))
# Actual startup script @retry(stop=tenacity.stop_after_attempt(100), wait=tenacity.wait_random(min=2, max=5))
def resolve():
os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front")) os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front"))
os.environ["REDIS_ADDRESS"] = socket.gethostbyname(os.environ.get("REDIS_ADDRESS", "redis"))
if os.environ["WEBMAIL"] != "none":
os.environ["WEBMAIL_ADDRESS"] = socket.gethostbyname(os.environ.get("WEBMAIL_ADDRESS", "webmail"))
# Actual startup script
resolve()
for dovecot_file in glob.glob("/conf/*.conf"): for dovecot_file in glob.glob("/conf/*.conf"):
convert(dovecot_file, os.path.join("/etc/dovecot", os.path.basename(dovecot_file))) convert(dovecot_file, os.path.join("/etc/dovecot", os.path.basename(dovecot_file)))

@ -6,5 +6,6 @@ COPY conf /conf
COPY *.py / COPY *.py /
EXPOSE 80/tcp 443/tcp 110/tcp 143/tcp 465/tcp 587/tcp 993/tcp 995/tcp 25/tcp 10025/tcp 10143/tcp EXPOSE 80/tcp 443/tcp 110/tcp 143/tcp 465/tcp 587/tcp 993/tcp 995/tcp 25/tcp 10025/tcp 10143/tcp
VOLUME ["/certs"]
CMD /start.py CMD /start.py

@ -2,11 +2,13 @@ FROM alpine:3.8
RUN apk add --no-cache postfix postfix-pcre rsyslog \ RUN apk add --no-cache postfix postfix-pcre rsyslog \
python3 py3-pip \ python3 py3-pip \
&& pip3 install jinja2 podop && pip3 install --upgrade pip
&& pip3 install jinja2 podop tenacity
COPY conf /conf COPY conf /conf
COPY start.py /start.py COPY start.py /start.py
EXPOSE 25/tcp 10025/tcp EXPOSE 25/tcp 10025/tcp
VOLUME ["/data"]
CMD /start.py CMD /start.py

@ -8,6 +8,7 @@ smtp inet n - n - - smtpd
10025 inet n - n - - smtpd 10025 inet n - n - - smtpd
-o smtpd_sasl_auth_enable=yes -o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=reject_unlisted_sender,reject_authenticated_sender_login_mismatch,permit -o smtpd_client_restrictions=reject_unlisted_sender,reject_authenticated_sender_login_mismatch,permit
-o smtpd_reject_unlisted_recipient={% if REJECT_UNLISTED_RECIPIENT %}{{ REJECT_UNLISTED_RECIPIENT }}{% else %}no{% endif %}
-o cleanup_service_name=outclean -o cleanup_service_name=outclean
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.cf

@ -5,8 +5,10 @@ import os
import socket import socket
import glob import glob
import shutil import shutil
import tenacity
import multiprocessing import multiprocessing
from tenacity import retry
from podop import run_server from podop import run_server
@ -22,8 +24,12 @@ def start_podop():
convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ))
# Actual startup script @retry(stop=tenacity.stop_after_attempt(100), wait=tenacity.wait_random(min=2, max=5))
def resolve():
os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front")) os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front"))
# Actual startup script
resolve()
os.environ["HOST_ANTISPAM"] = os.environ.get("HOST_ANTISPAM", "antispam:11332") os.environ["HOST_ANTISPAM"] = os.environ.get("HOST_ANTISPAM", "antispam:11332")
os.environ["HOST_LMTP"] = os.environ.get("HOST_LMTP", "imap:2525") os.environ["HOST_LMTP"] = os.environ.get("HOST_LMTP", "imap:2525")

@ -0,0 +1,14 @@
FROM python:3-alpine
COPY requirements.txt /requirements.txt
RUN pip install -r /requirements.txt \
&& apk add --no-cache nginx \
&& mkdir /run/nginx
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
COPY . /docs
RUN sphinx-build /docs /build
CMD nginx -g "daemon off;"

@ -132,3 +132,6 @@ REAL_IP_HEADER=
# IPs for nginx set_real_ip_from (CIDR list separated by commas) # IPs for nginx set_real_ip_from (CIDR list separated by commas)
REAL_IP_FROM= REAL_IP_FROM=
# choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no)
REJECT_UNLISTED_RECIPIENT=

@ -7,7 +7,7 @@ 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 = 'latest'
language = None language = None
@ -23,7 +23,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',
] ]
} }
@ -36,24 +36,3 @@ html_context = {
'github_version': 'master', 'github_version': '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)
)

@ -89,3 +89,20 @@ Any change to the files will automatically restart the Web server and reload the
When using the development environment, a debugging toolbar is displayed on the right side When using the development environment, a debugging toolbar is displayed on the right side
of the screen, that you can open to access query details, internal variables, etc. of the screen, that you can open to access query details, internal variables, etc.
Documentation
-------------
Documentation is maintained in the ``docs`` directory and are maintained as `reStructuredText`_ files. It is possible to run a local documentation server for reviewing purposes, using Docker:
.. code-block:: bash
cd <Mailu repo>
docker build -t docs docs
docker run -p 127.0.0.1:8080:80 docs
You can now read the local documentation by navigating to http://localhost:8080.
.. note:: After modifying the documentation, the image needs to be rebuild and the container restarted for the changes to become visible.
.. _`reStructuredText`: http://docutils.sourceforge.net/rst.html

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

@ -6,5 +6,6 @@ COPY conf /etc/clamav
COPY start.sh /start.sh COPY start.sh /start.sh
EXPOSE 3310/tcp EXPOSE 3310/tcp
VOLUME ["/data"]
CMD ["/start.sh"] CMD ["/start.sh"]

@ -6,5 +6,6 @@ RUN echo "@testing http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/re
COPY radicale.conf /radicale.conf COPY radicale.conf /radicale.conf
EXPOSE 5232/tcp EXPOSE 5232/tcp
VOLUME ["/data"]
CMD radicale -f -S -C /radicale.conf CMD radicale -f -S -C /radicale.conf

@ -1,6 +1,8 @@
FROM alpine:edge FROM alpine:edge
RUN apk add --no-cache python py-jinja2 rspamd rspamd-controller rspamd-proxy ca-certificates RUN apk add --no-cache python py-jinja2 rspamd rspamd-controller rspamd-proxy ca-certificates py-pip \
&& pip install --upgrade pip \
&& pip install tenacity
RUN mkdir /run/rspamd RUN mkdir /run/rspamd
@ -12,4 +14,6 @@ RUN sed -i '/fuzzy/,$d' /etc/rspamd/rspamd.conf
EXPOSE 11332/tcp 11334/tcp EXPOSE 11332/tcp 11334/tcp
VOLUME ["/var/lib/rspamd"]
CMD /start.py CMD /start.py

@ -0,0 +1,4 @@
try_fallback = true;
path = "/dkim/$domain.$selector.key";
selector = "dkim"
use_esld = false;

@ -4,11 +4,17 @@ import jinja2
import os import os
import socket import socket
import glob import glob
import tenacity
from tenacity import retry
convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ))
# Actual startup script @retry(stop=tenacity.stop_after_attempt(100), wait=tenacity.wait_random(min=2, max=5))
def resolve():
os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front")) os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front"))
# Actual startup script
resolve()
if "HOST_REDIS" not in os.environ: os.environ["HOST_REDIS"] = "redis" if "HOST_REDIS" not in os.environ: os.environ["HOST_REDIS"] = "redis"
for rspamd_file in glob.glob("/conf/*"): for rspamd_file in glob.glob("/conf/*"):

@ -3,45 +3,54 @@ version: '3'
services: services:
front: front:
image: mailu/nginx:$VERSION image: $DOCKER_ORG/nginx:$VERSION
build: ../core/nginx build: ../core/nginx
imap: imap:
image: mailu/dovecot:$VERSION image: $DOCKER_ORG/dovecot:$VERSION
build: ../core/dovecot build: ../core/dovecot
smtp: smtp:
image: mailu/postfix:$VERSION image: $DOCKER_ORG/postfix:$VERSION
build: ../core/postfix build: ../core/postfix
antispam: antispam:
image: mailu/rspamd:$VERSION image: $DOCKER_ORG/rspamd:$VERSION
build: ../services/rspamd build: ../services/rspamd
antivirus: antivirus:
image: mailu/clamav:$VERSION image: $DOCKER_ORG/clamav:$VERSION
build: ../optional/clamav build: ../optional/clamav
webdav: webdav:
image: mailu/radicale:$VERSION image: $DOCKER_ORG/radicale:$VERSION
build: ../optional/radicale build: ../optional/radicale
admin: admin:
image: mailu/admin:$VERSION image: $DOCKER_ORG/admin:$VERSION
build: ../core/admin build: ../core/admin
roundcube: roundcube:
image: mailu/roundcube:$VERSION image: $DOCKER_ORG/roundcube:$VERSION
build: ../webmails/roundcube build: ../webmails/roundcube
rainloop: rainloop:
image: mailu/rainloop:$VERSION image: $DOCKER_ORG/rainloop:$VERSION
build: ../webmails/rainloop build: ../webmails/rainloop
fetchmail: fetchmail:
image: mailu/fetchmail:$VERSION image: $DOCKER_ORG/fetchmail:$VERSION
build: ../services/fetchmail build: ../services/fetchmail
none: none:
image: mailu/none:$VERSION image: $DOCKER_ORG/none:$VERSION
build: ../core/none build: ../core/none
docs:
image: $DOCKER_ORG/docs:$VERSION
build: ../docs
setup:
image: $DOCKER_ORG/setup:$VERSION
build: ../setup

@ -0,0 +1,134 @@
# Mailu main configuration file
#
# Most configuration variables can be modified through the Web interface,
# these few settings must however be configured before starting the mail
# server and require a restart upon change.
###################################
# Common configuration variables
###################################
# Set this to the path where Mailu data and configuration is stored
ROOT=/mailu
# Mailu version to run (1.0, 1.1, etc. or master)
#VERSION=master
# Set to a randomly generated 16 bytes string
SECRET_KEY=ChangeMeChangeMe
# Address where listening ports should bind
BIND_ADDRESS4=127.0.0.1
#BIND_ADDRESS6=::1
# Main mail domain
DOMAIN=mailu.io
# Hostnames for this server, separated with comas
HOSTNAMES=mail.mailu.io,alternative.mailu.io,yetanother.mailu.io
# Postmaster local part (will append the main mail domain)
POSTMASTER=admin
# Choose how secure connections will behave (value: letsencrypt, cert, notls, mail, mail-letsencrypt)
TLS_FLAVOR=cert
# Authentication rate limit (per source IP address)
AUTH_RATELIMIT=10/minute;1000/hour
# Opt-out of statistics, replace with "True" to opt out
DISABLE_STATISTICS=False
###################################
# Optional features
###################################
# Expose the admin interface (value: true, false)
ADMIN=false
# Choose which webmail to run if any (values: roundcube, rainloop, none)
WEBMAIL=none
# Dav server implementation (value: radicale, none)
WEBDAV=none
# Antivirus solution (value: clamav, none)
ANTIVIRUS=none
###################################
# Mail settings
###################################
# Message size limit in bytes
# Default: accept messages up to 50MB
MESSAGE_SIZE_LIMIT=50000000
# Networks granted relay permissions, make sure that you include your Docker
# internal network (default to 172.17.0.0/16)
RELAYNETS=172.16.0.0/12
# Will relay all outgoing mails if configured
RELAYHOST=
# Fetchmail delay
FETCHMAIL_DELAY=600
# Recipient delimiter, character used to delimiter localpart from custom address part
# e.g. localpart+custom@domain;tld
RECIPIENT_DELIMITER=+
# DMARC rua and ruf email
DMARC_RUA=admin
DMARC_RUF=admin
# Welcome email, enable and set a topic and body if you wish to send welcome
# emails to all users.
WELCOME=false
WELCOME_SUBJECT=Welcome to your new email account
WELCOME_BODY=Welcome to your new email account, if you can read this, then it is configured properly!
# Maildir Compression
# choose compression-method, default: none (value: bz2, gz)
COMPRESSION=
# change compression-level, default: 6 (value: 1-9)
COMPRESSION_LEVEL=
###################################
# Web settings
###################################
# Path to the admin interface if enabled
WEB_ADMIN=/admin
# Path to the webmail if enabled
WEB_WEBMAIL=/webmail
# Website name
SITENAME=Mailu
# Linked Website URL
WEBSITE=https://mailu.io
# Registration reCaptcha settings (warning, this has some privacy impact)
# RECAPTCHA_PUBLIC_KEY=
# RECAPTCHA_PRIVATE_KEY=
# Domain registration, uncomment to enable
# DOMAIN_REGISTRATION=true
###################################
# Advanced settings
###################################
# Docker-compose project name, this will prepended to containers names.
#COMPOSE_PROJECT_NAME=mailu
# Default password scheme used for newly created accounts and changed passwords
# (value: SHA512-CRYPT, SHA256-CRYPT, MD5-CRYPT, CRYPT)
PASSWORD_SCHEME=SHA512-CRYPT
# Header to take the real ip from
REAL_IP_HEADER=
# IPs for nginx set_real_ip_from (CIDR list separated by commas)
REAL_IP_FROM=

@ -0,0 +1,99 @@
version: '2'
services:
front:
image: $DOCKER_ORG/nginx:$VERSION
restart: 'no'
env_file: $PWD/.env
ports:
- "$BIND_ADDRESS4:80:80"
- "$BIND_ADDRESS4:443:443"
- "$BIND_ADDRESS4:110:110"
- "$BIND_ADDRESS4:143:143"
- "$BIND_ADDRESS4:993:993"
- "$BIND_ADDRESS4:995:995"
- "$BIND_ADDRESS4:25:25"
- "$BIND_ADDRESS4:465:465"
- "$BIND_ADDRESS4:587:587"
volumes:
- "$ROOT/certs:/certs"
redis:
image: redis:alpine
restart: 'no'
volumes:
- "$ROOT/redis:/data"
imap:
image: $DOCKER_ORG/dovecot:$VERSION
restart: 'no'
env_file: $PWD/.env
volumes:
- "$ROOT/data:/data"
- "$ROOT/mail:/mail"
- "$ROOT/overrides:/overrides"
depends_on:
- front
smtp:
image: $DOCKER_ORG/postfix:$VERSION
restart: 'no'
env_file: $PWD/.env
volumes:
- "$ROOT/data:/data"
- "$ROOT/overrides:/overrides"
depends_on:
- front
antispam:
image: $DOCKER_ORG/rspamd:$VERSION
restart: 'no'
env_file: $PWD/.env
volumes:
- "$ROOT/filter:/var/lib/rspamd"
- "$ROOT/dkim:/dkim"
- "$ROOT/overrides/rspamd:/etc/rspamd/override.d"
depends_on:
- front
antivirus:
image: $DOCKER_ORG/$ANTIVIRUS:$VERSION
restart: 'no'
env_file: $PWD/.env
volumes:
- "$ROOT/filter:/data"
webdav:
image: $DOCKER_ORG/$WEBDAV:$VERSION
restart: 'no'
env_file: $PWD/.env
volumes:
- "$ROOT/dav:/data"
admin:
image: $DOCKER_ORG/admin:$VERSION
restart: 'no'
env_file: $PWD/.env
volumes:
- "$ROOT/data:/data"
- "$ROOT/dkim:/dkim"
- /var/run/docker.sock:/var/run/docker.sock:ro
depends_on:
- redis
webmail:
image: "$DOCKER_ORG/$WEBMAIL:$VERSION"
restart: 'no'
env_file: $PWD/.env
volumes:
- "$ROOT/webmail:/data"
depends_on:
- imap
fetchmail:
image: $DOCKER_ORG/fetchmail:$VERSION
restart: 'no'
env_file: $PWD/.env
volumes:
- "$ROOT/data:/data"

@ -0,0 +1,57 @@
#!/bin/bash
containers=(
webmail
imap
smtp
antispam
admin
redis
antivirus
webdav
# fetchmail
front
)
# Time to sleep in minutes after starting the containers
WAIT=1
containers_check() {
status=0
for container in "${containers[@]}"; do
name="${DOCKER_ORG}_${container}_1"
echo "Checking $name"
docker inspect "$name" | grep '"Status": "running"' || status=1
done
docker ps -a
return $status
}
container_logs() {
for container in "${containers[@]}"; do
name="${DOCKER_ORG}_${container}_1"
echo "Showing logs for $name"
docker container logs "$name"
done
}
clean() {
docker-compose -f tests/compose/run.yml -p $DOCKER_ORG down || exit 1
rm -fv .env
}
# Cleanup before callig exit
die() {
clean
exit $1
}
for file in tests/compose/*.env ; do
cp $file .env
docker-compose -f tests/compose/run.yml -p $DOCKER_ORG up -d
echo -e "\nSleeping for ${WAIT} minutes" # Clean terminal distortion from docker-compose in travis
travis_wait sleep ${WAIT}m || sleep ${WAIT}m #Fallback sleep for local run
container_logs
containers_check || die 1
clean
done

@ -0,0 +1,4 @@
#!/bin/bash
docker login -u $DOCKER_UN -p $DOCKER_PW
docker-compose -f tests/build.yml push

@ -24,4 +24,7 @@ COPY default.ini /default.ini
COPY start.py /start.py COPY start.py /start.py
EXPOSE 80/tcp
VOLUME ["/data"]
CMD /start.py CMD /start.py

@ -18,4 +18,7 @@ os.makedirs(base + "configs", exist_ok=True)
convert("/default.ini", "/data/_data_/_default_/domains/default.ini") convert("/default.ini", "/data/_data_/_default_/domains/default.ini")
convert("/config.ini", "/data/_data_/_default_/configs/config.ini") convert("/config.ini", "/data/_data_/_default_/configs/config.ini")
os.system("chown -R www-data:www-data /data")
os.execv("/usr/local/bin/apache2-foreground", ["apache2-foreground"]) os.execv("/usr/local/bin/apache2-foreground", ["apache2-foreground"])

@ -25,4 +25,7 @@ COPY config.inc.php /var/www/html/config/
COPY start.sh /start.sh COPY start.sh /start.sh
EXPOSE 80/tcp
VOLUME ["/data"]
CMD ["/start.sh"] CMD ["/start.sh"]

Loading…
Cancel
Save