Mailcow HowTo: Unterschied zwischen den Versionen

Aus Neobikers Wiki
Zur Navigation springen Zur Suche springen
Zeile 28: Zeile 28:
= Installation =
= Installation =


=== mailcow ===
== 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 richte für mailcow eine '''debian''' VM ein, und installiere darauf '''git''', '''docker''' und docker compose. Nach ''git clone mailcow'' wird konfiguriert und installiert.


=== getmail ===
=== 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'''
<pre>#!/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
</pre>
 
== 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 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.


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

Version vom 14. Mai 2026, 10:55 Uhr

Ü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

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.

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

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.

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