- 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
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.sh → void-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:
- Adds extra groups (
plugdev input network docker) to the live user - Writes
/etc/sudoers.d/live(full passwordless sudo) - Configures
/etc/nix/nix.conf(daemon mode,trusted-users = root live) - 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
Services (runsvdir/default symlinks in overlay)
Enabled at build time via symlinks in build/live-includes/etc/runit/runsvdir/default/:
dbusNetworkManagerlightdmnix-daemon
Note: Do NOT use mklive.sh's
-Sflag 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-chromenixpkgs#spotifynixpkgs#discordnixpkgs#localsendnixpkgs#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'sdconf-cli— it only updates the user's own db.dconf compileis 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:
- Claude Code — official installer from
https://claude.ai/install.sh - Nix user packages — from
/usr/local/libexec/nix-packages.list - NVM + Node LTS
- 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 outputcache/— 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.