Compare commits

...

9 Commits

Author SHA1 Message Date
Giancarmine Salucci
56dfe11039 refactor: unified multi-profile build system
Add 4 configurable profiles (stable-cinnamon, stable-niri,
mainline-cinnamon, mainline-niri) with a single unified build
entry point.

- iso/build.sh: replaces build-iso.sh / build-live-iso.sh /
  build-niri-live-iso.sh; accepts --profile and --type flags
- iso/_inner-build-unified.sh: replaces the three _inner-build-*.sh
  scripts; branches on BUILD_TYPE / DESKTOP / KERNEL_PKG env vars
- config/profiles/stable-niri/: new — linux (k6) + niri/Wayland/noctalia
- config/profiles/mainline-cinnamon/: new — linux-mainline (k7) + Cinnamon/X11
- config/profiles/mainline-niri/packages.live.list: symlink added
- config/profiles/stable-cinnamon/packages.live.list: symlink added
- Makefile: PROFILE variable (default stable-cinnamon), shellcheck updated
- installer/install.sh: respects DEFAULT_PROFILE env (set by live ISO)
- tests/run-qemu-test.sh: passes PROFILE through to build and overlay

Live ISOs embed the installer pre-configured for the same profile
they were built with (DEFAULT_PROFILE in /etc/profile.d/).
2026-04-26 12:42:11 +02:00
mozempk
88797bb1e9 Add kernel-7 audio diagnostics and verify SOF/UCM completeness
- Add alsa-ucm-conf to installed packages.list (already in live-desktop.list)
- Create KERNEL7_AUDIO_XPS9700.md with comprehensive audio diagnostics guide
- Document kernel parameter choices and SOF/SoundWire fix approach in build scripts
- Includes boot-time dsp_driver=3 test procedure for XPS 9700 kernel 7 compatibility
2026-04-25 22:35:46 +02:00
mozempk
2dc1881b69 Fix niri live ISO audio and dark theme setup 2026-04-25 22:22:07 +02:00
mozempk
21de42b6b1 feat(niri-live): dual-kernel boot menu, audio fix, elogind fix, rsync optimization
- Add kernel 7 (linux-mainline) as primary boot entry
- Add kernel 6 (linux) as secondary fallback boot entry
- Simplified boot menu: single entry per kernel, no failsafe variants
- Add snd-intel-dspcfg.dsp_driver=1 + snd_hda_intel.dmic_detect=0 to BOOT_CMDLINE
- Fix elogind sv: dual D-Bus + PID check to suppress already-running warnings
- Replace cp -rfpPv with rsync -aHX in copy_include_directories (nixpkgs speed fix)
- Fix trailing slash bash glob bug in mklive.sh and postsetup-nvidia.sh
- Add -v linux-mainline to _inner-build-niri-live.sh for correct primary kernel
2026-04-25 21:38:07 +02:00
mozempk
6bb29fc081 fix(mainline-niri): add linux-mainline-headers to enable nvidia DKMS on kernel 7.0 2026-04-25 20:15:43 +02:00
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
moze
40f4efceed Add GUI git-askpass so credential prompts work without a TTY
Installs /usr/local/bin/git-askpass (uses zenity or qarma) and sets
core.askPass in /etc/gitconfig. Git now pops a GUI dialog for username/
password instead of trying to open /dev/tty, which fails in headless
contexts (scripts, Claude Code terminal).

Adds zenity to live desktop package lists for both niri and cinnamon profiles.
Propagated to: both live ISO builders and the installer postinstall.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 13:04:54 +00:00
moze
106fa940d7 Configure git credential.helper=store system-wide
Prevents git from trying to open /dev/tty for password prompts in
environments without a controlling terminal (live ISO, scripts, Claude Code).
Credentials stored once in ~/.git-credentials and reused automatically.

Applied to: both live ISO include overlays and the installer target system.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 13:01:15 +00:00
moze
49d94bd2ac Fix nix single-user mode for live ISO and installer
- Store ownership: chown -R 1000:1000 at Docker build time (not runtime)
  so the live user can create lock files without flooding the tmpfs overlay
- nix.conf: add build-users-group= to force single-user mode and avoid
  daemon connection attempts (xbps nix-daemon v2.30.2 incompatible with
  pre-baked nix v2.34.6)
- profile.d: export NIX_REMOTE=local and NIXPKGS_ALLOW_UNFREE=1; wrap nix()
  to append --impure so flake installs work without extra flags
- Skel: add ~/.config/nixpkgs/config.nix with allowUnfree=true
- postinstall.sh: fix daemon socket path (/nix/var/nix/...), write
  ~/.config/nixpkgs/config.nix for installed user
- first-login.sh: add NIX_REMOTE=local alongside NIXPKGS_ALLOW_UNFREE=1
- Remove nix-daemon from live ISO services (wrong version for pre-baked client)
- Misc: bluetooth group, package list reorg, skip vscode install for niri profile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:56:13 +00:00
31 changed files with 3563 additions and 77 deletions

View File

@@ -1,8 +1,12 @@
# Void Installer — XPS 17 (xps9700)
#
# Profiles: stable-cinnamon | stable-niri | mainline-cinnamon | mainline-niri
# kernel: stable = Void's linux (k6), mainline = linux-mainline (k7)
# DE: cinnamon = Cinnamon/X11, niri = niri+noctalia-shell/Wayland
#
# Targets:
# make iso build the auto-installing ISO (uses docker)
# make live build the full Cinnamon live desktop ISO
# make iso build the auto-installing ISO (PROFILE=stable-cinnamon)
# make live build the full desktop live ISO (PROFILE=stable-cinnamon)
# make test-disk create a fresh QEMU test disk that mimics XPS 17 layout
# make test full automated QEMU smoke test
# make test-iso rebuild only the TEST ISO variant
@@ -10,10 +14,16 @@
# make shellcheck lint all installer/build shell scripts
# make clean remove build/, out/ (cache stays)
# make distclean also remove cache/
#
# Examples:
# make live PROFILE=mainline-niri
# make iso PROFILE=stable-cinnamon
# make test PROFILE=mainline-cinnamon
PROJECT_DIR := $(CURDIR)
OUT := $(PROJECT_DIR)/out
SECRETS := $(PROJECT_DIR)/secrets.env
PROFILE ?= stable-cinnamon
.PHONY: all iso live live-qemu test test-disk test-iso qemu shellcheck clean distclean check-secrets check-docker
@@ -27,10 +37,10 @@ check-docker:
@docker info >/dev/null 2>&1 || { echo "ERROR: docker daemon unreachable (in 'docker' group? systemctl start docker?)"; exit 1; }
iso: check-secrets check-docker
$(PROJECT_DIR)/iso/build-iso.sh
$(PROJECT_DIR)/iso/build.sh --profile $(PROFILE) --type installer
live: check-secrets check-docker
$(PROJECT_DIR)/iso/build-live-iso.sh
$(PROJECT_DIR)/iso/build.sh --profile $(PROFILE) --type live
# Launch the live ISO in QEMU with 12 GB RAM so nix packages fit in the tmpfs.
# The live session is a pure-RAM tmpfs overlay; Cinnamon + nix need ~7-8 GB total.
@@ -38,14 +48,14 @@ live-qemu:
$(PROJECT_DIR)/tests/launch-live-qemu.sh
test-iso: check-secrets check-docker
REBUILD_ISO=1 $(PROJECT_DIR)/tests/run-qemu-test.sh
REBUILD_ISO=1 PROFILE=$(PROFILE) $(PROJECT_DIR)/tests/run-qemu-test.sh
test-disk:
$(PROJECT_DIR)/tests/make-test-disk.sh $(OUT)/test-disk.img
test: check-secrets check-docker
@mkdir -p $(OUT)
$(PROJECT_DIR)/tests/run-qemu-test.sh
PROFILE=$(PROFILE) $(PROJECT_DIR)/tests/run-qemu-test.sh
qemu:
$(PROJECT_DIR)/tests/interactive-qemu.sh
@@ -55,10 +65,8 @@ shellcheck:
shellcheck -x \
$(PROJECT_DIR)/installer/install.sh \
$(PROJECT_DIR)/installer/lib/*.sh \
$(PROJECT_DIR)/iso/build-iso.sh \
$(PROJECT_DIR)/iso/_inner-build.sh \
$(PROJECT_DIR)/iso/_inner-build-live.sh \
$(PROJECT_DIR)/iso/build-live-iso.sh \
$(PROJECT_DIR)/iso/build.sh \
$(PROJECT_DIR)/iso/_inner-build-unified.sh \
$(PROJECT_DIR)/tests/*.sh \
$(PROJECT_DIR)/tests/lib/*.sh

View File

@@ -7,7 +7,7 @@ HOSTNAME="xps9700"
USERNAME="moze"
USER_FULLNAME="moze"
USER_UID="1000"
USER_GROUPS="wheel,docker,video,audio,input,plugdev,network,kvm,users"
USER_GROUPS="wheel,docker,video,audio,input,plugdev,network,kvm,users,bluetooth"
DEFAULT_SHELL="/bin/bash"
# ---------- Locale ----------

View File

@@ -0,0 +1,171 @@
# Packages installed into the target system for the mainline-cinnamon profile.
# Identical to stable-cinnamon but uses Linux mainline kernel (kernel 7) for
# cutting-edge hardware support. Includes alsa-ucm-conf for SoundWire audio.
# Lines beginning with '#' or empty are skipped.
# --- base / boot ---
base-system
linux-mainline
linux-mainline-headers
linux-firmware
linux-firmware-network
intel-ucode
grub-x86_64-efi
efibootmgr
os-prober
dracut
gptfdisk
parted
btrfs-progs
dosfstools
# --- core userspace ---
sudo
bash
bash-completion
git
curl
wget
vim
nano
htop
tmux
unzip
zip
xz
rsync
pciutils
usbutils
lsof
strace
file
which
man-pages
mdocml
ca-certificates
xtools
# --- networking ---
NetworkManager
NetworkManager-openvpn
openssh
iwd
wpa_supplicant
nftables
chrony
wireless-regdb
# --- audio (pipewire stack + SoundWire / SOF support for mainline) ---
pipewire
wireplumber
alsa-pipewire
pavucontrol
alsa-utils
alsa-ucm-conf
sof-firmware
# --- graphics / xorg ---
xorg-minimal
xorg-fonts
xorg-input-drivers
xf86-input-libinput
xf86-video-intel
mesa-dri
mesa-vulkan-intel
intel-video-accel
vulkan-loader
# --- nvidia (PRIME offload) ---
nvidia
nvidia-libs-32bit
nvidia-vaapi-driver
# --- desktop ---
cinnamon
xdg-user-dirs
xdg-utils
xdg-desktop-portal
xdg-desktop-portal-gtk
gvfs
gvfs-mtp
gvfs-smb
file-roller
gnome-keyring
seahorse
network-manager-applet
blueman
bluez
# --- display manager ---
lightdm
lightdm-gtk3-greeter
# --- fonts ---
noto-fonts-ttf
noto-fonts-emoji
noto-fonts-cjk
liberation-fonts-ttf
dejavu-fonts-ttf
font-awesome6
# --- containers ---
docker
docker-compose
# --- terminal ---
alacritty
# --- gtk theming deps (for gruvbox theme) ---
sassc
gnome-themes-extra
gtk-engine-murrine
dconf
dconf-editor
# --- media / utilities ---
vlc
obs
flameshot
# --- nix package manager ---
nix
# --- zram / swap ---
zramen
# --- power / laptop ---
tlp
tlp-rdw
acpi
acpid
upower
brightnessctl
# --- printing ---
cups
cups-filters
cups-pk-helper
ghostscript
foomatic-db
gutenprint
hplip
system-config-printer
sane
simple-scan
# --- bluetooth ---
bluez-alsa
# --- backups / snapshots ---
timeshift
grub-btrfs
inotify-tools
# --- trackpad gestures ---
libinput-gestures
wmctrl
xdotool
python3-setproctitle
# --- screenshots ---
xclip

View File

@@ -0,0 +1,153 @@
# Packages included in the LIVE desktop ISO squashfs for the mainline-cinnamon profile.
# Boots into a Cinnamon session with the mainline kernel as primary.
# Includes linux (kernel 6) as a secondary fallback boot entry — same as mainline-niri.
# Lines beginning with '#' or empty are skipped.
# --- base / boot ---
base-system
# Mainline (kernel 7) — primary boot kernel
linux-mainline
linux-mainline-headers
# Kernel 6 (stable) — secondary fallback boot entry
linux
linux-headers
linux-firmware
linux-firmware-network
intel-ucode
dracut
# --- core userspace ---
sudo
bash
bash-completion
git
zenity
curl
wget
vim
nano
htop
tmux
unzip
zip
xz
rsync
pciutils
usbutils
lsof
file
which
man-pages
mdocml
ca-certificates
xtools
gptfdisk
parted
btrfs-progs
dosfstools
# --- networking ---
NetworkManager
NetworkManager-openvpn
openssh
iwd
wpa_supplicant
chrony
wireless-regdb
# --- audio (pipewire stack + SoundWire / SOF support) ---
pipewire
wireplumber
alsa-pipewire
pavucontrol
alsa-utils
alsa-ucm-conf
sof-firmware
# --- graphics / xorg ---
xorg-minimal
xorg-fonts
xorg-input-drivers
xf86-input-libinput
xf86-video-intel
xf86-video-fbdev
xf86-video-vesa
mesa-dri
mesa-vulkan-intel
intel-video-accel
vulkan-loader
# --- nvidia PRIME (from nonfree repo) ---
nvidia
nvidia-libs
nvidia-vaapi-driver
# --- desktop ---
cinnamon
xdg-user-dirs
xdg-utils
xdg-desktop-portal
xdg-desktop-portal-gtk
gvfs
gvfs-mtp
gvfs-smb
file-roller
gnome-keyring
seahorse
network-manager-applet
blueman
bluez
# --- display manager ---
lightdm
lightdm-gtk3-greeter
# --- fonts ---
noto-fonts-ttf
noto-fonts-emoji
noto-fonts-cjk
liberation-fonts-ttf
dejavu-fonts-ttf
font-awesome6
# --- terminal ---
alacritty
setxkbmap
# --- gtk theming deps ---
sassc
gnome-themes-extra
gtk-engine-murrine
dconf
dconf-editor
# --- code editors ---
vscode
# --- media / utilities ---
vlc
flameshot
# --- containers ---
docker
docker-compose
# --- nix package manager ---
nix
# --- zram / swap ---
zramen
# --- power / laptop ---
tlp
tlp-rdw
acpi
acpid
upower
brightnessctl
# --- printing ---
cups
cups-filters
cups-pk-helper
system-config-printer

View File

@@ -0,0 +1,22 @@
# Mainline-cinnamon profile.
# Linux mainline kernel (kernel 7) + Cinnamon DE + X11.
# Useful when you need cutting-edge hardware support (GPU, WiFi, SoundWire)
# with the familiar Cinnamon desktop.
PROFILE_NAME="mainline-cinnamon"
PROFILE_DESC="Linux mainline kernel + Cinnamon (X11)"
# Kernel — mainline for newest hardware and driver support.
KERNEL_PKG="linux-mainline"
# Display server / DE.
DISPLAY_SERVER="x11"
DESKTOP="cinnamon"
# Package list (relative to repo root).
PROFILE_PACKAGES_FILE="config/profiles/mainline-cinnamon/packages.list"
# Default GTK theme + icons (overrides install.conf if set there).
GTK_THEME="Gruvbox-Dark"
ICON_THEME="Gruvbox-Plus-Dark"
DEFAULT_TERMINAL="alacritty"
CURSOR_THEME="Bibata-Modern-Ice"

View File

@@ -47,10 +47,9 @@ prefer-no-csd
spawn-at-startup "swaybg" "-i" "/usr/share/backgrounds/void-installer/pxfuel.jpg"
spawn-at-startup "mako"
spawn-at-startup "nm-applet" "--indicator"
spawn-at-startup "blueman-applet"
spawn-at-startup "/usr/libexec/polkit-gnome-authentication-agent-1"
spawn-at-startup "noctalia-shell"
spawn-at-startup "sh" "-c" "command -v gnome-keyring-daemon >/dev/null 2>&1 && gnome-keyring-daemon --start --components=secrets,pkcs11 >/dev/null 2>&1; true"
spawn-at-startup "sh" "-c" "i=0; while [ \$i -lt 30 ] && ! dbus-send --system --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.GetNameOwner string:org.bluez >/dev/null 2>&1; do sleep 1; i=\$((i+1)); done; exec noctalia-shell"
cursor {
xcursor-theme "Bibata-Modern-Ice"
@@ -59,7 +58,7 @@ cursor {
binds {
Mod+T { spawn "alacritty"; }
Mod+D { spawn "fuzzel"; }
Mod+D { spawn "sh" "-c" "quickshell msg -c noctalia-shell launcher toggle"; }
Mod+Q { close-window; }
Mod+Shift+E { quit; }
Print { screenshot; }
@@ -99,9 +98,74 @@ export MOZ_ENABLE_WAYLAND=1
export _JAVA_AWT_WM_NONREPARENTING=1
export XDG_CURRENT_DESKTOP=niri
export XDG_SESSION_TYPE=wayland
export GTK_USE_PORTAL=1
export ELECTRON_OZONE_PLATFORM_HINT=auto
EOF
chmod 0644 "$TARGET/etc/profile.d/wayland.sh"
log "wayland environment installed at /etc/profile.d/wayland.sh"
# Expose nix .desktop files and icons (installed via first-boot-nix)
cat > "$TARGET/etc/profile.d/nix-xdg.sh" <<'NIXEOF'
# Add nix profile share directory so launchers and icon themes pick up nix apps.
if [[ -d "${HOME:-}/.nix-profile/share" ]]; then
case ":${XDG_DATA_DIRS:-}:" in
*":$HOME/.nix-profile/share:"*) ;;
*) export XDG_DATA_DIRS="$HOME/.nix-profile/share:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" ;;
esac
fi
NIXEOF
chmod 0644 "$TARGET/etc/profile.d/nix-xdg.sh"
# /etc/environment: baseline XDG_DATA_DIRS loaded by pam_env for ALL session
# types (TTY login, greetd). The nix profile share path must be absolute here
# because pam_env does not expand $HOME when the key uses = (not DEFAULT=).
# The installed user's home is /home/$USERNAME, so we hardcode it.
cat > "$TARGET/etc/environment" <<ENVEOF
XDG_DATA_DIRS=/home/${USERNAME}/.nix-profile/share:/usr/local/share:/usr/share
QT_QPA_PLATFORM=wayland;xcb
GDK_BACKEND=wayland,x11
MOZ_ENABLE_WAYLAND=1
LIBSEAT_BACKEND=logind
GTK_USE_PORTAL=1
ELECTRON_OZONE_PLATFORM_HINT=auto
ENVEOF
log "/etc/environment written with XDG_DATA_DIRS for nix profile"
# dconf system keyfile: GTK dark theme reported to all apps via xdg-portal
install -d -m 0755 "$TARGET/etc/dconf/db/local.d"
install -d -m 0755 "$TARGET/etc/dconf/profile"
cat > "$TARGET/etc/dconf/db/local.d/01-dark-theme" <<'EOF'
[org/gnome/desktop/interface]
color-scheme='prefer-dark'
gtk-theme='Gruvbox-Dark'
icon-theme='Gruvbox-Plus-Dark'
cursor-theme='Bibata-Modern-Ice'
cursor-size=24
EOF
echo 'user-db:user
system-db:local' > "$TARGET/etc/dconf/profile/user"
run_chroot "dconf update 2>/dev/null || true"
log "dconf dark theme profile installed"
}
_niri_write_session_wrapper() {
local TARGET="$1"
# Create /usr/local/bin/niri-session: sources /etc/profile before exec'ing
# niri --session so that all /etc/profile.d/* scripts (nix paths,
# XDG_DATA_DIRS with ~/.nix-profile/share, wayland env, etc.) are in effect
# for the compositor and every app it spawns.
install -d -m 0755 "$TARGET/usr/local/bin"
cat > "$TARGET/usr/local/bin/niri-session" <<'EOF'
#!/bin/bash
# niri-session — wrapper started by greetd/tuigreet.
# Sources /etc/profile so that all /etc/profile.d/* scripts run
# (nix paths, wayland env, XDG_DATA_DIRS with ~/.nix-profile/share, etc.)
# before handing off to the compositor.
[ -f /etc/profile ] && . /etc/profile
exec niri --session "$@"
EOF
chmod 0755 "$TARGET/usr/local/bin/niri-session"
log "niri-session wrapper installed at /usr/local/bin/niri-session"
}
_niri_setup_greetd() {
@@ -143,7 +207,122 @@ EOF
fi
}
_niri_write_kdl "$TARGET"
_niri_write_env "$TARGET"
_niri_setup_greetd "$TARGET"
_niri_install_noctalia "$TARGET"
_niri_write_portal_config() {
local TARGET="$1"
# Tell xdg-desktop-portal to route all portals through the GTK backend when
# running under niri. Without this the dispatcher has no match for
# XDG_CURRENT_DESKTOP=niri and file-picker / open-with calls fail silently.
install -d -m 0755 "$TARGET/etc/xdg/xdg-desktop-portal"
cat > "$TARGET/etc/xdg/xdg-desktop-portal/niri-portals.conf" <<'EOF'
[preferred]
default=gtk
org.freedesktop.impl.portal.FileChooser=gtk
org.freedesktop.impl.portal.AppChooser=gtk
org.freedesktop.impl.portal.OpenURI=gtk
org.freedesktop.impl.portal.Print=gtk
org.freedesktop.impl.portal.Screenshot=gtk
org.freedesktop.impl.portal.Inhibit=gtk
org.freedesktop.impl.portal.Notification=gtk
org.freedesktop.impl.portal.Settings=gtk
EOF
log "niri portal config installed (gtk backend for all portals)"
}
_niri_write_sound_udev_rules() {
local TARGET="$1"
# PCM/control nodes are created root:root on some kernels before the audio
# group is provisioned. This persistent rule ensures correct ownership.
install -d -m 0755 "$TARGET/etc/udev/rules.d"
cat > "$TARGET/etc/udev/rules.d/70-sound-perms.rules" <<'EOF'
# Allow the audio group to access ALSA PCM and control devices.
SUBSYSTEM=="sound", GROUP="audio", MODE="0660"
EOF
log "sound udev rules installed"
}
_niri_write_noctalia_defaults() {
local TARGET="$1"
# Write a baseline noctalia settings.json into skel so every new user
# (including the installed user) gets the correct wallpaper directory,
# dark mode, and Gruvbox colour scheme out of the box.
local skel_noc="$TARGET/etc/skel/.config/noctalia"
install -d -m 0755 "$skel_noc"
cat > "$skel_noc/settings.json" <<NOCEOF
{
"appLauncher": {
"iconMode": "apps",
"sortByMostUsed": true,
"showCategories": true,
"viewMode": "grid",
"pinnedApps": [],
"terminalCommand": "alacritty -e",
"density": "default",
"position": "center"
},
"colorSchemes": {
"darkMode": true,
"predefinedScheme": "Gruvbox",
"generationMethod": "tonal-spot",
"manualSunrise": "06:30",
"manualSunset": "18:30",
"monitorForColors": ""
},
"wallpaper": {
"enabled": true,
"directory": "/usr/share/backgrounds/void-installer",
"fillMode": "crop",
"fillColor": "#000000",
"automationEnabled": false,
"favorites": [],
"hideWallpaperFilenames": false,
"linkLightAndDarkWallpapers": true,
"monitorDirectories": [],
"enableMultiMonitorDirectories": false
}
}
NOCEOF
chmod 0644 "$skel_noc/settings.json"
# Mirror into installed user's home
install -d -m 0755 "$TARGET/home/$USERNAME/.config/noctalia"
cp "$skel_noc/settings.json" "$TARGET/home/$USERNAME/.config/noctalia/settings.json"
run_chroot "chown -R $USERNAME:$USERNAME /home/$USERNAME/.config/noctalia" || true
log "noctalia default settings installed (wallpaper dir + Gruvbox dark)"
}
_niri_set_default_browser() {
local TARGET="$1"
# System-wide MIME defaults: google-chrome (installed via nix) as the
# default browser for http/https/html. Written to /etc/xdg/mimeapps.list
# (system default, read before ~/.config/mimeapps.list) and also into skel
# so the user-level entry is set from first login.
install -d -m 0755 "$TARGET/etc/xdg"
cat > "$TARGET/etc/xdg/mimeapps.list" <<'EOF'
[Default Applications]
text/html=google-chrome.desktop
x-scheme-handler/http=google-chrome.desktop
x-scheme-handler/https=google-chrome.desktop
x-scheme-handler/about=google-chrome.desktop
x-scheme-handler/unknown=google-chrome.desktop
application/pdf=google-chrome.desktop
application/xhtml+xml=google-chrome.desktop
application/xml=google-chrome.desktop
EOF
# Also set in user skel so ~/.config/mimeapps.list is populated on first login
install -d -m 0755 "$TARGET/etc/skel/.config"
cp "$TARGET/etc/xdg/mimeapps.list" "$TARGET/etc/skel/.config/mimeapps.list"
# Mirror into installed user home
install -d -m 0755 "$TARGET/home/$USERNAME/.config"
cp "$TARGET/etc/xdg/mimeapps.list" "$TARGET/home/$USERNAME/.config/mimeapps.list"
run_chroot "chown $USERNAME:$USERNAME /home/$USERNAME/.config/mimeapps.list" || true
log "google-chrome set as default browser (mimeapps.list)"
}
_niri_write_kdl "$TARGET"
_niri_write_env "$TARGET"
_niri_write_session_wrapper "$TARGET"
_niri_write_portal_config "$TARGET"
_niri_write_sound_udev_rules "$TARGET"
_niri_setup_greetd "$TARGET"
_niri_install_noctalia "$TARGET"
_niri_write_noctalia_defaults "$TARGET"
_niri_set_default_browser "$TARGET"

View File

@@ -4,6 +4,7 @@
# --- base / boot ---
base-system
linux-mainline
linux-mainline-headers
linux-firmware
linux-firmware-network
intel-ucode
@@ -49,6 +50,7 @@ openssh
iwd
nftables
chrony
wireless-regdb
# --- audio (pipewire stack) ---
pipewire
@@ -56,7 +58,9 @@ wireplumber
alsa-pipewire
pavucontrol
alsa-utils
alsa-ucm-conf
playerctl
sof-firmware
# --- graphics / wayland ---
wayland
@@ -70,12 +74,10 @@ libxkbcommon
# --- nvidia (PRIME offload) ---
nvidia
nvidia-libs-32bit
nvidia-vaapi-driver
# --- niri compositor + wayland ecosystem ---
niri
fuzzel
mako
swaybg
swayidle
@@ -89,6 +91,12 @@ xdg-desktop-portal-wlr
polkit-gnome
brightnessctl
# --- file manager ---
nautilus
# --- keyring (Chrome / VSCode secret storage) ---
gnome-keyring
# --- noctalia shell runtime deps (noctalia-shell itself is installed in
# niri.sh from the third-party XBPS repo at universalrepo.r1xelelo.workers.dev).
ImageMagick
@@ -110,10 +118,12 @@ gvfs-smb
file-roller
gnome-keyring
seahorse
network-manager-applet
blueman
bluez
# --- bluetooth audio ---
bluez-alsa
# --- display manager ---
# niri can be launched directly via TTY (`niri-session`) or via a wayland-aware
# greeter. We use greetd + tuigreet — lighter than lightdm under wayland.
@@ -170,9 +180,6 @@ system-config-printer
sane
simple-scan
# --- bluetooth audio ---
bluez-alsa
# --- backups / snapshots ---
timeshift
grub-btrfs

View File

@@ -5,6 +5,10 @@
# --- base / boot ---
base-system
linux-mainline
linux-mainline-headers
# Kernel 6 (stable) included for dual-boot menu entry fallback.
linux
linux-headers
linux-firmware
linux-firmware-network
intel-ucode
@@ -46,6 +50,7 @@ NetworkManager-openvpn
openssh
iwd
chrony
wireless-regdb
# --- audio (pipewire stack) ---
pipewire
@@ -53,7 +58,9 @@ wireplumber
alsa-pipewire
pavucontrol
alsa-utils
alsa-ucm-conf
playerctl
sof-firmware
# --- Wayland session ---
mesa-dri
@@ -63,14 +70,16 @@ elogind
seatd
dbus
# --- nvidia PRIME (from nonfree repo) ---
nvidia
nvidia-vaapi-driver
# --- display manager ---
greetd
tuigreet
# --- terminal + launcher ---
alacritty
fuzzel
foot
# --- notification + background ---
mako
@@ -89,16 +98,24 @@ brightnessctl
ImageMagick
python3
upower
power-profiles-daemon
wl-clipboard
network-manager-applet
zenity
# --- XDG portals ---
xdg-desktop-portal
xdg-desktop-portal-gtk
xdg-desktop-portal-gnome
xdg-utils
xdg-user-dirs
# --- nix (for prebaked packages — spotify, discord, vscode, fastfetch, etc.) ---
# --- file manager ---
nautilus
# --- keyring (Chrome / VSCode secret storage) ---
gnome-keyring
# --- nix (for prebaked packages — spotify, discord, google-chrome, vscode, fastfetch, etc.) ---
nix
# --- noctalia-shell (from noctalia third-party XBPS repo) ---

View File

@@ -0,0 +1 @@
packages.live-desktop.list

View File

@@ -50,6 +50,7 @@ iwd
wpa_supplicant
nftables
chrony
wireless-regdb
# --- audio ---
pipewire
@@ -57,6 +58,7 @@ wireplumber
alsa-pipewire
pavucontrol
alsa-utils
sof-firmware
# --- graphics / xorg ---
xorg-minimal

View File

@@ -15,6 +15,7 @@ sudo
bash
bash-completion
git
zenity
curl
wget
vim
@@ -46,6 +47,7 @@ openssh
iwd
wpa_supplicant
chrony
wireless-regdb
# --- audio ---
pipewire
@@ -53,6 +55,7 @@ wireplumber
alsa-pipewire
pavucontrol
alsa-utils
sof-firmware
# --- graphics / xorg ---
xorg-minimal

View File

@@ -0,0 +1 @@
packages.live-desktop.list

View File

@@ -0,0 +1,328 @@
#!/bin/bash
# Niri-specific customizations. Sourced by customizations.sh after the generic
# helpers when PROFILE=mainline-niri.
# Available env: $TARGET, $USERNAME, $PROFILE, $PROFILE_DIR, all install.conf vars.
_niri_write_kdl() {
local TARGET="$1"
local cfg="$TARGET/etc/skel/.config/niri"
install -d -m 0755 "$cfg"
cat > "$cfg/config.kdl" <<'EOF'
// niri config — generated by void-installer (mainline-niri profile).
input {
keyboard {
xkb {
layout "ch"
variant "fr"
}
}
touchpad {
tap
natural-scroll
dwt
}
mouse {
accel-speed 0.0
}
}
layout {
gaps 12
center-focused-column "never"
preset-column-widths {
proportion 0.33333
proportion 0.5
proportion 0.66667
}
default-column-width { proportion 0.5; }
focus-ring {
width 2
active-color "#fabd2f"
inactive-color "#3c3836"
}
border { off; }
}
prefer-no-csd
spawn-at-startup "swaybg" "-i" "/usr/share/backgrounds/void-installer/pxfuel.jpg"
spawn-at-startup "mako"
spawn-at-startup "/usr/libexec/polkit-gnome-authentication-agent-1"
spawn-at-startup "sh" "-c" "command -v gnome-keyring-daemon >/dev/null 2>&1 && gnome-keyring-daemon --start --components=secrets,pkcs11 >/dev/null 2>&1; true"
spawn-at-startup "sh" "-c" "i=0; while [ \$i -lt 30 ] && ! dbus-send --system --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.GetNameOwner string:org.bluez >/dev/null 2>&1; do sleep 1; i=\$((i+1)); done; exec noctalia-shell"
cursor {
xcursor-theme "Bibata-Modern-Ice"
xcursor-size 24
}
binds {
Mod+T { spawn "alacritty"; }
Mod+D { spawn "sh" "-c" "quickshell msg -c noctalia-shell launcher toggle"; }
Mod+Q { close-window; }
Mod+Shift+E { quit; }
Print { screenshot; }
Mod+H { focus-column-left; }
Mod+L { focus-column-right; }
Mod+J { focus-window-down; }
Mod+K { focus-window-up; }
Mod+Shift+H { move-column-left; }
Mod+Shift+L { move-column-right; }
Mod+1 { focus-workspace 1; }
Mod+2 { focus-workspace 2; }
Mod+3 { focus-workspace 3; }
Mod+4 { focus-workspace 4; }
Mod+Shift+1 { move-column-to-workspace 1; }
Mod+Shift+2 { move-column-to-workspace 2; }
XF86AudioRaiseVolume { spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "5%+"; }
XF86AudioLowerVolume { spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "5%-"; }
XF86AudioMute { spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle"; }
XF86MonBrightnessUp { spawn "brightnessctl" "set" "+5%"; }
XF86MonBrightnessDown { spawn "brightnessctl" "set" "5%-"; }
}
EOF
# Mirror into the actual user home.
install -d -m 0755 "$TARGET/home/$USERNAME/.config/niri"
cp "$cfg/config.kdl" "$TARGET/home/$USERNAME/.config/niri/config.kdl"
run_chroot "chown -R $USERNAME:$USERNAME /home/$USERNAME/.config/niri" || true
log "niri KDL config installed"
}
_niri_write_env() {
local TARGET="$1"
cat > "$TARGET/etc/profile.d/wayland.sh" <<'EOF'
# Wayland defaults installed by void-installer (mainline-niri profile).
export QT_QPA_PLATFORM="wayland;xcb"
export GDK_BACKEND=wayland,x11
export MOZ_ENABLE_WAYLAND=1
export _JAVA_AWT_WM_NONREPARENTING=1
export XDG_CURRENT_DESKTOP=niri
export XDG_SESSION_TYPE=wayland
export GTK_USE_PORTAL=1
export ELECTRON_OZONE_PLATFORM_HINT=auto
EOF
chmod 0644 "$TARGET/etc/profile.d/wayland.sh"
log "wayland environment installed at /etc/profile.d/wayland.sh"
# Expose nix .desktop files and icons (installed via first-boot-nix)
cat > "$TARGET/etc/profile.d/nix-xdg.sh" <<'NIXEOF'
# Add nix profile share directory so launchers and icon themes pick up nix apps.
if [[ -d "${HOME:-}/.nix-profile/share" ]]; then
case ":${XDG_DATA_DIRS:-}:" in
*":$HOME/.nix-profile/share:"*) ;;
*) export XDG_DATA_DIRS="$HOME/.nix-profile/share:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" ;;
esac
fi
NIXEOF
chmod 0644 "$TARGET/etc/profile.d/nix-xdg.sh"
# /etc/environment: baseline XDG_DATA_DIRS loaded by pam_env for ALL session
# types (TTY login, greetd). The nix profile share path must be absolute here
# because pam_env does not expand $HOME when the key uses = (not DEFAULT=).
# The installed user's home is /home/$USERNAME, so we hardcode it.
cat > "$TARGET/etc/environment" <<ENVEOF
XDG_DATA_DIRS=/home/${USERNAME}/.nix-profile/share:/usr/local/share:/usr/share
QT_QPA_PLATFORM=wayland;xcb
GDK_BACKEND=wayland,x11
MOZ_ENABLE_WAYLAND=1
LIBSEAT_BACKEND=logind
GTK_USE_PORTAL=1
ELECTRON_OZONE_PLATFORM_HINT=auto
ENVEOF
log "/etc/environment written with XDG_DATA_DIRS for nix profile"
# dconf system keyfile: GTK dark theme reported to all apps via xdg-portal
install -d -m 0755 "$TARGET/etc/dconf/db/local.d"
install -d -m 0755 "$TARGET/etc/dconf/profile"
cat > "$TARGET/etc/dconf/db/local.d/01-dark-theme" <<'EOF'
[org/gnome/desktop/interface]
color-scheme='prefer-dark'
gtk-theme='Gruvbox-Dark'
icon-theme='Gruvbox-Plus-Dark'
cursor-theme='Bibata-Modern-Ice'
cursor-size=24
EOF
echo 'user-db:user
system-db:local' > "$TARGET/etc/dconf/profile/user"
run_chroot "dconf update 2>/dev/null || true"
log "dconf dark theme profile installed"
}
_niri_write_session_wrapper() {
local TARGET="$1"
# Create /usr/local/bin/niri-session: sources /etc/profile before exec'ing
# niri --session so that all /etc/profile.d/* scripts (nix paths,
# XDG_DATA_DIRS with ~/.nix-profile/share, wayland env, etc.) are in effect
# for the compositor and every app it spawns.
install -d -m 0755 "$TARGET/usr/local/bin"
cat > "$TARGET/usr/local/bin/niri-session" <<'EOF'
#!/bin/bash
# niri-session — wrapper started by greetd/tuigreet.
# Sources /etc/profile so that all /etc/profile.d/* scripts run
# (nix paths, wayland env, XDG_DATA_DIRS with ~/.nix-profile/share, etc.)
# before handing off to the compositor.
[ -f /etc/profile ] && . /etc/profile
exec niri --session "$@"
EOF
chmod 0755 "$TARGET/usr/local/bin/niri-session"
log "niri-session wrapper installed at /usr/local/bin/niri-session"
}
_niri_setup_greetd() {
local TARGET="$1"
install -d -m 0755 "$TARGET/etc/greetd"
cat > "$TARGET/etc/greetd/config.toml" <<EOF
[terminal]
vt = 1
[default_session]
command = "tuigreet --time --remember --cmd niri-session"
user = "_greeter"
EOF
log "greetd configured for niri-session"
}
_niri_install_noctalia() {
local TARGET="$1"
# Third-party Void repo that ships noctalia-shell + noctalia-qs.
# Source: https://docs.noctalia.dev/getting-started/installation/#void
install -d -m 0755 "$TARGET/etc/xbps.d"
cat > "$TARGET/etc/xbps.d/10-noctalia.conf" <<'EOF'
repository=https://universalrepo.r1xelelo.workers.dev/void
EOF
# If quickshell is somehow installed it conflicts with noctalia-qs.
run_chroot "xbps-remove -y quickshell 2>/dev/null || true"
# Sync the new repo and install. Prefix with the proxy mirror configured
# by the installer environment so noctalia-shell deps still resolve fast.
if ! run_chroot "xbps-install -Sy"; then
log "WARN: noctalia repo sync failed; skipping noctalia-shell install"
return 0
fi
if run_chroot "xbps-install -y noctalia-shell"; then
log "noctalia-shell installed from third-party repo"
else
log "WARN: noctalia-shell install failed (repo may be down); shell omitted"
fi
}
_niri_write_portal_config() {
local TARGET="$1"
# Tell xdg-desktop-portal to route all portals through the GTK backend when
# running under niri. Without this the dispatcher has no match for
# XDG_CURRENT_DESKTOP=niri and file-picker / open-with calls fail silently.
install -d -m 0755 "$TARGET/etc/xdg/xdg-desktop-portal"
cat > "$TARGET/etc/xdg/xdg-desktop-portal/niri-portals.conf" <<'EOF'
[preferred]
default=gtk
org.freedesktop.impl.portal.FileChooser=gtk
org.freedesktop.impl.portal.AppChooser=gtk
org.freedesktop.impl.portal.OpenURI=gtk
org.freedesktop.impl.portal.Print=gtk
org.freedesktop.impl.portal.Screenshot=gtk
org.freedesktop.impl.portal.Inhibit=gtk
org.freedesktop.impl.portal.Notification=gtk
org.freedesktop.impl.portal.Settings=gtk
EOF
log "niri portal config installed (gtk backend for all portals)"
}
_niri_write_sound_udev_rules() {
local TARGET="$1"
# PCM/control nodes are created root:root on some kernels before the audio
# group is provisioned. This persistent rule ensures correct ownership.
install -d -m 0755 "$TARGET/etc/udev/rules.d"
cat > "$TARGET/etc/udev/rules.d/70-sound-perms.rules" <<'EOF'
# Allow the audio group to access ALSA PCM and control devices.
SUBSYSTEM=="sound", GROUP="audio", MODE="0660"
EOF
log "sound udev rules installed"
}
_niri_write_noctalia_defaults() {
local TARGET="$1"
# Write a baseline noctalia settings.json into skel so every new user
# (including the installed user) gets the correct wallpaper directory,
# dark mode, and Gruvbox colour scheme out of the box.
local skel_noc="$TARGET/etc/skel/.config/noctalia"
install -d -m 0755 "$skel_noc"
cat > "$skel_noc/settings.json" <<NOCEOF
{
"appLauncher": {
"iconMode": "apps",
"sortByMostUsed": true,
"showCategories": true,
"viewMode": "grid",
"pinnedApps": [],
"terminalCommand": "alacritty -e",
"density": "default",
"position": "center"
},
"colorSchemes": {
"darkMode": true,
"predefinedScheme": "Gruvbox",
"generationMethod": "tonal-spot",
"manualSunrise": "06:30",
"manualSunset": "18:30",
"monitorForColors": ""
},
"wallpaper": {
"enabled": true,
"directory": "/usr/share/backgrounds/void-installer",
"fillMode": "crop",
"fillColor": "#000000",
"automationEnabled": false,
"favorites": [],
"hideWallpaperFilenames": false,
"linkLightAndDarkWallpapers": true,
"monitorDirectories": [],
"enableMultiMonitorDirectories": false
}
}
NOCEOF
chmod 0644 "$skel_noc/settings.json"
# Mirror into installed user's home
install -d -m 0755 "$TARGET/home/$USERNAME/.config/noctalia"
cp "$skel_noc/settings.json" "$TARGET/home/$USERNAME/.config/noctalia/settings.json"
run_chroot "chown -R $USERNAME:$USERNAME /home/$USERNAME/.config/noctalia" || true
log "noctalia default settings installed (wallpaper dir + Gruvbox dark)"
}
_niri_set_default_browser() {
local TARGET="$1"
# System-wide MIME defaults: google-chrome (installed via nix) as the
# default browser for http/https/html. Written to /etc/xdg/mimeapps.list
# (system default, read before ~/.config/mimeapps.list) and also into skel
# so the user-level entry is set from first login.
install -d -m 0755 "$TARGET/etc/xdg"
cat > "$TARGET/etc/xdg/mimeapps.list" <<'EOF'
[Default Applications]
text/html=google-chrome.desktop
x-scheme-handler/http=google-chrome.desktop
x-scheme-handler/https=google-chrome.desktop
x-scheme-handler/about=google-chrome.desktop
x-scheme-handler/unknown=google-chrome.desktop
application/pdf=google-chrome.desktop
application/xhtml+xml=google-chrome.desktop
application/xml=google-chrome.desktop
EOF
# Also set in user skel so ~/.config/mimeapps.list is populated on first login
install -d -m 0755 "$TARGET/etc/skel/.config"
cp "$TARGET/etc/xdg/mimeapps.list" "$TARGET/etc/skel/.config/mimeapps.list"
# Mirror into installed user home
install -d -m 0755 "$TARGET/home/$USERNAME/.config"
cp "$TARGET/etc/xdg/mimeapps.list" "$TARGET/home/$USERNAME/.config/mimeapps.list"
run_chroot "chown $USERNAME:$USERNAME /home/$USERNAME/.config/mimeapps.list" || true
log "google-chrome set as default browser (mimeapps.list)"
}
_niri_write_kdl "$TARGET"
_niri_write_env "$TARGET"
_niri_write_session_wrapper "$TARGET"
_niri_write_portal_config "$TARGET"
_niri_write_sound_udev_rules "$TARGET"
_niri_setup_greetd "$TARGET"
_niri_install_noctalia "$TARGET"
_niri_write_noctalia_defaults "$TARGET"
_niri_set_default_browser "$TARGET"

View File

@@ -0,0 +1,190 @@
# Packages installed into the target system for the stable-niri profile.
# Identical to mainline-niri but uses Void's stable LTS kernel (linux, kernel 6)
# instead of linux-mainline.
# Lines beginning with '#' or empty are skipped.
# --- base / boot ---
base-system
linux
linux-headers
linux-firmware
linux-firmware-network
intel-ucode
grub-x86_64-efi
efibootmgr
os-prober
dracut
gptfdisk
parted
btrfs-progs
dosfstools
# --- core userspace ---
sudo
bash
bash-completion
git
curl
wget
vim
nano
htop
tmux
unzip
zip
xz
rsync
pciutils
usbutils
lsof
strace
file
which
man-pages
mdocml
ca-certificates
xtools
# --- networking ---
NetworkManager
NetworkManager-openvpn
openssh
iwd
nftables
chrony
wireless-regdb
# --- audio (pipewire stack + SoundWire / SOF support) ---
pipewire
wireplumber
alsa-pipewire
pavucontrol
alsa-utils
alsa-ucm-conf
playerctl
sof-firmware
# --- graphics / wayland ---
wayland
wayland-protocols
xorg-server-xwayland
mesa-dri
mesa-vulkan-intel
intel-video-accel
vulkan-loader
libxkbcommon
# --- nvidia (PRIME offload) ---
nvidia
nvidia-vaapi-driver
# --- niri compositor + wayland ecosystem ---
niri
mako
swaybg
swayidle
swaylock
grim
slurp
wl-clipboard
xdg-desktop-portal
xdg-desktop-portal-gtk
xdg-desktop-portal-wlr
polkit-gnome
brightnessctl
# --- file manager ---
nautilus
# --- keyring (Chrome / VSCode secret storage) ---
gnome-keyring
# --- noctalia shell runtime deps (noctalia-shell itself is installed in
# niri.sh from the third-party XBPS repo at universalrepo.r1xelelo.workers.dev).
ImageMagick
python3
ddcutil
power-profiles-daemon
upower
cliphist
wlsunset
evolution-data-server
# --- file manager extras ---
Thunar
thunar-volman
thunar-archive-plugin
gvfs
gvfs-mtp
gvfs-smb
file-roller
seahorse
# --- bluetooth ---
blueman
bluez
bluez-alsa
# --- display manager ---
greetd
tuigreet
# --- fonts ---
noto-fonts-ttf
noto-fonts-emoji
noto-fonts-cjk
liberation-fonts-ttf
dejavu-fonts-ttf
font-awesome6
# --- containers ---
docker
docker-compose
# --- terminal ---
alacritty
# --- gtk theming deps ---
sassc
gnome-themes-extra
gtk-engine-murrine
dconf
# --- media / utilities ---
vlc
obs
# --- nix package manager ---
nix
# --- zram / swap ---
zramen
# --- power / laptop ---
tlp
tlp-rdw
acpi
acpid
upower
# --- printing ---
cups
cups-filters
cups-pk-helper
ghostscript
foomatic-db
gutenprint
hplip
system-config-printer
sane
simple-scan
# --- backups / snapshots ---
timeshift
grub-btrfs
inotify-tools
# --- trackpad gestures ---
libinput-gestures
xdotool
python3-setproctitle

View File

@@ -0,0 +1,135 @@
# Packages included in the LIVE desktop ISO squashfs for the stable-niri profile.
# Boots into a niri/noctalia session. Single kernel (Void's stable LTS — no
# dual-kernel menu needed here; use mainline-niri if you want kernel 7 + fallback).
# Lines beginning with '#' or empty are skipped.
# --- base / boot ---
base-system
linux
linux-headers
linux-firmware
linux-firmware-network
intel-ucode
dracut
# --- core userspace ---
sudo
bash
bash-completion
git
curl
wget
vim
nano
htop
tmux
unzip
zip
xz
rsync
pciutils
usbutils
lsof
file
which
man-pages
mdocml
ca-certificates
xtools
gptfdisk
parted
btrfs-progs
dosfstools
efibootmgr
# --- networking ---
NetworkManager
NetworkManager-openvpn
openssh
iwd
chrony
wireless-regdb
# --- audio (pipewire stack + SoundWire / SOF support) ---
pipewire
wireplumber
alsa-pipewire
pavucontrol
alsa-utils
alsa-ucm-conf
playerctl
sof-firmware
# --- Wayland session ---
mesa-dri
niri
xwayland-satellite
elogind
seatd
dbus
wayland
# --- nvidia PRIME (from nonfree repo) ---
nvidia
nvidia-vaapi-driver
# --- display manager ---
greetd
tuigreet
# --- terminal + launcher ---
alacritty
# --- notification + background ---
mako
swaybg
# --- bluetooth ---
bluez
blueman
# --- polkit (auth dialogs) ---
polkit
polkit-gnome
# --- noctalia-shell runtime deps ---
brightnessctl
ImageMagick
python3
upower
power-profiles-daemon
wl-clipboard
zenity
# --- XDG portals ---
xdg-desktop-portal
xdg-desktop-portal-gtk
xdg-desktop-portal-gnome
xdg-utils
xdg-user-dirs
# --- file manager ---
nautilus
# --- keyring ---
gnome-keyring
# --- nix (for prebaked packages) ---
nix
# --- noctalia-shell (from noctalia third-party XBPS repo) ---
noctalia-shell
# --- fonts ---
noto-fonts-ttf
noto-fonts-emoji
noto-fonts-cjk
liberation-fonts-ttf
dejavu-fonts-ttf
font-awesome6
# --- gtk theming deps ---
sassc
gnome-themes-extra
gtk-engine-murrine
dconf

View File

@@ -0,0 +1,28 @@
# Stable-niri profile.
# Void's stable LTS kernel (linux, kernel 6) + niri Wayland tiling compositor + noctalia-shell.
PROFILE_NAME="stable-niri"
PROFILE_DESC="Stable Void kernel + niri Wayland (scrolling tiler) + noctalia-shell"
# Kernel — use Void's stable LTS.
KERNEL_PKG="linux"
# Display server / DE.
DISPLAY_SERVER="wayland"
DESKTOP="niri"
# noctalia-shell via third-party XBPS repo (universalrepo.r1xelelo.workers.dev).
WAYLAND_SHELL="noctalia"
# Package list (relative to repo root).
PROFILE_PACKAGES_FILE="config/profiles/stable-niri/packages.list"
# Theme settings (gtk3/gtk4 apps under wayland read these).
GTK_THEME="Gruvbox-Dark"
ICON_THEME="Gruvbox-Plus-Dark"
DEFAULT_TERMINAL="alacritty"
CURSOR_THEME="Bibata-Modern-Ice"
# Wayland env defaults (exported into /etc/environment by profile customisation).
QT_QPA_PLATFORM="wayland;xcb"
GDK_BACKEND="wayland,x11"
MOZ_ENABLE_WAYLAND="1"

View File

@@ -0,0 +1,122 @@
# Kernel 7 Audio Troubleshooting for Dell XPS 17 9700
## Symptom
On the Niri live ISO booting kernel 7, audio shows only HDMI devices and "Dummy Output" in `wpctl status`. Internal speakers and headphone jack do not appear.
## Root Cause
The XPS 17 9700 is a Dell SoundWire platform requiring **SOF + SoundWire** for internal audio on modern Linux kernels. The issue is **not** PipeWire misconfiguration. Common causes are:
1. Kernel driver stack selection issue (wrong path selected at boot)
2. Missing or mismatched SOF firmware/topology
3. Missing or mismatched `sof-soundwire` UCM profiles
4. A kernel SoundWire regression
## Diagnostic Steps
### 1. Verify the driver stack is loading
Boot into the live ISO and check which driver is active:
```bash
# Should show SOF driver for internal audio
lsmod | grep -E '^sof|^snd_sof'
# Should show the SoundWire machine driver
lsmod | grep -E 'sof_sdw|sof.*mach'
# Check kernel logs for SoundWire errors
sudo dmesg | grep -Ei 'soundwire|sdw|sof.*error|topology.*-22'
```
### 2. Test explicit SOF driver selection
If internal audio is missing, the kernel's auto-detection may have selected the wrong path. At the GRUB/boot menu, edit the kernel command line and add:
```
snd-intel-dspcfg.dsp_driver=3
```
This explicitly forces SOF (value `3`) for the audio stack. Options are:
- `0`: auto (kernel's default)
- `1`: legacy HDA
- `3`: SOF (correct for this laptop)
- `4`: AVS
Then boot and re-check:
```bash
wpctl status
```
### 3. Verify package completeness
Ensure the live image has the required audio support packages:
```bash
xbps-query -l | grep -E '^ii.*sof-firmware|alsa-ucm-conf|alsa-utils'
```
Expected output should include:
- `sof-firmware` (Sound Open Firmware binaries and topology)
- `alsa-ucm-conf` (Use Case Manager profiles for `sof-soundwire`)
- `alsa-utils` (ALSA utilities and initial mixer state)
### 4. Check PipeWire/WirePlumber state
Once the kernel exposes the internal audio devices (they should appear in `wpctl status`), verify PipeWire is reading them correctly:
```bash
# List all devices seen by PipeWire
pw-cli ls Device
# Check WirePlumber's ALSA monitor for errors
journalctl -u wireplumber.service -n 50
# Manually re-scan if needed
pw-cli list-objects Module | grep monitor
```
## If It Still Fails
### Check for a kernel regression
Recent kernel versions 6.9 and 6.10 had SoundWire regressions affecting XPS-series Dell laptops. These were fixed in:
- 6.9.x (fixed in 6.9 stable releases)
- 6.10.11+
If kernel 7.x shows the same symptoms, check the [SOF upstream issues](https://github.com/thesofproject/linux/issues) for similar reports.
### Compare against a known-good kernel
If kernel 7 still fails with `dsp_driver=3`:
1. Boot with the fallback kernel 6 (available in the Niri live ISO GRUB menu)
2. Test audio on kernel 6
3. If audio works on kernel 6 but not kernel 7, there is likely a kernel-7-specific regression
Report findings to:
- [SOF upstream](https://github.com/thesofproject/linux/issues)
- [Void Linux](https://github.com/void-linux/void-packages)
### Manual ALSA routing (for microphone issues)
If speakers work but the internal microphone does not, manually set the mic input mux:
```bash
amixer --card 1 set 'rt715 ADC 24 Mux' 'DMIC3'
```
Then persist the state:
```bash
sudo alsactl store
```
## References
- [ArchWiki: Dell XPS 17 (9700)](https://wiki.archlinux.org/title/Dell_XPS_17_(9700))
- [ArchWiki: Sound Open Firmware](https://wiki.archlinux.org/title/Sound_Open_Firmware)
- [SOF upstream issue 4999: 6.8.9→6.9.0 regression](https://github.com/thesofproject/linux/issues/4999)
- [Kernel docs: intel-dsp-config](https://github.com/torvalds/linux/blob/master/sound/hda/core/intel-dsp-config.c)
- [XPS 9700 Ubuntu fix guide](https://askubuntu.com/questions/1270442/no-sound-on-xps-17-9700)
## Live ISO Packages
The mainline-niri Niri live ISO includes:
- `sof-firmware`: Intel SOF binary and topology files
- `alsa-ucm-conf`: ALSA Use Case Manager profiles, including `sof-soundwire`
- `pipewire` and `wireplumber`: Session and policy management
- `linux-mainline`: Kernel 7.x with SOF support
- `linux`: Kernel 6.x fallback entry in GRUB
The build ensures both kernels are available to test audio driver behavior across kernel versions.

View File

@@ -2,7 +2,7 @@
# First-login one-shot setup for the user.
# Installs: Claude Code, NVM + node LTS, VS Code extensions,
# and (if NIX_PACKAGES_FILE is present) nix user packages
# (google-chrome, spotify, discord, localsend, mission-center).
# (google-chrome, spotify, discord, localsend, mission-center, vscode).
# Idempotent: creates ~/.first-login-done marker on success.
# NOTE: do NOT use `set -u` here — nvm.sh references unbound vars.
@@ -23,16 +23,17 @@ if ! curl -fsSL --max-time 3 --connect-timeout 3 -o /dev/null https://api.github
exit 0
fi
# --- Claude Code (official native installer) ---
mkdir -p "$HOME/.local/bin"
export PATH="$HOME/.local/bin:$PATH"
# --- Claude Code (official native installer) ---
if ! command -v claude >/dev/null 2>&1 && [[ ! -x "$HOME/.local/bin/claude" ]]; then
echo "==> installing Claude Code via official installer"
curl -fsSL https://claude.ai/install.sh | bash || {
echo "!! claude install failed"; }
fi
# --- Nix user packages (google-chrome, spotify, discord, etc.) ---
# --- Nix user packages (google-chrome, spotify, discord, vscode, etc.) ---
# Present when running from the live ISO (written by build-live-iso.sh).
# In the installed system the packages come from first-boot-nix.sh instead.
# NOTE: nix packages are intentionally skipped in the live session — they
@@ -72,6 +73,7 @@ if [[ -r "$NIX_PACKAGES_FILE" ]] && command -v nix >/dev/null 2>&1; then
fi
export NIXPKGS_ALLOW_UNFREE=1
export NIX_REMOTE=local
mapfile -t pkgs < <(grep -vE '^\s*(#|$)' "$NIX_PACKAGES_FILE")
if [[ ${#pkgs[@]} -gt 0 ]]; then

View File

@@ -35,12 +35,18 @@ export PKG_LIST_FILE
[[ -r "$PKG_LIST_FILE" ]] || die "packages.list $PKG_LIST_FILE missing"
# ---------- profile ----------
# PROFILE can be set on the command line or in the environment.
# If not set, fall back to DEFAULT_PROFILE which the live ISO writes to
# /etc/profile.d/00-void-installer.sh so the embedded installer defaults to
# the same configuration the live session was built with.
PROFILE="${PROFILE:-${DEFAULT_PROFILE:-stable-cinnamon}}"
export PROFILE
PROJECT_DIR="${PROJECT_DIR:-$SHARE_DIR}"
PROFILES_DIR="${PROFILES_DIR:-$SHARE_DIR/profiles}"
export PROJECT_DIR PROFILES_DIR
# shellcheck source=lib/profiles.sh
source "$INSTALLER_DIR/lib/profiles.sh"
load_profile || die "could not load profile '${PROFILE:-stable-cinnamon}'"
load_profile || die "could not load profile '${PROFILE}'"
# Profile may override the package list.
[[ -r "$PROFILE_PACKAGES_FILE" ]] && PKG_LIST_FILE="$PROFILE_PACKAGES_FILE"
log "using packages list: $PKG_LIST_FILE"
@@ -98,7 +104,7 @@ main() {
configure_nvidia_prime
configure_zram
configure_nix
install_vscode_real
[[ "${DESKTOP:-cinnamon}" != "niri" ]] && install_vscode_real
install_customizations
enable_services
install_grub

View File

@@ -18,7 +18,7 @@ install_grub() {
GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="Void"
GRUB_CMDLINE_LINUX_DEFAULT="loglevel=4 nvidia-drm.modeset=1"
GRUB_CMDLINE_LINUX_DEFAULT="loglevel=4 nvidia-drm.modeset=1 rd.driver.blacklist=nouveau modprobe.blacklist=nouveau btusb.enable_autosuspend=0"
GRUB_CMDLINE_LINUX=""
GRUB_DISABLE_OS_PROBER=false
GRUB_TERMINAL_OUTPUT="gfxterm"

View File

@@ -88,6 +88,25 @@ EOF
fi
chmod 440 "$TARGET/etc/sudoers.d/10-wheel"
# git: GUI askpass so prompts work without a controlling terminal
install -d -m 0755 "$TARGET/usr/local/bin"
cat > "$TARGET/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 "$TARGET/usr/local/bin/git-askpass"
cat > "$TARGET/etc/gitconfig" <<'EOF'
[core]
askPass = /usr/local/bin/git-askpass
EOF
ok "user '$USERNAME' created and added to: $USER_GROUPS"
}
@@ -141,7 +160,19 @@ Section "OutputClass"
EndSection
EOF
# 2) Modules to load early for KMS.
# 2) Blacklist nouveau so it doesn't grab the GPU before nvidia loads.
install -d -m 0755 "$TARGET/etc/modprobe.d"
cat > "$TARGET/etc/modprobe.d/blacklist-nouveau.conf" <<'EOF'
blacklist nouveau
options nouveau modeset=0
EOF
# btusb USB autosuspend causes firmware download failures on Intel BT.
cat > "$TARGET/etc/modprobe.d/btusb-quirks.conf" <<'EOF'
options btusb enable_autosuspend=0
EOF
# 3) Modules to load early for KMS.
install -d -m 0755 "$TARGET/etc/modules-load.d"
cat > "$TARGET/etc/modules-load.d/nvidia.conf" <<'EOF'
nvidia
@@ -150,7 +181,7 @@ nvidia_uvm
nvidia_drm
EOF
# 3) Wrapper script + desktop file: run any app on NVIDIA via `prime-run`.
# 4) Wrapper script + desktop file: run any app on NVIDIA via `prime-run`.
install -d -m 0755 "$TARGET/usr/local/bin"
cat > "$TARGET/usr/local/bin/prime-run" <<'EOF'
#!/bin/sh
@@ -162,10 +193,11 @@ exec env __NV_PRIME_RENDER_OFFLOAD=1 \
EOF
chmod 0755 "$TARGET/usr/local/bin/prime-run"
# 4) Make sure dracut picks up nvidia modules.
# 5) dracut: include nvidia modules in initramfs; explicitly omit nouveau.
install -d -m 0755 "$TARGET/etc/dracut.conf.d"
cat > "$TARGET/etc/dracut.conf.d/10-nvidia.conf" <<'EOF'
add_drivers+=" nvidia nvidia_modeset nvidia_uvm nvidia_drm "
omit_drivers+=" nouveau "
EOF
ok "NVIDIA PRIME offload configured (use 'prime-run <app>')"
@@ -206,13 +238,12 @@ mark=/var/lib/first-boot-nix.done
[[ -f "\$mark" ]] && exit 0
# Wait for nix-daemon to be available.
# The Void xbps nix package puts the socket at /var/nix/daemon-socket/socket.
for _ in \$(seq 1 60); do
[[ -S /var/nix/daemon-socket/socket ]] && break
[[ -S /nix/var/nix/daemon-socket/socket ]] && break
sleep 2
done
if [[ ! -S /var/nix/daemon-socket/socket ]]; then
if [[ ! -S /nix/var/nix/daemon-socket/socket ]]; then
echo "nix-daemon not available; aborting first-boot nix install" >&2
exit 0
fi
@@ -230,6 +261,12 @@ touch "\$mark"
EOF
chmod 0755 "$TARGET/usr/local/libexec/first-boot-nix.sh"
# Persistent nixpkgs config so the installed user can install unfree packages
# without needing to export NIXPKGS_ALLOW_UNFREE=1 every time.
install -d -m 0755 "$TARGET/home/$USERNAME/.config/nixpkgs"
echo '{ allowUnfree = true; }' > "$TARGET/home/$USERNAME/.config/nixpkgs/config.nix"
run_chroot "chown -R $USERNAME:$USERNAME /home/$USERNAME/.config/nixpkgs"
# runit one-shot service.
install -d -m 0755 "$TARGET/etc/sv/first-boot-nix"
cat > "$TARGET/etc/sv/first-boot-nix/run" <<'EOF'
@@ -312,10 +349,8 @@ enable_services() {
local enabled=(
dbus
NetworkManager
lightdm
polkitd
docker
bluetoothd
acpid
tlp
elogind
@@ -326,6 +361,14 @@ enable_services() {
cupsd
cups-browsed
)
# Display manager: greetd for wayland/niri, lightdm for cinnamon.
if [[ "${DESKTOP:-cinnamon}" == "niri" ]]; then
enabled+=(greetd bluetoothd)
else
enabled+=(lightdm bluetoothd)
fi
[[ "${SSHD_ENABLE:-no}" == "yes" ]] && enabled+=(sshd)
for svc in "${enabled[@]}"; do

View File

@@ -112,6 +112,8 @@ if [[ -n "${NIX_PACKAGES_PREBAKE:-}" ]]; then
echo " staging /nix into overlay ($(du -sh /nix/store 2>/dev/null | cut -f1))"
mkdir -p "$INCLUDE_DIR/nix"
rsync -a /nix/ "$INCLUDE_DIR/nix/" 2>&1 | tail -1
# Single-user nix: the live user (uid 1000) must own the store to create lock files and new paths.
chown -R 1000:1000 "$INCLUDE_DIR/nix"
# /etc/skel/.nix-profile → the pre-baked store profile path.
# dracut's adduser.sh runs 'useradd -m' which copies skel → /home/live,

View File

@@ -31,6 +31,38 @@ command -v xbps-install.static >/dev/null \
mkdir -p "$(dirname "$OUT_ISO")"
# ── Audio and Theme Support ──────────────────────────────────────────────
# The Niri live ISO includes:
# 1. sof-firmware: Intel Sound Open Firmware (required for modern Intel audio)
# 2. alsa-ucm-conf: ALSA Use Case Manager profiles (sof-soundwire for SoundWire hardware)
# 3. Removed forced audio kernel parameters: The kernel auto-selects SOF/HDA/AVS
# based on hardware IDs. Forced parameters (snd-intel-dspcfg.dsp_driver=1)
# broke audio on SoundWire platforms like XPS 9700.
# See iso/build-niri-live-iso.sh (BOOT_CMDLINE) and docs/KERNEL7_AUDIO_XPS9700.md
# 4. Dark theme dconf database: Compiled here during ISO build.
# Compile dconf system-db using Void's own dconf binary inside the target
# rootfs. The Niri live builder writes /etc/dconf/db/local.d/01-dark-theme,
# but the live system will not report the dark theme unless the GVDB is
# compiled into /etc/dconf/db/local (and into skel for the live user).
_DCONF_POSTSETUP="$(mktemp -p "$MKLIVE_DIR" postsetup-dconf.XXXXX.sh)"
cat > "$_DCONF_POSTSETUP" <<'PSEOF'
#!/bin/bash
ROOTFS="$1"
if [[ -x "$ROOTFS/usr/bin/dconf" ]] && [[ -d "$ROOTFS/etc/dconf/db/local.d" ]]; then
chroot "$ROOTFS" dconf compile /etc/dconf/db/local /etc/dconf/db/local.d \
&& echo "postsetup: system-db compiled ($(chroot "$ROOTFS" dconf --version 2>/dev/null))" \
|| echo "postsetup: system-db compile failed (non-fatal)"
mkdir -p "$ROOTFS/etc/skel/.config/dconf"
chroot "$ROOTFS" dconf compile /etc/skel/.config/dconf/user /etc/dconf/db/local.d \
&& echo "postsetup: skel user dconf db compiled" \
|| echo "postsetup: skel user dconf db compile failed (non-fatal)"
else
echo "postsetup: dconf or keyfile dir not found in rootfs - skipping dconf compile"
fi
PSEOF
chmod +x "$_DCONF_POSTSETUP"
cd "$MKLIVE_DIR"
# ── Pre-bake nix packages ────────────────────────────────────────────────
@@ -73,6 +105,8 @@ if [[ -n "${NIX_PACKAGES_PREBAKE:-}" ]]; then
echo " staging /nix into overlay ($(du -sh /nix/store 2>/dev/null | cut -f1))"
mkdir -p "$INCLUDE_DIR/nix"
rsync -a /nix/ "$INCLUDE_DIR/nix/" 2>&1 | tail -1
# Single-user nix: the live user (uid 1000) must own the store to create lock files and new paths.
chown -R 1000:1000 "$INCLUDE_DIR/nix"
_STORE_PROFILE=$(cat "$_NIX_CACHE/.profile-path" 2>/dev/null \
|| readlink -f /root/.nix-profile 2>/dev/null || echo "")
@@ -113,9 +147,10 @@ for ua in ("curl/8.0", "xbps/0.59.2", "Mozilla/5.0 (X11; Linux x86_64)"):
data = urllib.request.urlopen(req, timeout=15).read()
tf = tarfile.open(fileobj=io.BytesIO(data))
idx = plistlib.loads(tf.extractfile("index.plist").read())
for pkgver, meta in idx.items():
if isinstance(meta, dict) and meta.get("pkgname") in want:
found[meta["pkgname"]] = pkgver
# index.plist keys are package names; pkgver field holds the versioned name
for pkgname, meta in idx.items():
if isinstance(meta, dict) and pkgname in want:
found[pkgname] = meta.get("pkgver", pkgname)
if len(found) >= len(want):
break
except Exception:
@@ -205,6 +240,10 @@ trap _cleanup_mklive_builds EXIT
# mklive prepends -r args: LAST -r = HIGHEST priority.
# Our local signed repo is last so xbps resolves noctalia-* from it.
# -v linux-mainline: makes mainline (kernel 7) the PRIMARY boot kernel;
# mklive.sh sets KERNELVERSION from it and ignores the linux metapackage.
# The plain 'linux' (kernel 6) in ISO_PKGS still gets installed and the
# secondary-kernel loop in mklive.sh builds its initramfs + boot entry.
./mklive.sh \
-a "$ARCH" \
-r "$REPO_URL" \
@@ -216,8 +255,11 @@ trap _cleanup_mklive_builds EXIT
-k "$KEYMAP" \
-l "$LOCALE" \
-T "$ISO_TITLE" \
-v linux-mainline \
-p "$ISO_PKGS" \
-I "$INCLUDE_DIR" \
-x "$_DCONF_POSTSETUP" \
-x "$PROJECT_DIR/iso/postsetup-nvidia.sh" \
-C "${BOOT_CMDLINE:-}" \
-o "$OUT_ISO"

307
iso/_inner-build-unified.sh Executable file
View File

@@ -0,0 +1,307 @@
#!/bin/bash
# Unified inner-build script — runs INSIDE the docker container (as root).
# Invoked by iso/build.sh via 'docker run'.
#
# Replaces: _inner-build.sh, _inner-build-live.sh, _inner-build-niri-live.sh
#
# Required env:
# ARCH REPO_URL KEYMAP LOCALE ISO_PKGS ISO_TITLE OUT_ISO_REL INCLUDE_DIR_REL
# BUILD_TYPE — installer | live
# DESKTOP — cinnamon | niri
# KERNEL_PKG — linux | linux-mainline
#
# Optional env:
# NOCTALIA_REPO — CDN URL for noctalia-shell/noctalia-qs (niri only)
# NIX_PACKAGES_PREBAKE — space-separated nix package attrs (live only)
# BOOT_CMDLINE — extra kernel command-line
# HOST_UID / HOST_GID — fix output ownership for host user (default 1000)
set -Eeuo pipefail
# ── validate required env ─────────────────────────────────────────────────
: "${ARCH:?}"; : "${REPO_URL:?}"; : "${KEYMAP:?}"; : "${LOCALE:?}"
: "${ISO_PKGS:?}"; : "${ISO_TITLE:?}";
: "${OUT_ISO_REL:?}"; : "${INCLUDE_DIR_REL:?}"
: "${BUILD_TYPE:?}"; : "${DESKTOP:?}"; : "${KERNEL_PKG:?}"
# ── paths ─────────────────────────────────────────────────────────────────
CACHE_DIR=/cache
PROJECT_DIR=/work
INCLUDE_DIR="$PROJECT_DIR/$INCLUDE_DIR_REL"
OUT_ISO="$PROJECT_DIR/$OUT_ISO_REL"
# Niri profiles use a separate mklive clone to avoid races on parallel builds.
case "$DESKTOP" in
niri) MKLIVE_DIR="$CACHE_DIR/void-mklive-niri" ;;
*) MKLIVE_DIR="$CACHE_DIR/void-mklive" ;;
esac
# xbps package cache dir (per-desktop, same reason as above)
case "$DESKTOP" in
niri) XBPS_CACHE="$CACHE_DIR/xbps-niri-pkgs" ;;
*) XBPS_CACHE="$CACHE_DIR/xbps-live-pkgs" ;;
esac
export PATH="$CACHE_DIR/xbps-static/usr/bin:$PATH"
# ── sanity checks ─────────────────────────────────────────────────────────
[[ -d "$MKLIVE_DIR" ]] || { echo "ERROR: $MKLIVE_DIR missing (should have been cloned on host)"; exit 1; }
[[ -d "$INCLUDE_DIR" ]] || { echo "ERROR: $INCLUDE_DIR missing (staging failed on host)"; exit 1; }
command -v xbps-install.static >/dev/null \
|| { echo "ERROR: xbps-install.static not on PATH"; exit 1; }
mkdir -p "$(dirname "$OUT_ISO")" "$XBPS_CACHE"
# ═════════════════════════════════════════════════════════════════════════════
# 1) NIX PREBAKE (live ISOs only)
# ═════════════════════════════════════════════════════════════════════════════
if [[ "$BUILD_TYPE" == "live" && -n "${NIX_PACKAGES_PREBAKE:-}" ]]; then
echo ">>> pre-baking nix packages"
read -r -a _NIX_PKGS <<< "$NIX_PACKAGES_PREBAKE"
_NIX_CACHE="$CACHE_DIR/nix-prebake"
_CACHE_KEY="$_NIX_CACHE/.done.$(printf '%s\n' "${_NIX_PKGS[@]}" | sort | md5sum | cut -c1-8)"
mkdir -p "$_NIX_CACHE"
if [[ -f "$_CACHE_KEY" && -d "$_NIX_CACHE/store" && -f "$_NIX_CACHE/.profile-path" ]]; then
echo " restoring cached nix store ($(du -sh "$_NIX_CACHE/store" 2>/dev/null | cut -f1))"
mkdir -p /nix
rsync -a "$_NIX_CACHE/" /nix/ 2>&1 | tail -1
else
echo " installing nix (single-user, no-daemon) ..."
rm -rf /nix ~/.nix-profile ~/.nix-defexpr ~/.nix-channels
mkdir -m 0755 -p /nix
export NIX_CONFIG="build-users-group = "
curl -fsSL https://nixos.org/nix/install | \
NIX_INSTALLER_TRUST_INSTALLER=1 sh -s -- --no-daemon --no-channel-add
# shellcheck disable=SC1091
. /root/.nix-profile/etc/profile.d/nix.sh 2>/dev/null || true
export PATH="/root/.nix-profile/bin:/nix/var/nix/profiles/default/bin:$PATH"
export NIXPKGS_ALLOW_UNFREE=1
echo " nix profile install: ${_NIX_PKGS[*]}"
nix profile add --extra-experimental-features "nix-command flakes" \
--impure "${_NIX_PKGS[@]}" 2>&1
readlink -f /root/.nix-profile > "$_NIX_CACHE/.profile-path"
rsync -a /nix/ "$_NIX_CACHE/" 2>&1 | tail -1
touch "$_CACHE_KEY"
echo " cached nix store: $(du -sh "$_NIX_CACHE/store" 2>/dev/null | cut -f1)"
fi
echo " staging /nix into overlay ($(du -sh /nix/store 2>/dev/null | cut -f1))"
mkdir -p "$INCLUDE_DIR/nix"
rsync -a /nix/ "$INCLUDE_DIR/nix/" 2>&1 | tail -1
# Single-user nix: live user (uid 1000) owns the store.
chown -R 1000:1000 "$INCLUDE_DIR/nix"
_STORE_PROFILE=$(cat "$_NIX_CACHE/.profile-path" 2>/dev/null \
|| readlink -f /root/.nix-profile 2>/dev/null || echo "")
if [[ -n "$_STORE_PROFILE" && -d "$_STORE_PROFILE" ]]; then
mkdir -p "$INCLUDE_DIR/etc/skel"
ln -sf "$_STORE_PROFILE" "$INCLUDE_DIR/etc/skel/.nix-profile"
echo " skel/.nix-profile → $_STORE_PROFILE"
else
echo " WARNING: could not determine nix store profile path"
fi
fi
# ═════════════════════════════════════════════════════════════════════════════
# 2) NOCTALIA LOCAL SIGNED REPO (niri profiles only)
#
# noctalia-qs has a broken .sig2 on the CDN; noctalia-shell's CDN key import
# also fails intermittently (EAGAIN). Workaround: download both .xbps archives
# directly (curl skips sig checks), create a LOCAL SIGNED repo with a fresh RSA
# keypair, and register the public key in mklive/keys/ so copy_void_keys
# pre-trusts it in the rootfs. Our local repo is passed last to mklive.sh
# (-r last = HIGHEST priority) so xbps resolves and verifies both packages
# against our trusted key.
# ═════════════════════════════════════════════════════════════════════════════
_NOC_LOCAL=""
if [[ "$DESKTOP" == "niri" && -n "${NOCTALIA_REPO:-}" ]]; then
echo ">>> building local signed noctalia XBPS repo (CDN .sig2 workaround)"
_NOC_LOCAL="/tmp/noctalia-local"
_NOC_HOME="/tmp/noc-sign-home"
mkdir -p "$_NOC_LOCAL" "$_NOC_HOME"
export HOME="$_NOC_HOME"
# Discover exact package versions from CDN repodata
_NOC_VERS=$(python3 - <<'PYEOF' 2>/dev/null
import urllib.request, plistlib, tarfile, io, os
repo = os.environ.get("NOCTALIA_REPO", "https://universalrepo.r1xelelo.workers.dev/void")
arch = os.environ.get("ARCH", "x86_64")
want = {"noctalia-qs", "noctalia-shell"}
found = {}
for ua in ("curl/8.0", "xbps/0.59.2", "Mozilla/5.0 (X11; Linux x86_64)"):
try:
req = urllib.request.Request(f"{repo}/{arch}-repodata", headers={"User-Agent": ua})
data = urllib.request.urlopen(req, timeout=15).read()
tf = tarfile.open(fileobj=io.BytesIO(data))
idx = plistlib.loads(tf.extractfile("index.plist").read())
for pkgname, meta in idx.items():
if isinstance(meta, dict) and pkgname in want:
found[pkgname] = meta.get("pkgver", pkgname)
if len(found) >= len(want):
break
except Exception:
pass
# Fallback to versions confirmed by previous builds
defaults = {"noctalia-qs": "noctalia-qs-0.0.12_0", "noctalia-shell": "noctalia-shell-4.7.6_1"}
for pkg, ver in defaults.items():
if pkg not in found:
found[pkg] = ver
for ver in found.values():
print(ver)
PYEOF
)
for _ver in $_NOC_VERS; do
_fname="${_ver}.${ARCH}.xbps"
# Clear any cached bad sig2 from a previous failed build
rm -f "$CACHE_DIR/xbps-niri-pkgs/${_ver}"* 2>/dev/null || true
if [[ ! -f "$_NOC_LOCAL/$_fname" ]]; then
echo " downloading $_fname ..."
curl -fsSL "$NOCTALIA_REPO/$_fname" -o "$_NOC_LOCAL/$_fname" \
|| { echo " WARNING: failed to download $_fname"; rm -f "$_NOC_LOCAL/$_fname"; }
else
echo " cached: $_fname"
fi
done
xbps-rindex.static -a "$_NOC_LOCAL"/*.xbps
mkdir -p "$_NOC_HOME/.xbps-sign"
openssl genrsa -out "$_NOC_HOME/.xbps-sign/privkey.pem" 4096 2>/dev/null
[[ -s "$_NOC_HOME/.xbps-sign/privkey.pem" ]] \
|| { echo "ERROR: openssl genrsa failed"; exit 1; }
_NOC_PRIVKEY="$_NOC_HOME/.xbps-sign/privkey.pem"
xbps-rindex.static --sign --privkey "$_NOC_PRIVKEY" \
--signedby "noctalia-local" "$_NOC_LOCAL"
for _pkg in "$_NOC_LOCAL"/*.xbps; do
xbps-rindex.static --sign-pkg --privkey "$_NOC_PRIVKEY" \
--signedby "noctalia-local" "$_pkg"
done
openssl rsa -in "$_NOC_PRIVKEY" \
-pubout -outform DER -out "$_NOC_HOME/pubkey.der" 2>/dev/null
_FINGERPRINT=$(md5sum "$_NOC_HOME/pubkey.der" \
| cut -d' ' -f1 | sed 's/../&:/g; s/:$//')
_PUBKEY_B64=$(openssl rsa -in "$_NOC_PRIVKEY" \
-pubout 2>/dev/null | base64 -w 0)
cat > "$MKLIVE_DIR/keys/$_FINGERPRINT.plist" <<KEOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>public-key</key>
<data>$_PUBKEY_B64</data>
<key>public-key-size</key>
<integer>4096</integer>
<key>signature-by</key>
<string>noctalia-local</string>
</dict>
</plist>
KEOF
echo " local signed noctalia repo ready — key: $_FINGERPRINT"
unset HOME
fi
# ═════════════════════════════════════════════════════════════════════════════
# 3) DCONF POSTSETUP SCRIPT (live ISOs only — both cinnamon and niri)
#
# Compile system-db AND the skel user dconf db using Void's own binary inside
# the mklive rootfs chroot. No cross-distro format mismatch possible.
# ═════════════════════════════════════════════════════════════════════════════
_DCONF_POSTSETUP=""
if [[ "$BUILD_TYPE" == "live" ]]; then
_DCONF_POSTSETUP="$(mktemp -p "$MKLIVE_DIR" postsetup-dconf.XXXXX.sh)"
cat > "$_DCONF_POSTSETUP" <<'PSEOF'
#!/bin/bash
ROOTFS="$1"
if [[ -x "$ROOTFS/usr/bin/dconf" ]] && [[ -d "$ROOTFS/etc/dconf/db/local.d" ]]; then
chroot "$ROOTFS" dconf compile /etc/dconf/db/local /etc/dconf/db/local.d \
&& echo "postsetup-dconf: system-db compiled ($(chroot "$ROOTFS" dconf --version 2>/dev/null))" \
|| echo "postsetup-dconf: system-db compile failed (non-fatal)"
mkdir -p "$ROOTFS/etc/skel/.config/dconf"
chroot "$ROOTFS" dconf compile /etc/skel/.config/dconf/user /etc/dconf/db/local.d \
&& echo "postsetup-dconf: skel user dconf db compiled" \
|| echo "postsetup-dconf: skel user dconf db compile failed (non-fatal)"
else
echo "postsetup-dconf: dconf or keyfile dir not found in rootfs — skipping"
fi
PSEOF
chmod +x "$_DCONF_POSTSETUP"
fi
# ═════════════════════════════════════════════════════════════════════════════
# 4) CLEANUP TRAP
# ═════════════════════════════════════════════════════════════════════════════
cd "$MKLIVE_DIR"
_cleanup_mklive_builds() {
local d sub
for d in "$MKLIVE_DIR"/mklive-build.*/; do
[[ -d "$d" ]] || continue
for sub in tmp-rootfs/sys tmp-rootfs/proc tmp-rootfs/dev tmp-rootfs/run \
image/rootfs/sys image/rootfs/proc image/rootfs/dev image/rootfs/run; do
[[ -d "$d$sub" ]] && umount -R --lazy "$d$sub" 2>/dev/null || true
done
rm -rf "$d" 2>/dev/null || true
done
}
trap _cleanup_mklive_builds EXIT
# ═════════════════════════════════════════════════════════════════════════════
# 5) BUILD MKLIVE ARGS
# ═════════════════════════════════════════════════════════════════════════════
_MKLIVE_ARGS=(
-a "$ARCH"
-r "$REPO_URL"
-r "${REPO_URL%/current}/current/nonfree"
-c "$XBPS_CACHE"
-H "$CACHE_DIR/xbps-host-pkgs"
-k "$KEYMAP"
-l "$LOCALE"
-T "$ISO_TITLE"
-p "$ISO_PKGS"
-I "$INCLUDE_DIR"
-C "${BOOT_CMDLINE:-}"
-o "$OUT_ISO"
)
# Mainline kernel: pass as primary kernel so mklive builds the right initramfs.
# On dual-kernel ISOs the patched mklive loop also builds an entry for 'linux'.
[[ "$KERNEL_PKG" == "linux-mainline" ]] && _MKLIVE_ARGS+=( -v linux-mainline )
# Noctalia repos (niri only).
# Order matters: LAST -r = HIGHEST priority.
# We add CDN first, then our locally-signed repo so xbps resolves noctalia-*
# from the trusted local copy.
if [[ "$DESKTOP" == "niri" && -n "${NOCTALIA_REPO:-}" ]]; then
_MKLIVE_ARGS+=( -r "$NOCTALIA_REPO" )
[[ -n "$_NOC_LOCAL" ]] && _MKLIVE_ARGS+=( -r "$_NOC_LOCAL" )
fi
# NVIDIA postsetup (all builds)
_MKLIVE_ARGS+=( -x "$PROJECT_DIR/iso/postsetup-nvidia.sh" )
# dconf postsetup (live ISOs only)
[[ -n "$_DCONF_POSTSETUP" ]] && _MKLIVE_ARGS+=( -x "$_DCONF_POSTSETUP" )
# ═════════════════════════════════════════════════════════════════════════════
# 6) RUN MKLIVE
# ═════════════════════════════════════════════════════════════════════════════
echo ">>> running mklive.sh"
echo " type : $BUILD_TYPE"
echo " desktop : $DESKTOP"
echo " kernel : $KERNEL_PKG"
echo " output : $OUT_ISO"
./mklive.sh "${_MKLIVE_ARGS[@]}"
# Fix ownership so the host user can clean up without sudo.
chmod -R u+rwX "$INCLUDE_DIR" 2>/dev/null || true
chown -R "${HOST_UID:-1000}:${HOST_GID:-1000}" "$INCLUDE_DIR" 2>/dev/null || true
chown -R "${HOST_UID:-1000}:${HOST_GID:-1000}" "$OUT_ISO" "${OUT_ISO}".* 2>/dev/null || true

View File

@@ -47,6 +47,7 @@ trap _cleanup_mklive_builds EXIT
./mklive.sh \
-a "$ARCH" \
-r "$REPO_URL" \
-r "${REPO_URL%/current}/current/nonfree" \
-c "$CACHE_DIR/xbps-live-pkgs" \
-H "$CACHE_DIR/xbps-host-pkgs" \
-k "$KEYMAP" \
@@ -54,6 +55,7 @@ trap _cleanup_mklive_builds EXIT
-T "$ISO_TITLE" \
-p "$ISO_PKGS" \
-I "$INCLUDE_DIR" \
-x "$PROJECT_DIR/iso/postsetup-nvidia.sh" \
-C "${BOOT_CMDLINE:-}" \
-o "$OUT_ISO"

View File

@@ -90,7 +90,7 @@ LIVE_USER="${USERNAME:-live}"
LIVE_USER="${USERNAME:-live}"
# Extra groups (dracut only adds audio,video,wheel)
for g in plugdev input network docker; do
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
@@ -107,6 +107,7 @@ 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
@@ -134,6 +135,17 @@ if [ -f /etc/nsswitch.conf ]; then
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
@@ -263,6 +275,16 @@ 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)
@@ -288,7 +310,7 @@ EOF
install -d -m 0755 "$INCLUDE_DIR/etc/runit/runsvdir/default"
# Enable services for the live session.
for svc in dbus NetworkManager lightdm nix-daemon; do
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
@@ -560,6 +582,55 @@ 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'
@@ -570,6 +641,13 @@ if [[ -d "${HOME:-}/.nix-profile/bin" ]]; then
*) 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
@@ -641,6 +719,29 @@ 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'
@@ -665,7 +766,7 @@ 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}"
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 \

View File

@@ -84,7 +84,7 @@ cat > "$INCLUDE_DIR/etc/greetd/config.toml" <<'EOF'
vt = 2
[default_session]
command = "tuigreet --time --greeting 'Void Linux Live (niri)' --cmd 'niri --session'"
command = "tuigreet --time --greeting 'Void Linux Live (niri)' --cmd niri-session"
user = "_greeter"
EOF
@@ -102,8 +102,16 @@ BAUD_RATE=38400
TERM_NAME=linux
EOF
# ── 3b) noctalia XBPS repo ──────────────────────────────────────────────
# ── 3b) xbps repo configuration ─────────────────────────────────────────
# Write all repos into xbps.d so they are available in the live session
# (not just at build time). The nonfree repo is needed for nvidia et al.
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
cat > "$INCLUDE_DIR/etc/xbps.d/10-noctalia.conf" <<'EOF'
repository=https://universalrepo.r1xelelo.workers.dev/void
EOF
@@ -119,7 +127,7 @@ LIVE_USER="${USERNAME:-live}"
LIVE_USER="${LIVE_USER:-live}"
# Extra groups (dracut only adds audio,video,wheel)
for g in plugdev input network video audio _seatd; do
for g in plugdev input network video audio _seatd bluetooth; do
groupadd -f "$g" 2>/dev/null || true
usermod -aG "$g" "$LIVE_USER" 2>/dev/null || true
done
@@ -145,12 +153,16 @@ XDG_RUN="/run/user/$(id -u "$LIVE_USER" 2>/dev/null || echo 1000)"
install -d -m 0700 "$XDG_RUN" 2>/dev/null || true
chown "$LIVE_USER" "$XDG_RUN" 2>/dev/null || true
# Start elogind here, once, before runsvdir brings up greetd.
# Runit does NOT supervise it — this avoids cgroup-race restart spam.
# We wait until dbus is available (dbus service starts first in runsvdir),
# then start elogind as a background daemon.
# NOTE: live-setup.sh runs BEFORE runsvdir, so dbus isn't up yet here.
# We start elogind from a one-shot at-startup hook instead — see runit/2.
# Fix sound device permissions — PCM/control nodes are created root:root during
# early boot before the audio group is guaranteed to exist. Re-apply the correct
# ownership now that the group is present, then trigger udev to re-evaluate the
# sound subsystem so the persistent 70-sound-perms.rules rule is applied too.
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
# Timezone
ln -sf /usr/share/zoneinfo/Europe/Zurich /etc/localtime 2>/dev/null || true
echo "niri live-setup: done (user=$LIVE_USER)"
SV_EOF
@@ -193,23 +205,56 @@ install -d -m 0755 "$INCLUDE_DIR/etc/sv/elogind"
cat > "$INCLUDE_DIR/etc/sv/elogind/run" <<'EOF'
#!/bin/sh
exec 2>&1
# Helper: is elogind's login1 D-Bus name already registered on the system bus?
_login1_on_dbus() {
dbus-send --system --print-reply --dest=org.freedesktop.DBus \
/org/freedesktop/DBus org.freedesktop.DBus.GetNameOwner \
string:org.freedesktop.login1 >/dev/null 2>&1
}
# Helper: is elogind process alive via its PID file?
_elogind_alive() {
for _pf in /run/elogind.pid /run/elogind/elogind.pid; do
[ -f "$_pf" ] && kill -0 "$(cat "$_pf" 2>/dev/null)" 2>/dev/null && return 0
done
return 1
}
# If elogind is already running (process alive or D-Bus name taken), yield
# permanently so runit does not spam restarts.
if _elogind_alive || _login1_on_dbus; then
echo "elogind-sv: already running — yielding to avoid restart spam"
exec tail -f /dev/null
fi
# Wait for dbus socket — poll every second, up to 30s
i=0
while [ $i -lt 30 ] && [ ! -S /run/dbus/system_bus_socket ]; do
sleep 1; i=$((i+1))
done
exec /usr/libexec/elogind/elogind.wrapper
EOF
chmod 0755 "$INCLUDE_DIR/etc/sv/elogind/run"
# finish: rate-limit restarts to avoid "already running" spam
# finish: rate-limit restarts; if the run was very short (elogind crashed or
# reported "already running" before our detection), back off longer.
cat > "$INCLUDE_DIR/etc/sv/elogind/finish" <<'EOF'
#!/bin/sh
sleep 3
# $1=exitcode, $2=signal (or -1)
exitcode="$1"
# Non-zero exit with no signal = elogind bailed out (e.g. "already running").
# Sleep longer to avoid log spam; next run will yield via the D-Bus/PID check.
if [ "$exitcode" != "0" ] && [ "$2" = "-1" ]; then
sleep 10
else
sleep 3
fi
EOF
chmod 0755 "$INCLUDE_DIR/etc/sv/elogind/finish"
for svc in dbus elogind NetworkManager sshd; do
for svc in dbus elogind NetworkManager bluetoothd sshd; do
ln -sf "/etc/sv/$svc" "$INCLUDE_DIR/etc/runit/runsvdir/default/$svc"
done
@@ -225,6 +270,9 @@ export XDG_CURRENT_DESKTOP=niri
export XDG_SESSION_TYPE=wayland
# Use elogind's logind backend — works correctly on Void/runit via audit session IDs
export LIBSEAT_BACKEND=logind
# Force GTK apps (and Electron/VSCode) to use XDG portal file dialogs
export GTK_USE_PORTAL=1
export ELECTRON_OZONE_PLATFORM_HINT=auto
EOF
chmod 0644 "$INCLUDE_DIR/etc/profile.d/wayland.sh"
@@ -237,6 +285,20 @@ if [[ -d "${HOME:-}/.nix-profile/bin" ]]; then
*) export PATH="$HOME/.nix-profile/bin:$PATH" ;;
esac
fi
# Expose nix .desktop files and icons to XDG-compliant launchers/shells
if [[ -d "${HOME:-}/.nix-profile/share" ]]; then
case ":${XDG_DATA_DIRS:-}:" in
*":$HOME/.nix-profile/share:"*) ;;
*) export XDG_DATA_DIRS="$HOME/.nix-profile/share:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" ;;
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
EOF
chmod 0644 "$INCLUDE_DIR/etc/profile.d/nix-prebaked.sh"
@@ -244,6 +306,7 @@ chmod 0644 "$INCLUDE_DIR/etc/profile.d/nix-prebaked.sh"
install -d -m 0755 "$INCLUDE_DIR/etc/nix"
cat > "$INCLUDE_DIR/etc/nix/nix.conf" <<EOF
experimental-features = nix-command flakes
build-users-group =
sandbox = false
auto-optimise-store = true
trusted-users = root ${LIVE_USER}
@@ -251,6 +314,29 @@ max-jobs = 2
http-connections = 10
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
# ── 3e) niri config.kdl in /etc/skel ───────────────────────────────────
# dracut's adduser.sh copies skel → /home/live, so the live user gets a
# ready niri config without any first-boot setup step.
@@ -312,21 +398,25 @@ cursor {
// Audio / screen session services — started by niri as the live user
spawn-at-startup "pipewire"
spawn-at-startup "pipewire-pulse"
spawn-at-startup "wireplumber"
// Keep WirePlumber running: wait for the PipeWire socket then start it,
// restart on any crash or pipewire restart (e.g. after package installs).
spawn-at-startup "sh" "-c" "while true; do while [ ! -S /run/user/\$(id -u)/pipewire-0 ]; do sleep 0.2; done; wireplumber; sleep 1; done"
// Background, notifications, network, bluetooth, auth
// Background, notifications and auth
spawn-at-startup "swaybg" "-i" "/usr/share/backgrounds/void-installer/pxfuel.jpg" "-m" "fill"
spawn-at-startup "mako"
spawn-at-startup "nm-applet" "--indicator"
spawn-at-startup "blueman-applet"
spawn-at-startup "/usr/libexec/polkit-gnome-authentication-agent-1"
// noctalia-shell (Quickshell-based Wayland shell)
spawn-at-startup "quickshell" "-c" "noctalia-shell"
// noctalia-shell — wait for org.bluez before launching so the BT module
// initialises correctly (bluetoothd may not be on D-Bus yet at this point).
spawn-at-startup "sh" "-c" "i=0; while [ \$i -lt 30 ] && ! dbus-send --system --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.GetNameOwner string:org.bluez >/dev/null 2>&1; do sleep 1; i=\$((i+1)); done; exec quickshell -c noctalia-shell"
// First-login setup: installs Claude Code (and NVM) once, then closes
spawn-at-startup "sh" "-c" "[ -f ~/.first-login-done ] || alacritty -T 'Void Setup' -e /usr/local/libexec/first-login.sh"
binds {
Mod+T { spawn "alacritty"; }
Mod+D { spawn "fuzzel"; }
Mod+D { spawn "sh" "-c" "quickshell msg -c noctalia-shell launcher toggle"; }
Mod+Q { close-window; }
Mod+Shift+E { quit; }
Print { screenshot; }
@@ -408,6 +498,28 @@ if [[ -d "$BIBATA_SRC" ]]; then
echo " cursor: Bibata-Modern-Ice"
fi
# first-login.sh — deployed by _deploy_first_login() to the installed system.
[[ -r "$PROJECT_DIR/installer/first-login.sh" ]] && \
install -m 0755 "$PROJECT_DIR/installer/first-login.sh" "$OVERLAY/first-login.sh"
# Claude Code config + auth tokens from host (deployed to the installed system).
CLAUDE_SRC="${CLAUDE_SRC:-$HOME/.claude}"
[[ -d "$CLAUDE_SRC" ]] && { cp -a "$CLAUDE_SRC" "$OVERLAY/claude"; echo " claude: ~/.claude bundled"; }
[[ -r "${HOME}/.claude.json" ]] && install -m 0600 "${HOME}/.claude.json" "$OVERLAY/claude.json"
# VS Code user config + extension list from host.
VSCODE_SRC="${VSCODE_USER_SRC:-$HOME/.config/Code/User}"
install -d -m 0755 "$OVERLAY/vscode-user"
if [[ -d "$VSCODE_SRC" ]]; then
for f in settings.json keybindings.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"
command -v code >/dev/null 2>&1 && \
code --list-extensions > "$OVERLAY/vscode-extensions.txt" 2>/dev/null || true
echo " vscode-user: staged from $VSCODE_SRC"
fi
# Copy wallpapers and assets into usr/share (rootfs overlay)
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
@@ -418,11 +530,27 @@ install -d -m 0755 "$INCLUDE_DIR/usr/share/themes"
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
# ── 3g) GTK settings for Wayland apps ───────────────────────────────────
# ── 3g) GTK settings + dark theme dconf ────────────────────────────────
# Write GTK2/3/4 settings to skel so the live user picks them up.
install -d -m 0755 "$INCLUDE_DIR/etc/skel/.config/gtk-3.0"
install -d -m 0755 "$INCLUDE_DIR/etc/skel/.config/gtk-4.0"
# dconf system keyfile: ensures GTK dark theme is reported to all apps via
# xdg-desktop-portal-gtk regardless of whether the user has a dconf DB yet.
install -d -m 0755 "$INCLUDE_DIR/etc/dconf/db/local.d"
install -d -m 0755 "$INCLUDE_DIR/etc/dconf/profile"
cat > "$INCLUDE_DIR/etc/dconf/db/local.d/01-dark-theme" <<'EOF'
[org/gnome/desktop/interface]
color-scheme='prefer-dark'
gtk-theme='${GTK_THEME}'
icon-theme='${ICON_THEME}'
cursor-theme='${CURSOR_THEME:-Bibata-Modern-Ice}'
cursor-size=24
EOF
sed -i "s/'\${GTK_THEME}'/'${GTK_THEME}'/; s/'\${ICON_THEME}'/'${ICON_THEME}'/; s/'\${CURSOR_THEME:-Bibata-Modern-Ice}'/'${CURSOR_THEME:-Bibata-Modern-Ice}'/" \
"$INCLUDE_DIR/etc/dconf/db/local.d/01-dark-theme"
printf 'user-db:user\nsystem-db:local\n' > "$INCLUDE_DIR/etc/dconf/profile/user"
cat > "$INCLUDE_DIR/etc/skel/.config/gtk-3.0/settings.ini" <<EOF
[Settings]
gtk-theme-name=${GTK_THEME}
@@ -444,22 +572,214 @@ gtk-cursor-theme-size=24
gtk-font-name="Noto Sans 11"
EOF
# XDG_DATA_DIRS so Wayland apps pick up installed theme/icon .desktop files
cat > "$INCLUDE_DIR/etc/environment" <<'ENVEOF'
XDG_DATA_DIRS=/usr/local/share:/usr/share
# XDG_DATA_DIRS: include the pre-baked nix profile share dir so launchers
# and icon themes pick up nix-installed apps on first boot.
# /etc/environment is loaded by pam_env for ALL session types (tty autologin
# and greetd), so this is the most reliable place to set this baseline.
cat > "$INCLUDE_DIR/etc/environment" <<ENVEOF
XDG_DATA_DIRS=/home/${LIVE_USER}/.nix-profile/share:/usr/local/share:/usr/share
QT_QPA_PLATFORM=wayland;xcb
GDK_BACKEND=wayland,x11
MOZ_ENABLE_WAYLAND=1
LIBSEAT_BACKEND=logind
GTK_USE_PORTAL=1
ELECTRON_OZONE_PLATFORM_HINT=auto
ENVEOF
# ── noctalia default settings ─────────────────────────────────────────────
# Baseline settings.json placed in skel so the live user (and any installed
# user whose home is created from skel) starts with:
# - correct wallpaper directory pointing at the baked-in backgrounds
# - Gruvbox dark colour scheme
# - real app icons (iconMode=apps) instead of tabler monochrome icons
install -d -m 0755 "$INCLUDE_DIR/etc/skel/.config/noctalia"
cat > "$INCLUDE_DIR/etc/skel/.config/noctalia/settings.json" <<NOCEOF
{
"appLauncher": {
"iconMode": "apps",
"sortByMostUsed": true,
"showCategories": true,
"viewMode": "grid",
"pinnedApps": [],
"terminalCommand": "alacritty -e",
"density": "default",
"position": "center"
},
"colorSchemes": {
"darkMode": true,
"predefinedScheme": "Gruvbox",
"generationMethod": "tonal-spot",
"manualSunrise": "06:30",
"manualSunset": "18:30",
"monitorForColors": ""
},
"wallpaper": {
"enabled": true,
"directory": "/usr/share/backgrounds/void-installer",
"fillMode": "crop",
"fillColor": "#000000",
"automationEnabled": false,
"favorites": [],
"hideWallpaperFilenames": false,
"linkLightAndDarkWallpapers": true,
"monitorDirectories": [],
"enableMultiMonitorDirectories": false
}
}
NOCEOF
# ── Default browser: google-chrome (nix) ──────────────────────────────────
# System-wide MIME defaults so chrome is the default browser from first boot.
# Written to /etc/xdg/mimeapps.list (system default) and skel.
install -d -m 0755 "$INCLUDE_DIR/etc/xdg"
cat > "$INCLUDE_DIR/etc/xdg/mimeapps.list" <<'EOF'
[Default Applications]
text/html=google-chrome.desktop
x-scheme-handler/http=google-chrome.desktop
x-scheme-handler/https=google-chrome.desktop
x-scheme-handler/about=google-chrome.desktop
x-scheme-handler/unknown=google-chrome.desktop
application/pdf=google-chrome.desktop
application/xhtml+xml=google-chrome.desktop
application/xml=google-chrome.desktop
EOF
install -d -m 0755 "$INCLUDE_DIR/etc/skel/.config"
cp "$INCLUDE_DIR/etc/xdg/mimeapps.list" "$INCLUDE_DIR/etc/skel/.config/mimeapps.list"
# ── Portal backend configuration for niri ─────────────────────────────────
# Without this, xdg-desktop-portal doesn't know which backend to use for
# XDG_CURRENT_DESKTOP=niri, causing file-picker / portal calls to fail.
install -d -m 0755 "$INCLUDE_DIR/etc/xdg/xdg-desktop-portal"
cat > "$INCLUDE_DIR/etc/xdg/xdg-desktop-portal/niri-portals.conf" <<'EOF'
[preferred]
default=gtk
org.freedesktop.impl.portal.FileChooser=gtk
org.freedesktop.impl.portal.AppChooser=gtk
org.freedesktop.impl.portal.OpenURI=gtk
org.freedesktop.impl.portal.Print=gtk
org.freedesktop.impl.portal.Screenshot=gtk
org.freedesktop.impl.portal.Inhibit=gtk
org.freedesktop.impl.portal.Notification=gtk
org.freedesktop.impl.portal.Settings=gtk
EOF
# ── 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'
# Allow the audio group to access ALSA PCM and control devices.
SUBSYSTEM=="sound", GROUP="audio", MODE="0660"
EOF
# ── Timezone ──────────────────────────────────────────────────────────
# Create the /etc/localtime symlink and rc.conf TIMEZONE setting so the live
# session starts in the correct timezone without requiring a user step.
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
# ── NVIDIA PRIME overlay ────────────────────────────────────────────────
echo ">>> staging NVIDIA PRIME overlay"
# Blacklist nouveau — prevents the open-source driver from grabbing the GPU
# before the proprietary nvidia driver.
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
# Intel BT on this platform suffers firmware download failures when USB
# autosuspend is active for the btusb adapter.
cat > "$INCLUDE_DIR/etc/modprobe.d/btusb-quirks.conf" <<'EOF'
options btusb enable_autosuspend=0
EOF
# Load nvidia modules early so the DRM device node exists before niri starts.
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
# dracut: include nvidia modules in initramfs; omit nouveau.
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
# Xorg output-class configs: intel=modesetting (primary), nvidia=PRIME offload.
# Needed by xwayland-satellite for X11 clients running under niri.
install -d -m 0755 "$INCLUDE_DIR/etc/X11/xorg.conf.d"
cat > "$INCLUDE_DIR/etc/X11/xorg.conf.d/10-intel.conf" <<'EOF'
Section "OutputClass"
Identifier "intel"
MatchDriver "i915"
Driver "modesetting"
EndSection
EOF
cat > "$INCLUDE_DIR/etc/X11/xorg.conf.d/20-nvidia.conf" <<'EOF'
Section "OutputClass"
Identifier "nvidia"
MatchDriver "nvidia-drm"
Driver "nvidia"
Option "AllowEmptyInitialConfiguration"
Option "PrimaryGPU" "no"
ModulePath "/usr/lib/nvidia/xorg"
ModulePath "/usr/lib/xorg/modules"
EndSection
EOF
# prime-run: launch any app on the NVIDIA dGPU via PRIME render-offload.
cat > "$INCLUDE_DIR/usr/local/bin/prime-run" <<'EOF'
#!/bin/sh
# Run a program on the NVIDIA dGPU via PRIME render offload.
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"
# ── 3g2) niri-session wrapper ─────────────────────────────────────────────
# greetd/tuigreet starts niri-session (not niri --session directly) so that
# /etc/profile is sourced first, ensuring /etc/profile.d/* scripts run and
# XDG_DATA_DIRS gets ~/.nix-profile/share prepended for the compositor and
# all apps it spawns (noctalia-shell, fuzzel, etc.).
install -d -m 0755 "$INCLUDE_DIR/usr/local/bin"
cat > "$INCLUDE_DIR/usr/local/bin/niri-session" <<'EOF'
#!/bin/bash
# niri-session — wrapper started by greetd/tuigreet.
# Sources /etc/profile so that all /etc/profile.d/* scripts run
# (nix paths, wayland env, XDG_DATA_DIRS with ~/.nix-profile/share, etc.)
# before handing off to the compositor.
[ -f /etc/profile ] && . /etc/profile
exec niri --session "$@"
EOF
chmod 0755 "$INCLUDE_DIR/usr/local/bin/niri-session"
# ── 3h) .bash_profile: source .bashrc + launch niri on tty1 ───────────
# agetty autologin on tty1 runs a login shell; .bash_profile execs niri
# via dbus-run-session so it gets a D-Bus session bus.
cat > "$INCLUDE_DIR/etc/skel/.bash_profile" <<'EOF'
[[ -f ~/.bashrc ]] && . ~/.bashrc
if [[ "$(tty)" == /dev/tty1 ]] && [[ -z "$WAYLAND_DISPLAY" ]] && [[ -z "$NIRI_SOCKET" ]]; then
exec dbus-run-session -- niri --session
# Wrap niri startup: start GNOME Keyring daemon first (inside the D-Bus session)
# so Chrome/VSCode/apps can store secrets via org.freedesktop.secrets.
exec dbus-run-session -- sh -c '
if command -v gnome-keyring-daemon >/dev/null 2>&1; then
eval "$(gnome-keyring-daemon --start --components=secrets,pkcs11 2>/dev/null)" || true
export GNOME_KEYRING_CONTROL GNOME_KEYRING_PID SSH_AUTH_SOCK
fi
exec niri --session
'
fi
EOF
@@ -488,6 +808,20 @@ exec /usr/local/lib/void-installer/install.sh "$@"
WRAPEOF
chmod 0755 "$INCLUDE_DIR/usr/local/bin/void-install"
# first-login.sh at /usr/local/libexec: available in the live session and
# deployed by _deploy_first_login() to the installed system via the overlay.
install -d -m 0755 "$INCLUDE_DIR/usr/local/libexec"
install -m 0755 "$PROJECT_DIR/installer/first-login.sh" \
"$INCLUDE_DIR/usr/local/libexec/first-login.sh"
# nix-packages.list: tells first-login.sh which nix packages to install.
# In the live session these are already prebaked; in the installed system the
# first-boot-nix runit service handles them, so this is informational only.
{
printf '# Nix user packages\n'
printf '%s\n' "${NIX_USER_PACKAGES[@]}"
} > "$INCLUDE_DIR/usr/local/libexec/nix-packages.list"
_SECRETS_SRC="${SECRETS_ENV:-$PROJECT_DIR/secrets.env}"
if [[ -r "$_SECRETS_SRC" ]]; then
install -d -m 0755 "$INCLUDE_DIR/etc"
@@ -525,7 +859,17 @@ ISO_PKGS=$(grep -vE '^\s*(#|$)' \
| tr '\n' ' ')
TS="$(date -u +%Y%m%d)"
OUT_ISO="${OUTPUT_ISO:-$OUT_DIR/void-live-niri-${TS}.iso}"
BOOT_CMDLINE="${BOOT_CMDLINE:-live.user=${LIVE_USER} console=tty0 console=ttyS0,115200}"
# BOOT_CMDLINE notes (kernel audio, GPU, Bluetooth, etc.):
# • NO forced audio kernel parameters: Removed snd-intel-dspcfg.dsp_driver=1 and snd_hda_intel.dmic_detect=0
# because they forced legacy HDA mode, blocking SOF/SoundWire on hardware that needs it (e.g., XPS 9700).
# The kernel auto-selects the correct driver (0=auto, 3=SOF, 4=AVS) based on hardware IDs.
# Users can override at boot with "snd-intel-dspcfg.dsp_driver=3" if needed.
# See docs/KERNEL7_AUDIO_XPS9700.md for diagnostics.
# • nvidia-drm.modeset=1: Required for nvidia GPU to use atomic mode-setting on Wayland (Niri).
# • blacklist nouveau: nvidia GPU needs proprietary driver, not nouveau.
# • btusb.enable_autosuspend=0: Prevent Bluetooth headset dropouts when idle.
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 \

1025
iso/build.sh Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,161 @@
--- a/mklive.sh 2026-04-25 21:08:53.137743383 +0200
+++ b/mklive.sh 2026-04-25 21:25:02.321939679 +0200
@@ -211,7 +211,7 @@
copy_include_directories() {
for includedir in "${INCLUDE_DIRS[@]}"; do
info_msg "=> copying include directory '$includedir' ..."
- find "$includedir" -mindepth 1 -maxdepth 1 -exec cp -rfpPv {} "$ROOTFS"/ \;
+ rsync -aHX "$includedir"/ "$ROOTFS"/
done
}
@@ -229,6 +229,41 @@
i686*|x86_64*) cp "$ROOTFS/boot/vmlinuz-$KERNELVERSION" "$BOOT_DIR"/vmlinuz ;;
aarch64*) cp "$ROOTFS/boot/vmlinux-$KERNELVERSION" "$BOOT_DIR"/vmlinux ;;
esac
+
+ # Generate boot files for secondary kernels installed alongside KERNELVERSION.
+ SECONDARY_BOOT_KERNELS=()
+ for _kdir in "$ROOTFS/usr/lib/modules"/*/; do
+ [[ -d "${_kdir}/kernel" ]] || continue
+ # Strip trailing slash before extracting basename (glob */ appends /)
+ _kver="${_kdir%/}"; _kver="${_kver##*/}"
+ [[ "$_kver" == "$KERNELVERSION" ]] && continue
+ info_msg "Secondary kernel ${_kver}: generating live initramfs..."
+ chroot "$ROOTFS" env -i /usr/bin/dracut -N --"${INITRAMFS_COMPRESSION}" \
+ --add-drivers "ahci" --force-add "vmklive autoinstaller" --omit systemd \
+ "/boot/initrd-${_kver}" "${_kver}" || {
+ echo "WARNING: dracut failed for secondary kernel ${_kver} — skipping"
+ continue
+ }
+ mv "$ROOTFS/boot/initrd-${_kver}" "$BOOT_DIR/initrd-${_kver}"
+ case "$TARGET_ARCH" in
+ i686*|x86_64*)
+ if [[ -f "$ROOTFS/boot/vmlinuz-${_kver}" ]]; then
+ cp "$ROOTFS/boot/vmlinuz-${_kver}" "$BOOT_DIR/vmlinuz-${_kver}"
+ else
+ echo "WARNING: vmlinuz-${_kver} not found — skipping secondary kernel"
+ rm -f "$BOOT_DIR/initrd-${_kver}"; continue
+ fi ;;
+ aarch64*)
+ if [[ -f "$ROOTFS/boot/vmlinux-${_kver}" ]]; then
+ cp "$ROOTFS/boot/vmlinux-${_kver}" "$BOOT_DIR/vmlinux-${_kver}"
+ else
+ echo "WARNING: vmlinux-${_kver} not found — skipping secondary kernel"
+ rm -f "$BOOT_DIR/initrd-${_kver}"; continue
+ fi ;;
+ esac
+ SECONDARY_BOOT_KERNELS+=("${_kver}")
+ info_msg "Secondary kernel ${_kver}: boot files staged"
+ done
}
array_contains() {
@@ -273,6 +308,17 @@
-e "s|@@BOOT_CMDLINE@@|${BOOT_CMDLINE}|" \
"$ISOLINUX_DIR"/isolinux.cfg
+ # Append entries for secondary kernels (standard boot only).
+ for _skver in "${SECONDARY_BOOT_KERNELS[@]+"${SECONDARY_BOOT_KERNELS[@]}"}"; do
+ cat >> "$ISOLINUX_DIR"/isolinux.cfg << SEOF
+
+LABEL linux-${_skver%%_*}
+MENU LABEL ${BOOT_TITLE} ${_skver} ${TARGET_ARCH}
+KERNEL /boot/vmlinuz-${_skver}
+APPEND initrd=/boot/initrd-${_skver} root=live:CDLABEL=VOID_LIVE init=/sbin/init ro rd.luks=0 rd.md=0 rd.dm=0 loglevel=4 vconsole.unicode=1 vconsole.keymap=${KEYMAP} locale.LANG=${LOCALE} ${BOOT_CMDLINE}
+SEOF
+ done
+
# include memtest86+
if [ -e "$VOIDTARGETDIR"/boot/memtest86+/memtest.bin ]; then
cp "$VOIDTARGETDIR"/boot/memtest86+/memtest.bin "$BOOT_DIR"
@@ -290,15 +336,16 @@
esac
write_entry() {
- local entrytitle="$1" id="$2" cmdline="$3" dtb="$4" hotkey="$5"
+ local entrytitle="$1" id="$2" cmdline="$3" dtb="$4" hotkey="$5" \
+ kimg="${6:-${KERNEL_IMG}}" kidrd="${7:-initrd}"
cat << EOF >> "$GRUB_DIR"/grub_void.cfg
menuentry "${entrytitle}" --id "${id}" ${hotkey:+--hotkey $hotkey} {
set gfxpayload="keep"
- linux (\${voidlive})/boot/${KERNEL_IMG} \\
+ linux (\${voidlive})/boot/${kimg} \\
root=live:CDLABEL=VOID_LIVE ro init=/sbin/init \\
rd.luks=0 rd.md=0 rd.dm=0 loglevel=4 gpt add_efi_memmap \\
vconsole.unicode=1 vconsole.keymap=${KEYMAP} locale.LANG=${LOCALE} ${cmdline}
- initrd (\${voidlive})/boot/initrd
+ initrd (\${voidlive})/boot/${kidrd}
EOF
if [ -n "${dtb}" ]; then
printf ' devicetree (${voidlive})/boot/dtbs/%s\n' "${dtb}" >> "$GRUB_DIR"/grub_void.cfg
@@ -311,23 +358,25 @@
ENTRY_TITLE="${BOOT_TITLE} ${KERNELVERSION} ${title_sfx}(${TARGET_ARCH})"
+ # Standard boot only — failsafe/RAM/speech variants removed.
write_entry "${ENTRY_TITLE}" "linux${id_sfx}" \
"$BOOT_CMDLINE $cmdline" "$dtb"
- write_entry "${ENTRY_TITLE} (RAM)" "linuxram${id_sfx}" \
- "rd.live.ram $BOOT_CMDLINE $cmdline" "$dtb"
- write_entry "${ENTRY_TITLE} (graphics disabled)" "linuxnogfx${id_sfx}" \
- "nomodeset $BOOT_CMDLINE $cmdline" "$dtb"
- write_entry "${ENTRY_TITLE} with speech" "linuxa11y${id_sfx}" \
- "live.accessibility live.autologin $BOOT_CMDLINE $cmdline" "$dtb" 's'
- write_entry "${ENTRY_TITLE} with speech (RAM)" "linuxa11yram${id_sfx}" \
- "live.accessibility live.autologin rd.live.ram $BOOT_CMDLINE $cmdline" "$dtb" 'r'
- write_entry "${ENTRY_TITLE} with speech (graphics disabled)" "linuxa11ynogfx${id_sfx}" \
- "live.accessibility live.autologin nomodeset $BOOT_CMDLINE $cmdline" "$dtb" 'g'
}
write_entries
+ # Secondary kernel entries (standard boot only).
+ for _skver in "${SECONDARY_BOOT_KERNELS[@]+"${SECONDARY_BOOT_KERNELS[@]}"}";
+ do
+ case "$TARGET_ARCH" in
+ i686*|x86_64*) _skimg="vmlinuz-${_skver}" ;;
+ aarch64*) _skimg="vmlinux-${_skver}" ;;
+ esac
+ write_entry "${BOOT_TITLE} ${_skver} (${TARGET_ARCH})" \
+ "linux-${_skver%%_*}" "$BOOT_CMDLINE" "" "" "${_skimg}" "initrd-${_skver}"
+ done
+
for platform in "${PLATFORMS[@]}"; do
(
. "platforms/${platform}.sh"
--- a/isolinux/isolinux.cfg.in 2026-04-25 21:08:53.137743383 +0200
+++ b/isolinux/isolinux.cfg.in 2026-04-25 21:13:05.897818309 +0200
@@ -25,31 +25,6 @@
KERNEL /boot/vmlinuz
APPEND initrd=/boot/initrd root=live:CDLABEL=VOID_LIVE init=/sbin/init ro rd.luks=0 rd.md=0 rd.dm=0 loglevel=4 vconsole.unicode=1 vconsole.keymap=@@KEYMAP@@ locale.LANG=@@LOCALE@@ @@BOOT_CMDLINE@@
-LABEL linuxram
-MENU LABEL @@BOOT_TITLE@@ @@KERNVER@@ @@ARCH@@ (RAM)
-KERNEL /boot/vmlinuz
-APPEND initrd=/boot/initrd root=live:CDLABEL=VOID_LIVE init=/sbin/init ro rd.luks=0 rd.md=0 rd.dm=0 loglevel=4 vconsole.unicode=1 vconsole.keymap=@@KEYMAP@@ locale.LANG=@@LOCALE@@ @@BOOT_CMDLINE@@ rd.live.ram
-
-LABEL linuxnogfx
-MENU LABEL @@BOOT_TITLE@@ @@KERNVER@@ @@ARCH@@ (graphics disabled)
-KERNEL /boot/vmlinuz
-APPEND initrd=/boot/initrd root=live:CDLABEL=VOID_LIVE init=/sbin/init ro rd.luks=0 rd.md=0 rd.dm=0 loglevel=4 vconsole.unicode=1 vconsole.keymap=@@KEYMAP@@ locale.LANG=@@LOCALE@@ @@BOOT_CMDLINE@@ nomodeset
-
-LABEL linuxa11y
-MENU LABEL @@BOOT_TITLE@@ @@KERNVER@@ @@ARCH@@ with ^speech
-KERNEL /boot/vmlinuz
-APPEND initrd=/boot/initrd root=live:CDLABEL=VOID_LIVE init=/sbin/init ro rd.luks=0 rd.md=0 rd.dm=0 loglevel=4 vconsole.unicode=1 vconsole.keymap=@@KEYMAP@@ locale.LANG=@@LOCALE@@ @@BOOT_CMDLINE@@ live.accessibility live.autologin
-
-LABEL linuxa11yram
-MENU LABEL @@BOOT_TITLE@@ @@KERNVER@@ @@ARCH@@ with speech (^RAM)
-KERNEL /boot/vmlinuz
-APPEND initrd=/boot/initrd root=live:CDLABEL=VOID_LIVE init=/sbin/init ro rd.luks=0 rd.md=0 rd.dm=0 loglevel=4 vconsole.unicode=1 vconsole.keymap=@@KEYMAP@@ locale.LANG=@@LOCALE@@ @@BOOT_CMDLINE@@ live.accessibility live.autologin rd.live.ram
-
-LABEL linuxa11ynogfx
-MENU LABEL @@BOOT_TITLE@@ @@KERNVER@@ @@ARCH@@ with speech (^graphics disabled)
-KERNEL /boot/vmlinuz
-APPEND initrd=/boot/initrd root=live:CDLABEL=VOID_LIVE init=/sbin/init ro rd.luks=0 rd.md=0 rd.dm=0 loglevel=4 vconsole.unicode=1 vconsole.keymap=@@KEYMAP@@ locale.LANG=@@LOCALE@@ @@BOOT_CMDLINE@@ live.accessibility live.autologin nomodeset
-
LABEL c
MENU LABEL Boot first HD found by BIOS
COM32 chain.c32

82
iso/postsetup-nvidia.sh Executable file
View File

@@ -0,0 +1,82 @@
#!/bin/bash
# void-mklive postsetup hook (-x flag).
#
# Runs AFTER copy_include_directories (overlay applied) but BEFORE
# generate_initramfs (boot initramfs). The xbps initramfs-regenerate trigger
# fires during install_packages — BEFORE the overlay — so the squashfs rootfs's
# own /boot/initramfs-KVER.img is missing our etc/dracut.conf.d/10-nvidia.conf.
#
# This script re-runs dracut inside the rootfs chroot for every installed kernel
# so the squashfs initramfs:
# • includes nvidia.ko (add_drivers from 10-nvidia.conf)
# • excludes nouveau.ko (omit_drivers from 10-nvidia.conf)
#
# $1 = absolute path to the rootfs directory being built.
set -euo pipefail
ROOTFS="$1"
if [[ ! -d "$ROOTFS/usr/lib/modules" ]]; then
echo "[postsetup-nvidia] no modules dir — skipping"
exit 0
fi
if [[ ! -x "$ROOTFS/usr/bin/dracut" ]]; then
echo "[postsetup-nvidia] dracut not in rootfs — skipping"
exit 0
fi
if [[ ! -f "$ROOTFS/etc/dracut.conf.d/10-nvidia.conf" ]]; then
echo "[postsetup-nvidia] WARNING: 10-nvidia.conf not found in rootfs overlay — skipping"
exit 0
fi
# Mount pseudo-filesystems needed by dracut inside the chroot.
_umount_pseudo() {
for f in dev proc sys; do
[[ -d "$ROOTFS/$f" ]] && \
{ umount -R -f "$ROOTFS/$f" 2>/dev/null || umount -R -l "$ROOTFS/$f" 2>/dev/null || true; }
done
}
trap _umount_pseudo EXIT
for f in dev proc sys; do
mkdir -p "$ROOTFS/$f"
mount --rbind "/$f" "$ROOTFS/$f"
done
echo "[postsetup-nvidia] regenerating initramfs with nvidia config"
echo "[postsetup-nvidia] 10-nvidia.conf:"
sed 's/^/ /' "$ROOTFS/etc/dracut.conf.d/10-nvidia.conf"
found=0
for kdir in "$ROOTFS/usr/lib/modules"/*/; do
[[ -d "${kdir}/kernel" ]] || continue
# Strip trailing slash before extracting basename (glob */ appends /)
kver="${kdir%/}"; kver="${kver##*/}"
found=1
# Verify nvidia.ko is present for this kernel version.
nv_ko=$(find "$ROOTFS/usr/lib/modules/$kver" -name 'nvidia.ko' -o -name 'nvidia.ko.zst' 2>/dev/null | head -1)
if [[ -z "$nv_ko" ]]; then
echo "[postsetup-nvidia] WARNING: nvidia.ko not found for kernel $kver — initramfs regeneration skipped"
continue
fi
echo "[postsetup-nvidia] kernel $kver: nvidia.ko at ${nv_ko#"$ROOTFS"}"
echo "[postsetup-nvidia] running dracut --force for $kver ..."
chroot "$ROOTFS" env -i PATH=/usr/sbin:/usr/bin:/sbin:/bin \
/usr/bin/dracut --force \
"/boot/initramfs-${kver}.img" \
"$kver" 2>&1 | grep -v '^$' | tail -5 || {
echo "[postsetup-nvidia] WARNING: dracut failed for $kver — continuing"
continue
}
sz=$(ls -lh "$ROOTFS/boot/initramfs-${kver}.img" 2>/dev/null | awk '{print $5}')
echo "[postsetup-nvidia] done: /boot/initramfs-${kver}.img ($sz)"
done
[[ "$found" -eq 1 ]] || echo "[postsetup-nvidia] WARNING: no kernels found in rootfs"
echo "[postsetup-nvidia] complete"

View File

@@ -67,15 +67,17 @@ export TEST_PUBKEY
# ---------- 2) build TEST ISO ----------
TEST_ISO="$OUT_DIR/void-install-TEST.iso"
TEST_OVERLAY_DIR="$OUT_DIR/test-overlay"
"$PROJECT_DIR/tests/lib/make-test-overlay.sh" "$TEST_OVERLAY_DIR"
TEST_PROFILE="${PROFILE:-stable-cinnamon}" \
"$PROJECT_DIR/tests/lib/make-test-overlay.sh" "$TEST_OVERLAY_DIR"
if [[ ! -f "$TEST_ISO" || -n "${REBUILD_ISO:-}" ]]; then
blue "building test ISO -> $TEST_ISO"
blue "building test ISO -> $TEST_ISO (profile: ${PROFILE:-stable-cinnamon})"
EXTRA_INCLUDE_DIR="$TEST_OVERLAY_DIR" \
OUTPUT_ISO="$TEST_ISO" \
INSTALL_REPO_URL="http://10.0.2.2:3142/current" \
BOOT_CMDLINE="console=tty0 console=ttyS0,115200" \
"$PROJECT_DIR/iso/build-iso.sh"
PROFILE="${PROFILE:-stable-cinnamon}" \
"$PROJECT_DIR/iso/build.sh" --profile "${PROFILE:-stable-cinnamon}" --type installer
else
blue "reusing cached test ISO $TEST_ISO (set REBUILD_ISO=1 to rebuild)"
fi