From 59304ae02d12ec258598f751c516684dc2abd18b Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sun, 20 Sep 2026 20:27:55 +0200 Subject: [PATCH] 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) --- README.md | 50 ++++ README_en.md | 49 ++++ scripts/cmk-manage.sh | 525 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 609 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index e1581e1..3d5b443 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/README_en.md b/README_en.md index 2d4276b..3a59434 100644 --- a/README_en.md +++ b/README_en.md @@ -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) diff --git a/scripts/cmk-manage.sh b/scripts/cmk-manage.sh index 14cdcb6..0b9a377 100755 --- a/scripts/cmk-manage.sh +++ b/scripts/cmk-manage.sh @@ -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 < 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 < 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 | " ;; + *) 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 --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:-} 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)"