commit 018d4f3844e393ebb117dc69b0d3d124df78aa5d Author: lhahn Date: Sun Aug 20 10:31:02 2023 +0200 Git initial commit diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..2071b23 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100755 index 0000000..a4abc17 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# cloud-mailcow + +Ansible role to setup mailcow on a node. \ No newline at end of file diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100755 index 0000000..df726a9 --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,50 @@ +--- +cloud_apps: /opt +cloud_storage: /opt/storage +cloud_stage: prod +cloud_update: false +domain_external: "my-domain.tld" + + +mailcow_instance: "{{ domain_external.split('.')[:-1] | join('') | regex_replace('[^a-zA-Z0-9]+','') }}" + +mailcow_db_name: mailcow +mailcow_db_root_pass: rootpass +mailcow_db_user: mailcow +mailcow_db_pass: userpass + +mailcow_cert_subjects: + CN: "mail.{{ domain_external }}" + C: "DE" + L: "Willich" + O: "mailcow" + OU: "mailcow" + ST: "NRW" +mailcow_http: 80 +mailcow_https: 443 + +mailcow_docker_ipv4_cidr: 172.22.1 +mailcow_docker_ipv6_cidr: fd4d:6169:6c63:6f77::/64 + +mailcow_dovecot_target: 127.0.0.1:19991 +mailcow_sql_target: 127.0.0.1:13306 +mailcow_solr_target: 127.0.0.1:18983 +mailcow_redis_target: 127.0.0.1:7654 +mailcow_sieve_port: 4190 + +mailcow_other_configs: [] + +mailcow_timezone: Europe/Berlin + +mailcow_gc_time: 7200 +mailcow_solr_heap_mb: 1024 +mailcow_redis_log_count: 9999 +mailcow_sogo_timeout_m: 480 + +mailcow_use_letsencrypt: true +mailcow_use_clamav: true +mailcow_use_sogo: true +mailcow_use_solr: true +mailcow_ip_check: true +mailcow_http_check: true +mailcow_no_http: true \ No newline at end of file diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100755 index 0000000..0a00d55 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,9 @@ +--- +- name: restart mailcow docker containers + command: + cmd: "{{ item }}" + chdir: "{{ mailcow_folder }}/" + loop: + - docker-compose down + - docker-compose up -d + when: not mailcow_start.changed \ No newline at end of file diff --git a/meta/main.yml b/meta/main.yml new file mode 100755 index 0000000..dbe7fa7 --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,17 @@ +--- +galaxy_info: + role_name: mailcow + namespace: hahn-cloud + author: Lars Hahn + company: OpenDevChain + license: MIT + description: Role to setup mailcow based on docker. + min_ansible_version: 2.7 + platforms: + - name: Debian + versions: + - 10 + galaxy_tags: + - mailcow +dependencies: + - docker diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100755 index 0000000..bf0a9fc --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,112 @@ +--- +# Basic +- name: install requirements for mailcow + apt: + update_cache: yes + state: "{% if cloud_update | bool %}latest{% else %}present{% endif %}" + install_recommends: yes + pkg: + - python3-openssl + +# Mailcow Setup +- name: clone mailcow repository + git: + repo: https://github.com/mailcow/mailcow-dockerized + dest: "{{ mailcow_folder }}" + update: no + +# Mailcow Config +- name: setup mailcow config + template: + src: opt/mailcow/mailcow.conf.j2 + dest: "{{ mailcow_folder }}/mailcow.conf" + mode: 0600 + notify: restart mailcow docker containers + +- name: setup env link to mailcow conf + file: + src: "{{ mailcow_folder }}/mailcow.conf" + dest: "{{ mailcow_folder }}/.env" + state: link + +- name: setup ssl folder + file: + path: "{{ mailcow_ssl_path }}" + state: directory + +- name: generate dh params + openssl_dhparam: + path: "{{ mailcow_ssl_path }}/dhparams.pem" + size: 2048 + notify: restart mailcow docker containers + when: not (mailcow_use_letsencrypt | default('false')) + +- name: use predefined dh params + copy: + remote_src: yes + src: "{{ mailcow_folder }}/data/assets/ssl-example/dhparams.pem" + dest: "{{ mailcow_ssl_path }}/dhparams.pem" + notify: restart mailcow docker containers + when: (mailcow_use_letsencrypt | default('false')) + +- name: generate ssl private key + openssl_privatekey: + path: "{{ mailcow_ssl_path }}/key.pem" + size: 4096 + register: key_generation + notify: restart mailcow docker containers + +- name: generate ssl cert request + openssl_csr: + path: "{{ mailcow_ssl_path }}/csr.pem" + privatekey_path: "{{ mailcow_ssl_path }}/key.pem" + common_name: "{{ mailcow_cert_subjects.CN }}" + country_name: "{{ mailcow_cert_subjects.C }}" + locality_name: "{{ mailcow_cert_subjects.L }}" + organization_name: "{{ mailcow_cert_subjects.O }}" + organizational_unit_name: "{{ mailcow_cert_subjects.OU }}" + state_or_province_name: "{{ mailcow_cert_subjects.ST }}" + when: key_generation.changed + register: cert_request + +- name: generate selfsigned certificate + openssl_certificate: + provider: selfsigned + path: "{{ mailcow_ssl_path }}/cert.pem" + privatekey_path: "{{ mailcow_ssl_path }}/key.pem" + csr_path: "{{ mailcow_ssl_path }}/csr.pem" + when: cert_request.changed + notify: restart mailcow docker containers + +- name: set http redirect to https + template: + src: opt/mailcow/data/conf/nginx/redirect.conf.j2 + dest: "{{ mailcow_folder }}/data/conf/nginx/redirect.conf" + notify: restart mailcow docker containers + when: mailcow_no_http + +# Mailcow build +- name: check mailcow image availability + command: docker image list + changed_when: false + register: docker_image_list + +- name: pull images for mailcow containers + command: + cmd: docker-compose pull + chdir: "{{ mailcow_folder }}/" + when: "'mailcow' not in docker_image_list.stdout" + +- name: find running mailcow containers + command: + cmd: "{% raw %}docker ps --format '{{.Image}}'{% endraw %}" + chdir: "{{ mailcow_folder }}/" + changed_when: false + register: docker_running_list + +- name: start mailcow containers if not running + command: + cmd: docker-compose up -d + chdir: "{{ mailcow_folder }}/" + when: "'mailcow' not in docker_running_list.stdout" + register: mailcow_start \ No newline at end of file diff --git a/templates/opt/mailcow/data/conf/nginx/redirect.conf.j2 b/templates/opt/mailcow/data/conf/nginx/redirect.conf.j2 new file mode 100755 index 0000000..c725864 --- /dev/null +++ b/templates/opt/mailcow/data/conf/nginx/redirect.conf.j2 @@ -0,0 +1,14 @@ +server { + root /web; + listen 80 default_server; + listen [::]:80 default_server; + include /etc/nginx/conf.d/server_name.active; + if ( $request_uri ~* "%0A|%0D" ) { return 403; } + location ^~ /.well-known/acme-challenge/ { + allow all; + default_type "text/plain"; + } + location / { + return 301 https://$host$uri$is_args$args; + } +} \ No newline at end of file diff --git a/templates/opt/mailcow/mailcow.conf.j2 b/templates/opt/mailcow/mailcow.conf.j2 new file mode 100755 index 0000000..a97a96d --- /dev/null +++ b/templates/opt/mailcow/mailcow.conf.j2 @@ -0,0 +1,236 @@ +# ------------------------------ +# mailcow web ui configuration +# ------------------------------ +# example.org is _not_ a valid hostname, use a fqdn here. +# Default admin user is "admin" +# Default password is "moohoo" + +MAILCOW_HOSTNAME=mail.{{ domain_external }} + +# Password hash algorithm +# Only certain password hash algorithm are supported. For a fully list of supported schemes, +# see https://mailcow.github.io/mailcow-dockerized-docs/model-passwd/ +MAILCOW_PASS_SCHEME=BLF-CRYPT + +# ------------------------------ +# SQL database configuration +# ------------------------------ + +DBNAME={{ mailcow_db_name }} +DBUSER={{ mailcow_db_user }} + +# Please use long, random alphanumeric strings (A-Za-z0-9) + +DBPASS={{ mailcow_db_pass }} +DBROOT={{ mailcow_db_root_pass }} + +# ------------------------------ +# HTTP/S Bindings +# ------------------------------ + +# You should use HTTPS, but in case of SSL offloaded reverse proxies: +# Might be important: This will also change the binding within the container. +# If you use a proxy within Docker, point it to the ports you set below. +# Do _not_ use IP:PORT in HTTP(S)_BIND or HTTP(S)_PORT +# IMPORTANT: Do not use port 8081, 9081 or 65510! +# Example: HTTP_BIND=1.2.3.4 +# For IPv4 and IPv6 leave it empty: HTTP_BIND= & HTTPS_PORT= +# For IPv6 see https://mailcow.github.io/mailcow-dockerized-docs/firststeps-ip_bindings/ + +HTTP_PORT={{ mailcow_http }} +HTTP_BIND= + +HTTPS_PORT={{ mailcow_https }} +HTTPS_BIND= + +# ------------------------------ +# Other bindings +# ------------------------------ +# You should leave that alone +# Format: 11.22.33.44:25 or 12.34.56.78:465 etc. + +SMTP_PORT=25 +SMTPS_PORT=465 +SUBMISSION_PORT=587 +IMAP_PORT=143 +IMAPS_PORT=993 +POP_PORT=110 +POPS_PORT=995 +SIEVE_PORT={{ mailcow_sieve_port }} +DOVEADM_PORT={{ mailcow_dovecot_target }} +SQL_PORT={{ mailcow_sql_target }} +SOLR_PORT={{ mailcow_solr_target }} +REDIS_PORT={{ mailcow_redis_target }} + +# Your timezone +# See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a list of timezones +# Use the row named 'TZ database name' + pay attention for 'Notes' row + +TZ={{ mailcow_timezone }} + +# Fixed project name +# Please use lowercase letters only + +COMPOSE_PROJECT_NAME={{ mailcow_instance }} + +# Set this to "allow" to enable the anyone pseudo user. Disabled by default. +# When enabled, ACL can be created, that apply to "All authenticated users" +# This should probably only be activated on mail hosts, that are used exclusivly by one organisation. +# Otherwise a user might share data with too many other users. +ACL_ANYONE=disallow + +# Garbage collector cleanup +# Deleted domains and mailboxes are moved to /var/vmail/_garbage/timestamp_sanitizedstring +# How long should objects remain in the garbage until they are being deleted? (value in minutes) +# Check interval is hourly + +MAILDIR_GC_TIME={{ mailcow_gc_time }} + +# Additional SAN for the certificate +# +# You can use wildcard records to create specific names for every domain you add to mailcow. +# Example: Add domains "example.com" and "example.net" to mailcow, change ADDITIONAL_SAN to a value like: +#ADDITIONAL_SAN=imap.*,smtp.* +# This will expand the certificate to "imap.example.com", "smtp.example.com", "imap.example.net", "imap.example.net" +# plus every domain you add in the future. +# +# You can also just add static names... +#ADDITIONAL_SAN=srv1.example.net +# ...or combine wildcard and static names: +#ADDITIONAL_SAN=imap.*,srv1.example.com +# + +ADDITIONAL_SAN= + +# Additional server names for mailcow UI +# +# Specify alternative addresses for the mailcow UI to respond to +# This is useful when you set mail.* as ADDITIONAL_SAN and want to make sure mail.maildomain.com will always point to the mailcow UI. +# If the server name does not match a known site, Nginx decides by best-guess and may redirect users to the wrong web root. +# You can understand this as server_name directive in Nginx. +# Comma separated list without spaces! Example: ADDITIONAL_SERVER_NAMES=a.b.c,d.e.f + +ADDITIONAL_SERVER_NAMES= + +# Skip running ACME (acme-mailcow, Let's Encrypt certs) - y/n + +SKIP_LETS_ENCRYPT={{ 'n' if (mailcow_use_letsencrypt | default('true') | bool) else 'y' }} + +# Create seperate certificates for all domains - y/n +# this will allow adding more than 100 domains, but some email clients will not be able to connect with alternative hostnames +# see https://wiki.dovecot.org/SSL/SNIClientSupport +ENABLE_SSL_SNI=n + +# Skip IPv4 check in ACME container - y/n + +SKIP_IP_CHECK={{ 'n' if (mailcow_ip_check | default('true') | bool) else 'y' }} + +# Skip HTTP verification in ACME container - y/n + +SKIP_HTTP_VERIFICATION={{ 'n' if (mailcow_http_check | default('true') | bool) else 'y' }} + +# Skip ClamAV (clamd-mailcow) anti-virus (Rspamd will auto-detect a missing ClamAV container) - y/n + +SKIP_CLAMD={{ 'n' if (mailcow_use_clamav | default('true') | bool) else 'y' }} + +# Skip SOGo: Will disable SOGo integration and therefore webmail, DAV protocols and ActiveSync support (experimental, unsupported, not fully implemented) - y/n + +SKIP_SOGO={{ 'n' if (mailcow_use_sogo | default('true') | bool) else 'y' }} + +# Skip Solr on low-memory systems or if you do not want to store a readable index of your mails in solr-vol-1. + +SKIP_SOLR={{ 'n' if (mailcow_use_solr | default('true') | bool) else 'y' }} + +# Solr heap size in MB, there is no recommendation, please see Solr docs. +# Solr is a prone to run OOM and should be monitored. Unmonitored Solr setups are not recommended. + +SOLR_HEAP={{ mailcow_solr_heap_mb | default(1024) }} + +# Allow admins to log into SOGo as email user (without any password) + +ALLOW_ADMIN_EMAIL_LOGIN=n + +# Enable watchdog (watchdog-mailcow) to restart unhealthy containers + +USE_WATCHDOG=y + +# Send watchdog notifications by mail (sent from watchdog@MAILCOW_HOSTNAME) +# CAUTION: +# 1. You should use external recipients +# 2. Mails are sent unsigned (no DKIM) +# 3. If you use DMARC, create a separate DMARC policy ("v=DMARC1; p=none;" in _dmarc.MAILCOW_HOSTNAME) +# Multiple rcpts allowed, NO quotation marks, NO spaces + +#WATCHDOG_NOTIFY_EMAIL=a@example.com,b@example.com,c@example.com +#WATCHDOG_NOTIFY_EMAIL= + +# Notify about banned IP (includes whois lookup) +WATCHDOG_NOTIFY_BAN=n + +# Subject for watchdog mails. Defaults to "Watchdog ALERT" followed by the error message. +#WATCHDOG_SUBJECT= + +# Checks if mailcow is an open relay. Requires a SAL. More checks will follow. +# https://www.servercow.de/mailcow?lang=en +# https://www.servercow.de/mailcow?lang=de +# No data is collected. Opt-in and anonymous. +# Will only work with unmodified mailcow setups. +WATCHDOG_EXTERNAL_CHECKS=n + +# Max log lines per service to keep in Redis logs + +LOG_LINES={{ mailcow_redis_log_count }} + +# Internal IPv4 /24 subnet, format n.n.n (expands to n.n.n.0/24) +# Use private IPv4 addresses only, see https://en.wikipedia.org/wiki/Private_network#Private_IPv4_addresses + +IPV4_NETWORK={{ mailcow_docker_ipv4_cidr }} + +# Internal IPv6 subnet in fc00::/7 +# Use private IPv6 addresses only, see https://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses + +IPV6_NETWORK={{ mailcow_docker_ipv6_cidr }} + +# Use this IPv4 for outgoing connections (SNAT) + +#SNAT_TO_SOURCE= + +# Use this IPv6 for outgoing connections (SNAT) + +#SNAT6_TO_SOURCE= + +# Create or override an API key for the web UI +# You _must_ define API_ALLOW_FROM, which is a comma separated list of IPs +# An API key defined as API_KEY has read-write access +# An API key defined as API_KEY_READ_ONLY has read-only access +# Allowed chars for API_KEY and API_KEY_READ_ONLY: a-z, A-Z, 0-9, - +# You can define API_KEY and/or API_KEY_READ_ONLY + +#API_KEY= +#API_KEY_READ_ONLY= +#API_ALLOW_FROM=172.22.1.1,127.0.0.1 + +# mail_home is ~/Maildir +MAILDIR_SUB=Maildir + +# SOGo session timeout in minutes +SOGO_EXPIRE_SESSION={{ mailcow_sogo_timeout_m }} + +# DOVECOT_MASTER_USER and DOVECOT_MASTER_PASS must both be provided. No special chars. +# Empty by default to auto-generate master user and password on start. +# User expands to DOVECOT_MASTER_USER@mailcow.local +# LEAVE EMPTY IF UNSURE +DOVECOT_MASTER_USER= +# LEAVE EMPTY IF UNSURE +DOVECOT_MASTER_PASS= + +# Let's Encrypt registration contact information +# Optional: Leave empty for none +# This value is only used on first order! +# Setting it at a later point will require the following steps: +# https://mailcow.github.io/mailcow-dockerized-docs/debug-reset-tls/ +ACME_CONTACT= + +{% for config_line in mailcow_other_configs %} +{{ config_line }} +{% endfor %} \ No newline at end of file diff --git a/vars/main.yml b/vars/main.yml new file mode 100755 index 0000000..6c81d25 --- /dev/null +++ b/vars/main.yml @@ -0,0 +1,3 @@ +--- +mailcow_folder: "{{ cloud_apps }}/mailcow" +mailcow_ssl_path: "{{ mailcow_folder }}/data/assets/ssl" \ No newline at end of file