HTTPS im lokalen Netz (Port 8443, selbstsigniertes Zertifikat, optional mkcert)

This commit is contained in:
2026-07-19 16:03:29 +02:00
parent fc1ea4fa70
commit a695159b1d
8 changed files with 185 additions and 34 deletions

View File

@@ -39,3 +39,12 @@ FRITZBOX_PASSWORD=
# ─── Frontend (nur für lokale Entwicklung, `pnpm dev:frontend`) ─────
# Ziel für den Vite-Dev-Proxy von /api -> Backend
BACKEND_URL=http://localhost:3001
# ─── HTTPS im lokalen Netz ──────────────────────────────────────────
# IP-Adresse oder Hostname, unter der du LaunchPad im LAN aufrufst
# (z. B. https://192.168.1.49:8443). Wird für das beim Containerstart
# automatisch erzeugte selbstsignierte Zertifikat verwendet. Ohne eigenes
# Zertifikat zeigt der Browser trotzdem eine Warnung siehe README für eine
# Variante ohne Warnung (mkcert). Ändern erfordert einen Neustart des
# Frontend-Containers: docker compose up -d --force-recreate frontend
LAUNCHPAD_HOST=192.168.1.49

View File

@@ -14,9 +14,60 @@ cp .env.example .env # optional anpassen, siehe unten
docker compose up -d --build
```
- Frontend: http://localhost:8080
- Frontend: http://localhost:8080 (HTTPS: https://localhost:8443, siehe unten)
- Backend / Health-API: http://localhost:3001/api/health
## HTTPS im lokalen Netz
LaunchPad ist per HTTPS auf Port `8443` erreichbar (`https://<deine-ip>:8443`),
zusätzlich zu HTTP auf `8080` kein erzwungener Redirect. Beim ersten Start
erzeugt der Frontend-Container automatisch ein selbstsigniertes Zertifikat für
die in `.env` hinterlegte `LAUNCHPAD_HOST` (IP oder Hostname). Der Browser
zeigt dafür trotzdem eine Sicherheitswarnung das ist bei selbstsignierten
Zertifikaten normal, nicht per se unsicher fürs eigene LAN.
```bash
# .env: IP/Hostname eintragen, unter der du zugreifst
LAUNCHPAD_HOST=192.168.1.49
docker compose up -d --force-recreate frontend
```
Danach: `https://192.168.1.49:8443` (Warnung einmalig bestätigen/Ausnahme
hinzufügen).
### Ohne Browser-Warnung (empfohlen): eigenes Zertifikat per mkcert
[mkcert](https://github.com/FiloSottile/mkcert) erzeugt lokal vertrauenswürdige
Zertifikate dein Browser vertraut ihnen automatisch, keine Warnung mehr.
```bash
# Einmalig auf dem Rechner, von dem aus du zugreifst (nicht auf dem Server):
mkcert -install
# Zertifikat für IP/Hostname erzeugen:
mkcert -cert-file fullchain.pem -key-file privkey.pem 192.168.1.49 launchpad.home localhost 127.0.0.1
```
Die beiden erzeugten Dateien nach `/opt/LaunchPad/certs/` kopieren, dann in
`docker-compose.yml` die auskommentierten `volumes:`-Zeilen beim
`frontend`-Service aktivieren:
```yaml
volumes:
- ./certs/fullchain.pem:/etc/nginx/certs/fullchain.pem:ro
- ./certs/privkey.pem:/etc/nginx/certs/privkey.pem:ro
```
```bash
docker compose up -d --force-recreate frontend
```
`mkcert -install` legt eine lokale Root-CA an, der nur *dieser* Rechner
vertraut. Für weitere Geräte (Handy etc.) muss die Root-CA
(`mkcert -CAROOT` zeigt den Pfad) dort zusätzlich installiert werden, oder es
bleibt bei der Browser-Warnung des automatisch generierten Zertifikats.
## Quickstart (lokale Entwicklung)
Voraussetzungen: Node.js ≥ 20, pnpm ≥ 9.

View File

@@ -24,7 +24,12 @@ RUN pnpm --filter @launchpad/frontend build
# ---- runtime: nginx --------------------------------------------------------
FROM nginx:1.27-alpine AS runtime
RUN apk add --no-cache openssl
COPY apps/frontend/nginx.conf /etc/nginx/conf.d/default.conf
COPY apps/frontend/nginx-locations.conf /etc/nginx/launchpad-locations.conf
COPY apps/frontend/docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
COPY --from=build /app/apps/frontend/dist /usr/share/nginx/html
EXPOSE 80
EXPOSE 80 443
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -0,0 +1,38 @@
#!/bin/sh
set -e
CERT_DIR="/etc/nginx/certs"
CERT_FILE="$CERT_DIR/fullchain.pem"
KEY_FILE="$CERT_DIR/privkey.pem"
# Falls der Nutzer eigene Zertifikate eingebunden hat (siehe README, z. B.
# per mkcert erzeugt), diese nicht überschreiben.
if [ ! -f "$CERT_FILE" ] || [ ! -f "$KEY_FILE" ]; then
HOST="${LAUNCHPAD_HOST:-localhost}"
mkdir -p "$CERT_DIR"
# IP-Adresse oder Hostname? Browser verlangen bei IP-Zugriff eine
# IP-SAN, ein DNS-Name genügt da nicht.
case "$HOST" in
*[0-9]*.*[0-9]*.*[0-9]*.*[0-9]*)
SAN="IP:$HOST,DNS:localhost,IP:127.0.0.1"
;;
*)
SAN="DNS:$HOST,DNS:localhost,IP:127.0.0.1"
;;
esac
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout "$KEY_FILE" \
-out "$CERT_FILE" \
-subj "/CN=$HOST" \
-addext "subjectAltName=$SAN" \
2>/dev/null
echo "[entrypoint] Selbstsigniertes HTTPS-Zertifikat für '$HOST' erzeugt."
echo "[entrypoint] Der Browser zeigt dafür eine Sicherheitswarnung - siehe README fuer eine Variante ohne Warnung (mkcert)."
else
echo "[entrypoint] Verwende eingebundenes Zertifikat unter $CERT_DIR."
fi
exec "$@"

View File

@@ -0,0 +1,32 @@
# PWA / SPA: alle unbekannten Routen auf index.html zurückführen
location / {
try_files $uri $uri/ /index.html;
}
# API-Anfragen an den Backend-Container weiterleiten
location /api/ {
proxy_pass http://backend:3001/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Service Worker und Manifest dürfen nicht langfristig gecacht werden,
# sonst bekommen installierte PWA-Nutzer nie ein Update mit.
location = /sw.js {
add_header Cache-Control "no-cache";
try_files $uri =404;
}
location = /manifest.webmanifest {
add_header Cache-Control "no-cache";
default_type application/manifest+json;
try_files $uri =404;
}
location ~* \.(?:css|js|svg|png|jpg|jpeg|gif|ico|woff2?)$ {
expires 7d;
add_header Cache-Control "public, max-age=604800, immutable";
}

View File

@@ -1,39 +1,26 @@
# HTTP bleibt zusätzlich zu HTTPS verfügbar (kein erzwungener Redirect,
# damit bestehende http://-Zugriffe nicht plötzlich brechen).
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# PWA / SPA: alle unbekannten Routen auf index.html zurückführen
location / {
try_files $uri $uri/ /index.html;
}
# API-Anfragen an den Backend-Container weiterleiten
location /api/ {
proxy_pass http://backend:3001/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Service Worker und Manifest dürfen nicht langfristig gecacht werden,
# sonst bekommen installierte PWA-Nutzer nie ein Update mit.
location = /sw.js {
add_header Cache-Control "no-cache";
try_files $uri =404;
}
location = /manifest.webmanifest {
add_header Cache-Control "no-cache";
default_type application/manifest+json;
try_files $uri =404;
}
location ~* \.(?:css|js|svg|png|jpg|jpeg|gif|ico|woff2?)$ {
expires 7d;
add_header Cache-Control "public, max-age=604800, immutable";
}
include /etc/nginx/launchpad-locations.conf;
}
# HTTPS Zertifikat wird vom Entrypoint-Skript automatisch erzeugt
# (selbstsigniert) oder ist vom Nutzer eingebunden, siehe README.
server {
listen 443 ssl;
http2 on;
server_name _;
root /usr/share/nginx/html;
index index.html;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
include /etc/nginx/launchpad-locations.conf;
}

View File

@@ -25,10 +25,19 @@ services:
dockerfile: apps/frontend/Dockerfile
container_name: launchpad-frontend
restart: unless-stopped
env_file:
- path: ./.env
required: false
depends_on:
- backend
ports:
- "8080:80"
- "8443:443"
# Optional: eigenes Zertifikat statt des automatisch generierten
# selbstsignierten einbinden (z. B. per mkcert erzeugt, siehe README):
# volumes:
# - ./certs/fullchain.pem:/etc/nginx/certs/fullchain.pem:ro
# - ./certs/privkey.pem:/etc/nginx/certs/privkey.pem:ro
volumes:
launchpad-data:

View File

@@ -181,3 +181,23 @@ Geräte):
(3) Umbenennen einer Kategorie im Adminbereich aktualisiert automatisch
alle Dienste mit dem alten Namen. Einschränkung: manuell per API angelegte
Dienste (nicht über einen Scan) lösen diesen Sync aktuell nicht aus.
## HTTPS im lokalen Netz
- Frontend-Container liefert jetzt zusätzlich zu HTTP (Port 8080) auch HTTPS
auf Port 8443 aus, kein erzwungener Redirect.
- Entrypoint-Skript (`apps/frontend/docker-entrypoint.sh`) erzeugt beim
Containerstart automatisch ein selbstsigniertes Zertifikat für die in
`.env` hinterlegte `LAUNCHPAD_HOST` (IP-SAN bei IP-Adressen, DNS-SAN bei
Hostnamen) übernimmt nichts, falls unter `/etc/nginx/certs/` bereits
eigene Zertifikate eingebunden sind (siehe README, z. B. per mkcert für
eine Variante ohne Browser-Warnung).
- `nginx.conf` in HTTP- und HTTPS-Serverblock aufgeteilt, gemeinsame
Location-Regeln in `nginx-locations.conf` ausgelagert (kein doppelt
gepflegter Proxy-/Cache-Code).
> Verifiziert: Zertifikatserzeugung für IP-, Hostname- und Fallback-Fall
> getestet, komplette nginx-Konfiguration mit einem echten nginx-Binary
> validiert (`nginx -t` erfolgreich), echter TLS-Handshake gegen den
> laufenden Server durchgeführt und geprüft, dass das ausgelieferte
> Zertifikat die korrekte IP als Subject Alternative Name trägt.