Was dein Kunde sieht, wenn deine VM bootet
Du verkaufst virtuelle Maschinen unter deinem eigenen Namen. Ein Kunde schaltet eine ein, und das Erste auf dem Bildschirm ist das Logo von jemand anderem.
Das ist eine Standard-VM unter Proxmox. Nichts ist kaputt, und Proxmox machen nichts falsch: Es ist ihr Produkt und ihr Name, sie haben die Firmware und die Oberfläche geschrieben, in der er erscheint, und sie dürfen ihn dort hinsetzen, so wie ein Serverhersteller sein Abzeichen vorn auf das Gehäuse setzt. Es ist nur eben nicht deins.
Das Logo ist erst der Anfang.
So meldet sich ein UEFI-Gast mit der aktuellen Firmware von Proxmox, pve-edk2-firmware 4.2026.08-1, aus dem Gast heraus mit dmidecode gelesen:
| Wo der Gast nachsieht | Was er liest | Wessen Name |
|---|---|---|
| Bootbildschirm | das Logo von Proxmox | Proxmox |
| Firmware-Hersteller (SMBIOS-Typ 0) | Proxmox distribution of EDK II | Proxmox |
| Firmware-Version | 4.2026.08-1 | die Paketversion von Proxmox |
| Systemhersteller (Typ 1) | QEMU | QEMU |
| Produktname (Typ 1) | Standard PC (Q35 + ICH9, 2009) | QEMU |
| Gehäusehersteller (Typ 3) | QEMU | QEMU |
| Mainboard (Typ 2) | gar nicht vorhanden | niemandes |
| Web-Verwaltungsoberfläche | das Logo von Proxmox, oben links | Proxmox |
Beim Bootlogo hört es auch nicht an der Firmware auf. UEFI-Firmware gibt das Bild, das sie gezeichnet hat, über eine ACPI-Tabelle namens BGRT, die Boot Graphics Resource Table, an das Betriebssystem weiter. Die Tabelle existiert, um zu sagen „an image was drawn on the screen during boot“1, und das Betriebssystem zeichnet es dann auf seinem eigenen Bootbildschirm noch einmal. Das Fisher-Price OS (Windows) setzt es über seinen Kreisel, und Microsoft nennt die BGRT „the standard interface that Windows uses to access the logo“2. Das Standard-Theme von Plymouth bei Fedora macht unter Linux dasselbe3. Ein Gast auf der Firmware von Proxmox zeigt das Logo von Proxmox also zweimal, bevor sich überhaupt jemand angemeldet hat.
Nichts davon ist schwer zu ändern.
Es geändert zu lassen schon.
Das nächste apt full-upgrade stellt still die Hälfte davon wieder her, und die Hälfte, die es nicht wiederherstellt, ist die, die dir Sorgen machen sollte. Um die geht es in diesem Beitrag vor allem.
Die MSDM-Tabelle in dieser Übergabe ist überhaupt kein Branding. Sie trägt einen Lizenzschlüssel, und wie man einen in eine VM bringt, hat einen eigenen Beitrag: Eine OEM-Lizenz in eine Proxmox-VM bringen.
Alles unter /usr/share gehört apt
Eine Regel entscheidet jede Wahl weiter unten. Eine Datei, die ein Paket installiert hat, ist die Datei des Pakets. Ändere sie an Ort und Stelle, und das nächste Upgrade dieses Pakets überschreibt deine Änderung ohne ein Wort, denn aus Sicht von dpkg legt es nur seine eigene Datei dorthin zurück, wo es sie gelassen hat.
Jedes Stück Branding muss also irgendwo leben, das apt nicht gehört. Ein Proxmox-Knoten hat drei solche Orte, und jeder kostet etwas anderes:
| Wo es lebt | Wie es dorthin kommt | Übersteht ein Upgrade | Was es dich kostet |
|---|---|---|---|
Die VM-Konfiguration in /etc/pve | smbios1, und args für alles andere | Ja, Konfiguration fasst kein Paket an | args darf nur root setzen, und es erscheint nicht in der GUI |
| Eine Datei, die dpkg in Ruhe lassen soll | dpkg-divert | Ja, die Kopie des Pakets landet stattdessen unter einem .distrib-Namen | Upgrades erreichen die Datei nicht mehr, und das zählt, wenn die Datei Firmware ist |
| Dein eigenes Verzeichnis, außerhalb des Paketbaums | /usr/local, aus args referenziert | Ja, kein Paket besitzt es | Du musst es selbst auf jeden Knoten bringen |
Debians eigene Beschreibung einer Umleitung ist die klarste: „a way of forcing dpkg not to install a file into its location, but to a diverted location“4. Das deckt die Weboberfläche ab. Für die Firmware ist es eine von zwei Möglichkeiten, und die gefährlichere.
Das Gehäuse ist eine Handvoll Strings und eine Rechteprüfung
SMBIOS ist die Tabelle, mit der eine Maschine sich selbst beschreibt: wer sie gebaut hat, welches Modell sie ist, ihre Seriennummer, was Mainboard und Gehäuse sind5.
dmidecode liest sie, jedes Inventarisierungswerkzeug, das du je auf ein Netz losgelassen hast, liest sie, die Systeminformation des Fisher-Price OS (Windows) liest sie, und die meisten Lizenzprüfungen, die entscheiden, ob eine Software auf einer bestimmten Maschine überhaupt läuft, lesen sie auch.
Bei einer VM schreibt QEMU sie.
Daher QEMU und Standard PC in der Tabelle oben.
Typ 1 über smbios1
Proxmox legt eine einzige SMBIOS-Struktur in der VM-Konfiguration offen: Typ 1, System Information, als smbios16.
Sie nimmt manufacturer, product, version, serial, sku, family und uuid.
Der Haken steckt in qemu-server, nicht in der Doku.
Jedes Feld außer uuid muss auf ein Base64-Muster passen, [A-Za-z0-9+\/]+={0,2}, ein einfacher Wert mit einem Leerzeichen darin wird also glatt abgelehnt.
Echte Strings werden Base64-kodiert und mit base64=1 markiert, und qemu-server dekodiert sie wieder, bevor es die QEMU-Befehlszeile baut7.
Der SMBIOS-Editor der GUI macht das bei jedem Speichern, mit dem Kommentar „smbios values can be arbitrary, so encode and mark config as such“8.
Auf der Kommandozeile ist es deine Aufgabe:
b() { printf %s "$1" | base64 -w0; }
qm set 9000 --smbios1 "uuid=$(qm config 9000 | sed -n 's/.*uuid=\([0-9a-f-]*\).*/\1/p'),base64=1,\
manufacturer=$(b 'Example Cloud Ltd'),product=$(b 'EC Compute Instance'),\
version=$(b '2026.10'),family=$(b 'General Purpose'),sku=$(b 'ec-gp-4c16g')"
Beachte, dass die uuid wieder mit hineinkommt.
Sie ist die Maschinenidentität des Gasts, und was du an --smbios1 übergibst, wird als ganze Option gespeichert, also lies sie vorher aus und behalte sie.
Brande die Vorlage, nicht jede einzelne VM.
Ein Klon unter Proxmox bekommt eine frisch erzeugte UUID und behält jedes andere smbios1-Feld, jeder Klon kommt also mit eigener Identität und deinen Strings heraus9, mit einer Ausnahme, siehe unten.
Typen 0, 2, 3 und 11 über args
smbios1 endet bei Typ 1.
Firmware-Hersteller, Mainboard, Gehäuse und die OEM-Strings laufen alle über args, die Zeile, die Proxmox direkt an QEMU weiterreicht und die seine eigene Dokumentation „for experts only“ nennt6.
Die QEMU-Option -smbios nimmt die Felder für jeden Typ10:
args: -smbios 'type=0,vendor=Example Cloud Ltd,version=EC-FW 1.0,date=10/04/2026,uefi=on' -smbios 'type=2,manufacturer=Example Cloud Ltd,product=EC Virtual Board,version=1.0' -smbios 'type=3,manufacturer=Example Cloud Ltd,version=1.0,asset=EC-ASSET-123,sku=ec-gp' -smbios 'type=11,value=example-cloud:instance=123'
Mit diesen Zeilen auf QEMU gebootet, liest dmidecode im Gast:
| Struktur | Feld | Standard | Gebrandet |
|---|---|---|---|
| Typ 0 | Vendor | Proxmox distribution of EDK II | Example Cloud Ltd |
| Typ 0 | Version | 4.2026.08-1 | EC-FW 1.0 |
| Typ 1 | Manufacturer | QEMU | Example Cloud Ltd |
| Typ 1 | Product Name | Standard PC (Q35 + ICH9, 2009) | EC Compute Instance |
| Typ 1 | Family | nicht angegeben | General Purpose |
| Typ 2 | Manufacturer | Struktur fehlt | Example Cloud Ltd |
| Typ 2 | Product Name | Struktur fehlt | EC Virtual Board |
| Typ 3 | Manufacturer | QEMU | Example Cloud Ltd |
| Typ 3 | Asset Tag | nicht angegeben | EC-ASSET-123 |
| Typ 11 | String 1 | Struktur fehlt | example-cloud:instance=123 |
Unterwegs sind vier Fallen aufgetaucht. Alle vier sind still.
Wer Typ 0 liefert, verliert „UEFI is supported“.
OVMF schreibt seinen eigenen Typ 0 nur, wenn QEMU keinen geliefert hat; die Schleife, die durch die Tabellen von QEMU läuft, setzt NeedSmbiosType0 = FALSE, sobald sie auf einen Typ 0 trifft11.
Dein Herstellerstring ersetzt den von Proxmox, und genau darum geht es.
Aber der Typ 0 von QEMU ersetzt auch die Firmware-Eigenschaften von OVMF, und ohne uefi=on verschwindet die Zeile „UEFI is supported“ aus dmidecode.
Hol sie mit uefi=on zurück.
Für UEFI-Gäste gibt es für den Herstellerstring ohnehin ein besseres Zuhause, in der Firmware selbst, weiter unten.
Der Gehäusetyp lässt sich nicht setzen.
Der Typ 3 von QEMU nimmt manufacturer, version, serial, asset und sku, und sonst nichts10.
Er bleibt Other.
Zwei Definitionen eines Typs werden zusammengeführt, und die spätere gewinnt.
qemu-server setzt sein -smbios type=1 früh in die Befehlszeile und deine args ganz ans Ende12, ein zweites -smbios type=1,manufacturer=… in args wirft das erste also nicht weg: QEMU hat sie Feld für Feld zusammengeführt, der Hersteller kam aus args, und die UUID blieb, wo smbios1 sie hingesetzt hatte.
Das zählt für die vierte Falle.
Die beiden Hälften haben verschiedene Besitzer.
qemu-server prüft Rechte Option für Option.
smbios1 steht bei den Hardware-Optionen und braucht VM.Config.HWType, während args in den Auffangzweig ganz unten fällt, der sagt „only root can set“13.
Mainboard, Gehäuse, Firmware-Hersteller und OEM-Strings gehören dir allein.
Typ 1 nicht.
Jeder, dem du Hardware-Rechte gegeben hast, kann ihn umschreiben, und bei einem gehosteten Produkt kann das sehr wohl der Kunde sein.
Wenn der Herstellerstring zählt, setz ihn auch in args. Die spätere Definition gewinnt.
Die Seriennummer steht schon in der Konfiguration
Das meiste, was in Typ 1 gehört, steht schon in der VM-Konfiguration. Der Name gibt eine gute Seriennummer ab, weil qemu-server dort nur einen DNS-Namen akzeptiert14: kurz, druckbar, nie ein Komma darin, und das eine an der VM, das ein Kunde wiedererkennt.
| Feld in Typ 1 | Genommen aus | Für eine VM mit 4 Kernen und 16 GiB namens web-01, getaggt production;web |
|---|---|---|
| Serial Number | name | web-01 |
| SKU Number | cores × sockets, und memory | ec-4c16g |
| Family | der erste Eintrag in tags | production |
| UUID | die smbios1-UUID, die sie schon hat | unverändert |
| Manufacturer, Product | deine eigenen festen Strings | Example Cloud Ltd, EC Compute Instance |
Zwei Dinge bleiben draußen.
Der Knoten ändert sich bei jeder Migration, und vmgenid soll sich ändern: Seine ganze Aufgabe ist, dem Gast zu sagen, dass er aus einem Snapshot wiederhergestellt oder aus einer Vorlage gebaut wurde15.
Keins von beiden ist eine Identität.
#!/bin/bash
# /usr/local/sbin/ec-smbios VMID: rebuild smbios1 from the VM's own config.
set -euo pipefail
id=$1
cfg=$(qm config "$id" --current)
get() { sed -n "s/^$1: //p" <<<"$cfg"; }
b() { printf %s "$1" | base64 -w0; }
name=$(get name)
[ -n "$name" ] || { echo "VM $id has no name to use as a serial" >&2; exit 1; }
cores=$(get cores); sockets=$(get sockets); vcpu=$(( ${cores:-1} * ${sockets:-1} ))
mem=$(get memory); mem=${mem#current=}; mem=${mem%%,*}; mem=${mem:-512}
(( mem % 1024 )) && size="${mem}m" || size="$(( mem / 1024 ))g"
tag=$(get tags); tag=${tag%%;*}
uuid=$(get smbios1 | grep -o 'uuid=[0-9a-fA-F-]*' | cut -d= -f2 || true)
uuid=${uuid:-$(cat /proc/sys/kernel/random/uuid)}
s="uuid=$uuid,base64=1,manufacturer=$(b 'Example Cloud Ltd'),product=$(b 'EC Compute Instance')"
s+=",serial=$(b "$name"),sku=$(b "ec-${vcpu}c${size}")"
[ -n "$tag" ] && s+=",family=$(b "$tag")"
qm set "$id" --smbios1 "$s"
Die Vorgaben sind die von qemu-server selbst, ein Kern, ein Sockel und 512 MiB16, eine Konfiguration, die sie weglässt, bekommt also trotzdem eine wahre SKU.
Gegen die Konfiguration aus der Tabelle laufen gelassen, mit einem Skript anstelle von qm, das sie auslieferte, ging die geschriebene Zeile so dekodiert an QEMU, wie qemu-server sie dekodiert, und dmidecode im Gast las web-01, ec-4c16g, production und die UUID zurück, die er schon hatte.
Eine VM ohne Namen wird abgelehnt, statt eine leere Seriennummer zu bekommen.
Das naheliegende Zuhause dafür ist ein Hookscript.
Es ist das falsche.
qemu-server führt den pre-start-Hook innerhalb der Konfigurationssperre aus, die es zum Starten der VM genommen hat, und baut die QEMU-Befehlszeile aus der Konfiguration, die es vor dem Hook geladen hat17.
Ein Hook, der die Seriennummer umschreibt, ändert den nächsten Start, nicht diesen.
Lass es also aus deiner Provisionierung laufen, an den vier Punkten, an denen sich seine Eingaben ändern: Anlegen, Klonen, Umbenennen und Größenänderung.
Beim Klonen beißt es.
Ein Klon behält jedes smbios1-Feld außer der UUID9, lass den Schritt also aus, und jede VM, die aus web-template gebaut wurde, meldet den Namen der Vorlage als Seriennummer.
Das Bootlogo lebt an zwei verschiedenen Orten
Welches Logo ein Gast zeigt, hängt von seiner Firmware ab. Die beiden Firmwares, die Proxmox ausliefert, holen es von völlig verschiedenen Stellen:
SeaBIOS (bios: seabios, die Vorgabe) | OVMF (bios: ovmf, UEFI) | |
|---|---|---|
| Woher das Logo kommt | ein JPEG, das QEMU beim Booten übergibt | eine Bitmap, die in die Firmware kompiliert ist |
| Die Datei von Proxmox | /usr/share/qemu-server/bootsplash.jpg, 640×480 | Logo.bmp, 400×120, 8 Bit, eingebaut in OVMF_CODE_4M*.fd |
| Besitzendes Paket | qemu-server | pve-edk2-firmware-ovmf |
| Pro VM ändern | args: -boot splash=… | args, das die VM auf andere Firmware zeigt |
| Ändern, ohne etwas neu zu bauen | ja | nein |
| Erreicht das Gast-OS über die BGRT | nein | ja |
SeaBIOS: ein JPEG auf der Befehlszeile
qemu-server setzt bei jeder VM, die es startet, dieselbe -boot-Option, menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg18.
Die Dokumentation von QEMU sagt, das Bild werde gezeigt „when option splash=sp_name is given and menu=on, If firmware/BIOS supports them. Currently Seabios for X86 system support it“10.
OVMF nimmt daraus nichts außer splash-time, das es als Timeout für das Bootmenü verwendet, und in OvmfPkg gibt es nirgends einen Verweis auf die Splash-Datei19.
Ersetzen ist eine Zeile in der VM-Konfiguration:
args: -boot splash=/etc/pve/branding/splash.jpg
Ein zweites -boot kämpft nicht mit dem ersten.
QEMU führt es genauso zusammen wie -smbios, der spätere Wert gewinnt, und mit zwei angegebenen Splash-Dateien zeigte der Screenshot die zweite.
/etc/pve ist das richtige Zuhause für die Datei, weil es das Cluster-Dateisystem ist und jeder Knoten denselben Splash sieht, und seine Grenze von 1 MiB pro Datei ist weit weg von einem JPEG mit 640×48020.
Das Bild selbst muss drei Bedingungen erfüllen, und wer eine davon falsch macht, verliert das Logo oder seine Farben:
| Bedingung | Warum | Was sonst passiert |
|---|---|---|
| JPEG oder 24-Bit-BMP | QEMU prüft die Datei, bevor die VM startet | splash file … format not recognized; must be JPEG or 24 bit BMP |
| Baseline-JPEG, Chroma 4:2:0 | der Decoder von SeaBIOS kann nichts anderes21 | ERR_NOT_SEQUENTIAL_DCT oder ERR_NOT_YCBCR_221111, und ein leerer Bildschirm |
| 640×480, wie das von Proxmox | SeaBIOS fragt das VGA-BIOS nach einem Modus mit genau der Größe des Bilds | kein passender Modus, kein Splash |
Die meisten Bildwerkzeuge schreiben standardmäßig Baseline mit 4:2:0, der übliche Weg, das Logo zu verlieren, ist also eine dieser „Für Web speichern“-Optionen, die still progressive Kodierung einschaltet. Das sieht in jedem Bildbetrachter, den du hast, genau gleich aus, und SeaBIOS zeichnet gar nichts.
Die Farbfalle
Die hier hat einen Nachmittag gekostet. Dasselbe JPEG, von zwei Builds von SeaBIOS 1.17.0 gezeichnet, kommt in zwei verschiedenen Farben heraus:

Es steckt in jpeg.c von SeaBIOS.
Der Schreiber für 24 Bit pro Pixel hat einen Little-Endian-Zweig, der Blau in das erste Byte jedes Pixels setzt, und der Schreiber für 32 Bit pro Pixel, PIC_32, hat keinen solchen Zweig und schreibt dort stattdessen Rot21.
Auf einem Little-Endian-Framebuffer vertauscht das Rot und Blau.
Blau kommt als Gold heraus, und Orange käme als Blau heraus.
Welcher Schreiber läuft, hängt vom Videomodus ab, den das VGA-BIOS anbietet:
| Firmware | VBE-Modus für 640×480 | Bit pro Pixel | Farben |
|---|---|---|---|
Das vorgebaute SeaBIOS 1.17.0 von QEMU upstream, wie pve-qemu-kvm 11.0.3-4 es ausliefert | 0x111 | 16 | richtig, auf 65.536 quantisiert |
Fedora 44s eigener Build seabios 1.17.0-10 | 0x142 | 32 | Rot und Blau vertauscht |
Die Modusnummern stammen aus der eigenen Tabelle von SeaBIOS22, und die Proxmox-Zeile wurde gegen genau die Blobs im Paket geprüft: Byte für Byte identisch mit dem vorgebauten rel-1.17.0-0-gb52ca86e094d von QEMU.
Unter Proxmox stimmen deine Farben also, aber es gibt nur 65.536 davon, und ein feiner Verlauf bekommt Streifen.
Und falls dein Logo irgendwann auf einem anderen Hypervisor in falschen Farben erscheint: Es ist nicht dein JPEG.
Das UEFI-Logo ist in die Firmware kompiliert
UEFI-Gäste sind die schwierigere Hälfte, und auf einem modernen Proxmox sind sie die meisten Gäste.
Es gibt keine Splash-Option zum Überschreiben.
Das Logo ist eine Bitmap im Firmware-Image, und der Build von Proxmox setzt es mit einer Zeile in debian/rules dorthin:
debian/setup-build-stamp:
cp -a debian/Logo.bmp MdeModulePkg/Logo/Logo.bmp
Das kopiert ihr Logo.bmp mit 400×120 und 8 Bit über das von TianoCore, bevor edk2 gebaut wird23.
Dieselbe Datei setzt den Firmware-Hersteller als Konstante zur Build-Zeit, PcdFirmwareVendor=L"Proxmox distribution of EDK II", und daher kommt der Typ-0-Hersteller in der ersten Tabelle23.
Neues Logo, neue Firmware.
Der Weg zu einer, die sich genau wie die von Proxmox verhält, ist, sie genau so zu bauen wie Proxmox: ihr Baum an dem Tag, der zum ausgelieferten Paket passt, ihre Patches, ihre Flags, und eine Bitmap ausgetauscht.
pve-edk2-firmware 4.2026.08-1 pinnt edk2 auf 2970e56, und das ist der Upstream-Tag edk2-stable20260823.
git clone https://git.proxmox.com/git/pve-edk2-firmware.git
cd pve-edk2-firmware
git checkout f37039e52d228a7844a90aa2aaf1e161ebd04fcd # 4.2026.08-1
git submodule update --init --recursive
cd edk2
QUILT_PATCHES=../debian/patches quilt push -a
cp /path/to/your/Logo.bmp MdeModulePkg/Logo/Logo.bmp
. ./edksetup.sh && make -C BaseTools
F="-DNETWORK_HTTP_BOOT_ENABLE=TRUE -DNETWORK_IP6_ENABLE=TRUE -DNETWORK_TLS_ENABLE
-DSECURE_BOOT_ENABLE=TRUE -DPVSCSI_ENABLE=TRUE -DTPM2_ENABLE=TRUE
--pcd PcdUninstallMemAttrProtocol=TRUE -DFD_SIZE_4MB"
P=(--pcd "PcdFirmwareVendor=LExample Cloud Ltd\\0"
--pcd "PcdFirmwareVersionString=L4.2026.08-1+ec1\\0")
build -a IA32 -a X64 -t GCC -p OvmfPkg/OvmfPkgIa32X64.dsc $F "${P[@]}" -b RELEASE
cp Build/Ovmf3264/RELEASE_GCC/FV/OVMF_CODE.fd OVMF_CODE_4M.fd
rm -rf Build/Ovmf3264
build -a IA32 -a X64 -t GCC -p OvmfPkg/OvmfPkgIa32X64.dsc $F -DSMM_REQUIRE=TRUE "${P[@]}" -b RELEASE
cp Build/Ovmf3264/RELEASE_GCC/FV/OVMF_CODE.fd OVMF_CODE_4M.secboot.fd
Die Flags sind aus debian/rules übernommen, so wie es an diesem Commit steht.
Achte auf den Herstellerstring: Ihr Makefile schreibt ihn in doppelten Anführungszeichen, die Shell von make entfernt sie, und edk2 bekommt ihn nackt. So wird er hier auch übergeben, und ihn ein zweites Mal zu quoten ist ein Build-Fehler.
Lass die Bitmap bei 400×120 und 8 Bit wie ihre, dann verschiebt sich sonst nichts auf dem Bootbildschirm.
Du brauchst beide Images.
qemu-server bootet jede Q35-VM mit einer 4M-EFI-Disk auf OVMF_CODE_4M.secboot.fd, dem Build mit verpflichtendem SMM, und nimmt das einfache OVMF_CODE_4M.fd nur für i440fx24.
Auf einem aktuellen Proxmox läuft fast jeder UEFI-Gast mit dem Secboot-Image.
So gebaut, mit nichts gegenüber dem Baum von Proxmox geändert als der Bitmap und zwei Strings, bootet das SMM-Image so:

Und die Sicht des Gasts auf seine eigene Firmware ändert sich mit, ohne eine einzige -smbios-Option auf der Befehlszeile:
| Im Gast gelesen | 4.2026.08-1 von Proxmox | Aus demselben Baum neu gebaut |
|---|---|---|
| Firmware-Hersteller | Proxmox distribution of EDK II | Example Cloud Ltd |
| Firmware-Version | 4.2026.08-1 | 4.2026.08-1+ec1 |
| „UEFI is supported“ | aufgeführt | aufgeführt |
| ACPI-Tabellen | BGRT, WSMT und der Rest | derselbe Satz |
Damit ist der Hersteller zur Build-Zeit die bessere Antwort für UEFI-Gäste.
Es ist der eigene Typ 0 von OVMF, das UEFI-Bit bleibt also stehen, ohne dass irgendwer an uefi=on denken muss.
Der Weg über args für Typ 0 ist für SeaBIOS-Gäste, und für alle, die gar keine Firmware neu bauen.
Zwei Wege, einer VM die neue Firmware zu geben
qemu-server kodiert fest, wo es nach Firmware sucht: OVMF.pm hält eine Tabelle von Pfaden unter /usr/share/pve-edk2-firmware/, nach Maschinentyp und Secure-Boot-Optionen geordnet, und es gibt nirgends in der VM-Konfiguration, der GUI oder der API eine Option, die eine andere Datei wählt24.
Zwei Wege bleiben.
Weg A: auf jedem Knoten umleiten. Sag dpkg, dass die beiden Code-Images von Proxmox jetzt woanders hingehören, und setz deine an ihre Stelle:
D=/usr/share/pve-edk2-firmware
for f in OVMF_CODE_4M.fd OVMF_CODE_4M.secboot.fd; do
dpkg-divert --package example-branding --add --rename --divert "$D/$f.distrib" "$D/$f"
install -m 0644 "/root/branding/$f" "$D/$f"
done
Ab dem nächsten Start bootet jede UEFI-VM auf dem Knoten deine Firmware. Keine Änderung an irgendeiner VM-Konfiguration.
Weg B: ausgewählte VMs darauf zeigen lassen.
Behalte die Images in deinem eigenen Verzeichnis und überschreibe die Firmware VM für VM.
Seit dem Umstieg auf -blockdev hängt qemu-server das Code-Image als Blockknoten namens pflash0 an und nennt ihn in -machine24, und QEMU führt ein zweites -machine genauso zusammen wie -boot, args kann also einen eigenen Knoten hinzufügen und pflash0 stattdessen darauf zeigen lassen:
args: -blockdev driver=raw,node-name=brandcode,read-only=on,file.driver=file,file.filename=/usr/local/share/example-branding/OVMF_CODE_4M.secboot.fd -machine pflash0=brandcode
Das wurde auf dem langen Weg getestet.
Die Secboot-Firmware von Proxmox wurde genau so als pflash0 angehängt, wie qemu-server es macht, mit SMM an, dann kam diese args-Zeile dahinter, und der Gast bootete das gebrandete Image mit Logo und Herstellerstring. Daher stammt der Screenshot oben.
| Weg A, umleiten | Weg B, args pro VM | |
|---|---|---|
| Welche VMs es bekommen | jede UEFI-VM auf dem Knoten | nur VMs, bei denen du es setzt |
| Die Dateien von Proxmox | nach .distrib verschoben | unberührt |
| VM-Konfiguration | unverändert | eine args-Zeile, nur root |
| Maschinentyp muss passen | erledigt, beide Images sind umgeleitet | deine Aufgabe: Secboot-Image für Q35 |
| Rückgängig machen | dpkg-divert --remove --rename | die Zeile löschen |
| Migration | Zielknoten muss auch umgeleitet sein | Zielknoten muss die Datei haben |
Das Code-Image ist etwa 3,5 MB groß, bei einer Grenze von 1 MiB pro Datei in pmxcfs20.
Deshalb kann es nicht in /etc/pve liegen, und egal welchen Weg du nimmst, es muss auf jeden Knoten.
Migrierst du eine VM auf einen Knoten ohne es, bekommst du je nach Weg eins von zwei Ergebnissen: unter Weg A wieder die Firmware und das Logo von Proxmox, oder unter Weg B eine VM, die gar nicht startet, weil QEMU eine Datei nicht öffnen kann, die nicht da ist.
Das Upgrade, das dich zurücklässt
Eine Umleitung ist das richtige Werkzeug für ein Logo. Firmware ist etwas anderes.
Eine Umleitung bedeutet, dass die Upgrades von Proxmox die Datei nicht mehr erreichen. Genau das hast du verlangt. Genau das hält auch Sicherheitskorrekturen von edk2 von deinen Gästen fern: Das Changelog von Proxmox für 4.2026.08-1 beginnt mit „Besides many bug and security fixes“ und führt dann eine Korrektur für CVE-2024-13745 auf25, und ein gebrandeter Build aus dem Release davor hat nichts davon. Nichts sagt es dir.
Das wurde getestet, nicht angenommen.
In einem Debian-trixie-Container mit dem Repository von Proxmox kam pve-edk2-firmware-ovmf 4.2025.05-3 hinein, beide Code-Images wurden umgeleitet, und das Paket wurde auf 4.2026.08-1 aktualisiert:
| Nach dem Upgrade | Ergebnis |
|---|---|
dpkg-query -W pve-edk2-firmware-ovmf | 4.2026.08-1 |
| Das neue Image von Proxmox | als OVMF_CODE_4M.fd.distrib installiert, Hash geändert |
| Das gebrandete Image | unverändert, immer noch aus 4.2025.05-3 gebaut |
| Irgendetwas dazu auf der Konsole | nichts, bis zum Hook unten |
Also kommt ein Hook dazu.
apt führt nach jedem Lauf von dpkg eine Liste von DPkg::Post-Invoke-Befehlen aus26.
Halte fest, aus welcher Proxmox-Version du gebaut hast, und vergleiche nach jedem Lauf:
cat > /usr/local/sbin/example-branding-check <<'EOF'
#!/bin/sh
built=$(cat /usr/local/share/example-branding/ovmf.built-from 2>/dev/null)
now=$(dpkg-query -W -f '${Version}' pve-edk2-firmware-ovmf 2>/dev/null)
[ "$built" = "$now" ] && exit 0
echo "W: branded OVMF was built from pve-edk2-firmware-ovmf $built, Proxmox now ships $now." >&2
echo "W: guests still boot the old firmware. Rebuild before the next VM restart." >&2
exit 0
EOF
chmod +x /usr/local/sbin/example-branding-check
echo 'DPkg::Post-Invoke { "/usr/local/sbin/example-branding-check"; };' \
> /etc/apt/apt.conf.d/80example-branding
Bei genau diesem Upgrade gab er aus:
W: branded OVMF was built from pve-edk2-firmware-ovmf 4.2025.05-3, Proxmox now ships 4.2026.08-1.
W: guests still boot the old firmware. Rebuild before the next VM restart.
Er endet absichtlich mit 0.
apt bricht ab, wenn ein Post-Invoke-Befehl fehlschlägt26, und eine Branding-Prüfung ist kein Grund, einen Knoten halb aktualisiert stehen zu lassen.
Eine laute Warnung reicht.
Noch etwas. Neustarts. Ein Gast bekommt neue Firmware erst, wenn sein QEMU-Prozess neu startet, ein Neubau bedeutet also ein Stoppen und Starten aus Proxmox heraus statt eines Neustarts im Gast, und genau so erreichen auch die eigenen Firmware-Updates von Proxmox eine laufende VM. Die brauchen nur nicht, dass du vorher an einen Neubau denkst.
Oder lass die Firmware von Proxmox in Ruhe
Jeder Logo-Weg bisher ändert die Firmware, und der letzte Abschnitt ist die Rechnung dafür. Es gibt einen, der das nicht tut, und er wurde für diesen Beitrag als Prototyp gebaut.
Ein PCI-Gerät in QEMU kann ein Option-ROM tragen, jede Datei, die du mit romfile= angibst27, und OVMF führt den EFI-Treiber aus, den es dort findet.
Der Build von Proxmox führt ihn aus, ob er signiert ist oder nicht: OVMF setzt PcdOptionRomImageVerificationPolicy auf 0x00, immer ausführen28.
OVMF zeichnet sein eigenes Logo spät in der Auswahl des Bootgeräts29 und baut die BGRT erst bei ReadyToBoot, dem Moment, bevor es an einen Bootloader übergibt.
Und der BGRT-Treiber von edk2 nimmt über EDKII_BOOT_LOGO2_PROTOCOL ein Ersatzbild an und baut die Tabelle bei ReadyToBoot neu, wann immer sich das Bild geändert hat30.
Mehr braucht es nicht.
Der Treiber wartet bei TPL_NOTIFY auf ReadyToBoot, was vor dem eigenen Handler des BGRT-Treibers bei TPL_CALLBACK läuft, löscht den Bildschirm, zeichnet sein Logo und übergibt dasselbe Bild.
Gebaut ist es eine Datei von 8 KB, angehängt mit einer Zeile:
args: -device pci-testdev,romfile=/etc/pve/branding/brandrom.rom
Mit 8 KB liegt sie weit unter der Grenze von 1 MiB in pmxcfs20, anders als ein Firmware-Image mit 3,5 MB kann sie also in /etc/pve leben und der VM auf jeden Knoten folgen.
Getestet wurde sie gegen die eigene Firmware von Proxmox, direkt aus pve-edk2-firmware-ovmf 4.2026.08-1: OVMF_CODE_4M.secboot.fd mit den vorab eingetragenen Schlüsseln von Microsoft in OVMF_VARS_4M.ms.fd, auf Q35 mit SMM, und gebootet über den signierten Shim von Fedora, sodass Secure Boot an war und durchgesetzt wurde:
| Aus dem Gast gelesen | Ohne das ROM | Mit dem ROM |
|---|---|---|
Variable SecureBoot | 1 | 1 |
| Auf dem Bildschirm | das Logo von Proxmox | das von Proxmox für etwa 250 ms, dann deins |
| BGRT-Bild, 400×120 bei 440,340 | das von Proxmox, SHA-256 be5afe4b… | das des ROMs, SHA-256 6c494576… |
| Geänderte Firmware-Dateien | keine | keine |

Auf die letzte Zeile kommt es an. Die Firmware von Proxmox ist unberührt, ihr nächstes Sicherheits-Release erreicht deine Gäste also mit dem nächsten Neustart, und nichts aus dem vorigen Abschnitt gilt.
Umsonst ist es nicht:
| Kosten | Warum |
|---|---|
| Das Logo von Proxmox ist etwa eine Viertelsekunde zu sehen | OVMF zeichnet es, bevor irgendein Option-ROM-Treiber den Bildschirm bekommt; nur ein Neubau vermeidet das |
| Der Gast sieht ein PCI-Gerät mehr, ohne Treiber | das ROM muss auf einem Gerät mitfahren, und pci-testdev ist das Nichtstuer-Gerät von QEMU |
| Typ 0 sagt immer noch Proxmox | der Herstellerstring ist einkompiliert; setz ihn über args mit uefi=on, wie oben |
| Nur root | es ist eine args-Zeile |
Und der Befund darunter will deutlich gesagt sein.
Ein unsignierter Treiber lief in der Firmware eines Gasts mit eingetragenen Microsoft-Schlüsseln und durchgesetztem Secure Boot, weil das OVMF von Proxmox Option-ROMs nicht prüft.
Hier ist das nützlich.
Es bedeutet aber auch, dass Secure Boot auf dieser Firmware Bootloader prüft, nicht das, was die Hardware-Konfiguration der VM anhängt, und weil nur root args schreibt, ist das eine Frage danach, wer auf deinen Knoten root hat.
Es ist ein Prototyp. Es lief auf QEMU 10.2.2 mit der Firmware von Proxmox, noch nicht auf einem Proxmox-Knoten, und was folgt, ist Quellcode, kein Produkt:
github.com/damo2929/RebrandPCIRom: der Option-ROM-Treiber, der Logo-Konverter und der Container-Build.
Die Weboberfläche sind zwei Pakete
Das Logo oben links in der Weboberfläche ist nicht in pve-manager.
Workspace.js fragt nach einer Komponente proxmoxLogoSvg mit dem Präfix pwt, und die lebt in proxmox-widget-toolkit, das sich Backup Server und Mail Gateway teilen31.
Nur die Tab-Icons sind in pve-manager:
| Was | Datei | Paket |
|---|---|---|
| Header-Logo, gezeichnet mit 200×35 | /usr/share/javascript/proxmox-widget-toolkit/images/proxmox_logo.svg | proxmox-widget-toolkit |
| Icon im Browser-Tab | /usr/share/pve-manager/images/favicon.ico | pve-manager |
| Icon mit 128×128, auch das Touch-Icon | /usr/share/pve-manager/images/logo-128.png | pve-manager |
Alle drei sind einfache Dateien, das ist also ein Fall für dpkg-divert, und hier gelten die Kosten aus dem letzten Abschnitt überhaupt nicht, denn ein Logo hat keine Sicherheitskorrekturen, die es verpassen kann, und niemandes Gast ist unsicherer, weil ein Knoten noch das Favicon vom letzten Monat ausliefert.
divert() {
dpkg-divert --package example-branding --add --rename --divert "$1.distrib" "$1"
install -m 0644 "$2" "$1"
}
divert /usr/share/javascript/proxmox-widget-toolkit/images/proxmox_logo.svg /root/branding/logo.svg
divert /usr/share/pve-manager/images/favicon.ico /root/branding/favicon.ico
divert /usr/share/pve-manager/images/logo-128.png /root/branding/logo-128.png
Im selben Container getestet, gegen echte Pakete:
| Schritt | Ergebnis |
|---|---|
Umleitung auf proxmox-widget-toolkit 5.2.9, eigenes SVG installiert | das SVG von Proxmox umbenannt in proxmox_logo.svg.distrib |
| Upgrade auf 5.2.10 | eigenes SVG noch an seinem Platz, die Kopie von Proxmox aus 5.2.10 in .distrib |
dpkg --verify proxmox-widget-toolkit | keine Beschwerden, dpkg weiß von der Umleitung |
apt-get install --reinstall proxmox-widget-toolkit | eigenes SVG noch an seinem Platz |
dpkg-divert --remove --rename | das Original von Proxmox zurück, Byte für Byte |
Zwei Dinge bleiben bei Proxmox.
Das Header-Bild wird in einem festen Kasten von 200×35 gezeichnet, zeichne dein SVG also in dieser Form, sonst wird es gequetscht.
Und der alt-Text sagt Proxmox, und das Bild verlinkt auf https://www.proxmox.com, beides in die JavaScript-Komponente geschrieben statt in eine Datei, die du umleiten kannst31.
Das zu ändern heißt, ein minifiziertes Bundle zu patchen, das bei jedem Upgrade bricht.
Für einen Alt-Text lohnt sich das nicht.
Was Lizenz und Marke von Proxmox von dir verlangen
Proxmox VE steht unter der AGPL Version 332, und Abschnitt 13 dieser Lizenz ist die Netzwerkklausel: „if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network … an opportunity to receive the Corresponding Source of your version“33. Zählt das Austauschen von drei Bildern als Änderung des Programms? Das ist eine Frage für einen Anwalt. Die billige Antwort ist, deine Bilder und das Umleitungsskript irgendwo öffentlich abzulegen und von der Anmeldeseite darauf zu verlinken. Kostet nix und klärt die Frage.
Die Markenseite ist klarer. Das Media-Kit von Proxmox sagt „Don’t alter the logo or incorporate the logo or symbol into your logo“34. Ersetze ihres also vollständig durch dein eigenes, nie durch eine umgefärbte oder überarbeitete Version davon, und halte Proxmox aus dem Namen deines Produkts heraus.
Dein Name darauf heißt deine Wartung
Eine VM zu branden ist billig. Eine Handvoll SMBIOS-Strings, ein JPEG, eine Bitmap und drei Bilder in einer Weboberfläche: die Arbeit eines Nachmittags, das meiste davon damit verbracht, herauszufinden, wo die Dinge liegen.
Es zu halten ist die eigentliche Arbeit. Es ist auch der Teil, der ausgelassen wird.
Die SMBIOS-Strings und der Splash kümmern sich um sich selbst, weil sie in Konfiguration leben, die kein Paket je anfasst, und das Logo der Weboberfläche kümmert sich um sich selbst, weil dpkg davon weiß. Die Firmware nicht. An dem Tag, an dem du dein Logo in ein Firmware-Image setzt, hast du ein Stück vom Release-Prozess eines anderen übernommen. Proxmox bauen, testen und liefern neue Firmware mit Sicherheitskorrekturen, die ihre Kunden mit dem nächsten Upgrade bekommen, und deine bekommen sie, wenn du dazu kommst, neu zu bauen.
Dieser Tausch ist in Ordnung, wenn man ihn wissentlich macht. Ein Hoster, dessen Gäste Firmware booten, die zwei Releases zurückliegt, weil das Logo wichtiger war als das Changelog, hat kein Produkt gebaut. Er hat einen Aufkleber gebaut.
Wenn dein Name auf dem Bootbildschirm steht, ist die Firmware dahinter deine, um sie aktuell zu halten, egal wer sie geschrieben hat. Niemand wird es prüfen. Genau deshalb muss es gemacht werden.
UEFI Forum, ACPI Specification 6.6, §5.2.23 Boot Graphics Resource Table — „The Boot Graphics Resource Table (BGRT) is an optional table that provides a mechanism to indicate that an image was drawn on the screen during boot“. Archivierte Kopie; die Live-Seite antwortet automatisierten Anfragen mit 403. ↩︎
Microsoft Learn, Boot screen components — „This is the standard interface that Windows uses to access the logo.“ ↩︎
Fedora Project, Changes/FlickerFreeBoot — „a new plymouth theme which incorporates the firmware’s bootsplash image“; Fedoras plymouth.spec macht
bgrtzum Standard-Theme. ↩︎Debian, dpkg-divert(1), trixie — „File diversions are a way of forcing dpkg(1) not to install a file into its location, but to a diverted location.“ ↩︎
DMTF, DSP0134 System Management BIOS Reference Specification 3.10.0 — Typ 1 System Information, Typ 2 Mainboard, Typ 3 „the system’s mechanical enclosure(s)“, Typ 11 „free-form strings defined by the OEM“. ↩︎
Proxmox VE, qm.conf(5) —
smbios1: „Specify SMBIOS type 1 fields“;args: „Arbitrary arguments passed to kvm … this option is for experts only.“ ↩︎ ↩︎qemu-server,
src/PVE/QemuServer.pm, das Format vonsmbios1und der Aufbau der Befehlszeile — jedes Feld außeruuidhat das Muster[A-Za-z0-9+\/]+={0,2}; die Werte werden dekodiert, wennbase64gesetzt ist. ↩︎pve-manager,
www/manager6/Parser.js,printQemuSmbios1— „smbios values can be arbitrary, so encode and mark config as such“. ↩︎qemu-server,
src/PVE/API2/Qemu.pm, Klonen — „auto generate a new uuid“, die anderensmbios1-Felder bleiben erhalten. ↩︎ ↩︎QEMU, System Emulation, Invocation —
-smbios-Felder pro Typ;-acpitable„For file=, take whole ACPI table from the specified files, including all ACPI headers“;-boot„Currently Seabios for X86 system support it.“ ↩︎ ↩︎ ↩︎edk2-stable202608,
OvmfPkg/SmbiosPlatformDxe/SmbiosPlatformDxe.c— OVMF fügt seinen eigenen Typ 0 nur hinzu, wennNeedSmbiosType0nach dem Durchlauf durch die Tabellen von QEMU noch gesetzt ist. ↩︎qemu-server,
src/PVE/QemuServer.pm, eigene Argumente —argswerden aufgeteilt und ans Ende der Befehlszeile gehängt. ↩︎qemu-server,
src/PVE/API2/Qemu.pm, Rechteprüfung der Konfiguration —smbios1ist eine Hardware-Option und brauchtVM.Config.HWType; der Auffangzweig „catches args, lock, etc.“ und bricht mit „only root can set“ ab. ↩︎qemu-server,
src/PVE/QemuServer.pm, die Optionname—format => 'dns-name'. ↩︎qemu-server,
src/PVE/QemuServer.pm,vmgenid— „notify the guest operating system when the virtual machine is executed with a different configuration (e.g. snapshot execution or creation from a template)“. ↩︎qemu-server,
src/PVE/QemuServer/Memory.pm— Speicherdefault => 512;coresundsocketshaben inQemuServer.pmdie Vorgabe 1. ↩︎qemu-server,
src/PVE/QemuServer.pm,vm_start—lock_configin Zeile 5457,exec_hookscript($conf, $vmid, 'pre-start', 1)in 5581 undconfig_to_commandin 5634 mit demselben$conf, dazwischen nicht neu geladen. ↩︎qemu-server,
src/PVE/QemuServer.pm,-boot—menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg. ↩︎edk2-stable202608,
OvmfPkg/Library/QemuBootOrderLib/QemuBootOrderLib.c— OVMF liestetc/boot-menu-wait, den Wert vonsplash-time, und sonst nichts aus-boot. ↩︎pve-cluster,
src/pmxcfs/memdb.h—#define MEMDB_MAX_FILE_SIZE (1024 * 1024) // 1 MiB. ↩︎ ↩︎ ↩︎SeaBIOS,
src/jpeg.c—PICschreibt auf Little-Endian Blau zuerst,PIC_32in Zeile 946 schreibt Rot zuerst, ohne Endian-Zweig;ERR_NOT_SEQUENTIAL_DCTundERR_NOT_YCBCR_221111sind die einzigen abgelehnten Formate. ↩︎ ↩︎SeaBIOS,
vgasrc/svgamodes.c— Modus0x111ist 640×480 mit 16 Bit,0x142ist 640×480 mit 32. ↩︎pve-edk2-firmware,
debian/rulesbei 4.2026.08-1 —cp -a debian/Logo.bmp MdeModulePkg/Logo/Logo.bmp, der StringPcdFirmwareVendorund die Build-Flags für OVMF. ↩︎ ↩︎ ↩︎qemu-server,
src/PVE/QemuServer/OVMF.pm— die fest kodierte Firmware-Tabelle unter/usr/share/pve-edk2-firmware/, die Wahl von SMM und der Blockknotenpflash0. ↩︎ ↩︎ ↩︎pve-edk2-firmware,
debian/changelog— 4.2026.08-1: „Besides many bug and security fixes … fix CVE-2024-13745“. ↩︎Debian, apt.conf(5), trixie — „Pre-Invoke, Post-Invoke: This is a list of shell commands to run before/after invoking dpkg(1) … should any fail APT will abort.“ ↩︎ ↩︎
QEMU v11.0.3,
hw/pci/pci.c—DEFINE_PROP_STRING("romfile", PCIDevice, romfile), eine Eigenschaft, die jedes PCI-Gerät hat. ↩︎edk2-stable202608,
OvmfPkg/OvmfPkgIa32X64.dsc—gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00, die Plattformdatei, die Proxmox baut. ↩︎edk2-stable202608,
OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c—BootLogoEnableLogo (), aufgerufen ausPlatformBootManagerAfterConsole. ↩︎edk2-stable202608,
BootGraphicsResourceTableDxe.c—SetBootLogo2in Zeile 239 kopiert das Bild; der ReadyToBoot-Handler in 417 deinstalliert die Tabelle und installiert sie neu, „If BGRT data change happens“. ↩︎proxmox-widget-toolkit,
src/Logo.js—proxmoxLogoSvg, 200×35,alt: 'Proxmox', verlinkt auf proxmox.com; verwendet aus pve-managerWorkspace.jsmitprefix: 'pwt'. ↩︎ ↩︎Proxmox VE wiki, FAQ — „Proxmox VE code is licensed under the GNU Affero General Public License, version 3.“ ↩︎
GNU Affero General Public License v3, §13 Remote Network Interaction — im Text oben vollständig zitiert. ↩︎
Proxmox, Media kit — „Don’t alter the logo or incorporate the logo or symbol into your logo.“ ↩︎