Ein Stick, mehrere macOS-Versionen, frei mischbar aus Apple-Server- und Bibliotheks-Quelle. Partitioniert den Stick per diskutil partitionDisk in eine eigene HFS+-Partition je Version (eindeutige Labels), schreibt danach pro Partition gezielt per createinstallmedia --volume bzw. asr restore --target -- analog zur bereits vorhandenen Multiboot-Technik im Docker/Linux-Tool. Inkl. Groessen-Check mit Sicherheitspuffer vor jeder Aenderung am Stick, robustem Fehlerpfad je Partition (Weiter/Abbruch statt Stillschweigen) und End-to-End-Test der kompletten Ablauflogik unter Linux mit gestubbten macOS-Bordmitteln (Einzel-Modus + Multiboot, reine Apple-Quelle + gemischte Apple/Bibliotheks-Quelle).
924 lines
30 KiB
Bash
Executable File
924 lines
30 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# create-stick.sh — natives macOS-Terminal-Tool fuer bootfaehige
|
|
# Installer-Sticks. Laeuft NUR auf echtem macOS. Nutzt ausschliesslich
|
|
# Bordmittel: softwareupdate, diskutil, PlistBuddy, installer, asr,
|
|
# createinstallmedia. Kein Homebrew, keine externen Abhaengigkeiten.
|
|
#
|
|
# Zwei moegliche QUELLEN fuer den Installer-Payload:
|
|
#
|
|
# 1) Apple-Server (Download)
|
|
# softwareupdate --fetch-full-installer laedt die komplette
|
|
# "Install macOS X.app" direkt von Apple. Danach normaler
|
|
# createinstallmedia-Weg. Zukunftssicher: die Versionsliste kommt
|
|
# live von Apples eigenen Servern, neue macOS-Versionen tauchen
|
|
# automatisch auf.
|
|
#
|
|
# 2) Lokale Downloads-Bibliothek des Docker/Linux-Tools
|
|
# (../downloads/library relativ zu diesem Script, wenn das ganze
|
|
# Projekt inkl. Downloads z.B. per externer Platte auf den Mac
|
|
# kopiert wurde). Kein Internet noetig, kein erneuter Download.
|
|
# Zwei Payload-Formate stecken dort je nach macOS-Version, beide
|
|
# werden hier unterstuetzt:
|
|
# - InstallAssistant.pkg (Big Sur+): 'installer -pkg ... -target /'
|
|
# entpackt daraus die komplette Install-App (inkl.
|
|
# createinstallmedia-Binary) nach /Applications -- danach der
|
|
# normale createinstallmedia-Weg wie bei Quelle 1.
|
|
# - BaseSystem.dmg/InstallESD.dmg (bis Catalina): das ist schon
|
|
# ein fertiges, bootfaehiges Volume. createinstallmedia braucht
|
|
# es nicht extra -- Apples eigenes 'asr restore' schreibt es
|
|
# direkt auf den Stick. Kein App-Download noetig.
|
|
#
|
|
# So wird aus jeder bereits vom Docker-Tool gebauten Offline-Bibliothek
|
|
# (externe Platte, ohne Internet) auch am echten Mac ein bootfaehiger
|
|
# Stick, ganz ohne doppelten Download.
|
|
#
|
|
# ZWEI MODI:
|
|
#
|
|
# A) Einzelner Stick — ein Stick, eine macOS-Version (Original-Modus).
|
|
# B) Multiboot-Stick — ein Stick, MEHRERE macOS-Versionen gleichzeitig.
|
|
# Technik: der Stick wird per 'diskutil partitionDisk' in N eigene
|
|
# HFS+-Partitionen aufgeteilt (eine pro gewaehlter Version). Danach
|
|
# laeuft createinstallmedia bzw. asr restore genau wie im
|
|
# Einzel-Modus, nur pro Durchlauf gegen die jeweils EIGENE Partition
|
|
# statt gegen die ganze Platte -- beide Tools reformatieren dabei
|
|
# ausschliesslich ihr eigenes Zielvolume, die anderen Partitionen
|
|
# bleiben unberuehrt. Macs EFI-Bootpicker (Alt/Option beim Start)
|
|
# scannt alle Partitionen und zeigt jede bootfaehige einzeln an.
|
|
# Das ist dieselbe, in der Community etablierte Technik wie sie das
|
|
# Docker/Linux-Tool dieses Projekts fuer den dd-Weg schon nutzt.
|
|
|
|
set -u
|
|
|
|
# ---------- Darstellung ----------
|
|
BOLD="$(tput bold 2>/dev/null || true)"
|
|
RED="$(tput setaf 1 2>/dev/null || true)"
|
|
GREEN="$(tput setaf 2 2>/dev/null || true)"
|
|
YELLOW="$(tput setaf 3 2>/dev/null || true)"
|
|
CYAN="$(tput setaf 6 2>/dev/null || true)"
|
|
RESET="$(tput sgr0 2>/dev/null || true)"
|
|
|
|
hr() { printf '%s\n' "----------------------------------------------------------------------"; }
|
|
title() { clear; hr; printf "%s%s%s\n" "$BOLD$CYAN" " $1" "$RESET"; hr; echo; }
|
|
err() { printf "%s%s%s\n" "$RED" "FEHLER: $1" "$RESET" >&2; }
|
|
ok() { printf "%s%s%s\n" "$GREEN" "$1" "$RESET"; }
|
|
warn() { printf "%s%s%s\n" "$YELLOW" "$1" "$RESET"; }
|
|
|
|
# Sehr simpler Flat-JSON-Feld-Extraktor -- reicht fuer die meta.json-Dateien
|
|
# der Bibliothek (keine verschachtelten Objekte), spart eine Abhaengigkeit
|
|
# zu jq/python3 die auf einem frischen Mac nicht garantiert vorhanden ist.
|
|
json_field() {
|
|
local file="$1" key="$2"
|
|
sed -n 's/.*"'"$key"'":[[:space:]]*"\{0,1\}\([^",}]*\)"\{0,1\}.*/\1/p' "$file" | head -n1
|
|
}
|
|
|
|
# Bytes -> aufgerundete ganze GB (fuer Anzeige und Partitionsgroessen).
|
|
bytes_to_gb_ceil() {
|
|
local b="$1"
|
|
echo $(( (b + 999999999) / 1000000000 ))
|
|
}
|
|
|
|
# Kuerzt/saeubert einen Titel zu einem gueltigen, eindeutigen HFS+-Label.
|
|
# HFS+-Volume-Namen: max. 27 Zeichen, kein ':' und kein '/'. Wir haengen
|
|
# den Auswahl-Index voran, damit gleichnamige Versionen nie kollidieren.
|
|
make_label() {
|
|
local idx="$1" title="$2"
|
|
local clean
|
|
clean="$(echo "$title" | tr -d ':/' | tr -s ' ' ' ')"
|
|
clean="V${idx}_${clean}"
|
|
echo "${clean:0:27}"
|
|
}
|
|
|
|
# ---------- Vorpruefungen ----------
|
|
if [[ "$(uname)" != "Darwin" ]]; then
|
|
err "Dieses Script laeuft nur auf echtem macOS (kein Linux, keine VM ohne macOS)."
|
|
exit 1
|
|
fi
|
|
|
|
DEFAULT_LIB_DIR="$(cd "$(dirname "$0")/.." 2>/dev/null && pwd)/downloads/library"
|
|
|
|
# ======================================================================
|
|
# Gemeinsame Listen-Beschaffung (fuer Einzel- UND Multiboot-Modus)
|
|
# ======================================================================
|
|
|
|
# Fuellt die globalen Arrays A_TITLES/A_VERSIONS/A_SIZES mit Apples
|
|
# aktuell verfuegbaren kompletten Installern. Gibt 1 zurueck wenn keine
|
|
# Liste beschafft werden konnte (kein Abbruch des Scripts -- der Aufrufer
|
|
# entscheidet, ob das schlimm ist).
|
|
fetch_apple_list() {
|
|
A_TITLES=()
|
|
A_VERSIONS=()
|
|
A_SIZES=()
|
|
|
|
command -v softwareupdate >/dev/null 2>&1 || return 1
|
|
|
|
local list_raw
|
|
list_raw="$(softwareupdate --list-full-installers 2>/dev/null)"
|
|
grep -q "Title:" <<<"$list_raw" || return 1
|
|
|
|
local line t v s
|
|
while IFS= read -r line; do
|
|
[[ "$line" == *"Title:"* ]] || continue
|
|
t="$(sed -E 's/^\* Title: ([^,]+),.*/\1/' <<<"$line")"
|
|
v="$(sed -E 's/.*Version: ([^,]+),.*/\1/' <<<"$line")"
|
|
s="$(sed -E 's/.*Size: ([0-9]+),.*/\1/' <<<"$line")"
|
|
[[ "$s" =~ ^[0-9]+$ ]] || s="0"
|
|
A_TITLES+=("$t")
|
|
A_VERSIONS+=("$v")
|
|
A_SIZES+=("$s")
|
|
done <<<"$list_raw"
|
|
|
|
[[ ${#A_TITLES[@]} -gt 0 ]]
|
|
}
|
|
|
|
# Fuellt die globalen Arrays L_DIRS/L_TITLES/L_VERSIONS/L_MODES/L_SIZES/
|
|
# L_FILENAMES mit den vollstaendigen Eintraegen der lokalen Bibliothek.
|
|
# $1 = Pfad zur Bibliothek. Gibt 1 zurueck wenn nichts gefunden wurde.
|
|
fetch_library_list() {
|
|
local lib_dir="$1"
|
|
L_DIRS=()
|
|
L_TITLES=()
|
|
L_VERSIONS=()
|
|
L_MODES=()
|
|
L_SIZES=()
|
|
L_FILENAMES=()
|
|
|
|
[[ -d "$lib_dir" ]] || return 1
|
|
|
|
local entry_dir meta complete et ev emode esize efn
|
|
for entry_dir in "$lib_dir"/*/; do
|
|
[[ -d "$entry_dir" ]] || continue
|
|
meta="${entry_dir}meta.json"
|
|
[[ -f "$meta" ]] || continue
|
|
complete="$(json_field "$meta" complete)"
|
|
[[ "$complete" == "true" ]] || continue
|
|
et="$(json_field "$meta" title)"
|
|
ev="$(json_field "$meta" version)"
|
|
emode="$(json_field "$meta" mode)"
|
|
esize="$(json_field "$meta" size_bytes)"
|
|
efn="$(json_field "$meta" filename)"
|
|
[[ "$esize" =~ ^[0-9]+$ ]] || esize="0"
|
|
L_DIRS+=("$entry_dir")
|
|
L_TITLES+=("$et")
|
|
L_VERSIONS+=("$ev")
|
|
L_MODES+=("$emode")
|
|
L_SIZES+=("$esize")
|
|
L_FILENAMES+=("$efn")
|
|
done
|
|
|
|
[[ ${#L_DIRS[@]} -gt 0 ]]
|
|
}
|
|
|
|
# ======================================================================
|
|
# Payload-Beschaffung fuer EINEN gewaehlten Eintrag (von Apple oder aus
|
|
# der Bibliothek). Setzt bei Erfolg die globalen Variablen:
|
|
# RESOLVED_WRITE_MODE ("cim" oder "asr")
|
|
# RESOLVED_APP_PATH (bei cim)
|
|
# RESOLVED_DMG_PATH (bei asr)
|
|
# RESOLVED_SIZE_BYTES (reale Groesse nach Beschaffung, fuer Partition)
|
|
# Gibt bei Fehler != 0 zurueck (Aufrufer entscheidet ueber Abbruch).
|
|
#
|
|
# Argumente: resolve_payload <src:"apple"|"library"> <title> <version> \
|
|
# [lib_dir lib_mode lib_filename]
|
|
# ======================================================================
|
|
resolve_payload() {
|
|
local src="$1" title="$2" version="$3"
|
|
local lib_dir="${4:-}" lib_mode="${5:-}" lib_filename="${6:-}"
|
|
|
|
RESOLVED_WRITE_MODE=""
|
|
RESOLVED_APP_PATH=""
|
|
RESOLVED_DMG_PATH=""
|
|
RESOLVED_SIZE_BYTES="0"
|
|
|
|
if [[ "$src" == "apple" ]]; then
|
|
local app_path="/Applications/Install ${title}.app"
|
|
RESOLVED_WRITE_MODE="cim"
|
|
if [[ -d "$app_path" ]]; then
|
|
ok "'${app_path}' existiert bereits — Download wird uebersprungen."
|
|
else
|
|
warn "Das laedt mehrere GB von Apple und braucht sudo-Rechte (${title}, ${version})."
|
|
read -r -p "Download jetzt starten? [j/N]: " GO
|
|
if [[ ! "$GO" =~ ^[jJ]$ ]]; then
|
|
err "Abgebrochen fuer ${title}."
|
|
return 1
|
|
fi
|
|
echo
|
|
echo "Starte Download (kann je nach Version 30-90 Minuten dauern)..."
|
|
sudo softwareupdate --fetch-full-installer --full-installer-version "$version"
|
|
if [[ $? -ne 0 || ! -d "$app_path" ]]; then
|
|
err "Download fehlgeschlagen oder App nicht am erwarteten Ort. Erwartet: ${app_path}"
|
|
return 1
|
|
fi
|
|
ok "Download abgeschlossen: ${app_path}"
|
|
fi
|
|
RESOLVED_APP_PATH="$app_path"
|
|
RESOLVED_SIZE_BYTES="$(du -sk "$app_path" 2>/dev/null | awk '{print $1*1024}')"
|
|
[[ "$RESOLVED_SIZE_BYTES" =~ ^[0-9]+$ ]] || RESOLVED_SIZE_BYTES="0"
|
|
local cim_bin="${app_path}/Contents/Resources/createinstallmedia"
|
|
if [[ ! -x "$cim_bin" ]]; then
|
|
err "createinstallmedia nicht gefunden unter: ${cim_bin}"
|
|
return 1
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
# src == "library"
|
|
local payload_path="${lib_dir}${lib_filename}"
|
|
if [[ ! -f "$payload_path" ]]; then
|
|
err "Payload-Datei nicht gefunden: ${payload_path}"
|
|
return 1
|
|
fi
|
|
|
|
if [[ "$lib_mode" == "recovery" ]]; then
|
|
RESOLVED_WRITE_MODE="cim"
|
|
local app_path="/Applications/Install ${title}.app"
|
|
if [[ -d "$app_path" ]]; then
|
|
ok "'${app_path}' existiert bereits — Installer-Lauf wird uebersprungen."
|
|
else
|
|
echo "Entpacke ${payload_path} nach /Applications (installer -pkg, braucht sudo)..."
|
|
sudo installer -pkg "$payload_path" -target /
|
|
if [[ $? -ne 0 ]]; then
|
|
err "'installer -pkg' fehlgeschlagen fuer ${title}."
|
|
return 1
|
|
fi
|
|
if [[ ! -d "$app_path" ]]; then
|
|
local fallback
|
|
fallback="$(ls -dt /Applications/Install\ macOS*.app 2>/dev/null | head -n1)"
|
|
[[ -n "$fallback" ]] && app_path="$fallback"
|
|
fi
|
|
if [[ ! -d "$app_path" ]]; then
|
|
err "Konnte die installierte App nach 'installer -pkg' nicht finden (${title})."
|
|
return 1
|
|
fi
|
|
ok "Installiert: ${app_path}"
|
|
fi
|
|
RESOLVED_APP_PATH="$app_path"
|
|
RESOLVED_SIZE_BYTES="$(du -sk "$app_path" 2>/dev/null | awk '{print $1*1024}')"
|
|
[[ "$RESOLVED_SIZE_BYTES" =~ ^[0-9]+$ ]] || RESOLVED_SIZE_BYTES="0"
|
|
local cim_bin="${app_path}/Contents/Resources/createinstallmedia"
|
|
if [[ ! -x "$cim_bin" ]]; then
|
|
err "createinstallmedia nicht gefunden unter: ${cim_bin}"
|
|
return 1
|
|
fi
|
|
else
|
|
RESOLVED_WRITE_MODE="asr"
|
|
RESOLVED_DMG_PATH="$payload_path"
|
|
RESOLVED_SIZE_BYTES="$(stat -f%z "$payload_path" 2>/dev/null)"
|
|
[[ "$RESOLVED_SIZE_BYTES" =~ ^[0-9]+$ ]] || RESOLVED_SIZE_BYTES="0"
|
|
ok "Bibliothek liefert bereits ein fertiges, bootfaehiges Image (${payload_path}) -- kein App-Download noetig."
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# ======================================================================
|
|
# Externe physische Laufwerke auflisten. Fuellt D_IDS/D_DESCS/D_SIZE_BYTES.
|
|
# ======================================================================
|
|
list_external_disks() {
|
|
D_IDS=()
|
|
D_DESCS=()
|
|
D_SIZE_BYTES=()
|
|
|
|
local plist_tmp
|
|
plist_tmp="$(mktemp /tmp/diskutil_ext.XXXXXX.plist)"
|
|
diskutil list -plist external physical > "$plist_tmp" 2>/dev/null
|
|
|
|
local i=0 did info name size_bytes size_gb
|
|
while :; do
|
|
did="$(/usr/libexec/PlistBuddy -c "Print :AllDisksAndPartitions:${i}:DeviceIdentifier" "$plist_tmp" 2>/dev/null)"
|
|
[[ -z "$did" ]] && break
|
|
info="$(diskutil info -plist "$did" 2>/dev/null)"
|
|
name="$(echo "$info" | /usr/libexec/PlistBuddy -c "Print :MediaName" /dev/stdin 2>/dev/null)"
|
|
size_bytes="$(echo "$info" | /usr/libexec/PlistBuddy -c "Print :TotalSize" /dev/stdin 2>/dev/null)"
|
|
[[ "$size_bytes" =~ ^[0-9]+$ ]] || size_bytes="0"
|
|
size_gb="$(bytes_to_gb_ceil "$size_bytes")"
|
|
D_IDS+=("$did")
|
|
D_DESCS+=("/dev/${did} — ${name:-unbekannt} (~${size_gb} GB)")
|
|
D_SIZE_BYTES+=("$size_bytes")
|
|
i=$((i+1))
|
|
done
|
|
rm -f "$plist_tmp"
|
|
|
|
[[ ${#D_IDS[@]} -gt 0 ]]
|
|
}
|
|
|
|
# ======================================================================
|
|
# MODUS-Wahl
|
|
# ======================================================================
|
|
title "macOS USB-Stick-Creator (nativ) — Modus waehlen"
|
|
|
|
echo "Was soll erstellt werden?"
|
|
echo
|
|
echo " ${BOLD}1)${RESET} Einzelner Stick (eine macOS-Version)"
|
|
echo " ${BOLD}2)${RESET} Multiboot-Stick (mehrere macOS-Versionen auf einem Stick)"
|
|
echo
|
|
printf "%s0)%s Abbrechen\n" "$BOLD" "$RESET"
|
|
echo
|
|
read -r -p "Modus waehlen [Nummer]: " MODE_CHOICE
|
|
|
|
if [[ "$MODE_CHOICE" == "0" || -z "$MODE_CHOICE" ]]; then
|
|
echo "Abgebrochen."
|
|
exit 0
|
|
fi
|
|
|
|
if [[ "$MODE_CHOICE" != "1" && "$MODE_CHOICE" != "2" ]]; then
|
|
err "Ungueltige Auswahl."
|
|
exit 1
|
|
fi
|
|
|
|
# ======================================================================
|
|
# ================ MODUS 1: EINZELNER STICK ==========================
|
|
# ======================================================================
|
|
if [[ "$MODE_CHOICE" == "1" ]]; then
|
|
|
|
title "macOS USB-Stick-Creator (nativ) — Schritt 1/4: Quelle + Version"
|
|
|
|
echo "Woher soll der macOS-Installer kommen?"
|
|
echo
|
|
echo " ${BOLD}1)${RESET} Von Apple herunterladen (braucht Internet, mehrere GB)"
|
|
echo " ${BOLD}2)${RESET} Aus lokaler Downloads-Bibliothek (Docker-Tool) verwenden -- kein Internet noetig"
|
|
echo
|
|
printf "%s0)%s Abbrechen\n" "$BOLD" "$RESET"
|
|
echo
|
|
read -r -p "Quelle waehlen [Nummer]: " SRC_CHOICE
|
|
|
|
if [[ "$SRC_CHOICE" == "0" || -z "$SRC_CHOICE" ]]; then
|
|
echo "Abgebrochen."
|
|
exit 0
|
|
fi
|
|
|
|
case "$SRC_CHOICE" in
|
|
1) SOURCE="apple" ;;
|
|
2) SOURCE="library" ;;
|
|
*) err "Ungueltige Auswahl."; exit 1 ;;
|
|
esac
|
|
|
|
SEL_TITLE=""
|
|
SEL_VERSION=""
|
|
SEL_LIB_DIR=""
|
|
SEL_LIB_MODE=""
|
|
SEL_LIB_FILENAME=""
|
|
|
|
if [[ "$SOURCE" == "apple" ]]; then
|
|
if ! command -v softwareupdate >/dev/null 2>&1; then
|
|
err "'softwareupdate' nicht gefunden — ungewoehnliches macOS-Setup, Abbruch."
|
|
exit 1
|
|
fi
|
|
|
|
MACOS_VER="$(sw_vers -productVersion)"
|
|
if [[ "$MACOS_VER" == 10.* ]]; then
|
|
MINOR="${MACOS_VER#10.}"
|
|
MINOR="${MINOR%%.*}"
|
|
if [[ "$MINOR" -lt 15 ]]; then
|
|
err "macOS ${MACOS_VER} erkannt — 'softwareupdate --fetch-full-installer' braucht mind. Catalina (10.15)."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo
|
|
echo "Frage Apples Software-Update-Server nach verfuegbaren kompletten Installern ab..."
|
|
echo
|
|
|
|
if ! fetch_apple_list; then
|
|
err "Keine Installer gefunden. Internetverbindung pruefen, oder Apple hat gerade ein Problem."
|
|
exit 1
|
|
fi
|
|
|
|
echo "Verfuegbar:"
|
|
echo
|
|
for i in "${!A_TITLES[@]}"; do
|
|
printf " %s%2d)%s %s (%s)\n" "$BOLD" "$((i+1))" "$RESET" "${A_TITLES[$i]}" "${A_VERSIONS[$i]}"
|
|
done
|
|
echo
|
|
printf "%s0)%s Abbrechen\n" "$BOLD" "$RESET"
|
|
echo
|
|
read -r -p "Welche Version soll heruntergeladen/benutzt werden? [Nummer]: " CHOICE
|
|
|
|
if [[ "$CHOICE" == "0" || -z "$CHOICE" ]]; then
|
|
echo "Abgebrochen."
|
|
exit 0
|
|
fi
|
|
if ! [[ "$CHOICE" =~ ^[0-9]+$ ]] || (( CHOICE < 1 || CHOICE > ${#A_TITLES[@]} )); then
|
|
err "Ungueltige Auswahl."
|
|
exit 1
|
|
fi
|
|
|
|
SEL_TITLE="${A_TITLES[$((CHOICE-1))]}"
|
|
SEL_VERSION="${A_VERSIONS[$((CHOICE-1))]}"
|
|
echo
|
|
ok "Gewaehlt: ${SEL_TITLE} (${SEL_VERSION}) -- Quelle: Apple-Server"
|
|
else
|
|
echo
|
|
echo "Pfad zur lokalen Downloads-Bibliothek des Docker-Tools (Ordner mit einem"
|
|
echo "Unterordner pro macOS-Version, je mit meta.json + Installer-Datei)."
|
|
echo
|
|
read -r -p "Pfad [Enter fuer ${DEFAULT_LIB_DIR}]: " LIB_DIR_IN
|
|
LIB_DIR="${LIB_DIR_IN:-$DEFAULT_LIB_DIR}"
|
|
[[ "$LIB_DIR" == */ ]] || LIB_DIR="${LIB_DIR}/"
|
|
|
|
if ! fetch_library_list "$LIB_DIR"; then
|
|
err "Keine vollstaendigen Installer in ${LIB_DIR} gefunden."
|
|
exit 1
|
|
fi
|
|
|
|
echo
|
|
echo "In der Bibliothek gefunden:"
|
|
echo
|
|
for i in "${!L_DIRS[@]}"; do
|
|
esize_gb="$(bytes_to_gb_ceil "${L_SIZES[$i]}")"
|
|
mode_desc="bis Catalina, robust"
|
|
[[ "${L_MODES[$i]}" == "recovery" ]] && mode_desc="Big Sur+, ueber installer-pkg"
|
|
printf " %s%2d)%s %s (%s) -- ~%s GB, %s\n" "$BOLD" "$((i+1))" "$RESET" "${L_TITLES[$i]}" "${L_VERSIONS[$i]}" "$esize_gb" "$mode_desc"
|
|
done
|
|
echo
|
|
printf "%s0)%s Abbrechen\n" "$BOLD" "$RESET"
|
|
echo
|
|
read -r -p "Welche Version verwenden? [Nummer]: " LCHOICE
|
|
|
|
if [[ "$LCHOICE" == "0" || -z "$LCHOICE" ]]; then
|
|
echo "Abgebrochen."
|
|
exit 0
|
|
fi
|
|
if ! [[ "$LCHOICE" =~ ^[0-9]+$ ]] || (( LCHOICE < 1 || LCHOICE > ${#L_DIRS[@]} )); then
|
|
err "Ungueltige Auswahl."
|
|
exit 1
|
|
fi
|
|
|
|
idx=$((LCHOICE-1))
|
|
SEL_TITLE="${L_TITLES[$idx]}"
|
|
SEL_VERSION="${L_VERSIONS[$idx]}"
|
|
SEL_LIB_DIR="${L_DIRS[$idx]}"
|
|
SEL_LIB_MODE="${L_MODES[$idx]}"
|
|
SEL_LIB_FILENAME="${L_FILENAMES[$idx]}"
|
|
echo
|
|
ok "Gewaehlt: ${SEL_TITLE} (${SEL_VERSION}) -- Quelle: lokale Bibliothek"
|
|
fi
|
|
|
|
# ---------- Schritt 2: Installer-Payload besorgen ----------
|
|
title "Schritt 2/4: Installer-Payload besorgen"
|
|
|
|
if ! resolve_payload "$SOURCE" "$SEL_TITLE" "$SEL_VERSION" "$SEL_LIB_DIR" "$SEL_LIB_MODE" "$SEL_LIB_FILENAME"; then
|
|
exit 1
|
|
fi
|
|
WRITE_MODE="$RESOLVED_WRITE_MODE"
|
|
APP_PATH="$RESOLVED_APP_PATH"
|
|
DMG_PATH="$RESOLVED_DMG_PATH"
|
|
|
|
# ---------- Schritt 3: Ziel-USB-Stick waehlen ----------
|
|
title "Schritt 3/4: USB-Stick auswaehlen"
|
|
|
|
echo "Externe, physische Laufwerke (nur ganze Platten, keine Partitionen):"
|
|
echo
|
|
if ! list_external_disks; then
|
|
err "Keine externen physischen Laufwerke gefunden. Stick eingesteckt?"
|
|
exit 1
|
|
fi
|
|
|
|
for i in "${!D_IDS[@]}"; do
|
|
printf " %s%2d)%s %s\n" "$BOLD" "$((i+1))" "$RESET" "${D_DESCS[$i]}"
|
|
done
|
|
echo
|
|
printf "%s0)%s Abbrechen\n" "$BOLD" "$RESET"
|
|
echo
|
|
warn "ACHTUNG: Der gewaehlte Stick wird KOMPLETT GELOESCHT."
|
|
echo
|
|
read -r -p "Welcher Stick? [Nummer]: " DCHOICE
|
|
|
|
if [[ "$DCHOICE" == "0" || -z "$DCHOICE" ]]; then
|
|
echo "Abgebrochen."
|
|
exit 0
|
|
fi
|
|
if ! [[ "$DCHOICE" =~ ^[0-9]+$ ]] || (( DCHOICE < 1 || DCHOICE > ${#D_IDS[@]} )); then
|
|
err "Ungueltige Auswahl."
|
|
exit 1
|
|
fi
|
|
|
|
TARGET_DISK="${D_IDS[$((DCHOICE-1))]}"
|
|
TARGET_DESC="${D_DESCS[$((DCHOICE-1))]}"
|
|
|
|
echo
|
|
warn "Letzte Sicherheitsabfrage — dieser Stick wird geloescht:"
|
|
echo " ${TARGET_DESC}"
|
|
echo
|
|
read -r -p "Tippe exakt '/dev/${TARGET_DISK}' zum Bestaetigen: " CONFIRM
|
|
if [[ "$CONFIRM" != "/dev/${TARGET_DISK}" ]]; then
|
|
err "Eingabe stimmt nicht ueberein — abgebrochen, nichts wurde veraendert."
|
|
exit 1
|
|
fi
|
|
|
|
# ---------- Schritt 4: Stick schreiben ----------
|
|
title "Schritt 4/4: Stick wird erstellt"
|
|
|
|
if [[ "$WRITE_MODE" == "cim" ]]; then
|
|
CIM_BIN="${APP_PATH}/Contents/Resources/createinstallmedia"
|
|
echo "App: ${APP_PATH}"
|
|
echo "Ziel: /dev/${TARGET_DISK}"
|
|
echo
|
|
echo "createinstallmedia laeuft jetzt (braucht sudo, dauert typischerweise 10-30 Minuten)..."
|
|
echo "Der Stick wird zwischendurch umbenannt/neu gemountet — das ist normal."
|
|
echo
|
|
|
|
sudo "$CIM_BIN" --volume "/dev/${TARGET_DISK}" --nointeraction
|
|
RC=$?
|
|
|
|
echo
|
|
if [[ $RC -eq 0 ]]; then
|
|
ok "Fertig! Der Stick ist bootfaehig."
|
|
echo "Am Ziel-Mac: beim Start Option/Alt gedrueckt halten, den Stick im Boot-Picker waehlen."
|
|
else
|
|
err "createinstallmedia ist mit Fehlercode ${RC} beendet — siehe Ausgabe oben."
|
|
exit $RC
|
|
fi
|
|
|
|
echo
|
|
read -r -p "Die installierte App (mehrere GB) jetzt wieder loeschen um Platz zu sparen? [j/N]: " CLEAN
|
|
if [[ "$CLEAN" =~ ^[jJ]$ ]]; then
|
|
sudo rm -rf "$APP_PATH"
|
|
ok "Geloescht: ${APP_PATH}"
|
|
fi
|
|
else
|
|
echo "Quelle: ${DMG_PATH}"
|
|
echo "Ziel: /dev/${TARGET_DISK}"
|
|
echo
|
|
echo "Formatiere /dev/${TARGET_DISK} neu (HFS+) als Vorbereitung (braucht sudo)..."
|
|
sudo diskutil eraseDisk JHFS+ INSTALL "/dev/${TARGET_DISK}"
|
|
if [[ $? -ne 0 ]]; then
|
|
err "diskutil eraseDisk fehlgeschlagen."
|
|
exit 1
|
|
fi
|
|
|
|
VOL_MOUNT="/Volumes/INSTALL"
|
|
if [[ ! -d "$VOL_MOUNT" ]]; then
|
|
err "Erwarteter Mountpunkt ${VOL_MOUNT} nicht gefunden nach dem Formatieren."
|
|
echo "Tipp: 'diskutil list /dev/${TARGET_DISK}' zeigt den tatsaechlichen Zustand."
|
|
exit 1
|
|
fi
|
|
|
|
echo
|
|
echo "asr restore laeuft jetzt (braucht sudo, dauert typischerweise 10-20 Minuten)..."
|
|
echo "Der Stick wird dabei nochmal komplett neu formatiert -- das ist normal."
|
|
echo
|
|
|
|
sudo asr restore --source "$DMG_PATH" --target "$VOL_MOUNT" --erase --noprompt
|
|
RC=$?
|
|
|
|
echo
|
|
if [[ $RC -eq 0 ]]; then
|
|
ok "Fertig! Der Stick ist bootfaehig."
|
|
echo "Am Ziel-Mac: beim Start Option/Alt gedrueckt halten, den Stick im Boot-Picker waehlen."
|
|
else
|
|
err "asr restore ist mit Fehlercode ${RC} beendet — siehe Ausgabe oben."
|
|
exit $RC
|
|
fi
|
|
fi
|
|
|
|
echo
|
|
ok "Alles erledigt."
|
|
exit 0
|
|
|
|
fi
|
|
|
|
# ======================================================================
|
|
# ================ MODUS 2: MULTIBOOT-STICK ==========================
|
|
# ======================================================================
|
|
|
|
MAX_MULTIBOOT=8
|
|
|
|
title "Multiboot-Stick — Schritt 1/5: Versionen auswaehlen"
|
|
|
|
echo "Baue die Auswahlliste (Apple-Server + lokale Bibliothek)..."
|
|
echo
|
|
|
|
HAVE_APPLE=0
|
|
if command -v softwareupdate >/dev/null 2>&1 && fetch_apple_list; then
|
|
HAVE_APPLE=1
|
|
fi
|
|
|
|
HAVE_LIB=0
|
|
echo
|
|
read -r -p "Pfad zur lokalen Downloads-Bibliothek pruefen [Enter fuer ${DEFAULT_LIB_DIR}, '-' zum Ueberspringen]: " LIB_DIR_IN
|
|
if [[ "$LIB_DIR_IN" != "-" ]]; then
|
|
LIB_DIR="${LIB_DIR_IN:-$DEFAULT_LIB_DIR}"
|
|
[[ "$LIB_DIR" == */ ]] || LIB_DIR="${LIB_DIR}/"
|
|
if fetch_library_list "$LIB_DIR"; then
|
|
HAVE_LIB=1
|
|
fi
|
|
fi
|
|
|
|
if [[ $HAVE_APPLE -eq 0 && $HAVE_LIB -eq 0 ]]; then
|
|
err "Weder Apple-Server noch lokale Bibliothek haben Installer geliefert. Abbruch."
|
|
exit 1
|
|
fi
|
|
|
|
# Kombinierte Liste aufbauen: C_SRC/C_TITLE/C_VERSION/C_SIZE/C_LIBIDX
|
|
C_SRC=()
|
|
C_TITLE=()
|
|
C_VERSION=()
|
|
C_SIZE=()
|
|
C_LIBIDX=()
|
|
|
|
if [[ $HAVE_APPLE -eq 1 ]]; then
|
|
for i in "${!A_TITLES[@]}"; do
|
|
C_SRC+=("apple")
|
|
C_TITLE+=("${A_TITLES[$i]}")
|
|
C_VERSION+=("${A_VERSIONS[$i]}")
|
|
C_SIZE+=("${A_SIZES[$i]}")
|
|
C_LIBIDX+=("-1")
|
|
done
|
|
fi
|
|
if [[ $HAVE_LIB -eq 1 ]]; then
|
|
for i in "${!L_DIRS[@]}"; do
|
|
C_SRC+=("library")
|
|
C_TITLE+=("${L_TITLES[$i]}")
|
|
C_VERSION+=("${L_VERSIONS[$i]}")
|
|
C_SIZE+=("${L_SIZES[$i]}")
|
|
C_LIBIDX+=("$i")
|
|
done
|
|
fi
|
|
|
|
echo
|
|
echo "Verfuegbare Versionen (Mehrfachauswahl):"
|
|
echo
|
|
for i in "${!C_TITLE[@]}"; do
|
|
tag="[Apple, Download]"
|
|
mode_desc=""
|
|
if [[ "${C_SRC[$i]}" == "library" ]]; then
|
|
tag="[Bibliothek, offline]"
|
|
lidx="${C_LIBIDX[$i]}"
|
|
mode_desc=" -- bis Catalina, robust"
|
|
[[ "${L_MODES[$lidx]}" == "recovery" ]] && mode_desc=" -- Big Sur+, ueber installer-pkg"
|
|
fi
|
|
sgb="$(bytes_to_gb_ceil "${C_SIZE[$i]}")"
|
|
printf " %s%2d)%s %-22s %s (%s) -- ~%s GB%s\n" "$BOLD" "$((i+1))" "$RESET" "$tag" "${C_TITLE[$i]}" "${C_VERSION[$i]}" "$sgb" "$mode_desc"
|
|
done
|
|
echo
|
|
printf "%s0)%s Abbrechen\n" "$BOLD" "$RESET"
|
|
echo
|
|
echo "Mehrere Nummern durch Komma trennen, z.B. 1,3,4 (mind. 2, max. ${MAX_MULTIBOOT})."
|
|
read -r -p "Welche Versionen fuer den Multiboot-Stick? " MCHOICE_RAW
|
|
|
|
if [[ "$MCHOICE_RAW" == "0" || -z "$MCHOICE_RAW" ]]; then
|
|
echo "Abgebrochen."
|
|
exit 0
|
|
fi
|
|
|
|
IFS=',' read -r -a MCHOICE_PARTS <<<"$MCHOICE_RAW"
|
|
SEL_IDX=()
|
|
for part in "${MCHOICE_PARTS[@]}"; do
|
|
part="$(echo "$part" | tr -d '[:space:]')"
|
|
[[ -z "$part" ]] && continue
|
|
if ! [[ "$part" =~ ^[0-9]+$ ]] || (( part < 1 || part > ${#C_TITLE[@]} )); then
|
|
err "Ungueltige Nummer: '${part}'."
|
|
exit 1
|
|
fi
|
|
SEL_IDX+=("$((part-1))")
|
|
done
|
|
|
|
if (( ${#SEL_IDX[@]} < 2 )); then
|
|
err "Fuer nur eine Version reicht der Einzel-Modus (Option 1 im Startmenue)."
|
|
exit 1
|
|
fi
|
|
if (( ${#SEL_IDX[@]} > MAX_MULTIBOOT )); then
|
|
err "Zu viele Versionen gewaehlt (${#SEL_IDX[@]}), Maximum ist ${MAX_MULTIBOOT}."
|
|
exit 1
|
|
fi
|
|
|
|
echo
|
|
ok "Gewaehlt: ${#SEL_IDX[@]} Versionen fuer den Multiboot-Stick."
|
|
|
|
# ---------- Schritt 2/5: Payload je Version besorgen ----------
|
|
title "Schritt 2/5: Installer-Payloads besorgen"
|
|
|
|
M_WRITE_MODE=()
|
|
M_APP_PATH=()
|
|
M_DMG_PATH=()
|
|
M_SIZE_BYTES=()
|
|
M_TITLE=()
|
|
M_LABEL=()
|
|
|
|
n=1
|
|
for ci in "${SEL_IDX[@]}"; do
|
|
t="${C_TITLE[$ci]}"
|
|
v="${C_VERSION[$ci]}"
|
|
src="${C_SRC[$ci]}"
|
|
echo
|
|
echo "${BOLD}--- (${n}/${#SEL_IDX[@]}) ${t} (${v}) ---${RESET}"
|
|
|
|
lib_dir="" lib_mode="" lib_fn=""
|
|
if [[ "$src" == "library" ]]; then
|
|
lidx="${C_LIBIDX[$ci]}"
|
|
lib_dir="${L_DIRS[$lidx]}"
|
|
lib_mode="${L_MODES[$lidx]}"
|
|
lib_fn="${L_FILENAMES[$lidx]}"
|
|
fi
|
|
|
|
if ! resolve_payload "$src" "$t" "$v" "$lib_dir" "$lib_mode" "$lib_fn"; then
|
|
err "Konnte Payload fuer '${t}' nicht beschaffen — Auswahl wird komplett abgebrochen (noch nichts am Stick veraendert)."
|
|
exit 1
|
|
fi
|
|
|
|
M_WRITE_MODE+=("$RESOLVED_WRITE_MODE")
|
|
M_APP_PATH+=("$RESOLVED_APP_PATH")
|
|
M_DMG_PATH+=("$RESOLVED_DMG_PATH")
|
|
M_SIZE_BYTES+=("$RESOLVED_SIZE_BYTES")
|
|
M_TITLE+=("$t")
|
|
M_LABEL+=("$(make_label "$n" "$t")")
|
|
n=$((n+1))
|
|
done
|
|
|
|
# ---------- Schritt 3/5: Ziel-USB-Stick waehlen ----------
|
|
title "Schritt 3/5: USB-Stick auswaehlen"
|
|
|
|
echo "Externe, physische Laufwerke (nur ganze Platten, keine Partitionen):"
|
|
echo
|
|
if ! list_external_disks; then
|
|
err "Keine externen physischen Laufwerke gefunden. Stick eingesteckt?"
|
|
exit 1
|
|
fi
|
|
|
|
for i in "${!D_IDS[@]}"; do
|
|
printf " %s%2d)%s %s\n" "$BOLD" "$((i+1))" "$RESET" "${D_DESCS[$i]}"
|
|
done
|
|
echo
|
|
printf "%s0)%s Abbrechen\n" "$BOLD" "$RESET"
|
|
echo
|
|
warn "ACHTUNG: Der gewaehlte Stick wird KOMPLETT GELOESCHT (alle Partitionen)."
|
|
echo
|
|
read -r -p "Welcher Stick? [Nummer]: " DCHOICE
|
|
|
|
if [[ "$DCHOICE" == "0" || -z "$DCHOICE" ]]; then
|
|
echo "Abgebrochen."
|
|
exit 0
|
|
fi
|
|
if ! [[ "$DCHOICE" =~ ^[0-9]+$ ]] || (( DCHOICE < 1 || DCHOICE > ${#D_IDS[@]} )); then
|
|
err "Ungueltige Auswahl."
|
|
exit 1
|
|
fi
|
|
|
|
TARGET_DISK="${D_IDS[$((DCHOICE-1))]}"
|
|
TARGET_DESC="${D_DESCS[$((DCHOICE-1))]}"
|
|
TARGET_SIZE_BYTES="${D_SIZE_BYTES[$((DCHOICE-1))]}"
|
|
|
|
# ---------- Groessen-Check ----------
|
|
# Puffer je Partition: cim braucht etwas Kopfraum ueber der reinen App-
|
|
# Groesse (createinstallmedia kopiert BaseSystem etc. dazu), asr braucht
|
|
# kaum mehr als die Quellgroesse. Zusaetzlich Mindestgroessen, damit
|
|
# kleine/kaputte Groessenangaben nicht zu einer zu kleinen Partition fuehren.
|
|
TOTAL_NEEDED=0
|
|
declare -a PART_SIZE_BYTES=()
|
|
for i in "${!M_TITLE[@]}"; do
|
|
raw="${M_SIZE_BYTES[$i]}"
|
|
[[ "$raw" =~ ^[0-9]+$ ]] || raw=0
|
|
if [[ "${M_WRITE_MODE[$i]}" == "cim" ]]; then
|
|
need=$(( raw + raw / 3 )) # +33% Puffer
|
|
min=$(( 14 * 1000 * 1000 * 1000 ))
|
|
else
|
|
need=$(( raw + raw / 20 )) # +5% Puffer
|
|
min=$(( 6 * 1000 * 1000 * 1000 ))
|
|
fi
|
|
(( need < min )) && need=$min
|
|
PART_SIZE_BYTES+=("$need")
|
|
TOTAL_NEEDED=$(( TOTAL_NEEDED + need ))
|
|
done
|
|
|
|
# ~1GB Reserve fuer EFI-Partition + GPT-Tabellen.
|
|
GPT_OVERHEAD=$(( 1 * 1000 * 1000 * 1000 ))
|
|
TOTAL_NEEDED_WITH_OVERHEAD=$(( TOTAL_NEEDED + GPT_OVERHEAD ))
|
|
|
|
echo
|
|
echo "Benoetigter Platz (mit Puffer):"
|
|
for i in "${!M_TITLE[@]}"; do
|
|
printf " - %-30s ~%s GB (%s)\n" "${M_TITLE[$i]}" "$(bytes_to_gb_ceil "${PART_SIZE_BYTES[$i]}")" "${M_WRITE_MODE[$i]}"
|
|
done
|
|
echo " Gesamt (inkl. EFI-Reserve): ~$(bytes_to_gb_ceil "$TOTAL_NEEDED_WITH_OVERHEAD") GB"
|
|
echo " Stick-Kapazitaet: ~$(bytes_to_gb_ceil "$TARGET_SIZE_BYTES") GB"
|
|
echo
|
|
|
|
if (( TOTAL_NEEDED_WITH_OVERHEAD > TARGET_SIZE_BYTES )); then
|
|
err "Der gewaehlte Stick ist zu klein fuer diese Auswahl. Kleinere/weniger Versionen waehlen oder groesseren Stick nehmen."
|
|
echo "Es wurde NICHTS am Stick veraendert."
|
|
exit 1
|
|
fi
|
|
|
|
echo
|
|
warn "Letzte Sicherheitsabfrage — dieser Stick wird GELOESCHT und in ${#M_TITLE[@]} Partitionen aufgeteilt:"
|
|
echo " ${TARGET_DESC}"
|
|
for i in "${!M_TITLE[@]}"; do
|
|
echo " -> ${M_LABEL[$i]} (${M_TITLE[$i]})"
|
|
done
|
|
echo
|
|
read -r -p "Tippe exakt '/dev/${TARGET_DISK}' zum Bestaetigen: " CONFIRM
|
|
if [[ "$CONFIRM" != "/dev/${TARGET_DISK}" ]]; then
|
|
err "Eingabe stimmt nicht ueberein — abgebrochen, nichts wurde veraendert."
|
|
exit 1
|
|
fi
|
|
|
|
# ---------- Schritt 4/5: Partitionieren ----------
|
|
title "Schritt 4/5: Stick wird partitioniert"
|
|
|
|
# diskutil partitionDisk <device> <#Partitionen> GPT [Format Name Groesse] ...
|
|
# Letzte Partition bekommt 'R' (restlicher Platz) statt einer festen
|
|
# Groesse -- vermeidet Rundungsverluste und nutzt automatisch den vollen
|
|
# verbleibenden Stick fuer die letzte gewaehlte Version.
|
|
PART_ARGS=()
|
|
n_parts=${#M_TITLE[@]}
|
|
for i in "${!M_TITLE[@]}"; do
|
|
if (( i == n_parts - 1 )); then
|
|
PART_ARGS+=("JHFS+" "${M_LABEL[$i]}" "R")
|
|
else
|
|
gb="$(bytes_to_gb_ceil "${PART_SIZE_BYTES[$i]}")"
|
|
PART_ARGS+=("JHFS+" "${M_LABEL[$i]}" "${gb}G")
|
|
fi
|
|
done
|
|
|
|
echo "Partitioniere /dev/${TARGET_DISK} in ${n_parts} Partitionen (braucht sudo)..."
|
|
echo
|
|
sudo diskutil partitionDisk "/dev/${TARGET_DISK}" "${n_parts}" GPT "${PART_ARGS[@]}"
|
|
if [[ $? -ne 0 ]]; then
|
|
err "diskutil partitionDisk fehlgeschlagen — siehe Ausgabe oben."
|
|
exit 1
|
|
fi
|
|
ok "Partitionierung abgeschlossen."
|
|
|
|
# ---------- Schritt 5/5: Jede Partition beschreiben ----------
|
|
title "Schritt 5/5: Partitionen werden beschrieben"
|
|
|
|
FAILED_TITLES=()
|
|
OK_TITLES=()
|
|
|
|
for i in "${!M_TITLE[@]}"; do
|
|
t="${M_TITLE[$i]}"
|
|
label="${M_LABEL[$i]}"
|
|
vol="/Volumes/${label}"
|
|
|
|
echo
|
|
echo "${BOLD}--- $((i+1))/${n_parts}: ${t} -> ${label} ---${RESET}"
|
|
|
|
if [[ ! -d "$vol" ]]; then
|
|
err "Erwartetes Volume ${vol} nicht gefunden — ueberspringe ${t}."
|
|
FAILED_TITLES+=("$t")
|
|
continue
|
|
fi
|
|
|
|
if [[ "${M_WRITE_MODE[$i]}" == "cim" ]]; then
|
|
cim_bin="${M_APP_PATH[$i]}/Contents/Resources/createinstallmedia"
|
|
echo "createinstallmedia laeuft gegen ${vol} (braucht sudo, kann 10-30 Minuten dauern)..."
|
|
sudo "$cim_bin" --volume "$vol" --nointeraction
|
|
rc=$?
|
|
else
|
|
echo "asr restore laeuft gegen ${vol} (braucht sudo, kann 10-20 Minuten dauern)..."
|
|
sudo asr restore --source "${M_DMG_PATH[$i]}" --target "$vol" --erase --noprompt
|
|
rc=$?
|
|
fi
|
|
|
|
if [[ $rc -eq 0 ]]; then
|
|
ok "${t}: fertig."
|
|
OK_TITLES+=("$t")
|
|
else
|
|
err "${t}: fehlgeschlagen (Code ${rc})."
|
|
FAILED_TITLES+=("$t")
|
|
echo
|
|
read -r -p "Trotzdem mit den restlichen Versionen fortfahren? [j/N]: " CONT
|
|
if [[ ! "$CONT" =~ ^[jJ]$ ]]; then
|
|
err "Abgebrochen — restliche Partitionen wurden NICHT beschrieben."
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
|
|
echo
|
|
hr
|
|
if (( ${#OK_TITLES[@]} > 0 )); then
|
|
ok "Erfolgreich geschrieben: ${OK_TITLES[*]}"
|
|
fi
|
|
if (( ${#FAILED_TITLES[@]} > 0 )); then
|
|
err "Fehlgeschlagen/uebersprungen: ${FAILED_TITLES[*]}"
|
|
fi
|
|
echo
|
|
echo "Am Ziel-Mac: beim Start Option/Alt gedrueckt halten — alle erfolgreich"
|
|
echo "geschriebenen Versionen erscheinen einzeln im Boot-Picker."
|
|
|
|
# Aufraeumen: heruntergeladene/entpackte Apps (mehrere GB je Version).
|
|
CLEAN_APPS=()
|
|
for i in "${!M_TITLE[@]}"; do
|
|
[[ "${M_WRITE_MODE[$i]}" == "cim" ]] || continue
|
|
[[ -n "${M_APP_PATH[$i]}" ]] || continue
|
|
CLEAN_APPS+=("${M_APP_PATH[$i]}")
|
|
done
|
|
if (( ${#CLEAN_APPS[@]} > 0 )); then
|
|
echo
|
|
read -r -p "Heruntergeladene/entpackte Installer-Apps (${#CLEAN_APPS[@]} Stueck, mehrere GB) jetzt loeschen? [j/N]: " CLEAN
|
|
if [[ "$CLEAN" =~ ^[jJ]$ ]]; then
|
|
for app in "${CLEAN_APPS[@]}"; do
|
|
sudo rm -rf "$app"
|
|
done
|
|
ok "Aufgeraeumt."
|
|
fi
|
|
fi
|
|
|
|
echo
|
|
if (( ${#FAILED_TITLES[@]} > 0 )); then
|
|
exit 1
|
|
fi
|
|
ok "Alles erledigt."
|
|
exit 0
|