Wat je klant ziet als je VM opstart

Je verkoopt virtuele machines onder je eigen naam. Een klant zet er een aan, en het eerste wat op het scherm verschijnt is het logo van een ander.

Dat is een standaard Proxmox-VM. Er is niets kapot, en Proxmox doet niets verkeerd: het is hun product en hun naam, zij schreven de firmware en de interface waarin het verschijnt, en ze hebben het recht het daar te zetten, zoals een serverleverancier zijn badge op de voorkant van de kast zet. Het is alleen niet van jou.

Het logo is pas het begin. Dit meldt een standaard UEFI-gast op de huidige firmware van Proxmox, pve-edk2-firmware 4.2026.08-1, gelezen vanuit de gast met dmidecode:

Waar de gast kijktWat hij leestWiens naam
Opstartschermhet logo van ProxmoxProxmox
Firmwareleverancier (SMBIOS type 0)Proxmox distribution of EDK IIProxmox
Firmwareversie4.2026.08-1het pakketversienummer van Proxmox
Systeemfabrikant (type 1)QEMUQEMU
Productnaam (type 1)Standard PC (Q35 + ICH9, 2009)QEMU
Fabrikant van de behuizing (type 3)QEMUQEMU
Moederbord (type 2)helemaal niet aanwezigniemand
Webinterface voor beheerhet logo van Proxmox, linksbovenProxmox

Het opstartlogo stopt ook niet bij de firmware. UEFI-firmware geeft het beeld dat ze tekende door aan het besturingssysteem via een ACPI-tabel die BGRT heet, de Boot Graphics Resource Table, die bestaat om te zeggen “an image was drawn on the screen during boot”1, en het besturingssysteem tekent het dan opnieuw op zijn eigen opstartscherm. De Fisher-Price OS (Windows) zet het boven zijn draaiende bolletjes, en Microsoft noemt BGRT “the standard interface that Windows uses to access the logo”2. Het standaard Plymouth-thema van Fedora doet op Linux hetzelfde3. Een gast op de firmware van Proxmox toont het logo van Proxmox dus twee keer voordat iemand heeft ingelogd.

Niets hiervan is moeilijk te veranderen. Het veranderd houden wel. De volgende apt full-upgrade zet de helft stilletjes terug, en de helft die hij niet terugzet is de helft waar je je zorgen over zou moeten maken, en daar gaat het grootste deel van deze post over.

Waar elk stuk branding de opstart binnenkomtAanzettenQEMU bouwtSMBIOS en ACPIuit de configFirmwareOVMF tekent zijningebouwde logo,SeaBIOS een splashOverdrachtSMBIOS-tabellen,ACPI met BGRTen MSDMOpstartscherm OStekent hetfirmwarelogoopnieuw uit BGRTDraaiende gastleest de strings,activering leestde MSDM-sleutelVM-configpakketbestandVM-configpakketbestandVM-configstaat in /etc/pve, overleeft elke upgradestaat onder /usr/share, apt vervangt het
Je merk komt op vijf plekken de opstart binnen. Drie komen uit de eigen config van de VM en overleven alles wat apt doet. Twee komen uit bestanden van een Proxmox-pakket, en die neemt een upgrade terug.

De MSDM-tabel in die overdracht is helemaal geen branding. Hij draagt een licentiesleutel, en die naar een VM verhuizen heeft een eigen post: Een OEM-licentie naar een Proxmox-VM verhuizen.

Alles onder /usr/share is van apt

Eén regel beslist elke keuze hieronder. Een bestand dat een pakket installeerde, is het bestand van het pakket. Bewerk het ter plekke en de volgende upgrade van dat pakket overschrijft je wijziging zonder een woord, want voor dpkg zet het alleen zijn eigen bestand terug waar het het had neergelegd.

Elk stuk branding moet dus ergens staan dat apt niet bezit. Een Proxmox-node heeft drie zulke plekken, en elk kost iets anders:

Waar het staatHoe het daar komtOverleeft een upgradeWat het je kost
De VM-config in /etc/pvesmbios1, en args voor al het andereJa, config wordt nooit door een pakket aangeraaktargs is alleen voor root, en staat niet in de GUI
Een bestand waarvan dpkg is verteld het met rust te latendpkg-divertJa, de kopie van het pakket gaat in plaats daarvan naar een .distrib-naamUpgrades bereiken het bestand niet meer, en dat telt als het bestand firmware is
Je eigen map, buiten de pakketboom/usr/local, aangeroepen vanuit argsJa, geen pakket bezit hetJe moet het zelf op elke node zetten

De eigen beschrijving van Debian van een diversion is de duidelijkste: “a way of forcing dpkg not to install a file into its location, but to a diverted location”4. Dat dekt de webinterface. Voor de firmware is het een van twee opties, en de gevaarlijkste.

De behuizing is een handvol strings en een rechtencontrole

SMBIOS is de tabel waarmee een machine zichzelf beschrijft: wie haar maakte, welk model het is, het serienummer, wat het moederbord en de kast zijn5. dmidecode leest hem, elk inventarisgereedschap dat je ooit op een netwerk hebt gericht leest hem, het systeeminformatiepaneel van de Fisher-Price OS (Windows) leest hem, en dat doen ook de meeste licentiecontroles die beslissen of een stuk software op een bepaalde machine überhaupt draait. Op een VM schrijft QEMU hem. Vandaar QEMU en Standard PC in de tabel hierboven.

Type 1 via smbios1

Proxmox stelt één SMBIOS-structuur beschikbaar in de VM-config: type 1, System Information, als smbios16. Die neemt manufacturer, product, version, serial, sku, family en uuid.

Het addertje zit in qemu-server, niet in de documentatie. Elk veld behalve uuid moet voldoen aan een base64-patroon, [A-Za-z0-9+\/]+={0,2}, dus een gewone waarde met een spatie erin wordt meteen geweigerd. Echte strings worden base64-gecodeerd met base64=1 gezet, en qemu-server decodeert ze weer voordat hij de QEMU-opdrachtregel bouwt7. De SMBIOS-editor in de GUI doet dit bij elke opslag, met het commentaar “smbios values can be arbitrary, so encode and mark config as such”8. Op de opdrachtregel is het jouw werk:

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')"

Let op de uuid die er weer in gaat. Het is de identiteit van de gastmachine, en wat je aan --smbios1 meegeeft wordt als de hele optie opgeslagen, dus lees hem eerst en houd hem.

Geef de template je merk, niet elke VM. Een Proxmox-kloon krijgt een vers gegenereerde UUID en houdt elk ander smbios1-veld, dus elke kloon komt eruit met een eigen identiteit en jouw strings9, met één uitzondering, hieronder.

Types 0, 2, 3 en 11 via args

smbios1 stopt bij type 1. De firmwareleverancier, het moederbord, de behuizing en de OEM-strings gaan allemaal via args, de regel die Proxmox rechtstreeks aan QEMU doorgeeft en die de eigen documentatie “for experts only” noemt6. De -smbios-optie van QEMU neemt de velden per type10:

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'

Opgestart op QEMU met die regels leest dmidecode in de gast:

StructuurVeldStandaardMet je merk
Type 0VendorProxmox distribution of EDK IIExample Cloud Ltd
Type 0Version4.2026.08-1EC-FW 1.0
Type 1ManufacturerQEMUExample Cloud Ltd
Type 1Product NameStandard PC (Q35 + ICH9, 2009)EC Compute Instance
Type 1Familyniet opgegevenGeneral Purpose
Type 2Manufacturerstructuur ontbreektExample Cloud Ltd
Type 2Product Namestructuur ontbreektEC Virtual Board
Type 3ManufacturerQEMUExample Cloud Ltd
Type 3Asset Tagniet opgegevenEC-ASSET-123
Type 11String 1structuur ontbreektexample-cloud:instance=123

Onderweg kwamen vier valkuilen boven. Alle vier zijn stil.

Type 0 meegeven laat “UEFI is supported” vallen. OVMF schrijft zijn eigen type 0 alleen als QEMU er geen heeft meegegeven; de lus die de tabellen van QEMU afloopt zet NeedSmbiosType0 = FALSE zodra hij een type 0 tegenkomt11. Jouw vendorstring vervangt die van Proxmox, en daar gaat het om. Maar de type 0 van QEMU vervangt ook de firmwarekenmerken van OVMF, en zonder uefi=on verdwijnt de regel “UEFI is supported” uit dmidecode. Zet hem terug met uefi=on. Voor UEFI-gasten is er hoe dan ook een betere plek voor de vendorstring, in de firmware zelf, verderop.

Het type behuizing is niet in te stellen. De type 3 van QEMU neemt manufacturer, version, serial, asset en sku, en verder niets10. Het blijft Other.

Twee definities van één type worden samengevoegd, en de laatste wint. qemu-server zet zijn -smbios type=1 vroeg op de opdrachtregel en jouw args helemaal aan het eind12, dus een tweede -smbios type=1,manufacturer=… in args gooit de eerste niet weg: QEMU voegde ze veld voor veld samen, de fabrikant kwam uit args, en de UUID bleef waar smbios1 hem zette. Dat telt voor de vierde valkuil.

De twee helften hebben verschillende eigenaars. qemu-server controleert rechten optie voor optie. smbios1 hoort bij de hardwareopties en vraagt VM.Config.HWType, terwijl args doorvalt naar de vangnetregel onderaan, die zegt “only root can set”13. Het moederbord, de behuizing, de firmwareleverancier en de OEM-strings zijn alleen van jou. Type 1 niet. Iedereen aan wie je hardwarerechten hebt gegeven kan hem herschrijven, en bij een gehost product kan dat best de klant zijn. Telt de fabrikantstring, zet hem dan ook in args. De laatste definitie wint.

Het serienummer staat al in de config

Het meeste wat in type 1 thuishoort, staat al in de VM-config. De naam is een goed serienummer, want qemu-server accepteert daar alleen een DNS-naam14: kort, afdrukbaar, nooit een komma erin, en het ene aan de VM dat een klant herkent.

Veld in type 1Komt uitVoor een VM met 4 cores en 16 GiB, web-01 genoemd, getagd production;web
Serial Numbernameweb-01
SKU Numbercores × sockets, en memoryec-4c16g
Familyde eerste waarde in tagsproduction
UUIDde smbios1-UUID die hij al heeftongewijzigd
Manufacturer, Productje eigen vaste stringsExample Cloud Ltd, EC Compute Instance

Twee dingen blijven erbuiten. De node verandert bij elke migratie, en vmgenid is bedoeld om te veranderen: zijn hele taak is de gast te vertellen dat hij uit een snapshot is teruggezet of uit een template is gebouwd15. Geen van beide is een identiteit.

#!/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"

De standaardwaarden zijn die van qemu-server zelf, één core, één socket en 512 MiB16, dus een config die ze weglaat krijgt toch een kloppende SKU. Gedraaid tegen de config in de tabel, met qm vervangen door een script dat hem serveerde, ging de regel die het schreef naar QEMU, gedecodeerd zoals qemu-server decodeert, en dmidecode in de gast las web-01, ec-4c16g, production en de UUID die hij al had terug. Een VM zonder naam wordt geweigerd in plaats van een leeg serienummer te krijgen.

De voor de hand liggende plek hiervoor is een hookscript. Het is de verkeerde. qemu-server draait de pre-start-hook binnen de configlock die hij nam om de VM te starten, en bouwt de QEMU-opdrachtregel uit de config die hij laadde voordat de hook draaide17. Een hook die het serienummer herschrijft, verandert de volgende start, niet deze.

Draai het dus vanuit je provisioning, op de vier momenten dat de invoer verandert: aanmaken, klonen, hernoemen en vergroten. Klonen is degene die bijt. Een kloon houdt elk smbios1-veld behalve de UUID9, dus sla je het over, dan meldt elke VM die uit web-template is gebouwd de naam van de template als serienummer.

Het opstartlogo woont op twee verschillende plekken

Welk logo een gast toont hangt af van zijn firmware. De twee firmwares die Proxmox meelevert halen het van totaal verschillende plekken:

SeaBIOS (bios: seabios, de standaard)OVMF (bios: ovmf, UEFI)
Waar het logo vandaan komteen JPEG die QEMU bij het opstarten overhandigteen bitmap die in de firmware is gecompileerd
Het bestand van Proxmox/usr/share/qemu-server/bootsplash.jpg, 640×480Logo.bmp, 400×120, 8 bit, ingebouwd in OVMF_CODE_4M*.fd
Eigenaar-pakketqemu-serverpve-edk2-firmware-ovmf
Per VM aanpassenargs: -boot splash=…args die de VM naar andere firmware wijst
Aanpassen zonder iets te herbouwenjanee
Bereikt het gast-OS via BGRTneeja

SeaBIOS: een JPEG op de opdrachtregel

qemu-server zet dezelfde -boot-optie op elke VM die hij start, menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg18. De documentatie van QEMU zegt dat de afbeelding wordt getoond “when option splash=sp_name is given and menu=on, If firmware/BIOS supports them. Currently Seabios for X86 system support it”10. OVMF neemt er niets uit behalve splash-time, dat het als time-out voor het opstartmenu gebruikt, en nergens in OvmfPkg wordt naar het splashbestand verwezen19.

Vervangen is één regel in de VM-config:

args: -boot splash=/etc/pve/branding/splash.jpg

Een tweede -boot vecht niet met de eerste. QEMU voegt hem samen op dezelfde manier als -smbios, de latere waarde wint, en met twee splashbestanden opgegeven toonde de schermafbeelding de tweede. /etc/pve is de juiste plek voor het bestand, want het is het clusterbestandssysteem en elke node ziet dezelfde splash, en de limiet van 1 MiB per bestand komt bij lange na niet in de buurt van een JPEG van 640×48020.

De afbeelding zelf moet aan drie voorwaarden voldoen, en mis je er een, dan kost dat je het logo of de kleuren ervan:

VoorwaardeWaaromWat er anders gebeurt
JPEG of 24-bit BMPQEMU controleert het bestand voordat de VM startsplash file … format not recognized; must be JPEG or 24 bit BMP
Baseline JPEG, 4:2:0-chromade decoder van SeaBIOS kan niets anders aan21ERR_NOT_SEQUENTIAL_DCT of ERR_NOT_YCBCR_221111, en een leeg scherm
640×480, net als die van ProxmoxSeaBIOS vraagt de VGA-BIOS om een modus van precies de grootte van de afbeeldinggeen passende modus, geen splash

De meeste beeldbewerkers schrijven standaard baseline 4:2:0, dus de gebruikelijke manier om het logo kwijt te raken is een van die “opslaan voor web”-opties die stilletjes progressieve codering aanzet, wat er in elke beeldviewer die je hebt hetzelfde uitziet en SeaBIOS helemaal niets laat tekenen.

De kleurenval

Deze kostte een middag. Dezelfde JPEG, getekend door twee builds van SeaBIOS 1.17.0, komt in twee verschillende kleuren uit:

Een Example Cloud-splash drie keer: de bron-JPEG met een blauwe logotegel, SeaBIOS zoals Proxmox het meelevert met hetzelfde blauw, en een SeaBIOS-build die op 32 bits per pixel draait met het blauw goud geworden

Het zit in jpeg.c van SeaBIOS. De schrijver voor 24 bits per pixel heeft een little-endian-tak die blauw in de eerste byte van elke pixel zet, en de schrijver voor 32 bits per pixel, PIC_32, heeft zo’n tak niet en schrijft daar rood21. Op een little-endian-framebuffer verwisselt dat rood en blauw. Blauw komt eruit als goud, en oranje zou eruit komen als blauw.

Welke schrijver draait, hangt af van de videomodus die de VGA-BIOS aanbiedt:

FirmwareVBE-modus voor 640×480Bits per pixelKleuren
De vooraf gebouwde SeaBIOS 1.17.0 van QEMU upstream, zoals pve-qemu-kvm 11.0.3-4 hem meelevert0x11116juist, gekwantiseerd tot 65.536
De eigen seabios 1.17.0-10-build van Fedora 440x14232rood en blauw verwisseld

De modusnummers komen uit de eigen tabel van SeaBIOS22, en de rij voor Proxmox is gecontroleerd tegen precies de blobs in het pakket: byte voor byte gelijk aan de vooraf gebouwde rel-1.17.0-0-gb52ca86e094d van QEMU. Op Proxmox kloppen je kleuren dus, maar het zijn er maar 65.536, en een subtiel verloop krijgt banden. En komt je logo ooit in de verkeerde kleuren op een andere hypervisor, dan ligt het niet aan je JPEG.

Het UEFI-logo is in de firmware gecompileerd

UEFI-gasten zijn de moeilijkere helft, en op een moderne Proxmox zijn dat de meeste gasten.

Er is geen splashoptie om te overschrijven. Het logo is een bitmap binnen het firmware-image, en de build van Proxmox zet het daar met één regel in debian/rules:

debian/setup-build-stamp:
	cp -a debian/Logo.bmp MdeModulePkg/Logo/Logo.bmp

Dat kopieert hun Logo.bmp van 400×120 en 8 bit over die van TianoCore heen voordat edk2 wordt gebouwd23. Hetzelfde bestand zet de firmwareleverancier als constante tijdens de build, PcdFirmwareVendor=L"Proxmox distribution of EDK II", en daar komt de type 0-vendor in de eerste tabel vandaan23.

Nieuw logo, nieuwe firmware. De manier om er een te krijgen die zich precies gedraagt als die van Proxmox, is hem precies zo te bouwen als Proxmox doet: hun boom op de tag die past bij het pakket dat ze leveren, hun patches, hun vlaggen, en één bitmap verwisseld. pve-edk2-firmware 4.2026.08-1 pint edk2 op 2970e56, en dat is de 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

De vlaggen komen uit debian/rules zoals die op die commit staat. Let op de vendorstring: hun Makefile schrijft hem tussen dubbele aanhalingstekens, de shell van make haalt die weg, en edk2 krijgt hem kaal, dus zo wordt hij hier doorgegeven, en hem een tweede keer quoten is een buildfout. Houd de bitmap op 400×120 en 8 bit zoals die van hen, en niets anders op het opstartscherm verschuift.

Je hebt beide images nodig. qemu-server start elke Q35-VM met een EFI-disk van 4M op OVMF_CODE_4M.secboot.fd, de build met SMM vereist, en gebruikt de gewone OVMF_CODE_4M.fd alleen voor i440fx24. Op een huidige Proxmox draait vrijwel elke UEFI-gast op de secboot-variant.

Zo gebouwd, met niets veranderd aan de boom van Proxmox behalve de bitmap en twee strings, start het SMM-image zo op:

Het opstartscherm van een gast, getekend door het herbouwde OVMF-image, met een Example Cloud-logo waar dat van Proxmox zou staan

En het eigen beeld dat de gast van zijn firmware heeft, verandert mee, zonder één -smbios-optie op de opdrachtregel:

Gelezen in de gast4.2026.08-1 van ProxmoxHerbouwd uit dezelfde boom
FirmwareleverancierProxmox distribution of EDK IIExample Cloud Ltd
Firmwareversie4.2026.08-14.2026.08-1+ec1
“UEFI is supported”vermeldvermeld
ACPI-tabellenBGRT, WSMT en de restdezelfde set

Dat maakt de vendor tijdens de build het betere antwoord voor UEFI-gasten. Het is de eigen type 0 van OVMF, dus het UEFI-bit blijft staan zonder dat iemand aan uefi=on hoeft te denken. De args-route voor type 0 is voor SeaBIOS-gasten, en voor wie helemaal geen firmware herbouwt.

Twee manieren om de nieuwe firmware aan een VM te geven

qemu-server legt vast in de code waar hij firmware zoekt: OVMF.pm heeft een tabel met paden onder /usr/share/pve-edk2-firmware/, op sleutel van machinetype en Secure Boot-opties, en nergens in de VM-config, de GUI of de API is er een optie die een ander bestand kiest24. Er blijven twee routes over.

OVMF met je merk bouwen, en twee manieren om het aan een VM te gevenDe boom van Proxmoxpve-edk2-firmware 4.2026.08-1Jouw merkLogo.bmp + vendorstringZelfde build, zelfde vlaggenOVMF_CODE_4M.fd + .secboot.fdA: divert op elke nodeelke UEFI-VM krijgt het,geen wijziging in VM-configB: per VM via argsVM voor VM aanzetten,bestanden van Proxmox onaangeroerdapt full-upgradenieuwe firmware van Proxmox komt, de jouwe blijft oudPost-Invoke-hookversies verschillen, dus herbouw voor de volgende start
Eén build, twee routes erin. Hoe dan ook installeert de volgende upgrade de nieuwe firmware van Proxmox naast de jouwe en laat de jouwe zoals hij was, en daarom bestaat de hook onderaan.

Route A: divert hem op elke node. Vertel dpkg dat de twee code-images van Proxmox nu ergens anders horen, en zet de jouwe waar zij stonden:

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

Vanaf de volgende start draait elke UEFI-VM op de node jouw firmware. Geen enkele wijziging in de VM-config.

Route B: wijs gekozen VM’s ernaar. Houd de images in je eigen map en overschrijf de firmware VM voor VM. Sinds de overstap op -blockdev koppelt qemu-server het code-image als een blocknode die pflash0 heet en noemt hem in -machine24, en QEMU voegt een tweede -machine samen op dezelfde manier als -boot, dus args kan een eigen node toevoegen en pflash0 daarnaar laten wijzen:

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

Dat is op de lange manier getest. De secboot-firmware van Proxmox werd als pflash0 gekoppeld precies zoals qemu-server het doet, met SMM aan, en daarna werd die args-regel erachter gezet, en de gast startte het image met je merk met zowel het logo als de vendorstring, en daar kwam de schermafbeelding hierboven vandaan.

Route A, divertRoute B, args per VM
Welke VM’s het krijgenelke UEFI-VM op de nodealleen VM’s waarop je het zet
De bestanden van Proxmoxverplaatst naar .distribonaangeroerd
VM-configongewijzigdeen args-regel, alleen voor root
Machinetype moet passengeregeld, beide images zijn gediverteerdjouw werk: secboot-image voor Q35
Terugdraaiendpkg-divert --remove --renamede regel verwijderen
Migratiedoelnode moet ook gediverteerd zijndoelnode moet het bestand hebben

Het code-image is ongeveer 3,5 MB, tegenover een limiet van 1 MiB per bestand in pmxcfs20. Het kan als zodanig niet in /etc/pve, en welke route je ook neemt, het moet op elke node gezet worden. Migreer een VM naar een node zonder en je krijgt een van twee uitkomsten, afhankelijk van de route: onder route A weer de eigen firmware en het logo van Proxmox, of onder route B een VM die helemaal niet start, omdat QEMU een bestand dat er niet is niet kan openen.

De upgrade die je achterlaat

Een diversion is het juiste gereedschap voor een logo. Firmware is anders.

Een diversion betekent dat de upgrades van Proxmox het bestand niet meer bereiken. Dat is precies waar je om vroeg. Het is ook precies wat de beveiligingsfixes van edk2 tegenhoudt voordat ze je gasten bereiken: de changelog van Proxmox voor 4.2026.08-1 opent met “Besides many bug and security fixes” en noemt daarna een fix voor CVE-2024-1374525, en een build met je merk uit de release daarvoor heeft daar niets van. Niets vertelt het je.

Dat is getest, niet aangenomen. In een Debian trixie-container met de repository van Proxmox ging pve-edk2-firmware-ovmf 4.2025.05-3 erin, werden beide code-images gediverteerd, en werd het pakket geüpgraded naar 4.2026.08-1:

Na de upgradeResultaat
dpkg-query -W pve-edk2-firmware-ovmf4.2026.08-1
Het nieuwe image van Proxmoxgeïnstalleerd als OVMF_CODE_4M.fd.distrib, hash veranderd
Het image met je merkongewijzigd, nog steeds gebouwd uit 4.2025.05-3
Iets op de console eroverniets, tot de hook hieronder

Voeg dus een hook toe. apt draait na elke dpkg-run een lijst DPkg::Post-Invoke-opdrachten26. Leg vast uit welke Proxmox-versie je bouwde, en vergelijk na elke run:

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

Bij diezelfde upgrade drukte hij af:

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.

Hij eindigt bewust met 0. apt breekt af als een Post-Invoke-opdracht faalt26, en een brandingcontrole is geen reden om een node half geüpgraded te laten staan. Een luide waarschuwing volstaat.

Nog één ding. Herstarts. Een gast pakt nieuwe firmware pas op als zijn QEMU-proces herstart, dus een herbouw betekent stoppen en starten vanuit Proxmox, niet een reboot vanuit de gast, en zo bereiken de eigen firmware-updates van Proxmox een draaiende VM ook. Alleen hoef jij daar niet eerst aan een herbouw te denken.

Of laat de firmware van Proxmox met rust

Elke logoroute tot nu toe verandert de firmware, en de vorige sectie is de rekening daarvoor. Er is er één die dat niet doet, en die is voor deze post als prototype gebouwd.

Een PCI-apparaat in QEMU kan een option ROM dragen, elk bestand dat je met romfile= noemt27, en OVMF draait de EFI-driver die het daarin vindt. De build van Proxmox draait hem of hij nu ondertekend is of niet: OVMF zet PcdOptionRomImageVerificationPolicy op 0x00, altijd uitvoeren28. OVMF tekent zijn eigen logo laat in de selectie van het opstartapparaat29 en bouwt de BGRT pas bij ReadyToBoot, het moment voordat het aan een bootloader overdraagt. En de BGRT-driver van edk2 neemt een vervangende afbeelding aan via EDKII_BOOT_LOGO2_PROTOCOL, en bouwt de tabel bij ReadyToBoot opnieuw zodra de afbeelding is veranderd30.

Meer is er niet nodig. De driver wacht op ReadyToBoot op TPL_NOTIFY, dat vóór de eigen TPL_CALLBACK-handler van de BGRT-driver draait, maakt het scherm leeg, tekent zijn logo en geeft dezelfde afbeelding door. Gebouwd is het één bestand van 8 KB, gekoppeld met één regel:

args: -device pci-testdev,romfile=/etc/pve/branding/brandrom.rom

Met 8 KB zit het ruim binnen de limiet van 1 MiB van pmxcfs20, dus anders dan een firmware-image van 3,5 MB kan het in /etc/pve staan en de VM naar elke node volgen.

Het is getest tegen de eigen firmware van Proxmox, rechtstreeks uit pve-edk2-firmware-ovmf 4.2026.08-1: OVMF_CODE_4M.secboot.fd met de sleutels van Microsoft vooraf ingeschreven in OVMF_VARS_4M.ms.fd, op Q35 met SMM, en opgestart via de ondertekende shim van Fedora zodat Secure Boot aan stond en werd afgedwongen:

Gelezen uit de gastZonder de ROMMet de ROM
SecureBoot-variabele11
Op het schermhet logo van Proxmoxdat van Proxmox ongeveer 250 ms, daarna het jouwe
BGRT-afbeelding, 400×120 op 440,340die van Proxmox, SHA-256 be5afe4b…die van de ROM, SHA-256 6c494576…
Firmwarebestanden veranderdgeengeen

De afbeelding die een gast met de option ROM gekoppeld uit zijn BGRT-tabel teruglas: een Example Cloud-logo, met het onderschrift drawn by an option ROM

De laatste rij is waar het om gaat. De firmware van Proxmox is onaangeroerd, dus hun volgende beveiligingsrelease bereikt je gasten bij de volgende herstart, en niets uit de vorige sectie is van toepassing.

Gratis is het niet:

KostenWaarom
Het logo van Proxmox staat ongeveer een kwart seconde in beeldOVMF tekent het voordat een option-ROM-driver het scherm krijgt; alleen een herbouw voorkomt dat
De gast ziet één PCI-apparaat meer, zonder driverde ROM moet op een apparaat meerijden, en pci-testdev is het doe-niets-apparaat van QEMU
Type 0 zegt nog steeds Proxmoxde vendorstring is ingecompileerd; zet hem via args met uefi=on, zoals hierboven
Alleen voor roothet is een args-regel

En de bevinding eronder moet gewoon gezegd worden. Een niet-ondertekende driver draaide in firmware op een gast met de sleutels van Microsoft ingeschreven en Secure Boot afgedwongen, omdat de OVMF van Proxmox option ROM’s niet controleert. Hier is dat handig. Het betekent ook dat Secure Boot op deze firmware bootloaders controleert, niet wat de hardwareconfig van de VM ook maar koppelt, en omdat alleen root args schrijft, is dat een vraag over wie root heeft op je nodes.

Het is een prototype. Het heeft gedraaid op QEMU 10.2.2 met de firmware van Proxmox, nog niet op een Proxmox-node, en wat volgt is broncode, geen product:

github.com/damo2929/RebrandPCIRom: de option-ROM-driver, de logoconverter en de containerbuild.

De webinterface is twee pakketten

Het logo linksboven in de webinterface zit niet in pve-manager. Workspace.js vraagt om een component proxmoxLogoSvg met het prefix pwt, en dat woont in proxmox-widget-toolkit, dat de Backup Server en de Mail Gateway delen31. Alleen de tabbladiconen zitten in pve-manager:

WatBestandPakket
Headerlogo, getekend op 200×35/usr/share/javascript/proxmox-widget-toolkit/images/proxmox_logo.svgproxmox-widget-toolkit
Icoon in het browsertabblad/usr/share/pve-manager/images/favicon.icopve-manager
Icoon van 128×128, ook het touch-icoon/usr/share/pve-manager/images/logo-128.pngpve-manager

Alle drie zijn gewone bestanden, dus dit is een klus voor dpkg-divert, en hier geldt de prijs uit de vorige sectie helemaal niet, want een logo heeft geen beveiligingsfixes om te missen en niemands gast is ook maar iets minder veilig door een node die nog het favicon van vorige maand serveert.

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

Getest in dezelfde container, tegen echte pakketten:

StapResultaat
Divert op proxmox-widget-toolkit 5.2.9, eigen SVG installerende SVG van Proxmox hernoemd naar proxmox_logo.svg.distrib
Upgrade naar 5.2.10eigen SVG nog op zijn plek, de 5.2.10-kopie van Proxmox in .distrib
dpkg --verify proxmox-widget-toolkitgeen klachten, dpkg weet van de diversion
apt-get install --reinstall proxmox-widget-toolkiteigen SVG nog op zijn plek
dpkg-divert --remove --renamehet origineel van Proxmox terug, byte voor byte

Twee dingen blijven van Proxmox. De headerafbeelding wordt in een vast vak van 200×35 getekend, dus teken je SVG in die vorm of hij wordt platgedrukt. En de alt-tekst zegt Proxmox en de afbeelding linkt naar https://www.proxmox.com, allebei in het JavaScript-component geschreven en niet in een bestand dat je kunt diverten31. Die veranderen betekent een geminificeerde bundel patchen die bij elke upgrade breekt. Niet de moeite waard voor alt-tekst.

Wat de licentie en het handelsmerk van Proxmox van je vragen

Proxmox VE valt onder de AGPL versie 332, en sectie 13 van die licentie is de netwerkclausule: “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. Telt het verwisselen van drie afbeeldingen als het aanpassen van het Programma? Dat is iets voor een jurist. Het goedkope antwoord is je afbeeldingen en het divert-script ergens openbaar neer te zetten en ernaar te linken vanaf de inlogpagina. Het kost niks en het beslecht de vraag.

De kant van het handelsmerk is duidelijker. De mediakit van Proxmox zegt “Don’t alter the logo or incorporate the logo or symbol into your logo”34. Vervang dat van hen dus volledig door je eigen, nooit door een herkleurde of bewerkte versie ervan, en houd Proxmox uit de naam van je product.

Jouw naam erop betekent jouw onderhoud

Je merk op een VM zetten is goedkoop. Een handvol SMBIOS-strings, een JPEG, een bitmap en drie afbeeldingen in een webinterface: een middag werk, het meeste daarvan kwijt aan uitzoeken waar dingen wonen.

Het houden is het werk. Het is ook het deel dat wordt overgeslagen.

De SMBIOS-strings en de splash zorgen voor zichzelf, omdat ze in config staan die geen pakket ooit aanraakt, en het logo van de webinterface zorgt voor zichzelf omdat dpkg erover is ingelicht. De firmware niet. De dag dat je je logo in een firmware-image zette, nam je een stuk van het releaseproces van een ander over, en Proxmox bouwt, test en levert nieuwe firmware met beveiligingsfixes erin, die hun klanten krijgen bij de volgende upgrade, en die van jou als jij eraan toekomt om te herbouwen.

Die ruil is prima als je hem bewust maakt. Een hostingbedrijf waarvan de gasten firmware draaien die twee releases achterloopt omdat het logo belangrijker was dan de changelog, heeft geen product gebouwd. Het heeft een sticker gebouwd.

Staat jouw naam op het opstartscherm, dan is de firmware erachter de jouwe om bij te houden, wie hem ook schreef. Niemand gaat het controleren. Juist daarom moet het gebeuren.


  1. 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”. Gearchiveerde kopie; de live pagina geeft 403 op geautomatiseerde verzoeken. ↩︎

  2. Microsoft Learn, Boot screen components — “This is the standard interface that Windows uses to access the logo.” ↩︎

  3. Fedora Project, Changes/FlickerFreeBoot — “a new plymouth theme which incorporates the firmware’s bootsplash image”; de plymouth.spec van Fedora maakt bgrt het standaardthema. ↩︎

  4. 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.” ↩︎

  5. DMTF, DSP0134 System Management BIOS Reference Specification 3.10.0 — type 1 System Information, type 2 moederbord, type 3 “the system’s mechanical enclosure(s)”, type 11 “free-form strings defined by the OEM”. ↩︎

  6. Proxmox VE, qm.conf(5) — smbios1: “Specify SMBIOS type 1 fields”; args: “Arbitrary arguments passed to kvm … this option is for experts only.” ↩︎ ↩︎

  7. qemu-server, src/PVE/QemuServer.pm, het smbios1-formaat en de opdrachtregelbouwer — elk veld behalve uuid heeft het patroon [A-Za-z0-9+\/]+={0,2}; waarden worden gedecodeerd als base64 is gezet. ↩︎

  8. pve-manager, www/manager6/Parser.js, printQemuSmbios1 — “smbios values can be arbitrary, so encode and mark config as such”. ↩︎

  9. qemu-server, src/PVE/API2/Qemu.pm, klonen — “auto generate a new uuid”, de andere smbios1-velden blijven behouden. ↩︎ ↩︎

  10. QEMU, System Emulation, Invocation — -smbios-velden per type; -acpitable “For file=, take whole ACPI table from the specified files, including all ACPI headers”; -boot “Currently Seabios for X86 system support it.” ↩︎ ↩︎ ↩︎

  11. edk2-stable202608, OvmfPkg/SmbiosPlatformDxe/SmbiosPlatformDxe.c — OVMF voegt zijn eigen type 0 alleen toe als NeedSmbiosType0 na het aflopen van de tabellen van QEMU nog gezet is. ↩︎

  12. qemu-server, src/PVE/QemuServer.pm, eigen args — args worden gesplitst en achteraan op de opdrachtregel gezet. ↩︎

  13. qemu-server, src/PVE/API2/Qemu.pm, rechtencontroles op de config — smbios1 is een hardwareoptie die VM.Config.HWType vraagt; het vangnet “catches args, lock, etc.” en stopt met “only root can set”. ↩︎

  14. qemu-server, src/PVE/QemuServer.pm, de optie name — format => 'dns-name'. ↩︎

  15. 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)”. ↩︎

  16. qemu-server, src/PVE/QemuServer/Memory.pm — geheugen default => 512; cores en sockets staan in QemuServer.pm standaard op 1. ↩︎

  17. qemu-server, src/PVE/QemuServer.pm, vm_start — lock_config op regel 5457, exec_hookscript($conf, $vmid, 'pre-start', 1) op 5581, en config_to_command op 5634 met dezelfde $conf, daartussen niet opnieuw geladen. ↩︎

  18. qemu-server, src/PVE/QemuServer.pm, -boot — menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg. ↩︎

  19. edk2-stable202608, OvmfPkg/Library/QemuBootOrderLib/QemuBootOrderLib.c — OVMF leest etc/boot-menu-wait, de waarde van splash-time, en verder niets uit -boot. ↩︎

  20. pve-cluster, src/pmxcfs/memdb.h — #define MEMDB_MAX_FILE_SIZE (1024 * 1024) // 1 MiB. ↩︎ ↩︎ ↩︎

  21. SeaBIOS, src/jpeg.c — PIC schrijft op little-endian eerst blauw, PIC_32 op regel 946 schrijft eerst rood zonder endian-tak; ERR_NOT_SEQUENTIAL_DCT en ERR_NOT_YCBCR_221111 zijn de enige geweigerde formaten. ↩︎ ↩︎

  22. SeaBIOS, vgasrc/svgamodes.c — modus 0x111 is 640×480 op 16 bits, 0x142 is 640×480 op 32. ↩︎

  23. pve-edk2-firmware, debian/rules op 4.2026.08-1 — cp -a debian/Logo.bmp MdeModulePkg/Logo/Logo.bmp, de PcdFirmwareVendor-string en de buildvlaggen van OVMF. ↩︎ ↩︎ ↩︎

  24. qemu-server, src/PVE/QemuServer/OVMF.pm — de vastgelegde firmwaretabel onder /usr/share/pve-edk2-firmware/, de SMM-keuze en de blocknode pflash0. ↩︎ ↩︎ ↩︎

  25. pve-edk2-firmware, debian/changelog — 4.2026.08-1: “Besides many bug and security fixes … fix CVE-2024-13745”. ↩︎

  26. 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.” ↩︎ ↩︎

  27. QEMU v11.0.3, hw/pci/pci.c — DEFINE_PROP_STRING("romfile", PCIDevice, romfile), een eigenschap die elk PCI-apparaat heeft. ↩︎

  28. edk2-stable202608, OvmfPkg/OvmfPkgIa32X64.dsc — gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00, het platformbestand dat Proxmox bouwt. ↩︎

  29. edk2-stable202608, OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c — BootLogoEnableLogo (), aangeroepen vanuit PlatformBootManagerAfterConsole. ↩︎

  30. edk2-stable202608, BootGraphicsResourceTableDxe.c — SetBootLogo2 op regel 239 kopieert de afbeelding; de ReadyToBoot-handler op 417 verwijdert de tabel en installeert hem opnieuw “If BGRT data change happens”. ↩︎

  31. proxmox-widget-toolkit, src/Logo.js — proxmoxLogoSvg, 200×35, alt: 'Proxmox', met een link naar proxmox.com; gebruikt vanuit pve-manager Workspace.js met prefix: 'pwt'. ↩︎ ↩︎

  32. Proxmox VE wiki, FAQ — “Proxmox VE code is licensed under the GNU Affero General Public License, version 3.” ↩︎

  33. GNU Affero General Public License v3, §13 Remote Network Interaction — volledig geciteerd in de tekst hierboven. ↩︎

  34. Proxmox, Media kit — “Don’t alter the logo or incorporate the logo or symbol into your logo.” ↩︎