Lo que ve tu cliente cuando arranca tu VM
Vendes máquinas virtuales con tu propio nombre. Un cliente enciende una, y lo primero que sale en la pantalla es el logo de otro.
Eso es una VM de Proxmox de serie. No hay nada roto, y Proxmox no hace nada mal: es su producto y su nombre, escribieron el firmware y la interfaz donde aparece, y tienen todo el derecho a ponerlo ahí, igual que un fabricante de servidores pone su placa en el frontal de la caja. Solo que no es el tuyo.
El logo es solo el principio.
Esto es lo que declara un invitado UEFI de serie con el firmware actual de Proxmox, pve-edk2-firmware 4.2026.08-1, leído desde dentro del invitado con dmidecode:
| Dónde mira el invitado | Qué lee | De quién es el nombre |
|---|---|---|
| Pantalla de arranque | el logo de Proxmox | Proxmox |
| Fabricante del firmware (SMBIOS tipo 0) | Proxmox distribution of EDK II | Proxmox |
| Versión del firmware | 4.2026.08-1 | la versión del paquete de Proxmox |
| Fabricante del sistema (tipo 1) | QEMU | QEMU |
| Nombre del producto (tipo 1) | Standard PC (Q35 + ICH9, 2009) | QEMU |
| Fabricante del chasis (tipo 3) | QEMU | QEMU |
| Placa base (tipo 2) | no existe | nadie |
| Interfaz web de gestión | el logo de Proxmox, arriba a la izquierda | Proxmox |
Y el logo de arranque no se queda en el firmware. El firmware UEFI pasa la imagen que dibujó al sistema operativo mediante una tabla ACPI llamada BGRT, la Boot Graphics Resource Table, que existe para decir «an image was drawn on the screen during boot»1, y el sistema operativo la vuelve a dibujar en su propia pantalla de arranque. El Fisher-Price OS (Windows) la pone encima de su ruedecita, y Microsoft llama a la BGRT «the standard interface that Windows uses to access the logo»2. El tema de Plymouth por defecto de Fedora hace lo mismo en Linux3. Así que un invitado con el firmware de Proxmox enseña el logo de Proxmox dos veces antes de que nadie haya iniciado sesión.
Nada de esto es difícil de cambiar.
Lo difícil es que siga cambiado.
El próximo apt full-upgrade volverá a poner la mitad sin decir nada, y la mitad que no vuelve a poner es la que debería preocuparte, que es de lo que trata casi todo este artículo.
La tabla MSDM de ese traspaso no tiene nada que ver con la marca. Lleva una clave de licencia, y meter una en una VM tiene su propio artículo: Mover una licencia OEM a una VM de Proxmox.
Todo lo que hay bajo /usr/share es de apt
Una sola regla decide todas las elecciones de abajo. Un fichero que instaló un paquete es fichero del paquete. Edítalo en su sitio y la siguiente actualización de ese paquete machaca tu cambio sin decir palabra, porque para dpkg solo está devolviendo su propio fichero a donde lo dejó.
Así que cada pieza de la marca tiene que vivir en algún sitio que no sea de apt. Un nodo Proxmox tiene tres sitios así, y cada uno cuesta algo distinto:
| Dónde vive | Cómo llega ahí | Sobrevive a una actualización | Lo que te cuesta |
|---|---|---|---|
La config de la VM en /etc/pve | smbios1, y args para todo lo demás | Sí, ningún paquete toca la config | args es solo para root, y no sale en la GUI |
| Un fichero que dpkg tiene orden de dejar en paz | dpkg-divert | Sí, la copia del paquete va a un nombre .distrib | Las actualizaciones ya no llegan al fichero, lo que importa cuando el fichero es firmware |
| Tu propio directorio, fuera del árbol del paquete | /usr/local, referenciado desde args | Sí, no es de ningún paquete | Tienes que ponerlo tú en cada nodo |
La descripción que da Debian de una desviación es la más clara: «a way of forcing dpkg not to install a file into its location, but to a diverted location»4. Eso resuelve la interfaz web. Para el firmware es una de dos opciones, y la más peligrosa.
El chasis es un puñado de cadenas y una comprobación de permisos
SMBIOS es la tabla con la que una máquina se describe a sí misma: quién la hizo, qué modelo es, su número de serie, cómo son la placa y la caja5.
La lee dmidecode, la lee cualquier herramienta de inventario que hayas lanzado nunca contra una red, la lee el panel de información del sistema del Fisher-Price OS (Windows), y también la mayoría de las comprobaciones de licencia que deciden si un programa va a funcionar en una máquina concreta.
En una VM, la escribe QEMU.
De ahí QEMU y Standard PC en la tabla de arriba.
El tipo 1 con smbios1
Proxmox expone una sola estructura SMBIOS en la config de la VM: el tipo 1, System Information, como smbios16.
Acepta manufacturer, product, version, serial, sku, family y uuid.
La pega está en qemu-server, no en la documentación.
Todos los campos salvo uuid tienen que cumplir un patrón base64, [A-Za-z0-9+\/]+={0,2}, así que un valor normal con un espacio se rechaza sin más.
Las cadenas reales se codifican en base64 con base64=1 puesto, y qemu-server las vuelve a decodificar antes de montar la línea de comandos de QEMU7.
El editor SMBIOS de la GUI lo hace en cada guardado, con el comentario «smbios values can be arbitrary, so encode and mark config as such»8.
En la línea de comandos te toca a ti:
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')"
Fíjate en que el uuid vuelve a entrar.
Es la identidad de máquina del invitado, y lo que pases a --smbios1 se guarda como la opción entera, así que léelo primero y consérvalo.
Pon la marca en la plantilla, no en cada VM.
Un clon de Proxmox recibe un UUID recién generado y conserva todos los demás campos de smbios1, así que cada clon sale con su propia identidad y tus cadenas9, con una excepción, más abajo.
Los tipos 0, 2, 3 y 11 con args
smbios1 se queda en el tipo 1.
El fabricante del firmware, la placa base, el chasis y las cadenas OEM van todos por args, la línea que Proxmox pasa tal cual a QEMU, y que su propia documentación llama «for experts only»6.
La opción -smbios de QEMU acepta los campos de cada tipo10:
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'
Arrancado en QEMU con esas líneas, el dmidecode del invitado lee:
| Estructura | Campo | De serie | Con tu marca |
|---|---|---|---|
| Tipo 0 | Vendor | Proxmox distribution of EDK II | Example Cloud Ltd |
| Tipo 0 | Version | 4.2026.08-1 | EC-FW 1.0 |
| Tipo 1 | Manufacturer | QEMU | Example Cloud Ltd |
| Tipo 1 | Product Name | Standard PC (Q35 + ICH9, 2009) | EC Compute Instance |
| Tipo 1 | Family | sin especificar | General Purpose |
| Tipo 2 | Manufacturer | estructura ausente | Example Cloud Ltd |
| Tipo 2 | Product Name | estructura ausente | EC Virtual Board |
| Tipo 3 | Manufacturer | QEMU | Example Cloud Ltd |
| Tipo 3 | Asset Tag | sin especificar | EC-ASSET-123 |
| Tipo 11 | String 1 | estructura ausente | example-cloud:instance=123 |
Por el camino salieron cuatro trampas. Las cuatro son silenciosas.
Dar el tipo 0 quita «UEFI is supported».
OVMF escribe su propio tipo 0 solo cuando QEMU no ha dado uno; el bucle que recorre las tablas de QEMU pone NeedSmbiosType0 = FALSE en cuanto encuentra un tipo 011.
Tu cadena de fabricante sustituye a la de Proxmox, que es de lo que se trata.
Pero el tipo 0 de QEMU sustituye también las características de firmware de OVMF, y sin uefi=on la línea «UEFI is supported» desaparece de dmidecode.
Vuelve a ponerla con uefi=on.
Para los invitados UEFI hay de todas formas un sitio mejor para la cadena de fabricante, dentro del propio firmware, más abajo.
El tipo de chasis no se puede fijar.
El tipo 3 de QEMU acepta manufacturer, version, serial, asset y sku, y nada más10.
Se queda en Other.
Dos definiciones del mismo tipo se fusionan, y gana la última.
qemu-server pone su -smbios type=1 pronto en la línea de comandos y tus args al final del todo12, así que un segundo -smbios type=1,manufacturer=… en args no tira el primero: QEMU los fusionó campo a campo, el fabricante salió de args y el UUID se quedó donde lo puso smbios1.
Eso importa para la cuarta trampa.
Las dos mitades tienen dueños distintos.
qemu-server comprueba los permisos opción por opción.
smbios1 va con las opciones de hardware y necesita VM.Config.HWType, mientras que args cae en el comodín del final, que dice «only root can set»13.
La placa, el chasis, el fabricante del firmware y las cadenas OEM son solo tuyos.
El tipo 1 no.
Cualquiera a quien hayas dado derechos de hardware puede reescribirlo, y en un producto alojado ese bien puede ser el cliente.
Si la cadena del fabricante importa, ponla también en args. Gana la última definición.
El número de serie ya está en la config
Casi todo lo que pertenece al tipo 1 ya está en la config de la VM. El nombre es un buen número de serie, porque qemu-server solo acepta ahí un nombre DNS14: corto, imprimible, nunca con una coma, y lo único de la VM que un cliente va a reconocer.
| Campo del tipo 1 | Sacado de | Para una VM de 4 núcleos y 16 GiB llamada web-01, con etiquetas production;web |
|---|---|---|
| Serial Number | name | web-01 |
| SKU Number | cores × sockets, y memory | ec-4c16g |
| Family | la primera entrada de tags | production |
| UUID | el UUID de smbios1 que ya tiene | sin cambios |
| Manufacturer, Product | tus propias cadenas fijas | Example Cloud Ltd, EC Compute Instance |
Dos cosas se quedan fuera.
El nodo cambia en cada migración, y vmgenid está hecho para cambiar: todo su trabajo es decirle al invitado que lo han restaurado de una instantánea o construido desde una plantilla15.
Ninguno de los dos es una identidad.
#!/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"
Los valores por defecto son los de qemu-server, un núcleo, un socket y 512 MiB16, así que una config que no los pone sigue dando un SKU correcto.
Lanzado contra la config de la tabla, con qm sustituido por un script que la servía, la línea que escribió llegó a QEMU decodificada igual que la decodifica qemu-server, y el dmidecode del invitado devolvió web-01, ec-4c16g, production y el UUID que ya tenía.
Una VM sin nombre se rechaza en vez de recibir un número de serie vacío.
El sitio obvio para esto es un hookscript.
Es el equivocado.
qemu-server lanza el hook pre-start dentro del bloqueo de config que tomó para arrancar la VM, y monta la línea de comandos de QEMU con la config que cargó antes de que corriera el hook17.
Un hook que reescribe el número de serie cambia el siguiente arranque, no este.
Así que lánzalo desde tu aprovisionamiento, en los cuatro momentos en que cambian sus entradas: crear, clonar, renombrar y redimensionar.
Clonar es el que muerde.
Un clon conserva todos los campos de smbios1 menos el UUID9, así que sáltatelo y cada VM construida desde web-template declara el nombre de la plantilla como número de serie.
El logo de arranque vive en dos sitios distintos
Qué logo enseña un invitado depende de su firmware. Los dos firmwares que entrega Proxmox lo sacan de sitios completamente distintos:
SeaBIOS (bios: seabios, el de por defecto) | OVMF (bios: ovmf, UEFI) | |
|---|---|---|
| De dónde sale el logo | un JPEG que QEMU entrega al arrancar | un mapa de bits compilado dentro del firmware |
| El fichero de Proxmox | /usr/share/qemu-server/bootsplash.jpg, 640×480 | Logo.bmp, 400×120, 8 bits, compilado en OVMF_CODE_4M*.fd |
| Paquete dueño | qemu-server | pve-edk2-firmware-ovmf |
| Cambiarlo por VM | args: -boot splash=… | args apuntando la VM a otro firmware |
| Cambiarlo sin recompilar nada | sí | no |
| Llega al SO invitado por la BGRT | no | sí |
SeaBIOS: un JPEG en la línea de comandos
qemu-server pone la misma opción -boot en cada VM que arranca, menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg18.
La documentación de QEMU dice que la imagen se muestra «when option splash=sp_name is given and menu=on, If firmware/BIOS supports them. Currently Seabios for X86 system support it»10.
OVMF no toma de ahí nada más que splash-time, que usa como tiempo de espera del menú de arranque, y no hay ninguna referencia al fichero del splash en todo OvmfPkg19.
Sustituirlo es una línea en la config de la VM:
args: -boot splash=/etc/pve/branding/splash.jpg
Un segundo -boot no se pelea con el primero.
QEMU lo fusiona igual que fusiona -smbios, gana el último valor, y con dos ficheros de splash la captura mostró el segundo.
/etc/pve es el sitio correcto para el fichero, porque es el sistema de ficheros del clúster y todos los nodos ven el mismo splash, y su límite de 1 MiB por fichero queda muy lejos de un JPEG de 640×48020.
La imagen tiene que cumplir tres condiciones, y fallar en cualquiera te cuesta el logo o sus colores:
| Condición | Por qué | Qué pasa si no |
|---|---|---|
| JPEG o BMP de 24 bits | QEMU comprueba el fichero antes de arrancar la VM | splash file … format not recognized; must be JPEG or 24 bit BMP |
| JPEG baseline, croma 4:2:0 | el decodificador de SeaBIOS no maneja nada más21 | ERR_NOT_SEQUENTIAL_DCT o ERR_NOT_YCBCR_221111, y la pantalla en blanco |
| 640×480, como el de Proxmox | SeaBIOS pide a la VGA BIOS un modo exactamente del tamaño de la imagen | no hay modo que coincida, no hay splash |
Casi todas las herramientas de imagen escriben baseline 4:2:0 por defecto, así que la forma habitual de perder el logo es una de esas opciones de «guardar para web» que activa sin avisar la codificación progresiva, que se ve idéntica en todos los visores de imágenes que tienes y deja a SeaBIOS sin dibujar nada.
La trampa de los colores
Esta costó una tarde. El mismo JPEG, dibujado por dos compilaciones de SeaBIOS 1.17.0, sale en dos colores distintos:

Está en el jpeg.c de SeaBIOS.
El escritor de 24 bits por píxel tiene una rama little-endian que pone el azul en el primer byte de cada píxel, y el escritor de 32 bits por píxel, PIC_32, no tiene esa rama y escribe ahí el rojo21.
En un framebuffer little-endian eso intercambia el rojo y el azul.
El azul sale dorado, y el naranja saldría azul.
Qué escritor corre depende del modo de vídeo que ofrezca la VGA BIOS:
| Firmware | Modo VBE para 640×480 | Bits por píxel | Colores |
|---|---|---|---|
El SeaBIOS 1.17.0 precompilado de QEMU upstream, tal como lo entrega pve-qemu-kvm 11.0.3-4 | 0x111 | 16 | correctos, cuantizados a 65.536 |
La compilación propia de seabios 1.17.0-10 de Fedora 44 | 0x142 | 32 | rojo y azul intercambiados |
Los números de modo son de la propia tabla de SeaBIOS22, y la fila de Proxmox se comprobó contra los mismos blobs del paquete: idénticos byte a byte al precompilado de QEMU rel-1.17.0-0-gb52ca86e094d.
Así que en Proxmox tus colores salen bien, pero solo hay 65.536, y un degradado sutil hará bandas.
Y si alguna vez tu logo sale con los colores mal en otro hipervisor, no es tu JPEG.
El logo UEFI está compilado dentro del firmware
Los invitados UEFI son la mitad difícil, y en un Proxmox moderno son la mayoría.
No hay opción de splash que sobrescribir.
El logo es un mapa de bits dentro de la imagen del firmware, y la compilación de Proxmox lo mete ahí con una línea en debian/rules:
debian/setup-build-stamp:
cp -a debian/Logo.bmp MdeModulePkg/Logo/Logo.bmp
Eso copia su Logo.bmp de 400×120 y 8 bits encima del de TianoCore antes de compilar edk223.
El mismo fichero fija el fabricante del firmware como constante de compilación, PcdFirmwareVendor=L"Proxmox distribution of EDK II", que es de donde sale el fabricante del tipo 0 de la primera tabla23.
Logo nuevo, firmware nuevo.
La forma de conseguir uno que se comporte exactamente como el de Proxmox es compilarlo exactamente como lo hace Proxmox: su árbol en la etiqueta que corresponde al paquete que entregan, sus parches, sus flags, y un mapa de bits cambiado.
pve-edk2-firmware 4.2026.08-1 fija edk2 en 2970e56, que es la etiqueta upstream 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
Los flags están sacados de debian/rules tal como está en ese commit.
Cuidado con la cadena del fabricante: su Makefile la escribe entre comillas dobles, el shell de make las quita y edk2 la recibe desnuda, así que así se pasa aquí, y entrecomillarla otra vez es un error de compilación.
Deja el mapa de bits en 400×120 y 8 bits como el suyo y nada más se mueve en la pantalla de arranque.
Necesitas las dos imágenes.
qemu-server arranca cualquier VM Q35 con un disco EFI de 4M en OVMF_CODE_4M.secboot.fd, la compilación con SMM obligatorio, y usa el OVMF_CODE_4M.fd normal solo para i440fx24.
En un Proxmox actual, la secboot es la que corre casi cualquier invitado UEFI.
Compilada así, sin cambiar nada del árbol de Proxmox salvo el mapa de bits y dos cadenas, la imagen SMM arranca así:

Y la vista que tiene el invitado de su propio firmware cambia con ella, sin una sola opción -smbios en la línea de comandos:
| Leído dentro del invitado | El 4.2026.08-1 de Proxmox | Recompilado desde el mismo árbol |
|---|---|---|
| Fabricante del firmware | Proxmox distribution of EDK II | Example Cloud Ltd |
| Versión del firmware | 4.2026.08-1 | 4.2026.08-1+ec1 |
| «UEFI is supported» | aparece | aparece |
| Tablas ACPI | BGRT, WSMT y las demás | el mismo conjunto |
Eso hace del fabricante en tiempo de compilación la mejor respuesta para los invitados UEFI.
Es el propio tipo 0 de OVMF, así que el bit UEFI se queda en su sitio sin que nadie tenga que acordarse de uefi=on.
La vía de args para el tipo 0 es para los invitados SeaBIOS, y para quien no recompile firmware en absoluto.
Dos formas de darle el firmware nuevo a una VM
qemu-server tiene grabado dónde busca el firmware: OVMF.pm guarda una tabla de rutas bajo /usr/share/pve-edk2-firmware/, indexada por tipo de máquina y opciones de Secure Boot, y no hay ninguna opción en la config de la VM, la GUI ni la API que elija otro fichero24.
Quedan dos vías.
Vía A: desviarlo en cada nodo. Dile a dpkg que las dos imágenes de código de Proxmox ahora van a otro sitio, y pon las tuyas donde estaban:
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
Desde su próximo arranque, cada VM UEFI del nodo arranca tu firmware. Sin tocar ninguna config de VM.
Vía B: apuntar a ella las VM que elijas.
Guarda las imágenes en tu propio directorio y sustituye el firmware VM a VM.
Desde el paso a -blockdev, qemu-server conecta la imagen de código como un nodo de bloque llamado pflash0 y lo nombra en -machine24, y QEMU fusiona un segundo -machine igual que fusiona -boot, así que args puede añadir un nodo propio y apuntar pflash0 a ese:
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
Eso se probó por el camino largo.
El firmware secboot de Proxmox se conectó como pflash0 exactamente como lo hace qemu-server, con SMM activado, luego se añadió esa línea args detrás, y el invitado arrancó la imagen con tu marca, con el logo y la cadena del fabricante, que es de donde salió la captura de arriba.
| Vía A, desviar | Vía B, args por VM | |
|---|---|---|
| Qué VM la reciben | todas las VM UEFI del nodo | solo las VM donde la pongas |
| Los ficheros de Proxmox | movidos a .distrib | intactos |
| Config de la VM | sin cambios | una línea args, solo root |
| El tipo de máquina debe coincidir | resuelto, se desvían las dos imágenes | cosa tuya: imagen secboot para Q35 |
| Deshacer | dpkg-divert --remove --rename | borrar la línea |
| Migración | el nodo destino también tiene que estar desviado | el nodo destino tiene que tener el fichero |
La imagen de código ocupa unos 3,5 MB, frente a un límite de 1 MiB por fichero en pmxcfs20.
Como tal, no puede ir en /etc/pve, y elijas la vía que elijas, hay que ponerla en cada nodo.
Migra una VM a un nodo que no la tiene y obtienes uno de dos resultados, según la vía: con la vía A, otra vez el firmware y el logo de Proxmox, y con la vía B, una VM que no arranca en absoluto porque QEMU no puede abrir un fichero que no está.
La actualización que te deja atrás
Una desviación es la herramienta correcta para un logo. El firmware es otra cosa.
Una desviación significa que las actualizaciones de Proxmox ya no llegan al fichero. Eso es exactamente lo que pediste. También es exactamente lo que impide que los arreglos de seguridad de edk2 lleguen a tus invitados: el changelog de Proxmox para 4.2026.08-1 empieza con «Besides many bug and security fixes» y luego incluye un arreglo para CVE-2024-1374525, y una compilación con tu marca de la versión anterior no tiene nada de eso. Nada te avisa.
Eso se probó, no se supuso.
En un contenedor Debian trixie con el repositorio de Proxmox, se instaló pve-edk2-firmware-ovmf 4.2025.05-3, se desviaron las dos imágenes de código, y el paquete se actualizó a 4.2026.08-1:
| Después de la actualización | Resultado |
|---|---|
dpkg-query -W pve-edk2-firmware-ovmf | 4.2026.08-1 |
| La imagen nueva de Proxmox | instalada como OVMF_CODE_4M.fd.distrib, con el hash cambiado |
| La imagen con tu marca | sin cambios, sigue compilada desde 4.2025.05-3 |
| Algo en la consola sobre ello | nada, hasta el hook de abajo |
Así que añade un hook.
apt lanza una lista de comandos DPkg::Post-Invoke después de cada ejecución de dpkg26.
Apunta desde qué versión de Proxmox compilaste, y compárala después de cada una:
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
En esa misma actualización imprimió:
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.
Sale con 0 a propósito.
apt aborta si falla un comando Post-Invoke26, y una comprobación de marca no es motivo para dejar un nodo a medio actualizar.
Con un aviso bien alto basta.
Una cosa más. Los reinicios. Un invitado solo coge el firmware nuevo cuando se reinicia su proceso QEMU, así que una recompilación significa parar y arrancar desde Proxmox, no reiniciar desde dentro del invitado, que es exactamente como las actualizaciones de firmware del propio Proxmox llegan también a una VM en marcha. Solo que esas no necesitan que te acuerdes antes de recompilar.
O deja en paz el firmware de Proxmox
Todas las vías para el logo hasta ahora cambian el firmware, y la sección anterior es la factura de eso. Hay una que no lo cambia, y se hizo un prototipo para este artículo.
Un dispositivo PCI en QEMU puede llevar una option ROM, cualquier fichero que nombres con romfile=27, y OVMF ejecuta el driver EFI que encuentra ahí.
La compilación de Proxmox lo ejecuta esté firmado o no: OVMF pone PcdOptionRomImageVerificationPolicy a 0x00, ejecutar siempre28.
OVMF dibuja su propio logo tarde en la selección del dispositivo de arranque29 y monta la BGRT solo en ReadyToBoot, justo antes de pasar el control a un cargador de arranque.
Y el driver BGRT de edk2 acepta una imagen de sustitución mediante EDKII_BOOT_LOGO2_PROTOCOL, y rehace la tabla en ReadyToBoot siempre que la imagen haya cambiado30.
No necesita más.
El driver espera a ReadyToBoot en TPL_NOTIFY, que corre antes que el manejador TPL_CALLBACK del propio driver BGRT, limpia la pantalla, dibuja su logo y entrega esa misma imagen.
Compilado, es un único fichero de 8 KB, conectado con una línea:
args: -device pci-testdev,romfile=/etc/pve/branding/brandrom.rom
Con 8 KB queda de sobra dentro del límite de 1 MiB de pmxcfs20, así que, a diferencia de una imagen de firmware de 3,5 MB, puede vivir en /etc/pve y seguir a la VM a cualquier nodo.
Se probó contra el propio firmware de Proxmox, sacado directamente de pve-edk2-firmware-ovmf 4.2026.08-1: OVMF_CODE_4M.secboot.fd con las claves de Microsoft precargadas en OVMF_VARS_4M.ms.fd, en Q35 con SMM, y arrancando con el shim firmado de Fedora para que Secure Boot estuviera activo y aplicándose:
| Leído desde el invitado | Sin la ROM | Con la ROM |
|---|---|---|
Variable SecureBoot | 1 | 1 |
| En pantalla | el logo de Proxmox | el de Proxmox unos 250 ms, luego el tuyo |
| Imagen de la BGRT, 400×120 en 440,340 | la de Proxmox, SHA-256 be5afe4b… | la de la ROM, SHA-256 6c494576… |
| Ficheros de firmware cambiados | ninguno | ninguno |

La última fila es lo que importa. El firmware de Proxmox está intacto, así que su próxima versión de seguridad llega a tus invitados con el siguiente reinicio, y nada de la sección anterior aplica.
No sale gratis:
| Coste | Por qué |
|---|---|
| El logo de Proxmox se ve durante un cuarto de segundo | OVMF lo dibuja antes de que ningún driver de option ROM tenga la pantalla; solo una recompilación lo evita |
| El invitado ve un dispositivo PCI más, sin driver | la ROM tiene que ir montada en un dispositivo, y pci-testdev es el que no hace nada de QEMU |
| El tipo 0 sigue diciendo Proxmox | la cadena del fabricante está compilada dentro; ponla con args y uefi=on, como arriba |
| Solo root | es una línea args |
Y el hallazgo que hay debajo merece decirse claro.
Un driver sin firmar corrió en el firmware de un invitado con las claves de Microsoft cargadas y Secure Boot aplicándose, porque el OVMF de Proxmox no comprueba las option ROM.
Aquí eso es útil.
También significa que en este firmware Secure Boot comprueba los cargadores de arranque, no lo que la config de hardware de la VM conecte, y como solo root escribe args, eso es una pregunta sobre quién tiene root en tus nodos.
Es un prototipo. Ha corrido en QEMU 10.2.2 con el firmware de Proxmox, todavía no en un nodo Proxmox, y lo que sigue es código fuente, no un producto:
github.com/damo2929/RebrandPCIRom: el driver de option ROM, el conversor de logo y la compilación en contenedor.
La interfaz web son dos paquetes
El logo de arriba a la izquierda de la interfaz web no está en pve-manager.
Workspace.js pide un componente proxmoxLogoSvg con el prefijo pwt, y ese vive en proxmox-widget-toolkit, que comparten Backup Server y Mail Gateway31.
En pve-manager solo están los iconos de pestaña:
| Qué | Fichero | Paquete |
|---|---|---|
| Logo de la cabecera, dibujado a 200×35 | /usr/share/javascript/proxmox-widget-toolkit/images/proxmox_logo.svg | proxmox-widget-toolkit |
| Icono de la pestaña del navegador | /usr/share/pve-manager/images/favicon.ico | pve-manager |
| Icono de 128×128, también el icono táctil | /usr/share/pve-manager/images/logo-128.png | pve-manager |
Los tres son ficheros normales, así que esto es trabajo para dpkg-divert, y aquí el coste de la sección anterior no aplica en absoluto, porque un logo no tiene arreglos de seguridad que perderse y ningún invitado está menos seguro porque un nodo siga sirviendo el favicon del mes pasado.
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
Probado en el mismo contenedor, contra paquetes reales:
| Paso | Resultado |
|---|---|
Desviar en proxmox-widget-toolkit 5.2.9, instalar SVG propio | el SVG de Proxmox renombrado a proxmox_logo.svg.distrib |
| Actualizar a 5.2.10 | el SVG propio sigue en su sitio, la copia 5.2.10 de Proxmox en .distrib |
dpkg --verify proxmox-widget-toolkit | sin quejas, dpkg conoce la desviación |
apt-get install --reinstall proxmox-widget-toolkit | el SVG propio sigue en su sitio |
dpkg-divert --remove --rename | vuelve el original de Proxmox, byte a byte |
Dos cosas siguen siendo de Proxmox.
La imagen de la cabecera se dibuja en una caja fija de 200×35, así que dibuja tu SVG con esa forma o saldrá aplastado.
Y el texto alt dice Proxmox y la imagen enlaza a https://www.proxmox.com, ambos escritos en el componente JavaScript y no en un fichero que puedas desviar31.
Cambiarlos significa parchear un bundle minificado que se rompe en cada actualización.
No merece la pena por un texto alt.
Lo que te piden la licencia y la marca registrada de Proxmox
Proxmox VE se licencia bajo la AGPL versión 332, y la sección 13 de esa licencia es la cláusula de red: «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. ¿Cambiar tres imágenes cuenta como modificar el Programa? Eso es para un abogado. La respuesta barata es poner tus imágenes y el script de desviación en algún sitio público y enlazarlo desde la página de inicio de sesión. No cuesta nada y zanja la cuestión.
La parte de la marca registrada está más clara. El kit de prensa de Proxmox dice «Don’t alter the logo or incorporate the logo or symbol into your logo»34. Así que sustituye el suyo del todo por el tuyo, nunca una versión recoloreada o retocada del suyo, y deja Proxmox fuera del nombre de tu producto.
Tu nombre encima significa tu mantenimiento
Poner tu marca en una VM es barato. Un puñado de cadenas SMBIOS, un JPEG, un mapa de bits y tres imágenes en una interfaz web: una tarde de trabajo, la mayor parte buscando dónde vive cada cosa.
Conservarla es el trabajo de verdad. Y es la parte que se salta.
Las cadenas SMBIOS y el splash se cuidan solos, porque viven en config que ningún paquete va a tocar jamás, y el logo de la interfaz web se cuida solo porque dpkg está avisado. El firmware no. El día que metiste tu logo en una imagen de firmware, te quedaste con un trozo del proceso de versiones de otro, y Proxmox compila, prueba y entrega firmware nuevo con arreglos de seguridad que sus clientes reciben con la siguiente actualización, y los tuyos reciben cuando a ti te dé por recompilar.
Ese trato está bien si se hace sabiendo lo que se hace. Una empresa de hosting cuyos invitados arrancan un firmware dos versiones atrasado porque el logo importaba más que el changelog no ha construido un producto. Ha construido una pegatina.
Si tu nombre está en la pantalla de arranque, el firmware que hay detrás es tuyo y te toca tenerlo al día, lo escribiera quien lo escribiera. Nadie lo va a comprobar. Precisamente por eso hay que hacerlo.
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». Copia archivada; la página en vivo devuelve 403 a las peticiones automáticas. ↩︎
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»; el plymouth.spec de Fedora hace de
bgrtel tema por defecto. ↩︎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 — tipo 1 System Information, tipo 2 placa base, tipo 3 «the system’s mechanical enclosure(s)», tipo 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, el formatosmbios1y el constructor de la línea de comandos — todos los campos menosuuidtienen el patrón[A-Za-z0-9+\/]+={0,2}; los valores se decodifican cuandobase64está puesto. ↩︎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, clonado — «auto generate a new uuid», los demás campos desmbios1se conservan. ↩︎ ↩︎QEMU, System Emulation, Invocation — campos de
-smbiospor tipo;-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 añade su propio tipo 0 solo cuandoNeedSmbiosType0sigue puesto después de recorrer las tablas de QEMU. ↩︎qemu-server,
src/PVE/QemuServer.pm, args personalizados — losargsse trocean y se añaden al final de la línea de comandos. ↩︎qemu-server,
src/PVE/API2/Qemu.pm, comprobaciones de permisos de la config —smbios1es una opción de tipo hardware que necesitaVM.Config.HWType; el caso por defecto «catches args, lock, etc.» y muere con «only root can set». ↩︎qemu-server,
src/PVE/QemuServer.pm, la opciónname—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— memoriadefault => 512;coresysocketsvalen 1 por defecto enQemuServer.pm. ↩︎qemu-server,
src/PVE/QemuServer.pm,vm_start—lock_configen la línea 5457,exec_hookscript($conf, $vmid, 'pre-start', 1)en la 5581, yconfig_to_commanden la 5634 con el mismo$conf, sin recargarlo entre medias. ↩︎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 leeetc/boot-menu-wait, el valor desplash-time, y nada más de-boot. ↩︎pve-cluster,
src/pmxcfs/memdb.h—#define MEMDB_MAX_FILE_SIZE (1024 * 1024) // 1 MiB. ↩︎ ↩︎ ↩︎SeaBIOS,
src/jpeg.c—PICescribe primero el azul en little-endian,PIC_32en la línea 946 escribe primero el rojo sin rama de endianness;ERR_NOT_SEQUENTIAL_DCTyERR_NOT_YCBCR_221111son los únicos formatos que rechaza. ↩︎ ↩︎SeaBIOS,
vgasrc/svgamodes.c— el modo0x111es 640×480 a 16 bits, el0x142es 640×480 a 32. ↩︎pve-edk2-firmware,
debian/rulesen 4.2026.08-1 —cp -a debian/Logo.bmp MdeModulePkg/Logo/Logo.bmp, la cadenaPcdFirmwareVendory los flags de compilación de OVMF. ↩︎ ↩︎ ↩︎qemu-server,
src/PVE/QemuServer/OVMF.pm— la tabla de firmware grabada bajo/usr/share/pve-edk2-firmware/, la elección de SMM y el nodo de bloquepflash0. ↩︎ ↩︎ ↩︎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), una propiedad que tiene todo dispositivo PCI. ↩︎edk2-stable202608,
OvmfPkg/OvmfPkgIa32X64.dsc—gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00, el fichero de plataforma que compila Proxmox. ↩︎edk2-stable202608,
OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c—BootLogoEnableLogo (), llamada desdePlatformBootManagerAfterConsole. ↩︎edk2-stable202608,
BootGraphicsResourceTableDxe.c—SetBootLogo2en la línea 239 copia la imagen; el manejador de ReadyToBoot en la 417 desinstala y reinstala la tabla «If BGRT data change happens». ↩︎proxmox-widget-toolkit,
src/Logo.js—proxmoxLogoSvg, 200×35,alt: 'Proxmox', enlazando a proxmox.com; usado desdeWorkspace.jsde pve-manager conprefix: '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 — citada completa en el texto de arriba. ↩︎
Proxmox, Media kit — «Don’t alter the logo or incorporate the logo or symbol into your logo.» ↩︎