Files
server-cli-tools/install.sh
Marek Paulik eda09583b5 Change zsh theme
Custom theme is very minimal and has little info about your whereabouts
on the server. "bira" theme set as default.

Custom theme wasn't removed, just commented-out.
2026-06-05 11:33:51 +02:00

602 lines
15 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# ---------------------------------------------------------------------------
# Server CLI Tools — interactive installer
# Clone this repo to any Ubuntu/Debian server and run: ./install.sh
# ---------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIGS="$SCRIPT_DIR/configs"
BACKUP_DIR="$HOME/.server_cli_tools_bkp/$(date +%Y%m%d-%H%M%S)"
# SSH from Ghostty and other clients forwards TERM values (e.g. xterm-ghostty)
# that remote servers often lack in terminfo — breaks clear/tput and colors.
if ! infocmp "${TERM:-}" &>/dev/null; then
for _term_fallback in xterm-256color xterm screen dumb; do
if infocmp "$_term_fallback" &>/dev/null; then
export TERM="$_term_fallback"
break
fi
done
unset _term_fallback
fi
# Colors (matching the dotfiles convention)
e_err() { echo -e "\e[1;31m$1\e[0m"; }
e_info() { echo -e "\e[1;34m$1\e[0m"; }
e_succ() { echo -e "\e[1;32m$1\e[0m"; }
e_warn() { echo -e "\e[1;38;5;208m$1\e[0m"; }
INSTALLED=()
SKIPPED=()
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
backup_file() {
local src="$1"
if [ -e "$src" ]; then
local rel="${src#$HOME/}"
local dest="$BACKUP_DIR/$rel"
mkdir -p "$(dirname "$dest")"
cp -a "$src" "$dest"
e_info " Backed up $src"
fi
}
copy_config() {
local src="$1" dest="$2"
backup_file "$dest"
mkdir -p "$(dirname "$dest")"
cp -a "$src" "$dest"
}
copy_dir() {
local src="$1" dest="$2"
if [ -d "$dest" ]; then
backup_file "$dest"
fi
mkdir -p "$dest"
cp -a "$src"/. "$dest"/
}
command_exists() {
command -v "$1" >/dev/null 2>&1
}
safe_clear() {
clear 2>/dev/null || printf '\033[H\033[2J'
}
check_os() {
if [ ! -f /etc/os-release ]; then
e_err "Cannot detect OS. This script supports Ubuntu/Debian only."
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu | debian) ;;
*)
e_warn "Detected $ID — this script is designed for Ubuntu/Debian."
e_warn "Package installation may not work. Configs will still be copied."
;;
esac
}
apt_install() {
local packages=("$@")
local to_install=()
for pkg in "${packages[@]}"; do
if dpkg -s "$pkg" &>/dev/null; then
e_info " $pkg is already installed"
else
to_install+=("$pkg")
fi
done
if [ ${#to_install[@]} -gt 0 ]; then
e_info " Installing: ${to_install[*]}"
sudo apt-get install -y "${to_install[@]}"
fi
}
# ---------------------------------------------------------------------------
# Component installers
# ---------------------------------------------------------------------------
install_core_packages() {
e_info "\n[1/6] Core packages"
sudo apt-get update -qq
apt_install curl vim bat fzf htop tmux zsh ripgrep fd-find zoxide
# bat is installed as 'batcat' on Debian/Ubuntu
if command_exists batcat && ! command_exists bat; then
sudo ln -sf /usr/bin/batcat /usr/local/bin/bat
e_info " Symlinked batcat -> bat"
fi
# eza — not in all distro repos, try apt first, fall back to cargo/binary
if ! command_exists eza; then
if apt-cache show eza &>/dev/null; then
apt_install eza
else
e_warn " eza not in apt repos. Install manually: https://github.com/eza-community/eza"
fi
else
e_info " eza is already installed"
fi
# git-delta
if ! command_exists delta; then
if apt-cache show git-delta &>/dev/null; then
apt_install git-delta
else
e_warn " git-delta not in apt repos. Install from: https://github.com/dandavison/delta/releases"
fi
else
e_info " delta is already installed"
fi
INSTALLED+=("core-packages")
}
install_zsh() {
e_info "\n[2/6] Zsh + Oh-My-Zsh"
if ! command_exists zsh; then
apt_install zsh
fi
# Oh-My-Zsh
if [ ! -d "$HOME/.oh-my-zsh" ]; then
e_info " Installing Oh-My-Zsh..."
RUNZSH=no CHSH=no sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
else
e_info " Oh-My-Zsh already installed"
fi
# Plugins
local zsh_custom="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}"
if [ ! -d "$zsh_custom/plugins/zsh-autosuggestions" ]; then
e_info " Cloning zsh-autosuggestions..."
git clone --quiet https://github.com/zsh-users/zsh-autosuggestions "$zsh_custom/plugins/zsh-autosuggestions"
else
e_info " zsh-autosuggestions already installed"
fi
if [ ! -d "$zsh_custom/plugins/zsh-syntax-highlighting" ]; then
e_info " Cloning zsh-syntax-highlighting..."
git clone --quiet https://github.com/zsh-users/zsh-syntax-highlighting.git "$zsh_custom/plugins/zsh-syntax-highlighting"
else
e_info " zsh-syntax-highlighting already installed"
fi
# Theme
# mkdir -p "$HOME/.config/zsh"
# copy_config "$CONFIGS/zsh/zhann-elvis.zsh-theme" "$HOME/.config/zsh/zhann-elvis.zsh-theme"
# e_info " Installed zhann-elvis.zsh-theme"
# .zshrc
copy_config "$CONFIGS/zshrc" "$HOME/.zshrc"
e_info " Installed .zshrc"
# Change default shell to zsh (if not already)
if [ "$(basename "$SHELL")" != "zsh" ]; then
e_info " Changing default shell to zsh..."
if command_exists chsh; then
chsh -s "$(which zsh)" || e_warn " chsh failed — change shell manually: chsh -s \$(which zsh)"
fi
fi
INSTALLED+=("zsh")
}
install_aliases() {
e_info "\n[3/6] Aliases + custom tools"
copy_dir "$CONFIGS/aliases" "$HOME/.config/aliases"
chmod +x "$HOME/.config/aliases"/.aliases "$HOME/.config/aliases"/.functions.sh 2>/dev/null || true
e_info " Installed aliases to ~/.config/aliases/"
copy_dir "$CONFIGS/custom_tools" "$HOME/.config/custom_tools"
chmod +x "$HOME/.config/custom_tools"/*.sh 2>/dev/null || true
e_info " Installed custom tools to ~/.config/custom_tools/"
INSTALLED+=("aliases" "custom-tools")
}
install_terminal_configs() {
e_info "\n[4/6] Terminal configs (bat)"
mkdir -p "$HOME/.config/bat"
copy_config "$CONFIGS/bat/config" "$HOME/.config/bat/config"
e_info " Installed bat config (theme: base16)"
INSTALLED+=("terminal-configs")
}
install_docker() {
e_info "\n[5/6] Docker"
sudo apt-get update -qq
apt_install docker.io
if command_exists systemctl; then
sudo systemctl enable --now docker 2>/dev/null ||
e_warn " Could not enable docker service — start manually: sudo systemctl start docker"
fi
if id -nG "$USER" 2>/dev/null | grep -qw docker; then
e_info " $USER is already in the docker group"
else
e_info " Adding $USER to the docker group..."
sudo usermod -aG docker "$USER"
e_warn " Log out and back in (or run: newgrp docker) for group membership to apply"
fi
mkdir -p "$HOME/.config/aliases"
copy_config "$CONFIGS/aliases/.docker_aliases" "$HOME/.config/aliases/.docker_aliases"
e_info " Installed Docker aliases"
INSTALLED+=("docker")
}
install_git_config() {
e_info "\n[6/6] Git config (delta pager)"
if ! command_exists delta; then
e_warn " git-delta not installed — config will be ready when delta is available"
fi
local gitconfig="$HOME/.gitconfig"
# Only inject delta config if not already present
if [ -f "$gitconfig" ] && grep -q '\[delta\]' "$gitconfig" 2>/dev/null; then
e_info " Delta config already present in ~/.gitconfig"
else
e_info " Adding delta pager config to ~/.gitconfig"
cat >>"$gitconfig" <<'DELTA'
[core]
pager = delta
[interactive]
diffFilter = delta --color-only
[delta]
side-by-side = true
navigate = true
dark = true
[merge]
conflictStyle = zdiff3
DELTA
fi
INSTALLED+=("git-config")
}
# ---------------------------------------------------------------------------
# Uninstall
# ---------------------------------------------------------------------------
uninstall() {
e_warn "This will remove configs installed by Server CLI Tools."
e_warn "Installed packages (apt) will NOT be removed."
echo ""
printf "Continue? [y/N] "
read -r confirm
case "$confirm" in
y | Y) ;;
*)
echo "Aborted."
exit 0
;;
esac
local removed=()
# Aliases + custom tools
if [ -d "$HOME/.config/aliases" ]; then
rm -rf "$HOME/.config/aliases"
removed+=("aliases")
fi
if [ -d "$HOME/.config/custom_tools" ]; then
rm -rf "$HOME/.config/custom_tools"
removed+=("custom-tools")
fi
# Terminal configs
if [ -f "$HOME/.config/bat/config" ]; then
rm -f "$HOME/.config/bat/config"
removed+=("bat-config")
fi
# Zsh theme + zshrc
if [ -f "$HOME/.config/zsh/zhann-elvis.zsh-theme" ]; then
rm -f "$HOME/.config/zsh/zhann-elvis.zsh-theme"
removed+=("zsh-theme")
fi
if [ -f "$HOME/.zshrc" ]; then
rm -f "$HOME/.zshrc"
removed+=("zshrc")
fi
# Oh-My-Zsh plugins (only the ones we installed)
local zsh_custom="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}"
if [ -d "$zsh_custom/plugins/zsh-autosuggestions" ]; then
rm -rf "$zsh_custom/plugins/zsh-autosuggestions"
removed+=("zsh-autosuggestions")
fi
if [ -d "$zsh_custom/plugins/zsh-syntax-highlighting" ]; then
rm -rf "$zsh_custom/plugins/zsh-syntax-highlighting"
removed+=("zsh-syntax-highlighting")
fi
# Git config — strip delta block
local gitconfig="$HOME/.gitconfig"
if [ -f "$gitconfig" ] && grep -q '\[delta\]' "$gitconfig" 2>/dev/null; then
local tmp
tmp=$(mktemp)
awk '
/^\[core\]/ { if (skip==0) { peek=1; hold=$0; next } }
peek==1 {
if ($0 ~ /pager *= *delta/) { skip=1; next }
else { print hold; peek=0 }
}
/^\[interactive\]/ && skip { next }
/^\[delta\]/ { skip=1; next }
/^\[merge\]/ && skip { skip_merge=1; next }
skip_merge==1 { skip_merge=0; skip=0; next }
/^\[/ { skip=0 }
skip { next }
{ print }
' "$gitconfig" >"$tmp"
mv "$tmp" "$gitconfig"
removed+=("git-delta-config")
fi
echo ""
if [ ${#removed[@]} -gt 0 ]; then
e_succ "Removed:"
for item in "${removed[@]}"; do
echo " - $item"
done
else
e_info "Nothing to remove."
fi
# Offer to restore backups
local bkp_base="$HOME/.server_cli_tools_bkp"
if [ -d "$bkp_base" ]; then
echo ""
local latest
latest=$(ls -1t "$bkp_base" 2>/dev/null | head -n1)
if [ -n "$latest" ]; then
e_info "Backup found: $bkp_base/$latest"
printf "Restore from this backup? [y/N] "
read -r restore
case "$restore" in
y | Y)
cp -a "$bkp_base/$latest"/. "$HOME"/
e_succ "Restored from $bkp_base/$latest"
;;
*) e_info "Skipping restore." ;;
esac
fi
fi
echo ""
}
# ---------------------------------------------------------------------------
# Menu
# ---------------------------------------------------------------------------
COMPONENTS=(
"Core packages (vim, bat, eza, fzf, htop, tmux, rg, fd, zoxide, delta)"
"Zsh + Oh-My-Zsh (shell, plugins)"
"Aliases + custom tools"
"Terminal configs (bat)"
"Docker (engine + aliases)"
"Git config (delta pager)"
)
INSTALLERS=(
install_core_packages
install_zsh
install_aliases
install_terminal_configs
install_docker
install_git_config
)
show_menu() {
local count=${#COMPONENTS[@]}
local selected=()
for ((i = 0; i < count; i++)); do
selected+=(1)
done
while true; do
safe_clear
echo ""
e_info "Server CLI Tools — Installer"
echo "============================="
echo ""
echo "Select components (enter number to toggle, a=all, n=none):"
echo ""
for ((i = 0; i < count; i++)); do
local mark=" "
if [ "${selected[$i]}" -eq 1 ]; then
mark=" x "
fi
printf " [%s] %d) %s\n" "$mark" "$((i + 1))" "${COMPONENTS[$i]}"
done
echo ""
echo " [a] Select all [n] Select none [Enter] Install [q] Quit"
echo ""
printf " > "
read -r choice
case "$choice" in
q | Q)
echo "Aborted."
exit 0
;;
a | A)
for ((i = 0; i < count; i++)); do
selected[$i]=1
done
;;
n | N)
for ((i = 0; i < count; i++)); do
selected[$i]=0
done
;;
"")
# Run selected installers
local any=0
for ((i = 0; i < count; i++)); do
if [ "${selected[$i]}" -eq 1 ]; then
any=1
fi
done
if [ "$any" -eq 0 ]; then
e_warn "Nothing selected!"
sleep 1
continue
fi
echo ""
for ((i = 0; i < count; i++)); do
if [ "${selected[$i]}" -eq 1 ]; then
${INSTALLERS[$i]}
else
SKIPPED+=("${COMPONENTS[$i]}")
fi
done
return
;;
*)
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "$count" ]; then
local idx=$((choice - 1))
if [ "${selected[$idx]}" -eq 1 ]; then
selected[$idx]=0
else
selected[$idx]=1
fi
fi
;;
esac
done
}
# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------
print_summary() {
echo ""
echo "==========================================="
e_succ " Installation complete!"
echo "==========================================="
if [ ${#INSTALLED[@]} -gt 0 ]; then
echo ""
e_succ "Installed:"
for item in "${INSTALLED[@]}"; do
echo " + $item"
done
fi
if [ ${#SKIPPED[@]} -gt 0 ]; then
echo ""
e_warn "Skipped:"
for item in "${SKIPPED[@]}"; do
echo " - $item"
done
fi
if [ -d "$BACKUP_DIR" ]; then
echo ""
e_info "Backups saved to: $BACKUP_DIR"
fi
echo ""
e_info "To apply changes, start a new shell or run:"
echo " exec zsh"
echo ""
e_info "Type 'h' to see all available aliases."
echo ""
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
usage() {
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --all Install all components (non-interactive)"
echo " --update Pull latest changes and re-run installer"
echo " --update --all Pull latest and install all (non-interactive)"
echo " --uninstall Remove installed configs (keeps packages)"
echo " --help Show this help message"
echo ""
echo "Without options, an interactive menu is shown."
}
main() {
case "${1:-}" in
--help | -h)
usage
exit 0
;;
--uninstall)
uninstall
exit 0
;;
--update)
e_info "Pulling latest changes..."
git -C "$SCRIPT_DIR" pull || {
e_err "git pull failed"
exit 1
}
echo ""
check_os
if [ "${2:-}" = "--all" ] || [ "${2:-}" = "-a" ]; then
e_info "Server CLI Tools — installing all components"
for installer in "${INSTALLERS[@]}"; do
$installer
done
else
show_menu
fi
print_summary
;;
--all | -a)
check_os
e_info "Server CLI Tools — installing all components"
for installer in "${INSTALLERS[@]}"; do
$installer
done
print_summary
;;
"")
check_os
show_menu
print_summary
;;
*)
e_err "Unknown option: $1"
usage
exit 1
;;
esac
}
main "$@"