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

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

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 (xarpbzxcpio), 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 zwei Modi waehlen:

  • "Auf USB-Stick schreiben" (Standard) — wie bisher.
  • "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. 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 (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 — nur natives Linux.
S
Description
No description provided
Readme
86 KiB
Languages
Python 43.6%
Shell 33.8%
JavaScript 11.3%
CSS 4.1%
HTML 3.6%
Other 3.6%