diff --git a/scripts/proxmox-vpn-install-dev.sh b/scripts/proxmox-vpn-install-dev.sh index 4b2e7be..b043db6 100644 --- a/scripts/proxmox-vpn-install-dev.sh +++ b/scripts/proxmox-vpn-install-dev.sh @@ -1,14 +1,12 @@ #!/usr/bin/env bash # ============================================================================= -# Freqtrade NordVPN Addon – Proxmox Host Installer +# Freqtrade NordVPN Addon – Proxmox Host Installer (Custom Config Edition) # 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 +# - Richtet TUN-Device ein +# - Erstellt VPN-Ordner & Berechtigungen +# - Fragt NordVPN-Credentials ab & pausiert für custom.conf +# - Ermittelt automatisch das lokale Subnetz +# - Schreibt docker-compose.yml & startet den Stack # ============================================================================= set -euo pipefail @@ -29,11 +27,6 @@ ask() { 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]$ ]] @@ -44,15 +37,14 @@ print_banner() { clear echo -e "${BOLD}${BLUE}" cat << 'BANNER' - _ _ _ _ ___ _ _ + _ _ _ _ ___ _ _ | \| |___ _ _ __| \ \ / / | \| | | .` / _ \ '_/ _` \ V /| | .` | |_|\_\___/_| \__,_|\_/ |_|_|\_| - NordVPN Addon – Proxmox Host Installer + NordVPN Custom Config 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" + echo -e " ${CYAN}Automatisiertes Setup für feste IPs via custom.conf${NC}\n" } # ── Proxmox-Check ────────────────────────────────────────────────────────────── @@ -60,548 +52,191 @@ 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=() + local -a display_list=() + while IFS= read -r line; do - local ct_id status + local ct_id status hostname tags 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=" " + display_list+=("${ct_id}|${hostname}|${status}") 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 + [[ ${#display_list[@]} -eq 0 ]] && error "Keine Container mit Tag 'freqtrade' gefunden!" - # 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 " ${BOLD}Nr ID Hostname Status${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" + IFS='|' read -r cid cname cstatus <<< "$entry" + printf " ${CYAN}%-4s${NC} %-8s %-18s %-10s\n" "$i" "$cid" "$cname" "$cstatus" ((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))]}" + [[ ${#display_list[@]} -gt 1 ]] && { ask "Container Nummer wählen" "1"; sel_nr="$REPLY"; } + + 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}" - 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" + info "Starte Container..." + pct start "$SEL_ID" && sleep 5 fi } -# ── TUN-Device einrichten ────────────────────────────────────────────────────── -setup_tun() { - step "TUN-Device für CT ${SEL_ID} prüfen" +# ── TUN-Device & Ordner einrichten ───────────────────────────────────────────── +setup_system() { + step "System & VPN-Ordner vorbereiten" + + # 1. TUN-Device in LXC Config schreiben 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) + if ! grep -q "lxc.mount.entry: /dev/net/tun" "$conf"; then + info "Trage TUN-Device in Config ein..." + cat >> "$conf" << 'TUNCFG' 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" + sleep 5 + success "TUN-Device eingerichtet & Container neu gestartet" + else + success "TUN-Device war bereits konfiguriert" + fi - 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" + # 2. Ordner und Datei erstellen & Rechte setzen + pct exec "$SEL_ID" -- bash -c " + mkdir -p /opt/docker-projekte/VPN + touch /opt/docker-projekte/VPN/custom.conf + chown 1000:1000 -R /opt/docker-projekte/VPN + " + success "VPN-Ordner und custom.conf erstellt (/opt/docker-projekte/VPN)" } -# ── 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" - +# ── Konfiguration abfragen & Datei befüllen lassen ───────────────────────────── +collect_and_wait() { + step "NordVPN Credentials & Config" + + echo -e " ${YELLOW}Hinweis: Bitte nutze die Service-Credentials von der NordVPN-Website!${NC}" 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 docker-compose.yml lesen (ändert sich nicht) - FT_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") - info "FreqUI Port: ${FT_PORT} (aus bestehender Konfiguration)" - - 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; } + + echo -e "\n${BOLD}${CYAN}=== WICHTIGER SCHRITT ===${NC}" + echo -e "Bitte öffne nun auf deinem Server (oder via SFTP) die Datei im Container:" + echo -e " ${BOLD}/opt/docker-projekte/VPN/custom.conf${NC}" + echo -e "Füge dort den kompletten Text deiner NordVPN-Konfigurationsdatei (.ovpn) ein und speichere." + echo -e "Tipp: Du kannst auch in einer zweiten Konsole ausführen:" + echo -e " pct enter ${SEL_ID} und dann nano /opt/docker-projekte/VPN/custom.conf" + + while true; do + echo + if confirm "Hast du die custom.conf befüllt und gespeichert?"; then + # Kurzer Check, ob die Datei nicht mehr leer ist + local size + size=$(pct exec "$SEL_ID" -- stat -c%s /opt/docker-projekte/VPN/custom.conf 2>/dev/null || echo 0) + if [[ "$size" -gt 10 ]]; then + success "Datei ist befüllt!" + break + else + warn "Die Datei scheint noch leer zu sein. Bitte überprüfe das." + fi + fi + done } -# ── Konfiguration in Container schreiben ────────────────────────────────────── -write_vpn_config() { - step "VPN-Konfiguration in Container schreiben" - # Image: custom build bei Dedicated-IP, sonst offizielles Image - GLUETUN_IMAGE="${GLUETUN_IMAGE:-qmcgaw/gluetun:latest}" - +# ── Docker Compose schreiben & anwenden ──────────────────────────────────────── +deploy_stack() { + step "Docker Stack anwenden" 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) + + # Lokales Subnetz automatisch ermitteln (z.B. 192.168.178.0/24) + local SUBNET + SUBNET=$(pct exec "$SEL_ID" -- bash -c "ip -o -f inet addr show eth0 | awk '{print \$4}' | sed 's/\.[0-9]\{1,3\}\//.0\//'" 2>/dev/null || echo "192.168.0.0/24") + info "Ermitteltes lokales Subnetz: ${SUBNET}" - local VPN_SERVER_LINE="" - local VPN_CUSTOM_LINE="" - if [[ -n "$VPN_HOSTNAME" ]]; then - # Dedicated-IP: Custom Config direkt mit IP – bypasses Gluetun Hostname-Validierung - VPN_SERVER_LINE=" - VPN_SERVICE_PROVIDER=custom" - VPN_CUSTOM_LINE=" - OPENVPN_CUSTOM_CONFIG=/gluetun/nordvpn-custom.conf" - else - VPN_SERVER_LINE=" - VPN_SERVICE_PROVIDER=nordvpn" - VPN_CUSTOM_LINE=" - SERVER_COUNTRIES=${VPN_COUNTRY}" - fi + # Alten Stack stoppen + info "Stoppe aktuellen Stack..." + pct exec "$SEL_ID" -- bash -c "cd ${FT_DIR} && docker compose down 2>/dev/null || true" - # 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 + # Neue docker-compose.yml generieren + info "Schreibe neue docker-compose.yml..." 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} +version: '3.8' services: - - # ── VPN-Proxy ────────────────────────────────────────────────────────────── - # Gluetun läuft als HTTP-Proxy auf Port 8888. - # Freqtrade schickt nur den Exchange-Traffic darüber. gluetun: - image: ${GLUETUN_IMAGE:-qmcgaw/gluetun:latest} + image: qmcgaw/gluetun:latest container_name: gluetun - restart: unless-stopped cap_add: - NET_ADMIN devices: - /dev/net/tun:/dev/net/tun environment: -${VPN_SERVER_LINE} + - TZ=Europe/Berlin + - VPN_SERVICE_PROVIDER=custom - VPN_TYPE=openvpn - - OPENVPN_USER=\${OPENVPN_USER} - - OPENVPN_PASSWORD=\${OPENVPN_PASSWORD} -${VPN_CUSTOM_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\" + - OPENVPN_CUSTOM_CONFIG=/gluetun/custom.conf + - OPENVPN_USER=${NORD_USER} + - OPENVPN_PASSWORD=${NORD_PASS} + # Automatisch ermitteltes lokales Netz für Erreichbarkeit + - FIREWALL_OUTBOUND_SUBNETS=${SUBNET} 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 + - /opt/docker-projekte/VPN:/gluetun + ports: + - \"8080:8080\" + restart: unless-stopped - # ── 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 + restart: unless-stopped + network_mode: \"container:gluetun\" volumes: - \"./user_data:/freqtrade/user_data\" + depends_on: + - gluetun 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: Gluetun aus GitHub klonen, Server eintragen, neu kompilieren - # → Löst das Problem dauerhaft: de1370 wird in die Binary eingebaut - if [[ -n "${VPN_HOSTNAME:-}" ]]; then - step "Gluetun Custom Build für ${VPN_HOSTNAME}" - - # 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 "") - [[ -z "$server_ip" ]] && error "IP für ${VPN_HOSTNAME} nicht auflösbar!" - info "DNS: ${VPN_HOSTNAME} → ${server_ip}" - - # Prüfen ob Image bereits existiert (kein Rebuild nötig) - local img_exists - img_exists=$(pct exec "$SEL_ID" -- bash -c "docker image inspect gluetun-custom:latest 2>/dev/null | grep -c 'Id' || echo 0" 2>/dev/null || echo "0") - - if [[ "$img_exists" -gt 0 ]]; then - # Prüfen ob der Hostname schon drin ist - local host_in_img - host_in_img=$(pct exec "$SEL_ID" -- bash -c "docker run --rm --entrypoint sh gluetun-custom:latest -c 'cat /gluetun/servers.json 2>/dev/null | grep -c ${VPN_HOSTNAME} || echo 0'" 2>/dev/null || echo "0") - if [[ "$host_in_img" -gt 0 ]]; then - success "gluetun-custom:latest bereits vorhanden und enthält ${VPN_HOSTNAME}" - GLUETUN_IMAGE="gluetun-custom:latest" - return 0 2>/dev/null || true - fi - info "Image vorhanden, aber ${VPN_HOSTNAME} fehlt – baue neu..." - fi - - # git installieren falls nötig - pct exec "$SEL_ID" -- bash -c "command -v git &>/dev/null || apt-get install -y -qq git" 2>/dev/null - - info "Klone Gluetun von GitHub (einmalig, ~3-5 Min zum Kompilieren)..." - pct exec "$SEL_ID" -- bash -c "rm -rf /tmp/gluetun-build && git clone --depth 1 https://github.com/qdm12/gluetun.git /tmp/gluetun-build 2>&1 | tail -3" - - info "Füge ${VPN_HOSTNAME} (${server_ip}) in servers.json ein..." - pct exec "$SEL_ID" -- bash -c "python3 << PYEOF -import json, copy, sys - -path = '/tmp/gluetun-build/internal/storage/servers.json' -hostname = '${VPN_HOSTNAME}' -ip = '${server_ip}' - -with open(path) as f: - db = json.load(f) - -servers = db.get('nordvpn', {}).get('servers', []) - -if any(s.get('hostname') == hostname for s in servers): - print(f'Bereits vorhanden: {hostname}') - sys.exit(0) - -# Deutschen OpenVPN-Eintrag als Template klonen -template = next( - (s for s in servers if s.get('country','').lower() == 'germany' and s.get('vpn') == 'openvpn'), - servers[0] -) -entry = copy.deepcopy(template) -entry['hostname'] = hostname -entry['ips'] = [ip] - -servers.append(entry) -db['nordvpn']['servers'] = servers - -with open(path, 'w') as f: - json.dump(db, f, indent=2) -print(f'Eingetragen: {hostname} ({ip}) – Gesamt NordVPN-Server: {len(servers)}') -PYEOF" - - info "Kompiliere Gluetun Docker Image (dauert ~3-5 Minuten)..." - echo -e " ${YELLOW}Bitte warten – Go Multi-Stage Build...${NC}" - pct exec "$SEL_ID" -- bash -c "cd /tmp/gluetun-build && docker build -t gluetun-custom:latest . 2>&1 | grep -E '^Step|Successfully|error|Error|=>|RUN|COPY' | tail -30" && success "gluetun-custom:latest erfolgreich gebaut!" || error "Docker Build fehlgeschlagen. Prüfe: pct exec ${SEL_ID} -- docker build /tmp/gluetun-build" - - GLUETUN_IMAGE="gluetun-custom:latest" - info "Aufräumen..." - pct exec "$SEL_ID" -- bash -c "rm -rf /tmp/gluetun-build" 2>/dev/null || true - else - GLUETUN_IMAGE="qmcgaw/gluetun:latest" - fi - - # docker-compose.yml mit korrektem Image aktualisieren - info "Setze Image in docker-compose.yml: ${GLUETUN_IMAGE}" - pct exec "$SEL_ID" -- bash -c "sed -i 's|image: .*gluetun.*|image: ${GLUETUN_IMAGE}|' ${FT_DIR}/docker-compose.yml" - - - # 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 gluetun 2>&1 | tail -60" 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") - + + # Stack starten + info "Starte Stack neu..." + pct exec "$SEL_ID" -- bash -c "cd ${FT_DIR} && docker compose up -d" + + sleep 5 + success "Deployment abgeschlossen!" + + # VPN IP Check + local VPN_IP + VPN_IP=$(pct exec "$SEL_ID" -- bash -c "docker exec gluetun wget -qO- https://ifconfig.me 2>/dev/null" || echo "FEHLER") + echo echo -e "${BOLD}${GREEN}╔══════════════════════════════════════════╗${NC}" - echo -e "${BOLD}${GREEN}║ ✅ NordVPN erfolgreich eingerichtet! ║${NC}" + echo -e "${BOLD}${GREEN}║ ✅ Setup erfolgreich abgeschlossen! ║${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 + echo -e " Neue externe VPN-IP des Bots: ${CYAN}${VPN_IP}${NC}" + echo -e " Dein lokales Subnetz (${SUBNET}) wurde in der Firewall freigegeben." + echo -e " Das Freqtrade-Webinterface bleibt über Port 8080 erreichbar." } # ── Hauptprogramm ────────────────────────────────────────────────────────────── @@ -609,11 +244,9 @@ main() { print_banner check_host select_container - setup_tun - collect_vpn_config - write_vpn_config - start_and_verify - show_result + setup_system + collect_and_wait + deploy_stack } -main "$@" +main "$@" \ No newline at end of file