Files
freqtrade-proxmox-deploy/scripts/proxmox-install.sh
T
2026-04-20 15:33:56 +02:00

544 lines
20 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 NUR solche die rootdir (LXC-Rootfs) unterstützen
# "local" unterstützt nur ISO/Templates, nicht Container-Rootfs!
STORAGES=$(pvesm status --content rootdir 2>/dev/null | awk 'NR>1{print $1}' | tr '\n' ' ' || echo "local-lvm")
[[ -z "${STORAGES// }" ]] && STORAGES="local-lvm"
# Verfügbare Bridges
BRIDGES=$(ip link show 2>/dev/null | grep -oP '(?<=: )vmbr\d+' | tr '\n' ' ' || echo "vmbr0")
# Erster rootdir-fähiger Storage als Default
DEFAULT_STORAGE=$(pvesm status --content rootdir 2>/dev/null | awk 'NR==2{print $1}' || echo "local-lvm")
[[ -z "$DEFAULT_STORAGE" ]] && DEFAULT_STORAGE="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}Storages (rootdir-fähig):${NC} ${BOLD}$STORAGES${NC}"
echo -e " ${YELLOW} Nur diese unterstützen LXC-Container (local reicht nicht!)${NC}"
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 "$@"