Version 1

This commit is contained in:
root
2026-01-28 11:44:12 +01:00
parent 58e753e93c
commit 1b1b965c8c
7 changed files with 454 additions and 0 deletions

93
INSTALL.md Normal file
View File

@@ -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
<Proxy *>
Order allow,deny
Allow from all
</Proxy>
# 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**

View File

@@ -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

View File

@@ -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"

144
proxy-inventory.sh Executable file
View File

@@ -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 <n> Filtert nach der ISP-Server-ID"
echo " PROXY <status> Filtert nach Proxy-Status (OK, FEHLT)"
echo " DNS <status> Filtert nach DNS-Status (OK, N/A)"
echo " MODUS <typ> 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"

8
remoteip.conf Normal file
View File

@@ -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

22
sync-ispconfig-cleanup.sh Normal file
View File

@@ -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

167
sync-ispconfig-proxy.sh Executable file
View File

@@ -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 <<EOF > "$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 "$@"