diff --git a/.env.example b/.env.example index 9cd669c..76bd2ad 100644 --- a/.env.example +++ b/.env.example @@ -107,6 +107,17 @@ NPM_IMAGE=docker.io/jc21/nginx-proxy-manager:2 NPM_CONTAINER_NAME=nginx-proxy-manager NPM_DISABLE_IPV6=true +# Erstes Administratorkonto. +# WICHTIG: Ohne diese beiden Werte zeigt der Proxy Manager beim ersten +# Aufruf einen Einrichtungsassistenten im Browser - die frueher ueblichen +# Standardzugangsdaten admin@example.com/changeme gibt es seit 2.15 NICHT +# mehr. Mit den Werten legt der Container das Konto beim ERSTEN Start +# selbst an (nur bei leerer Datenbank) und die REST-API ist sofort +# nutzbar - damit funktioniert "cmk-manage.sh proxy ...". +# Ist das Konto bereits von Hand angelegt, hier dessen Daten eintragen. +NPM_ADMIN_EMAIL=admin@example.com +NPM_ADMIN_PASSWORD=bitte-aendern-mindestens-8-zeichen + # Port der Adminoberflaeche. Vorgabe im Image ist fest 81; ein abweichender # Port wird ueber einen zusaetzlichen nginx-Server bereitgestellt. # NICHT von Hand aendern - dabei muss die nginx-Zusatzdatei mitgezogen werden: @@ -119,6 +130,11 @@ NPM_ADMIN_PORT=81 # Fuer den Fall, dass Port 81 von aussen nicht erreichbar ist. # Aufruf: ./scripts/cmk-manage.sh browser # ===================================================================== +# Vorgaben fuer "cmk-manage.sh proxy add" (Proxy-Host fuer Checkmk) +PROXY_DOMAIN= +PROXY_FORWARD_HOST=127.0.0.1 +PROXY_FORWARD_PORT=5000 + # Startseite des Browsers. Leer = automatisch aus NPM_ADMIN_PORT abgeleitet. NPM_ADMIN_URL= diff --git a/README.md b/README.md index c197f2e..6e005c9 100644 --- a/README.md +++ b/README.md @@ -1038,7 +1038,7 @@ Zugang Checkmk (direkt) : http://10.0.0.5:5000/cmk/ Checkmk (über Proxy) : https:///cmk/ Agent-Receiver : 10.0.0.5:8000 - NGINX Proxy Manager : http://10.0.0.5:81/ (Erstlogin: admin@example.com / changeme) + NGINX Proxy Manager : http://10.0.0.5:81/ ``` Der Port der Adminoberfläche folgt `NPM_ADMIN_PORT` aus der `.env`; die Ausgabe @@ -1050,16 +1050,92 @@ Anmeldung an Checkmk: Benutzer `cmkadmin`, Passwort aus `CMK_PASSWORD`. ## 10. HTTPS einrichten – Let's Encrypt oder selbstsigniert -### 10.1 NGINX Proxy Manager vorbereiten +### 10.1 Das Administratorkonto + +> **Die früher üblichen Standardzugangsdaten `admin@example.com` / `changeme` +> gibt es seit Version 2.15 nicht mehr.** Der Container legt das erste Konto +> nur noch an, wenn beim **ersten** Start `INITIAL_ADMIN_EMAIL` und +> `INITIAL_ADMIN_PASSWORD` gesetzt sind – sonst erscheint im Browser ein +> Einrichtungsassistent. Nachgelesen in `backend/setup.js` von v2.15.1 und am +> laufenden Container überprüft. + +Deshalb stehen die Werte in der `.env` und werden von `docker-compose.yml` +durchgereicht: + +```bash +NPM_ADMIN_EMAIL=admin@example.com +NPM_ADMIN_PASSWORD=ein-ordentliches-kennwort +``` + +Beides **vor dem ersten Start** setzen. Dann ist das Konto sofort da – und +damit auch die REST-Schnittstelle nutzbar, über die +[Kapitel 10.2](#102-proxy-host-anlegen--ohne-browser) den Proxy-Host anlegt. + +Ist der Stack schon gelaufen und das Konto von Hand über den Assistenten +angelegt worden, wirken die Variablen nicht mehr (die Datenbank ist nicht mehr +leer). Dann einfach die selbst vergebenen Zugangsdaten eintragen – das Skript +meldet sich damit an. + +### 10.2 Proxy-Host anlegen – ohne Browser + +Die Weboberfläche lässt sich im Terminal-Browser nur mühsam bedienen; Formulare +mit Datei-Upload funktionieren dort gar nicht. Da der Proxy Manager ohnehin +lokal läuft, ist die Schnittstelle der verlässlichere Weg: + +```bash +# Checkmk unter checkmk.example.com veröffentlichen, mit selbstsigniertem Zertifikat +sudo ./scripts/cmk-manage.sh proxy add --domain checkmk.example.com --cert self + +# oder mit Let's Encrypt (Port 80 muss aus dem Internet erreichbar sein) +sudo ./scripts/cmk-manage.sh proxy add --domain checkmk.example.com --cert letsencrypt + +# auf einen anderen Zielport statt 5000 +sudo ./scripts/cmk-manage.sh proxy add --domain checkmk.example.com --forward-port 8080 +``` + +Das Kommando erledigt in einem Zug: Zertifikat erzeugen (bzw. bei Let's +Encrypt anfordern), in den Proxy Manager hochladen, Proxy-Host anlegen, HTTPS +erzwingen, HTTP/2 und Websockets einschalten und den empfohlenen +`advanced_config`-Abschnitt setzen. + +| Kommando | Wirkung | +|---|---| +| `proxy status` | Anmeldung prüfen, Anzahl Hosts und Zertifikate | +| `proxy list` | vorhandene Proxy-Hosts | +| `proxy certs` | vorhandene Zertifikate mit Ablaufdatum | +| `proxy add …` | Proxy-Host anlegen (`--update` ersetzt einen vorhandenen) | +| `proxy del --id N` | Proxy-Host löschen | + +Optionen von `proxy add`: + +| Option | Vorgabe | Bedeutung | +|---|---|---| +| `--domain FQDN` | `PROXY_DOMAIN` | öffentlicher Name | +| `--forward-host H` | `127.0.0.1` | Weiterleitungsziel | +| `--forward-port P` | `5000` | Zielport – **frei wählbar**, nicht auf Checkmk festgelegt | +| `--scheme http\|https` | `http` | Schema zum Ziel | +| `--cert self\|letsencrypt\|none` | `self` | Zertifikat | +| `--update` | – | vorhandenen Eintrag ersetzen | +| `--advanced TEXT` | Vorgabe unten | eigener nginx-Abschnitt | +| `--no-force-ssl`, `--no-http2`, `--no-websockets`, `--no-block-exploits` | – | Abschalter | + +In der Oberfläche liegt derselbe Ablauf unter **proxy → „Proxy-Host anlegen"** +(Name → Zielport → Zertifikat) sowie unter **Einstellungen → NGINX Proxy +Manager → „>> Proxy-Hosts anlegen/verwalten"**. + +> Ein selbstsigniertes Zertifikat wird bei wiederholtem Aufruf +> **wiederverwendet** statt jedes Mal neu angelegt – sonst sammeln sich im +> Proxy Manager Dutzende Einträge gleichen Namens an. + +### 10.3 Wenn doch die Weboberfläche 1. `http://:81` aufrufen – bzw. den in `NPM_ADMIN_PORT` gesetzten Port (siehe [Kapitel 12](#12-adminoberfläche-auf-einen-anderen-port-legen)); ist der Port im Netz gesperrt, hilft der Terminal-Browser aus [Kapitel 11](#11-proxy-manager-lokal-einrichten-terminal-browser) -2. Erstanmeldung: **admin@example.com** / **changeme** -3. Sofort E-Mail-Adresse und Passwort ändern (wird beim ersten Login erzwungen) +2. Anmelden mit `NPM_ADMIN_EMAIL` / `NPM_ADMIN_PASSWORD` -### 10.2 Proxy-Host für Checkmk anlegen +### 10.4 Proxy-Host im Browser anlegen **Hosts → Proxy Hosts → Add Proxy Host** @@ -1082,7 +1158,7 @@ proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; ``` -### 10.3 Variante A – Let's Encrypt (öffentlich erreichbarer Server) +### 10.5 Variante A – Let's Encrypt (öffentlich erreichbarer Server) Voraussetzungen: der DNS-Name zeigt öffentlich auf diesen Server und **Port 80 ist aus dem Internet erreichbar** (HTTP-01-Challenge). @@ -1102,7 +1178,7 @@ gesichert. Bei interner DNS-Zone ohne öffentlichen Zugang: **DNS-Challenge** verwenden (SSL → *Use a DNS Challenge* → Provider auswählen, API-Token hinterlegen). -### 10.4 Variante B – Selbstsigniertes Zertifikat (interne Umgebung) +### 10.6 Variante B – Selbstsigniertes Zertifikat (interne Umgebung) Erzeugen: @@ -1252,7 +1328,7 @@ ausgefüllt), aber das Hochladen eines **selbstsignierten Zertifikats** (*SSL Certificates → Custom*) funktioniert so nicht zuverlässig. Für genau diesen Fall führt der Weg über die REST-Schnittstelle des Proxy -Managers. Zertifikat wie in [Kapitel 10.4](#104-variante-b--selbstsigniertes-zertifikat-interne-umgebung) +Managers. Zertifikat wie in [Kapitel 10.6](#106-variante-b--selbstsigniertes-zertifikat-interne-umgebung) erzeugen, dann: ```bash @@ -1570,6 +1646,7 @@ Eine Dateiauswahl mit Verzeichnisnavigation ist eingebaut. | `config list\|get\|set\|edit` | Einstellungen der `.env` anzeigen und ändern | | `npm-port [PORT]` | Port der Proxy-Manager-Oberfläche setzen/anzeigen | | `npm-enable [on\|off]` | Proxy Manager ein-/ausschalten (Daten bleiben erhalten) | +| `proxy status\|list\|certs\|add\|del` | Proxy-Hosts über die API anlegen und verwalten | | `cert …` | selbstsigniertes Zertifikat erzeugen | | `shell` | Root-Shell im Checkmk-Container | | `omd …` | beliebiges `omd`-Kommando im Container | @@ -2077,6 +2154,8 @@ Checkmk-Werksnotizen (Werks/Release Notes) lesen. | `browser` meldet „braucht ein echtes Terminal" | Aufruf aus Pipe/Skript. Direkt auf der Konsole oder per SSH aufrufen; nur der Bau geht headless mit `--build-only` | | Menüpunkt bricht mit einer Fehlermeldung in die Shell ab | behoben: fehlende Container, Sites oder Backups werden jetzt als Dialog gemeldet, die Oberfläche bleibt offen | | `job auto on` meldet fehlende Rechte | `/etc/cron.d/` braucht root: mit `sudo` aufrufen, oder die ausgegebenen Zeilen selbst übernehmen | +| Proxy Manager verlangt beim ersten Aufruf ein neues Konto | seit 2.15 normal. `NPM_ADMIN_EMAIL`/`NPM_ADMIN_PASSWORD` **vor** dem ersten Start setzen; ist das Konto schon angelegt, dessen Daten dort eintragen | +| Im Terminal-Browser erscheint bei Proxy-Hosts ein JSON-Fehler | Die Oberfläche ist dafür zu aufwendig. Proxy-Hosts stattdessen über `cmk-manage.sh proxy add` anlegen ([Kapitel 10.2](#102-proxy-host-anlegen--ohne-browser)) | | TUI startet nicht | `dialog` installieren: `apt-get install -y dialog` bzw. `dnf install -y dialog` | | Site startet nicht, `tmp`-Fehler | `./scripts/cmk-manage.sh omd config set TMPFS off`, danach Site starten | @@ -2096,8 +2175,8 @@ docker exec -it checkmk omd su cmk # bzw. podman exec … * `CMK_PASSWORD` sofort nach der Installation in der Oberfläche ändern und aus der `.env` entfernen (wird nur bei der Neuanlage ausgewertet); `chmod 600 .env` ist gesetzt. -* Standardzugang des Proxy Managers (`admin@example.com` / `changeme`) beim - ersten Login zwingend ändern. +* `NPM_ADMIN_PASSWORD` vor dem ersten Start auf ein eigenes Kennwort setzen – + daraus wird das Administratorkonto des Proxy Managers erzeugt. * Port **81** (Adminoberfläche) nur aus dem Administrationsnetz erreichbar machen, Port **5000** gar nicht nach außen öffnen. * Backups enthalten **alle** Passwörter und Zertifikate der Site – verschlüsselt diff --git a/README_en.md b/README_en.md index e69e4f0..b3460bf 100644 --- a/README_en.md +++ b/README_en.md @@ -1041,7 +1041,7 @@ Zugang Checkmk (direkt) : http://10.0.0.5:5000/cmk/ Checkmk (über Proxy) : https:///cmk/ Agent-Receiver : 10.0.0.5:8000 - NGINX Proxy Manager : http://10.0.0.5:81/ (Erstlogin: admin@example.com / changeme) + NGINX Proxy Manager : http://10.0.0.5:81/ ``` (`läuft` = running, `gestoppt` = stopped, `Zugang` = access, `Erstlogin` = @@ -1054,16 +1054,90 @@ Logging in to Checkmk: user `cmkadmin`, password from `CMK_PASSWORD`. ## 10. Setting up HTTPS – Let's Encrypt or self-signed -### 10.1 Preparing the NGINX Proxy Manager +### 10.1 The administrator account + +> **The former default credentials `admin@example.com` / `changeme` no longer +> exist as of version 2.15.** The container only creates the first account if +> `INITIAL_ADMIN_EMAIL` and `INITIAL_ADMIN_PASSWORD` are set at the **first** +> start – otherwise the browser shows a setup wizard. Verified in +> `backend/setup.js` of v2.15.1 and against a running container. + +That is why the values live in `.env` and are passed through by +`docker-compose.yml`: + +```bash +NPM_ADMIN_EMAIL=admin@example.com +NPM_ADMIN_PASSWORD=a-proper-password +``` + +Set both **before the first start**. The account then exists right away – and +with it the REST interface used by +[chapter 10.2](#102-creating-a-proxy-host--without-a-browser). + +If the stack has already run and the account was created by hand through the +wizard, the variables no longer take effect (the database is no longer empty). +Simply enter the credentials you chose – the script logs in with those. + +### 10.2 Creating a proxy host – without a browser + +The web interface is painful in a terminal browser, and forms with file uploads +do not work there at all. Since the Proxy Manager runs locally anyway, its +interface is the more reliable route: + +```bash +# publish Checkmk at checkmk.example.com with a self-signed certificate +sudo ./scripts/cmk-manage.sh proxy add --domain checkmk.example.com --cert self + +# or with Let's Encrypt (port 80 must be reachable from the internet) +sudo ./scripts/cmk-manage.sh proxy add --domain checkmk.example.com --cert letsencrypt + +# forwarding to a different port instead of 5000 +sudo ./scripts/cmk-manage.sh proxy add --domain checkmk.example.com --forward-port 8080 +``` + +In one go the command: creates the certificate (or requests it from Let's +Encrypt), uploads it to the Proxy Manager, creates the proxy host, forces +HTTPS, enables HTTP/2 and websockets, and sets the recommended +`advanced_config` block. + +| Command | Effect | +|---|---| +| `proxy status` | check the login, count hosts and certificates | +| `proxy list` | existing proxy hosts | +| `proxy certs` | existing certificates with expiry date | +| `proxy add …` | create a proxy host (`--update` replaces an existing one) | +| `proxy del --id N` | delete a proxy host | + +Options for `proxy add`: + +| Option | Default | Meaning | +|---|---|---| +| `--domain FQDN` | `PROXY_DOMAIN` | public name | +| `--forward-host H` | `127.0.0.1` | forwarding target | +| `--forward-port P` | `5000` | target port – **freely selectable**, not fixed to Checkmk | +| `--scheme http\|https` | `http` | scheme towards the target | +| `--cert self\|letsencrypt\|none` | `self` | certificate | +| `--update` | – | replace an existing entry | +| `--advanced TEXT` | see below | custom nginx block | +| `--no-force-ssl`, `--no-http2`, `--no-websockets`, `--no-block-exploits` | – | switches | + +In the interface the same flow is under **proxy → "Proxy-Host anlegen"** +(name → target port → certificate) and under **Einstellungen → NGINX Proxy +Manager → ">> Proxy-Hosts anlegen/verwalten"**. + +> A self-signed certificate is **reused** on repeated runs instead of being +> created anew each time – otherwise dozens of entries with the same name pile +> up in the Proxy Manager. + +### 10.3 If you do want the web interface 1. open `http://:81` – or whichever port is set in `NPM_ADMIN_PORT` (see [chapter 12](#12-moving-the-admin-interface-to-a-different-port)); if that port is blocked on the network, use the terminal browser from [chapter 11](#11-configuring-the-proxy-manager-locally-terminal-browser) -2. initial login: **admin@example.com** / **changeme** -3. change the e-mail address and password immediately (enforced on first login) +2. log in with `NPM_ADMIN_EMAIL` / `NPM_ADMIN_PASSWORD` -### 10.2 Creating a proxy host for Checkmk +### 10.4 Creating a proxy host in the browser **Hosts → Proxy Hosts → Add Proxy Host** @@ -1086,7 +1160,7 @@ proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; ``` -### 10.3 Option A – Let's Encrypt (publicly reachable server) +### 10.5 Option A – Let's Encrypt (publicly reachable server) Prerequisites: the DNS name resolves publicly to this server and **port 80 is reachable from the internet** (HTTP-01 challenge). @@ -1106,7 +1180,7 @@ project directory. For an internal DNS zone without public access, use the **DNS challenge** (SSL → *Use a DNS Challenge* → select provider, store the API token). -### 10.4 Option B – self-signed certificate (internal environment) +### 10.6 Option B – self-signed certificate (internal environment) Creating one: @@ -1259,7 +1333,7 @@ work reliably this way. For exactly this case, use the Proxy Manager's REST interface. Create the certificate as described in -[chapter 10.4](#104-option-b--self-signed-certificate-internal-environment), then: +[chapter 10.6](#106-option-b--self-signed-certificate-internal-environment), then: ```bash NPM=http://127.0.0.1:81 @@ -1576,6 +1650,7 @@ A file chooser with directory navigation is built in. | `config list\|get\|set\|edit` | show and change settings in `.env` | | `npm-port [PORT]` | set/show the port of the Proxy Manager interface | | `npm-enable [on\|off]` | turn the Proxy Manager on/off (data is preserved) | +| `proxy status\|list\|certs\|add\|del` | create and manage proxy hosts through the API | | `cert …` | create a self-signed certificate | | `shell` | root shell in the Checkmk container | | `omd …` | any `omd` command inside the container | @@ -2086,6 +2161,8 @@ release notes beforehand. | `browser` reports "braucht ein echtes Terminal" (needs a real terminal) | called from a pipe/script. Call it on the console or over SSH; only the build runs headless, with `--build-only` | | A menu entry drops out to the shell with an error | fixed: missing containers, sites or backups are now reported in a dialog and the interface stays open | | `job auto on` reports missing permissions | `/etc/cron.d/` needs root: call it with `sudo`, or add the printed lines yourself | +| The Proxy Manager asks for a new account on first use | normal since 2.15. Set `NPM_ADMIN_EMAIL`/`NPM_ADMIN_PASSWORD` **before** the first start; if the account already exists, enter its credentials there | +| A JSON error appears for proxy hosts in the terminal browser | the interface is too heavy for it. Create proxy hosts with `cmk-manage.sh proxy add` instead ([chapter 10.2](#102-creating-a-proxy-host--without-a-browser)) | | The TUI does not start | install `dialog`: `apt-get install -y dialog` or `dnf install -y dialog` | | Site does not start, `tmp` errors | `./scripts/cmk-manage.sh omd config set TMPFS off`, then start the site | @@ -2105,8 +2182,8 @@ docker exec -it checkmk omd su cmk # or podman exec … * Change `CMK_PASSWORD` in the interface right after installation and remove it from `.env` (it is only evaluated when the site is created); `chmod 600 .env` is already set. -* The Proxy Manager's default login (`admin@example.com` / `changeme`) must be - changed on first login. +* Set `NPM_ADMIN_PASSWORD` to your own password before the first start – the + Proxy Manager's administrator account is created from it. * Make port **81** (admin interface) reachable from the administration network only, and never expose port **5000**. * Backups contain **all** passwords and certificates of the site – store them diff --git a/docker-compose.yml b/docker-compose.yml index 29d9d94..f125485 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -62,6 +62,12 @@ services: environment: DISABLE_IPV6: ${NPM_DISABLE_IPV6:-true} TZ: ${TZ:-Europe/Berlin} + # Legt das erste Administratorkonto an - aber NUR beim allerersten + # Start mit leerer Datenbank. Ohne diese beiden Werte zeigt der + # Proxy Manager stattdessen einen Einrichtungsassistenten, und die + # REST-API laesst sich nicht ohne Weiteres nutzen. + INITIAL_ADMIN_EMAIL: ${NPM_ADMIN_EMAIL:-} + INITIAL_ADMIN_PASSWORD: ${NPM_ADMIN_PASSWORD:-} volumes: - ${NPM_DATA_DIR:-./data/npm/data}:/data:Z - ${NPM_LE_DIR:-./data/npm/letsencrypt}:/etc/letsencrypt:Z diff --git a/scripts/cmk-manage.sh b/scripts/cmk-manage.sh index d8a7137..347186c 100755 --- a/scripts/cmk-manage.sh +++ b/scripts/cmk-manage.sh @@ -167,6 +167,21 @@ KOMMANDOS - Lokale Bedienung ohne externen Zugriff --build Werkzeug-Image neu bauen --pull Engine-Image neu laden +KOMMANDOS - Proxy-Hosts (ueber die API des Proxy Managers) + proxy status Anmeldung pruefen, Anzahl Hosts/Zertifikate + proxy list vorhandene Proxy-Hosts + proxy certs vorhandene Zertifikate + proxy add [Opt.] Proxy-Host anlegen + --domain FQDN oeffentlicher Name (Vorgabe: PROXY_DOMAIN) + --forward-host H Ziel (Vorgabe 127.0.0.1) + --forward-port P Zielport (Vorgabe 5000 = Checkmk) + --scheme http|https + --cert self|letsencrypt|none (Vorgabe self) + --update vorhandenen Host ersetzen + --advanced TEXT eigener nginx-Abschnitt + --no-force-ssl --no-http2 --no-websockets --no-block-exploits + proxy del --id N Proxy-Host loeschen + KOMMANDOS - Zertifikat cert [Optionen] selbstsigniertes Zertifikat fuer den Proxy erzeugen --domain FQDN Common Name / SAN @@ -2641,7 +2656,12 @@ checkmk|CMK_HOSTNAME|text|Hostname im Container|| checkmk|CMK_LIVESTATUS_TCP|choice|Livestatus ueber TCP (Port 6557)|off:on| checkmk|MAIL_RELAY_HOST|text|SMTP-Relay fuer Benachrichtigungen||1 npm|NPM_ENABLED|choice|Proxy Manager betreiben|on:off| +npm|NPM_ADMIN_EMAIL|text|Konto des Proxy Managers (E-Mail)||1 +npm|NPM_ADMIN_PASSWORD|password|Kennwort des Proxy-Manager-Kontos||1 npm|NPM_ADMIN_PORT|port|Port der Adminoberflaeche|| +npm|PROXY_DOMAIN|text|Vorgabe-Domain fuer Proxy-Hosts||1 +npm|PROXY_FORWARD_HOST|text|Weiterleitungsziel (Host)|| +npm|PROXY_FORWARD_PORT|port|Weiterleitungsziel (Port)|| npm|NPM_IMAGE|text|Image des Proxy Managers|| npm|NPM_CONTAINER_NAME|text|Containername Proxy Manager|| npm|NPM_DISABLE_IPV6|choice|IPv6 abschalten|true:false| @@ -3018,6 +3038,300 @@ EOF $RUNTIME run "${runopts[@]}" "$image" "${args[@]}" } +# ===================================================================== +# Proxy-Hosts ueber die REST-Schnittstelle des NGINX Proxy Managers +# +# Die Weboberflaeche laesst sich im Terminal-Browser nur muehsam +# bedienen - Datei-Uploads und einzelne Dialoge funktionieren dort +# nicht. Da der Proxy Manager ohnehin lokal laeuft, ist die API der +# verlaesslichere Weg. +# +# Voraussetzung ist ein Konto. Seit Version 2.15 legt der Container es +# nur noch an, wenn INITIAL_ADMIN_EMAIL und INITIAL_ADMIN_PASSWORD +# gesetzt sind (das erledigt die .env ueber NPM_ADMIN_EMAIL/-PASSWORD) +# - sonst erscheint beim ersten Aufruf ein Einrichtungsassistent. +# +# Geprueft an der Schnittstellenbeschreibung von v2.15.1: +# POST /api/tokens {identity, secret} +# GET|POST|DELETE /api/nginx/proxy-hosts +# POST /api/nginx/certificates {provider, ...} +# POST /api/nginx/certificates//upload multipart +# Let's Encrypt nimmt die E-Mail des angemeldeten Kontos - ein +# "letsencrypt_email" im Aufruf gibt es in 2.15 nicht mehr. +# ===================================================================== + +npm_api_base() { printf 'http://127.0.0.1:%s\n' "${NPM_ADMIN_PORT:-81}"; } + +# Sehr einfache Auswertung - kein jq noetig, das fehlt auf vielen Hosts +json_num() { printf '%s' "$1" | tr ',' '\n' | sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p" | head -n1; } +json_str() { printf '%s' "$1" | tr ',' '\n' | sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" | head -n1; } + +# IDs aus einer Listenantwort ziehen. "id": trifft dabei NICHT auf +# certificate_id oder owner_user_id zu, weil dort ein _ vor dem id steht. +json_ids() { printf '%s' "$1" | grep -o '"id":[0-9]*' | sed 's/.*://' ; } + +npm_api_token() { + local base; base="$(npm_api_base)" + [ -n "${NPM_ADMIN_EMAIL:-}" ] && [ -n "${NPM_ADMIN_PASSWORD:-}" ] || { + err "Es sind keine Zugangsdaten fuer den Proxy Manager hinterlegt." + cat >&2 </dev/null)" || true + local tok; tok="$(json_str "$body" token)" + if [ -z "$tok" ]; then + err "Anmeldung am Proxy Manager fehlgeschlagen." + local m; m="$(json_str "$body" message)" + [ -n "$m" ] && printf ' Antwort: %s\n' "$m" >&2 + printf ' Geprueft wurde: %s\n' "$base/api/tokens" >&2 + return 1 + fi + printf '%s' "$tok" +} + +# npm_api METHODE PFAD [JSON] -> Antwort auf stdout, HTTP-Code in NPM_CODE +npm_api() { + local method="$1" path="$2" data="${3:-}" + local base; base="$(npm_api_base)" + local -a a=(-sS --max-time 60 -X "$method" -H "Authorization: Bearer $NPM_TOKEN" + -w '\n%{http_code}') + [ -n "$data" ] && a+=(-H 'Content-Type: application/json' -d "$data") + local out; out="$(curl "${a[@]}" "$base$path" 2>/dev/null)" || out=$'\n000' + NPM_CODE="${out##*$'\n'}" + printf '%s' "${out%$'\n'*}" +} + +npm_require_running() { + npm_enabled || { err "Der NGINX Proxy Manager ist abgeschaltet (NPM_ENABLED=off)." + log "Einschalten: $SELF npm-enable on"; return 1; } + container_running "$NPM_CONTAINER_NAME" || { + err "Der Container '$NPM_CONTAINER_NAME' laeuft nicht." + log "Starten: $SELF up"; return 1; } + return 0 +} + +# --- Zertifikat anlegen -------------------------------------------------- +# gibt die Zertifikats-ID auf stdout aus +npm_cert_selfsigned() { # domain + local domain="$1" + local dir="$PROJECT_DIR/data/npm/custom-certs/$domain" + if [ ! -f "$dir/fullchain.pem" ] || [ ! -f "$dir/privkey.pem" ]; then + log "Erzeuge selbstsigniertes Zertifikat fuer '$domain' ..." >&2 + cmd_cert --domain "$domain" >/dev/null || return 1 + else + log "Verwende vorhandenes Zertifikat unter $dir" >&2 + fi + # Bereits vorhandenen Eintrag gleichen Namens wiederverwenden, sonst + # sammeln sich bei jedem Aufruf neue Zertifikate an. + local id="" body list cid o + list="$(npm_api GET /api/nginx/certificates)" + for cid in $(json_ids "$list"); do + o="$(npm_api GET "/api/nginx/certificates/$cid")" + if [ "$(json_str "$o" provider)" = other ] && [ "$(json_str "$o" nice_name)" = "$domain" ]; then + id="$cid"; log "Verwende vorhandenen Zertifikatseintrag $id wieder." >&2; break + fi + done + if [ -z "$id" ]; then + body="$(npm_api POST /api/nginx/certificates \ + "{\"provider\":\"other\",\"nice_name\":\"$domain\",\"domain_names\":[\"$domain\"]}")" + id="$(json_num "$body" id)" + [ -n "$id" ] || { err "Zertifikatseintrag konnte nicht angelegt werden (HTTP $NPM_CODE)." + printf ' %s\n' "$body" >&2; return 1; } + fi + log "Lade Schluessel und Zertifikat hoch (ID $id) ..." >&2 + local up + up="$(curl -sS --max-time 60 -X POST "$(npm_api_base)/api/nginx/certificates/$id/upload" \ + -H "Authorization: Bearer $NPM_TOKEN" \ + -F "certificate=@$dir/fullchain.pem" \ + -F "certificate_key=@$dir/privkey.pem" 2>/dev/null)" || true + case "$up" in + *'"error"'*|'') err "Hochladen fehlgeschlagen."; printf ' %s\n' "$up" >&2; return 1 ;; + esac + printf '%s' "$id" +} + +npm_cert_letsencrypt() { # domain + local domain="$1" body + log "Fordere Let's-Encrypt-Zertifikat fuer '$domain' an - das dauert ..." >&2 + body="$(npm_api POST /api/nginx/certificates \ + "{\"provider\":\"letsencrypt\",\"domain_names\":[\"$domain\"],\"meta\":{}}")" + local id; id="$(json_num "$body" id)" + if [ -z "$id" ]; then + err "Let's Encrypt hat kein Zertifikat ausgestellt (HTTP $NPM_CODE)." + local m; m="$(json_str "$body" message)" + [ -n "$m" ] && printf ' Antwort: %s\n' "$m" >&2 + cat >&2 </dev/null && echo "ja (Zert. $cid)" || echo nein)" + n=$((n+1)) + done + [ "$n" -eq 0 ] && echo "(noch kein Proxy-Host angelegt)" + return 0 ;; + + certs) + npm_require_running || return 1 + NPM_TOKEN="$(npm_api_token)" || return 1 + local body; body="$(npm_api GET /api/nginx/certificates)" + printf '%-5s %-12s %-28s %s\n' "ID" "ANBIETER" "NAME" "LAEUFT AB" + local id o n=0 + for id in $(json_ids "$body"); do + o="$(npm_api GET "/api/nginx/certificates/$id")" + printf '%-5s %-12s %-28s %s\n' "$id" "$(json_str "$o" provider)" \ + "$(json_str "$o" nice_name)" "$(json_str "$o" expires_on | awk '{print $1}')" + n=$((n+1)) + done + [ "$n" -eq 0 ] && echo "(noch kein Zertifikat vorhanden)" + return 0 ;; + + del|delete|rm) + local pid="" + while [ $# -gt 0 ]; do + case "$1" in --id) pid="$2"; shift 2 ;; *) pid="$1"; shift ;; esac + done + [ -n "$pid" ] || die "ID fehlt: $SELF proxy del --id N (IDs zeigt '$SELF proxy list')" + npm_require_running || return 1 + NPM_TOKEN="$(npm_api_token)" || return 1 + confirm "Proxy-Host mit der ID $pid wirklich loeschen?" || die "Abgebrochen." + npm_api DELETE "/api/nginx/proxy-hosts/$pid" >/dev/null + case "$NPM_CODE" in 200|201|204) ok "Proxy-Host $pid geloescht." ;; + *) die "Loeschen fehlgeschlagen (HTTP $NPM_CODE)." ;; esac + return 0 ;; + + add) + local domain="${PROXY_DOMAIN:-}" fhost="${PROXY_FORWARD_HOST:-127.0.0.1}" + local fport="${PROXY_FORWARD_PORT:-5000}" scheme=http cert=self + local force_ssl=true http2=true ws=true block=true update="" adv="" + local adv_set="" + while [ $# -gt 0 ]; do + case "$1" in + --domain) domain="$2"; shift 2 ;; + --forward-host) fhost="$2"; shift 2 ;; + --forward-port) fport="$2"; shift 2 ;; + --scheme) scheme="$2"; shift 2 ;; + --cert) cert="$2"; shift 2 ;; + --advanced) adv="$2"; adv_set=1; shift 2 ;; + --update) update=1; shift ;; + --no-force-ssl) force_ssl=false; shift ;; + --no-http2) http2=false; shift ;; + --no-websockets) ws=false; shift ;; + --no-block-exploits) block=false; shift ;; + *) die "Unbekannte Option fuer 'proxy add': $1" ;; + esac + done + [ -n "$domain" ] || die "--domain fehlt (oder PROXY_DOMAIN in der .env setzen)." + case "$scheme" in http|https) : ;; *) die "--scheme: http oder https" ;; esac + case "$cert" in none|self|letsencrypt) : ;; *) die "--cert: none | self | letsencrypt" ;; esac + valid_port "$fport" || die "--forward-port: keine gueltige Portnummer." + [ -n "$adv_set" ] || adv='client_max_body_size 0;\nproxy_read_timeout 300s;\nproxy_set_header X-Forwarded-Proto $scheme;\nproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;' + + npm_require_running || return 1 + NPM_TOKEN="$(npm_api_token)" || return 1 + + # schon vorhanden? + local existing + # Achtung: grep liefert 1, wenn nichts passt - unter "set -e" wuerde + # das die Zuweisung und damit das ganze Skript beenden. + existing="$(npm_api GET /api/nginx/proxy-hosts | tr '{' '\n' \ + | grep -F "\"$domain\"" | head -n1 || true)" + local exid=""; [ -n "$existing" ] && exid="$(json_num "$existing" id)" + if [ -n "$exid" ] && [ -z "$update" ]; then + die "Fuer '$domain' gibt es bereits den Proxy-Host $exid. + Ersetzen mit --update, oder vorher loeschen: $SELF proxy del --id $exid" + fi + + local certid=0 + case "$cert" in + self) certid="$(npm_cert_selfsigned "$domain")" || return 1 ;; + letsencrypt) certid="$(npm_cert_letsencrypt "$domain")" || return 1 ;; + none) certid=0; force_ssl=false; http2=false ;; + esac + + local payload + payload="{\"domain_names\":[\"$domain\"],\"forward_scheme\":\"$scheme\",\"forward_host\":\"$fhost\",\"forward_port\":$fport,\"certificate_id\":$certid,\"ssl_forced\":$force_ssl,\"http2_support\":$http2,\"block_exploits\":$block,\"allow_websocket_upgrade\":$ws,\"caching_enabled\":false,\"advanced_config\":\"$adv\",\"enabled\":true,\"meta\":{}}" + + local body + if [ -n "$exid" ]; then + log "Aktualisiere vorhandenen Proxy-Host $exid ..." + body="$(npm_api PUT "/api/nginx/proxy-hosts/$exid" "$payload")" + else + log "Lege Proxy-Host fuer '$domain' an ..." + body="$(npm_api POST /api/nginx/proxy-hosts "$payload")" + fi + local newid; newid="$(json_num "$body" id)" + if [ -z "$newid" ]; then + err "Der Proxy-Host konnte nicht angelegt werden (HTTP $NPM_CODE)." + local m; m="$(json_str "$body" message)" + [ -n "$m" ] && printf ' Antwort: %s\n' "$m" >&2 + return 1 + fi + ok "Proxy-Host $newid angelegt: $domain -> $scheme://$fhost:$fport" + cat < $([ "$force_ssl" = true ] && echo 'Weiterleitung auf HTTPS' || echo 'unverschluesselt') + +$([ "$cert" = self ] && printf '%s\n' "Das Zertifikat ist selbstsigniert. Damit Browser und Agents ihm +trauen, data/npm/custom-certs/$domain/fullchain.pem auf den Clients +als vertrauenswuerdig einrichten.") +EOF + return 0 ;; + + *) die "Unbekanntes Unterkommando 'proxy $sub'. + Moeglich: status | list | certs | add | del" ;; + esac +} + # ===================================================================== # Selbstsigniertes Zertifikat fuer den NGINX Proxy Manager # ===================================================================== @@ -3939,7 +4253,8 @@ gui_settings_category() { case "$cat" in image) items+=("__wizard__" ">> Image beschaffen (Edition, Version, Bezugsweg)") items+=("__test__" ">> Zugangsdaten jetzt pruefen (Registry + Downloadportal)") ;; - npm) items+=("__npm__" ">> Proxy Manager: Stand und Admin-Port anzeigen") ;; + npm) items+=("__proxy__" ">> Proxy-Hosts anlegen/verwalten") + items+=("__npm__" ">> Proxy Manager: Stand und Admin-Port anzeigen") ;; backup) items+=("__jobs__" ">> Backup-Jobs verwalten") ;; esac items+=("back" "zurueck") @@ -3948,6 +4263,7 @@ gui_settings_category() { case "$sel" in back) return 0 ;; __wizard__) gui_image_wizard || true; continue ;; + __proxy__) gui_proxy_menu || true; continue ;; __test__) gui_image_test || true; continue ;; __jobs__) gui_jobs_menu || true; continue ;; __npm__) local f; f="$(mktemp)" @@ -4032,6 +4348,115 @@ Trotzdem starten? done } +# --------------------------------------------------------------------- +# Proxy-Hosts im Menue +# --------------------------------------------------------------------- +gui_proxy_menu() { + while :; do + local sel + sel="$(ui_menu "Proxy-Hosts" \ +"NGINX Proxy Manager ueber seine Schnittstelle bedienen - +das ist verlaesslicher als der Terminal-Browser. + +Konto: ${NPM_ADMIN_EMAIL:-} +Ziel : ${PROXY_FORWARD_HOST:-127.0.0.1}:${PROXY_FORWARD_PORT:-5000}" \ + anlegen "Proxy-Host anlegen (Assistent)" \ + list "Vorhandene Proxy-Hosts anzeigen" \ + certs "Vorhandene Zertifikate anzeigen" \ + del "Proxy-Host loeschen" \ + status "Anmeldung pruefen" \ + back "zurueck")" || return 0 + case "$sel" in + anlegen) gui_proxy_add || true ;; + list) local f; f="$(mktemp)"; NO_COLOR=1 cmd_proxy list >"$f" 2>&1 || true + ui_text "Proxy-Hosts" "$f"; rm -f "$f" ;; + certs) local f2; f2="$(mktemp)"; NO_COLOR=1 cmd_proxy certs >"$f2" 2>&1 || true + ui_text "Zertifikate" "$f2"; rm -f "$f2" ;; + del) gui_proxy_del || true ;; + status) ui_run "Anmeldung pruefen" bash -c "cd '$PROJECT_DIR' && NO_COLOR=1 '$SELF' proxy status" ;; + back) return 0 ;; + esac + done +} + +gui_proxy_add() { + if ! npm_require_running 2>/dev/null; then + ui_msg "Proxy Manager laeuft nicht" \ +"Der NGINX Proxy Manager muss laufen, damit sich ein Proxy-Host +anlegen laesst. + + Abgeschaltet? Einstellungen -> NGINX Proxy Manager + Nicht gestartet? Stack steuern -> Stack starten" + return 0 + fi + if [ -z "${NPM_ADMIN_EMAIL:-}" ] || [ -z "${NPM_ADMIN_PASSWORD:-}" ]; then + ui_msg "Zugangsdaten fehlen" \ +"Fuer die Schnittstelle werden die Zugangsdaten des Proxy Managers +gebraucht: NPM_ADMIN_EMAIL und NPM_ADMIN_PASSWORD. + +Sie stehen unter Einstellungen -> NGINX Proxy Manager. + +Hinweis: Seit Version 2.15 legt der Container das Konto nur noch +beim ERSTEN Start an, und nur wenn beide Werte gesetzt sind. +Wurde das Konto im Browser angelegt, hier dessen Daten eintragen." + return 0 + fi + + local dom fport cert + dom="$(ui_input "Proxy-Host (1/3)" \ +"Unter welchem Namen soll Checkmk erreichbar sein? + +Der Name muss auf diesen Host zeigen - per DNS oder +per hosts-Datei der Arbeitsplatzrechner." "${PROXY_DOMAIN:-}")" || return 0 + [ -n "$dom" ] || return 0 + + fport="$(ui_input "Proxy-Host (2/3)" \ +"Auf welchen Port soll weitergeleitet werden? + + 5000 = Checkmk-Oberflaeche (Vorgabe) + 81 = Oberflaeche des Proxy Managers selbst" "${PROXY_FORWARD_PORT:-5000}")" || return 0 + valid_port "$fport" || { ui_msg "Ungueltig" "'$fport' ist keine gueltige Portnummer."; return 0; } + + cert="$(ui_menu "Proxy-Host (3/3) - Zertifikat" \ +"$dom -> 127.0.0.1:$fport + +Womit soll HTTPS abgesichert werden?" \ + self "Selbstsigniert (intern, sofort einsatzbereit)" \ + letsencrypt "Let's Encrypt (Port 80 muss aus dem Internet erreichbar sein)" \ + none "Ohne HTTPS (nur Port 80)")" || return 0 + + local upd="" + ui_yesno "Anlegen" \ +"$dom -> 127.0.0.1:$fport +Zertifikat: $cert + +Einen bereits vorhandenen Eintrag fuer diesen Namen ersetzen? + + ersetzt ihn + bricht ab, falls es ihn schon gibt" && upd=--update + + ui_run "Proxy-Host anlegen" bash -c \ + "cd '$PROJECT_DIR' && NO_COLOR=1 '$SELF' -y proxy add --domain '$dom' \ + --forward-port '$fport' --cert '$cert' $upd" + + # Die Vorgaben merken, damit der naechste Aufruf sie kennt + env_set PROXY_DOMAIN "$dom" 1 2>/dev/null || true + env_set PROXY_FORWARD_PORT "$fport" 2>/dev/null || true + load_env "${ENV_FILE:-$PROJECT_DIR/.env}" +} + +gui_proxy_del() { + local f; f="$(mktemp)" + NO_COLOR=1 cmd_proxy list >"$f" 2>&1 || true + ui_text "Vorhandene Proxy-Hosts" "$f"; rm -f "$f" + local id + id="$(ui_input "Loeschen" "ID des zu loeschenden Proxy-Hosts:" "")" || return 0 + [ -n "$id" ] || return 0 + case "$id" in ''|*[!0-9]*) ui_msg "Ungueltig" "Bitte eine Zahl angeben."; return 0 ;; esac + ui_yesno "Loeschen" "Proxy-Host $id wirklich loeschen?" || return 0 + ui_run "Proxy-Host loeschen" bash -c "cd '$PROJECT_DIR' && '$SELF' -y proxy del --id '$id'" +} + gui_cert() { local domain alt days domain="$(ui_input "Zertifikat" "FQDN / Common Name:" "${CERT_DOMAIN:-checkmk.example.local}")" || return 0 @@ -4059,6 +4484,7 @@ Backups : $BACKUP_DIR" \ manage "Backups verwalten (Liste/Details/Loeschen)" \ sites "Sites im Container verwalten" \ image "Checkmk-Image beschaffen/auswaehlen" \ + proxy "Proxy-Hosts anlegen/verwalten (HTTPS fuer Checkmk)" \ browser "Terminal-Browser (Proxy Manager lokal bedienen)" \ cert "Selbstsigniertes Zertifikat erzeugen" \ config "Einstellungen (.env) - menuegefuehrt" \ @@ -4073,6 +4499,7 @@ Backups : $BACKUP_DIR" \ manage) gui_backups_menu || true ;; sites) gui_sites_menu || true ;; image) gui_image_menu || true ;; + proxy) gui_proxy_menu || true ;; browser) gui_browser_menu || true ;; cert) gui_cert || true ;; config) gui_settings_menu || true ;; @@ -4131,6 +4558,7 @@ main() { browser) cmd_browser "$@" || true ;; npm-port) cmd_npm_port "$@" ;; npm-enable) cmd_npm_enable "$@" ;; + proxy) cmd_proxy "$@" ;; config) cmd_config "$@" || true ;; help|-h|--help) usage ;; *) err "Unbekanntes Kommando: $cmd"; echo; usage; exit 1 ;; diff --git a/scripts/lib/common.sh b/scripts/lib/common.sh index 910fd0e..a4af5b0 100644 --- a/scripts/lib/common.sh +++ b/scripts/lib/common.sh @@ -45,6 +45,8 @@ load_env() { CMK_IMAGE="${CMK_IMAGE:-docker.io/checkmk/check-mk-raw:2.3.0-latest}" NPM_ENABLED="${NPM_ENABLED:-on}" BACKUP_AUTO="${BACKUP_AUTO:-off}" + PROXY_FORWARD_HOST="${PROXY_FORWARD_HOST:-127.0.0.1}" + PROXY_FORWARD_PORT="${PROXY_FORWARD_PORT:-5000}" NPM_ADMIN_PORT="${NPM_ADMIN_PORT:-81}" # Leer = automatisch aus dem Admin-Port ableiten NPM_ADMIN_URL="${NPM_ADMIN_URL:-http://127.0.0.1:$NPM_ADMIN_PORT}" diff --git a/systemd/quadlet/nginx-proxy-manager.container b/systemd/quadlet/nginx-proxy-manager.container index 10a725d..e88c4f4 100644 --- a/systemd/quadlet/nginx-proxy-manager.container +++ b/systemd/quadlet/nginx-proxy-manager.container @@ -13,6 +13,9 @@ Image=__NPM_IMAGE__ Network=host EnvironmentFile=__PROJECT_DIR__/.env Environment=DISABLE_IPV6=true +# Erstkonto beim ersten Start (siehe .env) +Environment=INITIAL_ADMIN_EMAIL=${NPM_ADMIN_EMAIL} +Environment=INITIAL_ADMIN_PASSWORD=${NPM_ADMIN_PASSWORD} Volume=__PROJECT_DIR__/data/npm/data:/data:Z Volume=__PROJECT_DIR__/data/npm/letsencrypt:/etc/letsencrypt:Z