433 lines
15 KiB
Bash
433 lines
15 KiB
Bash
#!/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" <<EOF
|
|
# Cinnamon system-wide defaults — generated by void-installer.
|
|
[org/cinnamon/desktop/interface]
|
|
gtk-theme='${GTK_THEME}'
|
|
icon-theme='${ICON_THEME}'
|
|
cursor-theme='${CURSOR_THEME}'
|
|
|
|
[org/cinnamon/desktop/wm/preferences]
|
|
theme='${GTK_THEME}'
|
|
|
|
[org/cinnamon/theme]
|
|
name='${GTK_THEME}'
|
|
|
|
[org/cinnamon/desktop/background]
|
|
picture-uri='file:///usr/share/backgrounds/void-installer/${wallpaper}'
|
|
picture-options='zoom'
|
|
|
|
[org/gnome/desktop/background]
|
|
picture-uri='file:///usr/share/backgrounds/void-installer/${wallpaper}'
|
|
picture-options='zoom'
|
|
|
|
[org/gnome/desktop/input-sources]
|
|
xkb-options=['']
|
|
sources=[('xkb', 'ch+fr')]
|
|
|
|
[org/cinnamon/desktop/keybindings]
|
|
custom-list=['custom0', 'custom1']
|
|
|
|
[org/cinnamon/desktop/keybindings/custom-keybindings/custom0]
|
|
name='Open Terminal'
|
|
command='${DEFAULT_TERMINAL:-alacritty}'
|
|
binding=['<Primary><Alt>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" <<EOF
|
|
Section "InputClass"
|
|
Identifier "system-keyboard"
|
|
MatchIsKeyboard "on"
|
|
Option "XkbLayout" "ch"
|
|
Option "XkbVariant" "fr"
|
|
EndSection
|
|
EOF
|
|
log "X11 keymap pinned: ch(fr)"
|
|
}
|
|
|
|
_install_gestures() {
|
|
local TARGET="$1"
|
|
# System-wide default libinput-gestures config (workspace switching, overview, etc.)
|
|
install -d -m 0755 "$TARGET/etc/skel/.config"
|
|
cat > "$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"
|
|
}
|