From c5d376d3aca99f5c4777f321b0dff1e39862dd50 Mon Sep 17 00:00:00 2001 From: wm Date: Sun, 18 Jan 2026 17:28:42 +0100 Subject: [PATCH 1/3] =?UTF-8?q?Neue=20Option=20--opnsense-port=20f=C3=BCr?= =?UTF-8?q?=20Flexibilit=C3=A4t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup_nginx_proxy.sh | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/setup_nginx_proxy.sh b/setup_nginx_proxy.sh index 3f2a84b..217c348 100755 --- a/setup_nginx_proxy.sh +++ b/setup_nginx_proxy.sh @@ -29,7 +29,9 @@ die() { # Default Configuration # ============================================================================= # OPNsense kann über Hostname ODER IP angesprochen werden +# Port 4444 ist der Standard-Port für die OPNsense WebUI/API OPNSENSE_HOST="${OPNSENSE_HOST:-192.168.45.1}" +OPNSENSE_PORT="${OPNSENSE_PORT:-4444}" OPNSENSE_API_KEY="${OPNSENSE_API_KEY:-cUUs80IDkQelMJVgAVK2oUoDHrQf+cQPwXoPKNd3KDIgiCiEyEfMq38UTXeY5/VO/yWtCC7k9Y9kJ0Pn}" OPNSENSE_API_SECRET="${OPNSENSE_API_SECRET:-2egxxFYCAUjBDp0OrgbJO3NBZmR4jpDm028jeS8Nq8OtCGu/0lAxt4YXWXbdZjcFVMS0Nrhru1I2R1si}" @@ -54,6 +56,7 @@ Required options (for proxy setup): Optional: --opnsense-host OPNsense IP or hostname (default: 192.168.45.1) + --opnsense-port OPNsense WebUI/API port (default: 4444) --certificate-uuid UUID of the SSL certificate in OPNsense --list-certificates List available certificates and exit --test-connection Test API connection and exit @@ -98,6 +101,7 @@ while [[ $# -gt 0 ]]; do --backend-ip) BACKEND_IP="${2:-}"; shift 2 ;; --backend-port) BACKEND_PORT="${2:-}"; shift 2 ;; --opnsense-host) OPNSENSE_HOST="${2:-}"; shift 2 ;; + --opnsense-port) OPNSENSE_PORT="${2:-}"; shift 2 ;; --certificate-uuid) CERTIFICATE_UUID="${2:-}"; shift 2 ;; --list-certificates) LIST_CERTIFICATES="1"; shift 1 ;; --test-connection) TEST_CONNECTION="1"; shift 1 ;; @@ -110,7 +114,7 @@ done # ============================================================================= # API Base URL (nach Argument-Parsing setzen!) # ============================================================================= -API_BASE="https://${OPNSENSE_HOST}/api" +API_BASE="https://${OPNSENSE_HOST}:${OPNSENSE_PORT}/api" # ============================================================================= # API Helper Functions (MÜSSEN VOR list_certificates definiert werden!) @@ -202,17 +206,17 @@ except: # Test API connection test_connection() { - info "Testing API connection to OPNsense at ${OPNSENSE_HOST}..." + info "Testing API connection to OPNsense at ${OPNSENSE_HOST}:${OPNSENSE_PORT}..." local response response=$(api_request "GET" "/core/firmware/status") if echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); print('OK' if 'product' in d or 'status' in d else 'FAIL')" 2>/dev/null | grep -q "OK"; then - echo "✓ API connection successful to ${OPNSENSE_HOST}" + echo "✓ API connection successful to ${OPNSENSE_HOST}:${OPNSENSE_PORT}" echo "Response: $(echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); print(json.dumps(d, indent=2)[:500])" 2>/dev/null || echo "$response")" return 0 else - echo "✗ API connection failed to ${OPNSENSE_HOST}" + echo "✗ API connection failed to ${OPNSENSE_HOST}:${OPNSENSE_PORT}" echo "Response: $response" return 1 fi @@ -220,12 +224,12 @@ test_connection() { # List available certificates list_certificates() { - info "Fetching available certificates from OPNsense at ${OPNSENSE_HOST}..." + info "Fetching available certificates from OPNsense at ${OPNSENSE_HOST}:${OPNSENSE_PORT}..." local response response=$(api_request "GET" "/trust/cert/search") - echo "Available SSL Certificates in OPNsense (${OPNSENSE_HOST}):" + echo "Available SSL Certificates in OPNsense (${OPNSENSE_HOST}:${OPNSENSE_PORT}):" echo "============================================================" echo "$response" | python3 -c " import json, sys -- 2.49.1 From 15a9104a8e37f0c507ca2044bb8df0bcf97e9906 Mon Sep 17 00:00:00 2001 From: wm Date: Sun, 18 Jan 2026 18:17:40 +0100 Subject: [PATCH 2/3] Setup NGINX Final --- setup_nginx_proxy.sh | 285 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 227 insertions(+), 58 deletions(-) diff --git a/setup_nginx_proxy.sh b/setup_nginx_proxy.sh index 217c348..41b4209 100755 --- a/setup_nginx_proxy.sh +++ b/setup_nginx_proxy.sh @@ -8,6 +8,8 @@ set -Eeuo pipefail # für eine neue n8n-Instanz über die OPNsense API. # ============================================================================= +SCRIPT_VERSION="1.0.8" + # Debug mode: 0 = nur JSON, 1 = Logs auf stderr DEBUG="${DEBUG:-0}" export DEBUG @@ -132,28 +134,94 @@ api_request() { info "API ${method} ${url}" local response + local http_code + if [[ -n "$data" ]]; then - response=$(curl -s -k -X "${method}" \ + response=$(curl -s -k -w "\n%{http_code}" -X "${method}" \ -u "${auth}" \ -H "Content-Type: application/json" \ -d "${data}" \ "${url}" 2>&1) else - response=$(curl -s -k -X "${method}" \ + response=$(curl -s -k -w "\n%{http_code}" -X "${method}" \ -u "${auth}" \ "${url}" 2>&1) fi + # Extract HTTP code from last line + http_code=$(echo "$response" | tail -n1) + response=$(echo "$response" | sed '$d') + + # Check for permission errors + if [[ "$http_code" == "401" ]]; then + warn "API Error 401: Unauthorized - Check API key and secret" + elif [[ "$http_code" == "403" ]]; then + warn "API Error 403: Forbidden - API user lacks permission for ${endpoint}" + elif [[ "$http_code" == "404" ]]; then + warn "API Error 404: Not Found - Endpoint ${endpoint} does not exist" + elif [[ "$http_code" -ge 400 ]]; then + warn "API Error ${http_code} for ${endpoint}" + fi + echo "$response" } +# Check API response for errors and return status +# Usage: if check_api_response "$response" "endpoint_name"; then ... fi +check_api_response() { + local response="$1" + local endpoint_name="$2" + + # Check for JSON error responses + local status + status=$(echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('status', 'ok'))" 2>/dev/null || echo "ok") + + if [[ "$status" == "403" ]]; then + die "Permission denied for ${endpoint_name}. Please add the required API permission in OPNsense: System > Access > Users > [API User] > Effective Privileges" + elif [[ "$status" == "401" ]]; then + die "Authentication failed for ${endpoint_name}. Check your API key and secret." + fi + + # Check for validation errors + local validation_error + validation_error=$(echo "$response" | python3 -c " +import json,sys +try: + d=json.load(sys.stdin) + if 'validations' in d and d['validations']: + for field, errors in d['validations'].items(): + print(f'{field}: {errors}') +except: + pass +" 2>/dev/null || true) + + if [[ -n "$validation_error" ]]; then + warn "Validation errors: ${validation_error}" + return 1 + fi + + # Check for result status + local result + result=$(echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('result', 'unknown'))" 2>/dev/null || echo "unknown") + + if [[ "$result" == "failed" ]]; then + local message + message=$(echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('message', 'Unknown error'))" 2>/dev/null || echo "Unknown error") + warn "API operation failed: ${message}" + return 1 + fi + + return 0 +} + # Search for existing item by description +# OPNsense NGINX API uses "search" format, e.g., searchUpstreamServer search_by_description() { - local endpoint="$1" + local search_endpoint="$1" local description="$2" local response - response=$(api_request "GET" "${endpoint}/search") + response=$(api_request "GET" "${search_endpoint}") # Extract UUID where description matches echo "$response" | python3 -c " @@ -170,29 +238,22 @@ except: " 2>/dev/null || true } -# Find certificate by Common Name (CN) -find_certificate_by_cn() { - local cn_pattern="$1" +# Search for existing HTTP Server by servername +# HTTP Servers don't have a description field, they use servername +search_http_server_by_servername() { + local servername="$1" local response - response=$(api_request "GET" "/trust/cert/search") + response=$(api_request "GET" "/nginx/settings/searchHttpServer") - # Extract UUID where CN contains the pattern (for wildcard certs) + # Extract UUID where servername matches echo "$response" | python3 -c " import json, sys -pattern = '${cn_pattern}' try: data = json.load(sys.stdin) rows = data.get('rows', []) for row in rows: - cn = row.get('cn', '') - descr = row.get('descr', '') - # Match wildcard or exact domain - if pattern in cn or pattern in descr: - print(row.get('uuid', '')) - sys.exit(0) - # Also check for wildcard pattern - if cn.startswith('*.') and pattern.endswith(cn[2:]): + if row.get('servername', '') == '${servername}': print(row.get('uuid', '')) sys.exit(0) except: @@ -200,6 +261,34 @@ except: " 2>/dev/null || true } +# Find certificate by Common Name (CN) or Description +# Returns the certificate ID used by NGINX API (not the full UUID) +find_certificate_by_cn() { + local cn_pattern="$1" + + # First, get the certificate list from the HTTP Server schema + # This gives us the correct certificate IDs that NGINX expects + local response + response=$(api_request "GET" "/nginx/settings/getHttpServer") + + # Extract certificate ID where description contains the pattern + echo "$response" | python3 -c " +import json, sys +pattern = '${cn_pattern}'.lower() +try: + data = json.load(sys.stdin) + certs = data.get('httpserver', {}).get('certificate', {}) + for cert_id, cert_info in certs.items(): + if cert_id: # Skip empty key + value = cert_info.get('value', '').lower() + if pattern in value: + print(cert_id) + sys.exit(0) +except Exception as e: + print(f'Error: {e}', file=sys.stderr) +" 2>/dev/null || true +} + # ============================================================================= # Utility Functions # ============================================================================= @@ -208,18 +297,69 @@ except: test_connection() { info "Testing API connection to OPNsense at ${OPNSENSE_HOST}:${OPNSENSE_PORT}..." + echo "Testing various API endpoints..." + echo "" + + # Test 1: Firmware status (general API access) + echo "1. Testing /core/firmware/status..." local response response=$(api_request "GET" "/core/firmware/status") - - if echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); print('OK' if 'product' in d or 'status' in d else 'FAIL')" 2>/dev/null | grep -q "OK"; then - echo "✓ API connection successful to ${OPNSENSE_HOST}:${OPNSENSE_PORT}" - echo "Response: $(echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); print(json.dumps(d, indent=2)[:500])" 2>/dev/null || echo "$response")" - return 0 + if echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); print('OK' if 'product' in d or 'connection' in d else 'FAIL')" 2>/dev/null | grep -q "OK"; then + echo " ✓ Firmware API: OK" else - echo "✗ API connection failed to ${OPNSENSE_HOST}:${OPNSENSE_PORT}" - echo "Response: $response" - return 1 + echo " ✗ Firmware API: FAILED" + echo " Response: $response" fi + + # Test 2: NGINX settings (required for this script) + echo "" + echo "2. Testing /nginx/settings/searchHttpServer..." + response=$(api_request "GET" "/nginx/settings/searchHttpServer") + if echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); print('OK' if 'rows' in d or 'rowCount' in d else 'FAIL')" 2>/dev/null | grep -q "OK"; then + echo " ✓ NGINX HTTP Server API: OK" + local count + count=$(echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('rowCount', len(d.get('rows', []))))" 2>/dev/null || echo "?") + echo " Found ${count} HTTP Server(s)" + else + echo " ✗ NGINX HTTP Server API: FAILED" + echo " Response: $response" + fi + + # Test 3: NGINX upstream servers + echo "" + echo "3. Testing /nginx/settings/searchUpstreamServer..." + response=$(api_request "GET" "/nginx/settings/searchUpstreamServer") + if echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); print('OK' if 'rows' in d or 'rowCount' in d else 'FAIL')" 2>/dev/null | grep -q "OK"; then + echo " ✓ NGINX Upstream Server API: OK" + local count + count=$(echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('rowCount', len(d.get('rows', []))))" 2>/dev/null || echo "?") + echo " Found ${count} Upstream Server(s)" + else + echo " ✗ NGINX Upstream Server API: FAILED" + echo " Response: $response" + fi + + # Test 4: Trust/Certificates (optional) + echo "" + echo "4. Testing /trust/cert/search (optional)..." + response=$(api_request "GET" "/trust/cert/search") + if echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); print('OK' if 'rows' in d else 'FAIL')" 2>/dev/null | grep -q "OK"; then + echo " ✓ Trust/Cert API: OK" + else + local status + status=$(echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('status', 'unknown'))" 2>/dev/null || echo "unknown") + if [[ "$status" == "403" ]]; then + echo " ⚠ Trust/Cert API: 403 Forbidden (API user needs 'System: Trust: Certificates' permission)" + echo " Note: You can still use --certificate-uuid to specify the certificate manually" + else + echo " ✗ Trust/Cert API: FAILED" + echo " Response: $response" + fi + fi + + echo "" + echo "Connection test complete." + return 0 } # List available certificates @@ -276,12 +416,13 @@ fi [[ -n "$FQDN" ]] || die "--fqdn is required" [[ -n "$BACKEND_IP" ]] || die "--backend-ip is required" +info "Script Version: ${SCRIPT_VERSION}" info "Configuration:" info " CTID: ${CTID}" info " Hostname: ${HOSTNAME}" info " FQDN: ${FQDN}" info " Backend: ${BACKEND_IP}:${BACKEND_PORT}" -info " OPNsense: ${OPNSENSE_HOST}" +info " OPNsense: ${OPNSENSE_HOST}:${OPNSENSE_PORT}" info " Certificate UUID: ${CERTIFICATE_UUID:-auto-detect}" # ============================================================================= @@ -298,8 +439,10 @@ create_upstream_server() { # Check if upstream server already exists local existing_uuid - existing_uuid=$(search_by_description "/nginx/settings/upstream_server" "${description}") + existing_uuid=$(search_by_description "/nginx/settings/searchUpstreamServer" "${description}") + # Note: OPNsense API expects specific values + # no_use: empty string means "use this server" (not "0") local data data=$(cat </dev/null || true) + info "API Response: ${response}" + # OPNsense returns {"uuid":"xxx"} or {"result":"saved","uuid":"xxx"} + existing_uuid=$(echo "$response" | python3 -c " +import json,sys +try: + d = json.load(sys.stdin) + # Try different response formats + uuid = d.get('uuid', '') + if not uuid and 'rows' in d: + # Sometimes returns in rows format + uuid = d['rows'][0].get('uuid', '') if d['rows'] else '' + print(uuid) +except Exception as e: + print('', file=sys.stderr) +" 2>/dev/null || true) fi info "Upstream Server UUID: ${existing_uuid}" @@ -340,7 +496,7 @@ create_upstream() { # Check if upstream already exists local existing_uuid - existing_uuid=$(search_by_description "/nginx/settings/upstream" "${description}") + existing_uuid=$(search_by_description "/nginx/settings/searchUpstream" "${description}") local data data=$(cat </dev/null || true) + info "API Response: ${response}" + existing_uuid=$(echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('uuid',''))" 2>/dev/null || true) fi info "HTTP Server UUID: ${existing_uuid}" -- 2.49.1 From bef60a39b118638c22ac3528b52efadf4f2c4263 Mon Sep 17 00:00:00 2001 From: wm Date: Sun, 18 Jan 2026 22:35:29 +0100 Subject: [PATCH 3/3] feat: Add debug mode and NGINX proxy delete script --- NGINX_PROXY_SETUP.md | 261 +++++++++++++++++++++++++++ delete_nginx_proxy.sh | 389 +++++++++++++++++++++++++++++++++++++++++ delete_stopped_lxcs.sh | 87 +++++---- 3 files changed, 702 insertions(+), 35 deletions(-) create mode 100644 NGINX_PROXY_SETUP.md create mode 100644 delete_nginx_proxy.sh diff --git a/NGINX_PROXY_SETUP.md b/NGINX_PROXY_SETUP.md new file mode 100644 index 0000000..4dc9c68 --- /dev/null +++ b/NGINX_PROXY_SETUP.md @@ -0,0 +1,261 @@ +# OPNsense NGINX Reverse Proxy Setup + +Dieses Script automatisiert die Konfiguration eines NGINX Reverse Proxys auf OPNsense für n8n-Instanzen. + +## Voraussetzungen + +- OPNsense Firewall mit NGINX Plugin +- API-Zugang zu OPNsense (API Key + Secret) +- Wildcard-Zertifikat für die Domain (z.B. *.userman.de) + +## Installation + +Das Script befindet sich im Repository unter `setup_nginx_proxy.sh`. + +## Verwendung + +### Proxy einrichten + +```bash +# Minimale Konfiguration +bash setup_nginx_proxy.sh \ + --ctid 768736636 \ + --hostname sb-1768736636 \ + --fqdn sb-1768736636.userman.de \ + --backend-ip 192.168.45.135 + +# Mit Debug-Ausgabe +bash setup_nginx_proxy.sh --debug \ + --ctid 768736636 \ + --hostname sb-1768736636 \ + --fqdn sb-1768736636.userman.de \ + --backend-ip 192.168.45.135 + +# Mit benutzerdefiniertem Backend-Port +bash setup_nginx_proxy.sh \ + --ctid 768736636 \ + --hostname sb-1768736636 \ + --fqdn sb-1768736636.userman.de \ + --backend-ip 192.168.45.135 \ + --backend-port 8080 +``` + +### Proxy löschen + +```bash +# Proxy für eine CTID löschen +bash delete_nginx_proxy.sh --ctid 768736636 + +# Mit Debug-Ausgabe +bash delete_nginx_proxy.sh --debug --ctid 768736636 + +# Dry-Run (zeigt was gelöscht würde, ohne zu löschen) +bash delete_nginx_proxy.sh --dry-run --ctid 768736636 + +# Mit expliziter FQDN +bash delete_nginx_proxy.sh --ctid 768736636 --fqdn sb-1768736636.userman.de +``` + +### Hilfsfunktionen + +```bash +# API-Verbindung testen +bash setup_nginx_proxy.sh --test-connection --debug + +# Verfügbare Zertifikate auflisten +bash setup_nginx_proxy.sh --list-certificates --debug +``` + +## Parameter + +### Erforderliche Parameter (für Proxy-Setup) + +| Parameter | Beschreibung | Beispiel | +|-----------|--------------|----------| +| `--ctid ` | Container ID (wird als Beschreibung verwendet) | `768736636` | +| `--hostname ` | Hostname des Containers | `sb-1768736636` | +| `--fqdn ` | Vollständiger Domainname | `sb-1768736636.userman.de` | +| `--backend-ip ` | IP-Adresse des Backends | `192.168.45.135` | + +### Optionale Parameter + +| Parameter | Beschreibung | Standard | +|-----------|--------------|----------| +| `--backend-port ` | Backend-Port | `5678` | +| `--opnsense-host ` | OPNsense IP oder Hostname | `192.168.45.1` | +| `--opnsense-port ` | OPNsense WebUI/API Port | `4444` | +| `--certificate-uuid ` | UUID des SSL-Zertifikats | Auto-Detect | +| `--debug` | Debug-Modus aktivieren | Aus | +| `--help` | Hilfe anzeigen | - | + +### Spezielle Befehle + +| Parameter | Beschreibung | +|-----------|--------------| +| `--test-connection` | API-Verbindung testen und beenden | +| `--list-certificates` | Verfügbare Zertifikate auflisten und beenden | + +## Ausgabe + +### Normalmodus (ohne --debug) + +Das Script gibt nur JSON auf stdout aus: + +```json +{ + "success": true, + "ctid": "768736636", + "fqdn": "sb-1768736636.userman.de", + "backend": "192.168.45.135:5678", + "nginx": { + "upstream_server_uuid": "81f5f15b-978c-4839-b794-5ddb9f1c964e", + "upstream_uuid": "5fe99a9f-35fb-4141-9b89-238333604a0d", + "location_uuid": "5c3cc080-385a-4800-964d-ab01f33d45a8", + "http_server_uuid": "946489aa-7212-41b3-93e2-4972f6a26d4e" + } +} +``` + +Bei Fehlern: +```json +{"error": "Fehlerbeschreibung"} +``` + +### Debug-Modus (mit --debug) + +Zusätzlich werden Logs auf stderr ausgegeben: + +``` +[2026-01-18 17:57:04] INFO: Script Version: 1.0.8 +[2026-01-18 17:57:04] INFO: Configuration: +[2026-01-18 17:57:04] INFO: CTID: 768736636 +[2026-01-18 17:57:04] INFO: Hostname: sb-1768736636 +... +``` + +## Erstellte NGINX-Komponenten + +Das Script erstellt folgende Komponenten in OPNsense: + +1. **Upstream Server** - Backend-Server mit IP und Port +2. **Upstream** - Load-Balancer-Gruppe (verweist auf Upstream Server) +3. **Location** - URL-Pfad-Konfiguration mit WebSocket-Support +4. **HTTP Server** - Virtueller Host mit HTTPS und Zertifikat + +### Verknüpfungskette + +``` +HTTP Server (sb-1768736636.userman.de:443) + └── Location (/) + └── Upstream (768736636) + └── Upstream Server (192.168.45.135:5678) +``` + +## Umgebungsvariablen + +Das Script kann auch über Umgebungsvariablen konfiguriert werden: + +```bash +export OPNSENSE_HOST="192.168.45.1" +export OPNSENSE_PORT="4444" +export OPNSENSE_API_KEY="your-api-key" +export OPNSENSE_API_SECRET="your-api-secret" +export CERTIFICATE_UUID="your-cert-uuid" +export DEBUG="1" + +bash setup_nginx_proxy.sh --ctid 768736636 ... +``` + +## Delete Script Parameter + +### Erforderliche Parameter + +| Parameter | Beschreibung | Beispiel | +|-----------|--------------|----------| +| `--ctid ` | Container ID (zum Finden der Komponenten) | `768736636` | + +### Optionale Parameter + +| Parameter | Beschreibung | Standard | +|-----------|--------------|----------| +| `--fqdn ` | FQDN zum Finden des HTTP Servers | Auto-Detect | +| `--opnsense-host ` | OPNsense IP oder Hostname | `192.168.45.1` | +| `--opnsense-port ` | OPNsense WebUI/API Port | `4444` | +| `--dry-run` | Zeigt was gelöscht würde, ohne zu löschen | Aus | +| `--debug` | Debug-Modus aktivieren | Aus | + +### Delete Script Ausgabe + +```json +{ + "success": true, + "dry_run": false, + "ctid": "768736636", + "deleted_count": 4, + "failed_count": 0, + "components": { + "http_server": "deleted", + "location": "deleted", + "upstream": "deleted", + "upstream_server": "deleted" + }, + "reconfigure": "ok" +} +``` + +### Löschreihenfolge + +Das Script löscht die Komponenten in der richtigen Reihenfolge (von außen nach innen): + +1. **HTTP Server** - Virtueller Host +2. **Location** - URL-Pfad-Konfiguration +3. **Upstream** - Load-Balancer-Gruppe +4. **Upstream Server** - Backend-Server + +## Fehlerbehebung + +### API-Verbindungsfehler + +```bash +# Verbindung testen +bash setup_nginx_proxy.sh --test-connection --debug +``` + +### Zertifikat nicht gefunden + +```bash +# Verfügbare Zertifikate auflisten +bash setup_nginx_proxy.sh --list-certificates --debug + +# Zertifikat manuell angeben +bash setup_nginx_proxy.sh --certificate-uuid "695a8b67b35ae" ... +``` + +### Berechtigungsfehler (403) + +Der API-Benutzer benötigt folgende Berechtigungen in OPNsense: +- `NGINX: Settings` +- `NGINX: Service` +- `System: Trust: Certificates` (optional, für Auto-Detect) + +## Versionsverlauf + +### setup_nginx_proxy.sh + +| Version | Änderungen | +|---------|------------| +| 1.0.8 | HTTP Server Suche nach servername statt description | +| 1.0.7 | Listen-Adressen auf Port 80/443 gesetzt | +| 1.0.6 | Listen-Adressen hinzugefügt | +| 1.0.5 | verify_client und access_log_format hinzugefügt | +| 1.0.4 | Korrektes API-Format (httpserver statt http_server) | +| 1.0.3 | Vereinfachte HTTP Server Konfiguration | +| 1.0.0 | Initiale Version | + +### delete_nginx_proxy.sh + +| Version | Änderungen | +|---------|------------| +| 1.0.2 | Verbesserte Debug-Ausgabe für Suche nach Komponenten | +| 1.0.1 | Fix: Arithmetik-Fehler bei Counter-Inkrementierung behoben | +| 1.0.0 | Initiale Version | diff --git a/delete_nginx_proxy.sh b/delete_nginx_proxy.sh new file mode 100644 index 0000000..1ecd13e --- /dev/null +++ b/delete_nginx_proxy.sh @@ -0,0 +1,389 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +# ============================================================================= +# OPNsense NGINX Reverse Proxy Delete Script +# ============================================================================= +# Dieses Script löscht einen NGINX Reverse Proxy auf OPNsense +# für eine n8n-Instanz über die OPNsense API. +# ============================================================================= + +SCRIPT_VERSION="1.0.2" + +# Debug mode: 0 = nur JSON, 1 = Logs auf stderr +DEBUG="${DEBUG:-0}" +export DEBUG + +# Logging functions +log_ts() { date "+[%F %T]"; } +info() { [[ "$DEBUG" == "1" ]] && echo "$(log_ts) INFO: $*" >&2; return 0; } +warn() { [[ "$DEBUG" == "1" ]] && echo "$(log_ts) WARN: $*" >&2; return 0; } +die() { + if [[ "$DEBUG" == "1" ]]; then + echo "$(log_ts) ERROR: $*" >&2 + else + echo "{\"error\": \"$*\"}" + fi + exit 1 +} + +# ============================================================================= +# Default Configuration +# ============================================================================= +OPNSENSE_HOST="${OPNSENSE_HOST:-192.168.45.1}" +OPNSENSE_PORT="${OPNSENSE_PORT:-4444}" +OPNSENSE_API_KEY="${OPNSENSE_API_KEY:-cUUs80IDkQelMJVgAVK2oUoDHrQf+cQPwXoPKNd3KDIgiCiEyEfMq38UTXeY5/VO/yWtCC7k9Y9kJ0Pn}" +OPNSENSE_API_SECRET="${OPNSENSE_API_SECRET:-2egxxFYCAUjBDp0OrgbJO3NBZmR4jpDm028jeS8Nq8OtCGu/0lAxt4YXWXbdZjcFVMS0Nrhru1I2R1si}" + +# ============================================================================= +# Usage +# ============================================================================= +usage() { + cat >&2 <<'EOF' +Usage: + bash delete_nginx_proxy.sh [options] + +Required options: + --ctid Container ID (used to find components by description) + +Optional: + --fqdn Full domain name (to find HTTP Server by servername) + --opnsense-host OPNsense IP or hostname (default: 192.168.45.1) + --opnsense-port OPNsense WebUI/API port (default: 4444) + --dry-run Show what would be deleted without actually deleting + --debug Enable debug mode + --help Show this help + +Examples: + # Delete proxy by CTID: + bash delete_nginx_proxy.sh --ctid 768736636 + + # Delete proxy with debug output: + bash delete_nginx_proxy.sh --debug --ctid 768736636 + + # Dry run (show what would be deleted): + bash delete_nginx_proxy.sh --dry-run --ctid 768736636 + + # Delete by CTID and FQDN: + bash delete_nginx_proxy.sh --ctid 768736636 --fqdn sb-1768736636.userman.de +EOF +} + +# ============================================================================= +# Default values for arguments +# ============================================================================= +CTID="" +FQDN="" +DRY_RUN="0" + +# ============================================================================= +# Argument parsing +# ============================================================================= +while [[ $# -gt 0 ]]; do + case "$1" in + --ctid) CTID="${2:-}"; shift 2 ;; + --fqdn) FQDN="${2:-}"; shift 2 ;; + --opnsense-host) OPNSENSE_HOST="${2:-}"; shift 2 ;; + --opnsense-port) OPNSENSE_PORT="${2:-}"; shift 2 ;; + --dry-run) DRY_RUN="1"; shift 1 ;; + --debug) DEBUG="1"; export DEBUG; shift 1 ;; + --help|-h) usage; exit 0 ;; + *) die "Unknown option: $1 (use --help)" ;; + esac +done + +# ============================================================================= +# API Base URL +# ============================================================================= +API_BASE="https://${OPNSENSE_HOST}:${OPNSENSE_PORT}/api" + +# ============================================================================= +# API Helper Functions +# ============================================================================= + +# Make API request to OPNsense +api_request() { + local method="$1" + local endpoint="$2" + local data="${3:-}" + + local url="${API_BASE}${endpoint}" + local auth="${OPNSENSE_API_KEY}:${OPNSENSE_API_SECRET}" + + info "API ${method} ${url}" + + local response + + if [[ -n "$data" ]]; then + response=$(curl -s -k -X "${method}" \ + -u "${auth}" \ + -H "Content-Type: application/json" \ + -d "${data}" \ + "${url}" 2>&1) + else + response=$(curl -s -k -X "${method}" \ + -u "${auth}" \ + "${url}" 2>&1) + fi + + echo "$response" +} + +# Search for items by description +search_by_description() { + local search_endpoint="$1" + local description="$2" + + local response + response=$(api_request "GET" "${search_endpoint}") + + info "Search response for ${search_endpoint}: ${response:0:500}..." + + # Extract all UUIDs where description matches + local uuid + uuid=$(echo "$response" | python3 -c " +import json, sys +desc = sys.argv[1] if len(sys.argv) > 1 else '' +try: + data = json.load(sys.stdin) + rows = data.get('rows', []) + for row in rows: + row_desc = row.get('description', '') + if row_desc == desc: + print(row.get('uuid', '')) + sys.exit(0) +except Exception as e: + print(f'Error: {e}', file=sys.stderr) +" "${description}" 2>/dev/null || true) + + info "Found UUID for description '${description}': ${uuid:-none}" + echo "$uuid" +} + +# Search for HTTP Server by servername +search_http_server_by_servername() { + local servername="$1" + + local response + response=$(api_request "GET" "/nginx/settings/searchHttpServer") + + info "HTTP Server search response: ${response:0:500}..." + + # Extract UUID where servername matches + local uuid + uuid=$(echo "$response" | python3 -c " +import json, sys +sname = sys.argv[1] if len(sys.argv) > 1 else '' +try: + data = json.load(sys.stdin) + rows = data.get('rows', []) + for row in rows: + row_sname = row.get('servername', '') + if row_sname == sname: + print(row.get('uuid', '')) + sys.exit(0) +except Exception as e: + print(f'Error: {e}', file=sys.stderr) +" "${servername}" 2>/dev/null || true) + + info "Found HTTP Server UUID for servername '${servername}': ${uuid:-none}" + echo "$uuid" +} + +# ============================================================================= +# Delete Functions +# ============================================================================= + +delete_item() { + local item_type="$1" + local uuid="$2" + local endpoint="$3" + + if [[ -z "$uuid" ]]; then + info "No ${item_type} found to delete" + return 0 + fi + + if [[ "$DRY_RUN" == "1" ]]; then + info "[DRY-RUN] Would delete ${item_type}: ${uuid}" + echo "dry-run" + return 0 + fi + + info "Deleting ${item_type}: ${uuid}" + local response + response=$(api_request "POST" "${endpoint}/${uuid}") + + local result + result=$(echo "$response" | python3 -c "import json,sys; print(json.load(sys.stdin).get('result','unknown'))" 2>/dev/null || echo "unknown") + + if [[ "$result" == "deleted" ]]; then + info "${item_type} deleted successfully" + echo "deleted" + else + warn "Failed to delete ${item_type}: ${response}" + echo "failed" + fi +} + +# ============================================================================= +# Validation +# ============================================================================= +[[ -n "$CTID" ]] || die "--ctid is required" + +info "Script Version: ${SCRIPT_VERSION}" +info "Configuration:" +info " CTID: ${CTID}" +info " FQDN: ${FQDN:-auto-detect}" +info " OPNsense: ${OPNSENSE_HOST}:${OPNSENSE_PORT}" +info " Dry Run: ${DRY_RUN}" + +# ============================================================================= +# Main +# ============================================================================= +main() { + info "Starting NGINX Reverse Proxy deletion for CTID ${CTID}..." + + local description="${CTID}" + local deleted_count=0 + local failed_count=0 + + # Results tracking + local http_server_result="not_found" + local location_result="not_found" + local upstream_result="not_found" + local upstream_server_result="not_found" + + # Step 1: Find and delete HTTP Server + info "Step 1: Finding HTTP Server..." + local http_server_uuid="" + + # Try to find by FQDN first + if [[ -n "$FQDN" ]]; then + http_server_uuid=$(search_http_server_by_servername "${FQDN}") + fi + + # If not found by FQDN, try common patterns + if [[ -z "$http_server_uuid" ]]; then + # Try sb-.userman.de pattern + http_server_uuid=$(search_http_server_by_servername "sb-${CTID}.userman.de") + fi + + if [[ -z "$http_server_uuid" ]]; then + # Try sb-1.userman.de pattern (with leading 1) + http_server_uuid=$(search_http_server_by_servername "sb-1${CTID}.userman.de") + fi + + if [[ -n "$http_server_uuid" ]]; then + http_server_result=$(delete_item "HTTP Server" "$http_server_uuid" "/nginx/settings/delHttpServer") + if [[ "$http_server_result" == "deleted" || "$http_server_result" == "dry-run" ]]; then + deleted_count=$((deleted_count + 1)) + else + failed_count=$((failed_count + 1)) + fi + else + info "No HTTP Server found for CTID ${CTID}" + fi + + # Step 2: Find and delete Location + info "Step 2: Finding Location..." + local location_uuid + location_uuid=$(search_by_description "/nginx/settings/searchLocation" "${description}") + + if [[ -n "$location_uuid" ]]; then + location_result=$(delete_item "Location" "$location_uuid" "/nginx/settings/delLocation") + if [[ "$location_result" == "deleted" || "$location_result" == "dry-run" ]]; then + deleted_count=$((deleted_count + 1)) + else + failed_count=$((failed_count + 1)) + fi + else + info "No Location found for CTID ${CTID}" + fi + + # Step 3: Find and delete Upstream + info "Step 3: Finding Upstream..." + local upstream_uuid + upstream_uuid=$(search_by_description "/nginx/settings/searchUpstream" "${description}") + + if [[ -n "$upstream_uuid" ]]; then + upstream_result=$(delete_item "Upstream" "$upstream_uuid" "/nginx/settings/delUpstream") + if [[ "$upstream_result" == "deleted" || "$upstream_result" == "dry-run" ]]; then + deleted_count=$((deleted_count + 1)) + else + failed_count=$((failed_count + 1)) + fi + else + info "No Upstream found for CTID ${CTID}" + fi + + # Step 4: Find and delete Upstream Server + info "Step 4: Finding Upstream Server..." + local upstream_server_uuid + upstream_server_uuid=$(search_by_description "/nginx/settings/searchUpstreamServer" "${description}") + + if [[ -n "$upstream_server_uuid" ]]; then + upstream_server_result=$(delete_item "Upstream Server" "$upstream_server_uuid" "/nginx/settings/delUpstreamServer") + if [[ "$upstream_server_result" == "deleted" || "$upstream_server_result" == "dry-run" ]]; then + deleted_count=$((deleted_count + 1)) + else + failed_count=$((failed_count + 1)) + fi + else + info "No Upstream Server found for CTID ${CTID}" + fi + + # Step 5: Apply configuration (if not dry-run and something was deleted) + local reconfigure_result="skipped" + if [[ "$DRY_RUN" != "1" && $deleted_count -gt 0 ]]; then + info "Step 5: Applying NGINX configuration..." + local response + response=$(api_request "POST" "/nginx/service/reconfigure" "{}") + + local status + status=$(echo "$response" | python3 -c "import json,sys; print(json.load(sys.stdin).get('status',''))" 2>/dev/null || echo "unknown") + + if [[ "$status" == "ok" ]]; then + info "NGINX configuration applied successfully" + reconfigure_result="ok" + else + warn "NGINX reconfigure status: ${status}" + reconfigure_result="failed" + fi + elif [[ "$DRY_RUN" == "1" ]]; then + info "[DRY-RUN] Would apply NGINX configuration" + reconfigure_result="dry-run" + fi + + # Output result as JSON + local success="true" + [[ $failed_count -gt 0 ]] && success="false" + + local result + result=$(cat </dev/null || echo "$result" + fi +} + +main diff --git a/delete_stopped_lxcs.sh b/delete_stopped_lxcs.sh index 0ef378d..cc63e7c 100755 --- a/delete_stopped_lxcs.sh +++ b/delete_stopped_lxcs.sh @@ -1,52 +1,69 @@ #!/bin/bash +# delete_stopped_lxc.sh - Löscht alle gestoppten LXC Container auf PVE -# Skript zum Löschen aller gestoppten LXCs auf dem lokalen Proxmox-Node -# Verwendet pct destroy und berücksichtigt nur den lokalen Node +set -e -# Überprüfen, ob das Skript als Root ausgeführt wird -if [ "$(id -u)" -ne 0 ]; then - echo "Dieses Skript muss als Root ausgeführt werden." >&2 - exit 1 -fi +# Farben für Output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color -# Überprüfen, ob pct verfügbar ist -if ! command -v pct &> /dev/null; then - echo "pct ist nicht installiert. Bitte installieren Sie es zuerst." >&2 - exit 1 -fi +echo -e "${YELLOW}=== Gestoppte LXC Container finden ===${NC}\n" -# Alle gestoppten LXCs auf dem lokalen Node abrufen -echo "Suche nach gestoppten LXCs auf diesem Node..." -stopped_lxcs=$(pct list | awk '$2 == "stopped" {print $1}') +# Array für gestoppte Container +declare -a STOPPED_CTS -if [ -z "$stopped_lxcs" ]; then - echo "Keine gestoppten LXCs auf diesem Node gefunden." +# Alle Container durchgehen und gestoppte finden +while read -r line; do + VMID=$(echo "$line" | awk '{print $1}') + STATUS=$(echo "$line" | awk '{print $2}') + NAME=$(echo "$line" | awk '{print $3}') + + if [[ "$STATUS" == "stopped" ]]; then + STOPPED_CTS+=("$VMID:$NAME") + echo -e " ${RED}[STOPPED]${NC} CT $VMID - $NAME" + fi +done < <(pct list | tail -n +2) + +# Prüfen ob gestoppte Container gefunden wurden +if [[ ${#STOPPED_CTS[@]} -eq 0 ]]; then + echo -e "\n${GREEN}Keine gestoppten Container gefunden.${NC}" exit 0 fi -echo "Gefundene gestoppte LXCs auf diesem Node:" -echo "$stopped_lxcs" | while read -r lxc_id; do - lxc_name=$(pct config $lxc_id | grep '^hostname:' | awk '{print $2}') - echo " $lxc_id - $lxc_name" -done +echo -e "\n${YELLOW}Gefunden: ${#STOPPED_CTS[@]} gestoppte Container${NC}\n" -# Bestätigung einholen -read -p "Möchten Sie diese LXCs wirklich löschen? (y/n): " confirm -if [[ ! "$confirm" =~ ^[Yy]$ ]]; then - echo "Löschvorgang abgebrochen." +# Bestätigung anfordern +read -p "Möchten Sie ALLE gestoppten Container unwiderruflich löschen? (ja/nein): " CONFIRM + +if [[ "$CONFIRM" != "ja" ]]; then + echo -e "${GREEN}Abgebrochen. Keine Container wurden gelöscht.${NC}" exit 0 fi -# LXCs löschen -echo "Lösche gestoppte LXCs..." -for lxc_id in $stopped_lxcs; do - echo "Lösche LXC $lxc_id..." - pct destroy $lxc_id - if [ $? -eq 0 ]; then - echo "LXC $lxc_id erfolgreich gelöscht." +# Zweite Bestätigung +read -p "Sind Sie WIRKLICH sicher? Tippen Sie 'LÖSCHEN' ein: " CONFIRM2 + +if [[ "$CONFIRM2" != "LÖSCHEN" ]]; then + echo -e "${GREEN}Abgebrochen. Keine Container wurden gelöscht.${NC}" + exit 0 +fi + +echo -e "\n${RED}=== Lösche Container ===${NC}\n" + +# Container löschen +for CT in "${STOPPED_CTS[@]}"; do + VMID="${CT%%:*}" + NAME="${CT##*:}" + + echo -n "Lösche CT $VMID ($NAME)... " + + if pct destroy "$VMID" --purge 2>/dev/null; then + echo -e "${GREEN}OK${NC}" else - echo "Fehler beim Löschen von LXC $lxc_id." >&2 + echo -e "${RED}FEHLER${NC}" fi done -echo "Vorgang abgeschlossen." \ No newline at end of file +echo -e "\n${GREEN}=== Fertig ===${NC}" \ No newline at end of file -- 2.49.1