#!/bin/bash # User-environment customizations: themes, icons, wallpapers, dotfiles, # vscode config, cinnamon dconf defaults, default terminal, keymap. # Reads pre-baked overlay from /etc/installer-overlay/ on the live ISO. # shellcheck source=common.sh source "$(dirname "${BASH_SOURCE[0]}")/common.sh" OVERLAY_SRC="${OVERLAY_SRC:-/etc/installer-overlay}" install_customizations() { step "Installing customizations (themes / icons / wallpapers / dotfiles)" local TARGET="${TARGET:-/mnt}" if [[ ! -d "$OVERLAY_SRC" ]]; then warn "no overlay at $OVERLAY_SRC; skipping customizations" return 0 fi _deploy_themes "$TARGET" _deploy_icons "$TARGET" _deploy_wallpapers "$TARGET" _deploy_user_dotfiles "$TARGET" _deploy_vscode_config "$TARGET" _deploy_first_login "$TARGET" _set_default_terminal "$TARGET" _install_gestures "$TARGET" _install_snapshot_hook "$TARGET" _install_upgrade_applet "$TARGET" # Cinnamon/X11-specific helpers — only run on the cinnamon profile. if [[ "${DESKTOP:-cinnamon}" == "cinnamon" ]]; then _write_dconf_defaults "$TARGET" _install_nemo_actions "$TARGET" fi # Profile-specific hooks (e.g. niri KDL config, waybar, greetd). if declare -F run_profile_customizations >/dev/null 2>&1; then run_profile_customizations fi ok "customizations installed" } _deploy_themes() { local TARGET="$1" local src="$OVERLAY_SRC/themes" [[ -d "$src" ]] || { log "no themes overlay; skipping"; return 0; } install -d -m 0755 "$TARGET/usr/share/themes" # Each subdir of overlay/themes is a complete theme dir; just rsync them. cp -a "$src"/. "$TARGET/usr/share/themes/" 2>/dev/null || true log "themes deployed -> /usr/share/themes/" } _deploy_icons() { local TARGET="$1" local src="$OVERLAY_SRC/icons" [[ -d "$src" ]] || { log "no icons overlay; skipping"; return 0; } install -d -m 0755 "$TARGET/usr/share/icons" cp -a "$src"/. "$TARGET/usr/share/icons/" 2>/dev/null || true # Refresh icon cache (best-effort). for d in "$TARGET"/usr/share/icons/*/; do [[ -d "$d" ]] || continue run_chroot "gtk-update-icon-cache -f -t /usr/share/icons/$(basename "$d") 2>/dev/null || true" done log "icons deployed -> /usr/share/icons/" } _deploy_wallpapers() { local TARGET="$1" local src="$OVERLAY_SRC/wallpapers" [[ -d "$src" ]] || { log "no wallpapers overlay; skipping"; return 0; } local dst="$TARGET/usr/share/backgrounds/void-installer" install -d -m 0755 "$dst" cp -a "$src"/. "$dst/" chmod 0644 "$dst"/* 2>/dev/null || true log "wallpapers -> /usr/share/backgrounds/void-installer/" } _deploy_user_dotfiles() { local TARGET="$1" local src="$OVERLAY_SRC/skel" [[ -d "$src" ]] || { log "no skel overlay; skipping dotfiles"; return 0; } local home="$TARGET/home/$USERNAME" install -d -m 0755 "$home" # Copy dotfiles, preserving structure. Don't clobber .ssh (already set). ( cd "$src" && find . -mindepth 1 -maxdepth 1 \ ! -name '.ssh' -print0 ) | while IFS= read -r -d '' rel; do cp -a "$src/$rel" "$home/" 2>/dev/null || true done run_chroot "chown -R $USERNAME:$USERNAME /home/$USERNAME" log "dotfiles deployed -> /home/$USERNAME/" } _deploy_vscode_config() { local TARGET="$1" local src="$OVERLAY_SRC/vscode-user" [[ -d "$src" ]] || { log "no vscode-user overlay; skipping"; return 0; } local dst="$TARGET/home/$USERNAME/.config/Code/User" install -d -m 0755 "$dst" cp -a "$src"/. "$dst/" run_chroot "chown -R $USERNAME:$USERNAME /home/$USERNAME/.config" log "vscode user config -> ~/.config/Code/User/" } _deploy_first_login() { local TARGET="$1" local src="$OVERLAY_SRC/first-login.sh" local ext_list="$OVERLAY_SRC/vscode-extensions.txt" install -d -m 0755 "$TARGET/usr/local/libexec" [[ -r "$src" ]] && { install -m 0755 "$src" "$TARGET/usr/local/libexec/first-login.sh" log "first-login script staged" } [[ -r "$ext_list" ]] && { install -m 0644 "$ext_list" "$TARGET/etc/installer-vscode-extensions.txt" } # Inject one-shot trigger into ~/.bash_profile (appended; idempotent guard # in the script itself via /var/lib/first-login.done). local home="$TARGET/home/$USERNAME" install -d -m 0755 "$home" if [[ -x "$TARGET/usr/local/libexec/first-login.sh" ]]; then if ! grep -q "first-login.sh" "$home/.bash_profile" 2>/dev/null; then cat >> "$home/.bash_profile" <<'EOF' # 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 fi run_chroot "chown $USERNAME:$USERNAME /home/$USERNAME/.bash_profile" fi # Also autostart it from the desktop session so GUI-only users get it. if [[ -x "$TARGET/usr/local/libexec/first-login.sh" ]]; then local autostart="$TARGET/etc/xdg/autostart" install -d -m 0755 "$autostart" cat > "$autostart/void-installer-first-login.desktop" <<'EOF' [Desktop Entry] Type=Application Name=Void Installer First-Login Setup Exec=/usr/local/libexec/first-login.sh NoDisplay=true X-GNOME-Autostart-enabled=true OnlyShowIn=X-Cinnamon;GNOME;XFCE;KDE; EOF fi # Ensure ~/.local/bin is on PATH for every shell (login + non-login). install -d -m 0755 "$TARGET/etc/profile.d" cat > "$TARGET/etc/profile.d/local-bin.sh" <<'EOF' # Prepend the per-user bin dir so first-login symlinks (claude, node, npm) # are visible to every interactive shell. case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) export PATH="$HOME/.local/bin:$PATH" ;; esac EOF # Claude Code config (auth tokens) bundled from host overlay. local claude_src="$OVERLAY_SRC/claude" if [[ -d "$claude_src" ]]; then local cdst="$TARGET/home/$USERNAME/.claude" install -d -m 0700 "$cdst" cp -a "$claude_src"/. "$cdst/" log "claude config -> ~/.claude/" fi if [[ -r "$OVERLAY_SRC/claude.json" ]]; then install -m 0600 "$OVERLAY_SRC/claude.json" "$TARGET/home/$USERNAME/.claude.json" fi run_chroot "chown -R $USERNAME:$USERNAME /home/$USERNAME/.claude /home/$USERNAME/.claude.json 2>/dev/null || true" } _write_dconf_defaults() { local TARGET="$1" install -d -m 0755 "$TARGET/etc/dconf/db/local.d" \ "$TARGET/etc/dconf/profile" cat > "$TARGET/etc/dconf/profile/user" <<'EOF' user-db:user system-db:local EOF local wallpaper="${INITIAL_WALLPAPER:-pxfuel.jpg}" cat > "$TARGET/etc/dconf/db/local.d/00-cinnamon" <t'] [org/cinnamon/desktop/keybindings/custom-keybindings/custom1] name='Flameshot' command='flameshot gui' binding=['Print'] [org/cinnamon/desktop/keybindings/media-keys] screenshot=[''] area-screenshot=[''] window-screenshot=[''] [org/cinnamon] enabled-applets=['panel1:left:0:menu@cinnamon.org', 'panel1:left:1:show-desktop@cinnamon.org', 'panel1:left:2:grouped-window-list@cinnamon.org', 'panel1:right:0:systray@cinnamon.org', 'panel1:right:1:xapp-status@cinnamon.org', 'panel1:right:2:notifications@cinnamon.org', 'panel1:right:3:printers@cinnamon.org', 'panel1:right:4:removable-drives@cinnamon.org', 'panel1:right:5:keyboard@cinnamon.org', 'panel1:right:6:favorites@cinnamon.org', 'panel1:right:7:network@cinnamon.org', 'panel1:right:8:sound@cinnamon.org', 'panel1:right:9:power@cinnamon.org', 'panel1:right:10:calendar@cinnamon.org', 'panel1:right:11:user@cinnamon.org'] [org/cinnamon/desktop/default-applications/terminal] exec='${DEFAULT_TERMINAL:-alacritty}' exec-arg='-e' EOF # Compile dconf db inside the chroot. run_chroot "dconf update 2>/dev/null || true" log "dconf cinnamon defaults written" } _set_default_terminal() { local TARGET="$1" local term="${DEFAULT_TERMINAL:-alacritty}" # x-terminal-emulator-style alternative — Void doesn't ship update-alternatives # for terminals, so just symlink in /usr/local/bin. if [[ -x "$TARGET/usr/bin/$term" ]]; then ln -sf "/usr/bin/$term" "$TARGET/usr/local/bin/x-terminal-emulator" log "default terminal set to $term" else warn "$term not found in target; skipping default-terminal symlink" fi # Persistent X keymap (works for non-cinnamon login too). install -d -m 0755 "$TARGET/etc/X11/xorg.conf.d" cat > "$TARGET/etc/X11/xorg.conf.d/00-keyboard.conf" < "$TARGET/etc/skel/.config/libinput-gestures.conf" <<'EOF' # void-installer defaults gesture swipe up 3 wmctrl -k on gesture swipe down 3 wmctrl -k off gesture swipe left 3 xdotool key super+Right gesture swipe right 3 xdotool key super+Left gesture swipe left 4 xdotool key ctrl+alt+Right gesture swipe right 4 xdotool key ctrl+alt+Left gesture swipe up 4 xdotool key super gesture swipe down 4 xdotool key super+d gesture pinch in xdotool key ctrl+minus gesture pinch out xdotool key ctrl+plus EOF # Ensure user is in 'input' group (libinput-gestures needs /dev/input/event*). run_chroot "groupadd -f input; usermod -aG input $USERNAME" || true # Autostart for cinnamon session. install -d -m 0755 "$TARGET/etc/skel/.config/autostart" cat > "$TARGET/etc/skel/.config/autostart/libinput-gestures.desktop" <<'EOF' [Desktop Entry] Type=Application Name=Libinput Gestures Exec=libinput-gestures-setup start X-GNOME-Autostart-enabled=true NoDisplay=false EOF # Mirror into existing user's home (skel only applies to NEW users). if id "$USERNAME" >/dev/null 2>&1 || [[ -d "$TARGET/home/$USERNAME" ]]; then install -d -m 0755 "$TARGET/home/$USERNAME/.config/autostart" cp -a "$TARGET/etc/skel/.config/libinput-gestures.conf" \ "$TARGET/home/$USERNAME/.config/libinput-gestures.conf" 2>/dev/null || true cp -a "$TARGET/etc/skel/.config/autostart/libinput-gestures.desktop" \ "$TARGET/home/$USERNAME/.config/autostart/" 2>/dev/null || true run_chroot "chown -R $USERNAME:$USERNAME /home/$USERNAME/.config" || true fi log "trackpad gestures configured (libinput-gestures)" } _install_snapshot_hook() { local TARGET="$1" # Pre-upgrade btrfs snapshot via xbps-install hook. install -d -m 0755 "$TARGET/etc/xbps.d" install -d -m 0755 "$TARGET/usr/local/sbin" cat > "$TARGET/usr/local/sbin/xbps-pre-upgrade-snapshot.sh" <<'EOF' #!/bin/sh # Take a read-only btrfs snapshot of @ before an xbps upgrade/install. # Snapshots live in /.snapshots/auto-YYYYmmdd-HHMMSS so timeshift can manage them. set -e ROOT_SUBVOL=/run/btrfs-root/@ SNAP_DIR=/.snapshots [ -d "$SNAP_DIR" ] || mkdir -p "$SNAP_DIR" TS=$(date +%Y%m%d-%H%M%S) NAME="auto-pre-xbps-$TS" # Mount the toplevel of the btrfs FS once so we can snapshot @. ROOT_DEV=$(findmnt -no SOURCE /) mkdir -p /run/btrfs-root if ! mountpoint -q /run/btrfs-root; then mount -o subvolid=5 "$ROOT_DEV" /run/btrfs-root 2>/dev/null || exit 0 fi btrfs subvolume snapshot -r /run/btrfs-root/@ "/run/btrfs-root/.snapshots/$NAME" 2>/dev/null \ || mkdir -p /run/btrfs-root/.snapshots && \ btrfs subvolume snapshot -r /run/btrfs-root/@ "/run/btrfs-root/.snapshots/$NAME" echo "[snapshot] created $NAME" EOF chmod 0755 "$TARGET/usr/local/sbin/xbps-pre-upgrade-snapshot.sh" # xbps doesn't have native pre-hooks; wrap xbps-install via /usr/local/bin shim. cat > "$TARGET/usr/local/bin/xbps-install" <<'EOF' #!/bin/sh # Wrapper that snapshots @ before any state-changing xbps-install run. case " $* " in *" -S "*|*" --sync "*|*" -u "*|*" --update "*) /usr/local/sbin/xbps-pre-upgrade-snapshot.sh || true ;; *) [ -n "$1" ] && /usr/local/sbin/xbps-pre-upgrade-snapshot.sh || true ;; esac exec /usr/bin/xbps-install "$@" EOF chmod 0755 "$TARGET/usr/local/bin/xbps-install" log "btrfs pre-upgrade snapshot hook installed" } _install_upgrade_applet() { local TARGET="$1" install -d -m 0755 "$TARGET/usr/local/bin" \ "$TARGET/usr/share/applications" \ "$TARGET/etc/skel/.config/autostart" # Tiny GUI wrapper: uses zenity if available, else xterm. cat > "$TARGET/usr/local/bin/void-upgrade-gui" <<'EOF' #!/bin/sh # Check for upgrades, prompt user, run xbps-install -Su (with snapshot via wrapper). set -e PENDING=$(xbps-install -Sun 2>/dev/null | wc -l) if [ "$PENDING" -eq 0 ]; then notify-send "Void Upgrade" "System is up to date." 2>/dev/null || true exit 0 fi MSG="There are $PENDING package updates available.\nRun system upgrade now?\n(A btrfs snapshot will be taken automatically.)" if command -v zenity >/dev/null 2>&1; then zenity --question --title="Void Upgrade" --text="$MSG" || exit 0 fi pkexec xbps-install -Suy 2>&1 | tee /tmp/void-upgrade.log notify-send "Void Upgrade" "Upgrade finished. See /tmp/void-upgrade.log" 2>/dev/null || true EOF chmod 0755 "$TARGET/usr/local/bin/void-upgrade-gui" cat > "$TARGET/usr/share/applications/void-upgrade.desktop" <<'EOF' [Desktop Entry] Type=Application Name=Void Upgrade Comment=Check and apply system upgrades (with btrfs snapshot) Exec=void-upgrade-gui Icon=system-software-update Categories=System;PackageManager; Terminal=false EOF # Daily check on login (autostart, runs once per day). cat > "$TARGET/etc/skel/.config/autostart/void-upgrade-check.desktop" <<'EOF' [Desktop Entry] Type=Application Name=Void Upgrade Check Exec=sh -c '[ "$(date +%F)" != "$(cat ~/.cache/void-upgrade.last 2>/dev/null)" ] && void-upgrade-gui && date +%F > ~/.cache/void-upgrade.last' X-GNOME-Autostart-enabled=true NoDisplay=true EOF cp -a "$TARGET/etc/skel/.config/autostart/void-upgrade-check.desktop" \ "$TARGET/home/$USERNAME/.config/autostart/" 2>/dev/null || true run_chroot "chown -R $USERNAME:$USERNAME /home/$USERNAME/.config" || true log "void-upgrade GUI applet installed" } _install_nemo_actions() { local TARGET="$1" install -d -m 0755 "$TARGET/usr/share/nemo/actions" cat > "$TARGET/usr/share/nemo/actions/open-vscode.nemo_action" <<'EOF' [Nemo Action] Active=true Name=Open with VS Code Comment=Open the selected file or folder in Visual Studio Code Exec=code %F Icon-Name=com.visualstudio.code Selection=Any Extensions=any; EOF log "Nemo 'Open with VS Code' action installed" }