diff --git a/README.md b/README.md
index 602c456..76a20ff 100644
--- a/README.md
+++ b/README.md
@@ -1228,6 +1228,7 @@ erzwingen, HTTP/2 und Websockets einschalten und den empfohlenen
| `proxy default --domain X` | HTTPS über die blanke IP ermöglichen (spiegelt X in einen Standard-Server) |
| `proxy default --off` | diese Spiegelung wieder entfernen |
| `proxy admin --domain X` / `--root` | Adminoberfläche erreichbar machen – per Name oder über die Wurzel |
+| `proxy check` | antwortet hinter jedem Pfad etwas? (aus dem Proxy-Container geprüft) |
| `proxy reload` | nginx die Konfiguration neu einlesen lassen – ohne Unterbrechung |
| `proxy restart` | den Proxy-Manager-Container neu starten |
@@ -1677,6 +1678,75 @@ bei Variante A. Ohne Angabe zeigt `proxy admin` den Stand.
In der Oberfläche liegt beides unter **Proxy-Hosts → Adminoberfläche erreichbar
machen**.
+### 10.12 Weitere Dienste dazunehmen – Beispiel Grafana
+
+Mit einem einzigen freien Port lassen sich mehrere Dienste über Pfade
+auseinanderhalten:
+
+```
+ https://172.0.250.82/ Adminoberfläche des Proxy Managers
+ https://172.0.250.82/cmk/ Checkmk
+ https://172.0.250.82/grafana/ Grafana
+```
+
+Ob ein Dienst das mitmacht, hängt an ihm selbst – nicht am Proxy. Er muss
+wissen, dass er unter einem Präfix liegt, und ihn in jede Adresse einsetzen,
+die er erzeugt. Checkmk kann das von Haus aus (der Site-Pfad *ist* der
+Präfix), die Oberfläche des Proxy Managers kann es gar nicht
+([Kapitel 10.11](#1011-die-adminoberfläche-des-proxy-managers-erreichbar-machen)),
+**Grafana kann es, wenn man es ihm sagt**:
+
+```yaml
+# docker-compose.yml, Dienst grafana
+environment:
+ GF_SERVER_ROOT_URL: "https://172.0.250.82/grafana/"
+ GF_SERVER_SERVE_FROM_SUB_PATH: "true"
+```
+
+Gleichwertig in der `grafana.ini`:
+
+```ini
+[server]
+root_url = https://172.0.250.82/grafana/
+serve_from_sub_path = true
+```
+
+Damit setzt Grafana ein `` in seine Seiten, und alle
+Dateien werden relativ dazu geladen. Ohne die beiden Zeilen leitet es auf
+`/login` um – also auf die Wurzel, wo etwas ganz anderes liegt. Gemessen:
+
+| Grafana-Einstellung | `/grafana/login` | Dateien der Seite |
+|---|---|---|
+| ohne `serve_from_sub_path` | 302 auf `/login` | gehen an die Wurzel |
+| mit `root_url` + `serve_from_sub_path` | 200 | `/grafana/public/…` → 200 |
+
+Auf der Proxy-Seite ist es dann ein Pfad mehr:
+
+```bash
+./scripts/cmk-manage.sh proxy edit --id 1 --location /grafana=127.0.0.1:3000
+./scripts/cmk-manage.sh proxy default --domain --restart # falls eingerichtet
+./scripts/cmk-manage.sh proxy check
+```
+
+`proxy check` fragt jedes Ziel **aus dem Proxy-Container heraus** ab – von dort
+baut nginx die Verbindung auf, nicht von deinem Arbeitsplatz:
+
+```
+Proxy-Host 1 stack.local
+ PFAD ZIEL ANTWORT
+ / http://127.0.0.1:81 HTTP 200
+ /grafana http://127.0.0.1:3000 HTTP 302
+ /cmk http://127.0.0.1:9999 nicht erreichbar
+```
+
+Genau so findet man einen Tippfehler in der Zieladresse, bevor der Browser
+`502 Bad Gateway` zeigt. In der Oberfläche liegt das unter **Proxy-Hosts →
+Ziele prüfen**.
+
+> Port **3000 bleibt in der Firewall zu**. Grafana wird ausschließlich über den
+> Proxy erreicht, genau wie Checkmk auf 5000. `firewall close` bietet einen
+> versehentlich geöffneten Port wieder an.
+
---
## 11. Proxy Manager lokal einrichten (Terminal-Browser)
diff --git a/README_en.md b/README_en.md
index 55e567a..926b404 100644
--- a/README_en.md
+++ b/README_en.md
@@ -1227,6 +1227,7 @@ HTTPS, enables HTTP/2 and websockets, and sets the recommended
| `proxy default --domain X` | enable HTTPS on the bare IP (mirrors X into a default server) |
| `proxy default --off` | remove that mirror again |
| `proxy admin --domain X` / `--root` | make the admin interface reachable – by name or via the root |
+| `proxy check` | does something answer behind each path? (checked from inside the proxy container) |
| `proxy reload` | make nginx re-read its configuration – without interruption |
| `proxy restart` | restart the Proxy Manager container |
@@ -1668,6 +1669,73 @@ option A. Without arguments `proxy admin` shows the current state.
The GUI has both under **Proxy-Hosts → Adminoberfläche erreichbar machen**.
+### 10.12 Adding more services – Grafana as an example
+
+A single free port is enough to keep several services apart by path:
+
+```
+ https://172.0.250.82/ Proxy Manager admin interface
+ https://172.0.250.82/cmk/ Checkmk
+ https://172.0.250.82/grafana/ Grafana
+```
+
+Whether a service plays along is up to the service, not the proxy. It has to
+know that it lives under a prefix and put that prefix into every address it
+generates. Checkmk does this natively (the site path *is* the prefix), the
+Proxy Manager's interface cannot do it at all
+([chapter 10.11](#1011-making-the-proxy-managers-admin-interface-reachable)),
+and **Grafana can, once you tell it**:
+
+```yaml
+# docker-compose.yml, service grafana
+environment:
+ GF_SERVER_ROOT_URL: "https://172.0.250.82/grafana/"
+ GF_SERVER_SERVE_FROM_SUB_PATH: "true"
+```
+
+Equivalent in `grafana.ini`:
+
+```ini
+[server]
+root_url = https://172.0.250.82/grafana/
+serve_from_sub_path = true
+```
+
+With that Grafana puts a `` into its pages and loads
+every file relative to it. Without those two lines it redirects to `/login` –
+the root, where something else lives entirely. Measured:
+
+| Grafana setting | `/grafana/login` | page assets |
+|---|---|---|
+| without `serve_from_sub_path` | 302 to `/login` | go to the root |
+| with `root_url` + `serve_from_sub_path` | 200 | `/grafana/public/…` → 200 |
+
+On the proxy side it is one more path:
+
+```bash
+./scripts/cmk-manage.sh proxy edit --id 1 --location /grafana=127.0.0.1:3000
+./scripts/cmk-manage.sh proxy default --domain --restart # if set up
+./scripts/cmk-manage.sh proxy check
+```
+
+`proxy check` queries every target **from inside the proxy container** – that
+is where nginx opens the connection from, not your workstation:
+
+```
+Proxy-Host 1 stack.local
+ PFAD ZIEL ANTWORT
+ / http://127.0.0.1:81 HTTP 200
+ /grafana http://127.0.0.1:3000 HTTP 302
+ /cmk http://127.0.0.1:9999 nicht erreichbar
+```
+
+That is how you find a typo in a target address before the browser shows
+`502 Bad Gateway`. The GUI has it under **Proxy-Hosts → Ziele prüfen**.
+
+> Port **3000 stays closed in the firewall**. Grafana is reached exclusively
+> through the proxy, just like Checkmk on 5000. `firewall close` offers an
+> accidentally opened port for closing again.
+
---
## 11. Configuring the Proxy Manager locally (terminal browser)
diff --git a/scripts/cmk-manage.sh b/scripts/cmk-manage.sh
index 200c96e..c7de3dd 100755
--- a/scripts/cmk-manage.sh
+++ b/scripts/cmk-manage.sh
@@ -3940,6 +3940,23 @@ npm_reload_hint() { # restart-flag
fi
}
+# Ein Ziel aus Sicht des Proxy-Containers pruefen - von dort aus baut
+# nginx die Verbindung auf, nicht von diesem Rechner.
+proxy_check_one() { # pfad schema host port
+ local path="$1" sch="$2" host="$3" port="$4" code
+ code="$($RUNTIME exec "$NPM_CONTAINER_NAME" \
+ curl -s -o /dev/null -k --max-time 5 -w '%{http_code}' \
+ "$sch://$host:$port/" 2>/dev/null)" || code=000
+ case "${code:-000}" in
+ 000) printf ' %-9s %-34s %b\n' "$path" "$sch://$host:$port" \
+ "${C_RED}nicht erreichbar${C_RST}"
+ return 1 ;;
+ *) printf ' %-9s %-34s %b\n' "$path" "$sch://$host:$port" \
+ "${C_GRN}HTTP $code${C_RST}"
+ return 0 ;;
+ esac
+}
+
cmd_proxy() {
local sub="${1:-list}"; [ $# -gt 0 ] && shift || true
@@ -4025,6 +4042,69 @@ cmd_proxy() {
echo
return 0 ;;
+ check|test)
+ local pid="" dom=""
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ --id) pid="$2"; shift 2 ;;
+ --domain) dom="$2"; shift 2 ;;
+ *) die "Unbekannte Option fuer 'proxy check': $1" ;;
+ esac
+ done
+ npm_require_running || return 1
+ NPM_TOKEN="$(npm_api_token)" || return 1
+
+ local all i
+ all="$(npm_api GET /api/nginx/proxy-hosts)"
+ local -a ids=()
+ if [ -n "$pid" ]; then
+ ids=("$pid")
+ elif [ -n "$dom" ]; then
+ for i in $(json_ids "$all"); do
+ if ( set +o pipefail; npm_api GET "/api/nginx/proxy-hosts/$i" | grep -qF "\"$dom\"" ); then
+ ids=("$i"); break
+ fi
+ done
+ [ "${#ids[@]}" -gt 0 ] || die "Kein Proxy-Host fuer '$dom' gefunden."
+ else
+ mapfile -t ids < <(json_ids "$all")
+ fi
+ if [ "${#ids[@]}" -eq 0 ]; then
+ warn "Es gibt keine Proxy-Hosts."; return 0
+ fi
+
+ local fehler=0 o d sch host port el
+ for i in "${ids[@]}"; do
+ o="$(npm_api GET "/api/nginx/proxy-hosts/$i")"
+ [ -n "$(json_num "$o" id)" ] || { warn "Proxy-Host $i gibt es nicht."; continue; }
+ d="$(printf '%s' "$o" | sed -n 's/.*"domain_names"[[:space:]]*:[[:space:]]*\[[[:space:]]*"\([^"]*\)".*/\1/p')"
+ printf '\n%sProxy-Host %s %s%s\n' "$C_BLD" "$i" "${d:-?}" "$C_RST"
+ printf ' %-9s %-34s %s\n' "PFAD" "ZIEL" "ANTWORT"
+
+ sch="$(json_str "$o" forward_scheme)"; [ -n "$sch" ] || sch=http
+ host="$(json_str "$o" forward_host)"; port="$(json_num "$o" forward_port)"
+ proxy_check_one "/" "$sch" "$host" "$port" || fehler=$((fehler+1))
+
+ while IFS= read -r el; do
+ [ -n "$el" ] || continue
+ sch="$(json_str "$el" forward_scheme)"; [ -n "$sch" ] || sch=http
+ host="$(json_str "$el" forward_host)"; port="$(json_num "$el" forward_port)"
+ proxy_check_one "$(json_str "$el" path)" "$sch" "$host" "$port" || fehler=$((fehler+1))
+ done < <(json_split_array "$(json_raw "$o" locations)")
+ done
+
+ echo
+ if [ "$fehler" -eq 0 ]; then
+ ok "Alle Ziele antworten."
+ echo; return 0
+ fi
+ err "$fehler Ziel(e) antworten nicht - dort meldet nginx 502 Bad Gateway."
+ printf ' Haeufigste Ursachen: Tippfehler in der Adresse (127.0.0.1 statt
+ 172.0.0.1), der Dienst laeuft nicht, oder er lauscht nur auf einer
+ anderen Schnittstelle.
+ Aendern mit: %s proxy edit --id N --location /pfad=host:port\n\n' "$SELF"
+ return 1 ;;
+
admin|adminui)
local dom="" root="" off="" pid="" host="" path="" cert=self
while [ $# -gt 0 ]; do
@@ -4744,7 +4824,7 @@ EOF
*) die "Unbekanntes Unterkommando 'proxy $sub'.
Moeglich: status | list | show | certs | add | edit | del | default
- | admin | reload | restart" ;;
+ | admin | check | reload | restart" ;;
esac
}
@@ -5976,6 +6056,7 @@ Ziel : ${PROXY_FORWARD_HOST:-127.0.0.1}:${PROXY_FORWARD_PORT:-5000}" \
show "Einzelheiten eines Proxy-Hosts (mit Pfaden)" \
certs "Vorhandene Zertifikate anzeigen" \
ipzugang "HTTPS ueber die blanke IP (ohne Namen) ermoeglichen" \
+ pruefen "Ziele pruefen - antwortet hinter jedem Pfad etwas?" \
adminui "Adminoberflaeche erreichbar machen (Name oder Wurzel)" \
neustart "Proxy Manager neu starten / nginx neu laden" \
del "Proxy-Host loeschen" \
@@ -5995,6 +6076,9 @@ Ziel : ${PROXY_FORWARD_HOST:-127.0.0.1}:${PROXY_FORWARD_PORT:-5000}" \
certs) local f2; f2="$(mktemp)"; ( NO_COLOR=1 cmd_proxy certs ) >"$f2" 2>&1 || true
ui_text "Zertifikate" "$f2"; rm -f "$f2" ;;
ipzugang) gui_proxy_default || true ;;
+ pruefen) local fp; fp="$(mktemp)"
+ ( NO_COLOR=1 cmd_proxy check ) >"$fp" 2>&1 || true
+ ui_text "Ziele der Proxy-Hosts" "$fp"; rm -f "$fp" ;;
adminui) gui_proxy_admin || true ;;
neustart) gui_proxy_restart || true ;;
del) gui_proxy_del || true ;;