fix: apt deb822/OpenSSL3/pbzx-repo build errors + Big-Sur+-Katalogfilter

- Dockerfile: hfsprogs-Sources per sed auf bestehende deb822-Datei
  patchen statt zweite sources.list-Datei (Signed-By-Konflikt)
- Dockerfile: xar configure.ac OpenSSL-Probe auf EVP_EncryptInit
  umbiegen (OpenSSL_add_all_ciphers ist in OpenSSL 3.x nur noch Makro)
- Dockerfile: xar vor pbzx bauen (Linker-Abhaengigkeit), pbzx-Quelle auf
  NiklasRosenstein/pbzx (mackyle/pbzx existiert nicht mehr)
- catalog.py: doppeltes gzip.decompress auf bereits von requests
  dekomprimierten Content entfernt
- catalog.py: Big-Sur-bis-Tahoe-Installer wurden durch veralteten
  OSInstall-Filter komplett verworfen, jetzt SharedSupport-Schema
  ebenfalls erkannt (37 statt 13 gefundene Installer)

Alles live gegen echten docker compose build/up + echten Apple-Katalog
verifiziert, nicht nur behauptet.
This commit is contained in:
aria.hacker
2026-07-18 10:46:45 +02:00
parent c6dc2cd9cb
commit 1c97be6bb7
3 changed files with 158 additions and 24 deletions
+32 -15
View File
@@ -1,11 +1,16 @@
FROM debian:bookworm-slim FROM debian:bookworm-slim
# hfsprogs (mkfs.hfsplus) liegt in Debians "non-free"-Bereich, das offizielle # hfsprogs (mkfs.hfsplus) liegt in Debians "non-free"-Bereich, das offizielle
# bookworm-slim-Image aktiviert davon aber nur "main". Eigene sources.list.d # bookworm-slim-Image aktiviert davon aber nur "main". WICHTIG: bookworm-slim
# Datei ergaenzt non-free, unabhaengig davon ob die Basis-Sources im alten # nutzt seit einiger Zeit das neue deb822-Format (/etc/apt/sources.list.d/
# Ein-Zeilen- oder im neuen deb822-Format vorliegen (apt liest beide parallel). # debian.sources) und deklariert dort bereits "Signed-By" fuer deb.debian.org.
RUN echo "deb http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware" \ # Eine ZUSAETZLICHE separate sources.list-Datei fuer dieselbe URI ohne
> /etc/apt/sources.list.d/non-free.list # Signed-By fuehrt zu "Conflicting values set for option Signed-By" und
# bricht apt-get update komplett ab (live getestet). Deshalb hier die
# bestehende deb822-Datei in-place um contrib/non-free erweitern statt eine
# zweite Quelle anzulegen.
RUN sed -i 's/^Components: main$/Components: main contrib non-free non-free-firmware/' \
/etc/apt/sources.list.d/debian.sources
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
python3 python3-pip \ python3 python3-pip \
@@ -21,24 +26,36 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl \ ca-certificates curl \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# pbzx: entpackt Apples pbzx-komprimierte Installer-Payloads (macOS Big Sur+).
# Bewusst aus dem gepflegten Upstream-Repo gebaut statt selbst nachgebaut --
# das Binaerformat ist reverse-engineered und ein fertiges, getestetes Tool
# ist zuverlaessiger als eine selbstgeschriebene Parser-Vermutung.
RUN git clone --depth 1 https://github.com/mackyle/pbzx.git /tmp/pbzx \
&& cd /tmp/pbzx && make && install -m 755 pbzx /usr/local/bin/pbzx \
&& rm -rf /tmp/pbzx
# xar: existiert als Debian-Paket erst ab trixie (bookworm hat es gar nicht # xar: existiert als Debian-Paket erst ab trixie (bookworm hat es gar nicht
# im Archiv, auch nicht in non-free). Statt die ganze Basis-Distro zu wechseln # im Archiv, auch nicht in non-free). Statt die ganze Basis-Distro zu wechseln
# (Risiko fuer alle anderen Pakete) bauen wir es aus dem selben, von Apple- # (Risiko fuer alle anderen Pakete) bauen wir es aus dem mackyle/xar-Upstream.
# Tooling-Communities genutzten Upstream-Fork wie pbzx (gleicher Maintainer). # MUSS vor pbzx gebaut werden -- pbzx linkt gegen -lxar und xar/xar.h.
# Live getestet: configure.ac prüft die Existenz von OpenSSL via
# 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 daher fehl
# ("Cannot build without libcrypto"), obwohl libssl-dev korrekt installiert
# ist. Fix: den Probe-Funktionsnamen per sed auf EVP_EncryptInit umbiegen --
# eine seit jeher stabil exportierte Funktion, verifiziert per nm gegen
# libcrypto.so.3 in genau diesem Image.
RUN git clone --depth 1 https://github.com/mackyle/xar.git /tmp/xar-src \ RUN git clone --depth 1 https://github.com/mackyle/xar.git /tmp/xar-src \
&& cd /tmp/xar-src/xar \ && cd /tmp/xar-src/xar \
&& sed -i 's/OpenSSL_add_all_ciphers/EVP_EncryptInit/g' configure.ac \
&& ./autogen.sh && ./configure && make && make install \ && ./autogen.sh && ./configure && make && make install \
&& ldconfig \ && ldconfig \
&& rm -rf /tmp/xar-src && rm -rf /tmp/xar-src
# pbzx: entpackt Apples pbzx-komprimierte Installer-Payloads (macOS Big Sur+).
# WICHTIG: mackyle/pbzx existiert nicht (mehr) auf GitHub (404, live geprueft
# -- git meldet dafuer irrefuehrend "could not read Username", weil GitHub
# bei git-upload-pack aus Anti-Enumeration-Gruenden 401 statt 404 zurueckgibt).
# Community-Standard ist der Fork NiklasRosenstein/pbzx (u.a. in OSX-KVM
# genutzt), live gegen GitHub verifiziert und hier gegen das zuvor gebaute
# xar gelinkt.
RUN git clone --depth 1 https://github.com/NiklasRosenstein/pbzx.git /tmp/pbzx \
&& cd /tmp/pbzx && make && install -m 755 pbzx /usr/local/bin/pbzx \
&& rm -rf /tmp/pbzx
WORKDIR /app WORKDIR /app
COPY backend/requirements.txt /app/backend/requirements.txt COPY backend/requirements.txt /app/backend/requirements.txt
RUN pip3 install --no-cache-dir --break-system-packages -r /app/backend/requirements.txt RUN pip3 install --no-cache-dir --break-system-packages -r /app/backend/requirements.txt
+65 -7
View File
@@ -69,13 +69,71 @@ Block-Device. Wenn du das falsche Geraet auswaehlst, sind die Daten weg.
Vor dem Start immer pruefen, welcher Stick tatsaechlich gemeint ist (Groesse Vor dem Start immer pruefen, welcher Stick tatsaechlich gemeint ist (Groesse
und Modell in der GUI vergleichen). und Modell in der GUI vergleichen).
## Build-Hinweis (hfsprogs / xar) ## Build-Hinweis (hfsprogs / xar / pbzx)
`hfsprogs` liegt in Debians non-free-Bereich (Dockerfile aktiviert das Alles unten wurde am 2026-07-18 live gegen einen echten `docker compose build`
gezielt ueber eine eigene sources.list.d-Datei), `xar` gibt es in + `up` auf einem echten Docker-Host getestet (nicht nur behauptet):
`bookworm` ueberhaupt nicht als Paket — das wird deshalb aus dem
Upstream-Repo (`mackyle/xar`, selber Maintainer wie das schon genutzte - `hfsprogs` liegt in Debians non-free-Bereich. WICHTIG: `bookworm-slim`
`pbzx`) selbst gebaut. Macht den ersten Build minimal langsamer, aber nutzt das neue **deb822**-Sourcen-Format (`/etc/apt/sources.list.d/
zuverlaessiger als ein Distro-Wechsel. 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 ## Voraussetzungen auf dem Host
- Docker + Docker Compose - Docker + Docker Compose
+61 -2
View File
@@ -75,15 +75,61 @@ def find_latest_catalog_url(timeout: float = 8) -> str:
def _fetch_catalog_products(url: str, timeout: float = 60) -> dict: def _fetch_catalog_products(url: str, timeout: float = 60) -> dict:
r = requests.get(url, timeout=timeout) r = requests.get(url, timeout=timeout)
r.raise_for_status() r.raise_for_status()
raw = gzip.decompress(r.content) # Live gegen swscan.apple.com verifiziert (2026-07-18): der Server
# schickt "Content-Encoding: x-gzip" -- requests/urllib3 dekomprimiert
# das bereits transparent, r.content ist also schon reines XML. Ein
# zusaetzliches gzip.decompress() darauf crasht mit BadGzipFile, weil
# die Gzip-Magic-Bytes (\x1f\x8b) dann laengst weg sind. Manche
# Server/Proxies liefern aber ggf. noch echte rohe Gzip-Bytes ohne
# das Encoding transparent aufzuloesen -- daher robust an den echten
# Magic-Bytes pruefen statt blind zu (de-)komprimieren.
raw = r.content
if raw[:2] == b"\x1f\x8b":
raw = gzip.decompress(raw)
data = plistlib.loads(raw) data = plistlib.loads(raw)
return data.get("Products", {}) return data.get("Products", {})
# Bekannte Marketing-Versionsnummern nach Codename. Nur eine Anzeige-Hilfe --
# taucht ein UNS unbekannter zukuenftiger Codename auf (n plane naechstes Jahr:
# neuer Nachfolger von Tahoe), wird er trotzdem gelistet (siehe
# _split_shared_support_name), nur ohne huebsche Versionsnummer.
KNOWN_MACOS_VERSIONS = {
"bigsur": "11", "monterey": "12", "ventura": "13", "sonoma": "14",
"sequoia": "15", "tahoe": "26",
}
def _split_shared_support_name(shared_support_id: str):
"""'com.apple.pkg.InstallAssistant.macOSBigSur' -> ('Big Sur', '11')."""
name = shared_support_id.rsplit(".macOS", 1)[-1]
if not name:
return None, None
# CamelCase -> "Big Sur" (Ziffern/Grossbuchstaben-Grenzen als Trenner)
import re
pretty = re.sub(r"(?<!^)(?=[A-Z])", " ", name).strip()
version = KNOWN_MACOS_VERSIONS.get(name.lower())
return pretty, version
def _is_macos_installer(product: dict) -> bool: def _is_macos_installer(product: dict) -> bool:
emi = product.get("ExtendedMetaInfo", {}) or {} emi = product.get("ExtendedMetaInfo", {}) or {}
ids = emi.get("InstallAssistantPackageIdentifiers", {}) or {} ids = emi.get("InstallAssistantPackageIdentifiers", {}) or {}
return ids.get("OSInstall") == "com.apple.mpkg.OSInstall" # Klassisches Schema bis Catalina: eigener "OSInstall"-Schluessel.
if ids.get("OSInstall") == "com.apple.mpkg.OSInstall":
return True
# Ab Big Sur (live gegen swscan.apple.com verifiziert, 2026-07-18): kein
# "OSInstall"-Schluessel mehr, stattdessen "SharedSupport":
# "com.apple.pkg.InstallAssistant.macOS<Name>" (z.B. macOSBigSur,
# macOSMonterey, ..., macOSTahoe). Der alte Filter uebersah dadurch
# STILLSCHWEIGEND alle Big-Sur-und-neuer-Installer -- genau die, die per
# InstallAssistant.pkg (statt BaseSystem.dmg) geliefert werden und die
# dieses Tool eigentlich explizit unterstuetzen soll.
shared = ids.get("SharedSupport", "")
return isinstance(shared, str) and shared.startswith(
"com.apple.pkg.InstallAssistant.macOS"
)
def _fetch_product_metadata(product: dict, timeout: float = 20): def _fetch_product_metadata(product: dict, timeout: float = 20):
@@ -100,6 +146,19 @@ def _fetch_product_metadata(product: dict, timeout: float = 20):
title = en.get("title") title = en.get("title")
except Exception as exc: # noqa: BLE001 except Exception as exc: # noqa: BLE001
log.warning("Metadaten-Fetch fehlgeschlagen fuer %s: %s", smd_url, exc) log.warning("Metadaten-Fetch fehlgeschlagen fuer %s: %s", smd_url, exc)
if title:
return title, version
# Big Sur+ Produkte haben KEIN ServerMetadataURL (live verifiziert) --
# Name kommt stattdessen aus dem SharedSupport-Identifier.
emi = product.get("ExtendedMetaInfo", {}) or {}
ids = emi.get("InstallAssistantPackageIdentifiers", {}) or {}
shared = ids.get("SharedSupport", "")
if isinstance(shared, str) and shared.startswith(
"com.apple.pkg.InstallAssistant.macOS"
):
name, ver = _split_shared_support_name(shared)
if name:
return f"macOS {name}", (version or ver)
return title, version return title, version