diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c3fef3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +github-pat +projects/* +logs/* +config/global/* +config/projects/* +config/registries/* +_old +_info diff --git a/scripts/image-builder.sh b/scripts/image-builder.sh index 6f29e06..18745c9 100755 --- a/scripts/image-builder.sh +++ b/scripts/image-builder.sh @@ -1,855 +1,349 @@ #!/usr/bin/env bash + +# ----------------------------------------------------------------------------- +# Globale Einstellungen & UI-Theme +# ----------------------------------------------------------------------------- set -euo pipefail IFS=$'\n\t' -ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +export DIALOGRC=$HOME/.dialogrc +if [ ! -f "$DIALOGRC" ]; then + cat > "$DIALOGRC" <"$GLOBAL_CONFIG" < "$GLOBAL_CONFIG"; fi } -ensure_prereqs() { - local bins=("git" "jq" "dialog") - for b in "${bins[@]}"; do - if ! command -v "$b" >/dev/null 2>&1; then - echo "[WARN] Benoetigtes Programm fehlt: $b" - fi - done +get_glob() { jq -r "$1" "$GLOBAL_CONFIG" 2>/dev/null || echo "nano"; } + +# Sicherer Toggle ohne Sonderzeichen-Probleme +toggle_json_bool() { + local key=$1; local file=$2 + jq --arg k "$key" '.[$k] = if .[$k] == true then false else true end' "$file" > "$file.tmp" && mv "$file.tmp" "$file" } -load_global_config() { - if [[ -f "$GLOBAL_CONFIG" ]]; then - EDITOR_CMD=$(jq -r '.editor // "nano"' "$GLOBAL_CONFIG") - else - EDITOR_CMD="nano" - fi +# ----------------------------- +# Hilfsfunktion: sichere Strings für dialog +# ----------------------------- +safe() { + echo "$1" | tr -d '\n\r' | sed 's/"/'"'"'/g' } -ensure_dirs -load_global_config - -# ------------------------- -# Konfigurations-Menue -# ------------------------- -config_menu() { - while true; do - local builder_icon - builder_icon=$(get_builder_status_icon) - - choice=$(dialog --title "Konfiguration" --menu "Bitte waehlen:" 20 70 10 \ - 1 "Abhaengigkeiten installieren (docker, git, jq, dialog)" \ - 2 "Buildx-Builder einrichten | Status: $builder_icon" \ - 3 "Globalen Editor setzen" \ - 4 "Standard-Registry setzen" \ - 0 "Zurueck" \ - 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 +# ----------------------------------------------------------------------------- +# System & Registries (Bleiben unverändert) +# ----------------------------------------------------------------------------- +system_config_menu() { + while true; do + choice=$(dialog --title " System " --menu "Aktionen:" $W_H $W_W $M_H 1 "Pakete installieren" 2 "Docker/Buildx Setup" 3 "Editor" 0 "Zurueck" 3>&1 1>&2 2>&3) || return + case $choice in + 1) clear; sudo apt update && sudo apt install -y git jq dialog curl docker.io; read -p "Enter..." ;; + 2) clear; sudo docker run --rm --privileged tonistiigi/binfmt --install all; docker buildx create --name multiarch-builder --use || docker buildx use multiarch-builder; docker buildx inspect --bootstrap; sleep 2 ;; + 3) ed=$(dialog --inputbox "Editor:" 10 60 "$(get_glob '.editor')" 3>&1 1>&2 2>&3) && jq --arg e "$ed" '.editor=$e' "$GLOBAL_CONFIG" > "$GLOBAL_CONFIG.tmp" && mv "$GLOBAL_CONFIG.tmp" "$GLOBAL_CONFIG" ;; + 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 "[laeuft]" - else - echo "[vorhanden, aber gestoppt]" - fi - else - echo "[nicht eingerichtet]" - fi -} - -# Abhaengigkeiten installieren -install_dependencies() { - dialog --msgbox "Installation von Abhaengigkeiten (Beispiel fuer Debian/Ubuntu). Bitte root-Rechte eingeben." 10 70 - clear - sudo apt update - sudo apt install -y docker.io git jq dialog micro -} - -# BuildX-Builder einrichten -install_docker_and_buildx() { - # Pruefen ob Docker installiert ist - if ! command -v docker >/dev/null 2>&1; then - dialog --msgbox "Docker ist nicht installiert! Bitte zuerst ueber 'Abhaengigkeiten installieren' einrichten." 10 70 - return - fi - - # Pruefen ob Buildx verfuegbar ist - if ! docker buildx version >/dev/null 2>&1; then - dialog --msgbox "Docker Buildx-Plugin fehlt! Installiere Buildx..." 10 70 - clear - 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 - - # Pruefen, ob multiarch-builder existiert - if ! docker buildx ls | grep -q "multiarch-builder"; then - dialog --msgbox "Richte Buildx-Builder 'multiarch-builder' ein..." 10 70 - clear - docker run --rm --privileged tonistiigi/binfmt --install all - docker buildx create --name multiarch-builder --use - docker buildx inspect --bootstrap >/dev/null 2>&1 - fi - - # Finalpruefung: existiert Builder jetzt wirklich? - if docker buildx ls | grep -q "multiarch-builder"; then - dialog --msgbox "Buildx-Builder 'multiarch-builder' erfolgreich eingerichtet." 10 70 - else - dialog --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 - - # Pruefe, 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=$(dialog --title "Editor waehlen" --menu "Bitte Editor auswaehlen:" 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" - dialog --msgbox "Editor wurde auf '$choice' gesetzt." 10 60 - return - else - install_choice=$(dialog --title "Editor nicht installiert" \ - --yesno "Der Editor '$choice' ist nicht installiert. Moechten Sie ihn jetzt installieren?" 10 60 3>&1 1>&2 2>&3) - if [[ $? -eq 0 ]]; then - dialog --msgbox "Installation von ${editor_pkg[$choice]}..." 8 50 - clear - sudo apt update - sudo apt install -y "${editor_pkg[$choice]}" - - if command -v "$choice" >/dev/null 2>&1; then - # Speziell fuer 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 - clear - 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 - - dialog --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 - dialog --msgbox "Fehler: $choice konnte nicht installiert werden." 8 50 - fi - fi - fi - done -} - -# Standard-Registry setzen -set_default_registry() { - # Alle verfuegbaren Registries auslesen - local registries - registries=$(ls "$REGISTRY_CONFIG_DIR" 2>/dev/null) - [ -z "$registries" ] && { dialog --msgbox "Keine Registries verfuegbar. Bitte zuerst eine Registry erstellen." 10 60; return; } - - # Menue-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 dialog - local choice - choice=$(dialog --title "Standard-Registry waehlen" \ - --menu "Bitte Standard-Registry auswaehlen:" 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" - - dialog --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 -# =============================== - -# Hauptmenue Projektverwaltung -project_menu() { - while true; do - choice=$(dialog --title "Projektverwaltung" --menu "Bitte waehlen:" 20 70 10 \ - 1 "Projekt erstellen" \ - 2 "Projekt loeschen" \ - 3 "Projekt bearbeiten" \ - 0 "Zurueck" \ - 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=$(dialog --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 - clear - sed -i "s|^git_repo=.*|git_repo=$repo_url|" "$PROJECT_CONFIG_DIR/$project_name/config-file" - else - dialog --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 - - dialog --msgbox "Projekt $project_name wurde erstellt." 10 60 -} - -# Projekt loeschen -delete_project() { - projects=$(ls "$PROJECT_CONFIG_DIR" 2>/dev/null) - [ -z "$projects" ] && { dialog --msgbox "Keine Projekte vorhanden." 10 60; return; } - - menu_list=() - for p in $projects; do - menu_list+=("$p" "") - done - - project=$(dialog --menu "Projekt zum Loeschen auswaehlen:" 20 60 10 "${menu_list[@]}" 3>&1 1>&2 2>&3) || return - - if dialog --yesno "Projekt $project wirklich loeschen?" 10 60; then - rm -rf "$PROJECT_CONFIG_DIR/$project" "$PROJECTS_DIR/$project" - dialog --msgbox "Projekt $project wurde geloescht." 10 60 - fi -} - -# Projekt bearbeiten -edit_project() { - projects=$(ls "$PROJECT_CONFIG_DIR" 2>/dev/null) - [ -z "$projects" ] && { dialog --msgbox "Keine Projekte vorhanden." 10 60; return; } - - # Projekte fuer Menue vorbereiten (1 Spalte) - menu_list=() - for p in $projects; do - menu_list+=("$p" "") - done - - project=$(dialog --menu "Projekt auswaehlen:" 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 auswaehlen" - 2 "Architekturen auswaehlen" - 3 "Push aktivieren/deaktivieren" - 4 "Latest-Tag setzen/entfernen" - 5 "Version/Subversion aendern" - 6 "Automatische Subversion" - 7 "Projektdateien bearbeiten" - 8 "Image bauen" - ) - - [ -n "$repo_url" ] && menu_items+=("9" "Git Pull") - - menu_items+=("0" "Zurueck") - - choice=$(dialog --title "Projekt bearbeiten: $project" \ - --menu "Bitte waehlen:" 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) build_image "$project" ;; - 9) gitpull_project "$project";; - 0) return ;; - esac - done -} - - -# Registry fuer Projekt auswaehlen -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" ] && { dialog --msgbox "Keine Registries verfuegbar. Bitte zuerst eine Registry erstellen." 10 60; return; } - - # Default-Registry aus globaler Config - local default_registry - default_registry=$(get_default_registry) - - # menu_list fuer dialog 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 - - # dialog checklist - local selected - selected=$(dialog --title "Registries auswaehlen" --checklist "Mehrere auswaehlen (SPACE zum markieren)" 20 70 10 \ - "${menu_list[@]}" 3>&1 1>&2 2>&3) || return - - # dialog gibt die Auswahl in Anfuehrungszeichen zurueck, diese entfernen und Komma getrennt speichern - selected=$(echo $selected | tr -d '"' | tr ' ' ',') - - # In Projekt-Config speichern - sed -i "s|^registry=.*|registry=$selected|" "$config_file" -} - -# Schluessel im Config-File aendern -change_project_setting() { - local config_file=$1 - local key=$2 - local current=$(grep "^$key=" "$config_file" | cut -d= -f2) - new_value=$(dialog --inputbox "$key aendern (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" - - # Pruefen ob Projektordner existiert - if [[ ! -d "$project_dir" ]]; then - dialog --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 auswaehlen" --fselect "$project_dir/" 20 70 3>&1 1>&2 2>&3) || return - [[ -z "$file" ]] && return - - # Datei im Editor oeffnen - "$editor_cmd" "$file" -} - -edit_project_architectures() { - local config_file=$1 - local current=$(grep "^architectures=" "$config_file" | cut -d= -f2 | tr ',' ' ') - local all_archs=("amd64" "386" "arm64/v8" "arm/v7" "arm/v6" "arm/v5") - 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=$(dialog --title "Architekturen auswaehlen" --checklist "Mehrere auswaehlen" 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=$(dialog --title "Push aktivieren?" --checklist "Push auswaehlen" 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=$(dialog --title "Latest-Tag setzen?" --checklist "Latest auswaehlen" 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=$(dialog --title "Subversion automatisch erhoehen?" --checklist "Auto Subversion auswaehlen" 10 50 1 auto "Automatisch erhoehen" $value 3>&1 1>&2 2>&3) || return - [[ $selected == *auto* ]] && val="yes" || val="no" - # falls auto_subversion key nicht existiert, hinzufuegen - 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 -} - -gitpull_project() { - local project=$1 - local project_dir="$PROJECTS_DIR/$project" - local config_file="$PROJECT_CONFIG_DIR/$project/config-file" - - # Prüfen ob git_repo gesetzt ist - local repo_url - repo_url=$(grep "^git_repo=" "$config_file" | cut -d= -f2) - - if [[ -z "$repo_url" ]]; then - dialog --msgbox "Dieses Projekt ist nicht mit einem Git-Repository verknüpft." 10 60 - return - fi - - # Prüfen ob Ordner existiert - if [[ ! -d "$project_dir/.git" ]]; then - dialog --msgbox "Der Projektordner enthaelt kein Git-Repository.\nVielleicht wurde er gelöscht oder bearbeitet?" 12 60 - return - fi - - # Git Pull durchführen - { - echo "Starte git pull..." - git -C "$project_dir" pull - } >"$LOGS_DIR/gitpull_${project}_$(date +"%Y%m%d_%H%M%S").log" 2>&1 - - if [[ $? -eq 0 ]]; then - dialog --msgbox "Git Pull erfolgreich abgeschlossen." 10 60 - else - dialog --msgbox "FEHLER beim Git Pull!\nSiehe Log-Datei unter logs/." 12 60 - fi -} - -# Hauptmenue - Registryverwaltung registry_menu() { - while true; do - choice=$(dialog --title "Registry-Verwaltung" --menu "Bitte waehlen:" 20 70 10 \ - 1 "Registry erstellen" \ - 2 "Registry loeschen" \ - 3 "Registry bearbeiten" \ - 0 "Zurueck" \ - 3>&1 1>&2 2>&3) || return - - case $choice in - 1) create_registry ;; - 2) delete_registry ;; - 3) edit_registry ;; - 0) return ;; - esac - done + while true; do + local regs=$(ls "$REGISTRY_CONFIG_DIR" 2>/dev/null | sed 's/\.json//' || true) + local list=(); for r in $regs; do list+=("$r" "Registry"); done + choice=$(dialog --title " Registries " --menu "Liste:" $W_H $W_W $M_H "ADD" "[+] Neu" "${list[@]}" "0" "Zurueck" 3>&1 1>&2 2>&3) || return + if [ "$choice" = "0" ]; then return; fi + if [ "$choice" = "ADD" ]; then + local n=$(dialog --inputbox "Name:" 10 60 3>&1 1>&2 2>&3) || continue + local url=$(dialog --inputbox "URL (leer f. Dockerhub):" 10 60 3>&1 1>&2 2>&3) || continue + local user=$(dialog --inputbox "User:" 10 60 3>&1 1>&2 2>&3) || continue + local pass=$(dialog --passwordbox "Pass:" 10 60 3>&1 1>&2 2>&3) || continue + echo "{\"name\":\"$n\",\"url\":\"$url\",\"user\":\"$user\",\"pass\":\"$pass\"}" > "$REGISTRY_CONFIG_DIR/$n.json" + else + sub=$(dialog --menu "Aktion: $choice" 12 60 4 "EDIT" "Bearbeiten" "DEL" "Loeschen" "0" "Abbruch" 3>&1 1>&2 2>&3) || continue + if [ "$sub" = "EDIT" ]; then $(get_glob '.editor') "$REGISTRY_CONFIG_DIR/$choice.json"; fi + if [ "$sub" = "DEL" ]; then rm "$REGISTRY_CONFIG_DIR/$choice.json"; fi + fi + done } -# Registry anlegen -create_registry() { - local name url username password +# ----------------------------------------------------------------------------- +# Lokale Docker Images verwalten +# ----------------------------------------------------------------------------- +local_images_menu() { + while true; do + # Images auflisten (Repo:Tag) + mapfile -t images < <(docker images --format '{{.Repository}}:{{.Tag}}' | grep -v '' || true) - name=$(dialog --inputbox "Name der Registry (z.B. docker.io):" 10 60 3>&1 1>&2 2>&3) || return - [ -z "$name" ] && return + if [ ${#images[@]} -eq 0 ]; then + dialog --msgbox "Keine lokalen Images gefunden." 8 40 + return + fi - url=$(dialog --inputbox "URL der Registry:" 10 60 3>&1 1>&2 2>&3) || return - username=$(dialog --inputbox "Benutzername:" 10 60 3>&1 1>&2 2>&3) || return - password=$(dialog --passwordbox "Passwort:" 10 60 3>&1 1>&2 2>&3) || return + img_list=() + for img in "${images[@]}"; do + arch=$(docker inspect --format '{{.Architecture}}' "$img" 2>/dev/null || echo "unknown") + img_list+=("$img" "$arch") + done - mkdir -p "$REGISTRY_CONFIG_DIR/$name" - cat > "$REGISTRY_CONFIG_DIR/$name/config-file" <&1 1>&2 2>&3) || return - dialog --msgbox "Registry '$name' wurde erstellt." 10 60 + [ "$sel_img" = "0" ] && return + + action=$(dialog --menu "Aktion fuer $sel_img:" 12 60 5 \ + "INFOS" "Informationen zum Image anzeigen" \ + "TAG" "Neuen Tag erstellen" \ + "RMTAG" "Diesen Tag loeschen" \ + "RMIMG" "Ganzes Image loeschen" \ + "0" "Abbruch" \ + 3>&1 1>&2 2>&3) || continue + + case $action in + INFOS) + clear + docker inspect "$sel_img" | less + ;; + TAG) + new_tag=$(dialog --inputbox "Neuer vollstaendiger Tag (repo:tag):" 10 70 \ + "$sel_img" 3>&1 1>&2 2>&3) || continue + clear + docker tag "$sel_img" "$new_tag" && \ + dialog --msgbox "Tag erstellt:\n$new_tag" 8 50 + ;; + RMTAG) + if dialog --yesno "Tag wirklich loeschen?\n$sel_img" 8 50; then + clear + docker rmi "$sel_img" && \ + dialog --msgbox "Tag geloescht." 6 30 + fi + ;; + RMIMG) + repo="${sel_img%%:*}" + if dialog --yesno "ALLE Tags von $repo loeschen?" 8 60; then + clear + docker images "$repo" --format '{{.Repository}}:{{.Tag}}' \ + | xargs -r docker rmi && \ + dialog --msgbox "Image $repo entfernt." 6 40 + fi + ;; + 0) ;; + esac + done } -# Registry loeschen -delete_registry() { - local registries - registries=$(ls "$REGISTRY_CONFIG_DIR" 2>/dev/null) - [ -z "$registries" ] && { dialog --msgbox "Keine Registries vorhanden." 10 60; return; } +# ----------------------------------------------------------------------------- +# Projekt-Verwaltung (DIE FIX-VERSION) +# ----------------------------------------------------------------------------- +project_config_submenu() { + p_name="${1##*/}" # sicherer als basename + cfg="$PROJECT_CONFIG_DIR/${p_name}.json" + if [ ! -f "$cfg" ]; then + dialog --msgbox "Projekt-Konfig nicht gefunden:\n$cfg" 8 60 + return + fi + while true; do + if [ ! -f "$cfg" ]; then return; fi + + # EXPLIZITES EINLESEN OHNE FALLBACKS IN DER VARIABLE + # Wir lesen direkt 'true' oder 'false' als String + local img ver regs archs + img="$(jq -r '.image_name // ""' "$cfg")" + ver="$(jq -r '.version // ""' "$cfg")" + regs="$(jq -r '.registries // [] | join(",")' "$cfg")" + archs="$(jq -r '.architectures // [] | join(",")' "$cfg")" - menu_list=() - for r in $registries; do - menu_list+=("$r" "") - done + + # Hier liegt der Fix: Wir erzwingen die Auswertung des Booleans + local cur_push cur_ainc cur_cache cur_latest cur_force_local cur_pull_after + cur_push="$(jq -r '.push // false' "$cfg")" + cur_ainc="$(jq -r '.auto_inc // false' "$cfg")" + cur_cache="$(jq -r '.use_cache // true' "$cfg")" + cur_latest="$(jq -r '.use_latest // false' "$cfg")" + cur_force_local="$(jq -r '.force_local // false' "$cfg")" + cur_pull_after="$(jq -r '.pull_after_push // false' "$cfg")" - local reg - reg=$(dialog --menu "Registry zum Loeschen auswaehlen:" 20 60 10 "${menu_list[@]}" 3>&1 1>&2 2>&3) || return + local MENU_ITEMS=15 + local MENU_H=$(( M_H < MENU_ITEMS ? M_H : MENU_ITEMS )) - if dialog --yesno "Registry $reg wirklich loeschen?" 10 60; then - rm -rf "$REGISTRY_CONFIG_DIR/$reg" - dialog --msgbox "Registry $reg wurde geloescht." 10 60 - fi + choice=$(dialog --title " Projekt: $(safe "$p_name") " \ + --menu "Konfiguration (Werte werden in [ ] angezeigt):" $W_H $W_W $MENU_H \ + "1" "Image Name: $(safe "$img")" \ + "2" "Version: $(safe "$ver")" \ + "3" "Registries: $(safe "$regs")" \ + "4" "Architekturen: $(safe "$archs")" \ + "5" "Push n. Build: [$cur_push]" \ + "6" "Auto-Increment: [$cur_ainc]" \ + "7" "Build-Cache: [$cur_cache]" \ + "8" "Latest-Tag: [$cur_latest]" \ + "9" "Nur lokal bauen: [$cur_force_local]" \ + "10" "Nach Push pullen: [$cur_pull_after]" \ + "F" "Dateimanager" \ + "G" "Git Pull" \ + "B" "BUILD STARTEN" \ + "D" "Projekt loeschen" \ + "0" "Zurueck" \ + 3>&1 1>&2 2>&3) || return + + case $choice in + 1) val=$(dialog --inputbox "Name:" 10 60 "$img" 3>&1 1>&2 2>&3) && jq --arg v "$val" '.image_name=$v' "$cfg" > "$cfg.tmp" && mv "$cfg.tmp" "$cfg" ;; + 2) val=$(dialog --inputbox "Version:" 10 60 "$ver" 3>&1 1>&2 2>&3) && jq --arg v "$val" '.version=$v' "$cfg" > "$cfg.tmp" && mv "$cfg.tmp" "$cfg" ;; + 3) all_regs=$(ls "$REGISTRY_CONFIG_DIR" 2>/dev/null | sed 's/\.json//' || true) + reg_list=(); for r in $all_regs; do is_on="OFF"; if [[ "$regs" == *"$r"* ]]; then is_on="ON"; fi; reg_list+=("$r" "Registry" "$is_on"); done + sel_regs=$(dialog --separate-output --checklist "Registries:" 20 60 10 "${reg_list[@]}" 3>&1 1>&2 2>&3) || true + jq --argjson r "$(echo "$sel_regs" | jq -R . | jq -s .)" '.registries=$r' "$cfg" > "$cfg.tmp" && mv "$cfg.tmp" "$cfg" ;; + 4) a_sel=$(dialog --separate-output --checklist "Plattformen:" 12 60 4 "amd64" "x64" ON "arm64" "ARM64" OFF 3>&1 1>&2 2>&3) || true + jq --argjson a "$(echo "$a_sel" | jq -R . | jq -s .)" '.architectures=$a' "$cfg" > "$cfg.tmp" && mv "$cfg.tmp" "$cfg" ;; + 5) toggle_json_bool "push" "$cfg" ;; + 6) toggle_json_bool "auto_inc" "$cfg" ;; + 7) toggle_json_bool "use_cache" "$cfg" ;; + 8) toggle_json_bool "use_latest" "$cfg" ;; + 9) toggle_json_bool "force_local" "$cfg" ;; + 10) toggle_json_bool "pull_after_push" "$cfg" ;; + F) files=$(find "$PROJECTS_DIR/$p_name" -maxdepth 2 -not -path '*/.*' -type f) + f_list=(); for f in $files; do f_list+=("$f" "$(basename "$f")"); done + if [ ${#f_list[@]} -gt 0 ]; then sel_f=$(dialog --menu "Datei:" $W_H $W_W $M_H "${f_list[@]}" 3>&1 1>&2 2>&3) && $(get_glob '.editor') "$sel_f"; fi ;; + G) clear; cd "$PROJECTS_DIR/$p_name" && git pull || true; sleep 2 ;; + B) build_image "$p_name" ;; + D) if dialog --yesno "Loeschen?" 8 40; then rm "$cfg"; return; fi ;; + 0) return ;; + esac + done } -# Registry bearbeiten -edit_registry() { - local registries - registries=$(ls "$REGISTRY_CONFIG_DIR" 2>/dev/null) - [ -z "$registries" ] && { dialog --msgbox "Keine Registries vorhanden." 10 60; return; } - - menu_list=() - for r in $registries; do - menu_list+=("$r" "") - done - - local reg - reg=$(dialog --menu "Registry zum Bearbeiten auswaehlen:" 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 aendern - url=$(dialog --inputbox "URL:" 10 60 "$url" 3>&1 1>&2 2>&3) || return - username=$(dialog --inputbox "Benutzername:" 10 60 "$username" 3>&1 1>&2 2>&3) || return - password=$(dialog --passwordbox "Passwort:" 10 60 "$password" 3>&1 1>&2 2>&3) || return - - cat > "$config_file" <&1 | tee "$logfile"; then + if [ "$push_f" = "--push" ] && [ "$pull_after_push" = "true" ]; then + echo "Pull lokale Images..." + for img in "${tag_args[@]}"; do + [[ "$img" == "-t" ]] && continue + docker pull "$img" || true + done + fi + if [ "$(jq -r '.auto_inc' "$cfg")" = "true" ]; then + n_v=$(echo "$ver" | awk -F. '{$NF = $NF + 1;} 1' OFS=.) + jq --arg nv "$n_v" '.version=$nv' "$cfg" > "$cfg.tmp" && mv "$cfg.tmp" "$cfg" + fi + echo -e "\nERFOLG!" + else echo -e "\nFEHLER!"; fi + read -p "Enter..." +} - url=$(grep "^url=" "$config_path" | cut -d= -f2) - username=$(grep "^username=" "$config_path" | cut -d= -f2) - password=$(grep "^password=" "$config_path" | cut -d= -f2) - - registry_url["$r"]="$url" - - if [[ "$push" == "yes" ]]; then - echo "Logging in to $url..." - echo "$password" | docker login "$url" -u "$username" --password-stdin 2>&1 | tee -a "$logfile" +create_project() { + n=$(dialog --inputbox "Name:" 10 60 3>&1 1>&2 2>&3) || return + [ -z "$n" ] && return + git_url="" + if ! git_url=$(dialog --inputbox "Git URL (optional):" 10 70 3>&1 1>&2 2>&3); then + git_url="" fi - done + mkdir -p "$PROJECTS_DIR/$n" + if [ -n "$git_url" ]; then + dialog --infobox "Clone Repository...\n$git_url" 6 70 + mkdir -p "$PROJECTS_DIR/$n" - # Tags aufbauen - tags=() - for r in "${registry_list[@]}"; do - r=$(echo "$r" | xargs) - url="${registry_url[$r]}" - if [[ -z "$url" ]]; then - echo "WARNUNG: Registry $r konnte nicht gefunden werden, Tag wird uebersprungen." | tee -a "$logfile" - continue + if ! git clone "$git_url" "$PROJECTS_DIR/$n" >/tmp/git_clone.log 2>&1; then + dialog --textbox /tmp/git_clone.log 20 80 + return + fi + + sleep 1 + reset fi - tags+=("-t" "${url}/${image_name}:${version}") - [[ "$latest" == "yes" ]] && tags+=("-t" "${url}/${image_name}:latest") - done - - - # Push-Flag - local push_flag="" - [[ "$push" == "yes" ]] && push_flag="--push" - - # Build ausfuehren und Exit-Code speichern - if docker buildx build --platform "$platforms" "${tags[@]}" "$PROJECTS_DIR/$project" $push_flag 2>&1 | tee -a "$logfile"; then - build_status="erfolgreich" - else - build_status="FEHLGESCHLAGEN" - fi - - echo "==== Build beendet: $(date) - Status: $build_status ====" | 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)) - sed -i "s/^version=.*/version=${major}.${minor}/" "$config_file" - echo "Subversion automatisch auf ${major}.${minor} erhoeht." | tee -a "$logfile" - fi - - # Abfrage: Log anzeigen? - if dialog --yesno "Build $build_status. Log jetzt anzeigen?" 10 60; then - local editor_cmd - editor_cmd=$(get_editor_cmd) - $editor_cmd "$logfile" - fi + echo "{ + \"name\":\"$n\", + \"registries\":[], + \"image_name\":\"$n\", + \"architectures\":[\"amd64\"], + \"push\":false, + \"version\":\"1.0.0\", + \"auto_inc\":false, + \"use_cache\":true, + \"use_latest\":true, + \"force_local\": false, + \"pull_after_push\": false + }" > "$PROJECT_CONFIG_DIR/$n.json" + dialog --msgbox "Projekt '$n' wurde angelegt." 7 50 + return } - -# Logs ansehen (optional gefiltert nach Projekt) -view_logs() { - #ensure_logs_dir - - local project_filter="${1:-}" # wenn Projekt uebergeben, 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" ] && { dialog --msgbox "Keine Logs vorhanden." 10 60; return; } - - menu_list=() - for l in $logs; do - menu_list+=("$l" "") - done - - log_file=$(dialog --menu "Log auswaehlen:" 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" -} - -# Projektuebersicht anzeigen -project_overview() { - local project=$1 - local config_file="$PROJECT_CONFIG_DIR/$project/config-file" - load_project_config "$config_file" - - while true; do - choice=$(dialog --title "Projektuebersicht: $project" --menu "Parameter:" 20 70 10 \ - "1" "Registry: $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" \ - "L" "Logs ansehen" \ - "Z" "Zurueck" \ - 3>&1 1>&2 2>&3) || return - - case $choice in - B) build_image "$project" ;; - E) edit_project "$project" ;; # Reuse aus Projektverwaltung - P) select_project_for_build ;; - L) view_logs "$project" ;; - Z) return ;; - esac - done -} - -# Projektauswahl fuer Image-Bauen -select_project_for_build() { - local projects=($(ls "$PROJECT_CONFIG_DIR")) - [ ${#projects[@]} -eq 0 ] && { dialog --msgbox "Keine Projekte vorhanden." 10 60; return; } - - menu_list=() - for p in "${projects[@]}"; do - menu_list+=("$p" "Projekt") - done - - project=$(dialog --title "Projekt auswaehlen" --menu "Bitte Projekt auswaehlen:" 20 60 10 "${menu_list[@]}" 3>&1 1>&2 2>&3) || return - project_overview "$project" -} - -# Menuepunkt im Hauptmenue -image_build_menu() { - select_project_for_build -} - -# ------------------------- -# Hauptmenue -# ------------------------- main_menu() { - while true; do - choice=$(dialog --title "Image Builder" --menu "Bitte waehlen:" 20 70 10 \ - 1 "Konfiguration" \ - 2 "Projektverwaltung" \ - 3 "Registryverwaltung" \ - 4 "Image-Bauen" \ - 5 "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) view_logs ;; # <-- hier globale Logs - 0) exit 0;; - esac - done + while true; do + choice=$(dialog --title " Image Builder v3.0 " --menu "Menue:" $W_H $W_W $M_H 1 "Projekte" 2 "Registries" 3 "System" 4 "Logs" 5 "Lokale Images" 0 "Beenden" 3>&1 1>&2 2>&3) || exit 0 + case $choice in + 1) projs=$(ls "$PROJECT_CONFIG_DIR" 2>/dev/null | sed 's/\.json//' || true) + p_list=(); for p in $projs; do p_list+=("$p" "Projekt"); done + if ! p_sel=$(dialog --menu "Waehle:" $W_H $W_W $M_H "NEW" "[+] Neu" "${p_list[@]}" "0" "Zurueck" 3>&1 1>&2 2>&3); then + continue + fi + if [ "$p_sel" = "NEW" ]; then create_project; elif [ "$p_sel" != "0" ]; then project_config_submenu "$p_sel"; fi ;; + 2) registry_menu ;; 3) system_config_menu ;; + 4) ls_l=$(ls -t "$LOGS_DIR" 2>/dev/null | head -n 10 || true) + l_list=(); for l in $ls_l; do l_list+=("$l" "Log"); done + if [ ${#l_list[@]} -gt 0 ]; then sel_l=$(dialog --menu "Logs:" $W_H $W_W $M_H "${l_list[@]}" 3>&1 1>&2 2>&3) && $(get_glob '.editor') "$LOGS_DIR/$sel_l"; fi ;; + 5) local_images_menu ;; + 0) clear; exit 0 ;; + esac + done } -# ------------------------- -# Main -# ------------------------- -ensure_dirs -ensure_prereqs -main_menu - +ensure_dirs; main_menu \ No newline at end of file