Mailcow HowTo

Aus Neobikers Wiki
Zur Navigation springen Zur Suche springen

Übersicht

Mein aktueller Mailserver (Kopano) wird nicht mehr aktualisiert, ich muss migrieren.

Nach langem Überlegen welchen Mailserver anschliessend verwenden möchte, bin ich bei mailcow gelandet.

Die Gründe sind:

  • nie mehr migrieren müssen (nach Scalix -> Zarafa -> Kopano-> nicht grommunio)
  • Android/Apple Handy Anbindung mittels ActiveSync
  • mit SOGo ist ein Webmailer vorhanden, inkl. Kalender und Kontakte
  • möglichst wenig Pflegeaufwände (-> nicht Nextcloud + SOGo + xyz)

mailcow ist ein komplett konfigurierter Stack von Standard open source Mail-Server Programmen. Fertig konfiguriert verpackt in einem Docker container (der sogar täglich neu aktualisiert werden könnte). Der eigene Mailserver mit Webmail für Zuhause im CI/CD Modus sozusagen.

Vorbereitungen

DNS: autodiscover / autoconfig

Im DNS helfen autodiscover und autoconfig Einträge bei der Installation der Clients (Handys und Outlook).

Zertifikat

Der Server benötigt ein internes Zertifikat, welches in den mailcow container kopiert werden muss.

Proxy + Let's Encrypt

Mein nginx Proxy auf der Firewall bedient den mailcow Server hinter meiner dynamischen IP Adresse und bezieht bei Lets Encrypt die notwendigen externen Zertifikate.


Installation

getmail

Ich installiere getmail (anstatt fetchmail) auf dem server, weil mein Server ja hinter einer dynamischen IP Adresse steht, und nicht direkt als (SMTP Server) im Internet eingebunden ist. Ich habe eine getmail Version von Christian Bur (https://github.com/christianbur/getmail.git) modifiziert, welche in einem eigenem docker container auf dem mailcow docker host parallel zu mailcow installiert ist.

Ich habe gemerkt, dass getmail per LMTP die Emails an RSPAMD vorbei direkt abliefert. Ich möchte die Emails aber "ganz normal" über den komplettes Mail-Stack laufen lassen. Deshalb habe ich das Protokoll SMTP optional ergänzt, welches in der settings.ini individuell statt LMTP definiert werden kann. Wenn eine Email von RSPAMD abgelehnt wird, wird getmail die Email bei meinem Email-Hoster in den Junk Ordner verschieben (nicht abholen).

TODO: Ich müsste vielleicht noch ergänzen, dass man in den settings.ini einstellen kann, diese Emails gleich zu löschen.

Anstatt getmail_imap2lmtp.py verwende ich Dockerfiles/getmail_imap2xmtp.py:

import ssl
import time
import os
import datetime
import email
import smtplib
import threading
import traceback
import re
import base64
import quopri
import signal
import logging
import sys
import socket

import imapclient
import configparser

class Getmail(threading.Thread):

    def __init__(self, configparser_file, config_name):
        threading.Thread.__init__(self)
        self.event = threading.Event()
        #self.configparser_file = configparser_file
        #self.config_name = config_name
        #self.setName("Thread-%s" % config_name)
        self.name = "Thread-%s" % config_name
        self.imap = None
        self.exit_imap_idle_loop = False
        self.exception_counter = 0
        self.print_lock = threading.Lock()
        self.last_renew_imap_idle_connection = time.monotonic()
        self.idle_check_timeout = 1

        self.imap_hostname    = configparser_file.get(       config_name, 'imap_hostname')
        self.imap_port        = configparser_file.getint(    config_name, 'imap_port')
        self.imap_ssl         = configparser_file.getboolean(config_name, 'imap_ssl', fallback=True)
        self.imap_username    = configparser_file.get(       config_name, 'imap_username')
        self.imap_password    = configparser_file.get(       config_name, 'imap_password')
        self.imap_move_folder = configparser_file.get(       config_name, 'imap_move_folder')
        self.imap_sync_folder = configparser_file.get(       config_name, 'imap_sync_folder')
        self.imap_move_enable = configparser_file.getboolean(config_name, 'imap_move_enable')
        self.imap_debug       = configparser_file.getboolean(config_name, 'imap_debug')
        self.mail_hostname    = configparser_file.get(       config_name, 'lmtp_hostname')
        self.mail_port        = configparser_file.getint(    config_name, 'lmtp_port')
        self.mail_recipient   = configparser_file.get(       config_name, 'lmtp_recipient')
        self.mail_debug       = configparser_file.getboolean(config_name, 'lmtp_debug')
#        self.mail_hostname    = configparser_file.get(       config_name, 'mail_hostname')
#        self.mail_port        = configparser_file.getint(    config_name, 'mail_port')
#        self.mail_recipient   = configparser_file.get(       config_name, 'mail_recipient')
#        self.mail_debug       = configparser_file.getboolean(config_name, 'mail_debug')
        self.mail_protocol    = configparser_file.get(       config_name, 'mail_protocol')

    def run(self):
        while not self.exit_imap_idle_loop:
          try:
            self.event.wait(5)
            self.imap_idle()
          except Exception as e:
            logging.error("ERROR: %s" % (e))
            #traceback.print_exc()

          if not self.exit_imap_idle_loop:
            self.exception_counter += 1
            logging.error("ERROR: restart thread in %s minutes (counter: %d)" % (self.exception_counter * self.exception_counter, self.exception_counter))
            self.event.wait(60 * self.exception_counter * self.exception_counter )

    def imap_idle_stop(self):
        logging.info("IMAP_IDLE_STOP")
        self.exit_imap_idle_loop = True
        self.event.set()


    def imap_start_connection(self):
        logging.info("Start Getmail - server: %s:%s, username: %s, ssl: %s" % (self.imap_hostname, self.imap_port, self.imap_username, self.imap_ssl))

        self.imap = imapclient.IMAPClient(self.imap_hostname, port=self.imap_port, ssl=self.imap_ssl, use_uid=True)
        login_status = self.imap.login(self.imap_username, self.imap_password).decode("utf-8")
        logging.info("Login - status: %s" % login_status)

        if not self.imap.has_capability('IDLE'):
            logging.error("Server doesn't support IDLE!!")
            sys.exit()

#        if self.imap_debug:
#          self.imap.debug = True
#          logging.basicConfig(level=logging.DEBUG)
#        else:
#          self.imap.debug = False
#          logging.basicConfig(level=logging.INFO)

        self.imap.select_folder(self.imap_sync_folder)

        self.exception_counter = 0


    def imap_close_connection(self):
        if self.imap != None:
          status_logout = self.imap.logout()
          #logging.info("Close IMAP connection - status_logout: %s" % (status_logout))

    def imap_idle(self):
        self.imap_start_connection()
        self.create_imap_move_folder()
        logging.info("IMAP fetch mail - initial")
        self.imap_fetch_mail()

        # Start IDLE mode
        self.imap.idle()

        logging.info("Join infinite loop and wait for new mails, cancel with Ctrl-c")
        while not self.exit_imap_idle_loop:

            # Wait for up to x seconds for an IDLE response
            # https://imapclient.readthedocs.io/en/2.1.0/advanced.html
            start_time_idle_check = time.monotonic()
            responses = self.imap.idle_check(timeout=self.idle_check_timeout)
            execution_time_idle_check = time.monotonic() - start_time_idle_check

            self.check_imap_idle_response(responses, execution_time_idle_check)

        # End IDLE mode
        self.imap.idle_done()
        self.imap_close_connection()

    def check_imap_idle_response(self, responses, execution_time_idle_check):
        #https://tools.ietf.org/html/rfc3501#page-71

        self.renew_imap_idle_connection()

        if responses == []:
            if (execution_time_idle_check < self.idle_check_timeout / 2):
              #logging.info("TEST -- IMAP IDLE response: %s " % responses)
              raise Exception('idle_check responded too quickly, something is wrong with the IMAP Idle connection (execution_time_idle_check: %s' % execution_time_idle_check)
            else:
              # default action, when everything is ok
              return
        elif responses == None:
            return
        elif responses == [(b'OK', b'Still here')]:
            return
        elif responses == [(b'BYE', b'timeout')]:
            raise Exception('IMAP Connection Timeout, restart connection')

        logging.debug("IMAP IDLE response: %s " % responses)
        for item in responses:
            if len(item) == 2:
              if item[1] == b'EXISTS':
                self.imap.idle_done()
                self.imap_fetch_mail()
                self.imap.idle()


    def renew_imap_idle_connection(self):
        # https://tools.ietf.org/html/rfc2177
        # Because of that, clients using IDLE are advised to terminate the IDLE and
        # re-issue it at least every 29 minutes to avoid being logged off.
        fifteen_minutes = 15*60

        if time.monotonic() - self.last_renew_imap_idle_connection > fifteen_minutes:
            self.last_renew_imap_idle_connection = time.monotonic()
            logging.debug("renew imap idle session")
            self.imap.idle_done()
            self.imap.idle()
            self.check_imap_idle_response_counter_between_renew = 0

    def imap_fetch_mail(self):
        messages = self.imap.search(criteria=u'ALL')
        for uid, message_data in self.imap.fetch(messages, 'RFC822').items():
            try:
                # Mail parsen
                email_message = email.message_from_bytes(message_data[b'RFC822'])

                # Auslieferung versuchen
                delivery_status = self.xmtp_deliver_mail(email_message)

                if delivery_status is True:
                    # Fall A: Erfolgreich zugestellt
                    if self.imap_move_enable:
                        self.imap_move_mail(uid)
                    else:
                        self.imap_delete_mail(uid)
                else:
                    # Fall B: Mail wurde von Mailcow abgelehnt (Spam/Reject)
                    logging.info(f"Verschiebe Mail {uid} in Junk (Mailcow Reject)...")
                    self.imap.move(uid, 'Junk')

            except (ConnectionError, smtplib.SMTPException, socket.error) as e:
                # Fall C: Technischer Fehler (Server down, Timeout)
                # WICHTIG: Kein move, kein delete -> Mail bleibt im Posteingang für nächsten Run
                logging.error(f"Technischer Fehler bei UID {uid}, Mail bleibt im Posteingang: {e}")
                continue

            except Exception as e:
                # Fall D: Unbekannter/Kritischer Fehler (z.B. Parsing)
                logging.error(f"Kritischer Fehler bei UID {uid}: {e}")
                traceback.print_exc()
                logging.info(f"Sicherheitshalber Verschieben in Junk: {uid}")
                self.imap.move(uid, 'Junk')

    def imap_delete_mail(self, uid):
        self.imap.delete_messages([uid])
        self.imap.expunge()
        logging.info('IMAP delete: delete email (uid: %s)' % str(uid) )

    def create_imap_move_folder(self):
        if self.imap_move_enable:
          if self.imap.folder_exists(self.imap_move_folder):
            logging.info("imap_move_folder (%s) already exists, nothing to do." % (self.imap_move_folder))
          else:
            status =  self.imap.create_folder(self.imap_move_folder)
            logging.info("imap_move_folder (%s) create status: %s " % (self.imap_move_folder, status))


    def imap_move_mail(self, uid):
        self.imap.move(uid, self.imap_move_folder)
        logging.info('IMAP move: move email to imap_move_folder (%s)' % (self.imap_move_folder) )


    def xmtp_deliver_mail(self, email_message):
        mail_X = 'S' if self.mail_protocol.strip().lower() == 'smtp' else 'L'
        logging.info(f"{mail_X}MTP deliver: start -- Host: {self.mail_hostname}:{self.mail_port}")

        xmtp = None
        try:
            # 1. Verbindungsaufbau
            try:
                if mail_X == 'S':
                    xmtp = smtplib.SMTP(self.mail_hostname, self.mail_port, timeout=30)
                else:
                    xmtp = smtplib.LMTP(self.mail_hostname, self.mail_port, timeout=30)
            except Exception as e:
                logging.error(f"{mail_X}MTP connection failed: {e}")
                raise ConnectionError(f"Server {self.mail_hostname} nicht erreichbar")

            if self.mail_debug:
                xmtp.set_debuglevel(1)

            # Header setzen
            email_message['X-getmail-retrieved-from-mailbox-user'] = self.imap_username
            email_message['X-getmail-retrieved-from-mailbox-folder'] = self.imap_sync_folder

            # 2. Sendeversuch
            try:
                if mail_X == 'S':
                    # as_bytes() vermeidet Unicode-Fehler in Headern
                    xmtp.sendmail(self.imap_username, [self.mail_recipient], email_message.as_bytes())
                else:
                    xmtp.send_message(email_message, to_addrs=self.mail_recipient)

                # --- Ursprüngliches Logging-Format ---
                try:
                    email_from_raw = email_message.get('From', 'Unknown')
                    email_subject_raw = email_message.get('Subject', 'No Subject')

                    # Dekodierung der Header für saubere Log-Ausgabe
                    email_from_decoded = email.header.make_header(email.header.decode_header(email_from_raw))
                    email_subject_decoded = email.header.make_header(email.header.decode_header(email_subject_raw))

                    logging.info(f"{mail_X}MTP deliver: new eMail from: [{email_from_decoded}], subject: [{email_subject_decoded}]")

                except Exception as log_e:
                    logging.error(f"Logging Decode Error: {log_e}")
                    logging.info(f"{mail_X}MTP deliver: new eMail (could not decode headers)")

                return True # Erfolg -> Hauptprozess verschiebt/löscht

            except (smtplib.SMTPRecipientsRefused, smtplib.SMTPDataError) as e:
                # Das ist der Rspamd Reject
                logging.warning(f"Mailcow Reject (Spam?): {e}")
                return False # Führt zum Verschieben in Junk

            except Exception as e:
                logging.error(f"{mail_X}MTP send error: {e}")
                raise # Mail im Posteingang lassen

        finally:
            # Verbindung IMMER schließen
            if xmtp:
                try:
                    xmtp.quit()
                except:
                    pass

########################################################################################################################
########################################################################################################################
########################################################################################################################


def start_getmail():

  configparser_file = get_configparser_file()
  all_connections = {}

  for config_name in configparser_file.sections():
        all_connections[config_name] = Getmail(configparser_file, config_name)
        all_connections[config_name].start()

  try:
    exit_program = False
    while not exit_program:
      try:
        signal.pause()
      except KeyboardInterrupt:
        exit_program = True
  except Exception as e:
        logging.error("ERROR: %s" % (e))
        traceback.print_exc()
  finally:
    logging.info("START: shutdown all IMAP connections")
    for config_name in all_connections:
      all_connections[config_name].imap_idle_stop()
    for config_name in all_connections:
      all_connections[config_name].join()
    logging.info("END: shutdown all IMAP connections")


def get_configparser_file():

  if os.path.isfile("./settings.ini"):
    config_file_path = "./settings.ini"
  else:
    logging.error("ERROR settings.ini not found!")
    return

  logging.info("use config file: %s" % config_file_path)
  configparser_file = configparser.ConfigParser(interpolation=None)
  configparser_file.read([os.path.abspath(config_file_path)])

  return configparser_file

def exit_gracefully(signum, frame):
    logging.info("Caught signal %d" % signum)
    raise KeyboardInterrupt

if __name__ == "__main__":
    signal.signal(signal.SIGINT,  exit_gracefully)
    signal.signal(signal.SIGTERM, exit_gracefully)

    logging.basicConfig(
      format='%(asctime)s - %(threadName)s - %(levelname)s: %(message)s',
      level=logging.INFO
    )

    start_getmail()

Um dieses zu verwenden ändert man in Dockerfiles/Dockerfile:

COPY ./Dockerfiles/getmail_imap2xmtp.py .
CMD [ "python","-u", "/app/getmail_imap2xmtp.py" ]

und in docker-compose.yml, damit die Änderung wirkt (nicht ignoriert wird):

    getmail:
      image: cb/getmail:latest
      build:
        context: .
        dockerfile: Dockerfiles/Dockerfile
      #build:
      #  context: https://github.com/christianbur/getmail.git#master
      #  dockerfile: Dockerfiles/Dockerfile

In conf/settings.ini gibt man optional SMTP auf Port 25 an:

lmtp_hostname:     postfix-mailcow
lmtp_port:         25
mail_protocol:     smtp

Dazu muss man den postfix-mailcow container zusätzlich in das Netzwerk einbinden, das getmail verwendet. /opt/mailcow-dockerized/docker-compose.override.yml:

    dovecot-mailcow:
      networks:
        network-getmail:

    postfix-mailcow:
      networks:
        network-getmail:

mailcow

Ich richte für mailcow eine debian VM ein, und installiere darauf git, docker und docker compose. Nach git clone mailcow wird konfiguriert und installiert. Ich habe anschliessend die mail-volumes in ein eigenes ZVOL extrahiert, welches in mailcow einbinde. Damit kann ich die Email separat sichern und jederzeit in einer neuen mailcow Umgebung einbinden.

services:

    mysql-mailcow:
      volumes:
        - /mnt/maildata/mysql:/var/lib/mysql

    dovecot-mailcow:
      volumes:
        - /mnt/maildata/vmail:/var/vmail
        - /mnt/maildata/crypt:/mail_crypt

update script (PVE Host)

Mailcow lässt sich einfach über ein integriertes Skript update.sh in der VM aktualisieren. Ich möchte aber die Fallback Funktionalitäten von PVE nutzen. Dafür verwende ich folgendes Wrapper Skript, das vor dem Aktualisieren der VM Snapshots zieht.

root@pve # cat scripts/mailcow_update.sh

#!/bin/bash

# --- KONFIGURATION ---
MAILCOW_HOST="mailcow"          # DNS-Name der VM
MAILCOW_VM_NAME="mailcow"       # Name der VM in der Proxmox-GUI
MAILCOW_PATH="/opt/mailcow-dockerized"
POOL_PATH="rpool/encrypted_data"
DATE=$(date +%Y%m%d_%H%M)
SNAP_NAME="backup_$DATE"
PVE_SNAP_NAME="${SNAP_NAME}_pve"

# Variablen initialisieren
VMID=""
ZVOLS=""

# Fehler-Funktion mit automatischer Cleanup-Option
abort() {
    echo -e "\n\e[31m[FEHLER]\e[0m $1"
    if [[ -n "$VMID" ]]; then
        EXISTING_SNAPS=$(zfs list -t snapshot -H -o name | grep "@$SNAP_NAME")
        if [[ -n "$EXISTING_SNAPS" ]]; then
            echo -e "\n\e[33m[INFO]\e[0m Vorhandene Snapshots ($SNAP_NAME) gefunden."
            read -p "Sollen diese zur Bereinigung gelöscht werden? (j/n) " -n 1 -r
            echo
            if [[ $REPLY =~ ^[Jj]$ ]]; then
                qm delsnapshot $VMID "$PVE_SNAP_NAME" 2>/dev/null
                for zvol in $ZVOLS; do
                    zfs destroy "$zvol@$SNAP_NAME" 2>/dev/null && echo "   -> $zvol@$SNAP_NAME gelöscht."
                done
            fi
        fi
    fi
    exit 1
}

# Rollback-Funktion
run_rollback() {
    echo -e "\n\e[33m--- ROLLBACK-PROZESS GESTARTET ---\e[0m"
    qm stop $VMID
    for zvol in $ZVOLS; do
        zfs rollback "$zvol@$SNAP_NAME" && echo "   -> $zvol zurückgesetzt."
    done
    qm rollback $VMID "$PVE_SNAP_NAME"
    qm start $VMID
    echo -e "\e[32mRollback abgeschlossen. System ist wieder im Originalzustand.\e[0m"
    exit 1
}

echo "--- SUMMARY VOR UPDATE ---"
# 1. Daten dynamisch sammeln
VMID=$(qm list | grep -w "$MAILCOW_VM_NAME" | awk '{print $1}')
[[ -z "$VMID" ]] && abort "VMID für '$MAILCOW_VM_NAME' nicht gefunden."

VM_STATUS=$(qm status $VMID | awk '{print $2}')
ZVOLS=$(zfs list -H -o name | grep "$POOL_PATH/vm-${VMID}-disk-")
[[ -z "$ZVOLS" ]] && abort "Keine ZVOLs in $POOL_PATH für VM $VMID gefunden."

echo "Ziel-VM:     $MAILCOW_VM_NAME (ID: $VMID, Status: $VM_STATUS)"
echo "ZVOLs (PVE): "$(echo "$ZVOLS"|tr "\n" " ")
echo "ZFS-Snapshot: $SNAP_NAME"
echo "PVE-Snapshot: $PVE_SNAP_NAME"
echo "--------------------------"

# 2. Status-Check & Vorab-Konnektivität
if [ "$VM_STATUS" != "running" ]; then
    echo -e "\e[33m[WARNUNG]\e[0m VM läuft nicht. Stopp-Phase & Update werden fehlschlagen."
    read -p "Nur Backup-Phase (Snapshots) durchführen? (j/n) " -n 1 -r
    echo
    [[ ! $REPLY =~ ^[Jj]$ ]] && abort "Abbruch."
    ONLY_BACKUP=true
else
    ssh -q -o ConnectTimeout=5 "$MAILCOW_HOST" exit || abort "Mailcow-VM nicht via SSH erreichbar."
    ONLY_BACKUP=false
fi

read -p "Starten? (j/n) " -n 1 -r
echo
[[ ! $REPLY =~ ^[Jj]$ ]] && abort "Abbruch durch Benutzer."

# 3. STOPP-PHASE
if [ "$ONLY_BACKUP" = false ]; then
    echo -e "\n[1/3] STOPP-PHASE"
    ssh "$MAILCOW_HOST" "docker stop getmail-getmail-1" || abort "Getmail Stopp fehlgeschlagen."
    ssh "$MAILCOW_HOST" "cd $MAILCOW_PATH && docker compose down" || abort "Mailcow Down fehlgeschlagen."
fi

# 4. BACKUP-PHASE
echo -e "\n[2/3] BACKUP-PHASE"
for zvol in $ZVOLS; do
    zfs snapshot "$zvol@$SNAP_NAME" || abort "ZFS Snapshot für $zvol fehlgeschlagen."
    echo "   -> $zvol@$SNAP_NAME erstellt."
done
qm snapshot "$VMID" "$PVE_SNAP_NAME" --description "Vor-Update-Backup-$DATE" || abort "PVE Snapshot fehlgeschlagen."

[[ "$ONLY_BACKUP" = true ]] && echo "Backup-Phase beendet." && exit 0

# 5. UPDATE-PHASE mit Auto-Restart-Logik
echo -e "\n[3/3] UPDATE-PHASE"
MAX_ATTEMPTS=3
ATTEMPT=1
UPDATE_DONE=false

while [ $ATTEMPT -le $MAX_ATTEMPTS ] && [ "$UPDATE_DONE" = false ]; do
    echo "Update-Versuch $ATTEMPT von $MAX_ATTEMPTS..."
    ssh -t "$MAILCOW_HOST" "cd $MAILCOW_PATH && ./update.sh"
    UPDATE_RC=$?

    if [ $UPDATE_RC -eq 0 ]; then
        echo "Update erfolgreich."
        UPDATE_DONE=true
    elif [ $UPDATE_RC -eq 2 ]; then
        echo -e "\e[33m[INFO]\e[0m Scripte aktualisiert. Starte erneuten Durchlauf..."
        ((ATTEMPT++))
        sleep 2
    else
        echo -e "\n\e[31m[ALARM] Update-Fehler (Code: $UPDATE_RC)!\e[0m"
        read -p "Wahl: (r) Rollback, (i) Ignorieren/Start, (a) Abbrechen: " -n 1 -r
        echo
        case $REPLY in
            [Rr] ) run_rollback ;;
            [Ii] ) UPDATE_DONE=true ;;
            * ) abort "System bleibt 'down'. Snapshots $SNAP_NAME vorhanden." ;;
        esac
    fi
done

# 6. START-PHASE
echo -e "\n--- START-KONTROLLE ---"
ST_ERR=0
ssh "$MAILCOW_HOST" "cd $MAILCOW_PATH && docker compose up -d" || ST_ERR=1
if [ $ST_ERR -eq 0 ]; then
    ssh "$MAILCOW_HOST" "docker start getmail-getmail-1" || ST_ERR=1
fi

if [ $ST_ERR -ne 0 ]; then
    echo -e "\n\e[31m[ALARM] Start fehlgeschlagen!\e[0m"
    read -p "Rollback durchführen? (j/n) " -n 1 -r
    echo
    [[ $REPLY =~ ^[Jj]$ ]] && run_rollback || abort "Manueller Eingriff nötig."
fi

echo -e "\n\e[32m--- UPDATE ERFOLGREICH BEENDET ---\e[0m"

# 7. CLEANUP
echo "----------------------------------------------------"
read -p "Temporäre Snapshots löschen? (j/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Jj]$ ]]; then
    qm delsnapshot $VMID "$PVE_SNAP_NAME"
    for zvol in $ZVOLS; do
        zfs destroy "$zvol@$SNAP_NAME" && echo "   -> $zvol@$SNAP_NAME gelöscht."
    done
    echo -e "\e[32mCleanup fertig.\e[0m"
else
    echo -e "\e[33mSnapshots behalten.\e[0m"
    echo -e "\nNicht in GUI sichtbar:\e[0m"
    for zvol in $ZVOLS; do echo "   -> $zvol@$SNAP_NAME"; done
    echo -e "In GUI sichtbar: $PVE_SNAP_NAME"
fi

Outlook

  • Outlook 2016: OLCFG.exe (MLCFG32.CPL)
  • Outlook 2019 / 365: Deaktivieren der vereinfachten Kontenerstellung

Seit Outlook 2019/365 gibt es leider nicht mehr den vereinfachten Assistenten zur Konfiguration von zusätzlichen Postfächern und/oder Exchange Mailboxen. Mit folgenden Schritten bekommt man den alten Assistenten wieder zurück.und bekommt auch wieder die Möglichkeit den Exchange-Server händisch eingeben zu können.

REG ADD "HKCU\SOFTWARE\Microsoft\Office\16.0\Outlook\Setup" /v "DisableOffice365SimplifiedAccountCreation" /t REG_DWORD /d "1" /f
  • Outlook beenden
  • Registrierung: HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\16.0\Outlook\setup
  • DWORD-Wert (32-Bit): DisableOffice365SimplifiedAccountCreation = 1
  • Outlook starten

Android

  • Neues Konto: MS Exchange (ActiveSync)
  • Manuelle Konfiguration: Server = <dyndns IP> Port = 443