Skip to content

Instantly share code, notes, and snippets.

@Aabayoumy
Created July 30, 2026 05:20
Show Gist options
  • Select an option

  • Save Aabayoumy/11c23c69ea3cc6027bf24ee9027cd336 to your computer and use it in GitHub Desktop.

Select an option

Save Aabayoumy/11c23c69ea3cc6027bf24ee9027cd336 to your computer and use it in GitHub Desktop.
Unified Debian/Ubuntu + Alma/RHEL VM bootstrap script
#!/usr/bin/env bash
set -euo pipefail
# Optional override: TARGET_USER=myuser bash bootstrap.sh
TARGET_USER="${TARGET_USER:-abayoumy}"
if [ -r /etc/os-release ]; then
. /etc/os-release
else
echo "Cannot detect OS: /etc/os-release not found"
exit 1
fi
if command -v apt-get >/dev/null 2>&1; then
OS_FAMILY="debian"
SUDO_GROUP="sudo"
elif command -v dnf >/dev/null 2>&1; then
OS_FAMILY="rhel"
SUDO_GROUP="wheel"
else
echo "Unsupported distro. Supported: Debian/Ubuntu and Alma/RHEL-like systems."
exit 1
fi
# ===========================================================
# Step 0: Ensure user exists and has passwordless sudo
# ===========================================================
if ! id -u "$TARGET_USER" >/dev/null 2>&1; then
sudo useradd -m -s /bin/bash "$TARGET_USER"
fi
sudo usermod -aG "$SUDO_GROUP" "$TARGET_USER" || true
echo "$TARGET_USER ALL=(ALL) NOPASSWD:ALL" | sudo tee "/etc/sudoers.d/$TARGET_USER" >/dev/null
sudo chmod 440 "/etc/sudoers.d/$TARGET_USER"
# ===========================================================
# Step 1: Backup existing configurations
# ===========================================================
if ! command -v zip &> /dev/null; then
if [ "$OS_FAMILY" = "debian" ]; then
sudo apt-get update -qq
sudo apt-get install -y -qq zip
else
sudo dnf install -y zip
fi
fi
BACKUP_DATE=$(date +"%Y-%m-%d_%H-%M-%S")
BACKUP_FILE="$HOME/terminal_backup_$BACKUP_DATE.zip"
zip -rq "$BACKUP_FILE" \
~/.zshrc ~/.zprofile ~/.zsh_history ~/.oh-my-zsh \
~/.config/nvim ~/.local/share/nvim ~/.local/state/nvim ~/.cache/nvim \
~/.config/fastfetch ~/.config/starship.toml \
2>/dev/null || true
echo "Backup saved to: $BACKUP_FILE"
# ===========================================================
# Step 2: Clean existing config files
# ===========================================================
rm -rf ~/.config/nvim ~/.local/share/nvim ~/.local/state/nvim ~/.cache/nvim
rm -rf ~/.config/fastfetch
rm -rf ~/.oh-my-zsh
rm -f ~/.zshrc ~/.zprofile ~/.zsh_history
ARCH=$(uname -m)
echo "Detected architecture: $ARCH"
# ===========================================================
# Step 3: Install core packages
# ===========================================================
echo "Installing core packages..."
if [ "$OS_FAMILY" = "debian" ]; then
sudo apt-get update
sudo apt-get install -y \
curl git wget gawk zsh fzf \
bat ripgrep fd-find build-essential \
unzip luarocks xclip qemu-guest-agent aria2 ca-certificates
# Optional tools
sudo apt-get install -y zoxide || true
sudo apt-get install -y eza || sudo apt-get install -y exa || true
else
sudo dnf install -y epel-release dnf-plugins-core
if command -v crb &> /dev/null; then
sudo crb enable || true
else
sudo dnf config-manager --set-enabled crb || true
fi
sudo dnf makecache -y
sudo dnf install -y \
curl git wget gawk zsh fzf \
bat ripgrep gcc gcc-c++ make \
unzip luarocks xclip qemu-guest-agent aria2 tar ca-certificates
# Optional tools with distro naming differences
sudo dnf install -y zoxide || true
sudo dnf install -y eza || sudo dnf install -y exa || true
sudo dnf install -y fd-find || sudo dnf install -y fd || true
fi
sudo systemctl enable --now qemu-guest-agent || true
# fd-find installs as 'fdfind' on Debian/Ubuntu — symlink to expected name
if command -v fdfind &> /dev/null && ! command -v fd &> /dev/null; then
sudo ln -sf "$(which fdfind)" /usr/local/bin/fd
fi
# batcat → bat symlink on Debian/Ubuntu
if command -v batcat &> /dev/null && ! command -v bat &> /dev/null; then
sudo ln -sf "$(which batcat)" /usr/local/bin/bat
fi
# exa fallback when eza is unavailable
if command -v exa &> /dev/null && ! command -v eza &> /dev/null; then
sudo ln -sf "$(which exa)" /usr/local/bin/eza
fi
# ===========================================================
# Step 4: Install latest Fastfetch
# ===========================================================
echo "Installing Fastfetch..."
if [ "$ARCH" = "x86_64" ]; then
FF_DEB="fastfetch-linux-amd64.deb"
FF_RPM="fastfetch-linux-amd64.rpm"
elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
FF_DEB="fastfetch-linux-aarch64.deb"
FF_RPM="fastfetch-linux-aarch64.rpm"
else
echo "Unsupported architecture for fastfetch: $ARCH"
exit 1
fi
if [ "$OS_FAMILY" = "debian" ]; then
aria2c -x 4 -s 4 -o "$FF_DEB" \
"https://github.com/fastfetch-cli/fastfetch/releases/latest/download/$FF_DEB"
sudo apt-get install -y "./$FF_DEB" && rm "$FF_DEB"
else
aria2c -x 4 -s 4 -o "$FF_RPM" \
"https://github.com/fastfetch-cli/fastfetch/releases/latest/download/$FF_RPM"
sudo dnf install -y "./$FF_RPM" && rm "$FF_RPM"
fi
# ===========================================================
# Step 5: Remove any existing Neovim installation
# ===========================================================
echo "Removing existing Neovim..."
if [ "$OS_FAMILY" = "debian" ]; then
sudo apt-get purge -y neovim neovim-runtime || true
sudo apt-get autoremove -y || true
else
sudo dnf remove -y neovim neovim-runtime || true
fi
if command -v snap &> /dev/null; then sudo snap remove neovim || true; fi
if command -v flatpak &> /dev/null; then sudo flatpak uninstall -y org.neovim.nvim || true; fi
sudo rm -f /usr/bin/nvim /usr/local/bin/nvim
sudo rm -rf /opt/nvim-linux* /opt/nvim
# ===========================================================
# Step 6: Install latest stable Neovim from GitHub
# ===========================================================
echo "Installing Neovim..."
if [ "$ARCH" = "x86_64" ]; then
NVIM_URL="https://github.com/neovim/neovim/releases/latest/download/nvim-linux-x86_64.tar.gz"
elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
NVIM_URL="https://github.com/neovim/neovim/releases/latest/download/nvim-linux-arm64.tar.gz"
else
echo "Unsupported architecture: $ARCH"; exit 1
fi
NVIM_TAR="nvim-download.tar.gz"
aria2c -x 4 -s 4 -o "$NVIM_TAR" "$NVIM_URL"
sudo mkdir -p /opt/nvim
sudo tar -C /opt/nvim --strip-components=1 -xzf "$NVIM_TAR"
sudo ln -sf /opt/nvim/bin/nvim /usr/local/bin/nvim
rm "$NVIM_TAR"
# ===========================================================
# Step 7: Install Starship + download personal config from gist
# ===========================================================
echo "Installing Starship..."
curl -sS https://starship.rs/install.sh | sh -s -- --yes
mkdir -p ~/.config
curl -fsSL "https://gist.githubusercontent.com/Aabayoumy/0d9eaec0629bbaea0c2c318b058525c1/raw/3f9ac8c2edc61eb9531a70fe962964aab4afa8d1/starship.toml" \
-o ~/.config/starship.toml
# ===========================================================
# Step 8: Configure Fastfetch
# ===========================================================
echo "Configuring Fastfetch..."
mkdir -p ~/.config/fastfetch
cat << 'EOF' > ~/.config/fastfetch/config.jsonc
{
"$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json",
"logo": {
"type": "small",
"padding": { "top": 1 }
},
"display": {
"separator": " ❯ ",
"bar": {
"char": { "elapsed": "■", "total": " " },
"width": 12
}
},
"modules": [
"title",
"os",
{ "type": "cpu", "format": "{1} ({4} cores)" },
{ "type": "cpuusage", "key": "CPU ", "percent": { "type": 3 } },
{ "type": "memory", "key": "Mem ", "percent": { "type": 3 } },
{ "type": "disk", "key": "Disk", "percent": { "type": 3 } },
"break"
]
}
EOF
# ===========================================================
# Step 9: Download Catppuccin Mocha zsh-syntax-highlighting theme
# ===========================================================
echo "Downloading Catppuccin Mocha syntax highlight theme..."
mkdir -p ~/.config
curl -fsSL \
"https://raw.githubusercontent.com/catppuccin/zsh-syntax-highlighting/main/themes/catppuccin_mocha-zsh-syntax-highlighting.zsh" \
-o ~/.config/catppuccin_mocha-zsh-syntax-highlighting.zsh
# ===========================================================
# Step 10: Write .zshrc
# ===========================================================
echo "Writing .zshrc..."
cat << 'ZSHRC' > ~/.zshrc
# ===========================================================
# ZINIT — plugin manager (auto-installs on first run)
# ===========================================================
ZINIT_HOME="${XDG_DATA_HOME:-${HOME}/.local/share}/zinit/zinit.git"
if [ ! -d "$ZINIT_HOME" ]; then
mkdir -p "$(dirname "$ZINIT_HOME")"
git clone https://github.com/zdharma-continuum/zinit.git "$ZINIT_HOME"
fi
source "${ZINIT_HOME}/zinit.zsh"
# ===========================================================
# PLUGINS
# ===========================================================
# zsh-completions: use blockf so zinit registers the extra fpath entries
# before compinit runs — this is what enables filename/command completions
zinit light zsh-users/zsh-autosuggestions
zinit light zsh-users/zsh-history-substring-search
zinit ice blockf; zinit light zsh-users/zsh-completions
# Catppuccin Mocha syntax highlight theme — must load before the plugin
source ~/.config/catppuccin_mocha-zsh-syntax-highlighting.zsh
zinit light zsh-users/zsh-syntax-highlighting
# OMZ snippets
zinit snippet OMZL::git.zsh
zinit snippet OMZP::git
zinit snippet OMZP::sudo
zinit snippet OMZP::command-not-found
# ===========================================================
# COMPLETION ENGINE (compinit BEFORE fzf-tab)
# ===========================================================
autoload -Uz compinit
# Refresh .zcompdump at most once per day for faster startup
if [[ -n ~/.zcompdump(#qN.mh+24) ]]; then
compinit
else
compinit -C
fi
zinit cdreplay -q
# fzf-tab must load AFTER compinit — it replaces the completion menu
zinit light Aloxaf/fzf-tab
# Completion styling
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=*' 'l:|=* r:|=*'
zstyle ':completion:*' list-colors "${(s.:.)LS_COLORS}"
zstyle ':completion:*' group-name ''
zstyle ':completion:*:descriptions' format '%F{yellow}-- %d --%f'
zstyle ':completion:*:warnings' format '%F{red}no matches%f'
zstyle ':completion::complete:*' use-cache yes
zstyle ':completion::complete:*' cache-path ~/.zcompcache
zstyle ':completion:*' special-dirs true
zstyle ':completion:*' menu no # fzf-tab takes over the menu
# fzf-tab: previews
if command -v eza &>/dev/null; then
zstyle ':fzf-tab:complete:cd:*' fzf-preview 'eza --icons --group-directories-first --color=always $realpath'
zstyle ':fzf-tab:complete:__zoxide_z:*' fzf-preview 'eza --icons --group-directories-first --color=always $realpath'
zstyle ':fzf-tab:complete:*:*' fzf-preview 'bat --color=always --style=numbers $realpath 2>/dev/null || eza --icons --color=always $realpath'
else
zstyle ':fzf-tab:complete:cd:*' fzf-preview 'ls -la --color=always $realpath'
zstyle ':fzf-tab:complete:__zoxide_z:*' fzf-preview 'ls -la --color=always $realpath'
zstyle ':fzf-tab:complete:*:*' fzf-preview 'bat --color=always --style=numbers $realpath 2>/dev/null || ls -la --color=always $realpath'
fi
setopt AUTO_MENU COMPLETE_IN_WORD ALWAYS_TO_END AUTO_PARAM_SLASH NO_CASE_GLOB
# ===========================================================
# HISTORY
# ===========================================================
HISTFILE=~/.zsh_history
HISTSIZE=50000
SAVEHIST=50000
HISTDUP=erase
setopt APPEND_HISTORY INC_APPEND_HISTORY SHARE_HISTORY
setopt HIST_IGNORE_SPACE HIST_IGNORE_ALL_DUPS HIST_SAVE_NO_DUPS
setopt HIST_IGNORE_DUPS HIST_FIND_NO_DUPS HIST_REDUCE_BLANKS
setopt HIST_VERIFY EXTENDED_HISTORY
# ===========================================================
# KEY BINDINGS
# ===========================================================
bindkey -e
bindkey '^[[H' beginning-of-line
bindkey '^[[F' end-of-line
bindkey '^[[3~' delete-char
bindkey '^[[1;5C' forward-word # Ctrl→ jump word
bindkey '^[[1;5D' backward-word # Ctrl← jump word
bindkey '^[c' kill-region
# → accepts the current autosuggestion (same as macOS behaviour)
bindkey '^[[C' forward-char
# End key also accepts full autosuggestion
bindkey '^[[F' end-of-line
# Ctrl-Space accepts autosuggestion without moving cursor
bindkey '^ ' autosuggest-accept
# zsh-history-substring-search: ↑/↓ filters by typed prefix
bindkey '\e[A' history-substring-search-up
bindkey '\e[B' history-substring-search-down
# ===========================================================
# DIRECTORY NAVIGATION
# ===========================================================
setopt AUTO_CD AUTO_PUSHD PUSHD_IGNORE_DUPS PUSHD_SILENT
# ===========================================================
# MISC
# ===========================================================
setopt INTERACTIVE_COMMENTS CORRECT NO_BEEP MULTIOS
# ===========================================================
# FZF
# ===========================================================
eval "$(fzf --zsh)"
export FZF_DEFAULT_OPTS="
--height 40%
--layout=reverse
--border=rounded
--preview-window=right:50%:wrap
--color=fg:#cdd6f4,bg:#1e1e2e,hl:#89b4fa
--color=fg+:#cdd6f4,bg+:#313244,hl+:#89dceb
--color=info:#cba6f7,prompt:#f38ba8,pointer:#f5c2e7
--color=marker:#a6e3a1,spinner:#f5c2e7,header:#94e2d5
"
if command -v fd &> /dev/null; then
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
fi
ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE="fg=#585b70"
ZSH_AUTOSUGGEST_STRATEGY=(history completion)
HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND='fg=#1e1e2e,bg=#a6e3a1,bold'
HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND='fg=#1e1e2e,bg=#f38ba8,bold'
# ===========================================================
# EXTERNAL TOOLS
# ===========================================================
eval "$(zoxide init zsh)"
eval "$(starship init zsh)"
# ===========================================================
# ALIASES — Navigation
# ===========================================================
alias ..='cd ..'
alias ...='cd ../..'
alias ....='cd ../../..'
alias ~='cd ~'
alias -- -='cd -'
# ===========================================================
# ALIASES — ls / eza
# ===========================================================
if command -v eza &>/dev/null; then
alias l='eza --icons --group --group-directories-first'
alias ls='eza --icons --group --group-directories-first'
alias la='eza --icons -la --all --long --group --group-directories-first --header --time-style long-iso'
alias ll='eza --long --group --group-directories-first --icons --header --time-style long-iso'
alias lla='eza -lha --icons --git --group-directories-first --header --time-style long-iso'
alias lt='eza --icons -T -L=1'
alias llt='eza --tree --level=3 --icons --long'
alias tree="eza --tree --all --ignore-glob='*.o|*.log' --git"
else
alias l='ls -CF --color=auto'
alias ls='ls --color=auto'
alias la='ls -la --color=auto'
alias ll='ls -lh --color=auto'
alias lla='ls -lha --color=auto'
alias lt='ls -la --color=auto'
alias llt='ls -la --color=auto'
alias tree='find . -maxdepth 3 -print'
fi
# ===========================================================
# ALIASES — cat / bat
# ===========================================================
alias cat='bat --paging=never'
alias catp='bat'
# ===========================================================
# ALIASES — Editor
# ===========================================================
alias v='nvim'
alias vi='nvim'
alias vim='nvim'
alias nv='nvim'
alias svim='sudo nvim'
alias vz='nvim ~/.zshrc'
alias sz='source ~/.zshrc'
# ===========================================================
# ALIASES — Git
# ===========================================================
alias g='git'
alias gs='git status -sb'
alias gaa='git add .'
alias gc='git commit -m'
alias gca='git commit --amend --no-edit'
alias gp='git push'
alias gpf='git push --force-with-lease'
alias gl='git pull'
alias glog='git log --oneline --graph --decorate --all'
alias gd='git diff'
alias gds='git diff --staged'
alias gcb='git checkout -b'
alias gst='git stash'
alias gstp='git stash pop'
alias gri='git rebase -i'
# ===========================================================
# ALIASES — System / package manager
# ===========================================================
if command -v apt >/dev/null 2>&1; then
alias update='sudo apt update && sudo apt upgrade -y'
alias install='sudo apt install -y'
alias remove='sudo apt remove -y'
alias purge='sudo apt purge -y'
alias autoremove='sudo apt autoremove -y'
else
alias update='sudo dnf upgrade --refresh -y'
alias install='sudo dnf install -y'
alias remove='sudo dnf remove -y'
alias clean='sudo dnf autoremove -y && sudo dnf clean all'
fi
alias ports='ss -tulpn'
alias psg='ps aux | grep'
alias df='df -h'
alias du='du -sh'
alias free='free -h'
alias top='htop 2>/dev/null || top'
alias path='echo $PATH | tr ":" "\n"'
alias myip='curl -s ifconfig.me'
alias localip="ip -4 addr | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v 127"
# ===========================================================
# ALIASES — Safety
# ===========================================================
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
alias mkdir='mkdir -pv'
# ===========================================================
# ALIASES — Utilities
# ===========================================================
alias c='clear'
alias cls='clear'
alias q='exit'
alias h='history | tail -50'
alias hg='history | grep'
alias reload='exec zsh'
alias zshconfig='nvim ~/.zshrc'
alias week='date +%V'
alias timestamp='date +"%Y%m%d_%H%M%S"'
alias rg='rg --smart-case --hidden'
alias fd='fd -Lu'
alias weather='curl v2.wttr.in'
alias ff='clear && fastfetch'
# ===========================================================
# FUNCTIONS
# ===========================================================
# mkcd — mkdir + cd in one shot
mkcd() { mkdir -p "$1" && cd "$1"; }
# up N — go up N directories (e.g. up 3)
up() { local p=""; for _ in $(seq 1 "${1:-1}"); do p+="../"; done; cd "$p"; }
# extract — universal archive extractor
extract() {
if [ -f "$1" ]; then
case "$1" in
*.tar.bz2) tar xjf "$1" ;;
*.tar.gz) tar xzf "$1" ;;
*.tar.xz) tar xJf "$1" ;;
*.tar.zst) tar --zstd -xf "$1" ;;
*.bz2) bunzip2 "$1" ;;
*.gz) gunzip "$1" ;;
*.rar) unrar x "$1" ;;
*.tar) tar xf "$1" ;;
*.tbz2) tar xjf "$1" ;;
*.tgz) tar xzf "$1" ;;
*.zip) unzip "$1" ;;
*.Z) uncompress "$1" ;;
*.7z) 7z x "$1" ;;
*) echo "'$1' cannot be extracted" ;;
esac
else
echo "'$1' is not a valid file"
fi
}
# bak — timestamped backup copy
bak() { cp "$1" "$1.bak.$(date +%Y%m%d_%H%M%S)"; }
# fcd — fuzzy-find a directory and cd into it
fcd() { cd "$(find "${1:-.}" -type d 2>/dev/null | fzf)" || return; }
# fe — fuzzy-find a file and open in nvim
fe() { nvim "$(fzf --preview 'bat --color=always {}')"; }
# ===========================================================
# FASTFETCH — run on every new terminal
# ===========================================================
fastfetch
ZSHRC
# ===========================================================
# Step 11: Install LazyVim
# ===========================================================
echo "Setting up LazyVim..."
mkdir -p ~/.config/nvim
git clone https://github.com/LazyVim/starter ~/.config/nvim
rm -rf ~/.config/nvim/.git
# ===========================================================
# Step 12: Set Zsh as default shell
# ===========================================================
echo "Setting Zsh as default shell..."
sudo chsh -s "$(which zsh)" "$TARGET_USER"
echo ""
echo "Done! Backup saved to: $BACKUP_FILE"
echo "Run 'exec zsh' or open a new terminal to start."
echo "(zinit will auto-install all plugins on first launch)"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment