diff --git a/README.md b/README.md index 957446c..f0d9182 100644 --- a/README.md +++ b/README.md @@ -1104,6 +1104,7 @@ erzwingen, HTTP/2 und Websockets einschalten und den empfohlenen | `proxy list` | vorhandene Proxy-Hosts | | `proxy certs` | vorhandene Zertifikate mit Ablaufdatum | | `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 | Optionen von `proxy add`: @@ -1117,6 +1118,7 @@ Optionen von `proxy add`: | `--cert self\|letsencrypt\|none` | `self` | Zertifikat | | `--update` | – | vorhandenen Eintrag ersetzen | | `--advanced TEXT` | siehe unten | eigener nginx-Abschnitt | +| `--location /P=HOST:PORT` | – | weiterer Dienst unter einem Pfad, mehrfach angebbar – siehe [10.3](#103-mehrere-dienste-hinter-einem-port-pfade-statt-namen) | | `--no-force-ssl`, `--no-http2`, `--no-websockets`, `--no-block-exploits` | – | Abschalter | #### Der `advanced_config`-Abschnitt @@ -1160,7 +1162,101 @@ Manager → „>> Proxy-Hosts anlegen/verwalten"**. > **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 +### 10.3 Mehrere Dienste hinter einem Port (Pfade statt Namen) + +Der typische Fall: von außen ist **nur ein Port** erreichbar, auf dem Server +laufen aber mehrere Oberflächen – Checkmk, Grafana, vielleicht noch etwas. Statt +für jeden Dienst einen eigenen Namen zu brauchen, lassen sie sich über den +**Pfad** unterscheiden: + +``` +https://10.0.0.5/cmk/ -> Checkmk (127.0.0.1:5000) +https://10.0.0.5/grafana -> Grafana (127.0.0.1:3000) +https://10.0.0.5/sonstwas -> ein weiterer Dienst +``` + +Genau dafür ist der Proxy Manager da. In einem Aufruf: + +```bash +sudo ./scripts/cmk-manage.sh proxy add \ + --domain 10.0.0.5 \ + --forward-port 5000 \ + --cert self \ + --location /grafana=127.0.0.1:3000 \ + --location /sonstwas=127.0.0.1:8080 +``` + +* `--domain` darf eine **IP-Adresse** sein – ein DNS-Name ist nicht nötig. + (Die Schnittstelle akzeptiert alles außer einer Handvoll Sonderzeichen.) +* `--forward-port` ist das Ziel für alles, was **nicht** von einem Pfad + abgedeckt ist – hier Checkmk. +* `--location` ist beliebig oft angebbar. Schreibweise + `/pfad=host:port`, wahlweise mit Schema (`https://host:port`) und mit einem + Pfad, der beim Ziel vorangestellt wird (`/alt=host:3000/basis`). + +Anzeigen lässt sich das Ergebnis mit: + +```bash +./scripts/cmk-manage.sh proxy show --domain 10.0.0.5 +``` + +In der Oberfläche fragt der Assistent nach dem Zertifikat, ob weitere Dienste +unter demselben Namen liegen sollen, und nimmt dann Pfad und Ziel im Wechsel +auf, bis man ein Feld leer lässt. + +> **Die Pfadliste ist vollständig zu verstehen.** `proxy add --update` +> ersetzt den Eintrag mitsamt allen Pfaden. Wer später einen Pfad ergänzt, gibt +> die bereits vorhandenen also wieder mit an – `proxy show` zeigt, welche das +> sind. + +#### Was die Dienste selbst wissen müssen + +Ein Dienst hinter einem Unterpfad erzeugt sonst Verweise auf `/`, die ins Leere +gehen. **Checkmk** ist davon nicht betroffen – es liegt ohnehin unter +`//`. Andere brauchen eine Einstellung: + +**Grafana** (`/etc/grafana/grafana.ini` bzw. `GF_*`-Umgebungsvariablen): + +```ini +[server] +domain = 10.0.0.5 +root_url = %(protocol)s://%(domain)s/grafana/ +serve_from_sub_path = true +``` + +Als Container: + +```yaml +environment: + GF_SERVER_ROOT_URL: https://10.0.0.5/grafana/ + GF_SERVER_SERVE_FROM_SUB_PATH: "true" +``` + +Bleibt das aus, lädt Grafana zwar, sucht sein JavaScript aber unter +`https://10.0.0.5/public/...` statt `/grafana/public/...` – die Seite bleibt +weiß. Dasselbe gilt sinngemäß für jeden anderen Dienst: er muss seinen +Basispfad kennen. + +#### Warum überhaupt der Proxy Manager + +Alle Container laufen im Host-Netzwerk, die Dienste hören also direkt auf ihren +Ports (Checkmk 5000, Grafana 3000, …). Nach außen geben Sie davon **keinen** +frei – nur 443. Der Proxy Manager verteilt dahinter nach Pfad und erledigt +zugleich die HTTPS-Terminierung mit einem Zertifikat für alle Dienste. + +``` + Internet / LAN + | + :443 (einziger offener Port) + | + NGINX Proxy Manager + | | | + /cmk/ /grafana /sonstwas + | | | + :5000 :3000 :8080 (nur auf localhost) +``` + +### 10.4 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)); @@ -1168,7 +1264,7 @@ Manager → „>> Proxy-Hosts anlegen/verwalten"**. [Kapitel 11](#11-proxy-manager-lokal-einrichten-terminal-browser) 2. Anmelden mit `NPM_ADMIN_EMAIL` / `NPM_ADMIN_PASSWORD` -### 10.4 Proxy-Host im Browser anlegen +### 10.5 Proxy-Host im Browser anlegen **Hosts → Proxy Hosts → Add Proxy Host** @@ -1191,7 +1287,7 @@ proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; ``` -### 10.5 Variante A – Let's Encrypt (öffentlich erreichbarer Server) +### 10.6 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). @@ -1211,7 +1307,7 @@ gesichert. Bei interner DNS-Zone ohne öffentlichen Zugang: **DNS-Challenge** verwenden (SSL → *Use a DNS Challenge* → Provider auswählen, API-Token hinterlegen). -### 10.6 Variante B – Selbstsigniertes Zertifikat (interne Umgebung) +### 10.7 Variante B – Selbstsigniertes Zertifikat (interne Umgebung) Erzeugen: @@ -1361,7 +1457,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.6](#106-variante-b--selbstsigniertes-zertifikat-interne-umgebung) +Managers. Zertifikat wie in [Kapitel 10.7](#107-variante-b--selbstsigniertes-zertifikat-interne-umgebung) erzeugen, dann: ```bash diff --git a/README_en.md b/README_en.md index 9ecca35..73b189e 100644 --- a/README_en.md +++ b/README_en.md @@ -1106,6 +1106,7 @@ HTTPS, enables HTTP/2 and websockets, and sets the recommended | `proxy list` | existing proxy hosts | | `proxy certs` | existing certificates with expiry date | | `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 | Options for `proxy add`: @@ -1119,6 +1120,7 @@ Options for `proxy add`: | `--cert self\|letsencrypt\|none` | `self` | certificate | | `--update` | – | replace an existing entry | | `--advanced TEXT` | see below | custom nginx block | +| `--location /P=HOST:PORT` | – | another service under a path, repeatable – see [10.3](#103-several-services-behind-one-port-paths-instead-of-names) | | `--no-force-ssl`, `--no-http2`, `--no-websockets`, `--no-block-exploits` | – | switches | #### The `advanced_config` block @@ -1162,7 +1164,99 @@ Manager → ">> Proxy-Hosts anlegen/verwalten"**. > 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 +### 10.3 Several services behind one port (paths instead of names) + +The typical situation: only **one port** is reachable from outside, but the +server runs several interfaces – Checkmk, Grafana, maybe something else. +Instead of needing a separate name per service, they can be told apart by +**path**: + +``` +https://10.0.0.5/cmk/ -> Checkmk (127.0.0.1:5000) +https://10.0.0.5/grafana -> Grafana (127.0.0.1:3000) +https://10.0.0.5/other -> another service +``` + +This is exactly what the Proxy Manager is for. In one call: + +```bash +sudo ./scripts/cmk-manage.sh proxy add \ + --domain 10.0.0.5 \ + --forward-port 5000 \ + --cert self \ + --location /grafana=127.0.0.1:3000 \ + --location /other=127.0.0.1:8080 +``` + +* `--domain` may be an **IP address** – a DNS name is not required. (The + interface accepts anything except a handful of special characters.) +* `--forward-port` is the target for everything **not** covered by a path – + Checkmk here. +* `--location` can be given any number of times. Syntax `/path=host:port`, + optionally with a scheme (`https://host:port`) and with a path prefixed at + the target (`/alt=host:3000/base`). + +Inspect the result with: + +```bash +./scripts/cmk-manage.sh proxy show --domain 10.0.0.5 +``` + +In the interface the wizard asks about the certificate, whether further +services should live under the same name, and then takes path and target in +turn until you leave a field empty. + +> **The path list is absolute.** `proxy add --update` replaces the entry +> including all paths. So when adding a path later, pass the existing ones +> again – `proxy show` tells you which those are. + +#### What the services themselves need to know + +A service behind a sub-path otherwise generates links to `/` that lead nowhere. +**Checkmk** is unaffected – it lives under `//` anyway. Others need a +setting: + +**Grafana** (`/etc/grafana/grafana.ini` or `GF_*` environment variables): + +```ini +[server] +domain = 10.0.0.5 +root_url = %(protocol)s://%(domain)s/grafana/ +serve_from_sub_path = true +``` + +As a container: + +```yaml +environment: + GF_SERVER_ROOT_URL: https://10.0.0.5/grafana/ + GF_SERVER_SERVE_FROM_SUB_PATH: "true" +``` + +Without it Grafana loads but looks for its JavaScript under +`https://10.0.0.5/public/...` instead of `/grafana/public/...` – the page stays +blank. The same applies to any other service: it has to know its base path. + +#### Why the Proxy Manager at all + +All containers run on the host network, so the services listen directly on +their ports (Checkmk 5000, Grafana 3000, …). You expose **none** of them – +only 443. Behind it the Proxy Manager distributes by path and handles HTTPS +termination with one certificate for all services. + +``` + Internet / LAN + | + :443 (the only open port) + | + NGINX Proxy Manager + | | | + /cmk/ /grafana /other + | | | + :5000 :3000 :8080 (localhost only) +``` + +### 10.4 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 @@ -1170,7 +1264,7 @@ Manager → ">> Proxy-Hosts anlegen/verwalten"**. [chapter 11](#11-configuring-the-proxy-manager-locally-terminal-browser) 2. log in with `NPM_ADMIN_EMAIL` / `NPM_ADMIN_PASSWORD` -### 10.4 Creating a proxy host in the browser +### 10.5 Creating a proxy host in the browser **Hosts → Proxy Hosts → Add Proxy Host** @@ -1193,7 +1287,7 @@ proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; ``` -### 10.5 Option A – Let's Encrypt (publicly reachable server) +### 10.6 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). @@ -1213,7 +1307,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.6 Option B – self-signed certificate (internal environment) +### 10.7 Option B – self-signed certificate (internal environment) Creating one: @@ -1366,7 +1460,7 @@ work reliably this way. For exactly this case, use the Proxy Manager's REST interface. Create the certificate as described in -[chapter 10.6](#106-option-b--self-signed-certificate-internal-environment), then: +[chapter 10.7](#107-option-b--self-signed-certificate-internal-environment), then: ```bash NPM=http://127.0.0.1:81 diff --git a/scripts/cmk-manage.sh b/scripts/cmk-manage.sh index ac939c9..6fd2a99 100755 --- a/scripts/cmk-manage.sh +++ b/scripts/cmk-manage.sh @@ -180,7 +180,12 @@ KOMMANDOS - Proxy-Hosts (ueber die API des Proxy Managers) --cert self|letsencrypt|none (Vorgabe self) --update vorhandenen Host ersetzen --advanced TEXT eigener nginx-Abschnitt + --location /P=HOST:PORT weiterer Pfad auf demselben Namen, + mehrfach angebbar. Damit lassen sich mehrere + Dienste hinter EINEM Port unterscheiden: + --location /grafana=127.0.0.1:3000 --no-force-ssl --no-http2 --no-websockets --no-block-exploits + proxy show --domain X | --id N Einzelheiten samt Pfaden anzeigen proxy del --id N Proxy-Host loeschen KOMMANDOS - Zertifikat @@ -3182,6 +3187,36 @@ EOF printf '%s' "$id" } +# Einen --location-Eintrag in ein JSON-Objekt umsetzen. +# /grafana=127.0.0.1:3000 +# /grafana=http://127.0.0.1:3000 +# /alt=127.0.0.1:3000/basis (Pfad, der beim Ziel vorangestellt wird) +proxy_location_json() { # spec -> JSON-Objekt auf stdout + local spec="$1" path target scheme=http host port fwd="" + case "$spec" in + *=*) path="${spec%%=*}"; target="${spec#*=}" ;; + *) err "Ungueltige Angabe: '$spec'" + printf ' Erwartet: /pfad=host:port z.B. /grafana=127.0.0.1:3000\n' >&2 + return 1 ;; + esac + case "$path" in /*) : ;; *) path="/$path" ;; esac + case "$target" in + http://*) scheme=http; target="${target#http://}" ;; + https://*) scheme=https; target="${target#https://}" ;; + esac + host="${target%%:*}" + local rest="${target#*:}" + if [ "$rest" = "$target" ]; then + err "Im Ziel '$target' fehlt der Port (erwartet host:port)."; return 1 + fi + port="${rest%%/*}" + case "$rest" in */*) fwd="/${rest#*/}" ;; esac + valid_port "$port" || { err "'$port' ist keine gueltige Portnummer."; return 1; } + [ -n "$host" ] || { err "Im Ziel '$target' fehlt der Host."; return 1; } + printf '{"path":"%s","forward_scheme":"%s","forward_host":"%s","forward_port":%s,"forward_path":"%s","advanced_config":""}' \ + "$path" "$scheme" "$host" "$port" "$fwd" +} + # --- Proxy-Hosts --------------------------------------------------------- cmd_proxy() { local sub="${1:-list}"; [ $# -gt 0 ] && shift || true @@ -3234,6 +3269,39 @@ cmd_proxy() { [ "$n" -eq 0 ] && echo "(noch kein Zertifikat vorhanden)" return 0 ;; + show) + local pid="" dom="" + while [ $# -gt 0 ]; do + case "$1" in --id) pid="$2"; shift 2 ;; --domain) dom="$2"; shift 2 ;; *) pid="$1"; shift ;; 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." + local all; all="$(npm_api GET /api/nginx/proxy-hosts)" + local i + for i in $(json_ids "$all"); do + if 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 + local o; o="$(npm_api GET "/api/nginx/proxy-hosts/$pid")" + printf '\n%sProxy-Host %s%s\n' "$C_BLD" "$pid" "$C_RST" + printf ' Domains : %s\n' "$(printf '%s' "$o" | sed -n 's/.*"domain_names"[[:space:]]*:[[:space:]]*\[\([^]]*\)\].*/\1/p' | tr -d '"' || true)" + 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 + echo + return 0 ;; + del|delete|rm) local pid="" while [ $# -gt 0 ]; do @@ -3252,7 +3320,7 @@ cmd_proxy() { 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="" + local adv_set="" locs="" while [ $# -gt 0 ]; do case "$1" in --domain) domain="$2"; shift 2 ;; @@ -3261,6 +3329,10 @@ cmd_proxy() { --scheme) scheme="$2"; shift 2 ;; --cert) cert="$2"; shift 2 ;; --advanced) adv="$2"; adv_set=1; shift 2 ;; + --location) local lj + lj="$(proxy_location_json "$2")" || return 1 + if [ -n "$locs" ]; then locs="$locs,$lj"; else locs="$lj"; fi + shift 2 ;; --update) update=1; shift ;; --no-force-ssl) force_ssl=false; shift ;; --no-http2) http2=false; shift ;; @@ -3298,7 +3370,7 @@ cmd_proxy() { 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\":{}}" + 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\",\"locations\":[$locs],\"enabled\":true,\"meta\":{}}" local body if [ -n "$exid" ]; then @@ -3316,6 +3388,10 @@ cmd_proxy() { return 1 fi ok "Proxy-Host $newid angelegt: $domain -> $scheme://$fhost:$fport" + if [ -n "$locs" ]; then + printf ' Zusaetzliche Pfade:\n' + printf '%s' "$locs" | tr ',' '\n' | sed -n 's/.*"path":"\([^"]*\)".*"forward_host":"\([^"]*\)","forward_port":\([0-9]*\).*/ \1 -> \2:\3/p' + fi cat <} Ziel : ${PROXY_FORWARD_HOST:-127.0.0.1}:${PROXY_FORWARD_PORT:-5000}" \ anlegen "Proxy-Host anlegen (Assistent)" \ list "Vorhandene Proxy-Hosts anzeigen" \ + show "Einzelheiten eines Proxy-Hosts (mit Pfaden)" \ certs "Vorhandene Zertifikate anzeigen" \ del "Proxy-Host loeschen" \ status "Anmeldung pruefen" \ @@ -4371,6 +4448,11 @@ Ziel : ${PROXY_FORWARD_HOST:-127.0.0.1}:${PROXY_FORWARD_PORT:-5000}" \ 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" ;; + show) local d3; d3="$(ui_input "Einzelheiten" "Name oder ID des Proxy-Hosts:" "${PROXY_DOMAIN:-}")" || continue + [ -n "$d3" ] || continue + local f3; f3="$(mktemp)" + NO_COLOR=1 cmd_proxy show --domain "$d3" >"$f3" 2>&1 || true + ui_text "Proxy-Host $d3" "$f3"; rm -f "$f3" ;; 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 ;; @@ -4426,19 +4508,59 @@ Womit soll HTTPS abgesichert werden?" \ letsencrypt "Let's Encrypt (Port 80 muss aus dem Internet erreichbar sein)" \ none "Ohne HTTPS (nur Port 80)")" || return 0 + # --- weitere Dienste unter demselben Namen, nach Pfad getrennt ---- + local -a locopt=() + local loclist="" + if ui_yesno "Weitere Dienste?" \ +"Sollen unter demselben Namen noch andere Dienste erreichbar sein, +unterschieden nach dem Pfad? + + https://$dom/ -> Checkmk (das Ziel von eben) + https://$dom/grafana -> Grafana + https://$dom/sonstwas -> ein weiterer Dienst + +Das ist der Weg, wenn nur EIN Port nach aussen frei ist."; then + while :; do + local lpath ltarget + lpath="$(ui_input "Pfad" \ +"Unter welchem Pfad soll der Dienst liegen? + +Leer lassen beendet die Eingabe." "")" || break + [ -n "$lpath" ] || break + ltarget="$(ui_input "Ziel fuer $lpath" \ +"Wohin soll $lpath weitergeleitet werden? + +Schreibweise: host:port z.B. 127.0.0.1:3000 + https://host:port fuer verschluesselte Ziele" "127.0.0.1:3000")" || break + [ -n "$ltarget" ] || break + if ! proxy_location_json "$lpath=$ltarget" >/dev/null 2>&1; then + ui_msg "Ungueltig" "'$lpath=$ltarget' konnte nicht gelesen werden.\n\nErwartet: /pfad und host:port" + continue + fi + locopt+=(--location "$lpath=$ltarget") + loclist="$loclist + $lpath -> $ltarget" + ui_yesno "Noch einer?" "Bisher:$loclist + +Einen weiteren Pfad hinzufuegen?" || break + done + fi + local upd="" - ui_yesno "Anlegen" \ + if ui_yesno "Anlegen" \ "$dom -> 127.0.0.1:$fport -Zertifikat: $cert +Zertifikat: $cert${loclist:+ + +Zusaetzliche Pfade:$loclist} Einen bereits vorhandenen Eintrag fuer diesen Namen ersetzen? ersetzt ihn - bricht ab, falls es ihn schon gibt" && upd=--update + bricht ab, falls es ihn schon gibt"; then upd=--update; fi 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" + --forward-port '$fport' --cert '$cert' $upd ${locopt[*]+${locopt[*]}}" # Die Vorgaben merken, damit der naechste Aufruf sie kennt env_set PROXY_DOMAIN "$dom" 1 2>/dev/null || true