native-macos: Downloads-Bibliothek des Docker-Tools als zweite Quelle nutzbar machen
- Neuer Auswahlschritt: Apple-Server (Download) vs. lokale downloads/library (Docker-Tool) -- letzteres komplett offline nutzbar. - InstallAssistant.pkg (Big Sur+) wird per installer -pkg -target / zur vollstaendigen Install-App entpackt, danach normaler createinstallmedia-Weg. - BaseSystem.dmg/InstallESD.dmg (bis Catalina) ist bereits ein fertiges bootfaehiges Volume -- kein createinstallmedia noetig, stattdessen diskutil eraseDisk + asr restore --erase (etablierte Community-Technik). - Flat-JSON-Parser (sed) fuer meta.json ohne jq/python3-Abhaengigkeit. - Auswahl-/Verzweigungslogik unter Linux mit simulierter Bibliothek end-to-end durchgetestet (beide Modi laufen korrekt bis zum jeweils ersten macOS-only-Befehl).
This commit is contained in:
+80
-44
@@ -1,34 +1,57 @@
|
|||||||
# Nativer macOS-Weg (createinstallmedia)
|
# Nativer macOS-Weg (createinstallmedia / asr)
|
||||||
|
|
||||||
Alternative zum Docker/Linux-Tool im Projekt-Root — **nur nutzbar, wenn
|
Alternative zum Docker/Linux-Tool im Projekt-Root — **nur nutzbar, wenn
|
||||||
gerade ein echter Mac zur Verfuegung steht** (macOS 10.15 Catalina oder
|
gerade ein echter Mac zur Verfuegung steht**. Zwei Quellen fuer den
|
||||||
neuer). Dafuer bekommst du hierueber den offiziell von Apple sauber
|
Installer-Payload stehen zur Auswahl, direkt im Script:
|
||||||
funktionierenden Weg: die echte `createinstallmedia`, statt des
|
|
||||||
Recovery-Extraktions-Workarounds fuer den Big-Sur+-Pfad im Docker-Tool.
|
|
||||||
|
|
||||||
## Warum ein eigenes Script, warum nicht die `downloads/`-Bibliothek wiederverwenden?
|
1. **Von Apple herunterladen** (macOS 10.15 Catalina oder neuer als Host
|
||||||
|
noetig) — der offiziell von Apple unterstuetzte Weg ueber
|
||||||
|
`softwareupdate --fetch-full-installer`.
|
||||||
|
2. **Aus der lokalen Downloads-Bibliothek des Docker-Tools** — wenn das
|
||||||
|
ganze Projekt inkl. `downloads/`-Ordner z.B. auf eine externe Platte
|
||||||
|
kopiert wurde, braucht dieser Weg **kein Internet und keinen erneuten
|
||||||
|
Download**.
|
||||||
|
|
||||||
Kurz: **andere Bildformate, nicht kompatibel.**
|
## Warum ein eigenes Script, warum nicht einfach dd wie das Docker-Tool?
|
||||||
|
|
||||||
- Das Docker-Tool laedt rohe Katalog-Images (`BaseSystem.dmg` bzw.
|
Kurz: **`createinstallmedia` (der offiziell unterstuetzte macOS-Weg)
|
||||||
`InstallAssistant.pkg`) und schreibt sie per `dd` direkt auf den Stick.
|
braucht die vollstaendige, fertig gepackte "Install macOS X.app"** —
|
||||||
Funktioniert ohne Mac, aber `createinstallmedia` kann mit diesen rohen
|
nicht die rohen Katalog-Dateien, die das Docker-Tool per `dd` schreibt.
|
||||||
Dateien nichts anfangen — es braucht die komplette, fertig gepackte
|
Deshalb dieser separate, macOS-native Pfad.
|
||||||
**"Install macOS X.app"**.
|
|
||||||
- Der offizielle, von Apple unterstuetzte Weg an genau diese App zu kommen
|
|
||||||
(auch ohne App-Store-Anmeldung) ist der macOS-Bordbefehl
|
|
||||||
`softwareupdate --fetch-full-installer`. Das gibt es nur unter echtem
|
|
||||||
macOS ab Catalina.
|
|
||||||
|
|
||||||
Deshalb ist dieses Script komplett unabhaengig vom `./downloads`-Ordner
|
## Wie die Bibliotheks-Wiederverwendung funktioniert
|
||||||
des Docker-Tools. Es laedt sich seine App-Kopie selbst, direkt von Apple.
|
|
||||||
|
Die `downloads/library/`-Bibliothek des Docker-Tools enthaelt pro
|
||||||
|
macOS-Version genau eine von zwei Rohdateien (siehe `meta.json` im
|
||||||
|
jeweiligen Unterordner, Feld `mode`):
|
||||||
|
|
||||||
|
- **`InstallAssistant.pkg`** (Big Sur und neuer, `mode: "recovery"`):
|
||||||
|
Apples eigenes `installer -pkg ... -target /` entpackt daraus die
|
||||||
|
komplette Install-App (inkl. `createinstallmedia`-Binary) nach
|
||||||
|
`/Applications` — bestaetigt per Apple-Community-Thread, dass ein
|
||||||
|
`.pkg`-Installer aus dem Software-Update-Katalog 1:1 dasselbe Paket
|
||||||
|
ist wie der App-Store-Download. Danach laeuft der normale
|
||||||
|
`createinstallmedia`-Weg wie bei der Apple-Quelle.
|
||||||
|
- **`BaseSystem.dmg`/`InstallESD.dmg`** (bis Catalina, `mode:
|
||||||
|
"legacy_dmg"`): das ist bereits ein fertiges, bootfaehiges Volume.
|
||||||
|
`createinstallmedia` braucht es gar nicht erst — Apples eigenes
|
||||||
|
Bordmittel `asr restore --source ... --target ... --erase --noprompt`
|
||||||
|
schreibt es direkt und sauber auf den Stick (etablierte Technik, siehe
|
||||||
|
Quellen unten). Kein App-Download, kein `createinstallmedia` noetig.
|
||||||
|
|
||||||
|
In beiden Faellen: **kein zweiter Download**, die Datei aus der Bibliothek
|
||||||
|
wird 1:1 weiterverwendet.
|
||||||
|
|
||||||
## Zukunftssicherheit
|
## Zukunftssicherheit
|
||||||
|
|
||||||
`softwareupdate --list-full-installers` fragt Apples eigene Server live ab
|
Bei der Apple-Quelle fragt `softwareupdate --list-full-installers`
|
||||||
— genau wie der Katalog-Check im Docker-Tool, nur eben Apples eigenes
|
Apples eigene Server live ab — genau wie der Katalog-Check im Docker-Tool,
|
||||||
Tool statt eigenem Parser. Neue macOS-Versionen tauchen automatisch in der
|
nur eben Apples eigenes Tool statt eigenem Parser. Neue macOS-Versionen
|
||||||
Liste auf, ohne dass hier Code angepasst werden muss.
|
tauchen automatisch in der Liste auf.
|
||||||
|
|
||||||
|
Bei der Bibliotheks-Quelle haengt die Auswahl logischerweise davon ab, was
|
||||||
|
das Docker-Tool zuletzt heruntergeladen hat — dafuer aber komplett ohne
|
||||||
|
Internetzugriff nutzbar.
|
||||||
|
|
||||||
## Nutzung
|
## Nutzung
|
||||||
|
|
||||||
@@ -39,32 +62,45 @@ chmod +x create-stick.sh # falls das Ausfuehr-Bit beim Kopieren verloren ging
|
|||||||
```
|
```
|
||||||
|
|
||||||
Fuehrt durch vier Schritte (reines Terminal-Menue, keine externen
|
Fuehrt durch vier Schritte (reines Terminal-Menue, keine externen
|
||||||
Abhaengigkeiten — nur `softwareupdate`, `diskutil`, `PlistBuddy`,
|
Abhaengigkeiten — nur `softwareupdate`, `diskutil`, `PlistBuddy`,
|
||||||
`createinstallmedia`, alles Bordmittel):
|
`installer`, `asr`, `createinstallmedia`, alles Bordmittel):
|
||||||
|
|
||||||
1. **Versionsliste** von Apple abrufen, Version auswaehlen.
|
1. **Quelle + Version waehlen** — Apple-Server oder lokale Bibliothek
|
||||||
2. **Download** der vollen Installer-App via `softwareupdate
|
(Pfad wird abgefragt, Default rät `../downloads/library` relativ zum
|
||||||
--fetch-full-installer` (wird uebersprungen, wenn die App schon unter
|
Script — passt, wenn das komplette Projekt kopiert wurde).
|
||||||
`/Applications` liegt).
|
2. **Installer-Payload besorgen** — je nach Quelle/Modus: Download via
|
||||||
3. **USB-Stick auswaehlen** — Liste nur externer, physischer Laufwerke
|
`softwareupdate`, Entpacken via `installer -pkg`, oder (legacy_dmg
|
||||||
|
aus der Bibliothek) gar nichts, das Image liegt schon bereit.
|
||||||
|
3. **USB-Stick auswaehlen** — Liste nur externer, physischer Laufwerke
|
||||||
(`diskutil list -plist external physical`), danach Sicherheitsabfrage:
|
(`diskutil list -plist external physical`), danach Sicherheitsabfrage:
|
||||||
exakter Geraetepfad muss nochmal eingetippt werden, bevor irgendwas
|
exakter Geraetepfad muss nochmal eingetippt werden, bevor irgendwas
|
||||||
geloescht wird.
|
geloescht wird.
|
||||||
4. **`createinstallmedia --nointeraction`** laeuft mit `sudo`, live-Ausgabe
|
4. **Schreiben** — je nach Modus entweder `createinstallmedia
|
||||||
im Terminal. Am Ende optional die (mehrere GB grosse) Installer-App
|
--nointeraction` (Apple-Quelle und Bibliothek/Big-Sur+) oder
|
||||||
wieder loeschen.
|
`diskutil eraseDisk` + `asr restore --erase` (Bibliothek,
|
||||||
|
bis Catalina). Beides mit `sudo`, live-Ausgabe im Terminal.
|
||||||
|
|
||||||
## Grenzen
|
## Grenzen
|
||||||
|
|
||||||
- Nur macOS 10.15+ (Catalina oder neuer) als **Host**, auf dem das Script
|
- Nur macOS 10.15+ (Catalina oder neuer) als **Host**, auf dem das Script
|
||||||
laeuft — das Zielsystem, auf das installiert wird, kann trotzdem
|
laeuft — das Zielsystem, auf das installiert wird, kann trotzdem
|
||||||
jede von Apple unterstuetzte Version sein.
|
jede von Apple unterstuetzte Version sein. Diese Einschraenkung gilt nur
|
||||||
- Kein Multiboot-Support (anders als das Docker-Tool) —
|
fuer die Apple-Download-Quelle (`softwareupdate --fetch-full-installer`);
|
||||||
`createinstallmedia` erstellt immer einen kompletten Single-Version-Stick.
|
`installer`/`asr` funktionieren grundsaetzlich auch auf aelteren Hosts.
|
||||||
Waere technisch nachruestbar (mehrere Partitionen, mehrere
|
- Kein Multiboot-Support (anders als das Docker-Tool) —
|
||||||
`createinstallmedia`-Laeufe mit `--volume` auf je eine Partition), aber
|
jeder Lauf erstellt immer einen kompletten Single-Version-Stick.
|
||||||
aktuell nicht gebaut.
|
- **Ungetestet auf echtem Mac.** Die Auswahl-/Verzweigungslogik (Quelle,
|
||||||
- Downloads landen unter `/Applications` (Apples Standardverhalten von
|
Modus-Erkennung ueber `meta.json`) wurde unter Linux mit einer
|
||||||
`softwareupdate`), nicht im Projektordner — dieser Zweig ist bewusst
|
simulierten Bibliothek end-to-end durchgespielt (`bash -n` sauber,
|
||||||
nicht auf Portabilitaet/externe Platte ausgelegt wie das Docker-Tool,
|
beide Zweige laufen bis zum jeweils ersten macOS-only-Befehl korrekt
|
||||||
sondern auf den einfachsten Weg wenn gerade ein Mac da ist.
|
durch). Der eigentliche `installer`/`asr`/`createinstallmedia`-Lauf
|
||||||
|
selbst ist nur gegen offizielle Apple-Doku/Community-Quellen verifiziert,
|
||||||
|
nicht live auf einem Mac getestet.
|
||||||
|
|
||||||
|
## Quellen (fuer den asr-Weg, live nachgeschlagen)
|
||||||
|
|
||||||
|
- Apple Developer/Community — `asr restore` ist das Bordmittel fuer
|
||||||
|
Block-Level-Restores von Disk Images, seit jeher Teil von macOS.
|
||||||
|
- Cody Krieger — "Creating bootable macOS Sierra installation media on
|
||||||
|
macOS Catalina": beschreibt exakt den `diskutil eraseDisk` +
|
||||||
|
`asr restore --erase`-Ablauf fuer aeltere macOS-Installer.
|
||||||
|
|||||||
+335
-117
@@ -1,25 +1,37 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
#
|
#
|
||||||
# create-stick.sh — natives macOS-Terminal-Tool fuer bootfaehige
|
# create-stick.sh — natives macOS-Terminal-Tool fuer bootfaehige
|
||||||
# Installer-Sticks via Apples eigenem createinstallmedia.
|
# Installer-Sticks. Laeuft NUR auf echtem macOS. Nutzt ausschliesslich
|
||||||
#
|
# Bordmittel: softwareupdate, diskutil, PlistBuddy, installer, asr,
|
||||||
# Laeuft NUR auf echtem macOS (10.15 Catalina oder neuer). Nutzt
|
|
||||||
# ausschliesslich Bordmittel: softwareupdate, diskutil, PlistBuddy,
|
|
||||||
# createinstallmedia. Kein Homebrew, keine externen Abhaengigkeiten.
|
# createinstallmedia. Kein Homebrew, keine externen Abhaengigkeiten.
|
||||||
#
|
#
|
||||||
# Warum ein eigener Pfad neben dem Docker/Linux-Tool im Projekt-Root:
|
# Zwei moegliche QUELLEN fuer den Installer-Payload:
|
||||||
# Das Docker-Tool zieht rohe Katalog-Images (BaseSystem.dmg bzw.
|
|
||||||
# InstallAssistant.pkg) und schreibt sie per dd — das funktioniert ohne
|
|
||||||
# Mac, aber createinstallmedia braucht die VOLLSTAENDIGE "Install macOS
|
|
||||||
# X.app", nicht die rohen Katalog-Dateien. Der offizielle, von Apple
|
|
||||||
# unterstuetzte Weg an diese App zu kommen OHNE App Store ist
|
|
||||||
# `softwareupdate --fetch-full-installer`. Deshalb ist dieses Script
|
|
||||||
# bewusst komplett unabhaengig vom ./downloads/library-Ordner des
|
|
||||||
# Docker-Tools — beide Formate sind nicht kompatibel zueinander.
|
|
||||||
#
|
#
|
||||||
# Zukunftssicherheit: `softwareupdate --list-full-installers` fragt
|
# 1) Apple-Server (Download)
|
||||||
# Apples eigene Server live ab. Neue macOS-Versionen tauchen automatisch
|
# softwareupdate --fetch-full-installer laedt die komplette
|
||||||
# auf, ohne dass dieses Script angefasst werden muss.
|
# "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.
|
||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
@@ -37,112 +49,276 @@ err() { printf "%s%s%s\n" "$RED" "FEHLER: $1" "$RESET" >&2; }
|
|||||||
ok() { printf "%s%s%s\n" "$GREEN" "$1" "$RESET"; }
|
ok() { printf "%s%s%s\n" "$GREEN" "$1" "$RESET"; }
|
||||||
warn() { printf "%s%s%s\n" "$YELLOW" "$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
|
||||||
|
}
|
||||||
|
|
||||||
# ---------- Vorpruefungen ----------
|
# ---------- Vorpruefungen ----------
|
||||||
if [[ "$(uname)" != "Darwin" ]]; then
|
if [[ "$(uname)" != "Darwin" ]]; then
|
||||||
err "Dieses Script laeuft nur auf echtem macOS (kein Linux, keine VM ohne macOS)."
|
err "Dieses Script laeuft nur auf echtem macOS (kein Linux, keine VM ohne macOS)."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! command -v softwareupdate >/dev/null 2>&1; then
|
# ---------- Schritt 1: Quelle + Version waehlen ----------
|
||||||
err "'softwareupdate' nicht gefunden — ungewoehnliches macOS-Setup, Abbruch."
|
title "macOS USB-Stick-Creator (nativ) — Schritt 1/4: Quelle + Version"
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
MACOS_VER="$(sw_vers -productVersion)"
|
echo "Woher soll der macOS-Installer kommen?"
|
||||||
# fetch-full-installer gibt es erst ab Catalina (10.15)
|
|
||||||
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
|
|
||||||
|
|
||||||
# ---------- Schritt 1: verfuegbare Installer laden ----------
|
|
||||||
title "macOS USB-Stick-Creator (nativ) — Schritt 1/4: Verfuegbare Versionen"
|
|
||||||
echo "Frage Apples Software-Update-Server nach verfuegbaren kompletten Installern ab..."
|
|
||||||
echo
|
echo
|
||||||
|
echo " ${BOLD}1)${RESET} Von Apple herunterladen (braucht Internet, mehrere GB)"
|
||||||
LIST_RAW="$(softwareupdate --list-full-installers 2>/dev/null)"
|
echo " ${BOLD}2)${RESET} Aus lokaler Downloads-Bibliothek (Docker-Tool) verwenden -- kein Internet noetig"
|
||||||
if [[ -z "$LIST_RAW" ]] || ! grep -q "Title:" <<<"$LIST_RAW"; then
|
|
||||||
err "Keine Installer gefunden. Internetverbindung pruefen, oder Apple hat gerade ein Problem."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Zeilenformat: "* Title: macOS Sonoma, Version: 14.6.1, Size: 12345678900, Build: 23G93"
|
|
||||||
declare -a TITLES=()
|
|
||||||
declare -a VERSIONS=()
|
|
||||||
while IFS= read -r line; do
|
|
||||||
[[ "$line" == *"Title:"* ]] || continue
|
|
||||||
t="$(sed -E 's/^\* Title: ([^,]+),.*/\1/' <<<"$line")"
|
|
||||||
v="$(sed -E 's/.*Version: ([^,]+),.*/\1/' <<<"$line")"
|
|
||||||
TITLES+=("$t")
|
|
||||||
VERSIONS+=("$v")
|
|
||||||
done <<<"$LIST_RAW"
|
|
||||||
|
|
||||||
if [[ ${#TITLES[@]} -eq 0 ]]; then
|
|
||||||
err "Konnte die Liste nicht parsen. Rohausgabe:"
|
|
||||||
echo "$LIST_RAW"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Verfuegbar:"
|
|
||||||
echo
|
|
||||||
for i in "${!TITLES[@]}"; do
|
|
||||||
printf " %s%2d)%s %s (%s)\n" "$BOLD" "$((i+1))" "$RESET" "${TITLES[$i]}" "${VERSIONS[$i]}"
|
|
||||||
done
|
|
||||||
echo
|
echo
|
||||||
printf "%s0)%s Abbrechen\n" "$BOLD" "$RESET"
|
printf "%s0)%s Abbrechen\n" "$BOLD" "$RESET"
|
||||||
echo
|
echo
|
||||||
read -r -p "Welche Version soll heruntergeladen/benutzt werden? [Nummer]: " CHOICE
|
read -r -p "Quelle waehlen [Nummer]: " SRC_CHOICE
|
||||||
|
|
||||||
if [[ "$CHOICE" == "0" || -z "$CHOICE" ]]; then
|
if [[ "$SRC_CHOICE" == "0" || -z "$SRC_CHOICE" ]]; then
|
||||||
echo "Abgebrochen."
|
echo "Abgebrochen."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
if ! [[ "$CHOICE" =~ ^[0-9]+$ ]] || (( CHOICE < 1 || CHOICE > ${#TITLES[@]} )); then
|
|
||||||
err "Ungueltige Auswahl."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
SEL_TITLE="${TITLES[$((CHOICE-1))]}"
|
SOURCE=""
|
||||||
SEL_VERSION="${VERSIONS[$((CHOICE-1))]}"
|
WRITE_MODE="" # "cim" (createinstallmedia) oder "asr" (direktes Image-Restore)
|
||||||
# Apples Titel-Feld liefert z.B. "macOS Sonoma" -> App heisst "Install macOS Sonoma.app"
|
APP_PATH=""
|
||||||
APP_PATH="/Applications/Install ${SEL_TITLE}.app"
|
DMG_PATH=""
|
||||||
|
LIB_TITLE=""
|
||||||
|
LIB_VERSION=""
|
||||||
|
|
||||||
echo
|
case "$SRC_CHOICE" in
|
||||||
ok "Gewaehlt: ${SEL_TITLE} (${SEL_VERSION})"
|
1)
|
||||||
|
SOURCE="apple"
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
SOURCE="library"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
err "Ungueltige Auswahl."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
# ---------- Schritt 2: Download (falls noetig) ----------
|
if [[ "$SOURCE" == "apple" ]]; then
|
||||||
title "Schritt 2/4: Installer-App besorgen"
|
if ! command -v softwareupdate >/dev/null 2>&1; then
|
||||||
|
err "'softwareupdate' nicht gefunden — ungewoehnliches macOS-Setup, Abbruch."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -d "$APP_PATH" ]]; then
|
MACOS_VER="$(sw_vers -productVersion)"
|
||||||
ok "'${APP_PATH}' existiert bereits — Download wird uebersprungen."
|
# fetch-full-installer gibt es erst ab Catalina (10.15)
|
||||||
else
|
if [[ "$MACOS_VER" == 10.* ]]; then
|
||||||
warn "Das laedt mehrere GB von Apple und braucht sudo-Rechte."
|
MINOR="${MACOS_VER#10.}"
|
||||||
echo "Zielort: ${APP_PATH}"
|
MINOR="${MINOR%%.*}"
|
||||||
read -r -p "Download jetzt starten? [j/N]: " GO
|
if [[ "$MINOR" -lt 15 ]]; then
|
||||||
if [[ ! "$GO" =~ ^[jJ]$ ]]; 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
|
||||||
|
|
||||||
|
LIST_RAW="$(softwareupdate --list-full-installers 2>/dev/null)"
|
||||||
|
if [[ -z "$LIST_RAW" ]] || ! grep -q "Title:" <<<"$LIST_RAW"; then
|
||||||
|
err "Keine Installer gefunden. Internetverbindung pruefen, oder Apple hat gerade ein Problem."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Zeilenformat: "* Title: macOS Sonoma, Version: 14.6.1, Size: 12345678900, Build: 23G93"
|
||||||
|
declare -a TITLES=()
|
||||||
|
declare -a VERSIONS=()
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ "$line" == *"Title:"* ]] || continue
|
||||||
|
t="$(sed -E 's/^\* Title: ([^,]+),.*/\1/' <<<"$line")"
|
||||||
|
v="$(sed -E 's/.*Version: ([^,]+),.*/\1/' <<<"$line")"
|
||||||
|
TITLES+=("$t")
|
||||||
|
VERSIONS+=("$v")
|
||||||
|
done <<<"$LIST_RAW"
|
||||||
|
|
||||||
|
if [[ ${#TITLES[@]} -eq 0 ]]; then
|
||||||
|
err "Konnte die Liste nicht parsen. Rohausgabe:"
|
||||||
|
echo "$LIST_RAW"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Verfuegbar:"
|
||||||
|
echo
|
||||||
|
for i in "${!TITLES[@]}"; do
|
||||||
|
printf " %s%2d)%s %s (%s)\n" "$BOLD" "$((i+1))" "$RESET" "${TITLES[$i]}" "${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."
|
echo "Abgebrochen."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
echo
|
if ! [[ "$CHOICE" =~ ^[0-9]+$ ]] || (( CHOICE < 1 || CHOICE > ${#TITLES[@]} )); then
|
||||||
echo "Starte Download (kann je nach Version 30-90 Minuten dauern)..."
|
err "Ungueltige Auswahl."
|
||||||
sudo softwareupdate --fetch-full-installer --full-installer-version "$SEL_VERSION"
|
|
||||||
RC=$?
|
|
||||||
if [[ $RC -ne 0 || ! -d "$APP_PATH" ]]; then
|
|
||||||
err "Download fehlgeschlagen oder App nicht am erwarteten Ort. Erwartet: ${APP_PATH}"
|
|
||||||
echo "Tipp: 'ls /Applications | grep Install' zeigt den tatsaechlichen Namen."
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
ok "Download abgeschlossen: ${APP_PATH}"
|
|
||||||
|
SEL_TITLE="${TITLES[$((CHOICE-1))]}"
|
||||||
|
SEL_VERSION="${VERSIONS[$((CHOICE-1))]}"
|
||||||
|
WRITE_MODE="cim"
|
||||||
|
# Apples Titel-Feld liefert z.B. "macOS Sonoma" -> App heisst "Install macOS Sonoma.app"
|
||||||
|
APP_PATH="/Applications/Install ${SEL_TITLE}.app"
|
||||||
|
|
||||||
|
echo
|
||||||
|
ok "Gewaehlt: ${SEL_TITLE} (${SEL_VERSION}) -- Quelle: Apple-Server"
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
DEFAULT_LIB_DIR="$(cd "$(dirname "$0")/.." 2>/dev/null && pwd)/downloads/library"
|
||||||
|
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
|
||||||
|
LIB_DIR="${LIB_DIR:-$DEFAULT_LIB_DIR}"
|
||||||
|
|
||||||
|
if [[ ! -d "$LIB_DIR" ]]; then
|
||||||
|
err "Ordner nicht gefunden: ${LIB_DIR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
declare -a LIB_ENTRY_DIRS=()
|
||||||
|
declare -a LIB_ENTRY_DESC=()
|
||||||
|
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)"
|
||||||
|
esize_gb="?"
|
||||||
|
if [[ "$esize" =~ ^[0-9]+$ ]]; then
|
||||||
|
esize_gb="$(( esize / 1000 / 1000 / 1000 ))"
|
||||||
|
fi
|
||||||
|
mode_desc="bis Catalina, robust"
|
||||||
|
[[ "$emode" == "recovery" ]] && mode_desc="Big Sur+, ueber installer-pkg"
|
||||||
|
LIB_ENTRY_DIRS+=("$entry_dir")
|
||||||
|
LIB_ENTRY_DESC+=("${et} (${ev}) -- ~${esize_gb} GB, ${mode_desc}")
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#LIB_ENTRY_DIRS[@]} -eq 0 ]]; then
|
||||||
|
err "Keine vollstaendigen Installer in ${LIB_DIR} gefunden."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "In der Bibliothek gefunden:"
|
||||||
|
echo
|
||||||
|
for i in "${!LIB_ENTRY_DIRS[@]}"; do
|
||||||
|
printf " %s%2d)%s %s\n" "$BOLD" "$((i+1))" "$RESET" "${LIB_ENTRY_DESC[$i]}"
|
||||||
|
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 > ${#LIB_ENTRY_DIRS[@]} )); then
|
||||||
|
err "Ungueltige Auswahl."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SEL_ENTRY_DIR="${LIB_ENTRY_DIRS[$((LCHOICE-1))]}"
|
||||||
|
SEL_META="${SEL_ENTRY_DIR}meta.json"
|
||||||
|
LIB_TITLE="$(json_field "$SEL_META" title)"
|
||||||
|
LIB_VERSION="$(json_field "$SEL_META" version)"
|
||||||
|
LIB_MODE="$(json_field "$SEL_META" mode)"
|
||||||
|
LIB_FILENAME="$(json_field "$SEL_META" filename)"
|
||||||
|
LIB_PAYLOAD_PATH="${SEL_ENTRY_DIR}${LIB_FILENAME}"
|
||||||
|
|
||||||
|
if [[ ! -f "$LIB_PAYLOAD_PATH" ]]; then
|
||||||
|
err "Payload-Datei nicht gefunden: ${LIB_PAYLOAD_PATH}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$LIB_MODE" == "recovery" ]]; then
|
||||||
|
WRITE_MODE="cim"
|
||||||
|
else
|
||||||
|
WRITE_MODE="asr"
|
||||||
|
DMG_PATH="$LIB_PAYLOAD_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
ok "Gewaehlt: ${LIB_TITLE} (${LIB_VERSION}) -- Quelle: lokale Bibliothek (${LIB_PAYLOAD_PATH})"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
CIM_BIN="${APP_PATH}/Contents/Resources/createinstallmedia"
|
# ---------- Schritt 2: Installer-Payload besorgen ----------
|
||||||
if [[ ! -x "$CIM_BIN" ]]; then
|
title "Schritt 2/4: Installer-Payload besorgen"
|
||||||
err "createinstallmedia nicht gefunden unter: ${CIM_BIN}"
|
|
||||||
exit 1
|
if [[ "$SOURCE" == "apple" ]]; then
|
||||||
|
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."
|
||||||
|
echo "Zielort: ${APP_PATH}"
|
||||||
|
read -r -p "Download jetzt starten? [j/N]: " GO
|
||||||
|
if [[ ! "$GO" =~ ^[jJ]$ ]]; then
|
||||||
|
echo "Abgebrochen."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
echo "Starte Download (kann je nach Version 30-90 Minuten dauern)..."
|
||||||
|
sudo softwareupdate --fetch-full-installer --full-installer-version "$SEL_VERSION"
|
||||||
|
RC=$?
|
||||||
|
if [[ $RC -ne 0 || ! -d "$APP_PATH" ]]; then
|
||||||
|
err "Download fehlgeschlagen oder App nicht am erwarteten Ort. Erwartet: ${APP_PATH}"
|
||||||
|
echo "Tipp: 'ls /Applications | grep Install' zeigt den tatsaechlichen Namen."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
ok "Download abgeschlossen: ${APP_PATH}"
|
||||||
|
fi
|
||||||
|
elif [[ "$WRITE_MODE" == "cim" ]]; then
|
||||||
|
# Bibliothek, Big Sur+: InstallAssistant.pkg -> komplette Install-App
|
||||||
|
# nach /Applications entpacken (Apples Standardverhalten von 'installer').
|
||||||
|
if [[ -d "$APP_PATH" ]]; then
|
||||||
|
ok "'${APP_PATH}' existiert bereits — Installer-Lauf wird uebersprungen."
|
||||||
|
else
|
||||||
|
echo "Entpacke ${LIB_PAYLOAD_PATH} nach /Applications (installer -pkg, braucht sudo)..."
|
||||||
|
echo "Das dauert je nach Groesse ein paar Minuten."
|
||||||
|
echo
|
||||||
|
sudo installer -pkg "$LIB_PAYLOAD_PATH" -target /
|
||||||
|
RC=$?
|
||||||
|
if [[ $RC -ne 0 ]]; then
|
||||||
|
err "'installer -pkg' fehlgeschlagen ($RC)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ ! -d "$APP_PATH" ]]; then
|
||||||
|
# Fallback: Apples interner App-Name wich vom Bibliotheks-Titel ab --
|
||||||
|
# nimm das zuletzt angelegte "Install macOS *.app"-Bundle.
|
||||||
|
FALLBACK_APP="$(ls -dt /Applications/Install\ macOS*.app 2>/dev/null | head -n1)"
|
||||||
|
[[ -n "$FALLBACK_APP" ]] && APP_PATH="$FALLBACK_APP"
|
||||||
|
fi
|
||||||
|
if [[ ! -d "$APP_PATH" ]]; then
|
||||||
|
err "Konnte die installierte App nach 'installer -pkg' nicht finden."
|
||||||
|
echo "Tipp: 'ls /Applications | grep Install' zeigt was tatsaechlich da ist."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
ok "Installiert: ${APP_PATH}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
ok "Bibliothek liefert bereits ein fertiges, bootfaehiges Image (${DMG_PATH}) -- kein App-Download noetig."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$WRITE_MODE" == "cim" ]]; then
|
||||||
|
CIM_BIN="${APP_PATH}/Contents/Resources/createinstallmedia"
|
||||||
|
if [[ ! -x "$CIM_BIN" ]]; then
|
||||||
|
err "createinstallmedia nicht gefunden unter: ${CIM_BIN}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------- Schritt 3: Ziel-USB-Stick waehlen ----------
|
# ---------- Schritt 3: Ziel-USB-Stick waehlen ----------
|
||||||
@@ -209,33 +385,75 @@ if [[ "$CONFIRM" != "/dev/${TARGET_DISK}" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------- Schritt 4: createinstallmedia ausfuehren ----------
|
# ---------- Schritt 4: Stick schreiben ----------
|
||||||
title "Schritt 4/4: Stick wird erstellt"
|
title "Schritt 4/4: Stick wird erstellt"
|
||||||
|
|
||||||
echo "App: ${APP_PATH}"
|
if [[ "$WRITE_MODE" == "cim" ]]; then
|
||||||
echo "Ziel: /dev/${TARGET_DISK}"
|
echo "App: ${APP_PATH}"
|
||||||
echo
|
echo "Ziel: /dev/${TARGET_DISK}"
|
||||||
echo "createinstallmedia laeuft jetzt (braucht sudo, dauert typischerweise 10-30 Minuten)..."
|
echo
|
||||||
echo "Der Stick wird zwischendurch umbenannt/neu gemountet — das ist normal."
|
echo "createinstallmedia laeuft jetzt (braucht sudo, dauert typischerweise 10-30 Minuten)..."
|
||||||
echo
|
echo "Der Stick wird zwischendurch umbenannt/neu gemountet — das ist normal."
|
||||||
|
echo
|
||||||
|
|
||||||
sudo "$CIM_BIN" --volume "/dev/${TARGET_DISK}" --nointeraction
|
sudo "$CIM_BIN" --volume "/dev/${TARGET_DISK}" --nointeraction
|
||||||
RC=$?
|
RC=$?
|
||||||
|
|
||||||
echo
|
echo
|
||||||
if [[ $RC -eq 0 ]]; then
|
if [[ $RC -eq 0 ]]; then
|
||||||
ok "Fertig! Der Stick ist bootfaehig."
|
ok "Fertig! Der Stick ist bootfaehig."
|
||||||
echo "Am Ziel-Mac: beim Start Option/Alt gedrueckt halten, den Stick im Boot-Picker waehlen."
|
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
|
else
|
||||||
err "createinstallmedia ist mit Fehlercode ${RC} beendet — siehe Ausgabe oben."
|
# asr-Pfad: BaseSystem.dmg/InstallESD.dmg ist schon ein fertiges,
|
||||||
exit $RC
|
# bootfaehiges Volume. Erst Stick neu formatieren (liefert einen
|
||||||
fi
|
# Mountpunkt), dann Apples 'asr restore' das eigentliche Image drauf
|
||||||
|
# kopieren lassen -- das re-formatiert dabei nochmal komplett nach dem
|
||||||
|
# Schema des Quell-Images (--erase). Etablierte Community-Technik fuer
|
||||||
|
# macOS-Installer bis Catalina ohne createinstallmedia.
|
||||||
|
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
|
||||||
|
|
||||||
echo
|
VOL_MOUNT="/Volumes/INSTALL"
|
||||||
read -r -p "Die heruntergeladene Installer-App (mehrere GB) jetzt loeschen um Platz zu sparen? [j/N]: " CLEAN
|
if [[ ! -d "$VOL_MOUNT" ]]; then
|
||||||
if [[ "$CLEAN" =~ ^[jJ]$ ]]; then
|
err "Erwarteter Mountpunkt ${VOL_MOUNT} nicht gefunden nach dem Formatieren."
|
||||||
sudo rm -rf "$APP_PATH"
|
echo "Tipp: 'diskutil list /dev/${TARGET_DISK}' zeigt den tatsaechlichen Zustand."
|
||||||
ok "Geloescht: ${APP_PATH}"
|
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
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
|
|||||||
Reference in New Issue
Block a user