Dateien nach "scripts" hochladen

This commit is contained in:
2026-04-20 16:04:10 +02:00
parent 33d95b1c43
commit a9304d6d61
+519
View File
@@ -0,0 +1,519 @@
#!/usr/bin/env bash
# =============================================================================
# Freqtrade LXC Installer für Proxmox
# Erstellt automatisch einen Docker-LXC und installiert:
# Docker, Portainer, Filebrowser, Freqtrade
#
# Läuft auf dem PROXMOX HOST.
# Quelle: https://git.obraasch.de/olli1986/freqtrade-proxmox-deploy
# =============================================================================
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}"
}
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]$ ]]
}
# ── Banner ─────────────────────────────────────────────────────────────────────
print_banner() {
clear
echo -e "${BOLD}${BLUE}"
cat << 'BANNER'
___ _ _
| __| _ ___ __ _| |_ _ _ __ _ | | __ __ __
| _| '_/ -_) _` | _| '_/ _` | | |__/ _\ \/ /
|_||_| \___\__, |\__|_| \__,_| |____\__/_/\_/
|__/ Proxmox LXC Installer
BANNER
echo -e "${NC}"
echo -e " ${CYAN}Installiert: Docker · Portainer · Filebrowser · Freqtrade${NC}"
echo -e " ${CYAN}Quelle: git.obraasch.de/olli1986/freqtrade-proxmox-deploy${NC}\n"
echo -e " ${YELLOW}⚠ Nur für Test-/Lernzwecke. Trading auf eigenes Risiko!${NC}\n"
}
# ── Proxmox-Checks ─────────────────────────────────────────────────────────────
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."
command -v pveam &>/dev/null || error "pveam nicht gefunden."
success "Proxmox erkannt"
}
# ── Ressourcen ermitteln ───────────────────────────────────────────────────────
detect_resources() {
NEXT_ID=$(pvesh get /cluster/nextid 2>/dev/null | tr -d '[:space:]' || echo "100")
# Nur rootdir-fähige Storages (local unterstützt KEINE 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"
DEFAULT_STORAGE=$(pvesm status --content rootdir 2>/dev/null | awk 'NR==2{print $1}' || echo "local-lvm")
[[ -z "$DEFAULT_STORAGE" ]] && DEFAULT_STORAGE="local-lvm"
BRIDGES=$(ip link show 2>/dev/null | grep -oP '(?<=: )vmbr\d+' | tr '\n' ' ' || echo "vmbr0")
DEFAULT_BRIDGE=$(ip link show 2>/dev/null | grep -oP '(?<=: )vmbr\d+' | head -1 || echo "vmbr0")
}
# ── Debian 12 Template ─────────────────────────────────────────────────────────
prepare_template() {
step "Debian 12 Template prüfen"
TEMPLATE_FILE=$(pveam list local 2>/dev/null | grep -oP 'debian-12-standard_\S+' | sort -V | tail -1 || true)
if [[ -z "$TEMPLATE_FILE" ]]; then
info "Nicht lokal vorhanden lade herunter..."
pveam update &>/dev/null || warn "pveam update fehlgeschlagen"
TEMPLATE_FILE=$(pveam available --section system 2>/dev/null \
| grep -oP 'debian-12-standard_\S+' | sort -V | tail -1 || echo "debian-12-standard_12.7-1_amd64.tar.zst")
info "Lade: $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} ↑ 'local' reicht nicht braucht z.B. local-lvm${NC}"
echo -e " ${CYAN}Bridges:${NC} ${BRIDGES}\n"
ask "LXC Container-ID" "$NEXT_ID"
CT_ID="$REPLY"
[[ "$CT_ID" =~ ^[0-9]+$ ]] || error "Muss eine Zahl sein!"
pct status "$CT_ID" &>/dev/null && error "CT-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 in GB" "10" ; CT_DISK="$REPLY"
ask "Storage Pool" "$DEFAULT_STORAGE" ; CT_STORAGE="$REPLY"
ask "Bridge" "$DEFAULT_BRIDGE" ; CT_BRIDGE="$REPLY"
ask "Netzwerk (dhcp oder 192.168.1.100/24)" "dhcp" ; CT_IP="$REPLY"
CT_GW=""
if [[ "$CT_IP" != "dhcp" ]]; then
ask "Gateway"; CT_GW="$REPLY"
fi
ask_secret "Root-Passwort für den LXC"
CT_PASS="$REPLY"
[[ ${#CT_PASS} -lt 8 ]] && error "Passwort mind. 8 Zeichen!"
step "Freqtrade Konfiguration"
ask "FreqUI Port" "8080" ; FT_PORT="$REPLY"
ask "Portainer Port" "9000" ; PT_PORT="$REPLY"
ask "Filebrowser Port" "8081" ; FB_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 (mind. 8 Zeichen)"
FT_API_PASS="$REPLY"
[[ ${#FT_API_PASS} -lt 8 ]] && error "Passwort mind. 8 Zeichen!"
FT_TELEGRAM=false; TG_TOKEN=""; TG_CHATID=""
if confirm "Telegram-Benachrichtigungen?"; then
FT_TELEGRAM=true
ask "Telegram Bot-Token" ; TG_TOKEN="$REPLY"
ask "Telegram Chat-ID" ; TG_CHATID="$REPLY"
fi
# Passwörter base64-kodiert weitergeben (sicher für Heredoc-Transport)
CT_PASS_B64=$(printf '%s' "$CT_PASS" | base64 -w0)
FT_API_PASS_B64=$(printf '%s' "$FT_API_PASS" | base64 -w0)
TG_TOKEN_B64=$(printf '%s' "$TG_TOKEN" | base64 -w0)
# Zufällige Secrets
JWT_SECRET=$(openssl rand -hex 32)
WS_TOKEN=$(openssl rand -hex 16)
echo -e "\n${BOLD}${BLUE}══════════════ Zusammenfassung ══════════════${NC}"
echo -e " CT ${CYAN}${CT_ID}${NC} | ${CYAN}${CT_HOST}${NC} | ${CYAN}${CT_CORES}${NC}x CPU | ${CYAN}${CT_RAM}${NC}MB RAM | ${CYAN}${CT_DISK}${NC}GB | ${CYAN}${CT_STORAGE}${NC}"
echo -e " Netz: ${CYAN}${CT_IP}${NC} via ${CYAN}${CT_BRIDGE}${NC}"
echo -e " FreqUI: ${CYAN}:${FT_PORT}${NC} | Portainer: ${CYAN}:${PT_PORT}${NC} | Filebrowser: ${CYAN}:${FB_PORT}${NC}"
echo -e " Börse: ${CYAN}${FT_EXCHANGE}${NC} | Stake: ${CYAN}${FT_STAKE}${NC} | Telegram: ${CYAN}${FT_TELEGRAM}${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="name=eth0,bridge=${CT_BRIDGE},firewall=1"
if [[ "$CT_IP" == "dhcp" ]]; then
NET_CONFIG+=",ip=dhcp"
else
NET_CONFIG+=",ip=${CT_IP},gw=${CT_GW}"
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
success "LXC erstellt"
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 nicht erreichbar Bridge/IP prüfen!"
echo -n "."
done
echo
success "Container läuft und hat Netzwerk"
}
# ── Inner-Setup-Script generieren und im LXC ausführen ────────────────────────
run_inner_setup() {
step "Installation im LXC starten"
# Pairs korrekt aufbauen (BTC/STAKE, ETH/STAKE, BNB/STAKE)
local PAIRS="\"BTC/${FT_STAKE}\", \"ETH/${FT_STAKE}\", \"BNB/${FT_STAKE}\""
# Telegram JSON-Block
local TG_ENABLED="false"
[[ "$FT_TELEGRAM" == "true" ]] && TG_ENABLED="true"
cat > /tmp/ft_lxc_setup.sh << INNEREOF
#!/usr/bin/env bash
set -euo pipefail
GREEN='\033[0;32m'; CYAN='\033[0;36m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; 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"
# Konfigurationswerte (vom Installer übergeben)
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=\$(echo "${FT_API_PASS_B64}" | base64 -d)
TG_TOKEN=\$(echo "${TG_TOKEN_B64}" | base64 -d)
TG_CHATID="${TG_CHATID}"
TG_ENABLED="${TG_ENABLED}"
JWT_SECRET="${JWT_SECRET}"
WS_TOKEN="${WS_TOKEN}"
PT_PORT="${PT_PORT}"
FB_PORT="${FB_PORT}"
# ── System ──────────────────────────────────────────────────────────────────────
inf "System aktualisieren..."
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq curl ca-certificates gnupg python3 net-tools
ok "Systempakete installiert"
# ── Docker ──────────────────────────────────────────────────────────────────────
inf "Docker installieren..."
curl -fsSL https://get.docker.com | sh > /dev/null 2>&1
systemctl enable docker --now > /dev/null 2>&1
ok "Docker: \$(docker --version | grep -oP 'Docker version \K[^,]+')"
# ── Verzeichnisse ────────────────────────────────────────────────────────────────
mkdir -p "\$FT_DIR"
mkdir -p "\$INSTALL_DIR/portainer"
mkdir -p "\$INSTALL_DIR/filebrowser/config"
mkdir -p "\$INSTALL_DIR/filebrowser/database"
ok "Verzeichnisse angelegt"
# ── Portainer ───────────────────────────────────────────────────────────────────
inf "Portainer installieren (Port \${PT_PORT})..."
cat > "\$INSTALL_DIR/portainer/docker-compose.yml" << 'PTEOF'
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: unless-stopped
ports:
- "PTPORT:9000"
- "9443:9443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
volumes:
portainer_data:
PTEOF
sed -i "s/PTPORT/\${PT_PORT}/" "\$INSTALL_DIR/portainer/docker-compose.yml"
cd "\$INSTALL_DIR/portainer"
docker compose up -d > /dev/null 2>&1
ok "Portainer gestartet"
# ── Filebrowser ──────────────────────────────────────────────────────────────────
inf "Filebrowser installieren (Port \${FB_PORT})..."
FB_PASS=\$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 16)
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
sed -i "s/FBPORT/\${FB_PORT}/" "\$INSTALL_DIR/filebrowser/docker-compose.yml"
cd "\$INSTALL_DIR/filebrowser"
docker compose up -d > /dev/null 2>&1
sleep 5
# Passwort über filebrowser-CLI setzen (zuverlässiger als Log-Parsing)
docker exec filebrowser /filebrowser -d /database/filebrowser.db users update admin --password "\${FB_PASS}" > /dev/null 2>&1 \
|| docker exec filebrowser /filebrowser users update admin --password "\${FB_PASS}" -d /database/filebrowser.db > /dev/null 2>&1 \
|| warn "Filebrowser Passwort konnte nicht automatisch gesetzt werden"
ok "Filebrowser gestartet Passwort: \${FB_PASS}"
echo "FILEBROWSER_PASS=\${FB_PASS}" >> /root/ft_install_info.txt
# ── 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 2>/dev/null
# WebUI von außen erreichbar machen
sed -i 's/127\.0\.0\.1/0.0.0.0/g' docker-compose.yml
# Benutzerdefinierten Port setzen falls nicht 8080
if [[ "\${FT_PORT}" != "8080" ]]; then
sed -i "s/0\.0\.0\.0:8080/0.0.0.0:\${FT_PORT}/" docker-compose.yml
fi
ok "docker-compose.yml bereit"
# ── user_data erstellen ──────────────────────────────────────────────────────────
inf "user_data Verzeichnis erstellen..."
docker compose run --rm freqtrade create-userdir --userdir user_data > /dev/null 2>&1
ok "user_data erstellt"
# ── config.json mit Python3 generieren (sicheres JSON, keine Escaping-Probleme) ──
inf "config.json erstellen..."
export FT_EXCHANGE FT_STAKE FT_MAX_TRADES FT_TIMEFRAME FT_API_USER FT_API_PASS
export TG_TOKEN TG_CHATID TG_ENABLED JWT_SECRET WS_TOKEN FT_DIR FT_PORT
python3 << 'PYEOF'
import json, os
stake = os.environ["FT_STAKE"]
exchange = os.environ["FT_EXCHANGE"]
config = {
"max_open_trades": int(os.environ["FT_MAX_TRADES"]),
"stake_currency": stake,
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "EUR",
"timeframe": os.environ["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": exchange,
"key": "",
"secret": "",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
f"BTC/{stake}",
f"ETH/{stake}",
f"BNB/{stake}"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [{"method": "StaticPairList"}],
"telegram": {
"enabled": os.environ["TG_ENABLED"] == "true",
"token": os.environ["TG_TOKEN"],
"chat_id": os.environ["TG_CHATID"]
},
"api_server": {
"enabled": True,
"listen_ip_address": "0.0.0.0",
"listen_port": 8080,
"verbosity": "error",
"enable_openapi": False,
"jwt_secret_key": os.environ["JWT_SECRET"],
"ws_token": os.environ["WS_TOKEN"],
"CORS_origins": [],
"username": os.environ["FT_API_USER"],
"password": os.environ["FT_API_PASS"]
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": False,
"internals": {"process_throttle_secs": 5}
}
config_path = os.environ["FT_DIR"] + "/user_data/config.json"
with open(config_path, "w") as f:
json.dump(config, f, indent=4)
print(f"config.json geschrieben: {config_path}")
PYEOF
ok "config.json erstellt (via Python3, valides JSON)"
# ── Freqtrade starten ────────────────────────────────────────────────────────────
inf "Freqtrade starten..."
cd "\$FT_DIR"
docker compose up -d > /dev/null 2>&1
sleep 6
if docker ps --filter "name=freqtrade" --filter "status=running" | grep -q freqtrade; then
ok "Freqtrade läuft"
else
echo -e "\${YELLOW}[WARN]\${NC} Freqtrade nicht gestartet prüfe: docker logs freqtrade"
fi
# ── Ergebnis-Datei ────────────────────────────────────────────────────────────────
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}
PT_PORT=\${PT_PORT}
INFOEOF
echo ""
echo "======================================="
echo " Freqtrade Installation abgeschlossen "
echo " FreqUI: http://\$LXC_IP:\${FT_PORT} "
echo "======================================="
INNEREOF
pct push "$CT_ID" /tmp/ft_lxc_setup.sh /root/ft_setup.sh
pct exec "$CT_ID" -- chmod +x /root/ft_setup.sh
info "Starte Setup im LXC (ca. 2-3 Minuten)..."
pct exec "$CT_ID" -- bash /root/ft_setup.sh
rm -f /tmp/ft_lxc_setup.sh
success "Setup im LXC abgeschlossen"
}
# ── Ergebnis anzeigen ──────────────────────────────────────────────────────────
show_result() {
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=""; FILEBROWSER_PASS=""
if [[ -f "$TMP_INFO" ]]; then
source "$TMP_INFO" 2>/dev/null || true
rm -f "$TMP_INFO"
fi
[[ -z "${LXC_IP:-}" ]] && LXC_IP=$(pct exec "$CT_ID" -- hostname -I 2>/dev/null | awk '{print $1}' || echo "unbekannt")
[[ -z "${FILEBROWSER_PASS:-}" ]] && FILEBROWSER_PASS="${FB_PASS:-unbekannt}"
echo
echo -e "${BOLD}${GREEN}╔══════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}${GREEN}║ ✅ Freqtrade LXC Installation komplett! ║${NC}"
echo -e "${BOLD}${GREEN}╚══════════════════════════════════════════════╝${NC}"
echo
echo -e " ${BOLD}LXC:${NC} CT ${CYAN}${CT_ID}${NC} | ${CYAN}${CT_HOST}${NC} | IP: ${CYAN}${LXC_IP}${NC}"
echo
echo -e " ${BOLD}${GREEN}Freqtrade WebUI:${NC}"
echo -e " ${CYAN}http://${LXC_IP}:${FT_PORT}${NC}"
echo -e " Benutzer: ${CYAN}${FT_API_USER}${NC} | Passwort: ${CYAN}(dein gewähltes)${NC}"
echo
echo -e " ${BOLD}${GREEN}Portainer (Docker-Verwaltung):${NC}"
echo -e " ${CYAN}http://${LXC_IP}:${PT_PORT}${NC}"
echo -e " ${YELLOW}→ Beim ersten Aufruf Admin-Passwort setzen!${NC}"
echo
echo -e " ${BOLD}${GREEN}Filebrowser (Dateimanager):${NC}"
echo -e " ${CYAN}http://${LXC_IP}:${FB_PORT}${NC}"
echo -e " Benutzer: ${CYAN}admin${NC} | Passwort: ${BOLD}${CYAN}${FILEBROWSER_PASS}${NC}"
echo -e " ${YELLOW}→ Passwort nach dem Login ändern!${NC}"
echo
echo -e " ${BOLD}Wichtige Dateien:${NC}"
echo -e " Config: ${CYAN}/opt/docker-projekte/freqtrade/user_data/config.json${NC}"
echo -e " Logs: ${CYAN}/opt/docker-projekte/freqtrade/user_data/logs/${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. Strategie anpassen${NC}"
echo -e " ${YELLOW}4. NordVPN optional: install-vpn.sh im LXC ausführen${NC}"
echo
echo -e " ${BOLD}LXC-Konsole öffnen:${NC} ${CYAN}pct enter ${CT_ID}${NC}"
echo -e " ${BOLD}NordVPN Addon:${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
}
# ── Hauptprogramm ──────────────────────────────────────────────────────────────
main() {
print_banner
check_host
detect_resources
prepare_template
collect_config
create_lxc
run_inner_setup
show_result
}
main "$@"