#!/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" ;; 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" ;; 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 conflicts="$(dot checkout main 2>&1 | grep -oP '^\s+\K\S+\.\S+$' || 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 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"