Dateien nach "scripts" hochladen

This commit is contained in:
2026-04-22 13:49:08 +02:00
parent 37896be54b
commit 2a63e72862
+148 -454
View File
@@ -1,14 +1,13 @@
#!/usr/bin/env bash #!/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. # Läuft komplett auf dem PROXMOX HOST.
# - Zeigt Freqtrade-Container zur Auswahl (gefiltert nach Tag/Hostname) # - Richtet TUN-Device ein
# - Richtet TUN-Device ein (Container-Neustart) # - Erstellt VPN-Ordner & Berechtigungen
# - Fragt NordVPN-Credentials ab # - Fragt NordVPN-Credentials ab & pausiert für custom.conf
# - Schreibt .env + docker-compose.yml mit Gluetun im Container # - Ermittelt automatisch das lokale Subnetz
# - Startet Stack und prüft VPN-IP # - Schreibt docker-compose.yml & startet den Stack
# # - NEU: Integriert den VPN-Ordner automatisch in den Filebrowser
# Quelle: https://git.obraasch.de/olli1986/freqtrade-proxmox-deploy
# ============================================================================= # =============================================================================
set -euo pipefail set -euo pipefail
@@ -29,11 +28,6 @@ ask() {
read -r REPLY; REPLY="${REPLY:-$default}" read -r REPLY; REPLY="${REPLY:-$default}"
} }
ask_secret() {
echo -ne "${YELLOW}${NC}${1}: "
read -rs REPLY; echo
}
confirm() { confirm() {
echo -ne "${YELLOW}${NC}${1} [${CYAN}j${NC}/N]: " echo -ne "${YELLOW}${NC}${1} [${CYAN}j${NC}/N]: "
read -r ans; [[ "$ans" =~ ^[jJyY]$ ]] read -r ans; [[ "$ans" =~ ^[jJyY]$ ]]
@@ -44,15 +38,14 @@ print_banner() {
clear clear
echo -e "${BOLD}${BLUE}" echo -e "${BOLD}${BLUE}"
cat << 'BANNER' cat << 'BANNER'
_ _ _ _ ___ _ _ _ _ _ _ ___ _ _
| \| |___ _ _ __| \ \ / / | \| | | \| |___ _ _ __| \ \ / / | \| |
| .` / _ \ '_/ _` \ V /| | .` | | .` / _ \ '_/ _` \ V /| | .` |
|_|\_\___/_| \__,_|\_/ |_|_|\_| |_|\_\___/_| \__,_|\_/ |_|_|\_|
NordVPN Addon Proxmox Host Installer NordVPN Custom Config Installer
BANNER BANNER
echo -e "${NC}" echo -e "${NC}"
echo -e " ${CYAN}Richtet TUN + Gluetun/NordVPN vollautomatisch ein${NC}" echo -e " ${CYAN}Automatisiertes Setup für feste IPs via custom.conf${NC}\n"
echo -e " ${CYAN}Quelle: git.obraasch.de/olli1986/freqtrade-proxmox-deploy${NC}\n"
} }
# ── Proxmox-Check ────────────────────────────────────────────────────────────── # ── Proxmox-Check ──────────────────────────────────────────────────────────────
@@ -60,522 +53,223 @@ check_host() {
step "Proxmox Host prüfen" step "Proxmox Host prüfen"
[[ $EUID -ne 0 ]] && error "Bitte als root ausführen!" [[ $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 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" success "Proxmox Host erkannt"
} }
# ── Container-Auswahl ───────────────────────────────────────────────────────── # ── Container-Auswahl ─────────────────────────────────────────────────────────
select_container() { select_container() {
step "Freqtrade Container auswählen" step "Freqtrade Container auswählen"
local -a display_list=()
# Alle Container auflisten mit Tag-Info
local -a ct_list=()
while IFS= read -r line; do while IFS= read -r line; do
local ct_id status local ct_id status hostname tags
ct_id=$(echo "$line" | awk '{print $1}') ct_id=$(echo "$line" | awk '{print $1}')
status=$(echo "$line" | awk '{print $2}') status=$(echo "$line" | awk '{print $2}')
[[ "$ct_id" =~ ^[0-9]+$ ]] || continue [[ "$ct_id" =~ ^[0-9]+$ ]] || continue
local hostname tags marker=""
hostname=$(pct config "$ct_id" 2>/dev/null | awk '/^hostname:/{print $2}' || echo "?") 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 "") tags=$(pct config "$ct_id" 2>/dev/null | awk '/^tags:/{print $2}' || echo "")
# Freqtrade-Container markieren
if echo "$tags" | grep -qi "freqtrade"; then if echo "$tags" | grep -qi "freqtrade"; then
marker="${GREEN}${NC}" display_list+=("${ct_id}|${hostname}|${status}")
else
marker=" "
fi fi
ct_list+=("${ct_id}|${hostname}|${status}|${marker}")
done < <(pct list 2>/dev/null | tail -n +2) done < <(pct list 2>/dev/null | tail -n +2)
if [[ ${#ct_list[@]} -eq 0 ]]; then [[ ${#display_list[@]} -eq 0 ]] && error "Keine Container mit Tag 'freqtrade' gefunden!"
error "Keine Container gefunden!"
fi
# Nur Container mit Tag "freqtrade" anzeigen echo -e " ${BOLD}Nr ID Hostname Status${NC}"
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 <ID> --tags freqtrade"
fi
echo
echo -e " ${BOLD}Nr ID Hostname Status ${GREEN}${NC}${BOLD} = Freqtrade${NC}"
echo -e " ${BLUE}─────────────────────────────────────────${NC}" echo -e " ${BLUE}─────────────────────────────────────────${NC}"
local i=1 local i=1
for entry in "${display_list[@]}"; do for entry in "${display_list[@]}"; do
IFS='|' read -r cid cname cstatus cmark <<< "$entry" IFS='|' read -r cid cname cstatus <<< "$entry"
printf " ${CYAN}%-4s${NC} %-8s %-18s %-10s %b\n" "$i" "$cid" "$cname" "$cstatus" "$cmark" printf " ${CYAN}%-4s${NC} %-8s %-18s %-10s\n" "$i" "$cid" "$cname" "$cstatus"
((i++)) ((i++))
done done
echo echo
local sel_nr=1 local sel_nr=1
if [[ ${#display_list[@]} -gt 1 ]]; then [[ ${#display_list[@]} -gt 1 ]] && { ask "Container Nummer wählen" "1"; sel_nr="$REPLY"; }
ask "Container Nummer wählen" "1"
sel_nr="$REPLY" IFS='|' read -r SEL_ID SEL_NAME SEL_STATUS <<< "${display_list[$((sel_nr-1))]}"
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!" [[ -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 if [[ "$SEL_STATUS" != "running" ]]; then
warn "Container ist nicht running. Starte..." info "Starte Container..."
pct start "$SEL_ID" pct start "$SEL_ID" && sleep 5
sleep 5
success "Container gestartet"
fi fi
} }
# ── TUN-Device einrichten ────────────────────────────────────────────────────── # ── TUN-Device & Ordner einrichten ─────────────────────────────────────────────
setup_tun() { setup_system() {
step "TUN-Device für CT ${SEL_ID} prüfen" step "System & VPN-Ordner vorbereiten"
# 1. TUN-Device in LXC Config schreiben
local conf="/etc/pve/lxc/${SEL_ID}.conf" local conf="/etc/pve/lxc/${SEL_ID}.conf"
[[ ! -f "$conf" ]] && error "LXC-Config nicht gefunden: $conf" if ! grep -q "lxc.mount.entry: /dev/net/tun" "$conf"; then
info "Trage TUN-Device in Config ein..."
if grep -q "lxc.cgroup2.devices.allow: c 10:200 rwm" "$conf" && \ cat >> "$conf" << 'TUNCFG'
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.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file
TUNCFG 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" # 2. Ordner und Datei erstellen & Rechte setzen
sleep 3 pct exec "$SEL_ID" -- bash -c "
pct start "$SEL_ID" mkdir -p /opt/docker-projekte/VPN
touch /opt/docker-projekte/VPN/custom.conf
local tries=0 chmod -R 775 /opt/docker-projekte/VPN
info "Warte auf Netzwerk..." chown 1000:1000 -R /opt/docker-projekte/VPN
until pct exec "$SEL_ID" -- bash -c "ip route | grep -q default" 2>/dev/null; do "
sleep 3; tries=$((tries+1)); echo -n "." success "VPN-Ordner und custom.conf erstellt (/opt/docker-projekte/VPN)"
[[ $tries -ge 15 ]] && error "Netzwerk nach Neustart nicht erreichbar!"
done
echo
success "Container neu gestartet mit TUN-Device"
} }
# ── NordVPN Konfiguration abfragen ───────────────────────────────────────────── # ── Filebrowser anpassen ───────────────────────────────────────────────────────
collect_vpn_config() { update_filebrowser() {
step "NordVPN Konfiguration" step "Filebrowser aktualisieren"
echo -e "\n ${BOLD}Service-Credentials${NC} findest du unter:" local FB_DIR="/opt/docker-projekte/filebrowser"
echo -e " ${CYAN}https://my.nordaccount.com → NordVPN → 'Set up NordVPN manually'${NC}" local FB_YML="${FB_DIR}/docker-compose.yml"
echo -e " ${RED} Nicht deine E-Mail/Passwort die Service-Credentials!${NC}\n"
# Prüfen, ob der Filebrowser überhaupt da ist
if pct exec "$SEL_ID" -- bash -c "[ -f ${FB_YML} ]"; then
info "Filebrowser gefunden. Prüfe Konfiguration..."
# Prüfen, ob der Mount schon existiert, um Doppel-Einträge zu vermeiden
if pct exec "$SEL_ID" -- bash -c "grep -q '/opt/docker-projekte/VPN:/srv/VPN' ${FB_YML}"; then
success "VPN-Ordner ist bereits im Filebrowser gemountet."
else
info "Füge VPN-Mountpunkt zur docker-compose.yml hinzu..."
# Fügt die Zeile exakt nach 'volumes:' ein
pct exec "$SEL_ID" -- bash -c "sed -i '/volumes:/a \\ - /opt/docker-projekte/VPN:/srv/VPN' ${FB_YML}"
success "Konfiguration ergänzt."
info "Starte Filebrowser neu..."
pct exec "$SEL_ID" -- bash -c "cd ${FB_DIR} && docker compose down && docker compose up -d"
success "Filebrowser ist neugestartet und hat jetzt Zugriff auf den VPN-Ordner!"
fi
else
info "Kein Filebrowser unter ${FB_DIR} gefunden. Überspringe diesen Schritt."
fi
}
# ── 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" ask "NordVPN Service-Username"
NORD_USER="$REPLY" NORD_USER="$REPLY"
[[ -z "$NORD_USER" ]] && error "Username darf nicht leer sein!"
stty echo 2>/dev/null || true
ask "NordVPN Service-Passwort" ask "NordVPN Service-Passwort"
NORD_PASS="$REPLY" NORD_PASS="$REPLY"
[[ -z "$NORD_PASS" ]] && error "Passwort darf nicht leer sein!"
echo -e "\n${BOLD}${CYAN}=== 📝 KONFIGURATION ERFORDERLICH ===${NC}"
echo echo -e "Bitte öffne jetzt dein ${BOLD}Filebrowser Web-Interface${NC} (Port 8081)."
echo -e " ${BOLD}VPN-Server Modus:${NC}" echo -e "1. Navigiere in den Ordner: ${BOLD}VPN${NC}"
echo -e " ${CYAN}1)${NC} Serverland normaler Server (IP kann wechseln)" echo -e "2. Öffne die Datei: ${BOLD}custom.conf${NC}"
echo -e " ${CYAN}2)${NC} Hostname Dedicated-IP oder spez. Server pinnen" echo -e "3. Kopiere den ${BOLD}gesamten Inhalt${NC} deiner NordVPN .ovpn Datei hinein."
ask "Auswahl [1/2]" "1" echo -e "4. ${BOLD}Speichern${NC} nicht vergessen!"
VPN_MODE="$REPLY" echo -e "\n${CYAN}Tipp:${NC} Falls du lieber im Terminal arbeitest:"
echo -e "nano /opt/docker-projekte/VPN/custom.conf"
VPN_PROTO="udp" # Start mit UDP, automatischer Fallback auf TCP bei TLS-Fehler
while true; do
if [[ "$VPN_MODE" == "2" ]]; then echo
echo -e "\n ${YELLOW}Hostnamen Format: de507.nordvpn.com, de654.nordvpn.com etc.${NC}" if confirm "Hast du die custom.conf befüllt und gespeichert?"; then
echo -e " ${YELLOW}Wichtig: NordVPN zeigt oft neue Server die Gluetun noch nicht kennt.${NC}" # Kurzer Check, ob die Datei nicht mehr leer ist
echo -e " ${YELLOW}Falls 'Ungültiger Hostname' erscheint → die angezeigten gültigen Server nutzen.${NC}" local size
echo -e " ${CYAN}Tipp: Modus 1 (Serverland) ist einfacher Gluetun wählt selbst.${NC}" size=$(pct exec "$SEL_ID" -- stat -c%s /opt/docker-projekte/VPN/custom.conf 2>/dev/null || echo 0)
ask "Hostname (z.B. de507.nordvpn.com)" if [[ "$size" -gt 10 ]]; then
VPN_HOSTNAME="$REPLY"; VPN_COUNTRY="" success "Datei ist befüllt!"
else break
ask "Serverland" "Germany" else
VPN_COUNTRY="$REPLY"; VPN_HOSTNAME="" warn "Die Datei scheint noch leer zu sein. Bitte überprüfe das."
fi fi
fi
# FreqUI Port aus bestehender docker-compose.yml lesen (ändert sich nicht) done
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; }
} }
# ── Konfiguration in Container schreiben ────────────────────────────────────── # ── Docker Compose schreiben & anwenden ────────────────────────────────────────
write_vpn_config() { deploy_stack() {
step "VPN-Konfiguration in Container schreiben" step "Docker Stack anwenden"
# Image: custom build bei Dedicated-IP, sonst offizielles Image
GLUETUN_IMAGE="${GLUETUN_IMAGE:-qmcgaw/gluetun:latest}"
local FT_DIR="/opt/docker-projekte/freqtrade" local FT_DIR="/opt/docker-projekte/freqtrade"
local NORD_USER_B64 NORD_PASS_B64
NORD_USER_B64=$(printf '%s' "$NORD_USER" | base64 -w0) # Lokales Subnetz automatisch ermitteln (z.B. 192.168.178.0/24)
NORD_PASS_B64=$(printf '%s' "$NORD_PASS" | base64 -w0) 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")
SUBNET=$(pct exec "$SEL_ID" -- bash -c "python3 -c 'import ipaddress; import subprocess; ip = subprocess.check_output([\"ip\", \"-o\", \"-f\", \"inet\", \"addr\", \"show\", \"eth0\"]).decode().split()[3]; print(ipaddress.ip_interface(ip).network)'" 2>/dev/null || echo "192.168.0.0/23")
info "Ermitteltes lokales Subnetz: ${SUBNET}"
local VPN_SERVER_LINE="" # Alten Stack stoppen
local VPN_CUSTOM_LINE="" info "Stoppe aktuellen Stack..."
if [[ -n "$VPN_HOSTNAME" ]]; then pct exec "$SEL_ID" -- bash -c "cd ${FT_DIR} && docker compose down 2>/dev/null || true"
# Dedicated-IP: Custom Build hat den Hostname in der Binary → normaler nordvpn Provider
VPN_SERVER_LINE=" - VPN_SERVICE_PROVIDER=nordvpn"
VPN_CUSTOM_LINE=" - SERVER_HOSTNAMES=${VPN_HOSTNAME}"
else
VPN_SERVER_LINE=" - VPN_SERVICE_PROVIDER=nordvpn"
VPN_CUSTOM_LINE=" - SERVER_COUNTRIES=${VPN_COUNTRY}"
fi
# Backup # Neue docker-compose.yml generieren
pct exec "$SEL_ID" -- bash -c \ info "Schreibe neue docker-compose.yml..."
"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' 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: services:
# ── VPN-Proxy ──────────────────────────────────────────────────────────────
# Gluetun läuft als HTTP-Proxy auf Port 8888.
# Freqtrade schickt nur den Exchange-Traffic darüber.
gluetun: gluetun:
image: ${GLUETUN_IMAGE:-qmcgaw/gluetun:latest} image: qmcgaw/gluetun:latest
container_name: gluetun container_name: gluetun
restart: unless-stopped
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
devices: devices:
- /dev/net/tun:/dev/net/tun - /dev/net/tun:/dev/net/tun
environment: environment:
${VPN_SERVER_LINE} - TZ=Europe/Berlin
- VPN_SERVICE_PROVIDER=custom
- VPN_TYPE=openvpn - VPN_TYPE=openvpn
- OPENVPN_USER=\${OPENVPN_USER} - OPENVPN_CUSTOM_CONFIG=/gluetun/custom.conf
- OPENVPN_PASSWORD=\${OPENVPN_PASSWORD} - OPENVPN_USER=${NORD_USER}
${VPN_CUSTOM_LINE} - OPENVPN_PASSWORD=${NORD_PASS}
# HTTP-Proxy aktivieren darüber leitet Freqtrade den Binance-Traffic # Automatisch ermitteltes lokales Netz für Erreichbarkeit
- HTTPPROXY=on - FIREWALL_OUTBOUND_SUBNETS=${SUBNET}
- HTTPPROXY_LOG=off
- HTTPPROXY_STEALTH=on
ports:
# Kein WebUI-Port hier Freqtrade hat sein eigenes Netzwerk
- \"127.0.0.1:8888:8888\"
volumes: volumes:
- gluetun_data:/gluetun - /opt/docker-projekte/VPN:/gluetun
healthcheck: ports:
test: [\"CMD-SHELL\", \"wget -qO- https://ipinfo.io/ip || exit 1\"] - \"8080:8080\"
interval: 30s restart: unless-stopped
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: freqtrade:
image: freqtradeorg/freqtrade:stable image: freqtradeorg/freqtrade:stable
restart: unless-stopped
container_name: freqtrade container_name: freqtrade
depends_on: restart: unless-stopped
gluetun: network_mode: \"container: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: volumes:
- \"./user_data:/freqtrade/user_data\" - \"./user_data:/freqtrade/user_data\"
depends_on:
- gluetun
command: > command: >
trade trade
--logfile /freqtrade/user_data/logs/freqtrade.log --logfile /freqtrade/user_data/logs/freqtrade.log
--db-url sqlite:////freqtrade/user_data/tradesv3.sqlite --db-url sqlite:////freqtrade/user_data/tradesv3.sqlite
--config /freqtrade/user_data/config.json --config /freqtrade/user_data/config.json
--strategy SampleStrategy --strategy SampleStrategy
volumes:
gluetun_data:
COMPOSEEOF" COMPOSEEOF"
success "docker-compose.yml geschrieben"
# Stack starten
info "Starte Stack neu..."
pct exec "$SEL_ID" -- bash -c "cd ${FT_DIR} && docker compose up -d"
sleep 5
success "Deployment abgeschlossen!"
} }
# ── 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 ermitteln: zuerst per DNS, dann User bestätigen lassen
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 "")
# IP anzeigen und Korrektur ermöglichen
# Bei Dedicated-IP weicht DNS oft von der echten NordVPN-IP ab
if [[ -n "$server_ip" ]]; then
echo -e " ${YELLOW}DNS hat aufgelöst: ${CYAN}${VPN_HOSTNAME}${server_ip}${NC}"
else
echo -e " ${YELLOW}DNS-Auflösung fehlgeschlagen.${NC}"
fi
echo -e " ${YELLOW}Die korrekte IP findest du in deinem NordVPN-Account unter Dedicated IP.${NC}"
ask "IP-Adresse für ${VPN_HOSTNAME}" "${server_ip}"
server_ip="$REPLY"
[[ -z "$server_ip" ]] && error "Keine IP angegeben!"
info "Verwende IP: ${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 | tr -d '\n\r ' || echo "0")
if [[ "${img_exists:-0}" -gt 0 ]] 2>/dev/null; then
# Prüfen ob Image für diesen Hostname gebaut wurde
local built_for
built_for=$(pct exec "$SEL_ID" -- bash -c \
"docker inspect gluetun-custom:latest --format=\"{{json .Config.Labels}}\" 2>/dev/null" \
2>/dev/null | grep -o '["]built_for["]:["]' | cut -d'"' -f4 2>/dev/null | tr -d '\n' || echo "")
if [[ "$built_for" == "${VPN_HOSTNAME}" ]]; then
success "gluetun-custom:latest bereits für ${VPN_HOSTNAME} gebaut"
GLUETUN_IMAGE="gluetun-custom:latest"
pct exec "$SEL_ID" -- bash -c \
"sed -i 's|image: .*gluetun.*|image: gluetun-custom:latest|' ${FT_DIR}/docker-compose.yml"
return 0 2>/dev/null || true
fi
info "Image vorhanden, aber nicht für ${VPN_HOSTNAME} 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..."
# Python-Script lokal erstellen und in Container pushen
cat > /tmp/add_nordvpn_server.py << 'PYEOF'
import json, copy, sys
path, hostname, ip = sys.argv[1], sys.argv[2], sys.argv[3]
db = json.load(open(path))
servers = db.get("nordvpn", {}).get("servers", [])
if any(s.get("hostname") == hostname for s in servers):
sys.exit(0)
tmpl = next((s for s in servers if s.get("country","").lower()=="germany" and s.get("vpn")=="openvpn"), servers[0])
e = copy.deepcopy(tmpl)
e["hostname"] = hostname
e["ips"] = [ip]
servers.append(e)
db["nordvpn"]["servers"] = servers
json.dump(db, open(path,"w"), indent=2)
print("Eingetragen: " + hostname + " (" + ip + ") - Server: " + str(len(servers)))
PYEOF
pct push "$SEL_ID" /tmp/add_nordvpn_server.py /tmp/add_nordvpn_server.py
pct exec "$SEL_ID" -- python3 /tmp/add_nordvpn_server.py \
/tmp/gluetun-build/internal/storage/servers.json \
"${VPN_HOSTNAME}" "${server_ip}"
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 --label built_for=${VPN_HOSTNAME} -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 max_wait=75 # UDP: 75s reicht (TLS-Timeout ist 60s + retry)
[[ "$proto" == "tcp" ]] && max_wait=90
while [[ $((tries * 5)) -lt $max_wait ]]; do
sleep 5; tries=$((tries+1))
local elapsed=$(( tries * 5 ))
echo -ne "\r ${CYAN}[INFO]${NC} ${proto^^} ${elapsed}s... "
# Verbindungstest (schnell, kein docker logs)
if pct exec "$SEL_ID" -- bash -c "docker exec gluetun wget -qO- --timeout=3 https://ipinfo.io/ip 2>/dev/null" &>/dev/null; then
vpn_ok=true; break
fi
done
echo
# Nach Timeout: Log prüfen für Fehlerdiagnose
if [[ "$vpn_ok" != "true" ]]; then
local log_out
log_out=$(timeout 10 pct exec "$SEL_ID" -- bash -c "docker logs gluetun 2>&1 | tail -30" 2>/dev/null || echo "")
if echo "$log_out" | grep -qiE "TLS Error|TLS key negotiation|TLS handshake"; then
echo; warn "TLS-Fehler nach ${proto^^}-Timeout UDP geblockt, wechsle auf TCP..."
tls_error=true
elif echo "$log_out" | grep -qiE "AUTH_FAILED|auth.*fail|wrong.*password|authentication failed"; then
echo
echo -e " ${RED}✗ Authentifizierung fehlgeschlagen!${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
elif echo "$log_out" | grep -qiE "hostname.*not valid|not one of the possible choices"; then
echo -e " ${RED}✗ Ungültiger Hostname: ${VPN_HOSTNAME}${NC}"
pct exec "$SEL_ID" -- bash -c "cd ${FT_DIR} && docker compose down 2>/dev/null || true"
error "Hostname nicht in Gluetun-DB. Custom Build prüfen."
else
echo -e " ${YELLOW}Letztes Gluetun-Log:${NC}"
echo "$log_out" | grep -E "INFO|WARN|ERROR|openvpn" | tail -8 | while IFS= read -r l; do
echo -e " ${YELLOW}${NC} $l"
done
fi
fi
echo
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() { main() {
print_banner print_banner
check_host check_host
select_container select_container
setup_tun setup_system
collect_vpn_config update_filebrowser
write_vpn_config collect_and_wait
start_and_verify deploy_stack
show_result
# Finaler IP Check
local IP
IP=$(pct exec "$SEL_ID" -- bash -c "docker exec gluetun wget -qO- --user-agent=curl https://ifconfig.me 2>/dev/null" || echo "Check fehlgeschlagen")
echo -e "\n${GREEN}FERTIG! Externe VPN-IP: ${BOLD}${IP}${NC}"
} }
main "$@" main "$@"