#!/usr/bin/env bash # ============================================================================= # Freqtrade NordVPN Addon – Proxmox Host Installer (Custom Config Edition) # Läuft komplett auf dem PROXMOX HOST. # - 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 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}" } 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 Custom Config Installer BANNER echo -e "${NC}" echo -e " ${CYAN}Automatisiertes Setup für feste IPs via custom.conf${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?" success "Proxmox Host erkannt" } # ── Container-Auswahl ───────────────────────────────────────────────────────── select_container() { step "Freqtrade Container auswählen" local -a display_list=() while IFS= read -r line; do local ct_id status hostname tags ct_id=$(echo "$line" | awk '{print $1}') status=$(echo "$line" | awk '{print $2}') [[ "$ct_id" =~ ^[0-9]+$ ]] || continue 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 "") if echo "$tags" | grep -qi "freqtrade"; then display_list+=("${ct_id}|${hostname}|${status}") fi done < <(pct list 2>/dev/null | tail -n +2) [[ ${#display_list[@]} -eq 0 ]] && error "Keine Container mit Tag 'freqtrade' gefunden!" 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 <<< "$entry" printf " ${CYAN}%-4s${NC} %-8s %-18s %-10s\n" "$i" "$cid" "$cname" "$cstatus" ((i++)) done echo local 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}" if [[ "$SEL_STATUS" != "running" ]]; then info "Starte Container..." pct start "$SEL_ID" && sleep 5 fi } # ── 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" 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 # 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)" } # ── 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" ask "NordVPN Service-Passwort" NORD_PASS="$REPLY" 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 } # ── Docker Compose schreiben & anwenden ──────────────────────────────────────── deploy_stack() { step "Docker Stack anwenden" local FT_DIR="/opt/docker-projekte/freqtrade" # 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}" # Alten Stack stoppen info "Stoppe aktuellen Stack..." pct exec "$SEL_ID" -- bash -c "cd ${FT_DIR} && docker compose down 2>/dev/null || true" # Neue docker-compose.yml generieren info "Schreibe neue docker-compose.yml..." pct exec "$SEL_ID" -- bash -c "cat > ${FT_DIR}/docker-compose.yml << 'COMPOSEEOF' services: gluetun: image: qmcgaw/gluetun:latest container_name: gluetun cap_add: - NET_ADMIN devices: - /dev/net/tun:/dev/net/tun environment: - TZ=Europe/Berlin - VPN_SERVICE_PROVIDER=custom - VPN_TYPE=openvpn - 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: - /opt/docker-projekte/VPN:/gluetun ports: - \"8080:8080\" restart: unless-stopped freqtrade: image: freqtradeorg/freqtrade:stable container_name: freqtrade 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 COMPOSEEOF" # Stack starten info "Starte Stack neu..." pct exec "$SEL_ID" -- bash -c "cd ${FT_DIR} && docker compose up -d" sleep 5 success "Deployment abgeschlossen!" } main() { print_banner check_host select_container 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 "$@"