Files
void-installer/docs/LIVE_ISO.md
mozempk 5cd9b496fd feat: live ISO — nix daemon mode, autologin fix, GPU detection, app stack
- Switch nix from single-user to daemon mode (trusted-users = root live);
  Void socket at /var/nix/daemon-socket/socket confirmed
- Fix lightdm autologin: use session-wrapper=/etc/lightdm/Xsession (Void
  lightdm 1.32 has no lightdm-session binary)
- Fix session env: LIBGL_ALWAYS_SOFTWARE=1 via profile.d (session-env=
  is unsupported in this lightdm version)
- GPU auto-detection at boot: VIRT→software GL, NVIDIA PRIME offload,
  Intel/AMD/generic→modesetting
- Add nix-daemon to live runsvdir/default; remove unsupported -S mklive flag
- first-login.sh: install Claude Code + nix user packages (google-chrome,
  spotify, discord, localsend, mission-center) + NVM/node + VS Code exts
- build-live-iso.sh: write nix-packages.list from NIX_USER_PACKAGES
- postinstall.sh: fix nix-daemon socket path to /var/nix/daemon-socket/socket
- Dockerfile: add dconf-cli for build-time dconf compile
- _inner-build-live.sh: use correct 'dconf compile' API (not 'dconf update')
- .gitignore: add build/live-includes/ (generated staging tree)
- docs/LIVE_ISO.md: document all findings, gotchas and architecture
2026-04-23 07:42:35 +02:00

7.8 KiB

Live ISO Build — Findings & Architecture Notes

Overview

The live ISO boots directly into a Cinnamon desktop session as user live with no password prompt. It is designed for hardware testing on XPS 9700 and serves as the installer delivery vehicle.

Builder: iso/build-live-iso.sh (host) → Docker container running iso/_inner-build-live.shvoid-mklive/mklive.sh


Boot + Session Startup

Kernel Cmdline

live.user=live console=tty0 console=ttyS0,115200

The live.user=live parameter is consumed by the vmklive dracut hook (adduser.sh) which creates the user inside the initramfs and sets password voidlinux.

runit Stage 2 Override

We override /etc/runit/2 to run /etc/runit/live-setup.sh before handing off to runsvdir. The script:

  1. Adds extra groups (plugdev input network docker) to the live user
  2. Writes /etc/sudoers.d/live (full passwordless sudo)
  3. Configures /etc/nix/nix.conf (daemon mode, trusted-users = root live)
  4. Auto-detects GPU and writes /etc/X11/xorg.conf.d/20-gpu.conf

After live-setup.sh, stage 2 mirrors the real runit-void exactly:

runsvchdir "${runlevel}"
ln -sf /etc/runit/runsvdir/current /run/runit/runsvdir/current
exec runsvdir -P /run/runit/runsvdir/current

Enabled at build time via symlinks in build/live-includes/etc/runit/runsvdir/default/:

  • dbus
  • NetworkManager
  • lightdm
  • nix-daemon

Note: Do NOT use mklive.sh's -S flag for service enable — it is not supported by the version used. Services must be wired via runsvdir symlinks in the include overlay.


LightDM Autologin

Critical: lightdm-session does not exist on Void Linux

The Void lightdm 1.32 package does not ship the lightdm-session binary. The default LightDM behaviour of spawning lightdm-session causes the session to crash immediately (exit code 1 in ~20ms) with no error message.

Fix: Set session-wrapper=/etc/lightdm/Xsession in lightdm.conf. The /etc/lightdm/Xsession wrapper is provided by the Void lightdm package and correctly sources /etc/profile/etc/profile.d/.

greeter-env= and session-env= are not supported

These options are silently ignored in LightDM 1.32 on Void. To propagate environment variables to the session use /etc/profile.d/ scripts instead.

lightdm.conf autologin lines must be commented

The vmklive dracut hook display-manager-autologin.sh uses sed to uncomment lines. The autologin lines in lightdm.conf must be present but commented out — the hook finds them by regex and uncomments them at boot.

[Seat:*]
#autologin-user=
#autologin-user-timeout=0
#autologin-session=
#user-session=
session-wrapper=/etc/lightdm/Xsession
greeter-session=lightdm-gtk-greeter

The /etc/lightdm/.session file (content: cinnamon) is read by the hook to set the session name.


GPU Auto-Detection

live-setup.sh runs lspci at boot and writes /etc/X11/xorg.conf.d/20-gpu.conf:

Detected Xorg Config Extra
Virtual (virtio/VMware/QEMU/VirtualBox) modesetting, AccelMethod none LIBGL_ALWAYS_SOFTWARE=1 in /etc/profile.d/live-env.sh
NVIDIA + proprietary driver (nvidia_drv.so) PRIME offload: Intel modesetting + NVIDIA nvidia No software GL
NVIDIA without proprietary driver modesetting
Intel / AMD / other modesetting

LIBGL_ALWAYS_SOFTWARE=1 is set via /etc/profile.d/live-env.sh, not via session-env= (unsupported).


Nix Integration

Daemon mode (not single-user)

The Void nix xbps package ships nix-daemon with a runit service at /etc/sv/nix-daemon. The daemon puts its socket at:

/var/nix/daemon-socket/socket

We use daemon mode (not single-user) because /nix/store stays root-owned. The live user is granted trust via nix.conf:

experimental-features = nix-command flakes
sandbox = false
auto-optimise-store = true
trusted-users = root live

sandbox = false is required because the live system has no nixbld users and no user namespaces in the dracut initramfs environment.

Package list

/usr/local/libexec/nix-packages.list is written at ISO build time from NIX_USER_PACKAGES in config/install.conf. At first login, first-login.sh reads this file and runs nix profile install --impure with NIXPKGS_ALLOW_UNFREE=1.

Current packages:

  • nixpkgs#google-chrome
  • nixpkgs#spotify
  • nixpkgs#discord
  • nixpkgs#localsend
  • nixpkgs#mission-center

postinstall.sh socket path (installed system)

In the installed system (not live), installer/lib/postinstall.sh polls for the nix-daemon socket. The correct path is:

/var/nix/daemon-socket/socket

Not /nix/var/nix/daemon-socket/socket (upstream Nix default) — Void's package uses /var/nix/.


dconf / Theme

The Gruvbox-Dark GTK theme and Cinnamon dconf settings are pre-applied via a system-db. The dconf binary database must be compiled at ISO build time, not at runtime.

Build-time compilation

iso/_inner-build-live.sh runs inside the Debian Docker container. The Dockerfile installs dconf-cli for this step. The correct Debian dconf-cli API is:

dconf compile <output_binary_db> <input_keyfile_dir>
# e.g.:
dconf compile build/live-includes/etc/dconf/db/local \
              build/live-includes/etc/dconf/db/local.d

Note: dconf update <path> does not work in Debian's dconf-cli — it only updates the user's own db. dconf compile is the correct tool for building a system-db binary.

dconf profile

/etc/dconf/profile/user must point to the system-db:

user-db:user
system-db:local

Without this file, the compiled system-db is ignored and Cinnamon shows a black wallpaper with default GTK theme.


First-Login Setup (installer/first-login.sh)

Runs once via XDG autostart (~/.config/autostart/void-live-first-login.desktop) when Cinnamon first loads. Installs:

  1. Claude Code — official installer from https://claude.ai/install.sh
  2. Nix user packages — from /usr/local/libexec/nix-packages.list
  3. NVM + Node LTS
  4. VS Code extensions — from /etc/installer-vscode-extensions.txt

Idempotent: creates ~/.first-login-done on success. Logs to ~/.first-login.log.

The script does NOT use set -u because nvm.sh references unbound variables.


Build Pipeline

iso/build-live-iso.sh        (host — stages overlay, builds Docker image if needed)
  └─ Docker: void-installer-builder:latest
       └─ iso/_inner-build-live.sh
            ├─ dconf compile  (pre-bakes system-db)
            └─ void-mklive/mklive.sh -a x86_64 -r <repo> -I <include_dir> ...
                 └─ squashfs + GRUB + ISO 9660

Output: out/void-live-stable.iso (~2.9 GB)

Build artifacts that must NOT be committed

  • build/live-includes/ — generated staging tree (hundreds of binary assets)
  • out/ — ISO output
  • cache/ — cloned void-mklive, xbps package cache

QEMU Testing

cp /usr/share/OVMF/OVMF_VARS.fd out/OVMF_VARS.live.fd
qemu-system-x86_64 -name void-live-test -machine q35,accel=kvm:tcg -cpu max \
  -m 4096 -smp 4 \
  -drive "if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE.fd" \
  -drive "if=pflash,format=raw,file=out/OVMF_VARS.live.fd" \
  -cdrom out/void-live-stable.iso -boot order=d,menu=off \
  -netdev user,id=n0 -device virtio-net-pci,netdev=n0 \
  -serial "unix:out/live-serial.sock,server,nowait" \
  -monitor "unix:out/qemu-monitor.sock,server,nowait" \
  -device virtio-vga -display gtk,gl=off &

Serial console access (root shell for diagnostics):

import socket, time
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect('out/live-serial.sock')
# send commands, read output

GPU in QEMU: virtio-vga is detected as virtual → modesetting + LIBGL_ALWAYS_SOFTWARE=1.