Files
void-installer/iso/build-live-iso.sh
mozempk cd8248f2f5 feat: NVIDIA PRIME, audio fix, timezone, dmesg error cleanup
- Add nvidia/nvidia-dkms/nvidia-libs-32bit/nvidia-vaapi-driver to niri
  live and installed profiles; wireless-regdb and sof-firmware to all
  profiles (fixes regulatory.db and SOF firmware dmesg errors)

- iso/postsetup-nvidia.sh: new mklive -x hook that re-runs dracut inside
  the rootfs chroot after the overlay is applied; ensures the squashfs
  initramfs includes nvidia.ko and omits nouveau.ko at build time —
  no driver install needed at runtime (fixes /run tmpfs overflow that was
  killing wireplumber by corrupting D-Bus sockets)

- Both ISO inner build scripts gain -x postsetup-nvidia.sh and the nonfree
  repo flag so nvidia packages resolve correctly

- niri config: wireplumber started via supervisor loop (waits for PipeWire
  socket, auto-restarts on crash) replacing the one-shot exec — survives
  any D-Bus or pipewire disruption

- build-niri-live-iso.sh: NVIDIA modprobe blacklist-nouveau.conf,
  btusb-quirks.conf, modules-load.d/nvidia.conf, dracut/10-nvidia.conf,
  Xorg intel/nvidia configs, prime-run helper, elogind run script loop
  guard, timezone Europe/Zurich overlay, updated BOOT_CMDLINE

- build-live-iso.sh: same NVIDIA + timezone + sound udev rule overlays;
  live-setup.sh timezone and audio group fix

- installer/lib/grub.sh: GRUB_CMDLINE_LINUX_DEFAULT gains
  nvidia-drm.modeset=1 rd.driver.blacklist=nouveau btusb.enable_autosuspend=0

- installer/lib/postinstall.sh: configure_nvidia_prime() adds
  blacklist-nouveau.conf, btusb-quirks.conf, dracut omit_drivers nouveau,
  modules-load.d with all four nvidia modules
2026-04-25 18:27:06 +02:00

793 lines
32 KiB
Bash
Executable File

#!/bin/bash
# Build a full Cinnamon LIVE desktop ISO for testing on real hardware.
#
# Boots directly into a Cinnamon session as user 'live' (no password).
# All themes, icons, cursor, wallpapers, dotfiles and VS Code config are
# pre-applied from the same overlay used by the installer.
#
# Requires (host): bash, git, curl, docker, and Bibata-Modern-Ice cursor
# installed at /usr/share/icons/Bibata-Modern-Ice.
#
# Usage:
# iso/build-live-iso.sh
# OUTPUT_ISO=/path/to/output.iso iso/build-live-iso.sh
set -Eeuo pipefail
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CACHE_DIR="${CACHE_DIR:-$PROJECT_DIR/cache}"
OUT_DIR="${OUT_DIR:-$PROJECT_DIR/out}"
BUILD_DIR="${BUILD_DIR:-$PROJECT_DIR/build}"
INCLUDE_DIR="$BUILD_DIR/live-includes"
MKLIVE_DIR="$CACHE_DIR/void-mklive"
MKLIVE_REPO="${MKLIVE_REPO:-https://github.com/void-linux/void-mklive.git}"
MKLIVE_REF="${MKLIVE_REF:-master}"
PATCH_DIR="$PROJECT_DIR/iso/patches"
DOCKER_IMAGE="${DOCKER_IMAGE:-void-installer-builder:latest}"
DOCKER="${DOCKER:-docker}"
LIVE_USER="${LIVE_USER:-live}"
# shellcheck disable=SC1091
source "$PROJECT_DIR/config/install.conf"
# Load profile defaults (GTK_THEME, CURSOR_THEME, ICON_THEME etc.)
# shellcheck disable=SC1091
source "$PROJECT_DIR/config/profiles/stable-cinnamon/profile.conf"
command -v "$DOCKER" >/dev/null \
|| { echo "ERROR: '$DOCKER' not in PATH"; exit 1; }
"$DOCKER" info >/dev/null 2>&1 \
|| { echo "ERROR: '$DOCKER' daemon unreachable"; exit 1; }
mkdir -p "$CACHE_DIR" "$OUT_DIR" "$BUILD_DIR"
# 1) clone + patch mklive
if [[ ! -d "$MKLIVE_DIR/.git" ]]; then
echo ">>> cloning void-mklive"
git clone --depth=1 --branch "$MKLIVE_REF" "$MKLIVE_REPO" "$MKLIVE_DIR"
fi
if compgen -G "$PATCH_DIR/*.patch" >/dev/null; then
echo ">>> resetting + applying iso/patches/"
( cd "$MKLIVE_DIR" && git checkout -- . )
for p in "$PATCH_DIR"/*.patch; do
echo " $(basename "$p")"
( cd "$MKLIVE_DIR" && patch -p1 --silent < "$p" )
done
fi
# 2) xbps-static
XBPS_STATIC_DIR="$CACHE_DIR/xbps-static"
if [[ ! -x "$XBPS_STATIC_DIR/usr/bin/xbps-install.static" ]]; then
echo ">>> downloading xbps-static"
mkdir -p "$XBPS_STATIC_DIR"
curl -fsSL "https://repo-default.voidlinux.org/static/xbps-static-latest.x86_64-musl.tar.xz" \
| tar xJf - -C "$XBPS_STATIC_DIR"
fi
# 3) build includes overlay
echo ">>> staging live includes overlay at $INCLUDE_DIR"
# The nix store (staged by Docker/root) uses 444/555 permissions — chmod first.
chmod -R u+rwX "$INCLUDE_DIR" 2>/dev/null || true
rm -rf "$INCLUDE_DIR"
mkdir -p "$INCLUDE_DIR"
# ── 3a) live user account ───────────────────────────────────────────────
# The vmklive dracut hook (adduser.sh) creates the live user in initramfs
# using the live.user= kernel cmdline parameter (added to BOOT_CMDLINE below).
# We add extra groups and sudo via /etc/runit/2 override (after pivot_root).
install -d -m 0755 "$INCLUDE_DIR/etc"
# GPU detection + extra group setup — runs from our runit/2 override below.
install -d -m 0755 "$INCLUDE_DIR/etc/runit"
cat > "$INCLUDE_DIR/etc/runit/live-setup.sh" <<'SV_EOF'
#!/bin/sh
# Runs from /etc/runit/2 before any service starts.
# 1. Ensures live user has all needed groups.
# 2. Generates /etc/X11/xorg.conf.d/20-gpu.conf based on detected hardware.
LIVE_USER="${USERNAME:-live}"
[ -f /etc/default/live.conf ] && . /etc/default/live.conf
LIVE_USER="${USERNAME:-live}"
# Extra groups (dracut only adds audio,video,wheel)
for g in plugdev input network docker bluetooth; do
groupadd -f "$g" 2>/dev/null || true
usermod -aG "$g" "$LIVE_USER" 2>/dev/null || true
done
install -d -m 0755 /etc/sudoers.d
printf '%s ALL=(ALL) NOPASSWD: ALL\n' "$LIVE_USER" > /etc/sudoers.d/live
chmod 0440 /etc/sudoers.d/live
# ── Nix daemon-mode setup ───────────────────────────────────────────────
# Void's xbps nix package ships nix-daemon + a runit service at /etc/sv/nix-daemon
# and puts the socket at /var/nix/daemon-socket/socket.
# We configure the daemon to trust the live user so 'nix profile install'
# works without root and without owning /nix/store.
if [ -x /usr/bin/nix ]; then
install -d -m 0755 /etc/nix
cat > /etc/nix/nix.conf <<NIXCONF
experimental-features = nix-command flakes
build-users-group =
sandbox = false
auto-optimise-store = true
trusted-users = root $LIVE_USER
max-jobs = 2
http-connections = 10
NIXCONF
# Ensure nix.sh profile.d sets PATH for installed nix packages.
# The Void nix package ships /etc/profile.d/nix.sh; we just supplement it.
cat > /etc/profile.d/nix-live.sh <<'NIXSH'
# Add nix user-profile bin to PATH
if [ -d "$HOME/.nix-profile/bin" ]; then
export PATH="$HOME/.nix-profile/bin:$PATH"
fi
NIXSH
chmod 0644 /etc/profile.d/nix-live.sh
echo "live-setup: nix daemon mode configured (trusted-users = $LIVE_USER)"
fi
# ── nsswitch: remove mdns (library absent on Void; no Avahi daemon) ──────
# 'mdns' in nsswitch.conf without libnss_mdns causes hangs on every DNS
# lookup because glibc spins waiting for Avahi's socket. Remove it so
# standard DNS resolution is used directly.
if [ -f /etc/nsswitch.conf ]; then
sed -i '/^hosts:/s/mdns[^ ]* *//g' /etc/nsswitch.conf
echo "live-setup: removed mdns from nsswitch.conf (hosts line)"
fi
# ── Timezone ──────────────────────────────────────────────────────────────
ln -sf /usr/share/zoneinfo/Europe/Zurich /etc/localtime 2>/dev/null || true
echo "live-setup: timezone set to Europe/Zurich"
# ── Fix sound device permissions ───────────────────────────────────────────
# PCM/control nodes may be root:root at early boot before the audio group
# exists. Re-apply correct ownership so PipeWire/ALSA can open the devices.
chown root:audio /dev/snd/pcmC* /dev/snd/controlC* /dev/snd/hwC* 2>/dev/null || true
chmod 660 /dev/snd/pcmC* /dev/snd/controlC* /dev/snd/hwC* 2>/dev/null || true
udevadm trigger --subsystem-match=sound 2>/dev/null || true
# ── DNS: ensure a working nameserver is configured ───────────────────────
# NetworkManager will overwrite resolv.conf once DHCP completes.
# If the DHCP-provided nameserver is broken (e.g. QEMU's 10.0.2.3), add
# 8.8.8.8 as a fallback. We write it now so first-login.sh can reach the
# internet immediately after login without waiting for NM to settle.
if ! grep -q '^nameserver' /etc/resolv.conf 2>/dev/null || \
grep -q '^nameserver 10\.0\.2\.3' /etc/resolv.conf 2>/dev/null; then
printf 'nameserver 8.8.8.8\nnameserver 1.1.1.1\n' > /etc/resolv.conf
echo "live-setup: set fallback DNS (8.8.8.8, 1.1.1.1)"
fi
# ── D-Bus session socket for the live user's login session ───────────────
# dbus-launch is called by lightdm-session / Xsession automatically.
# Ensure dbus system bus is accessible (needed by nix and Cinnamon).
# The dbus runit service is enabled in runsvdir/default; no extra setup needed.
# ── GPU / Xorg driver detection ──────────────────────────────────────────
mkdir -p /etc/X11/xorg.conf.d
# Detect GPU via PCI
HAS_NVIDIA=0
HAS_INTEL=0
HAS_AMD=0
IS_VIRT=0
if command -v lspci >/dev/null 2>&1; then
lspci_out=$(lspci 2>/dev/null)
echo "$lspci_out" | grep -qi 'NVIDIA' && HAS_NVIDIA=1
echo "$lspci_out" | grep -qiE 'Intel.*VGA|VGA.*Intel|Intel.*Integrated' && HAS_INTEL=1
echo "$lspci_out" | grep -qiE 'AMD.*VGA|VGA.*AMD|ATI.*VGA|Radeon' && HAS_AMD=1
echo "$lspci_out" | grep -qiE 'virtio|VMware|QEMU|VirtualBox|bochs' && IS_VIRT=1
fi
if [ $IS_VIRT -eq 1 ]; then
# Virtual machine: force modesetting, disable accel, use software GL
cat > /etc/X11/xorg.conf.d/20-gpu.conf <<'EOF'
Section "Device"
Identifier "GPU"
Driver "modesetting"
Option "AccelMethod" "none"
EndSection
EOF
# Xsession sources /etc/profile which sources /etc/profile.d/ — so this
# env var reaches the cinnamon session (LightDM's session-env is not
# supported in the Void-packaged lightdm 1.32).
echo 'export LIBGL_ALWAYS_SOFTWARE=1' > /etc/profile.d/live-env.sh
chmod 0644 /etc/profile.d/live-env.sh
echo "live-setup: VIRT detected — modesetting (no accel), software GL"
elif [ $HAS_NVIDIA -eq 1 ] && [ -e /usr/lib/xorg/modules/drivers/nvidia_drv.so ]; then
# NVIDIA PRIME (Intel iGPU primary + NVIDIA render offload)
# Real hardware — ensure software GL is NOT forced
rm -f /etc/profile.d/live-env.sh
INTEL_BUSID=$(lspci | grep -iE 'VGA.*Intel|Intel.*VGA' | head -1 | \
awk '{print $1}' | awk -F'[.:]' '{printf "PCI:%d:%d:%d", strtonum("0x"$1), strtonum("0x"$2), strtonum("0x"$3)}')
NVIDIA_BUSID=$(lspci | grep -i NVIDIA | head -1 | \
awk '{print $1}' | awk -F'[.:]' '{printf "PCI:%d:%d:%d", strtonum("0x"$1), strtonum("0x"$2), strtonum("0x"$3)}')
cat > /etc/X11/xorg.conf.d/20-gpu.conf <<EOF
Section "ServerLayout"
Identifier "layout"
Option "AllowNVIDIAGPUScreens"
EndSection
Section "Device"
Identifier "Intel"
Driver "modesetting"
BusID "${INTEL_BUSID:-PCI:0:2:0}"
EndSection
Section "Device"
Identifier "NVIDIA"
Driver "nvidia"
BusID "${NVIDIA_BUSID:-PCI:1:0:0}"
Option "AllowEmptyInitialConfiguration"
EndSection
EOF
echo "live-setup: NVIDIA PRIME detected (Intel=${INTEL_BUSID} NVIDIA=${NVIDIA_BUSID})"
elif [ $HAS_NVIDIA -eq 1 ]; then
# NVIDIA present but no proprietary driver — use modesetting
rm -f /etc/profile.d/live-env.sh
cat > /etc/X11/xorg.conf.d/20-gpu.conf <<'EOF'
Section "Device"
Identifier "GPU"
Driver "modesetting"
EndSection
EOF
echo "live-setup: NVIDIA detected but no proprietary driver — modesetting"
else
# Intel / AMD / generic — modesetting
rm -f /etc/profile.d/live-env.sh
cat > /etc/X11/xorg.conf.d/20-gpu.conf <<'EOF'
Section "Device"
Identifier "GPU"
Driver "modesetting"
EndSection
EOF
echo "live-setup: Generic/Intel/AMD — modesetting"
fi
SV_EOF
chmod 0755 "$INCLUDE_DIR/etc/runit/live-setup.sh"
# Override /etc/runit/2 — mirrors real runit-void stage 2 but runs live-setup.sh first.
# Real runit-void/2: runsvchdir default → runsvdir /run/runit/runsvdir/current
cat > "$INCLUDE_DIR/etc/runit/2" <<'SV_EOF'
#!/bin/sh
PATH=/usr/bin:/usr/sbin
# Live session setup: extra groups, sudo, GPU/Xorg detection
[ -x /etc/runit/live-setup.sh ] && /etc/runit/live-setup.sh
# Mirror real runit-void stage 2: select runlevel from cmdline (default: default)
runlevel=default
for arg in $(cat /proc/cmdline); do
if [ -d /etc/runit/runsvdir/"$arg" ]; then
runlevel="$arg"
fi
done
[ -x /etc/rc.local ] && /etc/rc.local
runsvchdir "${runlevel}"
mkdir -p /run/runit/runsvdir
ln -sf /etc/runit/runsvdir/current /run/runit/runsvdir/current
exec env - PATH=$PATH \
runsvdir -P /run/runit/runsvdir/current \
'log: ...........................................................................................................................................................................................................................................................................................................................................................................................................'
SV_EOF
chmod 0755 "$INCLUDE_DIR/etc/runit/2"
install -d -m 0755 "$INCLUDE_DIR/etc/runit/runsvdir/default"
# Write all repos to xbps.d so they persist in the live squashfs.
# The nonfree repo is needed for nvidia and other proprietary drivers.
install -d -m 0755 "$INCLUDE_DIR/etc/xbps.d"
cat > "$INCLUDE_DIR/etc/xbps.d/00-void-repos.conf" <<EOF
repository=${REPO_URL:-https://repo-default.voidlinux.org/current}
repository=${REPO_URL%/current}/current/nonfree
repository=${REPO_URL%/current}/current/multilib
repository=${REPO_URL%/current}/current/multilib/nonfree
EOF
# ── 3b) LightDM autologin ───────────────────────────────────────────────
install -d -m 0755 "$INCLUDE_DIR/etc/lightdm"
# .session file: read by the vmklive dracut hook (display-manager-autologin.sh)
# to configure LightDM autologin session.
echo 'cinnamon' > "$INCLUDE_DIR/etc/lightdm/.session"
# lightdm.conf: autologin lines MUST be commented so the dracut
# display-manager-autologin.sh hook can sed-uncomment them.
cat > "$INCLUDE_DIR/etc/lightdm/lightdm.conf" <<'EOF'
[Seat:*]
#autologin-user=
#autologin-user-timeout=0
#autologin-session=
#user-session=
session-wrapper=/etc/lightdm/Xsession
greeter-session=lightdm-gtk-greeter
EOF
# NOTE: session-wrapper=/etc/lightdm/Xsession is required on Void Linux —
# LightDM defaults to 'lightdm-session' which is NOT provided by the Void
# lightdm package. The /etc/lightdm/Xsession wrapper is installed by the
# lightdm package and sources /etc/profile (and therefore /etc/profile.d/)
# so LIBGL_ALWAYS_SOFTWARE=1 placed there by live-setup.sh is propagated to
# the cinnamon session.
install -d -m 0755 "$INCLUDE_DIR/etc/runit/runsvdir/default"
# Enable services for the live session.
for svc in dbus NetworkManager lightdm bluetoothd; do
ln -sf "/etc/sv/$svc" "$INCLUDE_DIR/etc/runit/runsvdir/default/$svc" 2>/dev/null || true
done
# ── 3c) Themes / icons / wallpapers (same as installer) ─────────────────
echo ">>> staging customizations overlay"
OVERLAY="$INCLUDE_DIR/etc/installer-overlay"
install -d -m 0755 "$OVERLAY" "$OVERLAY/wallpapers" \
"$OVERLAY/themes" "$OVERLAY/icons" \
"$OVERLAY/skel" "$OVERLAY/vscode-user"
# Wallpapers
WP_SRC="${WALLPAPERS_SRC:-$HOME/Scaricati}"
shopt -s nullglob
for f in "$WP_SRC"/pxfuel*.jpg; do
install -m 0644 "$f" "$OVERLAY/wallpapers/$(basename "$f")"
done
shopt -u nullglob
echo " wallpapers: $(ls "$OVERLAY/wallpapers" | wc -l) file(s)"
# Gruvbox GTK theme
THEME_CACHE="$CACHE_DIR/gruvbox-gtk-theme"
THEME_BUILD="$CACHE_DIR/gruvbox-gtk-built"
if [[ ! -d "$THEME_CACHE/.git" ]]; then
git clone --depth=1 https://github.com/Fausto-Korpsvart/Gruvbox-GTK-Theme.git "$THEME_CACHE" || true
fi
if [[ -x "$THEME_CACHE/themes/install.sh" && ! -d "$THEME_BUILD" ]]; then
echo " building gruvbox themes"
install -d -m 0755 "$THEME_BUILD"
"$DOCKER" run --rm \
-v "$THEME_CACHE":/src \
-v "$THEME_BUILD":/out \
debian:stable-slim sh -c '
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq >/dev/null
apt-get install -y --no-install-recommends sassc bash >/dev/null
cd /src/themes && bash install.sh -d /out -t default -c dark -s standard
' || true
fi
if [[ -d "$THEME_BUILD" ]]; then
for d in "$THEME_BUILD"/Gruvbox-Dark*; do
[[ -d "$d" ]] && cp -a "$d" "$OVERLAY/themes/$(basename "$d")"
done
echo " themes: $(ls "$OVERLAY/themes" 2>/dev/null | wc -l) variant(s)"
fi
# Gruvbox Plus icons
ICON_CACHE="$CACHE_DIR/gruvbox-plus-icons"
if [[ ! -d "$ICON_CACHE/.git" ]]; then
git clone --depth=1 https://github.com/SylEleuth/gruvbox-plus-icon-pack.git "$ICON_CACHE" || true
fi
if [[ -d "$ICON_CACHE/Gruvbox-Plus-Dark" ]]; then
cp -a "$ICON_CACHE/Gruvbox-Plus-Dark" "$OVERLAY/icons/Gruvbox-Plus-Dark"
echo " icons: Gruvbox-Plus-Dark"
fi
# Bibata cursor
BIBATA_SRC="${BIBATA_SRC:-/usr/share/icons/Bibata-Modern-Ice}"
if [[ -d "$BIBATA_SRC" ]]; then
cp -a "$BIBATA_SRC" "$OVERLAY/icons/Bibata-Modern-Ice"
echo " cursor: Bibata-Modern-Ice"
fi
# Dotfiles
DOTFILES_SRC="${DOTFILES_SRC:-$HOME}"
for f in .bashrc .bash_aliases .gitconfig; do
[[ -r "$DOTFILES_SRC/$f" ]] && install -m 0644 "$DOTFILES_SRC/$f" "$OVERLAY/skel/$f"
done
# VS Code user config
VSCODE_SRC="${VSCODE_USER_SRC:-$HOME/.config/Code/User}"
if [[ -d "$VSCODE_SRC" ]]; then
for f in settings.json keybindings.json mcp.json tasks.json; do
[[ -r "$VSCODE_SRC/$f" ]] && install -m 0644 "$VSCODE_SRC/$f" "$OVERLAY/vscode-user/$f"
done
[[ -d "$VSCODE_SRC/snippets" ]] && cp -a "$VSCODE_SRC/snippets" "$OVERLAY/vscode-user/snippets"
[[ -d "$VSCODE_SRC/globalStorage" ]] && cp -a "$VSCODE_SRC/globalStorage" "$OVERLAY/vscode-user/globalStorage"
fi
if command -v code >/dev/null 2>&1; then
code --list-extensions > "$OVERLAY/vscode-extensions.txt" 2>/dev/null || true
fi
# Claude config
CLAUDE_SRC="${CLAUDE_SRC:-$HOME/.claude}"
[[ -d "$CLAUDE_SRC" ]] && cp -a "$CLAUDE_SRC" "$OVERLAY/claude"
[[ -r "${HOME}/.claude.json" ]] && install -m 0600 "${HOME}/.claude.json" "$OVERLAY/claude.json"
# first-login.sh
[[ -r "$PROJECT_DIR/installer/first-login.sh" ]] && \
install -m 0755 "$PROJECT_DIR/installer/first-login.sh" "$OVERLAY/first-login.sh"
# ── 3d) Pre-apply themes/dconf to the live user's home via /etc/skel ────
# We pre-write the dconf system-db so it takes effect without a user build step.
install -d -m 0755 "$INCLUDE_DIR/etc/dconf/db/local.d" \
"$INCLUDE_DIR/etc/dconf/profile"
cat > "$INCLUDE_DIR/etc/dconf/profile/user" <<'EOF'
user-db:user
system-db:local
EOF
WALLPAPER_FILE=$(ls "$OVERLAY/wallpapers/"*.jpg 2>/dev/null | head -1 | xargs basename 2>/dev/null || echo "pxfuel.jpg")
cat > "$INCLUDE_DIR/etc/dconf/db/local.d/00-cinnamon" <<EOF
[org/cinnamon/desktop/interface]
gtk-theme='${GTK_THEME}'
icon-theme='${ICON_THEME}'
cursor-theme='${CURSOR_THEME}'
[org/cinnamon/desktop/wm/preferences]
theme='${GTK_THEME}'
[org/cinnamon/theme]
name='${GTK_THEME}'
[org/cinnamon/desktop/background]
picture-uri='file:///usr/share/backgrounds/void-installer/${WALLPAPER_FILE}'
picture-options='zoom'
[org/gnome/desktop/background]
picture-uri='file:///usr/share/backgrounds/void-installer/${WALLPAPER_FILE}'
picture-options='zoom'
[org/gnome/desktop/input-sources]
sources=[('xkb', '${KEYMAP//-/+}')]
[org/gnome/desktop/interface]
color-scheme='prefer-dark'
[org/cinnamon/desktop/default-applications/terminal]
exec='alacritty'
exec-arg='-e'
[org/gnome/desktop/default-applications/terminal]
exec='alacritty'
exec-arg='-e'
[org/cinnamon/desktop/keybindings/custom-keybindings/custom0]
name='Open Terminal'
command='${DEFAULT_TERMINAL:-alacritty}'
binding=['<Primary><Alt>t']
EOF
# dconf lock file — prevents the live user's session from overriding the
# keyboard layout with an empty list on first start.
install -d -m 0755 "$INCLUDE_DIR/etc/dconf/db/local.d/locks"
cat > "$INCLUDE_DIR/etc/dconf/db/local.d/locks/keyboard" <<'EOF'
/org/gnome/desktop/input-sources/sources
/org/cinnamon/desktop/default-applications/terminal/exec
/org/cinnamon/desktop/default-applications/terminal/exec-arg
/org/gnome/desktop/default-applications/terminal/exec
/org/gnome/desktop/default-applications/terminal/exec-arg
EOF
# ── 3e) Copy themes/icons/wallpapers directly into usr/share ────────────
# (dconf compile happens inside the Docker container — see _inner-build-live.sh)
install -d -m 0755 "$INCLUDE_DIR/usr/share/backgrounds/void-installer"
cp -a "$OVERLAY/wallpapers"/. "$INCLUDE_DIR/usr/share/backgrounds/void-installer/" 2>/dev/null || true
install -d -m 0755 "$INCLUDE_DIR/usr/share/themes"
[[ -d "$OVERLAY/themes" ]] && cp -a "$OVERLAY/themes"/. "$INCLUDE_DIR/usr/share/themes/" 2>/dev/null || true
install -d -m 0755 "$INCLUDE_DIR/usr/share/icons"
[[ -d "$OVERLAY/icons" ]] && cp -a "$OVERLAY/icons"/. "$INCLUDE_DIR/usr/share/icons/" 2>/dev/null || true
# ── 3e-bis) Nemo actions ─────────────────────────────────────────────────
install -d -m 0755 "$INCLUDE_DIR/usr/share/nemo/actions"
install -d -m 0755 "$INCLUDE_DIR/usr/local/bin"
# Wrapper script: finds 'code' in the live user's nix profile.
# Used by the Nemo action so it doesn't need to know the nix profile path.
cat > "$INCLUDE_DIR/usr/local/bin/code-open" <<'EOF'
#!/bin/sh
# Open VS Code for the given file/folder arguments.
# Looks in the live user's nix profile first, then falls back to PATH.
for d in "/home/live/.nix-profile/bin" /nix/var/nix/profiles/per-user/live/profile/bin \
/usr/local/bin /usr/bin; do
if [ -x "$d/code" ]; then
exec "$d/code" "$@"
fi
done
exec code "$@"
EOF
chmod 0755 "$INCLUDE_DIR/usr/local/bin/code-open"
# Nemo action: Open in VS Code
cat > "$INCLUDE_DIR/usr/share/nemo/actions/open-in-vscode.nemo_action" <<'EOF'
[Nemo Action]
Name=Open in VS _Code
Comment=Open in Visual Studio Code
Exec=/usr/local/bin/code-open %F
Icon-Name=code
Selection=Any
Extensions=any;
Dependencies=/usr/local/bin/code-open;
EOF
# ── 3f) Live session baked-in configuration ──────────────────────────────
# No first-login script. Everything is baked in at build time.
# The apply-settings script runs once at first Cinnamon login to apply
# gsettings (100% reliable vs dconf system-db binary format variations).
install -d -m 0755 "$INCLUDE_DIR/usr/local/libexec"
install -d -m 0755 "$INCLUDE_DIR/etc/xdg/autostart"
# Keep first-login.sh available for MANUAL use (Claude, NVM installs) but
# do NOT autostart it. User can run it from a terminal if needed.
if [[ -r "$OVERLAY/first-login.sh" ]]; then
install -m 0755 "$OVERLAY/first-login.sh" "$INCLUDE_DIR/usr/local/libexec/first-login.sh"
fi
# apply-settings.sh: runs once at first Cinnamon login, applies theme/wallpaper/
# terminal via gsettings (writes to user dconf db — always works).
cat > "$INCLUDE_DIR/usr/local/libexec/apply-live-settings.sh" <<EOF
#!/bin/bash
# Baked live-session settings. Runs once at first Cinnamon login.
DONE="\$HOME/.void-live-settings-done"
[[ -f "\$DONE" ]] && exit 0
# Wait for D-Bus session bus (LightDM autologin starts it, be safe)
for _i in \$(seq 15); do
[[ -n "\${DBUS_SESSION_BUS_ADDRESS:-}" ]] && break
eval "\$(dbus-launch --sh-syntax 2>/dev/null)" || true
sleep 0.3
done
# GTK / Cinnamon DE theme
gsettings set org.cinnamon.desktop.interface gtk-theme '${GTK_THEME}'
gsettings set org.cinnamon.desktop.interface icon-theme '${ICON_THEME}'
gsettings set org.gnome.desktop.interface cursor-theme '${CURSOR_THEME}'
gsettings set org.cinnamon.theme name '${GTK_THEME}'
# Wallpaper (first .jpg found in the backgrounds dir)
_WP=\$(ls /usr/share/backgrounds/void-installer/*.jpg 2>/dev/null | head -1 || echo '')
[[ -n "\$_WP" ]] && gsettings set org.cinnamon.desktop.background picture-uri "file://\$_WP"
[[ -n "\$_WP" ]] && gsettings set org.gnome.desktop.background picture-uri "file://\$_WP"
# Default terminal
gsettings set org.cinnamon.desktop.default-applications.terminal exec '${DEFAULT_TERMINAL:-alacritty}'
gsettings set org.cinnamon.desktop.default-applications.terminal exec-arg '-e'
gsettings set org.gnome.desktop.default-applications.terminal exec '${DEFAULT_TERMINAL:-alacritty}'
gsettings set org.gnome.desktop.default-applications.terminal exec-arg '-e'
# Keyboard layout — set explicitly via gsettings (belt-and-suspenders alongside
# the user dconf db pre-baked in /etc/skel at build time).
gsettings set org.gnome.desktop.input-sources sources "[('xkb', '${KEYMAP//-/+}')]"
touch "\$DONE"
EOF
chmod 0755 "$INCLUDE_DIR/usr/local/libexec/apply-live-settings.sh"
cat > "$INCLUDE_DIR/etc/xdg/autostart/void-live-settings.desktop" <<'DESK'
[Desktop Entry]
Type=Application
Name=Void Live Apply Settings
Exec=/usr/local/libexec/apply-live-settings.sh
NoDisplay=true
X-GNOME-Autostart-enabled=true
OnlyShowIn=X-Cinnamon;
DESK
# /etc/environment: XDG_DATA_DIRS includes the pre-baked nix profile so that
# Cinnamon's app menu picks up .desktop files from the nix store without
# requiring any PATH tricks in the graphical session.
cat > "$INCLUDE_DIR/etc/environment" <<'ENVEOF'
XDG_DATA_DIRS=/home/live/.nix-profile/share:/usr/local/share:/usr/share
ENVEOF
# ── Timezone ──────────────────────────────────────────────────────────────
ln -sf /usr/share/zoneinfo/Europe/Zurich "$INCLUDE_DIR/etc/localtime"
cat > "$INCLUDE_DIR/etc/rc.conf" <<'RCEOF'
KEYMAP="ch"
HARDWARECLOCK="UTC"
TIMEZONE="Europe/Zurich"
RCEOF
# ── Sound device udev rules ──────────────────────────────────────────────
# PCM/control nodes are created root:root at early boot before the audio group
# is provisioned; this rule ensures correct ownership on every boot.
install -d -m 0755 "$INCLUDE_DIR/etc/udev/rules.d"
cat > "$INCLUDE_DIR/etc/udev/rules.d/70-sound-perms.rules" <<'EOF'
SUBSYSTEM=="sound", GROUP="audio", MODE="0660"
EOF
# ── NVIDIA PRIME overlay ────────────────────────────────────────────────
# Blacklist nouveau — the live-setup.sh PRIME detection block already writes
# Xorg config, but the kernel must also not load nouveau.
install -d -m 0755 "$INCLUDE_DIR/etc/modprobe.d"
cat > "$INCLUDE_DIR/etc/modprobe.d/blacklist-nouveau.conf" <<'EOF'
blacklist nouveau
options nouveau modeset=0
EOF
cat > "$INCLUDE_DIR/etc/modprobe.d/btusb-quirks.conf" <<'EOF'
options btusb enable_autosuspend=0
EOF
install -d -m 0755 "$INCLUDE_DIR/etc/modules-load.d"
cat > "$INCLUDE_DIR/etc/modules-load.d/nvidia.conf" <<'EOF'
nvidia
nvidia_modeset
nvidia_uvm
nvidia_drm
EOF
install -d -m 0755 "$INCLUDE_DIR/etc/dracut.conf.d"
cat > "$INCLUDE_DIR/etc/dracut.conf.d/10-nvidia.conf" <<'EOF'
add_drivers+=" nvidia nvidia_modeset nvidia_uvm nvidia_drm "
omit_drivers+=" nouveau "
EOF
install -d -m 0755 "$INCLUDE_DIR/usr/local/bin"
cat > "$INCLUDE_DIR/usr/local/bin/prime-run" <<'EOF'
#!/bin/sh
exec env __NV_PRIME_RENDER_OFFLOAD=1 \
__VK_LAYER_NV_optimus=NVIDIA_only \
__GLX_VENDOR_LIBRARY_NAME=nvidia \
"$@"
EOF
chmod 0755 "$INCLUDE_DIR/usr/local/bin/prime-run"
# /etc/profile.d: PATH for interactive terminals (alacritty, etc.)
install -d -m 0755 "$INCLUDE_DIR/etc/profile.d"
cat > "$INCLUDE_DIR/etc/profile.d/nix-prebaked.sh" <<'NIXEOF'
# Pre-baked nix profile — set up PATH for interactive shells.
if [[ -d "${HOME:-}/.nix-profile/bin" ]]; then
case ":$PATH:" in
*":$HOME/.nix-profile/bin:"*) ;;
*) export PATH="$HOME/.nix-profile/bin:$PATH" ;;
esac
fi
export NIXPKGS_ALLOW_UNFREE=1
# Pre-baked nix is single-user (no daemon) — bypass daemon connection attempt
export NIX_REMOTE=local
# Flake commands ignore NIXPKGS_ALLOW_UNFREE unless --impure is passed.
# Wrap nix so interactive installs work without extra flags.
nix() { command nix "$@" --impure; }
export -f nix
NIXEOF
# ── 3f-ins) Void Linux installer in live image ───────────────────────────
# install.sh uses INSTALLER_DIR=$(dirname $0)/lib for its libraries, so we
# keep the script and lib/ together under /usr/local/lib/void-installer/.
# SHARE_DIR (/usr/local/share/installer) holds config, packages, profiles.
install -d -m 0755 "$INCLUDE_DIR/usr/local/lib/void-installer/lib"
install -d -m 0755 "$INCLUDE_DIR/usr/local/share/installer"
install -m 0755 "$PROJECT_DIR/installer/install.sh" "$INCLUDE_DIR/usr/local/lib/void-installer/install.sh"
for f in "$PROJECT_DIR/installer/lib/"*.sh; do
install -m 0755 "$f" "$INCLUDE_DIR/usr/local/lib/void-installer/lib/$(basename "$f")"
done
# Config and packages — read by install.sh via SHARE_DIR
install -m 0644 "$PROJECT_DIR/config/install.conf" "$INCLUDE_DIR/usr/local/share/installer/install.conf"
install -m 0644 "$PROJECT_DIR/config/profiles/stable-cinnamon/packages.list" "$INCLUDE_DIR/usr/local/share/installer/packages.list"
# Profiles directory (profile.conf + per-profile packages.list)
if [[ -d "$PROJECT_DIR/config/profiles" ]]; then
cp -r "$PROJECT_DIR/config/profiles" "$INCLUDE_DIR/usr/local/share/installer/profiles"
fi
# Thin wrapper so the user can type `void-install` from anywhere
install -d -m 0755 "$INCLUDE_DIR/usr/local/bin"
cat > "$INCLUDE_DIR/usr/local/bin/void-install" <<'WRAPEOF'
#!/bin/sh
exec /usr/local/lib/void-installer/install.sh "$@"
WRAPEOF
chmod 0755 "$INCLUDE_DIR/usr/local/bin/void-install"
# Secrets: read from secrets.env at build time, baked as /etc/installer-secrets.env
# (the file is .gitignored; if absent the installer prompts interactively)
_SECRETS_SRC="${SECRETS_ENV:-$PROJECT_DIR/secrets.env}"
if [[ -r "$_SECRETS_SRC" ]]; then
install -d -m 0755 "$INCLUDE_DIR/etc"
install -m 0600 "$_SECRETS_SRC" "$INCLUDE_DIR/etc/installer-secrets.env"
echo " baked installer secrets from $_SECRETS_SRC"
else
echo " WARNING: no secrets.env found — installer will prompt for passwords at runtime"
fi
# Desktop launcher — opens alacritty running void-install as root (live user
# has passwordless sudo configured by mklive).
install -d -m 0755 "$INCLUDE_DIR/usr/share/applications"
cat > "$INCLUDE_DIR/usr/share/applications/void-installer.desktop" <<'DESKEOF'
[Desktop Entry]
Version=1.0
Type=Application
Name=Install Void Linux
Comment=Install Void Linux to this machine
Exec=alacritty --title "Void Linux Installer" -e sudo /usr/local/bin/void-install
Icon=system-software-install
Terminal=false
Categories=System;
StartupNotify=true
DESKEOF
install -d -m 0755 "$INCLUDE_DIR/etc/profile.d"
cat >> "$INCLUDE_DIR/etc/profile.d/nix-prebaked.sh" <<'EOF'
case ":$PATH:" in
*":$HOME/.local/bin:"*) ;;
*) export PATH="$HOME/.local/bin:$PATH" ;;
esac
EOF
# nix-packages.list: kept so first-login.sh can still be run manually
install -d -m 0755 "$INCLUDE_DIR/usr/local/libexec"
{
for pkg in "${NIX_USER_PACKAGES[@]}"; do echo "$pkg"; done
} > "$INCLUDE_DIR/usr/local/libexec/nix-packages.list"
cat > "$INCLUDE_DIR/etc/profile.d/nix-packages-path.sh" <<'EOF'
export NIX_PACKAGES_FILE=/usr/local/libexec/nix-packages.list
EOF
# nixpkgs config: allow unfree packages for all users
install -d -m 0755 "$INCLUDE_DIR/etc/skel/.config/nixpkgs"
echo '{ allowUnfree = true; }' > "$INCLUDE_DIR/etc/skel/.config/nixpkgs/config.nix"
# git: GUI askpass so prompts work without a controlling terminal
install -d -m 0755 "$INCLUDE_DIR/usr/local/bin"
cat > "$INCLUDE_DIR/usr/local/bin/git-askpass" <<'EOF'
#!/bin/sh
for cmd in zenity qarma; do
command -v "$cmd" >/dev/null 2>&1 || continue
case "$1" in
*[Uu]sername*) exec "$cmd" --entry --title="Git Credentials" --text="$1" ;;
*) exec "$cmd" --password --title="Git Credentials" --text="$1" ;;
esac
done
printf '%s' "$1" >&2; read -r answer; printf '%s\n' "$answer"
EOF
chmod 0755 "$INCLUDE_DIR/usr/local/bin/git-askpass"
cat > "$INCLUDE_DIR/etc/gitconfig" <<'EOF'
[core]
askPass = /usr/local/bin/git-askpass
EOF
# ── 3g) Skel: .bash_profile sources .bashrc only (no first-login autorun) ──
install -d -m 0755 "$INCLUDE_DIR/etc/skel"
cat > "$INCLUDE_DIR/etc/skel/.bash_profile" <<'EOF'
# Source .bashrc for interactive login shells.
[[ -f ~/.bashrc ]] && . ~/.bashrc
EOF
# 4) build Docker image
echo ">>> building docker image $DOCKER_IMAGE"
if "$DOCKER" buildx version >/dev/null 2>&1; then
"$DOCKER" build -t "$DOCKER_IMAGE" "$PROJECT_DIR/iso"
else
DOCKER_BUILDKIT=0 "$DOCKER" build -t "$DOCKER_IMAGE" "$PROJECT_DIR/iso"
fi
# 5) packages + output filename
ISO_PKGS=$(grep -vE '^\s*(#|$)' \
"$PROJECT_DIR/config/profiles/stable-cinnamon/packages.live-desktop.list" \
| tr '\n' ' ')
TS="$(date -u +%Y%m%d)"
OUT_ISO="${OUTPUT_ISO:-$OUT_DIR/void-live-stable-${TS}.iso}"
# live.user=live → vmklive dracut hook creates user 'live' (default would be 'anon')
# console=ttyS0 → serial output for QEMU/real hardware debugging
BOOT_CMDLINE="${BOOT_CMDLINE:-live.user=${LIVE_USER} console=tty0 console=ttyS0,115200 nvidia-drm.modeset=1 rd.driver.blacklist=nouveau modprobe.blacklist=nouveau btusb.enable_autosuspend=0}"
echo ">>> running mklive.sh inside docker — output: $OUT_ISO"
"$DOCKER" run --rm --privileged \
-v "$PROJECT_DIR:/work:rw" \
-v "$CACHE_DIR:/cache:rw" \
-e ARCH="${ARCH:-x86_64}" \
-e REPO_URL="${REPO_URL:-https://repo-default.voidlinux.org/current}" \
-e KEYMAP="${KEYMAP:-ch-fr_nodeadkeys}" \
-e LOCALE="${LOCALE:-en_US.UTF-8}" \
-e ISO_PKGS="$ISO_PKGS" \
-e ISO_TITLE="Void Live (${GTK_THEME} / Cinnamon)" \
-e OUT_ISO_REL="${OUT_ISO#$PROJECT_DIR/}" \
-e BOOT_CMDLINE="${BOOT_CMDLINE:-}" \
-e INCLUDE_DIR_REL="${INCLUDE_DIR#$PROJECT_DIR/}" \
-e NIX_PACKAGES_PREBAKE="${NIX_USER_PACKAGES[*]}" \
-e HOST_UID="$(id -u)" \
-e HOST_GID="$(id -g)" \
"$DOCKER_IMAGE" \
bash /work/iso/_inner-build-live.sh
echo
echo ">>> Live ISO built: $OUT_ISO"
sha256sum "$OUT_ISO" | tee "${OUT_ISO}.sha256" || true