bk99.de entertain the web since 1997

MacBook Pro 16 Zoll (2019) mit T2-Chip: wiederbeleben und Debian 13 neben macOS

Zusammenfassung

Mein MacBook Pro 16 Zoll von 2019 blieb schwarz, bis ich den T2-Chip von einem Linux-Rechner aus mit idevicerestore neu aufgespielt habe. Danach liefen macOS auf 99 GB und Debian 13 mit verschlüsselter Platte, T2-Kernel, Lüftersteuerung, WLAN und Bluetooth nebeneinander. Dieser Beitrag beschreibt jeden Schritt mit allen Befehlen und Dateien, damit du ein solches Gerät auf denselben Stand bringen kannst.

Ein Geschenk mit schwarzem Bildschirm

Das MacBook Pro habe ich von Dirk bekommen, meinem damaligen Chef. Es ist das 16-Zoll-Modell von 2019 (Modellnummer A2141, intern MacBookPro16,1) mit Intel Core i7-9750H, 16 GB RAM, 512 GB SSD, einer AMD Radeon Pro 5300M und einem Apple-T2-Chip. Eines Tages wirkte es komplett tot: schwarzer Bildschirm, kein Startton, kein Apfel und keinerlei Reaktion, egal welche Taste ich drückte.

Die üblichen Schritte halfen nicht. Originalnetzteil dran, SMC zurückgesetzt (Mac aus, rechte Umschalttaste, linke Option-Taste und linke Control-Taste 7 Sekunden halten, dann zusätzlich die Ein-Taste weitere 7 Sekunden), keine Änderung. Also wollte ich das Gehäuse öffnen. Die Bodenplatte ist mit Pentalobe-Schrauben (P5) befestigt, dafür hatte ich keinen passenden Bit und musste erst ein Feinmechanik-Bitset (Affiliate-Link: Bei einem Kauf erhalte ich eine kleine Provision, der Preis ändert sich für dich nicht) bestellen. Dann habe ich den Akkustecker vom Logicboard gelöst, gewartet und wieder angesteckt. Wo das Gerät schon offen war, habe ich Lüfter und Kühlrippen gleich mit einem elektrischen Druckluft-Bläser (Affiliate-Link) von Staub befreit. Der Mac blieb trotzdem schwarz.

Was du mitnehmen kannst: Ein schwarzer Bildschirm bei einem T2-Mac heißt nicht, dass das Logicboard tot ist. Oft ist nur die Firmware des T2-Chips beschädigt, und die lässt sich neu einspielen, auch ohne zweiten Mac.

Tastenkombinationen auf einen Blick

Diese Tasten braucht man auf dem Weg. Sie gehören zur Firmware des Macs und funktionieren deshalb auch mit der eingebauten Tastatur, wenn noch kein Betriebssystem läuft. Die Option-Taste trägt das Zeichen ⌥, die Befehlstaste heißt auf englischen Tastaturen Command (⌘).

WasTastenWofür
SMC zurücksetzenMac aus. Rechte Umschalttaste + linke Option-Taste (⌥) + linke Control-Taste 7 s halten, dann zusätzlich die Ein-Taste weitere 7 s. Loslassen, einige Sekunden warten, normal einschalten.Strom- und Lüfterprobleme, Mac reagiert nicht
Ausschalten erzwingenEin-Taste etwa 10 s halten.vor jedem DFU-Versuch
DFU-Modus des T2Kabel im DFU-Port. Ein-Taste drücken und halten, sofort rechte Umschalttaste + linke Option-Taste + linke Control-Taste dazu, alle vier etwa 3 s halten. Variante: Ein-Taste 1 s, dann die drei dazu, alle zusammen etwa 8 s.Firmware des T2 neu einspielen; Bildschirm bleibt schwarz
Internet-WiederherstellungBeim Einschalten Option-Taste + Befehlstaste (⌘) + R halten, bis eine Weltkugel erscheint.macOS auf leerer SSD neu installieren
macOS-WiederherstellungBeim Einschalten Befehlstaste + R halten.Startsicherheitsdienstprogramm, Festplattendienstprogramm
StartauswahlBeim Einschalten Option-Taste halten.zwischen macOS, Debian („EFI Boot“) und USB-Stick wählen
GRUB-MenüEscape während der kurzen Wartezeit; im Eintrag e zum Bearbeiten, F10 zum Starten.Kernelparameter einmalig ändern, älteren Kernel wählen
StartbildschirmEscape während des Starts.alle Startmeldungen als Text sehen

Was der Mac über USB verrät

Der T2-Chip ist ein eigener kleiner Rechner im Mac. Er startet zuerst, prüft die Firmware und gibt erst dann den Intel-Prozessor frei. Sein Betriebssystem heißt bridgeOS. Ist es kaputt, bleibt der Mac schwarz. Hängt der Mac per USB-C an einem Linux-Rechner, zeigt lsusb, in welchem Zustand der T2 ist:

  • 05ac:1670 „Macintosh“, Klasse Billboard: Der USB-C-Controller hat Strom, der T2 meldet sich nicht. So sah es bei mir anfangs aus.
  • 05ac:1227: DFU-Modus. Ab hier lässt sich Firmware einspielen.
  • 05ac:1281: Recovery-Modus des T2.
  • 05ac:12ac: Restore-Modus, während die Firmware geschrieben wird.
  • 05ac:8600 „Apple T2 Controller“: normaler Betrieb.

Was du mitnehmen kannst: Billboard ist ein gutes Zeichen. Der Mac bekommt Strom und antwortet. Bleibt der Mac auch nach vielen DFU-Versuchen bei Billboard, liegt eher ein Hardwarefehler vor.

Den T2 in den DFU-Modus bringen

Apple beschreibt das Wiederbeleben eines T2-Macs nur mit einem zweiten Mac und dem Finder. Die Tastenfolge für den DFU-Modus ist aber dieselbe, und den Rest kann ein Linux-Rechner übernehmen. So hat es bei mir geklappt:

  1. USB-C-Kabel, das Daten überträgt, direkt zwischen Linux-Rechner und Mac. Am Mac muss es im DFU-Port stecken: Apple nennt ihn in der Anleitung zum DFU-Port je Modell. Beim 16-Zoll-Modell 2019 ist es auf der linken Seite der Port, der von der Seite gesehen rechts liegt. Das Netzteil kommt rechts an den Mac.
  2. Mac ganz ausschalten: Ein-Taste etwa 10 Sekunden halten, dann 5 Sekunden warten.
  3. Ein-Taste drücken und halten, sofort rechte Umschalttaste, linke Option-Taste und linke Control-Taste dazunehmen. Alle vier Tasten etwa 3 Sekunden halten, dann loslassen. Klappt das nicht: zuerst nur die Ein-Taste 1 Sekunde, dann die anderen drei dazu und alle zusammen etwa 8 Sekunden halten.
  4. Der Bildschirm bleibt dabei schwarz. Ob es geklappt hat, zeigt nur lsusb mit 05ac:1227.

Bei mir brauchte es mehrere Versuche. Ein USB-Hub zwischen Mac und Linux-Rechner störte am Ende übrigens nicht, der DFU-Modus kam trotzdem an.

bridgeOS neu einspielen mit idevicerestore

Unter Linux übernimmt idevicerestore aus dem libimobiledevice-Projekt die Rolle des Finders. Es erkennt den T2 als iBridge2,14 (j152fap), lädt die passende bridgeOS-Version von Apple, lässt sie von Apples Server für genau diesen Chip signieren und spielt sie ein. Mit -l nimmt es die neueste Version, ohne -e ist es ein Revive, der die Daten auf der SSD lässt, mit -e ein vollständiger Restore, der alles löscht.

Der erste Versuch mit dem Paket aus Debian 13 scheiterte: „Unknown command received from ASR“. Der Debian-Stand ist von Juli 2024 und kennt das neuere ASR-Kommando „Initiate“ nicht, das bridgeOS 11 benutzt. Ein aktueller Stand aus dem Git-Repository behebt das. So baust du ihn auf einem Debian-13-Rechner. Ich habe dieselben Schritte in einem Container ausgeführt und die fertigen Programme dann auf den Rechner kopiert:

sudo apt install build-essential autoconf automake libtool-bin pkg-config git \
  libssl-dev libcurl4-openssl-dev libzip-dev zlib1g-dev libusb-1.0-0-dev libreadline-dev python3 usbmuxd
mkdir -p ~/src && cd ~/src
for r in libplist libimobiledevice-glue libusbmuxd libtatsu libimobiledevice libirecovery idevicerestore; do
  git clone --depth 1 https://github.com/libimobiledevice/$r.git
  (cd $r && ./autogen.sh --prefix=/opt/idevicerestore --without-cython && make -j"$(nproc)" && sudo make install)
done

Gestartet wird es direkt auf dem Rechner, an dem der Mac hängt, mit den eigenen Bibliotheken:

cd /var/tmp
sudo LD_LIBRARY_PATH=/opt/idevicerestore/lib /opt/idevicerestore/bin/idevicerestore -l -y      # Revive, Daten bleiben
sudo LD_LIBRARY_PATH=/opt/idevicerestore/lib /opt/idevicerestore/bin/idevicerestore -l -e -y   # Restore, SSD wird gelöscht

Zwei Stolperfallen habe ich unterwegs mitgenommen. Erstens nicht im Docker-Container starten: Der T2 wechselt während des Vorgangs mehrmals den USB-Modus, und der usbmuxd des Rechners legt dabei seinen Socket neu an. Der Container hängt dann am alten Socket und wartet ewig auf „restore mode“. Die Meldung „most likely image personalization failed“ führt dabei in die Irre.

Zweitens reichte der Revive nicht. Er schrieb die Firmware, brach aber bei 96 % mit „failed to mount data partition after invert“ ab: Die Datenpartition des T2 war defekt. Erst der Restore mit -e lief durch, mit „Restore Finished“. Danach meldete sich der T2 als 05ac:8600, und der Mac zeigte beim Einschalten den Ordner mit Fragezeichen. Das ist hier eine gute Nachricht: Die Firmware läuft und sucht ein Startvolume, die SSD ist nur leer.

Was du mitnehmen kannst: Erst den Revive versuchen, er lässt die Daten in Ruhe. Bricht er mit der Datenpartition ab, hilft nur der Restore. Wichtige Daten auf der SSD sind dann verloren, ohne funktionierenden T2 kommt man aber ohnehin nicht an sie heran.

macOS installieren und den Start von Linux erlauben

macOS kommt über die Internet-Wiederherstellung zurück: einschalten und sofort Option-Taste, Befehlstaste und R halten, bis eine Weltkugel erscheint. WLAN wählen, im Festplattendienstprogramm die SSD als APFS formatieren und macOS installieren.

Dabei einen lokalen Benutzer mit Administratorrechten anlegen, das ist wichtig. Den Start von fremden Systemen erlaubt nur das Startsicherheitsdienstprogramm, und das verlangt die Anmeldung eines Administrators aus dem installierten macOS. Bei einem ersten Anlauf hatte ich macOS schon gelöscht, und es kam nur „Kein Administrator gefunden“.

Danach mit Befehlstaste und R in die Wiederherstellung starten, unter Dienstprogramme das Startsicherheitsdienstprogramm öffnen und zwei Dinge umstellen:

  • Sicherer Start: „Keine Sicherheit“
  • Erlaubte Startmedien: „Starten von externen Medien erlauben“

Behalte macOS danach, auch wenn du es kaum nutzt. Firmware-Updates für T2 und EFI kommen nur über macOS-Updates, und ohne macOS kommst du an die Startsicherheit nicht mehr heran.

Platz schaffen: macOS auf 99 GB

macOS belegte frisch installiert rund 25 GB. Ich habe ihm 99 GB gelassen, damit Updates genug Luft haben, und den Rest als Platzhalter für Debian angelegt. Das geht im laufenden macOS im Terminal, auch mit eingeschaltetem FileVault. Die Kennung des APFS-Containers zeigt diskutil list, bei mir war es disk0s2:

diskutil list
diskutil apfs resizeContainer disk0s2 limits          # Mindest- und empfohlene Größe
sudo diskutil apfs resizeContainer disk0s2 99g FAT32 DEBIAN 0

Das dauerte zehn Sekunden, macOS prüft vorher das Dateisystem. Danach gab es drei Partitionen: die EFI-Partition (314,6 MB), macOS (99 GB) und „DEBIAN“ (401 GB, FAT32). Unter Linux heißen sie nvme0n1p1 bis nvme0n1p3.

WLAN-Firmware aus macOS retten

Der WLAN- und Bluetooth-Chip ist ein Broadcom BCM4364. Seine Firmware liegt nicht in Debian, Apple liefert sie nur mit macOS aus, unter /usr/share/firmware. Diese Dateien musst du sichern, solange macOS läuft. Ohne sie gibt es unter Linux kein WLAN.

# in macOS
tar -cf ~/macos-firmware-raw.tar -C /usr/share firmware

Unter Linux brauchen die Dateien andere Namen, zum Beispiel brcmfmac4364b3-pcie.apple,bali-HRPN-u-7.7-X3.txcap_blob. Das erledigt das Skript firmware.sh aus dem t2linux-Wiki mit dem Unterbefehl rename_only auf einem beliebigen Linux-Rechner mit Python 3. Ich habe genau diesen Python-Teil des Skripts direkt ausgeführt, das Ergebnis ist dasselbe:

mkdir roh && tar -xf macos-firmware-raw.tar -C roh
bash firmware.sh rename_only roh/firmware firmware.tar
mkdir linux && tar -xf firmware.tar -C linux     # 148 Dateien mit Linux-Namen

Die 148 Dateien kommen auf einen USB-Stick. Der Debian-Installer fragt zwar nach fehlender Firmware und soll sie in einem Ordner firmware/ auf einem Stick mit FAT32 finden. Bei mir hat das nicht funktioniert, weder vom Ventoy-Stick (dessen Datenpartition ist exFAT, die liest der Installer nicht) noch von einem zweiten Stick mit FAT32. Der verlässliche Weg: Debian ohne WLAN fertig installieren und die Dateien nach dem ersten Start von Hand an die richtige Stelle kopieren. Mit eingestecktem Stick, zum Beispiel unter /media/$USER/STICK:

sudo cp /media/$USER/STICK/firmware/* /lib/firmware/brcm/
sudo modprobe -r brcmfmac_wcc brcmfmac && sudo modprobe brcmfmac
sudo journalctl -k --grep=brcmfmac     # zeigt, welche Firmware geladen wurde

Danach taucht das WLAN als wlp5s0 auf und lässt sich wie gewohnt über NetworkManager verbinden. Die Dateien bleiben bei Kernel-Updates erhalten.

Debian 13 installieren

Zum Installieren brauchst du eine USB-Tastatur und eine USB-Maus. Die eingebaute Tastatur und das Trackpad hängen am T2 und funktionieren erst mit dem passenden Kernel. Das DVD-Image installiert auch ohne Netz, WLAN gibt es erst, wenn die Firmware nach der Installation kopiert ist.

  1. Mac ganz ausschalten, Installationsstick einstecken.
  2. Einschalten und die Option-Taste halten, bis die Startauswahl erscheint. Den orangen Eintrag „EFI Boot“ wählen.
  3. Die Frage nach fehlender Firmware und die Netzwerkeinrichtung überspringen.
  4. Partitionieren nur manuell oder in den freien Bereich. Die Partition „DEBIAN“ löschen und den Platz für Debian nutzen. Die APFS-Partition von macOS auf keinen Fall anfassen.

Bei mir sieht das Ergebnis so aus: eine eigene EFI-Partition für Debian (953 MB), /boot mit ext4 und eine mit LUKS verschlüsselte Partition mit btrfs für /. Die EFI-Partition von macOS blieb unberührt. Beim Einschalten mit gehaltener Option-Taste stehen danach macOS und „EFI Boot“ (Debian) zur Wahl, ohne Taste startet GRUB.

Laut t2linux-Wiki bricht die GRUB-Installation mit älteren Installationsimages manchmal ab. Dann im Startmenü des Sticks e drücken, efi=noruntime an die Zeile mit linux anhängen und mit F10 starten. Bei mir war das mit dem Image 13.0 nicht nötig.

Paketquellen und T2-Kernel

Nach der Installation von DVD standen bei mir keine Paketquellen in /etc/apt/sources.list, nur der auskommentierte DVD-Eintrag. Also zuerst die Quellen eintragen, dann aktualisieren:

deb http://deb.debian.org/debian trixie main contrib non-free non-free-firmware
deb http://deb.debian.org/debian trixie-updates main contrib non-free non-free-firmware
deb http://security.debian.org/debian-security trixie-security main contrib non-free non-free-firmware
sudo apt update && sudo apt full-upgrade

Den Kernel für T2-Macs gibt es im t2-ubuntu-repo von Aditya Garg, auch für Debian 13 (Codename trixie). Er bringt die Treiber für Tastatur, Trackpad, Touch Bar, Ton und den SMC mit:

sudo apt install -y curl
curl -s --compressed "https://adityagarg8.github.io/t2-ubuntu-repo/KEY.gpg" | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/t2-ubuntu-repo.gpg >/dev/null
sudo curl -s --compressed -o /etc/apt/sources.list.d/t2.list "https://adityagarg8.github.io/t2-ubuntu-repo/t2.list"
CODENAME=trixie
echo "deb [signed-by=/etc/apt/trusted.gpg.d/t2-ubuntu-repo.gpg] https://github.com/AdityaGarg8/t2-ubuntu-repo/releases/download/${CODENAME} ./" | sudo tee -a /etc/apt/sources.list.d/t2.list
sudo apt update
sudo apt install linux-t2 apple-t2-audio-config t2fanrd tiny-dfr t2-touchid fprintd libpam-fprintd

fprintd und libpam-fprintd stehen dabei, weil das Paket t2-touchid sonst bei der Einrichtung abbricht: Sein Skript startet fprintd neu, verlangt das Paket aber nicht. Pakete wie apple-bce oder applesmc-t2 nicht zusätzlich installieren, der Kernel enthält die Treiber schon.

Ein Wort zu vertrauenswürdiger Software: Der T2-Kernel kommt nicht von Debian, sondern aus einem Gemeinschaftsprojekt, und läuft mit vollen Rechten. Prüf deshalb vor dem Eintragen den Schlüssel: gpg --show-keys /etc/apt/trusted.gpg.d/t2-ubuntu-repo.gpg muss den Fingerabdruck 9F98 73A5 66A7 3E27 CFF0 294F E2E4 9611 4ACD BFD4 von Aditya Garg zeigen. Dasselbe gilt für Skripte aus dem Netz wie firmware.sh: erst lesen, dann ausführen, und nur von der Originalquelle laden. Bei bridgeOS ist das anders: Die Firmware kommt von Apple, ist für genau diesen Chip signiert und wird vom T2 selbst geprüft.

Dann die Kernelparameter aus dem t2linux-Wiki in /etc/default/grub eintragen. splash brauchst du nur für den Startbildschirm weiter unten:

GRUB_TIMEOUT=1
GRUB_CMDLINE_LINUX_DEFAULT="splash"
GRUB_CMDLINE_LINUX="intel_iommu=on iommu=pt pm_async=off"

Bei verschlüsselter Platte gehört noch etwas dazu, das leicht übersehen wird: Die eingebaute Tastatur hängt am T2. Damit sie bei der Passwortabfrage beim Start schon funktioniert, müssen die T2-Treiber ins initramfs. Ich habe dort gleich auch die Grafiktreiber eingetragen, warum, steht im Abschnitt zur Bootzeit. Meine /etc/initramfs-tools/modules:

t2bce_dma
t2bce_core
t2bce_vhci
usbhid
hid_apple
hid_generic
amdgpu
i915
apple_gmux
echo t2bce_vhci | sudo tee /etc/modules-load.d/t2.conf
sudo update-initramfs -u -k all
sudo update-grub

Nach dem Neustart mit Kernel 7.3.1-t2 liefen bei mir eingebaute Tastatur, Trackpad, Ton, WLAN und Bluetooth. Das WLAN funktionierte übrigens schon mit dem normalen Debian-Kernel, sobald die Firmware kopiert war.

Lüfter und Touch Bar

Mit dem Debian-Kernel wurde das Gerät warm, ohne dass die Lüfter hörbar hochdrehten. Der Prozessor lag bei über 60 °C, und Linux fehlte der Treiber für den SMC. Der T2-Kernel bringt macsmc_hwmon mit, damit sieht Linux 43 Temperaturen, beide Lüfter und viele Leistungswerte. t2fanrd regelt die Lüfter dann selbst. Seine Kurve steht in /etc/t2fand.conf, die Standardwerte (ab 55 °C hoch, bei 75 °C voll) passten für mich.

Die Touch Bar ist ein eigenes kleines Display am T2. tiny-dfr soll sie unter Linux zeichnen, dafür schaltet eine udev-Regel sie beim Start in einen anderen USB-Modus. Bei mir scheiterte danach der Anzeigetreiber appletbdrm mit Zeitüberschreitung (-110), auch nach Kaltstart, und die Touch Bar blieb dunkel. Lässt man den Modus, wie er ist, zeigt der T2 selbst Escape, Funktions- und Medientasten. Dafür reicht es, die Umschaltzeile in der Regel auszukommentieren:

sudo cp /etc/udev/rules.d/99-touchbar-tiny-dfr.rules /root/
sudo sed -i 's|^ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="05ac", ATTR{idProduct}=="8302"|# &|' /etc/udev/rules.d/99-touchbar-tiny-dfr.rules

Bootzeit und Akku

Gemessen habe ich mit systemd-analyze und systemd-analyze blame. Vorher stand die Oberfläche nach etwa 38 Sekunden ab dem Einschalten. Danach sind es rund 31 Sekunden einschließlich der Passworteingabe, und nach dem Entsperren braucht das System nur noch gut 3 Sekunden. Diese Schritte haben geholfen:

  • GRUB wartet 1 statt 5 Sekunden. Die Startauswahl von Apple mit der Option-Taste gibt es zusätzlich.
  • sudo systemctl disable NetworkManager-wait-online.service spart gut 5 Sekunden, auf einem Notebook braucht nichts beim Start das Netz.
  • initramfs mit MODULES=dep in /etc/initramfs-tools/initramfs.conf: 46 statt 95 MB, die die Apple-Firmware beim Start langsam von der SSD liest.
  • Ein langsamer erster Start nach dem Kernelwechsel ist normal: AppArmor übersetzt dann seine Profile neu und brauchte bei mir einmalig 9 Sekunden.

Bei MODULES=dep bin ich in eine Falle gelaufen. Das kleinere initramfs enthielt den AMD-Grafiktreiber nicht mehr, er wurde erst später geladen. Xorg startete dadurch eine halbe Sekunde zu früh, fand keine Grafikkarte, und die Anmeldung blieb aus. Deshalb stehen amdgpu, i915 und apple_gmux oben ausdrücklich in der Modulliste.

Für den Akku habe ich TLP installiert (sudo apt install --no-install-recommends tlp). Am Netz läuft der Rechner mit voller Leistung, auf Akku sparsam und ohne Turbo. Wichtig auf dem T2-Mac: kein USB-Autosuspend, denn Tastatur, Trackpad und Touch Bar hängen per USB am T2. Und der T2 selbst bleibt vom Stromsparen der PCI-Geräte ausgenommen. Meine /etc/tlp.d/90-macbookpro16-1.conf:

# MacBook Pro 16,1 (T2)
# Am Netz volle Leistung, auf Akku Stromsparen.
TLP_DEFAULT_MODE=AC
CPU_ENERGY_PERF_POLICY_ON_AC=balance_performance
CPU_ENERGY_PERF_POLICY_ON_BAT=balance_power
CPU_BOOST_ON_AC=1
CPU_BOOST_ON_BAT=0
PCIE_ASPM_ON_AC=default
PCIE_ASPM_ON_BAT=powersupersave
WIFI_PWR_ON_AC=off
WIFI_PWR_ON_BAT=on
# T2: interne Tastatur/Trackpad/Touch Bar hängen an USB über den T2 - kein USB-Autosuspend
USB_AUTOSUSPEND=0
# T2-Bridge, Secure Enclave, T2-Audio und Apple-NVMe (ANS2) vom Runtime-PM ausnehmen
RUNTIME_PM_DENYLIST="04:00.0 04:00.1 04:00.2 04:00.3"
# Ladegrenze setzt eine udev-Regel (macsmc), nicht TLP

Weitere Einstellungen für Akku und Speicher:

  • Ladegrenze 85 %, damit der Akku am Netzteil nicht dauernd voll steht. Der T2-Kernel bietet dafür charge_control_end_threshold an, eine udev-Regel setzt es bei jedem Start: /etc/udev/rules.d/90-akku-ladegrenze.rules mit ACTION=="add", SUBSYSTEM=="power_supply", KERNEL=="macsmc-battery", ATTR{charge_control_end_threshold}="85".
  • btrfs mit noatime,compress=zstd:1 in der Zeile für / in /etc/fstab schreibt weniger und spart Platz.
  • zram als komprimierter Swap im RAM: sudo apt install systemd-zram-generator, dann in /etc/systemd/zram-generator.conf unter [zram0] die Zeilen zram-size = min(ram / 4, 4096), compression-algorithm = zstd und swap-priority = 100.
  • ModemManager abschalten, der Mac hat kein Modem. Drucken über cups und die Druckersuche über avahi lasse ich an.

Größter Stromfresser bleibt die AMD-Grafik. Sie steuert den Bildschirm an und zieht im Leerlauf allein etwa 17 W, mit einem YouTube-Video rund 20 W, bei etwa 33 W für das ganze Gerät im Leerlauf. Das t2linux-Wiki beschreibt, wie man stattdessen die Intel-Grafik nutzt und die AMD-Karte abschaltet. Das habe ich noch nicht ausprobiert.

Flüssig scrollen mit zwei Fingern

Scrollen in Firefox mit zwei Fingern auf dem Trackpad ruckelte deutlich. Zwei Dinge haben das behoben. Erstens TearFree für den AMD-Treiber in /etc/X11/xorg.conf.d/20-amdgpu.conf:

Section "Device"
    Identifier "AMD"
    Driver "amdgpu"
    Option "TearFree" "true"
EndSection

Zweitens, und das war der größere Teil: Firefox nutzt unter X11 von sich aus nicht die feine Scroll-Schnittstelle XInput2 und bekommt vom Trackpad dann nur grobe Mausradschritte. Eine Zeile in /etc/X11/Xsession.d/80firefox-xinput2 ändert das nach der nächsten Anmeldung:

export MOZ_USE_XINPUT2=1

Snapshots mit Snapper

Mit btrfs als Wurzeldateisystem lohnen sich Snapshots. snapper legt stündlich einen an und vor und nach jedem apt-Lauf. Geht ein Update schief, lässt sich der Stand davor zurückholen:

sudo apt install snapper
sudo snapper -c root create-config /
sudo snapper -c root set-config TIMELINE_LIMIT_HOURLY=6 TIMELINE_LIMIT_DAILY=7 TIMELINE_LIMIT_WEEKLY=4 \
  TIMELINE_LIMIT_MONTHLY=3 TIMELINE_LIMIT_YEARLY=0 NUMBER_LIMIT=20 NUMBER_LIMIT_IMPORTANT=10 ALLOW_GROUPS=sudo
sudo snapper -c root create -d "Ausgangsstand" --userdata important=yes

Zwei Grenzen solltest du kennen. /boot liegt auf ext4 und ist nicht in den Snapshots. Und /home liegt bei der Standardinstallation im selben Subvolume wie das System, ein Rollback setzt also auch deine Dateien zurück. Wer das nicht will, legt /home in ein eigenes Subvolume.

Conky mit allen Sensoren

Auf dem Schreibtisch zeigt Conky alles, was der SMC liefert: CPU-Last pro Thread, 15 ausgewählte Temperaturen, beide Lüfter mit Ist- und Solldrehzahl, die Leistung von System, CPU, Grafik und Heatpipe, Akkuzustand, AMD-Grafik, Speicher, Snapshots, Prozesse und Netz. Die Sensoren findet ein kleines Lua-Skript über Name und Bezeichnung, nicht über die hwmon-Nummer. Die ändert sich sonst gern nach einem Kernel-Update.

Conky-Anzeige mit CPU-Last, Temperaturen, Lüfterdrehzahlen, Leistung, Akku, Grafik, Speicher, Prozessen und Netz auf dunklem Hintergrund
Conky am rechten Bildschirmrand. Die IP-Adresse im Abschnitt Netz habe ich unkenntlich gemacht.

Pakete: sudo apt install conky-all lm-sensors. Der Bildschirm hat 3072 × 1920 Pixel und läuft mit doppelter Skalierung. Conky rechnet Schrift und Abstände dann selbst hoch, alle Werte in der Konfiguration sind logische Größen. Die Konfiguration ~/.config/conky/conky.conf:

-- Conky für MacBook Pro 16,1 „MBP“ (Debian 13, T2-Kernel), angelegt 03.11.2026
-- Hinweis: Xft.dpi 192 (HiDPI ×2) - conky skaliert Schrift, Breiten und goto selbst, Angaben hier sind logisch
conky.config = {
    update_interval = 2,
    cpu_avg_samples = 2,
    net_avg_samples = 2,
    double_buffer = true,

    alignment = 'top_right',
    gap_x = 15,
    gap_y = 30,

    own_window = true,
    own_window_type = 'normal',
    own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager',
    own_window_argb_visual = true,
    own_window_argb_value = 0,

    font = 'DejaVu Sans Mono:size=9',
    default_color = 'white',
    color1 = '44aaff',
    color2 = 'ffaa00',
    color3 = '88ff88',
    color6 = '999999',

    minimum_width = 320,
    maximum_width = 320,
    draw_shades = false,
    draw_borders = false,
    draw_graph_borders = false,
    use_xft = true,
    default_bar_height = 5,

    lua_load = '~/.config/conky/conky.lua',
};

conky.text = [[
${color1}━━━  SYSTEM  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━${color}
${color3}${nodename}${color}  ${kernel}${alignr}up ${color3}${uptime_short}${color}
Load ${color3}${loadavg 1}  ${loadavg 2}  ${loadavg 3}${color}${alignr}${color3}${processes}${color} Proz.

${color1}━━━  CPU - i7-9750H (6C/12T)  ━━━━━━━━━━━${color}
${cpugraph cpu0 28,320 1166ff ff6600}
Gesamt ${color3}${cpu cpu0}%${color}  Takt ${color3}${freq_g}GHz${color}${alignr}Paket ${lua_parse w pkg}°C
${color1}1${color}${goto 26}${color3}${cpu cpu1}%${color}${goto 70}${color1}2${color}${goto 96}${color3}${cpu cpu2}%${color}${goto 140}${color1}3${color}${goto 166}${color3}${cpu cpu3}%${color}${goto 210}${color1}4${color}${goto 236}${color3}${cpu cpu4}%${color}${goto 270}${color1}5${color}${goto 296}${color3}${cpu cpu5}%${color}
${color1}6${color}${goto 26}${color3}${cpu cpu6}%${color}${goto 70}${color1}7${color}${goto 96}${color3}${cpu cpu7}%${color}${goto 140}${color1}8${color}${goto 166}${color3}${cpu cpu8}%${color}${goto 210}${color1}9${color}${goto 236}${color3}${cpu cpu9}%${color}${goto 270}${color1}10${color}${goto 296}${color3}${cpu cpu10}%${color}
${color1}11${color}${goto 26}${color3}${cpu cpu11}%${color}${goto 70}${color1}12${color}${goto 96}${color3}${cpu cpu12}%${color}

${color1}━━━  TEMPERATUR °C  ━━━━━━━━━━━━━━━━━━━━━${color}
CPU   ${lua_parse w pkg}${goto 110}PECI ${lua_parse w peci}${goto 220}PCH  ${lua_parse w pch}
dGPU  ${lua_parse w gpu}${goto 110}Junc ${lua_parse w gpuj}${goto 220}VR   ${lua_parse w gpuvr}
RAM   ${lua_parse w mem}${goto 110}NVMe ${lua_parse w nvme}${goto 220}WLAN ${lua_parse w wlan}
Kühl L ${lua_parse w finl}${goto 110}KühlR ${lua_parse w finr}${goto 220}Akku ${lua_parse w akkut}
Oben  ${lua_parse w oben}${goto 110}Unten ${lua_parse w unten}${goto 220}Handb ${lua_parse w palm}

${color1}━━━  LÜFTER  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━${color}
L ${lua_parse fan 0}
${lua_bar 5,320 fanproz 0}
R ${lua_parse fan 1}
${lua_bar 5,320 fanproz 1}

${color1}━━━  LEISTUNG W  ━━━━━━━━━━━━━━━━━━━━━━━━${color}
System ${lua_parse w p_sys}${goto 120}CPU ${lua_parse w p_cpu}${goto 220}dGPU ${lua_parse w p_gpu}
Heatpipe ${lua_parse w p_heat}/82${goto 220}Netz ${lua_parse w p_ac}
${lua_graph p_sys 24,320 1166ff ff6600 120}

${color1}━━━  AKKU  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${color}
${color3}${lua akku kap} %${color}  ${lua akku status}${alignr}${lua_parse w p_akku} W
${battery_bar 5,320 macsmc-battery}
Zustand ${color3}${lua akku health}${color}  ${color3}${lua akku voll}${color}/100 Wh  Zyklen ${color3}${lua akku zyklen}${color}

${color1}━━━  GRAFIK - Radeon Pro 5300M  ━━━━━━━━━━${color}
dGPU ${color3}${lua dgpu busy} %${color}  VRAM ${color3}${lua dgpu vram}${color}${alignr}${color6}${lua dgpu status}${color}
${lua_graph dgpu_busy 20,320 1166ff ff6600 100}

${color1}━━━  RAM / DISK  ━━━━━━━━━━━━━━━━━━━━━━━━${color}
RAM ${color3}${mem}${color} / ${memmax} (${lua_parse p ram}%)${alignr}Swap ${color3}${swap}${color}
${membar 5,320}
/   ${color3}${fs_used /}${color} / ${fs_size /} (${lua_parse p root}%)${alignr}btrfs+LUKS
${fs_bar 5,320 /}
/boot ${lua_parse p boot}% efi ${lua_parse p efi}% IO ${color3}${diskio /dev/nvme0n1}${color}${alignr}Snapshots ${color3}${execi 300 snapper -c root --csvout list --columns number 2>/dev/null | tail -n +3 | wc -l}${color}

${color1}━━━  PROZESSE  ━━━━━━━━━━━━━━━━━━━━━━━━━━${color}
${color2}CPU${color}${goto 170}${color2}CPU %${color}${goto 250}${color2}RAM${color}
${top name 1}${goto 170}${color3}${top cpu 1}${color}${goto 250}${top mem_res 1}
${top name 2}${goto 170}${color3}${top cpu 2}${color}${goto 250}${top mem_res 2}
${top name 3}${goto 170}${color3}${top cpu 3}${color}${goto 250}${top mem_res 3}
${color2}RAM${color} ${top_mem name 1}${goto 170}${color3}${top_mem mem_res 1}${color}${goto 250}${top_mem mem 1}%

${color1}━━━  NETZ  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${color}
${if_up wlp5s0}WLAN ${color3}${addr wlp5s0}${color}${alignr}${color3}${wireless_link_qual_perc wlp5s0}%${color} ${wireless_bitrate wlp5s0}
↓ ${color3}${downspeed wlp5s0}${color}${goto 120}↑ ${color3}${upspeed wlp5s0}${color}${alignr}BT ${color3}${execi 30 bluetoothctl show 2>/dev/null | awk '/Powered:/{print ($2=="yes")?"an":"aus"}'}${color}
${downspeedgraph wlp5s0 18,155 1166ff 44aaff}${goto 165}${upspeedgraph wlp5s0 18,155 ff6600 ffaa00}${else}WLAN ${color ff4040}getrennt${color}${alignr}BT ${color3}${execi 30 bluetoothctl show 2>/dev/null | awk '/Powered:/{print ($2=="yes")?"an":"aus"}'}${color}${endif}
]];

Das Lua-Skript ~/.config/conky/conky.lua:

-- Hilfsfunktionen für conky.conf (MacBook Pro 16,1 „MBP“, T2-Kernel)
-- Sensoren werden über hwmon-Namen und -Bezeichnungen gefunden, nicht über hwmon-Nummern,
-- damit die Anzeige nach Kernel-Updates oder anderer Ladereihenfolge weiter stimmt.
-- Farben: Werte ab Warn-/Kritisch-Schwelle gelb/rot.

local GELB, ROT, NORMAL = '${color ffd000}', '${color ff4040}', '${color3}'

local function lesen(pfad)
    local f = io.open(pfad, 'r')
    if not f then return nil end
    local s = f:read('*l')
    f:close()
    return s
end

-- Pfadcache: "name|label" -> Datei *_input
local cache = {}

local function hwmon_pfad(name, label)
    local key = name .. '|' .. label
    if cache[key] ~= nil then return cache[key] or nil end
    local p = io.popen('ls -d /sys/class/hwmon/hwmon* 2>/dev/null')
    local gefunden = false
    for h in p:lines() do
        if lesen(h .. '/name') == name then
            if label:match('^[a-z]+%d+$') then
                -- direkte Kanalangabe, z. B. temp1
                local f = h .. '/' .. label .. '_input'
                if lesen(f) then gefunden = f end
            else
                local q = io.popen('ls ' .. h .. '/*_label 2>/dev/null')
                for l in q:lines() do
                    if lesen(l) == label then
                        gefunden = l:gsub('_label$', '_input')
                        break
                    end
                end
                q:close()
            end
        end
        if gefunden then break end
    end
    p:close()
    cache[key] = gefunden
    return gefunden or nil
end

local function wert(name, label)
    local f = hwmon_pfad(name, label)
    if not f then return nil end
    local v = tonumber(lesen(f))
    if not v then cache[name .. '|' .. label] = nil end
    return v
end

-- Messgrößen: Kurzname -> { hwmon-Name, Label/Kanal, Teiler, Nachkommastellen, Warn, Kritisch }
local m = {
    -- Temperaturen in °C
    pkg    = { 'coretemp', 'Package id 0', 1000, 0, 85, 95 },
    peci   = { 'macsmc_hwmon', 'Max PECI Reported Temperature (TCMX)', 1000, 0, 85, 95 },
    gpu    = { 'amdgpu', 'edge', 1000, 0, 80, 90 },
    gpuj   = { 'amdgpu', 'junction', 1000, 0, 90, 100 },
    gpuvr  = { 'macsmc_hwmon', 'GPU VR Temperature (TGVP)', 1000, 0, 80, 90 },
    pch    = { 'macsmc_hwmon', 'PCH Die Cooked Temperature (TPCD)', 1000, 0, 75, 85 },
    mem    = { 'macsmc_hwmon', 'Memory Proximity Temperature (TM0P)', 1000, 0, 70, 80 },
    nvme   = { 'nvme', 'Composite', 1000, 0, 65, 75 },
    finl   = { 'macsmc_hwmon', 'Left Fin Stack Temperature (Th2H)', 1000, 0, 70, 80 },
    finr   = { 'macsmc_hwmon', 'Right Fin Stack Temperature (Th1H)', 1000, 0, 70, 80 },
    palm   = { 'macsmc_hwmon', 'Palm Rest Temperature (Ts0P)', 1000, 0, 38, 42 },
    unten  = { 'macsmc_hwmon', 'Synthetic Bottom Skin Temperature (Ts0S)', 1000, 0, 45, 50 },
    oben   = { 'macsmc_hwmon', 'Synthetic Top Skin Temperature (Ts1S)', 1000, 0, 45, 50 },
    akkut  = { 'macsmc_hwmon', 'Battery Temperature (TB0T)', 1000, 0, 40, 45 },
    wlan   = { 'macsmc_hwmon', 'Wireless Proximity Temperature (TW0P)', 1000, 0, 70, 80 },
    umgeb  = { 'macsmc_hwmon', 'Ambient Virtual Temperature (TA0V)', 1000, 0, 35, 40 },
    -- Lüfter in U/min
    f0     = { 'macsmc_hwmon', 'Fan 0', 1, 0 },
    f1     = { 'macsmc_hwmon', 'Fan 1', 1, 0 },
    -- Leistung in W (Sensoren liefern µW)
    p_sys  = { 'macsmc_hwmon', 'System Total, 1 s Delayed (PSTR)', 1e6, 1, 90, 120 },
    p_cpu  = { 'macsmc_hwmon', 'CPU High-Side Average (PC0R)', 1e6, 1, 60, 80 },
    p_gpu  = { 'macsmc_hwmon', 'GPU 0 Rail (PG0R)', 1e6, 1, 40, 60 },
    p_heat = { 'macsmc_hwmon', 'Heatpipe Average (PZ0G)', 1e6, 1, 75, 82 },
    p_ac   = { 'macsmc_ac', 'power1', 1e6, 1 },
    p_akku = { 'macsmc_battery', 'power1', 1e6, 1 },
}

local function zahl(k)
    local d = m[k]
    if not d then return nil end
    local v = wert(d[1], d[2])
    if not v then return nil end
    return v / d[3]
end

local function format(k, v)
    return string.format('%.' .. m[k][4] .. 'f', v)
end

-- Wert mit Warnfarbe
function conky_w(k)
    local v = zahl(k)
    if not v then return '${color6}-${color}' end
    local d = m[k]
    local farbe = NORMAL
    if d[6] and v >= d[6] then farbe = ROT elseif d[5] and v >= d[5] then farbe = GELB end
    return farbe .. format(k, v) .. '${color}'
end

-- reine Zahl (für Graphen/Balken)
function conky_n(k)
    return zahl(k) or 0
end

-- Lüfter: Ist / Soll in U/min und Anteil an der Höchstdrehzahl
local function luefter_dateien(nr)
    local f = hwmon_pfad('macsmc_hwmon', 'Fan ' .. nr)
    if not f then return nil end
    return f:gsub('_input$', '')
end

function conky_fan(nr)
    local b = luefter_dateien(nr)
    if not b then return '${color6}-${color}' end
    local ist = tonumber(lesen(b .. '_input')) or 0
    local soll = tonumber(lesen(b .. '_target')) or 0
    local min = tonumber(lesen(b .. '_min')) or 0
    local max = tonumber(lesen(b .. '_max')) or 1
    local proz = math.floor((ist - min) * 100 / math.max(1, max - min) + 0.5)
    if proz < 0 then proz = 0 end
    return string.format('%s%d${color} U/min  Soll %d  (%d %%)', NORMAL, ist, soll, proz)
end

function conky_fanproz(nr)
    local b = luefter_dateien(nr)
    if not b then return 0 end
    local ist = tonumber(lesen(b .. '_input')) or 0
    local min = tonumber(lesen(b .. '_min')) or 0
    local max = tonumber(lesen(b .. '_max')) or 1
    local p = (ist - min) * 100 / math.max(1, max - min)
    if p < 0 then p = 0 elseif p > 100 then p = 100 end
    return p
end

-- Akku aus /sys/class/power_supply
local AKKU = '/sys/class/power_supply/macsmc-battery/'

function conky_akku(feld)
    if feld == 'kap' then
        return lesen(AKKU .. 'capacity') or '?'
    elseif feld == 'status' then
        local s = lesen(AKKU .. 'status') or '?'
        local de = { ['Charging'] = 'lädt', ['Discharging'] = 'entlädt', ['Full'] = 'voll',
                     ['Not charging'] = 'Netz, lädt nicht' }
        return de[s] or s
    elseif feld == 'zyklen' then
        return lesen(AKKU .. 'cycle_count') or '?'
    elseif feld == 'voll' then
        -- Ist-Kapazität in Wh (Herstellerwert liefert der SMC-Treiber nicht; Neuwert A2141: 100 Wh)
        local voll = tonumber(lesen(AKKU .. 'energy_full'))
        if voll then return string.format('%.1f', voll / 1e6) end
        return '?'
    elseif feld == 'health' then
        local h = lesen(AKKU .. 'health') or '?'
        local de = { ['Good'] = 'gut', ['Overheat'] = 'überhitzt', ['Dead'] = 'defekt' }
        return de[h] or h
    elseif feld == 'grenze' then
        return lesen(AKKU .. 'charge_control_end_threshold') or '?'
    end
    return '?'
end

-- AMD-GPU (dGPU): Auslastung und Grafikspeicher
local function amdgpu_dir()
    local p = io.popen('ls -d /sys/class/drm/card? 2>/dev/null')
    local d
    for c in p:lines() do
        local t = io.popen('readlink ' .. c .. '/device/driver 2>/dev/null')
        local drv = t:read('*l') or ''
        t:close()
        if drv:match('amdgpu$') then d = c .. '/device/' break end
    end
    p:close()
    return d
end
local AMD = amdgpu_dir()

function conky_dgpu(feld)
    if not AMD then return '-' end
    if feld == 'busy' then
        return lesen(AMD .. 'gpu_busy_percent') or '?'
    elseif feld == 'vram' then
        local u = tonumber(lesen(AMD .. 'mem_info_vram_used'))
        local t = tonumber(lesen(AMD .. 'mem_info_vram_total'))
        if u and t then return string.format('%d / %d MiB', u // 1048576, t // 1048576) end
        return '?'
    elseif feld == 'status' then
        return lesen(AMD .. 'power/runtime_status') or '?'
    end
    return '?'
end

function conky_dgpu_busy()
    if not AMD then return 0 end
    return tonumber(lesen(AMD .. 'gpu_busy_percent')) or 0
end

-- Warnfarbe für Prozentwerte aus conky selbst (RAM, Dateisysteme)
local prozent = {
    ram  = { '${memperc}', 85, 95 },
    root = { '${fs_used_perc /}', 85, 95 },
    boot = { '${fs_used_perc /boot}', 80, 90 },
    efi  = { '${fs_used_perc /boot/efi}', 80, 90 },
}

function conky_p(k)
    local d = prozent[k]
    if not d then return '?' end
    local roh = conky_parse(d[1])
    local v = tonumber(roh)
    local farbe = NORMAL
    if v and v >= d[3] then farbe = ROT elseif v and v >= d[2] then farbe = GELB end
    return farbe .. roh .. '${color}'
end

-- für lua_graph (Funktion ohne Argument)
function conky_p_sys()
    return zahl('p_sys') or 0
end

Damit Conky nach der Anmeldung erst startet, wenn der SMC-Treiber geladen ist, wartet ein kleines Startskript /usr/local/bin/conky-autostart (ausführbar machen) auf ihn:

#!/bin/bash
# Startet conky nach der Anmeldung, sobald der SMC-Sensortreiber macsmc_hwmon geladen ist (max. 90 s warten)
pgrep -u "$USER" -x conky >/dev/null && exit 0
for i in $(seq 90); do
    grep -qx macsmc_hwmon /sys/class/hwmon/hwmon*/name 2>/dev/null && break
    sleep 1
done
exec conky -c "$HOME/.config/conky/conky.conf"

Eingetragen wird es in ~/.config/autostart/conky.desktop:

[Desktop Entry]
Type=Application
Name=Conky
Comment=Systemanzeige auf dem Desktop
Exec=/usr/local/bin/conky-autostart
Icon=conky-logomark-violet
X-MATE-Autostart-enabled=true
X-MATE-Autostart-Delay=5
NoDisplay=false

Dunkles MATE, Hintergrund und Startbildschirm

Den Dark Mode stellt man in MATE für die eigene Sitzung mit gsettings ein. Die zweite Zeile färbt das Fenstermanager-Theme, die letzten beiden gelten für GTK-4- und GNOME-Programme:

gsettings set org.mate.interface gtk-theme BlackMATE
gsettings set org.mate.Marco.general theme BlackMATE
gsettings set org.gnome.desktop.interface color-scheme prefer-dark
gsettings set org.gnome.desktop.interface gtk-theme Adwaita-dark

Als Hintergrund habe ich mir ein Motiv gezeichnet, das zu diesem Gerät passt: ein dunkelgrauer Apfel mit dem roten Debian-Swirl darin, auf fast schwarzem Grund. Es steht bei 30 % der Bildbreite, so verdeckt es weder das Anmeldefeld in der Mitte noch Conky rechts. Das Apple-Logo ist eine geschützte Marke, deshalb gibt es die Bilddatei hier nicht zum Herunterladen.

Ein Tipp für eigene dunkle Hintergründe: Sehr dunkle Verläufe zeigen in 8 Bit sichtbare Ringe, weil es dort nur wenige Helligkeitsstufen gibt. Ein feines Rauschen von etwa einer Stufe darüber, das Dithering, glättet sie, ohne dass man es sieht.

Den Anmeldebildschirm bekommt dasselbe Bild und dasselbe Theme, in /etc/lightdm/lightdm-gtk-greeter.conf unter [greeter]:

background=/usr/share/backgrounds/mbp-apfel/mbp-apfel-3072x1920.png
theme-name=BlackMATE
icon-theme-name=menta
user-background=false

Für den Startbildschirm habe ich ein eigenes Plymouth-Theme geschrieben. Oben steht die Passwortabfrage für die Verschlüsselung, unter dem Logo laufen die Startmeldungen über die ganze Breite. Wer alle Meldungen sehen will, drückt Escape. Die Punkte bleiben im Feld: Ist es voll, kommen keine weiteren dazu, ein langes Passwort verrät seine Länge also nicht.

Passwortabfrage des Startbildschirms: Überschrift Festplatte entsperren, darunter ein abgerundetes dunkles Feld mit fünf roten Punkten
Die Passwortabfrage im eigenen Plymouth-Theme.

Das Theme liegt in /usr/share/plymouth/themes/mbp-apfel/. Dazu gehören ein Hintergrundbild hintergrund.png in Bildschirmgröße und das Eingabefeld feld.png (880 × 108 Pixel, abgerundet, halbtransparent). Beschreibung mbp-apfel.plymouth:

[Plymouth Theme]
Name=MBP Apfel
Description=Apfel mit Debian-Swirl auf fast schwarz, mit Startmeldungen
ModuleName=script

[script]
ImageDir=/usr/share/plymouth/themes/mbp-apfel
ScriptFile=/usr/share/plymouth/themes/mbp-apfel/mbp-apfel.script

Das Skript mbp-apfel.script:

# MBP Apfel - Plymouth-Theme (03.11.2026, v4)
# Hintergrund wie der Desktop; Startmeldungen unter dem Apfel über die ganze Breite;
# Passwortabfrage oben über dem Apfel in einem Eingabefeld.
# Esc schaltet jederzeit auf die vollständige Textausgabe um.

Window.SetBackgroundTopColor(0.02, 0.02, 0.024);
Window.SetBackgroundBottomColor(0.02, 0.02, 0.024);

sw = Window.GetWidth();
sh = Window.GetHeight();
wx = Window.GetX();
wy = Window.GetY();

bild = Image("hintergrund.png");
hg = Sprite(bild.Scale(sw, sh));
hg.SetPosition(wx, wy, -100);

# --- Startmeldungen: unter dem Apfel, volle Breite ---
schrift = "DejaVu Sans Mono 10";
anzahl = 11;
zeilenhoehe = Image.Text("Mg", 1, 1, 1, 1, schrift).GetHeight() + 4;
x0 = wx + sw * 0.04;
maxbreite = sw * 0.92;
y_oben = wy + sh * 0.95 - anzahl * zeilenhoehe;   # Block endet 5 % über dem Rand
if (y_oben < wy + sh * 0.70) y_oben = wy + sh * 0.70;   # nie in den Apfel
zeichenbreite = Image.Text("MMMMMMMMMM", 1, 1, 1, 1, schrift).GetWidth() / 10;
maxzeichen = Math.Int(maxbreite / zeichenbreite);

i = 0;
while (i < anzahl) {
    zeile[i] = "";
    zsprite[i] = Sprite();
    zsprite[i].SetPosition(x0, y_oben + i * zeilenhoehe, 10);
    i++;
}

fun kuerzen(text) {
    if (text.CharAt(maxzeichen) == "") return text;
    return text.SubString(0, maxzeichen - 1) + "…";
}

fun neu_zeichnen() {
    j = 0;
    while (j < anzahl) {
        hell = 0.32 + 0.60 * (j / (anzahl - 1));
        if (zeile[j] != "")
            zsprite[j].SetImage(Image.Text(zeile[j], hell, hell, hell * 1.06, 1, schrift));
        j++;
    }
}

fun meldung(text) {
    if (text == "") return;
    k = 0;
    while (k < anzahl - 1) {
        zeile[k] = zeile[k + 1];
        k++;
    }
    zeile[anzahl - 1] = kuerzen(text);
    neu_zeichnen();
}

fun status_cb(status) { meldung(status); }
Plymouth.SetUpdateStatusFunction(status_cb);

fun message_cb(text) { meldung(text); }
Plymouth.SetMessageFunction(message_cb);

# --- Passwortabfrage (LUKS) oben, auf der Achse des Apfels ---
logo_x = wx + sw * 0.30;
feld_b = sw * 0.19;
feld_h = feld_b * 108 / 880;
feld_bild = Image("feld.png").Scale(feld_b, feld_h);
titel_bild = Image.Text("Festplatte entsperren", 0.80, 0.81, 0.85, 1, "DejaVu Sans 12");
titel_y = wy + sh * 0.10;
feld_y = titel_y + titel_bild.GetHeight() + sh * 0.012;

pw_titel = Sprite(titel_bild);
pw_titel.SetPosition(logo_x - titel_bild.GetWidth() / 2, titel_y, 20);
pw_feld = Sprite(feld_bild);
pw_feld.SetPosition(logo_x - feld_b / 2, feld_y, 20);
pw_punkte = Sprite();
pw_titel.SetOpacity(0);
pw_feld.SetOpacity(0);
pw_punkte.SetOpacity(0);

# höchstens so viele Punkte, wie mit Innenabstand ins Feld passen
punktbreite = Image.Text("● ", 1, 1, 1, 1, "DejaVu Sans 11").GetWidth();
maxpunkte = Math.Int((feld_b - feld_h * 1.2) / punktbreite);

fun passwort_cb(prompt, bullets) {
    s = "";
    b = 0;
    while (b < bullets && b < maxpunkte) {
        if (s == "") s = "●"; else s = s + " ●";
        b++;
    }
    if (s == "") {
        p = Image.Text("Passwort eingeben", 0.45, 0.46, 0.50, 1, "DejaVu Sans 11");
    } else {
        p = Image.Text(s, 0.93, 0.12, 0.42, 1, "DejaVu Sans 11");
    }
    pw_punkte.SetImage(p);
    pw_punkte.SetPosition(logo_x - p.GetWidth() / 2, feld_y + (feld_h - p.GetHeight()) / 2, 21);
    pw_titel.SetOpacity(1);
    pw_feld.SetOpacity(1);
    pw_punkte.SetOpacity(1);
}
Plymouth.SetDisplayPasswordFunction(passwort_cb);

fun normal_cb() {
    pw_titel.SetOpacity(0);
    pw_feld.SetOpacity(0);
    pw_punkte.SetOpacity(0);
}
Plymouth.SetDisplayNormalFunction(normal_cb);
sudo apt install plymouth plymouth-label
sudo plymouth-set-default-theme mbp-apfel
sudo update-initramfs -u

Zum Ausprobieren ohne Neustart gibt es das Paket plymouth-x11. Dann zeigt Plymouth das Theme in einem Fenster. Das Fenster ist allerdings nicht genau so groß wie der Bildschirm, für die Feinlage hilft nur ein echter Start.

Zuletzt die Tastaturbeleuchtung: Bei der Passwortabfrage war sie aus, weil sie erst nach dem Start vom System wieder eingeschaltet wird. Dieses Skript in /etc/initramfs-tools/scripts/init-premount/tastaturlicht (ausführbar, danach update-initramfs -u) schaltet sie vorher auf halbe Helligkeit:

#!/bin/sh
# Tastaturbeleuchtung vor der LUKS-Abfrage einschalten (MacBookPro16,1, T2) - 03.11.2026
PREREQ=""
prereqs() { echo "$PREREQ"; }
case "$1" in prereqs) prereqs; exit 0;; esac
i=0
while [ $i -lt 30 ]; do
    for l in /sys/class/leds/*kbd_backlight; do
        if [ -w "$l/brightness" ]; then
            echo $(( $(cat "$l/max_brightness") / 2 )) > "$l/brightness"
            exit 0
        fi
    done
    sleep 0.1; i=$((i + 1))
done
exit 0

Das Ergebnis

Aus einem Gerät, das nur noch als Billboard am USB hing, ist ein Arbeitsrechner geworden: macOS für Firmware-Updates, Debian 13 für alles andere, die Platte verschlüsselt, mit Snapshots, Lüftersteuerung und einem Akku, der am Netz bei 85 % stehen bleibt.

MacBook Pro mit Debian 13 und MATE: links der Hintergrund mit Apfel und rotem Debian-Swirl, rechts Conky mit Sensorwerten, beleuchtete Tastatur
Das MacBook Pro mit Debian 13, Hintergrund und Conky. Die IP-Adresse unten in Conky habe ich unkenntlich gemacht.

Offen sind bei mir noch drei Dinge: die Umstellung auf die Intel-Grafik, die Touch Bar im Linux-Modus und ein gründlicher Test von Ruhezustand und Touch ID. Wer diese Punkte auf einem MacBookPro16,1 gelöst hat, darf sich gern melden.

Zahlen im Überblick

  • Das MacBook Pro 16 Zoll von 2019 hat die Modellnummer A2141, intern MacBookPro16,1; der T2 meldet sich als iBridge2,14 (j152fap).
  • idevicerestore aus Debian 13 (Stand Juli 2024) scheitert bei bridgeOS 11 am ASR-Kommando „Initiate“; der Git-Stand vom 03.11.2026 (ae8cfb4) spielte bridgeOS 11.0 (24P6005) erfolgreich ein.
  • Der WLAN- und Bluetooth-Chip BCM4364 braucht Firmware aus macOS; nach der Umbenennung durch firmware.sh sind es 148 Dateien.
  • Kernel 7.3.1 aus dem t2-ubuntu-repo bringt Treiber für Tastatur, Trackpad, Touch Bar, T2-Audio und den SMC mit 43 Temperatursensoren und 2 Lüftern.
  • Im Leerlauf zieht das Gerät am Netz etwa 33 W, davon die AMD Radeon Pro 5300M allein etwa 17 W.

Referenzen

Bemerkungen

  • Bei der Einrichtung hat mir mein SoftwareSklave geholfen, ein KI-Assistent, der per SSH auf den Rechnern gearbeitet hat. Jeder Schritt wurde vorher gesichert und mit Rückweg dokumentiert. Die Dateien in diesem Beitrag sind die, die auf dem Gerät laufen; einzelne Befehle habe ich für den Nachbau zusammengefasst.
  • Das t2linux-Wiki und das t2-ubuntu-repo sind Gemeinschaftsprojekte. Versionen, Paketnamen und Kernelparameter ändern sich, im Zweifel gilt der aktuelle Stand dort.

Zum t2linux-Wiki