diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 00000000..7195e58e --- /dev/null +++ b/.mergify.yml @@ -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 diff --git a/.travis.yml b/.travis.yml index 2ee30837..d5114c0d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,11 @@ -language: python -python: - - "3.6" -install: - - pip install -r docs/requirements.txt +sudo: required +services: docker +addons: + apt: + packages: + - docker-ce +env: + - VERSION=$TRAVIS_BRANCH + script: - - sphinx-versioning build -b -B 1.5 -r 1.5 -w '^[0-9.]*$' -w master -W '^$' docs/ build/ - - python "docs/conf.py" "build" "$DEPLOY_HOST" "$DEPLOY_USERNAME" "$DEPLOY_PASSWORD" "$DEPLOY_REMOTEDIR" +- docker-compose -f tests/build.yml -p Mailu build diff --git a/README.md b/README.md index ccef2e7f..c4354b28 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -![Logo](docs/assets/logo.png) +

Mailu

+ Mailu is a simple yet full-featured mail server as a set of Docker images. It is free software (both as in free beer and as in free speech), open to diff --git a/core/admin/Dockerfile b/core/admin/Dockerfile index 0adc626c..08de0e88 100644 --- a/core/admin/Dockerfile +++ b/core/admin/Dockerfile @@ -17,5 +17,6 @@ COPY start.sh /start.sh RUN pybabel compile -d mailu/translations EXPOSE 80/tcp +VOLUME ["/data"] CMD ["/start.sh"] diff --git a/core/admin/mailu/internal/__init__.py b/core/admin/mailu/internal/__init__.py index 45084fe5..6419ad10 100644 --- a/core/admin/mailu/internal/__init__.py +++ b/core/admin/mailu/internal/__init__.py @@ -1,3 +1,5 @@ +from flask_limiter import RateLimitExceeded + from mailu import limiter import socket @@ -6,6 +8,14 @@ import flask internal = flask.Blueprint('internal', __name__) +@internal.app_errorhandler(RateLimitExceeded) +def rate_limit_handler(e): + response = flask.Response() + response.headers['Auth-Status'] = 'Authentication rate limit from one source exceeded' + response.headers['Auth-Error-Code'] = '451 4.3.2' + if int(flask.request.headers['Auth-Login-Attempt']) < 10: + response.headers['Auth-Wait'] = '3' + return response @limiter.request_filter def whitelist_webmail(): diff --git a/core/admin/mailu/ui/forms.py b/core/admin/mailu/ui/forms.py index c5ce5798..326d721b 100644 --- a/core/admin/mailu/ui/forms.py +++ b/core/admin/mailu/ui/forms.py @@ -6,6 +6,7 @@ import flask_login import flask_wtf import re +LOCALPART_REGEX = "^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+$" class DestinationField(fields.SelectMultipleField): """ Allow for multiple emails selection from current user choices and @@ -49,7 +50,7 @@ class DomainForm(flask_wtf.FlaskForm): max_quota_bytes = fields_.IntegerSliderField(_('Maximum user quota'), default=0) signup_enabled = fields.BooleanField(_('Enable sign-up'), default=False) comment = fields.StringField(_('Comment')) - submit = fields.SubmitField(_('Create')) + submit = fields.SubmitField(_('Save')) class DomainSignupForm(flask_wtf.FlaskForm): @@ -63,18 +64,18 @@ class DomainSignupForm(flask_wtf.FlaskForm): class AlternativeForm(flask_wtf.FlaskForm): name = fields.StringField(_('Alternative name'), [validators.DataRequired()]) - submit = fields.SubmitField(_('Create')) + submit = fields.SubmitField(_('Save')) class RelayForm(flask_wtf.FlaskForm): name = fields.StringField(_('Relayed domain name'), [validators.DataRequired()]) smtp = fields.StringField(_('Remote host')) comment = fields.StringField(_('Comment')) - submit = fields.SubmitField(_('Create')) + submit = fields.SubmitField(_('Save')) class UserForm(flask_wtf.FlaskForm): - localpart = fields.StringField(_('E-mail'), [validators.DataRequired()]) + localpart = fields.StringField(_('E-mail'), [validators.DataRequired(), validators.Regexp(LOCALPART_REGEX)]) pw = fields.PasswordField(_('Password'), [validators.DataRequired()]) pw2 = fields.PasswordField(_('Confirm password'), [validators.EqualTo('pw')]) quota_bytes = fields_.IntegerSliderField(_('Quota'), default=1000000000) @@ -86,7 +87,7 @@ class UserForm(flask_wtf.FlaskForm): class UserSignupForm(flask_wtf.FlaskForm): - localpart = fields.StringField(_('Email address'), [validators.DataRequired(), validators.Regexp("^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+$")]) + localpart = fields.StringField(_('Email address'), [validators.DataRequired(), validators.Regexp(LOCALPART_REGEX)]) pw = fields.PasswordField(_('Password'), [validators.DataRequired()]) pw2 = fields.PasswordField(_('Confirm password'), [validators.EqualTo('pw')]) captcha = flask_wtf.RecaptchaField() @@ -129,7 +130,7 @@ class TokenForm(flask_wtf.FlaskForm): ip = fields.StringField( _('Authorized IP'), [validators.Optional(), validators.IPAddress()] ) - submit = fields.SubmitField(_('Create')) + submit = fields.SubmitField(_('Save')) class AliasForm(flask_wtf.FlaskForm): @@ -138,7 +139,7 @@ class AliasForm(flask_wtf.FlaskForm): _('Use SQL LIKE Syntax (e.g. for catch-all aliases)')) destination = DestinationField(_('Destination')) comment = fields.StringField(_('Comment')) - submit = fields.SubmitField(_('Create')) + submit = fields.SubmitField(_('Save')) class AdminForm(flask_wtf.FlaskForm): diff --git a/core/admin/mailu/ui/templates/sidebar.html b/core/admin/mailu/ui/templates/sidebar.html index 7ba86e1f..c3a2ced6 100644 --- a/core/admin/mailu/ui/templates/sidebar.html +++ b/core/admin/mailu/ui/templates/sidebar.html @@ -32,7 +32,9 @@ + {% if current_user.manager_of or current_user.global_admin %}
  • {% trans %}Administration{% endtrans %}
  • + {% endif %} {% if current_user.global_admin %}
  • diff --git a/core/admin/start.sh b/core/admin/start.sh index 4f60e39d..8208e4a1 100755 --- a/core/admin/start.sh +++ b/core/admin/start.sh @@ -2,4 +2,4 @@ python manage.py advertise python manage.py db upgrade -gunicorn -w 4 -b 0.0.0.0:80 -b [::]:80 --access-logfile - --error-logfile - --preload mailu:app +gunicorn -w 4 -b :80 --access-logfile - --error-logfile - --preload mailu:app diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index cacfe354..36effc6a 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -1,14 +1,17 @@ -FROM alpine:edge +FROM alpine:3.7 RUN echo "@testing http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories \ && apk add --no-cache \ dovecot dovecot-sqlite dovecot-pigeonhole-plugin dovecot-pigeonhole-plugin-extdata \ - rspamd-client@testing python py-jinja2 + dovecot-fts-lucene rspamd-client@testing python py-jinja2 py-pip \ + && pip install --upgrade pip \ + && pip install tenacity COPY conf /conf COPY sieve /var/lib/dovecot COPY start.py /start.py EXPOSE 110/tcp 143/tcp 993/tcp 4190/tcp 2525/tcp +VOLUME ["/data", "/mail"] CMD /start.py diff --git a/core/dovecot/conf/dovecot.conf b/core/dovecot/conf/dovecot.conf index 94c43901..a5973bf8 100644 --- a/core/dovecot/conf/dovecot.conf +++ b/core/dovecot/conf/dovecot.conf @@ -18,6 +18,20 @@ dict { sieve = sqlite:/etc/dovecot/pigeonhole-sieve.dict } +############### +# Full-text search +############### +mail_plugins = $mail_plugins fts fts_lucene + +plugin { + fts = lucene + + fts_autoindex = yes + fts_autoindex_exclude = \Junk + + fts_lucene = whitespace_chars=@. +} + ############### # Mailboxes ############### @@ -32,7 +46,7 @@ mail_access_groups = mail maildir_stat_dirs = yes mailbox_list_index = yes mail_vsize_bg_after_count = 100 -mail_plugins = $mail_plugins quota quota_clone +mail_plugins = $mail_plugins quota quota_clone zlib namespace inbox { inbox = yes @@ -58,6 +72,14 @@ plugin { quota = count:User quota quota_vsizes = yes quota_clone_dict = redis:host={{ REDIS_ADDRESS }}:port=6379:db=1 + + {% if COMPRESSION in [ 'gz', 'bz2' ] %} + zlib_save = {{ COMPRESSION }} + {% endif %} + + {% if COMPRESSION_LEVEL %} + zlib_save_level = {{ COMPRESSION_LEVEL }} + {% endif %} } ############### diff --git a/core/dovecot/sieve/report-ham.sieve b/core/dovecot/sieve/report-ham.sieve index 89962067..1ad8abdf 100644 --- a/core/dovecot/sieve/report-ham.sieve +++ b/core/dovecot/sieve/report-ham.sieve @@ -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"; diff --git a/core/dovecot/start.py b/core/dovecot/start.py index 83f91fab..0aa7a365 100755 --- a/core/dovecot/start.py +++ b/core/dovecot/start.py @@ -4,15 +4,20 @@ import jinja2 import os import socket import glob +import tenacity +from tenacity import retry 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(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")) +@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["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/*"): convert(dovecot_file, os.path.join("/etc/dovecot", os.path.basename(dovecot_file))) diff --git a/core/nginx/Dockerfile b/core/nginx/Dockerfile index 3be4b50f..adb785d8 100644 --- a/core/nginx/Dockerfile +++ b/core/nginx/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:edge +FROM alpine:3.7 RUN apk add --no-cache nginx nginx-mod-mail python py-jinja2 certbot openssl @@ -6,5 +6,6 @@ COPY conf /conf 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 +VOLUME ["/certs"] CMD /start.py diff --git a/core/nginx/conf/nginx.conf b/core/nginx/conf/nginx.conf index 8b3d9be1..8fcda1c3 100644 --- a/core/nginx/conf/nginx.conf +++ b/core/nginx/conf/nginx.conf @@ -84,15 +84,17 @@ http { # Actual logic {% if WEBMAIL != 'none' %} + {% if WEB_WEBMAIL != '/' %} location / { return 301 {{ WEB_WEBMAIL }}; } + {% endif %} location {{ WEB_WEBMAIL }} { rewrite ^({{ WEB_WEBMAIL }})$ $1/ permanent; rewrite ^{{ WEB_WEBMAIL }}/(.*) /$1 break; include /etc/nginx/proxy.conf; - client_max_body_size 30M; + client_max_body_size {{ MESSAGE_SIZE_LIMIT|int + 8388608 }}; proxy_pass http://$webmail; } {% endif %} diff --git a/core/postfix/Dockerfile b/core/postfix/Dockerfile index bb5831a2..bb7acb9b 100644 --- a/core/postfix/Dockerfile +++ b/core/postfix/Dockerfile @@ -1,10 +1,13 @@ -FROM alpine +FROM alpine:3.7 -RUN apk add --no-cache postfix postfix-sqlite postfix-pcre rsyslog python py-jinja2 +RUN apk add --no-cache postfix postfix-sqlite postfix-pcre rsyslog python py-jinja2 py-pip \ + && pip install --upgrade pip \ + && pip install tenacity COPY conf /conf COPY start.py /start.py EXPOSE 25/tcp 10025/tcp +VOLUME ["/data"] CMD /start.py diff --git a/core/postfix/conf/master.cf b/core/postfix/conf/master.cf index cbcc5e56..15fd62dc 100644 --- a/core/postfix/conf/master.cf +++ b/core/postfix/conf/master.cf @@ -8,6 +8,7 @@ smtp inet n - n - - smtpd 10025 inet n - n - - smtpd -o smtpd_sasl_auth_enable=yes -o smtpd_recipient_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 outclean unix n - n - 0 cleanup -o header_checks=pcre:/etc/postfix/outclean_header_filter.cf diff --git a/core/postfix/start.py b/core/postfix/start.py index 4dbf2206..e3c13110 100755 --- a/core/postfix/start.py +++ b/core/postfix/start.py @@ -5,11 +5,17 @@ import os import socket import glob import shutil - +import tenacity +from tenacity import retry + convert = lambda 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)) +def resolve(): + os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front")) + # Actual startup script -os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front")) +resolve() os.environ["HOST_ANTISPAM"] = os.environ.get("HOST_ANTISPAM", "antispam:11332") os.environ["HOST_LMTP"] = os.environ.get("HOST_LMTP", "imap:2525") diff --git a/docs/Dockerfile b/docs/Dockerfile new file mode 100644 index 00000000..af481a27 --- /dev/null +++ b/docs/Dockerfile @@ -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;" \ No newline at end of file diff --git a/docs/assets/horizontal.png b/docs/assets/horizontal.png new file mode 100644 index 00000000..431874b4 Binary files /dev/null and b/docs/assets/horizontal.png differ diff --git a/docs/assets/horizontalv2.png b/docs/assets/horizontalv2.png new file mode 100644 index 00000000..85e13970 Binary files /dev/null and b/docs/assets/horizontalv2.png differ diff --git a/docs/assets/logomark.png b/docs/assets/logomark.png new file mode 100644 index 00000000..5921625b Binary files /dev/null and b/docs/assets/logomark.png differ diff --git a/docs/assets/logomarkv2.png b/docs/assets/logomarkv2.png new file mode 100644 index 00000000..7d0a904e Binary files /dev/null and b/docs/assets/logomarkv2.png differ diff --git a/docs/assets/vertical.png b/docs/assets/vertical.png new file mode 100644 index 00000000..f870a03c Binary files /dev/null and b/docs/assets/vertical.png differ diff --git a/docs/compose/.env b/docs/compose/.env index 06038bc8..721aaf22 100644 --- a/docs/compose/.env +++ b/docs/compose/.env @@ -87,6 +87,12 @@ 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 ################################### @@ -126,3 +132,6 @@ REAL_IP_HEADER= # IPs for nginx set_real_ip_from (CIDR list separated by commas) REAL_IP_FROM= + +# choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) +REJECT_UNLISTED_RECIPIENT= diff --git a/docs/compose/setup.rst b/docs/compose/setup.rst index 64e2fa22..8759e2f1 100644 --- a/docs/compose/setup.rst +++ b/docs/compose/setup.rst @@ -92,7 +92,7 @@ setting. The configuration option must be one of the following: - ``none`` disables antivirus checks; - ``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. If you run Mailu behind a reverse proxy you can use ``REAL_IP_HEADER`` and diff --git a/docs/conf.py b/docs/conf.py index 7a378132..f89b39fd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -7,7 +7,7 @@ templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' project = 'Mailu' -copyright = '2017, Mailu authors' +copyright = '2018, Mailu authors' author = 'Mailu authors' version = release = 'latest' language = None @@ -23,7 +23,7 @@ htmlhelp_basename = 'Mailudoc' # to template names. html_sidebars = { '**': [ - 'relations.html', # needs 'show_related': True theme option to display + 'relations.html', 'searchbox.html', ] } @@ -36,24 +36,3 @@ html_context = { 'github_version': 'master', '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) - ) diff --git a/docs/contributors/environment.rst b/docs/contributors/environment.rst index 0aac71f4..a1cce193 100644 --- a/docs/contributors/environment.rst +++ b/docs/contributors/environment.rst @@ -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 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 + 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 diff --git a/docs/index.rst b/docs/index.rst index 0920bb96..5219145f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -55,7 +55,7 @@ the version of Mailu that you are running. configuration compose/requirements compose/setup - kubernetes/index + kubernetes/stable/index dns reverse diff --git a/docs/kubernetes/1.6/README.md b/docs/kubernetes/1.6/README.md new file mode 100644 index 00000000..c0dd935b --- /dev/null +++ b/docs/kubernetes/1.6/README.md @@ -0,0 +1,157 @@ +# Install Mailu master on kubernetes + +## Prequisites + +### Structure + +There's chosen to have a double NGINX stack for Mailu, this way the main ingress can still be used to access other websites/domains on your cluster. This is the current structure: + +- `NGINX Ingress controller`: Listens to the nodes ports 80 & 443 and directly forwards all TCP traffic on the E-amail ports (993,143,25,587,...). This is because this `DaemonSet` already consumes ports 80 & 443 and uses `hostNetwork: true` +- `Cert manager`: Creates automatic Lets Encrypt certificates based on an `Ingress`-objects domain name. +- `Mailu NGINX Front container`: This container receives all the mail traffic forwarded from the ingress controller. The web traffic is also forwarded based on an ingress +- `Mailu components`: All Mailu components are split into separate files to make them more + +### What you need +- A working Kubernetes cluster (tested with 1.10.5) +- A working [cert-manager](https://github.com/jetstack/cert-manager) installation +- A working nginx-ingress controller needed for the lets-encrypt certificates. You can find those files in the `nginx` subfolder + +#### Cert manager + +The `Cert-manager` is quite easy to deploy using Helm when reading the [docs](https://cert-manager.readthedocs.io/en/latest/getting-started/2-installing.html). +After booting the `Cert-manager` you'll need a `ClusterIssuer` which takes care of all required certificates through `Ingress` items. An example: + +```yaml +apiVersion: certmanager.k8s.io/v1alpha1 +kind: ClusterIssuer +metadata: + name: letsencrypt-prod +spec: + acme: + email: something@example.com + http01: {} + privateKeySecretRef: + key: "" + name: letsencrypt-stage + server: https://acme-v02.api.letsencrypt.org/directory +``` + +## Deploying Mailu + +All manifests can be found in the `mailu` subdirectory. All commands below need to be run from this subdirectory + +### Personalization +- All services run in the same namespace, currently `mailu-mailserver`. So if you want to use a different one, change the `namespace` value in **every** file +- Check the `storage-class` field in the `pvc.yaml` file, you can also change the sizes to your liking. Note that you need `RWX` (read-write-many) and `RWO` (read-write-once) storageclasses. +- Check the `configmap.yaml` and adapt it to your needs. Be sure to check the kubernetes DNS values at the end (if you use a different namespace) +- Check the `ingress-ssl.yaml` and change it to the domain you want (this is for the kubernetes ingress controller, it will forward to `mailu/nginx` a.k.a. the `front` pod) + +## Installation +First run the command to start Mailu: + +```bash +kubectl create -f rbac.yaml +kubectl create -f configmap.yaml +kubectl create -f pvc.yaml +kubectl create -f ingress-ssl.yaml +kubectl create -f redis.yaml +kubectl create -f front.yaml +kubectl create -f webmail.yaml +kubectl create -f imap.yaml +kubectl create -f security.yaml +kubectl create -f smtp.yaml +kubectl create -f fetchmail.yaml +kubectl create -f admin.yaml +kubectl create -f webdav.yaml +``` + +## Create the first admin account + +When the cluster is online you need to create you master user to access `https://mail.example.com/admin`. +Enter the main `admin` pod to create the root account: + +```bash +kubectl -n mailu-mailserver get po +kubectl -n mailu-mailserver exec -it mailu-admin-.... /bin/sh +``` + +And in the pod run the following command. The command uses following entries: +- `admin` Make it an admin user +- `root` The first part of the e-mail adres (ROOT@example.com) +- `example.com` the domain appendix +- `password` the chosen password for the user + +```bash +python manage.py admin root example.com password +``` + +Now you should be able to login on the mail account: `https://mail.example.com/admin` + +## Adaptations + +### Postfix +I noticed you need an override for the `postfix` server in order to be able to send mail. I noticed Google wasn't able to deliver mail to my account and it had to do with the `smtpd_authorized_xclient_hosts` value in the config file. The config can be read [here](https://github.com/hacor/Mailu/blob/master/core/postfix/conf/main.cf#L35) and is pointing to a single IP of the service. But the requests come from the host IPs (the NGINX Ingress proxy) and they don't use the service specific IP. + +Enter the `postfix` pod: + +```bash +kubectl -n mailu-mailserver get po +kubectl -n mailu-mailserver exec -it mailu-smtp-.... /bin/sh +``` + +Now you're in the pod, create an override file like so: + +```bash +vi /overrides/postfix.cf +``` + +And give it the following contents, off course replacing `10.2.0.0/16` with the CIDR of your pod range. This way the NGINX pods can also restart and your mail server will still operate + +```bash +not_needed = true +smtpd_authorized_xclient_hosts = 10.2.0.0/16 +``` + +The first line seems stupid, but is needed because its pasted after a #, so from the second line we're really in action. +Save and close the file and exit. Now you need to delete the pod in order to recreate the config file. + +```bash +kubectl -n mailu-mailserver delete po/mailu-smtp-.... +``` + +### Dovecot +- If you are using Dovecot on a shared file system (Glusterfs, NFS,...), you need to create a special override otherwise a lot of indexing errors will occur on your Dovecot pod. +- I also higher the number of max connections per IP. Now it's limited to 10. +Enter the dovecot pod: + +```bash +kubectl -n mailu-mailserver get po +kubectl -n mailu-mailserver exec -it mailu-imap-.... /bin/sh +``` + +Create the file `/overrides/dovecot.conf` + +```bash +vi /overrides/dovecot.conf +``` + +And enter following contents: +```bash +mail_nfs_index = yes +mail_nfs_storage = yes +mail_fsync = always +mmap_disable = yes +mail_max_userip_connections=100 +``` + +Save and close the file and delete the imap pod to get it recreated. + +```bash +kubectl -n mailu-mailserver delete po/mailu-imap-.... +``` + +Wait for the pod to recreate and you're online! +Happy mailing! + +Wait for the pod to recreate and you're online! +Happy mailing! \ No newline at end of file diff --git a/docs/kubernetes/1.6/mailu/admin.yaml b/docs/kubernetes/1.6/mailu/admin.yaml new file mode 100644 index 00000000..b36760a2 --- /dev/null +++ b/docs/kubernetes/1.6/mailu/admin.yaml @@ -0,0 +1,64 @@ + +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: mailu-admin + namespace: mailu-mailserver +spec: + replicas: 1 + template: + metadata: + labels: + app: mailu-admin + role: mail + tier: backend + spec: + containers: + - name: admin + image: mailu/admin:master + imagePullPolicy: Always + envFrom: + - configMapRef: + name: mailu-config + volumeMounts: + - name: maildata + mountPath: /data + subPath: maildata + - name: maildata + mountPath: /dkim + subPath: dkim + ports: + - name: http + containerPort: 80 + protocol: TCP + resources: + requests: + memory: 500Mi + cpu: 500m + limits: + memory: 500Mi + cpu: 500m + volumes: + - name: maildata + persistentVolumeClaim: + claimName: mail-storage +--- + +apiVersion: v1 +kind: Service +metadata: + name: admin + namespace: mailu-mailserver + labels: + app: mailu-admin + role: mail + tier: backend +spec: + selector: + app: mailu-admin + role: mail + tier: backend + ports: + - name: http + port: 80 + protocol: TCP \ No newline at end of file diff --git a/docs/kubernetes/1.6/mailu/configmap.yaml b/docs/kubernetes/1.6/mailu/configmap.yaml new file mode 100644 index 00000000..9ebce8b1 --- /dev/null +++ b/docs/kubernetes/1.6/mailu/configmap.yaml @@ -0,0 +1,153 @@ + apiVersion: v1 + kind: ConfigMap + metadata: + name: mailu-config + namespace: mailu-mailserver + data: + # 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: "YourKeyHere" + + # Address where listening ports should bind + BIND_ADDRESS4: "127.0.0.1" + #BIND_ADDRESS6: "::1" + + # Main mail domain + DOMAIN: "example.com" + + # Hostnames for this server, separated with comas + HOSTNAMES: "mail.example.com" + + # 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: "true" + # Run the admin interface in debug mode + #DEBUG: "True" + + # Choose which webmail to run if any (values: roundcube, rainloop, none) + WEBMAIL: "roundcube" + + # Dav server implementation (value: radicale, none) + WEBDAV: "radicale" + + # Antivirus solution (value: clamav, none) + ANTIVIRUS: "clamav" + + ################################### + # 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) + # For kubernetes this is the CIDR of the pod network + RELAYNETS: "10.2.0.0/16" + POD_ADDRESS_RANGE: "10.2.0.0/16" + + + # Will relay all outgoing mails if configured + #RELAYHOST= + + # This part is needed for the XCLIENT login for postfix. This should be the POD ADDRESS range + FRONT_ADDRESS: "front.mailu-mailserver.svc.cluster.local" + + # 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: "root" + DMARC_RUF: "root" + + # 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!" + + ################################### + # Web settings + ################################### + + # Path to the admin interface if enabled + WEB_ADMIN: "/admin" + + # Path to the webmail if enabled + WEB_WEBMAIL: "/webmail" + + # Website name + SITENAME: "AppSynth" + + # Linked Website URL + WEBSITE: "https://example.com" + + # 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: + + # Host settings + HOST_IMAP: "imap.mailu-mailserver.svc.cluster.local" + HOST_POP3: "imap.mailu-mailserver.svc.cluster.local" + HOST_SMTP: "smtp.mailu-mailserver.svc.cluster.local" + HOST_AUTHSMTP: "smtp.mailu-mailserver.svc.cluster.local" + HOST_WEBMAIL: "webmail.mailu-mailserver.svc.cluster.local" + HOST_ADMIN: "admin.mailu-mailserver.svc.cluster.local" + HOST_WEBDAV: "webdav.mailu-mailserver.svc.cluster.local:5232" + HOST_ANTISPAM: "antispam.mailu-mailserver.svc.cluster.local:11332" + HOST_REDIS: "redis.mailu-mailserver.svc.cluster.local" diff --git a/docs/kubernetes/1.6/mailu/fetchmail.yaml b/docs/kubernetes/1.6/mailu/fetchmail.yaml new file mode 100644 index 00000000..cf3271e7 --- /dev/null +++ b/docs/kubernetes/1.6/mailu/fetchmail.yaml @@ -0,0 +1,39 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: mailu-fetchmail + namespace: mailu-mailserver +spec: + replicas: 1 + template: + metadata: + labels: + app: mailu-fetchmail + role: mail + tier: backend + spec: + containers: + - name: fetchmail + image: mailu/fetchmail:master + imagePullPolicy: Always + envFrom: + - configMapRef: + name: mailu-config + volumeMounts: + - name: maildata + mountPath: /data + subPath: maildata + ports: + - containerPort: 5232 + - containerPort: 80 + resources: + requests: + memory: 100Mi + cpu: 100m + limits: + memory: 100Mi + cpu: 100m + volumes: + - name: maildata + persistentVolumeClaim: + claimName: mail-storage \ No newline at end of file diff --git a/docs/kubernetes/1.6/mailu/front.yaml b/docs/kubernetes/1.6/mailu/front.yaml new file mode 100644 index 00000000..e25ac828 --- /dev/null +++ b/docs/kubernetes/1.6/mailu/front.yaml @@ -0,0 +1,129 @@ + +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: mailu-front + namespace: mailu-mailserver +spec: + replicas: 1 + template: + metadata: + labels: + app: mailu-front + role: mail + tier: backend + spec: + restartPolicy: Always + terminationGracePeriodSeconds: 60 + containers: + - name: front + image: mailu/nginx:latest + imagePullPolicy: Always + envFrom: + - configMapRef: + name: mailu-config + volumeMounts: + - name: certs + mountPath: /certs + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + - name: pop3 + containerPort: 110 + protocol: TCP + - name: pop3s + containerPort: 995 + protocol: TCP + - name: imap + containerPort: 143 + protocol: TCP + - name: imaps + containerPort: 993 + protocol: TCP + - name: smtp + containerPort: 25 + protocol: TCP + - name: smtp-auth + containerPort: 10025 + protocol: TCP + - name: imap-auth + containerPort: 10143 + protocol: TCP + - name: smtps + containerPort: 465 + protocol: TCP + - name: smtpd + containerPort: 587 + protocol: TCP + - name: auth + containerPort: 8000 + protocol: TCP + resources: + requests: + memory: 100Mi + cpu: 100m + limits: + memory: 200Mi + cpu: 200m + volumes: + - name: certs + secret: + items: + - key: tls.crt + path: cert.pem + - key: tls.key + path: key.pem + secretName: letsencrypt-certs-all +--- +apiVersion: v1 +kind: Service +metadata: + name: front + namespace: mailu-mailserver + labels: + app: mailu-admin + role: mail + tier: backend +spec: + selector: + app: mailu-front + role: mail + tier: backend + ports: + - name: http + port: 80 + protocol: TCP + - name: https + port: 443 + protocol: TCP + - name: pop3 + port: 110 + protocol: TCP + - name: pop3s + port: 995 + protocol: TCP + - name: imap + port: 143 + protocol: TCP + - name: imaps + port: 993 + protocol: TCP + - name: smtp + port: 25 + protocol: TCP + - name: smtps + port: 465 + protocol: TCP + - name: smtpd + port: 587 + protocol: TCP + - name: smtp-auth + port: 10025 + protocol: TCP + - name: imap-auth + port: 10143 + protocol: TCP diff --git a/docs/kubernetes/1.6/mailu/imap.yaml b/docs/kubernetes/1.6/mailu/imap.yaml new file mode 100644 index 00000000..069b7730 --- /dev/null +++ b/docs/kubernetes/1.6/mailu/imap.yaml @@ -0,0 +1,80 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: mailu-imap + namespace: mailu-mailserver +spec: + replicas: 1 + template: + metadata: + labels: + app: mailu-imap + role: mail + tier: backend + spec: + containers: + - name: imap + image: mailu/dovecot:master + imagePullPolicy: Always + envFrom: + - configMapRef: + name: mailu-config + volumeMounts: + - mountPath: /data + name: maildata + subPath: maildata + - mountPath: /mail + name: maildata + subPath: mailstate + - mountPath: /overrides + name: maildata + subPath: overrides + ports: + - containerPort: 2102 + - containerPort: 2525 + - containerPort: 143 + - containerPort: 993 + - containerPort: 4190 + resources: + requests: + memory: 500Mi + cpu: 500m + limits: + memory: 1Gi + cpu: 1000m + volumes: + - name: maildata + persistentVolumeClaim: + claimName: mail-storage +--- +apiVersion: v1 +kind: Service +metadata: + name: imap + namespace: mailu-mailserver + labels: + app: mailu + role: mail + tier: backend +spec: + selector: + app: mailu-imap + role: mail + tier: backend + ports: + ports: + - name: imap-auth + port: 2102 + protocol: TCP + - name: imap-transport + port: 2525 + protocol: TCP + - name: imap-default + port: 143 + protocol: TCP + - name: imap-ssl + port: 993 + protocol: TCP + - name: sieve + port: 4190 + protocol: TCP \ No newline at end of file diff --git a/docs/kubernetes/1.6/mailu/ingress-ssl.yaml b/docs/kubernetes/1.6/mailu/ingress-ssl.yaml new file mode 100644 index 00000000..61ae3cf7 --- /dev/null +++ b/docs/kubernetes/1.6/mailu/ingress-ssl.yaml @@ -0,0 +1,32 @@ +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: mailu-ssl-ingress + namespace: mailu-mailserver + annotations: + kubernetes.io/ingress.class: tectonic + kubernetes.io/tls-acme: "true" + nginx.ingress.kubernetes.io/proxy-body-size: "0" + ingress.kubernetes.io/ssl-redirect: "true" + # Replace letsencrypt-prod with the name of the certificate issuer + certmanager.k8s.io/cluster-issuer: letsencrypt-prod + #ingress.kubernetes.io/rewrite-target: "/" + #ingress.kubernetes.io/app-root: "/ui" + #ingress.kubernetes.io/follow-redirects: "true" + labels: + app: mailu + role: mail + tier: backend +spec: + tls: + - hosts: + - "mail.example.com" + secretName: letsencrypt-certs-all # If unsure how to generate these, check out https://github.com/ployst/docker-letsencrypt + rules: + - host: "mail.example.com" + http: + paths: + - path: "/" + backend: + serviceName: front + servicePort: 80 \ No newline at end of file diff --git a/docs/kubernetes/1.6/mailu/pvc.yaml b/docs/kubernetes/1.6/mailu/pvc.yaml new file mode 100644 index 00000000..0ec2852f --- /dev/null +++ b/docs/kubernetes/1.6/mailu/pvc.yaml @@ -0,0 +1,27 @@ +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: redis-hdd + namespace: mailu-mailserver + annotations: + volume.beta.kubernetes.io/storage-class: "glusterblock-hdd" +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: mail-storage + namespace: mailu-mailserver + annotations: + volume.beta.kubernetes.io/storage-class: "gluster-heketi-hdd" +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 100Gi diff --git a/docs/kubernetes/1.6/mailu/rbac.yaml b/docs/kubernetes/1.6/mailu/rbac.yaml new file mode 100644 index 00000000..33255130 --- /dev/null +++ b/docs/kubernetes/1.6/mailu/rbac.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: mailu-mailserver \ No newline at end of file diff --git a/docs/kubernetes/1.6/mailu/redis.yaml b/docs/kubernetes/1.6/mailu/redis.yaml new file mode 100644 index 00000000..d6bb1eb8 --- /dev/null +++ b/docs/kubernetes/1.6/mailu/redis.yaml @@ -0,0 +1,56 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: mailu-redis + namespace: mailu-mailserver +spec: + replicas: 1 + template: + metadata: + labels: + app: mailu-redis + role: mail + tier: backend + spec: + containers: + - name: redis + image: redis:4.0-alpine + imagePullPolicy: Always + volumeMounts: + - mountPath: /data + name: redisdata + ports: + - containerPort: 6379 + name: redis + protocol: TCP + resources: + requests: + memory: 200Mi + cpu: 100m + limits: + memory: 300Mi + cpu: 200m + volumes: + - name: redisdata + persistentVolumeClaim: + claimName: redis-hdd +--- + +apiVersion: v1 +kind: Service +metadata: + name: redis + namespace: mailu-mailserver + labels: + app: mailu-redis + role: mail + tier: backend +spec: + selector: + app: mailu-redis + role: mail + tier: backend + ports: + - name: redis + port: 6379 + protocol: TCP \ No newline at end of file diff --git a/docs/kubernetes/1.6/mailu/security.yaml b/docs/kubernetes/1.6/mailu/security.yaml new file mode 100644 index 00000000..c1c1ac0b --- /dev/null +++ b/docs/kubernetes/1.6/mailu/security.yaml @@ -0,0 +1,110 @@ + +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: mailu-security + namespace: mailu-mailserver +spec: + replicas: 1 + template: + metadata: + labels: + app: mailu-security + role: mail + tier: backend + spec: + containers: + - name: antispam + image: mailu/rspamd:master + imagePullPolicy: Always + envFrom: + - configMapRef: + name: mailu-config + resources: + requests: + memory: 100Mi + cpu: 100m + limits: + memory: 200Mi + cpu: 200m + ports: + - name: antispam + containerPort: 11332 + protocol: TCP + volumeMounts: + - name: filter + subPath: filter + mountPath: /var/lib/rspamd + - name: filter + mountPath: /dkim + subPath: dkim + - name: filter + mountPath: /etc/rspamd/override.d + subPath: rspamd-overrides + - name: antivirus + image: mailu/clamav:master + imagePullPolicy: Always + resources: + requests: + memory: 1Gi + cpu: 1000m + limits: + memory: 2Gi + cpu: 1000m + envFrom: + - configMapRef: + name: mailu-config + ports: + - name: antivirus + containerPort: 3310 + protocol: TCP + volumeMounts: + - name: filter + subPath: filter + mountPath: /data + volumes: + - name: filter + persistentVolumeClaim: + claimName: mail-storage + +--- + +apiVersion: v1 +kind: Service +metadata: + name: antispam + namespace: mailu-mailserver + labels: + app: mailu-antispam + role: mail + tier: backend +spec: + selector: + app: mailu-security + role: mail + tier: backend + ports: + - name: antispam + port: 11332 + protocol: TCP + +--- + +apiVersion: v1 +kind: Service +metadata: + name: antivirus + namespace: mailu-mailserver + labels: + app: mailu-antivirus + role: mail + tier: backend +spec: + selector: + app: mailu-security + role: mail + tier: backend + ports: + - name: antivirus + port: 3310 + protocol: TCP \ No newline at end of file diff --git a/docs/kubernetes/1.6/mailu/smtp.yaml b/docs/kubernetes/1.6/mailu/smtp.yaml new file mode 100644 index 00000000..454b8ed7 --- /dev/null +++ b/docs/kubernetes/1.6/mailu/smtp.yaml @@ -0,0 +1,80 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: mailu-smtp + namespace: mailu-mailserver +spec: + replicas: 1 + template: + metadata: + labels: + app: mailu-smtp + role: mail + tier: backend + spec: + containers: + - name: smtp + image: mailu/postfix:master + imagePullPolicy: Always + envFrom: + - configMapRef: + name: mailu-config + resources: + requests: + memory: 500Mi + cpu: 200m + limits: + memory: 1Gi + cpu: 500m + volumeMounts: + - mountPath: /data + name: maildata + subPath: maildata + - mountPath: /overrides + name: maildata + subPath: overrides + ports: + - name: smtp + containerPort: 25 + protocol: TCP + - name: smtp-ssl + containerPort: 465 + protocol: TCP + - name: smtp-starttls + containerPort: 587 + protocol: TCP + - name: smtp-auth + containerPort: 10025 + protocol: TCP + volumes: + - name: maildata + persistentVolumeClaim: + claimName: mail-storage +--- +apiVersion: v1 +kind: Service +metadata: + name: smtp + namespace: mailu-mailserver + labels: + app: mailu + role: mail + tier: backend +spec: + selector: + app: mailu-smtp + role: mail + tier: backend + ports: + - name: smtp + port: 25 + protocol: TCP + - name: smtp-ssl + port: 465 + protocol: TCP + - name: smtp-starttls + port: 587 + protocol: TCP + - name: smtp-auth + port: 10025 + protocol: TCP diff --git a/docs/kubernetes/1.6/mailu/static-ips.yaml b/docs/kubernetes/1.6/mailu/static-ips.yaml new file mode 100644 index 00000000..e69de29b diff --git a/docs/kubernetes/1.6/mailu/webdav.yaml b/docs/kubernetes/1.6/mailu/webdav.yaml new file mode 100644 index 00000000..07b7733c --- /dev/null +++ b/docs/kubernetes/1.6/mailu/webdav.yaml @@ -0,0 +1,63 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: mailu-webdav + namespace: mailu-mailserver +spec: + replicas: 1 + template: + metadata: + labels: + app: mailu-webdav + role: mail + tier: backend + spec: + containers: + - name: radicale + image: mailu/radicale:master + imagePullPolicy: Always + envFrom: + - configMapRef: + name: mailu-config + volumeMounts: + - mountPath: /data + name: maildata + subPath: dav + ports: + - containerPort: 5232 + - containerPort: 80 + resources: + requests: + memory: 100Mi + cpu: 100m + limits: + memory: 100Mi + cpu: 100m + volumes: + - name: maildata + persistentVolumeClaim: + claimName: mail-storage +--- + +apiVersion: v1 +kind: Service +metadata: + name: webdav + namespace: mailu-mailserver + labels: + app: mailu-webdav + role: mail + tier: backend +spec: + selector: + app: mailu-webdav + role: mail + tier: backend + ports: + ports: + - name: http + port: 80 + protocol: TCP + - name: http-ui + port: 5232 + protocol: TCP \ No newline at end of file diff --git a/docs/kubernetes/1.6/mailu/webmail.yaml b/docs/kubernetes/1.6/mailu/webmail.yaml new file mode 100644 index 00000000..81798782 --- /dev/null +++ b/docs/kubernetes/1.6/mailu/webmail.yaml @@ -0,0 +1,59 @@ + +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: mailu-roundcube + namespace: mailu-mailserver +spec: + replicas: 1 + template: + metadata: + labels: + app: mailu-roundcube + role: mail + tier: frontend + spec: + containers: + - name: roundcube + image: mailu/roundcube:1.5 + imagePullPolicy: Always + envFrom: + - configMapRef: + name: mailu-config + resources: + requests: + memory: 100Mi + cpu: 100m + limits: + memory: 200Mi + cpu: 200m + volumeMounts: + - mountPath: /data + name: maildata + subPath: webmail + ports: + - containerPort: 80 + volumes: + - name: maildata + persistentVolumeClaim: + claimName: mail-storage +--- +apiVersion: v1 +kind: Service +metadata: + name: webmail + namespace: mailu-mailserver + labels: + app: mailu-roundcube + role: mail + tier: frontend +spec: + selector: + app: mailu-roundcube + role: mail + tier: frontend + ports: + ports: + - name: http + port: 80 + protocol: TCP diff --git a/docs/kubernetes/1.6/nginx/default-http-backend.yaml b/docs/kubernetes/1.6/nginx/default-http-backend.yaml new file mode 100644 index 00000000..097fe7c5 --- /dev/null +++ b/docs/kubernetes/1.6/nginx/default-http-backend.yaml @@ -0,0 +1,55 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: default-http-backend + labels: + app: default-http-backend + namespace: kube-ingress +spec: + replicas: 1 + selector: + matchLabels: + app: default-http-backend + template: + metadata: + labels: + app: default-http-backend + spec: + terminationGracePeriodSeconds: 60 + containers: + - name: default-http-backend + # Any image is permissible as long as: + # 1. It serves a 404 page at / + # 2. It serves 200 on a /healthz endpoint + image: gcr.io/google_containers/defaultbackend:1.4 + livenessProbe: + httpGet: + path: /healthz + port: 8080 + scheme: HTTP + initialDelaySeconds: 30 + timeoutSeconds: 5 + ports: + - containerPort: 8080 + resources: + limits: + cpu: 10m + memory: 20Mi + requests: + cpu: 10m + memory: 20Mi +--- + +apiVersion: v1 +kind: Service +metadata: + name: default-http-backend + namespace: kube-ingress + labels: + app: default-http-backend +spec: + ports: + - port: 80 + targetPort: 8080 + selector: + app: default-http-backend \ No newline at end of file diff --git a/docs/kubernetes/1.6/nginx/nginx-ingress.yaml b/docs/kubernetes/1.6/nginx/nginx-ingress.yaml new file mode 100644 index 00000000..90b24f24 --- /dev/null +++ b/docs/kubernetes/1.6/nginx/nginx-ingress.yaml @@ -0,0 +1,139 @@ +apiVersion: v1 +kind: Service +metadata: + # keep it under 24 chars + name: appsynth-lb + namespace: kube-ingress + labels: + k8s-app: appsynth-lb + component: ingress-controller +spec: + type: ClusterIP + selector: + k8s-app: appsynth-lb + component: ingress-controller + ports: + - name: http + protocol: TCP + port: 80 + targetPort: 80 + - name: https + protocol: TCP + port: 443 + targetPort: 443 +--- +kind: ConfigMap +apiVersion: v1 +metadata: + name: udp-services + namespace: kube-ingress + +--- +kind: ConfigMap +apiVersion: v1 +metadata: + name: tcp-services + namespace: kube-ingress +data: + 25: "mailu-mailserver/front:25" + 110: "mailu-mailserver/front:110" + 465: "mailu-mailserver/front:465" + 587: "mailu-mailserver/front:587" + 143: "mailu-mailserver/front:143" + 993: "mailu-mailserver/front:993" + 995: "mailu-mailserver/front:995" + +--- +apiVersion: v1 +data: + enable-vts-status: "true" +kind: ConfigMap +metadata: + name: nginx-ingress-lb-conf + namespace: kube-ingress +--- +apiVersion: apps/v1beta2 +kind: DaemonSet +metadata: + name: ingress-controller + namespace: kube-ingress + annotations: + prometheus.io/port: "10254" + prometheus.io/scrape: "true" + labels: + k8s-app: appsynth-lb + component: ingress-controller + type: nginx +spec: + updateStrategy: + rollingUpdate: + maxUnavailable: 1 + type: RollingUpdate + selector: + matchLabels: + k8s-app: appsynth-lb + component: ingress-controller + type: nginx + template: + metadata: + labels: + k8s-app: appsynth-lb + component: ingress-controller + type: nginx + spec: + serviceAccount: kube-nginx-ingress + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/master + operator: DoesNotExist + containers: + - name: nginx-ingress-lb + image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.16.2 + args: + - /nginx-ingress-controller + - --configmap=$(POD_NAMESPACE)/tectonic-custom-error + - --default-backend-service=$(POD_NAMESPACE)/default-http-backend + #- --default-ssl-certificate=tectonic-system/tectonic-ingress-tls-secret + - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services + - --udp-services-configmap=$(POD_NAMESPACE)/udp-services + - --annotations-prefix=ingress.kubernetes.io + - --enable-ssl-passthrough + - --ingress-class=tectonic + # use downward API + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + ports: + - name: http + containerPort: 80 + hostPort: 80 + - name: https + containerPort: 443 + hostPort: 443 + readinessProbe: + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + livenessProbe: + initialDelaySeconds: 10 + timeoutSeconds: 1 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + hostNetwork: true + nodeSelector: + node-role.kubernetes.io/node: "" + dnsPolicy: ClusterFirst + restartPolicy: Always + terminationGracePeriodSeconds: 60 diff --git a/docs/kubernetes/1.6/nginx/rbac.yaml b/docs/kubernetes/1.6/nginx/rbac.yaml new file mode 100644 index 00000000..d3c01384 --- /dev/null +++ b/docs/kubernetes/1.6/nginx/rbac.yaml @@ -0,0 +1,129 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: kube-ingress +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: kube-nginx-ingress + namespace: kube-ingress +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + name: kube-nginx-ingress +rules: + - apiGroups: + - "" + resources: + - configmaps + - endpoints + - nodes + - pods + - secrets + verbs: + - list + - watch + - update + - apiGroups: + - "" + resources: + - nodes + verbs: + - get + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - apiGroups: + - "extensions" + resources: + - ingresses + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - "extensions" + resources: + - ingresses/status + verbs: + - update +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: Role +metadata: + name: kube-nginx-ingress + namespace: kube-ingress +rules: + - apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - namespaces + verbs: + - get + - apiGroups: + - "" + resources: + - configmaps + resourceNames: + - "ingress-controller-leader-nginx" + verbs: + - get + - update + - apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - apiGroups: + - "" + resources: + - endpoints + verbs: + - get + - create + - update +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: RoleBinding +metadata: + name: kube-nginx-ingress + namespace: kube-ingress +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: kube-nginx-ingress +subjects: + - kind: ServiceAccount + name: kube-nginx-ingress + namespace: kube-ingress +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + name: kube-nginx-ingress +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: kube-nginx-ingress +subjects: + - kind: ServiceAccount + name: kube-nginx-ingress + namespace: kube-ingress \ No newline at end of file diff --git a/docs/kubernetes/index.rst b/docs/kubernetes/stable/index.rst similarity index 100% rename from docs/kubernetes/index.rst rename to docs/kubernetes/stable/index.rst diff --git a/docs/kubernetes/kubernetes-mailu.yaml b/docs/kubernetes/stable/kubernetes-mailu.yaml similarity index 100% rename from docs/kubernetes/kubernetes-mailu.yaml rename to docs/kubernetes/stable/kubernetes-mailu.yaml diff --git a/docs/kubernetes/kubernetes-nginx-ingress-controller.yaml b/docs/kubernetes/stable/kubernetes-nginx-ingress-controller.yaml similarity index 100% rename from docs/kubernetes/kubernetes-nginx-ingress-controller.yaml rename to docs/kubernetes/stable/kubernetes-nginx-ingress-controller.yaml diff --git a/docs/nginx.conf b/docs/nginx.conf new file mode 100644 index 00000000..75b5be50 --- /dev/null +++ b/docs/nginx.conf @@ -0,0 +1,5 @@ +server { + listen 80; + listen [::]:80; + root /build; +} diff --git a/docs/requirements.txt b/docs/requirements.txt index 2572817f..4afd9bb6 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,3 @@ recommonmark Sphinx sphinx-autobuild sphinx-rtd-theme -sphinxcontrib-versioning -paramiko diff --git a/docs/swarm/1.5/README.md b/docs/swarm/1.5/README.md new file mode 100644 index 00000000..6b56e642 --- /dev/null +++ b/docs/swarm/1.5/README.md @@ -0,0 +1,364 @@ +# Install Mailu on a docker swarm + +## Prequisites + +### Swarm + +In order to deploy Mailu on a swarm, you will first need to initialize the swarm: + +The main command will be: +```bash +docker swarm init --advertise-addr +``` +See https://docs.docker.com/engine/swarm/swarm-tutorial/create-swarm/ + +If you want to add other managers or workers, please use: +```bash +docker swarm join --token xxxxx +``` +See https://docs.docker.com/engine/swarm/join-nodes/ + +You have now a working swarm, and you can check its status with: +```bash +core@coreos-01 ~/git/Mailu/docs/swarm/1.5 $ docker node ls +ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION +xhgeekkrlttpmtgmapt5hyxrb black-pearl Ready Active 18.06.0-ce +sczlqjgfhehsfdjhfhhph1nvb * coreos-01 Ready Active Leader 18.03.1-ce +mzrm9nbdggsfz4sgq6dhs5i6n flying-dutchman Ready Active 18.06.0-ce +``` + +### Volume definition +For data persistance (the Mailu services might be launched/relaunched on any of the swarm nodes), we need to have Mailu data stored in a manner accessible by every manager or worker in the swarm. +Hereafter we will use a NFS share: +```bash +core@coreos-01 ~ $ showmount -e 192.168.0.30 +Export list for 192.168.0.30: +/mnt/Pool1/pv 192.168.0.0 +``` + +on the nfs server, I am using the following /etc/exports +```bash +$more /etc/exports +/mnt/Pool1/pv -alldirs -mapall=root -network 192.168.0.0 -mask 255.255.255.0 +``` +on the nfs server, I created the Mailu directory (in fact I copied a working Mailu set-up) +```bash +$mkdir /mnt/Pool1/pv/mailu +``` + +On your manager node, mount the nfs share to check that the share is available: +```bash +core@coreos-01 ~ $ sudo mount -t nfs 192.168.0.30:/mnt/Pool1/pv/mailu /mnt/local/ +``` +If this is ok, you can umount it: +```bash +core@coreos-01 ~ $ sudo umount /mnt/local/ +``` + + +### Networking mode +On a swarm, the services are available (default mode) through a routing mesh managed by docker itself. With this mode, each service is given a virtual IP adress and docker manages the routing between this virtual IP and the container(s) provinding this service. +With this default networking mode, I cannot get login working properly... As found in https://github.com/Mailu/Mailu/issues/375 , a workaround is to use the dnsrr networking mode at least for the front services. + +The main consequence/limitation will be that the front services will *not* be available on every node, but only on the node where it will be deployed. In my case, I have only one manager and I choose to deploy the front service to the manager node, so I know on wich IP the front service will be available (aka the IP adress of my manager node). + +### Variable substitution and docker-compose.yml +The docker stack deploy command doesn't support variable substitution in the .yml file itself (but we still can use .env file to pass variables to the services). As a consequence we need to adjust the docker-compose file in order to : +- remove all variables : $VERSION , $BIND_ADDRESS4 , $BIND_ADDRESS6 , $ANTIVIRUS , $WEBMAIL , etc +- change the way we define the volumes (nfs share in our case) +- add a deploy section for every service + +### Docker compose +An example of docker-compose-stack.yml file is available here: + +```yaml + +version: '3.2' + +services: + + front: + image: mailu/nginx:1.5 + env_file: .env + ports: + - target: 80 + published: 80 + mode: host + - target: 443 + published: 443 + mode: host + - target: 110 + published: 110 + mode: host + - target: 143 + published: 143 + mode: host + - target: 993 + published: 993 + mode: host + - target: 995 + published: 995 + mode: host + - target: 25 + published: 25 + mode: host + - target: 465 + published: 465 + mode: host + - target: 587 + published: 587 + mode: host + volumes: +# - "$ROOT/certs:/certs" + - type: volume + source: mailu_certs + target: /certs + deploy: + endpoint_mode: dnsrr + replicas: 1 + placement: + constraints: [node.role == manager] + + redis: + image: redis:alpine + restart: always + volumes: +# - "$ROOT/redis:/data" + - type: volume + source: mailu_redis + target: /data + deploy: + endpoint_mode: dnsrr + replicas: 1 + placement: + constraints: [node.role == manager] + + imap: + image: mailu/dovecot:1.5 + restart: always + env_file: .env + volumes: +# - "$ROOT/data:/data" + - type: volume + source: mailu_data + target: /data +# - "$ROOT/mail:/mail" + - type: volume + source: mailu_mail + target: /mail +# - "$ROOT/overrides:/overrides" + - type: volume + source: mailu_overrides + target: /overrides + depends_on: + - front + deploy: + endpoint_mode: dnsrr + replicas: 1 + placement: + constraints: [node.role == manager] + + smtp: + image: mailu/postfix:1.5 + restart: always + env_file: .env + volumes: +# - "$ROOT/data:/data" + - type: volume + source: mailu_data + target: /data +# - "$ROOT/overrides:/overrides" + - type: volume + source: mailu_overrides + target: /overrides + depends_on: + - front + deploy: + endpoint_mode: dnsrr + replicas: 1 + placement: + constraints: [node.role == manager] + + antispam: + image: mailu/rspamd:1.5 + restart: always + env_file: .env + depends_on: + - front + volumes: +# - "$ROOT/filter:/var/lib/rspamd" + - type: volume + source: mailu_filter + target: /var/lib/rspamd +# - "$ROOT/dkim:/dkim" + - type: volume + source: mailu_dkim + target: /dkim +# - "$ROOT/overrides/rspamd:/etc/rspamd/override.d" + - type: volume + source: mailu_overrides_rspamd + target: /etc/rspamd/override.d + deploy: + endpoint_mode: dnsrr + replicas: 1 + placement: + constraints: [node.role == manager] + + antivirus: + image: mailu/none:1.5 + restart: always + env_file: .env + volumes: +# - "$ROOT/filter:/data" + - type: volume + source: mailu_filter + target: /data + deploy: + endpoint_mode: dnsrr + replicas: 1 + placement: + constraints: [node.role == manager] + + webdav: + image: mailu/none:1.5 + restart: always + env_file: .env + volumes: +# - "$ROOT/dav:/data" + - type: volume + source: mailu_dav + target: /data + deploy: + endpoint_mode: dnsrr + replicas: 1 + placement: + constraints: [node.role == manager] + + admin: + image: mailu/admin:1.5 + restart: always + env_file: .env + volumes: +# - "$ROOT/data:/data" + - type: volume + source: mailu_data + target: /data +# - "$ROOT/dkim:/dkim" + - type: volume + source: mailu_dkim + target: /dkim + - /var/run/docker.sock:/var/run/docker.sock:ro + depends_on: + - redis + deploy: + endpoint_mode: dnsrr + replicas: 1 + placement: + constraints: [node.role == manager] + + webmail: + image: "mailu/roundcube:1.5" + restart: always + env_file: .env + volumes: +# - "$ROOT/webmail:/data" + - type: volume + source: mailu_data + target: /data + depends_on: + - imap + deploy: + endpoint_mode: dnsrr + replicas: 1 + placement: + constraints: [node.role == manager] + + fetchmail: + image: mailu/fetchmail:1.5 + restart: always + env_file: .env + volumes: +# - "$ROOT/data:/data" + - type: volume + source: mailu_data + target: /data + logging: + driver: none + deploy: + endpoint_mode: dnsrr + replicas: 1 + placement: + constraints: [node.role == manager] + +volumes: + mailu_filter: + driver_opts: + type: "nfs" + o: "addr=192.168.0.30,nolock,soft,rw" + device: ":/mnt/Pool1/pv/mailu/filter" + mailu_dkim: + driver_opts: + type: "nfs" + o: "addr=192.168.0.30,nolock,soft,rw" + device: ":/mnt/Pool1/pv/mailu/dkim" + mailu_overrides_rspamd: + driver_opts: + type: "nfs" + o: "addr=192.168.0.30,nolock,soft,rw" + device: ":/mnt/Pool1/pv/mailu/overrides/rspamd" + mailu_data: + driver_opts: + type: "nfs" + o: "addr=192.168.0.30,nolock,soft,rw" + device: ":/mnt/Pool1/pv/mailu/data" + mailu_mail: + driver_opts: + type: "nfs" + o: "addr=192.168.0.30,nolock,soft,rw" + device: ":/mnt/Pool1/pv/mailu/mail" + mailu_overrides: + driver_opts: + type: "nfs" + o: "addr=192.168.0.30,nolock,soft,rw" + device: ":/mnt/Pool1/pv/mailu/overrides" + mailu_dav: + driver_opts: + type: "nfs" + o: "addr=192.168.0.30,nolock,soft,rw" + device: ":/mnt/Pool1/pv/mailu/dav" + mailu_certs: + driver_opts: + type: "nfs" + o: "addr=192.168.0.30,nolock,soft,rw" + device: ":/mnt/Pool1/pv/mailu/certs" + mailu_redis: + driver_opts: + type: "nfs" + o: "addr=192.168.0.30,nolock,soft,rw" + device: ":/mnt/Pool1/pv/mailu/redis" +``` + +### Deploy Mailu on the docker swarm +Run the following command: +```bash +docker stack deploy -c docker-compose-stack.yml mailu +``` +See how the services are being deployed: +```bash +core@coreos-01 ~ $ docker service ls +ID NAME MODE REPLICAS IMAGE PORTS +ywnsetmtkb1l mailu_antivirus replicated 1/1 mailu/none:1.5 +pqokiaz0q128 mailu_fetchmail replicated 1/1 mailu/fetchmail:1.5 +``` +check a specific service: +```bash +core@coreos-01 ~ $ docker service ps mailu_fetchmail +ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS +tbu8ppgsdffj mailu_fetchmail.1 mailu/fetchmail:1.5 coreos-01 Running Running 11 days ago +``` + +### Remove the stack +Run the follwoing command: +```bash +core@coreos-01 ~ $ docker stack rm mailu +``` diff --git a/optional/clamav/Dockerfile b/optional/clamav/Dockerfile index 92309c45..1c83d9c7 100644 --- a/optional/clamav/Dockerfile +++ b/optional/clamav/Dockerfile @@ -6,5 +6,6 @@ COPY conf /etc/clamav COPY start.sh /start.sh EXPOSE 3310/tcp +VOLUME ["/data"] CMD ["/start.sh"] diff --git a/optional/radicale/Dockerfile b/optional/radicale/Dockerfile index b1e63d7b..b82a0804 100644 --- a/optional/radicale/Dockerfile +++ b/optional/radicale/Dockerfile @@ -6,5 +6,6 @@ RUN echo "@testing http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/re COPY radicale.conf /radicale.conf EXPOSE 5232/tcp +VOLUME ["/data"] CMD radicale -f -S -C /radicale.conf diff --git a/services/rspamd/Dockerfile b/services/rspamd/Dockerfile index 1b8d7e6b..7dff8c1f 100644 --- a/services/rspamd/Dockerfile +++ b/services/rspamd/Dockerfile @@ -1,6 +1,8 @@ FROM alpine:edge -RUN apk add --no-cache python py-jinja2 rspamd rspamd-controller rspamd-proxy rspamd-fuzzy ca-certificates +RUN apk add --no-cache python py-jinja2 rspamd rspamd-controller rspamd-proxy rspamd-fuzzy ca-certificates py-pip \ + && pip install --upgrade pip \ + && pip install tenacity RUN mkdir /run/rspamd @@ -9,4 +11,6 @@ COPY start.py /start.py EXPOSE 11332/tcp 11334/tcp +VOLUME ["/var/lib/rspamd"] + CMD /start.py diff --git a/services/rspamd/conf/arc.conf b/services/rspamd/conf/arc.conf new file mode 100644 index 00000000..205d4284 --- /dev/null +++ b/services/rspamd/conf/arc.conf @@ -0,0 +1,4 @@ +try_fallback = true; +path = "/dkim/$domain.$selector.key"; +selector = "dkim" +use_esld = false; diff --git a/services/rspamd/start.py b/services/rspamd/start.py index 87309cee..b979517e 100755 --- a/services/rspamd/start.py +++ b/services/rspamd/start.py @@ -4,11 +4,17 @@ import jinja2 import os import socket import glob +import tenacity +from tenacity import retry convert = lambda 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)) +def resolve(): + os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front")) + # Actual startup script -os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front")) +resolve() if "HOST_REDIS" not in os.environ: os.environ["HOST_REDIS"] = "redis" for rspamd_file in glob.glob("/conf/*"): diff --git a/setup/Dockerfile b/setup/Dockerfile index 9111ae44..1fc808f1 100644 --- a/setup/Dockerfile +++ b/setup/Dockerfile @@ -15,4 +15,4 @@ RUN python setup.py https://github.com/mailu/mailu /data EXPOSE 80/tcp -CMD gunicorn -w 4 -b 0.0.0.0:80 -b [::]:80 --access-logfile - --error-logfile - --preload main:app +CMD gunicorn -w 4 -b :80 --access-logfile - --error-logfile - --preload main:app diff --git a/setup/docker-compose.yml b/setup/docker-compose.yml new file mode 100644 index 00000000..9288bb7e --- /dev/null +++ b/setup/docker-compose.yml @@ -0,0 +1,13 @@ +# This file is used to run the mailu/setup utility + +version: '2' + +services: + redis: + image: redis:alpine + + setup: + image: mailu/setup + ports: + - "80:80" + diff --git a/tests/build.yml b/tests/build.yml new file mode 100644 index 00000000..c39b0af4 --- /dev/null +++ b/tests/build.yml @@ -0,0 +1,51 @@ +version: '3' + +services: + + front: + image: mailu/nginx:$VERSION + build: ../core/nginx + + imap: + image: mailu/dovecot:$VERSION + build: ../core/dovecot + + smtp: + image: mailu/postfix:$VERSION + build: ../core/postfix + + antispam: + image: mailu/rspamd:$VERSION + build: ../services/rspamd + + antivirus: + image: mailu/clamav:$VERSION + build: ../optional/clamav + + webdav: + image: mailu/radicale:$VERSION + build: ../optional/radicale + + admin: + image: mailu/admin:$VERSION + build: ../core/admin + + roundcube: + image: mailu/roundcube:$VERSION + build: ../webmails/roundcube + + rainloop: + image: mailu/rainloop:$VERSION + build: ../webmails/rainloop + + fetchmail: + image: mailu/fetchmail:$VERSION + build: ../services/fetchmail + + none: + image: mailu/none:$VERSION + build: ../core/none + + docs: + image: mailu/docs:$VERSION + build: ../docs diff --git a/webmails/rainloop/Dockerfile b/webmails/rainloop/Dockerfile index 714390d8..f4571944 100644 --- a/webmails/rainloop/Dockerfile +++ b/webmails/rainloop/Dockerfile @@ -3,7 +3,7 @@ FROM php:5-apache RUN apt-get update && apt-get install -y \ unzip python3 python3-jinja2 -ENV RAINLOOP_URL https://github.com/RainLoop/rainloop-webmail/releases/download/v1.12.0/rainloop-community-1.12.0.zip +ENV RAINLOOP_URL https://github.com/RainLoop/rainloop-webmail/releases/download/v1.12.1/rainloop-community-1.12.1.zip RUN rm -rf /var/www/html/ \ && mkdir /var/www/html \ @@ -24,4 +24,7 @@ COPY default.ini /default.ini COPY start.py /start.py +EXPOSE 80/tcp +VOLUME ["/data"] + CMD /start.py diff --git a/webmails/roundcube/Dockerfile b/webmails/roundcube/Dockerfile index c779e71a..ad198236 100644 --- a/webmails/roundcube/Dockerfile +++ b/webmails/roundcube/Dockerfile @@ -1,13 +1,10 @@ -FROM php:7.0-apache +FROM php:7.2-apache RUN apt-get update && apt-get install -y \ - libfreetype6-dev \ - libjpeg62-turbo-dev \ - libmcrypt-dev \ - libpng12-dev \ - && docker-php-ext-install pdo_mysql mcrypt zip + zlib1g-dev \ + && docker-php-ext-install zip -ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.3.6/roundcubemail-1.3.6-complete.tar.gz +ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.3.7/roundcubemail-1.3.7-complete.tar.gz RUN echo date.timezone=UTC > /usr/local/etc/php/conf.d/timezone.ini @@ -28,4 +25,7 @@ COPY config.inc.php /var/www/html/config/ COPY start.sh /start.sh +EXPOSE 80/tcp +VOLUME ["/data"] + CMD ["/start.sh"] diff --git a/webmails/roundcube/config.inc.php b/webmails/roundcube/config.inc.php index 379b3025..35088107 100644 --- a/webmails/roundcube/config.inc.php +++ b/webmails/roundcube/config.inc.php @@ -6,6 +6,7 @@ $config = array(); $config['db_dsnw'] = 'sqlite:////data/roundcube.db'; $config['temp_dir'] = '/tmp/'; $config['des_key'] = getenv('SECRET_KEY'); +$config['cipher_method'] = 'AES-256-CBC'; $config['identities_level'] = 3; $config['reply_all_mode'] = 1; @@ -18,16 +19,19 @@ $config['plugins'] = array( 'enigma' ); +$front = getenv('FRONT_ADDRESS') ? getenv('FRONT_ADDRESS') : 'front'; +$imap = getenv('IMAP_ADDRESS') ? getenv('IMAP_ADDRESS') : 'imap'; + // Mail servers -$config['default_host'] = getenv('FRONT_ADDRESS') || 'front'; +$config['default_host'] = $front; $config['default_port'] = 10143; -$config['smtp_server'] = getenv('FRONT_ADDRESS') || 'front'; +$config['smtp_server'] = $front; $config['smtp_port'] = 10025; $config['smtp_user'] = '%u'; $config['smtp_pass'] = '%p'; // Sieve script management -$config['managesieve_host'] = getenv('IMAP_ADDRESS') || 'imap'; +$config['managesieve_host'] = $imap; $config['managesieve_usetls'] = false; // We access the IMAP and SMTP servers locally with internal names, SSL