main/modules/10-packages.sh

174 lines
7.2 KiB
Bash
Executable File

#!/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 ─────────────────────────────────────────────────────────────────────
# official-repo packages (verified in extra/core). Any name not in the official
# repos would abort the whole `pacman --needed` batch, so keep this list clean
# and route AUR-only tools through _install_aur_packages instead.
ARCH_OFFICIAL=(git 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)
# AUR-only packages (not in official repos) — installed through yay after build.
ARCH_AUR=(byobu)
_pkg_arch() {
sudo_ pacman -Sy --noconfirm --needed base-devel openssh
sudo_ pacman -Sy --noconfirm --needed "${ARCH_OFFICIAL[@]}"
_install_yay
_install_aur_packages
# 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 "${TARGET_USER:-${SUDO_USER:-}}" ]]; then
err "running as root with no target/sudo user — yay cannot build. Run the create-user step first, or re-run bootstrap as a normal user with sudo."
return 1
fi
local tmp; tmp="$(mktemp -d)"
# chown the build dir so the non-root build user can write into it
if (( EUID == 0 )); then
chown -R "${TARGET_USER:-${SUDO_USER}}" "$tmp"
fi
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"
}
_install_aur_packages() {
[[ "${#ARCH_AUR[@]}" -eq 0 ]] && return
if ! command -v yay >/dev/null 2>&1; then
warn "yay not available — skipping AUR packages: ${ARCH_AUR[*]}"
return
fi
log "installing AUR-only packages: ${ARCH_AUR[*]}"
# yay -S as the non-root target user (makepkg refuses root; TARGET_USER via as_user).
as_user yay -S --noconfirm --needed "${ARCH_AUR[@]}" \
|| warn "some AUR packages failed: ${ARCH_AUR[*]} (install manually with: yay -S ${ARCH_AUR[*]})"
}
# ── 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
}