Merge remote-tracking branch 'upstream/master' into feat-psql-support

master
Tim Möhlmann 6 years ago
commit b2823c23b8
No known key found for this signature in database
GPG Key ID: 8677988D8072E8DE

@ -2,15 +2,24 @@ pull_request_rules:
- name: Successful travis and 2 approved reviews - name: Successful travis and 2 approved reviews
conditions: conditions:
- status-success=continuous-integration/travis-ci/pr - status-success=continuous-integration/travis-ci/pr
- label!=["status"/wip","status/blocked"]
- "#approved-reviews-by>=2" - "#approved-reviews-by>=2"
actions: actions:
merge: merge:
method: merge method: merge
strict: true
dismiss_reviews:
approved: true
- name: Trusted author, successful travis and 1 approved review - name: Trusted author, successful travis and 1 approved review
conditions: conditions:
- author~=(kaiyou|muhlemmer|mildred|HorayNarea|adi90x|hoellen|ofthesun9) - author~=(kaiyou|muhlemmer|mildred|HorayNarea|adi90x|hoellen|ofthesun9)
- status-success=continuous-integration/travis-ci/pr - status-success=continuous-integration/travis-ci/pr
- label!=["status"/wip","status/blocked","review/need2"]
- "#approved-reviews-by>=1" - "#approved-reviews-by>=1"
actions: actions:
merge: merge:
method: merge method: merge
strict: true
dismiss_reviews:
approved: true

@ -22,3 +22,5 @@ Other contributors:
- "SunMar" - Dutch translation - "SunMar" - Dutch translation
- "Marty Hou" - Chinese Simple translation - "Marty Hou" - Chinese Simple translation
- [Thomas Sänger](https://github.com/HorayNarea) - German translation - [Thomas Sänger](https://github.com/HorayNarea) - German translation
- [Tim Mohlmann](https://github.com/muhlemmer) - [Contributions](https://github.com/Mailu/Mailu/commits?author=muhlemmer)
- [Ionut Filip](https://github.com/ionutfilip) - [Contributions](https://github.com/Mailu/Mailu/commits?author=ionutfilip)

@ -5,6 +5,109 @@ Notable changes to this project are documented in the current file. For more
details about individual changes, see the Git log. You should read this before details about individual changes, see the Git log. You should read this before
upgrading Freposte.io as some changes will include useful notes. upgrading Freposte.io as some changes will include useful notes.
v1.6.0 - unreleased
-------------------
- Global: Architecture of the central container ([#56](https://github.com/Mailu/Mailu/issues/56), [#108](https://github.com/Mailu/Mailu/issues/108))
- Global: Serve documentation with docker ([#601](https://github.com/Mailu/Mailu/issues/601), [#608](https://github.com/Mailu/Mailu/issues/608))
- Global: Travis-CI automated test build ([#602](https://github.com/Mailu/Mailu/issues/602))
- Global: Abstract db access from Postfix and Dovecot ([#612](https://github.com/Mailu/Mailu/issues/612))
- Global: Refactor the admin architecture and configuration management ([#670](https://github.com/Mailu/Mailu/issues/670))
- Feature: Used quota in admin interface ([#216](https://github.com/Mailu/Mailu/issues/216))
- Feature: User Signup ([#281](https://github.com/Mailu/Mailu/issues/281), [#340](https://github.com/Mailu/Mailu/issues/340))
- Feature: Client setup page ([#342](https://github.com/Mailu/Mailu/issues/342))
- Feature: Administration setup page ([#343](https://github.com/Mailu/Mailu/issues/343))
- Feature: Visual notice whether the mx record points to mailu server ([#356](https://github.com/Mailu/Mailu/issues/356))
- Feature: Option for vacation start ([#362](https://github.com/Mailu/Mailu/issues/362))
- Feature: Enable enigma in Roundcube ([#391](https://github.com/Mailu/Mailu/issues/391))
- Feature: Allow more charcaters as a valid email address ([#443](https://github.com/Mailu/Mailu/issues/443))
- Feature: IDNA support ([#446](https://github.com/Mailu/Mailu/issues/446))
- Feature: Disable user account ([#449](https://github.com/Mailu/Mailu/issues/449))
- Feature: Use fuzzy hashes in rpamd ([#456](https://github.com/Mailu/Mailu/issues/456), [#527](https://github.com/Mailu/Mailu/issues/527))
- Feature: Enable “doveadm -A” command ([#458](https://github.com/Mailu/Mailu/issues/458))
- Feature: Remove the Service Status page ([#463](https://github.com/Mailu/Mailu/issues/463))
- Feature: Automated Releases ([#487](https://github.com/Mailu/Mailu/issues/487))
- Feature: Support for ARC ([#495](https://github.com/Mailu/Mailu/issues/495))
- Feature: Add posibilty to run webmail on root ([#501](https://github.com/Mailu/Mailu/issues/501))
- Feature: Upgrade docker-compose.yml to version 3 ([#539](https://github.com/Mailu/Mailu/issues/539))
- Feature: Documentation to deploy mailu on a docker swarm ([#551](https://github.com/Mailu/Mailu/issues/551))
- Feature: Add full-text search support ([#552](https://github.com/Mailu/Mailu/issues/552))
- Feature: Add optional Maildir-Compression ([#553](https://github.com/Mailu/Mailu/issues/553))
- Feature: Preserve rspamd history on container restart ([#561](https://github.com/Mailu/Mailu/issues/561))
- Feature: FAQ ([#564](https://github.com/Mailu/Mailu/issues/564), [#677](https://github.com/Mailu/Mailu/issues/677))
- Feature: Kubernetes support ([#576](https://github.com/Mailu/Mailu/issues/576))
- Feature: Option to bounce or reject email when recipient is unknown ([#583](https://github.com/Mailu/Mailu/issues/583), [#626](https://github.com/Mailu/Mailu/issues/626))
- Feature: implement healthchecks for all containers ([#631](https://github.com/Mailu/Mailu/issues/631))
- Feature: Option to send front logs to journald or syslog ([#584](https://github.com/Mailu/Mailu/issues/584), [#661](https://github.com/Mailu/Mailu/issues/661))
- Feature: Support bcrypt and PBKDF2 ([#647](https://github.com/Mailu/Mailu/issues/647), [#667](https://github.com/Mailu/Mailu/issues/667))
- Feature: enable http2 ([#674](https://github.com/Mailu/Mailu/issues/674))
- Feature: Unbound DNS as optional service ([#681](https://github.com/Mailu/Mailu/issues/681))
- Feature: Re-write test suite ([#682](https://github.com/Mailu/Mailu/issues/682))
- Feature: Docker image prefixes ([#702](https://github.com/Mailu/Mailu/issues/702))
- Feature: Add authentication method “login” for Outlook ([#704](https://github.com/Mailu/Mailu/issues/704))
- Feature: Allow extending nginx config with overrides ([#713](https://github.com/Mailu/Mailu/issues/713))
- Feature: Dynamic attachment size limit ([#731](https://github.com/Mailu/Mailu/issues/731))
- Feature: Certificate watcher for external certs to reload nginx ([#732](https://github.com/Mailu/Mailu/issues/732))
- Feature: Kubernetes
- Enhancement: Use pre-defined dhparam ([#322](https://github.com/Mailu/Mailu/issues/322))
- Enhancement: Disable ssl_session_tickets ([#329](https://github.com/Mailu/Mailu/issues/329))
- Enhancement: max attachment size in roundcube ([#338](https://github.com/Mailu/Mailu/issues/338))
- Enhancement: Use x-forwarded-proto with redirects ([#347](https://github.com/Mailu/Mailu/issues/347))
- Enhancement: Added adress verification before accepting mails for delivery ([#353](https://github.com/Mailu/Mailu/issues/353))
- Enhancement: Reverse proxy - Real ip header and mail-letsencrypt ([#358](https://github.com/Mailu/Mailu/issues/358))
- Enhancement: Parametrize hosts ([#373](https://github.com/Mailu/Mailu/issues/373))
- Enhancement: Expose ports in dockerfiles ([#392](https://github.com/Mailu/Mailu/issues/392))
- Enhancement: Added webmail-imap dependency in docker-compose ([#403](https://github.com/Mailu/Mailu/issues/403))
- Enhancement: Add environment variables to allow running outside of docker-compose ([#429](https://github.com/Mailu/Mailu/issues/429))
- Enhancement: Add original Delivered-To header to received messages ([#433](https://github.com/Mailu/Mailu/issues/433))
- Enhancement: Use HOST_ADMIN in "Forwarding authentication server" ([#436](https://github.com/Mailu/Mailu/issues/436), [#437](https://github.com/Mailu/Mailu/issues/437))
- Enhancement: Use POD_ADDRESS_RANGE for Dovecot ([#448](https://github.com/Mailu/Mailu/issues/448))
- Enhancement: Using configurable filenames for TLS certs ([#468](https://github.com/Mailu/Mailu/issues/468))
- Enhancement: Don't require BootstrapCDN (GDPR-compliance) ([#477](https://github.com/Mailu/Mailu/issues/477))
- Enhancement: Use dynamic client_max_body_size for webmail ([#502](https://github.com/Mailu/Mailu/issues/502))
- Enhancement: New logo design ([#509](https://github.com/Mailu/Mailu/issues/509))
- Enhancement: New manifests for Kubernetes ([#544](https://github.com/Mailu/Mailu/issues/544))
- Enhancement: Pin Alpine image ([#548](https://github.com/Mailu/Mailu/issues/548), [#557](https://github.com/Mailu/Mailu/issues/557))
- Enhancement: Use safer cipher in roundcube ([#597](https://github.com/Mailu/Mailu/issues/597))
- Enhancement: Improve sender checks ([#633](https://github.com/Mailu/Mailu/issues/633))
- Enhancement: Use PHP 7.2 for rainloop and roundcube ([#606](https://github.com/Mailu/Mailu/issues/606), [#642](https://github.com/Mailu/Mailu/issues/642))
- Enhancement: Multi-version documentation ([#664](https://github.com/Mailu/Mailu/issues/664))
- Enhancement: Contribution documentation ([#700](https://github.com/Mailu/Mailu/issues/700))
- Enhancement: Move Mailu Docker network to a fixed subnet ([#727](https://github.com/Mailu/Mailu/issues/727))
- Enhancement: Added regex validation for alias username ([#764](https://github.com/Mailu/Mailu/issues/764))
- Enhancement: Update documentation
- Upstream: Update Roundcube
- Upstream: Update Rainloop
- Bug: Rainloop fails with "domain not allowed" ([#93](https://github.com/Mailu/Mailu/issues/93))
- Bug: Announces fail ([#309](https://github.com/Mailu/Mailu/issues/309))
- Bug: Authentication issues with rspamd admin ui ([#315](https://github.com/Mailu/Mailu/issues/315))
- Bug: front hangup on restart ([#341](https://github.com/Mailu/Mailu/issues/341))
- Bug: Display the proper user quota when set to 0/infinity ([#345](https://github.com/Mailu/Mailu/issues/345))
- Bug: Domain details button "Regenerate keys" when no keys are generated yet ([#346](https://github.com/Mailu/Mailu/issues/346))
- Bug: Relayed Domains: access denied error ([#351](https://github.com/Mailu/Mailu/issues/351))
- Bug: Do not deny HTTP access upon TLS error when the flavor is mail ([#352](https://github.com/Mailu/Mailu/issues/352))
- Bug: php_zip extension missing in Roundcube webmail ([#364](https://github.com/Mailu/Mailu/issues/364))
- Bug: RoundCube webmail .htaccess assumes PHP 5 ([#366](https://github.com/Mailu/Mailu/issues/366))
- Bug: No quota shows "0 Bytes" in user list ([#368](https://github.com/Mailu/Mailu/issues/368))
- Bug: RELAYNETS not honored when login is different from sender ([#369](https://github.com/Mailu/Mailu/issues/369))
- Bug: Request Entity Too Large ([#371](https://github.com/Mailu/Mailu/issues/371))
- Bug: Pass the full host to the backend ([#372](https://github.com/Mailu/Mailu/issues/372))
- Bug: Can't send from an email account that has forwarding ([#390](https://github.com/Mailu/Mailu/issues/390))
- Bug: SSL protocol error roundcube/imap ([#411](https://github.com/Mailu/Mailu/issues/411), [#414](https://github.com/Mailu/Mailu/issues/414))
- Bug: Unable to send from alternative domains ([#415](https://github.com/Mailu/Mailu/issues/415))
- Bug: Webadmin redirect ignores host port ([#419](https://github.com/Mailu/Mailu/issues/419))
- Bug: Disable esld when signing with dkim ([#435](https://github.com/Mailu/Mailu/issues/435))
- Bug: DKIM missing when using identities ([#462](https://github.com/Mailu/Mailu/issues/462))
- Bug: Moving mails from Junk to Trash flags them as ham ([#474](https://github.com/Mailu/Mailu/issues/474))
- Bug: Cannot set the "keep emails" for fetched accounts ([#479](https://github.com/Mailu/Mailu/issues/479))
- Bug: CVE-2018-8740 ([#482](https://github.com/Mailu/Mailu/issues/482))
- Bug: Hide administration header in sidebar for normal users ([#505](https://github.com/Mailu/Mailu/issues/505))
- Bug: Return correct status codes from auth rate limiter failure ([#513](https://github.com/Mailu/Mailu/issues/513))
- Bug: Domain edit page shows "Create" button ([#523](https://github.com/Mailu/Mailu/issues/523))
- Bug: Hostname resolving in start.py should retry on failure [docker swarm] ([#555](https://github.com/Mailu/Mailu/issues/555))
- Bug: Error when trying to log in with an account without domain ([#585](https://github.com/Mailu/Mailu/issues/585))
- Bug: Fix rainloop permissions ([#637](https://github.com/Mailu/Mailu/issues/637))
v1.5.1 - 2017-11-21 v1.5.1 - 2017-11-21
------------------- -------------------

@ -1,7 +1,7 @@
This project is open source, and your contributions are all welcome. There are mostly three different ways one can contribute to the project: This project is open source, and your contributions are all welcome. There are mostly three different ways one can contribute to the project:
1. use Mailu, either on test or on production instances, and report meaningful bugs when you find some; 1. use Mailu, either on test or on production instances, and report meaningful bugs when you find some;
2. contribute code and/or configuration to the repository (see [the development guidelines](https://mailu.io/contributors/guide.html) for details); 2. contribute code and/or configuration to the repository (see [the development guidelines](https://mailu.io/master/contributors/guide.html) for details);
3. contribute localization to your native language (see [the localization docs](https://mailu.io/contributors/localization.html) for details); 3. contribute localization to your native language (see [the localization docs](https://mailu.io/master/contributors/localization.html) for details);
Either way, keep in mind that the code you write or the translation you produce muts be licensed under the same conditions as the project itself. Additionally, all contributors are considered equal co-authors of the project. Either way, keep in mind that the code you write or the translation you produce muts be licensed under the same conditions as the project itself. Additionally, all contributors are considered equal co-authors of the project.

@ -21,7 +21,7 @@ COPY start.py /start.py
RUN pybabel compile -d mailu/translations RUN pybabel compile -d mailu/translations
EXPOSE 80/tcp EXPOSE 80/tcp
VOLUME ["/data"] VOLUME ["/data","/dkim"]
ENV FLASK_APP mailu ENV FLASK_APP mailu
CMD /start.py CMD /start.py

@ -8,7 +8,6 @@ def create_app_from_config(config):
""" Create a new application based on the given configuration """ Create a new application based on the given configuration
""" """
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.app_context().push()
app.cli.add_command(manage.mailu) app.cli.add_command(manage.mailu)
# Bootstrap is used for basic JS and CSS loading # Bootstrap is used for basic JS and CSS loading

@ -56,6 +56,7 @@ DEFAULT_CONFIG = {
'HOST_WEBMAIL': 'webmail', 'HOST_WEBMAIL': 'webmail',
'HOST_FRONT': 'front', 'HOST_FRONT': 'front',
'HOST_AUTHSMTP': os.environ.get('HOST_SMTP', 'smtp'), 'HOST_AUTHSMTP': os.environ.get('HOST_SMTP', 'smtp'),
'SUBNET': '192.168.203.0/24',
'POD_ADDRESS_RANGE': None 'POD_ADDRESS_RANGE': None
} }

@ -9,7 +9,7 @@ import base64
@internal.route("/auth/email") @internal.route("/auth/email")
@utils.limiter.limit( @utils.limiter.limit(
app.config["AUTH_RATELIMIT"], lambda: app.config["AUTH_RATELIMIT"],
lambda: flask.request.headers["Client-Ip"] lambda: flask.request.headers["Client-Ip"]
) )
def nginx_authentication(): def nginx_authentication():

@ -10,13 +10,9 @@ import os
def dovecot_passdb_dict(user_email): def dovecot_passdb_dict(user_email):
user = models.User.query.get(user_email) or flask.abort(404) user = models.User.query.get(user_email) or flask.abort(404)
allow_nets = [] allow_nets = []
allow_nets.append( allow_nets.append(app.config["SUBNET"])
app.config.get("POD_ADDRESS_RANGE") or if app.config["POD_ADDRESS_RANGE"]:
socket.gethostbyname(app.config["HOST_FRONT"]) allow_nets.append(app.config["POD_ADDRESS_RANGE"])
)
if os.environ["WEBMAIL"] != "none":
allow_nets.append(socket.gethostbyname(app.config["HOST_WEBMAIL"]))
print(allow_nets)
return flask.jsonify({ return flask.jsonify({
"password": None, "password": None,
"nopassword": "Y", "nopassword": "Y",

@ -433,7 +433,7 @@ class Alias(Base, Email):
) )
) )
) )
).first() ).order_by(cls.wildcard, sqlalchemy.func.char_length(cls.localpart).desc()).first()
class Token(Base): class Token(Base):

@ -136,7 +136,7 @@ class TokenForm(flask_wtf.FlaskForm):
class AliasForm(flask_wtf.FlaskForm): class AliasForm(flask_wtf.FlaskForm):
localpart = fields.StringField(_('Alias'), [validators.DataRequired()]) localpart = fields.StringField(_('Alias'), [validators.DataRequired(), validators.Regexp(LOCALPART_REGEX)])
wildcard = fields.BooleanField( wildcard = fields.BooleanField(
_('Use SQL LIKE Syntax (e.g. for catch-all aliases)')) _('Use SQL LIKE Syntax (e.g. for catch-all aliases)'))
destination = DestinationField(_('Destination')) destination = DestinationField(_('Destination'))

@ -53,7 +53,7 @@ configure your email client
</tr> </tr>
<tr> <tr>
<th>{% trans %}Username{% endtrans %}</th> <th>{% trans %}Username{% endtrans %}</th>
<td><pre>{{ current_user or "******" }}</pre></td> <td><pre>{{ current_user if current_user.is_authenticated else "******" }}</pre></td>
</tr> </tr>
<tr> <tr>
<th>{% trans %}Password{% endtrans %}</th> <th>{% trans %}Password{% endtrans %}</th>

@ -1,13 +1,13 @@
alembic==1.0.2 alembic==1.0.2
asn1crypto==0.24.0 asn1crypto==0.24.0
Babel==2.6.0 Babel==2.6.0
bcrypt==3.1.4 bcrypt==3.1.5
blinker==1.4 blinker==1.4
cffi==1.11.5 cffi==1.11.5
Click==7.0 Click==7.0
cryptography==2.3.1 cryptography==2.3.1
decorator==4.3.0 decorator==4.3.0
dnspython==1.15.0 dnspython==1.16.0
dominate==2.3.4 dominate==2.3.4
Flask==1.0.2 Flask==1.0.2
Flask-Babel==0.12.2 Flask-Babel==0.12.2
@ -15,7 +15,7 @@ Flask-Bootstrap==3.3.7.1
Flask-DebugToolbar==0.10.1 Flask-DebugToolbar==0.10.1
Flask-Limiter==1.0.1 Flask-Limiter==1.0.1
Flask-Login==0.4.1 Flask-Login==0.4.1
Flask-Migrate==2.3.0 Flask-Migrate==2.3.1
Flask-Script==2.0.6 Flask-Script==2.0.6
Flask-SQLAlchemy==2.3.2 Flask-SQLAlchemy==2.3.2
Flask-WTF==0.14.2 Flask-WTF==0.14.2
@ -35,15 +35,15 @@ python-dateutil==2.7.5
python-editor==1.0.3 python-editor==1.0.3
pytz==2018.7 pytz==2018.7
PyYAML==3.13 PyYAML==3.13
redis==2.10.6 redis==3.0.1
six==1.11.0 six==1.11.0
SQLAlchemy==1.2.13 SQLAlchemy==1.2.13
tabulate==0.8.2 tabulate==0.8.2
tenacity==5.0.2
validators==0.12.2 validators==0.12.2
visitor==0.1.3 visitor==0.1.3
Werkzeug==0.14.1 Werkzeug==0.14.1
WTForms==2.2.1 WTForms==2.2.1
WTForms-Components==0.10.3 WTForms-Components==0.10.3
psycopg2 psycopg2
tenacity
mysqlclient mysqlclient

@ -7,6 +7,7 @@ postmaster_address = {{ POSTMASTER }}@{{ DOMAIN }}
hostname = {{ HOSTNAMES.split(",")[0] }} hostname = {{ HOSTNAMES.split(",")[0] }}
submission_host = {{ FRONT_ADDRESS }} submission_host = {{ FRONT_ADDRESS }}
{% if DISABLE_FTS_LUCENE != 'true' %}
############### ###############
# Full-text search # Full-text search
############### ###############
@ -20,6 +21,7 @@ plugin {
fts_lucene = whitespace_chars=@. fts_lucene = whitespace_chars=@.
} }
{% endif %}
############### ###############
# Mailboxes # Mailboxes
@ -64,6 +66,7 @@ plugin {
############### ###############
# Authentication # Authentication
############### ###############
auth_username_chars =
auth_mechanisms = plain login auth_mechanisms = plain login
disable_plaintext_auth = no disable_plaintext_auth = no

@ -7,13 +7,14 @@ RUN apk add --no-cache \
RUN pip3 install jinja2 RUN pip3 install jinja2
# Image specific layers under this line # Image specific layers under this line
RUN apk add --no-cache certbot nginx nginx-mod-mail openssl curl \ RUN apk add --no-cache certbot nginx nginx-mod-mail openssl curl \
&& pip3 install idna requests && pip3 install idna requests watchdog
COPY conf /conf COPY conf /conf
COPY *.py / COPY *.py /
EXPOSE 80/tcp 443/tcp 110/tcp 143/tcp 465/tcp 587/tcp 993/tcp 995/tcp 25/tcp 10025/tcp 10143/tcp EXPOSE 80/tcp 443/tcp 110/tcp 143/tcp 465/tcp 587/tcp 993/tcp 995/tcp 25/tcp 10025/tcp 10143/tcp
VOLUME ["/certs"] VOLUME ["/certs"]
VOLUME ["/overrides"]
CMD /start.py CMD /start.py

@ -0,0 +1,63 @@
#!/usr/bin/python3
"""
Certificate watcher which reloads nginx or reconfigures it, depending on what
happens to externally supplied certificates. Only executed by start.py in case
of TLS_FLAVOR=[mail, cert]
"""
from os.path import exists, split as path_split
from os import system
import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler, FileDeletedEvent, \
FileCreatedEvent, FileModifiedEvent, FileMovedEvent
class ChangeHandler(FileSystemEventHandler):
"watchdog-handler listening on any event, executing the correct configuration/reload steps"
@staticmethod
def reload_nginx():
"merely reload nginx without re-configuring everything"
if exists("/var/run/nginx.pid"):
print("Reloading a running nginx")
system("nginx -s reload")
@staticmethod
def reexec_config():
"execute a reconfiguration of the system, which also reloads"
print("Reconfiguring system")
system("/config.py")
def on_any_event(self, event):
"event-listener checking if the affected files are the cert-files we're interested in"
if event.is_directory:
return
filename = path_split(event.src_path)[-1]
if isinstance(event, FileMovedEvent):
filename = path_split(event.dest_path)[-1]
if filename in ['cert.pem', 'key.pem']:
# all cases except for FileModified need re-configure
if isinstance(event, (FileCreatedEvent, FileMovedEvent, FileDeletedEvent)):
ChangeHandler.reexec_config()
# file modification needs only a nginx reload without config.py
elif isinstance(event, FileModifiedEvent):
ChangeHandler.reload_nginx()
# cert files have been moved away, re-configure
elif isinstance(event, FileMovedEvent) and path_split(event.src_path)[-1] in ['cert.pem', 'key.pem']:
ChangeHandler.reexec_config()
if __name__ == '__main__':
observer = Observer()
handler = ChangeHandler()
observer.schedule(handler, "/certs", recursive=False)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()

@ -84,14 +84,20 @@ http {
} }
{% else %} {% else %}
include /overrides/*.conf;
# Actual logic # Actual logic
{% if WEBMAIL != 'none' %}
{% if WEB_WEBMAIL != '/' %} {% if WEB_WEBMAIL != '/' %}
location / { location / {
return 301 {{ WEB_WEBMAIL }}; {% if WEBROOT_REDIRECT %}
return 301 {{ WEBROOT_REDIRECT }};
{% else %}
return 404;
{% endif %}
} }
{% endif %} {% endif %}
{% if WEBMAIL != 'none' %}
location {{ WEB_WEBMAIL }} { location {{ WEB_WEBMAIL }} {
{% if WEB_WEBMAIL != '/' %} {% if WEB_WEBMAIL != '/' %}
rewrite ^({{ WEB_WEBMAIL }})$ $1/ permanent; rewrite ^({{ WEB_WEBMAIL }})$ $1/ permanent;
@ -245,7 +251,7 @@ mail {
listen 465 ssl; listen 465 ssl;
listen [::]:465 ssl; listen [::]:465 ssl;
protocol smtp; protocol smtp;
smtp_auth plain; smtp_auth plain login;
} }
server { server {

@ -9,6 +9,8 @@ if os.path.exists("/var/run/nginx.pid"):
if os.environ["TLS_FLAVOR"] in [ "letsencrypt","mail-letsencrypt" ]: if os.environ["TLS_FLAVOR"] in [ "letsencrypt","mail-letsencrypt" ]:
subprocess.Popen(["/letsencrypt.py"]) subprocess.Popen(["/letsencrypt.py"])
elif os.environ["TLS_FLAVOR"] in [ "mail", "cert" ]:
subprocess.Popen(["/certwatcher.py"])
subprocess.call(["/config.py"]) subprocess.call(["/config.py"])
os.execv("/usr/sbin/nginx", ["nginx", "-g", "daemon off;"]) os.execv("/usr/sbin/nginx", ["nginx", "-g", "daemon off;"])

@ -14,7 +14,7 @@ queue_directory = /queue
message_size_limit = {{ MESSAGE_SIZE_LIMIT }} message_size_limit = {{ MESSAGE_SIZE_LIMIT }}
# Relayed networks # Relayed networks
mynetworks = 127.0.0.1/32 [::1]/128 {{ RELAYNETS }} mynetworks = 127.0.0.1/32 [::1]/128 {{ SUBNET }} {{ RELAYNETS }}
# Empty alias list to override the configuration variable and disable NIS # Empty alias list to override the configuration variable and disable NIS
alias_maps = alias_maps =
@ -32,7 +32,8 @@ relayhost = {{ RELAYHOST }}
recipient_delimiter = {{ RECIPIENT_DELIMITER }} recipient_delimiter = {{ RECIPIENT_DELIMITER }}
# Only the front server is allowed to perform xclient # Only the front server is allowed to perform xclient
smtpd_authorized_xclient_hosts={{ FRONT_ADDRESS }} {{ POD_ADDRESS_RANGE }} # In kubernetes and Docker swarm, such address cannot be determined using the hostname. Allow for the whole Mailu subnet instead.
smtpd_authorized_xclient_hosts={{ POD_ADDRESS_RANGE or SUBNET }}
############### ###############
# TLS # TLS

@ -0,0 +1,2 @@
# Hostname passed to Traefik
ADDRESS=docs.mailu.io

@ -18,5 +18,3 @@ RUN mkdir -p /build/$VERSION \
EXPOSE 80/tcp EXPOSE 80/tcp
CMD nginx -g "daemon off;" CMD nginx -g "daemon off;"
HEALTHCHECK CMD curl -f -L http://localhost/ || exit 1

@ -0,0 +1,4 @@
{%- extends "layout.html" %}
{% block body %}
{{ body|replace("VERSION_TAG", version) }}
{% endblock %}

@ -1,9 +1,12 @@
# Mailu main configuration file # Mailu main configuration file
# ## Most configuration variables can be modified through the Web interface,
# Most configuration variables can be modified through the Web interface,
# these few settings must however be configured before starting the mail # these few settings must however be configured before starting the mail
# server and require a restart upon change. # server and require a restart upon change.
# Set this to `true` to disable full text search by lucene (value: true, false)
# This is a workaround for the bug in issue #751 (indexer-worker crashes)
DISABLE_FTS_LUCENE=false
################################### ###################################
# Common configuration variables # Common configuration variables
################################### ###################################
@ -21,6 +24,9 @@ SECRET_KEY=ChangeMeChangeMe
BIND_ADDRESS4=127.0.0.1 BIND_ADDRESS4=127.0.0.1
BIND_ADDRESS6=::1 BIND_ADDRESS6=::1
# Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!)
SUBNET=192.168.203.0/24
# Main mail domain # Main mail domain
DOMAIN=mailu.io DOMAIN=mailu.io
@ -61,11 +67,12 @@ ANTIVIRUS=none
# Message size limit in bytes # Message size limit in bytes
# Default: accept messages up to 50MB # Default: accept messages up to 50MB
# Max attachment size will be 33% smaller
MESSAGE_SIZE_LIMIT=50000000 MESSAGE_SIZE_LIMIT=50000000
# Networks granted relay permissions, make sure that you include your Docker # Networks granted relay permissions
# internal network (default to 172.17.0.0/16) # Use this with care, all hosts in this networks will be able to send mail without authentication!
RELAYNETS=172.16.0.0/12 RELAYNETS=
# Will relay all outgoing mails if configured # Will relay all outgoing mails if configured
RELAYHOST= RELAYHOST=
@ -97,6 +104,9 @@ COMPRESSION_LEVEL=
# Web settings # Web settings
################################### ###################################
# Path to redirect / to
WEBROOT_REDIRECT=/webmail
# Path to the admin interface if enabled # Path to the admin interface if enabled
WEB_ADMIN=/admin WEB_ADMIN=/admin

@ -29,6 +29,7 @@ services:
- "$BIND_ADDRESS6:587:587" - "$BIND_ADDRESS6:587:587"
volumes: volumes:
- "$ROOT/certs:/certs" - "$ROOT/certs:/certs"
- "$ROOT/overrides/nginx:/overrides"
redis: redis:
image: redis:alpine image: redis:alpine
@ -87,7 +88,6 @@ services:
volumes: volumes:
- "$ROOT/data:/data" - "$ROOT/data:/data"
- "$ROOT/dkim:/dkim" - "$ROOT/dkim:/dkim"
- /var/run/docker.sock:/var/run/docker.sock:ro
depends_on: depends_on:
- redis - redis
@ -104,3 +104,11 @@ services:
image: mailu/fetchmail:$VERSION image: mailu/fetchmail:$VERSION
restart: always restart: always
env_file: .env env_file: .env
networks:
default:
driver: bridge
ipam:
driver: default
config:
- subnet: $SUBNET

@ -0,0 +1,143 @@
version: '2'
services:
# This would normally not be here, but where you define your system services
traefik:
image: traefik:alpine
command: --docker
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- "/data/traefik/acme.json:/acme.json"
- "/data/traefik/traefik.toml:/traefik.toml"
# This may be needed (plus defining mailu_default external: true) if traefik lives elsewhere
# networks:
# - mailu_default
certdumper:
restart: always
image: mailu/traefik-certdumper:$VERSION
environment:
# Make sure this is the same as the main=-domain in traefik.toml
# !!! Also dont forget to add "TRAEFIK_DOMAIN=[...]" to your .env!
- DOMAIN=$TRAEFIK_DOMAIN
volumes:
- "/data/traefik:/traefik"
- "$ROOT/certs:/output"
front:
image: mailu/nginx:$VERSION
restart: always
env_file: .env
logging:
driver: $LOG_DRIVER
labels: # Traefik labels for simple reverse-proxying
- "traefik.enable=true"
- "traefik.port=80"
- "traefik.frontend.rule=Host:$TRAEFIK_DOMAIN"
- "traefik.docker.network=mailu_default"
ports:
- "$BIND_ADDRESS4:110:110"
- "$BIND_ADDRESS4:143:143"
- "$BIND_ADDRESS4:993:993"
- "$BIND_ADDRESS4:995:995"
- "$BIND_ADDRESS4:25:25"
- "$BIND_ADDRESS4:465:465"
- "$BIND_ADDRESS4:587:587"
- "$BIND_ADDRESS6:110:110"
- "$BIND_ADDRESS6:143:143"
- "$BIND_ADDRESS6:993:993"
- "$BIND_ADDRESS6:995:995"
- "$BIND_ADDRESS6:25:25"
- "$BIND_ADDRESS6:465:465"
- "$BIND_ADDRESS6:587:587"
volumes:
- "$ROOT/overrides/nginx:/overrides"
- /data/traefik/ssl/$TRAEFIK_DOMAIN.crt:/certs/cert.pem
- /data/traefik/ssl/$TRAEFIK_DOMAIN.key:/certs/key.pem
redis:
image: redis:alpine
restart: always
volumes:
- "$ROOT/redis:/data"
imap:
image: mailu/dovecot:$VERSION
restart: always
env_file: .env
volumes:
- "$ROOT/mail:/mail"
- "$ROOT/overrides:/overrides"
depends_on:
- front
smtp:
image: mailu/postfix:$VERSION
restart: always
env_file: .env
volumes:
- "$ROOT/overrides:/overrides"
depends_on:
- front
antispam:
image: mailu/rspamd:$VERSION
restart: always
env_file: .env
volumes:
- "$ROOT/filter:/var/lib/rspamd"
- "$ROOT/dkim:/dkim"
- "$ROOT/overrides/rspamd:/etc/rspamd/override.d"
depends_on:
- front
antivirus:
image: mailu/$ANTIVIRUS:$VERSION
restart: always
env_file: .env
volumes:
- "$ROOT/filter:/data"
webdav:
image: mailu/$WEBDAV:$VERSION
restart: always
env_file: .env
volumes:
- "$ROOT/dav:/data"
admin:
image: mailu/admin:$VERSION
restart: always
env_file: .env
volumes:
- "$ROOT/data:/data"
- "$ROOT/dkim:/dkim"
depends_on:
- redis
webmail:
image: "mailu/$WEBMAIL:$VERSION"
restart: always
env_file: .env
volumes:
- "$ROOT/webmail:/data"
depends_on:
- imap
fetchmail:
image: mailu/fetchmail:$VERSION
restart: always
env_file: .env
networks:
default:
driver: bridge
ipam:
driver: default
config:
- subnet: $SUBNET

@ -0,0 +1,33 @@
# This is just boilerplate stuff you probably have in your own config
logLevel = "INFO"
defaultEntryPoints = ["https","http"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[docker]
endpoint = "unix:///var/run/docker.sock"
watch = true
exposedByDefault = false
# Make sure we get acme.json saved, and onHostRule enabled
[acme]
email = "your@mail.tld"
storage = "acme.json"
entryPoint = "https"
onHostRule = true
[acme.httpChallenge]
entryPoint = "http"
# This should include all of your mail domains, and main= should be your $TRAEFIK_DOMAIN
[[acme.domains]]
main = "mail.your.doma.in"
sans = ["web.mail.your.doma.in", "smtp.mail.doma.in", "imap.mail.doma.in"]

@ -24,6 +24,11 @@ The ``HOSTNAMES`` are all public hostnames for the mail server. Mailu supports
a mail server with multiple hostnames. The first declared hostname is the main a mail server with multiple hostnames. The first declared hostname is the main
hostname and will be exposed over SMTP, IMAP, etc. hostname and will be exposed over SMTP, IMAP, etc.
The ``SUBNET`` defines the address range of the docker network used by Mailu.
This should not conflict with any networks to which your system is connected.
(Internal and external!). Normally this does not need to be changed,
unless there is a conflict with existing networks.
The ``POSTMASTER`` is the local part of the postmaster email address. It is The ``POSTMASTER`` is the local part of the postmaster email address. It is
recommended to setup a generic value and later configure a mail alias for that recommended to setup a generic value and later configure a mail alias for that
address. address.
@ -40,9 +45,9 @@ be too low to avoid dropping legitimate emails and should not be too high to
avoid filling the disks with large junk emails. avoid filling the disks with large junk emails.
The ``RELAYNETS`` are network addresses for which mail is relayed for free with The ``RELAYNETS`` are network addresses for which mail is relayed for free with
no authentication required. This should be used with great care. It is no authentication required. This should be used with great care. If you want other
recommended to include your Docker internal network addresses if other Docker Docker services' outbound mail to be relayed, you can set this to ``172.16.0.0/12``
containers use Mailu as their mail relay. to include **all** Docker networks. The default is to leave this empty.
The ``RELAYHOST`` is an optional address of a mail server relaying all outgoing The ``RELAYHOST`` is an optional address of a mail server relaying all outgoing
mail. mail.

@ -184,7 +184,7 @@ directory structure.
If you do no posses the resources, but want to become an involved tester/reviewer. If you do no posses the resources, but want to become an involved tester/reviewer.
Please contact `muhlemmer on Matrix`_. Please contact `muhlemmer on Matrix`_.
He can provide access to a testing server, if a thrust relation can be established. He can provide access to a testing server, if a trust relation can be established.
.. _`muhlemmer on Matrix`: https://matrix.to/#/@muhlemmer:matrix.org .. _`muhlemmer on Matrix`: https://matrix.to/#/@muhlemmer:matrix.org

@ -5,21 +5,32 @@ The demo server is for demonstration and test purposes only. Please be
respectful and keep the demo server functional for others to be able to try it respectful and keep the demo server functional for others to be able to try it
out. out.
The server is reset every day at 3am, french time. If you find the server is If you find the server is unusable, you can ask for someone to reset it manually on our Matrix
unusable, you can still ask for someone to reset it manually on our Matrix chat channel. Please do not open tickets every time the server is down.
chat channel. Please do not open tickets everytime the server is down. Please Please do not open tickets if the server is quite slow: it *is* slow because the
do not open tickets if the server is quite slow: it *is* slow because the services have only limited resources available.
machine is a cheap leased server.
Keep in mind that the demo server is also used for some automated tests and runs Keep in mind that the demo server runs the latest unstable (master) version.
the latest unstable version. If you find actual bugs when using the demo If you find actual bugs when using the demo server, please report these!
server, please report these!
Functionality
-------------
- The server is reset every day at 3am, UTC.
- You can send mail from any client to the server.
However, the stmp server is made incapable of relaying the e-mail to the destination server.
As such, the mail will never arrive. This is to prevent abuse of the server.
- The server is capable of receiving mail for any configured domains.
- The server exposes IMAP, POP3 and SMTP as usual for connection with mail clients such as Thunderbird.
- The containers have limited (throttled) CPU, this means it can respond slow during heavy operations.
- The containers have limited memory available and will be killed when exceeded.
This is to prevent people from doing nasty things to the server as a whole.
Connecting to the server Connecting to the server
------------------------ ------------------------
* Server name : ``test.mailu.io`` * Server name : ``test.mailu.io``
* IP address : ``51.15.169.20`` * IP address : ``173.249.45.89``
* Webmail : https://test.mailu.io/webmail/ * Webmail : https://test.mailu.io/webmail/
* Admin UI : https://test.mailu.io/admin/ * Admin UI : https://test.mailu.io/admin/
* Admin login : ``admin@test.mailu.io`` * Admin login : ``admin@test.mailu.io``

@ -4,18 +4,25 @@ version: '3'
services: services:
docs_master: docs_master:
image: mailu/docs:master image: mailu/docs:master
networks:
- web
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.port=80 - traefik.port=80
- traefik.main.frontend.rule=Host:${hostname};PathPrefix:/master/ - traefik.main.frontend.rule=Host:${ADDRESS};PathPrefix:/master/
docs_15: docs_15:
image: mailu/docs:1.5 image: mailu/docs:1.5
networks:
- web
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.port=80 - traefik.port=80
- traefik.root.frontend.redirect.regex=.* - traefik.root.frontend.redirect.regex=.*
- traefik.root.frontend.redirect.replacement=/1.5/ - traefik.root.frontend.redirect.replacement=/1.5/
- traefik.root.frontend.rule=Host:${hostname};PathPrefix:/ - traefik.root.frontend.rule=Host:${ADDRESS};PathPrefix:/
- traefik.main.frontend.rule=Host:${hostname};PathPrefix:/1.5/ - traefik.main.frontend.rule=Host:${ADDRESS};PathPrefix:/1.5/
networks:
web:
external: true

@ -53,6 +53,11 @@
# This way we can make use of the advantages of the cert-manager deployment # This way we can make use of the advantages of the cert-manager deployment
KUBERNETES_INGRESS: "true" KUBERNETES_INGRESS: "true"
# POD_ADDRESS_RANGE is normally provided by default with Kubernetes
# Only use this value when you are using Flannel, Calico or a special kind of CNI
# Provide the IPs of your network interface or bridge which is used for VXLAN network traffic
# POD_ADDRESS_RANGE: 10.2.0.0/16,10.1.6.0/24
################################### ###################################
# Optional features # Optional features
################################### ###################################

@ -193,3 +193,18 @@ This problem can be easily fixed by running following commands:
kubectl -n mailu-mailserver exec -it mailu-imap-... /bin/sh kubectl -n mailu-mailserver exec -it mailu-imap-... /bin/sh
chmod 777 /data/main.db chmod 777 /data/main.db
If the login problem still persists, or more specific, happens now and then and you see some Auth problems on your webmail or mail client, try following steps:
- Add ``auth_debug=yes`` to the ``/overrides/dovecot.conf`` file and delete the pod in order to start a new one, which loads the configuration
- Depending on your network configuration you could still see some ``allow_nets check failed`` results in the logs. This means that the IP is not allowed a login
- If this is happening your network plugin has troubles with the Nginx Ingress Controller using the ``hostNetwork: true`` option. Known cases: Flannel and Calico.
- You should uncomment ``POD_ADDRESS_RANGE`` in the ``configmap.yaml`` file and add the IP range of your pod network bridge (the range that sadly has failed the ``allowed_nets`` test)
- Delete the IMAP pod and wait for it to restart
.. code:: bash
kubectl -n mailu-mailserver get po
kubectl -n mailu-mailserver delete po/mailu-imap...
Happy mailing!

@ -8,6 +8,7 @@ In such a configuration, one would usually run a frontend reverse proxy to serve
There are basically three options, from the most to the least recommended one: There are basically three options, from the most to the least recommended one:
- have Mailu Web frontend listen locally and use your own Web frontend on top of it - have Mailu Web frontend listen locally and use your own Web frontend on top of it
- use ``Traefik`` in another container as central system-reverse-proxy
- override Mailu Web frontend configuration - override Mailu Web frontend configuration
- disable Mailu Web frontend completely and use your own - disable Mailu Web frontend completely and use your own
@ -114,11 +115,87 @@ Depending on how you access the front server, you might want to add a ``proxy_re
This will stop redirects (301 and 302) sent by the Webmail, nginx front and admin interface from sending you to ``localhost``. This will stop redirects (301 and 302) sent by the Webmail, nginx front and admin interface from sending you to ``localhost``.
Use Traefik in another container as central system-reverse-proxy
--------------------------------------------------------------------
`Traefik`_ is a popular reverse-proxy aimed at containerized systems.
As such, many may wish to integrate Mailu into a system which already uses Traefik as its sole ingress/reverse-proxy.
As the ``mailu/front`` container uses Nginx not only for ``HTTP`` forwarding, but also for the mail-protocols like ``SMTP``, ``IMAP``, etc, we need to keep this
container around even when using another ``HTTP`` reverse-proxy. Furthermore, Traefik is neither able to forward non-HTTP, nor can it easily forward HTTPS-to-HTTPS.
This, however, means 3 things:
- ``mailu/front`` needs to listen internally on ``HTTP`` rather than ``HTTPS``
- ``mailu/front`` is not exposed to the outside world on ``HTTP``
- ``mailu/front`` still needs ``SSL`` certificates (here, we assume ``letsencrypt``) for a well-behaved mail service
This makes the setup with Traefik a bit harder: Traefik saves its certificates in a proprietary *JSON* file, which is not readable by Nginx in the ``front``-container.
To solve this, your ``acme.json`` needs to be exposed to the host or a ``docker-volume``. It will then be read by a script in another container,
which will dump the certificates as ``PEM`` files, readable for Nginx. The ``front`` container will automatically reload Nginx whenever these certificates change.
To set this up, first set ``TLS_FLAVOR=mail`` in your ``.env``. This tells ``mailu/front`` not to try to request certificates using ``letsencrypt``,
but to read provided certificates, and use them only for mail-protocols, not for ``HTTP``.
Next, in your ``docker-compose.yml``, comment out the ``port`` lines of the ``front`` section for port ``…:80`` and ``…:440``.
Add the respective Traefik labels for your domain/configuration, like
.. code-block:: yaml
labels:
- "traefik.enable=true"
- "traefik.port=80"
- "traefik.frontend.rule=Host:$TRAEFIK_DOMAIN"
.. note:: Please dont forget to add ``TRAEFIK_DOMAIN=[...]`` TO YOUR ``.env``
If your Traefik is configured to automatically request certificates from *letsencrypt*, then youll have a certificate for ``mail.your.doma.in`` now. However,
``mail.your.doma.in`` might only be the location where you want the Mailu web-interfaces to live — your mail should be sent/received from ``your.doma.in``,
and this is the ``DOMAIN`` in your ``.env``?
To support that use-case, Traefik can request ``SANs`` for your domain. Lets add something like
.. code-block:: guess
[acme]
[[acme.domains]]
main = "your.doma.in" # this is the same as $TRAEFIK_DOMAIN!
sans = ["mail.your.doma.in", "webmail.your.doma.in", "smtp.your.doma.in"]
to your ``traefik.toml``. You might need to clear your ``acme.json``, if a certificate for one of these domains already exists.
You will need some solution which dumps the certificates in ``acme.json``, so you can include them in the ``mailu/front`` container.
One such example is ``mailu/traefik-certdumper``, which has been adapted for use in Mailu. You can add it to your ``docker-compose.yml`` like:
.. code-block:: yaml
certdumper:
restart: always
image: mailu/traefik-certdumper:$VERSION
environment:
# Make sure this is the same as the main=-domain in traefik.toml
# !!! Also dont forget to add "TRAEFIK_DOMAIN=[...]" to your .env!
- DOMAIN=$TRAEFIK_DOMAIN
volumes:
- "/data/traefik:/traefik"
- "$ROOT/certs:/output"
Assuming you have ``volume-mounted`` your ``acme.json`` put to ``/data/traefik`` on your host. The dumper will then write out ``/data/traefik/ssl/your.doma.in.crt``
and ``/data/traefik/ssl/your.doma.in.key`` whenever ``acme.json`` is updated. Yay! Now lets mount this to our ``front`` container like:
.. code-block:: yaml
volumes:
- "$ROOT/overrides/nginx:/overrides"
- /data/traefik/ssl/$TRAEFIK_DOMAIN.crt:/certs/cert.pem
- /data/traefik/ssl/$TRAEFIK_DOMAIN.key:/certs/key.pem
.. _`Traefik`: https://traefik.io/
Override Mailu configuration Override Mailu configuration
---------------------------- ----------------------------
If you do not have the resources for running a separate reverse proxy, you could override Mailu reverse proxy configuration by using a Docker volume. Simply store your configuration file (Nginx format), in ``/mailu/nginx.conf`` for instance. If you do not have the resources for running a separate reverse proxy, you could override Mailu reverse proxy configuration by using a Docker volume.
Simply store your configuration file (Nginx format), in ``/mailu/nginx.conf`` for instance.
Then modify your ``docker-compose.yml`` file and change the ``front`` section to add a mount: Then modify your ``docker-compose.yml`` file and change the ``front`` section to add a mount:
@ -135,7 +212,10 @@ Then modify your ``docker-compose.yml`` file and change the ``front`` section to
- "$ROOT/certs:/certs" - "$ROOT/certs:/certs"
- "$ROOT/nginx.conf:/etc/nginx/nginx.conf" - "$ROOT/nginx.conf:/etc/nginx/nginx.conf"
You can use our default configuration file as a sane base for your configuration. You can also download the example configuration files:
- :download:`compose/traefik/docker-compose.yml`
- :download:`compose/traefik/traefik.toml`
Disable completely Mailu reverse proxy Disable completely Mailu reverse proxy
-------------------------------------- --------------------------------------

@ -30,7 +30,7 @@ mzrm9nbdggsfz4sgq6dhs5i6n flying-dutchman Ready Active
### Volume definition ### 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 assume that "Mailu Data" is available on every node at "$ROOT/certs:/certs" (GlusterFS and nfs shares have been successfully used). Hereafter we will assume that "Mailu Data" is available on every node at "$ROOT" (GlusterFS and nfs shares have been successfully used).
On this example, we are using: On this example, we are using:
- the mesh routing mode (default mode). With this mode, each service is given a virtual IP adress and docker manages the routing between this virtual IP and the container(s) providing this service. - the mesh routing mode (default mode). With this mode, each service is given a virtual IP adress and docker manages the routing between this virtual IP and the container(s) providing this service.
@ -77,7 +77,7 @@ Instead, we will use the following work-around:
We need also to: We need also to:
- add a deploy section for every service - add a deploy section for every service
- modify the way the ports are defined for the front service - modify the way the ports are defined for the front service
- add the POD_ADDRESS_RANGE definition for imap, smtp and antispam services - add the POD_ADDRESS_RANGE definition for admin (for imap), smtp and antispam services
## Docker compose ## Docker compose
An example of docker-compose-stack.yml file is available here: An example of docker-compose-stack.yml file is available here:
@ -128,8 +128,6 @@ services:
image: mailu/dovecot:$VERSION image: mailu/dovecot:$VERSION
restart: always restart: always
env_file: .env env_file: .env
environment:
- POD_ADDRESS_RANGE=10.0.1.0/24
volumes: volumes:
- "$ROOT/mail:/mail" - "$ROOT/mail:/mail"
- "$ROOT/overrides:/overrides" - "$ROOT/overrides:/overrides"
@ -188,6 +186,8 @@ services:
image: mailu/admin:$VERSION image: mailu/admin:$VERSION
restart: always restart: always
env_file: .env env_file: .env
environment:
- POD_ADDRESS_RANGE=10.0.1.0/24
volumes: volumes:
- "$ROOT/data:/data" - "$ROOT/data:/data"
- "$ROOT/dkim:/dkim" - "$ROOT/dkim:/dkim"

@ -157,8 +157,6 @@ services:
image: mailu/dovecot:$VERSION image: mailu/dovecot:$VERSION
restart: always restart: always
env_file: .env env_file: .env
environment:
- POD_ADDRESS_RANGE=10.0.1.0/24
volumes: volumes:
# - "$ROOT/mail:/mail" # - "$ROOT/mail:/mail"
- type: volume - type: volume
@ -241,6 +239,8 @@ services:
image: mailu/admin:$VERSION image: mailu/admin:$VERSION
restart: always restart: always
env_file: .env env_file: .env
environment:
- POD_ADDRESS_RANGE=10.0.1.0/24
volumes: volumes:
# - "$ROOT/data:/data" # - "$ROOT/data:/data"
- type: volume - type: volume

@ -15,4 +15,4 @@ VOLUME ["/data"]
CMD /start.py CMD /start.py
HEALTHCHECK CMD /health.sh HEALTHCHECK --start-period=350s CMD /health.sh

@ -0,0 +1,11 @@
FROM alpine:3.8
RUN apk --no-cache add inotify-tools jq openssl util-linux bash docker
# while not strictly documented, this script seems to always(?) support previous acme.json versions too
RUN wget https://raw.githubusercontent.com/containous/traefik/master/contrib/scripts/dumpcerts.sh -O dumpcerts.sh
VOLUME ["/traefik"]
VOLUME ["/output"]
COPY run.sh /
ENTRYPOINT ["/run.sh"]

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Sven Dowideit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,27 @@
# Single-domain traefik-certdumper for mailu
This is based on the work by Sven Dowideit on https://github.com/SvenDowideit/traefik-certdumper
## Fork?
This is a slight modification that is less flexible, but is adapted to the
usecase in mailu. If you wish to deploy mailu behind a traefik, you face many
problems. One of these is that you need to get the certificates into mailu in a
very defined manner. This will copy the certificate for the **Main:**-domain
given in the DOMAIN-environment onto `output`.
If your output happens to be mailu-front-`/certs`, the certificate-watcher in
the front-container will catch it and reload nginx. This works for mailu
`TLS_FLAVOR=[mail, cert]`
```
certdumper:
restart: always
image: Mailu/traefik-certdumper:$VERSION
environment:
- DOMAIN=$DOMAIN
volumes:
# your traefik data-volume is probably declared outside of the mailu composefile
- /data/traefik:/traefik
- $ROOT/certs/:/output/
```

@ -0,0 +1,30 @@
#!/bin/bash
function dump() {
echo "$(date) Dumping certificates"
bash dumpcerts.sh /traefik/acme.json /tmp/work/ || return
for crt_file in $(ls /tmp/work/certs/*); do
pem_file=$(echo $crt_file | sed 's/certs/pem/g' | sed 's/.crt/-public.pem/g')
echo "openssl x509 -inform PEM -in $crt_file > $pem_file"
openssl x509 -inform PEM -in $crt_file > $pem_file
done
for key_file in $(ls /tmp/work/private/*); do
pem_file=$(echo $key_file | sed 's/private/pem/g' | sed 's/.key/-private.pem/g')
echo "openssl rsa -in $key_file -text > $pem_file"
openssl rsa -in $key_file -text > $pem_file
done
echo "$(date) Copying certificates"
cp -v /tmp/work/pem/${DOMAIN}-private.pem /output/key.pem
cp -v /tmp/work/pem/${DOMAIN}-public.pem /output/cert.pem
}
mkdir -p /tmp/work/pem /tmp/work/certs
# run once on start to make sure we have any old certs
dump
while true; do
inotifywait -e modify /traefik/acme.json && \
dump
done

@ -1,4 +1,4 @@
type = "controller"; type = "controller";
bind_socket = "*:11334"; bind_socket = "*:11334";
password = "mailu"; password = "mailu";
secure_ip = "{% if POD_ADDRESS_RANGE %}{{ POD_ADDRESS_RANGE }}{% else %}{{ FRONT_ADDRESS }}{% endif %}"; secure_ip = "{{ POD_ADDRESS_RANGE or SUBNET }}";

@ -0,0 +1,8 @@
# Hostname passed to Traefik
ADDRESS=setup.mailu.io
# Current release
RELEASE=master
# Comma separated list of versions the user can choose.
VERSIONS=master

@ -4,20 +4,15 @@ RUN mkdir -p /app
WORKDIR /app WORKDIR /app
COPY requirements.txt requirements.txt COPY requirements.txt requirements.txt
RUN apk add --no-cache git curl \ RUN apk add --no-cache curl \
&& pip install -r requirements.txt && pip install -r requirements.txt
COPY server.py ./server.py COPY server.py ./server.py
COPY setup.py ./setup.py
COPY main.py ./main.py COPY main.py ./main.py
COPY flavors /data/master/flavors COPY flavors /data/flavors
COPY templates /data/master/templates COPY templates /data/templates
COPY static ./static COPY static ./static
#RUN python setup.py https://github.com/mailu/mailu /data
EXPOSE 80/tcp EXPOSE 80/tcp
CMD gunicorn -w 4 -b :80 --access-logfile - --error-logfile - --preload main:app CMD gunicorn -w 4 -b :80 --access-logfile - --error-logfile - --preload main:app
HEALTHCHECK CMD curl -f -L http://localhost/ || exit 1

@ -6,9 +6,37 @@ services:
redis: redis:
image: redis:alpine image: redis:alpine
setup: setup_master:
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}setup:${MAILU_VERSION:-master} image: mailu/setup:master
ports: networks:
- "8000:80" - web
build: . env_file: .env
environment:
this_version: "master"
labels:
- traefik.enable=true
- traefik.port=80
- traefik.main.frontend.rule=Host:${ADDRESS};PathPrefix:/master/
depends_on:
- redis
setup_release:
image: mailu/setup:${RELEASE}
networks:
- web
env_file: .env
environment:
this_version: ${RELEASE}
labels:
- traefik.enable=true
- traefik.port=80
- traefik.root.frontend.redirect.regex=.*
- traefik.root.frontend.redirect.replacement=/${RELEASE}/
- traefik.root.frontend.rule=Host:${ADDRESS};PathPrefix:/
- traefik.main.frontend.rule=Host:${ADDRESS};PathPrefix:/${RELEASE}/
depends_on:
- redis
networks:
web:
external: true

@ -16,7 +16,7 @@ services:
# Core services # Core services
front: front:
image: ${DOCKER_ORG:-mailu}/nginx:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}nginx:${MAILU_VERSION:-{{ version }}}
restart: always restart: always
env_file: {{ env }} env_file: {{ env }}
logging: logging:
@ -32,10 +32,11 @@ services:
{% endfor %} {% endfor %}
volumes: volumes:
- "{{ root }}/certs:/certs" - "{{ root }}/certs:/certs"
- "{{ root }}/overrides/nginx:/overrides"
{% if resolver_enabled %} {% if resolver_enabled %}
resolver: resolver:
image: mailu/unbound:{{ version }} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}unbound:${MAILU_VERSION:-{{ version }}}
env_file: {{ env }} env_file: {{ env }}
restart: always restart: always
networks: networks:
@ -44,7 +45,7 @@ services:
{% endif %} {% endif %}
admin: admin:
image: ${DOCKER_ORG:-mailu}/admin:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}admin:${MAILU_VERSION:-{{ version }}}
restart: always restart: always
env_file: {{ env }} env_file: {{ env }}
{% if not admin_enabled %} {% if not admin_enabled %}
@ -58,7 +59,7 @@ services:
- redis - redis
imap: imap:
image: ${DOCKER_ORG:-mailu}/dovecot:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}dovecot:${MAILU_VERSION:-{{ version }}}
restart: always restart: always
env_file: {{ env }} env_file: {{ env }}
volumes: volumes:
@ -68,7 +69,7 @@ services:
- front - front
smtp: smtp:
image: ${DOCKER_ORG:-mailu}/postfix:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}postfix:${MAILU_VERSION:-{{ version }}}
restart: always restart: always
env_file: {{ env }} env_file: {{ env }}
volumes: volumes:
@ -82,7 +83,7 @@ services:
{% endif %} {% endif %}
antispam: antispam:
image: ${DOCKER_ORG:-mailu}/rspamd:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}rspamd:${MAILU_VERSION:-{{ version }}}
restart: always restart: always
env_file: {{ env }} env_file: {{ env }}
volumes: volumes:
@ -100,7 +101,7 @@ services:
# Optional services # Optional services
{% if antivirus_enabled %} {% if antivirus_enabled %}
antivirus: antivirus:
image: ${DOCKER_ORG:-mailu}/clamav:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}clamav:${MAILU_VERSION:-{{ version }}}
restart: always restart: always
env_file: {{ env }} env_file: {{ env }}
volumes: volumes:
@ -115,7 +116,7 @@ services:
{% if webdav_enabled %} {% if webdav_enabled %}
webdav: webdav:
image: ${DOCKER_ORG:-mailu}/radicale:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}radicale:${MAILU_VERSION:-{{ version }}}
restart: always restart: always
env_file: {{ env }} env_file: {{ env }}
volumes: volumes:
@ -124,7 +125,7 @@ services:
{% if fetchmail_enabled %} {% if fetchmail_enabled %}
fetchmail: fetchmail:
image: ${DOCKER_ORG:-mailu}/fetchmail:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}fetchmail:${MAILU_VERSION:-{{ version }}}
restart: always restart: always
env_file: {{ env }} env_file: {{ env }}
{% if resolver_enabled %} {% if resolver_enabled %}
@ -138,7 +139,7 @@ services:
# Webmail # Webmail
{% if webmail_type != 'none' %} {% if webmail_type != 'none' %}
webmail: webmail:
image: ${DOCKER_ORG:-mailu}/{{ webmail_type }}:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}{{ webmail_type }}:${MAILU_VERSION:-{{ version }}}
restart: always restart: always
env_file: {{ env }} env_file: {{ env }}
volumes: volumes:
@ -146,10 +147,10 @@ services:
depends_on: depends_on:
- imap - imap
{% endif %} {% endif %}
{% if db_flavor == 'postgresql' and postgresql == 'internal' %} {% if db_flavor == 'postgresql' and postgresql == 'internal' %}
database: database:
image: ${DOCKER_ORG:-mailu}/postgresql:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}postgresql:${MAILU_VERSION:-{{ version }}}
restart: always restart: always
env_file: {{ env }} env_file: {{ env }}
volumes: volumes:
@ -157,7 +158,6 @@ services:
- "{{ root }}/data/psql_backup:/backup" - "{{ root }}/data/psql_backup:/backup"
{% endif %} {% endif %}
{% if resolver_enabled or db_flavor == 'postgresql' %}
networks: networks:
default: default:
driver: bridge driver: bridge
@ -165,4 +165,3 @@ networks:
driver: default driver: default
config: config:
- subnet: {{ subnet }} - subnet: {{ subnet }}
{% endif %}

@ -25,7 +25,7 @@ SECRET_KEY={{ secret(16) }}
# PUBLIC_IPV4= {{ bind4 }} (default: 127.0.0.1) # PUBLIC_IPV4= {{ bind4 }} (default: 127.0.0.1)
# PUBLIC_IPV6= {{ bind6 }} (default: ::1) # PUBLIC_IPV6= {{ bind6 }} (default: ::1)
# Subnet # Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!)
SUBNET={{ subnet }} SUBNET={{ subnet }}
# Main mail domain # Main mail domain
@ -73,11 +73,12 @@ ANTISPAM={{ antispam_enabled or 'none'}}
# Message size limit in bytes # Message size limit in bytes
# Default: accept messages up to 50MB # Default: accept messages up to 50MB
# Max attachment size will be 33% smaller
MESSAGE_SIZE_LIMIT={{ message_size_limit or '50000000' }} MESSAGE_SIZE_LIMIT={{ message_size_limit or '50000000' }}
# Networks granted relay permissions, make sure that you include your Docker # Networks granted relay permissions
# internal network (default to 172.17.0.0/16) # Use this with care, all hosts in this networks will be able to send mail without authentication!
RELAYNETS={{ relaynets or '172.17.0.0/16' }} RELAYNETS=
# Will relay all outgoing mails if configured # Will relay all outgoing mails if configured
RELAYHOST={{ relayhost }} RELAYHOST={{ relayhost }}
@ -92,13 +93,11 @@ RECIPIENT_DELIMITER={{ recipient_delimiter or '+' }}
DMARC_RUA={{ dmarc_rua or 'admin' }} DMARC_RUA={{ dmarc_rua or 'admin' }}
DMARC_RUF={{ dmarc_ruf or 'admin' }} DMARC_RUF={{ dmarc_ruf or 'admin' }}
{% if welcome_enabled %}
# Welcome email, enable and set a topic and body if you wish to send welcome # Welcome email, enable and set a topic and body if you wish to send welcome
# emails to all users. # emails to all users.
WELCOME={{ welcome_enable or 'false' }} WELCOME={{ welcome_enable or 'false' }}
WELCOME_SUBJECT={{ welcome_subject or 'Welcome to your new email account' }} WELCOME_SUBJECT={{ welcome_subject or 'Welcome to your new email account' }}
WELCOME_BODY={{ welcome_body or 'Welcome to your new email account, if you can read this, then it is configured properly!' }} WELCOME_BODY={{ welcome_body or 'Welcome to your new email account, if you can read this, then it is configured properly!' }}
{% endif %}
# Maildir Compression # Maildir Compression
# choose compression-method, default: none (value: bz2, gz) # choose compression-method, default: none (value: bz2, gz)
@ -110,6 +109,9 @@ COMPRESSION_LEVEL={{ compression_level }}
# Web settings # Web settings
################################### ###################################
# Path to redirect / to
WEBROOT_REDIRECT=/webmail
# Path to the admin interface if enabled # Path to the admin interface if enabled
WEB_ADMIN={{ admin_path }} WEB_ADMIN={{ admin_path }}

@ -36,7 +36,7 @@ docker-compose -p mailu up -d
Before you can use Mailu, you must create the primary administrator user account. This should be {{ postmaster }}@{{ domain }}. Use the following command, changing PASSWORD to your liking: Before you can use Mailu, you must create the primary administrator user account. This should be {{ postmaster }}@{{ domain }}. Use the following command, changing PASSWORD to your liking:
<pre><code>docker-compose -p mailu exec admin python manage.py admin {{ postmaster }} {{ domain }} PASSWORD <pre><code>docker-compose -p mailu exec admin flask mailu admin {{ postmaster }} {{ domain }} PASSWORD
</pre></code> </pre></code>
<p>Login to the admin interface to change the password for a safe one, at <p>Login to the admin interface to change the password for a safe one, at

@ -15,7 +15,7 @@ services:
# Core services # Core services
front: front:
image: ${DOCKER_ORG:-mailu}/nginx:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}nginx:${MAILU_VERSION:-{{ version }}}
env_file: {{ env }} env_file: {{ env }}
logging: logging:
driver: {{ log_driver or 'json-file' }} driver: {{ log_driver or 'json-file' }}
@ -27,12 +27,13 @@ services:
{% endfor %} {% endfor %}
volumes: volumes:
- "{{ root }}/certs:/certs" - "{{ root }}/certs:/certs"
- "{{ root }}/overrides/nginx:/overrides"
deploy: deploy:
replicas: {{ front_replicas }} replicas: {{ front_replicas }}
{% if resolver_enabled %} {% if resolver_enabled %}
resolver: resolver:
image: mailu/unbound:{{ version }} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}unbound:${MAILU_VERSION:-{{ version }}}
env_file: {{ env }} env_file: {{ env }}
networks: networks:
default: default:
@ -40,7 +41,7 @@ services:
{% endif %} {% endif %}
admin: admin:
image: ${DOCKER_ORG:-mailu}/admin:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}admin:${MAILU_VERSION:-{{ version }}}
env_file: {{ env }} env_file: {{ env }}
{% if not admin_enabled %} {% if not admin_enabled %}
ports: ports:
@ -53,11 +54,8 @@ services:
replicas: {{ admin_replicas }} replicas: {{ admin_replicas }}
imap: imap:
image: ${DOCKER_ORG:-mailu}/dovecot:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}dovecot:${MAILU_VERSION:-{{ version }}}
env_file: {{ env }} env_file: {{ env }}
environment:
# Default to 10.0.1.0/24
- POD_ADDRESS_RANGE={{ subnet }}
volumes: volumes:
- "{{ root }}/mail:/mail" - "{{ root }}/mail:/mail"
- "{{ root }}/overrides:/overrides" - "{{ root }}/overrides:/overrides"
@ -65,10 +63,8 @@ services:
replicas: {{ imap_replicas }} replicas: {{ imap_replicas }}
smtp: smtp:
image: ${DOCKER_ORG:-mailu}/postfix:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}postfix:${MAILU_VERSION:-{{ version }}}
env_file: {{ env }} env_file: {{ env }}
environment:
- POD_ADDRESS_RANGE={{ subnet }}
volumes: volumes:
- "{{ root }}/overrides:/overrides" - "{{ root }}/overrides:/overrides"
deploy: deploy:
@ -79,10 +75,8 @@ services:
{% endif %} {% endif %}
antispam: antispam:
image: ${DOCKER_ORG:-mailu}/rspamd:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}rspamd:${MAILU_VERSION:-{{ version }}}
env_file: {{ env }} env_file: {{ env }}
environment:
- POD_ADDRESS_RANGE={{ subnet }}
volumes: volumes:
- "{{ root }}/filter:/var/lib/rspamd" - "{{ root }}/filter:/var/lib/rspamd"
- "{{ root }}/dkim:/dkim" - "{{ root }}/dkim:/dkim"
@ -97,7 +91,7 @@ services:
# Optional services # Optional services
{% if antivirus_enabled %} {% if antivirus_enabled %}
antivirus: antivirus:
image: ${DOCKER_ORG:-mailu}/clamav:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}clamav:${MAILU_VERSION:-{{ version }}}
env_file: {{ env }} env_file: {{ env }}
volumes: volumes:
- "{{ root }}/filter:/data" - "{{ root }}/filter:/data"
@ -111,7 +105,7 @@ services:
{% if webdav_enabled %} {% if webdav_enabled %}
webdav: webdav:
image: ${DOCKER_ORG:-mailu}/none:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}radicale:${MAILU_VERSION:-{{ version }}}
env_file: {{ env }} env_file: {{ env }}
volumes: volumes:
- "{{ root }}/dav:/data" - "{{ root }}/dav:/data"
@ -121,7 +115,7 @@ services:
{% if fetchmail_enabled %} {% if fetchmail_enabled %}
fetchmail: fetchmail:
image: ${DOCKER_ORG:-mailu}/fetchmail:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}fetchmail:${MAILU_VERSION:-{{ version }}}
env_file: {{ env }} env_file: {{ env }}
volumes: volumes:
- "{{ root }}/data:/data" - "{{ root }}/data:/data"
@ -135,7 +129,7 @@ services:
{% if webmail_type != 'none' %} {% if webmail_type != 'none' %}
webmail: webmail:
image: ${DOCKER_ORG:-mailu}/roundcube:${MAILU_VERSION:-{{ version }}} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}{{ webmail_type }}:${MAILU_VERSION:-{{ version }}}
env_file: {{ env }} env_file: {{ env }}
volumes: volumes:
- "{{ root }}/webmail:/data" - "{{ root }}/webmail:/data"

@ -45,7 +45,7 @@ Command for removing docker stack is
Before you can use Mailu, you must create the primary administrator user account. This should be {{ postmaster }}@{{ domain }}. Use the following command, changing PASSWORD to your liking: Before you can use Mailu, you must create the primary administrator user account. This should be {{ postmaster }}@{{ domain }}. Use the following command, changing PASSWORD to your liking:
<pre><code>docker exec $(docker ps | grep admin | cut -d ' ' -f1) python manage.py admin {{ postmaster }} {{ domain }} PASSWORD <pre><code>docker exec $(docker ps | grep admin | cut -d ' ' -f1) flask mailu admin {{ postmaster }} {{ domain }} PASSWORD
</pre></code> </pre></code>
<p>Login to the admin interface to change the password for a safe one, at <p>Login to the admin interface to change the password for a safe one, at

@ -1,5 +1,4 @@
flask flask
flask-bootstrap flask-bootstrap
redis redis
gitpython
gunicorn gunicorn

@ -34,70 +34,60 @@ def secret(length=16):
def build_app(path): def build_app(path):
#Hardcoded master as the only version for test purposes
versions = [
# version for version in os.listdir(path)
# if os.path.isdir(os.path.join(path, version))
"master"
]
app.jinja_env.trim_blocks = True app.jinja_env.trim_blocks = True
app.jinja_env.lstrip_blocks = True app.jinja_env.lstrip_blocks = True
@app.context_processor @app.context_processor
def app_context(): def app_context():
return dict(versions=versions) return dict(versions=os.getenv("VERSIONS","master").split(','))
@app.route("/") version = os.getenv("this_version")
def index():
return flask.redirect(flask.url_for('{}.wizard'.format(versions[-1])))
for version in versions: bp = flask.Blueprint(version, __name__)
bp = flask.Blueprint(version, __name__) bp.jinja_loader = jinja2.ChoiceLoader([
bp.jinja_loader = jinja2.ChoiceLoader([ jinja2.FileSystemLoader(os.path.join(path, "templates")),
jinja2.FileSystemLoader(os.path.join(path, version, "templates")), jinja2.FileSystemLoader(os.path.join(path, "flavors"))
jinja2.FileSystemLoader(os.path.join(path, version, "flavors")) ])
])
@bp.context_processor @bp.context_processor
def bp_context(version=version): def bp_context(version=version):
return dict(version=version) return dict(version=version)
@bp.route("/") @bp.route("/")
def wizard(): def wizard():
return flask.render_template('wizard.html') return flask.render_template('wizard.html')
@bp.route("/submit_flavor", methods=["POST"]) @bp.route("/submit_flavor", methods=["POST"])
def submit_flavor(): def submit_flavor():
data = flask.request.form.copy() data = flask.request.form.copy()
steps = sorted(os.listdir(path + "/" + version + "/templates/steps/" + data["flavor"])) steps = sorted(os.listdir(os.path.join(path, "templates", "steps", data["flavor"])))
return flask.render_template('wizard.html', flavor=data["flavor"], steps=steps) return flask.render_template('wizard.html', flavor=data["flavor"], steps=steps)
@bp.route("/submit", methods=["POST"]) @bp.route("/submit", methods=["POST"])
def submit(): def submit():
data = flask.request.form.copy() data = flask.request.form.copy()
data['uid'] = str(uuid.uuid4()) data['uid'] = str(uuid.uuid4())
data['dns'] = str(ipaddress.IPv4Network(data['subnet'])[-2]) data['dns'] = str(ipaddress.IPv4Network(data['subnet'])[-2])
db.set(data['uid'], json.dumps(data)) db.set(data['uid'], json.dumps(data))
return flask.redirect(flask.url_for('.setup', uid=data['uid'])) return flask.redirect(flask.url_for('.setup', uid=data['uid']))
@bp.route("/setup/<uid>", methods=["GET"]) @bp.route("/setup/<uid>", methods=["GET"])
def setup(uid): def setup(uid):
data = json.loads(db.get(uid)) data = json.loads(db.get(uid))
flavor = data.get("flavor", "compose") flavor = data.get("flavor", "compose")
rendered = render_flavor(flavor, "setup.html", data) rendered = render_flavor(flavor, "setup.html", data)
return flask.render_template("setup.html", contents=rendered) return flask.render_template("setup.html", contents=rendered)
@bp.route("/file/<uid>/<filepath>", methods=["GET"]) @bp.route("/file/<uid>/<filepath>", methods=["GET"])
def file(uid, filepath): def file(uid, filepath):
data = json.loads(db.get(uid)) data = json.loads(db.get(uid))
flavor = data.get("flavor", "compose") flavor = data.get("flavor", "compose")
return flask.Response( return flask.Response(
render_flavor(flavor, filepath, data), render_flavor(flavor, filepath, data),
mimetype="application/text" mimetype="application/text"
) )
app.register_blueprint(bp, url_prefix="/{}".format(version)) app.register_blueprint(bp, url_prefix="/{}".format(version))
if __name__ == "__main__": if __name__ == "__main__":

@ -1,39 +0,0 @@
import git
import tempfile
import argparse
import os
import shutil
import re
VERSION_BRANCH = re.compile("(master|\d+\.\d+)")
def main(upstream, dest, dev=True):
shutil.rmtree(dest, ignore_errors=True)
os.makedirs(dest, exist_ok=True)
with tempfile.TemporaryDirectory() as clone_path:
repo = git.Repo.clone_from(upstream, clone_path)
for branch in repo.refs:
if not branch.name.startswith("origin/"):
continue
name = branch.name[len("origin/"):]
if not VERSION_BRANCH.match(name):
continue
branch.checkout()
config_path = os.path.join(clone_path, "setup")
if os.path.exists(config_path):
shutil.copytree(config_path, os.path.join(dest, name))
print("Imported branch {}".format(name))
if dev:
shutil.copytree(".", os.path.join(dest, "dev"))
print("Imported dev")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--dev", action="store_true", help="Copy the local dir in /dev")
parser.add_argument("upstream", help="Path to Mailu git repository")
parser.add_argument("dest", help="Destination directory for data files")
args = parser.parse_args()
main(**vars(args))

@ -9,8 +9,8 @@
<p> <p>
Version Version
<select onchange="window.location.href=this.value;" class="btn btn-primary dropdown-toggle"> <select onchange="window.location.href=this.value;" class="btn btn-primary dropdown-toggle">
{% for available in versions %} {% for module in versions %}
<option value="{{ url_for('{}.wizard'.format(available)) }}" {% if available == version %}selected{% endif %}>{{ available }}</option> <option value="/{{ module }}" {% if module == version %}selected{% endif %}>{{ module }}</option>
{% endfor %} {% endfor %}
</select> </select>
</p> </p>

@ -34,9 +34,9 @@ avoid generic all-interfaces addresses like <code>0.0.0.0</code> or <code>::</co
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Subnet</label> <label>Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!)</label>
<input class="form-control" type="text" name="subnet" required pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))$" <input class="form-control" type="text" name="subnet" required pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))$"
value="192.168.0.0/24"> value="192.168.203.0/24">
</div> </div>
<p>You server will be available under a main hostname but may expose multiple public <p>You server will be available under a main hostname but may expose multiple public

@ -11,9 +11,9 @@ you expose it to the world.</p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Subnet</label> <label>Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!)</label>
<input class="form-control" type="text" name="subnet" required pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))$" <input class="form-control" type="text" name="subnet" required pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))$"
value="192.168.0.0/24"> value="192.168.203.0/24">
</div> </div>
<p>You server will be available under a main hostname but may expose multiple public <p>You server will be available under a main hostname but may expose multiple public

@ -3,35 +3,39 @@ version: '3'
services: services:
front: front:
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}nginx:${MAILU_VERSION:-local} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}nginx:${MAILU_VERSION:-local}
build: ../core/nginx build: ../core/nginx
resolver: resolver:
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}unbound:${MAILU_VERSION:-local} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}unbound:${MAILU_VERSION:-local}
build: ../services/unbound build: ../services/unbound
imap: imap:
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}dovecot:${MAILU_VERSION:-local} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}dovecot:${MAILU_VERSION:-local}
build: ../core/dovecot build: ../core/dovecot
smtp: smtp:
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}postfix:${MAILU_VERSION:-local} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}postfix:${MAILU_VERSION:-local}
build: ../core/postfix build: ../core/postfix
antispam: antispam:
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}rspamd:${MAILU_VERSION:-local} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}rspamd:${MAILU_VERSION:-local}
build: ../services/rspamd build: ../services/rspamd
antivirus: antivirus:
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}clamav:${MAILU_VERSION:-local} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}clamav:${MAILU_VERSION:-local}
build: ../optional/clamav build: ../optional/clamav
webdav: webdav:
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}radicale:${MAILU_VERSION:-local} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}radicale:${MAILU_VERSION:-local}
build: ../optional/radicale build: ../optional/radicale
traefik-certdumper:
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}traefik-certdumper:${MAILU_VERSION:-local}
build: ../optional/traefik-certdumper
admin: admin:
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}admin:${MAILU_VERSION:-local} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}admin:${MAILU_VERSION:-local}
build: ../core/admin build: ../core/admin
postgresql: postgresql:
@ -39,26 +43,29 @@ services:
build: ../optional/postgresql build: ../optional/postgresql
roundcube: roundcube:
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}roundcube:${MAILU_VERSION:-local} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}roundcube:${MAILU_VERSION:-local}
build: ../webmails/roundcube build: ../webmails/roundcube
rainloop: rainloop:
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}rainloop:${MAILU_VERSION:-local} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}rainloop:${MAILU_VERSION:-local}
build: ../webmails/rainloop build: ../webmails/rainloop
fetchmail: fetchmail:
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}fetchmail:${MAILU_VERSION:-local} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}fetchmail:${MAILU_VERSION:-local}
build: ../services/fetchmail build: ../services/fetchmail
none: none:
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}none:${MAILU_VERSION:-local} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}none:${MAILU_VERSION:-local}
build: ../core/none build: ../core/none
docs: docs:
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}docs:${MAILU_VERSION:-local} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}docs:${MAILU_VERSION:-local}
build: ../docs build:
context: ../docs
args:
version: ${MAILU_VERSION:-local}
setup: setup:
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}setup:${MAILU_VERSION:-local} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}setup:${MAILU_VERSION:-local}
build: ../setup build: ../setup

@ -15,7 +15,7 @@ services:
# Core services # Core services
front: front:
image: ${DOCKER_ORG:-mailu}/nginx:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}nginx:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
logging: logging:
@ -34,7 +34,7 @@ services:
- "/mailu/certs:/certs" - "/mailu/certs:/certs"
admin: admin:
image: ${DOCKER_ORG:-mailu}/admin:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}admin:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -44,7 +44,7 @@ services:
- redis - redis
imap: imap:
image: ${DOCKER_ORG:-mailu}/dovecot:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}dovecot:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -54,7 +54,7 @@ services:
- front - front
smtp: smtp:
image: ${DOCKER_ORG:-mailu}/postfix:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}postfix:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -63,7 +63,7 @@ services:
- front - front
antispam: antispam:
image: ${DOCKER_ORG:-mailu}/rspamd:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}rspamd:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -78,3 +78,12 @@ services:
# Webmail # Webmail
networks:
default:
driver: bridge
ipam:
driver: default
config:
- subnet: 192.168.203.0/24

@ -25,6 +25,9 @@ SECRET_KEY=HGZCYGVI6FVG31HS
# PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1) # PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1)
# PUBLIC_IPV6= (default: ::1) # PUBLIC_IPV6= (default: ::1)
# Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!)
SUBNET=192.168.203.0/24
# Main mail domain # Main mail domain
DOMAIN=mailu.io DOMAIN=mailu.io
@ -70,9 +73,9 @@ ANTISPAM=none
# Default: accept messages up to 50MB # Default: accept messages up to 50MB
MESSAGE_SIZE_LIMIT=50000000 MESSAGE_SIZE_LIMIT=50000000
# Networks granted relay permissions, make sure that you include your Docker # Networks granted relay permissions
# internal network (default to 172.17.0.0/16) # Use this with care, all hosts in this networks will be able to send mail without authentication!
RELAYNETS=172.17.0.0/16 RELAYNETS=
# Will relay all outgoing mails if configured # Will relay all outgoing mails if configured
RELAYHOST= RELAYHOST=
@ -136,4 +139,4 @@ REAL_IP_HEADER=
REAL_IP_FROM= REAL_IP_FROM=
# choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) # choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no)
REJECT_UNLISTED_RECIPIENT= REJECT_UNLISTED_RECIPIENT=

@ -15,7 +15,7 @@ services:
# Core services # Core services
front: front:
image: ${DOCKER_ORG:-mailu}/nginx:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}nginx:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
logging: logging:
@ -34,7 +34,7 @@ services:
- "/mailu/certs:/certs" - "/mailu/certs:/certs"
admin: admin:
image: ${DOCKER_ORG:-mailu}/admin:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}admin:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -44,7 +44,7 @@ services:
- redis - redis
imap: imap:
image: ${DOCKER_ORG:-mailu}/dovecot:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}dovecot:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -54,7 +54,7 @@ services:
- front - front
smtp: smtp:
image: ${DOCKER_ORG:-mailu}/postfix:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}postfix:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -63,7 +63,7 @@ services:
- front - front
antispam: antispam:
image: ${DOCKER_ORG:-mailu}/rspamd:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}rspamd:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -77,8 +77,17 @@ services:
fetchmail: fetchmail:
image: ${DOCKER_ORG:-mailu}/fetchmail:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}fetchmail:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
# Webmail # Webmail
networks:
default:
driver: bridge
ipam:
driver: default
config:
- subnet: 192.168.203.0/24

@ -25,6 +25,9 @@ SECRET_KEY=JS48Q9KE3B6T97E6
# PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1) # PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1)
# PUBLIC_IPV6= (default: ::1) # PUBLIC_IPV6= (default: ::1)
# Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!)
SUBNET=192.168.203.0/24
# Main mail domain # Main mail domain
DOMAIN=mailu.io DOMAIN=mailu.io
@ -70,9 +73,9 @@ ANTISPAM=none
# Default: accept messages up to 50MB # Default: accept messages up to 50MB
MESSAGE_SIZE_LIMIT=50000000 MESSAGE_SIZE_LIMIT=50000000
# Networks granted relay permissions, make sure that you include your Docker # Networks granted relay permissions
# internal network (default to 172.17.0.0/16) # Use this with care, all hosts in this networks will be able to send mail without authentication!
RELAYNETS=172.17.0.0/16 RELAYNETS=
# Will relay all outgoing mails if configured # Will relay all outgoing mails if configured
RELAYHOST= RELAYHOST=
@ -136,4 +139,4 @@ REAL_IP_HEADER=
REAL_IP_FROM= REAL_IP_FROM=
# choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) # choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no)
REJECT_UNLISTED_RECIPIENT= REJECT_UNLISTED_RECIPIENT=

@ -15,7 +15,7 @@ services:
# Core services # Core services
front: front:
image: ${DOCKER_ORG:-mailu}/nginx:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}nginx:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
logging: logging:
@ -34,7 +34,7 @@ services:
- "/mailu/certs:/certs" - "/mailu/certs:/certs"
admin: admin:
image: ${DOCKER_ORG:-mailu}/admin:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}admin:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -44,7 +44,7 @@ services:
- redis - redis
imap: imap:
image: ${DOCKER_ORG:-mailu}/dovecot:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}dovecot:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -54,7 +54,7 @@ services:
- front - front
smtp: smtp:
image: ${DOCKER_ORG:-mailu}/postfix:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}postfix:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -63,7 +63,7 @@ services:
- front - front
antispam: antispam:
image: ${DOCKER_ORG:-mailu}/rspamd:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}rspamd:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -75,7 +75,7 @@ services:
# Optional services # Optional services
antivirus: antivirus:
image: ${DOCKER_ORG:-mailu}/clamav:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}clamav:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -84,3 +84,12 @@ services:
# Webmail # Webmail
networks:
default:
driver: bridge
ipam:
driver: default
config:
- subnet: 192.168.203.0/24

@ -25,6 +25,9 @@ SECRET_KEY=11H6XURLGE7GW3U1
# PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1) # PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1)
# PUBLIC_IPV6= (default: ::1) # PUBLIC_IPV6= (default: ::1)
# Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!)
SUBNET=192.168.203.0/24
# Main mail domain # Main mail domain
DOMAIN=mailu.io DOMAIN=mailu.io
@ -70,9 +73,9 @@ ANTISPAM=none
# Default: accept messages up to 50MB # Default: accept messages up to 50MB
MESSAGE_SIZE_LIMIT=50000000 MESSAGE_SIZE_LIMIT=50000000
# Networks granted relay permissions, make sure that you include your Docker # Networks granted relay permissions
# internal network (default to 172.17.0.0/16) # Use this with care, all hosts in this networks will be able to send mail without authentication!
RELAYNETS=172.17.0.0/16 RELAYNETS=
# Will relay all outgoing mails if configured # Will relay all outgoing mails if configured
RELAYHOST= RELAYHOST=
@ -136,4 +139,4 @@ REAL_IP_HEADER=
REAL_IP_FROM= REAL_IP_FROM=
# choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) # choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no)
REJECT_UNLISTED_RECIPIENT= REJECT_UNLISTED_RECIPIENT=

@ -15,7 +15,7 @@ services:
# Core services # Core services
front: front:
image: ${DOCKER_ORG:-mailu}/nginx:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}nginx:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
logging: logging:
@ -34,7 +34,7 @@ services:
- "/mailu/certs:/certs" - "/mailu/certs:/certs"
admin: admin:
image: ${DOCKER_ORG:-mailu}/admin:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}admin:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -44,7 +44,7 @@ services:
- redis - redis
imap: imap:
image: ${DOCKER_ORG:-mailu}/dovecot:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}dovecot:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -54,7 +54,7 @@ services:
- front - front
smtp: smtp:
image: ${DOCKER_ORG:-mailu}/postfix:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}postfix:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -63,7 +63,7 @@ services:
- front - front
antispam: antispam:
image: ${DOCKER_ORG:-mailu}/rspamd:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}rspamd:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -79,10 +79,19 @@ services:
# Webmail # Webmail
webmail: webmail:
image: ${DOCKER_ORG:-mailu}/rainloop:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}rainloop:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
- "/mailu/webmail:/data" - "/mailu/webmail:/data"
depends_on: depends_on:
- imap - imap
networks:
default:
driver: bridge
ipam:
driver: default
config:
- subnet: 192.168.203.0/24

@ -25,6 +25,9 @@ SECRET_KEY=V5J4SHRYVW9PZIQU
# PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1) # PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1)
# PUBLIC_IPV6= (default: ::1) # PUBLIC_IPV6= (default: ::1)
# Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!)
SUBNET=192.168.203.0/24
# Main mail domain # Main mail domain
DOMAIN=mailu.io DOMAIN=mailu.io
@ -70,9 +73,9 @@ ANTISPAM=none
# Default: accept messages up to 50MB # Default: accept messages up to 50MB
MESSAGE_SIZE_LIMIT=50000000 MESSAGE_SIZE_LIMIT=50000000
# Networks granted relay permissions, make sure that you include your Docker # Networks granted relay permissions
# internal network (default to 172.17.0.0/16) # Use this with care, all hosts in this networks will be able to send mail without authentication!
RELAYNETS=172.17.0.0/16 RELAYNETS=
# Will relay all outgoing mails if configured # Will relay all outgoing mails if configured
RELAYHOST= RELAYHOST=
@ -136,4 +139,4 @@ REAL_IP_HEADER=
REAL_IP_FROM= REAL_IP_FROM=
# choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) # choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no)
REJECT_UNLISTED_RECIPIENT= REJECT_UNLISTED_RECIPIENT=

@ -15,7 +15,7 @@ services:
# Core services # Core services
front: front:
image: ${DOCKER_ORG:-mailu}/nginx:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}nginx:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
logging: logging:
@ -34,7 +34,7 @@ services:
- "/mailu/certs:/certs" - "/mailu/certs:/certs"
admin: admin:
image: ${DOCKER_ORG:-mailu}/admin:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}admin:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -44,7 +44,7 @@ services:
- redis - redis
imap: imap:
image: ${DOCKER_ORG:-mailu}/dovecot:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}dovecot:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -54,7 +54,7 @@ services:
- front - front
smtp: smtp:
image: ${DOCKER_ORG:-mailu}/postfix:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}postfix:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -63,7 +63,7 @@ services:
- front - front
antispam: antispam:
image: ${DOCKER_ORG:-mailu}/rspamd:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}rspamd:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -79,10 +79,19 @@ services:
# Webmail # Webmail
webmail: webmail:
image: ${DOCKER_ORG:-mailu}/roundcube:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}roundcube:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
- "/mailu/webmail:/data" - "/mailu/webmail:/data"
depends_on: depends_on:
- imap - imap
networks:
default:
driver: bridge
ipam:
driver: default
config:
- subnet: 192.168.203.0/24

@ -25,6 +25,9 @@ SECRET_KEY=PGGO2JRQ59QV3DW7
# PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1) # PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1)
# PUBLIC_IPV6= (default: ::1) # PUBLIC_IPV6= (default: ::1)
# Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!)
SUBNET=192.168.203.0/24
# Main mail domain # Main mail domain
DOMAIN=mailu.io DOMAIN=mailu.io
@ -70,9 +73,9 @@ ANTISPAM=none
# Default: accept messages up to 50MB # Default: accept messages up to 50MB
MESSAGE_SIZE_LIMIT=50000000 MESSAGE_SIZE_LIMIT=50000000
# Networks granted relay permissions, make sure that you include your Docker # Networks granted relay permissions
# internal network (default to 172.17.0.0/16) # Use this with care, all hosts in this networks will be able to send mail without authentication!
RELAYNETS=172.17.0.0/16 RELAYNETS=
# Will relay all outgoing mails if configured # Will relay all outgoing mails if configured
RELAYHOST= RELAYHOST=
@ -136,4 +139,4 @@ REAL_IP_HEADER=
REAL_IP_FROM= REAL_IP_FROM=
# choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) # choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no)
REJECT_UNLISTED_RECIPIENT= REJECT_UNLISTED_RECIPIENT=

@ -15,7 +15,7 @@ services:
# Core services # Core services
front: front:
image: ${DOCKER_ORG:-mailu}/nginx:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}nginx:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
logging: logging:
@ -34,7 +34,7 @@ services:
- "/mailu/certs:/certs" - "/mailu/certs:/certs"
admin: admin:
image: ${DOCKER_ORG:-mailu}/admin:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}admin:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -44,7 +44,7 @@ services:
- redis - redis
imap: imap:
image: ${DOCKER_ORG:-mailu}/dovecot:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}dovecot:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -54,7 +54,7 @@ services:
- front - front
smtp: smtp:
image: ${DOCKER_ORG:-mailu}/postfix:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}postfix:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -63,7 +63,7 @@ services:
- front - front
antispam: antispam:
image: ${DOCKER_ORG:-mailu}/rspamd:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}rspamd:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -76,7 +76,7 @@ services:
# Optional services # Optional services
webdav: webdav:
image: ${DOCKER_ORG:-mailu}/radicale:${MAILU_VERSION:-master} image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}radicale:${MAILU_VERSION:-master}
restart: always restart: always
env_file: mailu.env env_file: mailu.env
volumes: volumes:
@ -84,3 +84,12 @@ services:
# Webmail # Webmail
networks:
default:
driver: bridge
ipam:
driver: default
config:
- subnet: 192.168.203.0/24

@ -25,6 +25,9 @@ SECRET_KEY=XVDDSWOAGVF5J9QJ
# PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1) # PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1)
# PUBLIC_IPV6= (default: ::1) # PUBLIC_IPV6= (default: ::1)
# Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!)
SUBNET=192.168.203.0/24
# Main mail domain # Main mail domain
DOMAIN=mailu.io DOMAIN=mailu.io
@ -70,9 +73,9 @@ ANTISPAM=none
# Default: accept messages up to 50MB # Default: accept messages up to 50MB
MESSAGE_SIZE_LIMIT=50000000 MESSAGE_SIZE_LIMIT=50000000
# Networks granted relay permissions, make sure that you include your Docker # Networks granted relay permissions
# internal network (default to 172.17.0.0/16) # Use this with care, all hosts in this networks will be able to send mail without authentication!
RELAYNETS=172.17.0.0/16 RELAYNETS=
# Will relay all outgoing mails if configured # Will relay all outgoing mails if configured
RELAYHOST= RELAYHOST=
@ -136,4 +139,4 @@ REAL_IP_HEADER=
REAL_IP_FROM= REAL_IP_FROM=
# choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) # choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no)
REJECT_UNLISTED_RECIPIENT= REJECT_UNLISTED_RECIPIENT=

@ -22,7 +22,7 @@ RUN apt-get update && apt-get install -y \
&& rm -rf /var/lib/apt/lists && rm -rf /var/lib/apt/lists
COPY include.php /var/www/html/include.php COPY include.php /var/www/html/include.php
COPY php.ini /usr/local/etc/php/conf.d/rainloop.ini COPY php.ini /php.ini
COPY config.ini /config.ini COPY config.ini /config.ini
COPY default.ini /default.ini COPY default.ini /default.ini

@ -1,7 +1,7 @@
; RainLoop Webmail configuration file ; RainLoop Webmail configuration file
[webmail] [webmail]
attachment_size_limit = 25 attachment_size_limit = {{ MAX_FILESIZE }}
[security] [security]
allow_admin_panel = Off allow_admin_panel = Off

@ -1,3 +1,4 @@
date.timezone=UTC date.timezone=UTC
upload_max_filesize = 25M upload_max_filesize = {{ MAX_FILESIZE }}M
post_max_size = 25M post_max_size = {{ MAX_FILESIZE }}M

@ -10,6 +10,8 @@ convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()
os.environ["FRONT_ADDRESS"] = os.environ.get("FRONT_ADDRESS", "front") os.environ["FRONT_ADDRESS"] = os.environ.get("FRONT_ADDRESS", "front")
os.environ["IMAP_ADDRESS"] = os.environ.get("IMAP_ADDRESS", "imap") os.environ["IMAP_ADDRESS"] = os.environ.get("IMAP_ADDRESS", "imap")
os.environ["MAX_FILESIZE"] = str(int(int(os.environ.get("MESSAGE_SIZE_LIMIT"))*0.66/1048576))
base = "/data/_data_/_default_/" base = "/data/_data_/_default_/"
shutil.rmtree(base + "domains/", ignore_errors=True) shutil.rmtree(base + "domains/", ignore_errors=True)
os.makedirs(base + "domains", exist_ok=True) os.makedirs(base + "domains", exist_ok=True)
@ -17,6 +19,7 @@ os.makedirs(base + "configs", exist_ok=True)
convert("/default.ini", "/data/_data_/_default_/domains/default.ini") convert("/default.ini", "/data/_data_/_default_/domains/default.ini")
convert("/config.ini", "/data/_data_/_default_/configs/config.ini") convert("/config.ini", "/data/_data_/_default_/configs/config.ini")
convert("/php.ini", "/usr/local/etc/php/conf.d/rainloop.ini")
os.system("chown -R www-data:www-data /data") os.system("chown -R www-data:www-data /data")

@ -7,7 +7,7 @@ RUN apt-get update && apt-get install -y \
ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.3.8/roundcubemail-1.3.8-complete.tar.gz ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.3.8/roundcubemail-1.3.8-complete.tar.gz
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
zlib1g-dev \ zlib1g-dev python3-jinja2 \
&& docker-php-ext-install zip \ && docker-php-ext-install zip \
&& echo date.timezone=UTC > /usr/local/etc/php/conf.d/timezone.ini \ && echo date.timezone=UTC > /usr/local/etc/php/conf.d/timezone.ini \
&& rm -rf /var/www/html/ \ && rm -rf /var/www/html/ \
@ -22,7 +22,7 @@ RUN apt-get update && apt-get install -y \
&& chown -R www-data: logs temp \ && chown -R www-data: logs temp \
&& rm -rf /var/lib/apt/lists && rm -rf /var/lib/apt/lists
COPY php.ini /usr/local/etc/php/conf.d/roundcube.ini COPY php.ini /php.ini
COPY config.inc.php /var/www/html/config/ COPY config.inc.php /var/www/html/config/
COPY start.py /start.py COPY start.py /start.py

@ -1,3 +1,4 @@
date.timezone=UTC date.timezone=UTC
upload_max_filesize = 25M upload_max_filesize = {{ MAX_FILESIZE }}M
post_max_size = 25M post_max_size = {{ MAX_FILESIZE }}M

@ -1,6 +1,13 @@
#!/usr/bin/python3 #!/usr/bin/python3
import os import os
import jinja2
convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ))
os.environ["MAX_FILESIZE"] = str(int(int(os.environ.get("MESSAGE_SIZE_LIMIT"))*0.66/1048576))
convert("/php.ini", "/usr/local/etc/php/conf.d/roundcube.ini")
# Fix some permissions # Fix some permissions
os.system("mkdir -p /data/gpg") os.system("mkdir -p /data/gpg")

Loading…
Cancel
Save