mirror of
				https://github.com/optim-enterprises-bv/Mailu.git
				synced 2025-10-31 01:57:59 +00:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/master' into extend-nginx
This commit is contained in:
		
							
								
								
									
										35
									
								
								.mergify.yml
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								.mergify.yml
									
									
									
									
									
								
							| @@ -1,10 +1,25 @@ | ||||
| rules: | ||||
|   default: null | ||||
|   branches: | ||||
|     master: | ||||
|       protection: | ||||
|         required_status_checks: | ||||
|           contexts: | ||||
|             - continuous-integration/travis-ci  | ||||
|         required_pull_request_reviews: | ||||
|           required_approving_review_count: 2 | ||||
| pull_request_rules: | ||||
|   - name: Successful travis and 2 approved reviews | ||||
|     conditions: | ||||
|       - status-success=continuous-integration/travis-ci/pr | ||||
|       - label!=["status"/wip","status/blocked"] | ||||
|       - "#approved-reviews-by>=2" | ||||
|     actions: | ||||
|       merge: | ||||
|         method: merge | ||||
|         strict: true | ||||
|       dismiss_reviews: | ||||
|         approved: true | ||||
|  | ||||
|   - name: Trusted author, successful travis and 1 approved review | ||||
|     conditions: | ||||
|       - author~=(kaiyou|muhlemmer|mildred|HorayNarea|adi90x|hoellen|ofthesun9) | ||||
|       - status-success=continuous-integration/travis-ci/pr | ||||
|       - label!=["status"/wip","status/blocked","review/need2"] | ||||
|       - "#approved-reviews-by>=1" | ||||
|     actions: | ||||
|       merge: | ||||
|         method: merge | ||||
|         strict: true | ||||
|       dismiss_reviews: | ||||
|         approved: true | ||||
|   | ||||
							
								
								
									
										27
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -4,14 +4,30 @@ addons: | ||||
|   apt: | ||||
|     packages: | ||||
|       - docker-ce | ||||
|  | ||||
| env: | ||||
|   - VERSION=$TRAVIS_BRANCH | ||||
|   - MAILU_VERSION=$TRAVIS_BRANCH | ||||
| language: python | ||||
| python: | ||||
|   - "3.6" | ||||
| install: | ||||
|   - pip install -r tests/requirements.txt | ||||
|   - sudo curl -L https://github.com/docker/compose/releases/download/1.23.0-rc3/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose | ||||
|   - sudo chmod +x /usr/local/bin/docker-compose | ||||
|  | ||||
| before_script: | ||||
|   - docker-compose -v | ||||
|   - docker-compose -f tests/build.yml build | ||||
|   - sudo -- sh -c 'mkdir -p /mailu && cp -r tests/certs /mailu && chmod 600 /mailu/certs/*' | ||||
|  | ||||
| script: | ||||
|   # Default to mailu for DOCKER_ORG | ||||
|   - if [ -z "$DOCKER_ORG" ]; then export DOCKER_ORG="mailu"; fi | ||||
|   - docker-compose -f tests/build.yml build | ||||
|   - tests/compose/test-script.sh | ||||
| # test.py, test name and timeout between start and tests. | ||||
|   - python tests/compose/test.py core 1 | ||||
|   - python tests/compose/test.py fetchmail 1 | ||||
|   - travis_wait python tests/compose/test.py filters 10 | ||||
|   - python tests/compose/test.py rainloop 1 | ||||
|   - python tests/compose/test.py roundcube 1 | ||||
|   - python tests/compose/test.py webdav 1 | ||||
|  | ||||
| deploy: | ||||
|   provider: script | ||||
| @@ -19,4 +35,3 @@ deploy: | ||||
|   on: | ||||
|     all_branches: true | ||||
|     condition: -n $DOCKER_UN | ||||
|  | ||||
|   | ||||
| @@ -15,14 +15,14 @@ RUN apk add --no-cache openssl curl \ | ||||
|  | ||||
| COPY mailu ./mailu | ||||
| COPY migrations ./migrations | ||||
| COPY manage.py . | ||||
| COPY start.py /start.py | ||||
|  | ||||
| RUN pybabel compile -d mailu/translations | ||||
|  | ||||
| EXPOSE 80/tcp | ||||
| VOLUME ["/data"] | ||||
| ENV FLASK_APP mailu | ||||
|  | ||||
| CMD /start.py | ||||
|  | ||||
| HEALTHCHECK CMD curl -f -L http://localhost/ui || exit 1 | ||||
| HEALTHCHECK CMD curl -f -L http://localhost/ui/login?next=ui.index || exit 1 | ||||
|   | ||||
| @@ -1,140 +1,57 @@ | ||||
| import flask | ||||
| import flask_sqlalchemy | ||||
| import flask_bootstrap | ||||
| import flask_login | ||||
| import flask_script | ||||
| import flask_migrate | ||||
| import flask_babel | ||||
| import flask_limiter | ||||
|  | ||||
| import os | ||||
| import docker | ||||
| import socket | ||||
| import uuid | ||||
|  | ||||
| from werkzeug.contrib import fixers, profiler | ||||
|  | ||||
| # Create application | ||||
| app = flask.Flask(__name__) | ||||
|  | ||||
| default_config = { | ||||
|     # Specific to the admin UI | ||||
|     'SQLALCHEMY_DATABASE_URI': 'sqlite:////data/main.db', | ||||
|     'SQLALCHEMY_TRACK_MODIFICATIONS': False, | ||||
|     'DOCKER_SOCKET': 'unix:///var/run/docker.sock', | ||||
|     'BABEL_DEFAULT_LOCALE': 'en', | ||||
|     'BABEL_DEFAULT_TIMEZONE': 'UTC', | ||||
|     'BOOTSTRAP_SERVE_LOCAL': True, | ||||
|     'RATELIMIT_STORAGE_URL': 'redis://redis/2', | ||||
|     'QUOTA_STORAGE_URL': 'redis://redis/1', | ||||
|     'DEBUG': False, | ||||
|     'DOMAIN_REGISTRATION': False, | ||||
|     # Statistics management | ||||
|     'INSTANCE_ID_PATH': '/data/instance', | ||||
|     'STATS_ENDPOINT': '0.{}.stats.mailu.io', | ||||
|     # Common configuration variables | ||||
|     'SECRET_KEY': 'changeMe', | ||||
|     'DOMAIN': 'mailu.io', | ||||
|     'HOSTNAMES': 'mail.mailu.io,alternative.mailu.io,yetanother.mailu.io', | ||||
|     'POSTMASTER': 'postmaster', | ||||
|     'TLS_FLAVOR': 'cert', | ||||
|     'AUTH_RATELIMIT': '10/minute;1000/hour', | ||||
|     'DISABLE_STATISTICS': 'False', | ||||
|     # Mail settings | ||||
|     'DMARC_RUA': None, | ||||
|     'DMARC_RUF': None, | ||||
|     'WELCOME': 'False', | ||||
|     'WELCOME_SUBJECT': 'Dummy welcome topic', | ||||
|     'WELCOME_BODY': 'Dummy welcome body', | ||||
|     'DKIM_SELECTOR': 'dkim', | ||||
|     'DKIM_PATH': '/dkim/{domain}.{selector}.key', | ||||
|     'DEFAULT_QUOTA': 1000000000, | ||||
|     # Web settings | ||||
|     'SITENAME': 'Mailu', | ||||
|     'WEBSITE': 'https://mailu.io', | ||||
|     'WEB_ADMIN': '/admin', | ||||
|     'WEB_WEBMAIL': '/webmail', | ||||
|     'RECAPTCHA_PUBLIC_KEY': '', | ||||
|     'RECAPTCHA_PRIVATE_KEY': '', | ||||
|     # Advanced settings | ||||
|     'PASSWORD_SCHEME': 'BLF-CRYPT', | ||||
|     # Host settings | ||||
|     'HOST_IMAP': 'imap', | ||||
|     'HOST_POP3': 'imap', | ||||
|     'HOST_SMTP': 'smtp', | ||||
|     'HOST_WEBMAIL': 'webmail', | ||||
|     'HOST_FRONT': 'front', | ||||
|     'HOST_AUTHSMTP': os.environ.get('HOST_SMTP', 'smtp'), | ||||
|     'POD_ADDRESS_RANGE': None | ||||
| } | ||||
|  | ||||
| # Load configuration from the environment if available | ||||
| for key, value in default_config.items(): | ||||
|     app.config[key] = os.environ.get(key, value) | ||||
|  | ||||
| # Base application | ||||
| flask_bootstrap.Bootstrap(app) | ||||
| db = flask_sqlalchemy.SQLAlchemy(app) | ||||
| migrate = flask_migrate.Migrate(app, db) | ||||
| limiter = flask_limiter.Limiter(app, key_func=lambda: current_user.username) | ||||
|  | ||||
| # Debugging toolbar | ||||
| if app.config.get("DEBUG"): | ||||
|     import flask_debugtoolbar | ||||
|     toolbar = flask_debugtoolbar.DebugToolbarExtension(app) | ||||
|  | ||||
| # Profiler | ||||
| if app.config.get("DEBUG"): | ||||
|     app.wsgi_app = profiler.ProfilerMiddleware(app.wsgi_app, restrictions=[30]) | ||||
|  | ||||
| # Manager commnad | ||||
| manager = flask_script.Manager(app) | ||||
| manager.add_command('db', flask_migrate.MigrateCommand) | ||||
|  | ||||
| # Babel configuration | ||||
| babel = flask_babel.Babel(app) | ||||
| translations = list(map(str, babel.list_translations())) | ||||
|  | ||||
| @babel.localeselector | ||||
| def get_locale(): | ||||
|     return flask.request.accept_languages.best_match(translations) | ||||
|  | ||||
| # Login configuration | ||||
| login_manager = flask_login.LoginManager() | ||||
| login_manager.init_app(app) | ||||
| login_manager.login_view = "ui.login" | ||||
|  | ||||
| @login_manager.unauthorized_handler | ||||
| def handle_needs_login(): | ||||
|     return flask.redirect( | ||||
|         flask.url_for('ui.login', next=flask.request.endpoint) | ||||
|     ) | ||||
|  | ||||
| @app.context_processor | ||||
| def inject_defaults(): | ||||
|     signup_domains = models.Domain.query.filter_by(signup_enabled=True).all() | ||||
|     return dict( | ||||
|         current_user=flask_login.current_user, | ||||
|         signup_domains=signup_domains, | ||||
|         config=app.config | ||||
|     ) | ||||
|  | ||||
| # Import views | ||||
| from mailu import ui, internal | ||||
| app.register_blueprint(ui.ui, url_prefix='/ui') | ||||
| app.register_blueprint(internal.internal, url_prefix='/internal') | ||||
|  | ||||
| # Create the prefix middleware | ||||
| class PrefixMiddleware(object): | ||||
|  | ||||
|     def __init__(self, app): | ||||
|         self.app = app | ||||
|  | ||||
|     def __call__(self, environ, start_response): | ||||
|         prefix = environ.get('HTTP_X_FORWARDED_PREFIX', '') | ||||
|         if prefix: | ||||
|             environ['SCRIPT_NAME'] = prefix | ||||
|         return self.app(environ, start_response) | ||||
| from mailu import utils, debug, models, manage, configuration | ||||
|  | ||||
|  | ||||
| app.wsgi_app = PrefixMiddleware(fixers.ProxyFix(app.wsgi_app)) | ||||
| def create_app_from_config(config): | ||||
|     """ Create a new application based on the given configuration | ||||
|     """ | ||||
|     app = flask.Flask(__name__) | ||||
|     app.app_context().push() | ||||
|     app.cli.add_command(manage.mailu) | ||||
|  | ||||
|     # Bootstrap is used for basic JS and CSS loading | ||||
|     # TODO: remove this and use statically generated assets instead | ||||
|     app.bootstrap = flask_bootstrap.Bootstrap(app) | ||||
|  | ||||
|     # Initialize application extensions | ||||
|     config.init_app(app) | ||||
|     models.db.init_app(app) | ||||
|     utils.limiter.init_app(app) | ||||
|     utils.babel.init_app(app) | ||||
|     utils.login.init_app(app) | ||||
|     utils.login.user_loader(models.User.get) | ||||
|     utils.proxy.init_app(app) | ||||
|     utils.migrate.init_app(app, models.db) | ||||
|  | ||||
|     # Initialize debugging tools | ||||
|     if app.config.get("DEBUG"): | ||||
|         debug.toolbar.init_app(app) | ||||
|         # TODO: add a specific configuration variable for profiling | ||||
|         # debug.profiler.init_app(app) | ||||
|  | ||||
|     # Inject the default variables in the Jinja parser | ||||
|     # TODO: move this to blueprints when needed | ||||
|     @app.context_processor | ||||
|     def inject_defaults(): | ||||
|         signup_domains = models.Domain.query.filter_by(signup_enabled=True).all() | ||||
|         return dict( | ||||
|             signup_domains=signup_domains, | ||||
|             config=app.config | ||||
|         ) | ||||
|  | ||||
|     # Import views | ||||
|     from mailu import ui, internal | ||||
|     app.register_blueprint(ui.ui, url_prefix='/ui') | ||||
|     app.register_blueprint(internal.internal, url_prefix='/internal') | ||||
|  | ||||
|     return app | ||||
|  | ||||
|  | ||||
| def create_app(): | ||||
|     """ Create a new application based on the config module  | ||||
|     """ | ||||
|     config = configuration.ConfigManager() | ||||
|     return create_app_from_config(config) | ||||
|  | ||||
|   | ||||
							
								
								
									
										90
									
								
								core/admin/mailu/configuration.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								core/admin/mailu/configuration.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| import os | ||||
|  | ||||
|  | ||||
| DEFAULT_CONFIG = { | ||||
|     # Specific to the admin UI | ||||
|     'SQLALCHEMY_DATABASE_URI': 'sqlite:////data/main.db', | ||||
|     'SQLALCHEMY_TRACK_MODIFICATIONS': False, | ||||
|     'DOCKER_SOCKET': 'unix:///var/run/docker.sock', | ||||
|     'BABEL_DEFAULT_LOCALE': 'en', | ||||
|     'BABEL_DEFAULT_TIMEZONE': 'UTC', | ||||
|     'BOOTSTRAP_SERVE_LOCAL': True, | ||||
|     'RATELIMIT_STORAGE_URL': 'redis://redis/2', | ||||
|     'QUOTA_STORAGE_URL': 'redis://redis/1', | ||||
|     'DEBUG': False, | ||||
|     'DOMAIN_REGISTRATION': False, | ||||
|     'TEMPLATES_AUTO_RELOAD': True, | ||||
|     # Statistics management | ||||
|     'INSTANCE_ID_PATH': '/data/instance', | ||||
|     'STATS_ENDPOINT': '0.{}.stats.mailu.io', | ||||
|     # Common configuration variables | ||||
|     'SECRET_KEY': 'changeMe', | ||||
|     'DOMAIN': 'mailu.io', | ||||
|     'HOSTNAMES': 'mail.mailu.io,alternative.mailu.io,yetanother.mailu.io', | ||||
|     'POSTMASTER': 'postmaster', | ||||
|     'TLS_FLAVOR': 'cert', | ||||
|     'AUTH_RATELIMIT': '10/minute;1000/hour', | ||||
|     'DISABLE_STATISTICS': 'False', | ||||
|     # Mail settings | ||||
|     'DMARC_RUA': None, | ||||
|     'DMARC_RUF': None, | ||||
|     'WELCOME': 'False', | ||||
|     'WELCOME_SUBJECT': 'Dummy welcome topic', | ||||
|     'WELCOME_BODY': 'Dummy welcome body', | ||||
|     'DKIM_SELECTOR': 'dkim', | ||||
|     'DKIM_PATH': '/dkim/{domain}.{selector}.key', | ||||
|     'DEFAULT_QUOTA': 1000000000, | ||||
|     # Web settings | ||||
|     'SITENAME': 'Mailu', | ||||
|     'WEBSITE': 'https://mailu.io', | ||||
|     'WEB_ADMIN': '/admin', | ||||
|     'WEB_WEBMAIL': '/webmail', | ||||
|     'RECAPTCHA_PUBLIC_KEY': '', | ||||
|     'RECAPTCHA_PRIVATE_KEY': '', | ||||
|     # Advanced settings | ||||
|     'PASSWORD_SCHEME': 'BLF-CRYPT', | ||||
|     # Host settings | ||||
|     'HOST_IMAP': 'imap', | ||||
|     'HOST_POP3': 'imap', | ||||
|     'HOST_SMTP': 'smtp', | ||||
|     'HOST_WEBMAIL': 'webmail', | ||||
|     'HOST_FRONT': 'front', | ||||
|     'HOST_AUTHSMTP': os.environ.get('HOST_SMTP', 'smtp'), | ||||
|     'POD_ADDRESS_RANGE': None | ||||
| } | ||||
|  | ||||
|  | ||||
| class ConfigManager(dict): | ||||
|     """ Naive configuration manager that uses environment only | ||||
|     """ | ||||
|  | ||||
|     def __init__(self): | ||||
|         self.config = dict() | ||||
|  | ||||
|     def init_app(self, app): | ||||
|         self.config.update(app.config) | ||||
|         self.config.update({ | ||||
|             key: os.environ.get(key, value) | ||||
|             for key, value in DEFAULT_CONFIG.items() | ||||
|         }) | ||||
|         app.config = self | ||||
|  | ||||
|     def setdefault(self, key, value): | ||||
|         if key not in self.config: | ||||
|             self.config[key] = value | ||||
|         return self.config[key] | ||||
|  | ||||
|     def get(self, *args): | ||||
|         return self.config.get(*args) | ||||
|  | ||||
|     def keys(self): | ||||
|         return self.config.keys() | ||||
|  | ||||
|     def __getitem__(self, key): | ||||
|         return self.config.get(key) | ||||
|  | ||||
|     def __setitem__(self, key, value): | ||||
|         self.config[key] = value | ||||
|  | ||||
|     def __contains__(self, key): | ||||
|         return key in self.config | ||||
							
								
								
									
										17
									
								
								core/admin/mailu/debug.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								core/admin/mailu/debug.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| import flask_debugtoolbar | ||||
|  | ||||
| from werkzeug.contrib import profiler as werkzeug_profiler | ||||
|  | ||||
|  | ||||
| # Debugging toolbar | ||||
| toolbar = flask_debugtoolbar.DebugToolbarExtension() | ||||
|  | ||||
|  | ||||
| # Profiler | ||||
| class Profiler(object): | ||||
|     def init_app(self, app): | ||||
|         app.wsgi_app = werkzeug_profiler.ProfilerMiddleware( | ||||
|             app.wsgi_app, restrictions=[30] | ||||
|         ) | ||||
|  | ||||
| profiler = Profiler() | ||||
| @@ -1,26 +0,0 @@ | ||||
| from mailu import app | ||||
|  | ||||
| import docker | ||||
| import signal | ||||
|  | ||||
|  | ||||
| # Connect to the Docker socket | ||||
| cli = docker.Client(base_url=app.config['DOCKER_SOCKET']) | ||||
|  | ||||
|  | ||||
| def get(*names): | ||||
|     result = {} | ||||
|     all_containers = cli.containers(all=True) | ||||
|     for brief in all_containers: | ||||
|         if brief['Image'].startswith('mailu/'): | ||||
|             container = cli.inspect_container(brief['Id']) | ||||
|             container['Image'] = cli.inspect_image(container['Image']) | ||||
|             name = container['Config']['Labels']['com.docker.compose.service'] | ||||
|             if not names or name in names: | ||||
|                 result[name] = container | ||||
|     return result | ||||
|  | ||||
|  | ||||
| def reload(*names): | ||||
|     for name, container in get(*names).items(): | ||||
|         cli.kill(container["Id"], signal.SIGHUP.value) | ||||
| @@ -1,6 +1,6 @@ | ||||
| from flask_limiter import RateLimitExceeded | ||||
|  | ||||
| from mailu import limiter | ||||
| from mailu import utils | ||||
|  | ||||
| import socket | ||||
| import flask | ||||
| @@ -19,7 +19,7 @@ def rate_limit_handler(e): | ||||
|     return response | ||||
|  | ||||
|  | ||||
| @limiter.request_filter | ||||
| @utils.limiter.request_filter | ||||
| def whitelist_webmail(): | ||||
|     try: | ||||
|         return flask.request.headers["Client-Ip"] ==\ | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| from mailu import db, models, app | ||||
| from mailu import models | ||||
| from flask import current_app as app | ||||
|  | ||||
| import re | ||||
| import socket | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| from mailu import db, models, app, limiter | ||||
| from mailu import models, utils | ||||
| from mailu.internal import internal, nginx | ||||
| from flask import current_app as app | ||||
|  | ||||
| import flask | ||||
| import flask_login | ||||
| @@ -7,7 +8,7 @@ import base64 | ||||
|  | ||||
|  | ||||
| @internal.route("/auth/email") | ||||
| @limiter.limit( | ||||
| @utils.limiter.limit( | ||||
|     app.config["AUTH_RATELIMIT"], | ||||
|     lambda: flask.request.headers["Client-Ip"] | ||||
| ) | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| from mailu import db, models, app | ||||
| from mailu import models | ||||
| from mailu.internal import internal | ||||
| from flask import current_app as app | ||||
|  | ||||
| import flask | ||||
| import socket | ||||
| @@ -36,7 +37,7 @@ def dovecot_quota(ns, user_email): | ||||
|     user = models.User.query.get(user_email) or flask.abort(404) | ||||
|     if ns == "storage": | ||||
|         user.quota_bytes_used = flask.request.get_json() | ||||
|         db.session.commit() | ||||
|         models.db.session.commit() | ||||
|     return flask.jsonify(None) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| from mailu import db, models | ||||
| from mailu import models | ||||
| from mailu.internal import internal | ||||
|  | ||||
| import flask | ||||
| @@ -27,6 +27,6 @@ def fetch_done(fetch_id): | ||||
|     fetch = models.Fetch.query.get(fetch_id) or flask.abort(404) | ||||
|     fetch.last_check = datetime.datetime.now() | ||||
|     fetch.error_message = str(flask.request.get_json()) | ||||
|     db.session.add(fetch) | ||||
|     db.session.commit() | ||||
|     models.db.session.add(fetch) | ||||
|     models.db.session.commit() | ||||
|     return "" | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| from mailu import db, models | ||||
| from mailu import models | ||||
| from mailu.internal import internal | ||||
|  | ||||
| import flask | ||||
| @@ -6,7 +6,9 @@ import flask | ||||
|  | ||||
| @internal.route("/postfix/domain/<domain_name>") | ||||
| def postfix_mailbox_domain(domain_name): | ||||
|     domain = models.Domain.query.get(domain_name) or flask.abort(404) | ||||
|     domain = models.Domain.query.get(domain_name) or \ | ||||
|              models.Alternative.query.get(domain_name) or \ | ||||
|              flask.abort(404) | ||||
|     return flask.jsonify(domain.name) | ||||
|  | ||||
|  | ||||
| @@ -18,37 +20,34 @@ def postfix_mailbox_map(email): | ||||
|  | ||||
| @internal.route("/postfix/alias/<alias>") | ||||
| def postfix_alias_map(alias): | ||||
|     localpart, domain = alias.split('@', 1) if '@' in alias else (None, alias) | ||||
|     alternative = models.Alternative.query.get(domain) | ||||
|     if alternative: | ||||
|         domain = alternative.domain_name | ||||
|     email = '{}@{}'.format(localpart, domain) | ||||
|     localpart, domain_name = models.Email.resolve_domain(alias) | ||||
|     if localpart is None: | ||||
|         return flask.jsonify(domain) | ||||
|     else: | ||||
|         alias_obj = models.Alias.resolve(localpart, domain) | ||||
|         if alias_obj: | ||||
|             return flask.jsonify(",".join(alias_obj.destination)) | ||||
|         user_obj = models.User.query.get(email) | ||||
|         if user_obj: | ||||
|             return flask.jsonify(user_obj.destination) | ||||
|         return flask.abort(404) | ||||
|         return flask.jsonify(domain_name) | ||||
|     destination = models.Email.resolve_destination(localpart, domain_name) | ||||
|     return flask.jsonify(",".join(destination)) if destination else flask.abort(404) | ||||
|  | ||||
|  | ||||
| @internal.route("/postfix/transport/<email>") | ||||
| def postfix_transport(email): | ||||
|     localpart, domain = email.split('@', 1) if '@' in email else (None, email) | ||||
|     relay = models.Relay.query.get(domain) or flask.abort(404) | ||||
|     if email == '*': | ||||
|         return flask.abort(404) | ||||
|     localpart, domain_name = models.Email.resolve_domain(email) | ||||
|     relay = models.Relay.query.get(domain_name) or flask.abort(404) | ||||
|     return flask.jsonify("smtp:[{}]".format(relay.smtp)) | ||||
|  | ||||
|  | ||||
| @internal.route("/postfix/sender/<sender>") | ||||
| def postfix_sender(sender): | ||||
| @internal.route("/postfix/sender/login/<sender>") | ||||
| def postfix_sender_login(sender): | ||||
|     localpart, domain_name = models.Email.resolve_domain(sender) | ||||
|     if localpart is None: | ||||
|         return flask.abort(404) | ||||
|     destination = models.Email.resolve_destination(localpart, domain_name, True) | ||||
|     return flask.jsonify(",".join(destination)) if destination else flask.abort(404) | ||||
|  | ||||
|  | ||||
| @internal.route("/postfix/sender/access/<sender>") | ||||
| def postfix_sender_access(sender): | ||||
|     """ Simply reject any sender that pretends to be from a local domain | ||||
|     """ | ||||
|     localpart, domain_name = sender.split('@', 1) if '@' in sender else (None, sender) | ||||
|     domain = models.Domain.query.get(domain_name) | ||||
|     alternative = models.Alternative.query.get(domain_name) | ||||
|     if domain or alternative: | ||||
|         return flask.jsonify("REJECT") | ||||
|     return flask.abort(404) | ||||
|     localpart, domain_name = models.Email.resolve_domain(sender) | ||||
|     return flask.jsonify("REJECT") if models.Domain.query.get(domain_name) else flask.abort(404) | ||||
|   | ||||
| @@ -1,11 +1,26 @@ | ||||
| from mailu import app, manager, db, models | ||||
| from mailu import models | ||||
| 
 | ||||
| from flask import current_app as app | ||||
| from flask import cli as flask_cli | ||||
| 
 | ||||
| import flask | ||||
| import os | ||||
| import socket | ||||
| import uuid | ||||
| import click | ||||
| 
 | ||||
| 
 | ||||
| @manager.command | ||||
| db = models.db | ||||
| 
 | ||||
| 
 | ||||
| @click.group() | ||||
| def mailu(cls=flask_cli.FlaskGroup): | ||||
|     """ Mailu command line | ||||
|     """ | ||||
| 
 | ||||
| 
 | ||||
| @mailu.command() | ||||
| @flask_cli.with_appcontext | ||||
| def advertise(): | ||||
|     """ Advertise this server against statistic services. | ||||
|     """ | ||||
| @@ -23,7 +38,11 @@ def advertise(): | ||||
|             pass | ||||
| 
 | ||||
| 
 | ||||
| @manager.command | ||||
| @mailu.command() | ||||
| @click.argument('localpart') | ||||
| @click.argument('domain_name') | ||||
| @click.argument('password') | ||||
| @flask_cli.with_appcontext | ||||
| def admin(localpart, domain_name, password): | ||||
|     """ Create an admin user | ||||
|     """ | ||||
| @@ -41,11 +60,17 @@ def admin(localpart, domain_name, password): | ||||
|     db.session.commit() | ||||
| 
 | ||||
| 
 | ||||
| @manager.command | ||||
| def user(localpart, domain_name, password, | ||||
|          hash_scheme=app.config['PASSWORD_SCHEME']): | ||||
| @mailu.command() | ||||
| @click.argument('localpart') | ||||
| @click.argument('domain_name') | ||||
| @click.argument('password') | ||||
| @click.argument('hash_scheme') | ||||
| @flask_cli.with_appcontext | ||||
| def user(localpart, domain_name, password, hash_scheme=None): | ||||
|     """ Create a user | ||||
|     """ | ||||
|     if hash_scheme is None: | ||||
|         hash_scheme = app.config['PASSWORD_SCHEME'] | ||||
|     domain = models.Domain.query.get(domain_name) | ||||
|     if not domain: | ||||
|         domain = models.Domain(name=domain_name) | ||||
| @@ -60,10 +85,12 @@ def user(localpart, domain_name, password, | ||||
|     db.session.commit() | ||||
| 
 | ||||
| 
 | ||||
| @manager.option('-n', '--domain_name', dest='domain_name') | ||||
| @manager.option('-u', '--max_users', dest='max_users') | ||||
| @manager.option('-a', '--max_aliases', dest='max_aliases') | ||||
| @manager.option('-q', '--max_quota_bytes', dest='max_quota_bytes') | ||||
| @mailu.command() | ||||
| @click.option('-n', '--domain_name') | ||||
| @click.option('-u', '--max_users') | ||||
| @click.option('-a', '--max_aliases') | ||||
| @click.option('-q', '--max_quota_bytes') | ||||
| @flask_cli.with_appcontext | ||||
| def domain(domain_name, max_users=0, max_aliases=0, max_quota_bytes=0): | ||||
|     domain = models.Domain.query.get(domain_name) | ||||
|     if not domain: | ||||
| @@ -72,15 +99,17 @@ def domain(domain_name, max_users=0, max_aliases=0, max_quota_bytes=0): | ||||
|         db.session.commit() | ||||
| 
 | ||||
| 
 | ||||
| @manager.command | ||||
| def user_import(localpart, domain_name, password_hash, | ||||
|                 hash_scheme=app.config['PASSWORD_SCHEME']): | ||||
|     """ Import a user along with password hash. Available hashes: | ||||
|                    'SHA512-CRYPT' | ||||
|                    'SHA256-CRYPT' | ||||
|                    'MD5-CRYPT' | ||||
|                    'CRYPT' | ||||
| @mailu.command() | ||||
| @click.argument('localpart') | ||||
| @click.argument('domain_name') | ||||
| @click.argument('password_hash') | ||||
| @click.argument('hash_scheme') | ||||
| @flask_cli.with_appcontext | ||||
| def user_import(localpart, domain_name, password_hash, hash_scheme = None): | ||||
|     """ Import a user along with password hash. | ||||
|     """ | ||||
|     if hash_scheme is None: | ||||
|         hash_scheme = app.config['PASSWORD_SCHEME'] | ||||
|     domain = models.Domain.query.get(domain_name) | ||||
|     if not domain: | ||||
|         domain = models.Domain(name=domain_name) | ||||
| @@ -95,7 +124,10 @@ def user_import(localpart, domain_name, password_hash, | ||||
|     db.session.commit() | ||||
| 
 | ||||
| 
 | ||||
| @manager.command | ||||
| @mailu.command() | ||||
| @click.option('-v', '--verbose') | ||||
| @click.option('-d', '--delete_objects') | ||||
| @flask_cli.with_appcontext | ||||
| def config_update(verbose=False, delete_objects=False): | ||||
|     """sync configuration with data from YAML-formatted stdin""" | ||||
|     import yaml | ||||
| @@ -234,7 +266,9 @@ def config_update(verbose=False, delete_objects=False): | ||||
|     db.session.commit() | ||||
| 
 | ||||
| 
 | ||||
| @manager.command | ||||
| @mailu.command() | ||||
| @click.argument('email') | ||||
| @flask_cli.with_appcontext | ||||
| def user_delete(email): | ||||
|     """delete user""" | ||||
|     user = models.User.query.get(email) | ||||
| @@ -243,7 +277,9 @@ def user_delete(email): | ||||
|     db.session.commit() | ||||
| 
 | ||||
| 
 | ||||
| @manager.command | ||||
| @mailu.command() | ||||
| @click.argument('email') | ||||
| @flask_cli.with_appcontext | ||||
| def alias_delete(email): | ||||
|     """delete alias""" | ||||
|     alias = models.Alias.query.get(email) | ||||
| @@ -252,7 +288,11 @@ def alias_delete(email): | ||||
|     db.session.commit() | ||||
| 
 | ||||
| 
 | ||||
| @manager.command | ||||
| @mailu.command() | ||||
| @click.argument('localpart') | ||||
| @click.argument('domain_name') | ||||
| @click.argument('destination') | ||||
| @flask_cli.with_appcontext | ||||
| def alias(localpart, domain_name, destination): | ||||
|     """ Create an alias | ||||
|     """ | ||||
| @@ -269,24 +309,31 @@ def alias(localpart, domain_name, destination): | ||||
|     db.session.add(alias) | ||||
|     db.session.commit() | ||||
| 
 | ||||
| # Set limits to a domain | ||||
| 
 | ||||
| 
 | ||||
| @manager.command | ||||
| @mailu.command() | ||||
| @click.argument('domain_name') | ||||
| @click.argument('max_users') | ||||
| @click.argument('max_aliases') | ||||
| @click.argument('max_quota_bytes') | ||||
| @flask_cli.with_appcontext | ||||
| def setlimits(domain_name, max_users, max_aliases, max_quota_bytes): | ||||
|     """ Set domain limits | ||||
|     """ | ||||
|     domain = models.Domain.query.get(domain_name) | ||||
|     domain.max_users = max_users | ||||
|     domain.max_aliases = max_aliases | ||||
|     domain.max_quota_bytes = max_quota_bytes | ||||
| 
 | ||||
|     db.session.add(domain) | ||||
|     db.session.commit() | ||||
| 
 | ||||
| # Make the user manager of a domain | ||||
| 
 | ||||
| 
 | ||||
| @manager.command | ||||
| @mailu.command() | ||||
| @click.argument('domain_name') | ||||
| @click.argument('user_name') | ||||
| @flask_cli.with_appcontext | ||||
| def setmanager(domain_name, user_name='manager'): | ||||
|     """ Make a user manager of a domain | ||||
|     """ | ||||
|     domain = models.Domain.query.get(domain_name) | ||||
|     manageruser = models.User.query.get(user_name + '@' + domain_name) | ||||
|     domain.managers.append(manageruser) | ||||
| @@ -294,5 +341,5 @@ def setmanager(domain_name, user_name='manager'): | ||||
|     db.session.commit() | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     manager.run() | ||||
| if __name__ == '__main__': | ||||
|     cli() | ||||
| @@ -1,10 +1,12 @@ | ||||
| from mailu import app, db, dkim, login_manager | ||||
| from mailu import dkim | ||||
|  | ||||
| from sqlalchemy.ext import declarative | ||||
| from passlib import context, hash | ||||
| from datetime import datetime, date | ||||
| from email.mime import text | ||||
| from flask import current_app as app | ||||
|  | ||||
| import flask_sqlalchemy | ||||
| import sqlalchemy | ||||
| import re | ||||
| import time | ||||
| @@ -15,6 +17,9 @@ import idna | ||||
| import dns | ||||
|  | ||||
|  | ||||
| db = flask_sqlalchemy.SQLAlchemy() | ||||
|  | ||||
|  | ||||
| class IdnaDomain(db.TypeDecorator): | ||||
|     """ Stores a Unicode string in it's IDNA representation (ASCII only) | ||||
|     """ | ||||
| @@ -67,7 +72,28 @@ class CommaSeparatedList(db.TypeDecorator): | ||||
|         return ",".join(value) | ||||
|  | ||||
|     def process_result_value(self, value, dialect): | ||||
|         return filter(bool, value.split(",")) | ||||
|         return filter(bool, value.split(",")) if value else [] | ||||
|  | ||||
|  | ||||
| class JSONEncoded(db.TypeDecorator): | ||||
|     """Represents an immutable structure as a json-encoded string. | ||||
|     """ | ||||
|  | ||||
|     impl = db.String | ||||
|  | ||||
|     def process_bind_param(self, value, dialect): | ||||
|         return json.dumps(value) if value else None | ||||
|  | ||||
|     def process_result_value(self, value, dialect): | ||||
|         return json.loads(value) if value else None | ||||
|  | ||||
|  | ||||
| class Config(db.Model): | ||||
|     """ In-database configuration values | ||||
|     """ | ||||
|  | ||||
|     name = db.Column(db.String(255), primary_key=True, nullable=False) | ||||
|     value = db.Column(JSONEncoded) | ||||
|  | ||||
|  | ||||
| # Many-to-many association table for domain managers | ||||
| @@ -224,6 +250,28 @@ class Email(object): | ||||
|             msg['To'] = to_address | ||||
|             smtp.sendmail(from_address, [to_address], msg.as_string()) | ||||
|  | ||||
|     @classmethod | ||||
|     def resolve_domain(cls, email): | ||||
|         localpart, domain_name = email.split('@', 1) if '@' in email else (None, email) | ||||
|         alternative = Alternative.query.get(domain_name) | ||||
|         if alternative: | ||||
|             domain_name = alternative.domain_name | ||||
|         return (localpart, domain_name) | ||||
|  | ||||
|     @classmethod | ||||
|     def resolve_destination(cls, localpart, domain_name, ignore_forward_keep=False): | ||||
|         alias = Alias.resolve(localpart, domain_name) | ||||
|         if alias: | ||||
|             return alias.destination | ||||
|         user = User.query.get('{}@{}'.format(localpart, domain_name)) | ||||
|         if user: | ||||
|             if user.forward_enabled: | ||||
|                 destination = user.forward_destination | ||||
|                 if user.forward_keep or ignore_forward_keep: | ||||
|                     destination.append(user.email) | ||||
|             else: | ||||
|                 destination = [user.email] | ||||
|             return destination | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.email | ||||
| @@ -248,7 +296,7 @@ class User(Base, Email): | ||||
|  | ||||
|     # Filters | ||||
|     forward_enabled = db.Column(db.Boolean(), nullable=False, default=False) | ||||
|     forward_destination = db.Column(db.String(255), nullable=True, default=None) | ||||
|     forward_destination = db.Column(CommaSeparatedList(), nullable=True, default=[]) | ||||
|     forward_keep = db.Column(db.Boolean(), nullable=False, default=True) | ||||
|     reply_enabled = db.Column(db.Boolean(), nullable=False, default=False) | ||||
|     reply_subject = db.Column(db.String(255), nullable=True, default=None) | ||||
| @@ -296,13 +344,15 @@ class User(Base, Email): | ||||
|                    'SHA256-CRYPT': "sha256_crypt", | ||||
|                    'MD5-CRYPT': "md5_crypt", | ||||
|                    'CRYPT': "des_crypt"} | ||||
|     pw_context = context.CryptContext( | ||||
|         schemes = scheme_dict.values(), | ||||
|         default=scheme_dict[app.config['PASSWORD_SCHEME']], | ||||
|     ) | ||||
|  | ||||
|     def get_password_context(self): | ||||
|         return context.CryptContext( | ||||
|             schemes=self.scheme_dict.values(), | ||||
|             default=self.scheme_dict[app.config['PASSWORD_SCHEME']], | ||||
|         ) | ||||
|  | ||||
|     def check_password(self, password): | ||||
|         context = User.pw_context | ||||
|         context = self.get_password_context() | ||||
|         reference = re.match('({[^}]+})?(.*)', self.password).group(2) | ||||
|         result = context.verify(password, reference) | ||||
|         if result and context.identify(reference) != context.default_scheme(): | ||||
| @@ -311,15 +361,17 @@ class User(Base, Email): | ||||
|             db.session.commit() | ||||
|         return result | ||||
|  | ||||
|     def set_password(self, password, hash_scheme=app.config['PASSWORD_SCHEME'], raw=False): | ||||
|     def set_password(self, password, hash_scheme=None, raw=False): | ||||
|         """Set password for user with specified encryption scheme | ||||
|            @password: plain text password to encrypt (if raw == True the hash itself) | ||||
|         """ | ||||
|         if hash_scheme is None: | ||||
|             hash_scheme = app.config['PASSWORD_SCHEME'] | ||||
|         # for the list of hash schemes see https://wiki2.dovecot.org/Authentication/PasswordSchemes | ||||
|         if raw: | ||||
|             self.password = '{'+hash_scheme+'}' + password | ||||
|         else: | ||||
|             self.password = '{'+hash_scheme+'}' + User.pw_context.encrypt(password, self.scheme_dict[hash_scheme]) | ||||
|             self.password = '{'+hash_scheme+'}' + self.get_password_context().encrypt(password, self.scheme_dict[hash_scheme]) | ||||
|  | ||||
|     def get_managed_domains(self): | ||||
|         if self.global_admin: | ||||
| @@ -340,13 +392,15 @@ class User(Base, Email): | ||||
|             self.sendmail(app.config["WELCOME_SUBJECT"], | ||||
|                 app.config["WELCOME_BODY"]) | ||||
|  | ||||
|     @classmethod | ||||
|     def get(cls, email): | ||||
|         return cls.query.get(email) | ||||
|  | ||||
|     @classmethod | ||||
|     def login(cls, email, password): | ||||
|         user = cls.query.get(email) | ||||
|         return user if (user and user.enabled and user.check_password(password)) else None | ||||
|  | ||||
| login_manager.user_loader(User.query.get) | ||||
|  | ||||
|  | ||||
| class Alias(Base, Email): | ||||
|     """ An alias is an email address that redirects to some destination. | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| from mailu import db, models | ||||
| from mailu import models | ||||
| from mailu.ui import forms | ||||
|  | ||||
| import flask | ||||
|   | ||||
| @@ -90,9 +90,10 @@ class UserSignupForm(flask_wtf.FlaskForm): | ||||
|     localpart = fields.StringField(_('Email address'), [validators.DataRequired(), validators.Regexp(LOCALPART_REGEX)]) | ||||
|     pw = fields.PasswordField(_('Password'), [validators.DataRequired()]) | ||||
|     pw2 = fields.PasswordField(_('Confirm password'), [validators.EqualTo('pw')]) | ||||
|     captcha = flask_wtf.RecaptchaField() | ||||
|     submit = fields.SubmitField(_('Sign up')) | ||||
|  | ||||
| class UserSignupFormCaptcha(UserSignupForm): | ||||
|     captcha = flask_wtf.RecaptchaField() | ||||
|  | ||||
| class UserSettingsForm(flask_wtf.FlaskForm): | ||||
|     displayed_name = fields.StringField(_('Displayed name')) | ||||
|   | ||||
| @@ -14,7 +14,9 @@ | ||||
|   {% call macros.box() %} | ||||
|   {{ macros.form_field(form.localpart, append='<span class="input-group-addon">@'+domain.name+'</span>') }} | ||||
|   {{ macros.form_fields((form.pw, form.pw2)) }} | ||||
|   {{ macros.form_field(form.captcha) }} | ||||
|   {% if form.captcha %} | ||||
|     {{ macros.form_field(form.captcha) }} | ||||
|   {% endif %} | ||||
|   {{ macros.form_field(form.submit) }} | ||||
|   {% endcall %} | ||||
| </form> | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| from mailu import db, models | ||||
| from mailu import models | ||||
| from mailu.ui import ui, forms, access | ||||
|  | ||||
| import flask | ||||
| @@ -25,7 +25,7 @@ def admin_create(): | ||||
|         user = models.User.query.get(form.admin.data) | ||||
|         if user: | ||||
|             user.global_admin = True | ||||
|             db.session.commit() | ||||
|             models.db.session.commit() | ||||
|             flask.flash('User %s is now admin' % user) | ||||
|             return flask.redirect(flask.url_for('.admin_list')) | ||||
|         else: | ||||
| @@ -40,7 +40,7 @@ def admin_delete(admin): | ||||
|     user = models.User.query.get(admin) | ||||
|     if user: | ||||
|         user.global_admin  = False | ||||
|         db.session.commit() | ||||
|         models.db.session.commit() | ||||
|         flask.flash('User %s is no longer admin' % user) | ||||
|         return flask.redirect(flask.url_for('.admin_list')) | ||||
|     else: | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| from mailu import db, models | ||||
| from mailu import models | ||||
| from mailu.ui import ui, forms, access | ||||
|  | ||||
| import flask | ||||
| @@ -27,8 +27,8 @@ def alias_create(domain_name): | ||||
|         else: | ||||
|             alias = models.Alias(domain=domain) | ||||
|             form.populate_obj(alias) | ||||
|             db.session.add(alias) | ||||
|             db.session.commit() | ||||
|             models.db.session.add(alias) | ||||
|             models.db.session.commit() | ||||
|             flask.flash('Alias %s created' % alias) | ||||
|             return flask.redirect( | ||||
|                 flask.url_for('.alias_list', domain_name=domain.name)) | ||||
| @@ -45,7 +45,7 @@ def alias_edit(alias): | ||||
|     form.localpart.validators = [] | ||||
|     if form.validate_on_submit(): | ||||
|         form.populate_obj(alias) | ||||
|         db.session.commit() | ||||
|         models.db.session.commit() | ||||
|         flask.flash('Alias %s updated' % alias) | ||||
|         return flask.redirect( | ||||
|             flask.url_for('.alias_list', domain_name=alias.domain.name)) | ||||
| @@ -59,8 +59,8 @@ def alias_edit(alias): | ||||
| def alias_delete(alias): | ||||
|     alias = models.Alias.query.get(alias) or flask.abort(404) | ||||
|     domain = alias.domain | ||||
|     db.session.delete(alias) | ||||
|     db.session.commit() | ||||
|     models.db.session.delete(alias) | ||||
|     models.db.session.commit() | ||||
|     flask.flash('Alias %s deleted' % alias) | ||||
|     return flask.redirect( | ||||
|         flask.url_for('.alias_list', domain_name=domain.name)) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| from mailu import db, models | ||||
| from mailu import models | ||||
| from mailu.ui import ui, forms, access | ||||
|  | ||||
| import flask | ||||
| @@ -26,8 +26,8 @@ def alternative_create(domain_name): | ||||
|         else: | ||||
|             alternative = models.Alternative(domain=domain) | ||||
|             form.populate_obj(alternative) | ||||
|             db.session.add(alternative) | ||||
|             db.session.commit() | ||||
|             models.db.session.add(alternative) | ||||
|             models.db.session.commit() | ||||
|             flask.flash('Alternative domain %s created' % alternative) | ||||
|             return flask.redirect( | ||||
|                 flask.url_for('.alternative_list', domain_name=domain.name)) | ||||
| @@ -41,8 +41,8 @@ def alternative_create(domain_name): | ||||
| def alternative_delete(alternative): | ||||
|     alternative = models.Alternative.query.get(alternative) or flask.abort(404) | ||||
|     domain = alternative.domain | ||||
|     db.session.delete(alternative) | ||||
|     db.session.commit() | ||||
|     models.db.session.delete(alternative) | ||||
|     models.db.session.commit() | ||||
|     flask.flash('Alternative %s deleted' % alternative) | ||||
|     return flask.redirect( | ||||
|         flask.url_for('.alternative_list', domain_name=domain.name)) | ||||
|   | ||||
| @@ -1,11 +1,9 @@ | ||||
| from mailu import dockercli, app, db, models | ||||
| from mailu import models | ||||
| from mailu.ui import ui, forms, access | ||||
|  | ||||
| import flask | ||||
| import flask_login | ||||
|  | ||||
| from urllib import parse | ||||
|  | ||||
|  | ||||
| @ui.route('/', methods=["GET"]) | ||||
| @access.authenticated | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| from mailu import app, db, models | ||||
| from mailu import models | ||||
| from mailu.ui import ui, forms, access | ||||
| from flask import current_app as app | ||||
|  | ||||
| import flask | ||||
| import flask_login | ||||
| @@ -26,8 +27,8 @@ def domain_create(): | ||||
|         else: | ||||
|             domain = models.Domain() | ||||
|             form.populate_obj(domain) | ||||
|             db.session.add(domain) | ||||
|             db.session.commit() | ||||
|             models.db.session.add(domain) | ||||
|             models.db.session.commit() | ||||
|             flask.flash('Domain %s created' % domain) | ||||
|             return flask.redirect(flask.url_for('.domain_list')) | ||||
|     return flask.render_template('domain/create.html', form=form) | ||||
| @@ -42,7 +43,7 @@ def domain_edit(domain_name): | ||||
|     form.name.validators = [] | ||||
|     if form.validate_on_submit(): | ||||
|         form.populate_obj(domain) | ||||
|         db.session.commit() | ||||
|         models.db.session.commit() | ||||
|         flask.flash('Domain %s saved' % domain) | ||||
|         return flask.redirect(flask.url_for('.domain_list')) | ||||
|     return flask.render_template('domain/edit.html', form=form, | ||||
| @@ -54,8 +55,8 @@ def domain_edit(domain_name): | ||||
| @access.confirmation_required("delete {domain_name}") | ||||
| def domain_delete(domain_name): | ||||
|     domain = models.Domain.query.get(domain_name) or flask.abort(404) | ||||
|     db.session.delete(domain) | ||||
|     db.session.commit() | ||||
|     models.db.session.delete(domain) | ||||
|     models.db.session.commit() | ||||
|     flask.flash('Domain %s deleted' % domain) | ||||
|     return flask.redirect(flask.url_for('.domain_list')) | ||||
|  | ||||
| @@ -99,7 +100,7 @@ def domain_signup(domain_name=None): | ||||
|             domain.max_users = 10 | ||||
|             domain.max_aliases = 10 | ||||
|             if domain.check_mx(): | ||||
|                 db.session.add(domain) | ||||
|                 models.db.session.add(domain) | ||||
|                 if flask_login.current_user.is_authenticated: | ||||
|                     user = models.User.query.get(flask_login.current_user.email) | ||||
|                 else: | ||||
| @@ -108,9 +109,9 @@ def domain_signup(domain_name=None): | ||||
|                     form.populate_obj(user) | ||||
|                     user.set_password(form.pw.data) | ||||
|                     user.quota_bytes = domain.max_quota_bytes | ||||
|                 db.session.add(user) | ||||
|                 models.db.session.add(user) | ||||
|                 domain.managers.append(user) | ||||
|                 db.session.commit() | ||||
|                 models.db.session.commit() | ||||
|                 flask.flash('Domain %s created' % domain) | ||||
|                 return flask.redirect(flask.url_for('.domain_list')) | ||||
|             else: | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| from mailu import db, models | ||||
| from mailu import models | ||||
| from mailu.ui import ui, forms, access | ||||
|  | ||||
| import flask | ||||
| @@ -24,8 +24,8 @@ def fetch_create(user_email): | ||||
|     if form.validate_on_submit(): | ||||
|         fetch = models.Fetch(user=user) | ||||
|         form.populate_obj(fetch) | ||||
|         db.session.add(fetch) | ||||
|         db.session.commit() | ||||
|         models.db.session.add(fetch) | ||||
|         models.db.session.commit() | ||||
|         flask.flash('Fetch configuration created') | ||||
|         return flask.redirect( | ||||
|             flask.url_for('.fetch_list', user_email=user.email)) | ||||
| @@ -39,7 +39,7 @@ def fetch_edit(fetch_id): | ||||
|     form = forms.FetchForm(obj=fetch) | ||||
|     if form.validate_on_submit(): | ||||
|         form.populate_obj(fetch) | ||||
|         db.session.commit() | ||||
|         models.db.session.commit() | ||||
|         flask.flash('Fetch configuration updated') | ||||
|         return flask.redirect( | ||||
|             flask.url_for('.fetch_list', user_email=fetch.user.email)) | ||||
| @@ -53,8 +53,8 @@ def fetch_edit(fetch_id): | ||||
| def fetch_delete(fetch_id): | ||||
|     fetch = models.Fetch.query.get(fetch_id) or flask.abort(404) | ||||
|     user = fetch.user | ||||
|     db.session.delete(fetch) | ||||
|     db.session.commit() | ||||
|     models.db.session.delete(fetch) | ||||
|     models.db.session.commit() | ||||
|     flask.flash('Fetch configuration delete') | ||||
|     return flask.redirect( | ||||
|         flask.url_for('.fetch_list', user_email=user.email)) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| from mailu import db, models | ||||
| from mailu import models | ||||
| from mailu.ui import ui, forms, access | ||||
|  | ||||
| import flask | ||||
| @@ -30,7 +30,7 @@ def manager_create(domain_name): | ||||
|             flask.flash('User %s is already manager' % user, 'error') | ||||
|         else: | ||||
|             domain.managers.append(user) | ||||
|             db.session.commit() | ||||
|             models.db.session.commit() | ||||
|             flask.flash('User %s can now manage %s' % (user, domain.name)) | ||||
|             return flask.redirect( | ||||
|                 flask.url_for('.manager_list', domain_name=domain.name)) | ||||
| @@ -46,7 +46,7 @@ def manager_delete(domain_name, user_email): | ||||
|     user = models.User.query.get(user_email) or flask.abort(404) | ||||
|     if user in domain.managers: | ||||
|         domain.managers.remove(user) | ||||
|         db.session.commit() | ||||
|         models.db.session.commit() | ||||
|         flask.flash('User %s can no longer manager %s' % (user, domain)) | ||||
|     else: | ||||
|         flask.flash('User %s is not manager' % user, 'error') | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| from mailu import db, models | ||||
| from mailu import models | ||||
| from mailu.ui import ui, forms, access | ||||
|  | ||||
| import flask | ||||
| @@ -25,8 +25,8 @@ def relay_create(): | ||||
|         else: | ||||
|             relay = models.Relay() | ||||
|             form.populate_obj(relay) | ||||
|             db.session.add(relay) | ||||
|             db.session.commit() | ||||
|             models.db.session.add(relay) | ||||
|             models.db.session.commit() | ||||
|             flask.flash('Relayed domain %s created' % relay) | ||||
|             return flask.redirect(flask.url_for('.relay_list')) | ||||
|     return flask.render_template('relay/create.html', form=form) | ||||
| @@ -41,7 +41,7 @@ def relay_edit(relay_name): | ||||
|     form.name.validators = [] | ||||
|     if form.validate_on_submit(): | ||||
|         form.populate_obj(relay) | ||||
|         db.session.commit() | ||||
|         models.db.session.commit() | ||||
|         flask.flash('Relayed domain %s saved' % relay) | ||||
|         return flask.redirect(flask.url_for('.relay_list')) | ||||
|     return flask.render_template('relay/edit.html', form=form, | ||||
| @@ -53,8 +53,8 @@ def relay_edit(relay_name): | ||||
| @access.confirmation_required("delete {relay_name}") | ||||
| def relay_delete(relay_name): | ||||
|     relay = models.Relay.query.get(relay_name) or flask.abort(404) | ||||
|     db.session.delete(relay) | ||||
|     db.session.commit() | ||||
|     models.db.session.delete(relay) | ||||
|     models.db.session.commit() | ||||
|     flask.flash('Relayed domain %s deleted' % relay) | ||||
|     return flask.redirect(flask.url_for('.relay_list')) | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| from mailu import db, models | ||||
| from mailu import models | ||||
| from mailu.ui import ui, forms, access | ||||
|  | ||||
| from passlib import pwd | ||||
| @@ -32,8 +32,8 @@ def token_create(user_email): | ||||
|         token = models.Token(user=user) | ||||
|         token.set_password(form.raw_password.data) | ||||
|         form.populate_obj(token) | ||||
|         db.session.add(token) | ||||
|         db.session.commit() | ||||
|         models.db.session.add(token) | ||||
|         models.db.session.commit() | ||||
|         flask.flash('Authentication token created') | ||||
|         return flask.redirect( | ||||
|             flask.url_for('.token_list', user_email=user.email)) | ||||
| @@ -46,8 +46,8 @@ def token_create(user_email): | ||||
| def token_delete(token_id): | ||||
|     token = models.Token.query.get(token_id) or flask.abort(404) | ||||
|     user = token.user | ||||
|     db.session.delete(token) | ||||
|     db.session.commit() | ||||
|     models.db.session.delete(token) | ||||
|     models.db.session.commit() | ||||
|     flask.flash('Authentication token deleted') | ||||
|     return flask.redirect( | ||||
|         flask.url_for('.token_list', user_email=user.email)) | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| from mailu import db, models, app | ||||
| from mailu import models | ||||
| from mailu.ui import ui, access, forms | ||||
| from flask import current_app as app | ||||
|  | ||||
| import flask | ||||
| import flask_login | ||||
| @@ -33,8 +34,8 @@ def user_create(domain_name): | ||||
|             user = models.User(domain=domain) | ||||
|             form.populate_obj(user) | ||||
|             user.set_password(form.pw.data) | ||||
|             db.session.add(user) | ||||
|             db.session.commit() | ||||
|             models.db.session.add(user) | ||||
|             models.db.session.commit() | ||||
|             user.send_welcome() | ||||
|             flask.flash('User %s created' % user) | ||||
|             return flask.redirect( | ||||
| @@ -63,7 +64,7 @@ def user_edit(user_email): | ||||
|         form.populate_obj(user) | ||||
|         if form.pw.data: | ||||
|             user.set_password(form.pw.data) | ||||
|         db.session.commit() | ||||
|         models.db.session.commit() | ||||
|         flask.flash('User %s updated' % user) | ||||
|         return flask.redirect( | ||||
|             flask.url_for('.user_list', domain_name=user.domain.name)) | ||||
| @@ -77,8 +78,8 @@ def user_edit(user_email): | ||||
| def user_delete(user_email): | ||||
|     user = models.User.query.get(user_email) or flask.abort(404) | ||||
|     domain = user.domain | ||||
|     db.session.delete(user) | ||||
|     db.session.commit() | ||||
|     models.db.session.delete(user) | ||||
|     models.db.session.commit() | ||||
|     flask.flash('User %s deleted' % user) | ||||
|     return flask.redirect( | ||||
|         flask.url_for('.user_list', domain_name=domain.name)) | ||||
| @@ -93,7 +94,7 @@ def user_settings(user_email): | ||||
|     form = forms.UserSettingsForm(obj=user) | ||||
|     if form.validate_on_submit(): | ||||
|         form.populate_obj(user) | ||||
|         db.session.commit() | ||||
|         models.db.session.commit() | ||||
|         flask.flash('Settings updated for %s' % user) | ||||
|         if user_email: | ||||
|             return flask.redirect( | ||||
| @@ -113,7 +114,7 @@ def user_password(user_email): | ||||
|             flask.flash('Passwords do not match', 'error') | ||||
|         else: | ||||
|             user.set_password(form.pw.data) | ||||
|             db.session.commit() | ||||
|             models.db.session.commit() | ||||
|             flask.flash('Password updated for %s' % user) | ||||
|             if user_email: | ||||
|                 return flask.redirect(flask.url_for('.user_list', | ||||
| @@ -130,7 +131,7 @@ def user_forward(user_email): | ||||
|     form = forms.UserForwardForm(obj=user) | ||||
|     if form.validate_on_submit(): | ||||
|         form.populate_obj(user) | ||||
|         db.session.commit() | ||||
|         models.db.session.commit() | ||||
|         flask.flash('Forward destination updated for %s' % user) | ||||
|         if user_email: | ||||
|             return flask.redirect( | ||||
| @@ -147,7 +148,7 @@ def user_reply(user_email): | ||||
|     form = forms.UserReplyForm(obj=user) | ||||
|     if form.validate_on_submit(): | ||||
|         form.populate_obj(user) | ||||
|         db.session.commit() | ||||
|         models.db.session.commit() | ||||
|         flask.flash('Auto-reply message updated for %s' % user) | ||||
|         if user_email: | ||||
|             return flask.redirect( | ||||
| @@ -170,7 +171,11 @@ def user_signup(domain_name=None): | ||||
|             available_domains=available_domains) | ||||
|     domain = available_domains.get(domain_name) or flask.abort(404) | ||||
|     quota_bytes = domain.max_quota_bytes or app.config['DEFAULT_QUOTA'] | ||||
|     form = forms.UserSignupForm() | ||||
|     if app.config['RECAPTCHA_PUBLIC_KEY'] == "" or app.config['RECAPTCHA_PRIVATE_KEY'] == "": | ||||
|         form = forms.UserSignupForm() | ||||
|     else: | ||||
|         form = forms.UserSignupFormCaptcha() | ||||
|  | ||||
|     if form.validate_on_submit(): | ||||
|         if domain.has_email(form.localpart.data): | ||||
|             flask.flash('Email is already used', 'error') | ||||
| @@ -179,8 +184,8 @@ def user_signup(domain_name=None): | ||||
|             form.populate_obj(user) | ||||
|             user.set_password(form.pw.data) | ||||
|             user.quota_bytes = quota_bytes | ||||
|             db.session.add(user) | ||||
|             db.session.commit() | ||||
|             models.db.session.add(user) | ||||
|             models.db.session.commit() | ||||
|             user.send_welcome() | ||||
|             flask.flash('Successfully signed up %s' % user) | ||||
|             return flask.redirect(flask.url_for('.index')) | ||||
|   | ||||
							
								
								
									
										53
									
								
								core/admin/mailu/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								core/admin/mailu/utils.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| from mailu import models | ||||
|  | ||||
| import flask | ||||
| import flask_login | ||||
| import flask_script | ||||
| import flask_migrate | ||||
| import flask_babel | ||||
| import flask_limiter | ||||
|  | ||||
| from werkzeug.contrib import fixers | ||||
|  | ||||
|  | ||||
| # Login configuration | ||||
| login = flask_login.LoginManager() | ||||
| login.login_view = "ui.login" | ||||
|  | ||||
| @login.unauthorized_handler | ||||
| def handle_needs_login(): | ||||
|     return flask.redirect( | ||||
|         flask.url_for('ui.login', next=flask.request.endpoint) | ||||
|     ) | ||||
|  | ||||
|  | ||||
| # Request rate limitation | ||||
| limiter = flask_limiter.Limiter(key_func=lambda: current_user.username) | ||||
|  | ||||
|  | ||||
| # Application translation | ||||
| babel = flask_babel.Babel() | ||||
|  | ||||
| @babel.localeselector | ||||
| def get_locale(): | ||||
|     translations = list(map(str, babel.list_translations())) | ||||
|     return flask.request.accept_languages.best_match(translations) | ||||
|  | ||||
|  | ||||
| # Proxy fixer | ||||
| class PrefixMiddleware(object): | ||||
|     def __call__(self, environ, start_response): | ||||
|         prefix = environ.get('HTTP_X_FORWARDED_PREFIX', '') | ||||
|         if prefix: | ||||
|             environ['SCRIPT_NAME'] = prefix | ||||
|         return self.app(environ, start_response) | ||||
|  | ||||
|     def init_app(self, app): | ||||
|         self.app = fixers.ProxyFix(app.wsgi_app) | ||||
|         app.wsgi_app = self | ||||
|  | ||||
| proxy = PrefixMiddleware() | ||||
|  | ||||
|  | ||||
| # Data migrate | ||||
| migrate = flask_migrate.Migrate() | ||||
| @@ -13,8 +13,6 @@ down_revision = '2335c80a6bc3' | ||||
| from alembic import op | ||||
| import sqlalchemy as sa | ||||
|  | ||||
| from mailu import app | ||||
|  | ||||
|  | ||||
| fetch_table = sa.Table( | ||||
|     'fetch', | ||||
| @@ -24,13 +22,7 @@ fetch_table = sa.Table( | ||||
|  | ||||
|  | ||||
| def upgrade(): | ||||
|     connection = op.get_bind() | ||||
|     op.add_column('fetch', sa.Column('keep', sa.Boolean(), nullable=False, server_default=sa.sql.expression.false())) | ||||
|     # also apply the current config value if set | ||||
|     if app.config.get("FETCHMAIL_KEEP", "False") == "True": | ||||
|         connection.execute( | ||||
|             fetch_table.update().values(keep=True) | ||||
|         ) | ||||
|  | ||||
|  | ||||
| def downgrade(): | ||||
|   | ||||
							
								
								
									
										25
									
								
								core/admin/migrations/versions/cd79ed46d9c2_.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								core/admin/migrations/versions/cd79ed46d9c2_.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| """ Add a configuration table | ||||
|  | ||||
| Revision ID: cd79ed46d9c2 | ||||
| Revises: 25fd6c7bcb4a | ||||
| Create Date: 2018-10-17 21:44:48.924921 | ||||
|  | ||||
| """ | ||||
|  | ||||
| revision = 'cd79ed46d9c2' | ||||
| down_revision = '3b281286c7bd' | ||||
|  | ||||
| from alembic import op | ||||
| import sqlalchemy as sa | ||||
|  | ||||
|  | ||||
| def upgrade(): | ||||
|     op.create_table('config', | ||||
|     sa.Column('name', sa.String(length=255), nullable=False), | ||||
|     sa.Column('value', sa.String(length=255), nullable=True), | ||||
|     sa.PrimaryKeyConstraint('name') | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def downgrade(): | ||||
|     op.drop_table('config') | ||||
| @@ -1,53 +1,46 @@ | ||||
| alembic==0.9.9 | ||||
| alembic==1.0.2 | ||||
| asn1crypto==0.24.0 | ||||
| Babel==2.5.3 | ||||
| Babel==2.6.0 | ||||
| bcrypt==3.1.4 | ||||
| blinker==1.4 | ||||
| certifi==2018.4.16 | ||||
| cffi==1.11.5 | ||||
| chardet==3.0.4 | ||||
| click==6.7 | ||||
| cryptography==2.2.2 | ||||
| Click==7.0 | ||||
| cryptography==2.3.1 | ||||
| decorator==4.3.0 | ||||
| dnspython==1.15.0 | ||||
| docker-py==1.10.6 | ||||
| docker-pycreds==0.2.2 | ||||
| dominate==2.3.1 | ||||
| Flask==0.12.2 | ||||
| Flask-Babel==0.11.2 | ||||
| dominate==2.3.4 | ||||
| Flask==1.0.2 | ||||
| Flask-Babel==0.12.2 | ||||
| Flask-Bootstrap==3.3.7.1 | ||||
| Flask-DebugToolbar==0.10.1 | ||||
| Flask-Limiter==1.0.1 | ||||
| Flask-Login==0.4.1 | ||||
| Flask-Migrate==2.1.1 | ||||
| Flask-Migrate==2.3.0 | ||||
| Flask-Script==2.0.6 | ||||
| Flask-SQLAlchemy==2.3.2 | ||||
| Flask-WTF==0.14.2 | ||||
| gunicorn==19.7.1 | ||||
| idna==2.6 | ||||
| gunicorn==19.9.0 | ||||
| idna==2.7 | ||||
| infinity==1.4 | ||||
| intervals==0.8.1 | ||||
| itsdangerous==0.24 | ||||
| itsdangerous==1.1.0 | ||||
| Jinja2==2.10 | ||||
| limits==1.3 | ||||
| Mako==1.0.7 | ||||
| MarkupSafe==1.0 | ||||
| MarkupSafe==1.1.0 | ||||
| passlib==1.7.1 | ||||
| pycparser==2.18 | ||||
| pyOpenSSL==17.5.0 | ||||
| python-dateutil==2.7.2 | ||||
| pycparser==2.19 | ||||
| pyOpenSSL==18.0.0 | ||||
| python-dateutil==2.7.5 | ||||
| python-editor==1.0.3 | ||||
| pytz==2018.4 | ||||
| PyYAML==3.12 | ||||
| pytz==2018.7 | ||||
| PyYAML==3.13 | ||||
| redis==2.10.6 | ||||
| requests==2.18.4 | ||||
| six==1.11.0 | ||||
| SQLAlchemy==1.2.6 | ||||
| SQLAlchemy==1.2.13 | ||||
| tabulate==0.8.2 | ||||
| urllib3==1.22 | ||||
| validators==0.12.1 | ||||
| validators==0.12.2 | ||||
| visitor==0.1.3 | ||||
| websocket-client==0.47.0 | ||||
| Werkzeug==0.14.1 | ||||
| WTForms==2.1 | ||||
| WTForms==2.2.1 | ||||
| WTForms-Components==0.10.3 | ||||
|   | ||||
| @@ -12,7 +12,6 @@ redis | ||||
| WTForms-Components | ||||
| passlib | ||||
| gunicorn | ||||
| docker-py | ||||
| tabulate | ||||
| PyYAML | ||||
| PyOpenSSL | ||||
|   | ||||
| @@ -1,7 +0,0 @@ | ||||
| import os | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     os.environ["DEBUG"] = "True" | ||||
|     from mailu import app | ||||
|     app.run() | ||||
| @@ -2,6 +2,6 @@ | ||||
|  | ||||
| import os | ||||
|  | ||||
| os.system("python3 manage.py advertise") | ||||
| os.system("python3 manage.py db upgrade") | ||||
| os.system("gunicorn -w 4 -b :80 --access-logfile - --error-logfile - --preload mailu:app") | ||||
| os.system("flask mailu advertise") | ||||
| os.system("flask db upgrade") | ||||
| os.system("gunicorn -w 4 -b :80 --access-logfile - --error-logfile - --preload 'mailu:create_app()'") | ||||
|   | ||||
| @@ -250,7 +250,7 @@ mail { | ||||
|       listen 465 ssl; | ||||
|       listen [::]:465 ssl; | ||||
|       protocol smtp; | ||||
|       smtp_auth plain; | ||||
|       smtp_auth plain login; | ||||
|     } | ||||
|  | ||||
|     server { | ||||
|   | ||||
| @@ -78,14 +78,14 @@ lmtp_host_lookup = native | ||||
| smtpd_delay_reject = yes | ||||
|  | ||||
| # Allowed senders are: the user or one of the alias destinations | ||||
| smtpd_sender_login_maps = $virtual_alias_maps | ||||
| smtpd_sender_login_maps = ${podop}senderlogin | ||||
|  | ||||
| # Restrictions for incoming SMTP, other restrictions are applied in master.cf | ||||
| smtpd_helo_required = yes | ||||
|  | ||||
| smtpd_client_restrictions = | ||||
|   permit_mynetworks, | ||||
|   check_sender_access ${podop}sender, | ||||
|   check_sender_access ${podop}senderaccess, | ||||
|   reject_non_fqdn_sender, | ||||
|   reject_unknown_sender_domain, | ||||
|   reject_unknown_recipient_domain, | ||||
|   | ||||
| @@ -19,7 +19,8 @@ def start_podop(): | ||||
| 		("alias", "url", "http://admin/internal/postfix/alias/§"), | ||||
| 		("domain", "url", "http://admin/internal/postfix/domain/§"), | ||||
|         ("mailbox", "url", "http://admin/internal/postfix/mailbox/§"), | ||||
|         ("sender", "url", "http://admin/internal/postfix/sender/§") | ||||
|         ("senderaccess", "url", "http://admin/internal/postfix/sender/access/§"), | ||||
|         ("senderlogin", "url", "http://admin/internal/postfix/sender/login/§") | ||||
|     ]) | ||||
|  | ||||
| convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) | ||||
|   | ||||
							
								
								
									
										12
									
								
								docs/cli.rst
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								docs/cli.rst
									
									
									
									
									
								
							| @@ -15,7 +15,7 @@ alias | ||||
|  | ||||
| .. code-block:: bash | ||||
|  | ||||
|   docker-compose run --rm admin python manage.py alias foo example.net "mail1@example.com,mail2@example.com" | ||||
|   docker-compose exec admin flask mailu alias foo example.net "mail1@example.com,mail2@example.com" | ||||
|  | ||||
|  | ||||
| alias_delete | ||||
| @@ -23,14 +23,14 @@ alias_delete | ||||
|  | ||||
| .. code-block:: bash | ||||
|  | ||||
|   docker-compose run --rm admin python manage.py alias_delete foo@example.net | ||||
|   docker-compose exec admin flask mailu alias_delete foo@example.net | ||||
|  | ||||
| user | ||||
| ---- | ||||
|  | ||||
| .. code-block:: bash | ||||
|  | ||||
|   docker-compose run --rm admin python manage.py user --hash_scheme='SHA512-CRYPT' myuser example.net 'password123' | ||||
|   docker-compose exec admin flask mailu user --hash_scheme='SHA512-CRYPT' myuser example.net 'password123' | ||||
|  | ||||
| user_import | ||||
| ----------- | ||||
| @@ -39,14 +39,14 @@ primary difference with simple `user` command is that password is being imported | ||||
|  | ||||
| .. code-block:: bash | ||||
|  | ||||
|   docker-compose run --rm admin python manage.py user_import --hash_scheme='SHA512-CRYPT' myuser example.net '$6$51ebe0cb9f1dab48effa2a0ad8660cb489b445936b9ffd812a0b8f46bca66dd549fea530ce' | ||||
|   docker-compose run --rm admin python manage.py user --hash_scheme='SHA512-CRYPT' myuser example.net '$6$51ebe0cb9f1dab48effa2a0ad8660cb489b445936b9ffd812a0b8f46bca66dd549fea530ce' | ||||
|  | ||||
| user_delete | ||||
| ------------ | ||||
|  | ||||
| .. code-block:: bash | ||||
|  | ||||
|   docker-compose run --rm admin python manage.py user_delete foo@example.net | ||||
|   docker-compose exec admin flask mailu user_delete foo@example.net | ||||
|  | ||||
| config_update | ||||
| ------------- | ||||
| @@ -55,7 +55,7 @@ The sole purpose of this command is for importing users/aliases in bulk and sync | ||||
|  | ||||
| .. code-block:: bash | ||||
|  | ||||
|   cat mail-config.yml | docker-compose run --rm admin python manage.py config_update --delete_objects | ||||
|   cat mail-config.yml | docker-compose exec admin flask mailu config_update --delete_objects | ||||
|  | ||||
| where mail-config.yml looks like: | ||||
|  | ||||
|   | ||||
| @@ -61,6 +61,7 @@ ANTIVIRUS=none | ||||
|  | ||||
| # Message size limit in bytes | ||||
| # Default: accept messages up to 50MB | ||||
| # Max attachment size will be 33% smaller | ||||
| MESSAGE_SIZE_LIMIT=50000000 | ||||
|  | ||||
| # Networks granted relay permissions, make sure that you include your Docker | ||||
|   | ||||
| @@ -151,6 +151,6 @@ Finally, you must create the initial admin user account: | ||||
|  | ||||
| .. code-block:: bash | ||||
|  | ||||
|   docker-compose run --rm admin python manage.py admin root example.net password | ||||
|   docker-compose exec admin flask mailu admin me example.net password | ||||
|  | ||||
| This will create a user named ``root@example.net`` with password ``password`` and administration privileges. Connect to the Web admin interface and change the password to a strong one. | ||||
| This will create a user named ``me@example.net`` with password ``password`` and administration privileges. Connect to the Web admin interface and change the password to a strong one. | ||||
|   | ||||
| @@ -17,7 +17,7 @@ migration script: | ||||
|  | ||||
| .. code-block:: bash | ||||
|  | ||||
|   python manage.py db migrate | ||||
|   flask db migrate | ||||
|  | ||||
| This will generate a new script in ``migrations/versions`` that you must review | ||||
| before adding it for commit. | ||||
| @@ -54,7 +54,7 @@ At that point, to start working on the changed database structure, you will need | ||||
|  | ||||
| .. code-block:: bash | ||||
|  | ||||
|   python manage.py db upgrade | ||||
|   flask db upgrade | ||||
|  | ||||
| If any error arises, restore the backup, fix the migration script and try again. | ||||
|  | ||||
|   | ||||
| @@ -12,6 +12,7 @@ COPY setup.py ./setup.py | ||||
| COPY main.py ./main.py | ||||
| COPY flavors /data/master/flavors | ||||
| COPY templates /data/master/templates | ||||
| COPY static ./static | ||||
|  | ||||
| #RUN python setup.py https://github.com/mailu/mailu /data | ||||
|  | ||||
|   | ||||
							
								
								
									
										59
									
								
								setup/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								setup/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| ## Adding more flavors/steps | ||||
| (Everything will go under setup/ directory - using Kubernetes flavor as example) | ||||
|  | ||||
| Until this point, the app is working as it follows: | ||||
| - when accesing the setup page it will display the flavors selection step (`templates/steps/flavor.html`) | ||||
| - after you choose your desired flavor it will iterare over the files in the flavor directory and building the page | ||||
|   (`templates/steps/config.html is general for all flavors`) | ||||
| - when you complete all required fields and press "Setup Mailu" button it will redirect you to the setup page (`flavors/choosen-flavor/setup.html`) | ||||
|    | ||||
| To add a new flavor you need to create a directory under `templates/steps/` in which you are adding actual steps. | ||||
| Eg: Adding a WIP step we'll create `templates/steps/kubernetes/wip.html` | ||||
|  | ||||
| *Note that wizard.html is iterating over files in this directory and building the page. Files are prefixed with a number for sorting purposes.* | ||||
|  | ||||
| wip.html will start with | ||||
|  | ||||
| ``` | ||||
| {% call macros.panel("info", "Step X - Work in progress") %} | ||||
| ```  | ||||
|  | ||||
| and end with  | ||||
| ``` | ||||
| {% endcall %} | ||||
| ``` | ||||
|  | ||||
| You store variable from front-page using the name attribute inside tag. | ||||
| In the example below the string entered in the input field is stored in the variable `named var_test` | ||||
| ``` | ||||
| <input type="text" name="var_test"> | ||||
| ``` | ||||
|  | ||||
| In order to user the variable furter you use it like `{{ var_test }}` | ||||
|  | ||||
| In the setup page (`flavors/kubernetes/setup.html`) you cand add steps by importing macros | ||||
|  | ||||
| ``` | ||||
| {% import "macros.html" as macros %} | ||||
| ``` | ||||
|  | ||||
| and start and end every step with | ||||
| ``` | ||||
| {% call macros.panel("info", "Step X - Title") %} | ||||
| ------------------- | ||||
| {% endcall %} | ||||
| ``` | ||||
|  | ||||
| ### Generating a file | ||||
| Create the file template in `flavors/kubernetes/` (eg. file.txt) in which you save your variables | ||||
| ``` | ||||
| ROOT = {{ root }} | ||||
| MY_VAR = {{ var_test }} | ||||
| ``` | ||||
|  | ||||
| When you submit to Setup Mailu the file will be generated. In order to get the file add the following command to setup.html | ||||
|  | ||||
| ``` | ||||
| <p>curl {{ url_for('.file', uid=uid, filepath='file.txt', _external=True) }} > file.txt</p> | ||||
| ``` | ||||
|  | ||||
| @@ -1,13 +1,13 @@ | ||||
| # This file is used to run the mailu/setup utility | ||||
|  | ||||
| version: '2' | ||||
| version: '3.6' | ||||
|  | ||||
| services: | ||||
|   redis: | ||||
|     image: redis:alpine | ||||
|  | ||||
|   setup: | ||||
|     image: mailu/setup | ||||
|     image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}setup:${MAILU_VERSION:-master} | ||||
|     ports: | ||||
|       - "8000:80" | ||||
|     build: . | ||||
|   | ||||
| @@ -10,13 +10,17 @@ services: | ||||
|   # External dependencies | ||||
|   redis: | ||||
|     image: redis:alpine | ||||
|     restart: always | ||||
|     volumes: | ||||
|       - "{{ root }}/redis:/data" | ||||
|  | ||||
|   # Core services | ||||
|   front: | ||||
|     image: mailu/nginx:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/nginx:${MAILU_VERSION:-{{ version }}} | ||||
|     restart: always | ||||
|     env_file: {{ env }} | ||||
|     logging: | ||||
|       driver: {{ log_driver or 'json-file' }} | ||||
|     ports: | ||||
|     {% for port in (80, 443, 25, 465, 587, 110, 995, 143, 993) %} | ||||
|     {% if bind4 %} | ||||
| @@ -41,7 +45,8 @@ services: | ||||
|   {% endif %} | ||||
|  | ||||
|   admin: | ||||
|     image: mailu/admin:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/admin:${MAILU_VERSION:-{{ version }}} | ||||
|     restart: always | ||||
|     env_file: {{ env }} | ||||
|     {% if not admin_enabled %} | ||||
|     ports: | ||||
| @@ -54,7 +59,8 @@ services: | ||||
|       - redis | ||||
|  | ||||
|   imap: | ||||
|     image: mailu/dovecot:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/dovecot:${MAILU_VERSION:-{{ version }}} | ||||
|     restart: always | ||||
|     env_file: {{ env }} | ||||
|     volumes: | ||||
|       - "{{ root }}/mail:/mail" | ||||
| @@ -63,7 +69,8 @@ services: | ||||
|       - front | ||||
|  | ||||
|   smtp: | ||||
|     image: mailu/postfix:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/postfix:${MAILU_VERSION:-{{ version }}} | ||||
|     restart: always | ||||
|     env_file: {{ env }} | ||||
|     volumes: | ||||
|       - "{{ root }}/overrides:/overrides" | ||||
| @@ -75,10 +82,9 @@ services: | ||||
|       - {{ dns }} | ||||
|     {% endif %} | ||||
|  | ||||
|   # Optional services | ||||
|   {% if antispam_enabled %} | ||||
|   antispam: | ||||
|     image: mailu/rspamd:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/rspamd:${MAILU_VERSION:-{{ version }}} | ||||
|     restart: always | ||||
|     env_file: {{ env }} | ||||
|     volumes: | ||||
|       - "{{ root }}/filter:/var/lib/rspamd" | ||||
| @@ -91,11 +97,12 @@ services: | ||||
|     dns: | ||||
|       - {{ dns }} | ||||
|     {% endif %} | ||||
|   {% endif %} | ||||
|  | ||||
|   # Optional services | ||||
|   {% if antivirus_enabled %} | ||||
|   antivirus: | ||||
|     image: mailu/clamav:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/clamav:${MAILU_VERSION:-{{ version }}} | ||||
|     restart: always | ||||
|     env_file: {{ env }} | ||||
|     volumes: | ||||
|       - "{{ root }}/filter:/data" | ||||
| @@ -109,7 +116,8 @@ services: | ||||
|  | ||||
|   {% if webdav_enabled %} | ||||
|   webdav: | ||||
|     image: mailu/radicale:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/radicale:${MAILU_VERSION:-{{ version }}} | ||||
|     restart: always | ||||
|     env_file: {{ env }} | ||||
|     volumes: | ||||
|       - "{{ root }}/dav:/data" | ||||
| @@ -117,7 +125,8 @@ services: | ||||
|  | ||||
|   {% if fetchmail_enabled %} | ||||
|   fetchmail: | ||||
|     image: mailu/fetchmail:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/fetchmail:${MAILU_VERSION:-{{ version }}} | ||||
|     restart: always | ||||
|     env_file: {{ env }} | ||||
|     {% if resolver_enabled %} | ||||
|     depends_on: | ||||
| @@ -130,7 +139,8 @@ services: | ||||
|   # Webmail | ||||
|   {% if webmail_type != 'none' %} | ||||
|   webmail: | ||||
|     image: mailu/{{ webmail_type }}:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/{{ webmail_type }}:${MAILU_VERSION:-{{ version }}} | ||||
|     restart: always | ||||
|     env_file: {{ env }} | ||||
|     volumes: | ||||
|       - "{{ root }}/webmail:/data" | ||||
|   | ||||
| @@ -73,6 +73,7 @@ ANTISPAM={{ antispam_enabled or 'none'}} | ||||
|  | ||||
| # Message size limit in bytes | ||||
| # Default: accept messages up to 50MB | ||||
| # Max attachment size will be 33% smaller | ||||
| MESSAGE_SIZE_LIMIT={{ message_size_limit or '50000000' }} | ||||
|  | ||||
| # Networks granted relay permissions, make sure that you include your Docker | ||||
| @@ -144,7 +145,7 @@ DOMAIN_REGISTRATION=true | ||||
| # json-file (default) | ||||
| # journald (On systemd platforms, useful for Fail2Ban integration) | ||||
| # syslog (Non systemd platforms, Fail2Ban integration. Disables `docker-compose log` for front!) | ||||
| LOG_DRIVER={{ log_driver or 'json-file' }} | ||||
| # LOG_DRIVER={{ log_driver or 'json-file' }} | ||||
|  | ||||
| # Docker-compose project name, this will prepended to containers names. | ||||
| COMPOSE_PROJECT_NAME={{ compose_project_name or 'mailu' }} | ||||
|   | ||||
| @@ -28,15 +28,15 @@ files before going any further.</p> | ||||
|  | ||||
| {% call macros.panel("info", "Step 3 - Start the Compose project") %} | ||||
| <p>To start your compose project, simply run the Docker Compose <code>up</code> | ||||
| command.</p> | ||||
| command using <code>-p mailu</code> flag for project name.</p> | ||||
|  | ||||
| <pre><code>cd {{ root }} | ||||
| docker-compose up -d | ||||
| docker-compose -p mailu up -d | ||||
| </pre></code> | ||||
|  | ||||
| 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 exec admin python manage.py admin {{ postmaster }} {{ domain }} PASSWORD | ||||
| <pre><code>docker-compose -p mailu exec admin python manage.py admin {{ postmaster }} {{ domain }} PASSWORD | ||||
| </pre></code> | ||||
|  | ||||
| <p>Login to the admin interface to change the password for a safe one, at | ||||
|   | ||||
| @@ -10,14 +10,15 @@ services: | ||||
| # External dependencies | ||||
|   redis: | ||||
|     image: redis:alpine | ||||
|     restart: always | ||||
|     volumes: | ||||
|       - "{{ root }}/redis:/data" | ||||
|  | ||||
| # Core services | ||||
|   front: | ||||
|     image: mailu/nginx:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/nginx:${MAILU_VERSION:-{{ version }}} | ||||
|     env_file: {{ env }} | ||||
|     logging: | ||||
|       driver: {{ log_driver or 'json-file' }} | ||||
|     ports: | ||||
|     {% for port in (80, 443, 25, 465, 587, 110, 995, 143, 993) %} | ||||
|       - target: {{ port }} | ||||
| @@ -28,7 +29,7 @@ services: | ||||
|       - "{{ root }}/certs:/certs" | ||||
|       - "{{ root }}/overrides/nginx:/overrides" | ||||
|     deploy: | ||||
|       replicas: 1 | ||||
|       replicas: {{ front_replicas }} | ||||
|  | ||||
|   {% if resolver_enabled %} | ||||
|   resolver: | ||||
| @@ -40,7 +41,7 @@ services: | ||||
|   {% endif %} | ||||
|  | ||||
|   admin: | ||||
|     image: mailu/admin:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/admin:${MAILU_VERSION:-{{ version }}} | ||||
|     env_file: {{ env }} | ||||
|     {% if not admin_enabled %} | ||||
|     ports: | ||||
| @@ -50,10 +51,10 @@ services: | ||||
|       - "{{ root }}/data:/data" | ||||
|       - "{{ root }}/dkim:/dkim" | ||||
|     deploy: | ||||
|       replicas: 1 | ||||
|       replicas: {{ admin_replicas }} | ||||
|  | ||||
|   imap: | ||||
|     image: mailu/dovecot:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/dovecot:${MAILU_VERSION:-{{ version }}} | ||||
|     env_file: {{ env }} | ||||
|     environment: | ||||
|     # Default to 10.0.1.0/24 | ||||
| @@ -62,26 +63,24 @@ services: | ||||
|       - "{{ root }}/mail:/mail" | ||||
|       - "{{ root }}/overrides:/overrides" | ||||
|     deploy: | ||||
|       replicas: 1 | ||||
|       replicas: {{ imap_replicas }} | ||||
|  | ||||
|   smtp: | ||||
|     image: mailu/postfix:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/postfix:${MAILU_VERSION:-{{ version }}} | ||||
|     env_file: {{ env }} | ||||
|     environment: | ||||
|       - POD_ADDRESS_RANGE={{ subnet }} | ||||
|     volumes: | ||||
|       - "{{ root }}/overrides:/overrides" | ||||
|     deploy: | ||||
|       replicas: 1 | ||||
|       replicas: {{ smtp_replicas }} | ||||
|     {% if resolver_enabled %} | ||||
|     dns: | ||||
|       - {{ dns }} | ||||
|     {% endif %} | ||||
|  | ||||
|   # Optional services | ||||
|   {% if antispam_enabled %} | ||||
|   antispam: | ||||
|     image: mailu/rspamd:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/rspamd:${MAILU_VERSION:-{{ version }}} | ||||
|     env_file: {{ env }} | ||||
|     environment: | ||||
|       - POD_ADDRESS_RANGE={{ subnet }} | ||||
| @@ -95,11 +94,11 @@ services: | ||||
|     dns: | ||||
|       - {{ dns }} | ||||
|     {% endif %} | ||||
|   {% endif %} | ||||
|  | ||||
|   # Optional services | ||||
|   {% if antivirus_enabled %} | ||||
|   antivirus: | ||||
|     image: mailu/clamav:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/clamav:${MAILU_VERSION:-{{ version }}} | ||||
|     env_file: {{ env }} | ||||
|     volumes: | ||||
|       - "{{ root }}/filter:/data" | ||||
| @@ -113,7 +112,7 @@ services: | ||||
|  | ||||
|   {% if webdav_enabled %} | ||||
|   webdav: | ||||
|     image: mailu/none:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/none:${MAILU_VERSION:-{{ version }}} | ||||
|     env_file: {{ env }} | ||||
|     volumes: | ||||
|       - "{{ root }}/dav:/data" | ||||
| @@ -123,7 +122,7 @@ services: | ||||
|  | ||||
|   {% if fetchmail_enabled %} | ||||
|   fetchmail: | ||||
|     image: mailu/fetchmail:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/fetchmail:${MAILU_VERSION:-{{ version }}} | ||||
|     env_file: {{ env }} | ||||
|     volumes: | ||||
|       - "{{ root }}/data:/data" | ||||
| @@ -137,7 +136,7 @@ services: | ||||
|  | ||||
|   {% if webmail_type != 'none' %} | ||||
|   webmail: | ||||
|     image: mailu/roundcube:{{ version }} | ||||
|     image: ${DOCKER_ORG:-mailu}/roundcube:${MAILU_VERSION:-{{ version }}} | ||||
|     env_file: {{ env }} | ||||
|     volumes: | ||||
|       - "{{ root }}/webmail:/data" | ||||
|   | ||||
							
								
								
									
										34
									
								
								setup/static/render.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								setup/static/render.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| $(document).ready(function() { | ||||
| 	if ($("#webmail").val() == 'none') { | ||||
| 		$("#webmail_path").hide(); | ||||
| 		$("#webmail_path").attr("value", ""); | ||||
| 	} else { | ||||
| 		$("#webmail_path").show(); | ||||
| 		$("#webmail_path").attr("value", "/webmail"); | ||||
| 	} | ||||
| 	$("#webmail").click(function() { | ||||
| 		if (this.value == 'none') { | ||||
| 			$("#webmail_path").hide(); | ||||
| 			$("#webmail_path").attr("value", ""); | ||||
| 		} else { | ||||
| 			$("#webmail_path").show(); | ||||
| 			$("#webmail_path").attr("value", "/webmail"); | ||||
| 		} | ||||
| 	}); | ||||
| }); | ||||
|  | ||||
| $(document).ready(function() { | ||||
| 	if ($('#admin').prop('checked')) { | ||||
| 		$("#admin_path").show(); | ||||
| 		$("#admin_path").attr("value", "/admin"); | ||||
| 	} | ||||
| 	$("#admin").change(function() { | ||||
| 		if ($(this).is(":checked")) { | ||||
| 			$("#admin_path").show(); | ||||
| 			$("#admin_path").attr("value", "/admin"); | ||||
| 		} else { | ||||
| 			$("#admin_path").hide(); | ||||
| 			$("#admin_path").attr("value", ""); | ||||
| 		} | ||||
| 	}); | ||||
| }); | ||||
| @@ -15,15 +15,14 @@ accessing messages for beginner users.</p> | ||||
| <!--     {{ macros.radio("webmail_type", "rainloop", "Rainloop", "lightweight Webmail based on PHP, no database") }} --> | ||||
| <!--   </div> --> | ||||
|   <br/> | ||||
|   <select class="btn btn-primary dropdown-toggle" name="webmail_type"> | ||||
|   <select class="btn btn-primary dropdown-toggle" name="webmail_type" id="webmail"> | ||||
|   	{% for webmailtype in ["none", "roundcube", "rainloop"] %} | ||||
|   	<option value="{{ webmailtype }}" >{{ webmailtype }}</option> | ||||
|   	{% endfor %} | ||||
|   </select> | ||||
|   <p></p> | ||||
|   <div class="input-group"> | ||||
| <!--     <div class="input-group-addon"><input type="checkbox" name="webmail_enabled" value="true"></div> --> | ||||
|     <input class="form-control" type="text" name="webmail_path" value="/webmail"> | ||||
|     <input class="form-control" type="text" name="webmail_path" id="webmail_path" style="display: none"> | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| @@ -32,12 +31,6 @@ will prevent Mailu from doing spam filtering, virus filtering, and from applying | ||||
| white and blacklists that you may configure in the admin interface. You may | ||||
| also disable the antivirus if required (it does use aroung 1GB of ram).</p> | ||||
|  | ||||
| <div class="form-check form-check-inline"> | ||||
|   <label class="form-check-label"> | ||||
|     <input class="form-check-input" type="checkbox" name="antispam_enabled" value="rspamd" checked> | ||||
|     Enable the spam filtering service | ||||
|   </label> | ||||
| </div> | ||||
| <div class="form-check form-check-inline"> | ||||
|   <label class="form-check-label"> | ||||
|     <input class="form-check-input" type="checkbox" name="antivirus_enabled" value="clamav"> | ||||
| @@ -59,4 +52,9 @@ also disable the antivirus if required (it does use aroung 1GB of ram).</p> | ||||
|   </label> | ||||
| </div> | ||||
|  | ||||
| <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> | ||||
|  | ||||
| <script type="text/javascript" src="{{ url_for('static', filename='render.js') }}"></script> | ||||
|  | ||||
|  | ||||
| {% endcall %} | ||||
|   | ||||
| @@ -68,11 +68,13 @@ Or in plain english: if receivers start to classify your mail as spam, this post | ||||
| manage your email domains, users, etc.</p> | ||||
|  | ||||
| <div class="form-group"> | ||||
|   <label>Enable the admin UI (and path to the admin UI)</label> | ||||
|   <div class="input-group"> | ||||
|     <div class="input-group-addon"><input type="checkbox" name="admin_enabled" value="true"></div> | ||||
|     <input class="form-control" type="text" name="admin_path" value="/admin"> | ||||
|   </div> | ||||
|     <input type="checkbox" name="admin_enabled" value="true" id="admin"> | ||||
|   	<label>Enable the admin UI (and path to the admin UI)</label> | ||||
|     <input class="form-control" type="text" name="admin_path" id="admin_path" style="display: none"> | ||||
| </div> | ||||
|  | ||||
| <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> | ||||
|  | ||||
| <script type="text/javascript" src="{{ url_for('static', filename='render.js') }}"></script> | ||||
|  | ||||
| {% endcall %} | ||||
|   | ||||
| @@ -15,15 +15,14 @@ accessing messages for beginner users.</p> | ||||
| <!--     {{ macros.radio("webmail_type", "rainloop", "Rainloop", "lightweight Webmail based on PHP, no database") }} --> | ||||
| <!--   </div> --> | ||||
|   <br/> | ||||
|   <select class="btn btn-primary dropdown-toggle" name="webmail_type"> | ||||
|   <select class="btn btn-primary dropdown-toggle" name="webmail_type" id="webmail"> | ||||
|   	{% for webmailtype in ["none", "roundcube", "rainloop"] %} | ||||
|   	<option value="{{ webmailtype }}" >{{ webmailtype }}</option> | ||||
|   	{% endfor %} | ||||
|   </select> | ||||
|   <p></p> | ||||
|   <div class="input-group"> | ||||
| <!--     <div class="input-group-addon"><input type="checkbox" name="webmail_enabled" value="true"></div> --> | ||||
|     <input class="form-control" type="text" name="webmail_path" value="/webmail"> | ||||
|     <input class="form-control" type="text" name="webmail_path" id="webmail_path" style="display: none"> | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| @@ -32,12 +31,6 @@ will prevent Mailu from doing spam filtering, virus filtering, and from applying | ||||
| white and blacklists that you may configure in the admin interface. You may | ||||
| also disable the antivirus if required (it does use aroung 1GB of ram).</p> | ||||
|  | ||||
| <div class="form-check form-check-inline"> | ||||
|   <label class="form-check-label"> | ||||
|     <input class="form-check-input" type="checkbox" name="antispam_enabled" value="rspamd" checked> | ||||
|     Enable the spam filtering service | ||||
|   </label> | ||||
| </div> | ||||
| <div class="form-check form-check-inline"> | ||||
|   <label class="form-check-label"> | ||||
|     <input class="form-check-input" type="checkbox" name="antivirus_enabled" value="clamav"> | ||||
| @@ -59,4 +52,8 @@ also disable the antivirus if required (it does use aroung 1GB of ram).</p> | ||||
|   </label> | ||||
| </div> | ||||
|  | ||||
| <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> | ||||
|  | ||||
| <script type="text/javascript" src="{{ url_for('static', filename='render.js') }}"></script> | ||||
|  | ||||
| {% endcall %} | ||||
|   | ||||
							
								
								
									
										28
									
								
								setup/templates/steps/stack/04_replicas.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								setup/templates/steps/stack/04_replicas.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| {% call macros.panel("info", "Step 5 - Number of replicas for containers") %} | ||||
| <p>Select number of replicas for containers</p> | ||||
|  | ||||
| <div class="form-group"> | ||||
|   <input class="form-control" type="number" name="front_replicas" min="1" required value="1" | ||||
|   		style="width: 6%; display: inline;"> | ||||
|   <label>Front</label> | ||||
| </div> | ||||
|  | ||||
| <div class="form-group"> | ||||
|   <input class="form-control" type="number" name="admin_replicas" min="1" required value="1" | ||||
|   		style="width: 6%; display: inline;"> | ||||
|   <label>Admin</label> | ||||
| </div> | ||||
|  | ||||
| <div class="form-group"> | ||||
|   <input class="form-control" type="number" name="imap_replicas" min="1" required value="1" | ||||
|   		style="width: 6%; display: inline;"> | ||||
|   <label>IMAP</label> | ||||
| </div> | ||||
|  | ||||
| <div class="form-group"> | ||||
|   <input class="form-control" type="number" name=smtp_replicas min="1" required value="1" | ||||
|   		style="width: 6%; display: inline;"> | ||||
|   <label>SMPT</label> | ||||
| </div> | ||||
|  | ||||
| {% endcall %} | ||||
| @@ -3,58 +3,58 @@ version: '3' | ||||
| services: | ||||
|  | ||||
|   front: | ||||
|     image: ${DOCKER_ORG:-mailu}/nginx:${VERSION:-local} | ||||
|     image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}nginx:${MAILU_VERSION:-local} | ||||
|     build: ../core/nginx | ||||
|  | ||||
|   resolver: | ||||
|     image: ${DOCKER_ORG:-mailu}/unbound:${VERSION:-local} | ||||
|     image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}unbound:${MAILU_VERSION:-local} | ||||
|     build: ../services/unbound | ||||
|  | ||||
|   imap: | ||||
|     image: ${DOCKER_ORG:-mailu}/dovecot:${VERSION:-local} | ||||
|     image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}dovecot:${MAILU_VERSION:-local} | ||||
|     build: ../core/dovecot | ||||
|  | ||||
|   smtp: | ||||
|     image: ${DOCKER_ORG:-mailu}/postfix:${VERSION:-local} | ||||
|     image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}postfix:${MAILU_VERSION:-local} | ||||
|     build: ../core/postfix | ||||
|  | ||||
|   antispam: | ||||
|     image: ${DOCKER_ORG:-mailu}/rspamd:${VERSION:-local} | ||||
|     image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}rspamd:${MAILU_VERSION:-local} | ||||
|     build: ../services/rspamd | ||||
|  | ||||
|   antivirus: | ||||
|     image: ${DOCKER_ORG:-mailu}/clamav:${VERSION:-local} | ||||
|     image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}clamav:${MAILU_VERSION:-local} | ||||
|     build: ../optional/clamav | ||||
|  | ||||
|   webdav: | ||||
|     image: ${DOCKER_ORG:-mailu}/radicale:${VERSION:-local} | ||||
|     image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}radicale:${MAILU_VERSION:-local} | ||||
|     build: ../optional/radicale | ||||
|  | ||||
|   admin: | ||||
|     image: ${DOCKER_ORG:-mailu}/admin:${VERSION:-local} | ||||
|     image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}admin:${MAILU_VERSION:-local} | ||||
|     build: ../core/admin | ||||
|  | ||||
|   roundcube: | ||||
|     image: ${DOCKER_ORG:-mailu}/roundcube:${VERSION:-local} | ||||
|     image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}roundcube:${MAILU_VERSION:-local} | ||||
|     build: ../webmails/roundcube | ||||
|  | ||||
|   rainloop: | ||||
|     image: ${DOCKER_ORG:-mailu}/rainloop:${VERSION:-local} | ||||
|     image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}rainloop:${MAILU_VERSION:-local} | ||||
|     build: ../webmails/rainloop | ||||
|  | ||||
|   fetchmail: | ||||
|     image: ${DOCKER_ORG:-mailu}/fetchmail:${VERSION:-local} | ||||
|     image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}fetchmail:${MAILU_VERSION:-local} | ||||
|     build: ../services/fetchmail | ||||
|  | ||||
|   none: | ||||
|     image: ${DOCKER_ORG:-mailu}/none:${VERSION:-local} | ||||
|     image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}none:${MAILU_VERSION:-local} | ||||
|     build: ../core/none | ||||
|  | ||||
|   docs: | ||||
|     image: ${DOCKER_ORG:-mailu}/docs:${VERSION:-local} | ||||
|     image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}docs:${MAILU_VERSION:-local} | ||||
|     build: ../docs | ||||
|  | ||||
|   setup: | ||||
|     image: ${DOCKER_ORG:-mailu}/setup:${VERSION:-local} | ||||
|     image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX}setup:${MAILU_VERSION:-local} | ||||
|     build: ../setup | ||||
|  | ||||
|   | ||||
							
								
								
									
										29
									
								
								tests/certs/cert.pem
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								tests/certs/cert.pem
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIE/jCCAuagAwIBAgIJAKVnyadXS7SuMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV | ||||
| BAMMCWxvY2FsaG9zdDAeFw0xODEwMzExMDE1MzFaFw0yODEwMjgxMDE1MzFaMBQx | ||||
| EjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC | ||||
| ggIBAOQ2ZDqR+YvW5FKykBXz/Ec+jSb0Lv7GYQkT5t+TB1NXuR+QH1LfNWFmXOo7 | ||||
| YXcPVXlmcuLDuUldrctdS59fx8dnFu5gRRUqJwZuEQICypsX0rTDtsV6xqZB8c8y | ||||
| 2+BztP9OHfPpZdnU1IBx2fDbjpdKUaoAMFMFvyTaEcIyp6aGAhejvJCwc3D8fIJI | ||||
| NhWA2O11sZQHUs7/MHzpu/IHpgutgk8EsNOUNLwB3+9p3IlOlTT6GilIXOYeTzoD | ||||
| hiI6B5BQqXHsRrkao3v0YL6Ekun4hOx3MYx09AZtmuyrlq1mkNueKS5JwKDrXXbq | ||||
| Ta0oyJ18UTZFRwVqApcuR4CA8vuhI9PsoDCvBQH1rW6FyiM4bhybatFJAYjQAODe | ||||
| gwh2p6JWux5C1gaBUubOrKO7o5ePI6s0MmK8ZxrL4PpBYt3B33ztFfjWmVbCTSvP | ||||
| GuQ2Ux73OY2NNxx2aNt4Th0IxrvMdsGLrZsdma2rWa5eTJTAuqbSjI/Wb1zjO0pi | ||||
| pwoxk6f1COFLopo2xgJj6+KKG1nKLfOzQFexcpdq/mpuulcVcLDPJzJTLX3qsgtD | ||||
| iBpm1ozNRT+M7XUavg8aHNfn6S+TcDb5hp+1yZ6obZq/VlA6atk0fuPzf+ndQ0fq | ||||
| YN1jlAIzZXt/Dpc+ObjS09WGDVQXobGesdwA6BH14OV+TxOHAgMBAAGjUzBRMB0G | ||||
| A1UdDgQWBBQy7kA8FbdcFpVU1AoFgzE7Fw1QqDAfBgNVHSMEGDAWgBQy7kA8Fbdc | ||||
| FpVU1AoFgzE7Fw1QqDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IC | ||||
| AQBLFlQKxztxm7MtsHs01Pl8/FpKzWekWK1ksf15d8mHBT30OTs+NXaJDuHTGL4r | ||||
| rPeFf3NZ1PZkGRnJCEWur+8e8Y5KwuMAaagneSYXU0gcZfvTidvf865Jiml8xO5x | ||||
| PAo8qTZQCHmYcvJQwBXMkq/2sFJCYeMOLoJdXXbTTe2ZQ/N3MSQbpgWJ8pF7srKU | ||||
| biw2RkNH39QPq9GpWRQGx2gwvZDy2oFG8cM1hJYmz0Y9clpBE0mSqypvA1E8ufKC | ||||
| uaUc0tpPI5H4efeWv/ObnFAJ3DMEmzUnQ8hdM/7cpf6AL8VRm4Wrw112gK7SbSdd | ||||
| mMsUfFIDfyE9vsZ3OC8C8LqXKLwMcm7Fdq0ym0NINtoVW0ukmVJzB78CdWaJ7ux1 | ||||
| WqitcnewgiMWuuwuepBmNurZtgDrg+zgMhNpuK0NzYyE+ZReoJIOJCub3SSEsWdl | ||||
| x5aJEYuFYJR5EvmxWeYv5p1GVOTL1TJqW7iRodzRoMc9u2vb0+tCbM5XSZVPul6P | ||||
| QimDui2Ogq0zYNbSkHaUGBpjGDvHYG0zXO2sWrdrAJQMHo8dGEe7FuSuAlWbQdb/ | ||||
| xgN4uwejxV6B2e6rjT6YMni+r5Qw0EhNka+Xohw5E68bEcQSrCP8j64qQLAeipuz | ||||
| ImqBTNyyR4WTcL+1HIVM7ZIw3igHH55zo5qTvyjKyZX9Uw== | ||||
| -----END CERTIFICATE----- | ||||
							
								
								
									
										52
									
								
								tests/certs/key.pem
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								tests/certs/key.pem
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| -----BEGIN PRIVATE KEY----- | ||||
| MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDkNmQ6kfmL1uRS | ||||
| spAV8/xHPo0m9C7+xmEJE+bfkwdTV7kfkB9S3zVhZlzqO2F3D1V5ZnLiw7lJXa3L | ||||
| XUufX8fHZxbuYEUVKicGbhECAsqbF9K0w7bFesamQfHPMtvgc7T/Th3z6WXZ1NSA | ||||
| cdnw246XSlGqADBTBb8k2hHCMqemhgIXo7yQsHNw/HyCSDYVgNjtdbGUB1LO/zB8 | ||||
| 6bvyB6YLrYJPBLDTlDS8Ad/vadyJTpU0+hopSFzmHk86A4YiOgeQUKlx7Ea5GqN7 | ||||
| 9GC+hJLp+ITsdzGMdPQGbZrsq5atZpDbnikuScCg61126k2tKMidfFE2RUcFagKX | ||||
| LkeAgPL7oSPT7KAwrwUB9a1uhcojOG4cm2rRSQGI0ADg3oMIdqeiVrseQtYGgVLm | ||||
| zqyju6OXjyOrNDJivGcay+D6QWLdwd987RX41plWwk0rzxrkNlMe9zmNjTccdmjb | ||||
| eE4dCMa7zHbBi62bHZmtq1muXkyUwLqm0oyP1m9c4ztKYqcKMZOn9QjhS6KaNsYC | ||||
| Y+viihtZyi3zs0BXsXKXav5qbrpXFXCwzycyUy196rILQ4gaZtaMzUU/jO11Gr4P | ||||
| GhzX5+kvk3A2+YaftcmeqG2av1ZQOmrZNH7j83/p3UNH6mDdY5QCM2V7fw6XPjm4 | ||||
| 0tPVhg1UF6GxnrHcAOgR9eDlfk8ThwIDAQABAoICACoHsnHvDIyqqSZp6IuCggYF | ||||
| CS4Rbs5RbvGjDrRCeejpkRi1DG/Q2B32IkqpYQvycQWIzsPg1DEk5as8pX7Wvw6E | ||||
| d/6zEEYTm1hd0RgTt4jU3GOaYAEC2a8pGgXVEhXGeaFDm9SeObnirrhxP3hSl3JZ | ||||
| p6ytmDjSKB/7YaXoemP67ku4RjRHqxs2BSBheESBlHI3aNsgdinVafK3gXvT2Mrx | ||||
| y7wN2xs8gnHVzo5jatCG/ofhQAw2XZWsI19F4uBO27HCiVKH94aD13Quz9qGxB// | ||||
| O0vpr+B0cbT1XsET4Q5Sg39PI7p4rtd0QaRzBpdLmZcXnEVogOoIWi3JwjVyik1g | ||||
| lcg+4A8wj4pDGsCmANt90YqedktQGiYsYozZHO3YCrnjO6lqYJLOBocRG9NJqldY | ||||
| kzs6UfJ+96FoYQVGNXyeQZizC26rQHll/rwsJnsB7GvM38f3q3cr3Borpwx3HosN | ||||
| mmM+WRcvV3WWjjx1870Jm+tIDu0clWvT7hdHSf4938/Xr9cUTyuX2LrqTfp6JThl | ||||
| +NbYgbuvd5leP94wPwRxfJL+PR5B4kbLPwDNCbpM8QTBm+9Y4kU+6ePmgcuRemMQ | ||||
| 8J41ocUjC4wR2j9Zgy0f0Rz4KiKM6IiVgKyqPUMaY+aJQ+yB5J+tlBkPJeZzft/e | ||||
| XAoxt0STTassHC+p9COxAoIBAQD2Vd2Q1rbxWGnWl0m1LcH5q4hsuUAhIYmuTMSO | ||||
| RkDLD/8yfPR4uUbTgrtdL2FaeOsCK7nrQAPxcfdD//+SoNVsAkMuNw6QvJn4ZXLf | ||||
| 5C45tN4pfoz/EwIRBvyJnI+HZuNaCUCfsQB9ggeEHgM2n36GBiOX82inQey3eREz | ||||
| wZjQqmCp+b1QiYoWrVCgOPOvB86kbNgHGacIS7cDe94OeP4dH+FAfWaIBab8sDnG | ||||
| K6+N6dWdj+b7veUWpXBs8beVCTO4GPnW5hnYOfuWkdpNCej/QbMeivMA4U7g+CeF | ||||
| Y5QB07EE5f35Epp8WoNtwVZoFgP72xMT1taz1Rx7dohdYvLVAoIBAQDtKoDiwi2V | ||||
| 07rOgsjgW972HdA0nOnja/lky6CKkY5BqNGMj63h0ysy8Fe8mEWdPXyY9f7TgWP9 | ||||
| sDMZMq+d8ZwAjfdYjYTKpxA3pA9oj66OCxtR6usElmeyultPjZ8FXJNXzOLv4dju | ||||
| FnELSFSSx8o6WHGq9l2eWNMFf46g70Bt+aiHV/VGLLSFTUcvd51H7jP+PFxrBn1k | ||||
| kz1u0n/RRuPMIru68lKJxrpDsr917Spw16O+uzjR99IqNPskVJxUnXV8qvMxeWVl | ||||
| wTOP9soqYv/KvqjsBO+nLNkLSH402Fp78e2Oe6KPKlF21kl5oA7Yn/w4MtyFpj65 | ||||
| fg6uDaPhgoLrAoIBAQCb9uWfzLJrwETSn1sFoYENKPPpkqjt0SQw/V39jrF7YBd9 | ||||
| yeune/dB96XVbChBdgmliDXgotlcR4H8xdr05Wv7RLtwSV+peCAsS18eLoSt+Lwo | ||||
| nX18CnbmfPvrzPp7CkOsP+twsErVLDzCA5aZQQaEqOJkVLLQI0dTKw4fLNYqV5V4 | ||||
| SSz6DvslPHqt1yFCkrjdFiT46d79u6KWTBjeJPEPU530jPEb8ig2GQWbWRF/0qtz | ||||
| ZSckAKlJW1oBQFGxxO/AAeA9ldaLNrr6LEKBQGMLKnfUQLl2tzCP885iABg3x+Zu | ||||
| aYgR6Rty3IQWO7EPmdDP53b+uqmZlra/3N6d8gY5AoIBADxkBk23hEQSlg7f3qbC | ||||
| vhONo+bBzgzLAcZY05h1V/QAONvB+lT2oJln+e9cFt3jOkb43NqeqAeBRoG0FmPx | ||||
| kffSLpmt75Jq2AZTEFlfvOMOkPZbC10vr1gje/zV4xhKanqBAYhzyflWXZKx6Fc3 | ||||
| 6JbSzp7p/QzFMXbE9Fymj5FxcSiFjT9BQvZupyG/I52dWj/yvtXB4Uwq8gm2MDXq | ||||
| BzeD4KnJ6pqKsANtELPGoHf7cQawRdexcyKsOwcVRHmHXtNP9H00nE081RRjkzcX | ||||
| 3mqSAhGXcC7xjJMC8qAiN2g4QnV1pf8ul2/bQPpnd2BR3Leyu9SMcIxrPPG1J3XU | ||||
| 9eECggEBAMMhMURUfLSXIkreMfxH4rSqk0r2xQ1rE1ChAIBQPfyx4KWUkBTdpoiv | ||||
| uKcPzAgN+bm3Y5wRGwoE22Ac0lWobnzaIYyYN9N7HU+86q92ozWW1lCUEE0kBt2r | ||||
| FnWCD/3B0LOX2Cn8HHYzroRmzMlRvBa7/GO1dqURz/OzjTWN0+k9mgE7oS5M8fQV | ||||
| AS3mxXZMPKSB0xTfJoXW8ui9MQZHcNSkNORNP/2doCkR2qDUkazbhi/3ghLmDGVJ | ||||
| p5OrIPQUwcp1bFOciX22fAaZwoa63ng3K+WZjSqqma05AiOc59MhDLAu6a0rKKO1 | ||||
| W3079UVfBB4hkfN2721fqyj+r/0z+R0= | ||||
| -----END PRIVATE KEY----- | ||||
							
								
								
									
										4
									
								
								tests/compose/core/00_create_users.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										4
									
								
								tests/compose/core/00_create_users.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| echo "Creating users ..." | ||||
| docker-compose -f tests/compose/core/docker-compose.yml exec admin flask mailu admin admin mailu.io password || exit 1 | ||||
| docker-compose -f tests/compose/core/docker-compose.yml exec admin flask mailu user user mailu.io 'password' 'SHA512-CRYPT' || exit 1 | ||||
| echo "Admin and user successfully created!" | ||||
							
								
								
									
										1
									
								
								tests/compose/core/01_email_test.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								tests/compose/core/01_email_test.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1 @@ | ||||
| python3 tests/email_test.py message-core | ||||
							
								
								
									
										80
									
								
								tests/compose/core/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								tests/compose/core/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| # This file is auto-generated by the Mailu configuration wizard. | ||||
| # Please read the documentation before attempting any change. | ||||
| # Generated for compose flavor | ||||
|  | ||||
| version: '3.6' | ||||
|  | ||||
| services: | ||||
|  | ||||
|   # External dependencies | ||||
|   redis: | ||||
|     image: redis:alpine | ||||
|     restart: always | ||||
|     volumes: | ||||
|       - "/mailu/redis:/data" | ||||
|  | ||||
|   # Core services | ||||
|   front: | ||||
|     image: ${DOCKER_ORG:-mailu}/nginx:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     logging: | ||||
|       driver: json-file | ||||
|     ports: | ||||
|       - "127.0.0.1:80:80" | ||||
|       - "127.0.0.1:443:443" | ||||
|       - "127.0.0.1:25:25" | ||||
|       - "127.0.0.1:465:465" | ||||
|       - "127.0.0.1:587:587" | ||||
|       - "127.0.0.1:110:110" | ||||
|       - "127.0.0.1:995:995" | ||||
|       - "127.0.0.1:143:143" | ||||
|       - "127.0.0.1:993:993" | ||||
|     volumes: | ||||
|       - "/mailu/certs:/certs" | ||||
|  | ||||
|   admin: | ||||
|     image: ${DOCKER_ORG:-mailu}/admin:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/data:/data" | ||||
|       - "/mailu/dkim:/dkim" | ||||
|     depends_on: | ||||
|       - redis | ||||
|  | ||||
|   imap: | ||||
|     image: ${DOCKER_ORG:-mailu}/dovecot:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/mail:/mail" | ||||
|       - "/mailu/overrides:/overrides" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   smtp: | ||||
|     image: ${DOCKER_ORG:-mailu}/postfix:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/overrides:/overrides" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   antispam: | ||||
|     image: ${DOCKER_ORG:-mailu}/rspamd:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/filter:/var/lib/rspamd" | ||||
|       - "/mailu/dkim:/dkim" | ||||
|       - "/mailu/overrides/rspamd:/etc/rspamd/override.d" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   # Optional services | ||||
|  | ||||
|  | ||||
|  | ||||
|   # Webmail | ||||
| @@ -1,31 +1,35 @@ | ||||
| # Mailu main configuration file | ||||
| # | ||||
| # Most configuration variables can be modified through the Web interface, | ||||
| # these few settings must however be configured before starting the mail | ||||
| # server and require a restart upon change. | ||||
| # Generated for compose flavor | ||||
| # | ||||
| # This file is autogenerated by the configuration management wizard. | ||||
| # For a detailed list of configuration variables, see the documentation at | ||||
| # https://mailu.io | ||||
| 
 | ||||
| ################################### | ||||
| # Common configuration variables | ||||
| ################################### | ||||
| 
 | ||||
| # Set this to the path where Mailu data and configuration is stored | ||||
| ROOT=/mailu | ||||
| # This variable is now set directly in `docker-compose.yml by the setup utility | ||||
| # ROOT=/mailu | ||||
| 
 | ||||
| # Mailu version to run (1.0, 1.1, etc. or master) | ||||
| #VERSION=master | ||||
| 
 | ||||
| # Set to a randomly generated 16 bytes string | ||||
| SECRET_KEY=ChangeMeChangeMe | ||||
| SECRET_KEY=HGZCYGVI6FVG31HS | ||||
| 
 | ||||
| # Address where listening ports should bind | ||||
| BIND_ADDRESS4=127.0.0.1 | ||||
| #BIND_ADDRESS6=::1 | ||||
| # This variables are now set directly in `docker-compose.yml by the setup utility | ||||
| # PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1) | ||||
| # PUBLIC_IPV6=  (default: ::1) | ||||
| 
 | ||||
| # Main mail domain | ||||
| DOMAIN=mailu.io | ||||
| 
 | ||||
| # Hostnames for this server, separated with comas | ||||
| HOSTNAMES=mail.mailu.io,alternative.mailu.io,yetanother.mailu.io | ||||
| HOSTNAMES=localhost | ||||
| 
 | ||||
| # Postmaster local part (will append the main mail domain) | ||||
| POSTMASTER=admin | ||||
| @@ -34,7 +38,7 @@ POSTMASTER=admin | ||||
| TLS_FLAVOR=cert | ||||
| 
 | ||||
| # Authentication rate limit (per source IP address) | ||||
| AUTH_RATELIMIT=10/minute;1000/hour | ||||
| AUTH_RATELIMIT=10/minute;1000/hour  | ||||
| 
 | ||||
| # Opt-out of statistics, replace with "True" to opt out | ||||
| DISABLE_STATISTICS=False | ||||
| @@ -44,7 +48,7 @@ DISABLE_STATISTICS=False | ||||
| ################################### | ||||
| 
 | ||||
| # Expose the admin interface (value: true, false) | ||||
| ADMIN=false | ||||
| ADMIN=true | ||||
| 
 | ||||
| # Choose which webmail to run if any (values: roundcube, rainloop, none) | ||||
| WEBMAIL=none | ||||
| @@ -53,7 +57,10 @@ WEBMAIL=none | ||||
| WEBDAV=none | ||||
| 
 | ||||
| # Antivirus solution (value: clamav, none) | ||||
| ANTIVIRUS=none | ||||
| #ANTIVIRUS=none | ||||
| 
 | ||||
| #Antispam solution | ||||
| ANTISPAM=none | ||||
| 
 | ||||
| ################################### | ||||
| # Mail settings | ||||
| @@ -65,7 +72,7 @@ MESSAGE_SIZE_LIMIT=50000000 | ||||
| 
 | ||||
| # Networks granted relay permissions, make sure that you include your Docker | ||||
| # internal network (default to 172.17.0.0/16) | ||||
| RELAYNETS=172.16.0.0/12 | ||||
| RELAYNETS=172.17.0.0/16 | ||||
| 
 | ||||
| # Will relay all outgoing mails if configured | ||||
| RELAYHOST= | ||||
| @@ -74,18 +81,12 @@ RELAYHOST= | ||||
| FETCHMAIL_DELAY=600 | ||||
| 
 | ||||
| # Recipient delimiter, character used to delimiter localpart from custom address part | ||||
| # e.g. localpart+custom@domain;tld | ||||
| RECIPIENT_DELIMITER=+ | ||||
| 
 | ||||
| # DMARC rua and ruf email | ||||
| DMARC_RUA=admin | ||||
| DMARC_RUF=admin | ||||
| 
 | ||||
| # Welcome email, enable and set a topic and body if you wish to send welcome | ||||
| # emails to all users. | ||||
| WELCOME=false | ||||
| WELCOME_SUBJECT=Welcome to your new email account | ||||
| WELCOME_BODY=Welcome to your new email account, if you can read this, then it is configured properly! | ||||
| 
 | ||||
| # Maildir Compression | ||||
| # choose compression-method, default: none (value: bz2, gz) | ||||
| @@ -109,12 +110,7 @@ SITENAME=Mailu | ||||
| # Linked Website URL | ||||
| WEBSITE=https://mailu.io | ||||
| 
 | ||||
| # Registration reCaptcha settings (warning, this has some privacy impact) | ||||
| # RECAPTCHA_PUBLIC_KEY= | ||||
| # RECAPTCHA_PRIVATE_KEY= | ||||
| 
 | ||||
| # Domain registration, uncomment to enable | ||||
| # DOMAIN_REGISTRATION=true | ||||
| 
 | ||||
| ################################### | ||||
| # Advanced settings | ||||
| @@ -124,17 +120,20 @@ WEBSITE=https://mailu.io | ||||
| # json-file (default) | ||||
| # journald (On systemd platforms, useful for Fail2Ban integration) | ||||
| # syslog (Non systemd platforms, Fail2Ban integration. Disables `docker-compose log` for front!) | ||||
| LOG_DRIVER=json-file | ||||
| # LOG_DRIVER=json-file | ||||
| 
 | ||||
| # Docker-compose project name, this will prepended to containers names. | ||||
| #COMPOSE_PROJECT_NAME=mailu | ||||
| COMPOSE_PROJECT_NAME=mailu | ||||
| 
 | ||||
| # Default password scheme used for newly created accounts and changed passwords | ||||
| # (value: SHA512-CRYPT, SHA256-CRYPT, MD5-CRYPT, CRYPT) | ||||
| PASSWORD_SCHEME=SHA512-CRYPT | ||||
| # (value: BLF-CRYPT, SHA512-CRYPT, SHA256-CRYPT, MD5-CRYPT, CRYPT) | ||||
| PASSWORD_SCHEME=BLF-CRYPT | ||||
| 
 | ||||
| # Header to take the real ip from | ||||
| REAL_IP_HEADER= | ||||
| 
 | ||||
| # IPs for nginx set_real_ip_from (CIDR list separated by commas) | ||||
| REAL_IP_FROM= | ||||
| 
 | ||||
| # choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) | ||||
| REJECT_UNLISTED_RECIPIENT= | ||||
							
								
								
									
										84
									
								
								tests/compose/fetchmail/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								tests/compose/fetchmail/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| # This file is auto-generated by the Mailu configuration wizard. | ||||
| # Please read the documentation before attempting any change. | ||||
| # Generated for compose flavor | ||||
|  | ||||
| version: '3.6' | ||||
|  | ||||
| services: | ||||
|  | ||||
|   # External dependencies | ||||
|   redis: | ||||
|     image: redis:alpine | ||||
|     restart: always | ||||
|     volumes: | ||||
|       - "/mailu/redis:/data" | ||||
|  | ||||
|   # Core services | ||||
|   front: | ||||
|     image: ${DOCKER_ORG:-mailu}/nginx:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     logging: | ||||
|       driver: json-file | ||||
|     ports: | ||||
|       - "127.0.0.1:80:80" | ||||
|       - "127.0.0.1:443:443" | ||||
|       - "127.0.0.1:25:25" | ||||
|       - "127.0.0.1:465:465" | ||||
|       - "127.0.0.1:587:587" | ||||
|       - "127.0.0.1:110:110" | ||||
|       - "127.0.0.1:995:995" | ||||
|       - "127.0.0.1:143:143" | ||||
|       - "127.0.0.1:993:993" | ||||
|     volumes: | ||||
|       - "/mailu/certs:/certs" | ||||
|  | ||||
|   admin: | ||||
|     image: ${DOCKER_ORG:-mailu}/admin:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/data:/data" | ||||
|       - "/mailu/dkim:/dkim" | ||||
|     depends_on: | ||||
|       - redis | ||||
|  | ||||
|   imap: | ||||
|     image: ${DOCKER_ORG:-mailu}/dovecot:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/mail:/mail" | ||||
|       - "/mailu/overrides:/overrides" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   smtp: | ||||
|     image: ${DOCKER_ORG:-mailu}/postfix:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/overrides:/overrides" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   antispam: | ||||
|     image: ${DOCKER_ORG:-mailu}/rspamd:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/filter:/var/lib/rspamd" | ||||
|       - "/mailu/dkim:/dkim" | ||||
|       - "/mailu/overrides/rspamd:/etc/rspamd/override.d" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   # Optional services | ||||
|  | ||||
|  | ||||
|   fetchmail: | ||||
|     image: ${DOCKER_ORG:-mailu}/fetchmail:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|  | ||||
|   # Webmail | ||||
							
								
								
									
										139
									
								
								tests/compose/fetchmail/mailu.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								tests/compose/fetchmail/mailu.env
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | ||||
| # Mailu main configuration file | ||||
| # | ||||
| # Generated for compose flavor | ||||
| # | ||||
| # This file is autogenerated by the configuration management wizard. | ||||
| # For a detailed list of configuration variables, see the documentation at | ||||
| # https://mailu.io | ||||
|  | ||||
| ################################### | ||||
| # Common configuration variables | ||||
| ################################### | ||||
|  | ||||
| # Set this to the path where Mailu data and configuration is stored | ||||
| # This variable is now set directly in `docker-compose.yml by the setup utility | ||||
| # ROOT=/mailu | ||||
|  | ||||
| # Mailu version to run (1.0, 1.1, etc. or master) | ||||
| #VERSION=master | ||||
|  | ||||
| # Set to a randomly generated 16 bytes string | ||||
| SECRET_KEY=JS48Q9KE3B6T97E6 | ||||
|  | ||||
| # Address where listening ports should bind | ||||
| # This variables are now set directly in `docker-compose.yml by the setup utility | ||||
| # PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1) | ||||
| # PUBLIC_IPV6=  (default: ::1) | ||||
|  | ||||
| # Main mail domain | ||||
| DOMAIN=mailu.io | ||||
|  | ||||
| # Hostnames for this server, separated with comas | ||||
| HOSTNAMES=localhost | ||||
|  | ||||
| # Postmaster local part (will append the main mail domain) | ||||
| POSTMASTER=admin | ||||
|  | ||||
| # Choose how secure connections will behave (value: letsencrypt, cert, notls, mail, mail-letsencrypt) | ||||
| TLS_FLAVOR=cert | ||||
|  | ||||
| # Authentication rate limit (per source IP address) | ||||
| AUTH_RATELIMIT=10/minute;1000/hour  | ||||
|  | ||||
| # Opt-out of statistics, replace with "True" to opt out | ||||
| DISABLE_STATISTICS=False | ||||
|  | ||||
| ################################### | ||||
| # Optional features | ||||
| ################################### | ||||
|  | ||||
| # Expose the admin interface (value: true, false) | ||||
| ADMIN=true | ||||
|  | ||||
| # Choose which webmail to run if any (values: roundcube, rainloop, none) | ||||
| WEBMAIL=none | ||||
|  | ||||
| # Dav server implementation (value: radicale, none) | ||||
| WEBDAV=none | ||||
|  | ||||
| # Antivirus solution (value: clamav, none) | ||||
| #ANTIVIRUS=none | ||||
|  | ||||
| #Antispam solution | ||||
| ANTISPAM=none | ||||
|  | ||||
| ################################### | ||||
| # Mail settings | ||||
| ################################### | ||||
|  | ||||
| # Message size limit in bytes | ||||
| # Default: accept messages up to 50MB | ||||
| MESSAGE_SIZE_LIMIT=50000000 | ||||
|  | ||||
| # Networks granted relay permissions, make sure that you include your Docker | ||||
| # internal network (default to 172.17.0.0/16) | ||||
| RELAYNETS=172.17.0.0/16 | ||||
|  | ||||
| # Will relay all outgoing mails if configured | ||||
| RELAYHOST= | ||||
|  | ||||
| # Fetchmail delay | ||||
| FETCHMAIL_DELAY=600 | ||||
|  | ||||
| # Recipient delimiter, character used to delimiter localpart from custom address part | ||||
| RECIPIENT_DELIMITER=+ | ||||
|  | ||||
| # DMARC rua and ruf email | ||||
| DMARC_RUA=admin | ||||
| DMARC_RUF=admin | ||||
|  | ||||
|  | ||||
| # Maildir Compression | ||||
| # choose compression-method, default: none (value: bz2, gz) | ||||
| COMPRESSION= | ||||
| # change compression-level, default: 6 (value: 1-9) | ||||
| COMPRESSION_LEVEL= | ||||
|  | ||||
| ################################### | ||||
| # Web settings | ||||
| ################################### | ||||
|  | ||||
| # Path to the admin interface if enabled | ||||
| WEB_ADMIN=/admin | ||||
|  | ||||
| # Path to the webmail if enabled | ||||
| WEB_WEBMAIL=/webmail | ||||
|  | ||||
| # Website name | ||||
| SITENAME=Mailu | ||||
|  | ||||
| # Linked Website URL | ||||
| WEBSITE=https://mailu.io | ||||
|  | ||||
|  | ||||
|  | ||||
| ################################### | ||||
| # Advanced settings | ||||
| ################################### | ||||
|  | ||||
| # Log driver for front service. Possible values: | ||||
| # json-file (default) | ||||
| # journald (On systemd platforms, useful for Fail2Ban integration) | ||||
| # syslog (Non systemd platforms, Fail2Ban integration. Disables `docker-compose log` for front!) | ||||
| # LOG_DRIVER=json-file | ||||
|  | ||||
| # Docker-compose project name, this will prepended to containers names. | ||||
| COMPOSE_PROJECT_NAME=mailu | ||||
|  | ||||
| # Default password scheme used for newly created accounts and changed passwords | ||||
| # (value: BLF-CRYPT, SHA512-CRYPT, SHA256-CRYPT, MD5-CRYPT, CRYPT) | ||||
| PASSWORD_SCHEME=BLF-CRYPT | ||||
|  | ||||
| # Header to take the real ip from | ||||
| REAL_IP_HEADER= | ||||
|  | ||||
| # IPs for nginx set_real_ip_from (CIDR list separated by commas) | ||||
| REAL_IP_FROM= | ||||
|  | ||||
| # choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) | ||||
| REJECT_UNLISTED_RECIPIENT= | ||||
							
								
								
									
										6
									
								
								tests/compose/filters/01_email_test.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										6
									
								
								tests/compose/filters/01_email_test.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| python3 tests/email_test.py message-virus "tests/compose/filters/eicar.com" | ||||
| if [ $? -eq 99 ]; then | ||||
| 	exit 0 | ||||
| else | ||||
| 	exit 1 | ||||
| fi | ||||
							
								
								
									
										86
									
								
								tests/compose/filters/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								tests/compose/filters/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| # This file is auto-generated by the Mailu configuration wizard. | ||||
| # Please read the documentation before attempting any change. | ||||
| # Generated for compose flavor | ||||
|  | ||||
| version: '3.6' | ||||
|  | ||||
| services: | ||||
|  | ||||
|   # External dependencies | ||||
|   redis: | ||||
|     image: redis:alpine | ||||
|     restart: always | ||||
|     volumes: | ||||
|       - "/mailu/redis:/data" | ||||
|  | ||||
|   # Core services | ||||
|   front: | ||||
|     image: ${DOCKER_ORG:-mailu}/nginx:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     logging: | ||||
|       driver: json-file | ||||
|     ports: | ||||
|       - "127.0.0.1:80:80" | ||||
|       - "127.0.0.1:443:443" | ||||
|       - "127.0.0.1:25:25" | ||||
|       - "127.0.0.1:465:465" | ||||
|       - "127.0.0.1:587:587" | ||||
|       - "127.0.0.1:110:110" | ||||
|       - "127.0.0.1:995:995" | ||||
|       - "127.0.0.1:143:143" | ||||
|       - "127.0.0.1:993:993" | ||||
|     volumes: | ||||
|       - "/mailu/certs:/certs" | ||||
|  | ||||
|   admin: | ||||
|     image: ${DOCKER_ORG:-mailu}/admin:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/data:/data" | ||||
|       - "/mailu/dkim:/dkim" | ||||
|     depends_on: | ||||
|       - redis | ||||
|  | ||||
|   imap: | ||||
|     image: ${DOCKER_ORG:-mailu}/dovecot:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/mail:/mail" | ||||
|       - "/mailu/overrides:/overrides" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   smtp: | ||||
|     image: ${DOCKER_ORG:-mailu}/postfix:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/overrides:/overrides" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   antispam: | ||||
|     image: ${DOCKER_ORG:-mailu}/rspamd:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/filter:/var/lib/rspamd" | ||||
|       - "/mailu/dkim:/dkim" | ||||
|       - "/mailu/overrides/rspamd:/etc/rspamd/override.d" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   # Optional services | ||||
|   antivirus: | ||||
|     image: ${DOCKER_ORG:-mailu}/clamav:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/filter:/data" | ||||
|  | ||||
|  | ||||
|  | ||||
|   # Webmail | ||||
							
								
								
									
										1
									
								
								tests/compose/filters/eicar.com
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/compose/filters/eicar.com
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H* | ||||
							
								
								
									
										139
									
								
								tests/compose/filters/mailu.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								tests/compose/filters/mailu.env
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | ||||
| # Mailu main configuration file | ||||
| # | ||||
| # Generated for compose flavor | ||||
| # | ||||
| # This file is autogenerated by the configuration management wizard. | ||||
| # For a detailed list of configuration variables, see the documentation at | ||||
| # https://mailu.io | ||||
|  | ||||
| ################################### | ||||
| # Common configuration variables | ||||
| ################################### | ||||
|  | ||||
| # Set this to the path where Mailu data and configuration is stored | ||||
| # This variable is now set directly in `docker-compose.yml by the setup utility | ||||
| # ROOT=/mailu | ||||
|  | ||||
| # Mailu version to run (1.0, 1.1, etc. or master) | ||||
| #VERSION=master | ||||
|  | ||||
| # Set to a randomly generated 16 bytes string | ||||
| SECRET_KEY=11H6XURLGE7GW3U1 | ||||
|  | ||||
| # Address where listening ports should bind | ||||
| # This variables are now set directly in `docker-compose.yml by the setup utility | ||||
| # PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1) | ||||
| # PUBLIC_IPV6=  (default: ::1) | ||||
|  | ||||
| # Main mail domain | ||||
| DOMAIN=mailu.io | ||||
|  | ||||
| # Hostnames for this server, separated with comas | ||||
| HOSTNAMES=localhost | ||||
|  | ||||
| # Postmaster local part (will append the main mail domain) | ||||
| POSTMASTER=admin | ||||
|  | ||||
| # Choose how secure connections will behave (value: letsencrypt, cert, notls, mail, mail-letsencrypt) | ||||
| TLS_FLAVOR=cert | ||||
|  | ||||
| # Authentication rate limit (per source IP address) | ||||
| AUTH_RATELIMIT=10/minute;1000/hour  | ||||
|  | ||||
| # Opt-out of statistics, replace with "True" to opt out | ||||
| DISABLE_STATISTICS=False | ||||
|  | ||||
| ################################### | ||||
| # Optional features | ||||
| ################################### | ||||
|  | ||||
| # Expose the admin interface (value: true, false) | ||||
| ADMIN=true | ||||
|  | ||||
| # Choose which webmail to run if any (values: roundcube, rainloop, none) | ||||
| WEBMAIL=none | ||||
|  | ||||
| # Dav server implementation (value: radicale, none) | ||||
| WEBDAV=none | ||||
|  | ||||
| # Antivirus solution (value: clamav, none) | ||||
| #ANTIVIRUS=clamav | ||||
|  | ||||
| #Antispam solution | ||||
| ANTISPAM=none | ||||
|  | ||||
| ################################### | ||||
| # Mail settings | ||||
| ################################### | ||||
|  | ||||
| # Message size limit in bytes | ||||
| # Default: accept messages up to 50MB | ||||
| MESSAGE_SIZE_LIMIT=50000000 | ||||
|  | ||||
| # Networks granted relay permissions, make sure that you include your Docker | ||||
| # internal network (default to 172.17.0.0/16) | ||||
| RELAYNETS=172.17.0.0/16 | ||||
|  | ||||
| # Will relay all outgoing mails if configured | ||||
| RELAYHOST= | ||||
|  | ||||
| # Fetchmail delay | ||||
| FETCHMAIL_DELAY=600 | ||||
|  | ||||
| # Recipient delimiter, character used to delimiter localpart from custom address part | ||||
| RECIPIENT_DELIMITER=+ | ||||
|  | ||||
| # DMARC rua and ruf email | ||||
| DMARC_RUA=admin | ||||
| DMARC_RUF=admin | ||||
|  | ||||
|  | ||||
| # Maildir Compression | ||||
| # choose compression-method, default: none (value: bz2, gz) | ||||
| COMPRESSION= | ||||
| # change compression-level, default: 6 (value: 1-9) | ||||
| COMPRESSION_LEVEL= | ||||
|  | ||||
| ################################### | ||||
| # Web settings | ||||
| ################################### | ||||
|  | ||||
| # Path to the admin interface if enabled | ||||
| WEB_ADMIN=/admin | ||||
|  | ||||
| # Path to the webmail if enabled | ||||
| WEB_WEBMAIL=/webmail | ||||
|  | ||||
| # Website name | ||||
| SITENAME=Mailu | ||||
|  | ||||
| # Linked Website URL | ||||
| WEBSITE=https://mailu.io | ||||
|  | ||||
|  | ||||
|  | ||||
| ################################### | ||||
| # Advanced settings | ||||
| ################################### | ||||
|  | ||||
| # Log driver for front service. Possible values: | ||||
| # json-file (default) | ||||
| # journald (On systemd platforms, useful for Fail2Ban integration) | ||||
| # syslog (Non systemd platforms, Fail2Ban integration. Disables `docker-compose log` for front!) | ||||
| # LOG_DRIVER=json-file | ||||
|  | ||||
| # Docker-compose project name, this will prepended to containers names. | ||||
| COMPOSE_PROJECT_NAME=mailu | ||||
|  | ||||
| # Default password scheme used for newly created accounts and changed passwords | ||||
| # (value: BLF-CRYPT, SHA512-CRYPT, SHA256-CRYPT, MD5-CRYPT, CRYPT) | ||||
| PASSWORD_SCHEME=BLF-CRYPT | ||||
|  | ||||
| # Header to take the real ip from | ||||
| REAL_IP_HEADER= | ||||
|  | ||||
| # IPs for nginx set_real_ip_from (CIDR list separated by commas) | ||||
| REAL_IP_FROM= | ||||
|  | ||||
| # choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) | ||||
| REJECT_UNLISTED_RECIPIENT= | ||||
							
								
								
									
										88
									
								
								tests/compose/rainloop/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								tests/compose/rainloop/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| # This file is auto-generated by the Mailu configuration wizard. | ||||
| # Please read the documentation before attempting any change. | ||||
| # Generated for compose flavor | ||||
|  | ||||
| version: '3.6' | ||||
|  | ||||
| services: | ||||
|  | ||||
|   # External dependencies | ||||
|   redis: | ||||
|     image: redis:alpine | ||||
|     restart: always | ||||
|     volumes: | ||||
|       - "/mailu/redis:/data" | ||||
|  | ||||
|   # Core services | ||||
|   front: | ||||
|     image: ${DOCKER_ORG:-mailu}/nginx:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     logging: | ||||
|       driver: json-file | ||||
|     ports: | ||||
|       - "127.0.0.1:80:80" | ||||
|       - "127.0.0.1:443:443" | ||||
|       - "127.0.0.1:25:25" | ||||
|       - "127.0.0.1:465:465" | ||||
|       - "127.0.0.1:587:587" | ||||
|       - "127.0.0.1:110:110" | ||||
|       - "127.0.0.1:995:995" | ||||
|       - "127.0.0.1:143:143" | ||||
|       - "127.0.0.1:993:993" | ||||
|     volumes: | ||||
|       - "/mailu/certs:/certs" | ||||
|  | ||||
|   admin: | ||||
|     image: ${DOCKER_ORG:-mailu}/admin:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/data:/data" | ||||
|       - "/mailu/dkim:/dkim" | ||||
|     depends_on: | ||||
|       - redis | ||||
|  | ||||
|   imap: | ||||
|     image: ${DOCKER_ORG:-mailu}/dovecot:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/mail:/mail" | ||||
|       - "/mailu/overrides:/overrides" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   smtp: | ||||
|     image: ${DOCKER_ORG:-mailu}/postfix:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/overrides:/overrides" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   antispam: | ||||
|     image: ${DOCKER_ORG:-mailu}/rspamd:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/filter:/var/lib/rspamd" | ||||
|       - "/mailu/dkim:/dkim" | ||||
|       - "/mailu/overrides/rspamd:/etc/rspamd/override.d" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   # Optional services | ||||
|  | ||||
|  | ||||
|  | ||||
|   # Webmail | ||||
|   webmail: | ||||
|     image: ${DOCKER_ORG:-mailu}/rainloop:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/webmail:/data" | ||||
|     depends_on: | ||||
|       - imap | ||||
							
								
								
									
										139
									
								
								tests/compose/rainloop/mailu.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								tests/compose/rainloop/mailu.env
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | ||||
| # Mailu main configuration file | ||||
| # | ||||
| # Generated for compose flavor | ||||
| # | ||||
| # This file is autogenerated by the configuration management wizard. | ||||
| # For a detailed list of configuration variables, see the documentation at | ||||
| # https://mailu.io | ||||
|  | ||||
| ################################### | ||||
| # Common configuration variables | ||||
| ################################### | ||||
|  | ||||
| # Set this to the path where Mailu data and configuration is stored | ||||
| # This variable is now set directly in `docker-compose.yml by the setup utility | ||||
| # ROOT=/mailu | ||||
|  | ||||
| # Mailu version to run (1.0, 1.1, etc. or master) | ||||
| #VERSION=master | ||||
|  | ||||
| # Set to a randomly generated 16 bytes string | ||||
| SECRET_KEY=V5J4SHRYVW9PZIQU | ||||
|  | ||||
| # Address where listening ports should bind | ||||
| # This variables are now set directly in `docker-compose.yml by the setup utility | ||||
| # PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1) | ||||
| # PUBLIC_IPV6=  (default: ::1) | ||||
|  | ||||
| # Main mail domain | ||||
| DOMAIN=mailu.io | ||||
|  | ||||
| # Hostnames for this server, separated with comas | ||||
| HOSTNAMES=localhost | ||||
|  | ||||
| # Postmaster local part (will append the main mail domain) | ||||
| POSTMASTER=admin | ||||
|  | ||||
| # Choose how secure connections will behave (value: letsencrypt, cert, notls, mail, mail-letsencrypt) | ||||
| TLS_FLAVOR=cert | ||||
|  | ||||
| # Authentication rate limit (per source IP address) | ||||
| AUTH_RATELIMIT=10/minute;1000/hour  | ||||
|  | ||||
| # Opt-out of statistics, replace with "True" to opt out | ||||
| DISABLE_STATISTICS=False | ||||
|  | ||||
| ################################### | ||||
| # Optional features | ||||
| ################################### | ||||
|  | ||||
| # Expose the admin interface (value: true, false) | ||||
| ADMIN=true | ||||
|  | ||||
| # Choose which webmail to run if any (values: roundcube, rainloop, none) | ||||
| WEBMAIL=rainloop | ||||
|  | ||||
| # Dav server implementation (value: radicale, none) | ||||
| WEBDAV=none | ||||
|  | ||||
| # Antivirus solution (value: clamav, none) | ||||
| #ANTIVIRUS=none | ||||
|  | ||||
| #Antispam solution | ||||
| ANTISPAM=none | ||||
|  | ||||
| ################################### | ||||
| # Mail settings | ||||
| ################################### | ||||
|  | ||||
| # Message size limit in bytes | ||||
| # Default: accept messages up to 50MB | ||||
| MESSAGE_SIZE_LIMIT=50000000 | ||||
|  | ||||
| # Networks granted relay permissions, make sure that you include your Docker | ||||
| # internal network (default to 172.17.0.0/16) | ||||
| RELAYNETS=172.17.0.0/16 | ||||
|  | ||||
| # Will relay all outgoing mails if configured | ||||
| RELAYHOST= | ||||
|  | ||||
| # Fetchmail delay | ||||
| FETCHMAIL_DELAY=600 | ||||
|  | ||||
| # Recipient delimiter, character used to delimiter localpart from custom address part | ||||
| RECIPIENT_DELIMITER=+ | ||||
|  | ||||
| # DMARC rua and ruf email | ||||
| DMARC_RUA=admin | ||||
| DMARC_RUF=admin | ||||
|  | ||||
|  | ||||
| # Maildir Compression | ||||
| # choose compression-method, default: none (value: bz2, gz) | ||||
| COMPRESSION= | ||||
| # change compression-level, default: 6 (value: 1-9) | ||||
| COMPRESSION_LEVEL= | ||||
|  | ||||
| ################################### | ||||
| # Web settings | ||||
| ################################### | ||||
|  | ||||
| # Path to the admin interface if enabled | ||||
| WEB_ADMIN=/admin | ||||
|  | ||||
| # Path to the webmail if enabled | ||||
| WEB_WEBMAIL=/webmail | ||||
|  | ||||
| # Website name | ||||
| SITENAME=Mailu | ||||
|  | ||||
| # Linked Website URL | ||||
| WEBSITE=https://mailu.io | ||||
|  | ||||
|  | ||||
|  | ||||
| ################################### | ||||
| # Advanced settings | ||||
| ################################### | ||||
|  | ||||
| # Log driver for front service. Possible values: | ||||
| # json-file (default) | ||||
| # journald (On systemd platforms, useful for Fail2Ban integration) | ||||
| # syslog (Non systemd platforms, Fail2Ban integration. Disables `docker-compose log` for front!) | ||||
| # LOG_DRIVER=json-file | ||||
|  | ||||
| # Docker-compose project name, this will prepended to containers names. | ||||
| COMPOSE_PROJECT_NAME=mailu | ||||
|  | ||||
| # Default password scheme used for newly created accounts and changed passwords | ||||
| # (value: BLF-CRYPT, SHA512-CRYPT, SHA256-CRYPT, MD5-CRYPT, CRYPT) | ||||
| PASSWORD_SCHEME=BLF-CRYPT | ||||
|  | ||||
| # Header to take the real ip from | ||||
| REAL_IP_HEADER= | ||||
|  | ||||
| # IPs for nginx set_real_ip_from (CIDR list separated by commas) | ||||
| REAL_IP_FROM= | ||||
|  | ||||
| # choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) | ||||
| REJECT_UNLISTED_RECIPIENT= | ||||
							
								
								
									
										88
									
								
								tests/compose/roundcube/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								tests/compose/roundcube/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| # This file is auto-generated by the Mailu configuration wizard. | ||||
| # Please read the documentation before attempting any change. | ||||
| # Generated for compose flavor | ||||
|  | ||||
| version: '3.6' | ||||
|  | ||||
| services: | ||||
|  | ||||
|   # External dependencies | ||||
|   redis: | ||||
|     image: redis:alpine | ||||
|     restart: always | ||||
|     volumes: | ||||
|       - "/mailu/redis:/data" | ||||
|  | ||||
|   # Core services | ||||
|   front: | ||||
|     image: ${DOCKER_ORG:-mailu}/nginx:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     logging: | ||||
|       driver: json-file | ||||
|     ports: | ||||
|       - "127.0.0.1:80:80" | ||||
|       - "127.0.0.1:443:443" | ||||
|       - "127.0.0.1:25:25" | ||||
|       - "127.0.0.1:465:465" | ||||
|       - "127.0.0.1:587:587" | ||||
|       - "127.0.0.1:110:110" | ||||
|       - "127.0.0.1:995:995" | ||||
|       - "127.0.0.1:143:143" | ||||
|       - "127.0.0.1:993:993" | ||||
|     volumes: | ||||
|       - "/mailu/certs:/certs" | ||||
|  | ||||
|   admin: | ||||
|     image: ${DOCKER_ORG:-mailu}/admin:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/data:/data" | ||||
|       - "/mailu/dkim:/dkim" | ||||
|     depends_on: | ||||
|       - redis | ||||
|  | ||||
|   imap: | ||||
|     image: ${DOCKER_ORG:-mailu}/dovecot:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/mail:/mail" | ||||
|       - "/mailu/overrides:/overrides" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   smtp: | ||||
|     image: ${DOCKER_ORG:-mailu}/postfix:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/overrides:/overrides" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   antispam: | ||||
|     image: ${DOCKER_ORG:-mailu}/rspamd:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/filter:/var/lib/rspamd" | ||||
|       - "/mailu/dkim:/dkim" | ||||
|       - "/mailu/overrides/rspamd:/etc/rspamd/override.d" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   # Optional services | ||||
|  | ||||
|  | ||||
|  | ||||
|   # Webmail | ||||
|   webmail: | ||||
|     image: ${DOCKER_ORG:-mailu}/roundcube:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/webmail:/data" | ||||
|     depends_on: | ||||
|       - imap | ||||
							
								
								
									
										139
									
								
								tests/compose/roundcube/mailu.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								tests/compose/roundcube/mailu.env
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | ||||
| # Mailu main configuration file | ||||
| # | ||||
| # Generated for compose flavor | ||||
| # | ||||
| # This file is autogenerated by the configuration management wizard. | ||||
| # For a detailed list of configuration variables, see the documentation at | ||||
| # https://mailu.io | ||||
|  | ||||
| ################################### | ||||
| # Common configuration variables | ||||
| ################################### | ||||
|  | ||||
| # Set this to the path where Mailu data and configuration is stored | ||||
| # This variable is now set directly in `docker-compose.yml by the setup utility | ||||
| # ROOT=/mailu | ||||
|  | ||||
| # Mailu version to run (1.0, 1.1, etc. or master) | ||||
| #VERSION=master | ||||
|  | ||||
| # Set to a randomly generated 16 bytes string | ||||
| SECRET_KEY=PGGO2JRQ59QV3DW7 | ||||
|  | ||||
| # Address where listening ports should bind | ||||
| # This variables are now set directly in `docker-compose.yml by the setup utility | ||||
| # PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1) | ||||
| # PUBLIC_IPV6=  (default: ::1) | ||||
|  | ||||
| # Main mail domain | ||||
| DOMAIN=mailu.io | ||||
|  | ||||
| # Hostnames for this server, separated with comas | ||||
| HOSTNAMES=localhost | ||||
|  | ||||
| # Postmaster local part (will append the main mail domain) | ||||
| POSTMASTER=admin | ||||
|  | ||||
| # Choose how secure connections will behave (value: letsencrypt, cert, notls, mail, mail-letsencrypt) | ||||
| TLS_FLAVOR=cert | ||||
|  | ||||
| # Authentication rate limit (per source IP address) | ||||
| AUTH_RATELIMIT=10/minute;1000/hour  | ||||
|  | ||||
| # Opt-out of statistics, replace with "True" to opt out | ||||
| DISABLE_STATISTICS=False | ||||
|  | ||||
| ################################### | ||||
| # Optional features | ||||
| ################################### | ||||
|  | ||||
| # Expose the admin interface (value: true, false) | ||||
| ADMIN=true | ||||
|  | ||||
| # Choose which webmail to run if any (values: roundcube, rainloop, none) | ||||
| WEBMAIL=roundcube | ||||
|  | ||||
| # Dav server implementation (value: radicale, none) | ||||
| WEBDAV=none | ||||
|  | ||||
| # Antivirus solution (value: clamav, none) | ||||
| #ANTIVIRUS=none | ||||
|  | ||||
| #Antispam solution | ||||
| ANTISPAM=none | ||||
|  | ||||
| ################################### | ||||
| # Mail settings | ||||
| ################################### | ||||
|  | ||||
| # Message size limit in bytes | ||||
| # Default: accept messages up to 50MB | ||||
| MESSAGE_SIZE_LIMIT=50000000 | ||||
|  | ||||
| # Networks granted relay permissions, make sure that you include your Docker | ||||
| # internal network (default to 172.17.0.0/16) | ||||
| RELAYNETS=172.17.0.0/16 | ||||
|  | ||||
| # Will relay all outgoing mails if configured | ||||
| RELAYHOST= | ||||
|  | ||||
| # Fetchmail delay | ||||
| FETCHMAIL_DELAY=600 | ||||
|  | ||||
| # Recipient delimiter, character used to delimiter localpart from custom address part | ||||
| RECIPIENT_DELIMITER=+ | ||||
|  | ||||
| # DMARC rua and ruf email | ||||
| DMARC_RUA=admin | ||||
| DMARC_RUF=admin | ||||
|  | ||||
|  | ||||
| # Maildir Compression | ||||
| # choose compression-method, default: none (value: bz2, gz) | ||||
| COMPRESSION= | ||||
| # change compression-level, default: 6 (value: 1-9) | ||||
| COMPRESSION_LEVEL= | ||||
|  | ||||
| ################################### | ||||
| # Web settings | ||||
| ################################### | ||||
|  | ||||
| # Path to the admin interface if enabled | ||||
| WEB_ADMIN=/admin | ||||
|  | ||||
| # Path to the webmail if enabled | ||||
| WEB_WEBMAIL=/webmail | ||||
|  | ||||
| # Website name | ||||
| SITENAME=Mailu | ||||
|  | ||||
| # Linked Website URL | ||||
| WEBSITE=https://mailu.io | ||||
|  | ||||
|  | ||||
|  | ||||
| ################################### | ||||
| # Advanced settings | ||||
| ################################### | ||||
|  | ||||
| # Log driver for front service. Possible values: | ||||
| # json-file (default) | ||||
| # journald (On systemd platforms, useful for Fail2Ban integration) | ||||
| # syslog (Non systemd platforms, Fail2Ban integration. Disables `docker-compose log` for front!) | ||||
| # LOG_DRIVER=json-file | ||||
|  | ||||
| # Docker-compose project name, this will prepended to containers names. | ||||
| COMPOSE_PROJECT_NAME=mailu | ||||
|  | ||||
| # Default password scheme used for newly created accounts and changed passwords | ||||
| # (value: BLF-CRYPT, SHA512-CRYPT, SHA256-CRYPT, MD5-CRYPT, CRYPT) | ||||
| PASSWORD_SCHEME=BLF-CRYPT | ||||
|  | ||||
| # Header to take the real ip from | ||||
| REAL_IP_HEADER= | ||||
|  | ||||
| # IPs for nginx set_real_ip_from (CIDR list separated by commas) | ||||
| REAL_IP_FROM= | ||||
|  | ||||
| # choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) | ||||
| REJECT_UNLISTED_RECIPIENT= | ||||
| @@ -1,101 +0,0 @@ | ||||
| version: '2' | ||||
|  | ||||
| services: | ||||
|  | ||||
|   front: | ||||
|     image: $DOCKER_ORG/nginx:$VERSION | ||||
|     restart: 'no' | ||||
|     env_file: $PWD/.env | ||||
|     logging: | ||||
|       driver: $LOG_DRIVER | ||||
|     ports: | ||||
|     - "$BIND_ADDRESS4:80:80" | ||||
|     - "$BIND_ADDRESS4:443:443" | ||||
|     - "$BIND_ADDRESS4:110:110" | ||||
|     - "$BIND_ADDRESS4:143:143" | ||||
|     - "$BIND_ADDRESS4:993:993" | ||||
|     - "$BIND_ADDRESS4:995:995" | ||||
|     - "$BIND_ADDRESS4:25:25" | ||||
|     - "$BIND_ADDRESS4:465:465" | ||||
|     - "$BIND_ADDRESS4:587:587" | ||||
|     volumes: | ||||
|       - "$ROOT/certs:/certs" | ||||
|  | ||||
|   redis: | ||||
|     image: redis:alpine | ||||
|     restart: 'no' | ||||
|     volumes: | ||||
|       - "$ROOT/redis:/data" | ||||
|  | ||||
|   imap: | ||||
|     image: $DOCKER_ORG/dovecot:$VERSION | ||||
|     restart: 'no' | ||||
|     env_file: $PWD/.env | ||||
|     volumes: | ||||
|       - "$ROOT/data:/data" | ||||
|       - "$ROOT/mail:/mail" | ||||
|       - "$ROOT/overrides:/overrides" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   smtp: | ||||
|     image: $DOCKER_ORG/postfix:$VERSION | ||||
|     restart: 'no' | ||||
|     env_file: $PWD/.env | ||||
|     volumes: | ||||
|       - "$ROOT/data:/data" | ||||
|       - "$ROOT/overrides:/overrides" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   antispam: | ||||
|     image: $DOCKER_ORG/rspamd:$VERSION | ||||
|     restart: 'no' | ||||
|     env_file: $PWD/.env | ||||
|     volumes: | ||||
|       - "$ROOT/filter:/var/lib/rspamd" | ||||
|       - "$ROOT/dkim:/dkim" | ||||
|       - "$ROOT/overrides/rspamd:/etc/rspamd/override.d" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   antivirus: | ||||
|     image: $DOCKER_ORG/$ANTIVIRUS:$VERSION | ||||
|     restart: 'no' | ||||
|     env_file: $PWD/.env | ||||
|     volumes: | ||||
|       - "$ROOT/filter:/data" | ||||
|  | ||||
|   webdav: | ||||
|     image: $DOCKER_ORG/$WEBDAV:$VERSION | ||||
|     restart: 'no' | ||||
|     env_file: $PWD/.env | ||||
|     volumes: | ||||
|       - "$ROOT/dav:/data" | ||||
|  | ||||
|   admin: | ||||
|     image: $DOCKER_ORG/admin:$VERSION | ||||
|     restart: 'no' | ||||
|     env_file: $PWD/.env | ||||
|     volumes: | ||||
|       - "$ROOT/data:/data" | ||||
|       - "$ROOT/dkim:/dkim" | ||||
|       - /var/run/docker.sock:/var/run/docker.sock:ro | ||||
|     depends_on: | ||||
|       - redis | ||||
|  | ||||
|   webmail: | ||||
|     image: "$DOCKER_ORG/$WEBMAIL:$VERSION" | ||||
|     restart: 'no' | ||||
|     env_file: $PWD/.env | ||||
|     volumes: | ||||
|       - "$ROOT/webmail:/data" | ||||
|     depends_on: | ||||
|       - imap | ||||
|  | ||||
|   fetchmail: | ||||
|     image: $DOCKER_ORG/fetchmail:$VERSION | ||||
|     restart: 'no' | ||||
|     env_file: $PWD/.env | ||||
|     volumes: | ||||
|       - "$ROOT/data:/data" | ||||
| @@ -1,57 +0,0 @@ | ||||
| #!/bin/bash | ||||
| containers=( | ||||
| 	webmail | ||||
| 	imap | ||||
| 	smtp | ||||
| 	antispam | ||||
| 	admin | ||||
| 	redis | ||||
| 	antivirus | ||||
| 	webdav | ||||
| #	fetchmail | ||||
| 	front | ||||
| ) | ||||
|  | ||||
| # Time to sleep in minutes after starting the containers | ||||
| WAIT=1 | ||||
|  | ||||
| containers_check() { | ||||
| 	status=0 | ||||
| 	for container in "${containers[@]}"; do | ||||
| 		name="${DOCKER_ORG}_${container}_1" | ||||
| 		echo "Checking $name" | ||||
| 		docker inspect "$name" | grep '"Status": "running"' || status=1 | ||||
| 	done | ||||
| 	docker ps -a | ||||
| 	return $status | ||||
| } | ||||
|  | ||||
| container_logs() { | ||||
| 	for container in "${containers[@]}"; do | ||||
| 		name="${DOCKER_ORG}_${container}_1" | ||||
|                 echo "Showing logs for $name" | ||||
|                 docker container logs "$name" | ||||
|         done | ||||
| } | ||||
|  | ||||
| clean() { | ||||
| 	docker-compose -f tests/compose/run.yml -p $DOCKER_ORG down || exit 1 | ||||
| 	rm -fv .env | ||||
| } | ||||
|  | ||||
| # Cleanup before callig exit | ||||
| die() { | ||||
| 	clean | ||||
| 	exit $1 | ||||
| } | ||||
|  | ||||
| for file in tests/compose/*.env ; do | ||||
| 	cp $file .env | ||||
| 	docker-compose -f tests/compose/run.yml -p $DOCKER_ORG up -d | ||||
| 	echo -e "\nSleeping for ${WAIT} minutes" # Clean terminal distortion from docker-compose in travis | ||||
| 	travis_wait sleep ${WAIT}m || sleep ${WAIT}m #Fallback sleep for local run | ||||
| 	container_logs | ||||
| 	containers_check || die 1 | ||||
| 	clean | ||||
| done | ||||
|  | ||||
							
								
								
									
										100
									
								
								tests/compose/test.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										100
									
								
								tests/compose/test.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| import sys | ||||
| import os | ||||
| import time | ||||
| import docker | ||||
| from colorama import Fore, Style | ||||
| import subprocess | ||||
|  | ||||
| # Declare variables for service name and sleep time | ||||
| test_name=sys.argv[1] | ||||
| timeout=int(sys.argv[2]) | ||||
| test_path="tests/compose/" + test_name + "/" | ||||
| compose_file=test_path + "docker-compose.yml" | ||||
|  | ||||
| client = docker.APIClient(base_url='unix://var/run/docker.sock') | ||||
|  | ||||
| containers = [] | ||||
|  | ||||
| # Stop containers | ||||
| def stop(exit_code): | ||||
|     print_logs() | ||||
|     sys.stdout.flush() | ||||
|     print(subprocess.check_output("docker-compose -f " + compose_file + " down", shell=True).decode()) | ||||
|     sys.exit(exit_code) | ||||
|  | ||||
| # Sleep for a defined amount of time | ||||
| def sleep(): | ||||
|     print(Fore.LIGHTMAGENTA_EX + "Sleeping for " + str(timeout) + "m" + Style.RESET_ALL) | ||||
|     time.sleep(timeout*60) | ||||
|  | ||||
| def health_checks(): | ||||
|     exit_code = 0 | ||||
|     #Iterating trough all containers dictionary | ||||
|     for container in client.containers(all=True): | ||||
|         #Perform "docker container inspect" on container based on container ID and save output to a dictionary | ||||
|         container_inspect = client.inspect_container(container['Id']) #Dict | ||||
|  | ||||
|         if "Health" in container_inspect['State'].keys(): | ||||
|             if container_inspect['State']['Health']['Status'] == "healthy": | ||||
|                 print(Fore.GREEN + "Health status for " + container_inspect['Name'].replace("/", "") + " : " + Fore.CYAN + container_inspect['State']['Health']['Status'] + Style.RESET_ALL) | ||||
|             if container_inspect['State']['Health']['Status'] != "healthy": | ||||
|                 print(Fore.RED + "Container " + container_inspect['Name'].replace("/", "") + " is " + Fore.YELLOW + container_inspect['State']['Health']['Status'] | ||||
|                       + Fore.RED + ", FailingStreak: " + Fore.YELLOW + str(container_inspect['State']['Health']['FailingStreak']) | ||||
|                       + Fore.RED + ", Log: " + Fore.YELLOW + str(container_inspect['State']['Health']['Log']) + Style.RESET_ALL) | ||||
|                 exit_code = 1 | ||||
|         else: | ||||
|             if container_inspect['State']['Status'] == "running": | ||||
|                 print(Fore.GREEN + "Running status for " + container_inspect['Name'].replace("/", "") + " : " + Fore.BLUE + container_inspect['State']['Status'] + Style.RESET_ALL) | ||||
|             if container_inspect['State']['Status'] != "running": | ||||
|                 print(Fore.RED + "Container " + container_inspect['Name'].replace("/", "") + " state is: " + Fore.YELLOW + container_inspect['State']['Status'] + Style.RESET_ALL) | ||||
|                 exit_code = 1 | ||||
|  | ||||
|         #Saving Id, Name and state to a new dictionary | ||||
|         containers_dict = {} | ||||
|         containers_dict['Name'] = container_inspect['Name'].replace("/", "") | ||||
|         containers_dict['Id'] = container_inspect['Id'] | ||||
|         containers_dict['State'] = container_inspect['State'] | ||||
|  | ||||
|         #Adding the generated dictionary to a list | ||||
|         containers.append(containers_dict) | ||||
|  | ||||
|     if exit_code != 0: | ||||
|         stop(exit_code) | ||||
|  | ||||
| def print_logs(): | ||||
|     print("Printing logs ...") | ||||
|     #Iterating through docker container inspect list and print logs | ||||
|     for container in containers: | ||||
|         print(Fore.LIGHTMAGENTA_EX + "Printing logs for: " + Fore.GREEN + container['Name'] + Style.RESET_ALL) | ||||
|         sys.stdout.flush() | ||||
|         print(subprocess.check_output('docker container logs ' + container['Name'], shell=True).decode()) | ||||
|  | ||||
| #Iterating over hooks in test folder and running them | ||||
| def hooks(): | ||||
|     print(Fore.LIGHTMAGENTA_EX + "Running hooks" + Style.RESET_ALL) | ||||
|     for test_file in sorted(os.listdir(test_path)): | ||||
|         try: | ||||
|             if test_file.endswith(".py"): | ||||
|                 sys.stdout.flush() | ||||
|                 print(subprocess.check_output("python3 " + test_path + test_file, shell=True).decode()) | ||||
|             elif test_file.endswith(".sh"): | ||||
|                 sys.stdout.flush() | ||||
|                 print(subprocess.check_output("./" + test_path + test_file, shell=True).decode()) | ||||
|         except subprocess.CalledProcessError as e: | ||||
|             sys.stderr.write("[ERROR]: output = %s, error code = %s\n" % (e.output.decode(), e.returncode)) | ||||
|             stop(1) | ||||
|  | ||||
| # Start up containers | ||||
| sys.stdout.flush() | ||||
| print(subprocess.check_output("docker-compose -f " + compose_file + " up -d", shell=True).decode()) | ||||
| print() | ||||
| sleep() | ||||
| print() | ||||
| sys.stdout.flush() | ||||
| print(subprocess.check_output("docker ps -a", shell=True).decode()) | ||||
| print() | ||||
| health_checks() | ||||
| print() | ||||
| hooks() | ||||
| print() | ||||
| stop(0) | ||||
							
								
								
									
										86
									
								
								tests/compose/webdav/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								tests/compose/webdav/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| # This file is auto-generated by the Mailu configuration wizard. | ||||
| # Please read the documentation before attempting any change. | ||||
| # Generated for compose flavor | ||||
|  | ||||
| version: '3.6' | ||||
|  | ||||
| services: | ||||
|  | ||||
|   # External dependencies | ||||
|   redis: | ||||
|     image: redis:alpine | ||||
|     restart: always | ||||
|     volumes: | ||||
|       - "/mailu/redis:/data" | ||||
|  | ||||
|   # Core services | ||||
|   front: | ||||
|     image: ${DOCKER_ORG:-mailu}/nginx:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     logging: | ||||
|       driver: json-file | ||||
|     ports: | ||||
|       - "127.0.0.1:80:80" | ||||
|       - "127.0.0.1:443:443" | ||||
|       - "127.0.0.1:25:25" | ||||
|       - "127.0.0.1:465:465" | ||||
|       - "127.0.0.1:587:587" | ||||
|       - "127.0.0.1:110:110" | ||||
|       - "127.0.0.1:995:995" | ||||
|       - "127.0.0.1:143:143" | ||||
|       - "127.0.0.1:993:993" | ||||
|     volumes: | ||||
|       - "/mailu/certs:/certs" | ||||
|  | ||||
|   admin: | ||||
|     image: ${DOCKER_ORG:-mailu}/admin:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/data:/data" | ||||
|       - "/mailu/dkim:/dkim" | ||||
|     depends_on: | ||||
|       - redis | ||||
|  | ||||
|   imap: | ||||
|     image: ${DOCKER_ORG:-mailu}/dovecot:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/mail:/mail" | ||||
|       - "/mailu/overrides:/overrides" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   smtp: | ||||
|     image: ${DOCKER_ORG:-mailu}/postfix:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/overrides:/overrides" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   antispam: | ||||
|     image: ${DOCKER_ORG:-mailu}/rspamd:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/filter:/var/lib/rspamd" | ||||
|       - "/mailu/dkim:/dkim" | ||||
|       - "/mailu/overrides/rspamd:/etc/rspamd/override.d" | ||||
|     depends_on: | ||||
|       - front | ||||
|  | ||||
|   # Optional services | ||||
|  | ||||
|   webdav: | ||||
|     image: ${DOCKER_ORG:-mailu}/radicale:${MAILU_VERSION:-master} | ||||
|     restart: always | ||||
|     env_file: mailu.env | ||||
|     volumes: | ||||
|       - "/mailu/dav:/data" | ||||
|  | ||||
|  | ||||
|   # Webmail | ||||
							
								
								
									
										139
									
								
								tests/compose/webdav/mailu.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								tests/compose/webdav/mailu.env
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | ||||
| # Mailu main configuration file | ||||
| # | ||||
| # Generated for compose flavor | ||||
| # | ||||
| # This file is autogenerated by the configuration management wizard. | ||||
| # For a detailed list of configuration variables, see the documentation at | ||||
| # https://mailu.io | ||||
|  | ||||
| ################################### | ||||
| # Common configuration variables | ||||
| ################################### | ||||
|  | ||||
| # Set this to the path where Mailu data and configuration is stored | ||||
| # This variable is now set directly in `docker-compose.yml by the setup utility | ||||
| # ROOT=/mailu | ||||
|  | ||||
| # Mailu version to run (1.0, 1.1, etc. or master) | ||||
| #VERSION=master | ||||
|  | ||||
| # Set to a randomly generated 16 bytes string | ||||
| SECRET_KEY=XVDDSWOAGVF5J9QJ | ||||
|  | ||||
| # Address where listening ports should bind | ||||
| # This variables are now set directly in `docker-compose.yml by the setup utility | ||||
| # PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1) | ||||
| # PUBLIC_IPV6=  (default: ::1) | ||||
|  | ||||
| # Main mail domain | ||||
| DOMAIN=mailu.io | ||||
|  | ||||
| # Hostnames for this server, separated with comas | ||||
| HOSTNAMES=localhost | ||||
|  | ||||
| # Postmaster local part (will append the main mail domain) | ||||
| POSTMASTER=admin | ||||
|  | ||||
| # Choose how secure connections will behave (value: letsencrypt, cert, notls, mail, mail-letsencrypt) | ||||
| TLS_FLAVOR=cert | ||||
|  | ||||
| # Authentication rate limit (per source IP address) | ||||
| AUTH_RATELIMIT=10/minute;1000/hour  | ||||
|  | ||||
| # Opt-out of statistics, replace with "True" to opt out | ||||
| DISABLE_STATISTICS=False | ||||
|  | ||||
| ################################### | ||||
| # Optional features | ||||
| ################################### | ||||
|  | ||||
| # Expose the admin interface (value: true, false) | ||||
| ADMIN=true | ||||
|  | ||||
| # Choose which webmail to run if any (values: roundcube, rainloop, none) | ||||
| WEBMAIL=none | ||||
|  | ||||
| # Dav server implementation (value: radicale, none) | ||||
| WEBDAV=radicale | ||||
|  | ||||
| # Antivirus solution (value: clamav, none) | ||||
| #ANTIVIRUS=none | ||||
|  | ||||
| #Antispam solution | ||||
| ANTISPAM=none | ||||
|  | ||||
| ################################### | ||||
| # Mail settings | ||||
| ################################### | ||||
|  | ||||
| # Message size limit in bytes | ||||
| # Default: accept messages up to 50MB | ||||
| MESSAGE_SIZE_LIMIT=50000000 | ||||
|  | ||||
| # Networks granted relay permissions, make sure that you include your Docker | ||||
| # internal network (default to 172.17.0.0/16) | ||||
| RELAYNETS=172.17.0.0/16 | ||||
|  | ||||
| # Will relay all outgoing mails if configured | ||||
| RELAYHOST= | ||||
|  | ||||
| # Fetchmail delay | ||||
| FETCHMAIL_DELAY=600 | ||||
|  | ||||
| # Recipient delimiter, character used to delimiter localpart from custom address part | ||||
| RECIPIENT_DELIMITER=+ | ||||
|  | ||||
| # DMARC rua and ruf email | ||||
| DMARC_RUA=admin | ||||
| DMARC_RUF=admin | ||||
|  | ||||
|  | ||||
| # Maildir Compression | ||||
| # choose compression-method, default: none (value: bz2, gz) | ||||
| COMPRESSION= | ||||
| # change compression-level, default: 6 (value: 1-9) | ||||
| COMPRESSION_LEVEL= | ||||
|  | ||||
| ################################### | ||||
| # Web settings | ||||
| ################################### | ||||
|  | ||||
| # Path to the admin interface if enabled | ||||
| WEB_ADMIN=/admin | ||||
|  | ||||
| # Path to the webmail if enabled | ||||
| WEB_WEBMAIL=/webmail | ||||
|  | ||||
| # Website name | ||||
| SITENAME=Mailu | ||||
|  | ||||
| # Linked Website URL | ||||
| WEBSITE=https://mailu.io | ||||
|  | ||||
|  | ||||
|  | ||||
| ################################### | ||||
| # Advanced settings | ||||
| ################################### | ||||
|  | ||||
| # Log driver for front service. Possible values: | ||||
| # json-file (default) | ||||
| # journald (On systemd platforms, useful for Fail2Ban integration) | ||||
| # syslog (Non systemd platforms, Fail2Ban integration. Disables `docker-compose log` for front!) | ||||
| # LOG_DRIVER=json-file | ||||
|  | ||||
| # Docker-compose project name, this will prepended to containers names. | ||||
| COMPOSE_PROJECT_NAME=mailu | ||||
|  | ||||
| # Default password scheme used for newly created accounts and changed passwords | ||||
| # (value: BLF-CRYPT, SHA512-CRYPT, SHA256-CRYPT, MD5-CRYPT, CRYPT) | ||||
| PASSWORD_SCHEME=BLF-CRYPT | ||||
|  | ||||
| # Header to take the real ip from | ||||
| REAL_IP_HEADER= | ||||
|  | ||||
| # IPs for nginx set_real_ip_from (CIDR list separated by commas) | ||||
| REAL_IP_FROM= | ||||
|  | ||||
| # choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) | ||||
| REJECT_UNLISTED_RECIPIENT= | ||||
							
								
								
									
										59
									
								
								tests/email_test.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										59
									
								
								tests/email_test.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| import smtplib | ||||
| import imaplib | ||||
| import time | ||||
| import sys | ||||
| from email.mime.multipart import MIMEMultipart | ||||
| from email.mime.text import MIMEText | ||||
| import ntpath | ||||
| from email.mime.base import MIMEBase | ||||
| from email import encoders | ||||
|  | ||||
| msg = MIMEMultipart() | ||||
| msg['From'] = "admin@mailu.io" | ||||
| msg['To'] = "user@mailu.io" | ||||
| msg['Subject'] = "File Test" | ||||
| msg.attach(MIMEText(sys.argv[1], 'plain')) | ||||
|  | ||||
| if len(sys.argv) == 3: | ||||
|     part = MIMEBase('application', 'octet-stream') | ||||
|     part.set_payload((open(sys.argv[2], "rb")).read()) | ||||
|     encoders.encode_base64(part) | ||||
|     part.add_header('Content-Disposition', "attachment; filename=%s" % ntpath.basename(sys.argv[2])) | ||||
|     msg.attach(part) | ||||
|  | ||||
| try: | ||||
|     smtp_server = smtplib.SMTP('localhost') | ||||
|     smtp_server.set_debuglevel(1) | ||||
|     smtp_server.connect('localhost', 587) | ||||
|     smtp_server.ehlo() | ||||
|     smtp_server.starttls() | ||||
|     smtp_server.ehlo() | ||||
|     smtp_server.login("admin@mailu.io", "password") | ||||
|      | ||||
|     smtp_server.sendmail("admin@mailu.io", "user@mailu.io", msg.as_string()) | ||||
|     smtp_server.quit() | ||||
| except: | ||||
|     sys.exit(25) | ||||
|  | ||||
| time.sleep(30) | ||||
|  | ||||
| try: | ||||
|     imap_server = imaplib.IMAP4_SSL('localhost') | ||||
|     imap_server.login('user@mailu.io', 'password') | ||||
| except: | ||||
|     sys.exit(110) | ||||
|  | ||||
| stat, count = imap_server.select('inbox') | ||||
| try: | ||||
|     stat, data = imap_server.fetch(count[0], '(UID BODY[TEXT])') | ||||
| except : | ||||
|     sys.exit(99) | ||||
|          | ||||
| if sys.argv[1] in str(data[0][1]): | ||||
|     print("Success sending and receiving email!") | ||||
| else: | ||||
|     print("Failed receiving email with message %s" % sys.argv[1]) | ||||
|     sys.exit(99) | ||||
|      | ||||
| imap_server.close() | ||||
| imap_server.logout() | ||||
							
								
								
									
										2
									
								
								tests/requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| docker | ||||
| colorama | ||||
| @@ -22,7 +22,7 @@ RUN apt-get update && apt-get install -y \ | ||||
|  && rm -rf /var/lib/apt/lists | ||||
|  | ||||
| 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 default.ini /default.ini | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| ; RainLoop Webmail configuration file | ||||
|  | ||||
| [webmail] | ||||
| attachment_size_limit = 25 | ||||
| attachment_size_limit = {{ MAX_FILESIZE }} | ||||
|  | ||||
| [security] | ||||
| allow_admin_panel = Off | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| date.timezone=UTC | ||||
| upload_max_filesize = 25M | ||||
| post_max_size = 25M | ||||
| upload_max_filesize = {{ MAX_FILESIZE }}M | ||||
| 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["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_/" | ||||
| shutil.rmtree(base + "domains/", ignore_errors=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("/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") | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
| RUN apt-get update && apt-get install -y \ | ||||
|       zlib1g-dev \ | ||||
|       zlib1g-dev python3-jinja2 \ | ||||
|  && docker-php-ext-install zip \ | ||||
|  && echo date.timezone=UTC > /usr/local/etc/php/conf.d/timezone.ini \ | ||||
|  && rm -rf /var/www/html/ \ | ||||
| @@ -22,7 +22,7 @@ RUN apt-get update && apt-get install -y \ | ||||
|  && chown -R www-data: logs temp \ | ||||
|  && 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 start.py /start.py | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| date.timezone=UTC | ||||
| upload_max_filesize = 25M | ||||
| post_max_size = 25M | ||||
| upload_max_filesize = {{ MAX_FILESIZE }}M | ||||
| post_max_size = {{ MAX_FILESIZE }}M | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,13 @@ | ||||
| #!/usr/bin/python3 | ||||
|  | ||||
| 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 | ||||
| os.system("mkdir -p /data/gpg") | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Tim Möhlmann
					Tim Möhlmann