349 lines
16 KiB
Bash
Executable File
349 lines
16 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Globale Einstellungen & UI-Theme
|
|
# -----------------------------------------------------------------------------
|
|
set -euo pipefail
|
|
IFS=$'\n\t'
|
|
|
|
export DIALOGRC=$HOME/.dialogrc
|
|
if [ ! -f "$DIALOGRC" ]; then
|
|
cat > "$DIALOGRC" <<EOF
|
|
use_shadow = ON
|
|
use_colors = ON
|
|
screen_color = (CYAN,BLUE,ON)
|
|
dialog_color = (BLACK,WHITE,OFF)
|
|
title_color = (BLUE,WHITE,ON)
|
|
border_color = (WHITE,WHITE,ON)
|
|
EOF
|
|
fi
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
CONFIG_DIR="$ROOT_DIR/config"
|
|
PROJECTS_DIR="$ROOT_DIR/projects"
|
|
PROJECT_CONFIG_DIR="$CONFIG_DIR/projects"
|
|
REGISTRY_CONFIG_DIR="$CONFIG_DIR/registries"
|
|
GLOBAL_CONFIG="$CONFIG_DIR/global/config.json"
|
|
LOGS_DIR="$ROOT_DIR/logs"
|
|
|
|
W_H=28; W_W=100; M_H=18
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Helfer
|
|
# -----------------------------------------------------------------------------
|
|
ensure_dirs() {
|
|
mkdir -p "$CONFIG_DIR/global" "$PROJECT_CONFIG_DIR" "$REGISTRY_CONFIG_DIR" "$PROJECTS_DIR" "$LOGS_DIR"
|
|
if [ ! -f "$GLOBAL_CONFIG" ]; then echo '{"editor": "nano"}' > "$GLOBAL_CONFIG"; fi
|
|
}
|
|
|
|
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"
|
|
}
|
|
|
|
# -----------------------------
|
|
# Hilfsfunktion: sichere Strings für dialog
|
|
# -----------------------------
|
|
safe() {
|
|
echo "$1" | tr -d '\n\r' | sed 's/"/'"'"'/g'
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 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
|
|
}
|
|
|
|
registry_menu() {
|
|
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
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Lokale Docker Images verwalten
|
|
# -----------------------------------------------------------------------------
|
|
local_images_menu() {
|
|
while true; do
|
|
# Images auflisten (Repo:Tag)
|
|
mapfile -t images < <(docker images --format '{{.Repository}}:{{.Tag}}' | grep -v '<none>' || true)
|
|
|
|
if [ ${#images[@]} -eq 0 ]; then
|
|
dialog --msgbox "Keine lokalen Images gefunden." 8 40
|
|
return
|
|
fi
|
|
|
|
img_list=()
|
|
for img in "${images[@]}"; do
|
|
arch=$(docker inspect --format '{{.Architecture}}' "$img" 2>/dev/null || echo "unknown")
|
|
img_list+=("$img" "$arch")
|
|
done
|
|
|
|
sel_img=$(dialog --title " Lokale Images " \
|
|
--menu "Waehle Image:" $W_H $W_W $M_H \
|
|
"${img_list[@]}" "0" "Zurueck" \
|
|
3>&1 1>&2 2>&3) || return
|
|
|
|
[ "$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
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 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")"
|
|
|
|
|
|
# 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 MENU_ITEMS=15
|
|
local MENU_H=$(( M_H < MENU_ITEMS ? M_H : MENU_ITEMS ))
|
|
|
|
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
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Build & Hauptmenue (Rest bleibt stabil wie v2.9)
|
|
# -----------------------------------------------------------------------------
|
|
build_image() {
|
|
local force_local=$(jq -r '.force_local' "$cfg")
|
|
local pull_after_push=$(jq -r '.pull_after_push' "$cfg")
|
|
local p_name=$1; local cfg="$PROJECT_CONFIG_DIR/$p_name.json"
|
|
readarray -t selected_regs < <(jq -r '.registries[]' "$cfg")
|
|
[ ${#selected_regs[@]} -eq 0 ] && dialog --msgbox "Keine Registry gewaehlt!" 8 40 && return
|
|
local arch_count=$(jq '.architectures | length' "$cfg")
|
|
local push_enabled=$(jq -r '.push' "$cfg")
|
|
local platforms=$(jq -r '.architectures | map("linux/" + .) | join(",")' "$cfg")
|
|
local use_latest=$(jq -r '.use_latest' "$cfg")
|
|
local push_f="--load"
|
|
if [ "$force_local" = "true" ]; then
|
|
push_f="--load"
|
|
elif [ "$arch_count" -gt 1 ] || [ "$push_enabled" = "true" ]; then
|
|
push_f="--push"
|
|
fi
|
|
clear
|
|
tag_args=()
|
|
for r_name in "${selected_regs[@]}"; do
|
|
r_file="$REGISTRY_CONFIG_DIR/$r_name.json"
|
|
[ ! -f "$r_file" ] && continue
|
|
u=$(jq -r '.user' "$r_file"); p=$(jq -r '.pass' "$r_file"); url=$(jq -r '.url' "$r_file")
|
|
echo "Login bei $r_name..."
|
|
if [ -z "$url" ] || [ "$url" = "null" ]; then echo "$p" | docker login -u "$u" --password-stdin
|
|
else echo "$p" | docker login "$url" -u "$u" --password-stdin; fi
|
|
local raw_img=$(jq -r '.image_name' "$cfg")
|
|
local tagged_img="$raw_img"
|
|
if [[ "$raw_img" != *"/"* ]]; then tagged_img="$u/$raw_img"; fi
|
|
local ver=$(jq -r '.version' "$cfg")
|
|
if [ -z "$url" ] || [ "$url" = "null" ]; then full_path="$tagged_img"
|
|
else url_clean=$(echo "$url" | sed 's|https://||; s|http://||; s|/$||'); full_path="$url_clean/$tagged_img"; fi
|
|
tag_args+=("-t" "$full_path:$ver")
|
|
[ "$use_latest" = "true" ] && tag_args+=("-t" "$full_path:latest")
|
|
done
|
|
cache_f=""; [ "$(jq -r '.use_cache' "$cfg")" = "false" ] && cache_f="--no-cache"
|
|
logfile="$LOGS_DIR/${p_name}_$(date +%Y%m%d_%H%M).log"
|
|
if docker buildx build --platform "$platforms" "${tag_args[@]}" $cache_f $push_f "$PROJECTS_DIR/$p_name" 2>&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..."
|
|
}
|
|
|
|
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
|
|
mkdir -p "$PROJECTS_DIR/$n"
|
|
if [ -n "$git_url" ]; then
|
|
dialog --infobox "Clone Repository...\n$git_url" 6 70
|
|
mkdir -p "$PROJECTS_DIR/$n"
|
|
|
|
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
|
|
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
|
|
}
|
|
|
|
main_menu() {
|
|
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
|
|
}
|
|
|
|
ensure_dirs; main_menu |