From eb344f0b2330354f11101ad9dc0c808a15765667 Mon Sep 17 00:00:00 2001 From: srdusr Date: Fri, 3 Oct 2025 16:27:13 +0200 Subject: Various changes/updates --- common/.bashrc | 1056 ++++++++++++++++++++++++++++----- common/config/zsh/.zshenv | 6 + common/config/zsh/user/completion.zsh | 1 - common/config/zsh/user/functions.zsh | 348 ++++++++--- common/config/zsh/user/options.zsh | 8 +- common/config/zsh/user/prompt.zsh | 88 ++- common/install.sh | 333 +++++++---- common/packages.yml | 4 - linux/home/.config/inputrc | 27 +- linux/home/.config/kitty/kitty.conf | 4 +- linux/home/.config/tmux/tmux.conf | 6 +- linux/home/.config/user-dirs.dirs | 5 +- 12 files changed, 1481 insertions(+), 405 deletions(-) diff --git a/common/.bashrc b/common/.bashrc index fbc86fe..38be903 100644 --- a/common/.bashrc +++ b/common/.bashrc @@ -15,40 +15,541 @@ if [[ $- != *i* ]]; then return fi +# If not running interactively, dont do anything +[[ $- != *i* ]] && return + # Get the current active terminal term="$(cat /proc/"$PPID"/comm)" -# Set a default prompt -p='\[\033[01;37m\]┌─[\[\033[01;32m\]srdusr\[\033[01;37m\]]-[\[\033[01;36m\]archlinux\[\033[01;37m\]]-[\[\033[01;33m\]\W\]\[\033[00;37m\]\[\033 -\[\033[01;37m\]└─[\[\033[05;33m\]$\[\033[00;37m\]\[\033[01;37m\]]\[\033[00;37m\] ' +########## Prompt ########## + +# ------------------------ +# Prompt Colors & Variables +# ------------------------ +RESET="\[\033[0m\]" +BOLD="\[\033[1m\]" +BLINK="\[\033[5m\]" -# Set transparency and prompt while using st -if [[ $term = "st" ]]; then - transset-df "0.65" --id "$WINDOWID" >/dev/null +FG_USER="\[\033[38;5;82m\]" # bright green +FG_HOST="\[\033[38;5;45m\]" # cyan/blue +FG_DIR="\[\033[38;5;214m\]" # orange +FG_PROMPT="\[\033[38;5;220m\]" # yellow +FG_BORDER="\[\033[37m\]" # light gray - # [Your_Name]-----| |=======|------[Your_Distro] - # [Color]--------| | [Color]------| | - # [Style]------------| | | [Style]---------| | | - # V V V V V V - p='\[\033[01;37m\]┌─[\[\033[01;32m\]srdusr\[\033[01;37m\]]-[\[\033[01;36m\]archlinux\[\033[01;37m\]]-[\[\033[01;33m\]\W\[\033[00;37m\]\[\033[01;37m\]] -\[\033[01;37m\]└─[\[\033[05;33m\]$\[\033[00;37m\]\[\033[01;37m\]]\[\033[00;37m\] ' -# A A A -# [Style]----| | |-------- [Your_Choice] -# [Color]------------| +USER_NAME="${USER}" +HOST_NAME="$(hostname -s)" +DISTRO="$(. /etc/os-release && echo $ID)" 2>/dev/null || DISTRO="$HOST_NAME" +# ------------------------ +# Git bare-dotfiles environment +# ------------------------ +set_git_env_vars() { + [[ -d "$HOME/.cfg" ]] || return + git --git-dir="$HOME/.cfg" rev-parse --is-bare-repository &>/dev/null || return + + # Only set env vars if not inside another Git repo + if ! git rev-parse --is-inside-work-tree &>/dev/null; then + export GIT_DIR="$HOME/.cfg" + export GIT_WORK_TREE="$HOME" + else + unset GIT_DIR + unset GIT_WORK_TREE + fi +} + +# Update git env on directory change +if [ -n "$BASH_VERSION" ]; then + PROMPT_COMMAND="set_git_env_vars; $PROMPT_COMMAND" fi +set_git_env_vars -# If not running interactively, dont do anything -[[ $- != *i* ]] && return +# ------------------------ +# Git branch info +# ------------------------ +git_branch() { + # Only inside a git repo + if git rev-parse --is-inside-work-tree &>/dev/null; then + local branch + branch=$(git symbolic-ref --short HEAD 2>/dev/null) + # Check for uncommitted changes + local status="" + if [[ -n $(git status --porcelain) ]]; then + status="*" + fi + echo "(${branch}${status})" + fi +} + +# ------------------------ +# Prompt with git info +# ------------------------ +PS1_FUNC() { + local git_info + git_info=$(git_branch) + # Blank line before prompt + echo + + PS1="${FG_BORDER}${BOLD}┌─[${FG_USER}${USER_NAME}${FG_BORDER}]" + PS1+=" ${FG_HOST}${DISTRO}${FG_BORDER}" + PS1+=" ${FG_DIR}\W${FG_BORDER}" + # Git info right after path + [[ -n "$git_info" ]] && PS1+=" ${FG_PROMPT}${git_info}${FG_BORDER}" + PS1+="\n${FG_BORDER}└──[${FG_PROMPT}${BLINK}\$${RESET}${FG_BORDER}]${RESET}" + export PS1 +} + +# Make PS1 dynamic each time +PROMPT_COMMAND="PS1_FUNC; $PROMPT_COMMAND" + +# ------------------------ +# Optional: git subtree helper +# ------------------------ +gsp() { + local GIT_SUBTREE_FILE="$PWD/.gitsubtrees" + [[ -f "$GIT_SUBTREE_FILE" ]] || { echo "No .gitsubtrees file."; return; } + + while IFS= read -r LINE; do + [[ $LINE =~ ^# ]] && continue + IFS=';' read -r PREFIX REMOTE BRANCH <<< "$LINE" + echo "Pulling subtree $PREFIX from $REMOTE $BRANCH..." + git subtree pull --prefix="$PREFIX" "$REMOTE" "$BRANCH" + done < "$GIT_SUBTREE_FILE" +} + +########## Bindings ########## + +bind -m vi-command 'Control-l: clear-screen' +bind -m vi-insert 'Control-l: clear-screen' + + +########## Env/exports ########## + +export PROMPT_COMMAND="resize &>/dev/null ; $PROMPT_COMMAND" + +# XDG Base Directories +#-------------------------------------- +export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" +export XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}" +export XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" + +export INPUTRC="$XDG_CONFIG_HOME/inputrc" + +# PATH Setup +#-------------------------------------- +# Base user paths +export PATH="$HOME/.bin:$HOME/.local/bin:$HOME/.scripts:/usr/local/bin:/sbin:/usr/sbin:$PATH" + +# Termux (Android) +if [ -d "/data/data/com.termux/files/usr/local/bin" ]; then + export PATH="/data/data/com.termux/files/usr/local/bin:$PATH" +fi + +# cmake +if [ -x "/usr/bin/cmake" ]; then + export PATH="/usr/bin/cmake:$PATH" +fi + +# Chrome +if [ -d "/opt/google/chrome" ]; then + export PATH="$PATH:/opt/google/chrome" +fi + +# Homebrew (macOS / Linux) +if [ -d "/opt/homebrew/bin" ]; then + export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:$PATH" +fi + +# Nix profile +if [ -d "$HOME/.nix-profile/bin" ]; then + export PATH="$HOME/.nix-profile/bin:$PATH" +fi + +# Add ~/.scripts (excluding some dirs) +#-------------------------------------- +EXCLUDE_DIRS=(".git" "assets" "test") +if [ -d "$HOME/.scripts" ]; then + while IFS= read -r -d '' dir; do + rel_path="${dir#$HOME/.scripts/}" + skip=false + for exclude in "${EXCLUDE_DIRS[@]}"; do + [[ "$rel_path" == "$exclude"* ]] && skip=true && break + done + [ "$skip" = false ] && PATH="$dir:$PATH" + done < <(find "$HOME/.scripts" -type d -print0) +fi + +# Terminal +#-------------------------------------- +export TERM="xterm-256color" +export COLORTERM="truecolor" + +# Choose default terminal +for term in wezterm kitty alacritty xterm; do + if command -v "$term" &>/dev/null; then + export TERMINAL="$term" + break + fi +done + +# Default Programs +#-------------------------------------- +export EDITOR="$(command -v nvim || command -v vim || echo nano)" +export TEXEDIT="$EDITOR" +export FCEDIT="$EDITOR" +export VISUAL="$EDITOR" +export GIT_EDITOR="$EDITOR" + +export READER="zathura" +export BROWSER="firefox" +export OPENER="xdg-open" +export VIDEO="mpv" +export IMAGE="phototonic" + +# Man pager +if command -v nvim &>/dev/null; then + export MANPAGER="sh -c 'col -b | nvim -c \"set ft=man ts=8 nomod nolist nonu noma\" -c \"autocmd VimEnter * call feedkeys(\\\"\\q\\\")\" -'" +else + export MANPAGER="bat" +fi +export MANROFFOPT="-c" +export PAGER="less" + +# Shell History +#-------------------------------------- +export HISTSIZE=1000000 +export SAVEHIST=1000000 + +# Colors +#-------------------------------------- +export LSCOLORS=ExGxBxDxCxEgEdxbxgxcxd + +# less colors +export LESS_TERMCAP_mb=$(tput bold; tput setaf 2) # green +export LESS_TERMCAP_md=$(tput bold; tput setaf 2) # green +export LESS_TERMCAP_so=$(tput bold; tput setaf 3) # yellow +export LESS_TERMCAP_se=$(tput rmso; tput sgr0) +export LESS_TERMCAP_us=$(tput smul; tput bold; tput setaf 1) # red +export LESS_TERMCAP_ue=$(tput sgr0) +export LESS_TERMCAP_me=$(tput sgr0) + +# Miscellaneous XDG-aware configs +#-------------------------------------- +export RIPGREP_CONFIG_PATH="$XDG_CONFIG_HOME/ripgrep/ripgreprc" +export DOCKER_CONFIG="$XDG_CONFIG_HOME/docker" +export VSCODE_PORTABLE="$XDG_DATA_HOME/vscode" +export GTK2_RC_FILES="$XDG_CONFIG_HOME/gtk-2.0/gtkrc" +export DISCORD_USER_DATA_DIR="$XDG_DATA_HOME" +export LYNX_CFG="$XDG_CONFIG_HOME/.lynxrc" + +# Languages / SDKs +#-------------------------------------- +# Rust +export RUSTUP_HOME="$XDG_DATA_HOME/rustup" +export CARGO_HOME="$XDG_DATA_HOME/cargo" +export PATH="$CARGO_HOME/bin:$RUSTUP_HOME/bin:$PATH" +command -v rustc &>/dev/null && export RUST_BACKTRACE=1 + +# Dotnet +export DOTNET_HOME="$XDG_DATA_HOME/dotnet" +export DOTNET_CLI_HOME="$XDG_CONFIG_HOME/dotnet" +export PATH="$DOTNET_HOME/tools:$PATH" +export DOTNET_ROOT="/opt/dotnet" +export DOTNET_CLI_TELEMETRY_OPTOUT=1 + +# Java +export JAVA_HOME="/usr/lib/jvm/java-20-openjdk" +export _JAVA_OPTIONS="-Djava.util.prefs.userRoot=$XDG_CONFIG_HOME/java" + +# Dart / Flutter +if [ -d "/opt/flutter/bin" ]; then + export PATH="/opt/flutter/bin:/usr/lib/dart/bin:$PATH" +fi + +# Go +export GOPATH="$XDG_DATA_HOME/go" + +# Node / NVM +export NVM_DIR="$XDG_CONFIG_HOME/nvm" +[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" + +[[ -d "$XDG_DATA_HOME/node/bin" ]] && PATH="$XDG_DATA_HOME/node/bin:$PATH" +export NODE_REPL_HISTORY="$XDG_DATA_HOME/node_repl_history" +export NPM_CONFIG_USERCONFIG="$XDG_CONFIG_HOME/npm/npmrc" + +# Bun +export BUN_INSTALL="$HOME/.bun" +export PATH="$BUN_INSTALL/bin:$PATH" + +# Ruby +export GEM_HOME="$XDG_DATA_HOME/ruby/gems" +export GEM_PATH="$GEM_HOME" +export GEM_SPEC_CACHE="$XDG_DATA_HOME/ruby/specs" + +# Python +if command -v virtualenvwrapper.sh >/dev/null 2>&1; then + export WORKON_HOME="$HOME/.virtualenvs" + export VIRTUALENVWRAPPER_PYTHON="$(command -v python3)" + export VIRTUALENVWRAPPER_VIRTUALENV="$(command -v virtualenv)" + source "$(command -v virtualenvwrapper.sh)" +fi +export VIRTUAL_ENV_DISABLE_PROMPT=false +export JUPYTER_CONFIG_DIR="$XDG_CONFIG_HOME/jupyter" +export IPYTHONDIR="$XDG_CONFIG_HOME/jupyter" + +[[ "$(uname)" == "Darwin" ]] && export PYTHON_CONFIGURE_OPTS="--enable-framework" +[[ "$(uname)" == "Linux" ]] && export PYTHON_CONFIGURE_OPTS="--enable-shared" + +export PYENV_ROOT="$HOME/.pyenv" +export PATH="$PYENV_ROOT/bin:$PATH" + +# PHP +export PATH="$HOME/.config/composer/vendor/bin:$PATH" + +# Lua +if [ -d "/usr/local/luarocks/bin" ]; then + export PATH="$PATH:/usr/local/luarocks/bin" +fi + +# Android SDK +#-------------------------------------- +if [ -d "/opt/android-sdk" ]; then + export ANDROID_HOME="/opt/android-sdk" + export ANDROID_SDK_ROOT="/opt/android-sdk" + export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$PATH" + export PATH="$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$PATH" + export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$PATH" +fi + +# Misc +#-------------------------------------- +export NVIM_TUI_ENABLE_TRUE_COLOR=1 +export GPG_TTY="$(tty)" +export XDG_MENU_PREFIX="gnome-" +export DEVELOPMENT_DIRECTORY="$HOME/code" +export PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig:$PKG_CONFIG_PATH" + +# FZF + rg +if command -v rg &>/dev/null; then + export FZF_DEFAULT_COMMAND="rg --files --hidden --glob '!{node_modules/*,.git/*}'" + export FZF_DEFAULT_OPTS='-m --height 50% --border' + export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND" +fi + +########## Aliases ########## + +# Define alias for nvim/vim (fallback to vim) +if command -v nvim > /dev/null; then + alias vi='nvim' +else + alias vi='vim' +fi + +#alias vv='$(history -p !vim)' +alias vv="vim -c 'norm! ^O'" + +# Confirmation # +alias mv='mv -i' +alias cp='cp -i' +alias ln='ln -i' + +# Disable 'rm' +#alias rm='function _rm() { echo -e "\033[0;31mrm\033[0m is disabled, use \033[0;32mtrash\033[0m or \033[0;32mdel \033[0m\033[0;33m$1\033[0m"; }; _rm' +#alias del='/bin/rm' + +# Use lsd for ls if available +if command -v lsd >/dev/null 2>&1; then + alias ls='lsd --color=auto --group-directories-first' +fi + +# ls variants +alias l='ls -FAh --group-directories-first' +alias la='ls -lAFh --group-directories-first' +alias lt='ls -lFAht --group-directories-first' +alias lr='ls -RFAh --group-directories-first' + +# more ls variants +alias ldot='ls -ld .* --group-directories-first' +alias lS='ls -1FASsh --group-directories-first' +alias lart='ls -1Fcart --group-directories-first' +alias lrt='ls -1Fcrt --group-directories-first' + +# ls with different alphabethical sorting +#unalias ll +#ll() { LC_COLLATE=C ls "$@" } + +# suffix aliases | commented out invalid bash syntax +#alias -g CP='| xclip -selection clipboard -rmlastnl' +#alias -g LL="| less exit 2>1 /dev/null" +#alias -g CA="| cat -A" +#alias -g KE="2>&1" +#alias -g NE="2>/dev/null" +#alias -g NUL=">/dev/null 2>&1" + +alias grep='grep --color=auto --exclude-dir={.git,.svn,.hg}' +alias egrep='egrep --color=auto --exclude-dir={.git,.svn,.hg}' +alias egrep='fgrep --color=auto --exclude-dir={.git,.svn,.hg}' + +#alias hist="grep '$1' $HISTFILE" +alias hist="history | grep $1" + + +alias gdb='gdb -q' +alias rust-gdb='rust-gdb -q' + +alias cd="cd-clear-ls" +alias clear='newline_clear' + +# List upto last 10 visited directories using "d" and quickly cd into any specific one +alias d="dirs -v | head -10" + +# Using just a number from "0" to "9" +alias 0="cd +0" +alias 1="cd +1" +alias 2="cd +2" +alias 3="cd +3" +alias 4="cd +4" +alias 5="cd +5" +alias 6="cd +6" +alias 7="cd +7" +alias 8="cd +8" +alias 9="cd +9" + +alias sudo='sudo ' # zsh: elligible for alias expansion/fix syntax highlight +alias sedit='sudoedit' +#alias se='sudoedit' +alias se='sudo -e' +alias :q='exit 2>1 /dev/null' +alias disk-destroyer='$(command -v dd)' +alias dd='echo "Warning use command: disk-destroyer"' +alias sc="systemctl" +alias jc="journalctl" +alias jck="journalctl -k" # Kernel +alias jce='sudo journalctl -b --priority 0..3' # error +alias journalctl-error='sudo journalctl -b --priority 0..3' +alias jcssh="sudo journalctl -u sshd" +alias tunnel='ssh -fNTL' +# tty aliases +#if [[ "$TERM" == 'linux' ]]; then +# alias tmux='/usr/bin/tmux -L linux' +#fi +#alias logout="loginctl kill-user $(whoami)" + +logout() { + local wm + wm="$(windowManagerName)" + if [[ -n "$wm" ]]; then + echo "Logging out by killing window manager: $wm" + pkill "$wm" + else + echo "No window manager detected!" >&2 + fi +} +alias lg="logout" + +#alias suspend='systemctl suspend && betterlockscreen -l' # Suspend(sleep) and lock screen if using systemctl +#alias suspend='systemctl suspend' # Suspend(sleep) and lock screen if using systemctl +alias suspend='loginctl suspend' # Suspend(sleep) and lock screen if using systemctl +#alias shutdown='loginctl poweroff' # Suspend(sleep) and lock screen if using systemctl +#alias shutdown='sudo /sbin/shutdown -h' +#alias poweroff='loginctl poweroff' +#alias reboot='loginctl reboot' +alias reboot='sudo reboot' +#alias hibernate='systemctl hibernate' # Hibernate +alias lock='DISPLAY=:0 xautolock -locknow' # Lock my workstation screen from my phone +alias oports="sudo lsof -i -P -n | grep -i 'listen'" # List open ports +alias keyname="xev | sed -n 's/[ ]*state.* \([^ ]*\)).*/\1/p'" +alias wget=wget --hsts-file="$XDG_CACHE_HOME/wget-hsts" # wget does not support environment variables +alias open="xdg-open" +alias pp='getlast 2>&1 |&tee -a output.txt' +#alias lg='la | grep' +alias pg='ps aux | grep' +alias py='python' +alias py3='python3' +alias activate='source ~/.local/share/venv/bin/activate' +alias sha256='shasum -a 256' +alias rgf='rg -F' +alias weather='curl wttr.in/durban' +alias diary='nvim "$HOME/documents/main/inbox/diary/$(date +'%Y-%m-%d').md"' +alias wifi='nmcli dev wifi show-password' +alias ddg='w3m lite.duckduckgo.com' +alias rss='newsboat' +alias vpn='protonvpn' +alias yt-dl="yt-dlp -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4' --restrict-filename" +#alias com.obsproject.Studio="obs" +#alias obs="com.obsproject.Studio" +#alias obs-stuido="obs" + +# Time aliases +alias utc='TZ=Africa/Johannesburg date' +alias ber='TZ=Europe/Berlin date' +alias nyc='TZ=America/New_York date' +alias sfo='TZ=America/Los_Angeles date' +alias utc='TZ=Etc/UTC date' + +alias src='source $ZDOTDIR/.zshrc' +alias p=proxy + +alias cheat='~/.scripts/cheat.sh ~/documents/notes/cheatsheets' +alias crypto='curl -s rate.sx | head -n -2 | tail -n +10' +#alias todo='glow "$HOME"/documents/main/notes/TODO.md' + +alias todo='$EDITOR "$(find "$HOME"/documents/main -type f -iname "todo.md" | head -n 1)"' +alias android-studio='/opt/android-studio/bin/studio.sh' # android-studio +alias nomachine='/usr/NX/bin/nxplayer' # nomachine +alias firefox="firefox-bin" +alias discord="vesktop-bin" +alias fetch="fastfetch" +alias batt='upower -i /org/freedesktop/UPower/devices/battery_BAT0 | grep -E "state|to full|percentage"' +alias emerge-fetch='sudo tail -f /var/log/emerge-fetch.log' +alias spotify="env LD_PRELOAD=/usr/local/lib/spotify-adblock.so spotify %U" + +alias proofread='firejail --private --private-tmp --net=none --seccomp --caps.drop=all zathura' + +# NVM +if [ -s "$NVM_DIR/nvm.sh" ]; then + nvm_cmds=(nvm node npm yarn) + for cmd in "${nvm_cmds[@]}"; do + alias "$cmd"="unalias ${nvm_cmds[*]} && unset nvm_cmds && . $NVM_DIR/nvm.sh && $cmd" + done +fi + +# Kubernetes +if command -v kubectl > /dev/null; then + replaceNS() { kubectl config view --minify --flatten --context=$(kubectl config current-context) | yq ".contexts[0].context.namespace=\"$1\"" ; } + alias kks='KUBECONFIG=<(replaceNS "kube-system") kubectl' + alias kam='KUBECONFIG=<(replaceNS "authzed-monitoring") kubectl' + alias kas='KUBECONFIG=<(replaceNS "authzed-system") kubectl' + alias kar='KUBECONFIG=<(replaceNS "authzed-region") kubectl' + alias kt='KUBECONFIG=<(replaceNS "tenant") kubectl' + + if command -v kubectl-krew > /dev/null; then + path=($XDG_CONFIG_HOME/krew/bin $path) + fi + + rmfinalizers() { + kubectl get deployment "$1" -o json | jq '.metadata.finalizers = null' | kubectl apply -f - + } +fi + +# Castero +castero() { + if [[ -f ~/.local/share/venv/bin/activate ]]; then + . ~/.local/share/venv/bin/activate + fi + command castero "$@" +} + +# Zoxide (cd alternative) +if command -v zoxide >/dev/null 2>&1; then + eval "$(zoxide init bash)" +fi -# My alias commands -alias ls='ls --color=auto -1' -alias shred='shred -uzvn3' -alias wallset='feh --bg-fill' + +########## Functions ########## # Dotfiles Management System if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then - # Core git wrapper with repository as work-tree + # Core git wrapper - .cfg is bare repo, work-tree points to .cfg itself _config() { git --git-dir="$HOME/.cfg" --work-tree="$HOME/.cfg" "$@" } @@ -61,86 +562,127 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then *) CFG_OS="other" ;; esac - # Map system path to repository path _repo_path() { local f="$1" - # If it's an absolute path that's not in HOME, handle it specially - if [[ "$f" == /* && "$f" != "$HOME/"* ]]; then - echo "$CFG_OS/${f#/}" - return + # Normalize absolute or relative + if [[ "$f" == "$HOME/"* ]]; then + f="${f#$HOME/}" + elif [[ "$f" == "./"* ]]; then + f="${f#./}" fi - # Check for paths that should go to the repository root + # Already tracked? Use that + local dirs=("common/" "$CFG_OS/home/" "$CFG_OS/Users/") + for d in "${dirs[@]}"; do + local match="$(_config ls-files --full-name | grep -F "/$f" | grep -F "$d" || true)" + if [[ -n "$match" ]]; then + echo "$match" + return + fi + done + + # Already a special repo path case "$f" in - common/*|linux/*|macos/*|windows/*|profile/*|README.md) + common/*|"$CFG_OS/home/"*|"$CFG_OS/Users/"*|profile/*|README.md) echo "$f" return ;; - "$HOME/"*) - f="${f#$HOME/}" - ;; esac - # Default: put under OS-specific home - echo "$CFG_OS/home/$f" + # Map everything else dynamically + case "$f" in + *) + case "$CFG_OS" in + linux) echo "linux/home/$f" ;; + macos) echo "macos/Users/$f" ;; + windows) echo "windows/Users/$f" ;; + *) echo "$CFG_OS/home/$f" ;; + esac + ;; + esac } _sys_path() { local repo_path="$1" - local os_path_pattern="$CFG_OS/" - # Handle OS-specific files that are not in the home subdirectory - if [[ "$repo_path" == "$os_path_pattern"* && "$repo_path" != */home/* ]]; then - echo "/${repo_path#$os_path_pattern}" - return - fi + # System HOME + local sys_home + case "$CFG_OS" in + linux|macos) sys_home="$HOME" ;; + windows) sys_home="$USERPROFILE" ;; + esac + + # Repo HOME roots + local repo_home + case "$CFG_OS" in + linux) repo_home="linux/home" ;; + macos) repo_home="macos/Users" ;; + windows) repo_home="windows/Users" ;; + esac case "$repo_path" in - # Common configs → OS-specific config dirs - common/config/*) - case "$CFG_OS" in - linux) - local base="${XDG_CONFIG_HOME:-$HOME/.config}" - echo "$base/${repo_path#common/config/}" + # Common files → $HOME/… but normalize well-known dirs + common/*) + local rel="${repo_path#common/}" + + case "$rel" in + # XDG config + .config/*|config/*) + local sub="${rel#*.config/}" + sub="${sub#config/}" + echo "${XDG_CONFIG_HOME:-$sys_home/.config}/$sub" ;; - macos) - echo "$HOME/Library/Application Support/${repo_path#common/config/}" + + # XDG data (assets, wallpapers, icons, fonts…) + assets/*|.local/share/*) + local sub="${rel#assets/}" + sub="${sub#.local/share/}" + echo "${XDG_DATA_HOME:-$sys_home/.local/share}/$sub" ;; - windows) - echo "$LOCALAPPDATA\\${repo_path#common/config/}" + + # XDG cache (if you ever store cached scripts/config) + .cache/*) + local sub="${rel#.cache/}" + echo "${XDG_CACHE_HOME:-$sys_home/.cache}/$sub" ;; + + # Scripts + .scripts/*|scripts/*) + local sub="${rel#*.scripts/}" + sub="${sub#scripts/}" + echo "$sys_home/.scripts/$sub" + ;; + + # Default: dump directly under $HOME *) - echo "$HOME/.config/${repo_path#common/config/}" + echo "$sys_home/$rel" ;; esac ;; - # Common assets → stay in repo - common/assets/*) - echo "$HOME/.cfg/$repo_path" + # Profile files → $HOME/… + profile/*) + local rel="${repo_path#profile/}" + echo "$sys_home/$rel" ;; - # Other common files (dotfiles like .bashrc, .gitconfig, etc.) → $HOME - common/*) - echo "$HOME/${repo_path#common/}" + # OS-specific home paths → $HOME or $USERPROFILE + "$repo_home"/*) + local rel="${repo_path#$repo_home/}" + echo "$sys_home/$rel" ;; - # OS-specific home - */home/*) - echo "$HOME/${repo_path#*/home/}" + # OS-specific system paths outside home/Users → absolute + "$CFG_OS/"*) + local rel="${repo_path#$CFG_OS/}" + echo "/$rel" ;; - # Profile configs and README → stay in repo - profile/*|README.md) + # Fallback: treat as repo-only + *) echo "$HOME/.cfg/$repo_path" ;; - - # Default fallback - *) - echo "$HOME/.cfg/$repo_path" - ;; - esac } @@ -165,63 +707,120 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then # Main config command config() { local cmd="$1"; shift - local target_dir="" - # Parse optional --target flag for add - if [[ "$cmd" == "add" ]]; then - while [[ "$1" == --* ]]; do - case "$1" in - --target|-t) - target_dir="$2" - shift 2 - ;; - *) - echo "Unknown option: $1" - return 1 - ;; - esac - done - fi case "$cmd" in add) local file_path - for file_path in "$@"; do + local git_opts=() + local files=() + local target_dir="" + + # Parse optional --target flag before files + while [[ $# -gt 0 ]]; do + case "$1" in + --target|-t) + target_dir="$2" + shift 2 + ;; + -*) # any other git flags + git_opts+=("$1") + shift + ;; + *) # files + files+=("$1") + shift + ;; + esac + done + + for file_path in "${files[@]}"; do + # Store original for rel_path calculation + local original_path="$file_path" + + # Make path absolute first + if [[ "$file_path" != /* && "$file_path" != "$HOME/"* ]]; then + file_path="$(pwd)/$file_path" + fi + + # Check if file exists + if [[ ! -e "$file_path" ]]; then + echo "Error: File not found: $file_path" + continue + fi + + # Calculate relative path from original input + local rel_path + if [[ "$original_path" == "$HOME/"* ]]; then + rel_path="${original_path#$HOME/}" + elif [[ "$original_path" == "./"* ]]; then + rel_path="${original_path#./}" + else + rel_path="$original_path" + fi + + # Check if file is already tracked + local existing_path="$(_config ls-files --full-name | grep -Fx "$(_repo_path "$file_path")" || true)" local repo_path - if [[ -n "$target_dir" ]]; then - local rel_path - if [[ "$file_path" == /* ]]; then - rel_path="$(basename "$file_path")" - else - rel_path="$file_path" - fi + if [[ -n "$existing_path" ]]; then + repo_path="$existing_path" + elif [[ -n "$target_dir" ]]; then repo_path="$target_dir/$rel_path" else repo_path="$(_repo_path "$file_path")" fi + # Copy file into bare repo local full_repo_path="$HOME/.cfg/$repo_path" mkdir -p "$(dirname "$full_repo_path")" cp -a "$file_path" "$full_repo_path" - git --git-dir="$HOME/.cfg" --work-tree="$HOME/.cfg" add "$repo_path" + # Add to git + _config add "${git_opts[@]}" "$repo_path" echo "Added: $file_path -> $repo_path" done ;; + rm) local rm_opts="" local file_path_list=() + local target_dir="" - for arg in "$@"; do - if [[ "$arg" == "-"* ]]; then - rm_opts+=" $arg" - else - file_path_list+=("$arg") - fi + # Parse options + while [[ $# -gt 0 ]]; do + case "$1" in + --target|-t) + target_dir="$2" + shift 2 + ;; + -*) + rm_opts+=" $1" + shift + ;; + *) + file_path_list+=("$1") + shift + ;; + esac done for file_path in "${file_path_list[@]}"; do - local repo_path="$(_repo_path "$file_path")" + local repo_path + # Check if already a repo path (exists in git index) - exact match + if _config ls-files --full-name | grep -qFx "$file_path"; then + repo_path="$file_path" + elif [[ -n "$target_dir" ]]; then + # Use target directory if specified + local rel_path + if [[ "$file_path" == "$HOME/"* ]]; then + rel_path="${file_path#$HOME/}" + else + rel_path="${file_path#./}" + fi + repo_path="$target_dir/$rel_path" + else + repo_path="$(_repo_path "$file_path")" + fi if [[ "$rm_opts" == *"-r"* ]]; then _config rm --cached -r "$repo_path" @@ -229,15 +828,21 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then _config rm --cached "$repo_path" fi - eval "rm $rm_opts \"$file_path\"" - echo "Removed: $file_path" + # Compute system path for actual file removal + local sys_file="$(_sys_path "$repo_path")" + if [[ -e "$sys_file" ]]; then + eval "rm $rm_opts \"$sys_file\"" + fi + echo "Removed: $repo_path" done ;; + sync) local direction="${1:-to-repo}"; shift _config ls-files | while read -r repo_file; do local sys_file="$(_sys_path "$repo_file")" local full_repo_path="$HOME/.cfg/$repo_file" + if [[ "$direction" == "to-repo" ]]; then if [[ -e "$sys_file" && -n "$(diff "$full_repo_path" "$sys_file" 2>/dev/null || echo "diff")" ]]; then cp -a "$sys_file" "$full_repo_path" @@ -258,32 +863,59 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then fi done ;; + status) - local auto_synced=() + local modified_files=() + local missing_files=() + + # Colors like git + local RED="\033[31m" + local GREEN="\033[32m" + local YELLOW="\033[33m" + local BLUE="\033[34m" + local BOLD="\033[1m" + local RESET="\033[0m" + while read -r repo_file; do local sys_file="$(_sys_path "$repo_file")" local full_repo_path="$HOME/.cfg/$repo_file" - if [[ -e "$sys_file" && -e "$full_repo_path" ]]; then + + if [[ ! -e "$full_repo_path" ]]; then + missing_files+=("$repo_file") + elif [[ -e "$sys_file" ]]; then if ! diff -q "$full_repo_path" "$sys_file" >/dev/null 2>&1; then - cp -fa "$sys_file" "$full_repo_path" - auto_synced+=("$repo_file") + modified_files+=("$repo_file") fi fi done < <(_config ls-files) - if [[ ${#auto_synced[@]} -gt 0 ]]; then - echo "=== Auto-synced Files ===" - for repo_file in "${auto_synced[@]}"; do - echo "synced: $(_sys_path "$repo_file") -> $repo_file" + + # Report missing files + if [[ ${#missing_files[@]} -gt 0 ]]; then + echo -e "${BOLD}${RED}=== Missing Files (consider removing from git) ===${RESET}" + for repo_file in "${missing_files[@]}"; do + echo -e " ${RED}deleted:${RESET} $(_sys_path "$repo_file") -> $repo_file" done echo fi - _config status - echo + + # Report modified files + if [[ ${#modified_files[@]} -gt 0 ]]; then + echo -e "${BOLD}${YELLOW}=== Modified Files (different from system) ===${RESET}" + for repo_file in "${modified_files[@]}"; do + echo -e " ${YELLOW}modified:${RESET} $(_sys_path "$repo_file") -> $repo_file" + done + echo + fi + + # Finally, show underlying git status (with colors) + _config -c color.status=always status ;; - deploy) + + deploy|checkout) + echo "Deploying dotfiles from .cfg..." _config ls-files | while read -r repo_file; do local full_repo_path="$HOME/.cfg/$repo_file" - local sys_file="$(_sys_path "$repo_file")" # destination only + local sys_file="$(_sys_path "$repo_file")" # Only continue if the source exists if [[ -e "$full_repo_path" && -n "$sys_file" ]]; then @@ -303,29 +935,7 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then fi done ;; - checkout) - echo "Checking out dotfiles from .cfg..." - _config ls-files | while read -r repo_file; do - local full_repo_path="$HOME/.cfg/$repo_file" - local sys_file="$(_sys_path "$repo_file")" - if [[ -e "$full_repo_path" && -n "$sys_file" ]]; then - local dest_dir - dest_dir="$(dirname "$sys_file")" - - # Create destination if it doesn't exist - if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then - _sudo_prompt mkdir -p "$dest_dir" - _sudo_prompt cp -a "$full_repo_path" "$sys_file" - else - mkdir -p "$dest_dir" - cp -a "$full_repo_path" "$sys_file" - fi - - echo "Checked out: $repo_file -> $sys_file" - fi - done - ;; backup) local timestamp=$(date +%Y%m%d%H%M%S) local backup_dir="$HOME/.dotfiles_backup/$timestamp" @@ -341,6 +951,7 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then done echo "Backup complete. To restore, copy files from $backup_dir to their original locations." ;; + *) _config "$cmd" "$@" ;; @@ -348,24 +959,169 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then } fi -PS1=$p +# Make SUDO_ASKPASS agnostic: pick the first available askpass binary. +# You can predefine SUDO_ASKPASS env var to force a particular path. +: "${SUDO_ASKPASS:=""}" -bind -m vi-command 'Control-l: clear-screen' -bind -m vi-insert 'Control-l: clear-screen' +# list of common askpass binaries (order: preferred -> fallback) +_askpass_candidates=( + "$SUDO_ASKPASS" # user-specified (if absolute path) + "/usr/lib/ssh/x11-ssh-askpass" + "/usr/libexec/openssh/ssh-askpass" + "/usr/lib/ssh/ssh-askpass" + "/usr/bin/ssh-askpass" + "/usr/bin/ssh-askpass-gtk" + "/usr/bin/ssh-askpass-gnome" + "/usr/bin/ssh-askpass-qt" + "/usr/bin/ksshaskpass" + "/usr/bin/zenity" # use zenity --entry as wrapper (see below) + "/usr/bin/mate-ssh-askpass" + "/usr/bin/xdg-open" # last-resort GUI helper (not ideal) +) -export EDITOR="nvim" +find_askpass() { + for p in "${_askpass_candidates[@]}"; do + [ -z "$p" ] && continue + # if user gave a path in SUDO_ASKPASS we accept it only if it's executable + if [ -n "$SUDO_ASKPASS" ] && [ "$p" = "$SUDO_ASKPASS" ]; then + [ -x "$p" ] && { printf '%s\n' "$p"; return 0; } + continue + fi -#export NVM_DIR="$HOME/.local/share/nvm" -#[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm -#[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion + # if candidate is an absolute path, test directly + if [ "${p#/}" != "$p" ]; then + [ -x "$p" ] && { printf '%s\n' "$p"; return 0; } + continue + fi -export PROMPT_COMMAND="resize &>/dev/null ; $PROMPT_COMMAND" + # otherwise try to resolve via PATH + if command -v "$p" >/dev/null 2>&1; then + # For zenity, we will use a small wrapper (see below) + printf '%s\n' "$(command -v "$p")" + return 0 + fi + done -# Rust environment (silent if not installed) -export RUSTUP_HOME="${XDG_DATA_HOME:-$HOME/.local/share}/rustup" -export CARGO_HOME="${XDG_DATA_HOME:-$HOME/.local/share}/cargo" -export PATH="$CARGO_HOME/bin:$RUSTUP_HOME/bin:$PATH" + return 1 +} -if command -v rustc >/dev/null 2>&1; then - export RUST_BACKTRACE=1 +# If zenity is chosen, use a thin wrapper script so sudo -A can call it like an askpass binary. +# This wrapper will be created in $XDG_RUNTIME_DIR or /tmp (non-persistent). +create_zenity_wrapper() { + local wrapper + wrapper="${XDG_RUNTIME_DIR:-/tmp}/.sudo_askpass_zenity.sh" + cat >"$wrapper" <<'EOF' +#!/bin/sh +# simple zenity askpass wrapper for sudo +# prints password to stdout so sudo -A works +zenity --entry --title="Authentication" --text="Elevated privileges are required" --hide-text 2>/dev/null || exit 1 +EOF + chmod 700 "$wrapper" + printf '%s\n' "$wrapper" +} + +# Set askpass +if [ -z "$SUDO_ASKPASS" ]; then + candidate="$(find_askpass || true)" + if [ -n "$candidate" ]; then + if command -v zenity >/dev/null 2>&1 && [ "$(command -v zenity)" = "$candidate" ]; then + # create the wrapper and export it + wrapper="$(create_zenity_wrapper)" + export SUDO_ASKPASS="$wrapper" + else + export SUDO_ASKPASS="$candidate" + fi + else + # optional: leave unset or set to empty to avoid mistakes + unset SUDO_ASKPASS + fi fi +# debug: (uncomment to print what was chosen) +# printf 'SUDO_ASKPASS -> %s\n' "${SUDO_ASKPASS:-}" + + +# Git +# No arguments: `git status` +# With arguments: acts like `git` +g() { + if [ $# -gt 0 ]; then + git "$@" # Pass arguments to git + else + git status # Default to `git status` + fi +} + +# Optional: enable bash completion for `g` like git +if type complete &>/dev/null; then + complete -o default -o nospace -F _git g +fi + +ga() { g add "$@"; } +gaw() { g add -A && g diff --cached -w | g apply --cached -R; } +grm() { g rm "$@"; } +gb() { g branch "$@"; } +gbl() { g branch -l "$@"; } +gbD() { g branch -D "$@"; } +gbu() { g branch -u "$@"; } +ge() { g clone "$@"; } +gc() { g commit "$@"; } +gcm() { g commit -m "$@"; } +gca() { g commit -a "$@"; } +gcaa() { g commit -a --amend "$@"; } +gcam() { g commit -a -m "$@"; } +gce() { g commit -e "$@"; } +gcfu() { g commit --fixup "$@"; } +gco() { g checkout "$@"; } +gcob() { g checkout -b "$@"; } +gcoB() { g checkout -B "$@"; } +gcp() { g cherry-pick "$@"; } +gcpc() { g cherry-pick --continue "$@"; } +gd() { g diff "$@"; } +gds() { g diff --staged "$@"; } +gdc() { g diff --cached "$@"; } +gl() { g lg "$@"; } # Assuming you have `lg` configured as alias/log format +glg() { g log --graph --decorate --all "$@"; } +gls() { + query="$1" + shift + glog --pickaxe-regex "-S$query" "$@" +} +gu() { g pull "$@"; } +gp() { g push "$@"; } +gpom() { g push origin main "$@"; } +gr() { g remote "$@"; } +gra() { g rebase --abort "$@"; } +grb() { g rebase --committer-date-is-author-date "$@"; } +grbom() { grb --onto master "$@"; } +grbasi() { g rebase --autosquash --interactive "$@"; } +grc() { g rebase --continue "$@"; } +grs() { g restore --staged "$@"; } +grv() { g remote -v "$@"; } +grh() { g reset --hard "$@"; } +grH() { g reset HEAD "$@"; } +gs() { g status -sb "$@"; } +gsd() { g stash drop "$@"; } +gsl() { g stash list --date=relative "$@"; } +gsp() { g stash pop "$@"; } +gss() { g stash show "$@"; } +gst() { g status "$@"; } +gsu() { g standup "$@"; } # Custom command +gforgotrecursive() { g submodule update --init --recursive --remote "$@"; } +gfp() { g commit --amend --no-edit && g push --force-with-lease "$@"; } + +# Enter directory and list contents +function cd-clear-ls() { + if [ -n "$1" ]; then + builtin cd "$@" 2>/dev/null || { echo "cd: no such file or directory: $1"; return 1; } + else + builtin cd ~ || return 1 + fi + + echo -e "\033[H\033[J" # Clear screen but keep scroll buffer + + if [ "$PWD" != "$HOME" ] && git rev-parse --is-inside-work-tree &>/dev/null; then + ls -a + else + ls + fi +} diff --git a/common/config/zsh/.zshenv b/common/config/zsh/.zshenv index b7b4f56..03d8f28 100644 --- a/common/config/zsh/.zshenv +++ b/common/config/zsh/.zshenv @@ -1,3 +1,9 @@ +# Source .profile if not already sourced +if [ -z "$PROFILE_SOURCED" ]; then + [ -f "$HOME/.profile" ] && source "$HOME/.profile" + export PROFILE_SOURCED=1 +fi + ####################################### # XDG Base Directories ####################################### diff --git a/common/config/zsh/user/completion.zsh b/common/config/zsh/user/completion.zsh index 2445548..984f28f 100644 --- a/common/config/zsh/user/completion.zsh +++ b/common/config/zsh/user/completion.zsh @@ -20,7 +20,6 @@ unset _comp_path # Skip the not really helpful global compinit skip_global_compinit=0 -DISABLE_MAGIC_FUNCTIONS=true #zstyle ':completion:*' menu select=1 diff --git a/common/config/zsh/user/functions.zsh b/common/config/zsh/user/functions.zsh index 7c79ee5..d93cec8 100644 --- a/common/config/zsh/user/functions.zsh +++ b/common/config/zsh/user/functions.zsh @@ -13,72 +13,124 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then *) CFG_OS="other" ;; esac - # Map system path to repository path _repo_path() { local f="$1" - # If it's an absolute path that's not in HOME, handle it specially - if [[ "$f" == /* && "$f" != "$HOME/"* ]]; then - echo "$CFG_OS/${f#/}" - return + # Normalize absolute or relative + if [[ "$f" == "$HOME/"* ]]; then + f="${f#$HOME/}" + elif [[ "$f" == "./"* ]]; then + f="${f#./}" fi - # Check for paths that should go to the repository root + # Already tracked? Use that + local dirs=("common/" "$CFG_OS/home/" "$CFG_OS/Users/") + for d in "${dirs[@]}"; do + local match="$(_config ls-files --full-name | grep -F "/$f" | grep -F "$d" || true)" + if [[ -n "$match" ]]; then + echo "$match" + return + fi + done + + # Already a special repo path case "$f" in - common/*|linux/*|macos/*|windows/*|profile/*|README.md) + common/*|"$CFG_OS/home/"*|"$CFG_OS/Users/"*|profile/*|README.md) echo "$f" return ;; - "$HOME/"*) - f="${f#$HOME/}" - ;; esac - # Default: put under OS-specific home - echo "$CFG_OS/home/$f" + # Map everything else dynamically + case "$f" in + *) + case "$CFG_OS" in + linux) echo "linux/home/$f" ;; + macos) echo "macos/Users/$f" ;; + windows) echo "windows/Users/$f" ;; + *) echo "$CFG_OS/home/$f" ;; + esac + ;; + esac } _sys_path() { local repo_path="$1" - local os_path_pattern="$CFG_OS/" - # Handle OS-specific files that are not in the home subdirectory - if [[ "$repo_path" == "$os_path_pattern"* && "$repo_path" != */home/* ]]; then - echo "/${repo_path#$os_path_pattern}" - return - fi + # System HOME + local sys_home + case "$CFG_OS" in + linux|macos) sys_home="$HOME" ;; + windows) sys_home="$USERPROFILE" ;; + esac + + # Repo HOME roots + local repo_home + case "$CFG_OS" in + linux) repo_home="linux/home" ;; + macos) repo_home="macos/Users" ;; + windows) repo_home="windows/Users" ;; + esac case "$repo_path" in - common/scripts/*) - echo "$HOME/.scripts/${repo_path#common/scripts/}" - ;; - common/config/*) - case "$CFG_OS" in - linux) - local base="${XDG_CONFIG_HOME:-$HOME/.config}" - echo "$base/${repo_path#common/config/}" + # Common files → $HOME/… but normalize well-known dirs + common/*) + local rel="${repo_path#common/}" + + case "$rel" in + # XDG config + .config/*|config/*) + local sub="${rel#*.config/}" + sub="${sub#config/}" + echo "${XDG_CONFIG_HOME:-$sys_home/.config}/$sub" + ;; + + # XDG data (assets, wallpapers, icons, fonts…) + assets/*|.local/share/*) + local sub="${rel#assets/}" + sub="${sub#.local/share/}" + echo "${XDG_DATA_HOME:-$sys_home/.local/share}/$sub" ;; - macos) - echo "$HOME/Library/Application Support/${repo_path#common/config/}" + + # XDG cache (if you ever store cached scripts/config) + .cache/*) + local sub="${rel#.cache/}" + echo "${XDG_CACHE_HOME:-$sys_home/.cache}/$sub" ;; - windows) - # Windows Bash (Git Bash, MSYS, WSL) respects LOCALAPPDATA - echo "$LOCALAPPDATA\\${repo_path#common/config/}" + + # Scripts + .scripts/*|scripts/*) + local sub="${rel#*.scripts/}" + sub="${sub#scripts/}" + echo "$sys_home/.scripts/$sub" ;; + + # Default: dump directly under $HOME *) - echo "$HOME/.config/${repo_path#common/config/}" + echo "$sys_home/$rel" ;; esac ;; - common/assets/*|profile/*|README.md) - echo "$HOME/.cfg/$repo_path" + + # Profile files → $HOME/… + profile/*) + local rel="${repo_path#profile/}" + echo "$sys_home/$rel" ;; - common/*) - echo "$HOME/.cfg/$repo_path" + + # OS-specific home paths → $HOME or $USERPROFILE + "$repo_home"/*) + local rel="${repo_path#$repo_home/}" + echo "$sys_home/$rel" ;; - */home/*) - echo "$HOME/${repo_path#*/home/}" + + # OS-specific system paths outside home/Users → absolute + "$CFG_OS/"*) + local rel="${repo_path#$CFG_OS/}" + echo "/$rel" ;; + + # Fallback: treat as repo-only *) echo "$HOME/.cfg/$repo_path" ;; @@ -106,68 +158,150 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then # Main config command config() { local cmd="$1"; shift - local target_dir="" - - # Parse optional --target flag for add - if [[ "$cmd" == "add" ]]; then - while [[ "$1" == --* ]]; do - case "$1" in - --target|-t) - target_dir="$2" - shift 2 - ;; - *) - echo "Unknown option: $1" - return 1 - ;; - esac - done - fi case "$cmd" in add) local file_path local git_opts=() local files=() + local target_dir="" - # Parse arguments + # Parse optional --target flag before files while [[ $# -gt 0 ]]; do case "$1" in --target|-t) target_dir="$2" shift 2 ;; - -*) # Any other flags are git flags + -*) # any other git flags git_opts+=("$1") shift ;; - *) # Anything else is a file + *) # files files+=("$1") shift ;; esac done - # Process each file for file_path in "${files[@]}"; do + # Store original for rel_path calculation + local original_path="$file_path" + + # Make path absolute first + if [[ "$file_path" != /* && "$file_path" != "$HOME/"* ]]; then + file_path="$(pwd)/$file_path" + fi + + # Check if file exists + if [[ ! -e "$file_path" ]]; then + echo "Error: File not found: $file_path" + continue + fi + + # Calculate relative path from original input + local rel_path + if [[ "$original_path" == "$HOME/"* ]]; then + rel_path="${original_path#$HOME/}" + elif [[ "$original_path" == "./"* ]]; then + rel_path="${original_path#./}" + else + rel_path="$original_path" + fi + + # Check if file is already tracked + local existing_path="$(_config ls-files --full-name | grep -Fx "$(_repo_path "$file_path")" || true)" local repo_path - if [[ -n "$target_dir" ]]; then - local rel_path - if [[ "$file_path" == /* ]]; then - rel_path="$(basename "$file_path")" - else - rel_path="$file_path" - fi + if [[ -n "$existing_path" ]]; then + repo_path="$existing_path" + elif [[ -n "$target_dir" ]]; then repo_path="$target_dir/$rel_path" else repo_path="$(_repo_path "$file_path")" fi + # Copy file into bare repo local full_repo_path="$HOME/.cfg/$repo_path" mkdir -p "$(dirname "$full_repo_path")" cp -a "$file_path" "$full_repo_path" - # Only git flags + repo_path go to git + # Add to git + _config add "${git_opts[@]}" "$repo_path" + + echo "Added: $file_path -> $repo_path" + done + ;; + + add) + local file_path + local git_opts=() + local files=() + local target_dir="" + local flatten=false + + # Parse options + while [[ $# -gt 0 ]]; do + case "$1" in + --target|-t) + target_dir="$2" + shift 2 + ;; + --flatten) + flatten=true + shift + ;; + -*) + git_opts+=("$1") + shift + ;; + *) + files+=("$1") + shift + ;; + esac + done + + for file_path in "${files[@]}"; do + local original_path="$file_path" + + if [[ "$file_path" != /* && "$file_path" != "$HOME/"* ]]; then + file_path="$(pwd)/$file_path" + fi + + if [[ ! -e "$file_path" ]]; then + echo "Error: File not found: $file_path" + continue + fi + + # Calculate relative path + local rel_path + if [[ "$original_path" == "$HOME/"* ]]; then + rel_path="${original_path#$HOME/}" + elif [[ "$original_path" == "./"* ]]; then + rel_path="${original_path#./}" + else + rel_path="$original_path" + fi + + # Already tracked? + local existing_path="$(_config ls-files --full-name | grep -Fx "$(_repo_path "$file_path")" || true)" + local repo_path + if [[ -n "$existing_path" ]]; then + repo_path="$existing_path" + elif [[ -n "$target_dir" ]]; then + if $flatten; then + repo_path="$target_dir/$(basename "$file_path")" + else + repo_path="$target_dir/$rel_path" + fi + else + repo_path="$(_repo_path "$file_path")" + fi + + # Copy and add + local full_repo_path="$HOME/.cfg/$repo_path" + mkdir -p "$(dirname "$full_repo_path")" + cp -a "$file_path" "$full_repo_path" _config add "${git_opts[@]}" "$repo_path" echo "Added: $file_path -> $repo_path" @@ -177,17 +311,43 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then rm) local rm_opts="" local file_path_list=() + local target_dir="" - for arg in "$@"; do - if [[ "$arg" == "-"* ]]; then - rm_opts+=" $arg" - else - file_path_list+=("$arg") - fi + # Parse options + while [[ $# -gt 0 ]]; do + case "$1" in + --target|-t) + target_dir="$2" + shift 2 + ;; + -*) + rm_opts+=" $1" + shift + ;; + *) + file_path_list+=("$1") + shift + ;; + esac done for file_path in "${file_path_list[@]}"; do - local repo_path="$(_repo_path "$file_path")" + local repo_path + # Check if already a repo path (exists in git index) - exact match + if _config ls-files --full-name | grep -qFx "$file_path"; then + repo_path="$file_path" + elif [[ -n "$target_dir" ]]; then + # Use target directory if specified + local rel_path + if [[ "$file_path" == "$HOME/"* ]]; then + rel_path="${file_path#$HOME/}" + else + rel_path="${file_path#./}" + fi + repo_path="$target_dir/$rel_path" + else + repo_path="$(_repo_path "$file_path")" + fi if [[ "$rm_opts" == *"-r"* ]]; then _config rm --cached -r "$repo_path" @@ -195,8 +355,12 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then _config rm --cached "$repo_path" fi - eval "rm $rm_opts \"$file_path\"" - echo "Removed: $file_path" + # Compute system path for actual file removal + local sys_file="$(_sys_path "$repo_path")" + if [[ -e "$sys_file" ]]; then + eval "rm $rm_opts \"$sys_file\"" + fi + echo "Removed: $repo_path" done ;; @@ -228,10 +392,17 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then ;; status) - # Check for missing files and auto-sync existing ones - local auto_synced=() + local modified_files=() local missing_files=() + # Colors like git + local RED="\033[31m" + local GREEN="\033[32m" + local YELLOW="\033[33m" + local BLUE="\033[34m" + local BOLD="\033[1m" + local RESET="\033[0m" + while read -r repo_file; do local sys_file="$(_sys_path "$repo_file")" local full_repo_path="$HOME/.cfg/$repo_file" @@ -240,31 +411,31 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then missing_files+=("$repo_file") elif [[ -e "$sys_file" ]]; then if ! diff -q "$full_repo_path" "$sys_file" >/dev/null 2>&1; then - cp -fa "$sys_file" "$full_repo_path" - auto_synced+=("$repo_file") + modified_files+=("$repo_file") fi fi done < <(_config ls-files) # Report missing files if [[ ${#missing_files[@]} -gt 0 ]]; then - echo "=== Missing Files (consider removing from git) ===" + echo -e "${BOLD}${RED}=== Missing Files (consider removing from git) ===${RESET}" for repo_file in "${missing_files[@]}"; do - echo "missing: $repo_file" + echo -e " ${RED}deleted:${RESET} $(_sys_path "$repo_file") -> $repo_file" done echo fi - # Report auto-synced files - if [[ ${#auto_synced[@]} -gt 0 ]]; then - echo "=== Auto-synced Files ===" - for repo_file in "${auto_synced[@]}"; do - echo "synced: $(_sys_path "$repo_file") -> $repo_file" + # Report modified files + if [[ ${#modified_files[@]} -gt 0 ]]; then + echo -e "${BOLD}${YELLOW}=== Modified Files (different from system) ===${RESET}" + for repo_file in "${modified_files[@]}"; do + echo -e " ${YELLOW}modified:${RESET} $(_sys_path "$repo_file") -> $repo_file" done echo fi - _config status + # Finally, show underlying git status (with colors) + _config -c color.status=always status ;; deploy|checkout) @@ -315,7 +486,6 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then } fi - # Make SUDO_ASKPASS agnostic: pick the first available askpass binary. # You can predefine SUDO_ASKPASS env var to force a particular path. : "${SUDO_ASKPASS:=""}" diff --git a/common/config/zsh/user/options.zsh b/common/config/zsh/user/options.zsh index 99840d7..aaa7b83 100644 --- a/common/config/zsh/user/options.zsh +++ b/common/config/zsh/user/options.zsh @@ -1,8 +1,8 @@ # Recursion limits FUNCNEST=999 - +# DISABLE_MAGIC_FUNCTIONS=true - +# # Enable various options for Zsh behavior setopt interactive_comments # Allow comments to appear in interactive mode unsetopt BEEP # Disable the system beep (to prevent annoying beeps) @@ -19,6 +19,10 @@ setopt AUTO_PUSHD # Save more directory history, and use "cd -" w # Hide history of commands starting with a space setopt histignorespace # Do not save commands that start with a space in the history +setopt BANG_HIST EXTENDED_HISTORY INC_APPEND_HISTORY SHARE_HISTORY +setopt HIST_EXPIRE_DUPS_FIRST HIST_IGNORE_DUPS HIST_IGNORE_ALL_DUPS +setopt HIST_FIND_NO_DUPS HIST_IGNORE_SPACE HIST_SAVE_NO_DUPS +setopt HIST_REDUCE_BLANKS HIST_VERIFY HIST_BEEP # --- Detect terminal control characters and behavior --- diff --git a/common/config/zsh/user/prompt.zsh b/common/config/zsh/user/prompt.zsh index c55a835..4a52138 100644 --- a/common/config/zsh/user/prompt.zsh +++ b/common/config/zsh/user/prompt.zsh @@ -1,6 +1,20 @@ #!/bin/zsh -########## Prompt(s) ########## +########## Prompt(s) ########## + +# Check if Nerd Fonts are installed +typeset -g _has_nerd_fonts=0 +if fc-list | grep -qi nerd; then + _has_nerd_fonts=1 +fi + +# Set git branch icon based on font availability +typeset -g _git_branch_icon +if [[ $_has_nerd_fonts -eq 1 ]]; then + _git_branch_icon=$'\uE0A0' # Nerd Font git branch icon +else + _git_branch_icon="±" # Fallback: plus-minus symbol +fi # Autoload necessary functions for vcs_info and coloring autoload -Uz vcs_info @@ -28,8 +42,8 @@ typeset -g _spinner_idx=0 typeset -ga _spinner_frames=('⣾' '⣽' '⣻' '⢿' '⡿' '⣟' '⣯' '⣷') typeset -g _cmd_is_running=0 typeset -g _show_spinner=0 -typeset -g _SPINNER_DELAY=5 # Only show spinner after 5 seconds -typeset -g _FINISHED_DELAY=10 # Only show finished message after 10 seconds +typeset -g _SPINNER_DELAY=5 # Only show spinner after 5 seconds +typeset -g _FINISHED_DELAY=10 # Only show finished message after 10 seconds # Register the ZLE widget for spinner updates - do this early zle -N update_spinner @@ -37,7 +51,7 @@ zle -N update_spinner # Cache git information to avoid repeated expensive operations typeset -g _git_cached_info="" typeset -g _git_cache_timestamp=0 -typeset -g _git_cache_lifetime=2 # seconds before cache expires +typeset -g _git_cache_lifetime=2 # seconds before cache expires # Calculate how much space is available for the prompt components function available_space() { @@ -48,8 +62,8 @@ function available_space() { # Check if we need to abbreviate git info function need_to_abbreviate_git() { local available=$(available_space) - local vi_mode_len=13 # Length of "-- INSERT --" - local prompt_base_len=20 # Base prompt elements length + local vi_mode_len=13 # Length of "-- INSERT --" + local prompt_base_len=20 # Base prompt elements length local path_len=${#${PWD/#$HOME/\~}} local git_full_len=0 @@ -70,9 +84,9 @@ function need_to_abbreviate_git() { # Determine if we need to abbreviate if [[ $total_needed -gt $available ]]; then - return 0 # Need to abbreviate + return 0 # Need to abbreviate else - return 1 # Don't need to abbreviate + return 1 # Don't need to abbreviate fi } @@ -158,15 +172,14 @@ function configure_vcs_styles() { fi zstyle ':vcs_info:*' actionformats '%F{5}%F{2}%b%F{3}|%F{1}%a%F{5}%f ' - zstyle ':vcs_info:*' formats '%F{208} '$'\uE0A0'' %f$(git_branch_test_color)%f%F{76}%c%F{3}%u%f ' + zstyle ':vcs_info:*' formats "%F{208} ${_git_branch_icon} %f\$(git_branch_test_color)%f%F{76}%c%F{3}%u%f " zstyle ':vcs_info:git*+set-message:*' hooks git-untracked git-dynamic } # Show "untracked" status in git - with conditional abbreviation +vi-git-untracked() { if [[ $(git rev-parse --is-inside-work-tree 2> /dev/null) == 'true' ]] && \ - git status --porcelain | grep '??' &> /dev/null ; then - + git status --porcelain | grep '??' &> /dev/null ; then if need_to_abbreviate_git; then hook_com[unstaged]+='%F{196} !%f%F{15}u%f' else @@ -184,7 +197,6 @@ function configure_vcs_styles() { ssh_name() { if [[ -n $SSH_CONNECTION ]]; then local ssh_info - if need_to_abbreviate_git; then # Abbreviated SSH info ssh_info="ssh:%F{green}%n$nc%f" @@ -217,7 +229,6 @@ function job_name() { job_name="${title_jobs}" job_length=$((${available}-70)) [ "${job_length}" -lt "0" ] && job_length=0 - if [ "${job_length}" -gt 0 ]; then job_name+="%F{green}$(jobs | grep + | tr -s " " | cut -d " " -f 4- | cut -b 1-${job_length} | sed "s/\(.*\)/\1/")%f" else @@ -226,7 +237,6 @@ function job_name() { fi fi fi - echo "${job_name}" } @@ -239,12 +249,12 @@ function should_show_spinner() { # Show spinner only after delay threshold if [[ $elapsed -ge $_SPINNER_DELAY ]]; then _show_spinner=1 - return 0 # Yes, show spinner + return 0 # Yes, show spinner fi fi _show_spinner=0 - return 1 # No, don't show spinner + return 1 # No, don't show spinner } # Update spinner animation - simplified version @@ -258,10 +268,10 @@ function update_spinner() { function start_spinner_timer() { _spinner_idx=0 _cmd_is_running=1 - _show_spinner=0 # Start with spinner hidden until delay passes + _show_spinner=0 # Start with spinner hidden until delay passes # Set up the TRAPALRM for periodic updates - CRITICAL FIX - TMOUT=0.5 # Update spinner every 0.5 seconds + TMOUT=0.5 # Update spinner every 0.5 seconds # Define TRAPALRM function - this is key to the spinner working TRAPALRM() { @@ -385,7 +395,6 @@ function exit_code_info() { if [[ $exit_code -ne 0 ]]; then local signal_name="" - # Check if it's a signal if [[ $exit_code -gt 128 && $exit_code -le 165 ]]; then local signal_num=$((exit_code - 128)) @@ -398,12 +407,12 @@ function exit_code_info() { # Return formatted error code echo "%F{red}✘ $exit_code$signal_name%f" else - echo "%F{green}✓%f" # Success indicator + echo "%F{green}✓%f" # Success indicator fi } abbreviated_path() { - local full_path="${PWD/#$HOME/~}" # Replace $HOME with ~ + local full_path="${PWD/#$HOME/~}" # Replace $HOME with ~ local available=$(available_space) # If path is root @@ -450,12 +459,11 @@ abbreviated_path() { for i in {1..$((last_index - 1))}; do [[ -n ${parts[i]} ]] && result+="/${parts[i]:0:1}" done - result+="/${parts[last_index]}" + echo "%F{4}${result}%f" } - # Prompt variables user="%n" at="%F{15}at%{$reset_color%}" @@ -482,14 +490,10 @@ v4="]" newline=$'\n' # Indicate INSERT mode for vi - NEVER truncate this -function insert-mode () { - echo "-- INSERT --" -} +function insert-mode () { echo "-- INSERT --" } # Indicate NORMAL mode for vi - NEVER truncate this -function normal-mode () { - echo "-- NORMAL --" -} +function normal-mode () { echo "-- NORMAL --" } # Vi mode indicator vi-mode-indicator () { @@ -505,15 +509,15 @@ vi-mode-indicator () { # Prompt function to ensure the prompt stays on one line, even in narrow terminals function set-prompt() { vi-mode-indicator - configure_vcs_styles # Dynamically set vcs styles based on available space - vcs_info # Refresh vcs info with new styles + configure_vcs_styles # Dynamically set vcs styles based on available space + vcs_info # Refresh vcs info with new styles local available=$(available_space) + if (( available < 14 )); then # Extremely narrow terminal — use minimal prompt PS1="${carriage_return}${dollar}${space}${empty_line_bottom}" RPROMPT='$(exit_code_info)' - else # Path display - always show something for path, but adapt based on space local path_display="$(abbreviated_path)" @@ -550,7 +554,7 @@ my_precmd() { _cmd_duration=0 fi - stop_spinner_timer # Make sure spinner is stopped + stop_spinner_timer # Make sure spinner is stopped vcs_info set-prompt vi-mode-indicator @@ -609,12 +613,8 @@ function zle-line-init() { zle reset-prompt vi-mode-indicator case "${KEYMAP}" in - vicmd) - echo -ne '\e[1 q' - ;; - main|viins|*) - echo -ne '\e[5 q' - ;; + vicmd) echo -ne '\e[1 q' ;; + main|viins|*) echo -ne '\e[5 q' ;; esac } @@ -625,12 +625,8 @@ function zle-keymap-select() { zle -R vi-mode-indicator case "${KEYMAP}" in - vicmd) - echo -ne '\e[1 q' - ;; - main|viins|*) - echo -ne '\e[5 q' - ;; + vicmd) echo -ne '\e[1 q' ;; + main|viins|*) echo -ne '\e[5 q' ;; esac } @@ -648,7 +644,7 @@ function preexec() { _last_executed_command=$1 _cmd_start_time=$(date +%s) _cmd_is_running=1 - _show_spinner=0 # Reset spinner flag + _show_spinner=0 # Reset spinner flag # Start the spinner timer immediately start_spinner_timer diff --git a/common/install.sh b/common/install.sh index 4f531bf..cea4b52 100755 --- a/common/install.sh +++ b/common/install.sh @@ -6,6 +6,8 @@ # Dependencies: git, curl +# TODO: install fonts/icons ie Whitesur, San Francisco JetBrains Mono + # POSIX-compatible shim: if not running under bash (e.g., invoked via `sh -c "$(curl ...)"`), # re-exec the remainder of this script with bash. if [ -z "${BASH_VERSION:-}" ]; then @@ -147,6 +149,7 @@ declare -A INSTALLATION_STEPS=( ["detect_package_manager"]="Detect or configure package manager" ["install_dependencies"]="Install dependencies" ["install_dotfiles"]="Install dotfiles repository" + ["deploy_config"]="Deploy config command and dotfiles" ["setup_user_dirs"]="Setup user directories" ["setup_passwords"]="Setup user and root passwords (optional)" ["install_essentials"]="Install essential tools" @@ -154,9 +157,9 @@ declare -A INSTALLATION_STEPS=( ["setup_shell"]="Setup shell environment" ["setup_ssh"]="Setup SSH configuration" ["configure_services"]="Configure system services" + ["configure_git"]="Configure git" ["setup_development_environment"]="Setup development environment" ["apply_tweaks"]="Apply system tweaks" - ["deploy_config"]="Deploy config command and dotfiles" ) # Step order @@ -174,6 +177,7 @@ STEP_ORDER=( "setup_shell" "setup_ssh" "configure_services" + "configure_git" "setup_development_environment" "apply_tweaks" ) @@ -655,7 +659,6 @@ manual_package_manager_setup() { # Utility Functions #====================================== - command_exists() { command -v "$1" &>/dev/null } @@ -932,7 +935,6 @@ install_package_offline() { # Package Management Functions #====================================== - install_single_package() { local package="$1" local package_type="${2:-system}" @@ -1348,72 +1350,124 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then *) CFG_OS="other" ;; esac - # Map system path to repository path _repo_path() { local f="$1" - # If it's an absolute path that's not in HOME, handle it specially - if [[ "$f" == /* && "$f" != "$HOME/"* ]]; then - echo "$CFG_OS/${f#/}" - return + # Normalize absolute or relative + if [[ "$f" == "$HOME/"* ]]; then + f="${f#$HOME/}" + elif [[ "$f" == "./"* ]]; then + f="${f#./}" fi - # Check for paths that should go to the repository root + # Already tracked? Use that + local dirs=("common/" "$CFG_OS/home/" "$CFG_OS/Users/") + for d in "${dirs[@]}"; do + local match="$(_config ls-files --full-name | grep -F "/$f" | grep -F "$d" || true)" + if [[ -n "$match" ]]; then + echo "$match" + return + fi + done + + # Already a special repo path case "$f" in - common/*|linux/*|macos/*|windows/*|profile/*|README.md) + common/*|"$CFG_OS/home/"*|"$CFG_OS/Users/"*|profile/*|README.md) echo "$f" return ;; - "$HOME/"*) - f="${f#$HOME/}" - ;; esac - # Default: put under OS-specific home - echo "$CFG_OS/home/$f" + # Map everything else dynamically + case "$f" in + *) + case "$CFG_OS" in + linux) echo "linux/home/$f" ;; + macos) echo "macos/Users/$f" ;; + windows) echo "windows/Users/$f" ;; + *) echo "$CFG_OS/home/$f" ;; + esac + ;; + esac } _sys_path() { local repo_path="$1" - local os_path_pattern="$CFG_OS/" - # Handle OS-specific files that are not in the home subdirectory - if [[ "$repo_path" == "$os_path_pattern"* && "$repo_path" != */home/* ]]; then - echo "/${repo_path#$os_path_pattern}" - return - fi + # System HOME + local sys_home + case "$CFG_OS" in + linux|macos) sys_home="$HOME" ;; + windows) sys_home="$USERPROFILE" ;; + esac + + # Repo HOME roots + local repo_home + case "$CFG_OS" in + linux) repo_home="linux/home" ;; + macos) repo_home="macos/Users" ;; + windows) repo_home="windows/Users" ;; + esac case "$repo_path" in - common/scripts/*) - echo "$HOME/.scripts/${repo_path#common/scripts/}" - ;; - common/config/*) - case "$CFG_OS" in - linux) - local base="${XDG_CONFIG_HOME:-$HOME/.config}" - echo "$base/${repo_path#common/config/}" + # Common files → $HOME/… but normalize well-known dirs + common/*) + local rel="${repo_path#common/}" + + case "$rel" in + # XDG config + .config/*|config/*) + local sub="${rel#*.config/}" + sub="${sub#config/}" + echo "${XDG_CONFIG_HOME:-$sys_home/.config}/$sub" ;; - macos) - echo "$HOME/Library/Application Support/${repo_path#common/config/}" + + # XDG data (assets, wallpapers, icons, fonts…) + assets/*|.local/share/*) + local sub="${rel#assets/}" + sub="${sub#.local/share/}" + echo "${XDG_DATA_HOME:-$sys_home/.local/share}/$sub" ;; - windows) - # Windows Bash (Git Bash, MSYS, WSL) respects LOCALAPPDATA - echo "$LOCALAPPDATA\\${repo_path#common/config/}" + + # XDG cache (if you ever store cached scripts/config) + .cache/*) + local sub="${rel#.cache/}" + echo "${XDG_CACHE_HOME:-$sys_home/.cache}/$sub" + ;; + + # Scripts + .scripts/*|scripts/*) + local sub="${rel#*.scripts/}" + sub="${sub#scripts/}" + echo "$sys_home/.scripts/$sub" ;; + + # Default: dump directly under $HOME *) - echo "$HOME/.config/${repo_path#common/config/}" + echo "$sys_home/$rel" ;; esac ;; - common/assets/*|profile/*|README.md) - echo "$HOME/.cfg/$repo_path" + + # Profile files → $HOME/… + profile/*) + local rel="${repo_path#profile/}" + echo "$sys_home/$rel" ;; - common/*) - echo "$HOME/.cfg/$repo_path" + + # OS-specific home paths → $HOME or $USERPROFILE + "$repo_home"/*) + local rel="${repo_path#$repo_home/}" + echo "$sys_home/$rel" ;; - */home/*) - echo "$HOME/${repo_path#*/home/}" + + # OS-specific system paths outside home/Users → absolute + "$CFG_OS/"*) + local rel="${repo_path#$CFG_OS/}" + echo "/$rel" ;; + + # Fallback: treat as repo-only *) echo "$HOME/.cfg/$repo_path" ;; @@ -1441,68 +1495,74 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then # Main config command config() { local cmd="$1"; shift - local target_dir="" - - # Parse optional --target flag for add - if [[ "$cmd" == "add" ]]; then - while [[ "$1" == --* ]]; do - case "$1" in - --target|-t) - target_dir="$2" - shift 2 - ;; - *) - echo "Unknown option: $1" - return 1 - ;; - esac - done - fi case "$cmd" in add) local file_path local git_opts=() local files=() + local target_dir="" - # Parse arguments + # Parse optional --target flag before files while [[ $# -gt 0 ]]; do case "$1" in --target|-t) target_dir="$2" shift 2 ;; - -*) # Any other flags are git flags + -*) # any other git flags git_opts+=("$1") shift ;; - *) # Anything else is a file + *) # files files+=("$1") shift ;; esac done - # Process each file for file_path in "${files[@]}"; do + # Store original for rel_path calculation + local original_path="$file_path" + + # Make path absolute first + if [[ "$file_path" != /* && "$file_path" != "$HOME/"* ]]; then + file_path="$(pwd)/$file_path" + fi + + # Check if file exists + if [[ ! -e "$file_path" ]]; then + echo "Error: File not found: $file_path" + continue + fi + + # Calculate relative path from original input + local rel_path + if [[ "$original_path" == "$HOME/"* ]]; then + rel_path="${original_path#$HOME/}" + elif [[ "$original_path" == "./"* ]]; then + rel_path="${original_path#./}" + else + rel_path="$original_path" + fi + + # Check if file is already tracked + local existing_path="$(_config ls-files --full-name | grep -Fx "$(_repo_path "$file_path")" || true)" local repo_path - if [[ -n "$target_dir" ]]; then - local rel_path - if [[ "$file_path" == /* ]]; then - rel_path="$(basename "$file_path")" - else - rel_path="$file_path" - fi + if [[ -n "$existing_path" ]]; then + repo_path="$existing_path" + elif [[ -n "$target_dir" ]]; then repo_path="$target_dir/$rel_path" else repo_path="$(_repo_path "$file_path")" fi + # Copy file into bare repo local full_repo_path="$HOME/.cfg/$repo_path" mkdir -p "$(dirname "$full_repo_path")" cp -a "$file_path" "$full_repo_path" - # Only git flags + repo_path go to git + # Add to git _config add "${git_opts[@]}" "$repo_path" echo "Added: $file_path -> $repo_path" @@ -1512,17 +1572,43 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then rm) local rm_opts="" local file_path_list=() + local target_dir="" - for arg in "$@"; do - if [[ "$arg" == "-"* ]]; then - rm_opts+=" $arg" - else - file_path_list+=("$arg") - fi + # Parse options + while [[ $# -gt 0 ]]; do + case "$1" in + --target|-t) + target_dir="$2" + shift 2 + ;; + -*) + rm_opts+=" $1" + shift + ;; + *) + file_path_list+=("$1") + shift + ;; + esac done for file_path in "${file_path_list[@]}"; do - local repo_path="$(_repo_path "$file_path")" + local repo_path + # Check if already a repo path (exists in git index) - exact match + if _config ls-files --full-name | grep -qFx "$file_path"; then + repo_path="$file_path" + elif [[ -n "$target_dir" ]]; then + # Use target directory if specified + local rel_path + if [[ "$file_path" == "$HOME/"* ]]; then + rel_path="${file_path#$HOME/}" + else + rel_path="${file_path#./}" + fi + repo_path="$target_dir/$rel_path" + else + repo_path="$(_repo_path "$file_path")" + fi if [[ "$rm_opts" == *"-r"* ]]; then _config rm --cached -r "$repo_path" @@ -1530,8 +1616,12 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then _config rm --cached "$repo_path" fi - eval "rm $rm_opts \"$file_path\"" - echo "Removed: $file_path" + # Compute system path for actual file removal + local sys_file="$(_sys_path "$repo_path")" + if [[ -e "$sys_file" ]]; then + eval "rm $rm_opts \"$sys_file\"" + fi + echo "Removed: $repo_path" done ;; @@ -1563,10 +1653,17 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then ;; status) - # Check for missing files and auto-sync existing ones - local auto_synced=() + local modified_files=() local missing_files=() + # Colors like git + local RED="\033[31m" + local GREEN="\033[32m" + local YELLOW="\033[33m" + local BLUE="\033[34m" + local BOLD="\033[1m" + local RESET="\033[0m" + while read -r repo_file; do local sys_file="$(_sys_path "$repo_file")" local full_repo_path="$HOME/.cfg/$repo_file" @@ -1575,31 +1672,31 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then missing_files+=("$repo_file") elif [[ -e "$sys_file" ]]; then if ! diff -q "$full_repo_path" "$sys_file" >/dev/null 2>&1; then - cp -fa "$sys_file" "$full_repo_path" - auto_synced+=("$repo_file") + modified_files+=("$repo_file") fi fi done < <(_config ls-files) # Report missing files if [[ ${#missing_files[@]} -gt 0 ]]; then - echo "=== Missing Files (consider removing from git) ===" + echo -e "${BOLD}${RED}=== Missing Files (consider removing from git) ===${RESET}" for repo_file in "${missing_files[@]}"; do - echo "missing: $repo_file" + echo -e " ${RED}deleted:${RESET} $(_sys_path "$repo_file") -> $repo_file" done echo fi - # Report auto-synced files - if [[ ${#auto_synced[@]} -gt 0 ]]; then - echo "=== Auto-synced Files ===" - for repo_file in "${auto_synced[@]}"; do - echo "synced: $(_sys_path "$repo_file") -> $repo_file" + # Report modified files + if [[ ${#modified_files[@]} -gt 0 ]]; then + echo -e "${BOLD}${YELLOW}=== Modified Files (different from system) ===${RESET}" + for repo_file in "${modified_files[@]}"; do + echo -e " ${YELLOW}modified:${RESET} $(_sys_path "$repo_file") -> $repo_file" done echo fi - _config status + # Finally, show underlying git status (with colors) + _config -c color.status=always status ;; deploy|checkout) @@ -2172,13 +2269,47 @@ setup_passwords() { print_section "Setting Up Passwords (Optional)" save_state "setup_passwords" "started" - if [[ "$ASK_MODE" != true && "$FORCE_MODE" != true ]]; then - print_info "Skipping password setup (non-interactive). Use --ask to be prompted." + # FORCE_MODE → change passwords directly (no prompt_user) + if [[ "$FORCE_MODE" == true ]]; then + print_info "FORCE mode: changing passwords without prompt confirmation" + + # Change current user password + print_color "$YELLOW" "Enter new password for $USER: " + read -rs __pw_user; echo + print_color "$YELLOW" "Confirm new password for $USER: " + read -rs __pw_user2; echo + if [[ "$__pw_user" == "$__pw_user2" && -n "$__pw_user" ]]; then + if execute_with_privilege "bash -lc 'echo \"$USER:$__pw_user\" | chpasswd'"; then + print_success "Password updated for $USER" + else + print_error "Failed to update password for $USER" + fi + else + print_warning "Passwords did not match; skipping $USER" + fi + unset __pw_user __pw_user2 + + # Change root password + print_color "$YELLOW" "Enter new password for root: " + read -rs __pw_root; echo + print_color "$YELLOW" "Confirm new password for root: " + read -rs __pw_root2; echo + if [[ "$__pw_root" == "$__pw_root2" && -n "$__pw_root" ]]; then + if execute_with_privilege "bash -lc 'echo \"root:$__pw_root\" | chpasswd'"; then + print_success "Password updated for root" + else + print_error "Failed to update password for root" + fi + else + print_warning "Passwords did not match; skipping root" + fi + unset __pw_root __pw_root2 + mark_step_completed "setup_passwords" return 0 fi - # Change current user password + # Always ask if not in FORCE_MODE if prompt_user "Change password for user '$USER'?" "N"; then print_color "$YELLOW" "Enter new password for $USER: " read -rs __pw_user; echo @@ -2198,7 +2329,6 @@ setup_passwords() { print_skip "User password change (skipped)" fi - # Change root password if prompt_user "Change password for 'root'?" "N"; then print_color "$YELLOW" "Enter new password for root: " read -rs __pw_root; echo @@ -2487,7 +2617,6 @@ setup_shell() { fi if command_exists zsh; then - local zsh_path zsh_path="$(command -v zsh)" if [[ "$FORCE_MODE" == true ]]; then @@ -2498,7 +2627,8 @@ setup_shell() { else print_error "Failed to change default shell" fi - elif [[ "$ASK_MODE" == true ]]; then + else + # Always ask if not in FORCE mode if prompt_user "Change default shell to Zsh?" "N"; then if execute_with_privilege "chsh -s '$zsh_path' '$USER'"; then print_success "Default shell changed to Zsh" @@ -2509,17 +2639,15 @@ setup_shell() { else print_skip "Default shell change (user chose No)" fi - else - print_info "Skipping shell change (non-interactive mode). Use --ask to be prompted or --force to auto-change." fi else print_warning "Zsh not installed, skipping shell setup" fi - # Zsh plugins are managed via packages.yml custom_installs (zsh_plugins) - # No direct plugin installation here to avoid duplication. + # Zsh plugins are managed via packages.yml custom_installs (zsh_plugins) + # No direct plugin installation here to avoid duplication. - mark_step_completed "setup_shell" + mark_step_completed "setup_shell" } setup_ssh() { @@ -2817,7 +2945,6 @@ setup_tmux_plugins() { # Development Environment Setup #====================================== - setup_development_environment() { # Accept optional packages_file argument. If missing, try to locate a default. local packages_file="${1:-}" diff --git a/common/packages.yml b/common/packages.yml index 2aa435f..233e714 100644 --- a/common/packages.yml +++ b/common/packages.yml @@ -1,10 +1,6 @@ # Dotfiles Installation Packages Configuration # This file defines packages to install based on installation profiles and distribution-specific mappings -# TODO: -# tree-sitter-cli -# make sure rust is installed, go pipx virtualenvwrapper - #====================================== # Installation Profiles #====================================== diff --git a/linux/home/.config/inputrc b/linux/home/.config/inputrc index adfeec4..435f529 100644 --- a/linux/home/.config/inputrc +++ b/linux/home/.config/inputrc @@ -2,12 +2,22 @@ $include /etc/inputrc "\f": clear-screen -set bell-style none +# Enable bracketed paste mode (allows pasting in TUI apps) +set enable-bracketed-paste on +#set enable-bracketed-paste off + +set echo-control-characters off + +# Bind Ctrl+V to accept pasted text +#"\C-v": "\e[200~" +#"\C-v": paste-from-clipboard + set meta-flag on set input-meta on set convert-meta off set output-meta on set show-all-if-ambiguous on # set show-all-if-unmodified on +set bell-style none # Color files by types # Note that this may cause completion text blink in some terminals (e.g. xterm). @@ -39,6 +49,14 @@ $if mode=vi "\e[A": history-search-backward "\e[B": history-search-forward "jk" # escape + + # Enable arrow keys (already default in readline) + "\e[D": backward-char + "\e[C": forward-char + + # Allow Alt+h and Alt+l for navigation + "\eh": backward-char + "\el": forward-char $endif $if mode=emacs @@ -72,6 +90,13 @@ $if mode=emacs "\e[H": beginning-of-line "\e[F": end-of-line + # Enable arrow keys (already default in readline) + "\e[D": backward-char + "\e[C": forward-char + + # Allow Alt+h and Alt+l for navigation + "\eh": backward-char + "\el": forward-char $endif #set editing-mode emacs diff --git a/linux/home/.config/kitty/kitty.conf b/linux/home/.config/kitty/kitty.conf index cbf9f70..e42a1b4 100644 --- a/linux/home/.config/kitty/kitty.conf +++ b/linux/home/.config/kitty/kitty.conf @@ -23,7 +23,7 @@ bold_italic_font auto #: italic_font Operator Mono Book Italic #: bold_italic_font Operator Mono Medium Italic -font_size 9.0 +font_size 13.0 #: Font size (in pts) @@ -333,7 +333,7 @@ background #000000 #: The foreground and background colors -background_opacity 0.7 +background_opacity 0.9 dynamic_background_opacity yes # Increase background opacity ctrl+shift+a>m diff --git a/linux/home/.config/tmux/tmux.conf b/linux/home/.config/tmux/tmux.conf index a62e3e3..3028e2c 100644 --- a/linux/home/.config/tmux/tmux.conf +++ b/linux/home/.config/tmux/tmux.conf @@ -419,7 +419,7 @@ bind-key -n M-y if-shell -F '#{==:#{session_name},virt}' { detach-client -P } { set -gF '@last_session_name' '#S' - display-popup -E -x200% -y0 -w40% -h60% "tmux new-session -A -s virt bash -lc 'echo \"### VM Manager ###\"; echo; echo \"Available VM scripts:\"; ls -1 ~/.scripts/env/virt/ 2>/dev/null || echo \"No scripts found in ~/.scripts/env/virt/\"; echo; echo \"Run your VM by typing its script name (e.g., ubuntu, fedora, win11).\"; exec \$SHELL'" + display-popup -E -x200% -y0 -w40% -h60% "tmux new-session -A -s virt bash -lc 'echo \"### VM Manager ###\"; echo; echo \"Available VM scripts:\"; ls -1 ~/.scripts/virt/ 2>/dev/null || echo \"No scripts found in ~/.scripts/virt/\"; echo; echo \"Run your VM by typing its script name (e.g., ubuntu, fedora, win11).\"; exec \$SHELL'" } # M-H → Open history in popup @@ -747,10 +747,6 @@ set -g status-right '#( \ #set-hook -g after-new-session 'source-file ~/.tmux.conf' #set-hook -g client-attached 'source-file ~/.tmux.conf' - -# Set environment variable from script output -#run-shell 'tmux set-environment -g NERD_FONT_DETECTED "$(~/.config/tmux/detect_nerd_font)"' - ## Reload Status with IP addr, Cpu, Mem and Date bind a run-shell ~/.config/tmux/tmux-toggle-option.sh diff --git a/linux/home/.config/user-dirs.dirs b/linux/home/.config/user-dirs.dirs index 0db0cae..989eb64 100644 --- a/linux/home/.config/user-dirs.dirs +++ b/linux/home/.config/user-dirs.dirs @@ -4,8 +4,8 @@ # Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped # homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an # absolute path. No other format is supported. -# -XDG_DESKTOP_DIR="$HOME/" +# +XDG_DESKTOP_DIR="$HOME/desktop" XDG_DOWNLOAD_DIR="$HOME/downloads" XDG_TEMPLATES_DIR="$HOME/" XDG_PUBLICSHARE_DIR="$HOME/" @@ -13,3 +13,4 @@ XDG_DOCUMENTS_DIR="$HOME/documents" XDG_MUSIC_DIR="$HOME/music" XDG_PICTURES_DIR="$HOME/pictures" XDG_VIDEOS_DIR="$HOME/videos" +XDG_GAMES_DIR="$HOME/games" -- cgit v1.2.3