Add pre-push gate, container smoke test, README docs; drop vendored gum.md

- hooks/pre-push: bash -n + shellcheck -S warning -e SC1090 on every
  tracked *.sh; fails closed when shellcheck is missing, bypassable with
  --no-verify; enable with 'git config core.hooksPath hooks' (documented)
- tests/smoke.sh: manual docker/podman Arch-container run of bat.sh,
  zsh.sh and fish.sh with assertions (idempotent bat config, timestamped
  zsh backups, fisher actually installed in fish); LazyVim.sh opt-in.
  This catches the inverted-condition/quoting class shellcheck cannot.
  fish.sh gains a SKIP_END4_SETUP=1 test hook to skip end-4's installer
- README: scripts table (documents installbeta.sh vs install.sh and why
  SecureBoot.sh is not in the menu), apt full-upgrade note, development
  section; claims audited against current behavior (gum wording already
  correct, no whiptail/selection claims remain)
- remove untracked vendored gum.md (reference only, per decision)
This commit is contained in:
alvinlollo
2026-10-08 14:19:32 +11:00
parent bca2dd45ba
commit f8bb57f666
4 changed files with 218 additions and 5 deletions
+29
View File
@@ -29,6 +29,35 @@ For installing zsh and themes (also works on Debian based systems):
curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/zsh.sh | bash
```
> **Note:** on Debian-based systems the installer runs `apt full-upgrade -y` as part of its prerequisites. On Arch it installs prerequisites through shelly without upgrading the whole system.
## Scripts
| Script | Purpose |
| -------------- | ----------------------------------------------------------------------- |
| `install.sh` | Main menu (gum) that dispatches to the scripts below. |
| `installbeta.sh` | Same menu, but logs every step and records failures instead of aborting, then exits non-zero if anything failed. This is the next `install.sh` — once tested it replaces it. |
| `zsh.sh` | zsh + oh-my-zsh + plugins and my config (also works on Debian). |
| `fish.sh` | fish + Fisher plugins + end-4 dotfiles config. |
| `LazyVim.sh` | My LazyVim neovim config. |
| `bat.sh` | bat + Catppuccin Mocha theme + MANPAGER setup. |
| `SecureBoot.sh`| **Not in the menu** (intentional): manual UEFI-only Secure Boot setup with `sbctl`. It verifies every sign target and asks before enrolling keys — read it before running. |
## Development
Enable the pre-push gate once per clone (runs `bash -n` and `shellcheck` on every tracked `*.sh`):
```bash
git config core.hooksPath hooks
```
There is also a manual end-to-end smoke test that runs the installers in a throwaway Arch container (needs docker or podman, network access; **not** part of the hook):
```bash
tests/smoke.sh # bat.sh, zsh.sh, fish.sh
tests/smoke.sh LazyVim.sh # opt in to the heavy LazyVim run
```
## Zsh configuration
My Zsh configuration includes the following plugins run through [oh-my-zsh](https://ohmyz.sh/): git, [zsh-history-substring-search](https://github.com/zsh-users/zsh-history-substring-search), [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions), [zsh-autocomplete](https://github.com/marlonrichert/zsh-autocomplete), [fzf-zsh-plugin](https://github.com/unixorn/fzf-zsh-plugin) and [zsh-syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting.git)
+7 -1
View File
@@ -98,13 +98,19 @@ if [ -n "$SCRIPT_DIR" ] && [ -d "$SCRIPT_DIR/configs" ]; then
done
fi
# Run end-4 setup script with flags to skip unnecessary components
# Run end-4 setup script with flags to skip unnecessary components.
# SKIP_END4_SETUP=1 is a test hook used by tests/smoke.sh to validate this
# script's own logic without running end-4's heavyweight installer.
echo "WARNING: the end-4 installer runs with --force and --skip-backup;"
echo "it may overwrite an existing Hyprland configuration without a backup."
if [ "${SKIP_END4_SETUP:-0}" = "1" ]; then
echo "(SKIP_END4_SETUP=1 - skipping the end-4 installer)"
else
(
cd "$WORK_DIR/dots-hyprland"
./setup install --force --skip-plasmaintg --skip-backup --skip-quickshell --skip-hyprland --skip-hyprland-entry
)
fi
# Resolve a config file: prefer this repo's copy, fall back to the end-4 clone
cfg_src() {
Executable
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# Pre-push gate: syntax-check and shellcheck every tracked shell script.
# Enable once per clone with: git config core.hooksPath hooks
# Bypass a single push with: git push --no-verify
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
mapfile -t scripts < <(git ls-files '*.sh')
if [ "${#scripts[@]}" -eq 0 ]; then
echo "pre-push: no tracked shell scripts found" >&2
exit 0
fi
status=0
for f in "${scripts[@]}"; do
if ! bash -n "$f"; then
echo "pre-push: bash -n FAILED for $f" >&2
status=1
fi
done
if ! command -v shellcheck >/dev/null; then
echo "pre-push: shellcheck is not installed - the gate cannot run." >&2
echo " install it (e.g. 'pacman -S shellcheck' or 'apt install shellcheck')" >&2
echo " or bypass this once with: git push --no-verify" >&2
exit 1
fi
# SC1090 (can't follow non-constant source) is excluded: every 'source ~/.bashrc'
# style call in these scripts triggers it and it is not actionable.
if ! shellcheck -S warning -e SC1090 "${scripts[@]}"; then
echo "pre-push: shellcheck reported problems (see above)" >&2
status=1
fi
if [ "$status" -eq 0 ]; then
echo "pre-push: all ${#scripts[@]} shell scripts pass bash -n + shellcheck"
fi
exit "$status"
Executable
+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/env bash
# Manual end-to-end smoke test. NOT part of the pre-push hook - run by hand:
#
# tests/smoke.sh # default set: bat.sh zsh.sh fish.sh
# tests/smoke.sh LazyVim.sh # opt in to the heavy LazyVim run
# tests/smoke.sh bat.sh # run specific scripts only
#
# Runs each script inside a throwaway Arch container with a stubbed 'sudo'
# (the container is already root). Needs docker or podman and network access.
# This is what catches the bug class shellcheck cannot: inverted conditions,
# swapped shell branches, quoting that collapses when written to a file.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
if command -v docker >/dev/null 2>&1; then
RUNTIME=docker
elif command -v podman >/dev/null 2>&1; then
RUNTIME=podman
else
echo "error: neither docker nor podman found - cannot run the smoke test." >&2
exit 1
fi
DEFAULT_SCRIPTS=(bat.sh zsh.sh fish.sh)
if [ "$#" -gt 0 ]; then
SCRIPTS=("$@")
else
SCRIPTS=("${DEFAULT_SCRIPTS[@]}")
fi
# Validate arguments up front so a typo does not run in a container first
for s in "${SCRIPTS[@]}"; do
if [ ! -f "$ROOT/$s" ]; then
echo "error: $s not found in $ROOT" >&2
exit 1
fi
done
# Everything inside the container:
# - copy the read-only mount so scripts that write to their own directory
# (fish.sh syncing configs/) cannot mutate the host checkout
# - stub 'sudo' -> exec (container already runs as root)
# - pacman -Sy so package lookups work on a fresh image
# - HOME=/root keeps every script writing inside the container
container_run() {
local body="$1"
"$RUNTIME" run --rm \
-v "$ROOT:/src:ro" \
-e HOME=/root \
archlinux:latest \
bash -c "
set -euo pipefail
cp -r /src /work
cd /work
printf '#!/bin/sh\nexec \"\$@\"\n' > /usr/local/bin/sudo
chmod +x /usr/local/bin/sudo
pacman -Sy --noconfirm >/dev/null 2>&1 || true
$body
"
}
declare -a RESULTS=()
overall=0
run_test() {
local script="$1" body="$2"
echo ""
echo "=== smoke: $script ==="
if container_run "$body"; then
echo "=== PASS: $script ==="
RESULTS+=("PASS $script")
else
echo "=== FAIL: $script (exit $?)" >&2
RESULTS+=("FAIL $script")
overall=1
fi
}
for script in "${SCRIPTS[@]}"; do
case "$script" in
bat.sh)
run_test "$script" '
bash bat.sh --skip-watermark
command -v bat >/dev/null || { echo "bat not on PATH after install"; exit 1; }
grep -qxF -- "--theme=\"Catppuccin Mocha\"" "$(bat --config-dir)/config" \
|| { echo "theme line missing from bat config:"; cat "$(bat --config-dir)/config" 2>/dev/null; exit 1; }
[ -s "$(bat --config-dir)/themes/Catppuccin Mocha.tmTheme" ] \
|| { echo "theme file was not downloaded"; exit 1; }
# re-run must be idempotent (no duplicate config lines)
bash bat.sh --skip-watermark
[ "$(grep -cF -- "--theme=" "$(bat --config-dir)/config")" -eq 1 ] \
|| { echo "bat config is not idempotent"; exit 1; }
'
;;
zsh.sh)
run_test "$script" '
bash zsh.sh --skip-watermark
[ -f /root/.zshrc ] || { echo ".zshrc missing"; exit 1; }
[ -d /root/.oh-my-zsh/custom/plugins/zsh-autosuggestions ] || { echo "plugins missing"; exit 1; }
# second run must rotate the backup, not overwrite it
bash zsh.sh --skip-watermark
[ "$(find /root -maxdepth 1 -name ".zshrc.bak-*" | wc -l)" -ge 2 ] \
|| { echo "backup rotation failed"; exit 1; }
'
;;
fish.sh)
run_test "$script" '
export SKIP_END4_SETUP=1
bash fish.sh --skip-watermark
[ -f /root/.config/fish/config.fish ] || { echo "fish config not installed"; exit 1; }
fish -c "type -q fisher" || { echo "fisher not installed in fish"; exit 1; }
# the fixed prerequisite check must have installed fish itself
command -v fish >/dev/null || { echo "fish missing"; exit 1; }
'
;;
LazyVim.sh)
run_test "$script" '
bash LazyVim.sh
[ -d /root/.config/nvim ] || { echo "nvim config not cloned"; exit 1; }
command -v node >/dev/null || { echo "no node after install"; exit 1; }
# timestamped backup only if a config existed; none here, so just assert
# the clone exists and nothing is named plain nvim.bak
[ ! -e /root/.config/nvim.bak ] || { echo "legacy .bak name used"; exit 1; }
'
;;
*)
echo "error: unknown smoke test '$script' (known: bat.sh zsh.sh fish.sh LazyVim.sh)" >&2
exit 1
;;
esac
done
echo ""
echo "=== smoke summary ==="
printf '%s\n' "${RESULTS[@]}"
exit "$overall"