Custom NixOS ISOs and Distro Branding
Custom NixOS ISOs and Distro Branding
A rescue ISO with a renamed file is not a distro. The boring default is: import the official installer CD module, put identity and artwork in one shared module, and install from that ISO into the same flake — do not fork nixpkgs to change a logo.
The generators chapter builds images (ISO, qcow, SD). This chapter is the installer / live distro: boot menu, /etc/os-release, Plymouth, greeter, Calamares. The desk ships DeskOS internally — NixOS 26.05 with a name, colours, and a first-boot story the onboarding laptop already trusts.
NixOS logos and the snowflake are a separate brand. Do not recolour them and call the result yours. Use your own mark. Keep ID_LIKE=nixos so tools still know what they are on.
Mental model
| Layer | What a human sees | Nix |
|---|---|---|
| File name / USB label | DeskOS-26.05.iso, volume in lsblk |
isoImage.isoName, volumeID, edition |
| Boot menu | Title, background, GRUB theme | distroName, splash images, grubTheme, syslinuxTheme |
| Live OS | hostnamectl, cat /etc/os-release |
system.nixos.distroId / distroName / extraOSReleaseArgs |
| Boot splash | Logo while the kernel starts | boot.plymouth |
| Graphical installer | Calamares window, slideshow | vendored branding.desc + PNGs |
| Installed machine | Same name after nixos-install |
same identity module in nixosConfigurations |
artwork/ + modules/desk-identity.nix
│
┌───────────┼───────────┐
▼ ▼ ▼
installer ISO live USB desk-laptop (flake)
iso (live) ≠ install-iso (has nixos-install / Calamares). Pick the installer module on purpose.
ISO9660 volumeID is short (keep it under 32 characters). A 60-character marketing name belongs in isoName and PRETTY_NAME, not on the volume.
Worked examples
Case 1: Minimal installer ISO from modulesPath
Save as flake.nix:
# flake.nix
{
description = "DeskOS installer ISO";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
outputs = { self, nixpkgs }: {
nixosConfigurations.desk-iso = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
({ modulesPath, pkgs, lib, ... }: {
imports = [
(modulesPath + "/installer/cd-dvd/installation-cd-minimal.nix")
];
isoImage.isoName = "DeskOS-26.05-x86_64.iso";
isoImage.edition = "desk";
isoImage.volumeID = "DESKOS-2605-X64";
isoImage.makeEfiBootable = true;
isoImage.makeUsbBootable = true;
isoImage.appendToMenuLabel = " installer";
environment.systemPackages = with pkgs; [
vim git curl jq
disko
];
nix.settings.experimental-features = [ "nix-command" "flakes" ];
services.openssh.enable = true;
users.users.nixos.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI… deskadmin@ops-01"
];
system.stateVersion = "26.05";
})
];
};
};
}nix build .#nixosConfigurations.desk-iso.config.system.build.isoImage
ls -lh result/iso/Output (shape):
-r--r--r-- 1 root root 1.2G DeskOS-26.05-x86_64.iso
On 26.05 you can also:
nixos-rebuild build-image --image-variant iso-installer --flake .#desk-isoIf the flake already has nixos-generators, format = "install-iso" is the same installer family. format = "iso" is a live image without the installer scripts — wrong for onboarding.
Flash:
sudo dd if=$(readlink -f result/iso/*.iso) of=/dev/sdX bs=4M status=progress conv=fsyncCase 2: Identity — /etc/os-release, not a wallpaper
Save as modules/desk-identity.nix:
# modules/desk-identity.nix
{ lib, ... }:
{
system.nixos.distroId = "deskos";
system.nixos.distroName = "DeskOS";
system.nixos.vendorId = "desk";
system.nixos.vendorName = "Desk Platform";
system.nixos.variant_id = "workstation";
system.nixos.variantName = "DeskOS Workstation";
system.image.id = "deskos-installer";
system.image.version = "26.05";
# When distroId != "nixos", NixOS already sets ID_LIKE=nixos.
system.nixos.extraOSReleaseArgs = {
HOME_URL = "https://git.desk.internal/platform/deskos";
DOCUMENTATION_URL = "https://git.desk.internal/platform/deskos#readme";
SUPPORT_URL = "https://desk.internal/helpdesk";
BUG_REPORT_URL = "https://git.desk.internal/platform/deskos/issues";
ANSI_COLOR = "0;38;2;47;95;117";
LOGO = "deskos-mark";
CPE_NAME = "cpe:/o:desk:deskos:26.05";
};
system.nixos.extraLSBReleaseArgs = {
DISTRIB_DESCRIPTION = "DeskOS 26.05 (Yarara)";
};
networking.hostName = lib.mkDefault "deskos";
}Import it from the ISO and from nixosConfigurations.desk-laptop. After install:
cat /etc/os-release
hostnamectlNAME=DeskOS
ID=deskos
ID_LIKE=nixos
PRETTY_NAME="DeskOS 26.05 (Yarara)"
VARIANT="DeskOS Workstation"
IMAGE_ID=deskos-installer
ID=deskos is a lowercase token (os-release). NAME is the human string. Do not set ID=NixOS and a custom NAME — scripts key off ID.
Leave system.nixos.release / codeName alone. Those still say 26.05 / Yarara because that is the nixpkgs train. A fake VERSION_ID=1.0 that hides 26.05 is how advisories miss you.
Case 3: Bootloader artwork (GRUB + BIOS splash)
Splash files are PNG (or non-progressive JPEG). Generate placeholders with ImageMagick so the flake builds before the designer ships real art:
# modules/desk-iso-brand.nix
{ pkgs, lib, ... }:
let
splashBios = pkgs.runCommand "deskos-splash-bios.png" {
nativeBuildInputs = [ pkgs.imagemagick ];
} ''
magick -size 800x600 xc:'#1b2838' \
-gravity center -fill '#7ebae4' -pointsize 48 \
-annotate 0 'DeskOS' "$out"
'';
splashEfi = pkgs.runCommand "deskos-splash-efi.png" {
nativeBuildInputs = [ pkgs.imagemagick ];
} ''
magick -size 1920x1080 xc:'#1b2838' \
-gravity center -fill '#7ebae4' -pointsize 72 \
-annotate 0 'DeskOS' "$out"
'';
in
{
isoImage.splashImage = splashBios;
isoImage.efiSplashImage = splashEfi;
isoImage.grubTheme = null; # skip nixos-grub2-theme; use colours below
isoImage.syslinuxTheme = ''
MENU TITLE DeskOS 26.05
MENU RESOLUTION 800 600
MENU COLOR SEL 7;37;40 #FFFFFFFF #FF2F5F75 std
MENU COLOR UNSEL 37;40 #FF7EBAE4 #00000000 none
'';
boot.loader.grub.extraConfig = ''
set color_normal=light-cyan/black
set color_highlight=black/light-cyan
set menu_color_normal=light-cyan/black
set menu_color_highlight=black/light-cyan
'';
}Replace the runCommand PNGs with ./artwork/splash-bios.png once the files exist. Keep artwork in git (or a FOD), not fetched from a CMS at eval time.
isoImage.grubTheme expects a GRUB2 theme directory (theme.txt + fonts + background). pkgs.nixos-grub2-theme is the NixOS default — set it to null or to your theme package. Mixing a custom distroName with the stock snowflake theme looks like a counterfeit.
isoImage.contents drops extra files on the ISO:
{
isoImage.contents = [
{
source = ./README-INSTALL.md;
target = "/README-INSTALL.md";
}
];
}Case 4: Plymouth + live graphical session
For a graphical installer, import the Calamares CD instead of minimal:
# modules/desk-iso-graphical.nix
{ modulesPath, pkgs, ... }:
{
imports = [
(modulesPath + "/installer/cd-dvd/installation-cd-graphical-calamares-plasma6.nix")
./desk-identity.nix
./desk-iso-brand.nix
];
isoImage.edition = "plasma6";
boot.plymouth.enable = true;
boot.plymouth.theme = "breeze";
boot.plymouth.logo = ./artwork/logo-48.png; # PNG only; 48×48 matches GDM
boot.kernelParams = [ "quiet" ];
boot.consoleLogLevel = 3;
boot.initrd.verbose = false;
services.displayManager.sddm.enable = true;
services.desktopManager.plasma6.enable = true;
}boot.plymouth.theme = "breeze" pulls a Plymouth theme whose osName is config.system.nixos.distroName — so Case 2’s DeskOS appears on the splash without a custom theme package. theme = "bgrt" keeps the firmware OEM logo; that is often what you want on laptops, not on a USB installer.
A live wallpaper (Plasma):
{
environment.etc."deskos/wallpaper.png".source = ./artwork/wallpaper.png;
}Point the Plasma look-and-feel at that path in a small xdg autostart if you must; do not vendor all of plasma-workspace-wallpapers (it inflates the ISO by hundreds of MiB).
Case 5: Calamares branding (the installer window)
Calamares on the NixOS graphical ISO uses pkgs.calamares-nixos-extensions, branding component nixos. Override the component with files you own.
Save as artwork/calamares/branding.desc (Calamares format; truncated to the strings that matter):
# artwork/calamares/branding.desc
---
componentName: deskos
windowExpanding: fullscreen
windowPlacement: center
sidebar: widget
navigation: widget
strings:
productName: DeskOS
shortProductName: DeskOS
version: 26.05
shortVersion: 26.05
versionedName: DeskOS 26.05
shortVersionedName: DeskOS
bootloaderEntryName: DeskOS
productUrl: https://git.desk.internal/platform/deskos
supportUrl: https://desk.internal/helpdesk
knownIssuesUrl: https://git.desk.internal/platform/deskos/issues
releaseNotesUrl: https://git.desk.internal/platform/deskos
donateUrl: https://git.desk.internal/platform/deskos
images:
productIcon: logo.svg
productLogo: logo-white.png
productWelcome: logo.svg
slideshow: show.qml
style: Raleway
welcomeStyleCalamares: falsePut logo.svg, logo-white.png, and a one-slide show.qml next to that file. Then:
# modules/desk-calamares.nix
{ pkgs, ... }:
{
environment.etc."calamares/branding/deskos".source = ./artwork/calamares;
environment.etc."calamares/settings.conf".text = ''
---
modules-search: [ local, /run/current-system/sw/lib/calamares/modules ]
sequence:
- show: [ welcome, locale, keyboard, partition, users, summary ]
- exec: [ partition, mount, unpackfs, networkcfg, machineid, locale, keyboard, users, displaymanager, nixos, umount ]
- show: [ finished ]
branding: deskos
prompt-install: false
dont-chroot: false
'';
}bootloaderEntryName: DeskOS is what shows up in the firmware boot menu after install. If you leave it NixOS, the USB said DeskOS and the disk says NixOS — that is the bug users file as “install failed.”
Calamares still writes a NixOS configuration.nix. After first boot, switch the machine to the flake (nixos-rebuild switch --flake git+ssh://git.desk.internal/platform/deskos#desk-laptop) so identity + sshd + disk layout are the desk modules, not a one-shot Calamares file. The ISO is a delivery vehicle. The flake is the distro.
The trap
The trap is a wallpaper and a hostname with ID=nixos still in /etc/os-release, plus the official snowflake on the GRUB theme. That is a reskin people will treat as NixOS support. Set distroId, URLs, and your artwork, keep ID_LIKE=nixos, and say “based on NixOS 26.05” in README-INSTALL.md.
The other traps:
format = "iso"when you neededinstall-iso/installation-cd-minimal.nix— nonixos-installon the USB.volumeIDlonger than the ISO9660 limit — stage-1 cannot find the CD.- Copying NixOS logo assets in violation of the NixOS branding guide.
- A password in the ISO module (
users.users.root.password = "desk") that ships to every USB in the drawer. SSH keys. Hashed passwords if you must. - Branding only the ISO, not
nixosConfigurations. Day-twonixos-rebuildrestores the snowflake.
The boring rule
- Official installer module (
installation-cd-minimalor graphical-calamares). 26.05 lock. - One
desk-identity.niximported by ISO and hosts.distroIdlowercase;ID_LIKE=nixos. - Your artwork in git. No snowflake. Splash PNG / GRUB theme / Plymouth logo / Calamares
branding.desc. isoName+ shortvolumeID. USB bootable (makeUsbBootable).- After install, the flake is source of truth. Calamares is first boot only.
- Do not fork nixpkgs to change a string in
os-release.
Try this
- Build Case 1.
iso-infoorisoinfo -d -i result/iso/*.isoand read the volume id. - Boot the ISO in QEMU;
cat /etc/os-release—ID=deskosandID_LIKE=nixos. - Swap Case 3 placeholders for real PNGs; rebuild; confirm GRUB no longer shows
nixos-grub2-theme. - On a graphical ISO, open Calamares and read the window title /
bootloaderEntryName. - Install into a VM, then
nixos-rebuild switch --flakethe same identity module;hostnamectlstill says DeskOS.