1
0
mirror of https://github.com/mailcow/mailcow-dockerized.git synced 2025-12-19 12:51:29 +00:00

[Dovecot] use python bootstrapper to start DOVECOT container

This commit is contained in:
FreddleSpl0it
2025-05-21 07:56:28 +02:00
parent 5a097ed5f7
commit 2efea9c832
36 changed files with 1223 additions and 690 deletions

View File

@@ -10,13 +10,15 @@ def main():
from modules.BootstrapNginx import Bootstrap from modules.BootstrapNginx import Bootstrap
elif container_name == "postfix-mailcow": elif container_name == "postfix-mailcow":
from modules.BootstrapPostfix import Bootstrap from modules.BootstrapPostfix import Bootstrap
elif container_name == "dovecot-mailcow":
from modules.BootstrapDovecot import Bootstrap
else: else:
print(f"No bootstrap handler for container: {container_name}", file=sys.stderr) print(f"No bootstrap handler for container: {container_name}", file=sys.stderr)
sys.exit(1) sys.exit(1)
b = Bootstrap( b = Bootstrap(
container=container_name, container=container_name,
db_config = { db_config={
"host": "localhost", "host": "localhost",
"user": os.getenv("DBUSER"), "user": os.getenv("DBUSER"),
"password": os.getenv("DBPASS"), "password": os.getenv("DBPASS"),
@@ -25,7 +27,13 @@ def main():
'connection_timeout': 2 'connection_timeout': 2
}, },
db_table="service_settings", db_table="service_settings",
db_settings=['sogo'] db_settings=['sogo'],
redis_config={
"host": os.getenv("REDIS_SLAVEOF_IP") or "redis-mailcow",
"port": int(os.getenv("REDIS_SLAVEOF_PORT") or 6379),
"password": os.getenv("REDISPASS"),
"db": 0
}
) )
b.bootstrap() b.bootstrap()

View File

@@ -9,21 +9,25 @@ import time
import socket import socket
import signal import signal
import re import re
import redis
import hashlib
import json import json
from pathlib import Path from pathlib import Path
import mysql.connector import mysql.connector
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
class BootstrapBase: class BootstrapBase:
def __init__(self, container, db_config, db_table, db_settings): def __init__(self, container, db_config, db_table, db_settings, redis_config):
self.container = container self.container = container
self.db_config = db_config self.db_config = db_config
self.db_table = db_table self.db_table = db_table
self.db_settings = db_settings self.db_settings = db_settings
self.redis_config = redis_config
self.env = None self.env = None
self.env_vars = None self.env_vars = None
self.mysql_conn = None self.mysql_conn = None
self.redis_conn = None
def render_config(self, template_name, output_path): def render_config(self, template_name, output_path):
""" """
@@ -184,16 +188,21 @@ class BootstrapBase:
Args: Args:
path (str or Path): Path to the file or directory. path (str or Path): Path to the file or directory.
user (str): Username for new owner. user (str or int): Username or UID for new owner.
group (str, optional): Group name; defaults to user's group if not provided. group (str or int, optional): Group name or GID; defaults to user's group if not provided.
recursive (bool): If True and path is a directory, ownership is applied recursively. recursive (bool): If True and path is a directory, ownership is applied recursively.
Raises: Raises:
FileNotFoundError: If the path does not exist. FileNotFoundError: If the path does not exist.
""" """
uid = pwd.getpwnam(user).pw_uid # Resolve UID
gid = grp.getgrnam(group or user).gr_gid uid = int(user) if str(user).isdigit() else pwd.getpwnam(user).pw_uid
# Resolve GID
if group is not None:
gid = int(group) if str(group).isdigit() else grp.getgrnam(group).gr_gid
else:
gid = uid if isinstance(user, int) or str(user).isdigit() else grp.getgrnam(user).gr_gid
p = Path(path) p = Path(path)
if not p.exists(): if not p.exists():
@@ -231,6 +240,67 @@ class BootstrapBase:
shutil.move(str(src_path), str(dst_path)) shutil.move(str(src_path), str(dst_path))
def copy_file(self, src, dst, overwrite=True):
"""
Copies a file from src to dst using shutil.
Args:
src (str or Path): Source file path.
dst (str or Path): Destination file path.
overwrite (bool): Whether to overwrite the destination if it exists.
Raises:
FileNotFoundError: If the source file doesn't exist.
FileExistsError: If the destination exists and overwrite is False.
IOError: If the copy operation fails.
"""
src_path = Path(src)
dst_path = Path(dst)
if not src_path.is_file():
raise FileNotFoundError(f"Source file not found: {src_path}")
if dst_path.exists() and not overwrite:
raise FileExistsError(f"Destination exists: {dst_path}")
dst_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src_path, dst_path)
def remove(self, path, recursive=False, wipe_contents=False):
"""
Removes a file or directory.
Args:
path (str or Path): The file or directory path to remove.
recursive (bool): If True, directories will be removed recursively.
wipe_contents (bool): If True and path is a directory, only its contents are removed, not the dir itself.
Raises:
FileNotFoundError: If the path does not exist.
ValueError: If a directory is passed without recursive or wipe_contents.
"""
path = Path(path)
if not path.exists():
raise FileNotFoundError(f"Cannot remove: {path} does not exist")
if wipe_contents and path.is_dir():
for child in path.iterdir():
if child.is_dir():
shutil.rmtree(child)
else:
child.unlink()
elif path.is_file():
path.unlink()
elif path.is_dir():
if recursive:
shutil.rmtree(path)
else:
raise ValueError(f"{path} is a directory. Use recursive=True or wipe_contents=True to remove it.")
def create_dir(self, path): def create_dir(self, path):
""" """
Creates a directory if it does not exist. Creates a directory if it does not exist.
@@ -376,6 +446,49 @@ class BootstrapBase:
if self.mysql_conn and self.mysql_conn.is_connected(): if self.mysql_conn and self.mysql_conn.is_connected():
self.mysql_conn.close() self.mysql_conn.close()
def connect_redis(self, retries=10, delay=2):
"""
Establishes a Redis connection and stores it in `self.redis_conn`.
Args:
retries (int): Number of ping retries before giving up.
delay (int): Seconds between retries.
"""
client = redis.Redis(
host=self.redis_config['host'],
port=self.redis_config['port'],
password=self.redis_config['password'],
db=self.redis_config['db'],
decode_responses=True
)
for _ in range(retries):
try:
if client.ping():
self.redis_conn = client
return
except redis.RedisError as e:
print(f"Waiting for Redis... ({e})")
time.sleep(delay)
raise ConnectionError("Redis is not available after multiple attempts.")
def close_redis(self):
"""
Closes the Redis connection if it's open.
Safe to call even if Redis was never connected or already closed.
"""
if self.redis_conn:
try:
self.redis_conn.close()
except Exception as e:
print(f"Error while closing Redis connection: {e}")
finally:
self.redis_conn = None
def wait_for_schema_update(self, init_file_path="init_db.inc.php", check_interval=5): def wait_for_schema_update(self, init_file_path="init_db.inc.php", check_interval=5):
""" """
Waits until the current database schema version matches the expected version Waits until the current database schema version matches the expected version
@@ -559,4 +672,7 @@ class BootstrapBase:
print(e.stderr.strip()) print(e.stderr.strip())
if check: if check:
raise raise
return e return e
def sha1_filter(self, value):
return hashlib.sha1(value.encode()).hexdigest()

View File

@@ -0,0 +1,307 @@
from jinja2 import Environment, FileSystemLoader
from modules.BootstrapBase import BootstrapBase
from pathlib import Path
import os
import sys
import time
import pwd
import hashlib
class Bootstrap(BootstrapBase):
def bootstrap(self):
# Connect to MySQL
self.connect_mysql()
self.wait_for_schema_update()
# Connect to Redis
self.connect_redis()
self.redis_conn.set("DOVECOT_REPL_HEALTH", 1)
# Wait for DNS
self.wait_for_dns("mailcow.email")
# Create missing directories
self.create_dir("/etc/dovecot/sql/")
self.create_dir("/etc/dovecot/auth/")
self.create_dir("/var/vmail/_garbage")
self.create_dir("/var/vmail/sieve")
self.create_dir("/etc/sogo")
self.create_dir("/var/volatile")
# Setup Jinja2 Environment and load vars
self.env = Environment(
loader=FileSystemLoader('./etc/dovecot/config_templates'),
keep_trailing_newline=True,
lstrip_blocks=True,
trim_blocks=True
)
extra_vars = {
"VALID_CERT_DIRS": self.get_valid_cert_dirs(),
"RAND_USER": self.rand_pass(),
"RAND_PASS": self.rand_pass(),
"RAND_PASS2": self.rand_pass(),
"ENV_VARS": dict(os.environ)
}
self.env_vars = self.prepare_template_vars('/overwrites.json', extra_vars)
# Escape DBPASS
self.env_vars['DBPASS'] = self.env_vars['DBPASS'].replace('"', r'\"')
# Set custom filters
self.env.filters['sha1'] = self.sha1_filter
print("Set Timezone")
self.set_timezone()
print("Render config")
self.render_config("dovecot-dict-sql-quota.conf.j2", "/etc/dovecot/sql/dovecot-dict-sql-quota.conf")
self.render_config("dovecot-dict-sql-userdb.conf.j2", "/etc/dovecot/sql/dovecot-dict-sql-userdb.conf")
self.render_config("dovecot-dict-sql-sieve_before.conf.j2", "/etc/dovecot/sql/dovecot-dict-sql-sieve_before.conf")
self.render_config("dovecot-dict-sql-sieve_after.conf.j2", "/etc/dovecot/sql/dovecot-dict-sql-sieve_after.conf")
self.render_config("mail_plugins.j2", "/etc/dovecot/mail_plugins")
self.render_config("mail_plugins_imap.j2", "/etc/dovecot/mail_plugins_imap")
self.render_config("mail_plugins_lmtp.j2", "/etc/dovecot/mail_plugins_lmtp")
self.render_config("global_sieve_after.sieve.j2", "/var/vmail/sieve/global_sieve_after.sieve")
self.render_config("global_sieve_before.sieve.j2", "/var/vmail/sieve/global_sieve_before.sieve")
self.render_config("dovecot-master.passwd.j2", "/etc/dovecot/dovecot-master.passwd")
self.render_config("dovecot-master.userdb.j2", "/etc/dovecot/dovecot-master.userdb")
self.render_config("sieve.creds.j2", "/etc/sogo/sieve.creds")
self.render_config("sogo-sso.pass.j2", "/etc/phpfpm/sogo-sso.pass")
self.render_config("cron.creds.j2", "/etc/sogo/cron.creds")
self.render_config("source_env.sh.j2", "/source_env.sh")
self.render_config("maildir_gc.sh.j2", "/usr/local/bin/maildir_gc.sh")
self.render_config("dovecot.conf.j2", "/etc/dovecot/dovecot.conf")
files = [
"/etc/dovecot/mail_plugins",
"/etc/dovecot/mail_plugins_imap",
"/etc/dovecot/mail_plugins_lmtp",
"/templates/quarantine.tpl"
]
for file in files:
self.set_permissions(file, 0o644)
try:
# Migrate old sieve_after file
self.move_file("/etc/dovecot/sieve_after", "/var/vmail/sieve/global_sieve_after.sieve")
except Exception as e:
pass
try:
# Cleanup random user maildirs
self.remove("/var/vmail/mailcow.local", wipe_contents=True)
except Exception as e:
pass
try:
# Cleanup PIDs
self.remove("/tmp/quarantine_notify.pid")
except Exception as e:
pass
try:
self.remove("/var/run/dovecot/master.pid")
except Exception as e:
pass
# Check permissions of vmail/index/garbage directories.
# Do not do this every start-up, it may take a very long time. So we use a stat check here.
files = [
"/var/vmail",
"/var/vmail/_garbage",
"/var/vmail_index"
]
for file in files:
path = Path(file)
try:
stat_info = path.stat()
current_user = pwd.getpwuid(stat_info.st_uid).pw_name
if current_user != "vmail":
print(f"Ownership of {path} is {current_user}, fixing to vmail:vmail...")
self.set_owner(path, user="vmail", group="vmail", recursive=True)
else:
print(f"Ownership of {path} is already correct (vmail)")
except Exception as e:
print(f"Error checking ownership of {path}: {e}")
# Compile sieve scripts
files = [
"/var/vmail/sieve/global_sieve_before.sieve",
"/var/vmail/sieve/global_sieve_after.sieve",
"/usr/lib/dovecot/sieve/report-spam.sieve",
"/usr/lib/dovecot/sieve/report-ham.sieve",
]
for file in files:
self.run_command(["sievec", file], check=False)
# Fix permissions
for path in Path("/etc/dovecot/sql").glob("*.conf"):
self.set_owner(path, "root", "root")
self.set_permissions(path, 0o640)
files = [
"/etc/dovecot/auth/passwd-verify.lua",
*Path("/etc/dovecot/sql").glob("dovecot-dict-sql-sieve*"),
*Path("/etc/dovecot/sql").glob("dovecot-dict-sql-quota*")
]
for file in files:
self.set_owner(file, "root", "dovecot")
self.set_permissions("/etc/dovecot/auth/passwd-verify.lua", 0o640)
for file in ["/var/vmail/sieve", "/var/volatile", "/var/vmail_index"]:
self.set_owner(file, "vmail", "vmail", recursive=True)
self.run_command(["adduser", "vmail", "tty"])
self.run_command(["chmod", "g+rw", "/dev/console"])
self.set_owner("/dev/console", "root", "tty")
files = [
"/usr/lib/dovecot/sieve/rspamd-pipe-ham",
"/usr/lib/dovecot/sieve/rspamd-pipe-spam",
"/usr/local/bin/imapsync_runner.pl",
"/usr/local/bin/imapsync",
"/usr/local/bin/trim_logs.sh",
"/usr/local/bin/sa-rules.sh",
"/usr/local/bin/clean_q_aged.sh",
"/usr/local/bin/maildir_gc.sh",
"/usr/local/sbin/stop-supervisor.sh",
"/usr/local/bin/quota_notify.py",
"/usr/local/bin/repl_health.sh",
"/usr/local/bin/optimize-fts.sh"
]
for file in files:
self.set_permissions(file, 0o755)
# Collect SA rules once now
self.run_command(["/usr/local/bin/sa-rules.sh"], check=False)
self.generate_mail_crypt_keys()
self.cleanup_imapsync_jobs()
self.generate_guid_version()
def get_valid_cert_dirs(self):
"""
Returns a mapping of domains to their certificate directory path.
Example:
{
"example.com": "/etc/ssl/mail/example.com/",
"www.example.com": "/etc/ssl/mail/example.com/"
}
"""
sni_map = {}
base_path = Path("/etc/ssl/mail")
if not base_path.exists():
return sni_map
for cert_dir in base_path.iterdir():
if not cert_dir.is_dir():
continue
domains_file = cert_dir / "domains"
cert_file = cert_dir / "cert.pem"
key_file = cert_dir / "key.pem"
if not (domains_file.exists() and cert_file.exists() and key_file.exists()):
continue
with open(domains_file, "r") as f:
domains = [line.strip() for line in f if line.strip()]
for domain in domains:
sni_map[domain] = str(cert_dir)
return sni_map
def generate_mail_crypt_keys(self):
"""
Ensures mail_crypt EC keypair exists. Generates if missing. Adjusts permissions.
"""
key_dir = Path("/mail_crypt")
priv_key = key_dir / "ecprivkey.pem"
pub_key = key_dir / "ecpubkey.pem"
# Generate keys if they don't exist or are empty
if not priv_key.exists() or priv_key.stat().st_size == 0 or \
not pub_key.exists() or pub_key.stat().st_size == 0:
self.run_command(
"openssl ecparam -name prime256v1 -genkey | openssl pkey -out /mail_crypt/ecprivkey.pem",
shell=True
)
self.run_command(
"openssl pkey -in /mail_crypt/ecprivkey.pem -pubout -out /mail_crypt/ecpubkey.pem",
shell=True
)
# Set ownership to UID 401 (dovecot)
self.set_owner(priv_key, user='401')
self.set_owner(pub_key, user='401')
def cleanup_imapsync_jobs(self):
"""
Cleans up stale imapsync locks and resets running status in the database.
Deletes the imapsync_busy.lock file if present and sets `is_running` to 0
in the `imapsync` table, if it exists.
Logs:
Any issues with file operations or SQL execution.
"""
lock_file = Path("/tmp/imapsync_busy.lock")
if lock_file.exists():
try:
lock_file.unlink()
except Exception as e:
print(f"Failed to remove lock file: {e}")
try:
cursor = self.mysql_conn.cursor()
cursor.execute("SHOW TABLES LIKE 'imapsync'")
result = cursor.fetchone()
if result:
cursor.execute("UPDATE imapsync SET is_running='0'")
self.mysql_conn.commit()
cursor.close()
except Exception as e:
print(f"Error updating imapsync table: {e}")
def generate_guid_version(self):
"""
Waits for the `versions` table to be created, then generates a GUID
based on the mail hostname and Dovecot's public key and inserts it
into the `versions` table.
If the key or hash is missing or malformed, marks it as INVALID.
"""
try:
result = self.run_command(["doveconf", "-P"], check=True)
pubkey_path = None
for line in result.stdout.splitlines():
if "mail_crypt_global_public_key" in line:
parts = line.split('<')
if len(parts) > 1:
pubkey_path = parts[1].strip()
break
if pubkey_path and Path(pubkey_path).exists():
with open(pubkey_path, "rb") as key_file:
pubkey_data = key_file.read()
hostname = self.env_vars.get("MAILCOW_HOSTNAME", "mailcow.local").encode("utf-8")
concat = hostname + pubkey_data
guid = hashlib.sha256(concat).hexdigest()
if len(guid) == 64:
version_value = guid
else:
version_value = "INVALID"
cursor = self.mysql_conn.cursor()
cursor.execute(
"REPLACE INTO versions (application, version) VALUES (%s, %s)",
("GUID", version_value)
)
self.mysql_conn.commit()
cursor.close()
else:
print("Public key not found or unreadable. GUID not generated.")
except Exception as e:
print(f"Failed to generate or store GUID: {e}")

View File

@@ -87,7 +87,7 @@ RUN addgroup -g 5000 vmail \
perl-proc-processtable \ perl-proc-processtable \
perl-app-cpanminus \ perl-app-cpanminus \
procps \ procps \
python3 \ python3 py3-pip \
py3-mysqlclient \ py3-mysqlclient \
py3-html2text \ py3-html2text \
py3-jinja2 \ py3-jinja2 \
@@ -115,25 +115,34 @@ RUN addgroup -g 5000 vmail \
&& chmod +x /usr/local/bin/gosu \ && chmod +x /usr/local/bin/gosu \
&& gosu nobody true && gosu nobody true
COPY trim_logs.sh /usr/local/bin/trim_logs.sh RUN pip install --break-system-packages \
COPY clean_q_aged.sh /usr/local/bin/clean_q_aged.sh mysql-connector-python \
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf jinja2 \
COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf redis
COPY imapsync /usr/local/bin/imapsync
COPY imapsync_runner.pl /usr/local/bin/imapsync_runner.pl
COPY report-spam.sieve /usr/lib/dovecot/sieve/report-spam.sieve COPY data/Dockerfiles/bootstrap /bootstrap
COPY report-ham.sieve /usr/lib/dovecot/sieve/report-ham.sieve COPY data/Dockerfiles/dovecot/trim_logs.sh /usr/local/bin/trim_logs.sh
COPY rspamd-pipe-ham /usr/lib/dovecot/sieve/rspamd-pipe-ham COPY data/Dockerfiles/dovecot/clean_q_aged.sh /usr/local/bin/clean_q_aged.sh
COPY rspamd-pipe-spam /usr/lib/dovecot/sieve/rspamd-pipe-spam COPY data/Dockerfiles/dovecot/syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
COPY sa-rules.sh /usr/local/bin/sa-rules.sh COPY data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf
COPY maildir_gc.sh /usr/local/bin/maildir_gc.sh COPY data/Dockerfiles/dovecot/imapsync /usr/local/bin/imapsync
COPY docker-entrypoint.sh / COPY data/Dockerfiles/dovecot/imapsync_runner.pl /usr/local/bin/imapsync_runner.pl
COPY supervisord.conf /etc/supervisor/supervisord.conf COPY data/Dockerfiles/dovecot/report-spam.sieve /usr/lib/dovecot/sieve/report-spam.sieve
COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh COPY data/Dockerfiles/dovecot/report-ham.sieve /usr/lib/dovecot/sieve/report-ham.sieve
COPY quarantine_notify.py /usr/local/bin/quarantine_notify.py COPY data/Dockerfiles/dovecot/rspamd-pipe-ham /usr/lib/dovecot/sieve/rspamd-pipe-ham
COPY quota_notify.py /usr/local/bin/quota_notify.py COPY data/Dockerfiles/dovecot/rspamd-pipe-spam /usr/lib/dovecot/sieve/rspamd-pipe-spam
COPY repl_health.sh /usr/local/bin/repl_health.sh COPY data/Dockerfiles/dovecot/sa-rules.sh /usr/local/bin/sa-rules.sh
COPY optimize-fts.sh /usr/local/bin/optimize-fts.sh COPY data/Dockerfiles/dovecot/docker-entrypoint.sh /
COPY data/Dockerfiles/dovecot/supervisord.conf /etc/supervisor/supervisord.conf
COPY data/Dockerfiles/dovecot/stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
COPY data/Dockerfiles/dovecot/quarantine_notify.py /usr/local/bin/quarantine_notify.py
COPY data/Dockerfiles/dovecot/quota_notify.py /usr/local/bin/quota_notify.py
COPY data/Dockerfiles/dovecot/repl_health.sh /usr/local/bin/repl_health.sh
COPY data/Dockerfiles/dovecot/optimize-fts.sh /usr/local/bin/optimize-fts.sh
RUN chmod +x /docker-entrypoint.sh \
/usr/local/sbin/stop-supervisor.sh
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]

View File

@@ -1,253 +1,15 @@
#!/bin/bash #!/bin/bash
set -e
# Wait for MySQL to warm-up # Run hooks
while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do for file in /hooks/*; do
echo "Waiting for database to come up..." if [ -x "${file}" ]; then
sleep 2 echo "Running hook ${file}"
done "${file}"
until dig +short mailcow.email > /dev/null; do
echo "Waiting for DNS..."
sleep 1
done
# Do not attempt to write to slave
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning"
else
REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning"
fi
until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do
echo "Waiting for Redis..."
sleep 2
done
${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null
# Create missing directories
[[ ! -d /etc/dovecot/sql/ ]] && mkdir -p /etc/dovecot/sql/
[[ ! -d /etc/dovecot/auth/ ]] && mkdir -p /etc/dovecot/auth/
[[ ! -d /etc/dovecot/conf.d/ ]] && mkdir -p /etc/dovecot/conf.d/
[[ ! -d /var/vmail/_garbage ]] && mkdir -p /var/vmail/_garbage
[[ ! -d /var/vmail/sieve ]] && mkdir -p /var/vmail/sieve
[[ ! -d /etc/sogo ]] && mkdir -p /etc/sogo
[[ ! -d /var/volatile ]] && mkdir -p /var/volatile
# Set Dovecot sql config parameters, escape " in db password
DBPASS=$(echo ${DBPASS} | sed 's/"/\\"/g')
# Create quota dict for Dovecot
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
QUOTA_TABLE=quota2
else
QUOTA_TABLE=quota2replica
fi
cat <<EOF > /etc/dovecot/sql/dovecot-dict-sql-quota.conf
# Autogenerated by mailcow
connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
map {
pattern = priv/quota/storage
table = ${QUOTA_TABLE}
username_field = username
value_field = bytes
}
map {
pattern = priv/quota/messages
table = ${QUOTA_TABLE}
username_field = username
value_field = messages
}
EOF
# Create dict used for sieve pre and postfilters
cat <<EOF > /etc/dovecot/sql/dovecot-dict-sql-sieve_before.conf
# Autogenerated by mailcow
connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
map {
pattern = priv/sieve/name/\$script_name
table = sieve_before
username_field = username
value_field = id
fields {
script_name = \$script_name
}
}
map {
pattern = priv/sieve/data/\$id
table = sieve_before
username_field = username
value_field = script_data
fields {
id = \$id
}
}
EOF
cat <<EOF > /etc/dovecot/sql/dovecot-dict-sql-sieve_after.conf
# Autogenerated by mailcow
connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
map {
pattern = priv/sieve/name/\$script_name
table = sieve_after
username_field = username
value_field = id
fields {
script_name = \$script_name
}
}
map {
pattern = priv/sieve/data/\$id
table = sieve_after
username_field = username
value_field = script_data
fields {
id = \$id
}
}
EOF
echo -n ${ACL_ANYONE} > /etc/dovecot/acl_anyone
if [[ "${SKIP_FTS}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo -e "\e[33mDetecting SKIP_FTS=y... not enabling Flatcurve (FTS) then...\e[0m"
echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify listescape replication lazy_expunge' > /etc/dovecot/mail_plugins
echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify listescape replication mail_log' > /etc/dovecot/mail_plugins_imap
echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl notify listescape replication' > /etc/dovecot/mail_plugins_lmtp
else
echo -e "\e[32mDetecting SKIP_FTS=n... enabling Flatcurve (FTS)\e[0m"
echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify fts fts_flatcurve listescape replication lazy_expunge' > /etc/dovecot/mail_plugins
echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify mail_log fts fts_flatcurve listescape replication' > /etc/dovecot/mail_plugins_imap
echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl fts fts_flatcurve notify listescape replication' > /etc/dovecot/mail_plugins_lmtp
fi
chmod 644 /etc/dovecot/mail_plugins /etc/dovecot/mail_plugins_imap /etc/dovecot/mail_plugins_lmtp /templates/quarantine.tpl
cat <<EOF > /etc/dovecot/sql/dovecot-dict-sql-userdb.conf
# Autogenerated by mailcow
driver = mysql
connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
user_query = SELECT CONCAT(JSON_UNQUOTE(JSON_VALUE(attributes, '$.mailbox_format')), mailbox_path_prefix, '%d/%n/${MAILDIR_SUB}:VOLATILEDIR=/var/volatile/%u:INDEX=/var/vmail_index/%u') AS mail, '%s' AS protocol, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND (active = '1' OR active = '2')
iterate_query = SELECT username FROM mailbox WHERE active = '1' OR active = '2';
EOF
# Migrate old sieve_after file
[[ -f /etc/dovecot/sieve_after ]] && mv /etc/dovecot/sieve_after /etc/dovecot/global_sieve_after
# Create global sieve scripts
cat /etc/dovecot/global_sieve_after > /var/vmail/sieve/global_sieve_after.sieve
cat /etc/dovecot/global_sieve_before > /var/vmail/sieve/global_sieve_before.sieve
# Check permissions of vmail/index/garbage directories.
# Do not do this every start-up, it may take a very long time. So we use a stat check here.
if [[ $(stat -c %U /var/vmail/) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail ; fi
if [[ $(stat -c %U /var/vmail/_garbage) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail/_garbage ; fi
if [[ $(stat -c %U /var/vmail_index) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail_index ; fi
# Cleanup random user maildirs
rm -rf /var/vmail/mailcow.local/*
# Cleanup PIDs
[[ -f /tmp/quarantine_notify.pid ]] && rm /tmp/quarantine_notify.pid
# create sni configuration
echo "" > /etc/dovecot/sni.conf
for cert_dir in /etc/ssl/mail/*/ ; do
if [[ ! -f ${cert_dir}domains ]] || [[ ! -f ${cert_dir}cert.pem ]] || [[ ! -f ${cert_dir}key.pem ]]; then
continue
fi fi
domains=($(cat ${cert_dir}domains))
for domain in ${domains[@]}; do
echo 'local_name '${domain}' {' >> /etc/dovecot/sni.conf;
echo ' ssl_cert = <'${cert_dir}'cert.pem' >> /etc/dovecot/sni.conf;
echo ' ssl_key = <'${cert_dir}'key.pem' >> /etc/dovecot/sni.conf;
echo '}' >> /etc/dovecot/sni.conf;
done
done done
# Create random master for SOGo sieve features python3 -u /bootstrap/main.py
RAND_USER=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 16 | head -n 1) BOOTSTRAP_EXIT_CODE=$?
RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 24 | head -n 1)
if [[ ! -z ${DOVECOT_MASTER_USER} ]] && [[ ! -z ${DOVECOT_MASTER_PASS} ]]; then
RAND_USER=${DOVECOT_MASTER_USER}
RAND_PASS=${DOVECOT_MASTER_PASS}
fi
echo ${RAND_USER}@mailcow.local:{SHA1}$(echo -n ${RAND_PASS} | sha1sum | awk '{print $1}'):::::: > /etc/dovecot/dovecot-master.passwd
echo ${RAND_USER}@mailcow.local::5000:5000:::: > /etc/dovecot/dovecot-master.userdb
echo ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/sieve.creds
if [[ -z ${MAILDIR_SUB} ]]; then
MAILDIR_SUB_SHARED=
else
MAILDIR_SUB_SHARED=/${MAILDIR_SUB}
fi
cat <<EOF > /etc/dovecot/shared_namespace.conf
# Autogenerated by mailcow
namespace {
type = shared
separator = /
prefix = Shared/%%u/
location = maildir:%%h${MAILDIR_SUB_SHARED}:INDEX=~${MAILDIR_SUB_SHARED}/Shared/%%u
subscriptions = no
list = children
}
EOF
cat <<EOF > /etc/dovecot/sogo_trusted_ip.conf
# Autogenerated by mailcow
remote ${IPV4_NETWORK}.248 {
disable_plaintext_auth = no
}
EOF
# Create random master Password for SOGo SSO
RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 32 | head -n 1)
echo -n ${RAND_PASS} > /etc/phpfpm/sogo-sso.pass
# Creating additional creds file for SOGo notify crons (calendars, etc)
echo -n ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/cron.creds
cat <<EOF > /etc/dovecot/sogo-sso.conf
# Autogenerated by mailcow
passdb {
driver = static
args = allow_real_nets=${IPV4_NETWORK}.248/32 password={plain}${RAND_PASS}
}
EOF
if [[ "${MASTER}" =~ ^([nN][oO]|[nN])+$ ]]; then
# Toggling MASTER will result in a rebuild of containers, so the quota script will be recreated
cat <<'EOF' > /usr/local/bin/quota_notify.py
#!/usr/bin/python3
import sys
sys.exit()
EOF
fi
# Set mail_replica for HA setups
if [[ -n ${MAILCOW_REPLICA_IP} && -n ${DOVEADM_REPLICA_PORT} ]]; then
cat <<EOF > /etc/dovecot/mail_replica.conf
# Autogenerated by mailcow
mail_replica = tcp:${MAILCOW_REPLICA_IP}:${DOVEADM_REPLICA_PORT}
EOF
fi
# Setting variables for indexer-worker inside fts.conf automatically according to mailcow.conf settings
if [[ "${SKIP_FTS}" =~ ^([nN][oO]|[nN])+$ ]]; then
echo -e "\e[94mConfiguring FTS Settings...\e[0m"
echo -e "\e[94mSetting FTS Memory Limit (per process) to ${FTS_HEAP} MB\e[0m"
sed -i "s/vsz_limit\s*=\s*[0-9]*\s*MB*/vsz_limit=${FTS_HEAP} MB/" /etc/dovecot/conf.d/fts.conf
echo -e "\e[94mSetting FTS Process Limit to ${FTS_PROCS}\e[0m"
sed -i "s/process_limit\s*=\s*[0-9]*/process_limit=${FTS_PROCS}/" /etc/dovecot/conf.d/fts.conf
fi
# 401 is user dovecot
if [[ ! -s /mail_crypt/ecprivkey.pem || ! -s /mail_crypt/ecpubkey.pem ]]; then
openssl ecparam -name prime256v1 -genkey | openssl pkey -out /mail_crypt/ecprivkey.pem
openssl pkey -in /mail_crypt/ecprivkey.pem -pubout -out /mail_crypt/ecpubkey.pem
chown 401 /mail_crypt/ecprivkey.pem /mail_crypt/ecpubkey.pem
else
chown 401 /mail_crypt/ecprivkey.pem /mail_crypt/ecpubkey.pem
fi
# Fix OpenSSL 3.X TLS1.0, 1.1 support (https://community.mailcow.email/d/4062-hi-all/20) # Fix OpenSSL 3.X TLS1.0, 1.1 support (https://community.mailcow.email/d/4062-hi-all/20)
if grep -qE 'ssl_min_protocol\s*=\s*(TLSv1|TLSv1\.1)\s*$' /etc/dovecot/dovecot.conf /etc/dovecot/extra.conf; then if grep -qE 'ssl_min_protocol\s*=\s*(TLSv1|TLSv1\.1)\s*$' /etc/dovecot/dovecot.conf /etc/dovecot/extra.conf; then
@@ -260,89 +22,10 @@ if grep -qE 'ssl_min_protocol\s*=\s*(TLSv1|TLSv1\.1)\s*$' /etc/dovecot/dovecot.c
echo "CipherString = DEFAULT@SECLEVEL=0" >> /etc/ssl/openssl.cnf echo "CipherString = DEFAULT@SECLEVEL=0" >> /etc/ssl/openssl.cnf
fi fi
# Compile sieve scripts if [ $BOOTSTRAP_EXIT_CODE -ne 0 ]; then
sievec /var/vmail/sieve/global_sieve_before.sieve echo "Bootstrap failed with exit code $BOOTSTRAP_EXIT_CODE. Not starting Dovecot."
sievec /var/vmail/sieve/global_sieve_after.sieve exit $BOOTSTRAP_EXIT_CODE
sievec /usr/lib/dovecot/sieve/report-spam.sieve
sievec /usr/lib/dovecot/sieve/report-ham.sieve
# Fix permissions
chown root:root /etc/dovecot/sql/*.conf
chown root:dovecot /etc/dovecot/sql/dovecot-dict-sql-sieve* /etc/dovecot/sql/dovecot-dict-sql-quota* /etc/dovecot/auth/passwd-verify.lua
chmod 640 /etc/dovecot/sql/*.conf /etc/dovecot/auth/passwd-verify.lua
chown -R vmail:vmail /var/vmail/sieve
chown -R vmail:vmail /var/volatile
chown -R vmail:vmail /var/vmail_index
adduser vmail tty
chmod g+rw /dev/console
chown root:tty /dev/console
chmod +x /usr/lib/dovecot/sieve/rspamd-pipe-ham \
/usr/lib/dovecot/sieve/rspamd-pipe-spam \
/usr/local/bin/imapsync_runner.pl \
/usr/local/bin/imapsync \
/usr/local/bin/trim_logs.sh \
/usr/local/bin/sa-rules.sh \
/usr/local/bin/clean_q_aged.sh \
/usr/local/bin/maildir_gc.sh \
/usr/local/sbin/stop-supervisor.sh \
/usr/local/bin/quota_notify.py \
/usr/local/bin/repl_health.sh \
/usr/local/bin/optimize-fts.sh
# Prepare environment file for cronjobs
printenv | sed 's/^\(.*\)$/export \1/g' > /source_env.sh
# Clean old PID if any
[[ -f /var/run/dovecot/master.pid ]] && rm /var/run/dovecot/master.pid
# Clean stopped imapsync jobs
rm -f /tmp/imapsync_busy.lock
IMAPSYNC_TABLE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'imapsync'" -Bs)
[[ ! -z ${IMAPSYNC_TABLE} ]] && mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "UPDATE imapsync SET is_running='0'"
# Envsubst maildir_gc
echo "$(envsubst < /usr/local/bin/maildir_gc.sh)" > /usr/local/bin/maildir_gc.sh
# GUID generation
while [[ ${VERSIONS_OK} != 'OK' ]]; do
if [[ ! -z $(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = \"${DBNAME}\" AND TABLE_NAME = 'versions'") ]]; then
VERSIONS_OK=OK
else
echo "Waiting for versions table to be created..."
sleep 3
fi
done
PUBKEY_MCRYPT=$(doveconf -P 2> /dev/null | grep -i mail_crypt_global_public_key | cut -d '<' -f2)
if [ -f ${PUBKEY_MCRYPT} ]; then
GUID=$(cat <(echo ${MAILCOW_HOSTNAME}) /mail_crypt/ecpubkey.pem | sha256sum | cut -d ' ' -f1 | tr -cd "[a-fA-F0-9.:/] ")
if [ ${#GUID} -eq 64 ]; then
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
REPLACE INTO versions (application, version) VALUES ("GUID", "${GUID}");
EOF
else
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
REPLACE INTO versions (application, version) VALUES ("GUID", "INVALID");
EOF
fi
fi fi
# Collect SA rules once now echo "Bootstrap succeeded. Starting Dovecot..."
/usr/local/bin/sa-rules.sh /usr/sbin/dovecot -F
# Run hooks
for file in /hooks/*; do
if [ -x "${file}" ]; then
echo "Running hook ${file}"
"${file}"
fi
done
# For some strange, unknown and stupid reason, Dovecot may run into a race condition, when this file is not touched before it is read by dovecot/auth
# May be related to something inside Docker, I seriously don't know
touch /etc/dovecot/auth/passwd-verify.lua
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf
fi
exec "$@"

View File

@@ -1,2 +0,0 @@
#!/bin/bash
[ -d /var/vmail/_garbage/ ] && /usr/bin/find /var/vmail/_garbage/ -mindepth 1 -maxdepth 1 -type d -cmin +${MAILDIR_GC_TIME} -exec rm -r {} \;

View File

@@ -14,6 +14,11 @@ import sys
import html2text import html2text
from subprocess import Popen, PIPE, STDOUT from subprocess import Popen, PIPE, STDOUT
# Don't run if role is not master
if os.getenv("MASTER").lower() in ["n", "no"]:
sys.exit()
if len(sys.argv) > 2: if len(sys.argv) > 2:
percent = int(sys.argv[1]) percent = int(sys.argv[1])
username = str(sys.argv[2]) username = str(sys.argv[2])

View File

@@ -11,8 +11,8 @@ stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0 stderr_logfile_maxbytes=0
autostart=true autostart=true
[program:dovecot] [program:bootstrap]
command=/usr/sbin/dovecot -F command=/docker-entrypoint.sh
stdout_logfile=/dev/stdout stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0 stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr stderr_logfile=/dev/stderr

View File

@@ -9,7 +9,8 @@ RUN apk add --no-cache nginx \
py3-pip && \ py3-pip && \
pip install --upgrade pip && \ pip install --upgrade pip && \
pip install Jinja2 \ pip install Jinja2 \
mysql-connector-python mysql-connector-python \
redis
RUN mkdir -p /etc/nginx/includes RUN mkdir -p /etc/nginx/includes

View File

@@ -42,7 +42,8 @@ RUN groupadd -g 102 postfix \
RUN pip install --break-system-packages \ RUN pip install --break-system-packages \
mysql-connector-python \ mysql-connector-python \
jinja2 jinja2 \
redis
COPY data/Dockerfiles/bootstrap /bootstrap COPY data/Dockerfiles/bootstrap /bootstrap
COPY data/Dockerfiles/postfix/supervisord.conf /etc/supervisor/supervisord.conf COPY data/Dockerfiles/postfix/supervisord.conf /etc/supervisor/supervisord.conf
@@ -55,6 +56,7 @@ COPY data/Dockerfiles/postfix/stop-supervisor.sh /usr/local/sbin/stop-supervisor
COPY data/Dockerfiles/postfix/docker-entrypoint.sh /docker-entrypoint.sh COPY data/Dockerfiles/postfix/docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /usr/local/bin/rspamd-pipe-ham \ RUN chmod +x /usr/local/bin/rspamd-pipe-ham \
/docker-entrypoint.sh \
/usr/local/bin/rspamd-pipe-spam \ /usr/local/bin/rspamd-pipe-spam \
/usr/local/bin/whitelist_forwardinghosts.sh \ /usr/local/bin/whitelist_forwardinghosts.sh \
/usr/local/sbin/stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh

View File

@@ -45,7 +45,8 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
RUN pip install --break-system-packages \ RUN pip install --break-system-packages \
mysql-connector-python \ mysql-connector-python \
jinja2 jinja2 \
redis
COPY data/Dockerfiles/bootstrap /bootstrap COPY data/Dockerfiles/bootstrap /bootstrap

View File

@@ -0,0 +1 @@
{{ RAND_USER }}@mailcow.local:{{ RAND_PASS2 }}

View File

@@ -0,0 +1,14 @@
{% set QUOTA_TABLE = "quota2" if MASTER|lower in ["y", "yes"] else "quota2replica" %}
connect = "host=/var/run/mysqld/mysqld.sock dbname={{ DBNAME }} user={{ DBUSER }} password={{ DBPASS }}"
map {
pattern = priv/quota/storage
table = {{ QUOTA_TABLE }}
username_field = username
value_field = bytes
}
map {
pattern = priv/quota/messages
table = {{ QUOTA_TABLE }}
username_field = username
value_field = messages
}

View File

@@ -0,0 +1,19 @@
connect = "host=/var/run/mysqld/mysqld.sock dbname={{ DBNAME }} user={{ DBUSER }} password={{ DBPASS }}"
map {
pattern = priv/sieve/name/\$script_name
table = sieve_after
username_field = username
value_field = id
fields {
script_name = \$script_name
}
}
map {
pattern = priv/sieve/data/\$id
table = sieve_after
username_field = username
value_field = script_data
fields {
id = \$id
}
}

View File

@@ -0,0 +1,19 @@
connect = "host=/var/run/mysqld/mysqld.sock dbname={{ DBNAME }} user={{ DBUSER }} password={{ DBPASS }}"
map {
pattern = priv/sieve/name/\$script_name
table = sieve_before
username_field = username
value_field = id
fields {
script_name = \$script_name
}
}
map {
pattern = priv/sieve/data/\$id
table = sieve_before
username_field = username
value_field = script_data
fields {
id = \$id
}
}

View File

@@ -0,0 +1,4 @@
driver = mysql
connect = "host=/var/run/mysqld/mysqld.sock dbname={{ DBNAME }} user={{ DBUSER }} password={{ DBPASS }}"
user_query = SELECT CONCAT(JSON_UNQUOTE(JSON_VALUE(attributes, '$.mailbox_format')), mailbox_path_prefix, '%d/%n/{{ MAILDIR_SUB }}:VOLATILEDIR=/var/volatile/%u:INDEX=/var/vmail_index/%u') AS mail, '%s' AS protocol, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND (active = '1' OR active = '2')
iterate_query = SELECT username FROM mailbox WHERE active = '1' OR active = '2';

View File

@@ -0,0 +1,3 @@
{%- set master_user = DOVECOT_MASTER_USER or RAND_USER %}
{%- set master_pass = DOVECOT_MASTER_PASS or RAND_PASS %}
{{ master_user }}@mailcow.local:{SHA1}{{ master_pass | sha1 }}::::::

View File

@@ -0,0 +1 @@
{{ DOVECOT_MASTER_USER or RAND_USER }}@mailcow.local::5000:5000::::

View File

@@ -0,0 +1,309 @@
auth_mechanisms = plain login
#mail_debug = yes
#auth_debug = yes
#log_debug = category=fts-flatcurve # Activate Logging for Flatcurve FTS Searchings
log_path = syslog
disable_plaintext_auth = yes
# Uncomment on NFS share
#mmap_disable = yes
#mail_fsync = always
#mail_nfs_index = yes
#mail_nfs_storage = yes
login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k"
mail_home = /var/vmail/%d/%n
mail_location = maildir:~/
mail_plugins = </etc/dovecot/mail_plugins
mail_attachment_fs = crypt:set_prefix=mail_crypt_global:posix:
mail_attachment_dir = /var/attachments
mail_attachment_min_size = 128k
# Significantly speeds up very large mailboxes, but is only safe to enable if
# you do not manually modify the files in the `cur` directories in
# mailcowdockerized_vmail-vol-1.
# https://docs.mailcow.email/manual-guides/Dovecot/u_e-dovecot-performance/
maildir_very_dirty_syncs = yes
# Dovecot 2.2
#ssl_protocols = !SSLv3
# Dovecot 2.3
ssl_min_protocol = TLSv1.2
ssl_prefer_server_ciphers = yes
ssl_cipher_list = ALL:!ADH:!LOW:!SSLv2:!SSLv3:!EXP:!aNULL:!eNULL:!3DES:!MD5:!PSK:!DSS:!RC4:!SEED:!IDEA:+HIGH:+MEDIUM
# Default in Dovecot 2.3
ssl_options = no_compression no_ticket
# New in Dovecot 2.3
ssl_dh = </etc/ssl/mail/dhparams.pem
# Dovecot 2.2
#ssl_dh_parameters_length = 2048
log_timestamp = "%Y-%m-%d %H:%M:%S "
recipient_delimiter = +
auth_master_user_separator = *
mail_shared_explicit_inbox = yes
mail_prefetch_count = 30
passdb {
driver = lua
args = file=/etc/dovecot/auth/passwd-verify.lua blocking=yes cache_key=%s:%u:%w
result_success = return-ok
result_failure = continue
result_internalfail = continue
}
# try a master passwd
passdb {
driver = passwd-file
args = /etc/dovecot/dovecot-master.passwd
master = yes
skip = authenticated
}
# check for regular password - if empty (e.g. force-passwd-reset), previous pass=yes passdbs also fail
# a return of the following passdb is mandatory
passdb {
driver = lua
args = file=/etc/dovecot/auth/passwd-verify.lua blocking=yes
}
# Set doveadm_password=your-secret-password in data/conf/dovecot/extra.conf (create if missing)
service doveadm {
inet_listener {
port = 12345
}
vsz_limit=2048 MB
}
{% include 'dovecot.folders.conf.j2' %}
protocols = imap sieve lmtp pop3
service dict {
unix_listener dict {
mode = 0660
user = vmail
group = vmail
}
}
service log {
user = dovenull
}
service config {
unix_listener config {
user = root
group = vmail
mode = 0660
}
}
service auth {
inet_listener auth-inet {
port = 10001
}
unix_listener auth-master {
mode = 0600
user = vmail
}
unix_listener auth-userdb {
mode = 0600
user = vmail
}
vsz_limit = 2G
}
service managesieve-login {
inet_listener sieve {
port = 4190
}
inet_listener sieve_haproxy {
port = 14190
haproxy = yes
}
service_count = 1
process_min_avail = 2
vsz_limit = 1G
}
service imap-login {
service_count = 1
process_min_avail = 2
process_limit = 10000
vsz_limit = 1G
user = dovenull
inet_listener imap_haproxy {
port = 10143
haproxy = yes
}
inet_listener imaps_haproxy {
port = 10993
ssl = yes
haproxy = yes
}
}
service pop3-login {
service_count = 1
process_min_avail = 1
vsz_limit = 1G
inet_listener pop3_haproxy {
port = 10110
haproxy = yes
}
inet_listener pop3s_haproxy {
port = 10995
ssl = yes
haproxy = yes
}
}
service imap {
executable = imap
user = vmail
vsz_limit = 1G
}
service managesieve {
process_limit = 256
}
service lmtp {
inet_listener lmtp-inet {
port = 24
}
user = vmail
}
listen = *,[::]
ssl_cert = </etc/ssl/mail/cert.pem
ssl_key = </etc/ssl/mail/key.pem
userdb {
driver = passwd-file
args = /etc/dovecot/dovecot-master.userdb
}
userdb {
args = /etc/dovecot/sql/dovecot-dict-sql-userdb.conf
driver = sql
skip = found
}
protocol imap {
mail_plugins = </etc/dovecot/mail_plugins_imap
imap_metadata = yes
}
mail_attribute_dict = file:%h/dovecot-attributes
protocol lmtp {
mail_plugins = </etc/dovecot/mail_plugins_lmtp
auth_socket_path = /var/run/dovecot/auth-master
}
protocol sieve {
managesieve_logout_format = bytes=%i/%o
}
plugin {
# Allow "any" or "authenticated" to be used in ACLs
acl_anyone = {{ ACL_ANYONE }}
acl_shared_dict = file:/var/vmail/shared-mailboxes.db
acl = vfile
acl_user = %u
quota = dict:Userquota::proxy::sqlquota
quota_rule2 = Trash:storage=+100%%
sieve = /var/vmail/sieve/%u.sieve
sieve_plugins = sieve_imapsieve sieve_extprograms
sieve_vacation_send_from_recipient = yes
sieve_redirect_envelope_from = recipient
# From elsewhere to Spam folder
imapsieve_mailbox1_name = Junk
imapsieve_mailbox1_causes = COPY
imapsieve_mailbox1_before = file:/usr/lib/dovecot/sieve/report-spam.sieve
# END
# From Spam folder to elsewhere
imapsieve_mailbox2_name = *
imapsieve_mailbox2_from = Junk
imapsieve_mailbox2_causes = COPY
imapsieve_mailbox2_before = file:/usr/lib/dovecot/sieve/report-ham.sieve
# END
master_user = %u
quota_warning = storage=95%% quota-warning 95 %u
quota_warning2 = storage=80%% quota-warning 80 %u
sieve_pipe_bin_dir = /usr/lib/dovecot/sieve
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute
sieve_extensions = +notify +imapflags +vacation-seconds +editheader
sieve_max_script_size = 1M
sieve_max_redirects = 100
sieve_max_actions = 101
sieve_quota_max_scripts = 0
sieve_quota_max_storage = 0
listescape_char = "\\"
sieve_vacation_min_period = 5s
sieve_vacation_max_period = 0
sieve_vacation_default_period = 60s
sieve_before = /var/vmail/sieve/global_sieve_before.sieve
sieve_before2 = dict:proxy::sieve_before;name=active;bindir=/var/vmail/sieve_before_bindir
sieve_after = dict:proxy::sieve_after;name=active;bindir=/var/vmail/sieve_after_bindir
sieve_after2 = /var/vmail/sieve/global_sieve_after.sieve
sieve_duplicate_default_period = 1m
sieve_duplicate_max_period = 7d
# -- Global keys
mail_crypt_global_private_key = </mail_crypt/ecprivkey.pem
mail_crypt_global_public_key = </mail_crypt/ecpubkey.pem
mail_crypt_save_version = 2
# Enable compression while saving, lz4 Dovecot v2.3.17+
zlib_save = lz4
mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename
mail_log_fields = uid box msgid size
mail_log_cached_only = yes
# Try set mail_replica
{% include 'mail_replica.conf.j2' %}
}
service quota-warning {
executable = script /usr/local/bin/quota_notify.py
# use some unprivileged user for executing the quota warnings
user = vmail
unix_listener quota-warning {
user = vmail
}
}
dict {
sqlquota = mysql:/etc/dovecot/sql/dovecot-dict-sql-quota.conf
sieve_after = mysql:/etc/dovecot/sql/dovecot-dict-sql-sieve_after.conf
sieve_before = mysql:/etc/dovecot/sql/dovecot-dict-sql-sieve_before.conf
}
remote 127.0.0.1 {
disable_plaintext_auth = no
}
submission_host = postfix:588
mail_max_userip_connections = 500
service stats {
unix_listener stats-writer {
mode = 0660
user = vmail
}
}
imap_max_line_length = 2 M
auth_cache_verify_password_with_worker = yes
auth_cache_negative_ttl = 60s
auth_cache_ttl = 300s
auth_cache_size = 10M
auth_verbose_passwords = sha1:6
service replicator {
process_min_avail = 1
}
service aggregator {
fifo_listener replication-notify-fifo {
user = vmail
}
unix_listener replication-notify {
user = vmail
}
}
service replicator {
unix_listener replicator-doveadm {
mode = 0666
}
}
replication_max_conns = 10
doveadm_port = 12345
replication_dsync_parameters = -d -l 30 -U -n INBOX
{% include 'sogo_trusted_ip.conf.j2' %}
{% include 'shared_namespace.conf.j2' %}
{% include 'fts.conf.j2' %}
{% include 'sni.conf.j2' %}
# <Includes>
!include_try /etc/dovecot/extra.conf
# </Includes>
default_client_limit = 10400
default_vsz_limit = 1024 M

View File

@@ -1,308 +1,308 @@
namespace inbox { namespace inbox {
inbox = yes inbox = yes
location = location =
separator = / separator = /
mailbox "Trash" { mailbox "Trash" {
auto = subscribe auto = subscribe
special_use = \Trash special_use = \Trash
} }
mailbox "Deleted Messages" { mailbox "Deleted Messages" {
special_use = \Trash special_use = \Trash
} }
mailbox "Deleted Items" { mailbox "Deleted Items" {
special_use = \Trash special_use = \Trash
} }
mailbox "Rubbish" { mailbox "Rubbish" {
special_use = \Trash special_use = \Trash
} }
mailbox "Gelöschte Objekte" { mailbox "Gelöschte Objekte" {
special_use = \Trash special_use = \Trash
} }
mailbox "Gelöschte Elemente" { mailbox "Gelöschte Elemente" {
special_use = \Trash special_use = \Trash
} }
mailbox "Papierkorb" { mailbox "Papierkorb" {
special_use = \Trash special_use = \Trash
} }
mailbox "Itens Excluidos" { mailbox "Itens Excluidos" {
special_use = \Trash special_use = \Trash
} }
mailbox "Itens Excluídos" { mailbox "Itens Excluídos" {
special_use = \Trash special_use = \Trash
} }
mailbox "Lixeira" { mailbox "Lixeira" {
special_use = \Trash special_use = \Trash
} }
mailbox "Prullenbak" { mailbox "Prullenbak" {
special_use = \Trash special_use = \Trash
} }
mailbox "Odstránené položky" { mailbox "Odstránené položky" {
special_use = \Trash special_use = \Trash
} }
mailbox "Koš" { mailbox "Koš" {
special_use = \Trash special_use = \Trash
} }
mailbox "Verwijderde items" { mailbox "Verwijderde items" {
special_use = \Trash special_use = \Trash
} }
mailbox "Удаленные" { mailbox "Удаленные" {
special_use = \Trash special_use = \Trash
} }
mailbox "Удаленные элементы" { mailbox "Удаленные элементы" {
special_use = \Trash special_use = \Trash
} }
mailbox "Корзина" { mailbox "Корзина" {
special_use = \Trash special_use = \Trash
} }
mailbox "Видалені" { mailbox "Видалені" {
special_use = \Trash special_use = \Trash
} }
mailbox "Видалені елементи" { mailbox "Видалені елементи" {
special_use = \Trash special_use = \Trash
} }
mailbox "Кошик" { mailbox "Кошик" {
special_use = \Trash special_use = \Trash
} }
mailbox "废件箱" { mailbox "废件箱" {
special_use = \Trash special_use = \Trash
} }
mailbox "已删除消息" { mailbox "已删除消息" {
special_use = \Trash special_use = \Trash
} }
mailbox "已删除邮件" { mailbox "已删除邮件" {
special_use = \Trash special_use = \Trash
} }
mailbox "Archive" { mailbox "Archive" {
auto = subscribe auto = subscribe
special_use = \Archive special_use = \Archive
} }
mailbox "Archiv" { mailbox "Archiv" {
special_use = \Archive special_use = \Archive
} }
mailbox "Archives" { mailbox "Archives" {
special_use = \Archive special_use = \Archive
} }
mailbox "Arquivo" { mailbox "Arquivo" {
special_use = \Archive special_use = \Archive
} }
mailbox "Arquivos" { mailbox "Arquivos" {
special_use = \Archive special_use = \Archive
} }
mailbox "Archief" { mailbox "Archief" {
special_use = \Archive special_use = \Archive
} }
mailbox "Archív" { mailbox "Archív" {
special_use = \Archive special_use = \Archive
} }
mailbox "Archivovať" { mailbox "Archivovať" {
special_use = \Archive special_use = \Archive
} }
mailbox "归档" { mailbox "归档" {
special_use = \Archive special_use = \Archive
} }
mailbox "Архив" { mailbox "Архив" {
special_use = \Archive special_use = \Archive
} }
mailbox "Архів" { mailbox "Архів" {
special_use = \Archive special_use = \Archive
} }
mailbox "Sent" { mailbox "Sent" {
auto = subscribe auto = subscribe
special_use = \Sent special_use = \Sent
} }
mailbox "Sent Messages" { mailbox "Sent Messages" {
special_use = \Sent special_use = \Sent
} }
mailbox "Sent Items" { mailbox "Sent Items" {
special_use = \Sent special_use = \Sent
} }
mailbox "已发送" { mailbox "已发送" {
special_use = \Sent special_use = \Sent
} }
mailbox "已发送消息" { mailbox "已发送消息" {
special_use = \Sent special_use = \Sent
} }
mailbox "已发送邮件" { mailbox "已发送邮件" {
special_use = \Sent special_use = \Sent
} }
mailbox "Отправленные" { mailbox "Отправленные" {
special_use = \Sent special_use = \Sent
} }
mailbox "Отправленные элементы" { mailbox "Отправленные элементы" {
special_use = \Sent special_use = \Sent
} }
mailbox "Надіслані" { mailbox "Надіслані" {
special_use = \Sent special_use = \Sent
} }
mailbox "Надіслані елементи" { mailbox "Надіслані елементи" {
special_use = \Sent special_use = \Sent
} }
mailbox "Gesendet" { mailbox "Gesendet" {
special_use = \Sent special_use = \Sent
} }
mailbox "Gesendete Objekte" { mailbox "Gesendete Objekte" {
special_use = \Sent special_use = \Sent
} }
mailbox "Gesendete Elemente" { mailbox "Gesendete Elemente" {
special_use = \Sent special_use = \Sent
} }
mailbox "Itens Enviados" { mailbox "Itens Enviados" {
special_use = \Sent special_use = \Sent
} }
mailbox "Enviados" { mailbox "Enviados" {
special_use = \Sent special_use = \Sent
} }
mailbox "Verzonden items" { mailbox "Verzonden items" {
special_use = \Sent special_use = \Sent
} }
mailbox "Verzonden" { mailbox "Verzonden" {
special_use = \Sent special_use = \Sent
} }
mailbox "Odoslaná pošta" { mailbox "Odoslaná pošta" {
special_use = \Sent special_use = \Sent
} }
mailbox "Odoslané" { mailbox "Odoslané" {
special_use = \Sent special_use = \Sent
} }
mailbox "Drafts" { mailbox "Drafts" {
auto = subscribe auto = subscribe
special_use = \Drafts special_use = \Drafts
} }
mailbox "Entwürfe" { mailbox "Entwürfe" {
special_use = \Drafts special_use = \Drafts
} }
mailbox "Rascunhos" { mailbox "Rascunhos" {
special_use = \Drafts special_use = \Drafts
} }
mailbox "Concepten" { mailbox "Concepten" {
special_use = \Drafts special_use = \Drafts
} }
mailbox "Koncepty" { mailbox "Koncepty" {
special_use = \Drafts special_use = \Drafts
} }
mailbox "草稿" { mailbox "草稿" {
special_use = \Drafts special_use = \Drafts
} }
mailbox "草稿箱" { mailbox "草稿箱" {
special_use = \Drafts special_use = \Drafts
} }
mailbox "Черновики" { mailbox "Черновики" {
special_use = \Drafts special_use = \Drafts
} }
mailbox "Чернетки" { mailbox "Чернетки" {
special_use = \Drafts special_use = \Drafts
} }
mailbox "Junk" { mailbox "Junk" {
auto = subscribe auto = subscribe
special_use = \Junk special_use = \Junk
} }
mailbox "Junk-E-Mail" { mailbox "Junk-E-Mail" {
special_use = \Junk special_use = \Junk
} }
mailbox "Junk E-Mail" { mailbox "Junk E-Mail" {
special_use = \Junk special_use = \Junk
} }
mailbox "Spam" { mailbox "Spam" {
special_use = \Junk special_use = \Junk
} }
mailbox "Lixo Eletrônico" { mailbox "Lixo Eletrônico" {
special_use = \Junk special_use = \Junk
} }
mailbox "Nevyžiadaná pošta" { mailbox "Nevyžiadaná pošta" {
special_use = \Junk special_use = \Junk
} }
mailbox "Infikované položky" { mailbox "Infikované položky" {
special_use = \Junk special_use = \Junk
} }
mailbox "Ongewenste e-mail" { mailbox "Ongewenste e-mail" {
special_use = \Junk special_use = \Junk
} }
mailbox "垃圾" { mailbox "垃圾" {
special_use = \Junk special_use = \Junk
} }
mailbox "垃圾箱" { mailbox "垃圾箱" {
special_use = \Junk special_use = \Junk
} }
mailbox "Нежелательная почта" { mailbox "Нежелательная почта" {
special_use = \Junk special_use = \Junk
} }
mailbox "Спам" { mailbox "Спам" {
special_use = \Junk special_use = \Junk
} }
mailbox "Небажана пошта" { mailbox "Небажана пошта" {
special_use = \Junk special_use = \Junk
} }
mailbox "Koncepty" { mailbox "Koncepty" {
special_use = \Drafts special_use = \Drafts
} }
mailbox "Nevyžádaná pošta" { mailbox "Nevyžádaná pošta" {
special_use = \Junk special_use = \Junk
} }
mailbox "Odstraněná pošta" { mailbox "Odstraněná pošta" {
special_use = \Trash special_use = \Trash
} }
mailbox "Odeslaná pošta" { mailbox "Odeslaná pošta" {
special_use = \Sent special_use = \Sent
} }
mailbox "Skräp" { mailbox "Skräp" {
special_use = \Trash special_use = \Trash
} }
mailbox "Borttagna Meddelanden" { mailbox "Borttagna Meddelanden" {
special_use = \Trash special_use = \Trash
} }
mailbox "Arkiv" { mailbox "Arkiv" {
special_use = \Archive special_use = \Archive
} }
mailbox "Arkeverat" { mailbox "Arkeverat" {
special_use = \Archive special_use = \Archive
} }
mailbox "Skickat" { mailbox "Skickat" {
special_use = \Sent special_use = \Sent
} }
mailbox "Skickade Meddelanden" { mailbox "Skickade Meddelanden" {
special_use = \Sent special_use = \Sent
} }
mailbox "Utkast" { mailbox "Utkast" {
special_use = \Drafts special_use = \Drafts
} }
mailbox "Skraldespand" { mailbox "Skraldespand" {
special_use = \Trash special_use = \Trash
} }
mailbox "Slettet mails" { mailbox "Slettet mails" {
special_use = \Trash special_use = \Trash
} }
mailbox "Arkiv" { mailbox "Arkiv" {
special_use = \Archive special_use = \Archive
} }
mailbox "Arkiveret mails" { mailbox "Arkiveret mails" {
special_use = \Archive special_use = \Archive
} }
mailbox "Sendt" { mailbox "Sendt" {
special_use = \Sent special_use = \Sent
} }
mailbox "Sendte mails" { mailbox "Sendte mails" {
special_use = \Sent special_use = \Sent
} }
mailbox "Udkast" { mailbox "Udkast" {
special_use = \Drafts special_use = \Drafts
} }
mailbox "Kladde" { mailbox "Kladde" {
special_use = \Drafts special_use = \Drafts
} }
mailbox "Πρόχειρα" { mailbox "Πρόχειρα" {
special_use = \Drafts special_use = \Drafts
} }
mailbox "Απεσταλμένα" { mailbox "Απεσταλμένα" {
special_use = \Sent special_use = \Sent
} }
mailbox "Κάδος απορριμάτων" { mailbox "Κάδος απορριμάτων" {
special_use = \Trash special_use = \Trash
} }
mailbox "Ανεπιθύμητα" { mailbox "Ανεπιθύμητα" {
special_use = \Junk special_use = \Junk
} }
mailbox "Αρχειοθετημένα" { mailbox "Αρχειοθετημένα" {
special_use = \Archive special_use = \Archive
} }
prefix = prefix =
} }

View File

@@ -1,4 +1,4 @@
# mailcow FTS Flatcurve Settings, change them as you like. {% if SKIP_FTS|lower in ['n', 'no'] %}
plugin { plugin {
fts_autoindex = yes fts_autoindex = yes
fts_autoindex_exclude = \Junk fts_autoindex_exclude = \Junk
@@ -24,14 +24,11 @@ plugin {
fts_index_timeout = 300s fts_index_timeout = 300s
} }
### THIS PART WILL BE CHANGED BY MODIFYING mailcow.conf AUTOMATICALLY DURING RUNTIME! ###
service indexer-worker { service indexer-worker {
# Max amount of simultaniously running indexer jobs. # Max amount of simultaniously running indexer jobs.
process_limit=1 process_limit = {{ FTS_PROCS }}
# Max amount of RAM used by EACH indexer process. # Max amount of RAM used by EACH indexer process.
vsz_limit=128 MB vsz_limit = {{ FTS_HEAP }} MB
} }
{% endif %}
### THIS PART WILL BE CHANGED BY MODIFYING mailcow.conf AUTOMATICALLY DURING RUNTIME! ###

View File

@@ -0,0 +1,5 @@
{%- if SKIP_FTS|lower in ["y", "yes"] -%}
quota acl zlib mail_crypt mail_crypt_acl mail_log notify listescape replication lazy_expunge
{%- else -%}
quota acl zlib mail_crypt mail_crypt_acl mail_log notify fts fts_flatcurve listescape replication lazy_expunge
{%- endif -%}

View File

@@ -0,0 +1,5 @@
{%- if SKIP_FTS|lower in ["y", "yes"] -%}
quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify listescape replication mail_log
{%- else -%}
quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify mail_log fts fts_flatcurve listescape replication
{%- endif -%}

View File

@@ -0,0 +1,5 @@
{%- if SKIP_FTS|lower in ["y", "yes"] -%}
quota sieve acl zlib mail_crypt mail_crypt_acl notify listescape replication
{%- else -%}
quota sieve acl zlib mail_crypt mail_crypt_acl fts fts_flatcurve notify listescape replication
{%- endif -%}

View File

@@ -0,0 +1,3 @@
{% if MAILCOW_REPLICA_IP and DOVEADM_REPLICA_PORT %}
mail_replica = tcp:{{ MAILCOW_REPLICA_IP }}:{{ DOVEADM_REPLICA_PORT }}
{% endif %}

View File

@@ -0,0 +1,2 @@
#!/bin/bash
[ -d /var/vmail/_garbage/ ] && /usr/bin/find /var/vmail/_garbage/ -mindepth 1 -maxdepth 1 -type d -cmin +{{ MAILDIR_GC_TIME }} -exec rm -r {} \;

View File

@@ -0,0 +1,9 @@
{% set MAILDIR_SUB_SHARED = '' if not MAILDIR_SUB else '/' ~ MAILDIR_SUB %}
namespace {
type = shared
separator = /
prefix = Shared/%%u/
location = maildir:%%h{{ MAILDIR_SUB_SHARED }}:INDEX=~{{ MAILDIR_SUB_SHARED }}/Shared/%%u
subscriptions = no
list = children
}

View File

@@ -0,0 +1 @@
{{ DOVECOT_MASTER_USER or RAND_USER }}@mailcow.local:{{ DOVECOT_MASTER_PASS or RAND_PASS }}

View File

@@ -0,0 +1,6 @@
{% for domain, path in VALID_CERT_DIRS.items() %}
local_name "{{ domain }}" {
ssl_cert = <{{ path }}/cert.pem
ssl_key = <{{ path }}/key.pem
}
{% endfor %}

View File

@@ -0,0 +1 @@
{{ RAND_PASS2 }}

View File

@@ -0,0 +1,3 @@
remote {{ IPV4_NETWORK }}.248 {
disable_plaintext_auth = no
}

View File

@@ -0,0 +1,3 @@
{% for key, value in ENV_VARS.items() %}
export {{ key }}="{{ value | replace('"', '\\"') }}"
{% endfor %}

View File

@@ -1,9 +0,0 @@
#hosts = 1.2.3.4
#dn = cn=admin,dc=example,dc=local
#dnpass = password
#ldap_version = 3
#base = ou=People,dc=example,dc=local
#auth_bind = no
#pass_filter = (&(objectClass=posixAccount)(mail=%u))
#pass_attrs = mail=user,userPassword=password
#default_pass_scheme = SSHA

View File

@@ -252,7 +252,7 @@ services:
- sogo - sogo
dovecot-mailcow: dovecot-mailcow:
image: ghcr.io/mailcow/dovecot:2.33 image: ghcr.io/mailcow/dovecot:nightly-19052025
depends_on: depends_on:
- mysql-mailcow - mysql-mailcow
- netfilter-mailcow - netfilter-mailcow
@@ -267,6 +267,7 @@ services:
- ./data/assets/ssl:/etc/ssl/mail/:ro,z - ./data/assets/ssl:/etc/ssl/mail/:ro,z
- ./data/conf/sogo/:/etc/sogo/:z - ./data/conf/sogo/:/etc/sogo/:z
- ./data/conf/phpfpm/sogo-sso/:/etc/phpfpm/:z - ./data/conf/phpfpm/sogo-sso/:/etc/phpfpm/:z
- ./data/web/inc/init_db.inc.php:/init_db.inc.php:z
- vmail-vol-1:/var/vmail - vmail-vol-1:/var/vmail
- vmail-index-vol-1:/var/vmail_index - vmail-index-vol-1:/var/vmail_index
- crypt-vol-1:/mail_crypt/ - crypt-vol-1:/mail_crypt/
@@ -275,6 +276,7 @@ services:
- rspamd-vol-1:/var/lib/rspamd - rspamd-vol-1:/var/lib/rspamd
- mysql-socket-vol-1:/var/run/mysqld/ - mysql-socket-vol-1:/var/run/mysqld/
environment: environment:
- CONTAINER_NAME=dovecot-mailcow
- DOVECOT_MASTER_USER=${DOVECOT_MASTER_USER:-} - DOVECOT_MASTER_USER=${DOVECOT_MASTER_USER:-}
- DOVECOT_MASTER_PASS=${DOVECOT_MASTER_PASS:-} - DOVECOT_MASTER_PASS=${DOVECOT_MASTER_PASS:-}
- MAILCOW_REPLICA_IP=${MAILCOW_REPLICA_IP:-} - MAILCOW_REPLICA_IP=${MAILCOW_REPLICA_IP:-}