Dateien nach "scripts" hochladen
This commit is contained in:
+148
-454
@@ -1,14 +1,13 @@
|
||||
#!/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
|
||||
# - NEU: Integriert den VPN-Ordner automatisch in den Filebrowser
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
@@ -29,11 +28,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 +38,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,522 +53,223 @@ 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 <ID> --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
|
||||
chmod -R 775 /opt/docker-projekte/VPN
|
||||
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"
|
||||
# ── Filebrowser anpassen ───────────────────────────────────────────────────────
|
||||
update_filebrowser() {
|
||||
step "Filebrowser aktualisieren"
|
||||
local FB_DIR="/opt/docker-projekte/filebrowser"
|
||||
local FB_YML="${FB_DIR}/docker-compose.yml"
|
||||
|
||||
# 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"
|
||||
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}=== 📝 KONFIGURATION ERFORDERLICH ===${NC}"
|
||||
echo -e "Bitte öffne jetzt dein ${BOLD}Filebrowser Web-Interface${NC} (Port 8081)."
|
||||
echo -e "1. Navigiere in den Ordner: ${BOLD}VPN${NC}"
|
||||
echo -e "2. Öffne die Datei: ${BOLD}custom.conf${NC}"
|
||||
echo -e "3. Kopiere den ${BOLD}gesamten Inhalt${NC} deiner NordVPN .ovpn Datei hinein."
|
||||
echo -e "4. ${BOLD}Speichern${NC} nicht vergessen!"
|
||||
echo -e "\n${CYAN}Tipp:${NC} Falls du lieber im Terminal arbeitest:"
|
||||
echo -e "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")
|
||||
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=""
|
||||
local VPN_CUSTOM_LINE=""
|
||||
if [[ -n "$VPN_HOSTNAME" ]]; then
|
||||
# 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
|
||||
# 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}
|
||||
|
||||
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
|
||||
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() {
|
||||
print_banner
|
||||
check_host
|
||||
select_container
|
||||
setup_tun
|
||||
collect_vpn_config
|
||||
write_vpn_config
|
||||
start_and_verify
|
||||
show_result
|
||||
setup_system
|
||||
update_filebrowser
|
||||
collect_and_wait
|
||||
deploy_stack
|
||||
|
||||
# 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 "$@"
|
||||
Reference in New Issue
Block a user