Proxy-Host bearbeiten, ohne den Rest zu verlieren

Aendern ging bisher nur ueber "proxy add --update", und das schreibt den
Eintrag vollstaendig neu: wer den Port aendern wollte, verlor still die
zusaetzlichen Pfade und die erweiterte Konfiguration, weil die Nutzlast
aus Vorgaben zusammengesetzt wird.

Die Schnittstelle des Proxy Managers arbeitet als Patch - ein PUT mit
nur einem Feld aendert auch nur dieses; an einem laufenden NPM 2.15.1
nachgemessen. Darauf setzt "proxy edit" auf: angegeben wird, was sich
aendern soll, alles Uebrige bleibt unberuehrt.

  proxy edit --id 3 --forward-port 5001
  proxy edit --domain checkmk.example.local --cert self
  proxy edit --id 3 --location /grafana=127.0.0.1:3000
  proxy edit --id 3 --del-location /grafana

Dazu Ziel, Schema, Name, Zertifikat, erweiterte Konfiguration, die vier
Schalter und ein/aus. Ohne Angabe wird nur der Stand gezeigt. In der
Oberflaeche liegt dasselbe unter "Proxy-Host bearbeiten", mit den
aktuellen Werten vorbelegt.

Pfade kennt die Schnittstelle nur als Ganzes, sie werden deshalb gelesen,
geaendert und zurueckgeschrieben. Das an "}" aufzuteilen geht nicht:
"advanced_config" darf nginx-Bloecke mit geschweiften Klammern und
maskierte Anfuehrungszeichen enthalten. Dafuer gibt es jetzt json_raw
und json_split_array, die die Verschachtelung mitzaehlen und
Zeichenketten erkennen. "proxy show" liest die Pfade ebenfalls darueber.

Zwei Fehler kamen dabei ans Licht:

  - NPM_CODE wurde in npm_api gesetzt, aber npm_api wird fast immer in
    einer Kommandoersetzung aufgerufen - also in einer Subshell. Der
    HTTP-Code kam nie beim Aufrufer an; unter "set -u" brach das Skript
    im Fehlerpfad sogar ab, statt den Fehler zu zeigen. Betraf auch
    "proxy add" und das Anlegen von Zertifikaten. Der Code wird jetzt
    zusaetzlich abgelegt und mit npm_code gelesen.
  - Der Proxy Manager schaltet ohne Zertifikat http2_support und
    ssl_forced von sich aus ab. Das blieb unbemerkt; jetzt wird nach dem
    Schreiben verglichen und eine Abweichung gemeldet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-20 20:27:55 +02:00
co-authored by Claude Opus 5
parent 8782c6d5c7
commit 59304ae02d
3 changed files with 609 additions and 15 deletions
+50
View File
@@ -1224,6 +1224,7 @@ erzwingen, HTTP/2 und Websockets einschalten und den empfohlenen
| `proxy add …` | Proxy-Host anlegen (`--update` ersetzt einen vorhandenen) |
| `proxy show --domain X` | Einzelheiten samt zusätzlicher Pfade |
| `proxy del --id N` | Proxy-Host löschen |
| `proxy edit --id N …` | vorhandenen Proxy-Host ändern – nur die angegebenen Felder |
| `proxy default --domain X` | HTTPS über die blanke IP ermöglichen (spiegelt X in einen Standard-Server) |
| `proxy default --off` | diese Spiegelung wieder entfernen |
@@ -1523,6 +1524,55 @@ Die saubere Alternative bleibt ein Name: ein DNS-Eintrag oder eine Zeile in der
`hosts`-Datei der Clients genügt, damit der Browser SNI sendet und der
Proxy-Host ganz normal greift.
### 10.9 Einen Proxy-Host ändern
`proxy add --update` schreibt den Eintrag **vollständig** neu – wer damit nur
den Port ändern will, verliert unbeabsichtigt die Pfade und die erweiterte
Konfiguration. Dafür gibt es `proxy edit`: angegeben wird nur, was sich ändern
soll, alles Übrige bleibt unangetastet.
```bash
./scripts/cmk-manage.sh proxy edit --id 3 --forward-port 5001
./scripts/cmk-manage.sh proxy edit --domain checkmk.example.local --cert self
./scripts/cmk-manage.sh proxy edit --id 3 --location /grafana=127.0.0.1:3000
./scripts/cmk-manage.sh proxy edit --id 3 --del-location /grafana
./scripts/cmk-manage.sh proxy edit --id 3 # nur ansehen
```
Der Eintrag wird über `--id` oder `--domain` gewählt.
| Option | Ändert |
|---|---|
| `--rename NAME` | den Domainnamen |
| `--forward-host HOST`, `--forward-port PORT` | das Weiterleitungsziel |
| `--scheme http\|https` | wie der Proxy das Ziel anspricht |
| `--cert self\|letsencrypt\|none\|ID` | das Zertifikat |
| `--advanced 'TEXT'` | die erweiterte nginx-Konfiguration (`\n` trennt Zeilen) |
| `--force-ssl`, `--http2`, `--websockets`, `--block-exploits` | jeweils `on`/`off` |
| `--enable`, `--disable` | den Eintrag ein- oder ausschalten |
| `--location /pfad=host:port` | einen Pfad hinzufügen oder ersetzen |
| `--del-location /pfad` | einen Pfad entfernen |
Zwei Dinge sind dabei zu wissen:
- **Pfade** kennt die Schnittstelle nur als Ganzes. `--location` und
`--del-location` lesen deshalb den Bestand, ändern den einen Eintrag und
schreiben die Liste zurück. Hatte ein ersetzter Pfad eine eigene erweiterte
Konfiguration, wird das vorher gesagt.
- Der Proxy Manager **biegt manches still zurecht**: ohne Zertifikat schaltet
er `http2_support` und `ssl_forced` von sich aus ab. `proxy edit` vergleicht
danach, was gespeichert wurde, und meldet eine Abweichung – sonst sucht man
den Fehler später an der falschen Stelle.
In der Oberfläche liegt dasselbe unter **Proxy-Hosts → Proxy-Host bearbeiten**;
dort sind die Felder mit den aktuellen Werten vorbelegt, und die Schalter
stehen als Auswahlliste bereit.
> Wurde der Standard-Server für den IP-Zugang eingerichtet
> ([Kapitel 10.8](#108-https-über-die-blanke-ip)), spiegelt er einen Stand von
> vorher. Nach inhaltlichen Änderungen neu schreiben – `proxy edit` erinnert
> daran.
---
## 11. Proxy Manager lokal einrichten (Terminal-Browser)
+49
View File
@@ -1223,6 +1223,7 @@ HTTPS, enables HTTP/2 and websockets, and sets the recommended
| `proxy add …` | create a proxy host (`--update` replaces an existing one) |
| `proxy show --domain X` | details including additional paths |
| `proxy del --id N` | delete a proxy host |
| `proxy edit --id N …` | change an existing proxy host – only the fields you name |
| `proxy default --domain X` | enable HTTPS on the bare IP (mirrors X into a default server) |
| `proxy default --off` | remove that mirror again |
@@ -1519,6 +1520,54 @@ The clean alternative is still a name: a DNS record or one line in the clients'
`hosts` file is enough for the browser to send SNI, and the proxy host applies
as usual.
### 10.9 Changing a proxy host
`proxy add --update` rewrites the entry **completely** – using it to change
just a port silently loses the paths and the advanced configuration. That is
what `proxy edit` is for: you name only what should change, everything else is
left alone.
```bash
./scripts/cmk-manage.sh proxy edit --id 3 --forward-port 5001
./scripts/cmk-manage.sh proxy edit --domain checkmk.example.local --cert self
./scripts/cmk-manage.sh proxy edit --id 3 --location /grafana=127.0.0.1:3000
./scripts/cmk-manage.sh proxy edit --id 3 --del-location /grafana
./scripts/cmk-manage.sh proxy edit --id 3 # just look
```
The entry is selected with `--id` or `--domain`.
| Option | Changes |
|---|---|
| `--rename NAME` | the domain name |
| `--forward-host HOST`, `--forward-port PORT` | the forwarding target |
| `--scheme http\|https` | how the proxy talks to the target |
| `--cert self\|letsencrypt\|none\|ID` | the certificate |
| `--advanced 'TEXT'` | the advanced nginx configuration (`\n` separates lines) |
| `--force-ssl`, `--http2`, `--websockets`, `--block-exploits` | each `on`/`off` |
| `--enable`, `--disable` | switch the entry on or off |
| `--location /path=host:port` | add or replace a path |
| `--del-location /path` | remove a path |
Two things to know:
- The API only knows **paths** as a whole. `--location` and `--del-location`
therefore read the current list, change the one entry and write the list
back. If a replaced path had an advanced configuration of its own, you are
told beforehand.
- The Proxy Manager **quietly corrects some things**: without a certificate it
turns `http2_support` and `ssl_forced` off by itself. `proxy edit` compares
what was stored afterwards and reports a difference – otherwise you would
look for the fault in the wrong place later.
The GUI has the same under **Proxy-Hosts → Proxy-Host bearbeiten**; there the
fields come prefilled with the current values and the switches are offered as a
checklist.
> If the default server for IP access is set up
> ([chapter 10.8](#108-https-on-the-bare-ip)), it mirrors an earlier state.
> Rewrite it after content changes – `proxy edit` reminds you.
---
## 11. Configuring the Proxy Manager locally (terminal browser)
+510 -15
View File
@@ -21,6 +21,7 @@ SELF="$SCRIPT_DIR/cmk-manage.sh"
BUNDLE_DIRNAME="cmkbundle"
ASSUME_YES=""
VERBOSE=""
NPM_TOKEN=""
ENVFILE_OPT=""
# =====================================================================
@@ -3470,7 +3471,10 @@ EOF
printf '%s' "$tok"
}
# npm_api METHODE PFAD [JSON] -> Antwort auf stdout, HTTP-Code in NPM_CODE
# npm_api METHODE PFAD [JSON] -> Antwort auf stdout, HTTP-Code via npm_code
NPM_CODE_FILE="${TMPDIR:-/tmp}/.cmk-manage-npmcode.$$"
trap 'rm -f "$NPM_CODE_FILE"' EXIT
npm_api() {
local method="$1" path="$2" data="${3:-}"
local base; base="$(npm_api_base)"
@@ -3479,9 +3483,20 @@ npm_api() {
[ -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'}"
# Wird npm_api in einer Kommandoersetzung aufgerufen - und das ist der
# Regelfall, weil die Antwort gebraucht wird -, laeuft sie in einer
# Subshell. Die Zuweisung oben ist dann beim Rueckkehren wieder weg.
# Deshalb zusaetzlich ablegen; gelesen wird mit npm_code.
printf '%s' "$NPM_CODE" > "$NPM_CODE_FILE" 2>/dev/null || true
printf '%s' "${out%$'\n'*}"
}
# HTTP-Code des letzten API-Aufrufs - auch nach einer Kommandoersetzung
npm_code() {
local c; c="$(cat "$NPM_CODE_FILE" 2>/dev/null || true)"
printf '%s' "${c:-${NPM_CODE:-000}}"
}
npm_require_running() {
npm_enabled || { err "Der NGINX Proxy Manager ist abgeschaltet (NPM_ENABLED=off)."
log "Einschalten: $SELF npm-enable on"; return 1; }
@@ -3516,7 +3531,7 @@ npm_cert_selfsigned() { # domain
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)."
[ -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
@@ -3538,7 +3553,7 @@ npm_cert_letsencrypt() { # domain
"{\"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)."
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 <<EOF
@@ -3584,6 +3599,99 @@ proxy_location_json() { # spec -> JSON-Objekt auf stdout
}
# --- Proxy-Hosts ---------------------------------------------------------
# ---------------------------------------------------------------------
# Zwei kleine JSON-Helfer, die mehr koennen als ein sed-Ausdruck
#
# "advanced_config" darf geschweifte Klammern enthalten (nginx-Bloecke),
# und Zeichenketten duerfen maskierte Anfuehrungszeichen tragen. Ein
# Aufteilen an "}" wuerde daran zerbrechen. Diese beiden zaehlen die
# Verschachtelung mit und achten auf Zeichenketten.
# ---------------------------------------------------------------------
# Text fuer eine JSON-Zeichenkette absichern. Anfuehrungszeichen werden
# maskiert, echte Zeilenumbrueche werden zu \n - ein vom Benutzer selbst
# getipptes \n bleibt, was es ist: ein Zeilenumbruch fuer nginx.
json_str_escape() {
printf '%s' "$1" | sed 's/"/\\"/g' | awk '{ if (NR>1) printf "\\n"; printf "%s", $0 }'
}
# Rohen Wert eines Schluessels der obersten Ebene ausschneiden
json_raw() { # json schluessel
printf '%s' "$1" | awk -v key="$2" '
{ s = s $0 }
function cut(s, p, n, c, k, ch, d2, q2, e2) {
c = substr(s, p, 1)
if (c == "{" || c == "[") {
d2 = 0; q2 = 0; e2 = 0
for (k = p; k <= n; k++) {
ch = substr(s, k, 1)
if (q2) { if (e2) e2 = 0; else if (ch == "\\") e2 = 1; else if (ch == "\"") q2 = 0; continue }
if (ch == "\"") { q2 = 1; continue }
if (ch == "{" || ch == "[") d2++
else if (ch == "}" || ch == "]") { d2--; if (d2 == 0) return substr(s, p, k - p + 1) }
}
return substr(s, p)
}
if (c == "\"") {
e2 = 0
for (k = p + 1; k <= n; k++) {
ch = substr(s, k, 1)
if (e2) { e2 = 0; continue }
if (ch == "\\") { e2 = 1; continue }
if (ch == "\"") return substr(s, p, k - p + 1)
}
return substr(s, p)
}
for (k = p; k <= n; k++) {
ch = substr(s, k, 1)
if (ch == "," || ch == "}" || ch == "]") return substr(s, p, k - p)
}
return substr(s, p)
}
END {
t = "\"" key "\""; tl = length(t); n = length(s)
d = 0; q = 0; e = 0
for (i = 1; i <= n; i++) {
c = substr(s, i, 1)
if (q) { if (e) e = 0; else if (c == "\\") e = 1; else if (c == "\"") q = 0; continue }
if (c == "\"") {
if (d == 1 && substr(s, i, tl) == t) {
j = i + tl
while (j <= n && index(" \t:", substr(s, j, 1)) > 0) j++
print cut(s, j, n); exit
}
q = 1; continue
}
if (c == "{" || c == "[") d++
else if (c == "}" || c == "]") d--
}
}'
}
# JSON-Wahrheitswert eines Schluessels als on/off (fuer Auswahllisten)
json_bool_on() { # json schluessel
case "$(json_raw "$1" "$2")" in true) printf 'on' ;; *) printf 'off' ;; esac
}
# Ein JSON-Feld in seine Elemente zerlegen, je Element eine Zeile
json_split_array() { # array-text
printf '%s' "$1" | awk '
{ s = s $0 }
END {
n = length(s); d = 0; q = 0; e = 0; start = 0
for (i = 1; i <= n; i++) {
c = substr(s, i, 1)
if (q) { if (e) e = 0; else if (c == "\\") e = 1; else if (c == "\"") q = 0; continue }
if (c == "\"") { q = 1; continue }
if (c == "[" || c == "{") { d++; if (d == 2 && start == 0) start = i; continue }
if (c == "]" || c == "}") {
d--
if (d == 1 && start > 0) { print substr(s, start, i - start + 1); start = 0 }
}
}
}'
}
# =====================================================================
# HTTPS ueber die blanke IP
#
@@ -3795,14 +3903,15 @@ cmd_proxy() {
printf ' Ziel : %s://%s:%s\n' "$(json_str "$o" forward_scheme)" "$(json_str "$o" forward_host)" "$(json_num "$o" forward_port)"
printf ' Zertifikat : %s\n' "$(json_num "$o" certificate_id)"
printf '\n %sZusaetzliche Pfade%s\n' "$C_BLD" "$C_RST"
local n
n="$(printf '%s' "$o" | grep -o '"path":"[^"]*"' | wc -l || true)"
if [ "${n:-0}" -eq 0 ]; then
printf ' (keine - alles geht an das Ziel oben)\n'
else
printf '%s' "$o" | tr '}' '\n' \
| sed -n 's/.*"path":"\([^"]*\)".*"forward_host":"\([^"]*\)","forward_port":\([0-9]*\).*/ \1 -> \2:\3/p'
fi
local el n=0
while IFS= read -r el; do
[ -n "$el" ] || continue
n=$((n+1))
printf ' %-20s -> %s://%s:%s\n' "$(json_str "$el" path)" \
"$(json_str "$el" forward_scheme)" "$(json_str "$el" forward_host)" \
"$(json_num "$el" forward_port)"
done < <(json_split_array "$(json_raw "$o" locations)")
if [ "$n" -eq 0 ]; then printf ' (keine - alles geht an das Ziel oben)\n'; fi
echo
return 0 ;;
@@ -3916,6 +4025,237 @@ EOF
npm_reload_hint "$restart"
return 0 ;;
edit|set)
local pid="" dom="" cert="" adv="" advset="" rename=""
local fhost="" fport="" scheme="" enabled=""
local fssl="" h2="" ws="" blk=""
local -a addloc=() delloc=()
while [ $# -gt 0 ]; do
case "$1" in
--id) pid="$2"; shift 2 ;;
--domain) dom="$2"; shift 2 ;;
--rename) rename="$2"; shift 2 ;;
--forward-host) fhost="$2"; shift 2 ;;
--forward-port) fport="$2"; shift 2 ;;
--scheme) scheme="$2"; shift 2 ;;
--advanced) adv="$2"; advset=1; shift 2 ;;
--cert) cert="$2"; shift 2 ;;
--force-ssl) fssl="$2"; shift 2 ;;
--http2) h2="$2"; shift 2 ;;
--websockets) ws="$2"; shift 2 ;;
--block-exploits) blk="$2"; shift 2 ;;
--enable) enabled=true; shift ;;
--disable) enabled=false; shift ;;
--location) addloc+=("$2"); shift 2 ;;
--del-location) delloc+=("$2"); shift 2 ;;
--help|-h)
cat <<EOF
Einen vorhandenen Proxy-Host aendern. Angegeben wird nur, was sich
aendern soll - alles andere bleibt, wie es ist.
$SELF proxy edit --id 3 --forward-port 5001
$SELF proxy edit --domain checkmk.example.local --cert self
$SELF proxy edit --id 3 --location /grafana=127.0.0.1:3000
$SELF proxy edit --id 3 --del-location /grafana
Auswahl
--id N | --domain NAME
Aenderungen
--rename NAME Domainname ersetzen
--forward-host HOST Weiterleitungsziel (Host)
--forward-port PORT Weiterleitungsziel (Port)
--scheme http|https Schema zum Ziel
--cert self|letsencrypt|none|ID
--advanced 'TEXT' erweiterte nginx-Konfiguration
--force-ssl on|off HTTP auf HTTPS umleiten
--http2 on|off
--websockets on|off
--block-exploits on|off
--enable | --disable Proxy-Host ein-/ausschalten
--location /pfad=host:port hinzufuegen oder ersetzen
--del-location /pfad entfernen
Ohne Aenderung wird nur der aktuelle Stand gezeigt.
EOF
return 0 ;;
*) die "Unbekannte Option fuer 'proxy edit': $1" ;;
esac
done
npm_require_running || return 1
NPM_TOKEN="$(npm_api_token)" || return 1
if [ -z "$pid" ]; then
[ -n "$dom" ] || die "--id oder --domain angeben. ('$SELF proxy list' zeigt die IDs)"
local all i
all="$(npm_api GET /api/nginx/proxy-hosts)"
for i in $(json_ids "$all"); do
if ( set +o pipefail; npm_api GET "/api/nginx/proxy-hosts/$i" | grep -qF "\"$dom\"" ); then
pid="$i"; break
fi
done
[ -n "$pid" ] || die "Kein Proxy-Host fuer '$dom' gefunden."
fi
case "$pid" in ''|*[!0-9]*) die "'$pid' ist keine gueltige ID." ;; esac
local o; o="$(npm_api GET "/api/nginx/proxy-hosts/$pid")"
[ -n "$(json_num "$o" id)" ] || die "Proxy-Host $pid gibt es nicht."
# Nichts angegeben -> nur zeigen
if [ -z "$rename$fhost$fport$scheme$advset$cert$fssl$h2$ws$blk$enabled" ] \
&& [ "${#addloc[@]}" -eq 0 ] && [ "${#delloc[@]}" -eq 0 ]; then
cmd_proxy show --id "$pid"
printf ' Aendern mit: %s proxy edit --id %s --forward-port 5001
Alle Moeglichkeiten: %s proxy edit --help\n\n' "$SELF" "$pid" "$SELF"
return 0
fi
local -a parts=() changed=()
if [ -n "$rename" ]; then
parts+=("\"domain_names\":[\"$(json_str_escape "$rename")\"]")
changed+=("Domain -> $rename")
fi
if [ -n "$fhost" ]; then
parts+=("\"forward_host\":\"$(json_str_escape "$fhost")\""); changed+=("Ziel-Host -> $fhost")
fi
if [ -n "$fport" ]; then
valid_port "$fport" || die "--forward-port: keine gueltige Portnummer."
parts+=("\"forward_port\":$fport"); changed+=("Ziel-Port -> $fport")
fi
if [ -n "$scheme" ]; then
case "$scheme" in http|https) : ;; *) die "--scheme: http oder https" ;; esac
parts+=("\"forward_scheme\":\"$scheme\""); changed+=("Schema -> $scheme")
fi
if [ -n "$advset" ]; then
parts+=("\"advanced_config\":\"$(json_str_escape "$adv")\"")
changed+=("erweiterte Konfiguration ersetzt")
fi
local b
for b in "fssl:--force-ssl:ssl_forced:HTTPS erzwingen" \
"h2:--http2:http2_support:HTTP/2" \
"ws:--websockets:allow_websocket_upgrade:WebSockets" \
"blk:--block-exploits:block_exploits:Exploit-Sperre"; do
local var opt rest key label val
var="${b%%:*}"; rest="${b#*:}"
opt="${rest%%:*}"; rest="${rest#*:}"
key="${rest%%:*}"; label="${rest#*:}"
eval "val=\"\${$var}\""
[ -n "$val" ] || continue
case "$(fw_lc "$val")" in
on|true|1|ja|yes) val=true ;;
off|false|0|nein|no) val=false ;;
*) die "$opt: on oder off" ;;
esac
parts+=("\"$key\":$val"); changed+=("$label -> $val")
done
if [ -n "$enabled" ]; then
parts+=("\"enabled\":$enabled")
changed+=("Proxy-Host $([ "$enabled" = true ] && echo eingeschaltet || echo ausgeschaltet)")
fi
if [ -n "$cert" ]; then
local certid="" cdom
cdom="${rename:-$(printf '%s' "$o" | sed -n 's/.*"domain_names"[[:space:]]*:[[:space:]]*\[[[:space:]]*"\([^"]*\)".*/\1/p')}"
case "$cert" in
self) certid="$(npm_cert_selfsigned "$cdom")" || return 1 ;;
letsencrypt) certid="$(npm_cert_letsencrypt "$cdom")" || return 1 ;;
none) certid=0 ;;
*[!0-9]*) die "--cert: self | letsencrypt | none | <ID>" ;;
*) certid="$cert" ;;
esac
parts+=("\"certificate_id\":$certid")
changed+=("Zertifikat -> $certid")
if [ "$certid" = 0 ]; then
parts+=("\"ssl_forced\":false" "\"http2_support\":false")
changed+=("HTTPS erzwingen -> false (ohne Zertifikat nicht moeglich)")
fi
fi
# Pfade: die API ersetzt das ganze Feld, also aus dem Bestand neu bauen
if [ "${#addloc[@]}" -gt 0 ] || [ "${#delloc[@]}" -gt 0 ]; then
local cur; cur="$(json_raw "$o" locations)"
local -a keep=()
local el elpath spec p drop
while IFS= read -r el; do
[ -n "$el" ] || continue
elpath="$(json_str "$el" path)"
drop=""
for p in ${delloc[@]+"${delloc[@]}"}; do
case "$p" in /*) : ;; *) p="/$p" ;; esac
if [ "$p" = "$elpath" ]; then drop=1; changed+=("Pfad $elpath entfernt"); fi
done
for spec in ${addloc[@]+"${addloc[@]}"}; do
p="${spec%%=*}"; case "$p" in /*) : ;; *) p="/$p" ;; esac
if [ "$p" = "$elpath" ]; then
drop=1
if [ -n "$(json_str "$el" advanced_config)" ]; then
warn "Der Pfad $elpath hatte eine eigene erweiterte Konfiguration.
Beim Ersetzen geht sie verloren."
fi
fi
done
[ -n "$drop" ] || keep+=("$el")
done < <(json_split_array "$cur")
for spec in ${addloc[@]+"${addloc[@]}"}; do
local lj; lj="$(proxy_location_json "$spec")" || return 1
keep+=("$lj")
changed+=("Pfad ${spec%%=*} -> ${spec#*=}")
done
local joined=""
for el in ${keep[@]+"${keep[@]}"}; do
if [ -n "$joined" ]; then joined="$joined,$el"; else joined="$el"; fi
done
parts+=("\"locations\":[$joined]")
fi
local payload="{" first=1 x
for x in "${parts[@]}"; do
if [ -n "$first" ]; then payload="$payload$x"; first=""; else payload="$payload,$x"; fi
done
payload="$payload}"
log "Aendere Proxy-Host $pid ..."
local body; body="$(npm_api PUT "/api/nginx/proxy-hosts/$pid" "$payload")"
case "$(npm_code)" in
200|201) : ;;
*) err "Aenderung fehlgeschlagen (HTTP $(npm_code))."
local m; m="$(json_str "$body" message)"
if [ -n "$m" ]; then printf ' Antwort: %s\n' "$m" >&2; fi
return 1 ;;
esac
ok "Proxy-Host $pid geaendert."
for x in "${changed[@]}"; do printf ' %s\n' "$x"; done
# Der Proxy Manager biegt manches still zurecht - ohne Zertifikat
# etwa HTTP/2 und die HTTPS-Umleitung. Das darf nicht unbemerkt
# bleiben, sonst sucht man den Fehler spaeter an der falschen Stelle.
local k want got
for x in "${parts[@]}"; do
k="${x%%:*}"; k="${k#\"}"; k="${k%\"}"
case "$k" in locations|domain_names|advanced_config) continue ;; esac
want="${x#*:}"
got="$(json_raw "$body" "$k")"
[ -n "$got" ] || continue
[ "$got" != "$want" ] || continue
warn "'$k': gesendet wurde $want, gespeichert ist $got."
if [ "$(json_raw "$body" certificate_id)" = 0 ]; then
printf ' Ohne Zertifikat laesst der Proxy Manager das nicht zu.
Erst eines zuweisen: %s proxy edit --id %s --cert self\n' "$SELF" "$pid"
fi
done
echo
cmd_proxy show --id "$pid"
if grep -qsF "$NPM_DEF_START" "$(npm_custom_conf_path)"; then
log "Der Standard-Server fuer den IP-Zugang spiegelt einen Stand von vorher.
Nach inhaltlichen Aenderungen neu schreiben:
$SELF proxy default --domain <name> --restart"
fi
return 0 ;;
del|delete|rm)
local pid=""
while [ $# -gt 0 ]; do
@@ -3926,8 +4266,8 @@ EOF
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
case "$(npm_code)" in 200|201|204) ok "Proxy-Host $pid geloescht." ;;
*) die "Loeschen fehlgeschlagen (HTTP $(npm_code))." ;; esac
return 0 ;;
add)
@@ -3996,7 +4336,7 @@ EOF
fi
local newid; newid="$(json_num "$body" id)"
if [ -z "$newid" ]; then
err "Der Proxy-Host konnte nicht angelegt werden (HTTP $NPM_CODE)."
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
@@ -4019,7 +4359,7 @@ EOF
return 0 ;;
*) die "Unbekanntes Unterkommando 'proxy $sub'.
Moeglich: status | list | show | certs | add | del" ;;
Moeglich: status | list | show | certs | add | edit | del | default" ;;
esac
}
@@ -5245,6 +5585,7 @@ Konto: ${NPM_ADMIN_EMAIL:-<nicht gesetzt>}
Ziel : ${PROXY_FORWARD_HOST:-127.0.0.1}:${PROXY_FORWARD_PORT:-5000}" \
anlegen "Proxy-Host anlegen (Assistent)" \
list "Vorhandene Proxy-Hosts anzeigen" \
edit "Proxy-Host bearbeiten (Ziel, Pfade, Zertifikat ...)" \
show "Einzelheiten eines Proxy-Hosts (mit Pfaden)" \
certs "Vorhandene Zertifikate anzeigen" \
ipzugang "HTTPS ueber die blanke IP (ohne Namen) ermoeglichen" \
@@ -5254,6 +5595,7 @@ Ziel : ${PROXY_FORWARD_HOST:-127.0.0.1}:${PROXY_FORWARD_PORT:-5000}" \
back "zurueck")" || return 0
case "$sel" in
anlegen) gui_proxy_add || true ;;
edit) gui_proxy_edit || true ;;
list) local f; f="$(mktemp)"; NO_COLOR=1 cmd_proxy list >"$f" 2>&1 || true
ui_text "Proxy-Hosts" "$f"; rm -f "$f" ;;
show) local d3; d3="$(ui_input "Einzelheiten" "Name oder ID des Proxy-Hosts:" "${PROXY_DOMAIN:-}")" || continue
@@ -5272,6 +5614,159 @@ Ziel : ${PROXY_FORWARD_HOST:-127.0.0.1}:${PROXY_FORWARD_PORT:-5000}" \
done
}
# Proxy-Host auswaehlen -> ID auf stdout
gui_pick_proxy() { # titel
if ! npm_require_running >/dev/null 2>&1; then
ui_msg "Proxy Manager" "Der Proxy Manager laeuft nicht oder ist abgeschaltet." >&2
return 1
fi
NPM_TOKEN="$(npm_api_token 2>/dev/null)" || {
ui_msg "Proxy Manager" "Anmeldung fehlgeschlagen.
Zugangsdaten pruefen unter Einstellungen > NGINX Proxy Manager,
oder die Datenbank zuruecksetzen." >&2
return 1
}
local all i o dom
local -a items=()
all="$(npm_api GET /api/nginx/proxy-hosts)"
for i in $(json_ids "$all"); do
o="$(npm_api GET "/api/nginx/proxy-hosts/$i")"
dom="$(printf '%s' "$o" | sed -n 's/.*"domain_names"[[:space:]]*:[[:space:]]*\[[[:space:]]*"\([^"]*\)".*/\1/p')"
items+=("$i" "${dom:-?} -> $(json_str "$o" forward_host):$(json_num "$o" forward_port)")
done
if [ "${#items[@]}" -eq 0 ]; then
ui_msg "Proxy-Hosts" "Es gibt noch keinen Proxy-Host.
Anlegen unter: Proxy-Host anlegen (Assistent)" >&2
return 1
fi
ui_menu "${1:-Proxy-Host waehlen}" "Welchen Eintrag?" "${items[@]}"
}
# Einen vorhandenen Proxy-Host bearbeiten
gui_proxy_edit() {
local pid
pid="$(gui_pick_proxy "Proxy-Host bearbeiten")" || return 0
[ -n "$pid" ] || return 0
# gui_pick_proxy lief in einer Kommandoersetzung, sein Token ist mit der
# Subshell verschwunden - hier neu holen.
NPM_TOKEN="$(npm_api_token)" || { ui_msg "Proxy Manager" "Anmeldung fehlgeschlagen."; return 0; }
while :; do
local o dom
o="$(npm_api GET "/api/nginx/proxy-hosts/$pid")"
if [ -z "$(json_num "$o" id)" ]; then
ui_msg "Proxy-Host" "Der Eintrag $pid ist nicht mehr da."; return 0
fi
dom="$(printf '%s' "$o" | sed -n 's/.*"domain_names"[[:space:]]*:[[:space:]]*\[[[:space:]]*"\([^"]*\)".*/\1/p')"
local sel
sel="$(ui_menu "Proxy-Host $pid bearbeiten" \
"Name : $dom
Ziel : $(json_str "$o" forward_scheme)://$(json_str "$o" forward_host):$(json_num "$o" forward_port)
Zertifikat : $(json_num "$o" certificate_id) Aktiv: $(json_raw "$o" enabled)
Jede Aenderung wirkt sofort; alles Uebrige bleibt unangetastet." \
ziel "Weiterleitungsziel (Host und Port)" \
schema "Schema zum Ziel (http/https)" \
name "Domainname aendern" \
cert "Zertifikat zuweisen oder entfernen" \
pfad "Pfad hinzufuegen oder ersetzen" \
pfadweg "Pfad entfernen" \
schalter "Schalter (HTTPS erzwingen, HTTP/2, WebSockets, Sperre)" \
anaus "Proxy-Host ein- oder ausschalten" \
adv "Erweiterte nginx-Konfiguration" \
zeigen "Aktuellen Stand anzeigen" \
back "zurueck")" || return 0
local -a args=()
case "$sel" in
back) return 0 ;;
zeigen) local f; f="$(mktemp)"
NO_COLOR=1 cmd_proxy show --id "$pid" >"$f" 2>&1 || true
ui_text "Proxy-Host $pid" "$f"; rm -f "$f"; continue ;;
ziel) local h pt
h="$(ui_input "Weiterleitungsziel" "Host:" "$(json_str "$o" forward_host)")" || continue
[ -n "$h" ] || continue
pt="$(ui_input "Weiterleitungsziel" "Port:" "$(json_num "$o" forward_port)")" || continue
[ -n "$pt" ] || continue
args=(--forward-host "$h" --forward-port "$pt") ;;
schema) local sc
sc="$(ui_menu "Schema zum Ziel" "Wie spricht der Proxy das Ziel an?" \
http "unverschluesselt (der Regelfall im eigenen Netz)" \
https "verschluesselt")" || continue
args=(--scheme "$sc") ;;
name) local nn
nn="$(ui_input "Domainname" "Neuer Name:" "$dom")" || continue
[ -n "$nn" ] || continue
args=(--rename "$nn") ;;
cert) local cc
cc="$(ui_menu "Zertifikat" "Was soll ausgeliefert werden?" \
self "selbstsigniert (fuer das eigene Netz)" \
letsencrypt "Lets Encrypt (Server muss oeffentlich erreichbar sein)" \
none "keines - nur HTTP")" || continue
args=(--cert "$cc") ;;
pfad) local sp
sp="$(ui_input "Pfad" "Pfad und Ziel, z.B. /grafana=127.0.0.1:3000" "")" || continue
[ -n "$sp" ] || continue
args=(--location "$sp") ;;
pfadweg)
local el
local -a pitems=()
while IFS= read -r el; do
[ -n "$el" ] || continue
pitems+=("$(json_str "$el" path)" "-> $(json_str "$el" forward_host):$(json_num "$el" forward_port)")
done < <(json_split_array "$(json_raw "$o" locations)")
if [ "${#pitems[@]}" -eq 0 ]; then
ui_msg "Pfade" "Dieser Proxy-Host hat keine zusaetzlichen Pfade."; continue
fi
local dp
dp="$(ui_menu "Pfad entfernen" "Welchen Pfad?" "${pitems[@]}")" || continue
ui_yesno "Pfad entfernen" "Pfad $dp wirklich entfernen?" || continue
args=(--del-location "$dp") ;;
schalter)
local cur
cur="$(ui_checklist "Schalter" "Mit der Leertaste umschalten:" \
force-ssl "HTTP auf HTTPS umleiten" "$(json_bool_on "$o" ssl_forced)" \
http2 "HTTP/2" "$(json_bool_on "$o" http2_support)" \
websockets "WebSockets durchreichen" "$(json_bool_on "$o" allow_websocket_upgrade)" \
block-exploits "Bekannte Angriffsmuster sperren" "$(json_bool_on "$o" block_exploits)")" || continue
local o1 o2 o3 o4
case " $cur " in *" force-ssl "*|*'"force-ssl"'*) o1=on ;; *) o1=off ;; esac
case " $cur " in *" http2 "*|*'"http2"'*) o2=on ;; *) o2=off ;; esac
case " $cur " in *" websockets "*|*'"websockets"'*) o3=on ;; *) o3=off ;; esac
case " $cur " in *" block-exploits "*|*'"block-exploits"'*) o4=on ;; *) o4=off ;; esac
args=(--force-ssl "$o1" --http2 "$o2" --websockets "$o3" --block-exploits "$o4") ;;
anaus) if [ "$(json_bool_on "$o" enabled)" = on ]; then
ui_yesno "Ausschalten" "Proxy-Host $pid ausschalten?" || continue
args=(--disable)
else
ui_yesno "Einschalten" "Proxy-Host $pid einschalten?" || continue
args=(--enable)
fi ;;
adv) local a1 curadv
curadv="$(json_raw "$o" advanced_config)"
curadv="${curadv#\"}"; curadv="${curadv%\"}"
a1="$(ui_input "Erweiterte Konfiguration" \
"nginx-Anweisungen fuer diesen Proxy-Host.
Mehrere Anweisungen durch \\\\n trennen." "$curadv")" || continue
args=(--advanced "$a1") ;;
esac
[ "${#args[@]}" -gt 0 ] || continue
local f2 rc=0
f2="$(mktemp)"
NO_COLOR=1 cmd_proxy edit --id "$pid" "${args[@]}" >"$f2" 2>&1 || rc=$?
if [ "$rc" -eq 0 ]; then
ui_text "Proxy-Host $pid geaendert" "$f2"
else
ui_text "Aenderung fehlgeschlagen" "$f2"
fi
rm -f "$f2"
done
}
gui_proxy_default() {
local f rc=0
f="$(mktemp)"