#!/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!" ask_secret "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" if [[ "$VPN_MODE" == "2" ]]; then echo -e "\n ${YELLOW}Gültige Hostnamen haben das Format: de507.nordvpn.com, de654.nordvpn.com etc.${NC}" echo -e " ${YELLOW}Beispiele für Deutschland: de507, de654, de675 ... de1118${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_healthy 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 Fehlerbehandlung ───────────────────────────────────────── start_and_verify() { step "VPN-Stack starten" local FT_DIR="/opt/docker-projekte/freqtrade" local MAX_ATTEMPTS=3 local attempt=1 while [[ $attempt -le $MAX_ATTEMPTS ]]; do echo -e "\n ${CYAN}Versuch ${attempt}/${MAX_ATTEMPTS}...${NC}" # Stack starten pct exec "$SEL_ID" -- bash -c "cd ${FT_DIR} && docker compose up -d 2>&1" || true # Auf Gluetun warten (bis zu 60s) info "Warte auf VPN-Verbindung (bis zu 60s)..." local tries=0 local vpn_ok=false while [[ $tries -lt 12 ]]; do sleep 5; echo -n "."; tries=$((tries+1)) 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 if [[ "$vpn_ok" == "true" ]]; then VPN_IP=$(pct exec "$SEL_ID" -- bash -c \ "docker exec gluetun wget -qO- https://ipinfo.io/ip 2>/dev/null" || echo "unbekannt") success "VPN verbunden! Ausgehende IP: ${BOLD}${GREEN}${VPN_IP}${NC}" break fi # Fehleranalyse echo warn "VPN-Verbindung fehlgeschlagen. Gluetun-Log:" echo -e "${YELLOW}─────────────────────────────────────────${NC}" pct exec "$SEL_ID" -- bash -c \ "docker logs gluetun 2>&1 | grep -E 'ERROR|WARN|hostname|credentials|password|Invalid' | tail -20" \ 2>/dev/null || true echo -e "${YELLOW}─────────────────────────────────────────${NC}" # Häufige Fehler erkennen und erklären local gluetun_errors gluetun_errors=$(pct exec "$SEL_ID" -- bash -c \ "docker logs gluetun 2>&1 | tail -30" 2>/dev/null || echo "") if echo "$gluetun_errors" | grep -qi "hostname.*not valid\|not one of the possible choices"; then echo -e "\n ${RED}FEHLER: Ungültiger Hostname!${NC}" echo -e " ${YELLOW}Gültige deutsche Server haben das Format: de507.nordvpn.com${NC}" echo -e " ${YELLOW}Sieh die Fehlermeldung für eine Liste gültiger Hostnamen.${NC}" elif echo "$gluetun_errors" | grep -qi "auth\|credential\|password\|unauthorized\|401"; then echo -e "\n ${RED}FEHLER: Ungültige Credentials!${NC}" echo -e " ${YELLOW}Bitte Service-Credentials prüfen unter:${NC}" echo -e " ${CYAN}https://my.nordaccount.com → NordVPN → Set up NordVPN manually${NC}" fi pct exec "$SEL_ID" -- bash -c "cd ${FT_DIR} && docker compose down 2>/dev/null || true" if [[ $attempt -lt $MAX_ATTEMPTS ]]; then echo if confirm "Andere Konfiguration versuchen?"; then collect_vpn_config write_vpn_config else error "Abgebrochen. Stack wurde gestoppt." fi fi attempt=$((attempt+1)) done [[ "$vpn_ok" != "true" ]] && error "VPN konnte nach ${MAX_ATTEMPTS} Versuchen nicht verbunden werden." # 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 "$@"