Files
void-installer/docs/LIVE_ISO.md
mozempk c462bd9d31 fix: nix profile circular symlink, mdns hang, deprecated install command
- first-login.sh: remove nix-env --switch-profile (caused .nix-profile->
  .nix-profile circular symlink, breaking all nix profile commands and
  causing ELOOP on any exec via nix PATH including xz/tar/node)
- first-login.sh: add circular symlink guard before nix profile add
- first-login.sh: nix profile install -> nix profile add (deprecated alias)
- live-setup.sh: strip mdns from nsswitch.conf hosts line at boot (no
  libnss_mdns/Avahi in live; caused first-login DNS hang)
- docs/LIVE_ISO.md: document all three issues and their fixes
2026-04-23 08:01:11 +02:00

9.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

Known Issues & Fixes

Symptom: error: filesystem error: status: Too many levels of symbolic links [/home/live/.nix-profile/manifest.json] and tar: xz: Cannot exec: Too many levels of symbolic links (all binaries fail to exec via nix PATH).
Cause: Passing $HOME/.nix-profile as the target to nix-env --switch-profile creates ~/.nix-profile -> .nix-profile — a symlink that points to itself. This corrupts the nix profile directory and causes ELOOP on any file lookup under that path.
Fix: Do not call nix-env --switch-profile at all when using nix profile add (new-style commands). Let nix profile add initialise the profile automatically. The first-login script also contains a guard that detects and removes the circular symlink before proceeding.

nix profile install is deprecated

Use nix profile add instead. nix profile install is an alias that emits a warning and will be removed in a future Nix version.

DNS hang in live environment (nsswitch mdns without Avahi)

Symptom: getent hosts github.com hangs indefinitely; first-login.sh stuck at "starting".
Cause: /etc/nsswitch.conf includes mdns in the hosts: line. On Void Linux, libnss_mdns.so.2 may not be present, and even if it is, the Avahi daemon is not running in the live session. glibc waits for Avahi's D-Bus socket before timing out.
Fix: live-setup.sh runs at boot and removes mdns from nsswitch.conf: sed -i '/^hosts:/s/mdns[^ ]* *//g' /etc/nsswitch.conf. This is safe on real hardware (NetworkManager provides proper DNS via DHCP).

QEMU internal DNS (10.0.2.3) unreliable

Symptom: Even after removing mdns, DNS queries to QEMU's built-in resolver (10.0.2.3) time out.
Cause: QEMU's user-mode DNS proxy may not forward queries correctly depending on the host network configuration.
Workaround for QEMU testing: echo nameserver 8.8.8.8 > /etc/resolv.conf. This is not needed on real hardware.


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.