#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" CONFIG_DIR="$ROOT_DIR/config" GLOBAL_CONFIG="$CONFIG_DIR/global/config.json" PROJECTS_DIR="$ROOT_DIR/projects" PROJECT_CONFIG_DIR="$CONFIG_DIR/projects" REGISTRY_CONFIG_DIR="$CONFIG_DIR/registries" LOGS_DIR="$ROOT_DIR/logs" ensure_dirs() { mkdir -p "$CONFIG_DIR/global" "$CONFIG_DIR/projects" "$CONFIG_DIR/registries" "$ROOT_DIR/projects" "$LOGS_DIR" if [[ ! -f "$GLOBAL_CONFIG" ]]; then cat >"$GLOBAL_CONFIG" </dev/null 2>&1; then echo "[WARN] Benötigtes Programm fehlt: $b" fi done } load_global_config() { if [[ -f "$GLOBAL_CONFIG" ]]; then EDITOR_CMD=$(jq -r '.editor // "nano"' "$GLOBAL_CONFIG") else EDITOR_CMD="nano" fi } ensure_dirs load_global_config # ------------------------- # Konfigurations-Menü # ------------------------- config_menu() { while true; do local builder_icon builder_icon=$(get_builder_status_icon) choice=$(whiptail --title "Konfiguration" --menu "Bitte wählen:" 20 70 10 \ 1 "Abhängigkeiten installieren (docker, git, jq, whiptail)" \ 2 "Buildx-Builder einrichten | Status: $builder_icon" \ 3 "Globalen Editor setzen" \ 4 "Standard-Registry setzen" \ 0 "Zurück" \ 3>&1 1>&2 2>&3) || return case $choice in 1) install_dependencies;; 2) install_docker_and_buildx;; 3) set_editor;; 4) set_default_registry ;; 0) return;; esac done } # BuildX-Builder Status Icon get_builder_status_icon() { if docker buildx ls 2>/dev/null | grep -q "multiarch-builder"; then if docker buildx ls | grep "multiarch-builder" | grep -q "running"; then echo "[OK]" else echo "[WARN]" fi else echo "[ERR]" fi } # BuildX-Builder-Status abrufen get_builder_status() { if docker buildx ls 2>/dev/null | grep -q "multiarch-builder"; then if docker buildx ls | grep "multiarch-builder" | grep -q "running"; then echo "[läuft]" else echo "[vorhanden, aber gestoppt]" fi else echo "[nicht eingerichtet]" fi } # Abhängigkeiten installieren install_dependencies() { whiptail --msgbox "Installation von Abhängigkeiten (Beispiel für Debian/Ubuntu). Bitte root-Rechte eingeben." 10 70 sudo apt update sudo apt install -y docker.io git jq whiptail micro } # BuildX-Builder einrichten install_docker_and_buildx() { # Prüfen ob Docker installiert ist if ! command -v docker >/dev/null 2>&1; then whiptail --msgbox "Docker ist nicht installiert! Bitte zuerst über 'Abhängigkeiten installieren' einrichten." 10 70 return fi # Prüfen ob Buildx verfügbar ist if ! docker buildx version >/dev/null 2>&1; then whiptail --msgbox "Docker Buildx-Plugin fehlt! Installiere Buildx..." 10 70 mkdir -p ~/.docker/cli-plugins BUILDX_URL="https://github.com/docker/buildx/releases/latest/download/buildx-$(uname -s)-$(uname -m)" curl -sSL "$BUILDX_URL" -o ~/.docker/cli-plugins/docker-buildx chmod +x ~/.docker/cli-plugins/docker-buildx fi # Prüfen, ob multiarch-builder existiert if ! docker buildx ls | grep -q "multiarch-builder"; then whiptail --msgbox "Richte Buildx-Builder 'multiarch-builder' ein..." 10 70 docker run --rm --privileged tonistiigi/binfmt --install all docker buildx create --name multiarch-builder --use docker buildx inspect --bootstrap >/dev/null 2>&1 fi # Finalprüfung: existiert Builder jetzt wirklich? if docker buildx ls | grep -q "multiarch-builder"; then whiptail --msgbox "Buildx-Builder 'multiarch-builder' erfolgreich eingerichtet." 10 70 else whiptail --msgbox "Fehler: Buildx-Builder konnte nicht eingerichtet werden!" 10 70 fi } # Standard Editor setzen set_editor() { # editor -> paketname declare -A editor_pkg editor_pkg=( ["nano"]="nano" ["vim"]="vim" ["micro"]="micro" ["nvim"]="neovim" ) local options=() declare -A editor_status # Prüfe, ob Editor installiert ist for cmd in "${!editor_pkg[@]}"; do if command -v "$cmd" >/dev/null 2>&1; then editor_status[$cmd]="(installiert)" else editor_status[$cmd]="(nicht installiert)" fi options+=("$cmd" "${editor_status[$cmd]}") done while true; do choice=$(whiptail --title "Editor wählen" --menu "Bitte Editor auswählen:" 20 70 10 "${options[@]}" 3>&1 1>&2 2>&3) || return if command -v "$choice" >/dev/null 2>&1; then jq --arg ed "$choice" '.editor = $ed' "$GLOBAL_CONFIG" >"$GLOBAL_CONFIG.tmp" && mv "$GLOBAL_CONFIG.tmp" "$GLOBAL_CONFIG" whiptail --msgbox "Editor wurde auf '$choice' gesetzt." 10 60 return else install_choice=$(whiptail --title "Editor nicht installiert" \ --yesno "Der Editor '$choice' ist nicht installiert. Möchten Sie ihn jetzt installieren?" 10 60 3>&1 1>&2 2>&3) if [[ $? -eq 0 ]]; then whiptail --msgbox "Installation von ${editor_pkg[$choice]}..." 8 50 sudo apt update sudo apt install -y "${editor_pkg[$choice]}" if command -v "$choice" >/dev/null 2>&1; then # Speziell für nano: Syntax-Highlighting installieren if [[ "$choice" == "nano" ]]; then NANO_DIR="$HOME/.nano" NANORC="$HOME/.nanorc" if [[ -d "$NANO_DIR/.git" ]]; then git -C "$NANO_DIR" pull --quiet else git clone https://github.com/scopatz/nanorc.git "$NANO_DIR" fi if ! grep -q "include $NANO_DIR" "$NANORC" 2>/dev/null; then { echo "## nano syntax highlighting" echo "include $NANO_DIR/*.nanorc" } >> "$NANORC" fi fi whiptail --msgbox "$choice erfolgreich installiert." 8 50 jq --arg ed "$choice" '.editor = $ed' "$GLOBAL_CONFIG" >"$GLOBAL_CONFIG.tmp" && mv "$GLOBAL_CONFIG.tmp" "$GLOBAL_CONFIG" return else whiptail --msgbox "Fehler: $choice konnte nicht installiert werden." 8 50 fi fi fi done } # Standard-Registry setzen set_default_registry() { # Alle verfügbaren Registries auslesen local registries registries=$(ls "$REGISTRY_CONFIG_DIR" 2>/dev/null) [ -z "$registries" ] && { whiptail --msgbox "Keine Registries verfügbar. Bitte zuerst eine Registry erstellen." 10 60; return; } # Menü-Liste vorbereiten local menu_list=() for r in $registries; do menu_list+=("$r" "$r") done # Aktuelle Default-Registry auslesen local current current=$(jq -r '.default_registry // ""' "$GLOBAL_CONFIG") # Auswahl per Whiptail local choice choice=$(whiptail --title "Standard-Registry wählen" \ --menu "Bitte Standard-Registry auswählen:" 20 70 10 \ "${menu_list[@]}" 3>&1 1>&2 2>&3) || return # Speichern in global/config.json jq --arg reg "$choice" '.default_registry = $reg' "$GLOBAL_CONFIG" >"$GLOBAL_CONFIG.tmp" && mv "$GLOBAL_CONFIG.tmp" "$GLOBAL_CONFIG" whiptail --msgbox "Standard-Registry auf '$choice' gesetzt." 10 60 } # Standard-Registry auslesen get_default_registry() { if [[ -f "$GLOBAL_CONFIG" ]]; then jq -r '.default_registry // ""' "$GLOBAL_CONFIG" else echo "" fi } # =============================== # Projektverwaltung # =============================== # Hauptmenü Projektverwaltung project_menu() { while true; do choice=$(whiptail --title "Projektverwaltung" --menu "Bitte wählen:" 20 70 10 \ 1 "Projekt erstellen" \ 2 "Projekt löschen" \ 3 "Projekt bearbeiten" \ 0 "Zurück" \ 3>&1 1>&2 2>&3) || return case $choice in 1) create_project ;; 2) delete_project ;; 3) edit_project ;; 0) return ;; esac done } # Neues Projekt anlegen create_project() { project_name=$(whiptail --inputbox "Projektname eingeben:" 10 60 3>&1 1>&2 2>&3) || return [ -z "$project_name" ] && return mkdir -p "$PROJECT_CONFIG_DIR/$project_name" "$PROJECTS_DIR/$project_name" # Standardwerte setzen default_registry=$(get_default_registry) # Default Config-File cat > "$PROJECT_CONFIG_DIR/$project_name/config-file" <&1 1>&2 2>&3) || return if git clone "$repo_url" "$PROJECTS_DIR/$project_name"; then sed -i "s|^git_repo=.*|git_repo=$repo_url|" "$PROJECT_CONFIG_DIR/$project_name/config-file" else whiptail --msgbox "Fehler beim Klonen des Git-Repos!" 10 60 fi else echo -e "FROM debian:bookworm-slim\nCMD echo 'Hello from $project_name'" > "$PROJECTS_DIR/$project_name/Dockerfile" fi whiptail --msgbox "Projekt $project_name wurde erstellt." 10 60 } # Projekt löschen delete_project() { projects=$(ls "$PROJECT_CONFIG_DIR" 2>/dev/null) [ -z "$projects" ] && { whiptail --msgbox "Keine Projekte vorhanden." 10 60; return; } menu_list=() for p in $projects; do menu_list+=("$p" "") done project=$(whiptail --menu "Projekt zum Löschen auswählen:" 20 60 10 "${menu_list[@]}" 3>&1 1>&2 2>&3) || return if whiptail --yesno "Projekt $project wirklich löschen?" 10 60; then rm -rf "$PROJECT_CONFIG_DIR/$project" "$PROJECTS_DIR/$project" whiptail --msgbox "Projekt $project wurde gelöscht." 10 60 fi } # Projekt bearbeiten edit_project() { projects=$(ls "$PROJECT_CONFIG_DIR" 2>/dev/null) [ -z "$projects" ] && { whiptail --msgbox "Keine Projekte vorhanden." 10 60; return; } # Projekte für Menü vorbereiten (1 Spalte) menu_list=() for p in $projects; do menu_list+=("$p" "") done project=$(whiptail --menu "Projekt auswählen:" 20 60 10 "${menu_list[@]}" 3>&1 1>&2 2>&3) || return local config_file="$PROJECT_CONFIG_DIR/$project/config-file" local project_dir="$PROJECTS_DIR/$project" while true; do repo_url=$(grep "^git_repo=" "$config_file" | cut -d= -f2) menu_items=( 1 "Registries auswählen" 2 "Architekturen auswählen" 3 "Push aktivieren/deaktivieren" 4 "Latest-Tag setzen/entfernen" 5 "Version/Subversion ändern" 6 "Automatische Subversion" 7 "Projektdateien bearbeiten" 9 "Image bauen" ) [ -n "$repo_url" ] && menu_items+=("8" "Git Pull") menu_items+=("0" "Zurück") choice=$(whiptail --title "Projekt bearbeiten: $project" \ --menu "Bitte wählen:" 20 70 12 \ "${menu_items[@]}" \ 3>&1 1>&2 2>&3) || return case $choice in 1) edit_project_registries "$config_file" ;; 2) edit_project_architectures "$config_file" ;; 3) edit_project_push "$config_file" ;; 4) edit_project_latest "$config_file" ;; 5) change_project_setting "$config_file" "version" ;; 6) edit_project_auto_subversion "$config_file" ;; 7) edit_project_files "$project" ;; 8) [ -n "$repo_url" ] && (cd "$project_dir" && git pull || whiptail --msgbox "Git Pull fehlgeschlagen." 10 60) ;; 9) build_image "$project" ;; 0) return ;; esac done } # Registry für Projekt auswählen edit_project_registries() { local config_file=$1 # aktuelle registries im Projekt auslesen local current=$(grep "^registry=" "$config_file" 2>/dev/null | cut -d= -f2 | tr ',' ' ') # alle registries aus Registry-Verwaltung local all_registries all_registries=$(ls "$REGISTRY_CONFIG_DIR" 2>/dev/null) [ -z "$all_registries" ] && { whiptail --msgbox "Keine Registries verfügbar. Bitte zuerst eine Registry erstellen." 10 60; return; } # Default-Registry aus globaler Config local default_registry default_registry=$(get_default_registry) # menu_list für whiptail vorbereiten local menu_list=() for r in $all_registries; do if [[ " $current " =~ " $r " ]]; then menu_list+=("$r" "$r" ON) elif [[ -z "$current" && "$r" == "$default_registry" ]]; then # Projekt hat noch keine registries, Default wird ON menu_list+=("$r" "$r" ON) else menu_list+=("$r" "$r" OFF) fi done # whiptail checklist local selected selected=$(whiptail --title "Registries auswählen" --checklist "Mehrere auswählen (SPACE zum markieren)" 20 70 10 \ "${menu_list[@]}" 3>&1 1>&2 2>&3) || return # Whiptail gibt die Auswahl in Anführungszeichen zurück, diese entfernen und Komma getrennt speichern selected=$(echo $selected | tr -d '"' | tr ' ' ',') # In Projekt-Config speichern sed -i "s|^registry=.*|registry=$selected|" "$config_file" } # Schlüssel im Config-File ändern change_project_setting() { local config_file=$1 local key=$2 local current=$(grep "^$key=" "$config_file" | cut -d= -f2) new_value=$(whiptail --inputbox "$key ändern (aktuell: $current)" 10 60 "$current" 3>&1 1>&2 2>&3) || return sed -i "s|^$key=.*|$key=$new_value|" "$config_file" } # Aktuellen Editor aus global/config.json holen get_editor_cmd() { if [[ -f "$GLOBAL_CONFIG" ]]; then jq -r '.editor // "nano"' "$GLOBAL_CONFIG" else echo "nano" fi } # Projektdateien bearbeiten (Dateiauswahl + Editor) edit_project_files() { local project=$1 local project_dir="$PROJECTS_DIR/$project" # Prüfen ob Projektordner existiert if [[ ! -d "$project_dir" ]]; then whiptail --msgbox "Projektordner nicht gefunden." 10 60 return fi # Editor aus globaler Config holen local editor_cmd editor_cmd=$(get_editor_cmd) # dialog-Dateiauswahl local file file=$(dialog --title "Datei auswählen" --fselect "$project_dir/" 20 70 3>&1 1>&2 2>&3) || return [[ -z "$file" ]] && return # Datei im Editor öffnen "$editor_cmd" "$file" } edit_project_architectures() { local config_file=$1 local current=$(grep "^architectures=" "$config_file" | cut -d= -f2 | tr ',' ' ') local all_archs=("amd64" "arm64" "armhf" "x86") menu_list=() for a in "${all_archs[@]}"; do if [[ " $current " =~ " $a " ]]; then menu_list+=("$a" "$a" ON) else menu_list+=("$a" "$a" OFF) fi done selected=$(whiptail --title "Architekturen auswählen" --checklist "Mehrere auswählen" 20 70 10 "${menu_list[@]}" 3>&1 1>&2 2>&3) || return selected=$(echo $selected | tr -d '"') sed -i "s|^architectures=.*|architectures=$selected|" "$config_file" } edit_project_push() { local config_file=$1 local current=$(grep "^push=" "$config_file" | cut -d= -f2) local value="OFF" [ "$current" == "yes" ] && value="ON" selected=$(whiptail --title "Push aktivieren?" --checklist "Push auswählen" 10 50 1 push "Push aktivieren" $value 3>&1 1>&2 2>&3) || return [[ $selected == *push* ]] && val="yes" || val="no" sed -i "s|^push=.*|push=$val|" "$config_file" } edit_project_latest() { local config_file=$1 local current=$(grep "^latest=" "$config_file" | cut -d= -f2) local value="OFF" [ "$current" == "yes" ] && value="ON" selected=$(whiptail --title "Latest-Tag setzen?" --checklist "Latest auswählen" 10 50 1 latest "Latest setzen" $value 3>&1 1>&2 2>&3) || return [[ $selected == *latest* ]] && val="yes" || val="no" sed -i "s|^latest=.*|latest=$val|" "$config_file" } edit_project_auto_subversion() { local config_file=$1 local current=$(grep "^auto_subversion=" "$config_file" | cut -d= -f2) local value="OFF" [ "$current" == "yes" ] && value="ON" selected=$(whiptail --title "Subversion automatisch erhöhen?" --checklist "Auto Subversion auswählen" 10 50 1 auto "Automatisch erhöhen" $value 3>&1 1>&2 2>&3) || return [[ $selected == *auto* ]] && val="yes" || val="no" # falls auto_subversion key nicht existiert, hinzufügen if grep -q "^auto_subversion=" "$config_file"; then sed -i "s|^auto_subversion=.*|auto_subversion=$val|" "$config_file" else echo "auto_subversion=$val" >> "$config_file" fi } # Hauptmenü - Registryverwaltung registry_menu() { while true; do choice=$(whiptail --title "Registry-Verwaltung" --menu "Bitte wählen:" 20 70 10 \ 1 "Registry erstellen" \ 2 "Registry löschen" \ 3 "Registry bearbeiten" \ 0 "Zurück" \ 3>&1 1>&2 2>&3) || return case $choice in 1) create_registry ;; 2) delete_registry ;; 3) edit_registry ;; 0) return ;; esac done } # Registry anlegen create_registry() { local name url username password name=$(whiptail --inputbox "Name der Registry (z.B. docker.io):" 10 60 3>&1 1>&2 2>&3) || return [ -z "$name" ] && return url=$(whiptail --inputbox "URL der Registry:" 10 60 3>&1 1>&2 2>&3) || return username=$(whiptail --inputbox "Benutzername:" 10 60 3>&1 1>&2 2>&3) || return password=$(whiptail --passwordbox "Passwort:" 10 60 3>&1 1>&2 2>&3) || return mkdir -p "$REGISTRY_CONFIG_DIR/$name" cat > "$REGISTRY_CONFIG_DIR/$name/config-file" </dev/null) [ -z "$registries" ] && { whiptail --msgbox "Keine Registries vorhanden." 10 60; return; } menu_list=() for r in $registries; do menu_list+=("$r" "") done local reg reg=$(whiptail --menu "Registry zum Löschen auswählen:" 20 60 10 "${menu_list[@]}" 3>&1 1>&2 2>&3) || return if whiptail --yesno "Registry $reg wirklich löschen?" 10 60; then rm -rf "$REGISTRY_CONFIG_DIR/$reg" whiptail --msgbox "Registry $reg wurde gelöscht." 10 60 fi } # Registry bearbeiten edit_registry() { local registries registries=$(ls "$REGISTRY_CONFIG_DIR" 2>/dev/null) [ -z "$registries" ] && { whiptail --msgbox "Keine Registries vorhanden." 10 60; return; } menu_list=() for r in $registries; do menu_list+=("$r" "") done local reg reg=$(whiptail --menu "Registry zum Bearbeiten auswählen:" 20 60 10 "${menu_list[@]}" 3>&1 1>&2 2>&3) || return local config_file="$REGISTRY_CONFIG_DIR/$reg/config-file" local url username password url=$(grep "^url=" "$config_file" | cut -d= -f2) username=$(grep "^username=" "$config_file" | cut -d= -f2) password=$(grep "^password=" "$config_file" | cut -d= -f2) # Eingaben ändern url=$(whiptail --inputbox "URL:" 10 60 "$url" 3>&1 1>&2 2>&3) || return username=$(whiptail --inputbox "Benutzername:" 10 60 "$username" 3>&1 1>&2 2>&3) || return password=$(whiptail --passwordbox "Passwort:" 10 60 "$password" 3>&1 1>&2 2>&3) || return cat > "$config_file" <>"$logfile" || { echo "WARNUNG: Login bei $reg fehlgeschlagen!" | tee -a "$logfile" } fi fi tags+=("-t" "$reg/$image_name:$version") if [[ "$latest" == "yes" ]]; then tags+=("-t" "$reg/$image_name:latest") fi done if [[ ${#tags[@]} -eq 0 ]]; then echo "Keine Registry-Tags definiert. Build abgebrochen." | tee -a "$logfile" return 1 fi # Build-Befehl zusammenbauen if [[ "$push" == "yes" ]]; then cmd=(docker buildx build --platform "$platforms" "${tags[@]}" "$PROJECTS_DIR/$project" --push) else cmd=(docker buildx build --platform "$platforms" "${tags[@]}" "$PROJECTS_DIR/$project" --load) fi echo "${cmd[*]}" | tee -a "$logfile" if "${cmd[@]}" >>"$logfile" 2>&1; then echo "==== Build beendet: $(date) ====" | tee -a "$logfile" # Auto-Subversion if [[ "$auto_subversion" == "yes" ]]; then local major minor major=$(echo "$version" | cut -d. -f1) minor=$(echo "$version" | cut -d. -f2) minor=$((minor + 1)) new_version="${major}.${minor}" sed -i "s/^version=.*/version=$new_version/" "$config_file" echo "Subversion automatisch auf $new_version erhöht." | tee -a "$logfile" fi else echo "==== Build fehlgeschlagen: $(date) ====" | tee -a "$logfile" return 1 fi } # Logs ansehen (optional gefiltert nach Projekt) view_logs() { #ensure_logs_dir local project_filter="${1:-}" # wenn Projekt übergeben, nur dessen Logs anzeigen if [ -n "$project_filter" ]; then logs=$(ls -1t "$LOGS_DIR/${project_filter}"_*.log 2>/dev/null | sed "s|$LOGS_DIR/||") else logs=$(ls -1t "$LOGS_DIR"/*.log 2>/dev/null | sed "s|$LOGS_DIR/||") fi [ -z "$logs" ] && { whiptail --msgbox "Keine Logs vorhanden." 10 60; return; } menu_list=() for l in $logs; do menu_list+=("$l" "") done log_file=$(whiptail --menu "Log auswählen:" 20 70 12 "${menu_list[@]}" 3>&1 1>&2 2>&3) || return # Editor aus globaler Config lesen local editor=$(jq -r '.editor' "$GLOBAL_CONFIG") ${editor:-nano} "$LOGS_DIR/$log_file" } # Projektübersicht anzeigen # Projektübersicht mit Aktionen project_overview() { local project=$1 local config_file="$PROJECT_CONFIG_DIR/$project/config-file" load_project_config "$config_file" # Registries als Array IFS=' ' read -r -a regs <<< "$registry" while true; do local choice choice=$(whiptail --title "Projektübersicht: $project" --menu "Parameter:" 20 70 12 \ "1" "Registries: ${registry}" \ "2" "Image-Name: $image_name" \ "3" "Architekturen: $architectures" \ "4" "Push: $push" \ "5" "Version: $version" \ "6" "Latest-Tag: $latest" \ "7" "Git-Repo: $git_repo" \ "B" "Bauen" \ "E" "Bearbeiten" \ "P" "Projektauswahl wechseln" \ "L" "Logs ansehen" \ "Z" "Zurück" \ 3>&1 1>&2 2>&3) || return case $choice in B) build_image "$project" ;; E) edit_project "$project" ;; # Bearbeitungsmenü für das Projekt P) return ;; # zurück zur Projektauswahl L) view_logs ;; Z) return ;; esac done } # Projektauswahl für Image-Bauen select_project_for_build() { local projects projects=$(ls "$PROJECT_CONFIG_DIR" 2>/dev/null) [ -z "$projects" ] && { whiptail --msgbox "Keine Projekte vorhanden." 10 60; return; } # Projekte für Menü vorbereiten (1-Spalte) local menu_list=() for p in $projects; do menu_list+=("$p" "") done while true; do local project project=$(whiptail --title "Projekt auswählen" --menu "Projekt für Build auswählen:" 20 60 10 "${menu_list[@]}" 3>&1 1>&2 2>&3) || return # Projektübersicht anzeigen project_overview "$project" done } # Menüpunkt im Hauptmenü image_build_menu() { select_project_for_build } # ------------------------- # Hauptmenü # ------------------------- main_menu() { while true; do choice=$(whiptail --title "Image Builder" --menu "Bitte wählen:" 20 70 10 \ 1 "Konfiguration" \ 2 "Projektverwaltung" \ 3 "Registryverwaltung" \ 4 "Image-Bauen" \ 5 "Image-Verwaltung" \ 6 "Logs ansehen" \ 0 "Beenden" \ 3>&1 1>&2 2>&3) || exit 0 case $choice in 1) config_menu;; 2) project_menu;; 3) registry_menu ;; 4) image_build_menu;; 5) whiptail --msgbox "Image-Verwaltung (noch nicht implementiert)" 10 70;; 6) view_logs ;; # <-- hier globale Logs 0) exit 0;; esac done } # ------------------------- # Main # ------------------------- ensure_dirs ensure_prereqs main_menu