Compare commits

...
11 Commits
Author SHA1 Message Date
alvinlollo 8320546ad4 Fix fisher conflicts and stale end-4 paths found by the smoke test
First real run of tests/smoke.sh surfaced three issues:

- fish.sh: repo configs/ ships fisher.fish and nvm.fish functions, so the
  new (previously always-broken) 'fisher install' hit fisher's own
  conflict check and aborted. Now: check 'type -q fisher' / 'type -q nvm'
  inside fish and only bootstrap what is actually missing.
- fish.sh: end-4 renamed hypr/hyprland/colors.conf to colors.lua and
  moved hyprlock colors under hypr/hyprlock/ - the stale paths made the
  old script abort on cp. Updated paths, warn-and-skip stays as the
  safety net if upstream moves again.
- LazyVim.sh: same fisher conflict class in its fish branch - guard the
  nvm.fish install with a 'type -q nvm' check.
- tests/smoke.sh: the zsh backup assertion was wrong (fresh container has
  no .zshrc for run 1 to back up). Pre-seed a marker config instead and
  assert both rotation (2 backups) and that neither run destroys the
  original content.

Verified: tests/smoke.sh fish.sh zsh.sh -> PASS/PASS, bat.sh -> PASS;
pre-push hook green on all 9 scripts.
2026-10-08 14:26:56 +11:00
alvinlollo f8bb57f666 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)
2026-10-08 14:19:32 +11:00
alvinlollo bca2dd45ba zsh.sh nits: backup rotation, guarded rm, --needed, drop dead placeholder
- timestamped .zshrc backups (was: .zshrc.bak overwritten every run, so
  only the latest pre-run state survived)
- only remove ~/.oh-my-zsh when it actually contains oh-my-zsh.sh; warn
  and leave anything else alone (was: unguarded rm -rf)
- add --needed to the pacman prerequisite install
- drop the empty ~/.fzf/shell/key-bindings.zsh placeholder (nothing
  sourced it; fzf integration comes from the oh-my-zsh plugin)
- LICENSE spelling in banners (zsh.sh, installbeta.sh)
2026-10-08 14:16:37 +11:00
alvinlollo a012aeab9c installbeta.sh UX: docker group notice, drop fake command echoes and sleeps
- print a 'log out and back in' notice after usermod -aG docker (both
  fresh-install and already-installed branches; previously missing)
- remove the unexplained sleep 10 in the already-installed docker branch
- remove 'echo "+ sleep 10" && sleep 10' fake-command lines in failure
  branches (record_failure already reports the failure)
- affinity keeps its 10s read-the-instructions delay but announces it
  honestly instead of echoing a fake '+ 10 sleep' command
2026-10-08 14:15:00 +11:00
alvinlollo 23915c301a Strict mode from the start, contextual errors, non-zero exit in installbeta.sh
- set -Eeuo pipefail at the top (was only enabled after the menu, so
  package installs and shelly bootstrap ran unchecked); preamble audited
  for guarded vs unguarded commands ([ -t 1 ] && ... became an if, clear
  is now 'clear || true')
- error_handler now reports exit status, line number and the failing
  command, and the ERR trap is installed after strict mode so preamble
  failures abort instead of printing a message and continuing
- exit 1 when any step was recorded as failed (was: always exit 0)
- single STAMP shared by LOG and REPORT; both created via mktemp
  (predictable /tmp log names + tee -a was the same symlink class)
- local script/config fallbacks resolved via BASH_SOURCE (SCRIPT_DIR),
  not the caller's CWD; falls back to remote fetch when piped
- $? captured via rc=$? inside each failure branch (idempotent under
  reordering) for all record_failure call sites
- ${1:-} for the flag check, ${SHELL:-} in the fish detection,
  tty-guarded fish warning prompt
2026-10-08 14:13:57 +11:00
alvinlollo 0d605940b4 Harden installbeta.sh: temp workspace, curl -f, gum menu fallback
- mktemp -d workspace (trap-cleaned) for the shelly AUR clone, shelly toml
  downloads, docker installer and affinity script - no more predictable
  /tmp/shelly_install, /tmp/shelly-standard.toml, /tmp/shelly-aur.toml
- download get.docker.sh and AffinityLinuxInstaller.py with curl -fsSL to a
  file instead of piping to sh/python3 (the python pipe was missing -f, so an
  HTTP 404 body was executed as Python)
- plain numbered menu fallback when gum is unavailable (Fedora has neither
  shelly nor apt, so gum was never installed and 'gum choose' failed with a
  misleading 'User cancelled installation.')
- $? capture via rc=$? inside the failure branch (safe under reordering)
2026-10-08 14:10:43 +11:00
alvinlollo 696badb67a Fix LazyVim.sh shell branches, backups, and nvm detection
- swap the crossed branch tests (fish branch checked ~/.bashrc, bash branch
  checked ~/.config/fish/config.fish - each never matched its shell)
- install fisher/nvm.fish via 'fish -c' instead of calling fisher from bash
- timestamped backups (nvim.bak-YYYYmmdd-HHMMSS) instead of rm -rf of all
  *.bak dirs first - a re-run no longer destroys the original backup
- check git clone result and point at the preserved backup on failure
- load nvm by sourcing its init script (nvm is a function; 'command -v nvm'
  never succeeded, so the whole block was dead code)
- guard nvm init sourcing under set +u, fall back to system nodejs
- idempotent rc appends (grep guard), tty-guarded Ctrl+C grace period
- set -euo pipefail with guarded/unguarded audit
- typos: prerequisites, secconds
2026-10-08 14:08:20 +11:00
alvinlollo 99d94ab615 Fix fish.sh prerequisites, fisher install, and CWD-dependent paths
- prerequisite condition tested fish (was inverted: 'zsh missing and
  git/curl/fzf present', so fish was never installed on a working machine)
- run fisher bootstrap inside fish via 'fish -c' (was: 'curl | source' in
  bash, which always failed with 'filename argument required')
- download fisher.fish with curl -fsSL to a temp file first (fail on HTTP error)
- resolve configs/ via BASH_SOURCE; when piped (curl | bash) fall back to
  the end-4 clone instead of failing on relative paths from an arbitrary CWD
- clone dots-hyprland into mktemp -d, never into the caller's CWD; git -C
  style usage so a failed pull can't strand the script's working directory
- set -euo pipefail from the top with a guarded/unguarded audit
- warn visibly that end-4 still runs with --force --skip-backup (kept per decision)
- mkdir -p before config copies; tty-guarded fish warning; ${1:-}
2026-10-08 14:06:51 +11:00
alvinlollo 49e00110bf Fix bat.sh config target, theme name, MANPAGER quoting, shell detection
- set -euo pipefail with audit: every tolerated failure is guarded
- write --theme to bat's config file, not into the themes/ directory (was: append to a directory, failed silently)
- download and configure the same theme (Catppuccin Mocha; was: Macchiato downloaded, Mocha configured)
- preserve MANPAGER quoting via printf (was: collapsed to export MANPAGER=bat -plman, invalid when sourced)
- detect shell via $SHELL instead of $0 (was: always matched *) and reported 'Unsupported shell: bat.sh')
- fish branch appends fish syntax, no more exec of a mangled command string
- check pacman/apt bat install results (was: unchecked, continued on failure)
- idempotent appends (grep guard), tty-guarded fish warning
- remove broken rc-sourcing block (sourced .zshrc/config.fish from bash)
2026-10-08 14:04:49 +11:00
alvinlollo e215b85d90 Harden SecureBoot.sh: UEFI check, verify paths, confirm before enroll
- set -euo pipefail and fix pacman --noconfirm typo (was -noconfirm)
- abort on non-UEFI systems (/sys/firmware/efi check)
- verify sbctl installed and sign targets exist before touching keys
- fix x84_64-efi typo, skip missing targets instead of failing silently
- require sbctl verify to pass before offering enrollment
- prompt y/N before enroll-keys with BIOS/USB recovery guidance
- gate success message on final sbctl verify, non-zero exit on failure
2026-10-08 14:03:31 +11:00
alvinlollo a38a007ce3 Migrate menu from whiptail to gum with label-based dispatch 2026-10-08 14:02:43 +11:00
10 changed files with 961 additions and 384 deletions
+100 -79
View File
@@ -1,14 +1,18 @@
#!/usr/bin/bash #!/usr/bin/bash
set -euo pipefail
# Detect fish shell and ask user to switch to bash (bash-only syntax) # Detect fish shell and ask user to switch to bash (bash-only syntax)
if [ -n "${FISH_VERSION:-}" ] || case "$SHELL" in *fish*) ;; *) false ;; esac; then if [ -n "${FISH_VERSION:-}" ] || case "${SHELL:-}" in *fish*) ;; *) false ;; esac; then
echo "Warning: fish shell detected." echo "Warning: fish shell detected."
echo "This script is written for bash and may have syntax issues under fish." echo "This script is written for bash and may have syntax issues under fish."
echo "Please switch to bash first and re-run it, e.g.: bash $0" echo "Please switch to bash first and re-run it, e.g.: bash $0"
if [ -t 0 ] && [ -e /dev/tty ]; then
read -r -p "Press Enter to continue anyway, or Ctrl+C to cancel..." </dev/tty || true read -r -p "Press Enter to continue anyway, or Ctrl+C to cancel..." </dev/tty || true
fi fi
fi
# Install prerequisetes # Install prerequisites
if command -v pacman >/dev/null; then if command -v pacman >/dev/null; then
echo "pacman detected. Installing prerequisites" echo "pacman detected. Installing prerequisites"
# sclip is not packaged for arch, tree-sitter-cli ships the tree-sitter binary # sclip is not packaged for arch, tree-sitter-cli ships the tree-sitter binary
@@ -48,61 +52,34 @@ elif command -v apt >/dev/null; then
else else
echo "Cannot proceed: pacman or apt is required" echo "Cannot proceed: pacman or apt is required"
echo "This script supports arch based and debian based systems" echo "This script supports arch based and debian based systems"
echo "+ sleep 10" && sleep 10
exit 1 exit 1
fi fi
echo "" echo ""
echo 'This script will move your current nvim config to ~/.config/nvim.bak' echo 'This script will back up your current nvim config with a timestamped name, e.g. ~/.config/nvim.bak-20261008-130000'
echo "Press CTRL+C within 10 secconds if you don't want these actions to be made" if [ -t 0 ]; then
echo "Press CTRL+C within 10 seconds if you don't want these actions to be made"
sleep 10 sleep 10
fi
echo "" echo ""
# Remove old backups # Back up current nvim directories with a timestamp so a re-run never
# Remove existing .bak directories first to ensure a clean slate # destroys the user's original backup (old behaviour deleted *.bak first)
echo " Checking for and removing old Neovim backup directories..." STAMP="$(date +%Y%m%d-%H%M%S)"
# Use $HOME for reliable path expansion backup_if_exists() {
if [ -e "$HOME/.config/nvim.bak" ]; then local src="$1"
echo "Path $HOME/.config/nvim.bak exists. Removing..." if [ -e "$src" ]; then
rm -rf "$HOME/.config/nvim.bak" echo " Backing up $src -> ${src}.bak-$STAMP"
mv "$src" "${src}.bak-$STAMP"
fi fi
}
if [ -e "$HOME/.local/share/nvim.bak" ]; then
echo "Path $HOME/.local/share/nvim.bak exists. Removing..."
rm -rf "$HOME/.local/share/nvim.bak"
fi
if [ -e "$HOME/.local/state/nvim.bak" ]; then
echo "Path $HOME/.local/state/nvim.bak exists. Removing..."
rm -rf "$HOME/.local/state/nvim.bak"
fi
if [ -e "$HOME/.cache/nvim.bak" ]; then
echo "Path $HOME/.cache/nvim.bak exists. Removing..."
rm -rf "$HOME/.cache/nvim.bak"
fi
# Remove the current nvim config and create a new backup
echo ""
echo " Backing up current Neovim configuration..." echo " Backing up current Neovim configuration..."
if [ -d "$HOME/.config/nvim" ]; then # Check if nvim config exists before moving backup_if_exists "$HOME/.config/nvim"
mv "$HOME/.config/nvim" "$HOME/.config/nvim.bak" backup_if_exists "$HOME/.local/share/nvim"
else backup_if_exists "$HOME/.local/state/nvim"
echo "No existing ~/.config/nvim found to back up." backup_if_exists "$HOME/.cache/nvim"
fi
echo ""
echo " Backing up Neovim share, state, and cache directories..."
if [ -d "$HOME/.local/share/nvim" ]; then
mv "$HOME/.local/share/nvim" "$HOME/.local/share/nvim.bak"
fi
if [ -d "$HOME/.local/state/nvim" ]; then
mv "$HOME/.local/state/nvim" "$HOME/.local/state/nvim.bak"
fi
if [ -d "$HOME/.cache/nvim" ]; then
mv "$HOME/.cache/nvim" "$HOME/.cache/nvim.bak"
fi
# Remove any leftover # Remove any leftover
if [ -e "$HOME/.config/.nvim" ]; then if [ -e "$HOME/.config/.nvim" ]; then
@@ -113,51 +90,95 @@ fi
# Install LazyVim # Install LazyVim
echo "" echo ""
git clone https://github.com/alvinlollo/LazyVim ~/.config/nvim if ! git clone https://github.com/alvinlollo/LazyVim "$HOME/.config/nvim"; then
echo "error: failed to clone https://github.com/alvinlollo/LazyVim into ~/.config/nvim" >&2
# Check if nvm is in shell path echo "Your previous config is preserved in ~/.config/nvim.bak-$STAMP (if one existed)." >&2
if command -v nvm &>/dev/null; then exit 1
# Get current shell name
CURRENT_SHELL=$(basename -- "$SHELL")
# 2. Source the correct configuration file safely
if [ "$CURRENT_SHELL" = "fish" ] && [ -f ~/.bashrc ]; then
# Check if fish is installed first, then check for fisher
if command -v fish &>/dev/null && fish -c "functions -q fisher" &>/dev/null; then
fisher install jorgebucaran/nvm.fish
source ~/.config/fish/config.fish
else
curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source
fisher install jorgebucaran/fisher
fisher install jorgebucaran/nvm.fish
source ~/.config/fish/config.fish
fi fi
elif [ "$CURRENT_SHELL" = "zsh" ] && [ -f ~/.zshrc ]; then # Load nvm into this shell if it exists. nvm is a shell function, so
# 'command -v nvm' only works after its init script has been sourced.
NVM_LOADED=false
for nvm_init in "$HOME/.nvm/nvm.sh" /usr/share/nvm/nvm.sh /usr/share/nvm/init-nvm.sh; do
if [ -f "$nvm_init" ]; then
set +u # nvm's init script is not always nounset-safe
# shellcheck disable=SC1090
if ! . "$nvm_init"; then
set -u
continue # try the next candidate
fi
set -u
NVM_LOADED=true
break
fi
done
echo -e "export NVM_DIR=\"$HOME/.nvm\"\n[ -s \"/usr/share/nvm/nvm.sh\" ] && \. \"/usr/share/nvm/nvm.sh\"\n[ -s \"/usr/share/nvm/init-nvm.sh\" ] && \. \"/usr/share/nvm/init-nvm.sh\"" >>~/.zshrc CURRENT_SHELL=$(basename -- "${SHELL:-/bin/bash}")
source ~/.zshrc
elif [ "$CURRENT_SHELL" = "bash" ] && [ -f ~/.config/fish/config.fish ]; then
echo "source /usr/share/nvm/init-nvm.sh" >>~/.bashrc
source ~/.bashrc
case "$CURRENT_SHELL" in
fish)
# fish cannot source nvm.sh - it uses the nvm.fish plugin instead
if [ -f "$HOME/.config/fish/config.fish" ]; then
if ! fish -c 'type -q fisher' 2>/dev/null; then
fisher_src="$(mktemp)"
if ! curl -fsSL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish -o "$fisher_src"; then
echo "error: failed to download fisher.fish" >&2
rm -f "$fisher_src"
exit 1
fi
fish -c "source '$fisher_src' && fisher install jorgebucaran/fisher"
rm -f "$fisher_src"
fi
# fisher refuses to install over existing files - skip when already present
if fish -c 'type -q nvm' 2>/dev/null; then
echo "nvm.fish already available in fish, skipping install."
else else
fish -c 'fisher install jorgebucaran/nvm.fish'
fi
else
echo "No ~/.config/fish/config.fish found, skipping fish plugin setup"
fi
;;
zsh)
if [ -f "$HOME/.zshrc" ]; then
if ! grep -q 'nvm.sh' "$HOME/.zshrc"; then
printf '%s\n' \
'export NVM_DIR="$HOME/.nvm"' \
'[ -s "/usr/share/nvm/nvm.sh" ] && . "/usr/share/nvm/nvm.sh"' \
'[ -s "/usr/share/nvm/init-nvm.sh" ] && . "/usr/share/nvm/init-nvm.sh"' >>"$HOME/.zshrc"
fi
else
echo "No ~/.zshrc found, skipping nvm shell wiring"
fi
;;
bash)
if [ -f "$HOME/.bashrc" ]; then
if ! grep -q 'nvm.sh\|init-nvm.sh' "$HOME/.bashrc"; then
printf '%s\n' \
'export NVM_DIR="$HOME/.nvm"' \
'[ -s "/usr/share/nvm/nvm.sh" ] && . "/usr/share/nvm/nvm.sh"' \
'[ -s "/usr/share/nvm/init-nvm.sh" ] && . "/usr/share/nvm/init-nvm.sh"' >>"$HOME/.bashrc"
fi
else
echo "No ~/.bashrc found, skipping nvm shell wiring"
fi
;;
*)
echo "Unsupported or unreadable shell configuration: $CURRENT_SHELL" echo "Unsupported or unreadable shell configuration: $CURRENT_SHELL"
echo "Failed to add NVM to shell path. Please add it manually to your shell configuration file." echo "Failed to add NVM to shell path. Please add it manually to your shell configuration file."
fi ;;
fi esac
# Use NVM to install the latest LTS version of Node.js # Use nvm (when loaded) to install the latest LTS version of Node.js
if command -v nvm &>/dev/null; then if [ "$NVM_LOADED" = true ]; then
nvm install lts nvm install lts
nvm use lts nvm use lts
elif command -v node >/dev/null; then elif command -v node >/dev/null; then
echo "nvm not found, using system Node.js $(node --version)" echo "nvm not available, using system Node.js $(node --version)"
else else
echo "No Node.js installation found." echo "No Node.js installation found."
echo "Source your nvm install (e.g. . /usr/share/nvm/nvm.sh) or install nodejs, then re-run this script." echo "Install nvm (Arch: pacman -S nvm) or nodejs, then re-run this script."
exit 1 exit 1
fi fi
echo "LazyVim setup complete."
+31 -2
View File
@@ -1,7 +1,7 @@
# Single-install-script # Single-install-script
This script automatically installs my favourite Linux applications I use every day such as: This script automatically installs my favourite Linux applications I use every day such as:
[Docker](https://www.docker.com/), [zsh, oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh) and [neovim](https://neovim.io/). This uses the [shelly-cli](https://github.com/Seafoam-Labs/Shelly-ALPM) package manager. You can choose what sections to install with the whiptail menu. [Docker](https://www.docker.com/), [zsh, oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh) and [neovim](https://neovim.io/). This uses the [shelly-cli](https://github.com/Seafoam-Labs/Shelly-ALPM) package manager. You can choose what sections to install with the [gum](https://github.com/charmbracelet/gum) menu.
This script will automatically install [shelly-cli](https://github.com/Seafoam-Labs/Shelly-ALPM) if it is not already present. This script will automatically install [shelly-cli](https://github.com/Seafoam-Labs/Shelly-ALPM) if it is not already present.
@@ -15,7 +15,7 @@ Please consider staring this project. It helps me see how important this is for
## Usage ## Usage
> **Warning:** Run this script with **bash**, not fish (or another shell). It uses bash-only syntax and will fail if run under fish. Keep `bash` in the commands below — do not replace it with `fish` or `source` the script. If fish is your default shell, a warning (dialog if whiptail is installed, otherwise a text prompt) will remind you before anything is installed. > **Warning:** Run this script with **bash**, not fish (or another shell). It uses bash-only syntax and will fail if run under fish. Keep `bash` in the commands below — do not replace it with `fish` or `source` the script. If fish is your default shell, a warning (a `gum confirm` dialog if gum is installed, otherwise a text prompt) will remind you before anything is installed.
To use this script paste the following in the Terminal: To use this script paste the following in the Terminal:
@@ -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 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 ## 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) 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)
+97 -11
View File
@@ -1,25 +1,111 @@
#!/usr/bin/bash #!/usr/bin/bash
set -euo pipefail
# Detect fish shell and ask user to switch to bash (bash-only syntax) # Detect fish shell and ask user to switch to bash (bash-only syntax)
if [ -n "${FISH_VERSION:-}" ] || case "$SHELL" in *fish*) ;; *) false ;; esac; then if [ -n "${FISH_VERSION:-}" ] || case "${SHELL:-}" in *fish*) ;; *) false ;; esac; then
echo "Warning: fish shell detected." echo "Warning: fish shell detected."
echo "This script is written for bash and may have syntax issues under fish." echo "This script is written for bash and may have syntax issues under fish."
echo "Please switch to bash first and re-run it, e.g.: bash $0" echo "Please switch to bash first and re-run it, e.g.: bash $0"
if [ -t 0 ] && [ -e /dev/tty ]; then
read -r -p "Press Enter to continue anyway, or Ctrl+C to cancel..." </dev/tty || true read -r -p "Press Enter to continue anyway, or Ctrl+C to cancel..." </dev/tty || true
fi fi
fi
# Install Prerequisites # Secure Boot setup only applies to UEFI systems (meaningless on BIOS/CSM)
sudo pacman -S --needed -noconfirm efibootmgr sbsigntools mokutil sbctl grub if [ ! -d /sys/firmware/efi ]; then
echo "error: this system is not booted in UEFI mode (/sys/firmware/efi is missing)." >&2
echo "Secure Boot setup does not apply to BIOS/CSM machines. Aborting." >&2
exit 1
fi
if ! command -v pacman >/dev/null; then
echo "error: pacman not found. This script only supports Arch-based systems." >&2
exit 1
fi
# Install prerequisites
sudo pacman -S --needed --noconfirm efibootmgr sbsigntools mokutil sbctl grub
# Fail early if the toolchain is still missing (do not touch keys otherwise)
if ! command -v sbctl >/dev/null; then
echo "error: sbctl is not available after installation. Install it manually and re-run." >&2
exit 1
fi
# Candidate sign targets. GRUB's path and the kernel image name vary by
# machine, so every path is checked before signing instead of trusted blindly.
SIGN_TARGETS=(
/boot/EFI/BOOT/BOOTX64.EFI
/boot/grub/x86_64-efi/core.efi
/boot/grub/x86_64-efi/grub.efi
/boot/vmlinuz-linux
)
missing=0
for target in "${SIGN_TARGETS[@]}"; do
if [ ! -e "$target" ]; then
echo "warning: $target does not exist on this machine, skipping" >&2
missing=$((missing + 1))
fi
done
if [ "$missing" -eq "${#SIGN_TARGETS[@]}" ]; then
echo "error: none of the expected boot files exist; refusing to continue." >&2
echo "Adjust SIGN_TARGETS for this machine and re-run." >&2
exit 1
fi
sudo sbctl create-keys sudo sbctl create-keys
for target in "${SIGN_TARGETS[@]}"; do
if [ -e "$target" ]; then
sudo sbctl sign -s "$target"
fi
done
# Every file must verify BEFORE enrollment - enrollment is hard to undo
echo "Verifying signatures before enrollment..."
if ! sudo sbctl verify; then
echo "error: sbctl verify reports unsigned or badly signed files." >&2
echo "Fix these first (sudo sbctl sign -s <file>). Not enrolling." >&2
exit 1
fi
echo ""
echo "All boot files are signed and verified."
echo ""
echo "Before enrolling keys, be ready for what can go wrong:"
echo " - After enrollment, any file not signed with these keys will not boot."
echo " - To recover: enter BIOS and disable Secure Boot (or boot a USB stick),"
echo " then run 'sudo sbctl unenroll' or re-sign the missing files."
echo " - Keep a bootable USB / BIOS access available for the first reboot."
echo ""
# Only enroll when a human explicitly says so, and only when we have a terminal
enroll=""
if [ -t 0 ] && [ -e /dev/tty ]; then
read -r -p "Enroll Secure Boot keys now? [y/N] " enroll </dev/tty || enroll=""
fi
case "$enroll" in
y | Y | yes | YES)
sudo sbctl enroll-keys --microsoft sudo sbctl enroll-keys --microsoft
sudo sbctl verify ;;
# use sudo sbctl sign -s for every unsigned item *)
sudo sbctl sign -s /boot/EFI/BOOT/BOOTX64.EFI echo "Enrollment skipped. Nothing has been enrolled."
sudo sbctl sign -s /boot/grub/x84_64-efi/core.efi echo "When you are ready, run: sudo sbctl enroll-keys --microsoft"
sudo sbctl sign -s /boot/grub/x86_64-efi/grub.efi exit 0
sudo sbctl sign -s /boot/vmlinuz-linux ;;
esac
sudo sbctl status sudo sbctl status
sudo sbctl verify
echo "Use sudo sbctl sign -s for every unsigned item" if sudo sbctl verify; then
echo ""
echo "Success: every file is signed and verified."
echo "Reboot and confirm the machine boots with Secure Boot enabled."
else
echo "error: verification failed after signing. Do not reboot yet." >&2
exit 1
fi
+50 -35
View File
@@ -1,15 +1,19 @@
#!/usr/bin/bash #!/usr/bin/bash
set -euo pipefail
# Detect fish shell and ask user to switch to bash (bash-only syntax) # Detect fish shell and ask user to switch to bash (bash-only syntax)
if [ -n "${FISH_VERSION:-}" ] || case "$SHELL" in *fish*) ;; *) false ;; esac; then if [ -n "${FISH_VERSION:-}" ] || case "${SHELL:-}" in *fish*) ;; *) false ;; esac; then
echo "Warning: fish shell detected." echo "Warning: fish shell detected."
echo "This script is written for bash and may have syntax issues under fish." echo "This script is written for bash and may have syntax issues under fish."
echo "Please switch to bash first and re-run it, e.g.: bash $0" echo "Please switch to bash first and re-run it, e.g.: bash $0"
if [ -t 0 ] && [ -e /dev/tty ]; then
read -r -p "Press Enter to continue anyway, or Ctrl+C to cancel..." </dev/tty || true read -r -p "Press Enter to continue anyway, or Ctrl+C to cancel..." </dev/tty || true
fi fi
fi
skip_watermark=false skip_watermark=false
if [ "$1" = "--skip-watermark" ]; then if [ "${1:-}" = "--skip-watermark" ]; then
skip_watermark=true skip_watermark=true
fi fi
@@ -23,8 +27,8 @@ if [ "$skip_watermark" = false ]; then
|___/ |___/
--------------- bat Install Script --------------- --------------- bat Install Script ---------------
BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE UNDER THE GPL-2.0 LICENCE, THERE IS NO WARRANTY BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE UNDER THE GPL-2.0 LICENSE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. See the LICENCE for more detail FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. See the LICENSE for more detail
' '
fi fi
@@ -50,20 +54,24 @@ if ! command -v git >/dev/null || ! command -v curl >/dev/null || ! command -v w
fi fi
# Install bat # Install bat
fresh_bat_install=false
if ! command -v bat >/dev/null && ! command -v batcat >/dev/null; then if ! command -v bat >/dev/null && ! command -v batcat >/dev/null; then
if command -v pacman >/dev/null; then if command -v pacman >/dev/null; then
echo "pacman detected. Installing bat" echo "pacman detected. Installing bat"
sudo pacman -S --needed --noconfirm bat bat-extras if ! sudo pacman -S --needed --noconfirm bat bat-extras; then
echo "Failed to install bat. Install it manually, then re-run this script."
exit 1
fi
elif command -v apt >/dev/null; then elif command -v apt >/dev/null; then
echo "apt detected. Installing bat" echo "apt detected. Installing bat"
sudo apt install bat -y if ! sudo apt install bat -y; then
echo "Failed to install bat. Install it manually, then re-run this script."
exit 1
fi
else else
echo "No supported package manager found (pacman/apt)." echo "No supported package manager found (pacman/apt)."
echo "Please install bat manually, then re-run this script." echo "Please install bat manually, then re-run this script."
exit 1 exit 1
fi fi
fresh_bat_install=true
fi fi
# Debian/Ubuntu ship the binary as batcat, provide bat for the commands below # Debian/Ubuntu ship the binary as batcat, provide bat for the commands below
@@ -76,38 +84,45 @@ if ! command -v bat >/dev/null; then
exit 1 exit 1
fi fi
if [ "$fresh_bat_install" = true ]; then # Install the Catppuccin Mocha theme (downloaded and configured names must match)
# 1. Detect the actual active shell name (not just the login default) BAT_CONFIG_DIR="$(bat --config-dir)"
CURRENT_SHELL=$(basename -- "$SHELL") mkdir -p "$BAT_CONFIG_DIR/themes"
if [ ! -f "$BAT_CONFIG_DIR/themes/Catppuccin Mocha.tmTheme" ]; then
# 2. Source the correct configuration file safely if ! wget -q -O "$BAT_CONFIG_DIR/themes/Catppuccin Mocha.tmTheme" \
if [ "$CURRENT_SHELL" = "bash" ] && [ -f ~/.bashrc ]; then "https://github.com/catppuccin/bat/raw/main/themes/Catppuccin%20Mocha.tmTheme"; then
source ~/.bashrc echo "Failed to download the Catppuccin Mocha theme."
elif [ "$CURRENT_SHELL" = "zsh" ] && [ -f ~/.zshrc ]; then exit 1
source ~/.zshrc
elif [ "$CURRENT_SHELL" = "fish" ] && [ -f ~/.config/fish/config.fish ]; then
source ~/.config/fish/config.fish
else
echo "Unsupported or unreadable shell configuration: $CURRENT_SHELL"
fi fi
fi fi
# Install Catppuccin themes
source ~/.bashrc
mkdir -p "$(bat --config-dir)/themes"
wget -P "$(bat --config-dir)/themes" https://github.com/catppuccin/bat/raw/main/themes/Catppuccin%20Macchiato.tmTheme
bat cache --build bat cache --build
echo "--theme="Catppuccin Mocha"" >>"$(bat --config-dir)/themes" # Set the default theme in bat's config file (the themes/ directory only holds themes)
if ! grep -qxF -- '--theme="Catppuccin Mocha"' "$BAT_CONFIG_DIR/config" 2>/dev/null; then
printf '%s\n' '--theme="Catppuccin Mocha"' >>"$BAT_CONFIG_DIR/config"
fi
# Detect shell name via POSIX standard # Configure the MANPAGER for the user's actual login shell ($SHELL, not $0)
CURRENT_SHELL=$(basename "$0" 2>/dev/null || echo "$0") CURRENT_SHELL=$(basename -- "${SHELL:-/bin/bash}")
CURRENT_SHELL="${CURRENT_SHELL#-}"
case "$CURRENT_SHELL" in case "$CURRENT_SHELL" in
"bash") [ -f ~/.bashrc ] && echo "export MANPAGER="bat -plman"" >>~/.bashrc ;; bash)
"zsh") [ -f ~/.zshrc ] && echo "export MANPAGER="bat -plman"" >>~/.zshrc ;; if [ -f "$HOME/.bashrc" ] && ! grep -qxF 'export MANPAGER="bat -plman"' "$HOME/.bashrc"; then
"fish") exec fish -c "echo "export MANPAGER="bat -plman"" >> ~/.config/fish/config.fish" ;; printf '%s\n' 'export MANPAGER="bat -plman"' >>"$HOME/.bashrc"
*) echo "Unsupported shell: $CURRENT_SHELL" ;; fi
;;
zsh)
if [ -f "$HOME/.zshrc" ] && ! grep -qxF 'export MANPAGER="bat -plman"' "$HOME/.zshrc"; then
printf '%s\n' 'export MANPAGER="bat -plman"' >>"$HOME/.zshrc"
fi
;;
fish)
if [ -f "$HOME/.config/fish/config.fish" ] && ! grep -qxF 'set -gx MANPAGER "bat -plman"' "$HOME/.config/fish/config.fish"; then
printf '%s\n' 'set -gx MANPAGER "bat -plman"' >>"$HOME/.config/fish/config.fish"
fi
;;
*)
echo "Unsupported shell: $CURRENT_SHELL (skipping MANPAGER setup)"
;;
esac esac
echo "bat configured with the Catppuccin Mocha theme."
+127 -57
View File
@@ -1,15 +1,19 @@
#!/usr/bin/bash #!/usr/bin/bash
set -euo pipefail
# Detect fish shell and ask user to switch to bash (bash-only syntax) # Detect fish shell and ask user to switch to bash (bash-only syntax)
if [ -n "${FISH_VERSION:-}" ] || case "$SHELL" in *fish*) ;; *) false ;; esac; then if [ -n "${FISH_VERSION:-}" ] || case "${SHELL:-}" in *fish*) ;; *) false ;; esac; then
echo "Warning: fish shell detected." echo "Warning: fish shell detected."
echo "This script is written for bash and may have syntax issues under fish." echo "This script is written for bash and may have syntax issues under fish."
echo "Please switch to bash first and re-run it, e.g.: bash $0" echo "Please switch to bash first and re-run it, e.g.: bash $0"
if [ -t 0 ] && [ -e /dev/tty ]; then
read -r -p "Press Enter to continue anyway, or Ctrl+C to cancel..." </dev/tty || true read -r -p "Press Enter to continue anyway, or Ctrl+C to cancel..." </dev/tty || true
fi fi
fi
skip_watermark=false skip_watermark=false
if [ "$1" = "--skip-watermark" ]; then if [ "${1:-}" = "--skip-watermark" ]; then
skip_watermark=true skip_watermark=true
fi fi
@@ -23,94 +27,160 @@ if [ "$skip_watermark" = false ]; then
|___/ |___/
--------------- FISH Install Script --------------- --------------- FISH Install Script ---------------
BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE UNDER THE GPL-2.0 LICENCE, THERE IS NO WARRANTY BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE UNDER THE GPL-2.0 LICENSE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. See the LICENCE for more detail FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. See the LICENSE for more detail
' '
fi fi
# Show disclaimer # Show disclaimer
echo "This script will add fish functions and install fish" echo "This script will add fish functions and install fish"
# Enable exit on error # Resolve where this script (and its configs/) live. When the script arrives
set -eu # over a pipe (curl | bash) there is no local checkout, so repo-config syncing
# is skipped and only the user's home directory is populated.
if [ -n "${BASH_SOURCE[0]:-}" ] && [ -f "${BASH_SOURCE[0]}" ]; then
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
else
SCRIPT_DIR=""
fi
# Install prerequisites if installed skips # Never clone or write into the caller's current directory
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
if ! command -v zsh >/dev/null && command -v git >/dev/null && command -v curl >/dev/null && command -v fzf >/dev/null; then # Install prerequisites if any of them are missing
if ! command -v fish >/dev/null || ! command -v git >/dev/null ||
! command -v curl >/dev/null || ! command -v fzf >/dev/null; then
if command -v pacman >/dev/null; then if command -v pacman >/dev/null; then
echo "pacman detected. Installing prerequisites" echo "pacman detected. Installing prerequisites"
sudo pacman -S fish git curl fzf --noconfirm if ! sudo pacman -S --needed --noconfirm fish git curl fzf; then
echo "Failed to install prerequisites (fish, git, curl, fzf). Install them manually, then re-run this script."
exit 1
fi fi
elif command -v apt >/dev/null; then
if command -v apt >/dev/null; then
echo "apt detected. Installing prerequisites" echo "apt detected. Installing prerequisites"
sudo apt install git curl fish fzf -y if ! sudo apt update || ! sudo apt install git curl fish fzf -y; then
echo "Failed to install prerequisites (fish, git, curl, fzf). Install them manually, then re-run this script."
exit 1
fi fi
fi
# Clone or pull end-4 dotfiles repository
if [ -d dots-hyprland ]; then
cd dots-hyprland && git pull && cd ..
else else
git clone https://github.com/end-4/dots-hyprland.git echo "No supported package manager found (pacman/apt)."
echo "Please install fish, git, curl and fzf manually, then re-run this script."
exit 1
fi
fi fi
# Copy fish config files from end-4 to git repo if they don't exist if ! command -v fish >/dev/null; then
if [ ! -f configs/fish/config.fish ]; then echo "error: fish could not be installed (no supported package manager, or install failed)." >&2
cp dots-hyprland/dots/.config/fish/config.fish configs/fish/config.fish echo "Install fish manually, then re-run this script." >&2
exit 1
fi fi
if [ ! -f configs/fish/auto-Hypr.fish ]; then # Clone the end-4 dotfiles repository into the temp workspace
cp dots-hyprland/dots/.config/fish/auto-Hypr.fish configs/fish/auto-Hypr.fish if ! git clone --depth=1 https://github.com/end-4/dots-hyprland.git "$WORK_DIR/dots-hyprland"; then
echo "error: failed to clone https://github.com/end-4/dots-hyprland.git" >&2
exit 1
fi fi
if [ ! -f configs/fish/fish_variables ]; then # Sync fresh end-4 files into this repo's configs/ (only when running from a
cp dots-hyprland/dots/.config/fish/fish_variables configs/fish/fish_variables # local checkout, and only for files that do not exist yet)
if [ -n "$SCRIPT_DIR" ] && [ -d "$SCRIPT_DIR/configs" ]; then
for rel in \
fish/config.fish \
fish/auto-Hypr.fish \
fish/fish_variables \
hypr/hyprland/colors.lua \
hypr/hyprlock/colors.conf; do
if [ ! -f "$SCRIPT_DIR/configs/$rel" ] && [ -f "$WORK_DIR/dots-hyprland/dots/.config/$rel" ]; then
mkdir -p "$(dirname "$SCRIPT_DIR/configs/$rel")"
cp "$WORK_DIR/dots-hyprland/dots/.config/$rel" "$SCRIPT_DIR/configs/$rel"
fi
done
fi fi
# Copy color configs from end-4 to git repo if they don't exist # Run end-4 setup script with flags to skip unnecessary components.
if [ ! -f configs/hypr/hyprland/colors.conf ]; then # SKIP_END4_SETUP=1 is a test hook used by tests/smoke.sh to validate this
cp dots-hyprland/dots/.config/hypr/hyprland/colors.conf configs/hypr/hyprland/colors.conf # 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 fi
if [ ! -f configs/hypr/hyprlock/colors.conf ]; then # Resolve a config file: prefer this repo's copy, fall back to the end-4 clone
cp dots-hyprland/dots/.config/hypr/hyprlock/colors.conf configs/hypr/hyprlock/colors.conf cfg_src() {
local rel="$1"
if [ -n "$SCRIPT_DIR" ] && [ -e "$SCRIPT_DIR/configs/$rel" ]; then
printf '%s\n' "$SCRIPT_DIR/configs/$rel"
elif [ -e "$WORK_DIR/dots-hyprland/dots/.config/$rel" ]; then
printf '%s\n' "$WORK_DIR/dots-hyprland/dots/.config/$rel"
else
return 1
fi fi
}
# Run end-4 setup script with flags to skip unnecessary components # Copy a config file into $HOME if it does not exist yet
dots-hyprland/setup install --force --skip-plasmaintg --skip-backup --skip-quickshell --skip-hyprland --skip-hyprland-entry install_file() {
local rel="$1" dest="$2" src
# Create fish config directory if [ -e "$dest" ]; then
mkdir -p "$HOME/.config/fish" return 0
fi
if ! src="$(cfg_src "$rel")"; then
echo "warning: $rel not found in configs/ or the end-4 clone, skipping" >&2
return 0
fi
mkdir -p "$(dirname "$dest")"
cp "$src" "$dest"
}
# Copy fish config files to user config if they don't exist # Copy fish config files to user config if they don't exist
if [ ! -f "$HOME/.config/fish/config.fish" ]; then install_file fish/config.fish "$HOME/.config/fish/config.fish"
cp configs/fish/config.fish "$HOME/.config/fish/config.fish" install_file fish/auto-Hypr.fish "$HOME/.config/fish/auto-Hypr.fish"
if cfg_src fish/functions >/dev/null 2>&1 && [ ! -d "$HOME/.config/fish/functions" ]; then
mkdir -p "$HOME/.config/fish"
cp -r "$(cfg_src fish/functions)" "$HOME/.config/fish/"
fi fi
if [ -d configs/fish/functions ] && [ ! -d "$HOME/.config/fish/functions" ]; then if cfg_src fish/conf.d >/dev/null 2>&1 && [ ! -d "$HOME/.config/fish/conf.d" ]; then
cp -r configs/fish/functions "$HOME/.config/fish/" mkdir -p "$HOME/.config/fish"
cp -r "$(cfg_src fish/conf.d)" "$HOME/.config/fish/"
fi fi
if [ -d configs/fish/conf.d ] && [ ! -d "$HOME/.config/fish/conf.d" ]; then # Copy color configs to user config if they don't exist.
cp -r configs/fish/conf.d "$HOME/.config/fish/" # end-4 renamed hyprland's colors.conf to colors.lua and keeps hyprlock's
# colors under hypr/hyprlock/ - warn and skip if upstream moves again.
install_file hypr/hyprland/colors.lua "$HOME/.config/hypr/hyprland/colors.lua"
install_file hypr/hyprlock/colors.conf "$HOME/.config/hypr/hyprlock/colors.conf"
# Install the Fisher plugin manager and nvm.fish, running inside fish (this is
# bash). The repo configs may already ship these functions (copied above) -
# fisher refuses to install over existing files, so only bootstrap what is
# actually missing instead of forcing a re-install.
if fish -c 'type -q fisher' 2>/dev/null; then
echo "Fisher is already available in fish (from configs/), skipping bootstrap."
else
if ! curl -fsSL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish \
-o "$WORK_DIR/fisher.fish"; then
echo "error: failed to download the fisher.fish installer" >&2
exit 1
fi
if ! fish -c "source '$WORK_DIR/fisher.fish' && fisher install jorgebucaran/fisher"; then
echo "error: failed to install Fisher into fish" >&2
exit 1
fi
fi fi
if [ ! -f "$HOME/.config/fish/auto-Hypr.fish" ]; then if fish -c 'type -q nvm' 2>/dev/null; then
cp configs/fish/auto-Hypr.fish "$HOME/.config/fish/" echo "nvm.fish is already available in fish, skipping install."
elif ! fish -c 'fisher install jorgebucaran/nvm.fish'; then
echo "error: failed to install nvm.fish into fish" >&2
exit 1
fi fi
# Copy color configs to user config if they don't exist echo "fish setup complete."
if [ ! -f "$HOME/.config/hypr/hyprland/colors.conf" ]; then
cp configs/hypr/hyprland/colors.conf "$HOME/.config/hypr/hyprland/colors.conf"
fi
if [ ! -f "$HOME/.config/hypr/hyprlock/colors.conf" ]; then
cp configs/hypr/hyprlock/colors.conf "$HOME/.config/hypr/hyprlock/colors.conf"
fi
# Install Fisher plugin manager for fish shell
curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source && fisher install jorgebucaran/fisher
# Install plugins using Fisher
fisher install jorgebucaran/nvm.fish
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"
+44 -45
View File
@@ -8,9 +8,8 @@ fi
# Detect fish shell and warn user to run with bash instead (bash-only syntax) # Detect fish shell and warn user to run with bash instead (bash-only syntax)
if [ -n "${FISH_VERSION:-}" ] || case "$SHELL" in *fish*) ;; *) false ;; esac; then if [ -n "${FISH_VERSION:-}" ] || case "$SHELL" in *fish*) ;; *) false ;; esac; then
if command -v whiptail >/dev/null; then if command -v gum >/dev/null; then
if ! whiptail --title "Warning: use bash, not fish" --yesno \ if ! gum confirm "$(printf 'Your default shell appears to be fish.\n\nThis script must be run with bash. It uses bash-only syntax and will fail under fish.\n\nRun it with:\n bash install.sh\n\nContinue anyway?')"; then
"Your default shell appears to be fish.\n\nThis script must be run with bash. It uses bash-only syntax and will fail under fish.\n\nRun it with:\n bash install.sh\n\nContinue anyway?" 15 72; then
echo "Aborted. Please run this script with bash, not fish. e.g.: bash install.sh" echo "Aborted. Please run this script with bash, not fish. e.g.: bash install.sh"
exit 1 exit 1
fi fi
@@ -84,70 +83,70 @@ fi
# Install prerequisites # Install prerequisites
if command -v shelly >/dev/null; then if command -v shelly >/dev/null; then
echo "Shelly detected. Installing prerequisites" echo "Shelly detected. Installing prerequisites"
shelly install standard git zsh curl wget libnewt rsync whiptail --upgrade --no-confirm shelly install standard git zsh curl wget libnewt rsync gum --upgrade --no-confirm
fi fi
if command -v apt >/dev/null; then if command -v apt >/dev/null; then
echo "apt detected. Installing prerequisites" echo "apt detected. Installing prerequisites"
sudo apt update sudo apt update
sudo apt full-upgrade -y sudo apt full-upgrade -y
sudo apt install git zsh curl wget whiptail rsync -y sudo apt install git zsh curl wget rsync gum -y
fi fi
# Ensure whiptail is installed # Ensure gum is installed
if ! command -v whiptail >/dev/null; then if ! command -v gum >/dev/null; then
echo "whiptail is not installed. Installing it now..." echo "gum is not installed. Installing it now..."
if command -v shelly >/dev/null; then if command -v shelly >/dev/null; then
echo "shelly detected. Installing whiptail" echo "shelly detected. Installing gum"
shelly install standard whiptail --no-confirm || { shelly install standard gum --no-confirm || {
echo "Failed to install whiptail. Exiting." echo "Failed to install gum. Exiting."
exit 1 exit 1
} }
fi fi
if command -v apt >/dev/null; then if command -v apt >/dev/null; then
echo "apt detected. Installing whiptail" echo "apt detected. Installing gum"
sudo apt install whiptail -y || { sudo apt install gum -y || {
echo "Failed to install whiptail. Exiting." echo "Failed to install gum. Exiting."
exit 1 exit 1
} }
fi fi
fi fi
# Options for the whiptail menu # Options for the gum menu
OPTIONS=( OPTIONS=(
1 "Run zsh setup script" OFF "Run zsh setup script"
2 "Run fish setup script" ON "Run fish setup script"
3 "Run LazyVim setup script" OFF "Run LazyVim setup script"
4 "Install Docker" OFF "Install Docker"
5 "Install Standard Packages (Shelly)" ON "Install Standard Packages (Shelly)"
6 "Install AUR Packages (Shelly)" ON "Install AUR Packages (Shelly)"
7 "Install affinity with GUI" OFF "Install affinity with GUI"
8 "Run bat setup script" ON "Run bat setup script"
) )
CHOICE=$(whiptail --title "Installation Options" --checklist \ CHOICE=$(gum choose --no-limit --height 14 \
"Choose components to install:" 20 78 10 \ --header "Tab = select, Enter = confirm:" \
"${OPTIONS[@]}" 3>&1 1>&2 2>&3) --selected "Run fish setup script,Install Standard Packages (Shelly),Install AUR Packages (Shelly),Run bat setup script" \
"${OPTIONS[@]}") || {
exitstatus=$?
if [ $exitstatus = 0 ]; then
echo "User selected: $CHOICE"
else
echo "User cancelled installation." echo "User cancelled installation."
exit 1 exit 1
fi }
echo "User selected:"
echo "$CHOICE"
# Fail on any command. # Fail on any command.
set -euo pipefail set -euo pipefail
# Process selected options # Process selected options in menu order
for selection in $CHOICE; do while IFS= read -r selection; do
clean_selection=$(echo "$selection" | tr -d '"') # Skip options the user did not select
case $clean_selection in grep -qxF "$selection" <<<"$CHOICE" || continue
"1") case "$selection" in
"Run zsh setup script")
echo "Running zsh setup script..." echo "Running zsh setup script..."
# Runs local script unless it does not exist or fails # Runs local script unless it does not exist or fails
if [[ -f "zsh.sh" ]]; then if [[ -f "zsh.sh" ]]; then
@@ -157,7 +156,7 @@ for selection in $CHOICE; do
curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/zsh.sh | bash -s -- --skip-watermark curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/zsh.sh | bash -s -- --skip-watermark
fi fi
;; ;;
"2") "Run fish setup script")
echo "Running fish setup script..." echo "Running fish setup script..."
# Runs local script unless it does not exist or fails # Runs local script unless it does not exist or fails
if [[ -f "fish.sh" ]]; then if [[ -f "fish.sh" ]]; then
@@ -167,7 +166,7 @@ for selection in $CHOICE; do
curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/fish.sh | bash -s -- --skip-watermark curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/fish.sh | bash -s -- --skip-watermark
fi fi
;; ;;
"3") "Run LazyVim setup script")
echo "Running LazyVim setup script..." echo "Running LazyVim setup script..."
# Runs local script unless it does not exist or fails # Runs local script unless it does not exist or fails
if [[ -f "LazyVim.sh" ]]; then if [[ -f "LazyVim.sh" ]]; then
@@ -177,7 +176,7 @@ for selection in $CHOICE; do
curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/LazyVim.sh | bash curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/LazyVim.sh | bash
fi fi
;; ;;
"4") "Install Docker")
echo "Installing Docker..." echo "Installing Docker..."
if ! command -v docker >/dev/null; then if ! command -v docker >/dev/null; then
echo "docker is NOT installed. Installing..." echo "docker is NOT installed. Installing..."
@@ -189,7 +188,7 @@ for selection in $CHOICE; do
echo "+ sleep 10" && sleep 10 echo "+ sleep 10" && sleep 10
fi fi
;; ;;
"5") "Install Standard Packages (Shelly)")
echo "Installing Standard Packages via Shelly backup..." echo "Installing Standard Packages via Shelly backup..."
# Check if shelly binary is installed # Check if shelly binary is installed
if ! command -v shelly >/dev/null; then if ! command -v shelly >/dev/null; then
@@ -215,7 +214,7 @@ for selection in $CHOICE; do
echo "+ sleep 10" && sleep 10 echo "+ sleep 10" && sleep 10
fi fi
;; ;;
"6") "Install AUR Packages (Shelly)")
echo "Installing AUR Packages via Shelly backup..." echo "Installing AUR Packages via Shelly backup..."
# Check for shelly before installing AUR packages # Check for shelly before installing AUR packages
if ! command -v shelly >/dev/null; then if ! command -v shelly >/dev/null; then
@@ -241,7 +240,7 @@ for selection in $CHOICE; do
echo "+ sleep 10" && sleep 10 echo "+ sleep 10" && sleep 10
fi fi
;; ;;
"7") "Install affinity with GUI")
echo "Installing GUI dependencies" echo "Installing GUI dependencies"
if command -v shelly >/dev/null; then if command -v shelly >/dev/null; then
echo "shelly found" echo "shelly found"
@@ -262,7 +261,7 @@ for selection in $CHOICE; do
sleep 10 sleep 10
curl -sSL https://raw.githubusercontent.com/ryzendew/AffinityOnLinux/refs/heads/main/AffinityScripts/AffinityLinuxInstaller.py | python3 curl -sSL https://raw.githubusercontent.com/ryzendew/AffinityOnLinux/refs/heads/main/AffinityScripts/AffinityLinuxInstaller.py | python3
;; ;;
"8") "Run bat setup script")
echo "Running bat setup script..." echo "Running bat setup script..."
# Runs local script unless it does not exist or fails # Runs local script unless it does not exist or fails
if [[ -f "bat.sh" ]]; then if [[ -f "bat.sh" ]]; then
@@ -277,6 +276,6 @@ for selection in $CHOICE; do
echo "Invalid option selected: $selection" echo "Invalid option selected: $selection"
;; ;;
esac esac
done done < <(printf '%s\n' "${OPTIONS[@]}")
echo "Installation process complete." echo "Installation process complete."
+283 -116
View File
@@ -1,16 +1,19 @@
#!/usr/bin/bash #!/usr/bin/bash
PreviousWD=$(pwd) # Strict mode from the very start: every tolerated failure in this script is
# an explicit guard (if ! / || record_failure), so preamble steps are checked
# too. -E (errtrace) makes the ERR trap fire inside helper functions as well.
set -Eeuo pipefail
skip_watermark=false skip_watermark=false
if [ "$1" = "--skip-watermark" ]; then if [ "${1:-}" = "--skip-watermark" ]; then
skip_watermark=true skip_watermark=true
fi fi
# Detect fish shell and warn user to run with bash instead (bash-only syntax) # Detect fish shell and warn user to run with bash instead (bash-only syntax)
if [ -n "${FISH_VERSION:-}" ] || case "$SHELL" in *fish*) ;; *) false ;; esac; then if [ -n "${FISH_VERSION:-}" ] || case "${SHELL:-}" in *fish*) ;; *) false ;; esac; then
if command -v whiptail >/dev/null; then if command -v gum >/dev/null; then
if ! whiptail --title "Warning: use bash, not fish" --yesno \ if ! gum confirm "$(printf 'Your default shell appears to be fish.\n\nThis script must be run with bash. It uses bash-only syntax and will fail under fish.\n\nRun it with:\n bash installbeta.sh\n\nContinue anyway?')"; then
"Your default shell appears to be fish.\n\nThis script must be run with bash. It uses bash-only syntax and will fail under fish.\n\nRun it with:\n bash installbeta.sh\n\nContinue anyway?" 15 72; then
echo "Aborted. Please run this script with bash, not fish. e.g.: bash installbeta.sh" echo "Aborted. Please run this script with bash, not fish. e.g.: bash installbeta.sh"
exit 1 exit 1
fi fi
@@ -18,12 +21,14 @@ if [ -n "${FISH_VERSION:-}" ] || case "$SHELL" in *fish*) ;; *) false ;; esac; t
echo "Warning: fish shell detected." echo "Warning: fish shell detected."
echo "This script must be run with bash, not fish (bash-only syntax)." echo "This script must be run with bash, not fish (bash-only syntax)."
echo "Please run it with bash, e.g.: bash installbeta.sh" echo "Please run it with bash, e.g.: bash installbeta.sh"
if [ -t 0 ] && [ -e /dev/tty ]; then
read -r -p "Press Enter to continue anyway, or Ctrl+C to cancel..." </dev/tty || true read -r -p "Press Enter to continue anyway, or Ctrl+C to cancel..." </dev/tty || true
fi fi
fi fi
fi
if [ "$skip_watermark" = false ]; then if [ "$skip_watermark" = false ]; then
clear clear 2>/dev/null || true
echo ' echo '
____ _ _ _ _ _ ____ _ _ _ _ _
| __ ) _ _ __ _| |_ _(_)_ __ | | ___ | | | ___ | __ ) _ _ __ _| |_ _(_)_ __ | | ___ | | | ___
@@ -33,20 +38,70 @@ if [ "$skip_watermark" = false ]; then
|___/ |___/
--------------- Single Download script --------------- --------------- Single Download script ---------------
BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE UNDER THE GPL-2.0 LICENCE, BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE UNDER THE GPL-2.0 LICENSE,
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
See the LICENCE for more detail See the LICENSE for more detail
' '
fi fi
# Function to display error message # Report errors with context (line + failing command), and only once strict
# mode is active so preamble failures abort instead of printing and continuing
function error_handler() { function error_handler() {
local rc="$1" line="$2" cmd="$3"
set +x set +x
echo -e "An error occurred. Please check the output above for details." echo ""
echo "An error occurred (exit status $rc) at line $line while running:"
echo " $cmd"
echo "Please check the output above for details."
} }
# Trap errors # Trap errors (after set -euo pipefail above)
trap error_handler ERR trap 'error_handler "$?" "$LINENO" "$BASH_COMMAND"' ERR
# --- Failure handling & full console log ------------------------------------
# Everything from here on is teed to a log file in /tmp, and Arch-only step
# failures are recorded instead of aborting the rest of the installation.
TTY_OK=false
if [ -t 1 ]; then
TTY_OK=true
fi
# One timestamp for both files, mktemp so the names are not predictable
STAMP="$(date +%Y%m%d-%H%M%S)"
LOG="$(mktemp "/tmp/installbeta-$STAMP-XXXXXX.log")"
REPORT="$(mktemp "/tmp/installbeta-failures-$STAMP-XXXXXX.txt")"
FAILED=()
exec > >(tee -a "$LOG") 2>&1
# gum draws its TUI on stderr - keep that on the real terminal, not the log pipe
gum_tty() {
if [ "$TTY_OK" = true ]; then
gum "$@" 2>/dev/tty
else
gum "$@"
fi
}
# Record a failed step: remember it, print it now, keep installing
record_failure() {
FAILED+=("$1 — $2")
echo "!! FAILED: $1 — $2 (continuing with the rest of the installation)"
echo "$(date '+%F %T') $1 — $2" >>"$REPORT"
}
# Private temp workspace for downloads and clones. Predictable names in /tmp
# are a symlink attack vector on multi-user systems; the trap cleans up too.
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
# Resolve where this script lives so local script/config fallbacks do not
# depend on the caller's current directory. Empty when piped (curl | bash).
if [ -n "${BASH_SOURCE[0]:-}" ] && [ -f "${BASH_SOURCE[0]}" ]; then
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
else
SCRIPT_DIR=""
fi
# Install shelly as a dependency if not present (Arch-based) # Install shelly as a dependency if not present (Arch-based)
if command -v pacman >/dev/null; then if command -v pacman >/dev/null; then
@@ -56,26 +111,24 @@ if command -v pacman >/dev/null; then
echo "Failed to install prerequisite packages for Shelly. You can try running it manually:" echo "Failed to install prerequisite packages for Shelly. You can try running it manually:"
echo "sudo pacman -S --needed --noconfirm base-devel git" echo "sudo pacman -S --needed --noconfirm base-devel git"
echo "--------------------------------------------------------------------" echo "--------------------------------------------------------------------"
exit 1 # exit with an error record_failure "Shelly bootstrap (Arch-only)" "failed to install base-devel/git prerequisites"
fi fi
# Check if shelly binary exists # Check if shelly binary exists
if ! command -v shelly >/dev/null; then if ! command -v shelly >/dev/null; then
echo "shelly is NOT installed. Running installation commands..." echo "shelly is NOT installed. Running installation commands..."
if ! git clone https://aur.archlinux.org/shelly.git /tmp/shelly_install; then if ! git clone https://aur.archlinux.org/shelly.git "$WORK_DIR/shelly"; then
echo "--------------------------------------------------------------------" echo "--------------------------------------------------------------------"
echo "Failed to clone shelly repository. You can try running it manually:" echo "Failed to clone shelly repository. You can try running it manually:"
echo "git clone https://aur.archlinux.org/shelly.git /tmp/shelly_install" echo "git clone https://aur.archlinux.org/shelly.git \"$WORK_DIR/shelly\""
echo "--------------------------------------------------------------------" echo "--------------------------------------------------------------------"
exit 1 # exit with an error record_failure "Shelly bootstrap (Arch-only)" "failed to clone the shelly AUR repository"
elif ! (cd /tmp/shelly_install && makepkg -si --noconfirm); then elif ! (cd "$WORK_DIR/shelly" && makepkg -si --noconfirm); then
echo "--------------------------------------------------------------------" echo "--------------------------------------------------------------------"
echo "Failed to build and install shelly. You can try running it manually:" echo "Failed to build and install shelly. You can try running it manually:"
echo "cd /tmp/shelly_install && makepkg -si --noconfirm" echo "cd \"$WORK_DIR/shelly\" && makepkg -si --noconfirm"
echo "--------------------------------------------------------------------" echo "--------------------------------------------------------------------"
exit 1 # exit with an error record_failure "Shelly bootstrap (Arch-only)" "failed to build shelly with makepkg"
fi fi
cd "$PreviousWD"
rm -rf /tmp/shelly_install
else else
echo "Shelly is already installed." echo "Shelly is already installed."
fi fi
@@ -84,198 +137,312 @@ fi
# Install prerequisites # Install prerequisites
if command -v shelly >/dev/null; then if command -v shelly >/dev/null; then
echo "Shelly detected. Installing prerequisites" echo "Shelly detected. Installing prerequisites"
shelly install standard git zsh curl wget libnewt rsync whiptail --upgrade --no-confirm shelly install standard git zsh curl wget libnewt rsync gum --upgrade --no-confirm ||
record_failure "Prerequisites (shelly)" "installing base packages via shelly failed"
fi fi
if command -v apt >/dev/null; then if command -v apt >/dev/null; then
echo "apt detected. Installing prerequisites" echo "apt detected. Installing prerequisites"
sudo apt update sudo apt update || record_failure "Prerequisites (apt)" "apt update failed"
sudo apt full-upgrade -y sudo apt full-upgrade -y || record_failure "Prerequisites (apt)" "apt full-upgrade failed"
sudo apt install git zsh curl wget whiptail rsync -y sudo apt install git zsh curl wget rsync gum -y ||
record_failure "Prerequisites (apt)" "apt install of base packages failed"
fi fi
# Ensure whiptail is installed # Ensure gum is installed
if ! command -v whiptail >/dev/null; then if ! command -v gum >/dev/null; then
echo "whiptail is not installed. Installing it now..." echo "gum is not installed. Installing it now..."
if command -v shelly >/dev/null; then if command -v shelly >/dev/null; then
echo "shelly detected. Installing whiptail" echo "shelly detected. Installing gum"
shelly install standard whiptail --no-confirm || { shelly install standard gum --no-confirm || {
echo "Failed to install whiptail. Exiting." echo "Failed to install gum. Exiting."
exit 1 exit 1
} }
fi fi
if command -v apt >/dev/null; then if command -v apt >/dev/null; then
echo "apt detected. Installing whiptail" echo "apt detected. Installing gum"
sudo apt install whiptail -y || { sudo apt install gum -y || {
echo "Failed to install whiptail. Exiting." echo "Failed to install gum. Exiting."
exit 1 exit 1
} }
fi fi
fi fi
# Options for the whiptail menu # Options for the gum menu
OPTIONS=( OPTIONS=(
1 "Run zsh setup script" OFF "Run zsh setup script"
2 "Run fish setup script" ON "Run fish setup script"
3 "Run LazyVim setup script" OFF "Run LazyVim setup script"
4 "Install Docker" OFF "Install Docker"
5 "Install Standard Packages (Shelly)" ON "Install Standard Packages (Shelly)"
6 "Install AUR Packages (Shelly)" ON "Install AUR Packages (Shelly)"
7 "Install affinity with GUI" OFF "Install affinity with GUI"
8 "Run bat setup script" ON "Run bat setup script"
) )
CHOICE=$(whiptail --title "Installation Options" --checklist \ # Present the menu: gum when available, plain numbered fallback otherwise
"Choose components to install:" 20 78 10 \ # (systems with neither shelly nor apt never get gum installed - the menu
"${OPTIONS[@]}" 3>&1 1>&2 2>&3) # must still work there instead of failing with a misleading message)
select_options() {
if command -v gum >/dev/null; then
gum_tty choose --no-limit --height 14 \
--header "Tab = select, Enter = confirm:" \
--selected "Run fish setup script,Install Standard Packages (Shelly),Install AUR Packages (Shelly),Run bat setup script" \
"${OPTIONS[@]}"
return
fi
exitstatus=$? echo "gum is not installed and could not be installed on this system." >&2
if [ $exitstatus = 0 ]; then echo "Falling back to a plain numbered menu." >&2
echo "User selected: $CHOICE" local i=1 opt
for opt in "${OPTIONS[@]}"; do
printf ' %d) %s\n' "$i" "$opt"
i=$((i + 1))
done
local reply=""
if [ -e /dev/tty ]; then
read -r -p "Enter numbers separated by spaces (Enter = cancel): " reply </dev/tty || reply=""
else else
echo "User cancelled installation." echo "No interactive terminal available, cannot show a menu." >&2
return 2
fi
[ -n "$reply" ] || return 1
local -a picked=()
local n
for n in $reply; do
case "$n" in
'' | *[!0-9]*)
echo "Invalid selection: $n" >&2
return 1
;;
esac
if [ "$n" -ge 1 ] && [ "$n" -le "${#OPTIONS[@]}" ]; then
picked+=("${OPTIONS[$((n - 1))]}")
else
echo "Selection out of range: $n" >&2
return 1
fi
done
[ "${#picked[@]}" -gt 0 ] || return 1
printf '%s\n' "${picked[@]}"
}
if ! CHOICE="$(select_options)"; then
echo "No options selected (menu cancelled or unavailable). Nothing was installed."
exit 1 exit 1
fi fi
# Fail on any command. echo "User selected:"
set -euo pipefail echo "$CHOICE"
# Process selected options # Process selected options in menu order
for selection in $CHOICE; do while IFS= read -r selection; do
clean_selection=$(echo "$selection" | tr -d '"') # Skip options the user did not select
case $clean_selection in grep -qxF "$selection" <<<"$CHOICE" || continue
"1") case "$selection" in
"Run zsh setup script")
echo "Running zsh setup script..." echo "Running zsh setup script..."
# Runs local script unless it does not exist or fails # Runs local script unless it does not exist (SCRIPT_DIR, not CWD)
if [[ -f "zsh.sh" ]]; then if [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/zsh.sh" ]; then
echo "Found local script, running..." echo "Found local script, running..."
bash zsh.sh --skip-watermark bash "$SCRIPT_DIR/zsh.sh" --skip-watermark || {
rc=$?
record_failure "Run zsh setup script" "zsh.sh exited with status $rc"
}
else else
curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/zsh.sh | bash -s -- --skip-watermark curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/zsh.sh | bash -s -- --skip-watermark || {
rc=$?
record_failure "Run zsh setup script" "downloaded zsh.sh exited with status $rc"
}
fi fi
;; ;;
"2") "Run fish setup script")
echo "Running fish setup script..." echo "Running fish setup script..."
# Runs local script unless it does not exist or fails # Runs local script unless it does not exist (SCRIPT_DIR, not CWD)
if [[ -f "fish.sh" ]]; then if [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/fish.sh" ]; then
echo "Found local script, running..." echo "Found local script, running..."
bash fish.sh --skip-watermark bash "$SCRIPT_DIR/fish.sh" --skip-watermark || {
rc=$?
record_failure "Run fish setup script" "fish.sh exited with status $rc"
}
else else
curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/fish.sh | bash -s -- --skip-watermark curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/fish.sh | bash -s -- --skip-watermark || {
rc=$?
record_failure "Run fish setup script" "downloaded fish.sh exited with status $rc"
}
fi fi
;; ;;
"3") "Run LazyVim setup script")
echo "Running LazyVim setup script..." echo "Running LazyVim setup script..."
# Runs local script unless it does not exist or fails # Runs local script unless it does not exist (SCRIPT_DIR, not CWD)
if [[ -f "LazyVim.sh" ]]; then if [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/LazyVim.sh" ]; then
echo "Found local script, running..." echo "Found local script, running..."
bash LazyVim.sh bash "$SCRIPT_DIR/LazyVim.sh" || {
rc=$?
record_failure "Run LazyVim setup script" "LazyVim.sh exited with status $rc"
}
else else
curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/LazyVim.sh | bash curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/LazyVim.sh | bash || {
rc=$?
record_failure "Run LazyVim setup script" "downloaded LazyVim.sh exited with status $rc"
}
fi fi
;; ;;
"4") "Install Docker")
echo "Installing Docker..." echo "Installing Docker..."
if ! command -v docker >/dev/null; then if ! command -v docker >/dev/null; then
echo "docker is NOT installed. Installing..." echo "docker is NOT installed. Installing..."
curl -fsSL https://get.docker.com | sh if curl -fsSL https://get.docker.com -o "$WORK_DIR/get-docker.sh" &&
sudo usermod -aG docker "$USER" sh "$WORK_DIR/get-docker.sh"; then
sudo usermod -aG docker "$USER" ||
record_failure "Install Docker" "adding $USER to the docker group failed"
else else
sudo usermod -aG docker "$USER" rc=$?
echo "Docker is already installed." record_failure "Install Docker" "docker installer script exited with status $rc"
echo "+ sleep 10" && sleep 10
fi fi
else
sudo usermod -aG docker "$USER" ||
record_failure "Install Docker" "adding $USER to the docker group failed"
echo "Docker is already installed."
fi
echo "NOTE: $USER has been added to the docker group."
echo "Log out and back in (or reboot) for the group membership to take effect."
;; ;;
"5") "Install Standard Packages (Shelly)")
echo "Installing Standard Packages via Shelly backup..." echo "Installing Standard Packages via Shelly backup..."
# Check if shelly binary is installed # Check if shelly binary is installed
if ! command -v shelly >/dev/null; then if ! command -v shelly >/dev/null; then
echo "Cannot proceed: shelly binary not found" echo "Cannot proceed: shelly binary not found (Arch-only)"
exit 1 # exit with an error record_failure "Install Standard Packages (Shelly)" "shelly binary not found (Arch-only)"
fi
# Install standard packages from shelly backup # Install standard packages from shelly backup
# Runs local script unless it does not exist or fails # Runs local script unless it does not exist (SCRIPT_DIR, not CWD)
if [[ -f "./configs/shelly-standard.toml" ]]; then elif [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/configs/shelly-standard.toml" ]; then
echo "Found local backup" echo "Found local backup"
shelly backup --import --name shelly-standard --directory ./configs --no-confirm shelly backup --import --name shelly-standard --directory "$SCRIPT_DIR/configs" --no-confirm || {
elif ! curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/configs/shelly-standard.toml -o /tmp/shelly-standard.toml; then rc=$?
record_failure "Install Standard Packages (Shelly)" "shelly backup import exited with status $rc"
}
elif ! curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/configs/shelly-standard.toml -o "$WORK_DIR/shelly-standard.toml"; then
echo "--------------------------------------------------------------------" echo "--------------------------------------------------------------------"
echo "Failed to download shelly backup. You can try running it manually:" echo "Failed to download shelly backup. You can try running it manually:"
echo "curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/configs/shelly-standard.toml -o /tmp/shelly-standard.toml" echo "curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/configs/shelly-standard.toml -o \"$WORK_DIR/shelly-standard.toml\""
echo "--------------------------------------------------------------------" echo "--------------------------------------------------------------------"
echo "+ sleep 10" && sleep 10 record_failure "Install Standard Packages (Shelly)" "failed to download shelly-standard.toml"
elif ! shelly backup --import --name shelly-standard --directory /tmp --no-confirm; then elif ! shelly backup --import --name shelly-standard --directory "$WORK_DIR" --no-confirm; then
echo "--------------------------------------------------------------------" echo "--------------------------------------------------------------------"
echo "Failed to install Standard packages. You can try running it manually:" echo "Failed to install Standard packages. You can try running it manually:"
echo "shelly backup --import --name shelly-standard --directory /tmp --no-confirm" echo "shelly backup --import --name shelly-standard --directory \"$WORK_DIR\" --no-confirm"
echo "--------------------------------------------------------------------" echo "--------------------------------------------------------------------"
echo "+ sleep 10" && sleep 10 record_failure "Install Standard Packages (Shelly)" "shelly backup import failed"
fi fi
;; ;;
"6") "Install AUR Packages (Shelly)")
echo "Installing AUR Packages via Shelly backup..." echo "Installing AUR Packages via Shelly backup..."
# Check for shelly before installing AUR packages # Check for shelly before installing AUR packages
if ! command -v shelly >/dev/null; then if ! command -v shelly >/dev/null; then
echo "Cannot proceed: shelly binary not found" echo "Cannot proceed: shelly binary not found (Arch-only)"
exit 1 # Exit with an error record_failure "Install AUR Packages (Shelly)" "shelly binary not found (Arch-only)"
fi
# Install AUR packages from shelly backup # Install AUR packages from shelly backup
# Runs local script unless it does not exist or fails # Runs local script unless it does not exist (SCRIPT_DIR, not CWD)
if [[ -f "./configs/shelly-aur.toml" ]]; then elif [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/configs/shelly-aur.toml" ]; then
echo "Found local backup" echo "Found local backup"
shelly backup --import --name shelly-aur --directory ./configs --no-confirm shelly backup --import --name shelly-aur --directory "$SCRIPT_DIR/configs" --no-confirm || {
elif ! curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/configs/shelly-aur.toml -o /tmp/shelly-aur.toml; then rc=$?
record_failure "Install AUR Packages (Shelly)" "shelly backup import exited with status $rc"
}
elif ! curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/configs/shelly-aur.toml -o "$WORK_DIR/shelly-aur.toml"; then
echo "--------------------------------------------------------------------" echo "--------------------------------------------------------------------"
echo "Failed to download shelly backup. You can try running it manually:" echo "Failed to download shelly backup. You can try running it manually:"
echo "curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/configs/shelly-aur.toml -o /tmp/shelly-aur.toml" echo "curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/configs/shelly-aur.toml -o \"$WORK_DIR/shelly-aur.toml\""
echo "--------------------------------------------------------------------" echo "--------------------------------------------------------------------"
echo "+ sleep 10" && sleep 10 record_failure "Install AUR Packages (Shelly)" "failed to download shelly-aur.toml"
elif ! shelly backup --import --name shelly-aur --directory /tmp --no-confirm; then elif ! shelly backup --import --name shelly-aur --directory "$WORK_DIR" --no-confirm; then
echo "--------------------------------------------------------------------" echo "--------------------------------------------------------------------"
echo "Failed to install AUR packages. You can try running it manually:" echo "Failed to install AUR packages. You can try running it manually:"
echo "shelly backup --import --name shelly-aur --directory /tmp --no-confirm" echo "shelly backup --import --name shelly-aur --directory \"$WORK_DIR\" --no-confirm"
echo "--------------------------------------------------------------------" echo "--------------------------------------------------------------------"
echo "+ sleep 10" && sleep 10 record_failure "Install AUR Packages (Shelly)" "shelly backup import failed"
fi fi
;; ;;
"7") "Install affinity with GUI")
echo "Installing GUI dependencies" echo "Installing GUI dependencies"
if command -v shelly >/dev/null; then if command -v shelly >/dev/null; then
echo "shelly found" echo "shelly found"
shelly install standard python-pyqt6 --no-confirm shelly install standard python-pyqt6 --no-confirm ||
record_failure "Install affinity with GUI" "installing python-pyqt6 via shelly failed"
fi fi
if command -v dnf >/dev/null; then if command -v dnf >/dev/null; then
echo "dnf found" echo "dnf found"
sudo dnf install python3-pyqt6 sudo dnf install python3-pyqt6 ||
record_failure "Install affinity with GUI" "installing python3-pyqt6 via dnf failed"
fi fi
if command -v apt >/dev/null; then if command -v apt >/dev/null; then
echo "apt found" echo "apt found"
sudo apt install python3-pyqt6 -y sudo apt install python3-pyqt6 -y ||
record_failure "Install affinity with GUI" "installing python3-pyqt6 via apt failed"
fi fi
echo "Install affinity with ryzendew's gui installer" echo "Install affinity with ryzendew's gui installer"
echo "You must manually select to install in the GUI" echo "You must manually select to install in the GUI"
echo "Github repo: https://github.com/ryzendew/Linux-Affinity-Installer" echo "Github repo: https://github.com/ryzendew/Linux-Affinity-Installer"
echo "+ 10 sleep" echo "Starting the GUI installer in 10 seconds - read the notes above first."
sleep 10 sleep 10
curl -sSL https://raw.githubusercontent.com/ryzendew/AffinityOnLinux/refs/heads/main/AffinityScripts/AffinityLinuxInstaller.py | python3 { curl -fsSL https://raw.githubusercontent.com/ryzendew/AffinityOnLinux/refs/heads/main/AffinityScripts/AffinityLinuxInstaller.py \
-o "$WORK_DIR/AffinityLinuxInstaller.py" &&
python3 "$WORK_DIR/AffinityLinuxInstaller.py"; } || {
rc=$?
record_failure "Install affinity with GUI" "affinity installer exited with status $rc"
}
;; ;;
"8") "Run bat setup script")
echo "Running bat setup script..." echo "Running bat setup script..."
# Runs local script unless it does not exist or fails # Runs local script unless it does not exist (SCRIPT_DIR, not CWD)
if [[ -f "bat.sh" ]]; then if [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/bat.sh" ]; then
echo "Found local script, running..." echo "Found local script, running..."
bash bat.sh --skip-watermark bash "$SCRIPT_DIR/bat.sh" --skip-watermark || {
rc=$?
record_failure "Run bat setup script" "bat.sh exited with status $rc"
}
else else
curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/bat.sh | bash -s -- --skip-watermark curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/bat.sh | bash -s -- --skip-watermark || {
rc=$?
record_failure "Run bat setup script" "downloaded bat.sh exited with status $rc"
}
fi fi
;; ;;
*) *)
echo "Invalid option selected: $selection" echo "Invalid option selected: $selection"
;; ;;
esac esac
done done < <(printf '%s\n' "${OPTIONS[@]}")
echo "Installation process complete." echo "Installation process complete."
# Show (and save) what failed - the run itself never aborts on a failed step
if [ "${#FAILED[@]}" -gt 0 ]; then
{
echo ""
echo "================ FAILURE SUMMARY ================"
echo "installbeta.sh finished at $(date '+%F %T') - ${#FAILED[@]} step(s) failed:"
printf ' - %s\n' "${FAILED[@]}"
echo ""
echo "Full console log: $LOG"
echo "Failure report: $REPORT"
echo "==================================================="
} | tee -a "$REPORT"
if [ "$TTY_OK" = true ] && command -v gum >/dev/null; then
gum_tty pager < "$REPORT" || true
fi
# Non-zero so callers and CI can detect the failed run
exit 1
fi
echo "All steps completed with no failures."
exit 0
Executable
+145
View File
@@ -0,0 +1,145 @@
#!/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" '
# pre-seed a config so BOTH runs have something to back up
printf "# preexisting zshrc marker\n" > /root/.zshrc
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; }
n=$(find /root -maxdepth 1 -name ".zshrc.bak-*" | wc -l)
[ "$n" -eq 1 ] || { echo "expected 1 backup after first run, got $n"; exit 1; }
grep -q "preexisting zshrc marker" /root/.zshrc.bak-* \
|| { echo "first backup does not hold the original config"; exit 1; }
# second run must rotate (new timestamped backup), not overwrite
bash zsh.sh --skip-watermark
n=$(find /root -maxdepth 1 -name ".zshrc.bak-*" | wc -l)
[ "$n" -eq 2 ] || { echo "expected 2 backups after second run (rotation), got $n"; exit 1; }
grep -q "preexisting zshrc marker" /root/.zshrc.bak-* \
|| { echo "second run destroyed an earlier backup"; 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"
+21 -17
View File
@@ -25,8 +25,8 @@ echo '
|___/ |___/
--------------- ZSH Install Script --------------- --------------- ZSH Install Script ---------------
BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE UNDER THE GPL-2.0 LICENCE, THERE IS NO WARRANTY BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE UNDER THE GPL-2.0 LICENSE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. See the LICENCE for more detail FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. See the LICENSE for more detail
' '
fi fi
@@ -40,7 +40,7 @@ set -eu
if ! command -v zsh >/dev/null || ! command -v git >/dev/null || ! command -v curl >/dev/null || ! command -v fzf >/dev/null; then if ! command -v zsh >/dev/null || ! command -v git >/dev/null || ! command -v curl >/dev/null || ! command -v fzf >/dev/null; then
if command -v pacman >/dev/null; then if command -v pacman >/dev/null; then
echo "pacman detected. Installing prerequisites" echo "pacman detected. Installing prerequisites"
sudo pacman -S zsh git curl fzf --noconfirm sudo pacman -S --needed --noconfirm zsh git curl fzf
fi fi
if command -v apt >/dev/null; then if command -v apt >/dev/null; then
@@ -67,24 +67,28 @@ echo '
# Print commands # Print commands
set -x set -x
# Disable exit on error # Remove an existing oh-my-zsh install, but only when it really is one
set +eu if [ -d "$HOME/.oh-my-zsh" ]; then
if [ -f "$HOME/.oh-my-zsh/oh-my-zsh.sh" ]; then
rm -rf ~/.oh-my-zsh echo "Removing existing ~/.oh-my-zsh for a clean install"
rm -rf "$HOME/.oh-my-zsh"
# Enable exit on error else
set -eu echo "warning: ~/.oh-my-zsh exists but does not look like an oh-my-zsh install - leaving it in place" >&2
fi
fi
# Do not print commands # Do not print commands
set +x set +x
# Backup the user's current config before oh-my-zsh overwrites it # Backup the user's current config before oh-my-zsh overwrites it.
# Timestamped so re-running the script never overwrites the previous backup.
if [ -f "$HOME/.zshrc" ]; then if [ -f "$HOME/.zshrc" ]; then
if ! cp "$HOME/.zshrc" "$HOME/.zshrc.bak"; then ZSHRC_BACKUP="$HOME/.zshrc.bak-$(date +%Y%m%d-%H%M%S)"
echo "error: failed to back up $HOME/.zshrc to $HOME/.zshrc.bak" >&2 if ! cp "$HOME/.zshrc" "$ZSHRC_BACKUP"; then
echo "error: failed to back up $HOME/.zshrc to $ZSHRC_BACKUP" >&2
exit 1 exit 1
fi fi
echo "Backed up $HOME/.zshrc to $HOME/.zshrc.bak" echo "Backed up $HOME/.zshrc to $ZSHRC_BACKUP"
fi fi
# Install oh-my-zsh without entering zsh # Install oh-my-zsh without entering zsh
@@ -122,9 +126,9 @@ curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/re
# Download generic fzf configuration # Download generic fzf configuration
curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/configs/.fzf.zsh -o ~/.fzf.zsh curl -fsSL https://raw.githubusercontent.com/alvinlollo/Single-install-script/refs/heads/main/configs/.fzf.zsh -o ~/.fzf.zsh
# Setup fzf # Setup fzf: the oh-my-zsh fzf-zsh-plugin provides the integration.
mkdir -p ~/.fzf/shell # (Previously an empty ~/.fzf/shell/key-bindings.zsh placeholder was created
touch ~/.fzf/shell/key-bindings.zsh # here - nothing ever sourced it, and it would shadow real bindings.)
# Do not print commands # Do not print commands
set +x set +x