diff --git a/.gitignore b/.gitignore index 1c4d6ec..b2c9bb2 100644 --- a/.gitignore +++ b/.gitignore @@ -12,9 +12,7 @@ id_ovh* authorized_keys # ── Generated build staging (build-iso.sh populates this at build time) ─ -build/includes/ -build/live-includes/ -build/first-login.sh +build/* # ── Build artifacts ──────────────────────────────────────────────────── out/ diff --git a/config/install.conf b/config/install.conf index bff7936..f1d71d8 100644 --- a/config/install.conf +++ b/config/install.conf @@ -78,6 +78,7 @@ NIX_USER_PACKAGES=( "nixpkgs#google-chrome" "nixpkgs#mission-center" "nixpkgs#vscode" + "nixpkgs#fastfetch" ) # ---------- Cinnamon customization ---------- diff --git a/config/profiles/mainline-niri/packages.live-desktop.list b/config/profiles/mainline-niri/packages.live-desktop.list new file mode 100644 index 0000000..13972aa --- /dev/null +++ b/config/profiles/mainline-niri/packages.live-desktop.list @@ -0,0 +1,109 @@ +# Packages included in the LIVE desktop ISO squashfs for the mainline-niri profile. +# Lines beginning with '#' or empty are skipped. +# noctalia-shell comes from the third-party repo added by build-niri-live-iso.sh. + +# --- base / boot --- +base-system +linux-mainline +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 + +# --- audio (pipewire stack) --- +pipewire +wireplumber +alsa-pipewire +pavucontrol +alsa-utils +playerctl + +# --- Wayland session --- +mesa-dri +niri +xwayland-satellite +elogind +seatd +dbus + +# --- display manager --- +greetd +tuigreet + +# --- terminal + launcher --- +alacritty +fuzzel +foot + +# --- notification + background --- +mako +swaybg + +# --- bluetooth --- +bluez +blueman + +# --- polkit (auth dialogs) --- +polkit +polkit-gnome + +# --- noctalia-shell runtime deps --- +brightnessctl +ImageMagick +python3 +upower +wl-clipboard +network-manager-applet + +# --- XDG portals --- +xdg-desktop-portal +xdg-desktop-portal-gnome +xdg-utils +xdg-user-dirs + +# --- nix (for prebaked packages — spotify, discord, vscode, fastfetch, etc.) --- +nix + +# --- noctalia-shell (from noctalia third-party XBPS repo) --- +noctalia-shell + +# --- fonts --- +noto-fonts-ttf +noto-fonts-emoji diff --git a/docs/LIVE_ISO.md b/docs/LIVE_ISO.md index 292eb6d..aae3497 100644 --- a/docs/LIVE_ISO.md +++ b/docs/LIVE_ISO.md @@ -85,44 +85,74 @@ The `/etc/lightdm/.session` file (content: `cinnamon`) is read by the hook to se ## Nix Integration -### Daemon mode (not single-user) -The Void `nix` xbps package ships `nix-daemon` with a runit service at `/etc/sv/nix-daemon`. The daemon puts its socket at: -``` -/var/nix/daemon-socket/socket +### Prebake architecture (packages baked into squashfs) +Nix packages are **pre-installed at ISO build time** inside the Docker container and the entire `/nix` store is rsynced into the squashfs overlay. This means packages are available immediately on boot — no downloads, no tmpfs space pressure. + +**Why not install at first login?** The live system mounts squashfs + tmpfs overlay. Installing ~4 GB of nix packages at runtime fills the tmpfs overlay and causes out-of-space failures. Baking them into squashfs sidesteps this completely. + +### Build-time nix install (inside Docker, single-user) +Docker runs as root. Nix is installed in single-user mode (no daemon, no nixbld group): +```sh +mkdir -m 0755 -p /nix +export NIX_CONFIG="build-users-group = " # suppress nixbld group requirement +curl -L https://nixos.org/nix/install | sh -s -- --no-daemon +source /root/.nix-profile/etc/profile.d/nix.sh +export PATH="/root/.nix-profile/bin:$PATH" +NIXPKGS_ALLOW_UNFREE=1 nix profile add \ + --extra-experimental-features "nix-command flakes" --impure \ + nixpkgs#spotify nixpkgs#discord ... ``` -We use daemon mode (not single-user) because `/nix/store` stays root-owned. The live user is granted trust via `nix.conf`: +The full `/nix` directory is then staged into the squashfs overlay: +```sh +rsync -a /nix/ "$INCLUDE_DIR/nix/" +``` + +### Nix prebake cache +To avoid re-downloading packages on every build, the nix store is cached at: +``` +cache/nix-prebake// +``` +If the cache exists and the package list md5 matches, the build restores from cache instead of re-running `nix profile add`. Cache is ~5 GB. Subsequent builds with an unchanged package list complete the nix step in ~1 minute instead of ~20 minutes. + +### Current packages (NIX_USER_PACKAGES in build-live-iso.sh) +- `nixpkgs#google-chrome` — replaces chromium (removed from xbps packages) +- `nixpkgs#spotify` +- `nixpkgs#discord` +- `nixpkgs#localsend` +- `nixpkgs#mission-center` +- `nixpkgs#vscode` + +### XDG / PATH setup for live user +For Cinnamon to find nix `.desktop` files and for terminals to find nix binaries: +- `/etc/environment`: `XDG_DATA_DIRS=/home/live/.nix-profile/share:/usr/local/share:/usr/share` +- `/etc/profile.d/nix-prebaked.sh`: adds nix profile to `PATH` for terminal sessions +- `/etc/skel/.nix-profile` → symlink to the pre-baked store profile, copied to `/home/live/` when the live user is created by the dracut hook + +### Live system nix-daemon (daemon mode) +On the **booted live system**, the Void `nix` xbps package provides `nix-daemon` as a runit service. `/nix/store` stays root-owned; the live user is granted trust via `nix.conf`: ``` experimental-features = nix-command flakes sandbox = false auto-optimise-store = true trusted-users = root live ``` +The daemon socket is at `/var/nix/daemon-socket/socket` (Void's path, not the upstream default `/nix/var/nix/daemon-socket/socket`). -`sandbox = false` is required because the live system has no `nixbld` users and no user namespaces in the dracut initramfs environment. - -### Package list -`/usr/local/libexec/nix-packages.list` is written at ISO build time from `NIX_USER_PACKAGES` in `config/install.conf`. At first login, `first-login.sh` reads this file and runs `nix profile install --impure` with `NIXPKGS_ALLOW_UNFREE=1`. - -Current packages: -- `nixpkgs#google-chrome` -- `nixpkgs#spotify` -- `nixpkgs#discord` -- `nixpkgs#localsend` -- `nixpkgs#mission-center` +`sandbox = false` is required — no `nixbld` users exist in the dracut initramfs environment. ### postinstall.sh socket path (installed system) -In the **installed system** (not live), `installer/lib/postinstall.sh` polls for the nix-daemon socket. The correct path is: +In the **installed system** (not live), `installer/lib/postinstall.sh` polls for the nix-daemon socket at: ``` /var/nix/daemon-socket/socket ``` -Not `/nix/var/nix/daemon-socket/socket` (upstream Nix default) — Void's package uses `/var/nix/`. +Not `/nix/var/nix/daemon-socket/socket` — Void's package uses `/var/nix/`. --- ## dconf / Theme -The Gruvbox-Dark GTK theme and Cinnamon dconf settings are pre-applied via a system-db. The dconf binary database must be compiled at **ISO build time**, not at runtime. +Cinnamon settings (theme, keyboard layout, dark mode, etc.) are pre-applied via a dconf system-db. The binary database is compiled at **ISO build time** inside the Docker container. ### Build-time compilation `iso/_inner-build-live.sh` runs inside the Debian Docker container. The Dockerfile installs `dconf-cli` for this step. The correct Debian `dconf-cli` API is: @@ -143,20 +173,47 @@ system-db:local ``` Without this file, the compiled system-db is ignored and Cinnamon shows a black wallpaper with default GTK theme. +### System DB keyfile (`/etc/dconf/db/local.d/00-cinnamon`) +Built by `iso/build-live-iso.sh` from config values. Relevant excerpts: +```ini +[org/gnome/desktop/input-sources] +sources=[('xkb', 'ch+fr_nodeadkeys')] + +[org/gnome/desktop/interface] +color-scheme='prefer-dark' +``` +The `KEYMAP` variable comes from `config/install.conf` as `ch-fr_nodeadkeys` (vconsole dash format). The system DB uses XKB plus format. The substitution `${KEYMAP//-/+}` handles this conversion at build time. + +### dconf lock file (critical for keyboard) +A lock file at `/etc/dconf/db/local.d/locks/keyboard` lists: +``` +/org/gnome/desktop/input-sources/sources +``` +This makes the keyboard setting **non-writable from the user session** — `gsettings set org.gnome.desktop.input-sources sources ...` silently does nothing when this lock is in place. The correct value must be set in the system DB itself (see above). Do not attempt to override the keyboard via `gsettings` from `apply-live-settings.sh` or any autostart script. + +### Keyboard format: vconsole (dash) vs XKB (plus) +- mklive.sh `-k` flag accepts vconsole format: `ch-fr_nodeadkeys` (dash-separated) +- XKB / gsettings / dconf uses plus format: `ch+fr_nodeadkeys` +- Bash substitution: `${KEYMAP//-/+}` converts vconsole → XKB +- `KEYMAP` is defined in `config/install.conf` in vconsole (dash) format + --- -## First-Login Setup (`installer/first-login.sh`) +## First-Login Setup (`apply-live-settings.sh`) -Runs once via XDG autostart (`~/.config/autostart/void-live-first-login.desktop`) when Cinnamon first loads. Installs: +A lightweight XDG autostart script runs once when Cinnamon first loads and applies theme/UX settings via `gsettings`. It does **not** install packages (packages are pre-baked into squashfs). -1. **Claude Code** — official installer from `https://claude.ai/install.sh` -2. **Nix user packages** — from `/usr/local/libexec/nix-packages.list` -3. **NVM + Node LTS** -4. **VS Code extensions** — from `/etc/installer-vscode-extensions.txt` +**Location in ISO:** `/usr/local/libexec/apply-live-settings.sh` +**Autostart:** `/etc/xdg/autostart/void-live-settings.desktop` (only in Cinnamon: `OnlyShowIn=X-Cinnamon`) +**Idempotency guard:** creates `~/.void-live-settings-done` on success -Idempotent: creates `~/.first-login-done` on success. Logs to `~/.first-login.log`. +Settings applied: +- GTK/icon/cursor theme (Gruvbox-Dark) +- Cinnamon shell theme +- Wallpaper +- Default terminal (alacritty) -The script does NOT use `set -u` because `nvm.sh` references unbound variables. +The script waits for `DBUS_SESSION_BUS_ADDRESS` to be set before calling `gsettings`. It does **not** set keyboard layout — that is locked in the dconf system DB (see dconf section above). --- @@ -164,19 +221,45 @@ The script does NOT use `set -u` because `nvm.sh` references unbound variables. ``` iso/build-live-iso.sh (host — stages overlay, builds Docker image if needed) - └─ Docker: void-installer-builder:latest + └─ Docker: void-installer-builder:latest (debian:stable-slim) └─ iso/_inner-build-live.sh - ├─ dconf compile (pre-bakes system-db) - └─ void-mklive/mklive.sh -a x86_64 -r -I ... - └─ squashfs + GRUB + ISO 9660 + ├─ nix prebake: install packages into /nix, rsync to $INCLUDE_DIR/nix/ + │ └─ cache/nix-prebake// used if package list unchanged + ├─ dconf compile (compiles system-db binary from keyfile) + ├─ void-mklive/mklive.sh -a x86_64 -r -I ... + │ └─ squashfs (xz) + GRUB + ISO 9660 + └─ chown -R $HOST_UID:$HOST_GID $INCLUDE_DIR (fix Docker root ownership) ``` -Output: `out/void-live-stable.iso` (~2.9 GB) +Output: `out/void-live-stable.iso` (~4.8 GB, xz-compressed squashfs ~22 GB uncompressed) + +### Docker UID/GID ownership fix +Docker runs as root. Without remediation, files created inside the container (especially the ~5 GB nix store) are owned by `root` on the host, causing `rm -rf build/live-includes` to fail with `Permission denied` on the next build. + +**Fix in `_inner-build-live.sh`** (end of script): +```sh +# Fix ownership so host user can clean up on next build +if [[ -n "${HOST_UID:-}" && "$HOST_UID" != "0" ]]; then + chmod -R u+w "$INCLUDE_DIR" 2>/dev/null || true + chown -R "${HOST_UID}:${HOST_GID}" "$INCLUDE_DIR" 2>/dev/null || true +fi +``` +`HOST_UID` and `HOST_GID` are passed via `docker run -e HOST_UID=$(id -u) -e HOST_GID=$(id -g)`. + +**Belt-and-suspenders guard in `build-live-iso.sh`** (before `rm -rf $INCLUDE_DIR`): +```sh +chmod -R u+w "$INCLUDE_DIR/nix" 2>/dev/null || sudo rm -rf "$INCLUDE_DIR/nix" +``` + +**Emergency manual cleanup:** `sudo rm -rf build/live-includes/nix` + +### Dockerfile dependencies +`iso/Dockerfile` (based on `debian:stable-slim`) installs: `bash git curl ca-certificates xz-utils tar patch python3 mtools xorriso squashfs-tools dosfstools e2fsprogs kmod dconf-cli rsync`. The `rsync` package is required for nix store staging. ### Build artifacts that must NOT be committed -- `build/live-includes/` — generated staging tree (hundreds of binary assets) +- `build/live-includes/` — generated staging tree (hundreds of binary assets, nix store) - `out/` — ISO output -- `cache/` — cloned void-mklive, xbps package cache +- `cache/` — cloned void-mklive, xbps/nix package cache --- @@ -200,12 +283,50 @@ Use `nix profile add` instead. `nix profile install` is an alias that emits a wa **Cause:** QEMU's user-mode DNS proxy may not forward queries correctly depending on the host network configuration. **Workaround for QEMU testing:** `echo nameserver 8.8.8.8 > /etc/resolv.conf`. This is not needed on real hardware. +### Docker root-owned files break next build +**Symptom:** `rm -rf build/live-includes` or `rm -rf build/live-includes/nix` fails with `Permission denied` at the start of a rebuild. +**Cause:** Docker runs as root. The ~5 GB nix store rsynced into `build/live-includes/nix/` is owned by `root:root` on the host. +**Fix:** `_inner-build-live.sh` now `chown -R $HOST_UID:$HOST_GID $INCLUDE_DIR` at the end of each Docker run. `HOST_UID`/`HOST_GID` are passed as env vars. See Build Pipeline section. +**Emergency cleanup:** `sudo rm -rf build/live-includes/nix` + +### dconf lock file silently blocks `gsettings set` +**Symptom:** `gsettings set org.gnome.desktop.input-sources sources "[('xkb', 'ch+fr_nodeadkeys')]"` runs without error but the keyboard layout is not applied. +**Cause:** `/etc/dconf/db/local.d/locks/keyboard` locks the `input-sources` key. Any `gsettings set` targeting a locked key is silently ignored in the user session. +**Fix:** Set the correct value in the system dconf DB keyfile at ISO build time. Do not attempt to set it from an autostart script. + +### Keyboard format mismatch (vconsole dash vs XKB plus) +**Symptom:** Keyboard layout reverts to US QWERTY even though `KEYMAP=ch-fr_nodeadkeys` is set. +**Cause:** mklive.sh accepts the vconsole format (`ch-fr_nodeadkeys`, dash-separated). XKB / dconf uses plus format (`ch+fr_nodeadkeys`). Passing the vconsole string directly to the dconf system DB or to `gsettings` sets an unknown layout that falls back to US. +**Fix:** In `build-live-iso.sh`, use `${KEYMAP//-/+}` when writing the dconf keyfile: +```ini +[org/gnome/desktop/input-sources] +sources=[('xkb', 'ch+fr_nodeadkeys')] # generated as: ${KEYMAP//-/+} +``` + --- +## QEMU Testing + +### Quick launch +```bash +bash tests/launch-live-qemu.sh +# or via Makefile: +make live-qemu +``` + +### What `launch-live-qemu.sh` does +- RAM: 12288 MB, 4 CPUs, KVM acceleration +- Device: `virtio-vga` with `display gtk,gl=off` (no hardware GL) +- Searches `out/void-live-stable*.iso` for the ISO +- Serial console socket: `out/live-serial.sock` +- Monitor socket: `out/qemu-monitor.sock` +- Credentials: `live`/`voidlinux` (desktop), `root`/`voidlinux` (TTY) + +### Manual launch (if needed) ```bash cp /usr/share/OVMF/OVMF_VARS.fd out/OVMF_VARS.live.fd qemu-system-x86_64 -name void-live-test -machine q35,accel=kvm:tcg -cpu max \ - -m 4096 -smp 4 \ + -m 12288 -smp 4 \ -drive "if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE.fd" \ -drive "if=pflash,format=raw,file=out/OVMF_VARS.live.fd" \ -cdrom out/void-live-stable.iso -boot order=d,menu=off \ @@ -215,12 +336,20 @@ qemu-system-x86_64 -name void-live-test -machine q35,accel=kvm:tcg -cpu max \ -device virtio-vga -display gtk,gl=off & ``` -Serial console access (root shell for diagnostics): +### Serial console access (Python) ```python -import socket, time +import socket s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect('out/live-serial.sock') # send commands, read output ``` -GPU in QEMU: `virtio-vga` is detected as virtual → `modesetting + LIBGL_ALWAYS_SOFTWARE=1`. +### GPU in QEMU +`virtio-vga` is detected as a virtual GPU by `live-setup.sh` → writes `modesetting + AccelMethod none` xorg conf, sets `LIBGL_ALWAYS_SOFTWARE=1` in `/etc/profile.d/live-env.sh`. + +### Verifying keyboard layout (in live session) +```bash +DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \ + gsettings get org.gnome.desktop.input-sources sources +# expected: [('xkb', 'ch+fr_nodeadkeys')] +``` diff --git a/iso/Dockerfile b/iso/Dockerfile index bcc3c3e..7b22315 100644 --- a/iso/Dockerfile +++ b/iso/Dockerfile @@ -10,7 +10,7 @@ ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y --no-install-recommends \ bash git curl ca-certificates xz-utils tar patch python3 \ mtools xorriso squashfs-tools dosfstools e2fsprogs \ - kmod dconf-cli rsync \ + kmod dconf-cli rsync openssl \ && rm -rf /var/lib/apt/lists/* # xbps-static is downloaded into /cache by the host script and added to PATH diff --git a/iso/_inner-build-live.sh b/iso/_inner-build-live.sh index 2dc04f0..05558c9 100755 --- a/iso/_inner-build-live.sh +++ b/iso/_inner-build-live.sh @@ -27,14 +27,35 @@ command -v xbps-install.static >/dev/null \ mkdir -p "$(dirname "$OUT_ISO")" -# Compile dconf system-db inside the include dir so it ships compiled. -# Debian's dconf-cli provides 'dconf compile '. -if command -v dconf >/dev/null 2>&1 && [[ -d "$INCLUDE_DIR/etc/dconf/db/local.d" ]]; then - dconf compile "$INCLUDE_DIR/etc/dconf/db/local" \ - "$INCLUDE_DIR/etc/dconf/db/local.d" 2>/dev/null \ - && echo "dconf: compiled system-db/local" \ - || echo "dconf: compile failed (non-fatal)" +# Compile dconf system-db using Void's own dconf binary (inside the Void +# rootfs chroot via mklive's -x postsetup hook). This guarantees the GVDB +# binary is produced by the exact same dconf/glib version that runs on the +# live system — no cross-distro format mismatch possible. +# (We do NOT pre-compile with Debian's dconf here; that caused silent failures +# when the GVDB format differed between the host and Void's glib.) +_DCONF_POSTSETUP="$(mktemp -p "$MKLIVE_DIR" postsetup-dconf.XXXXX.sh)" +cat > "$_DCONF_POSTSETUP" <<'PSEOF' +#!/bin/bash +# Postsetup script: compile dconf system-db AND user skel db with Void's own binary. +ROOTFS="$1" +if [[ -x "$ROOTFS/usr/bin/dconf" ]] && [[ -d "$ROOTFS/etc/dconf/db/local.d" ]]; then + # 1. Compile system-db (read by all users via /etc/dconf/profile/user) + 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)" + # 2. Compile a USER dconf db directly into /etc/skel/.config/dconf/user. + # dracut's adduser.sh copies skel → /home/live on first boot, so the live + # user starts with the keyboard in their OWN user db — no dependency on + # dconf profile/system-db loading order. + 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" @@ -132,6 +153,13 @@ trap _cleanup_mklive_builds EXIT -p "$ISO_PKGS" \ -I "$INCLUDE_DIR" \ -C "${BOOT_CMDLINE:-}" \ + -x "$_DCONF_POSTSETUP" \ -o "$OUT_ISO" -chown "$(stat -c '%u:%g' "$PROJECT_DIR")" "$OUT_ISO" "${OUT_ISO}".* 2>/dev/null || true +# ownership of OUT_ISO is fixed unconditionally below with the include dir + +# Fix ownership of include dir so the host user can clean up without sudo. +# u+rwX: sets read+write on all, execute only on directories (capital X). +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/_inner-build-niri-live.sh b/iso/_inner-build-niri-live.sh new file mode 100755 index 0000000..f4a4257 --- /dev/null +++ b/iso/_inner-build-niri-live.sh @@ -0,0 +1,228 @@ +#!/bin/bash +# Runs INSIDE the docker container (as root). Invoked by iso/build-niri-live-iso.sh. +# Niri/Wayland variant: nix prebake (shared cache with Cinnamon), adds noctalia repo. +# Expects the project bind-mounted at /work and the cache at /cache. +# +# Required env (set by build-niri-live-iso.sh): +# ARCH, REPO_URL, KEYMAP, LOCALE, ISO_PKGS, ISO_TITLE, OUT_ISO_REL, +# INCLUDE_DIR_REL, NIX_PACKAGES_PREBAKE (optional) + +set -Eeuo pipefail + +: "${ARCH:?}"; : "${REPO_URL:?}"; : "${KEYMAP:?}"; : "${LOCALE:?}" +: "${ISO_PKGS:?}"; : "${ISO_TITLE:?}"; : "${OUT_ISO_REL:?}" +: "${INCLUDE_DIR_REL:?}" + +CACHE_DIR=/cache +PROJECT_DIR=/work +MKLIVE_DIR="$CACHE_DIR/void-mklive-niri" # separate clone — avoids race with Cinnamon parallel build +INCLUDE_DIR="$PROJECT_DIR/$INCLUDE_DIR_REL" +OUT_ISO="$PROJECT_DIR/$OUT_ISO_REL" + +# Third-party Void repo that ships noctalia-shell + noctalia-qs. +NOCTALIA_REPO="${NOCTALIA_REPO:-https://universalrepo.r1xelelo.workers.dev/void}" + +export PATH="$CACHE_DIR/xbps-static/usr/bin:$PATH" + +[[ -d "$MKLIVE_DIR" ]] || { echo "ERROR: $MKLIVE_DIR missing"; exit 1; } +[[ -d "$INCLUDE_DIR" ]] || { echo "ERROR: $INCLUDE_DIR missing"; exit 1; } +command -v xbps-install.static >/dev/null \ + || { echo "ERROR: xbps-install.static not on PATH"; exit 1; } + +mkdir -p "$(dirname "$OUT_ISO")" + +cd "$MKLIVE_DIR" + +# ── Pre-bake nix packages ──────────────────────────────────────────────── +# Shared with the Cinnamon build — same cache dir, same store. +if [[ -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 + + _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" + fi +fi +# ── end nix prebake ────────────────────────────────────────────────────── + +# ── Noctalia: build a locally SIGNED XBPS repo ─────────────────────────── +# noctalia-qs has a broken .sig2 on the CDN (RSA signature not valid). +# noctalia-shell's CDN key import also fails intermittently (EAGAIN). +# Solution: download both .xbps archives directly (no sig check), create a +# LOCAL SIGNED repo with a fresh keypair, register our public key in +# mklive/keys/ so copy_void_keys pre-trusts it in the rootfs. +# Our local signed repo gets HIGHEST priority (-r last = prepended first), +# so xbps resolves and verifies both packages against our trusted key. +echo ">>> building local signed noctalia XBPS repo (CDN .sig2 workaround)" +_NOC_LOCAL="/tmp/noctalia-local" +_NOC_HOME="/tmp/noc-sign-home" +_ARCH="${ARCH:-x86_64}" +mkdir -p "$_NOC_LOCAL" "$_NOC_HOME" +export HOME="$_NOC_HOME" + +# Discover exact package versions from CDN repodata (try multiple User-Agents). +# Falls back to versions confirmed by previous build errors. +_NOC_VERS=$(python3 - <<'PYEOF' 2>/dev/null +import urllib.request, plistlib, tarfile, io, sys, 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 pkgver, meta in idx.items(): + if isinstance(meta, dict) and meta.get("pkgname") in want: + found[meta["pkgname"]] = pkgver + if len(found) >= len(want): + break + except Exception: + pass +# Fallback to versions confirmed by last build errors +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 +) + +# Download both .xbps archives directly (curl never checks .sig2) +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 + +# Build index +xbps-rindex.static -a "$_NOC_LOCAL"/*.xbps + +# Generate RSA keypair. xbps-rindex requires --privkey passed explicitly. +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" + +# Sign repodata then each package (separate calls — xbps-rindex restriction). +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 + +# Register public key in mklive/keys/ so copy_void_keys installs it in the rootfs. +# xbps fingerprint = MD5 of DER-encoded public key, formatted as aa:bb:cc:... +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/:$//') +# The plist field = base64 of the PEM public key (headers included) +_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 +# ── end noctalia local repo ─────────────────────────────────────────────── + +_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 + +# mklive prepends -r args: LAST -r = HIGHEST priority. +# Our local signed repo is last so xbps resolves noctalia-* from it. +./mklive.sh \ + -a "$ARCH" \ + -r "$REPO_URL" \ + -r "${REPO_URL%/current}/current/nonfree" \ + -r "$NOCTALIA_REPO" \ + -r "$_NOC_LOCAL" \ + -c "$CACHE_DIR/xbps-niri-pkgs" \ + -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" + +# Fix ownership so the host user can clean up without sudo. +# u+rwX: sets read+write on all, execute only on directories (capital X). +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-live-iso.sh b/iso/build-live-iso.sh index bbb29c3..7d19e05 100755 --- a/iso/build-live-iso.sh +++ b/iso/build-live-iso.sh @@ -66,6 +66,8 @@ fi # 3) build includes overlay echo ">>> staging live includes overlay at $INCLUDE_DIR" +# The nix store (staged by Docker/root) uses 444/555 permissions — chmod first. +chmod -R u+rwX "$INCLUDE_DIR" 2>/dev/null || true rm -rf "$INCLUDE_DIR" mkdir -p "$INCLUDE_DIR" @@ -408,7 +410,10 @@ picture-uri='file:///usr/share/backgrounds/void-installer/${WALLPAPER_FILE}' picture-options='zoom' [org/gnome/desktop/input-sources] -sources=[('xkb', 'ch+fr')] +sources=[('xkb', '${KEYMAP//-/+}')] + +[org/gnome/desktop/interface] +color-scheme='prefer-dark' [org/cinnamon/desktop/default-applications/terminal] exec='alacritty' @@ -530,8 +535,9 @@ gsettings set org.cinnamon.desktop.default-applications.terminal exec-arg '-e' gsettings set org.gnome.desktop.default-applications.terminal exec '${DEFAULT_TERMINAL:-alacritty}' gsettings set org.gnome.desktop.default-applications.terminal exec-arg '-e' -# Keyboard layout (Swiss French) -gsettings set org.gnome.desktop.input-sources sources "[('xkb', '${KEYMAP:-ch+fr_nodeadkeys}')]" +# Keyboard layout — set explicitly via gsettings (belt-and-suspenders alongside +# the user dconf db pre-baked in /etc/skel at build time). +gsettings set org.gnome.desktop.input-sources sources "[('xkb', '${KEYMAP//-/+}')]" touch "\$DONE" EOF diff --git a/iso/build-niri-live-iso.sh b/iso/build-niri-live-iso.sh new file mode 100755 index 0000000..eef314e --- /dev/null +++ b/iso/build-niri-live-iso.sh @@ -0,0 +1,551 @@ +#!/bin/bash +# Build a niri/Wayland LIVE desktop ISO (mainline-niri profile). +# +# Boots directly into a niri session with noctalia-shell as user 'live' +# (no password). agetty autologin on tty1 → .bash_profile → dbus-run-session niri. +# All themes, wallpapers, and the +# void-installer are pre-baked into the squashfs. +# +# Requires (host): bash, git, curl, docker, and Bibata-Modern-Ice cursor +# installed at /usr/share/icons/Bibata-Modern-Ice. +# +# Usage: +# iso/build-niri-live-iso.sh +# OUTPUT_ISO=/path/to/output.iso iso/build-niri-live-iso.sh + +set -Eeuo pipefail + +PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +CACHE_DIR="${CACHE_DIR:-$PROJECT_DIR/cache}" +OUT_DIR="${OUT_DIR:-$PROJECT_DIR/out}" +BUILD_DIR="${BUILD_DIR:-$PROJECT_DIR/build}" +INCLUDE_DIR="$BUILD_DIR/niri-live-includes" +MKLIVE_DIR="$CACHE_DIR/void-mklive-niri" # separate clone — avoids race with Cinnamon parallel build +MKLIVE_REPO="${MKLIVE_REPO:-https://github.com/void-linux/void-mklive.git}" +MKLIVE_REF="${MKLIVE_REF:-master}" +PATCH_DIR="$PROJECT_DIR/iso/patches" +DOCKER_IMAGE="${DOCKER_IMAGE:-void-installer-builder:latest}" +DOCKER="${DOCKER:-docker}" +LIVE_USER="${LIVE_USER:-live}" + +# shellcheck disable=SC1091 +source "$PROJECT_DIR/config/install.conf" + +# Load niri profile settings (KERNEL_PKG, GTK_THEME, CURSOR_THEME, etc.) +# shellcheck disable=SC1091 +source "$PROJECT_DIR/config/profiles/mainline-niri/profile.conf" + +command -v "$DOCKER" >/dev/null \ + || { echo "ERROR: '$DOCKER' not in PATH"; exit 1; } +"$DOCKER" info >/dev/null 2>&1 \ + || { echo "ERROR: '$DOCKER' daemon unreachable"; exit 1; } + +mkdir -p "$CACHE_DIR" "$OUT_DIR" "$BUILD_DIR" + +# 1) clone + patch mklive (shared with Cinnamon build) +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 (shared cache) +XBPS_STATIC_DIR="$CACHE_DIR/xbps-static" +if [[ ! -x "$XBPS_STATIC_DIR/usr/bin/xbps-install.static" ]]; then + echo ">>> downloading xbps-static" + mkdir -p "$XBPS_STATIC_DIR" + curl -fsSL "https://repo-default.voidlinux.org/static/xbps-static-latest.x86_64-musl.tar.xz" \ + | tar xJf - -C "$XBPS_STATIC_DIR" +fi + +# 3) build includes overlay (separate dir from Cinnamon build) +echo ">>> staging niri live includes overlay at $INCLUDE_DIR" +# The nix store (staged by Docker/root) uses 444/555 permissions — chmod first. +chmod -R u+rwX "$INCLUDE_DIR" 2>/dev/null || true +rm -rf "$INCLUDE_DIR" +mkdir -p "$INCLUDE_DIR" + +install -d -m 0755 "$INCLUDE_DIR/etc" + +# ── 3a) greetd config (fallback TUI on tty2) + agetty autologin on tty1 ─ +# greetd's initial_session is not used: its PAM session setup fails silently +# in the live environment (pam_elogind ENOSYS, missing D-Bus session bus). +# Instead we autologin via agetty on tty1 and launch niri from .bash_profile. +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 conf so agetty-tty1 (mklive's default sv) +# automatically logs in the live user on tty1 without racing with a custom sv. +install -d -m 0755 "$INCLUDE_DIR/etc/sv/agetty-tty1" +cat > "$INCLUDE_DIR/etc/sv/agetty-tty1/conf" < "$INCLUDE_DIR/etc/xbps.d/10-noctalia.conf" <<'EOF' +repository=https://universalrepo.r1xelelo.workers.dev/void +EOF + +# ── 3c) live-setup.sh (runs from runit/2 before any service starts) ──── +install -d -m 0755 "$INCLUDE_DIR/etc/runit" +cat > "$INCLUDE_DIR/etc/runit/live-setup.sh" <<'SV_EOF' +#!/bin/sh +# Niri live session setup. Runs from /etc/runit/2 before services start. + +LIVE_USER="${USERNAME:-live}" +[ -f /etc/default/live.conf ] && . /etc/default/live.conf +LIVE_USER="${LIVE_USER:-live}" + +# Extra groups (dracut only adds audio,video,wheel) +for g in plugdev input network video audio _seatd; 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 + +# nsswitch: remove mdns (library absent on Void; causes DNS lookup hangs) +if [ -f /etc/nsswitch.conf ]; then + sed -i '/^hosts:/s/mdns[^ ]* *//g' /etc/nsswitch.conf +fi + +# Fallback DNS (QEMU's 10.0.2.3 is unreliable; NM will overwrite once DHCP settles) +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 + +# Ensure XDG_RUNTIME_DIR exists for the live user (greetd + elogind handle +# this normally, but set it here as a safety net). +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. + +echo "niri live-setup: done (user=$LIVE_USER)" +SV_EOF +chmod 0755 "$INCLUDE_DIR/etc/runit/live-setup.sh" + +# runit/2: standard Void runit stage 2 — runs live-setup then hands off to runsvdir +cat > "$INCLUDE_DIR/etc/runit/2" <<'SV_EOF' +#!/bin/sh +PATH=/usr/bin:/usr/sbin + +# Live session setup: groups, sudo, DNS +[ -x /etc/runit/live-setup.sh ] && /etc/runit/live-setup.sh + +# Select runlevel from cmdline (default: default) +runlevel=default +for arg in $(cat /proc/cmdline); do + if [ -d /etc/runit/runsvdir/"$arg" ]; then + runlevel="$arg" + fi +done + +[ -x /etc/rc.local ] && /etc/rc.local + +runsvchdir "${runlevel}" +mkdir -p /run/runit/runsvdir +ln -sf /etc/runit/runsvdir/current /run/runit/runsvdir/current + +exec env - PATH=$PATH \ + runsvdir -P /run/runit/runsvdir/current \ + 'log: ...........................................................................................................................................................................................................................................................................................................................................................................................................' +SV_EOF +chmod 0755 "$INCLUDE_DIR/etc/runit/2" + +# Enable services for the niri live session +install -d -m 0755 "$INCLUDE_DIR/etc/runit/runsvdir/default" + +# Custom elogind sv: uses correct binary path and waits for dbus socket before +# starting (prevents "elogind is already running" spam from rapid runit restarts). +install -d -m 0755 "$INCLUDE_DIR/etc/sv/elogind" +cat > "$INCLUDE_DIR/etc/sv/elogind/run" <<'EOF' +#!/bin/sh +exec 2>&1 +# 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 +cat > "$INCLUDE_DIR/etc/sv/elogind/finish" <<'EOF' +#!/bin/sh +sleep 3 +EOF +chmod 0755 "$INCLUDE_DIR/etc/sv/elogind/finish" + +for svc in dbus elogind NetworkManager sshd; do + ln -sf "/etc/sv/$svc" "$INCLUDE_DIR/etc/runit/runsvdir/default/$svc" +done + +# ── 3d) Wayland environment ───────────────────────────────────────────── +install -d -m 0755 "$INCLUDE_DIR/etc/profile.d" +cat > "$INCLUDE_DIR/etc/profile.d/wayland.sh" <<'EOF' +# Wayland defaults (mainline-niri live session) +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 +# Use elogind's logind backend — works correctly on Void/runit via audit session IDs +export LIBSEAT_BACKEND=logind +EOF +chmod 0644 "$INCLUDE_DIR/etc/profile.d/wayland.sh" + +# Nix profile.d: adds ~/.nix-profile/bin to PATH for interactive shells +cat > "$INCLUDE_DIR/etc/profile.d/nix-prebaked.sh" <<'EOF' +# Pre-baked nix profile — expose nix package binaries +if [[ -d "${HOME:-}/.nix-profile/bin" ]]; then + case ":$PATH:" in + *":$HOME/.nix-profile/bin:"*) ;; + *) export PATH="$HOME/.nix-profile/bin:$PATH" ;; + esac +fi +EOF +chmod 0644 "$INCLUDE_DIR/etc/profile.d/nix-prebaked.sh" + +# Nix daemon config (trusted live user so nix commands work without root) +install -d -m 0755 "$INCLUDE_DIR/etc/nix" +cat > "$INCLUDE_DIR/etc/nix/nix.conf" </dev/null | head -1) && [ -n "$_HOST_PUBKEY" ]; then + install -d -m 0700 "$INCLUDE_DIR/etc/skel/.ssh" + echo "$_HOST_PUBKEY" > "$INCLUDE_DIR/etc/skel/.ssh/authorized_keys" + chmod 0600 "$INCLUDE_DIR/etc/skel/.ssh/authorized_keys" +fi +KEYMAP_XKB_LAYOUT="${KEYMAP%%-*}" # ch-fr_nodeadkeys → ch +KEYMAP_XKB_VARIANT="${KEYMAP#*-}" # ch-fr_nodeadkeys → fr_nodeadkeys +KEYMAP_XKB_VARIANT="${KEYMAP_XKB_VARIANT//_nodeadkeys/}" # strip trailing _nodeadkeys + +install -d -m 0755 "$INCLUDE_DIR/etc/skel/.config/niri" +cat > "$INCLUDE_DIR/etc/skel/.config/niri/config.kdl" <>> staging niri customizations overlay" +OVERLAY="$INCLUDE_DIR/etc/installer-overlay" +install -d -m 0755 "$OVERLAY" "$OVERLAY/wallpapers" \ + "$OVERLAY/themes" "$OVERLAY/icons" + +# Wallpapers +WP_SRC="${WALLPAPERS_SRC:-$HOME/Scaricati}" +shopt -s nullglob +for f in "$WP_SRC"/pxfuel*.jpg; do + install -m 0644 "$f" "$OVERLAY/wallpapers/$(basename "$f")" +done +shopt -u nullglob +echo " wallpapers: $(ls "$OVERLAY/wallpapers" 2>/dev/null | wc -l) file(s)" + +# Gruvbox GTK theme (for GTK apps running under niri) +THEME_CACHE="$CACHE_DIR/gruvbox-gtk-theme" +THEME_BUILD="$CACHE_DIR/gruvbox-gtk-built" +if [[ ! -d "$THEME_CACHE/.git" ]]; then + git clone --depth=1 https://github.com/Fausto-Korpsvart/Gruvbox-GTK-Theme.git "$THEME_CACHE" || true +fi +if [[ -x "$THEME_CACHE/themes/install.sh" && ! -d "$THEME_BUILD" ]]; then + echo " building gruvbox themes" + install -d -m 0755 "$THEME_BUILD" + "$DOCKER" run --rm \ + -v "$THEME_CACHE":/src \ + -v "$THEME_BUILD":/out \ + debian:stable-slim sh -c ' + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq >/dev/null + apt-get install -y --no-install-recommends sassc bash >/dev/null + cd /src/themes && bash install.sh -d /out -t default -c dark -s standard + ' || true +fi +if [[ -d "$THEME_BUILD" ]]; then + for d in "$THEME_BUILD"/Gruvbox-Dark*; do + [[ -d "$d" ]] && cp -a "$d" "$OVERLAY/themes/$(basename "$d")" + done + echo " themes: $(ls "$OVERLAY/themes" 2>/dev/null | wc -l) variant(s)" +fi + +# Gruvbox Plus icons +ICON_CACHE="$CACHE_DIR/gruvbox-plus-icons" +if [[ ! -d "$ICON_CACHE/.git" ]]; then + git clone --depth=1 https://github.com/SylEleuth/gruvbox-plus-icon-pack.git "$ICON_CACHE" || true +fi +if [[ -d "$ICON_CACHE/Gruvbox-Plus-Dark" ]]; then + cp -a "$ICON_CACHE/Gruvbox-Plus-Dark" "$OVERLAY/icons/Gruvbox-Plus-Dark" + echo " icons: Gruvbox-Plus-Dark" +fi + +# Bibata cursor +BIBATA_SRC="${BIBATA_SRC:-/usr/share/icons/Bibata-Modern-Ice}" +if [[ -d "$BIBATA_SRC" ]]; then + cp -a "$BIBATA_SRC" "$OVERLAY/icons/Bibata-Modern-Ice" + echo " cursor: Bibata-Modern-Ice" +fi + +# 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 + +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 + +# ── 3g) GTK settings for Wayland apps ─────────────────────────────────── +# 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" + +cat > "$INCLUDE_DIR/etc/skel/.config/gtk-3.0/settings.ini" < "$INCLUDE_DIR/etc/skel/.gtkrc-2.0" < "$INCLUDE_DIR/etc/environment" <<'ENVEOF' +XDG_DATA_DIRS=/usr/local/share:/usr/share +QT_QPA_PLATFORM=wayland;xcb +GDK_BACKEND=wayland,x11 +MOZ_ENABLE_WAYLAND=1 +LIBSEAT_BACKEND=logind +ENVEOF + +# ── 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 +fi +EOF + +# ── 3i) Void Linux installer baked in ─────────────────────────────────── +install -d -m 0755 "$INCLUDE_DIR/usr/local/lib/void-installer/lib" +install -d -m 0755 "$INCLUDE_DIR/usr/local/share/installer" +install -m 0755 "$PROJECT_DIR/installer/install.sh" \ + "$INCLUDE_DIR/usr/local/lib/void-installer/install.sh" +for f in "$PROJECT_DIR/installer/lib/"*.sh; do + install -m 0755 "$f" \ + "$INCLUDE_DIR/usr/local/lib/void-installer/lib/$(basename "$f")" +done +install -m 0644 "$PROJECT_DIR/config/install.conf" \ + "$INCLUDE_DIR/usr/local/share/installer/install.conf" +install -m 0644 "$PROJECT_DIR/config/profiles/mainline-niri/packages.list" \ + "$INCLUDE_DIR/usr/local/share/installer/packages.list" +if [[ -d "$PROJECT_DIR/config/profiles" ]]; then + cp -r "$PROJECT_DIR/config/profiles" \ + "$INCLUDE_DIR/usr/local/share/installer/profiles" +fi + +install -d -m 0755 "$INCLUDE_DIR/usr/local/bin" +cat > "$INCLUDE_DIR/usr/local/bin/void-install" <<'WRAPEOF' +#!/bin/sh +exec /usr/local/lib/void-installer/install.sh "$@" +WRAPEOF +chmod 0755 "$INCLUDE_DIR/usr/local/bin/void-install" + +_SECRETS_SRC="${SECRETS_ENV:-$PROJECT_DIR/secrets.env}" +if [[ -r "$_SECRETS_SRC" ]]; then + install -d -m 0755 "$INCLUDE_DIR/etc" + install -m 0600 "$_SECRETS_SRC" "$INCLUDE_DIR/etc/installer-secrets.env" + echo " baked installer secrets from $_SECRETS_SRC" +else + echo " WARNING: no secrets.env found — installer will prompt at runtime" +fi + +install -d -m 0755 "$INCLUDE_DIR/usr/share/applications" +cat > "$INCLUDE_DIR/usr/share/applications/void-installer.desktop" <<'DESKEOF' +[Desktop Entry] +Version=1.0 +Type=Application +Name=Install Void Linux +Comment=Install Void Linux to this machine +Exec=alacritty --title "Void Linux Installer" -e sudo /usr/local/bin/void-install +Icon=system-software-install +Terminal=false +Categories=System; +StartupNotify=true +DESKEOF + +# 4) build Docker image (reuse the same image as Cinnamon build) +echo ">>> building docker image $DOCKER_IMAGE" +if "$DOCKER" buildx version >/dev/null 2>&1; then + "$DOCKER" build -t "$DOCKER_IMAGE" "$PROJECT_DIR/iso" +else + DOCKER_BUILDKIT=0 "$DOCKER" build -t "$DOCKER_IMAGE" "$PROJECT_DIR/iso" +fi + +# 5) packages + output filename +ISO_PKGS=$(grep -vE '^\s*(#|$)' \ + "$PROJECT_DIR/config/profiles/mainline-niri/packages.live-desktop.list" \ + | 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}" + +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="Void Live (niri / noctalia-shell)" \ + -e OUT_ISO_REL="${OUT_ISO#$PROJECT_DIR/}" \ + -e BOOT_CMDLINE="${BOOT_CMDLINE:-}" \ + -e INCLUDE_DIR_REL="${INCLUDE_DIR#$PROJECT_DIR/}" \ + -e NIX_PACKAGES_PREBAKE="${NIX_USER_PACKAGES[*]}" \ + -e HOST_UID="$(id -u)" \ + -e HOST_GID="$(id -g)" \ + "$DOCKER_IMAGE" \ + bash /work/iso/_inner-build-niri-live.sh + +echo +echo ">>> Niri live ISO built: $OUT_ISO" +sha256sum "$OUT_ISO" | tee "${OUT_ISO}.sha256" || true diff --git a/tests/debug-qemu.sh b/tests/debug-qemu.sh new file mode 100755 index 0000000..24e81ca --- /dev/null +++ b/tests/debug-qemu.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# Like interactive-qemu.sh but logs serial console to /tmp/qemu-serial.log +# for post-boot log inspection. Uses the niri ISO by default. +set -Eeuo pipefail +PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +OUT_DIR="$PROJECT_DIR/out" +ISO="${ISO:-$(ls -t "$OUT_DIR"/void-live-niri-*.iso 2>/dev/null | head -1 || true)}" +DISK="${DISK:-$OUT_DIR/test-disk.img}" +RAM_MB="${RAM_MB:-6144}" +SMP="${SMP:-4}" + +OVMF_CODE="${OVMF_CODE:-/usr/share/OVMF/OVMF_CODE.fd}" +OVMF_VARS_TPL="${OVMF_VARS_TPL:-/usr/share/OVMF/OVMF_VARS.fd}" +[[ -r "$OVMF_CODE" ]] || { echo "no OVMF — install ovmf"; exit 1; } +[[ -r "$ISO" ]] || { echo "no ISO at $ISO"; exit 1; } + +if [[ ! -f "$DISK" ]]; then + "$PROJECT_DIR/tests/make-test-disk.sh" "$DISK" +fi +VARS="$OUT_DIR/OVMF_VARS.debug.fd" +[[ -f "$VARS" ]] || cp "$OVMF_VARS_TPL" "$VARS" + +SERIAL_PORT="${SERIAL_PORT:-4444}" +echo ">>> serial console → TCP port $SERIAL_PORT (connect: socat -,raw,echo=0 TCP:127.0.0.1:$SERIAL_PORT)" +echo ">>> ISO: $ISO" + +exec qemu-system-x86_64 \ + -name void-niri-debug \ + -machine q35,accel=kvm:tcg -cpu max \ + -m "$RAM_MB" -smp "$SMP" \ + -drive "if=pflash,format=raw,readonly=on,file=$OVMF_CODE" \ + -drive "if=pflash,format=raw,file=$VARS" \ + -drive "if=virtio,file=$DISK,format=raw,cache=none" \ + -cdrom "$ISO" \ + -boot menu=on \ + -netdev user,id=n0,hostfwd=tcp:127.0.0.1:2222-:22 \ + -device virtio-net-pci,netdev=n0 \ + -serial "tcp::${SERIAL_PORT},server,nowait" \ + -device virtio-gpu-gl -display gtk,gl=on