From 4dfcc3303fd58a6269f9f773876b42d838950d52 Mon Sep 17 00:00:00 2001 From: ARIA Date: Mon, 20 Jul 2026 20:20:49 +0000 Subject: [PATCH] Let's Encrypt Support: DOMAIN+LETSENCRYPT_EMAIL in .env aktivieren echtes Zertifikat - caddy/entrypoint.sh generiert Caddyfile jetzt dynamisch: sind DOMAIN und LETSENCRYPT_EMAIL gesetzt, holt Caddy automatisch ein Let's-Encrypt-Zertifikat; sonst Fallback auf das bisherige selbstsignierte 100-Jahres-Zertifikat. - .env.example um DOMAIN/LETSENCRYPT_EMAIL ergaenzt (auskommentiert), inkl. Erklaerung. - Caddy /data und /config (ACME-Cache) jetzt unter ./daten gebind-mounted statt Named Volumes, damit ein einzelner Ordner fuer Backup/Umzug reicht. - statische Caddyfile entfernt (wird zur Laufzeit erzeugt), README entsprechend um beide Zertifikats-Modi erweitert. --- .env.example | 16 +++++++- .gitignore | 2 + Caddyfile | 24 ----------- README.md | 59 ++++++++++++++++++-------- caddy/entrypoint.sh | 82 +++++++++++++++++++++++++++++++------ daten/caddy_config/.gitkeep | 0 daten/caddy_data/.gitkeep | 0 docker-compose.yml | 22 ++++++---- 8 files changed, 140 insertions(+), 65 deletions(-) delete mode 100644 Caddyfile mode change 100644 => 100755 caddy/entrypoint.sh create mode 100644 daten/caddy_config/.gitkeep create mode 100644 daten/caddy_data/.gitkeep diff --git a/.env.example b/.env.example index 5d3ee6a..c6180ce 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,15 @@ -# Hostname/IP fuer das selbstsignierte 100-Jahres-Zertifikat (CN + SAN). -# Nach dem ersten Start nur wirksam, wenn daten/caddy_certs vorher geleert wird. +# --- Let's Encrypt (echtes, oeffentlich vertrauenswuerdiges Zertifikat) --- +# Werden BEIDE Felder ausgefuellt, fordert Caddy beim Start automatisch ein +# Let's-Encrypt-Zertifikat fuer DOMAIN an (und erneuert es danach selbst). +# Voraussetzung: DOMAIN zeigt per DNS-A/AAAA-Record auf diesen Host, Port 80 +# UND 443 sind aus dem Internet erreichbar (HTTP-01 Challenge). +# +# DOMAIN=baikal.meine-domain.de +# LETSENCRYPT_EMAIL=ich@meine-domain.de + +# --- Fallback: selbstsigniertes 100-Jahres-Zertifikat --- +# Wird automatisch genutzt (bzw. beim ersten Start erzeugt), wenn DOMAIN +# und/oder LETSENCRYPT_EMAIL oben LEER bleiben. Bereits vorhandenes +# Zertifikat unter daten/caddy_certs wird dabei nicht ueberschrieben. +# CADDY_CERT_CN steuert nur CN/SAN dieses selbstsignierten Zertifikats. CADDY_CERT_CN=baikal.local diff --git a/.gitignore b/.gitignore index 7f53db2..4c6cb7c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ daten/config/* daten/data/* daten/caddy_certs/* +daten/caddy_data/* +daten/caddy_config/* !daten/**/.gitkeep .env diff --git a/Caddyfile b/Caddyfile deleted file mode 100644 index 5945490..0000000 --- a/Caddyfile +++ /dev/null @@ -1,24 +0,0 @@ -# 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 index 61af6ac..5c7bb22 100644 --- a/README.md +++ b/README.md @@ -16,36 +16,60 @@ 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) + caddy_certs/ Selbstsigniertes TLS-Zertifikat (nur Fallback-Modus, s.u.) + caddy_data/ Let's-Encrypt-Zertifikatscache (nur Let's-Encrypt-Modus) + caddy_config/ Caddy Runtime-Config-Autosave ``` `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) +## TLS-Zertifikat (Let's Encrypt oder 100 Jahre selbstsigniert, 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. +Der Caddy-Container hat einen eigenen Entrypoint (`caddy/entrypoint.sh`), der +das `Caddyfile` bei JEDEM Start dynamisch erzeugt — abhaengig davon, ob in +`.env` eine Domain + Email fuer Let's Encrypt hinterlegt sind: -- **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). +### Modus 1 — Let's Encrypt (echtes Zertifikat) -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. +Werden **beide** Variablen `DOMAIN` und `LETSENCRYPT_EMAIL` in `.env` +ausgefuellt, fordert Caddy automatisch ein echtes, oeffentlich +vertrauenswuerdiges Let's-Encrypt-Zertifikat fuer `DOMAIN` an und erneuert es +danach von selbst (Caddys eingebautes automatisches HTTPS). + +Voraussetzungen: +- `DOMAIN` zeigt per DNS (A/AAAA-Record) auf diesen Host. +- Port `80` UND `443` sind aus dem Internet erreichbar (HTTP-01 Challenge). + +```bash +cp .env.example .env +# .env bearbeiten: +# DOMAIN=baikal.meine-domain.de +# LETSENCRYPT_EMAIL=ich@meine-domain.de +``` + +### Modus 2 — Fallback: selbstsigniertes 100-Jahres-Zertifikat + +Bleibt `DOMAIN` oder `LETSENCRYPT_EMAIL` leer (Default, z.B. bei reiner +LAN/IP-Nutzung ohne oeffentliche Erreichbarkeit), nutzt der Entrypoint +automatisch ein selbstsigniertes Zertifikat: + +- **Noch keins vorhanden** unter `daten/caddy_certs/baikal.crt` + + `baikal.key` -> es wird automatisch per `openssl` neu erzeugt, gueltig fuer + **36500 Tage (100 Jahre)**. +- **Bereits vorhanden** -> wird unveraendert weiterverwendet (kein + Ueberschreiben bei jedem Neustart). 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. +als vertrauenswuerdig akzeptiert bzw. importiert werden. Bei Modus 1 (Let's +Encrypt) entfaellt diese Warnung, da das Zertifikat echt ist. -### Hostname/CN anpassen +### Hostname/CN des selbstsignierten Zertifikats anpassen -Der Common Name (CN) und SAN-Eintrag des Zertifikats werden ueber die -Umgebungsvariable `CADDY_CERT_CN` gesteuert (Default: `baikal.local`). +Nur relevant fuer Modus 2. Der Common Name (CN) und SAN-Eintrag 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 @@ -95,4 +119,5 @@ docker compose up -d --build ``` Beim naechsten Start wird automatisch ein neues 100-Jahres-Zertifikat mit dem -aktuellen `CADDY_CERT_CN` erzeugt. +aktuellen `CADDY_CERT_CN` erzeugt (nur relevant im Fallback-Modus 2, siehe +oben — bei aktivem Let's Encrypt kuemmert sich Caddy selbst um Erneuerung). diff --git a/caddy/entrypoint.sh b/caddy/entrypoint.sh old mode 100644 new mode 100755 index 5c15f17..4b3cd3d --- a/caddy/entrypoint.sh +++ b/caddy/entrypoint.sh @@ -4,26 +4,82 @@ set -e CERT_DIR="/certs" CERT_FILE="$CERT_DIR/baikal.crt" KEY_FILE="$CERT_DIR/baikal.key" +CADDYFILE="/etc/caddy/Caddyfile" + +DOMAIN="${DOMAIN:-}" +LETSENCRYPT_EMAIL="${LETSENCRYPT_EMAIL:-}" 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 ..." +if [ -n "$DOMAIN" ] && [ -n "$LETSENCRYPT_EMAIL" ]; then + # DOMAIN + LETSENCRYPT_EMAIL gesetzt -> echtes, oeffentlich vertrauenswuerdiges + # Zertifikat ueber Caddys eingebautes automatisches HTTPS (Let's Encrypt). + # Voraussetzung: DOMAIN zeigt per DNS auf diesen Host, Port 80+443 sind von + # aussen erreichbar (HTTP-01 Challenge). + echo "[entrypoint] DOMAIN=$DOMAIN und LETSENCRYPT_EMAIL=$LETSENCRYPT_EMAIL gesetzt - hole/verwalte Zertifikat automatisch ueber Let's Encrypt." - 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" + cat > "$CADDYFILE" < Fallback auf + # selbstsigniertes Langzeit-Zertifikat (100 Jahre), einmalig erzeugt und + # danach unter daten/caddy_certs persistiert/weiterverwendet. + echo "[entrypoint] DOMAIN/LETSENCRYPT_EMAIL nicht gesetzt - nutze selbstsigniertes 100-Jahres-Zertifikat (CN=$CN)." + + 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 + + cat > "$CADDYFILE" <HTTPS Redirect + redir https://{host}{uri} permanent +} +EOF + fi -exec caddy run --config /etc/caddy/Caddyfile --adapter caddyfile +exec caddy run --config "$CADDYFILE" --adapter caddyfile diff --git a/daten/caddy_config/.gitkeep b/daten/caddy_config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/daten/caddy_data/.gitkeep b/daten/caddy_data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml index 1039d23..f4365e2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,17 +22,25 @@ services: container_name: baikal_caddy restart: unless-stopped environment: - # Hostname/CN fuer das selbstsignierte 100-Jahres-Zertifikat. - # Bei Bedarf auf die tatsaechliche Domain/IP anpassen. + # Hostname/CN fuer das selbstsignierte 100-Jahres-Zertifikat (Fallback, + # nur relevant wenn DOMAIN/LETSENCRYPT_EMAIL NICHT gesetzt sind). - CADDY_CERT_CN=${CADDY_CERT_CN:-baikal.local} + # Domain + Kontakt-Email fuer ein echtes Let's Encrypt Zertifikat. + # Sind BEIDE gesetzt, holt/verwaltet Caddy automatisch ein Let's-Encrypt- + # Zertifikat fuer DOMAIN. Ist eines von beiden leer, faellt der + # Entrypoint auf das selbstsignierte 100-Jahres-Zertifikat zurueck. + - DOMAIN=${DOMAIN:-} + - LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL:-} ports: - "80:80" - "443:443" volumes: - - ./Caddyfile:/etc/caddy/Caddyfile:ro - ./daten/caddy_certs:/certs - - caddy_data:/data - - caddy_config:/config + # /data enthaelt u.a. den Let's-Encrypt-Zertifikatscache (Modus 1), + # /config Caddys Runtime-Config-Autosave. Beides unter ./daten, damit + # dieser eine Ordner fuer beide Zertifikats-Modi ausreicht (Backup/Umzug). + - ./daten/caddy_data:/data + - ./daten/caddy_config:/config depends_on: - baikal networks: @@ -40,7 +48,3 @@ services: networks: baikal_net: - -volumes: - caddy_data: - caddy_config: