From 0284b6a8e9f01f35e9c7c63fc508871e4281e65f Mon Sep 17 00:00:00 2001 From: mfashby Date: Sun, 10 Jun 2018 15:15:36 +0000 Subject: [PATCH 01/70] Made User and UserSignup validation consistent for the local part of the email address --- core/admin/mailu/ui/forms.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/admin/mailu/ui/forms.py b/core/admin/mailu/ui/forms.py index c5ce5798..82d98210 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 @@ -74,7 +75,7 @@ class RelayForm(flask_wtf.FlaskForm): 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() From a1fb8442e3435d04022f09d1e9f19bc4099a221b Mon Sep 17 00:00:00 2001 From: hoellen Date: Mon, 25 Jun 2018 15:45:43 +0200 Subject: [PATCH 02/70] Add posibilty to run webmail on root '/' --- core/nginx/conf/nginx.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/nginx/conf/nginx.conf b/core/nginx/conf/nginx.conf index 8b3d9be1..777b2a3d 100644 --- a/core/nginx/conf/nginx.conf +++ b/core/nginx/conf/nginx.conf @@ -84,9 +84,11 @@ http { # Actual logic {% if WEBMAIL != 'none' %} + {% if WEB_WEBMAIL != '/' %} location / { return 301 {{ WEB_WEBMAIL }}; } + {% endif %} location {{ WEB_WEBMAIL }} { rewrite ^({{ WEB_WEBMAIL }})$ $1/ permanent; From 81a6a7cbf6e0ef1b4e968eb04a717b4e68beea35 Mon Sep 17 00:00:00 2001 From: hoellen Date: Mon, 25 Jun 2018 15:51:20 +0200 Subject: [PATCH 03/70] Use message_size variable from env for webmail --- core/nginx/conf/nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/nginx/conf/nginx.conf b/core/nginx/conf/nginx.conf index 8b3d9be1..d1103f79 100644 --- a/core/nginx/conf/nginx.conf +++ b/core/nginx/conf/nginx.conf @@ -92,7 +92,7 @@ http { 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 }}; proxy_pass http://$webmail; } {% endif %} From 9091e54fda8455846157f24cbfc84718237beadc Mon Sep 17 00:00:00 2001 From: hoellen Date: Mon, 25 Jun 2018 21:35:40 +0200 Subject: [PATCH 04/70] Hide administration header in sidebar for normal users. --- core/admin/mailu/ui/templates/sidebar.html | 2 ++ 1 file changed, 2 insertions(+) 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 %}
  • From b7ece9f9b89aec0fa427ab20cf99a8c6d4205cb5 Mon Sep 17 00:00:00 2001 From: Mildred Ki'Lya Date: Wed, 27 Jun 2018 16:40:04 +0000 Subject: [PATCH 05/70] roundcube: fix host parametrization Roundcube can be parametrized so it can take a different hostname than 'front' or 'imap' to connect to the mail servers through environment variables. Unfortunately, this was not correct and in PHP a `||` operator always returns a boolean. It did not work as expected. Instead use the ternary operator `:?` that works in all cases. --- webmails/roundcube/config.inc.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/webmails/roundcube/config.inc.php b/webmails/roundcube/config.inc.php index 379b3025..603fc95b 100644 --- a/webmails/roundcube/config.inc.php +++ b/webmails/roundcube/config.inc.php @@ -18,16 +18,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 From 6478400cbab14f7e1c7c8a738c09ef985a020fb5 Mon Sep 17 00:00:00 2001 From: reallinfo <36298335+reallinfo@users.noreply.github.com> Date: Thu, 28 Jun 2018 17:28:37 +0300 Subject: [PATCH 06/70] Add files via upload --- docs/assets/horizontal.png | Bin 0 -> 8881 bytes docs/assets/horizontalv2.png | Bin 0 -> 8398 bytes docs/assets/logomark.png | Bin 0 -> 9233 bytes docs/assets/logomarkv2.png | Bin 0 -> 4647 bytes docs/assets/vertical.png | Bin 0 -> 7517 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/assets/horizontal.png create mode 100644 docs/assets/horizontalv2.png create mode 100644 docs/assets/logomark.png create mode 100644 docs/assets/logomarkv2.png create mode 100644 docs/assets/vertical.png diff --git a/docs/assets/horizontal.png b/docs/assets/horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..431874b4d71abe2cbd455501ee5c07ec896644fb GIT binary patch literal 8881 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

    ^(({d;uBl^#N(m0g01JH6DTBm@>pzl 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 HoSyz4w@y|$ literal 0 HcmV?d00001 diff --git a/docs/assets/logomark.png b/docs/assets/logomark.png new file mode 100644 index 0000000000000000000000000000000000000000..5921625bf67267b46815404f5822aa55453c3e8b GIT binary patch literal 9233 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 Date: Thu, 28 Jun 2018 17:29:52 +0300 Subject: [PATCH 07/70] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From c51e1b9eefb6f59cdeff7ba284cb413ea4afc65e Mon Sep 17 00:00:00 2001 From: hoellen Date: Thu, 28 Jun 2018 19:23:08 +0200 Subject: [PATCH 08/70] webmail client_max_body_size with message_size_limit and 8M tolerance --- core/nginx/conf/nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/nginx/conf/nginx.conf b/core/nginx/conf/nginx.conf index d1103f79..b779f98e 100644 --- a/core/nginx/conf/nginx.conf +++ b/core/nginx/conf/nginx.conf @@ -92,7 +92,7 @@ http { rewrite ^({{ WEB_WEBMAIL }})$ $1/ permanent; rewrite ^{{ WEB_WEBMAIL }}/(.*) /$1 break; include /etc/nginx/proxy.conf; - client_max_body_size {{ MESSAGE_SIZE_LIMIT }}; + client_max_body_size {{ MESSAGE_SIZE_LIMIT|int + 8388608 }}; proxy_pass http://$webmail; } {% endif %} From ca26264d0119261da6c37f95bce5ad3572de02cd Mon Sep 17 00:00:00 2001 From: hoellen Date: Fri, 29 Jun 2018 13:47:55 +0200 Subject: [PATCH 09/70] Dont flag spam as ham if moved to trash (fix #474) --- core/dovecot/sieve/report-ham.sieve | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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"; From f5e7751835764a819678f58be0098cd7a62cb691 Mon Sep 17 00:00:00 2001 From: Michal Prihoda Date: Fri, 22 Jun 2018 11:47:18 +0200 Subject: [PATCH 10/70] Return correct status codes from auth rate limiter failure. --- core/admin/mailu/internal/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) 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(): From 147a1359cdd028ae1709d4ec48e0916f2e00566e Mon Sep 17 00:00:00 2001 From: Michal Prihoda Date: Mon, 2 Jul 2018 21:39:31 +0200 Subject: [PATCH 11/70] Fixed libpng12-dev dependency, called libpng-dev now. --- webmails/roundcube/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webmails/roundcube/Dockerfile b/webmails/roundcube/Dockerfile index c779e71a..a3fe4e24 100644 --- a/webmails/roundcube/Dockerfile +++ b/webmails/roundcube/Dockerfile @@ -4,7 +4,7 @@ RUN apt-get update && apt-get install -y \ libfreetype6-dev \ libjpeg62-turbo-dev \ libmcrypt-dev \ - libpng12-dev \ + libpng-dev \ && docker-php-ext-install pdo_mysql mcrypt zip ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.3.6/roundcubemail-1.3.6-complete.tar.gz From 109842502aa19cf8478e95de9376121c6fc389a2 Mon Sep 17 00:00:00 2001 From: Jake Walker Date: Fri, 13 Jul 2018 15:11:40 +0100 Subject: [PATCH 12/70] Fix typo --- docs/compose/setup.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 14a6cfb5c6b327baf161a524bcd3c8ba091796b2 Mon Sep 17 00:00:00 2001 From: d-fens Date: Sat, 28 Jul 2018 21:46:33 +0100 Subject: [PATCH 13/70] [Security] Update Roundcube to 1.3.7 https://github.com/roundcube/roundcubemail/releases/tag/1.3.7 --- webmails/roundcube/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webmails/roundcube/Dockerfile b/webmails/roundcube/Dockerfile index c779e71a..c411226c 100644 --- a/webmails/roundcube/Dockerfile +++ b/webmails/roundcube/Dockerfile @@ -7,7 +7,7 @@ RUN apt-get update && apt-get install -y \ libpng12-dev \ && docker-php-ext-install pdo_mysql mcrypt 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 From 9e24064e354a881e60b0d4750fc959cffc2877dd Mon Sep 17 00:00:00 2001 From: hoellen Date: Tue, 31 Jul 2018 00:07:11 +0200 Subject: [PATCH 14/70] update rainloop to 1.12.1 --- webmails/rainloop/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webmails/rainloop/Dockerfile b/webmails/rainloop/Dockerfile index 714390d8..dfc6c83e 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 \ From eb9649db4e1daa59271d51cc34ea4ee478cc2294 Mon Sep 17 00:00:00 2001 From: hacor Date: Tue, 31 Jul 2018 17:15:25 +0200 Subject: [PATCH 15/70] Added a new release for Kubernetes Signed-off-by: hacor --- docs/kubernetes/1.6/README.md | 118 ++++++++++++++ docs/kubernetes/1.6/mailu/admin.yaml | 64 ++++++++ docs/kubernetes/1.6/mailu/configmap.yaml | 153 ++++++++++++++++++ docs/kubernetes/1.6/mailu/fetchmail.yaml | 39 +++++ docs/kubernetes/1.6/mailu/front.yaml | 129 +++++++++++++++ docs/kubernetes/1.6/mailu/imap.yaml | 80 +++++++++ docs/kubernetes/1.6/mailu/ingress-ssl.yaml | 32 ++++ docs/kubernetes/1.6/mailu/pvc.yaml | 27 ++++ docs/kubernetes/1.6/mailu/rbac.yaml | 4 + docs/kubernetes/1.6/mailu/redis.yaml | 56 +++++++ docs/kubernetes/1.6/mailu/security.yaml | 110 +++++++++++++ docs/kubernetes/1.6/mailu/smtp.yaml | 80 +++++++++ docs/kubernetes/1.6/mailu/static-ips.yaml | 0 docs/kubernetes/1.6/mailu/webdav.yaml | 63 ++++++++ docs/kubernetes/1.6/mailu/webmail.yaml | 59 +++++++ .../1.6/nginx/default-http-backend.yaml | 55 +++++++ docs/kubernetes/1.6/nginx/nginx-ingress.yaml | 139 ++++++++++++++++ docs/kubernetes/1.6/nginx/rbac.yaml | 129 +++++++++++++++ docs/kubernetes/{ => stable}/index.rst | 0 .../{ => stable}/kubernetes-mailu.yaml | 0 .../kubernetes-nginx-ingress-controller.yaml | 0 21 files changed, 1337 insertions(+) create mode 100644 docs/kubernetes/1.6/README.md create mode 100644 docs/kubernetes/1.6/mailu/admin.yaml create mode 100644 docs/kubernetes/1.6/mailu/configmap.yaml create mode 100644 docs/kubernetes/1.6/mailu/fetchmail.yaml create mode 100644 docs/kubernetes/1.6/mailu/front.yaml create mode 100644 docs/kubernetes/1.6/mailu/imap.yaml create mode 100644 docs/kubernetes/1.6/mailu/ingress-ssl.yaml create mode 100644 docs/kubernetes/1.6/mailu/pvc.yaml create mode 100644 docs/kubernetes/1.6/mailu/rbac.yaml create mode 100644 docs/kubernetes/1.6/mailu/redis.yaml create mode 100644 docs/kubernetes/1.6/mailu/security.yaml create mode 100644 docs/kubernetes/1.6/mailu/smtp.yaml create mode 100644 docs/kubernetes/1.6/mailu/static-ips.yaml create mode 100644 docs/kubernetes/1.6/mailu/webdav.yaml create mode 100644 docs/kubernetes/1.6/mailu/webmail.yaml create mode 100644 docs/kubernetes/1.6/nginx/default-http-backend.yaml create mode 100644 docs/kubernetes/1.6/nginx/nginx-ingress.yaml create mode 100644 docs/kubernetes/1.6/nginx/rbac.yaml rename docs/kubernetes/{ => stable}/index.rst (100%) rename docs/kubernetes/{ => stable}/kubernetes-mailu.yaml (100%) rename docs/kubernetes/{ => stable}/kubernetes-nginx-ingress-controller.yaml (100%) diff --git a/docs/kubernetes/1.6/README.md b/docs/kubernetes/1.6/README.md new file mode 100644 index 00000000..345609f9 --- /dev/null +++ b/docs/kubernetes/1.6/README.md @@ -0,0 +1,118 @@ +# 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 +``` + +### Things to change +- 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 + +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-.... +``` + +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 From db3cb2aac15104843cd858227635face8c961a02 Mon Sep 17 00:00:00 2001 From: hacor Date: Tue, 31 Jul 2018 17:17:21 +0200 Subject: [PATCH 16/70] Updated docs Signed-off-by: hacor --- docs/kubernetes/1.6/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/kubernetes/1.6/README.md b/docs/kubernetes/1.6/README.md index 345609f9..dcbd2418 100644 --- a/docs/kubernetes/1.6/README.md +++ b/docs/kubernetes/1.6/README.md @@ -36,7 +36,11 @@ spec: server: https://acme-v02.api.letsencrypt.org/directory ``` -### Things to change +## 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) From 699a25939f2ecd8da77eb2830169fdbc562d00d1 Mon Sep 17 00:00:00 2001 From: hacor Date: Tue, 31 Jul 2018 17:45:11 +0200 Subject: [PATCH 17/70] Updated docs for Travis Signed-off-by: hacor --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 151aeb9c067a3005750e773bc035b9443ff52131 Mon Sep 17 00:00:00 2001 From: hacor Date: Wed, 1 Aug 2018 15:45:49 +0200 Subject: [PATCH 18/70] Updated adaptations for dovecot on shared filesystem and indexing errors Signed-off-by: hacor --- docs/kubernetes/1.6/README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/kubernetes/1.6/README.md b/docs/kubernetes/1.6/README.md index dcbd2418..c0dd935b 100644 --- a/docs/kubernetes/1.6/README.md +++ b/docs/kubernetes/1.6/README.md @@ -89,6 +89,7 @@ Now you should be able to login on the mail account: `https://mail.example.com/a ## 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: @@ -118,5 +119,39 @@ Save and close the file and exit. Now you need to delete the pod in order to rec 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 From 5ad02ae2e55f4622873165d43c710b251e4e5f9f Mon Sep 17 00:00:00 2001 From: Pierre Jaury Date: Wed, 1 Aug 2018 21:23:50 +0200 Subject: [PATCH 19/70] Use a more uniform 'Save' for most form submits, fixes #523 --- core/admin/mailu/ui/forms.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/admin/mailu/ui/forms.py b/core/admin/mailu/ui/forms.py index 82d98210..326d721b 100644 --- a/core/admin/mailu/ui/forms.py +++ b/core/admin/mailu/ui/forms.py @@ -50,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): @@ -64,14 +64,14 @@ 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): @@ -130,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): @@ -139,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): From 18fe8cd9f21f916cc64f188aaf45ab6a69f3d560 Mon Sep 17 00:00:00 2001 From: Pierre Jaury Date: Wed, 1 Aug 2018 21:29:18 +0200 Subject: [PATCH 20/70] Pin alpine:3.7 for Dovecot since extdata was removed from repos, fixes #528 --- core/dovecot/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index cacfe354..f27bbdba 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:edge +FROM alpine:3.7 RUN echo "@testing http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories \ && apk add --no-cache \ From 3dca1a834cb17086c32e8889874528f43c8a29a9 Mon Sep 17 00:00:00 2001 From: Pierre Jaury Date: Wed, 1 Aug 2018 21:56:29 +0200 Subject: [PATCH 21/70] Pin alpine 3.7 until we fix the certbot issue, see #522 --- core/nginx/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/nginx/Dockerfile b/core/nginx/Dockerfile index 3be4b50f..8a6536eb 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 From 9350bb9b9a0234afa8ed844a7e7777b426b71898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Fri, 3 Aug 2018 00:18:39 +0300 Subject: [PATCH 22/70] Use fixed alpine:3.7 tag to prevent postix upgrade --- core/postfix/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/postfix/Dockerfile b/core/postfix/Dockerfile index bb5831a2..168f3c60 100644 --- a/core/postfix/Dockerfile +++ b/core/postfix/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine +FROM alpine:3.7 RUN apk add --no-cache postfix postfix-sqlite postfix-pcre rsyslog python py-jinja2 From f506966abc5ec5452f7db599a11e16d7cc497e80 Mon Sep 17 00:00:00 2001 From: kaiyou Date: Fri, 3 Aug 2018 08:24:06 +0200 Subject: [PATCH 23/70] Pin Alpine 3.7 to preserve the Postfix version --- core/postfix/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/postfix/Dockerfile b/core/postfix/Dockerfile index bb5831a2..168f3c60 100644 --- a/core/postfix/Dockerfile +++ b/core/postfix/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine +FROM alpine:3.7 RUN apk add --no-cache postfix postfix-sqlite postfix-pcre rsyslog python py-jinja2 From bd6026384aab0bc2e6530ea1c76f5342210750f5 Mon Sep 17 00:00:00 2001 From: ofthesun9 Date: Sat, 4 Aug 2018 15:27:27 +0000 Subject: [PATCH 24/70] Documentation to deploy mailu on a doxker swarm --- docs/swarm/1.5/README.md | 67 +++++ .../swarm/1.5/docker-compose-stack-simple.yml | 275 ++++++++++++++++++ 2 files changed, 342 insertions(+) create mode 100644 docs/swarm/1.5/README.md create mode 100644 docs/swarm/1.5/docker-compose-stack-simple.yml diff --git a/docs/swarm/1.5/README.md b/docs/swarm/1.5/README.md new file mode 100644 index 00000000..c9b10dc8 --- /dev/null +++ b/docs/swarm/1.5/README.md @@ -0,0 +1,67 @@ +# Install Mailu master on kubernetes + +## Prequisites + +### Swarm + +You need to have a swarm running + +```bash +In order to deploy mailu on a swarm, you will first need to initialize it: +The main command will be 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 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 +docker node ls +```bash +core@coreos-01 ~/git/Mailu/docs/swarm/1.5 $ docker node ls +ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION +ptpmtgih78v9q14mapt5hyxrb black-pearl Ready Active 18.06.0-ce +sczlqi2pigpw7117hbkh71nvb * coreos-01 Ready Active Leader 18.03.1-ce +mzrm98cc9i2y8obvi2fzo5i6n 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. +Hereafer we will use a NFS share: +```bash +core@coreos-01 ~/git/Mailu/docs $ 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: +```bashcore@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/limiation 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 +The docker stack deploy command doesn't support variable substitution in the .yml file itself (vut we still can use .env file to pass variables to the services). As a consequence we need to adjust the docker-compose file to : +- remove all variables : $VERSION , $BIND_ADDRESS4 , $BIND_ADDRESS6 , $ANTIVIRUS , $WEBMAIL , etc +- change the way we define the volumes (nfs share in our case) + +### Docker compose +A working docker-compose.yml file is avalable here: + + diff --git a/docs/swarm/1.5/docker-compose-stack-simple.yml b/docs/swarm/1.5/docker-compose-stack-simple.yml new file mode 100644 index 00000000..47ef7cb1 --- /dev/null +++ b/docs/swarm/1.5/docker-compose-stack-simple.yml @@ -0,0 +1,275 @@ +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: +# - "/mailu/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: +# - "/mailu/redis:/data" + - type: volume + source: mailu_redis + target: /data + deploy: + endpoint_mode: dnsrr + replicas: 1 + placement: + constraints: [node.role == manager] + + imap: +# image: mailu/dovecot:$VERSION + image: ofthesun9/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: ofthesun9/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:$VERSION + image: ofthesun9/rspamd:fuzzydev + 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: +# - "/mailu/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: +# - /mailu/dav:/data" + - type: volume + source: mailu_dav + target: /data + deploy: + endpoint_mode: dnsrr + replicas: 1 + placement: + constraints: [node.role == manager] + + admin: + image: ofthesun9/admin:1.5-backports + restart: always + env_file: .env + volumes: +# - "/mailu/data:/data" + - type: volume + source: mailu_data + target: /data +# - "/mailu/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: +# - "/mailu/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: +# - "/mailu/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_nginx.conf: + driver_opts: + type: "nfs" + o: "addr=192.168.0.30,nolock,soft,rw" + device: ":/mnt/Pool1/pv/mailu/1.5/nginx.conf.wp" + mailu_tls.conf: + driver_opts: + type: "nfs" + o: "addr=192.168.0.30,nolock,soft,rw" + device: ":/mnt/Pool1/pv/mailu/1.5/tls.conf" + mailu_redis: + driver_opts: + type: "nfs" + o: "addr=192.168.0.30,nolock,soft,rw" + device: ":/mnt/Pool1/pv/mailu/redis" From 806dfc804a1d20a7392800c82248ee592fac7fbb Mon Sep 17 00:00:00 2001 From: ofthesun9 Date: Sat, 4 Aug 2018 15:33:08 +0000 Subject: [PATCH 25/70] Typo --- docs/swarm/1.5/README.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/swarm/1.5/README.md b/docs/swarm/1.5/README.md index c9b10dc8..e7c37ac9 100644 --- a/docs/swarm/1.5/README.md +++ b/docs/swarm/1.5/README.md @@ -6,21 +6,25 @@ You need to have a swarm running -```bash In order to deploy mailu on a swarm, you will first need to initialize it: -The main command will be docker swarm init --advertise-addr +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 docker swarm join --token xxxxx +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 -docker node ls +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 -ptpmtgih78v9q14mapt5hyxrb black-pearl Ready Active 18.06.0-ce -sczlqi2pigpw7117hbkh71nvb * coreos-01 Ready Active Leader 18.03.1-ce -mzrm98cc9i2y8obvi2fzo5i6n flying-dutchman Ready Active 18.06.0-ce +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 From a34090502d7cc9ae73e59c79d117783a5db761a3 Mon Sep 17 00:00:00 2001 From: ofthesun9 Date: Sat, 4 Aug 2018 15:38:25 +0000 Subject: [PATCH 26/70] Documentation to deploy mailu on a docker swarm --- docs/swarm/1.5/README.md | 283 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 281 insertions(+), 2 deletions(-) diff --git a/docs/swarm/1.5/README.md b/docs/swarm/1.5/README.md index e7c37ac9..6793a267 100644 --- a/docs/swarm/1.5/README.md +++ b/docs/swarm/1.5/README.md @@ -12,6 +12,7 @@ The main command will be: 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 @@ -29,7 +30,7 @@ mzrm9nbdggsfz4sgq6dhs5i6n flying-dutchman Ready Active ### 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. -Hereafer we will use a NFS share: +Hereafter we will use a NFS share: ```bash core@coreos-01 ~/git/Mailu/docs $ showmount -e 192.168.0.30 Export list for 192.168.0.30: @@ -51,7 +52,8 @@ On your manager node, mount the nfs share to check that the share is available: 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: -```bashcore@coreos-01 ~ $ sudo umount /mnt/local/ +```bash +core@coreos-01 ~ $ sudo umount /mnt/local/ ``` @@ -68,4 +70,281 @@ The docker stack deploy command doesn't support variable substitution in the .ym ### Docker compose A working docker-compose.yml file is avalable 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: +# - "/mailu/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: +# - "/mailu/redis:/data" + - type: volume + source: mailu_redis + target: /data + deploy: + endpoint_mode: dnsrr + replicas: 1 + placement: + constraints: [node.role == manager] + + imap: +# image: mailu/dovecot:$VERSION + image: ofthesun9/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: ofthesun9/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:$VERSION + image: ofthesun9/rspamd:fuzzydev + 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: +# - "/mailu/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: +# - /mailu/dav:/data" + - type: volume + source: mailu_dav + target: /data + deploy: + endpoint_mode: dnsrr + replicas: 1 + placement: + constraints: [node.role == manager] + + admin: + image: ofthesun9/admin:1.5-backports + restart: always + env_file: .env + volumes: +# - "/mailu/data:/data" + - type: volume + source: mailu_data + target: /data +# - "/mailu/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: +# - "/mailu/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: +# - "/mailu/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_nginx.conf: + driver_opts: + type: "nfs" + o: "addr=192.168.0.30,nolock,soft,rw" + device: ":/mnt/Pool1/pv/mailu/1.5/nginx.conf.wp" + mailu_tls.conf: + driver_opts: + type: "nfs" + o: "addr=192.168.0.30,nolock,soft,rw" + device: ":/mnt/Pool1/pv/mailu/1.5/tls.conf" + mailu_redis: + driver_opts: + type: "nfs" + o: "addr=192.168.0.30,nolock,soft,rw" + device: ":/mnt/Pool1/pv/mailu/redis" +``` From 8a0ff1153e4eb28be86f60cadadd921eae5259db Mon Sep 17 00:00:00 2001 From: ofthesun9 Date: Sat, 4 Aug 2018 15:44:43 +0000 Subject: [PATCH 27/70] Documentation to deploy mailu on a docker swarm --- docs/swarm/1.5/README.md | 13 +- .../swarm/1.5/docker-compose-stack-simple.yml | 275 ------------------ 2 files changed, 6 insertions(+), 282 deletions(-) delete mode 100644 docs/swarm/1.5/docker-compose-stack-simple.yml diff --git a/docs/swarm/1.5/README.md b/docs/swarm/1.5/README.md index 6793a267..ef8cbb6b 100644 --- a/docs/swarm/1.5/README.md +++ b/docs/swarm/1.5/README.md @@ -59,7 +59,8 @@ 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 +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/limiation 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 @@ -133,8 +134,7 @@ services: constraints: [node.role == manager] imap: -# image: mailu/dovecot:$VERSION - image: ofthesun9/dovecot:1.5 + image: mailu/dovecot:1.5 restart: always env_file: .env volumes: @@ -159,7 +159,7 @@ services: constraints: [node.role == manager] smtp: - image: ofthesun9/postfix:1.5 + image: mailu/postfix:1.5 restart: always env_file: .env volumes: @@ -180,8 +180,7 @@ services: constraints: [node.role == manager] antispam: -# image: mailu/rspamd:$VERSION - image: ofthesun9/rspamd:fuzzydev + image: mailu/rspamd:1.5 restart: always env_file: .env depends_on: @@ -236,7 +235,7 @@ services: constraints: [node.role == manager] admin: - image: ofthesun9/admin:1.5-backports + image: mailu/admin:1.5 restart: always env_file: .env volumes: diff --git a/docs/swarm/1.5/docker-compose-stack-simple.yml b/docs/swarm/1.5/docker-compose-stack-simple.yml deleted file mode 100644 index 47ef7cb1..00000000 --- a/docs/swarm/1.5/docker-compose-stack-simple.yml +++ /dev/null @@ -1,275 +0,0 @@ -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: -# - "/mailu/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: -# - "/mailu/redis:/data" - - type: volume - source: mailu_redis - target: /data - deploy: - endpoint_mode: dnsrr - replicas: 1 - placement: - constraints: [node.role == manager] - - imap: -# image: mailu/dovecot:$VERSION - image: ofthesun9/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: ofthesun9/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:$VERSION - image: ofthesun9/rspamd:fuzzydev - 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: -# - "/mailu/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: -# - /mailu/dav:/data" - - type: volume - source: mailu_dav - target: /data - deploy: - endpoint_mode: dnsrr - replicas: 1 - placement: - constraints: [node.role == manager] - - admin: - image: ofthesun9/admin:1.5-backports - restart: always - env_file: .env - volumes: -# - "/mailu/data:/data" - - type: volume - source: mailu_data - target: /data -# - "/mailu/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: -# - "/mailu/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: -# - "/mailu/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_nginx.conf: - driver_opts: - type: "nfs" - o: "addr=192.168.0.30,nolock,soft,rw" - device: ":/mnt/Pool1/pv/mailu/1.5/nginx.conf.wp" - mailu_tls.conf: - driver_opts: - type: "nfs" - o: "addr=192.168.0.30,nolock,soft,rw" - device: ":/mnt/Pool1/pv/mailu/1.5/tls.conf" - mailu_redis: - driver_opts: - type: "nfs" - o: "addr=192.168.0.30,nolock,soft,rw" - device: ":/mnt/Pool1/pv/mailu/redis" From 820e5c667bf9ddf226d6d601ab768ffa56ef35ce Mon Sep 17 00:00:00 2001 From: ofthesun9 Date: Sat, 4 Aug 2018 17:47:10 +0200 Subject: [PATCH 28/70] Update README.md Typo --- docs/swarm/1.5/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/swarm/1.5/README.md b/docs/swarm/1.5/README.md index ef8cbb6b..8a890b74 100644 --- a/docs/swarm/1.5/README.md +++ b/docs/swarm/1.5/README.md @@ -61,7 +61,7 @@ core@coreos-01 ~ $ sudo umount /mnt/local/ 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/limiation 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). +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 The docker stack deploy command doesn't support variable substitution in the .yml file itself (vut we still can use .env file to pass variables to the services). As a consequence we need to adjust the docker-compose file to : From 91300c1c5cf26906e92e933fe324f5174f55a791 Mon Sep 17 00:00:00 2001 From: ofthesun9 Date: Sat, 4 Aug 2018 17:48:37 +0200 Subject: [PATCH 29/70] Update README.md Typo --- docs/swarm/1.5/README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/swarm/1.5/README.md b/docs/swarm/1.5/README.md index 8a890b74..42e3b174 100644 --- a/docs/swarm/1.5/README.md +++ b/docs/swarm/1.5/README.md @@ -331,16 +331,6 @@ volumes: type: "nfs" o: "addr=192.168.0.30,nolock,soft,rw" device: ":/mnt/Pool1/pv/mailu/certs" - mailu_nginx.conf: - driver_opts: - type: "nfs" - o: "addr=192.168.0.30,nolock,soft,rw" - device: ":/mnt/Pool1/pv/mailu/1.5/nginx.conf.wp" - mailu_tls.conf: - driver_opts: - type: "nfs" - o: "addr=192.168.0.30,nolock,soft,rw" - device: ":/mnt/Pool1/pv/mailu/1.5/tls.conf" mailu_redis: driver_opts: type: "nfs" From 27d43384c5227e5e13fa935662721ca720375a4b Mon Sep 17 00:00:00 2001 From: ofthesun9 Date: Sat, 4 Aug 2018 15:58:53 +0000 Subject: [PATCH 30/70] Documentation to deploy mailu on a docker swarm --- docs/swarm/1.5/README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/swarm/1.5/README.md b/docs/swarm/1.5/README.md index 42e3b174..026b5d11 100644 --- a/docs/swarm/1.5/README.md +++ b/docs/swarm/1.5/README.md @@ -337,3 +337,28 @@ volumes: 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 +``` From b3131496c6e427a9225ac7d86ca02ef91744a8fe Mon Sep 17 00:00:00 2001 From: ofthesun9 Date: Sat, 4 Aug 2018 18:00:57 +0200 Subject: [PATCH 31/70] Update README.md --- docs/swarm/1.5/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/swarm/1.5/README.md b/docs/swarm/1.5/README.md index 026b5d11..15d79068 100644 --- a/docs/swarm/1.5/README.md +++ b/docs/swarm/1.5/README.md @@ -69,7 +69,7 @@ The docker stack deploy command doesn't support variable substitution in the .ym - change the way we define the volumes (nfs share in our case) ### Docker compose -A working docker-compose.yml file is avalable here: +A working docker-compose-stack.yml file is available here: ```yaml From a6412f3f23bd1d92dfedf2f84ae9145a7b9ff420 Mon Sep 17 00:00:00 2001 From: ofthesun9 Date: Sat, 4 Aug 2018 18:03:50 +0200 Subject: [PATCH 32/70] Update README.md --- docs/swarm/1.5/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/swarm/1.5/README.md b/docs/swarm/1.5/README.md index 15d79068..f9e9dfb8 100644 --- a/docs/swarm/1.5/README.md +++ b/docs/swarm/1.5/README.md @@ -1,4 +1,4 @@ -# Install Mailu master on kubernetes +# Install Mailu on a docker swarm ## Prequisites @@ -6,7 +6,7 @@ You need to have a swarm running -In order to deploy mailu on a swarm, you will first need to initialize it: +In order to deploy Mailu on a swarm, you will first need to initialize it: The main command will be: ```bash docker swarm init --advertise-addr @@ -29,10 +29,10 @@ mzrm9nbdggsfz4sgq6dhs5i6n flying-dutchman Ready Active ``` ### 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. +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 ~/git/Mailu/docs $ showmount -e 192.168.0.30 +core@coreos-01 ~ $ showmount -e 192.168.0.30 Export list for 192.168.0.30: /mnt/Pool1/pv 192.168.0.0 ``` @@ -42,7 +42,7 @@ on the nfs server, I am using the following /etc/exports $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) +on the nfs server, I created the Mailu directory (in fact I copied a working Mailu set-up) ```bash $mkdir /mnt/Pool1/pv/mailu ``` @@ -338,7 +338,7 @@ volumes: device: ":/mnt/Pool1/pv/mailu/redis" ``` -### Deploy mailu on the docker swarm +### Deploy Mailu on the docker swarm Run the following command: ```bash docker stack deploy -c docker-compose-stack.yml mailu From dc8df569763e09fdaecd1da0c2bcc6fcf2cf4e35 Mon Sep 17 00:00:00 2001 From: ofthesun9 Date: Sat, 4 Aug 2018 18:08:07 +0200 Subject: [PATCH 33/70] Update README.md Typo --- docs/swarm/1.5/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/swarm/1.5/README.md b/docs/swarm/1.5/README.md index f9e9dfb8..da0ecf0b 100644 --- a/docs/swarm/1.5/README.md +++ b/docs/swarm/1.5/README.md @@ -109,7 +109,7 @@ services: published: 587 mode: host volumes: -# - "/mailu/certs:/certs" +# - "$ROOT/certs:/certs" - type: volume source: mailu_certs target: /certs @@ -123,7 +123,7 @@ services: image: redis:alpine restart: always volumes: -# - "/mailu/redis:/data" +# - "$ROOT/redis:/data" - type: volume source: mailu_redis target: /data @@ -209,7 +209,7 @@ services: restart: always env_file: .env volumes: -# - "/mailu/filter:/data" +# - "$ROOT/filter:/data" - type: volume source: mailu_filter target: /data @@ -224,7 +224,7 @@ services: restart: always env_file: .env volumes: -# - /mailu/dav:/data" +# - "$ROOT/dav:/data" - type: volume source: mailu_dav target: /data @@ -239,11 +239,11 @@ services: restart: always env_file: .env volumes: -# - "/mailu/data:/data" +# - "$ROOT/data:/data" - type: volume source: mailu_data target: /data -# - "/mailu/dkim:/dkim" +# - "$ROOT/dkim:/dkim" - type: volume source: mailu_dkim target: /dkim @@ -261,7 +261,7 @@ services: restart: always env_file: .env volumes: -# - "/mailu/webmail:/data" +# - "$ROOT/webmail:/data" - type: volume source: mailu_data target: /data @@ -278,7 +278,7 @@ services: restart: always env_file: .env volumes: -# - "/mailu/data:/data" +# - "$ROOT/data:/data" - type: volume source: mailu_data target: /data From d13725ce337036cf152a893c647db258bb968c8f Mon Sep 17 00:00:00 2001 From: ofthesun9 Date: Sat, 4 Aug 2018 18:09:18 +0200 Subject: [PATCH 34/70] Update README.md Typo --- docs/swarm/1.5/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/swarm/1.5/README.md b/docs/swarm/1.5/README.md index da0ecf0b..8e468f51 100644 --- a/docs/swarm/1.5/README.md +++ b/docs/swarm/1.5/README.md @@ -4,9 +4,7 @@ ### Swarm -You need to have a swarm running - -In order to deploy Mailu on a swarm, you will first need to initialize it: +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 From 480fc6c4374daf3359d3c2a240a5c0b8fd6ab0e4 Mon Sep 17 00:00:00 2001 From: ofthesun9 Date: Sat, 4 Aug 2018 18:12:41 +0200 Subject: [PATCH 35/70] Update README.md Typo --- docs/swarm/1.5/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/swarm/1.5/README.md b/docs/swarm/1.5/README.md index 8e468f51..058de519 100644 --- a/docs/swarm/1.5/README.md +++ b/docs/swarm/1.5/README.md @@ -61,13 +61,14 @@ With this default networking mode, I cannot get login working properly... As fou 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 -The docker stack deploy command doesn't support variable substitution in the .yml file itself (vut we still can use .env file to pass variables to the services). As a consequence we need to adjust the docker-compose file to : +### 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 -A working docker-compose-stack.yml file is available here: +An example of docker-compose-stack.yml file is available here: ```yaml From 935cd7f706d58e383a11e41039350a2b80607a13 Mon Sep 17 00:00:00 2001 From: ofthesun9 Date: Sat, 4 Aug 2018 18:13:08 +0200 Subject: [PATCH 36/70] Update README.md --- docs/swarm/1.5/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/swarm/1.5/README.md b/docs/swarm/1.5/README.md index 058de519..6b56e642 100644 --- a/docs/swarm/1.5/README.md +++ b/docs/swarm/1.5/README.md @@ -5,6 +5,7 @@ ### 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 From fb62e6b5a206ac45b2ecaaaadd03e15d45bd0543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20S=C3=A4nger?= Date: Sun, 5 Aug 2018 18:59:57 +0200 Subject: [PATCH 37/70] add full-text search support --- core/dovecot/Dockerfile | 2 +- core/dovecot/conf/dovecot.conf | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index f27bbdba..80e3539a 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -3,7 +3,7 @@ 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 COPY conf /conf COPY sieve /var/lib/dovecot diff --git a/core/dovecot/conf/dovecot.conf b/core/dovecot/conf/dovecot.conf index 94c43901..b7ce1834 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 ############### From 0bdb2a16bcd1f6c89f9fe7731b819af83331e8d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20S=C3=A4nger?= Date: Sun, 5 Aug 2018 19:48:24 +0200 Subject: [PATCH 38/70] add optional Maildir-Compression --- core/dovecot/conf/dovecot.conf | 10 +++++++++- docs/compose/.env | 6 ++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/dovecot/conf/dovecot.conf b/core/dovecot/conf/dovecot.conf index 94c43901..d6b79410 100644 --- a/core/dovecot/conf/dovecot.conf +++ b/core/dovecot/conf/dovecot.conf @@ -32,7 +32,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 +58,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/docs/compose/.env b/docs/compose/.env index 06038bc8..9477448a 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 ################################### From cc8e15748bebd090f8abcc6ca06147843236864d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Wed, 8 Aug 2018 17:54:15 +0300 Subject: [PATCH 39/70] Retry 10 times when resolving fails in start.py scripts --- core/dovecot/start.py | 20 ++++++++++++++++---- core/postfix/start.py | 16 ++++++++++++++-- services/rspamd/start.py | 14 +++++++++++++- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/core/dovecot/start.py b/core/dovecot/start.py index 83f91fab..2de114fd 100755 --- a/core/dovecot/start.py +++ b/core/dovecot/start.py @@ -4,14 +4,26 @@ import jinja2 import os import socket import glob +import time 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")) +i = 0 +t = 10 +while True: + i += 1 + try: + 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")) + except socket.gaierror as err: + if i >= t: + raise + time.sleep(10) + continue + break for dovecot_file in glob.glob("/conf/*"): convert(dovecot_file, os.path.join("/etc/dovecot", os.path.basename(dovecot_file))) diff --git a/core/postfix/start.py b/core/postfix/start.py index 4dbf2206..f3c6aaca 100755 --- a/core/postfix/start.py +++ b/core/postfix/start.py @@ -5,11 +5,23 @@ import os import socket import glob import shutil - +import time + 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")) +i = 0 +t = 10 +while True: + i += 1 + try: + os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front")) + except socket.gaierror as err: + if i >= t: + raise + time.sleep(10) + continue + break 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/services/rspamd/start.py b/services/rspamd/start.py index 87309cee..08301a0d 100755 --- a/services/rspamd/start.py +++ b/services/rspamd/start.py @@ -4,11 +4,23 @@ import jinja2 import os import socket import glob +import time 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")) +i = 0 +t = 10 +while True: + i += 1 + try: + os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front")) + except socket.gaierror as err: + if i >= t: + raise + time.sleep(10) + continue + break if "HOST_REDIS" not in os.environ: os.environ["HOST_REDIS"] = "redis" for rspamd_file in glob.glob("/conf/*"): From 6fc51d879b341c1abf8fd9c18a76282895266480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Sun, 26 Aug 2018 21:08:20 +0300 Subject: [PATCH 40/70] Add docker-compose.yml file for Setup utility --- setup/docker-compose.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 setup/docker-compose.yml 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" + From fe7e32dc82eb745a6e79cc06c6a484e09dbb08f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Sun, 26 Aug 2018 21:11:48 +0300 Subject: [PATCH 41/70] Make gunicorn bind to port 80 of any available protocol --- setup/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 89c55ba8fe59b6f629659fa70385ca150b1cd0df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20S=C3=A4nger?= Date: Wed, 19 Sep 2018 01:36:22 +0200 Subject: [PATCH 42/70] use safer cipher in roundcube "Default is set for backward compatibility to DES-EDE3-CBC, but you can choose e.g. AES-256-CBC which we consider a better choice." https://github.com/roundcube/roundcubemail/blob/master/config/defaults.inc.php#L512 --- webmails/roundcube/config.inc.php | 1 + 1 file changed, 1 insertion(+) diff --git a/webmails/roundcube/config.inc.php b/webmails/roundcube/config.inc.php index 603fc95b..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; From f5f8d1d84b5661bf984c2af3f0f340973ac23e9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Mon, 24 Sep 2018 02:23:07 +0300 Subject: [PATCH 43/70] Test-building using travis-ci --- .travis.yml | 17 ++++++++++------- tests/build.yml | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 tests/build.yml 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/tests/build.yml b/tests/build.yml new file mode 100644 index 00000000..674abf8c --- /dev/null +++ b/tests/build.yml @@ -0,0 +1,47 @@ +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 From a684739b9c481d5e4021a810953f62968f32714f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20S=C3=A4nger?= Date: Tue, 25 Sep 2018 05:59:31 +0200 Subject: [PATCH 44/70] update to PHP 7.2 and remove mcrypt removed mcrypt because Rouncube uses openssl exclusively since version 1.2 and mcrypt was removed from PHP 7.2 --- webmails/roundcube/Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/webmails/roundcube/Dockerfile b/webmails/roundcube/Dockerfile index 3f7eee0d..5ff4dcb8 100644 --- a/webmails/roundcube/Dockerfile +++ b/webmails/roundcube/Dockerfile @@ -1,11 +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 \ libpng-dev \ - && docker-php-ext-install pdo_mysql mcrypt zip + && docker-php-ext-install pdo_mysql zip ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.3.7/roundcubemail-1.3.7-complete.tar.gz From 0b885548ab03e135b558e4e23c716b024f193470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20S=C3=A4nger?= Date: Tue, 25 Sep 2018 06:29:53 +0200 Subject: [PATCH 45/70] bind to any protocol --- core/admin/start.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From cbaac01790b412a89b1da3bc880272e74c762cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20S=C3=A4nger?= Date: Tue, 25 Sep 2018 07:38:49 +0200 Subject: [PATCH 46/70] remove unused dependencies --- webmails/roundcube/Dockerfile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/webmails/roundcube/Dockerfile b/webmails/roundcube/Dockerfile index 5ff4dcb8..99567502 100644 --- a/webmails/roundcube/Dockerfile +++ b/webmails/roundcube/Dockerfile @@ -1,10 +1,8 @@ FROM php:7.2-apache RUN apt-get update && apt-get install -y \ - libfreetype6-dev \ - libjpeg62-turbo-dev \ - libpng-dev \ - && docker-php-ext-install pdo_mysql zip + zlib1g-dev \ + && docker-php-ext-install zip ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.3.7/roundcubemail-1.3.7-complete.tar.gz From 5341ee4472e0f9067b5391d918dda38f77e99c46 Mon Sep 17 00:00:00 2001 From: kaiyou Date: Tue, 25 Sep 2018 21:04:30 +0200 Subject: [PATCH 47/70] Add a Dockerfile for buliding the docs --- docs/Dockerfile | 13 +++++++++++++ docs/requirements.txt | 1 - 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 docs/Dockerfile diff --git a/docs/Dockerfile b/docs/Dockerfile new file mode 100644 index 00000000..a850a664 --- /dev/null +++ b/docs/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3-alpine + +RUN apk add --no-cache git + +COPY docs/requirements.txt requirements.txt +RUN pip install -r /requirements.txt \ + && mkdir /src + +WORKDIR /src +COPY .git /src/.git + +RUN sphinx-versioning build -b -B 1.5 -r 1.5 -w '^[0-9.]*$' -w master -W '^$' /src /build + diff --git a/docs/requirements.txt b/docs/requirements.txt index 2572817f..98338e63 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -3,4 +3,3 @@ Sphinx sphinx-autobuild sphinx-rtd-theme sphinxcontrib-versioning -paramiko From 72cfadd5e8d722b0a4b7e039c6f095c10241fb69 Mon Sep 17 00:00:00 2001 From: kaiyou Date: Tue, 25 Sep 2018 21:08:04 +0200 Subject: [PATCH 48/70] Build the docs during tests --- tests/build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/build.yml b/tests/build.yml index 674abf8c..308b821a 100644 --- a/tests/build.yml +++ b/tests/build.yml @@ -45,3 +45,9 @@ services: none: image: mailu/none:$VERSION build: ../core/none + + docs: + image: mailu/docs:$VERSION + build: + context: ../ + dockerfile: ../docs/Dockerfile From 69c19dca55f321e95d66fa5d6f9aaa1a4ad3fa42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Thu, 27 Sep 2018 21:45:06 +0300 Subject: [PATCH 49/70] Attempt to fix the docs build context --- tests/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/build.yml b/tests/build.yml index 308b821a..ff08de5d 100644 --- a/tests/build.yml +++ b/tests/build.yml @@ -50,4 +50,4 @@ services: image: mailu/docs:$VERSION build: context: ../ - dockerfile: ../docs/Dockerfile + dockerfile: docs/Dockerfile From 339b3c1b24c7821c91f7090fb3a714efb328947f Mon Sep 17 00:00:00 2001 From: kaiyou Date: Fri, 28 Sep 2018 10:41:17 +0200 Subject: [PATCH 50/70] Build the documentation as a Docker image --- docs/Dockerfile | 13 +++++++------ docs/conf.py | 25 ++----------------------- docs/nginx.conf | 5 +++++ docs/requirements.txt | 1 - tests/build.yml | 4 +--- 5 files changed, 15 insertions(+), 33 deletions(-) create mode 100644 docs/nginx.conf diff --git a/docs/Dockerfile b/docs/Dockerfile index a850a664..af481a27 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -1,13 +1,14 @@ FROM python:3-alpine -RUN apk add --no-cache git +COPY requirements.txt /requirements.txt -COPY docs/requirements.txt requirements.txt RUN pip install -r /requirements.txt \ - && mkdir /src + && apk add --no-cache nginx \ + && mkdir /run/nginx -WORKDIR /src -COPY .git /src/.git +COPY ./nginx.conf /etc/nginx/conf.d/default.conf +COPY . /docs -RUN sphinx-versioning build -b -B 1.5 -r 1.5 -w '^[0-9.]*$' -w master -W '^$' /src /build +RUN sphinx-build /docs /build +CMD nginx -g "daemon off;" \ No newline at end of file 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/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 98338e63..4afd9bb6 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,4 +2,3 @@ recommonmark Sphinx sphinx-autobuild sphinx-rtd-theme -sphinxcontrib-versioning diff --git a/tests/build.yml b/tests/build.yml index ff08de5d..c39b0af4 100644 --- a/tests/build.yml +++ b/tests/build.yml @@ -48,6 +48,4 @@ services: docs: image: mailu/docs:$VERSION - build: - context: ../ - dockerfile: docs/Dockerfile + build: ../docs From f97d0d9e437bceaf7dfd54d37cac23c46f1fd7b7 Mon Sep 17 00:00:00 2001 From: kaiyou Date: Tue, 25 Sep 2018 21:04:30 +0200 Subject: [PATCH 51/70] Add a Dockerfile for buliding the docs --- docs/Dockerfile | 13 +++++++++++++ docs/requirements.txt | 1 - 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 docs/Dockerfile diff --git a/docs/Dockerfile b/docs/Dockerfile new file mode 100644 index 00000000..a850a664 --- /dev/null +++ b/docs/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3-alpine + +RUN apk add --no-cache git + +COPY docs/requirements.txt requirements.txt +RUN pip install -r /requirements.txt \ + && mkdir /src + +WORKDIR /src +COPY .git /src/.git + +RUN sphinx-versioning build -b -B 1.5 -r 1.5 -w '^[0-9.]*$' -w master -W '^$' /src /build + diff --git a/docs/requirements.txt b/docs/requirements.txt index 2572817f..98338e63 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -3,4 +3,3 @@ Sphinx sphinx-autobuild sphinx-rtd-theme sphinxcontrib-versioning -paramiko From b287a85124f0485ee97e7deb963d4cb7d53c6ae2 Mon Sep 17 00:00:00 2001 From: kaiyou Date: Tue, 25 Sep 2018 21:08:04 +0200 Subject: [PATCH 52/70] Build the docs during tests --- tests/build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/build.yml b/tests/build.yml index 674abf8c..308b821a 100644 --- a/tests/build.yml +++ b/tests/build.yml @@ -45,3 +45,9 @@ services: none: image: mailu/none:$VERSION build: ../core/none + + docs: + image: mailu/docs:$VERSION + build: + context: ../ + dockerfile: ../docs/Dockerfile From 11bcae4c5744a8c93929e3c6d26030813dff477c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Thu, 27 Sep 2018 21:45:06 +0300 Subject: [PATCH 53/70] Attempt to fix the docs build context --- tests/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/build.yml b/tests/build.yml index 308b821a..ff08de5d 100644 --- a/tests/build.yml +++ b/tests/build.yml @@ -50,4 +50,4 @@ services: image: mailu/docs:$VERSION build: context: ../ - dockerfile: ../docs/Dockerfile + dockerfile: docs/Dockerfile From 2cba045013acab4a14abc338fb0e2650f07df4a0 Mon Sep 17 00:00:00 2001 From: kaiyou Date: Fri, 28 Sep 2018 17:12:50 +0200 Subject: [PATCH 54/70] Explicitely declare required volumes, fixes #568 --- core/dovecot/Dockerfile | 1 + core/nginx/Dockerfile | 1 + core/postfix/Dockerfile | 1 + optional/clamav/Dockerfile | 1 + optional/radicale/Dockerfile | 1 + services/rspamd/Dockerfile | 2 ++ webmails/rainloop/Dockerfile | 3 +++ webmails/roundcube/Dockerfile | 3 +++ 8 files changed, 13 insertions(+) diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index 80e3539a..363a7244 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -10,5 +10,6 @@ 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/nginx/Dockerfile b/core/nginx/Dockerfile index 8a6536eb..adb785d8 100644 --- a/core/nginx/Dockerfile +++ b/core/nginx/Dockerfile @@ -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/postfix/Dockerfile b/core/postfix/Dockerfile index 168f3c60..d853c9f9 100644 --- a/core/postfix/Dockerfile +++ b/core/postfix/Dockerfile @@ -6,5 +6,6 @@ COPY conf /conf COPY start.py /start.py EXPOSE 25/tcp 10025/tcp +VOLUME ["/data"] CMD /start.py 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 c6c2afdd..d5e93db7 100644 --- a/services/rspamd/Dockerfile +++ b/services/rspamd/Dockerfile @@ -12,4 +12,6 @@ RUN sed -i '/fuzzy/,$d' /etc/rspamd/rspamd.conf EXPOSE 11332/tcp 11334/tcp +VOLUME ["/var/lib/rspamd"] + CMD /start.py diff --git a/webmails/rainloop/Dockerfile b/webmails/rainloop/Dockerfile index dfc6c83e..f4571944 100644 --- a/webmails/rainloop/Dockerfile +++ b/webmails/rainloop/Dockerfile @@ -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 99567502..ad198236 100644 --- a/webmails/roundcube/Dockerfile +++ b/webmails/roundcube/Dockerfile @@ -25,4 +25,7 @@ COPY config.inc.php /var/www/html/config/ COPY start.sh /start.sh +EXPOSE 80/tcp +VOLUME ["/data"] + CMD ["/start.sh"] From 4d70a8737e17ac049f9bc030b27cad41c8912d31 Mon Sep 17 00:00:00 2001 From: kaiyou Date: Fri, 28 Sep 2018 17:42:10 +0200 Subject: [PATCH 55/70] Expose the data volume for admin container --- core/admin/Dockerfile | 1 + 1 file changed, 1 insertion(+) 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"] From 76923d80d89447dbaa08ef05e5ee3a372b3d2ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20S=C3=A4nger?= Date: Sat, 29 Sep 2018 13:03:33 +0200 Subject: [PATCH 56/70] implement support for ARC --- services/rspamd/conf/arc.conf | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 services/rspamd/conf/arc.conf 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; From 73add1b428450d78d68852369c670b8f199b4271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Mon, 1 Oct 2018 01:47:40 +0300 Subject: [PATCH 57/70] Documentation on running a local docs container --- docs/contributors/environment.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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 From 6490a43492550fa1a9b42e175668aea0d9995c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Mon, 1 Oct 2018 12:06:18 +0300 Subject: [PATCH 58/70] Revert "Attempt to fix the docs build context" This reverts commit 11bcae4c5744a8c93929e3c6d26030813dff477c. --- tests/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/build.yml b/tests/build.yml index ff08de5d..308b821a 100644 --- a/tests/build.yml +++ b/tests/build.yml @@ -50,4 +50,4 @@ services: image: mailu/docs:$VERSION build: context: ../ - dockerfile: docs/Dockerfile + dockerfile: ../docs/Dockerfile From 07af9978e254162d14b303ca63b72f7dbaeb969e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Mon, 1 Oct 2018 12:06:56 +0300 Subject: [PATCH 59/70] Revert "Build the docs during tests" This reverts commit b287a85124f0485ee97e7deb963d4cb7d53c6ae2. --- tests/build.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/build.yml b/tests/build.yml index 308b821a..674abf8c 100644 --- a/tests/build.yml +++ b/tests/build.yml @@ -45,9 +45,3 @@ services: none: image: mailu/none:$VERSION build: ../core/none - - docs: - image: mailu/docs:$VERSION - build: - context: ../ - dockerfile: ../docs/Dockerfile From 6479f5177b350769ebfa9c8635bc8d4d81802a6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Mon, 1 Oct 2018 12:07:49 +0300 Subject: [PATCH 60/70] Revert "Add a Dockerfile for buliding the docs" This reverts commit f97d0d9e437bceaf7dfd54d37cac23c46f1fd7b7. --- docs/Dockerfile | 13 ------------- docs/requirements.txt | 1 + 2 files changed, 1 insertion(+), 13 deletions(-) delete mode 100644 docs/Dockerfile diff --git a/docs/Dockerfile b/docs/Dockerfile deleted file mode 100644 index a850a664..00000000 --- a/docs/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM python:3-alpine - -RUN apk add --no-cache git - -COPY docs/requirements.txt requirements.txt -RUN pip install -r /requirements.txt \ - && mkdir /src - -WORKDIR /src -COPY .git /src/.git - -RUN sphinx-versioning build -b -B 1.5 -r 1.5 -w '^[0-9.]*$' -w master -W '^$' /src /build - diff --git a/docs/requirements.txt b/docs/requirements.txt index 98338e63..2572817f 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -3,3 +3,4 @@ Sphinx sphinx-autobuild sphinx-rtd-theme sphinxcontrib-versioning +paramiko From cc17962c86fdbd9e16fae4b5f5e613021bccc626 Mon Sep 17 00:00:00 2001 From: ofthesun9 Date: Thu, 4 Oct 2018 17:25:22 +0000 Subject: [PATCH 61/70] fixes #583 --- core/postfix/conf/master.cf | 1 + 1 file changed, 1 insertion(+) diff --git a/core/postfix/conf/master.cf b/core/postfix/conf/master.cf index cbcc5e56..aa1e967a 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={{ REJECT_UNLISTED_RECIPIENT }} -o cleanup_service_name=outclean outclean unix n - n - 0 cleanup -o header_checks=pcre:/etc/postfix/outclean_header_filter.cf From 09d77bc2de8b639b1c655b18c290314297836345 Mon Sep 17 00:00:00 2001 From: ofthesun9 Date: Thu, 4 Oct 2018 18:31:04 +0000 Subject: [PATCH 62/70] Handle the case where the variable REJECT_UNLISTED_RECIPIENT is not set --- core/postfix/conf/master.cf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/postfix/conf/master.cf b/core/postfix/conf/master.cf index aa1e967a..15fd62dc 100644 --- a/core/postfix/conf/master.cf +++ b/core/postfix/conf/master.cf @@ -8,7 +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={{ REJECT_UNLISTED_RECIPIENT }} + -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 From 58a83a93e6599c1b0c090bb2bc237f37d2b10f1d Mon Sep 17 00:00:00 2001 From: ofthesun9 Date: Thu, 4 Oct 2018 18:44:27 +0000 Subject: [PATCH 63/70] Add REJECT_UNLISTED_RECIPIENT variable in .env file --- docs/compose/.env | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/compose/.env b/docs/compose/.env index 9477448a..721aaf22 100644 --- a/docs/compose/.env +++ b/docs/compose/.env @@ -132,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= From c135b37b076c148597eb59c1dcb3b5d38fb144ee Mon Sep 17 00:00:00 2001 From: kaiyou Date: Sat, 6 Oct 2018 18:53:49 +0200 Subject: [PATCH 64/70] Enable mergify --- .mergify.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .mergify.yml diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 00000000..27d34606 --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,11 @@ +rules: + default: null + branches: + master: + protection: + required_status_checks: + strict: true + contexts: + - continuous-integration/travis-ci + required_pull_request_reviews: + required_approving_review_count: 2 From 7a7854bf3fcdaecc8a23193f6707bf5dab2a879b Mon Sep 17 00:00:00 2001 From: kaiyou Date: Sat, 6 Oct 2018 19:09:59 +0200 Subject: [PATCH 65/70] Disable strict checking --- .mergify.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.mergify.yml b/.mergify.yml index 27d34606..7195e58e 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -4,7 +4,6 @@ rules: master: protection: required_status_checks: - strict: true contexts: - continuous-integration/travis-ci required_pull_request_reviews: From c457ccfa6060229faf39cfa5aceedd0fd047d1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Sun, 7 Oct 2018 00:32:05 +0300 Subject: [PATCH 66/70] Use tenacity for resolver retries --- core/dovecot/Dockerfile | 3 ++- core/dovecot/start.py | 26 +++++++++----------------- core/postfix/Dockerfile | 3 ++- core/postfix/start.py | 19 ++++++------------- services/rspamd/Dockerfile | 3 ++- services/rspamd/start.py | 19 ++++++------------- 6 files changed, 27 insertions(+), 46 deletions(-) diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index 363a7244..3bfd67fc 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -3,7 +3,8 @@ 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 \ - dovecot-fts-lucene rspamd-client@testing python py-jinja2 + dovecot-fts-lucene rspamd-client@testing python py-jinja2 py-pip \ + && pip install tenacity COPY conf /conf COPY sieve /var/lib/dovecot diff --git a/core/dovecot/start.py b/core/dovecot/start.py index 2de114fd..bab5f1ee 100755 --- a/core/dovecot/start.py +++ b/core/dovecot/start.py @@ -4,27 +4,19 @@ import jinja2 import os import socket import glob -import time +from tenacity import retry convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) -# Actual startup script -i = 0 -t = 10 -while True: - i += 1 - try: - 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")) - except socket.gaierror as err: - if i >= t: - raise - time.sleep(10) - continue - break +@retry(stop=stop_after_attempt(10), wait=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/postfix/Dockerfile b/core/postfix/Dockerfile index d853c9f9..61b8f1ea 100644 --- a/core/postfix/Dockerfile +++ b/core/postfix/Dockerfile @@ -1,6 +1,7 @@ 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 tenacity COPY conf /conf COPY start.py /start.py diff --git a/core/postfix/start.py b/core/postfix/start.py index f3c6aaca..30167da5 100755 --- a/core/postfix/start.py +++ b/core/postfix/start.py @@ -5,23 +5,16 @@ import os import socket import glob import shutil -import time +from tenacity import retry convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) +@retry(stop=stop_after_attempt(10), wait=wait_random(min=2, max=5)) +def resolve(): + os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front")) + # Actual startup script -i = 0 -t = 10 -while True: - i += 1 - try: - os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front")) - except socket.gaierror as err: - if i >= t: - raise - time.sleep(10) - continue - break +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/services/rspamd/Dockerfile b/services/rspamd/Dockerfile index d5e93db7..e8eb49f9 100644 --- a/services/rspamd/Dockerfile +++ b/services/rspamd/Dockerfile @@ -1,6 +1,7 @@ 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 tenacity RUN mkdir /run/rspamd diff --git a/services/rspamd/start.py b/services/rspamd/start.py index 08301a0d..cb9a1788 100755 --- a/services/rspamd/start.py +++ b/services/rspamd/start.py @@ -4,23 +4,16 @@ import jinja2 import os import socket import glob -import time +from tenacity import retry convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) +@retry(stop=stop_after_attempt(10), wait=wait_random(min=2, max=5)) +def resolve(): + os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front")) + # Actual startup script -i = 0 -t = 10 -while True: - i += 1 - try: - os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front")) - except socket.gaierror as err: - if i >= t: - raise - time.sleep(10) - continue - break +resolve() if "HOST_REDIS" not in os.environ: os.environ["HOST_REDIS"] = "redis" for rspamd_file in glob.glob("/conf/*"): From 1bae5968ade8a945873c63c5e5f7bbe92564fcf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Sun, 7 Oct 2018 01:39:02 +0300 Subject: [PATCH 67/70] Import tenacy and fix syntax errors --- core/dovecot/start.py | 3 ++- core/postfix/start.py | 3 ++- services/rspamd/start.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/core/dovecot/start.py b/core/dovecot/start.py index bab5f1ee..40e1edf0 100755 --- a/core/dovecot/start.py +++ b/core/dovecot/start.py @@ -4,11 +4,12 @@ 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=stop_after_attempt(10), wait=wait_random(min=2, max=5)) +@retry(stop=tenacity.stop_after_attempt(10), 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")) diff --git a/core/postfix/start.py b/core/postfix/start.py index 30167da5..bfa13da2 100755 --- a/core/postfix/start.py +++ b/core/postfix/start.py @@ -5,11 +5,12 @@ 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=stop_after_attempt(10), wait=wait_random(min=2, max=5)) +@retry(stop=tenacity.stop_after_attempt(10), wait=tenacity.wait_random(min=2, max=5)) def resolve(): os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front")) diff --git a/services/rspamd/start.py b/services/rspamd/start.py index cb9a1788..3ef309b2 100755 --- a/services/rspamd/start.py +++ b/services/rspamd/start.py @@ -4,11 +4,12 @@ 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=stop_after_attempt(10), wait=wait_random(min=2, max=5)) +@retry(stop=tenacity.stop_after_attempt(10), wait=tenacity.wait_random(min=2, max=5)) def resolve(): os.environ["FRONT_ADDRESS"] = socket.gethostbyname(os.environ.get("FRONT_ADDRESS", "front")) From 16469d72823c36718a30691cb210cb74fcd2fc70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Sun, 7 Oct 2018 01:40:22 +0300 Subject: [PATCH 68/70] Upgrade to newer pip version --- core/dovecot/Dockerfile | 1 + core/postfix/Dockerfile | 1 + services/rspamd/Dockerfile | 1 + 3 files changed, 3 insertions(+) diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index 3bfd67fc..a9a3f854 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -4,6 +4,7 @@ RUN echo "@testing http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/re && apk add --no-cache \ dovecot dovecot-sqlite dovecot-pigeonhole-plugin dovecot-pigeonhole-plugin-extdata \ dovecot-fts-lucene rspamd-client@testing python py-jinja2 py-pip \ + && pip install --upgrade pip && pip install tenacity COPY conf /conf diff --git a/core/postfix/Dockerfile b/core/postfix/Dockerfile index 61b8f1ea..bb7acb9b 100644 --- a/core/postfix/Dockerfile +++ b/core/postfix/Dockerfile @@ -1,6 +1,7 @@ FROM alpine:3.7 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 diff --git a/services/rspamd/Dockerfile b/services/rspamd/Dockerfile index e8eb49f9..987e5ab0 100644 --- a/services/rspamd/Dockerfile +++ b/services/rspamd/Dockerfile @@ -1,6 +1,7 @@ FROM alpine:edge 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 From 716ed16f34056e717fd92a851c047bc5b3f77e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Sun, 7 Oct 2018 01:52:52 +0300 Subject: [PATCH 69/70] Fix typo --- core/dovecot/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index a9a3f854..36effc6a 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -4,7 +4,7 @@ RUN echo "@testing http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/re && apk add --no-cache \ dovecot dovecot-sqlite dovecot-pigeonhole-plugin dovecot-pigeonhole-plugin-extdata \ dovecot-fts-lucene rspamd-client@testing python py-jinja2 py-pip \ - && pip install --upgrade pip + && pip install --upgrade pip \ && pip install tenacity COPY conf /conf From 081762986990bd26ddf7d2cd891e60c57e6ec50b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Sun, 7 Oct 2018 02:10:13 +0300 Subject: [PATCH 70/70] Increase attempts as it failed on fresh Swarm host --- core/dovecot/start.py | 2 +- core/postfix/start.py | 2 +- services/rspamd/start.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/dovecot/start.py b/core/dovecot/start.py index 40e1edf0..0aa7a365 100755 --- a/core/dovecot/start.py +++ b/core/dovecot/start.py @@ -9,7 +9,7 @@ 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(10), wait=tenacity.wait_random(min=2, max=5)) +@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")) diff --git a/core/postfix/start.py b/core/postfix/start.py index bfa13da2..e3c13110 100755 --- a/core/postfix/start.py +++ b/core/postfix/start.py @@ -10,7 +10,7 @@ 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(10), wait=tenacity.wait_random(min=2, max=5)) +@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")) diff --git a/services/rspamd/start.py b/services/rspamd/start.py index 3ef309b2..b979517e 100755 --- a/services/rspamd/start.py +++ b/services/rspamd/start.py @@ -9,7 +9,7 @@ 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(10), wait=tenacity.wait_random(min=2, max=5)) +@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"))