#!/usr/bin/env bash set -euo pipefail # --- Root / Sudo Logik --- if [[ $EUID -ne 0 ]]; then if ! command -v sudo >/dev/null 2>&1; then echo -e "\033[0;31mFehler: Dieses Script benötigt sudo, ist aber nicht installiert.\033[0m" echo "Bitte installiere sudo zuerst oder führe das Script als root aus." exit 1 fi SUDO="sudo" else SUDO="" fi BASE_URL="http://192.168.19.10:3020/madgerm/installerscript-sh.git" API_URL="$BASE_URL/info.php" # Annahme: info.php ist weiterhin unter BASE_URL/info.php erreichbar LOG_FILE="/tmp/homelab-installer.log" TMP_DIR="/tmp/homelab-installer" mkdir -p "$TMP_DIR" # Farben GREEN="\033[0;32m" YELLOW="\033[1;33m" RED="\033[0;31m" NC="\033[0m" log() { echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" } cleanup() { echo "" log "${RED}⛔ Installation abgebrochen durch Benutzer.${NC}" rm -rf "$TMP_DIR" 2>/dev/null || true log "🛑 Abbruch abgeschlossen." exit 130 } trap cleanup INT # --- Globale Pfadbasis für Passwort-Speicherung --- INSTALLER_BASE_DIR="$(pwd)" PASSWORD_STORE_FILE="$INSTALLER_BASE_DIR/keys.txt" # --- Root / Sudo Helper --- ensure_root() { if [[ $EUID -ne 0 ]]; then if ! command -v sudo >/dev/null 2>&1; then log "${RED}Fehler: Dieses Script benötigt Root oder sudo.${NC}" exit 1 fi SUDO="sudo" else SUDO="" fi } # --- Package Manager Erkennung --- detect_pkg_manager() { if command -v apt >/dev/null 2>&1; then PKG="apt" elif command -v dnf >/dev/null 2>&1; then PKG="dnf" elif command -v pacman >/dev/null 2>&1; then PKG="pacman" elif command -v apk >/dev/null 2>&1; then PKG="apk" else log "${RED}Kein unterstützter Paketmanager gefunden.${NC}" exit 1 fi } pkg_install() { case "$PKG" in apt) $SUDO apt update && $SUDO apt install -y "$@" ;; dnf) $SUDO dnf install -y "$@" ;; pacman) $SUDO pacman --noconfirm -Sy "$@" ;; apk) $SUDO apk add "$@" ;; esac } # --- Passwort Generator --- generate_password() { local name="$1" local pass pass="$(openssl rand -base64 24)" echo "$name = $pass" >> "$PASSWORD_STORE_FILE" log "${GREEN}🔐 Passwort erzeugt und gespeichert unter:${NC} $PASSWORD_STORE_FILE" echo "$pass" } check_internet() { ping -c 1 1.1.1.1 &>/dev/null || { log "${RED}❗ Kein Internet erkannt.${NC}" exit 1 } } log "🔍 Prüfe benötigte Programme..." MISSING_PKGS=() # --- Passwort Bereich schreiben --- begin_password_section() { local section="$1" echo "" >> "$PASSWORD_STORE_FILE" echo "===== $section =====" >> "$PASSWORD_STORE_FILE" } end_password_section() { echo "===== ENDE $1 =====" >> "$PASSWORD_STORE_FILE" echo "" >> "$PASSWORD_STORE_FILE" } # Passwort Generator (nutzt nun Section-Kontext) generate_password() { local key_name="$1" local password password="$(openssl rand -base64 24)" echo "$key_name = $password" >> "$PASSWORD_STORE_FILE" log "${GREEN}🔐 Passwort erzeugt:${NC} $key_name" echo "$password" } need_cmd() { local c="$1" if ! command -v "$c" &>/dev/null; then MISSING_PKGS+=("$c") else log "${GREEN}OK:${NC} $c vorhanden." fi } need_cmd curl need_cmd wget need_cmd jq need_cmd whiptail if (( ${#MISSING_PKGS[@]} > 0 )); then log "${YELLOW}Fehlende Pakete:${NC} ${MISSING_PKGS[*]}" read -rp "Soll ich diese installieren? [n/Y]: " ans [[ "$ans" =~ ^[YyJj]$ ]] || { log "Abbruch."; exit 1; } $SUDO apt update $SUDO apt install -y "${MISSING_PKGS[@]}" fi log "${GREEN}✅ Grundpakete vollständig.${NC}" # --- Optional Ansible --- if ! command -v ansible-playbook &>/dev/null; then echo "" echo "Ansible wird nur benötigt, wenn du Playbook-basierte Rezepte nutzen möchtest." read -rp "Möchtest du Ansible installieren? [n/Y]: " install_ansible if [[ "$install_ansible" =~ ^[YyJj]$ ]]; then echo "" echo "Installationsart:" echo " 1) apt (einfach, aber ältere Version möglich)" echo " 2) pip (empfohlen; ARM & x86 kompatibel; immer aktuell)" read -rp "Auswahl [1/2, default 2]: " mode mode="${mode:-2}" if [[ "$mode" == "1" ]]; then $SUDO apt update $SUDO apt install -y ansible else if ! command -v pip3 &>/dev/null; then $SUDO apt update $SUDO apt install -y python3-pip fi pip3 install --break-system-packages ansible fi log "${GREEN}✅ Ansible installiert.${NC}" else log "${YELLOW}⏭ Ansible wird übersprungen.${NC}" fi else log "${GREEN}OK:${NC} ansible-playbook vorhanden." fi install_docker() { if ! command -v docker &> /dev/null; then log "📦 Installiere Docker..." pkg_install ca-certificates curl gnupg lsb-release $SUDO install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/debian/gpg | $SUDO gpg --dearmor -o /etc/apt/keyrings/docker.gpg $SUDO chmod a+r /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | $SUDO tee /etc/apt/sources.list.d/docker.list > /dev/null pkg_install docker-ce docker-ce-cli containerd.io docker-compose-plugin log "${GREEN}✅ Docker installiert.${NC}" else log "${GREEN}OK:${NC} Docker ist bereits installiert." fi } ask_to_install() { local name="$1" read -rp "Möchtest du '$name' installieren? [J/n]: " ans [[ "$ans" =~ ^[JjYy]$ || -z "$ans" ]] } choose_from_list() { local title="$1" shift local items=("$@") local ROWS COLS H W ROWS=$(tput lines) COLS=$(tput cols) H=$((ROWS * 80 / 100)) W=$((COLS * 80 / 100)) (( H < 15 )) && H=15 (( W < 40 )) && W=40 local menu_items=() for item in "${items[@]}"; do menu_items+=("$item" "") done local choice choice=$(whiptail --title "$title" --menu "Mit ↑ ↓ und ENTER auswählen:" \ "$H" "$W" 15 \ "${menu_items[@]}" \ 3>&1 1>&2 2>&3) || echo "back" echo "$choice" } run_shell_recipe() { local category="$1" local recipe="$2" local url="$BASE_URL/recipes/$category/$recipe/install.sh" local script="$TMP_DIR/${category}_${recipe}_$(date +%s).sh" log "📥 Lade Shell Installer..." curl -fsSL "$url" -o "$script" chmod +x "$script" log "🚀 Starte Shell Installer..." bash "$script" } run_ansible_recipe() { local category="$1" local recipe="$2" local url="$BASE_URL/recipes/$category/$recipe/playbook.yml" local file="/opt/homelab/playbooks/${category}_${recipe}.yml" $SUDO mkdir -p /opt/homelab/playbooks log "📥 Lade Ansible Playbook..." curl -fsSL "$url" -o "$file" log "🔧 Führe Ansible Playbook aus..." ansible-playbook -i localhost, "$file" } run_recipe() { local category="$1" local recipe="$2" local base="$BASE_URL/recipes/$category/$recipe" local has_shell=$(curl -s --head "$base/install.sh" | grep -q "200" && echo yes || echo no) local has_playbook=$(curl -s --head "$base/playbook.yml" | grep -q "200" && echo yes || echo no) if [[ "$has_shell" == "yes" && "$has_playbook" == "no" ]]; then run_shell_recipe "$category" "$recipe"; return; fi if [[ "$has_playbook" == "yes" && "$has_shell" == "no" ]]; then run_ansible_recipe "$category" "$recipe"; return; fi if [[ "$has_shell" == "yes" && "$has_playbook" == "yes" ]]; then mode=$(choose_from_list "Installationsmodus wählen" "Shell" "Ansible") [[ "$mode" == "Shell" ]] && run_shell_recipe "$category" "$recipe" [[ "$mode" == "Ansible" ]] && run_ansible_recipe "$category" "$recipe" return fi whiptail --title "Fehler" --msgbox "Kein Installer gefunden." 10 50 } open_category() { local category="$1" mapfile -t recipes < <(jq -r ".recipes.\"$category\"[]" "$TMP_DIR/info.json") while true; do choice=$(choose_from_list "Rezept wählen ($category)" "${recipes[@]}") [[ "$choice" == "back" || -z "$choice" ]] && return run_recipe "$category" "$choice" done } main_menu() { mapfile -t categories < <(jq -r '.recipes | keys[]' "$TMP_DIR/info.json") while true; do choice=$(choose_from_list "Kategorie wählen" "${categories[@]}") [[ "$choice" == "back" || -z "$choice" ]] && continue open_category "$choice" done } check_internet log "📥 Lade Menüstruktur..." curl -fsSL "$API_URL" -o "$TMP_DIR/info.json" log "🚀 Starte Homelab Installer" main_menu