From dde789ba7890a8406f35a30c7f6cd084a38d87b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20P=C4=99dzim=C4=85=C5=BC?= Date: Sat, 19 Sep 2026 21:13:10 +0200 Subject: [PATCH 1/2] feat: install and update bat like the other tools bat was absent from this repo entirely - not installed, not updated, not tested, and not referenced anywhere - while every comparable tool (rg, fd, jq, zoxide, delta, eza, yazi) was managed. Hosts that needed it were fixed by hand. It follows the fd pattern exactly, including fd's trap: the Debian/Ubuntu package installs the binary as 'batcat', so `has bat` is false and the installer would self-skip forever. Both install_base and install_base_docker create the shim. The docker branch needs it even though its apt list does not ask for bat, because a base image may already carry batcat - without the shim that path would end with no `bat` on PATH at all. test.sh gains a core check (accepting either name) and a strict ~/.local/bin check for the no-sudo profile. tmux joins that strict check too, covering the installer added in 1.12.0; check_local_bin takes a version flag now because tmux prints its usage for --version and that would have been reported as the version string. Verified: ci-local.sh minimal on ubuntu:24.04 (apt path - shim created /root/.local/bin/bat -> /usr/bin/batcat) and nosudo-auto (GitHub path - bat 0.26.1 and tmux 3.5a both strict-checked in ~/.local/bin), both PASS 1/1; test.sh workstation 62/63, 0 failures. Red-proofed: deleting ~/.local/bin/bat fails the suite with 2 findings, deleting tmux with 3. --- AGENTS.md | 6 ++++++ CHANGELOG.md | 6 ++++++ VERSION | 2 +- modules/base.sh | 52 ++++++++++++++++++++++++++++++++++++++++++++++++- test.sh | 18 +++++++++++++++-- update.sh | 20 +++++++++++++++++-- 6 files changed, 98 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e050231..c4b533d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,6 +36,12 @@ fallback and the unapplied-pins path - 28 cells total. Also avoid `[ cond ] && var=true` - when the condition is false, the expression exits with 1 and trips `set -e`. Use `if [ cond ]; then var=true; fi` instead. - Every function variable must be declared `local` (or `local -a` for arrays). +- A tool whose Debian package renames the binary needs a SHIM, or the installer + skips it forever: `has bat` is false when apt supplied `batcat`, exactly as with + `fd`/`fdfind`. Add the shim in the same change as the installer, in BOTH + `install_base` and `install_base_docker` - and note the docker branch needs it + even though its apt list does not ask for the package, since a base image may + already carry the renamed binary. - **Run `bash tests/lint-workflows.sh` before pushing a workflow change.** An invalid workflow runs nothing, so CI cannot report the fault, and with `CI gate` required the PR blocks with no visible cause. It catches the two diff --git a/CHANGELOG.md b/CHANGELOG.md index d4d09ee..fcdd782 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.13.0] - 2026-09-19 + +### Added +- `bat` is now installed and updated like the other tools: apt where available, GitHub binary otherwise, with a `bat` shim when apt supplies `batcat`. `test.sh` checks it, and the no-sudo profile checks it strictly in `~/.local/bin`. +- `test.sh` now also checks tmux strictly in `~/.local/bin` for the no-sudo profile, covering the installer added in 1.12.0. + ## [1.12.0] - 2026-09-19 ### Added diff --git a/VERSION b/VERSION index 0eed1a2..feaae22 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.12.0 +1.13.0 diff --git a/modules/base.sh b/modules/base.sh index d6d6389..8d5efa3 100755 --- a/modules/base.sh +++ b/modules/base.sh @@ -17,7 +17,7 @@ install_base() { jq man-db gnupg python3 python3-venv - ripgrep fd-find tig + ripgrep fd-find tig bat parallel shellcheck ) log_info "Installing via apt: ${_pkgs[*]} (versions resolved by apt)" @@ -48,12 +48,19 @@ install_base() { log_ok "Created fd → fdfind shim in ~/.local/bin" fi + # Same story for bat: Debian/Ubuntu install the binary as 'batcat'. + if ! has bat && has batcat; then + ln -sf "$(command -v batcat)" ~/.local/bin/bat + log_ok "Created bat → batcat shim in ~/.local/bin" + fi + # Cover what apt could not supply (no ripgrep/fd-find before 19.10). # Each installer returns early when the binary is already present, so # this is a no-op on releases where apt carried them. _install_ripgrep _install_fd _install_jq + _install_bat else if $CAN_SUDO; then log_warn "No apt on this system - skipping system packages; fetching tools as local binaries" @@ -70,6 +77,7 @@ install_base() { _install_ripgrep _install_fd _install_jq + _install_bat _install_tmux fi @@ -115,11 +123,18 @@ install_base_docker() { if ! has fd && has fdfind; then ln -sf "$(command -v fdfind)" ~/.local/bin/fd fi + # Needed even though this list does not ask apt for bat: a base image may + # already carry batcat, and _install_bat treats that as installed - without + # the shim there would then be no `bat` on PATH at all. + if ! has bat && has batcat; then + ln -sf "$(command -v batcat)" ~/.local/bin/bat + fi # Cover what apt could not supply; each installer self-skips when present. _install_ripgrep _install_fd _install_jq + _install_bat else log_warn "No apt - skipping system packages; fetching tools as local binaries" # git, zsh and python3 have no practical single-binary fallback, so they @@ -132,6 +147,7 @@ install_base_docker() { _install_ripgrep _install_fd _install_jq + _install_bat _install_tmux fi @@ -461,6 +477,40 @@ _install_ripgrep() { fi } +# bat: GitHub tarball - binary is 'bat'. +# Debian/Ubuntu ship the binary as 'batcat' (the name 'bat' was already taken), +# exactly like fd/fdfind, so both names count as installed and the shim in +# install_base puts a 'bat' on PATH when apt supplied batcat. +_install_bat() { + if has bat || has batcat; then + log_ok "bat already installed - skipping" + return + fi + log_step "bat (GitHub binary)" + local arch; arch=$(uname -m) + local bat_arch + case "$arch" in + x86_64) bat_arch="x86_64-unknown-linux-musl" ;; + aarch64) bat_arch="aarch64-unknown-linux-gnu" ;; + *) + log_warn "bat: unsupported arch $arch - skipping" + return + ;; + esac + local tag="" url="" + read -r tag url < <(_gh_release_info "sharkdp/bat" "${bat_arch}.tar.gz") || true + if [ -z "$url" ]; then + log_warn "bat: could not find release URL - skipping" + return + fi + log_info "bat: installing ${tag:-unknown} → ~/.local/bin/bat" + if _download_tar_bin "$url" "bat" ~/.local/bin/bat; then + log_ok "bat installed → ~/.local/bin/bat ($(~/.local/bin/bat --version 2>/dev/null))" + else + log_warn "bat: download failed - skipping" + fi +} + # tmux: only reachable here as a static AppImage. # # This repo ships .tmux.conf, .tmux.conf.local and clones tmux plugins, so a host diff --git a/test.sh b/test.sh index 2b169ab..7558371 100644 --- a/test.sh +++ b/test.sh @@ -127,6 +127,15 @@ else _fail "fd / fdfind not found" fi +# bat has the same Debian/Ubuntu rename as fd: the binary ships as 'batcat'. +if command -v bat &>/dev/null; then + _ok "bat → $(command -v bat) ($(bat --version 2>&1 | head -1))" +elif command -v batcat &>/dev/null; then + _ok "bat (as batcat) → $(command -v batcat)" +else + _fail "bat / batcat not found" +fi + # ── 4. fzf shell integration ─────────────────────────────────────────────────── _hdr "fzf shell integration" # fzf is installed via git clone to ~/.fzf; the installer generates ~/.fzf.zsh @@ -393,10 +402,12 @@ if [ "$PROFILE" = "nosudo" ]; then _hdr "No-sudo: ~/.local/bin binaries" check_local_bin() { - local cmd="$1" label="${2:-$1}" + # $3 is the version flag: tmux answers -V and prints its usage for + # --version, which would otherwise be reported as the "version". + local cmd="$1" label="${2:-$1}" vflag="${3:---version}" local p="$HOME/.local/bin/$cmd" if [ -x "$p" ]; then - _ok "$label → $p ($("$p" --version 2>&1 | head -1))" + _ok "$label → $p ($("$p" "$vflag" 2>&1 | head -1))" else # Strict: must be in ~/.local/bin - not just anywhere on PATH. # For nosudo-forced this verifies NOSUDO=1 was respected (sudo was @@ -407,12 +418,15 @@ if [ "$PROFILE" = "nosudo" ]; then check_local_bin rg "ripgrep" check_local_bin fd "fd" + check_local_bin bat "bat" check_local_bin jq "jq" check_local_bin fzf "fzf" check_local_bin zoxide "zoxide" check_local_bin delta "git-delta" check_local_bin eza "eza" check_local_bin yazi "yazi" + # tmux is a no-sudo install too since 1.12.0 (static AppImage). + check_local_bin tmux "tmux" -V _hdr "No-sudo: sudo availability" # nosudo-auto: sudo binary absent -> detect_sudo() auto-detected CAN_SUDO=false diff --git a/update.sh b/update.sh index c448d29..68fe23a 100755 --- a/update.sh +++ b/update.sh @@ -8,7 +8,7 @@ # --help, -h Show this message # # Available tools (pass one or more to update only those): -# apt omz tmux-plugins zsh-plugins fzf rg fd shellcheck +# apt omz tmux-plugins zsh-plugins fzf rg fd bat shellcheck # zoxide delta eza yazi uv ruff neovim tree-sitter cheat pre-commit xcape # # A PATH shadow check always runs at the end (read-only). It detects older @@ -49,7 +49,7 @@ while [[ $# -gt 0 ]]; do shift done -_KNOWN_TOOLS=(apt omz tmux-plugins zsh-plugins fzf rg fd shellcheck +_KNOWN_TOOLS=(apt omz tmux-plugins zsh-plugins fzf rg fd bat shellcheck zoxide delta eza yazi uv ruff neovim tree-sitter cheat pre-commit xcape) # Validate SELECTED against known tool names. @@ -494,6 +494,22 @@ if _should_run fd; then fi fi +# ── bat ──────────────────────────────────────────────────────────────────────── +# Same special case as fd: Debian/Ubuntu install the binary as 'batcat', so both +# names count as present and the updated binary always lands at ~/.local/bin/bat. +if _should_run bat; then + log_step "bat" + if has bat || has batcat; then + case "$ARCH" in + x86_64) _gh_update_binary bat "sharkdp/bat" "x86_64-unknown-linux-musl.tar.gz" bat ~/.local/bin/bat || true ;; + aarch64) _gh_update_binary bat "sharkdp/bat" "aarch64-unknown-linux-gnu.tar.gz" bat ~/.local/bin/bat || true ;; + *) log_warn "bat: unsupported arch $ARCH - skipping" ;; + esac + else + log_warn "bat not installed - skipping" + fi +fi + # ── shellcheck ───────────────────────────────────────────────────────────────── # Uses bare arch names (x86_64/aarch64), not Rust triples - keep its own section. if _should_run shellcheck; then From 36c6e191d27a1c0f47d3370233cde93bc1607e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20P=C4=99dzim=C4=85=C5=BC?= Date: Sat, 19 Sep 2026 21:18:01 +0200 Subject: [PATCH 2/2] test: report tmux's real version in the core check too check_cmd hardcoded --version, and tmux answers that with its usage, so the suite printed 'usage: tmux [-2CDlNuVv]...' where a version belongs - visible in CI right next to the strict check that had already been fixed. Same optional version-flag parameter as check_local_bin; every other caller keeps the --version default. --- test.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test.sh b/test.sh index 7558371..1dd9275 100644 --- a/test.sh +++ b/test.sh @@ -29,9 +29,11 @@ _hdr() { echo -e "\n${BOLD}── $* ──${NC}"; } # ── Helpers ─────────────────────────────────────────────────────────────────── check_cmd() { - local cmd="$1" label="${2:-$1}" + # $3 is the version flag, for tools that do not answer --version: tmux prints + # its usage instead, which was being reported as the version string. + local cmd="$1" label="${2:-$1}" vflag="${3:---version}" if command -v "$cmd" &>/dev/null; then - _ok "$label → $(command -v "$cmd") ($(${cmd} --version 2>&1 | head -1))" + _ok "$label → $(command -v "$cmd") ($("$cmd" "$vflag" 2>&1 | head -1))" else _fail "$label not found" fi @@ -109,7 +111,7 @@ check_link ~/.gitattributes # ── 3. Core tools ────────────────────────────────────────────────────────────── _hdr "Core tools" check_cmd zsh -check_cmd tmux +check_cmd tmux "tmux" -V check_cmd git check_cmd python3 check_cmd fzf