commit 0b6acbb8748db7468e10fc408b7b88dae72aeded Author: ARIA Date: Mon Jul 20 20:16:23 2026 +0000 Baikal CalDAV/CardDAV Server mit Caddy-TLS-Proxy (100 Jahre Zertifikat) - docker-compose.yml: baikal (ckulka/baikal:nginx) + caddy Reverse Proxy - Persistente Daten unter ./daten (config, data, caddy_certs) - Caddy-Image mit Entrypoint: erstellt beim Start automatisch ein selbstsigniertes 100-Jahres-Zertifikat, falls noch keins vorhanden ist - README mit Setup-, Backup- und Zertifikats-Anleitung diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5d3ee6a --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +# Hostname/IP fuer das selbstsignierte 100-Jahres-Zertifikat (CN + SAN). +# Nach dem ersten Start nur wirksam, wenn daten/caddy_certs vorher geleert wird. +CADDY_CERT_CN=baikal.local diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f53db2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Persistente Laufzeitdaten nicht versionieren +daten/config/* +daten/data/* +daten/caddy_certs/* +!daten/**/.gitkeep + +.env diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..5945490 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,24 @@ +# Caddyfile fuer den Baikal CalDAV/CardDAV Server +# +# auto_https wird global abgeschaltet, weil wir selbst ein Zertifikat +# bereitstellen (siehe caddy/entrypoint.sh) statt Caddy per ACME/Let's +# Encrypt bzw. seiner internen CA (kurzlebige Zertifikate) machen zu lassen. +{ + auto_https off +} + +:443 { + tls /certs/baikal.crt /certs/baikal.key + + reverse_proxy baikal:80 + + encode gzip + log { + output stdout + } +} + +:80 { + # Reiner HTTP->HTTPS Redirect + redir https://{host}{uri} permanent +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..61af6ac --- /dev/null +++ b/README.md @@ -0,0 +1,98 @@ +# Baikal Server (Docker) + +CalDAV/CardDAV-Server ([sabre/baikal](https://sabre.io/baikal/)) per Docker Compose, +mit vorgeschaltetem [Caddy](https://caddyserver.com/) als TLS-Reverse-Proxy. + +## Aufbau + +- **baikal** — `ckulka/baikal:nginx`, liefert Baikal auf Port 80 (intern) +- **caddy** — eigenes Image (`caddy:2-alpine` + `openssl`), terminiert TLS auf + Port 443 und leitet an `baikal` weiter. HTTP (Port 80) wird permanent auf + HTTPS umgeleitet. + +Alle persistenten Daten liegen im Projektverzeichnis unter `./daten`: + +``` +daten/ + config/ Baikal-Konfiguration (Setup, Admin-Login, DB-Settings) + data/ Baikal-Nutzdaten (Kalender, Adressbuecher, SQLite-DB) + caddy_certs/ Selbstsigniertes TLS-Zertifikat (baikal.crt / baikal.key) +``` + +`daten/` ist damit der einzige Ordner, den man fuer Backups sichern bzw. bei +einem Umzug/Redeploy 1:1 mitnehmen muss. + +## TLS-Zertifikat (100 Jahre, automatisch) + +Der Caddy-Container hat einen eigenen Entrypoint +(`caddy/entrypoint.sh`): Beim Start prueft er, ob unter +`daten/caddy_certs/baikal.crt` + `baikal.key` bereits ein Zertifikat liegt. + +- **Nicht vorhanden** -> es wird automatisch ein neues, selbstsigniertes + Zertifikat per `openssl` erzeugt, gueltig fuer **36500 Tage (100 Jahre)**. +- **Vorhanden** -> wird unveraendert weiterverwendet (kein Ueberschreiben bei + jedem Neustart). + +Caddys eingebautes automatisches HTTPS (ACME / interne CA mit kurzlebigen +Zertifikaten) ist im `Caddyfile` bewusst per `auto_https off` deaktiviert — +stattdessen wird das selbst erzeugte Langzeit-Zertifikat direkt referenziert. + +Da es sich um ein selbstsigniertes Zertifikat handelt, werden CalDAV/CardDAV- +Clients (Browser, Thunderbird, DAVx5, iOS/macOS Kalender-App, ...) beim ersten +Verbinden eine Zertifikatswarnung anzeigen. Das Zertifikat muss dort einmalig +als vertrauenswuerdig akzeptiert bzw. importiert werden. + +### Hostname/CN anpassen + +Der Common Name (CN) und SAN-Eintrag des Zertifikats werden ueber die +Umgebungsvariable `CADDY_CERT_CN` gesteuert (Default: `baikal.local`). + +Eigene Domain/IP setzen, bevor der Container zum ersten Mal hochfaehrt (das +Zertifikat wird nur EINMAL erzeugt — danach `daten/caddy_certs` loeschen und +neu starten, falls sich der Hostname aendert): + +```bash +cp .env.example .env +# .env bearbeiten: CADDY_CERT_CN=baikal.meine-domain.de +``` + +## Start + +```bash +docker compose up -d --build +``` + +Danach ist der Setup-Wizard erreichbar unter: + +``` +https:///admin/ +``` + +Dort einmalig Admin-Zugang, Zeitzone und Datenbank (Standard: SQLite, kein +externer DB-Server noetig) einrichten. + +## CalDAV/CardDAV-URLs (nach Setup) + +- CalDAV: `https:///dav.php/calendars//` +- CardDAV: `https:///dav.php/addressbooks//` + +## Neustart / Update + +```bash +docker compose pull +docker compose up -d --build +``` + +Alle Nutzdaten bleiben in `./daten` erhalten, solange dieser Ordner nicht +geloescht wird. + +## Zertifikat erneuern / Hostname aendern + +```bash +docker compose down +rm -rf daten/caddy_certs +docker compose up -d --build +``` + +Beim naechsten Start wird automatisch ein neues 100-Jahres-Zertifikat mit dem +aktuellen `CADDY_CERT_CN` erzeugt. diff --git a/caddy/Dockerfile b/caddy/Dockerfile new file mode 100644 index 0000000..ffb64a7 --- /dev/null +++ b/caddy/Dockerfile @@ -0,0 +1,9 @@ +FROM caddy:2-alpine + +# openssl wird fuer die automatische Zertifikatserstellung im Entrypoint gebraucht +RUN apk add --no-cache openssl + +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/caddy/entrypoint.sh b/caddy/entrypoint.sh new file mode 100644 index 0000000..5c15f17 --- /dev/null +++ b/caddy/entrypoint.sh @@ -0,0 +1,29 @@ +#!/bin/sh +set -e + +CERT_DIR="/certs" +CERT_FILE="$CERT_DIR/baikal.crt" +KEY_FILE="$CERT_DIR/baikal.key" +CN="${CADDY_CERT_CN:-baikal.local}" + +mkdir -p "$CERT_DIR" + +if [ ! -f "$CERT_FILE" ] || [ ! -f "$KEY_FILE" ]; then + echo "[entrypoint] Kein Zertifikat unter $CERT_DIR gefunden - erstelle neues, 100 Jahre gueltiges Zertifikat fuer CN=$CN ..." + + openssl req -x509 -nodes -newkey rsa:4096 -sha256 \ + -days 36500 \ + -keyout "$KEY_FILE" \ + -out "$CERT_FILE" \ + -subj "/C=DE/ST=Niedersachsen/L=Oldenburg/O=HackerSoft/CN=${CN}" \ + -addext "subjectAltName=DNS:${CN},DNS:localhost,IP:127.0.0.1" + + chmod 600 "$KEY_FILE" + chmod 644 "$CERT_FILE" + + echo "[entrypoint] Zertifikat erstellt (gueltig fuer 36500 Tage / 100 Jahre)." +else + echo "[entrypoint] Bestehendes Zertifikat in $CERT_DIR gefunden, wird weiterverwendet." +fi + +exec caddy run --config /etc/caddy/Caddyfile --adapter caddyfile diff --git a/daten/caddy_certs/.gitkeep b/daten/caddy_certs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/daten/config/.gitkeep b/daten/config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/daten/data/.gitkeep b/daten/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1039d23 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,46 @@ +version: "3.8" + +# Baikal CalDAV/CardDAV Server + Caddy Reverse Proxy +# Alle persistenten Daten (Baikal-Konfig, Kalender/Kontakte, TLS-Zertifikat) +# liegen unter ./daten im Projektverzeichnis. + +services: + baikal: + image: ckulka/baikal:nginx + container_name: baikal + restart: unless-stopped + environment: + - TZ=Europe/Berlin + volumes: + - ./daten/config:/var/www/baikal/config + - ./daten/data:/var/www/baikal/Specific + networks: + - baikal_net + + caddy: + build: ./caddy + container_name: baikal_caddy + restart: unless-stopped + environment: + # Hostname/CN fuer das selbstsignierte 100-Jahres-Zertifikat. + # Bei Bedarf auf die tatsaechliche Domain/IP anpassen. + - CADDY_CERT_CN=${CADDY_CERT_CN:-baikal.local} + ports: + - "80:80" + - "443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - ./daten/caddy_certs:/certs + - caddy_data:/data + - caddy_config:/config + depends_on: + - baikal + networks: + - baikal_net + +networks: + baikal_net: + +volumes: + caddy_data: + caddy_config: