Alternative zum Docker/Linux-Tool: nutzt softwareupdate --fetch-full-installer + createinstallmedia direkt, wenn ein Mac (10.15+) verfuegbar ist. Bewusst unabhaengig vom ./downloads-Ordner, da createinstallmedia die volle Install-App braucht, nicht die rohen Katalog-Images des Docker-Pfads. Reines Terminal-Menue ohne externe Abhaengigkeiten (softwareupdate, diskutil, PlistBuddy). Zukunftssicher analog zum Docker-Tool, da Apples eigene Versionsliste live abgefragt wird.
246 lines
13 KiB
Markdown
246 lines
13 KiB
Markdown
# macOS USB Installer Tool
|
|
|
|
Lokale Web-GUI (Docker Compose) die macOS-Installer direkt von Apples
|
|
Servern laedt und einen bootfaehigen USB-Stick daraus baut — ohne Mac.
|
|
|
|
## Start
|
|
|
|
```bash
|
|
docker compose up --build
|
|
```
|
|
|
|
Dann im Browser: **http://127.0.0.1:8080**
|
|
|
|
Der Port bindet bewusst NUR an `127.0.0.1` (nicht `0.0.0.0`) — von aussen
|
|
im Netzwerk nicht erreichbar.
|
|
|
|
## Wie es funktioniert
|
|
|
|
### 1. Versions-Liste (automatisch aktuell)
|
|
Beim Container-Start wird gegen Apples echten Software-Update-Katalog
|
|
(`swscan.apple.com`) geprobt, welche macOS-Installer aktuell verfuegbar
|
|
sind. Das Namensschema der Katalog-URLs ist bekannt und wurde live
|
|
verifiziert (Stand: Erstellung dieses Tools). Wenn Apple eine neue
|
|
macOS-Hauptversion veroeffentlicht, taucht sie **automatisch beim naechsten
|
|
Container-Start** in der Liste auf — ohne Code-Aenderung, solange Apple
|
|
das Namensschema beibehaelt (das ist seit vielen Jahren stabil, aber keine
|
|
100%-Garantie fuer alle Zeit).
|
|
|
|
Per "neu laden"-Button in der GUI kann der Katalog auch ohne Neustart
|
|
aktualisiert werden.
|
|
|
|
### 2. Zwei Installations-Pfade
|
|
|
|
| macOS-Version | Pfad | Status |
|
|
|---|---|---|
|
|
| bis Catalina (10.15) | `legacy_dmg`: BaseSystem.dmg direkt per `dd` auf den Stick | **robust, gut etabliert** |
|
|
| ab Big Sur (11) aufwaerts | `recovery`: `InstallAssistant.pkg` wird entpackt (`xar` → `pbzx` → `cpio`), das darin enthaltene `SharedSupport.dmg`/`BaseSystem.dmg` wird dann per `dd` geschrieben | **experimentell** |
|
|
|
|
**Warum der Unterschied:** Ab macOS Big Sur liefert Apple keinen fertigen
|
|
Installer-Datentraeger mehr, sondern ein Programm-Paket. Das eigentliche
|
|
Tool das daraus einen vollstaendigen Multi-Volume-Installer-Stick baut
|
|
(`createinstallmedia`) laeuft nur unter echtem macOS. Dieses Tool geht
|
|
stattdessen den Community-erprobten Weg: das darin enthaltene
|
|
Recovery-/BaseSystem-Image direkt extrahieren und schreiben. Der Stick
|
|
bootet damit in ein Internet-Recovery-System, das den Rest der
|
|
Installation von Apples Servern nachlaedt.
|
|
|
|
**Das ist der fragilste Teil des ganzen Tools.** Apple hat den internen
|
|
Aufbau von `InstallAssistant.pkg` in der Vergangenheit leicht veraendert.
|
|
Wenn die Extraktion fehlschlaegt (Fehlermeldung im Log-Fenster), ist das
|
|
kein Bug im eigentlichen Sinn, sondern ein Zeichen dass sich intern was
|
|
geaendert hat — dann muss `backend/installer.py` (`_extract_recovery_dmg`)
|
|
angepasst werden.
|
|
|
|
### 3. Offline-Bibliothek / "nur herunterladen"-Modus
|
|
Oben in der GUI laesst sich per Umschalter zwischen drei Modi waehlen:
|
|
|
|
- **"Auf USB-Stick schreiben"** (Standard) — wie bisher, eine Version, ein
|
|
kompletter Stick.
|
|
- **"Multiboot-Stick (mehrere Versionen)"** — siehe Abschnitt 4 unten.
|
|
- **"Nur herunterladen (Offline-Bibliothek)"** — laedt ausschliesslich das
|
|
Original-Image (`BaseSystem.dmg`/`InstallESD.dmg` bzw. `InstallAssistant.pkg`,
|
|
je nach Version) und legt es **dauerhaft** unter `./downloads/library/<name>/`
|
|
ab (inkl. `meta.json` mit Titel/Version/Groesse/Download-Zeitpunkt). Kein
|
|
Geraet noetig, nichts wird geloescht.
|
|
|
|
Damit kannst du dir vorab — solange Internet da ist — eine komplette
|
|
Offline-Sammlung aller verfuegbaren macOS-Versionen aufbauen. Weil die
|
|
Downloads im Bind-Mount `./downloads` liegen (siehe oben, kein Docker-Volume),
|
|
reicht es, den kompletten Projektordner (inkl. `downloads/library/`) auf eine
|
|
externe Platte zu kopieren — dort ist dann alles fuer den Stick-Bau parat,
|
|
auch ganz ohne Internetzugang.
|
|
|
|
**Wichtig:** ein "Stick erstellen"-Job prueft VOR jedem Download automatisch,
|
|
ob das Image schon in der Bibliothek liegt (`installer.get_cached_payload`),
|
|
und ueberspringt den Download dann komplett — das gilt fuer beide Modi.
|
|
Jeder normale Stick-Bau traegt also nebenbei zur Bibliothek bei, und ein
|
|
spaeterer Stick-Bau aus derselben Bibliothek (z.B. von der externen Platte)
|
|
laedt nichts erneut herunter.
|
|
|
|
Die GUI zeigt oben die Gesamtgroesse + Anzahl bereits heruntergeladener
|
|
Installer an, und markiert bereits vorhandene Versionen in der Auswahlliste
|
|
mit "bereits offline vorhanden".
|
|
|
|
### 4. Multiboot-Stick (mehrere macOS-Versionen auf einem Stick)
|
|
Im Modus **"Multiboot-Stick"** waehlst du in Schritt 1 statt einer Version
|
|
per Checkbox **2 bis 8** Versionen gleichzeitig aus. Das Tool baut daraus
|
|
einen einzigen Stick mit **einer eigenen GPT-Partition pro Version**.
|
|
|
|
**Warum das funktioniert:** Macs EFI-Bootmanager (beim Start `Alt`/`Option`
|
|
gedrueckt halten) scannt beim Boot-Picker **alle** Partitionen eines
|
|
angeschlossenen Datentraegers nach bootfaehigen Volumes, nicht nur die
|
|
erste. Legt man also mehrere `Apple_HFS`-Partitionen an und schreibt in
|
|
jede das vollstaendige, fuer sich bootfaehige Installer-Image, zeigt der
|
|
Boot-Picker automatisch alle als eigene Wahlmoeglichkeiten an. Das ist eine
|
|
in der Mac-Community seit Jahren dokumentierte Technik, kein Neuland dieses
|
|
Tools.
|
|
|
|
**Ablauf im Backend (`backend/installer.py`, `_run_multiboot_job`):**
|
|
1. Alle gewaehlten Images werden (aus der Bibliothek oder frisch) geladen
|
|
und wie im Einzel-Stick-Modus konvertiert -- ERST wenn alle fertig
|
|
konvertiert sind, geht's weiter (kein Halb-Zustand auf dem Stick).
|
|
2. Anhand der tatsaechlichen Image-Groessen + Puffer (`PARTITION_PADDING_MB`)
|
|
wird geprueft, ob der ausgewaehlte Stick ueberhaupt gross genug ist --
|
|
**bevor** irgendetwas geschrieben wird. Ist er zu klein, bricht der Job
|
|
mit einer klaren Fehlermeldung (benoetigt vs. vorhanden in GB) ab, ohne
|
|
den Stick anzuruehren.
|
|
3. `wipefs -a` + `sgdisk --zap-all` loeschen den Stick komplett, dann legt
|
|
`sgdisk` in einem Rutsch N Partitionen an (Typ `AF00` = Apple HFS/HFS+,
|
|
mit Label = Titel/Version der jeweiligen macOS-Version, sichtbar im
|
|
Boot-Picker).
|
|
4. Jedes Image wird per `dd` in seine eigene Partition geschrieben
|
|
(`/dev/sdb1`, `/dev/sdb2`, ...), nicht auf das gesamte Geraet.
|
|
|
|
**Ehrliche Einschraenkung -- als experimentell markiert:**
|
|
- Fuer den `legacy_dmg`-Pfad (bis Catalina) ist die Technik gut etabliert
|
|
und robust.
|
|
- Fuer den `recovery`-Pfad (Big Sur+) erbt Multiboot dieselbe Unsicherheit
|
|
wie der normale Einzel-Stick-Modus (siehe Abschnitt 2) -- kommt on top zur
|
|
Multiboot-Partitionierung selbst dazu. Mischungen aus alten + neuen
|
|
Versionen auf einem Stick sind technisch kein Problem, aber jede
|
|
Big-Sur+-Version im Mix bleibt der fragilste Teil.
|
|
- Groessenbedarf: 3 Versionen macOS koennen leicht 30-50GB+ zusammen
|
|
ergeben, ein Stick mit 64GB+ ist fuer sinnvolle Kombinationen realistisch.
|
|
- Es gab noch **keinen echten Boot-Test auf echter Mac-Hardware** durch
|
|
dieses Projekt selbst -- die Partitionierungs-/Schreib-Logik ist gegen
|
|
einen echten Docker-Host verifiziert (Build + Job-Ablauf), das
|
|
tatsaechliche "zeigt der Boot-Picker alle Partitionen an und bootet jede"
|
|
muss beim ersten echten Einsatz am Mac verifiziert werden.
|
|
|
|
### 5. USB-Geraete-Erkennung
|
|
Der Container laeuft mit `privileged: true` und mountet `/dev` sowie
|
|
`/run/udev` vom Host durch — noetig, um Sticks direkt zu partitionieren
|
|
und zu beschreiben. Das ist bewusst so gebaut, bedeutet aber: der Container
|
|
hat vollen Zugriff auf **alle** Block-Devices des Rechners. Deshalb:
|
|
|
|
- Port ist NUR auf `127.0.0.1` gebunden (kein Netzwerk-Zugriff von aussen).
|
|
- Die System-Platte (auf der `/` liegt) wird automatisch als Ziel gesperrt.
|
|
- Nur Geraete mit USB-Transport werden als Ziel akzeptiert.
|
|
- Vor dem Schreiben muss der exakte Geraetepfad (z.B. `/dev/sdb`) nochmal
|
|
von Hand eingetippt werden — kein versehentliches Ein-Klick-Loeschen.
|
|
|
|
**Trotzdem gilt:** das Tool schreibt mit `dd` und `wipefs` direkt auf ein
|
|
Block-Device. Wenn du das falsche Geraet auswaehlst, sind die Daten weg.
|
|
Vor dem Start immer pruefen, welcher Stick tatsaechlich gemeint ist (Groesse
|
|
und Modell in der GUI vergleichen).
|
|
|
|
## Build-Hinweis (hfsprogs / xar / pbzx)
|
|
Alles unten wurde am 2026-07-18 live gegen einen echten `docker compose build`
|
|
+ `up` auf einem echten Docker-Host getestet (nicht nur behauptet):
|
|
|
|
- `hfsprogs` liegt in Debians non-free-Bereich. WICHTIG: `bookworm-slim`
|
|
nutzt das neue **deb822**-Sourcen-Format (`/etc/apt/sources.list.d/
|
|
debian.sources` statt der alten `sources.list`). Eine zusaetzliche
|
|
separate `sources.list`-Datei fuer dieselbe `deb.debian.org`-URI OHNE
|
|
`Signed-By` fuehrt zu `Conflicting values set for option Signed-By` und
|
|
bricht `apt-get update` komplett ab. Der Dockerfile patcht deshalb die
|
|
bestehende `debian.sources` per `sed` (Components-Zeile um
|
|
`contrib non-free non-free-firmware` erweitert) statt eine zweite Quelle
|
|
anzulegen.
|
|
- `xar` gibt es in `bookworm` ueberhaupt nicht als Paket, wird aus dem
|
|
Upstream-Repo `mackyle/xar` gebaut. Dessen `configure.ac` prueft
|
|
OpenSSL ueber `AC_CHECK_LIB([crypto], [OpenSSL_add_all_ciphers])` --
|
|
diese Funktion ist in OpenSSL 3.x (Debian bookworm) nur noch ein
|
|
no-op-Praeprozessor-Makro in `evp.h`, kein linkbares Symbol mehr. Der
|
|
Autoconf-Test schlaegt deshalb fehl ("Cannot build without libcrypto"),
|
|
obwohl `libssl-dev` korrekt installiert ist. Fix: der Dockerfile biegt
|
|
den Probe-Funktionsnamen per `sed` auf `EVP_EncryptInit` um (gegen
|
|
`libcrypto.so.3` per `nm` verifiziert, seit jeher stabil exportiert).
|
|
- `xar` MUSS vor `pbzx` gebaut werden -- `pbzx` linkt gegen `-lxar` und
|
|
`xar/xar.h`.
|
|
- `pbzx`: das Repo `mackyle/pbzx` existiert nicht (mehr) auf GitHub (404).
|
|
Git meldet das irrefuehrend als `could not read Username for
|
|
'https://github.com'`, weil GitHub bei `git-upload-pack`-Requests gegen
|
|
nichtexistente/private Repos aus Anti-Enumeration-Gruenden 401 statt 404
|
|
zurueckgibt. Fix: Community-Standard-Fork
|
|
[`NiklasRosenstein/pbzx`](https://github.com/NiklasRosenstein/pbzx)
|
|
(u.a. in OSX-KVM genutzt) wird stattdessen geklont.
|
|
|
|
Macht den ersten Build minimal langsamer, aber zuverlaessiger als ein
|
|
Distro-Wechsel.
|
|
|
|
## Katalog-Bugs (gefunden + gefixt, 2026-07-18)
|
|
Zwei echte Bugs in `backend/catalog.py`, beide live gegen den echten
|
|
Apple-Katalog (`swscan.apple.com`) reproduziert und verifiziert:
|
|
|
|
1. **Doppeltes Entpacken:** Apples Server schickt den Katalog mit
|
|
`Content-Encoding: x-gzip`. `requests`/`urllib3` dekomprimiert das
|
|
bereits TRANSPARENT -- der Code rief zusaetzlich `gzip.decompress()`
|
|
drauf auf, was mit `BadGzipFile: Not a gzipped file` crashte (die
|
|
Gzip-Magic-Bytes waren durch die automatische Dekompression laengst
|
|
weg). Fix: nur noch entpacken wenn die Bytes wirklich noch mit der
|
|
Gzip-Magic (`\x1f\x8b`) anfangen.
|
|
2. **Big Sur+ wurde komplett ausgefiltert:** `_is_macos_installer()` prüfte
|
|
nur auf das klassische Schema
|
|
(`ExtendedMetaInfo.InstallAssistantPackageIdentifiers.OSInstall ==
|
|
"com.apple.mpkg.OSInstall"`, gueltig bis Catalina). Ab Big Sur nutzt
|
|
Apple stattdessen einen `SharedSupport`-Key
|
|
(`com.apple.pkg.InstallAssistant.macOSBigSur`, `...macOSTahoe`, etc.) --
|
|
ohne den alten Key. Der Filter hat dadurch STILLSCHWEIGEND alle
|
|
Big-Sur-bis-Tahoe-Installer verworfen, obwohl sie im selben Katalog
|
|
laengst enthalten waren (live verifiziert: 24 InstallAssistant.pkg-
|
|
Eintraege im Katalog, 0 davon trafen den alten Filter). Genau der
|
|
`recovery`-Pfad, den dieses Tool eigentlich anbieten soll. Fix:
|
|
`_is_macos_installer` erkennt jetzt beide Schemata; `_fetch_product_
|
|
metadata` faellt bei fehlendem `ServerMetadataURL` (Big Sur+ hat keins)
|
|
auf den Namen aus dem `SharedSupport`-Identifier zurueck. Nach dem Fix:
|
|
37 statt 13 Installer gefunden, inkl. macOS Tahoe.
|
|
|
|
Beide Fixes wurden nicht nur im Code geaendert, sondern gegen den echten,
|
|
frisch gebauten Container end-to-end verifiziert (`/api/versions` liefert
|
|
jetzt tatsaechlich Big Sur bis Tahoe zurueck).
|
|
|
|
## Voraussetzungen auf dem Host
|
|
- Docker + Docker Compose
|
|
- Linux (getestet gegen die Grundannahmen von `lsblk`/`/dev`/`udev`; auf
|
|
macOS/Windows-Docker-Desktop funktioniert das Device-Passthrough so nicht)
|
|
|
|
## Alternative: nativer Mac-Weg (createinstallmedia)
|
|
Wenn zufaellig ein echter Mac zur Verfuegung steht (macOS 10.15+), gibt es
|
|
unter [`native-macos/`](native-macos/) ein eigenstaendiges Terminal-Script
|
|
(`create-stick.sh`), das statt des Recovery-Extraktions-Workarounds die
|
|
**echte** `createinstallmedia` nutzt — ueber Apples eigenen
|
|
`softwareupdate --fetch-full-installer`-Befehl. Kein Docker noetig, keine
|
|
Abhaengigkeiten ausser macOS-Bordmitteln. Details siehe dortiges README;
|
|
wichtig: die beiden Wege sind bewusst unabhaengig, weil `createinstallmedia`
|
|
ein anderes Installer-Format braucht als die rohen Katalog-Images, die das
|
|
Docker-Tool hier im Root verwendet.
|
|
|
|
## Bekannte Grenzen (ehrlich, nicht schoengeredet)
|
|
1. Der `recovery`-Pfad (Big Sur+) ist nicht production-hardened getestet
|
|
worden, sondern nach dokumentiertem, in der Hackintosh-/Recovery-Community
|
|
etabliertem Verfahren gebaut. Rechne bei sehr neuen macOS-Versionen mit
|
|
Nacharbeit.
|
|
2. Die Katalog-Zukunftssicherheit haengt an Apples URL-Namensschema. Bricht
|
|
Apple das Schema, muss `backend/catalog.py` angepasst werden (kurzer Fix,
|
|
kein Rewrite).
|
|
3. Kein Windows/macOS-Docker-Host-Support fuer das Device-Passthrough —
|
|
nur natives Linux.
|
|
4. Der Multiboot-Modus (Abschnitt 4) wurde bislang nur end-to-end bis zur
|
|
fertig geschriebenen Partitionstabelle/den Images verifiziert, NICHT
|
|
durch einen echten Boot-Test auf Mac-Hardware. Die zugrundeliegende
|
|
Multi-Partition-Bootpicker-Technik ist etabliertes Community-Wissen,
|
|
aber "funktioniert bei diesem Tool nachweislich" != "wurde von uns am
|
|
Mac gebootet".
|