#!/usr/bin/env bash # ============================================================================= # Freqtrade Proxmox Installer # Erstellt automatisch einen Docker-LXC und installiert Freqtrade darin. # Läuft auf dem PROXMOX HOST. # # Quelle: https://git.obraasch.de/olli1986/freqtrade-proxmox-deploy # ============================================================================= set -euo pipefail # ── Farben ──────────────────────────────────────────────────────────────────── 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:-}" if [[ -n "$default" ]]; then echo -ne "${YELLOW} ➜ ${NC}${prompt} [${CYAN}${default}${NC}]: " else echo -ne "${YELLOW} ➜ ${NC}${prompt}: " fi 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]$ ]] } # ── Voraussetzungen ─────────────────────────────────────────────────────────── check_host() { [[ $EUID -ne 0 ]] && error "Bitte als root ausführen!" command -v pct &>/dev/null || error "pct nicht gefunden – läuft das auf einem Proxmox Host?" command -v pvesh &>/dev/null || error "pvesh nicht gefunden – läuft das auf einem Proxmox Host?" command -v pveam &>/dev/null || error "pveam nicht gefunden." success "Proxmox Host erkannt" } # ── Banner ──────────────────────────────────────────────────────────────────── print_banner() { clear echo -e "${BOLD}${BLUE}" cat << 'BANNER' ___ _ _ | _ \_ _ _____ _| |_ _ __ _____ __ | |_ ___ ___ | _/ '_/ _ \ \ / | ' \/ _ \ \ / / | _/ -_) _ \ |_| |_| \___/_\_\_|_||_\___/_\_\_| \__\___\___/ + NordVPN + Proxmox LXC Installer BANNER echo -e "${NC}" echo -e " ${CYAN}Freqtrade vollautomatischer Installer für Proxmox${NC}" echo -e " ${CYAN}git.obraasch.de/olli1986/freqtrade-proxmox-deploy${NC}\n" echo -e " ${YELLOW}⚠ Nur für Test- und Lernzwecke. Trading auf eigenes Risiko!${NC}\n" } # ── Verfügbare Ressourcen ermitteln ─────────────────────────────────────────── detect_resources() { # Nächste freie CT-ID NEXT_ID=$(pvesh get /cluster/nextid 2>/dev/null | tr -d '[:space:]' || echo "100") # Verfügbare Storages (für LXC-Rootfs) STORAGES=$(pvesm status 2>/dev/null | awk 'NR>1 && $2!="dir" || $2=="dir" {print $1}' | tr '\n' ' ' || echo "local-lvm local") # Verfügbare Bridges BRIDGES=$(ip link show 2>/dev/null | grep -oP '(?<=: )vmbr\d+' | tr '\n' ' ' || echo "vmbr0") # Erster Storage als Default DEFAULT_STORAGE=$(pvesm status 2>/dev/null | awk 'NR==2{print $1}' || echo "local-lvm") # Erste Bridge als Default DEFAULT_BRIDGE=$(ip link show 2>/dev/null | grep -oP '(?<=: )vmbr\d+' | head -1 || echo "vmbr0") } # ── Template holen ──────────────────────────────────────────────────────────── prepare_template() { step "Debian 12 Template prüfen" # Suche lokal vorhandenes Debian 12 Template TEMPLATE_FILE=$(pveam list local 2>/dev/null | grep -oP 'debian-12-standard_\S+' | head -1 || true) if [[ -z "$TEMPLATE_FILE" ]]; then info "Template nicht lokal vorhanden – lade herunter..." pveam update &>/dev/null || warn "pveam update fehlgeschlagen, versuche trotzdem..." # Neuestes Debian 12 Template aus der Liste holen TEMPLATE_FILE=$(pveam available --section system 2>/dev/null \ | grep -oP 'debian-12-standard_\S+' | sort -V | tail -1 || true) [[ -z "$TEMPLATE_FILE" ]] && TEMPLATE_FILE="debian-12-standard_12.7-1_amd64.tar.zst" info "Lade Template: $TEMPLATE_FILE" pveam download local "$TEMPLATE_FILE" || error "Template-Download fehlgeschlagen!" fi TEMPLATE_PATH="local:vztmpl/${TEMPLATE_FILE}" success "Template bereit: $TEMPLATE_FILE" } # ── Konfiguration abfragen ──────────────────────────────────────────────────── collect_config() { step "LXC Konfiguration" echo -e " ${CYAN}Verfügbare Storages:${NC} $STORAGES" echo -e " ${CYAN}Verfügbare Bridges:${NC} $BRIDGES\n" ask "LXC Container-ID" "$NEXT_ID" CT_ID="$REPLY" [[ "$CT_ID" =~ ^[0-9]+$ ]] || error "Container-ID muss eine Zahl sein!" pct status "$CT_ID" &>/dev/null && error "Container-ID $CT_ID existiert bereits!" ask "Hostname" "freqtrade" CT_HOST="$REPLY" ask "CPU Kerne" "2" CT_CORES="$REPLY" ask "RAM in MB" "2048" CT_RAM="$REPLY" ask "Swap in MB" "512" CT_SWAP="$REPLY" ask "Disk-Größe in GB" "10" CT_DISK="$REPLY" ask "Storage Pool" "$DEFAULT_STORAGE" CT_STORAGE="$REPLY" ask "Netzwerk-Bridge" "$DEFAULT_BRIDGE" CT_BRIDGE="$REPLY" ask "Netzwerk (dhcp oder z.B. 192.168.1.100/24)" "dhcp" CT_IP="$REPLY" if [[ "$CT_IP" != "dhcp" ]]; then ask "Gateway (nur bei statischer IP nötig)" CT_GW="$REPLY" else CT_GW="" fi ask_secret "Root-Passwort für den LXC" CT_PASS="$REPLY" [[ ${#CT_PASS} -lt 8 ]] && error "Passwort muss mind. 8 Zeichen haben!" step "Freqtrade Konfiguration" ask "FreqUI Port" "8080" FT_PORT="$REPLY" ask "Börse" "binance" FT_EXCHANGE="$REPLY" ask "Stake-Währung" "USDC" FT_STAKE="$REPLY" ask "Max. offene Trades" "3" FT_MAX_TRADES="$REPLY" ask "Timeframe" "5m" FT_TIMEFRAME="$REPLY" ask "FreqUI Benutzername" "freqtrader" FT_API_USER="$REPLY" ask_secret "FreqUI Passwort (min. 8 Zeichen)" FT_API_PASS="$REPLY" [[ ${#FT_API_PASS} -lt 8 ]] && error "FreqUI Passwort muss mind. 8 Zeichen haben!" if confirm "Telegram-Benachrichtigungen aktivieren?"; then FT_TELEGRAM=true ask "Telegram Bot-Token" TG_TOKEN="$REPLY" ask "Telegram Chat-ID" TG_CHATID="$REPLY" else FT_TELEGRAM=false TG_TOKEN="" TG_CHATID="" fi if confirm "Filebrowser (Web-Dateimanager) installieren?"; then INSTALL_FB=true ask "Filebrowser Port" "8081" FB_PORT="$REPLY" else INSTALL_FB=false FB_PORT="8081" fi # ── Zusammenfassung ── echo -e "\n${BOLD}${BLUE}══════════════ Zusammenfassung ══════════════${NC}" echo -e " CT-ID: ${CYAN}$CT_ID${NC} Hostname: ${CYAN}$CT_HOST${NC}" echo -e " CPU/RAM: ${CYAN}$CT_CORES Kerne / ${CT_RAM}MB RAM / ${CT_SWAP}MB Swap${NC}" echo -e " Disk: ${CYAN}${CT_DISK}GB auf $CT_STORAGE${NC}" echo -e " Netzwerk: ${CYAN}$CT_IP via $CT_BRIDGE${NC}" echo -e " FreqUI Port: ${CYAN}$FT_PORT${NC} Börse: ${CYAN}$FT_EXCHANGE${NC}" echo -e " Filebrowser: $([ "$INSTALL_FB" = true ] && echo "${CYAN}Ja (Port $FB_PORT)${NC}" || echo "${CYAN}Nein${NC}")" echo -e " Telegram: $([ "$FT_TELEGRAM" = true ] && echo "${CYAN}Ja${NC}" || echo "${CYAN}Nein${NC}")" echo -e "${BOLD}${BLUE}════════════════════════════════════════════${NC}\n" confirm "Installation starten?" || { info "Abgebrochen."; exit 0; } } # ── LXC erstellen ───────────────────────────────────────────────────────────── create_lxc() { step "LXC Container $CT_ID erstellen" local NET_CONFIG if [[ "$CT_IP" == "dhcp" ]]; then NET_CONFIG="name=eth0,bridge=${CT_BRIDGE},ip=dhcp,firewall=1" else NET_CONFIG="name=eth0,bridge=${CT_BRIDGE},ip=${CT_IP},gw=${CT_GW},firewall=1" fi pct create "$CT_ID" "$TEMPLATE_PATH" \ --hostname "$CT_HOST" \ --password "$CT_PASS" \ --cores "$CT_CORES" \ --memory "$CT_RAM" \ --swap "$CT_SWAP" \ --rootfs "${CT_STORAGE}:${CT_DISK}" \ --net0 "$NET_CONFIG" \ --features "nesting=1,keyctl=1" \ --unprivileged 1 \ --onboot 1 \ --timezone "Europe/Berlin" \ --start 0 # TUN-Device für spätere VPN-Nutzung vorbereiten cat >> "/etc/pve/lxc/${CT_ID}.conf" << 'TUNCFG' # TUN-Device für NordVPN/Gluetun (wird von install-vpn.sh genutzt) lxc.cgroup2.devices.allow: c 10:200 rwm lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file TUNCFG success "LXC $CT_ID erstellt (mit TUN-Device vorbereitet)" # Starten info "Starte Container..." pct start "$CT_ID" info "Warte auf Netzwerk..." local tries=0 until pct exec "$CT_ID" -- bash -c "ip route | grep -q default" 2>/dev/null; do sleep 3; ((tries++)) [[ $tries -ge 20 ]] && error "Netzwerk im LXC nicht erreichbar. Prüfe Bridge/IP." echo -n "." done echo success "Container läuft und hat Netzwerk" } # ── Inneres Setup-Script generieren und ausführen ───────────────────────────── run_inner_setup() { step "Freqtrade innerhalb des LXC installieren" # Zufällige Secrets für FreqUI generieren JWT_SECRET=$(openssl rand -hex 32 2>/dev/null || tr -dc 'a-f0-9' < /dev/urandom | head -c 64) WS_TOKEN=$(openssl rand -hex 16 2>/dev/null || tr -dc 'a-f0-9' < /dev/urandom | head -c 32) # Telegram-Block für config.json vorbereiten if [[ "$FT_TELEGRAM" == "true" ]]; then TG_JSON='"telegram": {"enabled": true, "token": "'"$TG_TOKEN"'", "chat_id": "'"$TG_CHATID"'"},' else TG_JSON='"telegram": {"enabled": false, "token": "", "chat_id": ""},' fi # Inner-Script in eine Temp-Datei schreiben (Variablen werden jetzt expandiert) cat > /tmp/ft_inner_setup.sh << INNEREOF #!/usr/bin/env bash set -euo pipefail RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m' ok() { echo -e "\${GREEN}[ OK ]\${NC} \$*"; } inf() { echo -e "\${CYAN}[INF]\${NC} \$*"; } fail() { echo -e "\${RED}[ERR]\${NC} \$*"; exit 1; } INSTALL_DIR="/opt/docker-projekte" FT_DIR="\$INSTALL_DIR/freqtrade" FT_PORT="${FT_PORT}" FT_EXCHANGE="${FT_EXCHANGE}" FT_STAKE="${FT_STAKE}" FT_MAX_TRADES="${FT_MAX_TRADES}" FT_TIMEFRAME="${FT_TIMEFRAME}" FT_API_USER="${FT_API_USER}" FT_API_PASS="${FT_API_PASS}" JWT_SECRET="${JWT_SECRET}" WS_TOKEN="${WS_TOKEN}" INSTALL_FB="${INSTALL_FB}" FB_PORT="${FB_PORT}" # ── System-Pakete ───────────────────────────────────────────────────────────── inf "System aktualisieren..." export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq curl ca-certificates gnupg lsb-release net-tools ok "Systempakete installiert" # ── Docker installieren ─────────────────────────────────────────────────────── inf "Docker installieren..." curl -fsSL https://get.docker.com | sh > /dev/null 2>&1 systemctl enable docker --now > /dev/null 2>&1 ok "Docker installiert: \$(docker --version)" # ── Verzeichnisse anlegen ───────────────────────────────────────────────────── mkdir -p "\$FT_DIR" mkdir -p "\$INSTALL_DIR/filebrowser/config" mkdir -p "\$INSTALL_DIR/filebrowser/database" ok "Verzeichnisse angelegt" # ── Filebrowser (optional) ──────────────────────────────────────────────────── if [[ "\$INSTALL_FB" == "true" ]]; then inf "Filebrowser installieren..." cat > "\$INSTALL_DIR/filebrowser/docker-compose.yml" << 'FBEOF' services: filebrowser: image: filebrowser/filebrowser:latest container_name: filebrowser user: "0:0" ports: - "FBPORT:8080" volumes: - /opt/docker-projekte:/srv - /opt/docker-projekte/filebrowser/database:/database - /opt/docker-projekte/filebrowser/config:/config command: ["--address","0.0.0.0","--port","8080", "--database","/database/filebrowser.db", "--root","/srv","--noauth=false"] restart: unless-stopped FBEOF # Port einsetzen (war im Heredoc wegen einfacher Anführungszeichen nicht ersetzt) sed -i "s/FBPORT/\${FB_PORT}/g" "\$INSTALL_DIR/filebrowser/docker-compose.yml" cd "\$INSTALL_DIR/filebrowser" docker compose up -d > /dev/null 2>&1 sleep 4 FB_PASS=\$(docker logs filebrowser 2>&1 | grep -oP "(?<=randomly generated password: )\S+" | tail -1 || echo "siehe: docker logs filebrowser") ok "Filebrowser gestartet – Passwort: \$FB_PASS" echo "FILEBROWSER_PASS=\$FB_PASS" >> /root/ft_install_info.txt fi # ── Freqtrade docker-compose.yml ────────────────────────────────────────────── inf "Freqtrade docker-compose.yml herunterladen..." cd "\$FT_DIR" curl -fsSL https://raw.githubusercontent.com/freqtrade/freqtrade/stable/docker-compose.yml \ -o docker-compose.yml # Nur localhost → 0.0.0.0 für WebUI-Erreichbarkeit sed -i 's/127\.0\.0\.1/0.0.0.0/g' docker-compose.yml ok "docker-compose.yml vorbereitet" # ── user_data Verzeichnis erstellen ─────────────────────────────────────────── inf "user_data erstellen..." docker compose run --rm freqtrade create-userdir --userdir user_data > /dev/null 2>&1 ok "user_data erstellt" # ── config.json generieren ──────────────────────────────────────────────────── inf "config.json erstellen..." cat > "\$FT_DIR/user_data/config.json" << CONFIGEOF { "max_open_trades": ${FT_MAX_TRADES}, "stake_currency": "${FT_STAKE}", "stake_amount": "unlimited", "tradable_balance_ratio": 0.99, "fiat_display_currency": "EUR", "timeframe": "${FT_TIMEFRAME}", "dry_run": true, "dry_run_wallet": 1000, "cancel_open_orders_on_exit": false, "trading_mode": "spot", "margin_mode": "", "unfilledtimeout": { "entry": 10, "exit": 10, "exit_timeout_count": 0, "unit": "minutes" }, "entry_pricing": { "price_side": "same", "use_order_book": true, "order_book_top": 1, "price_last_balance": 0.0, "check_depth_of_market": {"enabled": false, "bids_to_ask_delta": 1} }, "exit_pricing": { "price_side": "same", "use_order_book": true, "order_book_top": 1 }, "exchange": { "name": "${FT_EXCHANGE}", "key": "", "secret": "", "ccxt_config": {}, "ccxt_async_config": {} }, "pairlists": [{"method": "StaticPairList"}], "pair_whitelist": [ "${FT_STAKE}/USDT", "BTC/${FT_STAKE}", "ETH/${FT_STAKE}" ], ${TG_JSON} "api_server": { "enabled": true, "listen_ip_address": "0.0.0.0", "listen_port": 8080, "verbosity": "error", "enable_openapi": false, "jwt_secret_key": "${JWT_SECRET}", "ws_token": "${WS_TOKEN}", "CORS_origins": [], "username": "${FT_API_USER}", "password": "${FT_API_PASS}" }, "bot_name": "freqtrade", "initial_state": "running", "force_entry_enable": false, "internals": {"process_throttle_secs": 5} } CONFIGEOF ok "config.json erstellt" # ── Freqtrade starten ───────────────────────────────────────────────────────── inf "Freqtrade starten..." cd "\$FT_DIR" docker compose up -d > /dev/null 2>&1 sleep 5 if docker ps | grep -q freqtrade; then ok "Freqtrade läuft" else fail "Freqtrade gestartet nicht – prüfe: docker logs freqtrade" fi # ── Info-Datei für Proxmox-Script ───────────────────────────────────────────── LXC_IP=\$(hostname -I | awk '{print \$1}') cat > /root/ft_install_info.txt << INFOEOF LXC_IP=\$LXC_IP FT_PORT=${FT_PORT} FT_API_USER=${FT_API_USER} FB_PORT=${FB_PORT} INSTALL_FB=${INSTALL_FB} INFOEOF echo "" echo "=== Freqtrade Installation abgeschlossen ===" echo "WebUI: http://\$LXC_IP:${FT_PORT}" echo "User: ${FT_API_USER}" INNEREOF # Script in den LXC schieben und ausführen pct push "$CT_ID" /tmp/ft_inner_setup.sh /root/ft_setup.sh pct exec "$CT_ID" -- chmod +x /root/ft_setup.sh info "Starte Installation im LXC (kann 2-3 Minuten dauern)..." pct exec "$CT_ID" -- bash /root/ft_setup.sh rm -f /tmp/ft_inner_setup.sh success "Installation im LXC abgeschlossen" } # ── Ergebnis anzeigen ───────────────────────────────────────────────────────── show_result() { # Info-Datei aus dem LXC lesen local TMP_INFO="/tmp/ft_info_${CT_ID}.txt" pct pull "$CT_ID" /root/ft_install_info.txt "$TMP_INFO" 2>/dev/null || true LXC_IP="" FB_PASS="" if [[ -f "$TMP_INFO" ]]; then # shellcheck disable=SC1090 source "$TMP_INFO" rm -f "$TMP_INFO" fi # Falls IP leer, über pct ermitteln if [[ -z "$LXC_IP" ]]; then LXC_IP=$(pct exec "$CT_ID" -- hostname -I 2>/dev/null | awk '{print $1}' || echo "unbekannt") fi echo echo -e "${BOLD}${GREEN}╔══════════════════════════════════════════╗${NC}" echo -e "${BOLD}${GREEN}║ ✅ Installation erfolgreich! ║${NC}" echo -e "${BOLD}${GREEN}╚══════════════════════════════════════════╝${NC}" echo echo -e " ${BOLD}LXC Container:${NC} ID ${CYAN}$CT_ID${NC} | Host: ${CYAN}$CT_HOST${NC}" echo -e " ${BOLD}IP-Adresse:${NC} ${CYAN}$LXC_IP${NC}" echo echo -e " ${BOLD}${GREEN}FreqUI (Freqtrade WebUI):${NC}" echo -e " ${CYAN}http://${LXC_IP}:${FT_PORT}${NC}" echo -e " Benutzer: ${CYAN}$FT_API_USER${NC}" echo if [[ "$INSTALL_FB" == "true" ]]; then echo -e " ${BOLD}${GREEN}Filebrowser (Dateimanager):${NC}" echo -e " ${CYAN}http://${LXC_IP}:${FB_PORT}${NC}" echo -e " Benutzer: ${CYAN}admin${NC}" [[ -n "${FILEBROWSER_PASS:-}" ]] && echo -e " Passwort: ${CYAN}$FILEBROWSER_PASS${NC} ${YELLOW}← sofort ändern!${NC}" echo fi echo -e " ${BOLD}Config-Datei:${NC}" echo -e " ${CYAN}/opt/docker-projekte/freqtrade/user_data/config.json${NC}" echo echo -e " ${BOLD}${YELLOW}Nächste Schritte:${NC}" echo -e " ${YELLOW}1. Binance API-Keys in config.json eintragen${NC}" echo -e " ${YELLOW}2. dry_run: false setzen wenn bereit${NC}" echo -e " ${YELLOW}3. Für NordVPN: install-vpn.sh im LXC ausführen${NC}" echo echo -e " ${BOLD}NordVPN Addon (im LXC ausführen):${NC}" echo -e " ${CYAN}bash -c \"\$(curl -fsSL https://git.obraasch.de/olli1986/freqtrade-proxmox-deploy/raw/branch/main/scripts/install-vpn.sh)\"${NC}" echo echo -e " ${BOLD}LXC Konsole öffnen:${NC}" echo -e " ${CYAN}pct enter $CT_ID${NC}" echo } # ── Hauptprogramm ───────────────────────────────────────────────────────────── main() { print_banner check_host detect_resources prepare_template collect_config create_lxc run_inner_setup show_result } main "$@"