1d3818e725
- dms-lxc.sh: Proxmox-Host-Installer (unprivilegierter LXC, Debian 13, Docker), curl-Self-Download, Multi-Domain-DKIM, SnappyMail-Provisionierung, PVE-Firewall - Stack: docker-mailserver, Node-Admin-API (Supabase-Auth), React-Admin-UI (OPENBUREAU-Look), SnappyMail (Shibui-Theme), Rspamd-Web-UI, docker-socket-proxy - Admin: Postfächer/Aliase/Catch-all/Quota, editierbare Domains+Settings, Server (Quota/Queue über abgesicherte Bridge), Status & DNS - Hardening: no-new-privileges, Whitelisted exec-Bridge, Rspamd-Passwort, .env chmod 600, PVE-CT-Firewall, generisch/teilbar (keine festen Domains) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
346 lines
15 KiB
Markdown
346 lines
15 KiB
Markdown
# docker-mailserver auf Proxmox (LXC) — mit React-Admin UI & SnappyMail
|
||
|
||
Ein vollständiges, selbst gehostetes Mail-Setup für ein kleines Büro (4–5 Personen):
|
||
|
||
| Komponente | Aufgabe |
|
||
|---|---|
|
||
| **docker-mailserver (DMS)** | Postfix + Dovecot + Rspamd (SMTP/IMAP, Spam, DKIM) |
|
||
| **Admin-API** (Node.js) | verwaltet Postfächer/Aliase/Quotas über die DMS-Config-Dateien — Auth via Supabase |
|
||
| **Admin-UI** (React-Admin) | Weboberfläche im Stil von OPENBUREAU |
|
||
| **SnappyMail** | schlankes Webmail für die Mitarbeiter |
|
||
| **Nginx Proxy Manager** *(bereits vorhanden)* | HTTPS/Let's-Encrypt für Admin-UI & Webmail |
|
||
|
||
Das Ganze läuft in **einem unprivilegierten LXC-Container** (Debian 13) auf Proxmox, in dem Docker mit `nesting=1` betrieben wird.
|
||
|
||
```
|
||
Internet
|
||
│
|
||
Port 25/465/587/ │ Port 80/443
|
||
143/993 ──────► ▼ ──────► Nginx Proxy Manager (HTTPS)
|
||
direkt zum DMS │
|
||
(Mail-TLS im DMS) ├─► admin.example.com → Admin-UI (:8080)
|
||
└─► mail.example.com → SnappyMail (:8888)
|
||
```
|
||
|
||
---
|
||
|
||
## 1. Voraussetzungen
|
||
|
||
- **Proxmox VE** (8.x), du arbeitest als `root` auf dem Host.
|
||
- Eine **Domain** (`example.com`) mit Zugriff auf die DNS-Einstellungen.
|
||
- **Port 25 ausgehend/eingehend** beim Hoster/ISP freigegeben (viele Privatanschlüsse blockieren 25!) und eine **statische öffentliche IP** mit der Möglichkeit, einen **PTR/rDNS** zu setzen.
|
||
- Eine **Supabase-Instanz** (dieselbe wie bei OPENBUREAU) für das Admin-Login.
|
||
- Der bereits vorhandene **Nginx Proxy Manager**.
|
||
|
||
---
|
||
|
||
## 2. Installation
|
||
|
||
**Variante A — Einzeiler** (auf dem Proxmox-Host als root). Das Skript lädt den
|
||
`stack/` bei Bedarf selbst herunter:
|
||
|
||
```bash
|
||
bash <(curl -fsSL https://git.kgva.ch/karim/DOCKERMAILSERVER-LXC/raw/branch/main/dms-lxc.sh)
|
||
# privates Repo: GIT_TOKEN=<token> bash <(curl -fsSL .../dms-lxc.sh)
|
||
```
|
||
|
||
**Variante B — Ordner kopieren** (inkl. `stack/`) und ausführen:
|
||
|
||
```bash
|
||
cd /root/dms && bash dms-lxc.sh
|
||
```
|
||
|
||
Das Skript fragt **alles interaktiv ab** (Domains, Webmail-/Admin-Domain, Brand,
|
||
Supabase, NPM-IP …) — oder per ENV vorbelegen:
|
||
|
||
```bash
|
||
CTID=110 \
|
||
MAIL_FQDN=mail.example.com MAIL_DOMAIN=example.com MAIL_DOMAINS="example.com weitere.tld" \
|
||
BRAND="Mein Büro" WEBMAIL_FQDN=mail.example.com ADMIN_FQDN=admin.example.com \
|
||
NET_IP=192.168.1.50/24 NET_GW=192.168.1.1 \
|
||
NPM_IP=192.168.1.10 \
|
||
ADMIN_ALLOWED_EMAILS="admin@example.com" \
|
||
SUPABASE_URL=https://xxxx.supabase.co SUPABASE_ANON_KEY=eyJ... \
|
||
bash dms-lxc.sh
|
||
```
|
||
|
||
> **Generisch & teilbar:** Es sind **keine festen Domains** im Stack — alles kommt aus dem
|
||
> Dialog und ist später in der Admin-UI editierbar. Du kannst das Skript also weitergeben.
|
||
|
||
Es macht automatisch:
|
||
1. unprivilegierten LXC (Debian 13, `nesting=1,keyctl=1`) anlegen + **PVE-CT-Firewall**,
|
||
2. Docker installieren,
|
||
3. **Self-signed-Cert + erstes Postfach vor-seeden**, dann den Stack bauen & starten,
|
||
4. **DKIM je Domain** erzeugen, SnappyMail provisionieren (Domain→mailserver, Shibui-Theme),
|
||
5. am Ende **DNS-Records (alle Domains, inkl. DKIM)** + **Rspamd-Passwort** + Hardening-Checkliste ausgeben.
|
||
|
||
> **Wichtige Variablen** (oben im Skript / per ENV): `CORES`, `RAM_MB` (4096), `DISK_GB` (20),
|
||
> `STORAGE`, `BRIDGE`, `ADMIN_PORT`/`WEBMAIL_PORT`/`RSPAMD_PORT`, `NPM_IP`, `HARDEN_FIREWALL`, `ENABLE_CLAMAV`.
|
||
|
||
Verwaltung später:
|
||
|
||
```bash
|
||
pct enter 110
|
||
cd /opt/dms-stack
|
||
docker compose ps
|
||
docker compose logs -f mailserver
|
||
```
|
||
|
||
---
|
||
|
||
## 3. DNS einrichten
|
||
|
||
**Ein Mailserver, mehrere Domains:** Es gibt **einen** Mailhost (`mail.kgva.ch`), der die Postfächer
|
||
**aller** Domains bedient (kgva.ch, gabrielevarano.ch, karimgabrielevarano.xyz, openbureau.ch …).
|
||
Alle Clients/Webmail verbinden sich immer zu `mail.kgva.ch` — egal welche Adress-Domain.
|
||
|
||
**Einmalig für den Mailhost** (`<IP>` = öffentliche IP):
|
||
|
||
| Typ | Name | Wert |
|
||
|---|---|---|
|
||
| A | `mail.kgva.ch.` | `<IP>` |
|
||
| **PTR/rDNS** | `<IP>` | `mail.kgva.ch` — **beim Hoster/ISP setzen!** |
|
||
|
||
**Pro Mail-Domain** (Beispiel `kgva.ch` — analog für jede weitere Domain):
|
||
|
||
| Typ | Name | Wert |
|
||
|---|---|---|
|
||
| MX | `kgva.ch.` | `10 mail.kgva.ch.` |
|
||
| TXT (SPF) | `kgva.ch.` | `v=spf1 mx ~all` |
|
||
| TXT (DMARC) | `_dmarc.kgva.ch.` | `v=DMARC1; p=quarantine; rua=mailto:postmaster@kgva.ch` |
|
||
| TXT (DKIM) | je Domain eigener Key — siehe Skript-Ausgabe / Admin-UI → **Status & DNS** | (langer Public-Key) |
|
||
|
||
> Jede Domain hat einen **eigenen DKIM-Schlüssel**. Die fertigen Records (alle Domains) findest du
|
||
> nach dem Setup in der Admin-UI unter **Status & DNS** zum Kopieren — oder per Skript-Ausgabe.
|
||
> Weitere Domain später hinzufügen: `MAIL_DOMAINS` in `.env` ergänzen, Konto anlegen,
|
||
> `docker exec mailserver setup config dkim domain neue-domain.tld`, DNS setzen.
|
||
|
||
Der **PTR-Record** ist entscheidend dafür, dass deine Mails nicht als Spam landen.
|
||
|
||
DKIM später erneut anzeigen:
|
||
```bash
|
||
pct enter 110
|
||
cat /opt/dms-stack/docker-data/dms/config/rspamd/dkim/*.dns.txt
|
||
```
|
||
|
||
Prüfen lassen kannst du alles bequem über **[mail-tester.com](https://www.mail-tester.com)** und **[dkimvalidator.com](https://dkimvalidator.com)**.
|
||
|
||
---
|
||
|
||
## 4. Port-Weiterleitung / Firewall
|
||
|
||
Diese Ports müssen von außen zum Container (`<IP>`) gelangen:
|
||
|
||
| Port | Zweck |
|
||
|---|---|
|
||
| 25 | SMTP (eingehende Mails von anderen Servern) |
|
||
| 587 / 465 | Mail-Versand der Mitarbeiter (Submission) |
|
||
| 143 / 993 | IMAP (Mailabruf) |
|
||
|
||
Falls der Container hinter NAT liegt, am Router entsprechend weiterleiten.
|
||
Die Web-Ports **80/443** gehen an den **Nginx Proxy Manager**, nicht direkt an den Container.
|
||
|
||
---
|
||
|
||
## 5. Supabase-Login einrichten (Admin-UI)
|
||
|
||
Die Admin-UI nutzt Supabase-Auth (wie OPENBUREAU):
|
||
|
||
1. In deinem Supabase-Projekt unter **Authentication → Users** einen Benutzer mit deiner E-Mail anlegen (oder Einladung).
|
||
2. Diese E-Mail muss in `ADMIN_ALLOWED_EMAILS` stehen (in `/opt/dms-stack/.env`).
|
||
3. `SUPABASE_URL` und `SUPABASE_ANON_KEY` in `.env` müssen gesetzt sein.
|
||
|
||
Änderungen an der `.env` aktiv machen:
|
||
```bash
|
||
cd /opt/dms-stack
|
||
docker compose up -d # liest .env neu (UI/-API neu starten)
|
||
docker compose restart admin-ui admin-api
|
||
```
|
||
|
||
> Nur E-Mails aus `ADMIN_ALLOWED_EMAILS` erhalten Zugriff — alle anderen Supabase-User werden von der API mit 403 abgewiesen.
|
||
|
||
---
|
||
|
||
## 6. Nginx Proxy Manager (HTTPS für Web)
|
||
|
||
Im NPM zwei **Proxy Hosts** anlegen:
|
||
|
||
| Domain | Forward Hostname/IP | Port | SSL |
|
||
|---|---|---|---|
|
||
| `admin.example.com` | `<Container-IP>` | `8080` | Let's Encrypt anfordern, „Force SSL" |
|
||
| `mail.example.com` | `<Container-IP>` | `8888` | Let's Encrypt anfordern, „Force SSL" |
|
||
|
||
Für SnappyMail unter **Advanced** ggf. „Websockets Support" aktivieren.
|
||
|
||
---
|
||
|
||
## 7. TLS für den Mailserver (Port 465/587/993)
|
||
|
||
Mail-Protokolle laufen **nicht** über NPM, der Mailserver braucht also ein eigenes Zertifikat.
|
||
DMS ist auf **`SSL_TYPE=manual`** mit festem Pfad konfiguriert:
|
||
|
||
```
|
||
/opt/dms-stack/docker-data/certs/cert.pem (Zertifikat / fullchain)
|
||
/opt/dms-stack/docker-data/certs/key.pem (privater Schlüssel)
|
||
```
|
||
|
||
Das Setup-Skript legt dort beim ersten Start automatisch ein **self-signed** Zertifikat ab,
|
||
damit STARTTLS sofort funktioniert (Clients warnen wegen Selbstsignierung — für interne
|
||
Tests okay). **Für ein echtes Let's-Encrypt-Zertifikat einfach diese zwei Dateien ersetzen:**
|
||
|
||
1. In NPM unter **SSL Certificates** ein Zertifikat für `mail.kgva.ch` via **DNS-Challenge**
|
||
anfordern (funktioniert auch ohne offenen Port 80).
|
||
2. Dessen `fullchain.pem` → `docker-data/certs/cert.pem` und `privkey.pem` → `docker-data/certs/key.pem` kopieren.
|
||
3. `cd /opt/dms-stack && docker compose restart mailserver` — **keine Config-Änderung nötig**, der Pfad bleibt gleich.
|
||
|
||
> Bei Erneuerung (alle ~90 Tage) die beiden Dateien neu kopieren und `docker compose restart mailserver`.
|
||
> Das lässt sich per Cron/Hook automatisieren. Alternativ `acme.sh`/certbot direkt im LXC (DNS-Plugin)
|
||
> und die Cert-Dateien an dieselben Pfade schreiben.
|
||
|
||
---
|
||
|
||
## 8. Admin-UI benutzen
|
||
|
||
`https://admin.example.com` öffnen → mit Supabase-E-Mail/Passwort einloggen.
|
||
|
||
- **Übersicht (Dashboard)**: Zähler (Postfächer/Aliase/Domains), Domains, Schnell-Links.
|
||
- **Postfächer**: Konten anlegen/löschen, Passwort ändern, **Quota** setzen (z.B. `5G`, leer = unbegrenzt). Validierung von E-Mail/Quota/Passwortlänge.
|
||
- **Aliase**: Weiterleitungen, z.B. `info@example.com` → `chef@example.com` (mehrere Ziele mit Komma).
|
||
**Catch-all**: als Quelle `@example.com` eintragen → fängt alle unbekannten Adressen der Domain.
|
||
- **Einstellungen**: **Domains hinzufügen/entfernen** (Hinzufügen erzeugt automatisch den DKIM-Key),
|
||
Brand, Webmail-/Admin-Domain und Mailserver-FQDN bearbeiten. (Erst-Befüllung aus dem Deploy-Dialog.)
|
||
- **Server**: **Quota-Auslastung** (belegt/frei je Postfach), **Mail-Queue**, **aktive Sessions**.
|
||
Läuft über eine abgesicherte Bridge (docker-socket-proxy, nur `exec`, Whitelist).
|
||
- **Status & DNS**: pro Domain MX/SPF/DMARC + DKIM zum Kopieren, „DKIM erzeugen/erneuern"-Button.
|
||
|
||
Konten/Aliase werden direkt in die DMS-Config-Dateien geschrieben; DMS übernimmt Änderungen automatisch.
|
||
|
||
---
|
||
|
||
## 9. SnappyMail (Webmail) einrichten
|
||
|
||
1. `https://mail.example.com` öffnen.
|
||
2. Admin-Panel beim ersten Start: `https://mail.example.com/?admin` — das Admin-Passwort steht in
|
||
`/opt/dms-stack/docker-data/snappymail/_data_/_default_/admin_password.txt`.
|
||
3. Unter **Domains** die Domain `example.com` hinzufügen:
|
||
- IMAP: `mailserver` Port `993` (SSL/TLS) — innerhalb des Docker-Netzes per Containername erreichbar,
|
||
extern `mail.example.com` Port `993`.
|
||
- SMTP: Port `587` (STARTTLS).
|
||
4. Mitarbeiter loggen sich dann mit ihrer vollen E-Mail-Adresse + Passwort ein.
|
||
|
||
### KGVA-Theme („Shibui")
|
||
|
||
SnappyMail wird im selben Look wie das KGVA-Nextcloud-Theme ausgeliefert. Das Theme liegt unter
|
||
`stack/snappymail-theme/Shibui/styles.css` und wird per Volume nach `/snappymail/themes/` gemountet —
|
||
es erscheint in SnappyMail als **`Shibui@custom`**.
|
||
|
||
Aktivieren (eine der beiden Varianten):
|
||
- **Für alle (empfohlen):** Admin-Panel (`https://mail.example.com/?admin`) → **Themes** → Standard-Theme
|
||
auf `Shibui` setzen. Alternativ in `data/_data_/_default_/configs/application.ini` unter `[webmail]`
|
||
`theme = "Shibui@custom"` eintragen und `docker compose restart snappymail`.
|
||
- **Pro Nutzer:** Webmail → **Einstellungen → Allgemein → Theme → Shibui**.
|
||
|
||
> Anpassen: einfach `styles.css` editieren und `docker compose restart snappymail`. Die Farb-/Font-Variablen
|
||
> stehen gesammelt im `:root`-Block oben (Petrol-Akzent `#7BA89B`, washi-Neutraltöne, Inter/Instrument Serif/JetBrains Mono).
|
||
> Hinweis: Die Fonts werden aktuell von Google Fonts geladen; auf Wunsch hoste ich sie lokal (DSGVO).
|
||
|
||
---
|
||
|
||
## 10. Mail-Client-Einstellungen (für Outlook/Thunderbird/Apple Mail)
|
||
|
||
| Einstellung | Wert |
|
||
|---|---|
|
||
| Posteingang (IMAP) | `mail.example.com`, Port **993**, SSL/TLS |
|
||
| Postausgang (SMTP) | `mail.example.com`, Port **587** (STARTTLS) oder **465** (SSL) |
|
||
| Benutzername | volle E-Mail-Adresse |
|
||
| Authentifizierung | normales Passwort |
|
||
|
||
---
|
||
|
||
## 11. Backup
|
||
|
||
Alle Daten liegen unter `/opt/dms-stack/docker-data/`:
|
||
|
||
```bash
|
||
pct enter 110
|
||
cd /opt/dms-stack
|
||
docker compose down
|
||
tar czf /root/dms-backup-$(date +%F).tar.gz docker-data .env mailserver.env
|
||
docker compose up -d
|
||
```
|
||
|
||
Zusätzlich empfiehlt sich ein **Proxmox-Backup (vzdump)** des ganzen LXC sowie für Postfächer ggf. eine Offsite-Kopie von `docker-data/dms/mail-data/`.
|
||
|
||
---
|
||
|
||
## 12. Troubleshooting
|
||
|
||
```bash
|
||
pct enter 110 && cd /opt/dms-stack
|
||
|
||
docker compose ps # Status aller Container
|
||
docker compose logs -f mailserver # Mail-Logs
|
||
docker exec mailserver setup email list # Postfächer auflisten (CLI)
|
||
docker exec mailserver postqueue -p # Mail-Queue ansehen
|
||
docker exec mailserver setup debug fetchmail # Debug
|
||
```
|
||
|
||
**Häufige Stolperfallen:**
|
||
- **Mails kommen nicht raus / landen im Spam:** Port 25 vom ISP geblockt, fehlender PTR, oder DKIM/SPF/DMARC nicht gesetzt → `mail-tester.com` nutzen.
|
||
- **Docker startet im LXC nicht:** `nesting=1,keyctl=1` prüfen (`pct config 110`); bei Bedarf `pct set 110 --features nesting=1,keyctl=1` und Container neu starten.
|
||
- **Admin-UI „Token ungültig":** E-Mail steht nicht in `ADMIN_ALLOWED_EMAILS`, oder `SUPABASE_URL/ANON_KEY` falsch.
|
||
- **Admin-UI lädt, aber Login schlägt fehl:** `config.js` prüfen → `curl http://<ip>:8080/config.js` sollte deine Supabase-URL zeigen; sonst `docker compose up -d admin-ui`.
|
||
|
||
---
|
||
|
||
## 13. Spam-Filter (Rspamd)
|
||
|
||
docker-mailserver bringt **Rspamd** mit (aktiviert: Bayes, SPF/DKIM/DMARC-Checks, Greylisting, RBLs) —
|
||
ein sehr guter Spam-Filter ohne Extra-Infrastruktur. Die **Web-UI** (Statistik/Training) läuft auf Port
|
||
`11334` und ist **passwortgeschützt** (Passwort wird beim Deploy generiert und ausgegeben; steht in
|
||
`docker-data/dms/config/rspamd/override.d/worker-controller.inc`).
|
||
|
||
- Im NPM als Proxy-Host `rspamd.<domain>` → `<CT-IP>:11334` (HTTPS).
|
||
- Passwort ändern: Datei editieren (`password` / `enable_password`) + `docker compose restart mailserver`.
|
||
- Optional härter hashen: `docker exec mailserver rspamadm pw` → Hash statt Klartext eintragen.
|
||
|
||
> Für größere Setups/dedizierte Quarantäne kann man optional ein **Proxmox Mail Gateway** davorschalten — für ein kleines Büro ist Rspamd i.d.R. ausreichend.
|
||
|
||
---
|
||
|
||
## 14. Hardening
|
||
|
||
**Schon eingebaut:**
|
||
- **Unprivilegierter LXC** + Nesting; Container mit `no-new-privileges` (admin-api, admin-ui, snappymail, socket-proxy).
|
||
- **Mailserver-Bridge** nur über **docker-socket-proxy** (ausschließlich `exec`) + Kommando-Whitelist in der API — **kein** roher Docker-Socket an die App.
|
||
- **Rspamd-UI** passwortgeschützt; **Fail2ban** aktiv (`cap_add: NET_ADMIN`); DMS kein Open-Relay (`PERMIT_DOCKER=none`, `SPOOF_PROTECTION=1`).
|
||
- Admin nur für E-Mails aus `ADMIN_ALLOWED_EMAILS` (Supabase). **`.env` ist `chmod 600`**.
|
||
- **Proxmox-CT-Firewall**: Mail-Ports offen, Web/Admin/Webmail/Rspamd nur von `NPM_IP`. Wirkt, sobald die **Datacenter/Node-Firewall aktiv** ist (`Datacenter → Firewall → Options → Enable: Yes`).
|
||
|
||
**Vor dem Echtbetrieb noch erledigen:**
|
||
- [ ] **Echtes TLS** für Mail: NPM-Zertifikat (DNS-Challenge) → `docker-data/certs/cert.pem|key.pem`, `restart mailserver` (ersetzt self-signed).
|
||
- [ ] Admin-UI/Webmail/Rspamd **nur über NPM (HTTPS)** erreichbar machen — rohe Ports nicht ins Internet; `NPM_IP` setzen.
|
||
- [ ] **Supabase-Login** mit 2FA absichern; starke Postfach-Passwörter.
|
||
- [ ] **Backups** (vzdump + `docker-data/`), siehe Abschnitt 11.
|
||
- [ ] **PTR/Reverse-DNS** auf `mail.<domain>` setzen; **Port 25** beim ISP prüfen (sonst Smarthost/`RELAY_HOST`).
|
||
- [ ] **DMARC** nach der Testphase auf `p=reject` verschärfen.
|
||
|
||
---
|
||
|
||
## Projektstruktur
|
||
|
||
```
|
||
.
|
||
├── dms-lxc.sh # Proxmox-Host-Skript (LXC anlegen + Stack deployen)
|
||
├── README.md
|
||
└── stack/ # wird in den Container nach /opt/dms-stack kopiert
|
||
├── docker-compose.yml
|
||
├── .env.example
|
||
├── mailserver.env
|
||
├── api/ # Node.js Admin-API (Supabase-Auth)
|
||
│ ├── server.js
|
||
│ └── lib/{store,auth}.js
|
||
└── admin/ # React-Admin UI (Vite → nginx)
|
||
└── src/{App,Layout,Status,authProvider,dataProvider}.jsx ...
|
||
```
|