#!/usr/bin/env bash # ============================================================================= # Freqtrade NordVPN Addon – Proxmox Host Installer # Läuft komplett auf dem PROXMOX HOST. # - Zeigt Freqtrade-Container zur Auswahl (gefiltert nach Tag/Hostname) # - Richtet TUN-Device ein (Container-Neustart) # - Fragt NordVPN-Credentials ab # - Schreibt .env + docker-compose.yml mit Gluetun im Container # - Startet Stack und prüft VPN-IP # # Quelle: https://git.obraasch.de/olli1986/freqtrade-proxmox-deploy # ============================================================================= set -euo pipefail RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m' info() { echo -e "${CYAN}[INFO]${NC} $*"; } success() { echo -e "${GREEN}[ OK ]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } error() { echo -e "${RED}[FAIL]${NC} $*"; exit 1; } step() { echo -e "\n${BOLD}${BLUE}▶ $*${NC}"; } ask() { local prompt="$1" default="${2:-}" [[ -n "$default" ]] \ && echo -ne "${YELLOW} ➜ ${NC}${prompt} [${CYAN}${default}${NC}]: " \ || echo -ne "${YELLOW} ➜ ${NC}${prompt}: " read -r REPLY; REPLY="${REPLY:-$default}" } ask_secret() { echo -ne "${YELLOW} ➜ ${NC}${1}: " read -rs REPLY; echo } confirm() { echo -ne "${YELLOW} ➜ ${NC}${1} [${CYAN}j${NC}/N]: " read -r ans; [[ "$ans" =~ ^[jJyY]$ ]] } # ── Banner ───────────────────────────────────────────────────────────────────── print_banner() { clear echo -e "${BOLD}${BLUE}" cat << 'BANNER' _ _ _ _ ___ _ _ | \| |___ _ _ __| \ \ / / | \| | | .` / _ \ '_/ _` \ V /| | .` | |_|\_\___/_| \__,_|\_/ |_|_|\_| NordVPN Addon – Proxmox Host Installer BANNER echo -e "${NC}" echo -e " ${CYAN}Richtet TUN + Gluetun/NordVPN vollautomatisch ein${NC}" echo -e " ${CYAN}Quelle: git.obraasch.de/olli1986/freqtrade-proxmox-deploy${NC}\n" } # ── Proxmox-Check ────────────────────────────────────────────────────────────── check_host() { step "Proxmox Host prüfen" [[ $EUID -ne 0 ]] && error "Bitte als root ausführen!" command -v pct &>/dev/null || error "pct nicht gefunden – läuft das auf Proxmox?" command -v pvesh &>/dev/null || error "pvesh nicht gefunden." success "Proxmox Host erkannt" } # ── Container-Auswahl ───────────────────────────────────────────────────────── select_container() { step "Freqtrade Container auswählen" # Alle Container auflisten mit Tag-Info local -a ct_list=() while IFS= read -r line; do local ct_id status ct_id=$(echo "$line" | awk '{print $1}') status=$(echo "$line" | awk '{print $2}') [[ "$ct_id" =~ ^[0-9]+$ ]] || continue local hostname tags marker="" hostname=$(pct config "$ct_id" 2>/dev/null | awk '/^hostname:/{print $2}' || echo "?") tags=$(pct config "$ct_id" 2>/dev/null | awk '/^tags:/{print $2}' || echo "") # Freqtrade-Container markieren if echo "$tags" | grep -qi "freqtrade"; then marker="${GREEN}★${NC}" else marker=" " fi ct_list+=("${ct_id}|${hostname}|${status}|${marker}") done < <(pct list 2>/dev/null | tail -n +2) if [[ ${#ct_list[@]} -eq 0 ]]; then error "Keine Container gefunden!" fi # Nur Container mit Tag "freqtrade" anzeigen local -a display_list=() for entry in "${ct_list[@]}"; do if echo "$entry" | grep -q "★"; then display_list+=("$entry") fi done if [[ ${#display_list[@]} -eq 0 ]]; then error "Keine Container mit Tag 'freqtrade' gefunden!\n Bitte zuerst freqtrade-lxc-install.sh ausführen oder manuell setzen:\n pct set --tags freqtrade" fi echo echo -e " ${BOLD}Nr ID Hostname Status ${GREEN}★${NC}${BOLD} = Freqtrade${NC}" echo -e " ${BLUE}─────────────────────────────────────────${NC}" local i=1 for entry in "${display_list[@]}"; do IFS='|' read -r cid cname cstatus cmark <<< "$entry" printf " ${CYAN}%-4s${NC} %-8s %-18s %-10s %b\n" "$i" "$cid" "$cname" "$cstatus" "$cmark" ((i++)) done echo local sel_nr=1 if [[ ${#display_list[@]} -gt 1 ]]; then ask "Container Nummer wählen" "1" sel_nr="$REPLY" else info "Nur ein Container vorhanden – wird automatisch gewählt." fi IFS='|' read -r SEL_ID SEL_NAME SEL_STATUS _ <<< "${display_list[$((sel_nr-1))]}" [[ -z "${SEL_ID:-}" ]] && error "Ungültige Auswahl!" success "Gewählt: CT ${SEL_ID} | ${SEL_NAME} | ${SEL_STATUS}" # Starten falls nötig if [[ "$SEL_STATUS" != "running" ]]; then warn "Container ist nicht running. Starte..." pct start "$SEL_ID" sleep 5 success "Container gestartet" fi } # ── TUN-Device einrichten ────────────────────────────────────────────────────── setup_tun() { step "TUN-Device für CT ${SEL_ID} prüfen" local conf="/etc/pve/lxc/${SEL_ID}.conf" [[ ! -f "$conf" ]] && error "LXC-Config nicht gefunden: $conf" if grep -q "lxc.cgroup2.devices.allow: c 10:200 rwm" "$conf" && \ grep -q "lxc.mount.entry: /dev/net/tun" "$conf"; then success "TUN-Device bereits konfiguriert" return fi info "Trage TUN-Device in Config ein und starte Container neu..." cat >> "$conf" << 'TUNCFG' # TUN-Device fuer NordVPN/Gluetun (eingetragen von proxmox-vpn-install.sh) lxc.cgroup2.devices.allow: c 10:200 rwm lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file TUNCFG pct stop "$SEL_ID" sleep 3 pct start "$SEL_ID" local tries=0 info "Warte auf Netzwerk..." until pct exec "$SEL_ID" -- bash -c "ip route | grep -q default" 2>/dev/null; do sleep 3; tries=$((tries+1)); echo -n "." [[ $tries -ge 15 ]] && error "Netzwerk nach Neustart nicht erreichbar!" done echo success "Container neu gestartet mit TUN-Device" } # ── NordVPN Konfiguration abfragen ───────────────────────────────────────────── collect_vpn_config() { step "NordVPN Konfiguration" echo -e "\n ${BOLD}Service-Credentials${NC} findest du unter:" echo -e " ${CYAN}https://my.nordaccount.com → NordVPN → 'Set up NordVPN manually'${NC}" echo -e " ${RED} Nicht deine E-Mail/Passwort – die Service-Credentials!${NC}\n" ask "NordVPN Service-Username" NORD_USER="$REPLY" [[ -z "$NORD_USER" ]] && error "Username darf nicht leer sein!" stty echo 2>/dev/null || true ask "NordVPN Service-Passwort" NORD_PASS="$REPLY" [[ -z "$NORD_PASS" ]] && error "Passwort darf nicht leer sein!" echo echo -e " ${BOLD}VPN-Server Modus:${NC}" echo -e " ${CYAN}1)${NC} Serverland – normaler Server (IP kann wechseln)" echo -e " ${CYAN}2)${NC} Hostname – Dedicated-IP oder spez. Server pinnen" ask "Auswahl [1/2]" "1" VPN_MODE="$REPLY" VPN_PROTO="udp" # Start mit UDP, automatischer Fallback auf TCP bei TLS-Fehler if [[ "$VPN_MODE" == "2" ]]; then echo -e "\n ${YELLOW}Hostnamen Format: de507.nordvpn.com, de654.nordvpn.com etc.${NC}" echo -e " ${YELLOW}Wichtig: NordVPN zeigt oft neue Server die Gluetun noch nicht kennt.${NC}" echo -e " ${YELLOW}Falls 'Ungültiger Hostname' erscheint → die angezeigten gültigen Server nutzen.${NC}" echo -e " ${CYAN}Tipp: Modus 1 (Serverland) ist einfacher – Gluetun wählt selbst.${NC}" ask "Hostname (z.B. de507.nordvpn.com)" VPN_HOSTNAME="$REPLY"; VPN_COUNTRY="" else ask "Serverland" "Germany" VPN_COUNTRY="$REPLY"; VPN_HOSTNAME="" fi # FreqUI Port aus bestehender config ermitteln local current_port current_port=$(pct exec "$SEL_ID" -- bash -c \ "grep -oP '0\.0\.0\.0:\K[0-9]+(?=:[0-9]+)' /opt/docker-projekte/freqtrade/docker-compose.yml 2>/dev/null | head -1 || echo 8080" \ 2>/dev/null || echo "8080") ask "FreqUI Port" "$current_port" FT_PORT="$REPLY" echo -e "\n${BOLD}${BLUE}══════════ Zusammenfassung ══════════${NC}" echo -e " Container: ${CYAN}CT ${SEL_ID} | ${SEL_NAME}${NC}" echo -e " NordVPN User: ${CYAN}${NORD_USER}${NC}" [[ -n "$VPN_HOSTNAME" ]] \ && echo -e " VPN Server: ${CYAN}${VPN_HOSTNAME}${NC}" \ || echo -e " VPN Land: ${CYAN}${VPN_COUNTRY}${NC}" echo -e " FreqUI Port: ${CYAN}${FT_PORT}${NC}" echo -e "${BOLD}${BLUE}=====================================\n${NC}" confirm "Einrichten?" || { info "Abgebrochen."; exit 0; } } # ── Konfiguration in Container schreiben ────────────────────────────────────── write_vpn_config() { step "VPN-Konfiguration in Container schreiben" local FT_DIR="/opt/docker-projekte/freqtrade" local NORD_USER_B64 NORD_PASS_B64 NORD_USER_B64=$(printf '%s' "$NORD_USER" | base64 -w0) NORD_PASS_B64=$(printf '%s' "$NORD_PASS" | base64 -w0) local VPN_SERVER_LINE="" [[ -n "$VPN_HOSTNAME" ]] \ && VPN_SERVER_LINE=" - SERVER_HOSTNAMES=${VPN_HOSTNAME}" \ || VPN_SERVER_LINE=" - SERVER_COUNTRIES=${VPN_COUNTRY}" # Backup pct exec "$SEL_ID" -- bash -c \ "cp ${FT_DIR}/docker-compose.yml ${FT_DIR}/docker-compose.yml.bak-\$(date +%Y%m%d_%H%M%S) 2>/dev/null || true" success "Backup erstellt" # Stack stoppen pct exec "$SEL_ID" -- bash -c \ "cd ${FT_DIR} && docker compose down 2>/dev/null || true" success "Stack gestoppt" # .env schreiben pct exec "$SEL_ID" -- bash -c " echo \"OPENVPN_USER=\$(echo ${NORD_USER_B64} | base64 -d)\" > ${FT_DIR}/.env echo \"OPENVPN_PASSWORD=\$(echo ${NORD_PASS_B64} | base64 -d)\" >> ${FT_DIR}/.env chmod 600 ${FT_DIR}/.env " success ".env geschrieben (chmod 600)" # docker-compose.yml schreiben # Architektur: Gluetun als HTTP-Proxy (Port 8888) # → Freqtrade behält eigenes Netzwerk (WebUI bleibt auf LXC-IP erreichbar) # → Nur Binance-Traffic läuft via HTTPS_PROXY durch den VPN-Tunnel pct exec "$SEL_ID" -- bash -c "cat > ${FT_DIR}/docker-compose.yml << 'COMPOSEEOF' # Freqtrade + NordVPN/Gluetun | generiert von proxmox-vpn-install.sh # # Architektur: # Freqtrade (eigenes Netz, WebUI :${FT_PORT} lokal erreichbar) # |-- Binance API --> HTTP_PROXY --> Gluetun --> NordVPN --> Internet # # WebUI bleibt erreichbar unter: http://LXC-IP:${FT_PORT} services: # ── VPN-Proxy ────────────────────────────────────────────────────────────── # Gluetun läuft als HTTP-Proxy auf Port 8888. # Freqtrade schickt nur den Exchange-Traffic darüber. gluetun: image: qmcgaw/gluetun:latest container_name: gluetun restart: unless-stopped cap_add: - NET_ADMIN devices: - /dev/net/tun:/dev/net/tun environment: - VPN_SERVICE_PROVIDER=nordvpn - VPN_TYPE=openvpn - OPENVPN_USER=\${OPENVPN_USER} - OPENVPN_PASSWORD=\${OPENVPN_PASSWORD} ${VPN_SERVER_LINE} # HTTP-Proxy aktivieren – darüber leitet Freqtrade den Binance-Traffic - HTTPPROXY=on - HTTPPROXY_LOG=off - HTTPPROXY_STEALTH=on ports: # Kein WebUI-Port hier – Freqtrade hat sein eigenes Netzwerk - \"127.0.0.1:8888:8888\" volumes: - gluetun_data:/gluetun healthcheck: test: [\"CMD-SHELL\", \"wget -qO- https://ipinfo.io/ip || exit 1\"] interval: 30s timeout: 15s retries: 5 start_period: 45s # ── Freqtrade ─────────────────────────────────────────────────────────────── # Eigenes Netzwerk → WebUI bleibt auf LXC-IP:${FT_PORT} erreichbar. # Exchange-Traffic geht via HTTP_PROXY durch Gluetun. freqtrade: image: freqtradeorg/freqtrade:stable restart: unless-stopped container_name: freqtrade depends_on: gluetun: condition: service_started ports: - \"0.0.0.0:${FT_PORT}:8080\" environment: # Nur HTTPS-Traffic (Binance API) läuft durch den VPN-Proxy - HTTPS_PROXY=http://gluetun:8888 - HTTP_PROXY=http://gluetun:8888 # Lokale Verbindungen (WebUI, DB) NICHT durch Proxy - NO_PROXY=localhost,127.0.0.1 volumes: - \"./user_data:/freqtrade/user_data\" command: > trade --logfile /freqtrade/user_data/logs/freqtrade.log --db-url sqlite:////freqtrade/user_data/tradesv3.sqlite --config /freqtrade/user_data/config.json --strategy SampleStrategy volumes: gluetun_data: COMPOSEEOF" success "docker-compose.yml geschrieben" } # ── Stack starten mit automatischem UDP→TCP Fallback ────────────────────────── start_and_verify() { step "VPN-Stack starten" local FT_DIR="/opt/docker-projekte/freqtrade" local vpn_ok=false VPN_IP="" # Bei Hostname-Modus: Server in Gluetun-DB eintragen (Dedicated-IP Support) if [[ -n "${VPN_HOSTNAME:-}" ]]; then info "Lade aktuelle Gluetun Server-Datenbank von GitHub..." local DB_PATH="/var/lib/docker/volumes/freqtrade_gluetun_data/_data/servers.json" local SERVERS_URL="https://raw.githubusercontent.com/qdm12/gluetun/refs/heads/master/internal/storage/servers.json" # Volume-Verzeichnis anlegen falls noch nicht vorhanden pct exec "$SEL_ID" -- bash -c "mkdir -p /var/lib/docker/volumes/freqtrade_gluetun_data/_data" 2>/dev/null || true # Aktuelle servers.json von GitHub laden (immer aktuell, richtiges Format) if pct exec "$SEL_ID" -- bash -c \ "curl -fsSL '${SERVERS_URL}' -o ${DB_PATH} 2>/dev/null"; then local srv_count srv_count=$(pct exec "$SEL_ID" -- bash -c \ "python3 -c \"import json; d=json.load(open('${DB_PATH}')); print(len(d.get('nordvpn',{}).get('servers',[])))\" 2>/dev/null" || echo "?") success "Server-Datenbank geladen (${srv_count} NordVPN-Server)" else warn "GitHub-Download fehlgeschlagen – fahre trotzdem fort" fi # IP per DNS auflösen local server_ip server_ip=$(pct exec "$SEL_ID" -- bash -c "getent hosts ${VPN_HOSTNAME} 2>/dev/null | awk '{print \$1}' | head -1" 2>/dev/null || echo "") [[ -z "$server_ip" ]] && server_ip=$(pct exec "$SEL_ID" -- bash -c "nslookup ${VPN_HOSTNAME} 2>/dev/null | awk '/Address:/ && !/#/{print \$2}' | head -1" 2>/dev/null || echo "") if [[ -z "$server_ip" ]]; then warn "DNS-Auflösung für ${VPN_HOSTNAME} fehlgeschlagen – Hostname trotzdem versuchen" else info "DNS: ${VPN_HOSTNAME} → ${server_ip}" # Eintrag mit korrektem Gluetun-Format hinzufügen # Format: {"vpn":"openvpn","country":"Germany","region":"Europe","city":"Frankfurt", # "hostname":"de1370.nordvpn.com","tcp":true,"udp":true,"ips":["1.2.3.4"]} pct exec "$SEL_ID" -- bash -c "python3 << PYEOF import json, sys, copy path = '${DB_PATH}' hostname = '${VPN_HOSTNAME}' ip = '${server_ip}' try: with open(path) as f: db = json.load(f) except Exception as e: print(f'Lesefehler: {e}'); sys.exit(1) servers = db.get('nordvpn', {}).get('servers', []) # Bereits vorhanden? if any(s.get('hostname') == hostname for s in servers): print(f'Bereits vorhanden: {hostname}') sys.exit(0) # Vorhandenen deutschen OpenVPN-Eintrag als Template klonen (garantiert richtiges Format) template = next( (s for s in servers if s.get('country','').lower() == 'germany' and s.get('vpn') == 'openvpn'), None ) if template is None: template = next((s for s in servers if s.get('vpn') == 'openvpn'), None) if template is None: print('Kein Template gefunden'); sys.exit(1) entry = copy.deepcopy(template) entry['hostname'] = hostname entry['ips'] = [ip] # Format ist immer ips:[] laut GitHub-Quelle servers.append(entry) db['nordvpn']['servers'] = servers with open(path, 'w') as f: json.dump(db, f, indent=2) print(f'Eingetragen: {hostname} ({ip})') PYEOF" 2>&1 && success "${VPN_HOSTNAME} (${server_ip}) in Gluetun-DB eingetragen" || { warn "DB-Eintrag fehlgeschlagen"; } fi fi # Protokoll-Reihenfolge: UDP zuerst, dann automatisch TCP for proto in udp tcp; do [[ "$proto" == "tcp" ]] && info "UDP fehlgeschlagen – versuche TCP automatisch..." # Protokoll in .env setzen (Gluetun liest OPENVPN_PROTOCOL) pct exec "$SEL_ID" -- bash -c "sed -i '/^OPENVPN_PROTOCOL=/d' ${FT_DIR}/.env; echo 'OPENVPN_PROTOCOL=${proto}' >> ${FT_DIR}/.env" # Stack starten (ggf. neu) pct exec "$SEL_ID" -- bash -c "cd ${FT_DIR} && docker compose down 2>/dev/null || true" pct exec "$SEL_ID" -- bash -c "cd ${FT_DIR} && docker compose up -d 2>&1" || true info "Warte auf VPN-Verbindung via ${proto^^}..." local tries=0 local tls_error=false while [[ $tries -lt 18 ]]; do sleep 5; tries=$((tries+1)) local elapsed=$(( tries * 5 )) echo -ne " ${CYAN}[INFO]${NC} ${proto^^} – ${elapsed}s..." local log_snippet log_snippet=$(pct exec "$SEL_ID" -- bash -c "docker logs --since 30s gluetun 2>&1" 2>/dev/null || echo "") # Auth-Fehler → sofort abbrechen, kein Retry if echo "$log_snippet" | grep -qi "AUTH_FAILED\|auth.*fail\|wrong.*password\|incorrect.*password"; then echo echo -e " ${RED}✗ Authentifizierung fehlgeschlagen!${NC}" echo -e " ${YELLOW}Service-Credentials prüfen:${NC}" echo -e " ${CYAN}https://my.nordaccount.com → NordVPN → Set up NordVPN manually${NC}" pct exec "$SEL_ID" -- bash -c "cd ${FT_DIR} && docker compose down 2>/dev/null || true" if confirm "Neue Credentials eingeben?"; then collect_vpn_config write_vpn_config start_and_verify return else error "Abgebrochen." fi fi # Hostname ungültig → gültige Server aus Log parsen und zur Auswahl anbieten if echo "$log_snippet" | grep -qi "hostname.*not valid\|not one of the possible choices"; then echo echo -e " ${RED}✗ Ungültiger Hostname: ${VPN_HOSTNAME}${NC}" echo -e " ${YELLOW}Nicht in Gluetuns Server-Datenbank. Bitte einen der folgenden wählen:${NC} " # Mehr Log holen – die Server-Liste ist sehr lang local full_log valid_servers full_log=$(pct exec "$SEL_ID" -- bash -c "docker logs gluetun 2>&1" 2>/dev/null || echo "") # Nur deutsche Server aus der Liste filtern (de*) valid_servers=$(echo "$full_log" | grep -oP 'de[0-9]+\.nordvpn\.com' | sort -uV | head -20) # Falls keine deutschen → alle zeigen [[ -z "$valid_servers" ]] && valid_servers=$(echo "$full_log" | grep -oP '[a-z]{2}[0-9]+\.nordvpn\.com' | sort -u | head -20) if [[ -n "$valid_servers" ]]; then local i=1 while IFS= read -r srv; do printf " ${CYAN}%2s)${NC} %s " "$i" "$srv" i=$((i+1)) done <<< "$valid_servers" echo echo -ne "${YELLOW} ➜ ${NC}Nummer wählen oder Hostname direkt eingeben: " read -r sel if [[ "$sel" =~ ^[0-9]+$ ]]; then VPN_HOSTNAME=$(echo "$valid_servers" | sed -n "${sel}p") elif [[ -n "$sel" ]]; then VPN_HOSTNAME="$sel" fi if [[ -n "$VPN_HOSTNAME" ]]; then info "Neuer Hostname: $VPN_HOSTNAME – schreibe Config neu..." pct exec "$SEL_ID" -- bash -c "cd ${FT_DIR} && docker compose down 2>/dev/null || true" write_vpn_config tries=0; tls_error=false pct exec "$SEL_ID" -- bash -c "cd ${FT_DIR} && docker compose up -d 2>&1" || true continue fi else echo -e " ${YELLOW}Keine Server im Log gefunden. Modus 1 (Serverland) empfohlen.${NC}" fi pct exec "$SEL_ID" -- bash -c "cd ${FT_DIR} && docker compose down 2>/dev/null || true" error "Abgebrochen." fi # TLS-Fehler → nächstes Protokoll versuchen if echo "$log_snippet" | grep -q "TLS Error\|TLS key negotiation"; then tls_error=true break fi # Verbunden? if pct exec "$SEL_ID" -- bash -c "docker exec gluetun wget -qO- https://ipinfo.io/ip 2>/dev/null" &>/dev/null; then vpn_ok=true break fi done echo [[ "$vpn_ok" == "true" ]] && break # Bei TCP auch TLS-Fehler → endgültig fehlgeschlagen if [[ "$proto" == "tcp" ]]; then echo -e " ${RED}✗ VPN konnte weder via UDP noch TCP verbunden werden.${NC}" echo -e " ${YELLOW}Mögliche Ursachen:${NC}" echo -e " ${YELLOW}· Falsche NordVPN Service-Credentials${NC}" echo -e " ${YELLOW}· Server temporär nicht erreichbar – anderen Server wählen${NC}" echo -e " ${YELLOW}· Netzwerkproblem im LXC${NC}" pct exec "$SEL_ID" -- bash -c "cd ${FT_DIR} && docker compose down 2>/dev/null || true" if confirm "Neue Konfiguration eingeben?"; then collect_vpn_config write_vpn_config start_and_verify return else error "Abgebrochen. Stack gestoppt." fi fi done [[ "$vpn_ok" != "true" ]] && error "VPN-Verbindung fehlgeschlagen." # Freqtrade prüfen sleep 6 if pct exec "$SEL_ID" -- bash -c \ "docker ps --filter name=freqtrade --filter status=running | grep -q freqtrade" 2>/dev/null; then success "Freqtrade läuft" else warn "Freqtrade prüfen: pct exec ${SEL_ID} -- docker logs freqtrade" fi } # ── Ergebnis anzeigen ────────────────────────────────────────────────────────── show_result() { local LXC_IP LXC_IP=$(pct exec "$SEL_ID" -- hostname -I 2>/dev/null | awk '{print $1}' || echo "unbekannt") echo echo -e "${BOLD}${GREEN}╔══════════════════════════════════════════╗${NC}" echo -e "${BOLD}${GREEN}║ ✅ NordVPN erfolgreich eingerichtet! ║${NC}" echo -e "${BOLD}${GREEN}╚══════════════════════════════════════════╝${NC}" echo echo -e " ${BOLD}FreqUI (lokal erreichbar):${NC}" echo -e " ${CYAN}http://${LXC_IP}:${FT_PORT}${NC}" echo echo -e " ${BOLD}Binance-Traffic läuft über NordVPN:${NC}" echo -e " Ausgehende IP: ${BOLD}${GREEN}${VPN_IP}${NC}" echo echo -e " ${BOLD}${YELLOW}⚠ Binance API-Key anpassen:${NC}" echo -e " ${YELLOW}Binance → API Management → IP-Beschränkung${NC}" echo -e " ${YELLOW}Diese NordVPN-IP eintragen: ${BOLD}${VPN_IP}${NC}" echo echo -e " ${BOLD}Architektur:${NC}" echo -e " ${CYAN}WebUI${NC} → direkt über LXC-IP (kein VPN)" echo -e " ${CYAN}Binance${NC} → HTTP_PROXY → Gluetun → NordVPN" echo echo -e " ${BOLD}Nützliche Befehle (im LXC):${NC}" echo -e " ${CYAN}docker exec gluetun wget -qO- https://ipinfo.io/ip${NC} # VPN-IP prüfen" echo -e " ${CYAN}docker logs gluetun${NC} # VPN-Logs" echo -e " ${CYAN}docker logs freqtrade${NC} # Freqtrade-Logs" echo -e " ${CYAN}cd /opt/docker-projekte/freqtrade && docker compose restart${NC}" echo } # ── Hauptprogramm ────────────────────────────────────────────────────────────── main() { print_banner check_host select_container setup_tun collect_vpn_config write_vpn_config start_and_verify show_result } main "$@"