#!/usr/bin/env bash # 10-packages — core CLI utilities, per distro. step_name="packages" step_desc="git byobu ufw mosh btop eza fzf ripgrep bat zoxide jq fd ..." step_run="run_packages" # Tools we want everywhere (names canonical, mapped per-distro). PKGS_COMMON=( git byobu ufw mosh btop fzf ripgrep fd bat dust procs jq yq tree ncdu duf tldr httpie rsync lazygit neovim curl unzip less starship eza zoxide openssh ) run_packages() { log "installing core packages ($DISTRO)" case "$DISTRO" in arch) _pkg_arch ;; debian) _pkg_debian ;; esac } # ── arch ───────────────────────────────────────────────────────────────────── _pkg_arch() { sudo_ pacman -Sy --noconfirm --needed base-devel openssh # most tools live in extra/community (the merged repos); names map 1:1 local p=(git byobu ufw mosh btop fzf ripgrep fd bat dust procs jq yq tree ncdu duf tldr httpie rsync lazygit neovim curl unzip less starship eza zoxide openssh) sudo_ pacman -Sy --noconfirm --needed "${p[@]}" _install_yay # fd/bat ship with their real names on arch — no shims needed } _install_yay() { if command -v yay >/dev/null 2>&1; then ok "yay already installed"; return; fi log "building yay from AUR (requires a non-root sudoer)" if (( EUID == 0 )) && [[ -z "${SUDO_USER:-}" ]]; then err "running as root with no SUDO_USER — yay cannot build. Re-run bootstrap as a normal user with sudo." return 1 fi local tmp; tmp="$(mktemp -d)" as_user git clone --depth 1 https://aur.archlinux.org/yay.git "$tmp/yay" ( cd "$tmp/yay" && as_user makepkg -si --noconfirm ) rm -rf "$tmp" command -v yay >/dev/null 2>&1 && ok "yay installed" || warn "yay install may need a manual pass" } # ── debian/ubuntu ──────────────────────────────────────────────────────────── _pkg_debian() { sudo_ apt-get update -y sudo_ apt-get install -y ca-certificates curl gnupg lsb-release unzip less \ git byobu ufw mosh btop fzf ripgrep jq tree ncdu rsync neovim curl \ openssh-server sudo # rename packages: fd -> fdfind, bat -> batcat (fix via aliases in zsh) sudo_ apt-get install -y fdfind batcat 2>/dev/null || true # eza via the gierens PPA (debs not in base apt) if ! command -v eza >/dev/null 2>&1; then sudo_ install -m 0755 -d /etc/apt/keyrings curl -fsSL https://raw.githubusercontent.com/eza-community/eza/main/deb.asc \ | sudo_ tee /etc/apt/keyrings/gierens.gpg >/dev/null echo "deb [signed-by=/etc/apt/keyrings/gierens.gpg] http://apt.fury.io/eza/ /" \ | sudo_ tee /etc/apt/sources.list.d/eza.list >/dev/null sudo_ apt-get update -y && sudo_ apt-get install -y eza || warn "eza PPA failed" fi # starship: official installer if ! command -v starship >/dev/null 2>&1; then curl -sS https://starship.rs/install.sh | sh -s -- -y || warn "starship install failed" fi # zoxide: official installer if ! command -v zoxide >/dev/null 2>&1; then curl -sS https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | bash || warn "zoxide install failed" fi # yq: direct binary from mikefarah/yq releases (stable URL) if ! command -v yq >/dev/null 2>&1; then local suffix="amd64"; [[ "$ARCH" == "arm64" ]] && suffix="arm64" sudo_ wget -qO /usr/local/bin/yq "https://github.com/mikefarah/yq/releases/latest/download/yq_linux_${suffix}" \ && sudo_ chmod +x /usr/local/bin/yq || warn "yq install failed" fi # tldr on debian: python3 package if present, else skip (npm-based out of scope here) if ! command -v tldr >/dev/null 2>&1; then sudo_ apt-get install -y tldr 2>/dev/null || warn "tldr not packaged on this release — skipped (install via npm later)" fi # rest via GitHub release tarballs where missing (best-effort, arch auto-detected) _gh_release_binary bootandy/dust dust _gh_release_binary muesli/duf duf _gh_release_binary dalance/procs procs _gh_release_binary jesseduffield/lazygit lazygit # fd/bat on debian come from apt as fdfind/batcat (shimmed below) _apt_aliases_fixup } # generic best-effort: fetch latest release archive matching host arch, # extract a single binary into /usr/local/bin. Warns and continues on failure. _gh_release_binary() { local repo="$1" binname="$2" command -v "$binname" >/dev/null 2>&1 && { ok "$binname already present"; return; } local api="https://api.github.com/repos/$repo/releases/latest" local archpat case "$ARCH" in amd64) archpat='(x86_64|amd64)' ;; arm64) archpat='(aarch64|arm64)' ;; *) archpat="$ARCH" ;; esac local url url_line url_line=$(curl -fsSL "$api" \ | grep -oE '"browser_download_url":\s*"[^"]+"' \ | sed -E 's/.*"([^"]+)"/\1/' \ | grep -Ei "\\.(tar\\.gz|zip)$" \ | grep -Ei "$archpat" \ | grep -Ei 'linux|musl|gnu|unknown' \ | grep -viE 'sig|sha|checksum|deb|rpm|apk|msi|darwin|windows' \ | head -1) url="$url_line" if [[ -z "$url" ]]; then warn "could not resolve $binname release asset for $ARCH — skipped"; return; fi local tmp; tmp="$(mktemp -d)" if ! curl -fsSL "$url" -o "$tmp/asset"; then warn "download $binname failed"; rm -rf "$tmp"; return; fi case "$url" in *.tar.gz|*.tgz) tar -xzf "$tmp/asset" -C "$tmp" ;; *.zip) unzip -oq "$tmp/asset" -d "$tmp" ;; esac local found found=$(find "$tmp" -type f -name "$binname" -perm -u+x | head -1) [[ -z "$found" ]] && found=$(find "$tmp" -type f -name "$binname" | head -1) if [[ -n "$found" ]]; then sudo_ install -m 0755 "$found" "/usr/local/bin/$binname" ok "$binname installed from GitHub release" else warn "$binname binary not found in archive — skipped" fi rm -rf "$tmp" } # fd/bat: apt ships fdfind/batcat on debian; create canonical symlinks in ~/.local/bin. _apt_aliases_fixup() { local bin="$HOME/.local/bin"; mkdir -p "$bin" if command -v fdfind >/dev/null 2>&1 && ! command -v fd >/dev/null 2>&1; then ln -sf "$(command -v fdfind)" "$bin/fd" fi if command -v batcat >/dev/null 2>&1 && ! command -v bat >/dev/null 2>&1; then ln -sf "$(command -v batcat)" "$bin/bat" fi }