dotfiles/.config/rice/install.sh
Sarthak 0780a3abf5 readme at the root, and fix what would break on a fresh machine
The repo landing page showed .config, .themes and .gtkrc-2.0 and nothing else,
so there was no sign the bootstrap existed. It has always been at
.config/rice/install.sh - that is just where it lands on disk. Root README now
carries the curl one-liner, a map of what is tracked, and a note that this is a
bare repo over $HOME so nobody goes looking for a wrapper folder again.

Things that would not have survived a replication:

  - polybar's click and scroll handlers called $HOME/bin/system-info.sh and
    $HOME/bin/volume.sh. That directory does not exist; the scripts are in
    .config/scripts. The exec lines already pointed there, so only the
    interactive bits were dead.
  - bspwmrc hardcoded DisplayPort-0, so no desktops on any other machine.
    Takes the xrandr primary now, falling back to the first monitor bspc sees.
  - install.sh picked conflicting files out of git's checkout error with a
    regex that required a dot inside the name, so .bashrc and .xinitrc were
    never matched and never backed up - and .bashrc always exists on a fresh
    Arch install, which is precisely when the checkout aborts.
  - bspwmrc sourced ~/.env and merged ~/.Xresources unconditionally; neither
    exists. Guarded.

packages.txt was missing programs whose configs this repo already carries:
fish, neovim, yazi, kitty, plus xrdb/xsetroot/xsettingsd/wmname that bspwmrc
launches, gtk3 for the control panel, and wireplumber for the wpctl calls.

Newly tracked: .xinitrc (the thing that actually starts bspwm), .bashrc (which
holds the dotfiles alias), greenclip, flameshot, kitty, Thunar, mimeapps and
user-dirs. Dropped six editor backups that had been committed by accident.
Skipped QtProject.conf - it is file-dialog history, not configuration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011beBgzmH72WFn8wiWqRZPf
2026-09-04 10:09:16 +05:30

252 lines
11 KiB
Bash
Executable file

#!/usr/bin/env bash
#
# Sakura Line - one-shot rice bootstrap.
#
# bash <(curl -fsSL https://git.srtk.in/sarthak/dotfiles/raw/branch/main/.config/rice/install.sh)
#
# or, from a clone: ~/.config/rice/install.sh
#
# Arch is the first-class target. Debian/Ubuntu and Fedora are best-effort:
# the desktop and build steps work, the AUR extras are skipped.
#
# Safe to re-run. Nothing in $HOME is overwritten without a backup first.
set -uo pipefail
DOTS_REMOTE="${DOTS_REMOTE:-https://git.srtk.in/sarthak/dotfiles.git}"
ST_REMOTE="${ST_REMOTE:-https://github.com/siduck/st}"
DOTS_DIR="$HOME/.dotfiles"
RICE_DIR="$HOME/.config/rice"
SRC_DIR="${SRC_DIR:-$HOME/gitclone}"
BACKUP="$HOME/.rice-backup-$(date +%Y%m%d-%H%M%S)"
STEPS_FAILED=()
DRY=0
for a in "$@"; do
case "$a" in
-n|--dry-run) DRY=1 ;;
-h|--help)
sed -n '2,14p' "$0" | sed 's/^#\s\?//'
printf '\nOptions:\n -n, --dry-run show what would happen, change nothing\n -h, --help this text\n\n'
exit 0 ;;
*) printf 'unknown option: %s (try --help)\n' "$a"; exit 2 ;;
esac
done
c_hd=$'\e[1;38;5;111m'; c_ok=$'\e[38;5;108m'; c_wa=$'\e[38;5;179m'
c_er=$'\e[38;5;168m'; c_dim=$'\e[38;5;103m'; c_off=$'\e[0m'
say() { printf '%s==>%s %s\n' "$c_hd" "$c_off" "$*"; }
# every mutating command goes through this so --dry-run is honest
run() { if [ "$DRY" = 1 ]; then printf ' %s$ %s%s\n' "$c_dim" "$*" "$c_off"; else "$@"; fi; }
ok() { printf ' %s+%s %s\n' "$c_ok" "$c_off" "$*"; }
warn() { printf ' %s!%s %s\n' "$c_wa" "$c_off" "$*"; }
die() { printf ' %sx%s %s\n' "$c_er" "$c_off" "$*"; exit 1; }
note() { printf ' %s%s%s\n' "$c_dim" "$*" "$c_off"; }
fail_step() { STEPS_FAILED+=("$1"); printf ' %sx%s %s\n' "$c_er" "$c_off" "$1"; }
[ "$(id -u)" -eq 0 ] && die "run this as your normal user, not root (it uses sudo where needed)"
# ---------------------------------------------------------------- distro
PM=""; AUR=""
if command -v pacman >/dev/null 2>&1; then PM=pacman
elif command -v apt-get >/dev/null 2>&1; then PM=apt
elif command -v dnf >/dev/null 2>&1; then PM=dnf
else warn "no supported package manager found; skipping installs"; PM=none
fi
PM_LINE="package manager: ${PM}"
# Package names differ per distro; this maps the ones that actually differ.
map_pkg() {
local p="$1"
case "$PM" in
apt) case "$p" in
base-devel) echo "build-essential" ;;
pkgconf) echo "pkg-config" ;;
libx11) echo "libx11-dev" ;;
libxext) echo "libxext-dev" ;;
libxft) echo "libxft-dev" ;;
libxrandr) echo "libxrandr-dev" ;;
libxfixes) echo "libxfixes-dev" ;;
libxinerama) echo "libxinerama-dev" ;;
cairo) echo "libcairo2-dev" ;;
harfbuzz) echo "libharfbuzz-dev" ;;
fontconfig) echo "libfontconfig1-dev" ;;
freetype2) echo "libfreetype6-dev" ;;
gd) echo "libgd-dev" ;;
xorg-xrandr) echo "x11-xserver-utils" ;;
xorg-xprop) echo "x11-utils" ;;
xorg-xrdb) echo "x11-xserver-utils" ;;
xorg-xsetroot) echo "x11-xserver-utils" ;;
wmname) echo "suckless-tools" ;;
gtk3) echo "libgtk-3-0" ;;
ttf-jetbrains-mono-nerd) echo "fonts-jetbrains-mono" ;;
noto-fonts) echo "fonts-noto" ;;
noto-fonts-emoji) echo "fonts-noto-color-emoji" ;;
papirus-icon-theme) echo "papirus-icon-theme" ;;
qt6ct) echo "qt6ct" ;;
*) echo "$p" ;;
esac ;;
dnf) case "$p" in
base-devel) echo "@development-tools" ;;
pkgconf) echo "pkgconf-pkg-config" ;;
libx11) echo "libX11-devel" ;;
libxext) echo "libXext-devel" ;;
libxft) echo "libXft-devel" ;;
libxrandr) echo "libXrandr-devel" ;;
libxfixes) echo "libXfixes-devel" ;;
libxinerama) echo "libXinerama-devel" ;;
cairo) echo "cairo-devel" ;;
harfbuzz) echo "harfbuzz-devel" ;;
fontconfig) echo "fontconfig-devel" ;;
freetype2) echo "freetype-devel" ;;
gd) echo "gd-devel" ;;
xorg-xrandr) echo "xrandr" ;;
xorg-xprop) echo "xprop" ;;
xorg-xrdb) echo "xorg-x11-server-utils" ;;
xorg-xsetroot) echo "xsetroot" ;;
wmname) echo "suckless-tools" ;;
ttf-jetbrains-mono-nerd) echo "jetbrains-mono-fonts" ;;
noto-fonts) echo "google-noto-sans-fonts" ;;
noto-fonts-emoji) echo "google-noto-emoji-fonts" ;;
*) echo "$p" ;;
esac ;;
*) echo "$p" ;;
esac
}
install_packages() {
[ "$PM" = none ] && { warn "skipping package install"; return 0; }
local list="$RICE_DIR/packages.txt" repo=() aur=() line pkg
[ -f "$list" ] || { warn "packages.txt not found, skipping"; return 0; }
while IFS= read -r line; do
line="${line%%#*}"; line="$(echo "$line" | tr -d '[:space:]')"
[ -z "$line" ] && continue
if [[ "$line" == aur:* ]]; then aur+=("${line#aur:}")
else repo+=("$(map_pkg "$line")"); fi
done < "$list"
say "installing ${#repo[@]} packages"
case "$PM" in
pacman) run sudo pacman -S --needed --noconfirm "${repo[@]}" || fail_step "some packages failed" ;;
apt) run sudo apt-get update -qq && run sudo apt-get install -y "${repo[@]}" || fail_step "some packages failed" ;;
dnf) run sudo dnf install -y "${repo[@]}" || fail_step "some packages failed" ;;
esac
if [ "$PM" = pacman ] && [ "${#aur[@]}" -gt 0 ]; then
local helper=""
for h in paru yay pikaur; do command -v "$h" >/dev/null 2>&1 && { helper="$h"; break; }; done
if [ -z "$helper" ]; then
warn "no AUR helper; skipping: ${aur[*]}"
note "install paru, then re-run to get them"
else
say "installing ${#aur[@]} AUR packages with $helper"
run "$helper" -S --needed --noconfirm "${aur[@]}" || fail_step "some AUR packages failed"
fi
elif [ "${#aur[@]}" -gt 0 ]; then
warn "not Arch; skipping AUR extras: ${aur[*]}"
fi
}
# ------------------------------------------------------------- dotfiles
dot() { git --git-dir="$DOTS_DIR" --work-tree="$HOME" "$@"; }
deploy_dotfiles() {
if [ -d "$DOTS_DIR" ]; then
ok "dotfiles repo already present"
else
say "cloning dotfiles"
run git clone --bare "$DOTS_REMOTE" "$DOTS_DIR" || { fail_step "dotfiles clone"; return 1; }
fi
run dot config status.showUntrackedFiles no
if [ "$DRY" = 1 ]; then note "would checkout main into \$HOME (backing up conflicts)"; return 0; fi
# Move anything that would be clobbered into a backup instead of losing it.
mkdir -p "$BACKUP"
local conflicts
# git lists the offending paths indented by a tab; anything else it prints
# in that message is flush-left prose. Matching on a dot in the name would
# miss dotfiles like .bashrc, which is exactly what collides on a fresh box.
conflicts="$(dot checkout main 2>&1 | sed -n 's/^[[:space:]]\{1,\}\([^[:space:]].*\)$/\1/p' || true)"
if [ -n "$conflicts" ]; then
say "backing up files that would be overwritten -> $BACKUP"
while IFS= read -r f; do
[ -z "$f" ] && continue
[ -e "$HOME/$f" ] || continue
mkdir -p "$BACKUP/$(dirname "$f")"
mv "$HOME/$f" "$BACKUP/$f" && note "$f"
done <<< "$conflicts"
dot checkout main || { fail_step "dotfiles checkout"; return 1; }
fi
rmdir "$BACKUP" 2>/dev/null
ok "dotfiles deployed"
}
# --------------------------------------------------------------- builds
build_st() {
command -v gcc >/dev/null 2>&1 || { warn "no compiler; skipping st"; return 0; }
mkdir -p "$SRC_DIR"
if [ ! -d "$SRC_DIR/st/.git" ]; then
say "cloning st"
run git clone --depth 1 "$ST_REMOTE" "$SRC_DIR/st" || { fail_step "st clone"; return 1; }
fi
# config.h is gitignored upstream, so the rice bundle carries ours
run cp "$RICE_DIR/st/config.h" "$SRC_DIR/st/config.h" || { fail_step "st config"; return 1; }
say "building st"
run bash -c 'cd "$1" && make clean >/dev/null 2>&1; make' _ "$SRC_DIR/st" || { fail_step "st build"; return 1; }
run bash -c 'cd "$1" && sudo make install' _ "$SRC_DIR/st" || { fail_step "st install"; return 1; }
ok "st installed"
}
build_particles() {
command -v gcc >/dev/null 2>&1 || { warn "no compiler; skipping particles"; return 0; }
pkg-config --exists cairo x11 xext xfixes xrandr 2>/dev/null || {
warn "cairo/X11 dev headers missing; skipping particles"; return 0; }
say "building particles"
run bash -c 'cd "$1" && make clean >/dev/null 2>&1; make' _ "$RICE_DIR/particles" || { fail_step "particles build"; return 1; }
run bash -c 'cd "$1" && sudo make install' _ "$RICE_DIR/particles" || { fail_step "particles install"; return 1; }
ok "particles installed (run 'particles' to toggle)"
}
# ---------------------------------------------------------------- setup
post_setup() {
say "post-setup"
chmod +x "$HOME"/.config/bspwm/bspwmrc 2>/dev/null
chmod +x "$HOME"/.config/polybar/launch.sh 2>/dev/null
chmod +x "$HOME"/.config/scripts/*.sh 2>/dev/null
chmod +x "$HOME"/.config/scripts/rofi-menus/*.sh 2>/dev/null
chmod +x "$HOME"/.config/rice/install.sh 2>/dev/null
[ "$DRY" = 1 ] && { note "would fix permissions, rebuild fonts, set wallpaper"; return 0; }
command -v fc-cache >/dev/null 2>&1 && fc-cache -f >/dev/null 2>&1 && ok "font cache rebuilt"
if command -v gsettings >/dev/null 2>&1; then
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' 2>/dev/null \
&& ok "gtk colour-scheme set to prefer-dark"
fi
if command -v papirus-folders >/dev/null 2>&1; then
papirus-folders -C blue --theme Papirus-Dark >/dev/null 2>&1 && ok "papirus folders set to blue"
fi
if [ -f "$HOME/.config/bspwm/wallpaper/wallpaper.jpg" ] && command -v feh >/dev/null 2>&1; then
feh --bg-fill "$HOME/.config/bspwm/wallpaper/wallpaper.jpg" >/dev/null 2>&1 && ok "wallpaper set"
fi
ok "post-setup done"
}
# ----------------------------------------------------------------- main
printf '\n%s Sakura Line%s - bspwm rice bootstrap\n\n' "$c_hd" "$c_off"
say "$PM_LINE"
[ "$DRY" = 1 ] && say "dry run - nothing will be changed"
install_packages
deploy_dotfiles
build_st
build_particles
post_setup
printf '\n'
if [ "${#STEPS_FAILED[@]}" -eq 0 ]; then
say "done - log out and back into bspwm to pick everything up"
else
say "finished with ${#STEPS_FAILED[@]} problem(s):"
for s in "${STEPS_FAILED[@]}"; do printf ' %s- %s%s\n' "$c_er" "$s" "$c_off"; done
fi
printf '\n %sst%s must be reopened to show new colours; %sparticles%s toggles the wallpaper layer.\n\n' \
"$c_hd" "$c_off" "$c_hd" "$c_off"