Files
macos-usb-stick-creator/README.md
T
ARIA 5c2cdd7543 feat: Offline-Bibliothek + Download-only-Modus
Neuer Modus in der GUI: Image nur herunterladen und dauerhaft unter
./downloads/library/ ablegen, ohne USB-Stick. Baut sich so vorab eine
komplette Offline-Sammlung aller macOS-Versionen auf (z.B. auf externe
Platte kopierbar). Stick-Jobs pruefen jetzt IMMER zuerst die lokale
Bibliothek und ueberspringen den Download bei Treffer -- jeder normale
Stick-Bau traegt so nebenbei zur Bibliothek bei.

Backend: installer.py bekommt job_type (stick|download), persistente
Downloads in library/<name>/ mit meta.json + atomarem .part-Rename,
neue /api/library. Frontend: Modus-Umschalter, Bibliotheks-Status,
Offline-Badges in der Versionsliste.

Live getestet auf aria-wohnung: Container neu gebaut, Katalog laedt 37
Installer, Download-Job End-to-End ueber die echte API (Cache-Miss dann
Cache-Hit verifiziert), Stick-Job-Validierung weiterhin intakt.
2026-07-18 10:58:55 +02:00

181 lines
9.3 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 &mdash; 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`) &mdash; 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 &mdash; 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 &mdash; 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 zwei Modi waehlen:
- **"Auf USB-Stick schreiben"** (Standard) &mdash; wie bisher.
- **"Nur herunterladen (Offline-Bibliothek)"** &mdash; 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 &mdash; solange Internet da ist &mdash; 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 &mdash; 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 &mdash; 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. USB-Geraete-Erkennung
Der Container laeuft mit `privileged: true` und mountet `/dev` sowie
`/run/udev` vom Host durch &mdash; 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 &mdash; 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)
## 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 &mdash;
nur natives Linux.