#!/bin/bash # Build a full Cinnamon LIVE desktop ISO for testing on real hardware. # # Boots directly into a Cinnamon session as user 'live' (no password). # All themes, icons, cursor, wallpapers, dotfiles and VS Code config are # pre-applied from the same overlay used by the installer. # # Requires (host): bash, git, curl, docker, and Bibata-Modern-Ice cursor # installed at /usr/share/icons/Bibata-Modern-Ice. # # Usage: # iso/build-live-iso.sh # OUTPUT_ISO=/path/to/output.iso iso/build-live-iso.sh set -Eeuo pipefail PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" CACHE_DIR="${CACHE_DIR:-$PROJECT_DIR/cache}" OUT_DIR="${OUT_DIR:-$PROJECT_DIR/out}" BUILD_DIR="${BUILD_DIR:-$PROJECT_DIR/build}" INCLUDE_DIR="$BUILD_DIR/live-includes" MKLIVE_DIR="$CACHE_DIR/void-mklive" MKLIVE_REPO="${MKLIVE_REPO:-https://github.com/void-linux/void-mklive.git}" MKLIVE_REF="${MKLIVE_REF:-master}" PATCH_DIR="$PROJECT_DIR/iso/patches" DOCKER_IMAGE="${DOCKER_IMAGE:-void-installer-builder:latest}" DOCKER="${DOCKER:-docker}" LIVE_USER="${LIVE_USER:-live}" # shellcheck disable=SC1091 source "$PROJECT_DIR/config/install.conf" # Load profile defaults (GTK_THEME, CURSOR_THEME, ICON_THEME etc.) # shellcheck disable=SC1091 source "$PROJECT_DIR/config/profiles/stable-cinnamon/profile.conf" command -v "$DOCKER" >/dev/null \ || { echo "ERROR: '$DOCKER' not in PATH"; exit 1; } "$DOCKER" info >/dev/null 2>&1 \ || { echo "ERROR: '$DOCKER' daemon unreachable"; exit 1; } mkdir -p "$CACHE_DIR" "$OUT_DIR" "$BUILD_DIR" # 1) clone + patch mklive if [[ ! -d "$MKLIVE_DIR/.git" ]]; then echo ">>> cloning void-mklive" git clone --depth=1 --branch "$MKLIVE_REF" "$MKLIVE_REPO" "$MKLIVE_DIR" fi if compgen -G "$PATCH_DIR/*.patch" >/dev/null; then echo ">>> resetting + applying iso/patches/" ( cd "$MKLIVE_DIR" && git checkout -- . ) for p in "$PATCH_DIR"/*.patch; do echo " $(basename "$p")" ( cd "$MKLIVE_DIR" && patch -p1 --silent < "$p" ) done fi # 2) xbps-static XBPS_STATIC_DIR="$CACHE_DIR/xbps-static" if [[ ! -x "$XBPS_STATIC_DIR/usr/bin/xbps-install.static" ]]; then echo ">>> downloading xbps-static" mkdir -p "$XBPS_STATIC_DIR" curl -fsSL "https://repo-default.voidlinux.org/static/xbps-static-latest.x86_64-musl.tar.xz" \ | tar xJf - -C "$XBPS_STATIC_DIR" fi # 3) build includes overlay echo ">>> staging live includes overlay at $INCLUDE_DIR" rm -rf "$INCLUDE_DIR" mkdir -p "$INCLUDE_DIR" # ── 3a) live user account ─────────────────────────────────────────────── # Create user 'live' with no password in the live image. # mklive populates /etc/passwd at install time — we drop a post-install hook # via the live init that calls useradd. We use an install.conf.d override # that mklive will source, plus an rc.local-style runit oneshot. install -d -m 0755 "$INCLUDE_DIR/etc" cat > "$INCLUDE_DIR/etc/passwd-live" < "$INCLUDE_DIR/etc/sv/live-user-setup/run" <<'SV_EOF' #!/bin/sh # Create the 'live' user (no password) and set up home directory. # Runs once; removes itself from default runsvdir on completion. set -e if ! id live >/dev/null 2>&1; then useradd -m -s /bin/bash -G wheel,audio,video,plugdev,input,network,docker live passwd -d live install -d -m 0700 /home/live/.ssh # Apply skel dotfiles if [ -d /etc/skel ]; then cp -a /etc/skel/. /home/live/ 2>/dev/null || true fi chown -R live:live /home/live fi # Enable sudoers entry for live if ! grep -q '^live ' /etc/sudoers 2>/dev/null; then echo 'live ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers fi # Remove self from runsvdir so it doesn't restart rm -f /etc/runit/runsvdir/default/live-user-setup sv stop live-user-setup 2>/dev/null || true SV_EOF chmod 0755 "$INCLUDE_DIR/etc/sv/live-user-setup/run" ln -sf /etc/sv/live-user-setup "$INCLUDE_DIR/etc/runit/runsvdir/default/live-user-setup" # ── 3b) LightDM autologin ─────────────────────────────────────────────── install -d -m 0755 "$INCLUDE_DIR/etc/lightdm" cat > "$INCLUDE_DIR/etc/lightdm/lightdm.conf" </dev/null || true done # ── 3c) Themes / icons / wallpapers (same as installer) ───────────────── echo ">>> staging customizations overlay" OVERLAY="$INCLUDE_DIR/etc/installer-overlay" install -d -m 0755 "$OVERLAY" "$OVERLAY/wallpapers" \ "$OVERLAY/themes" "$OVERLAY/icons" \ "$OVERLAY/skel" "$OVERLAY/vscode-user" # Wallpapers WP_SRC="${WALLPAPERS_SRC:-$HOME/Scaricati}" shopt -s nullglob for f in "$WP_SRC"/pxfuel*.jpg; do install -m 0644 "$f" "$OVERLAY/wallpapers/$(basename "$f")" done shopt -u nullglob echo " wallpapers: $(ls "$OVERLAY/wallpapers" | wc -l) file(s)" # Gruvbox GTK theme THEME_CACHE="$CACHE_DIR/gruvbox-gtk-theme" THEME_BUILD="$CACHE_DIR/gruvbox-gtk-built" if [[ ! -d "$THEME_CACHE/.git" ]]; then git clone --depth=1 https://github.com/Fausto-Korpsvart/Gruvbox-GTK-Theme.git "$THEME_CACHE" || true fi if [[ -x "$THEME_CACHE/themes/install.sh" && ! -d "$THEME_BUILD" ]]; then echo " building gruvbox themes" install -d -m 0755 "$THEME_BUILD" "$DOCKER" run --rm \ -v "$THEME_CACHE":/src \ -v "$THEME_BUILD":/out \ debian:stable-slim sh -c ' export DEBIAN_FRONTEND=noninteractive apt-get update -qq >/dev/null apt-get install -y --no-install-recommends sassc bash >/dev/null cd /src/themes && bash install.sh -d /out -t default -c dark -s standard ' || true fi if [[ -d "$THEME_BUILD" ]]; then for d in "$THEME_BUILD"/Gruvbox-Dark*; do [[ -d "$d" ]] && cp -a "$d" "$OVERLAY/themes/$(basename "$d")" done echo " themes: $(ls "$OVERLAY/themes" 2>/dev/null | wc -l) variant(s)" fi # Gruvbox Plus icons ICON_CACHE="$CACHE_DIR/gruvbox-plus-icons" if [[ ! -d "$ICON_CACHE/.git" ]]; then git clone --depth=1 https://github.com/SylEleuth/gruvbox-plus-icon-pack.git "$ICON_CACHE" || true fi if [[ -d "$ICON_CACHE/Gruvbox-Plus-Dark" ]]; then cp -a "$ICON_CACHE/Gruvbox-Plus-Dark" "$OVERLAY/icons/Gruvbox-Plus-Dark" echo " icons: Gruvbox-Plus-Dark" fi # Bibata cursor BIBATA_SRC="${BIBATA_SRC:-/usr/share/icons/Bibata-Modern-Ice}" if [[ -d "$BIBATA_SRC" ]]; then cp -a "$BIBATA_SRC" "$OVERLAY/icons/Bibata-Modern-Ice" echo " cursor: Bibata-Modern-Ice" fi # Dotfiles DOTFILES_SRC="${DOTFILES_SRC:-$HOME}" for f in .bashrc .bash_aliases .gitconfig; do [[ -r "$DOTFILES_SRC/$f" ]] && install -m 0644 "$DOTFILES_SRC/$f" "$OVERLAY/skel/$f" done # VS Code user config VSCODE_SRC="${VSCODE_USER_SRC:-$HOME/.config/Code/User}" if [[ -d "$VSCODE_SRC" ]]; then for f in settings.json keybindings.json mcp.json tasks.json; do [[ -r "$VSCODE_SRC/$f" ]] && install -m 0644 "$VSCODE_SRC/$f" "$OVERLAY/vscode-user/$f" done [[ -d "$VSCODE_SRC/snippets" ]] && cp -a "$VSCODE_SRC/snippets" "$OVERLAY/vscode-user/snippets" [[ -d "$VSCODE_SRC/globalStorage" ]] && cp -a "$VSCODE_SRC/globalStorage" "$OVERLAY/vscode-user/globalStorage" fi if command -v code >/dev/null 2>&1; then code --list-extensions > "$OVERLAY/vscode-extensions.txt" 2>/dev/null || true fi # Claude config CLAUDE_SRC="${CLAUDE_SRC:-$HOME/.claude}" [[ -d "$CLAUDE_SRC" ]] && cp -a "$CLAUDE_SRC" "$OVERLAY/claude" [[ -r "${HOME}/.claude.json" ]] && install -m 0600 "${HOME}/.claude.json" "$OVERLAY/claude.json" # first-login.sh [[ -r "$PROJECT_DIR/installer/first-login.sh" ]] && \ install -m 0755 "$PROJECT_DIR/installer/first-login.sh" "$OVERLAY/first-login.sh" # ── 3d) Pre-apply themes/dconf to the live user's home via /etc/skel ──── # We pre-write the dconf system-db so it takes effect without a user build step. install -d -m 0755 "$INCLUDE_DIR/etc/dconf/db/local.d" \ "$INCLUDE_DIR/etc/dconf/profile" cat > "$INCLUDE_DIR/etc/dconf/profile/user" <<'EOF' user-db:user system-db:local EOF WALLPAPER_FILE=$(ls "$OVERLAY/wallpapers/"*.jpg 2>/dev/null | head -1 | xargs basename 2>/dev/null || echo "pxfuel.jpg") cat > "$INCLUDE_DIR/etc/dconf/db/local.d/00-cinnamon" <t'] EOF # ── 3e) Copy themes/icons/wallpapers directly into usr/share ──────────── # (dconf compile happens inside the Docker container — see _inner-build-live.sh) install -d -m 0755 "$INCLUDE_DIR/usr/share/backgrounds/void-installer" cp -a "$OVERLAY/wallpapers"/. "$INCLUDE_DIR/usr/share/backgrounds/void-installer/" 2>/dev/null || true install -d -m 0755 "$INCLUDE_DIR/usr/share/themes" [[ -d "$OVERLAY/themes" ]] && cp -a "$OVERLAY/themes"/. "$INCLUDE_DIR/usr/share/themes/" 2>/dev/null || true install -d -m 0755 "$INCLUDE_DIR/usr/share/icons" [[ -d "$OVERLAY/icons" ]] && cp -a "$OVERLAY/icons"/. "$INCLUDE_DIR/usr/share/icons/" 2>/dev/null || true # ── 3f) first-login autostart + profile.d for the live user ───────────── install -d -m 0755 "$INCLUDE_DIR/etc/xdg/autostart" if [[ -r "$OVERLAY/first-login.sh" ]]; then install -d -m 0755 "$INCLUDE_DIR/usr/local/libexec" install -m 0755 "$OVERLAY/first-login.sh" "$INCLUDE_DIR/usr/local/libexec/first-login.sh" cat > "$INCLUDE_DIR/etc/xdg/autostart/void-live-first-login.desktop" < "$INCLUDE_DIR/etc/profile.d/local-bin.sh" <<'EOF' case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) export PATH="$HOME/.local/bin:$PATH" ;; esac EOF # ── 3g) Live-session skel: pre-wire .bash_profile for first-login ──────── install -d -m 0755 "$INCLUDE_DIR/etc/skel" cat > "$INCLUDE_DIR/etc/skel/.bash_profile" <<'EOF' # Source .bashrc for interactive login shells. [[ -f ~/.bashrc ]] && . ~/.bashrc # Auto-run user environment setup on first interactive login. if [[ -z "$_FIRST_LOGIN_RAN" && -x /usr/local/libexec/first-login.sh \ && ! -f "$HOME/.first-login-done" ]]; then export _FIRST_LOGIN_RAN=1 /usr/local/libexec/first-login.sh 2>&1 | tee -a "$HOME/.first-login.log" fi EOF # 4) build Docker image echo ">>> building docker image $DOCKER_IMAGE" if "$DOCKER" buildx version >/dev/null 2>&1; then "$DOCKER" build -t "$DOCKER_IMAGE" "$PROJECT_DIR/iso" else DOCKER_BUILDKIT=0 "$DOCKER" build -t "$DOCKER_IMAGE" "$PROJECT_DIR/iso" fi # 5) packages + output filename ISO_PKGS=$(grep -vE '^\s*(#|$)' \ "$PROJECT_DIR/config/profiles/stable-cinnamon/packages.live-desktop.list" \ | tr '\n' ' ') TS="$(date -u +%Y%m%d)" OUT_ISO="${OUTPUT_ISO:-$OUT_DIR/void-live-stable-${TS}.iso}" echo ">>> running mklive.sh inside docker — output: $OUT_ISO" "$DOCKER" run --rm --privileged \ -v "$PROJECT_DIR:/work:rw" \ -v "$CACHE_DIR:/cache:rw" \ -e ARCH="${ARCH:-x86_64}" \ -e REPO_URL="${REPO_URL:-https://repo-default.voidlinux.org/current}" \ -e KEYMAP="${KEYMAP:-ch-fr_nodeadkeys}" \ -e LOCALE="${LOCALE:-en_US.UTF-8}" \ -e ISO_PKGS="$ISO_PKGS" \ -e ISO_TITLE="Void Live (${GTK_THEME} / Cinnamon)" \ -e OUT_ISO_REL="${OUT_ISO#$PROJECT_DIR/}" \ -e BOOT_CMDLINE="${BOOT_CMDLINE:-}" \ -e INCLUDE_DIR_REL="${INCLUDE_DIR#$PROJECT_DIR/}" \ -e HOST_UID="$(id -u)" \ -e HOST_GID="$(id -g)" \ "$DOCKER_IMAGE" \ bash /work/iso/_inner-build-live.sh echo echo ">>> Live ISO built: $OUT_ISO" sha256sum "$OUT_ISO" | tee "${OUT_ISO}.sha256"