Add server CLI tools installer for Ubuntu/Debian servers
Introduce a self-contained repo with an interactive install.sh that provisions zsh, oh-my-zsh, aliases, bat/yazi configs, docker shortcuts, and git-delta pager setup. Configs are adapted from dotfiles for SSH-only servers — no desktop or neovim dependencies. Installer supports --all, --update (git pull + re-run), and --uninstall with backup restore. Core packages include vim (default editor), yazi via apt, and idempotent apt installs with warnings for eza and git-delta when repos lack them.
This commit is contained in:
69
.agents/handoff/2026-06-04-1854-initial-setup.md
Normal file
69
.agents/handoff/2026-06-04-1854-initial-setup.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# Handoff: Server CLI Tools — initial setup complete
|
||||||
|
|
||||||
|
**Date:** 2026-06-04T18:54+02:00
|
||||||
|
**Focus for next session:** Initial commit, push to remote, and any refinements to the installer or configs.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Standalone repo for provisioning Ubuntu/Debian servers with a consistent CLI environment — zsh, aliases, git config, Docker shortcuts, and core tools. Extracted and adapted from the user's personal [dotfiles repo](https://github.com/neurobrko/dotfiles), stripped of desktop/neovim/GUI items to be server-SSH-only.
|
||||||
|
|
||||||
|
## Current state
|
||||||
|
- Working directory: `/home/elvis/code/server_cli_tools`
|
||||||
|
- Branch / commit: `master` — repo initialized, all files staged, **no commits yet**
|
||||||
|
- Last action taken: `git init && git add .`
|
||||||
|
- Build/test status: `bash -n install.sh` passes; `./install.sh --help` works
|
||||||
|
|
||||||
|
## Artifacts
|
||||||
|
|
||||||
|
### Key files
|
||||||
|
- `install.sh` — main installer (6 components, interactive menu or `--all`)
|
||||||
|
- `configs/aliases/.aliases` — general aliases (server-adapted, no neovim/opencode/SyncSuite)
|
||||||
|
- `configs/aliases/.functions.sh` — helper functions, alias help system, uses `${EDITOR:-nano}`
|
||||||
|
- `configs/aliases/.git_aliases` — git shortcuts (removed lazygit, hardcoded gp path)
|
||||||
|
- `configs/aliases/.docker_aliases` — docker management (unchanged from dotfiles)
|
||||||
|
- `configs/aliases/.git_bash_autocomplete` — bash git completion
|
||||||
|
- `configs/custom_tools/backup_dotfiles.sh` — backup conflicting dotfiles
|
||||||
|
- `configs/custom_tools/get_ip.sh` — get+copy interface IP
|
||||||
|
- `configs/custom_tools/strip_file.sh` — strip comments/blanks from configs
|
||||||
|
- `configs/bat/config` — bat theme (base16)
|
||||||
|
- `configs/yazi/yazi.toml` — yazi file manager config
|
||||||
|
- `configs/zsh/zhann-elvis.zsh-theme` — custom git-aware zsh prompt
|
||||||
|
- `configs/zshrc` — .zshrc with oh-my-zsh, plugins, fzf integration
|
||||||
|
- `README.md` — repo documentation
|
||||||
|
- `.gitignore` — swap files, DS_Store
|
||||||
|
|
||||||
|
### Uncommitted changes
|
||||||
|
Everything is staged (`git add .`), awaiting initial commit. 15 files total.
|
||||||
|
|
||||||
|
## What's done
|
||||||
|
- Analyzed dotfiles repo structure and identified server-relevant components
|
||||||
|
- Created self-contained repo with all configs bundled (no dependency on dotfiles repo)
|
||||||
|
- Adapted aliases: removed `nv`, `noc`, `fnv`, `nvc`, `oc`, `rtr`, `rfm`, `gp`, `lg`, `xc`, `dot`, `ape`
|
||||||
|
- Adapted functions: replaced `nv` with `${EDITOR:-nano}`, removed `fuzzy_nvim`, `activate_poetry_env`
|
||||||
|
- Changed `py` alias from `python3.12` to `python3`
|
||||||
|
- Created `.zshrc` with portable fzf sourcing (tries multiple paths)
|
||||||
|
- Built interactive installer with toggle menu, `--all` mode, backup system, idempotent checks
|
||||||
|
- Installer components: core packages, zsh+oh-my-zsh, aliases+tools, terminal configs (bat/yazi), docker aliases, git config (delta)
|
||||||
|
|
||||||
|
## What's next
|
||||||
|
1. Create initial commit
|
||||||
|
2. Set up remote (Gitea at git.mpx.sk or GitHub) and push
|
||||||
|
3. Test on a fresh server (e.g. Hetzner VPS from the re_kit project)
|
||||||
|
4. Consider: add `uninstall.sh` or `--uninstall` flag?
|
||||||
|
5. Consider: add `update.sh` to pull latest and re-run selective installs?
|
||||||
|
|
||||||
|
## Open questions / decisions pending
|
||||||
|
- **Remote location**: where to host this repo (Gitea vs GitHub)
|
||||||
|
- **Branch name**: currently `master` (git default), may want to rename to `main`
|
||||||
|
- **yazi installation**: currently only installs config; consider adding cargo-based yazi install to the script
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
- The `eza` package is not available in all Ubuntu versions (e.g. older than 24.04). The installer warns and suggests manual install.
|
||||||
|
- Same for `git-delta` — not in all apt repos. Falls back to a warning with a link.
|
||||||
|
- The `bat` binary is `batcat` on Debian/Ubuntu — the installer creates a symlink at `/usr/local/bin/bat`.
|
||||||
|
- The `.zshrc` sets `EDITOR=nano` by default since this is server-only (no neovim).
|
||||||
|
- `fzf` shell integration varies across distro versions — the zshrc tries both the packaged path and `fzf --zsh`.
|
||||||
|
|
||||||
|
## Suggested skills
|
||||||
|
- `create-commit-message` — for the initial commit
|
||||||
|
- `copy-to-clipboard` — if user wants to copy commands for server testing
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
78
README.md
Normal file
78
README.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# Server CLI Tools
|
||||||
|
|
||||||
|
Interactive installer for setting up a consistent shell environment on Ubuntu/Debian servers. Clone to any server, run the installer, pick what you need.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone <repo-url> ~/server_cli_tools
|
||||||
|
cd ~/server_cli_tools
|
||||||
|
./install.sh # interactive menu
|
||||||
|
./install.sh --all # install everything
|
||||||
|
./install.sh --update # pull latest + re-run menu
|
||||||
|
./install.sh --uninstall # remove installed configs
|
||||||
|
```
|
||||||
|
|
||||||
|
## What's included
|
||||||
|
|
||||||
|
| # | Component | What it does |
|
||||||
|
|---|-----------|-------------|
|
||||||
|
| 1 | **Core packages** | `vim`, `bat`, `eza`, `fzf`, `htop`, `tmux`, `zsh`, `ripgrep`, `fd-find`, `zoxide`, `yazi`, `git-delta` |
|
||||||
|
| 2 | **Zsh + Oh-My-Zsh** | Shell, plugins (`zsh-autosuggestions`, `zsh-syntax-highlighting`, `z`), custom prompt theme |
|
||||||
|
| 3 | **Aliases + tools** | General, git, and docker aliases with a self-documenting help system (`h`) |
|
||||||
|
| 4 | **Terminal configs** | `bat` theme (base16), `yazi` file manager config |
|
||||||
|
| 5 | **Docker aliases** | Start/stop toggle, bulk stop/remove, image management |
|
||||||
|
| 6 | **Git config** | `git-delta` as pager with side-by-side diffs |
|
||||||
|
|
||||||
|
## Alias highlights
|
||||||
|
|
||||||
|
Type `h` after installation to see all aliases with descriptions. Some highlights:
|
||||||
|
|
||||||
|
```
|
||||||
|
ll — eza with icons, groups, timestamps
|
||||||
|
pf — fzf + bat file preview
|
||||||
|
cdf — fuzzy cd into any subdirectory
|
||||||
|
mkcd — mkdir + cd in one command
|
||||||
|
twd — tar current directory
|
||||||
|
gdn — numbered git diff (then gdn 3 to diff file #3)
|
||||||
|
gcpm — git add -u + commit + push in one command
|
||||||
|
gstm — git stash with mandatory message
|
||||||
|
ds — toggle docker container start/stop
|
||||||
|
da — docker ps -a
|
||||||
|
```
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
- **Idempotent** — safe to run multiple times; skips already-installed packages
|
||||||
|
- **Non-destructive** — existing configs are backed up to `~/.server_cli_tools_bkp/<timestamp>/`
|
||||||
|
- **Selective** — interactive menu lets you toggle individual components
|
||||||
|
- **Ubuntu/Debian** — uses `apt`; warns on other distros (configs still work)
|
||||||
|
|
||||||
|
## Repo structure
|
||||||
|
|
||||||
|
```
|
||||||
|
server_cli_tools/
|
||||||
|
install.sh # main installer
|
||||||
|
configs/
|
||||||
|
aliases/
|
||||||
|
.aliases # general aliases (server-adapted)
|
||||||
|
.functions.sh # helper functions + alias help system
|
||||||
|
.git_aliases # git shortcuts + stash management
|
||||||
|
.docker_aliases # docker management aliases
|
||||||
|
.git_bash_autocomplete # bash git completion
|
||||||
|
custom_tools/
|
||||||
|
backup_dotfiles.sh # backup conflicting dotfiles
|
||||||
|
bat/config # bat theme
|
||||||
|
yazi/yazi.toml # yazi file manager config
|
||||||
|
zsh/zhann-elvis.zsh-theme # custom zsh prompt (git-aware)
|
||||||
|
zshrc # .zshrc with oh-my-zsh + plugins
|
||||||
|
```
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
After installation, create local overrides that won't be tracked:
|
||||||
|
|
||||||
|
- `~/.config/aliases/.local_aliases` — machine-specific aliases
|
||||||
|
- `~/.config/aliases/.custom_aliases` — temporary/personal aliases
|
||||||
|
|
||||||
|
Use `ea l` to edit local aliases, `ea c` for custom, `sa` to re-source.
|
||||||
52
configs/aliases/.aliases
Normal file
52
configs/aliases/.aliases
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# shellcheck source=.functions.sh
|
||||||
|
. ~/.config/aliases/.functions.sh
|
||||||
|
|
||||||
|
### General aliases
|
||||||
|
# ll pimped with eza
|
||||||
|
alias ll="eza -lag --icons=always --group-directories-first --smart-group --time-style='+%d %b %y %H:%M'"
|
||||||
|
# du all items in folder, human readable
|
||||||
|
alias dh='du -chs *'
|
||||||
|
# yazi file manager
|
||||||
|
alias y='yazi'
|
||||||
|
# preview/find file using fzf and bat
|
||||||
|
alias pf='fzf --preview "bat --color=always {}"'
|
||||||
|
# cd to dir using fuzzy finder (fzf)
|
||||||
|
alias cdf='cd "$(find . -type d | fzf)"'
|
||||||
|
# (executed from / or /home might be slow)
|
||||||
|
next_line
|
||||||
|
# cd to file's directory
|
||||||
|
alias cdtf=cd_to_file
|
||||||
|
# mkdir & cd to it
|
||||||
|
alias mkcd=mkdir_and_cd
|
||||||
|
# grep shorthand
|
||||||
|
alias g='grep'
|
||||||
|
# python interpreter/REPL
|
||||||
|
alias py='python3'
|
||||||
|
# source ~/.config/aliases/.aliases
|
||||||
|
alias sa='source ~/.config/aliases/.aliases'
|
||||||
|
# edit aliases files ('ea h' for help)
|
||||||
|
alias ea=edit_aliases
|
||||||
|
# pack 'cwd' to tar.gz in parent with 'cwd' name
|
||||||
|
alias twd='tar -czf "../$(basename "$PWD").tar.gz" .'
|
||||||
|
# print alias description
|
||||||
|
alias h='display_alias_descriptions'
|
||||||
|
|
||||||
|
# LOCAL/CUSTOM bash aliases
|
||||||
|
# You can create custom local aliases in .local_aliases
|
||||||
|
# or .custom_aliases file for some temporary aliases
|
||||||
|
if [ -f ~/.config/aliases/.local_aliases ]; then
|
||||||
|
. ~/.config/aliases/.local_aliases
|
||||||
|
fi
|
||||||
|
if [ -f ~/.config/aliases/.custom_aliases ]; then
|
||||||
|
. ~/.config/aliases/.custom_aliases
|
||||||
|
fi
|
||||||
|
# source git aliases
|
||||||
|
if [ -f ~/.config/aliases/.git_aliases ]; then
|
||||||
|
. ~/.config/aliases/.git_aliases
|
||||||
|
fi
|
||||||
|
# source docker aliases
|
||||||
|
if [ -f ~/.config/aliases/.docker_aliases ]; then
|
||||||
|
. ~/.config/aliases/.docker_aliases
|
||||||
|
fi
|
||||||
69
configs/aliases/.docker_aliases
Normal file
69
configs/aliases/.docker_aliases
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
docker_start_stop() {
|
||||||
|
local cname="$1"
|
||||||
|
if [ -z "$cname" ]; then
|
||||||
|
e_info "Usage: docker_toggle <container_name_or_id>"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
state=$(docker inspect -f '{{.State.Status}}' "$cname" 2>/dev/null)
|
||||||
|
case $state in
|
||||||
|
"running")
|
||||||
|
docker stop "$cname"
|
||||||
|
e_info "Container '$cname' stopped."
|
||||||
|
;;
|
||||||
|
"exited")
|
||||||
|
docker start "$cname"
|
||||||
|
e_info "Container '$cname' started."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
e_err "Container '$cname' not found or not in a valid state."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
docker_stop_all() {
|
||||||
|
for cont in $(docker ps -a --format {{.Names}}); do
|
||||||
|
docker stop $cont >/dev/null
|
||||||
|
e_info "Container $cont stopped."
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
docker_remove() {
|
||||||
|
container=$(docker rm $1)
|
||||||
|
e_info "Container $container removed."
|
||||||
|
}
|
||||||
|
|
||||||
|
docker_remove_all() {
|
||||||
|
for cont in $(docker ps -a --format {{.Names}}); do
|
||||||
|
docker rm $cont >/dev/null
|
||||||
|
e_info "Container $cont removed."
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
docker_remove_all_images() {
|
||||||
|
for img in $(docker image ls --format '{{.Repository}}:{{.Tag}}'); do
|
||||||
|
docker image rm $img >/dev/null
|
||||||
|
e_info "Image $img removed."
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
### Docker aliases
|
||||||
|
# show docker containers
|
||||||
|
alias da='docker ps -a'
|
||||||
|
# start or stop docker <container>
|
||||||
|
alias ds=docker_start_stop
|
||||||
|
# stop all docker containers
|
||||||
|
alias dsa=docker_stop_all
|
||||||
|
# remove docker <container>
|
||||||
|
alias dr=docker_remove
|
||||||
|
# remove all docker containers
|
||||||
|
alias dra=docker_remove_all
|
||||||
|
# list docker images in 'repo:tag'
|
||||||
|
alias dia='docker image ls --format {{.Repository}}:{{.Tag}}'
|
||||||
|
# remove docker <image>
|
||||||
|
alias dri='docker image rm'
|
||||||
|
# remove all docker images
|
||||||
|
alias dria=docker_remove_all_images
|
||||||
|
|
||||||
136
configs/aliases/.functions.sh
Normal file
136
configs/aliases/.functions.sh
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
function fake_alias() {
|
||||||
|
:
|
||||||
|
}
|
||||||
|
|
||||||
|
function next_line() {
|
||||||
|
:
|
||||||
|
}
|
||||||
|
|
||||||
|
# FILL LINE FUNCTIONS
|
||||||
|
fill_line_with_string() {
|
||||||
|
local string="$1"
|
||||||
|
local length="$2"
|
||||||
|
local fill="${3:- }"
|
||||||
|
if [[ ${#fill} -eq 1 ]]; then
|
||||||
|
beg_char=$fill
|
||||||
|
end_char=$fill
|
||||||
|
fi
|
||||||
|
if [[ ${#fill} -eq 2 ]]; then
|
||||||
|
beg_char="${fill:0:1}"
|
||||||
|
end_char="${fill:1:1}"
|
||||||
|
fi
|
||||||
|
if [[ ${#fill} -gt 2 ]]; then
|
||||||
|
beg_char="${fill:0:1}"
|
||||||
|
end_char="${fill:0:1}"
|
||||||
|
fi
|
||||||
|
local padding=$(((length - ${#string} - 2) / 2))
|
||||||
|
local beg
|
||||||
|
beg=$(printf "%${padding}s" | tr ' ' "$beg_char")
|
||||||
|
local end
|
||||||
|
end=$(printf "%${padding}s" | tr ' ' "$end_char")
|
||||||
|
printf "%s%s%s" "$beg" " $string " "$end"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fill_line() {
|
||||||
|
local length="$1"
|
||||||
|
local fill="$2"
|
||||||
|
printf "%*s\n" "$length" "" | tr ' ' "$fill"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ALIAS DESCRIPTIONS
|
||||||
|
line_length=59
|
||||||
|
|
||||||
|
get_alias_descriptions() {
|
||||||
|
local file_path="$1"
|
||||||
|
if [ -f "$file_path" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "@ $file_path"
|
||||||
|
local description=""
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if [[ "$line" =~ ^### ]]; then
|
||||||
|
fill_line_with_string "${line#'### '}" "$line_length" "-"
|
||||||
|
elif [[ "$line" =~ ^# ]]; then
|
||||||
|
description="${line#"# "}"
|
||||||
|
elif [[ "$line" =~ ^(alias|fake_alias) ]]; then
|
||||||
|
alias_name=$(printf "%10s" "$(echo "$line" | cut -d'=' -f1 | awk '{print $2}')")
|
||||||
|
echo "$alias_name :: $description"
|
||||||
|
elif [[ $line == "next_line" ]]; then
|
||||||
|
printf "%*s%s\n" "14" "" "$description"
|
||||||
|
else
|
||||||
|
description=""
|
||||||
|
fi
|
||||||
|
done <"$file_path"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
display_alias_descriptions() {
|
||||||
|
echo ""
|
||||||
|
fill_line_with_string "ALIASES DESCRIPTIONS" "$line_length" " "
|
||||||
|
get_alias_descriptions "$ALIASES"/.aliases
|
||||||
|
get_alias_descriptions "$ALIASES"/.git_aliases
|
||||||
|
get_alias_descriptions "$ALIASES"/.docker_aliases
|
||||||
|
get_alias_descriptions "$ALIASES"/.local_aliases
|
||||||
|
get_alias_descriptions "$ALIASES"/.custom_aliases
|
||||||
|
fill_line "$line_length" "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
ea_help() {
|
||||||
|
e_info "Usage: ea [c|l|h]"
|
||||||
|
echo "Edit ~/.config/aliases/.aliases file when used without arguments"
|
||||||
|
echo "Args: c :: edit ~/.config/aliases/.custom_aliases"
|
||||||
|
echo " l :: edit ~/.config/aliases/.local_aliases"
|
||||||
|
echo " f edit ~/.config/aliases/.functions.sh"
|
||||||
|
echo " h :: print help"
|
||||||
|
}
|
||||||
|
|
||||||
|
edit_aliases() {
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
${EDITOR:-nano} "$ALIASES"/.aliases
|
||||||
|
elif [ $# -gt 1 ]; then
|
||||||
|
e_err "Too many arguments!"
|
||||||
|
ea_help
|
||||||
|
else
|
||||||
|
case "$1" in
|
||||||
|
l) ${EDITOR:-nano} "$ALIASES"/.local_aliases ;;
|
||||||
|
c) ${EDITOR:-nano} "$ALIASES"/.custom_aliases ;;
|
||||||
|
f) ${EDITOR:-nano} "$ALIASES"/.functions.sh ;;
|
||||||
|
h) ea_help ;;
|
||||||
|
*) e_err "Invalid option!"; ea_help ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cd_to_file() {
|
||||||
|
cwd=$(pwd)
|
||||||
|
cd "${1:-.}" || return
|
||||||
|
file=$(fzf --preview 'bat --color=always {}')
|
||||||
|
if [ -z "$file" ]; then
|
||||||
|
cd "$cwd" || return
|
||||||
|
else
|
||||||
|
cd "$(dirname "$file")" || return
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir_and_cd() {
|
||||||
|
mkdir -p "$1" || e_err "Failed to create directory: $1"
|
||||||
|
cd "$1" || e_err "Failed to navigate to directory: $1"
|
||||||
|
}
|
||||||
179
configs/aliases/.git_aliases
Normal file
179
configs/aliases/.git_aliases
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# GIT FUNCTIONS
|
||||||
|
|
||||||
|
# git checkout with tab completion
|
||||||
|
# bash override for zsh gco alias
|
||||||
|
if [ -n "$BASH_VERSION" ] && [ -f ~/.config/aliases/.git_bash_autocomplete ]; then
|
||||||
|
. ~/.config/aliases/.git_bash_autocomplete
|
||||||
|
fi
|
||||||
|
|
||||||
|
# git create stast with message
|
||||||
|
git_stash_msg() {
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
e_warn "Stash changes with message! Don't be lazy..."
|
||||||
|
else
|
||||||
|
local msg="$*"
|
||||||
|
git stash -m "$msg"
|
||||||
|
e_succ "Succesfully stashed changes as '$msg' on branch '$(git branch --show-current)'."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# git apply stash by number
|
||||||
|
git_apply_stash() {
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
e_warn "Please specify stash number!"
|
||||||
|
return 1
|
||||||
|
elif [ $# -gt 1 ]; then
|
||||||
|
e_warn "Please specify only one stash number!"
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
git stash apply stash@"{$1}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# git echo stasth diff by number
|
||||||
|
# useful combined with '| xc'
|
||||||
|
git_stash_patch() {
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
e_warn "Please specify a stash number!"
|
||||||
|
return 1
|
||||||
|
elif [ $# -gt 1 ]; then
|
||||||
|
e_warn "Please specify only one stash number!"
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
git stash show -p stash@"{$1}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# drop stash by number with confirmation
|
||||||
|
# I accidentally dropped stashes, because I mix up gsp and gsd
|
||||||
|
git_stash_drop() {
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
e_warn "Please specify a stash number!"
|
||||||
|
return 1
|
||||||
|
elif [ $# -gt 1 ]; then
|
||||||
|
e_warn "Please specify only one stash number!"
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
printf "Are you sure you want to drop stash #$1? (Y/N): "
|
||||||
|
read -r confirm
|
||||||
|
if [[ "$confirm" =~ ^[yY]$ ]]; then
|
||||||
|
git stash drop stash@"{$1}"
|
||||||
|
e_info "Stash #$1 dropped."
|
||||||
|
else
|
||||||
|
e_warn "Aborted. Stash #$1 not dropped."
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# git diff with optional filename from git diff --names-only | nl
|
||||||
|
git_diff_numbered() {
|
||||||
|
local files file
|
||||||
|
files=()
|
||||||
|
while IFS= read -r line; do
|
||||||
|
files+=("$line")
|
||||||
|
done < <(git diff --name-only)
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
printf "%s\n" "${files[@]}" | nl
|
||||||
|
elif [ $# -eq 1 ] && [[ $1 =~ ^[0-9]+$ ]]; then
|
||||||
|
if [ -n "$BASH_VERSION" ]; then
|
||||||
|
file="${files[$1 - 1]}"
|
||||||
|
else
|
||||||
|
file="${files[$1]}"
|
||||||
|
fi
|
||||||
|
[ -n "$file" ] && git diff "$file"
|
||||||
|
else
|
||||||
|
e_err "Invalid argument! Provide number from the output of 'gdn'."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# git restore file based on the number from git diff --names-only | nl
|
||||||
|
git_restore_by_number() {
|
||||||
|
local files file
|
||||||
|
files=()
|
||||||
|
while IFS= read -r line; do
|
||||||
|
files+=("$line")
|
||||||
|
done < <(git diff --name-only)
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
e_err "Missing argument! Provide number from the output of 'gdn'."
|
||||||
|
elif [ $# -eq 1 ] && [[ $1 =~ ^[0-9]+$ ]]; then
|
||||||
|
if [ -n "$BASH_VERSION" ]; then
|
||||||
|
file="${files[$1 - 1]}"
|
||||||
|
else
|
||||||
|
file="${files[$1]}"
|
||||||
|
fi
|
||||||
|
[ -n "$file" ] && git restore "$file"
|
||||||
|
else
|
||||||
|
e_err "Invalid argument! Provide number from the output of 'gdn'."
|
||||||
|
fi
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# git add untracked files, commit and push
|
||||||
|
git_commit_push() {
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
e_err "No commit message provided!"
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
local message="$*"
|
||||||
|
git add -u
|
||||||
|
git commit -m "$message"
|
||||||
|
git push
|
||||||
|
e_succ -e "\nCommit '$message' succesfully pushed to branch: '$(git branch --show-current)'."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# git list stashes
|
||||||
|
git_stash_list() {
|
||||||
|
printf "%s\n" "$(git stash list)"
|
||||||
|
}
|
||||||
|
|
||||||
|
### Git aliases
|
||||||
|
# git status
|
||||||
|
fake_alias gst
|
||||||
|
if [ -n "$BASH_VERSION" ]; then
|
||||||
|
alias gst='git status'
|
||||||
|
fi
|
||||||
|
# git fetch all
|
||||||
|
fake_alias gfa
|
||||||
|
if [ -n "$BASH_VERSION" ]; then
|
||||||
|
alias gfa='git fetch --all --prune'
|
||||||
|
fi
|
||||||
|
# git diff
|
||||||
|
fake_alias gd
|
||||||
|
if [ -n "$BASH_VERSION" ]; then
|
||||||
|
alias gd='git diff'
|
||||||
|
fi
|
||||||
|
# git diff classic with less pager
|
||||||
|
alias gdc="git -c core.pager='less' diff"
|
||||||
|
# git diff numbered
|
||||||
|
alias gdn=git_diff_numbered
|
||||||
|
# (with filenum works only in repo root)
|
||||||
|
next_line
|
||||||
|
# git commit and push with message
|
||||||
|
alias gcpm=git_commit_push
|
||||||
|
# git checkout with tab completion
|
||||||
|
fake_alias gco
|
||||||
|
# git submodule update
|
||||||
|
fake_alias gsu
|
||||||
|
if [ -n "$BASH_VERSION" ]; then
|
||||||
|
alias gsu='git submodule update'
|
||||||
|
fi
|
||||||
|
# git stash with message
|
||||||
|
alias gstm=git_stash_msg
|
||||||
|
# git stash list
|
||||||
|
alias gstl=git_stash_list
|
||||||
|
# git stash patch <#>
|
||||||
|
alias gstp=git_stash_patch
|
||||||
|
# git stash apply <#>
|
||||||
|
alias gsta=git_apply_stash
|
||||||
|
# gti stash drop <#>
|
||||||
|
alias gstd=git_stash_drop
|
||||||
|
# git restore
|
||||||
|
alias grs='git restore'
|
||||||
|
# git restore by number from gdn
|
||||||
|
alias grsn=git_restore_by_number
|
||||||
|
# run pre-commit on changed files only
|
||||||
|
alias pc='pre-commit run --files $(git diff --name-only)'
|
||||||
11
configs/aliases/.git_bash_autocomplete
Normal file
11
configs/aliases/.git_bash_autocomplete
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
gco() {
|
||||||
|
git checkout "$@"
|
||||||
|
}
|
||||||
|
# Enable git completion if available
|
||||||
|
if [ -f /usr/share/bash-completion/completions/git ]; then
|
||||||
|
. /usr/share/bash-completion/completions/git
|
||||||
|
fi
|
||||||
|
# Enable git branch completion for gc
|
||||||
|
if type __git_complete &>/dev/null; then
|
||||||
|
__git_complete gco _git_checkout
|
||||||
|
fi
|
||||||
1
configs/bat/config
Normal file
1
configs/bat/config
Normal file
@@ -0,0 +1 @@
|
|||||||
|
--theme="base16"
|
||||||
30
configs/custom_tools/backup_dotfiles.sh
Normal file
30
configs/custom_tools/backup_dotfiles.sh
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# TODO: update REPO_URL once remote is set up (Gitea or GitHub)
|
||||||
|
REPO_URL="https://github.com/neurobrko/server_cli_tools/archive/refs/heads/main.tar.gz"
|
||||||
|
BACKUP_DIR="$HOME/.server_cli_tools_bkp"
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
if [[ "$1" == "--dry-run" ]]; then
|
||||||
|
DRY_RUN=true
|
||||||
|
echo "Dry run mode — no files will be moved."
|
||||||
|
fi
|
||||||
|
|
||||||
|
curl -s -L "$REPO_URL" | \
|
||||||
|
tar -tzf - | \
|
||||||
|
grep -v '/$' | \
|
||||||
|
while read -r file; do
|
||||||
|
target="${file#*/}"
|
||||||
|
|
||||||
|
target_path="$HOME/$target"
|
||||||
|
|
||||||
|
if [ -f "$target_path" ]; then
|
||||||
|
if $DRY_RUN; then
|
||||||
|
echo "Would move $target_path to $BACKUP_DIR/$target"
|
||||||
|
else
|
||||||
|
echo "Moving $target_path to $BACKUP_DIR/$target"
|
||||||
|
mkdir -p "$(dirname "$BACKUP_DIR/$target")"
|
||||||
|
mv "$target_path" "$BACKUP_DIR/$target"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
3
configs/yazi/yazi.toml
Normal file
3
configs/yazi/yazi.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[mgr]
|
||||||
|
ratio = [ 1, 2, 5 ]
|
||||||
|
show_hidden = true
|
||||||
29
configs/zsh/zhann-elvis.zsh-theme
Normal file
29
configs/zsh/zhann-elvis.zsh-theme
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
autoload -Uz vcs_info
|
||||||
|
|
||||||
|
zstyle ':vcs_info:*' stagedstr '%F{green}●'
|
||||||
|
zstyle ':vcs_info:*' unstagedstr '%F{yellow}●'
|
||||||
|
zstyle ':vcs_info:*' check-for-changes true
|
||||||
|
zstyle ':vcs_info:svn:*' branchformat '%b'
|
||||||
|
zstyle ':vcs_info:svn:*' formats ' [%b%F{1}:%F{11}%i%c%u%B%F{green}]'
|
||||||
|
zstyle ':vcs_info:*' enable git svn
|
||||||
|
|
||||||
|
theme_precmd () {
|
||||||
|
if [[ -z $(git ls-files --other --exclude-standard 2> /dev/null) ]]; then
|
||||||
|
zstyle ':vcs_info:git:*' formats ' [%b%c%u%B%F{green}]'
|
||||||
|
else
|
||||||
|
zstyle ':vcs_info:git:*' formats ' [%b%c%u%B%F{red}●%F{green}]'
|
||||||
|
fi
|
||||||
|
|
||||||
|
vcs_info
|
||||||
|
}
|
||||||
|
|
||||||
|
setopt prompt_subst
|
||||||
|
PROMPT='%B%F{33}%c%B%F{green}${vcs_info_msg_0_}%B%F{magenta} %{$reset_color%}% '
|
||||||
|
|
||||||
|
autoload -U add-zsh-hook
|
||||||
|
add-zsh-hook precmd theme_precmd
|
||||||
|
|
||||||
|
# to preview colors
|
||||||
|
# for i in {0..255}; do
|
||||||
|
# print -P "%F{$i}$i%f "
|
||||||
|
# done
|
||||||
19
configs/zshrc
Normal file
19
configs/zshrc
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
export ZSH="$HOME/.oh-my-zsh"
|
||||||
|
export ALIASES="$HOME/.config/aliases"
|
||||||
|
export CT_DIR="$HOME/.config/custom_tools"
|
||||||
|
export EDITOR="vim"
|
||||||
|
ZSH_THEME=""
|
||||||
|
DISABLE_AUTO_TITLE="true"
|
||||||
|
plugins=(
|
||||||
|
git
|
||||||
|
zsh-syntax-highlighting
|
||||||
|
zsh-autosuggestions
|
||||||
|
z
|
||||||
|
)
|
||||||
|
source $ZSH/oh-my-zsh.sh
|
||||||
|
source $HOME/.config/zsh/zhann-elvis.zsh-theme
|
||||||
|
source $ALIASES/.aliases
|
||||||
|
autoload -Uz zcalc
|
||||||
|
[ -f /usr/share/doc/fzf/examples/key-bindings.zsh ] && source /usr/share/doc/fzf/examples/key-bindings.zsh
|
||||||
|
[ -f /usr/share/doc/fzf/examples/completion.zsh ] && source /usr/share/doc/fzf/examples/completion.zsh
|
||||||
|
command -v fzf >/dev/null 2>&1 && eval "$(fzf --zsh 2>/dev/null)" || true
|
||||||
578
install.sh
Executable file
578
install.sh
Executable file
@@ -0,0 +1,578 @@
|
|||||||
|
#!/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)"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 yazi
|
||||||
|
|
||||||
|
# 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, yazi)"
|
||||||
|
|
||||||
|
# bat
|
||||||
|
mkdir -p "$HOME/.config/bat"
|
||||||
|
copy_config "$CONFIGS/bat/config" "$HOME/.config/bat/config"
|
||||||
|
e_info " Installed bat config (theme: base16)"
|
||||||
|
|
||||||
|
# yazi
|
||||||
|
mkdir -p "$HOME/.config/yazi"
|
||||||
|
copy_config "$CONFIGS/yazi/yazi.toml" "$HOME/.config/yazi/yazi.toml"
|
||||||
|
e_info " Installed yazi config"
|
||||||
|
|
||||||
|
INSTALLED+=("terminal-configs")
|
||||||
|
}
|
||||||
|
|
||||||
|
install_docker_aliases() {
|
||||||
|
e_info "\n[5/6] Docker aliases"
|
||||||
|
|
||||||
|
if command_exists docker; then
|
||||||
|
copy_config "$CONFIGS/aliases/.docker_aliases" "$HOME/.config/aliases/.docker_aliases"
|
||||||
|
e_info " Installed Docker aliases"
|
||||||
|
else
|
||||||
|
e_warn " Docker not found — aliases installed anyway (will work once Docker is available)"
|
||||||
|
copy_config "$CONFIGS/aliases/.docker_aliases" "$HOME/.config/aliases/.docker_aliases"
|
||||||
|
fi
|
||||||
|
|
||||||
|
INSTALLED+=("docker-aliases")
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
if [ -f "$HOME/.config/yazi/yazi.toml" ]; then
|
||||||
|
rm -f "$HOME/.config/yazi/yazi.toml"
|
||||||
|
removed+=("yazi-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, yazi, delta)"
|
||||||
|
"Zsh + Oh-My-Zsh (shell, plugins, theme)"
|
||||||
|
"Aliases + custom tools"
|
||||||
|
"Terminal configs (bat, yazi)"
|
||||||
|
"Docker aliases"
|
||||||
|
"Git config (delta pager)"
|
||||||
|
)
|
||||||
|
|
||||||
|
INSTALLERS=(
|
||||||
|
install_core_packages
|
||||||
|
install_zsh
|
||||||
|
install_aliases
|
||||||
|
install_terminal_configs
|
||||||
|
install_docker_aliases
|
||||||
|
install_git_config
|
||||||
|
)
|
||||||
|
|
||||||
|
show_menu() {
|
||||||
|
local count=${#COMPONENTS[@]}
|
||||||
|
local selected=()
|
||||||
|
for ((i = 0; i < count; i++)); do
|
||||||
|
selected+=(1)
|
||||||
|
done
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
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 "$@"
|
||||||
Reference in New Issue
Block a user