From 56dfe11039b6ec100b3576662ec2ff63e3efb1cb Mon Sep 17 00:00:00 2001 From: Giancarmine Salucci Date: Sun, 26 Apr 2026 12:42:11 +0200 Subject: [PATCH] refactor: unified multi-profile build system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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/). --- Makefile | 28 +- .../profiles/mainline-cinnamon/packages.list | 171 +++ .../mainline-cinnamon/packages.live.list | 153 +++ .../profiles/mainline-cinnamon/profile.conf | 22 + .../profiles/mainline-niri/packages.live.list | 1 + .../stable-cinnamon/packages.live.list | 1 + .../stable-niri/customizations/niri.sh | 328 ++++++ config/profiles/stable-niri/packages.list | 190 +++ .../profiles/stable-niri/packages.live.list | 135 +++ config/profiles/stable-niri/profile.conf | 28 + installer/install.sh | 8 +- iso/_inner-build-unified.sh | 307 +++++ iso/build.sh | 1025 +++++++++++++++++ tests/run-qemu-test.sh | 8 +- 14 files changed, 2391 insertions(+), 14 deletions(-) create mode 100644 config/profiles/mainline-cinnamon/packages.list create mode 100644 config/profiles/mainline-cinnamon/packages.live.list create mode 100644 config/profiles/mainline-cinnamon/profile.conf create mode 120000 config/profiles/mainline-niri/packages.live.list create mode 120000 config/profiles/stable-cinnamon/packages.live.list create mode 100644 config/profiles/stable-niri/customizations/niri.sh create mode 100644 config/profiles/stable-niri/packages.list create mode 100644 config/profiles/stable-niri/packages.live.list create mode 100644 config/profiles/stable-niri/profile.conf create mode 100755 iso/_inner-build-unified.sh create mode 100755 iso/build.sh diff --git a/Makefile b/Makefile index a68811f..2f57529 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/config/profiles/mainline-cinnamon/packages.list b/config/profiles/mainline-cinnamon/packages.list new file mode 100644 index 0000000..958ea9c --- /dev/null +++ b/config/profiles/mainline-cinnamon/packages.list @@ -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 diff --git a/config/profiles/mainline-cinnamon/packages.live.list b/config/profiles/mainline-cinnamon/packages.live.list new file mode 100644 index 0000000..f35a3a9 --- /dev/null +++ b/config/profiles/mainline-cinnamon/packages.live.list @@ -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 diff --git a/config/profiles/mainline-cinnamon/profile.conf b/config/profiles/mainline-cinnamon/profile.conf new file mode 100644 index 0000000..04e9740 --- /dev/null +++ b/config/profiles/mainline-cinnamon/profile.conf @@ -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" diff --git a/config/profiles/mainline-niri/packages.live.list b/config/profiles/mainline-niri/packages.live.list new file mode 120000 index 0000000..42e5bcf --- /dev/null +++ b/config/profiles/mainline-niri/packages.live.list @@ -0,0 +1 @@ +packages.live-desktop.list \ No newline at end of file diff --git a/config/profiles/stable-cinnamon/packages.live.list b/config/profiles/stable-cinnamon/packages.live.list new file mode 120000 index 0000000..42e5bcf --- /dev/null +++ b/config/profiles/stable-cinnamon/packages.live.list @@ -0,0 +1 @@ +packages.live-desktop.list \ No newline at end of file diff --git a/config/profiles/stable-niri/customizations/niri.sh b/config/profiles/stable-niri/customizations/niri.sh new file mode 100644 index 0000000..5970a6c --- /dev/null +++ b/config/profiles/stable-niri/customizations/niri.sh @@ -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" < "$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" < "$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" < "$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" diff --git a/config/profiles/stable-niri/packages.list b/config/profiles/stable-niri/packages.list new file mode 100644 index 0000000..9dba7e6 --- /dev/null +++ b/config/profiles/stable-niri/packages.list @@ -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 diff --git a/config/profiles/stable-niri/packages.live.list b/config/profiles/stable-niri/packages.live.list new file mode 100644 index 0000000..dd09399 --- /dev/null +++ b/config/profiles/stable-niri/packages.live.list @@ -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 diff --git a/config/profiles/stable-niri/profile.conf b/config/profiles/stable-niri/profile.conf new file mode 100644 index 0000000..de2e9cf --- /dev/null +++ b/config/profiles/stable-niri/profile.conf @@ -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" diff --git a/installer/install.sh b/installer/install.sh index 25d6737..919ab1d 100755 --- a/installer/install.sh +++ b/installer/install.sh @@ -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" diff --git a/iso/_inner-build-unified.sh b/iso/_inner-build-unified.sh new file mode 100755 index 0000000..4c413b4 --- /dev/null +++ b/iso/_inner-build-unified.sh @@ -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" < + + + + public-key + $_PUBKEY_B64 + public-key-size + 4096 + signature-by + noctalia-local + + +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 diff --git a/iso/build.sh b/iso/build.sh new file mode 100755 index 0000000..c512263 --- /dev/null +++ b/iso/build.sh @@ -0,0 +1,1025 @@ +#!/bin/bash +# Unified ISO build script. +# +# Usage: +# iso/build.sh [--profile PROFILE] [--type installer|live] +# iso/build.sh -p PROFILE -t TYPE +# +# PROFILE: stable-cinnamon | stable-niri | mainline-cinnamon | mainline-niri +# TYPE: installer — small auto-installing ISO (~600 MB), boots directly to +# the installer; used for unattended / scripted deploys. +# live — full desktop ISO with the installer also embedded; +# boots to the selected DE so you can test before installing. +# +# The live ISO pre-configures the embedded installer to default to the SAME +# PROFILE it was built with, so running 'void-install' from the live session +# will install the exact same configuration you are running. +# +# Requires (host): bash, git, curl, docker. + +set -Eeuo pipefail + +# ── argument parsing ────────────────────────────────────────────────────── +PROFILE="${PROFILE:-stable-cinnamon}" +BUILD_TYPE="${BUILD_TYPE:-live}" + +while [[ $# -gt 0 ]]; do + case $1 in + --profile|-p) PROFILE="$2"; shift 2 ;; + --type|-t) BUILD_TYPE="$2"; shift 2 ;; + *) echo "Unknown argument: $1"; exit 1 ;; + esac +done + +case "$PROFILE" in + stable-cinnamon|stable-niri|mainline-cinnamon|mainline-niri) ;; + *) + echo "Unknown profile: '$PROFILE'" + echo "Valid values: stable-cinnamon stable-niri mainline-cinnamon mainline-niri" + exit 1 + ;; +esac + +case "$BUILD_TYPE" in + installer|live) ;; + *) + echo "Unknown build type: '$BUILD_TYPE'" + echo "Valid values: installer live" + exit 1 + ;; +esac + +# ── paths ───────────────────────────────────────────────────────────────── +PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +CACHE_DIR="${CACHE_DIR:-$PROJECT_DIR/cache}" +OUT_DIR="${OUT_DIR:-$PROJECT_DIR/out}" +BUILD_DIR="${BUILD_DIR:-$PROJECT_DIR/build}" +PATCH_DIR="$PROJECT_DIR/iso/patches" +DOCKER_IMAGE="${DOCKER_IMAGE:-void-installer-builder:latest}" +DOCKER="${DOCKER:-docker}" +LIVE_USER="${LIVE_USER:-live}" + +# Niri profiles use a separate mklive clone directory to avoid races when +# building cinnamon and niri ISOs in parallel on the same host. +case "$PROFILE" in + *-niri) MKLIVE_DIR="$CACHE_DIR/void-mklive-niri" ;; + *) MKLIVE_DIR="$CACHE_DIR/void-mklive" ;; +esac +MKLIVE_REPO="${MKLIVE_REPO:-https://github.com/void-linux/void-mklive.git}" +MKLIVE_REF="${MKLIVE_REF:-master}" + +# Per-profile, per-type build directory — parallel builds don't clobber each other. +INCLUDE_DIR="$BUILD_DIR/${PROFILE}-${BUILD_TYPE}-includes" + +# ── load configuration ──────────────────────────────────────────────────── +PROFILE_DIR="$PROJECT_DIR/config/profiles/$PROFILE" +[[ -d "$PROFILE_DIR" ]] || { echo "Profile directory not found: $PROFILE_DIR"; exit 1; } + +# shellcheck disable=SC1091 +source "$PROJECT_DIR/config/install.conf" +# shellcheck disable=SC1090 +source "$PROFILE_DIR/profile.conf" + +SECRETS_FILE="${SECRETS_FILE:-$PROJECT_DIR/secrets.env}" +[[ -r "$SECRETS_FILE" ]] || { echo "missing $SECRETS_FILE — copy from template"; exit 1; } +# shellcheck disable=SC1090 +source "$SECRETS_FILE" +: "${USER_PASSWORD:?}"; : "${ROOT_PASSWORD:?}" + +SSH_SRC_DIR="${SSH_SRC_DIR:-$HOME/.ssh}" +[[ -d "$SSH_SRC_DIR" ]] || { echo "no SSH dir at $SSH_SRC_DIR"; exit 1; } + +# ── validate docker ──────────────────────────────────────────────────────── +command -v "$DOCKER" >/dev/null \ + || { echo "ERROR: '$DOCKER' not in PATH"; exit 1; } +"$DOCKER" info >/dev/null 2>&1 \ + || { echo "ERROR: '$DOCKER' daemon unreachable"; exit 1; } + +mkdir -p "$CACHE_DIR" "$OUT_DIR" "$BUILD_DIR" + +# ═════════════════════════════════════════════════════════════════════════════ +# HELPER FUNCTIONS +# (all defined here so main execution at the bottom can call them) +# ═════════════════════════════════════════════════════════════════════════════ + +_stage_installer_overlay() { + # Installer ISO: auto-login root on tty1, run install-void, drop to shell. + install -d -m 0755 "$INCLUDE_DIR/etc/sv/installer" + cat > "$INCLUDE_DIR/etc/sv/installer/run" <<'SV_EOF' +#!/bin/sh +exec /sbin/agetty --autologin root --noclear tty1 linux +SV_EOF + chmod 0755 "$INCLUDE_DIR/etc/sv/installer/run" + + install -d -m 0755 "$INCLUDE_DIR/etc/sv/agetty-tty1" + : > "$INCLUDE_DIR/etc/sv/agetty-tty1/down" + + install -d -m 0755 "$INCLUDE_DIR/etc/runit/runsvdir/default" + ln -sf /etc/sv/installer "$INCLUDE_DIR/etc/runit/runsvdir/default/installer" + + install -d -m 0700 "$INCLUDE_DIR/root" + cat > "$INCLUDE_DIR/root/.bash_profile" <<'PROFILE_EOF' +case "$(tty)" in + /dev/tty1) + if [ ! -f /tmp/.installer-done ]; then + touch /tmp/.installer-done + clear + echo + echo " Void Linux Installer" + echo " Press ENTER to start, or Ctrl-C within 5s for a shell." + sleep 5 || true + /usr/local/sbin/install-void || { + echo "Installer exited with $?. Dropping to shell." + exec /bin/bash + } + echo "Install complete. Type 'reboot' or 'poweroff'." + exec /bin/bash + fi + ;; +esac +PROFILE_EOF + chmod 0644 "$INCLUDE_DIR/root/.bash_profile" + + # shellcheck disable=SC2154 # PROFILE and HOSTNAME are from sourced config + cat > "$INCLUDE_DIR/etc/motd" < "$INCLUDE_DIR/etc/xbps.d/00-void-repos.conf" < "$INCLUDE_DIR/etc/runit/2" <<'SV_EOF' +#!/bin/sh +PATH=/usr/bin:/usr/sbin + +[ -x /etc/runit/live-setup.sh ] && /etc/runit/live-setup.sh + +runlevel=default +for arg in $(cat /proc/cmdline); do + [ -d /etc/runit/runsvdir/"$arg" ] && runlevel="$arg" +done + +[ -x /etc/rc.local ] && /etc/rc.local + +runsvchdir "${runlevel}" +mkdir -p /run/runit/runsvdir +ln -sf /etc/runit/runsvdir/current /run/runit/runsvdir/current + +exec env - PATH=$PATH \ + runsvdir -P /run/runit/runsvdir/current \ + 'log: ...........................................................................................................................................................................................................................................................................................................................................................................................................' +SV_EOF + chmod 0755 "$INCLUDE_DIR/etc/runit/2" + + # /etc/environment (XDG_DATA_DIRS includes nix profile share for app menus) + cat > "$INCLUDE_DIR/etc/environment" <<'ENVEOF' +XDG_DATA_DIRS=/home/live/.nix-profile/share:/usr/local/share:/usr/share +ENVEOF + + # Timezone and rc.conf + # shellcheck disable=SC2154 + ln -sf "/usr/share/zoneinfo/${TIMEZONE:-Europe/Zurich}" "$INCLUDE_DIR/etc/localtime" + cat > "$INCLUDE_DIR/etc/rc.conf" < "$INCLUDE_DIR/etc/udev/rules.d/70-sound-perms.rules" <<'EOF' +SUBSYSTEM=="sound", GROUP="audio", MODE="0660" +EOF + + # NVIDIA PRIME overlay (blacklist nouveau, prime-run wrapper, dracut config) + 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" + + # nix prebake profile.d + cat > "$INCLUDE_DIR/etc/profile.d/nix-prebaked.sh" <<'NIXEOF' +if [[ -d "${HOME:-}/.nix-profile/bin" ]]; then + case ":$PATH:" in + *":$HOME/.nix-profile/bin:"*) ;; + *) export PATH="$HOME/.nix-profile/bin:$PATH" ;; + esac +fi +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 +export NIX_REMOTE=local +nix() { command nix "$@" --impure; } +export -f nix +NIXEOF + chmod 0644 "$INCLUDE_DIR/etc/profile.d/nix-prebaked.sh" + + # nix daemon config + install -d -m 0755 "$INCLUDE_DIR/etc/nix" + # shellcheck disable=SC2154 + cat > "$INCLUDE_DIR/etc/nix/nix.conf" < "$INCLUDE_DIR/etc/skel/.config/nixpkgs/config.nix" + + # git askpass (works without 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 + + # skel .bash_profile + install -d -m 0755 "$INCLUDE_DIR/etc/skel" + cat > "$INCLUDE_DIR/etc/skel/.bash_profile" <<'EOF' +[[ -f ~/.bashrc ]] && . ~/.bashrc +EOF + + # themes / icons / wallpapers in usr/share + install -d -m 0755 "$INCLUDE_DIR/usr/share/backgrounds/void-installer" + cp -a "$OVERLAY/wallpapers"/. "$INCLUDE_DIR/usr/share/backgrounds/void-installer/" 2>/dev/null || true + + install -d -m 0755 "$INCLUDE_DIR/usr/share/themes" + [[ -d "$OVERLAY/themes" ]] && cp -a "$OVERLAY/themes"/. "$INCLUDE_DIR/usr/share/themes/" 2>/dev/null || true + + install -d -m 0755 "$INCLUDE_DIR/usr/share/icons" + [[ -d "$OVERLAY/icons" ]] && cp -a "$OVERLAY/icons"/. "$INCLUDE_DIR/usr/share/icons/" 2>/dev/null || true + + # first-login.sh available for manual use from live session + install -d -m 0755 "$INCLUDE_DIR/usr/local/libexec" + if [[ -r "$OVERLAY/first-login.sh" ]]; then + install -m 0755 "$OVERLAY/first-login.sh" "$INCLUDE_DIR/usr/local/libexec/first-login.sh" + fi + + # installer desktop entry (double-click to launch in a terminal) + install -d -m 0755 "$INCLUDE_DIR/usr/share/applications" + cat > "$INCLUDE_DIR/usr/share/applications/void-installer.desktop" <<'DESKEOF' +[Desktop Entry] +Version=1.0 +Type=Application +Name=Install Void Linux +Comment=Install Void Linux to this machine +Exec=alacritty --title "Void Linux Installer" -e sudo /usr/local/sbin/install-void +Icon=system-software-install +Terminal=false +Categories=System; +StartupNotify=true +DESKEOF +} + +_stage_live_cinnamon() { + # live-setup.sh: extra groups, sudo, nix, DNS, timezone, Xorg GPU detection + cat > "$INCLUDE_DIR/etc/runit/live-setup.sh" <<'SV_EOF' +#!/bin/sh +LIVE_USER="${USERNAME:-live}" +[ -f /etc/default/live.conf ] && . /etc/default/live.conf +LIVE_USER="${LIVE_USER:-live}" + +for g in plugdev input network docker bluetooth; do + groupadd -f "$g" 2>/dev/null || true + usermod -aG "$g" "$LIVE_USER" 2>/dev/null || true +done +install -d -m 0755 /etc/sudoers.d +printf '%s ALL=(ALL) NOPASSWD: ALL\n' "$LIVE_USER" > /etc/sudoers.d/live +chmod 0440 /etc/sudoers.d/live + +if [ -x /usr/bin/nix ]; then + install -d -m 0755 /etc/nix + cat > /etc/nix/nix.conf < /etc/profile.d/nix-live.sh <<'NIXSH' +if [ -d "$HOME/.nix-profile/bin" ]; then export PATH="$HOME/.nix-profile/bin:$PATH"; fi +NIXSH + chmod 0644 /etc/profile.d/nix-live.sh +fi + +[ -f /etc/nsswitch.conf ] && sed -i '/^hosts:/s/mdns[^ ]* *//g' /etc/nsswitch.conf + +if ! grep -q '^nameserver' /etc/resolv.conf 2>/dev/null || \ + grep -q '^nameserver 10\.0\.2\.3' /etc/resolv.conf 2>/dev/null; then + printf 'nameserver 8.8.8.8\nnameserver 1.1.1.1\n' > /etc/resolv.conf +fi + +ln -sf /usr/share/zoneinfo/Europe/Zurich /etc/localtime 2>/dev/null || true + +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 + +mkdir -p /etc/X11/xorg.conf.d +HAS_NVIDIA=0; IS_VIRT=0 +if command -v lspci >/dev/null 2>&1; then + lspci_out=$(lspci 2>/dev/null) + echo "$lspci_out" | grep -qi 'NVIDIA' && HAS_NVIDIA=1 + echo "$lspci_out" | grep -qiE 'virtio|VMware|QEMU|VirtualBox|bochs' && IS_VIRT=1 +fi + +if [ $IS_VIRT -eq 1 ]; then + printf 'Section "Device"\n Identifier "GPU"\n Driver "modesetting"\n Option "AccelMethod" "none"\nEndSection\n' \ + > /etc/X11/xorg.conf.d/20-gpu.conf + echo 'export LIBGL_ALWAYS_SOFTWARE=1' > /etc/profile.d/live-env.sh + chmod 0644 /etc/profile.d/live-env.sh +elif [ $HAS_NVIDIA -eq 1 ] && [ -e /usr/lib/xorg/modules/drivers/nvidia_drv.so ]; then + rm -f /etc/profile.d/live-env.sh + INTEL_BUSID=$(lspci | grep -iE 'VGA.*Intel|Intel.*VGA' | head -1 | \ + awk '{print $1}' | awk -F'[.:]' '{printf "PCI:%d:%d:%d", strtonum("0x"$1), strtonum("0x"$2), strtonum("0x"$3)}') + NVIDIA_BUSID=$(lspci | grep -i NVIDIA | head -1 | \ + awk '{print $1}' | awk -F'[.:]' '{printf "PCI:%d:%d:%d", strtonum("0x"$1), strtonum("0x"$2), strtonum("0x"$3)}') + cat > /etc/X11/xorg.conf.d/20-gpu.conf < /etc/X11/xorg.conf.d/20-gpu.conf +fi + +echo "live-setup (cinnamon): done" +SV_EOF + chmod 0755 "$INCLUDE_DIR/etc/runit/live-setup.sh" + + # LightDM autologin — session file read by vmklive dracut hook + install -d -m 0755 "$INCLUDE_DIR/etc/lightdm" + echo 'cinnamon' > "$INCLUDE_DIR/etc/lightdm/.session" + # Autologin lines commented so display-manager-autologin.sh hook uncomments them + cat > "$INCLUDE_DIR/etc/lightdm/lightdm.conf" <<'EOF' +[Seat:*] +#autologin-user= +#autologin-user-timeout=0 +#autologin-session= +#user-session= +session-wrapper=/etc/lightdm/Xsession +greeter-session=lightdm-gtk-greeter +EOF + # NOTE: session-wrapper=/etc/lightdm/Xsession required on Void — 'lightdm-session' + # binary is NOT packaged; Xsession sources /etc/profile so profile.d/ vars reach + # the Cinnamon session (LIBGL_ALWAYS_SOFTWARE=1 from live-setup.sh, etc.) + + # runit services + install -d -m 0755 "$INCLUDE_DIR/etc/runit/runsvdir/default" + for svc in dbus NetworkManager lightdm bluetoothd nix-daemon; do + ln -sf "/etc/sv/$svc" "$INCLUDE_DIR/etc/runit/runsvdir/default/$svc" 2>/dev/null || true + done + + # dconf keyfile for Cinnamon theme / keyboard + install -d -m 0755 "$INCLUDE_DIR/etc/dconf/db/local.d" \ + "$INCLUDE_DIR/etc/dconf/profile" + cat > "$INCLUDE_DIR/etc/dconf/profile/user" <<'EOF' +user-db:user +system-db:local +EOF + + WALLPAPER_FILE=$(ls "$OVERLAY/wallpapers/"*.jpg 2>/dev/null | head -1 | \ + xargs basename 2>/dev/null || echo "pxfuel.jpg") + # shellcheck disable=SC2154 + cat > "$INCLUDE_DIR/etc/dconf/db/local.d/00-cinnamon" <t'] +EOF + + install -d -m 0755 "$INCLUDE_DIR/etc/dconf/db/local.d/locks" + cat > "$INCLUDE_DIR/etc/dconf/db/local.d/locks/keyboard" <<'EOF' +/org/gnome/desktop/input-sources/sources +/org/cinnamon/desktop/default-applications/terminal/exec +/org/cinnamon/desktop/default-applications/terminal/exec-arg +/org/gnome/desktop/default-applications/terminal/exec +/org/gnome/desktop/default-applications/terminal/exec-arg +EOF + + # Nemo: "Open in VS Code" action + install -d -m 0755 "$INCLUDE_DIR/usr/share/nemo/actions" + install -d -m 0755 "$INCLUDE_DIR/usr/local/bin" + cat > "$INCLUDE_DIR/usr/local/bin/code-open" <<'EOF' +#!/bin/sh +for d in "/home/live/.nix-profile/bin" /nix/var/nix/profiles/per-user/live/profile/bin \ + /usr/local/bin /usr/bin; do + if [ -x "$d/code" ]; then exec "$d/code" "$@"; fi +done +exec code "$@" +EOF + chmod 0755 "$INCLUDE_DIR/usr/local/bin/code-open" + cat > "$INCLUDE_DIR/usr/share/nemo/actions/open-in-vscode.nemo_action" <<'EOF' +[Nemo Action] +Name=Open in VS _Code +Comment=Open in Visual Studio Code +Exec=/usr/local/bin/code-open %F +Icon-Name=code +Selection=Any +Extensions=any; +Dependencies=/usr/local/bin/code-open; +EOF + + # apply-live-settings.sh: runs once at first Cinnamon login + install -d -m 0755 "$INCLUDE_DIR/usr/local/libexec" \ + "$INCLUDE_DIR/etc/xdg/autostart" + # shellcheck disable=SC2154 + cat > "$INCLUDE_DIR/usr/local/libexec/apply-live-settings.sh" </dev/null)" || true; sleep 0.3 +done +gsettings set org.cinnamon.desktop.interface gtk-theme '${GTK_THEME}' +gsettings set org.cinnamon.desktop.interface icon-theme '${ICON_THEME}' +gsettings set org.gnome.desktop.interface cursor-theme '${CURSOR_THEME}' +gsettings set org.cinnamon.theme name '${GTK_THEME}' +_WP=\$(ls /usr/share/backgrounds/void-installer/*.jpg 2>/dev/null | head -1 || true) +[[ -n "\$_WP" ]] && gsettings set org.cinnamon.desktop.background picture-uri "file://\$_WP" +[[ -n "\$_WP" ]] && gsettings set org.gnome.desktop.background picture-uri "file://\$_WP" +gsettings set org.cinnamon.desktop.default-applications.terminal exec '${DEFAULT_TERMINAL:-alacritty}' +gsettings set org.cinnamon.desktop.default-applications.terminal exec-arg '-e' +gsettings set org.gnome.desktop.default-applications.terminal exec '${DEFAULT_TERMINAL:-alacritty}' +gsettings set org.gnome.desktop.default-applications.terminal exec-arg '-e' +gsettings set org.gnome.desktop.input-sources sources "[('xkb', '${KEYMAP//-/+}')]" +touch "\$DONE" +APPLYEOF + chmod 0755 "$INCLUDE_DIR/usr/local/libexec/apply-live-settings.sh" + cat > "$INCLUDE_DIR/etc/xdg/autostart/void-live-settings.desktop" <<'DESK' +[Desktop Entry] +Type=Application +Name=Void Live Apply Settings +Exec=/usr/local/libexec/apply-live-settings.sh +NoDisplay=true +X-GNOME-Autostart-enabled=true +OnlyShowIn=X-Cinnamon; +DESK +} + +_stage_live_niri() { + # live-setup.sh: groups, sudo, DNS, sound, XDG_RUNTIME_DIR + cat > "$INCLUDE_DIR/etc/runit/live-setup.sh" </dev/null || true + usermod -aG "\$g" "\$LIVE_USER" 2>/dev/null || true +done +install -d -m 0755 /etc/sudoers.d +printf '%s ALL=(ALL) NOPASSWD: ALL\n' "\$LIVE_USER" > /etc/sudoers.d/live +chmod 0440 /etc/sudoers.d/live + +[ -f /etc/nsswitch.conf ] && sed -i '/^hosts:/s/mdns[^ ]* *//g' /etc/nsswitch.conf + +if ! grep -q '^nameserver' /etc/resolv.conf 2>/dev/null || \ + grep -q '^nameserver 10\\.0\\.2\\.3' /etc/resolv.conf 2>/dev/null; then + printf 'nameserver 8.8.8.8\nnameserver 1.1.1.1\n' > /etc/resolv.conf +fi + +_UID=\$(id -u "\$LIVE_USER" 2>/dev/null || echo 1000) +XDG_RUN="/run/user/\$_UID" +install -d -m 0700 "\$XDG_RUN" 2>/dev/null || true +chown "\$LIVE_USER" "\$XDG_RUN" 2>/dev/null || true + +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 + +ln -sf /usr/share/zoneinfo/Europe/Zurich /etc/localtime 2>/dev/null || true +echo "niri live-setup: done (user=\$LIVE_USER)" +SV_EOF + chmod 0755 "$INCLUDE_DIR/etc/runit/live-setup.sh" + + # greetd config (tty2 TUI fallback greeter) + install -d -m 0755 "$INCLUDE_DIR/etc/greetd" + cat > "$INCLUDE_DIR/etc/greetd/config.toml" <<'EOF' +[terminal] +vt = 2 + +[default_session] +command = "tuigreet --time --greeting 'Void Linux Live (niri)' --cmd niri-session" +user = "_greeter" +EOF + + # agetty-tty1 autologin override + install -d -m 0755 "$INCLUDE_DIR/etc/sv/agetty-tty1" + cat > "$INCLUDE_DIR/etc/sv/agetty-tty1/conf" <> "$INCLUDE_DIR/etc/xbps.d/00-void-repos.conf" <<'EOF' +repository=https://universalrepo.r1xelelo.workers.dev/void +EOF + + # Wayland environment + cat > "$INCLUDE_DIR/etc/profile.d/wayland.sh" <<'EOF' +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 LIBSEAT_BACKEND=logind +export GTK_USE_PORTAL=1 +export ELECTRON_OZONE_PLATFORM_HINT=auto +EOF + chmod 0644 "$INCLUDE_DIR/etc/profile.d/wayland.sh" + + # elogind custom sv (waits for dbus socket, avoids restart spam) + install -d -m 0755 "$INCLUDE_DIR/etc/sv/elogind" + cat > "$INCLUDE_DIR/etc/sv/elogind/run" <<'EOF' +#!/bin/sh +exec 2>&1 +_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 +} +_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_alive || _login1_on_dbus; then + echo "elogind-sv: already running — yielding" + exec tail -f /dev/null +fi +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" + cat > "$INCLUDE_DIR/etc/sv/elogind/finish" <<'EOF' +#!/bin/sh +if [ "$1" != "0" ] && [ "$2" = "-1" ]; then sleep 10; else sleep 3; fi +EOF + chmod 0755 "$INCLUDE_DIR/etc/sv/elogind/finish" + + # runit services + install -d -m 0755 "$INCLUDE_DIR/etc/runit/runsvdir/default" + for svc in dbus elogind NetworkManager bluetoothd sshd; do + ln -sf "/etc/sv/$svc" "$INCLUDE_DIR/etc/runit/runsvdir/default/$svc" + done + + # niri skel config (dracut copies skel → /home/live at boot) + local KEYMAP_XKB_LAYOUT="${KEYMAP%%-*}" + local KEYMAP_XKB_VARIANT="${KEYMAP#*-}" + KEYMAP_XKB_VARIANT="${KEYMAP_XKB_VARIANT//_nodeadkeys/}" + + install -d -m 0755 "$INCLUDE_DIR/etc/skel/.config/niri" + # shellcheck disable=SC2154 + cat > "$INCLUDE_DIR/etc/skel/.config/niri/config.kdl" </dev/null 2>&1; do sleep 1; i=\\\$((i+1)); done; exec quickshell -c noctalia-shell" +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 "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 + + # dconf dark theme (GTK apps under niri still read dconf) + install -d -m 0755 "$INCLUDE_DIR/etc/dconf/db/local.d" \ + "$INCLUDE_DIR/etc/dconf/profile" + cat > "$INCLUDE_DIR/etc/dconf/profile/user" <<'EOF' +user-db:user +system-db:local +EOF + cat > "$INCLUDE_DIR/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 + + # noctalia defaults in skel + install -d -m 0755 "$INCLUDE_DIR/etc/skel/.config/noctalia" + cat > "$INCLUDE_DIR/etc/skel/.config/noctalia/settings.json" <<'NOCEOF' +{ + "colorSchemes": { "darkMode": true, "predefinedScheme": "Gruvbox" }, + "wallpaper": { + "enabled": true, + "directory": "/usr/share/backgrounds/void-installer", + "fillMode": "crop" + } +} +NOCEOF +} + +# ═════════════════════════════════════════════════════════════════════════════ +# MAIN EXECUTION +# ═════════════════════════════════════════════════════════════════════════════ + +echo ">>> building ${BUILD_TYPE} ISO" +echo " profile : ${PROFILE} — ${PROFILE_DESC:-}" +echo " kernel : ${KERNEL_PKG}" +echo " desktop : ${DESKTOP} (${DISPLAY_SERVER})" + +# 1) clone + patch mklive +if [[ ! -d "$MKLIVE_DIR/.git" ]]; then + echo ">>> cloning void-mklive" + git clone --depth=1 --branch "$MKLIVE_REF" "$MKLIVE_REPO" "$MKLIVE_DIR" +fi +if compgen -G "$PATCH_DIR/*.patch" >/dev/null; then + echo ">>> resetting + applying iso/patches/" + ( cd "$MKLIVE_DIR" && git checkout -- . ) + for p in "$PATCH_DIR"/*.patch; do + echo " $(basename "$p")" + ( cd "$MKLIVE_DIR" && patch -p1 --silent < "$p" ) + done +fi + +# 2) xbps-static +XBPS_STATIC_DIR="$CACHE_DIR/xbps-static" +if [[ ! -x "$XBPS_STATIC_DIR/usr/bin/xbps-install.static" ]]; then + echo ">>> downloading xbps-static" + mkdir -p "$XBPS_STATIC_DIR" + curl -fsSL "https://repo-default.voidlinux.org/static/xbps-static-latest.x86_64-musl.tar.xz" \ + | tar xJf - -C "$XBPS_STATIC_DIR" +fi + +# 3) stage overlay +echo ">>> staging includes overlay at $INCLUDE_DIR" +chmod -R u+rwX "$INCLUDE_DIR" 2>/dev/null || true +rm -rf "$INCLUDE_DIR" +mkdir -p "$INCLUDE_DIR" + +# 3a) Installer scripts + profiles (always present in both ISO types) +install -d -m 0755 "$INCLUDE_DIR/usr/local/share/installer/lib" +install -m 0644 "$PROJECT_DIR/config/install.conf" \ + "$INCLUDE_DIR/usr/local/share/installer/install.conf" +install -m 0644 "$PROJECT_DIR/config/profiles/stable-cinnamon/packages.list" \ + "$INCLUDE_DIR/usr/local/share/installer/packages.list" +install -m 0755 "$PROJECT_DIR/installer/install.sh" \ + "$INCLUDE_DIR/usr/local/share/installer/install.sh" +for f in "$PROJECT_DIR"/installer/lib/*.sh; do + install -m 0755 "$f" "$INCLUDE_DIR/usr/local/share/installer/lib/$(basename "$f")" +done +install -d -m 0755 "$INCLUDE_DIR/usr/local/share/installer/profiles" +cp -a "$PROJECT_DIR/config/profiles/." "$INCLUDE_DIR/usr/local/share/installer/profiles/" + +install -d -m 0755 "$INCLUDE_DIR/usr/local/sbin" +ln -sf /usr/local/share/installer/install.sh "$INCLUDE_DIR/usr/local/sbin/install-void" + +# 3b) DEFAULT_PROFILE env marker — the embedded installer defaults to this profile +install -d -m 0755 "$INCLUDE_DIR/etc/profile.d" +cat > "$INCLUDE_DIR/etc/profile.d/00-void-installer.sh" < "$INCLUDE_DIR/etc/installer-secrets.env" +chmod 0600 "$INCLUDE_DIR/etc/installer-secrets.env" + +install -d -m 0700 "$INCLUDE_DIR/etc/installer-ssh" +cp -a "$SSH_SRC_DIR"/. "$INCLUDE_DIR/etc/installer-ssh/" +find "$INCLUDE_DIR/etc/installer-ssh" -type f -exec chmod 0600 {} + +find "$INCLUDE_DIR/etc/installer-ssh" -type d -exec chmod 0700 {} + + +# 3d) Customizations overlay (themes / icons / wallpapers / dotfiles / vscode) +OVERLAY="$INCLUDE_DIR/etc/installer-overlay" +install -d -m 0755 "$OVERLAY" "$OVERLAY/wallpapers" "$OVERLAY/themes" \ + "$OVERLAY/icons" "$OVERLAY/skel" "$OVERLAY/vscode-user" + +WP_SRC="${WALLPAPERS_SRC:-$HOME/Scaricati}" +shopt -s nullglob +for f in "$WP_SRC"/pxfuel*.jpg; do + install -m 0644 "$f" "$OVERLAY/wallpapers/$(basename "$f")" +done +shopt -u nullglob +echo " wallpapers: $(ls "$OVERLAY/wallpapers" 2>/dev/null | wc -l) file(s)" + +THEME_CACHE="$CACHE_DIR/gruvbox-gtk-theme" +THEME_BUILD="$CACHE_DIR/gruvbox-gtk-built" +if [[ ! -d "$THEME_CACHE/.git" ]]; then + git clone --depth=1 https://github.com/Fausto-Korpsvart/Gruvbox-GTK-Theme.git \ + "$THEME_CACHE" || echo " (warning: could not clone theme repo)" +fi +if [[ -x "$THEME_CACHE/themes/install.sh" && ! -d "$THEME_BUILD" ]]; then + echo " building gruvbox themes" + install -d -m 0755 "$THEME_BUILD" + "$DOCKER" run --rm \ + -v "$THEME_CACHE":/src -v "$THEME_BUILD":/out \ + debian:stable-slim sh -c ' + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq >/dev/null + apt-get install -y --no-install-recommends sassc bash >/dev/null + cd /src/themes && bash install.sh -d /out -t default -c dark -s standard + ' || echo " (warning: theme build failed)" +fi +if [[ -d "$THEME_BUILD" ]]; then + for d in "$THEME_BUILD"/Gruvbox-Dark*; do + [[ -d "$d" ]] && cp -a "$d" "$OVERLAY/themes/$(basename "$d")" + done + echo " themes: $(ls "$OVERLAY/themes" 2>/dev/null | wc -l) variant(s)" +fi + +ICON_CACHE="$CACHE_DIR/gruvbox-plus-icons" +if [[ ! -d "$ICON_CACHE/.git" ]]; then + git clone --depth=1 https://github.com/SylEleuth/gruvbox-plus-icon-pack.git \ + "$ICON_CACHE" || echo " (warning: could not clone icon repo)" +fi +if [[ -d "$ICON_CACHE/Gruvbox-Plus-Dark" ]]; then + cp -a "$ICON_CACHE/Gruvbox-Plus-Dark" "$OVERLAY/icons/Gruvbox-Plus-Dark" + echo " icons: Gruvbox-Plus-Dark" +fi + +BIBATA_SRC="${BIBATA_SRC:-/usr/share/icons/Bibata-Modern-Ice}" +if [[ -d "$BIBATA_SRC" ]]; then + cp -a "$BIBATA_SRC" "$OVERLAY/icons/Bibata-Modern-Ice" + echo " cursor: Bibata-Modern-Ice" +else + echo " (warning: $BIBATA_SRC missing — cursor theme skipped)" +fi + +DOTFILES_SRC="${DOTFILES_SRC:-$HOME}" +for f in .bashrc .bash_aliases .gitconfig; do + [[ -r "$DOTFILES_SRC/$f" ]] && install -m 0644 "$DOTFILES_SRC/$f" "$OVERLAY/skel/$f" +done + +VSCODE_SRC="${VSCODE_USER_SRC:-$HOME/.config/Code/User}" +if [[ -d "$VSCODE_SRC" ]]; then + for f in settings.json keybindings.json mcp.json tasks.json; do + [[ -r "$VSCODE_SRC/$f" ]] && install -m 0644 "$VSCODE_SRC/$f" "$OVERLAY/vscode-user/$f" + done + [[ -d "$VSCODE_SRC/snippets" ]] && cp -a "$VSCODE_SRC/snippets" "$OVERLAY/vscode-user/snippets" + [[ -d "$VSCODE_SRC/globalStorage" ]] && cp -a "$VSCODE_SRC/globalStorage" "$OVERLAY/vscode-user/globalStorage" +fi +command -v code >/dev/null 2>&1 && \ + code --list-extensions > "$OVERLAY/vscode-extensions.txt" 2>/dev/null || true + +CLAUDE_SRC="${CLAUDE_SRC:-$HOME/.claude}" +[[ -d "$CLAUDE_SRC" ]] && cp -a "$CLAUDE_SRC" "$OVERLAY/claude" +[[ -r "${HOME}/.claude.json" ]] && install -m 0600 "${HOME}/.claude.json" "$OVERLAY/claude.json" + +[[ -r "$PROJECT_DIR/installer/first-login.sh" ]] && \ + install -m 0755 "$PROJECT_DIR/installer/first-login.sh" "$OVERLAY/first-login.sh" + +install -d -m 0755 "$INCLUDE_DIR/usr/local/libexec" +{ + printf '# Nix user packages\n' + printf '%s\n' "${NIX_USER_PACKAGES[@]}" +} > "$INCLUDE_DIR/usr/local/libexec/nix-packages.list" + +# 3e) Type-specific + DE-specific overlay (uses functions defined above) +if [[ "$BUILD_TYPE" == "installer" ]]; then + _stage_installer_overlay +else + _stage_live_overlay +fi + +# 4) package list for ISO +if [[ "$BUILD_TYPE" == "installer" ]]; then + ISO_PKGS=$(grep -vE '^\s*(#|$)' "$PROJECT_DIR/config/packages.live.list" | tr '\n' ' ') +else + # Prefer new canonical name, fall back to old name for existing profiles + _PKG_LIST="$PROFILE_DIR/packages.live.list" + [[ -f "$_PKG_LIST" ]] || _PKG_LIST="$PROFILE_DIR/packages.live-desktop.list" + [[ -f "$_PKG_LIST" ]] || { echo "ERROR: no packages.live.list found in $PROFILE_DIR"; exit 1; } + ISO_PKGS=$(grep -vE '^\s*(#|$)' "$_PKG_LIST" | tr '\n' ' ') +fi + +# 5) output filename +TS="$(date -u +%Y%m%d)" +case "$BUILD_TYPE" in + installer) OUT_ISO="${OUTPUT_ISO:-$OUT_DIR/void-install-${PROFILE}-${TS}.iso}" ;; + live) OUT_ISO="${OUTPUT_ISO:-$OUT_DIR/void-live-${PROFILE}-${TS}.iso}" ;; +esac + +# 6) boot cmdline +_BOOT_BASE="nvidia-drm.modeset=1 rd.driver.blacklist=nouveau modprobe.blacklist=nouveau btusb.enable_autosuspend=0" +if [[ "$BUILD_TYPE" == "live" ]]; then + BOOT_CMDLINE="${BOOT_CMDLINE:-live.user=${LIVE_USER} console=tty0 console=ttyS0,115200 ${_BOOT_BASE}}" +else + BOOT_CMDLINE="${BOOT_CMDLINE:-${_BOOT_BASE}}" +fi + +# 7) ISO title +case "${BUILD_TYPE}-${DESKTOP}" in + installer-*) ISO_TITLE="Void Installer (${PROFILE})" ;; + live-cinnamon) ISO_TITLE="Void Live — Cinnamon (${KERNEL_PKG})" ;; + live-niri) ISO_TITLE="Void Live — niri / noctalia-shell (${KERNEL_PKG})" ;; + *) ISO_TITLE="Void Linux (${PROFILE})" ;; +esac + +# 8) build docker image +echo ">>> building docker image $DOCKER_IMAGE" +if "$DOCKER" buildx version >/dev/null 2>&1; then + "$DOCKER" build -t "$DOCKER_IMAGE" "$PROJECT_DIR/iso" +else + DOCKER_BUILDKIT=0 "$DOCKER" build -t "$DOCKER_IMAGE" "$PROJECT_DIR/iso" +fi + +# 9) noctalia repo (niri profiles only) +NOCTALIA_REPO="" +[[ "$DESKTOP" == "niri" ]] && \ + NOCTALIA_REPO="${NOCTALIA_REPO:-https://universalrepo.r1xelelo.workers.dev/void}" + +# 10) nix prebake packages (live ISOs only) +NIX_PREBAKE_PKGS="" +[[ "$BUILD_TYPE" == "live" ]] && NIX_PREBAKE_PKGS="${NIX_USER_PACKAGES[*]}" + +echo ">>> running mklive.sh inside docker — output: $OUT_ISO" +"$DOCKER" run --rm --privileged \ + -v "$PROJECT_DIR:/work:rw" \ + -v "$CACHE_DIR:/cache:rw" \ + -e ARCH="${ARCH:-x86_64}" \ + -e REPO_URL="${REPO_URL:-https://repo-default.voidlinux.org/current}" \ + -e KEYMAP="${KEYMAP:-us}" \ + -e LOCALE="${LOCALE:-en_US.UTF-8}" \ + -e ISO_PKGS="$ISO_PKGS" \ + -e ISO_TITLE="$ISO_TITLE" \ + -e OUT_ISO_REL="${OUT_ISO#$PROJECT_DIR/}" \ + -e INCLUDE_DIR_REL="${INCLUDE_DIR#$PROJECT_DIR/}" \ + -e BUILD_TYPE="$BUILD_TYPE" \ + -e DESKTOP="$DESKTOP" \ + -e KERNEL_PKG="${KERNEL_PKG:-linux}" \ + -e NOCTALIA_REPO="$NOCTALIA_REPO" \ + -e NIX_PACKAGES_PREBAKE="$NIX_PREBAKE_PKGS" \ + -e BOOT_CMDLINE="${BOOT_CMDLINE:-}" \ + -e HOST_UID="$(id -u)" \ + -e HOST_GID="$(id -g)" \ + "$DOCKER_IMAGE" \ + bash /work/iso/_inner-build-unified.sh + +echo +echo ">>> ISO built: $OUT_ISO" +sha256sum "$OUT_ISO" | tee "${OUT_ISO}.sha256" || true diff --git a/tests/run-qemu-test.sh b/tests/run-qemu-test.sh index ba6918d..504f56c 100755 --- a/tests/run-qemu-test.sh +++ b/tests/run-qemu-test.sh @@ -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