299 lines
8.5 KiB
Bash
299 lines
8.5 KiB
Bash
#!/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="https://install-daten.ploeger-online.de"
|
|
API_URL="$BASE_URL/info.php"
|
|
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
|
|
|