From 1b1b965c8c6964a13b5f871769cfc22276809f55 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 28 Jan 2026 11:44:12 +0100 Subject: [PATCH] Version 1 --- INSTALL.md | 93 +++++++++++++++++ default.proxy_based_server.conf | 15 +++ default.sync-ispconfig-proxy.conf | 5 + proxy-inventory.sh | 144 ++++++++++++++++++++++++++ remoteip.conf | 8 ++ sync-ispconfig-cleanup.sh | 22 ++++ sync-ispconfig-proxy.sh | 167 ++++++++++++++++++++++++++++++ 7 files changed, 454 insertions(+) create mode 100644 INSTALL.md create mode 100644 default.proxy_based_server.conf create mode 100644 default.sync-ispconfig-proxy.conf create mode 100755 proxy-inventory.sh create mode 100644 remoteip.conf create mode 100644 sync-ispconfig-cleanup.sh create mode 100755 sync-ispconfig-proxy.sh diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..6de04ed --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,93 @@ +# Proxyserver: Edge-Proxy & Backend Dokumentation + +**Stand:** Januar 2026 +**Fokus:** Routed IPv6 Setup & Internes IPv4-VPN/Bridge für isolierte Backends (Root-Server / LXC) + +## Konzept-Hinweis + +Dieses Setup ist für Root-Server optimiert, auf denen Proxyserver und die Backends (VMs/LXCs) über interne Bridges (z. B. `vmbr1`) kommunizieren. +Dies umgeht MTU-Probleme, wie sie bei WireGuard im mobilen Bereich auftreten. + +--- + +## 1. Netzwerk-Architektur (IPv4-VPN) + +Für IPv4-only Backends ohne öffentliche IP wird ein isoliertes Segment genutzt: + +- **Proxyserver (Gateway):** `10.8.1.x` +- **Clients (Backends):** `10.8.1.y` +- **Interface:** `vmbr1` (Bridge) oder Tinc-Tunnel + +Proxyserver übernimmt das NAT (Masquerading), damit Clients Updates ziehen können, und dient als Standard-Gateway für die Backends. + +--- + +## 2. Proxyserver Setup (Der Proxy-Dienst) + +Installiere Nginx und ACME.sh auf Proxyserver. +Die Synchronisation erfolgt über ein zentrales Bash-Skript. +ACME Zertifikatserneuerung wie gewohnt über crontab + +### 2.1 Konfigurationsdatei + +Pfad: `/usr/local/bin/sync-ispconfig-proxy.conf` + +### 2.2 Server-Liste + +Pfad: `/usr/local/bin/proxy_based_server.conf` + + +### 2.3 Das Sync-Skript + +Pfad: `/usr/local/bin/sync-ispconfig-proxy.sh` + +## 3. Client Setup (vServer / Apache) + +Die Backends müssen den Proxy als vertrauenswürdig einstufen. + +### 3.1 Remote-IP Konfiguration + +Pfad: `/etc/apache2/conf-available/remoteip.conf` + + +```bash +a2enmod remoteip proxy_http rewrite headers +a2enconf remoteip +systemctl restart apache2 +``` + +--- + +## 4. ISPConfig Integration (Apache Direktiven Beispiel Gitea) + +Eintragen unter **Webseite → Optionen → Apache Direktiven** für den vServer: + +```apache +# Gitea / Backend Proxy +RequestHeader set X-Forwarded-Proto "https" +ProxyPreserveHost On + + + Order allow,deny + Allow from all + + +# Lokale Weiterleitung an den Dienst-Port (z. B. Gitea) +ProxyPass / http://[::1]:3000/ +ProxyPassReverse / http://[::1]:3000/ + +# WebSocket Upgrade Support +RewriteEngine On +RewriteCond %{HTTP:Upgrade} =websocket [NC] +RewriteRule /(.*) ws://[::1]:3000/$1 [P,L] +``` + +**Achtung:** +Deaktiviere SSL und Let's Encrypt innerhalb von ISPConfig für diese Webseite, da Proxyserver die Zertifikatsverwaltung übernimmt. +Deaktivere Zertifikatserneuerung auf den Clients + +Werden doppelte Domains eingetragen um auf weiteren Servern Subdomains zu nutzen, muss +1. der Weiterleitungspfad /urldummy/ auf den Servern bei denen die Hauptdomin nungenutzt ist lauten +2. auf dem Server bei dem die Hauptdomain genutzt wird darf der Weiterleitungspfad nicht /urldummy/ lauten +**nur so wird sichergestellt, dass das sync-ispconfig-proxy.sh script die Zertifikatserstellung für diese Dummy Hauptdomains auslässt** + diff --git a/default.proxy_based_server.conf b/default.proxy_based_server.conf new file mode 100644 index 0000000..994c552 --- /dev/null +++ b/default.proxy_based_server.conf @@ -0,0 +1,15 @@ +[server] +#Enter the exact server name registered with ispconfig here. +servername1 +servername2 +#servername3 +servername4 +[blocklist] +#Enter (sub)domains here for which no nginx conf should be created automatically. +domain1.com +sub1.domain2.com +sub2.domain3.com +[nocert] +#Enter domains here for which certificates are created manually. +domain3.com +sub4.domain4.com diff --git a/default.sync-ispconfig-proxy.conf b/default.sync-ispconfig-proxy.conf new file mode 100644 index 0000000..8549391 --- /dev/null +++ b/default.sync-ispconfig-proxy.conf @@ -0,0 +1,5 @@ +DB_HOST="ISPconfigMasterURL" +DB_USER="ISPconfig Master DB readonly User (must be created)" +DB_PASS="password" +DB_NAME="dbispconfig" +NGINX_CONF_DIR="/etc/nginx/conf.d/proxy_generated" diff --git a/proxy-inventory.sh b/proxy-inventory.sh new file mode 100755 index 0000000..1dceb94 --- /dev/null +++ b/proxy-inventory.sh @@ -0,0 +1,144 @@ +#!/bin/bash + +# --- KONFIGURATION & PFADE --- +CONF_FILE="/usr/local/bin/sync-ispconfig-proxy.conf" +SERVER_CONF="/usr/local/bin/proxy_based_server.conf" +CONFIG_DIR="/etc/nginx/conf.d/proxy_generated" +OUTPUT_FILE="/var/www/html/proxy_inventory.txt" +DATE_STR=$(date "+%d.%m.%Y %H:%M:%S") + +[ -f "$CONF_FILE" ] && source "$CONF_FILE" + +# --- GLOBALE FILTER VARIABLEN --- +DOMAIN_REG="" +ID_REG="" +PROXY_REG="" +DNS_REG="" +MODUS_REG="" + +# --- FUNKTIONEN --- + +usage() { + echo "Usage: $(basename "$0") [DOMAIN] [OPTIONS]" + echo "" + echo "Filtert das ISPConfig-Proxy-Inventar basierend auf Domains oder Spaltenwerten." + echo "" + echo "Argumente:" + echo " DOMAIN Exakter Name oder mit Wildcard (z.B. knut-krueger.de oder knut*)" + echo "" + echo "Optionen (kumulativ):" + echo " ID Filtert nach der ISP-Server-ID" + echo " PROXY Filtert nach Proxy-Status (OK, FEHLT)" + echo " DNS Filtert nach DNS-Status (OK, N/A)" + echo " MODUS Filtert nach Modus (NORMAL, DUMMY)" + echo " -h, --help Zeigt diese Hilfe an" + echo "" + echo "Beispiele:" + echo " $(basename "$0") knut* ID 17 Zeigt alle Knut-Domains auf Server 17" + echo " $(basename "$0") ID 112 MODUS DUMMY Zeigt alle Dummys auf Server 112" + echo " $(basename "$0") PROXY FEHLT Zeigt alle nicht synchronisierten Domains" + exit 0 +} + +get_allowed_server_names() { + sed -n "/^\[server\]/,/^\[/p" "$SERVER_CONF" | grep -v '^\[' | grep -v '^#' | sed '/^$/d' | tr -d '\r' +} + +extract_proxy_ip() { + local file=$1 + if [ -f "$file" ]; then + grep -oP 'proxy_pass http://\K[^;]+' "$file" | sed -n ' + s/^\[\(.*\)\].*/\1/p + t + s/^\([^:/]*\).*/\1/p + ' | head -n 1 + else + echo "-" + fi +} + +check_dns() { + local domain=$1 + dig +short AAAA "$domain" | grep -q "::160" && echo "OK" || echo "N/A" +} + +parse_args() { + [[ $# -eq 0 ]] && return + while [[ $# -gt 0 ]]; do + case "${1^^}" in + -H|--HELP) usage ;; + ID) ID_REG="$2"; shift 2 ;; + PROXY) PROXY_REG="$2"; shift 2 ;; + DNS) DNS_REG="$2"; shift 2 ;; + MODUS) MODUS_REG="$2"; shift 2 ;; + *) + if [[ "$1" == *"*"* ]]; then + DOMAIN_REG="${1//\*/.*}" + else + DOMAIN_REG="^$1$" + fi + shift 1 ;; + esac + done +} + +write_header() { + { + echo "================================================================================================================================================" + echo "ISPCONFIG & PROXY INVENTAR - Stand: $DATE_STR" + echo "================================================================================================================================================" + printf "%-45s | %-8s | %-40s | %-8s | %-8s | %-10s\n" "DOMAIN" "S-ID" "IP (ROUTING)" "PROXY" "DNS" "MODUS" + echo "----------------------------------------------|----------|------------------------------------------|----------|----------|----------" + } > "$OUTPUT_FILE" +} + +generate_inventory() { + local temp_raw=$(mktemp) + local servers=$(get_allowed_server_names) + + for current_server in $servers; do + local sql="SELECT wd.domain, wd.server_id, si.ip_address, IFNULL(wd.redirect_path, '') + FROM web_domain wd + JOIN server s ON wd.server_id = s.server_id + JOIN server_ip si ON (wd.ip_address = si.ip_address OR wd.server_id = si.server_id) + WHERE wd.active = 'y' AND s.server_name = '$current_server' + AND wd.type IN ('vhost', 'alias', 'vhostalias', 'subdomain', 'vhostsubdomain');" + + local db_data=$(mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" -N -e "$sql") + + while read -r domain server_id db_ip redirect_path; do + [ -z "$domain" ] && continue + local vhost_file="$CONFIG_DIR/$domain.conf" + local modus="NORMAL"; [[ "$redirect_path" == "/urldummy/" ]] && modus="DUMMY" + local proxy_status="FEHLT"; [ -f "$vhost_file" ] && proxy_status="OK" + + local display_ip="-" + if [ "$modus" == "NORMAL" ] && [ -f "$vhost_file" ]; then + display_ip=$(extract_proxy_ip "$vhost_file") + else + display_ip="$db_ip" + fi + + local dns_status=$(check_dns "$domain") + + # Filter-Prüfung (UND-Verknüpfung) + [[ -n "$DOMAIN_REG" && ! "$domain" =~ $DOMAIN_REG ]] && continue + [[ -n "$ID_REG" && ! "$server_id" =~ ^$ID_REG$ ]] && continue + [[ -n "$PROXY_REG" && ! "$proxy_status" =~ $PROXY_REG ]] && continue + [[ -n "$DNS_REG" && ! "$dns_status" =~ $DNS_REG ]] && continue + [[ -n "$MODUS_REG" && ! "$modus" =~ $MODUS_REG ]] && continue + + printf "%-45s | %-8s | %-40s | %-8s | %-8s | %-10s\n" "$domain" "$server_id" "$display_ip" "$proxy_status" "$dns_status" "$modus" >> "$temp_raw" + done <<< "$db_data" + done + + sort -u "$temp_raw" >> "$OUTPUT_FILE" + echo "================================================================================================================================================" >> "$OUTPUT_FILE" + rm "$temp_raw" +} + +# --- EXECUTION --- +parse_args "$@" +write_header +generate_inventory +cat "$OUTPUT_FILE" diff --git a/remoteip.conf b/remoteip.conf new file mode 100644 index 0000000..6eddad6 --- /dev/null +++ b/remoteip.conf @@ -0,0 +1,8 @@ +RemoteIPHeader X-Forwarded-For + +# Vertrauen zu Hidalgo (Public IPv6 & VPN IPv4) +RemoteIPInternalProxy 2a01:4f8:130:516e::160 +RemoteIPInternalProxy 10.8.1.1 + +# HTTPS-Status für PHP (verhindert Redirect-Loops) +SetEnvIf X-Forwarded-Proto "https" HTTPS=on diff --git a/sync-ispconfig-cleanup.sh b/sync-ispconfig-cleanup.sh new file mode 100644 index 0000000..b125fe3 --- /dev/null +++ b/sync-ispconfig-cleanup.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Vergleicht lokale Configs mit der Datenbank und löscht Überreste +source /usr/local/bin/sync-ispconfig-proxy.conf + +# Hol alle aktuell aktiven Domains aus der DB +SQL_QUERY="SELECT domain FROM web_domain WHERE active = 'y';" +DB_DOMAINS=$(mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" -Bse "$SQL_QUERY") + +# Gehe durch alle Dateien im Proxy-Ordner +for file in /etc/nginx/conf.d/proxy_generated/*.conf; do + [ -e "$file" ] || continue + filename=$(basename "$file" .conf) + + if ! echo "$DB_DOMAINS" | grep -qxw "$filename"; then + echo "Lösche veraltete Config: $filename" + rm "$file" + # Optional: acme.sh Zertifikate auch entfernen + # /root/.acme.sh/acme.sh --remove -d "$filename" --ecc + fi +done + +nginx -t && systemctl reload nginx diff --git a/sync-ispconfig-proxy.sh b/sync-ispconfig-proxy.sh new file mode 100755 index 0000000..7bb3fa6 --- /dev/null +++ b/sync-ispconfig-proxy.sh @@ -0,0 +1,167 @@ +#!/bin/bash + +# ============================================================================== +# ISPConfig Proxy Sync Script - Version 3.34 (Fix: Auto-Subdomain Support) +# ============================================================================== + +# --- KONFIGURATION --- +CONF_FILE="/usr/local/bin/sync-ispconfig-proxy.conf" +SERVER_LIST="/usr/local/bin/proxy_based_server.conf" +LAST_ID_FILE="/var/local/sync-ispconfig-last-id" + +# --- GLOBALE VARIABLEN --- +TARGET_DOMAIN="" +FORCE_ALL=false +RENEW_CERT=false +TEST_MODE=false +DEBUG_MODE=false +UPDATE_LAST_ID=false +PROCESSED_DOMAINS=" " + +# --- FUNKTIONEN --- + +load_config() { + [ -f "$CONF_FILE" ] && source "$CONF_FILE" || { echo "Fehler: $CONF_FILE fehlt"; exit 1; } +} + +parse_params() { + for arg in "$@"; do + case $arg in + test) TEST_MODE=true ;; + -debug) DEBUG_MODE=true ;; + force) FORCE_ALL=true ;; + renew|repair) RENEW_CERT=true ;; + *) TARGET_DOMAIN="$arg" ;; + esac + done +} + +get_server_array() { + sed -n "/^\[server\]/,/^\[/p" "$SERVER_LIST" | grep -v '^\[' | grep -v '^#' | sed '/^$/d' | tr -d '\r' | xargs +} + +write_nginx_config() { + local domain=$1 + local target_ip=$2 + local redirect_path=$3 + local sub_type=$4 # Dies ist das Feld 'subdomain' aus ISPConfig + local cert_dir="/root/.acme.sh/${domain}_ecc" + local config_path="$NGINX_CONF_DIR/${domain}.conf" + + # s_names nur um www erweitern, wenn subdomain auf 'www' steht + local s_names="$domain" + [[ "$sub_type" == "www" ]] && s_names="$domain www.$domain" + + local ssl_block="" + local if_ssl="" + + if [ -f "$cert_dir/fullchain.cer" ]; then + if_ssl="if (\$scheme != \"https\") { return 301 https://\$host\$request_uri; }" + ssl_block="listen 443 ssl http2; listen [::]:443 ssl http2; + ssl_certificate $cert_dir/fullchain.cer; + ssl_certificate_key $cert_dir/${domain}.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers off; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;" + fi + + local proxy_settings=" + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + proxy_read_timeout 90;" + + local local_root="" + [ -d "/var/www/${domain}/web" ] && local_root="root /var/www/${domain}/web/; index index.php index.html;" + + local path_block="" + [[ "$redirect_path" == /* && "$redirect_path" != "/urldummy/" ]] && path_block="location ${redirect_path} { $if_ssl proxy_pass http://[$target_ip]:80${redirect_path}; $proxy_settings proxy_http_version 1.1; proxy_set_header Upgrade \$http_upgrade; proxy_set_header Connection \"upgrade\"; }" + + cat < "$config_path" +server { + $ssl_block + listen 80; listen [::]:80; + server_name $s_names; + $local_root + location /.well-known/acme-challenge/ { root /var/www/html; } + $if_ssl + $path_block + location / { + $( [ -n "$local_root" ] && echo "try_files \$uri \$uri/ @proxy;" || echo "proxy_pass http://[$target_ip]:80;" ) + $( [ -n "$local_root" ] || echo "$proxy_settings" ) + } + $( [ -n "$local_root" ] && echo "location @proxy { proxy_pass http://[$target_ip]:80; $proxy_settings }") +} +EOF +} + +main() { + parse_params "$@" + load_config + local servers=$(get_server_array) + local last_id=$(cat "$LAST_ID_FILE" 2>/dev/null || echo 0) + local global_filter="" + + if [ -n "$TARGET_DOMAIN" ]; then + global_filter="AND (wd.domain = '${TARGET_DOMAIN}' OR parent.domain = '${TARGET_DOMAIN}')" + elif [ "$FORCE_ALL" = false ] && [ "$TEST_MODE" = false ]; then + local raw_ids=$(mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" -Bse "SELECT DISTINCT dbidx FROM sys_datalog WHERE dbtable = 'web_domain' AND datalog_id > $last_id;" 2>/dev/null) + [ $? -ne 0 ] && { echo "FEHLER: SQL-Abfrage fehlgeschlagen." >&2; exit 1; } + local changed_ids=$(echo "$raw_ids" | grep -oE '[0-9]+') + if [ -z "$changed_ids" ]; then exit 0; fi + global_filter="AND wd.domain_id IN ($(echo "$changed_ids" | paste -sd "," -))" + UPDATE_LAST_ID=true + else + UPDATE_LAST_ID=true + fi + + for CURRENT_SERVER in $servers; do + echo "=== Server: $CURRENT_SERVER ===" + local sql_query="SELECT CONCAT_WS('|', wd.domain, IF(wd.ipv6_address != '', wd.ipv6_address, parent.ipv6_address), IFNULL(wd.redirect_path, ''), IFNULL(wd.subdomain, ''), wd.active) + FROM web_domain wd JOIN server s ON wd.server_id = s.server_id LEFT JOIN web_domain parent ON wd.parent_domain_id = parent.domain_id + WHERE TRIM(s.server_name) = TRIM('$CURRENT_SERVER') AND wd.type IN ('vhost', 'alias', 'vhostalias', 'subdomain', 'vhostsubdomain') $global_filter ORDER BY wd.domain_id ASC;" + + local sql_result=$(mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" -N -B -e "$sql_query") + + while read -r row; do + [ -z "$row" ] && continue + local domain=$(echo "$row" | cut -d'|' -f1) + local target_ip=$(echo "$row" | cut -d'|' -f2) + local redirect_path=$(echo "$row" | cut -d'|' -f3) + local sub_type=$(echo "$row" | cut -d'|' -f4) + local is_active=$(echo "$row" | cut -d'|' -f5) + + [[ "$is_active" != "y" || "$PROCESSED_DOMAINS" == *" $domain "* || "$redirect_path" == "/urldummy/" ]] && continue + [[ "$target_ip" == *"222:1808"* || "$target_ip" == *"113"* ]] && target_ip="2a01:4f8:130:516e::113" + [ -z "$target_ip" ] && continue + + echo " [OK] $domain -> $target_ip" + if [ "$TEST_MODE" = false ]; then + write_nginx_config "$domain" "$target_ip" "$redirect_path" "$sub_type" + nginx -t >/dev/null 2>&1 && systemctl reload nginx + + local cert_dir="/root/.acme.sh/${domain}_ecc" + if [ ! -f "$cert_dir/fullchain.cer" ] || [ "$RENEW_CERT" = true ]; then + # ACME Aufruf: Nur -d www hinzufügen, wenn sub_type == "www" + local acme_cmd="/root/.acme.sh/acme.sh --issue -d $domain" + [[ "$sub_type" == "www" ]] && acme_cmd="$acme_cmd -d www.$domain" + $acme_cmd -w /var/www/html --server letsencrypt --ecc --force + + [ -f "$cert_dir/fullchain.cer" ] && write_nginx_config "$domain" "$target_ip" "$redirect_path" "$sub_type" + fi + PROCESSED_DOMAINS="${PROCESSED_DOMAINS}${domain} " + fi + done <<< "$sql_result" + done + + if [ "$TEST_MODE" = false ]; then + nginx -t && systemctl reload nginx + [ "$UPDATE_LAST_ID" = true ] && mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" -Bse "SELECT MAX(datalog_id) FROM sys_datalog;" > "$LAST_ID_FILE" + fi +} + +main "$@"