From a9ba810bbcef33e0fce06adc412c199bf8a945b3 Mon Sep 17 00:00:00 2001 From: mk Date: Tue, 28 Jul 2026 04:32:38 +0300 Subject: [PATCH] modules: preflight + core packages (arch pacman + apt, yay, gh-release fallback) --- modules/00-preflight.sh | 38 ++++++++++ modules/10-packages.sh | 152 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100755 modules/00-preflight.sh create mode 100755 modules/10-packages.sh diff --git a/modules/00-preflight.sh b/modules/00-preflight.sh new file mode 100755 index 0000000..41cc20f --- /dev/null +++ b/modules/00-preflight.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# 00-preflight — verify we can proceed at all. + +step_name="preflight" +step_desc="verify distro, network, privileges" +step_run="run_preflight" + +run_preflight() { + log "preflight checks" + + if [[ "$DISTRO" == "unknown" ]]; then + err "unsupported distro (could not read ID/ID_LIKE from /etc/os-release)" + err "bootstrap supports debian/ubuntu and arch only." + exit 1 + fi + + if (( EUID == 0 )); then + SUDO="" + export SUDO + else + SUDO="sudo" + export SUDO + if ! sudo -n true 2>/dev/null; then + warn "not root and passwordless sudo unavailable — sudo will prompt per-use" + fi + fi + + if command -v ping >/dev/null 2>&1 && ping -c1 -W2 8.8.8.8 >/dev/null 2>&1; then + : + elif curl -fsS --max-time 3 https://g.mk.fo >/dev/null 2>&1; then + : + else + err "no outbound network — cannot install anything" + exit 1 + fi + + ok "distro=$DISTRO arch=$ARCH user=$USER sudo=${SUDO:-root}" +} \ No newline at end of file diff --git a/modules/10-packages.sh b/modules/10-packages.sh new file mode 100755 index 0000000..4b29d2e --- /dev/null +++ b/modules/10-packages.sh @@ -0,0 +1,152 @@ +#!/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 +} \ No newline at end of file