Dotfiles update 2025-12-22 12:00

This commit is contained in:
Aaron D. Lee
2025-12-22 12:00:32 -05:00
parent 23ee772ac4
commit 9e916c6c54
13 changed files with 646 additions and 1525 deletions

View File

@@ -2,63 +2,38 @@
# ============================================================================
# Dotfiles Compile - Pre-compile zsh files for faster loading
# ============================================================================
# Compiles .zsh and .zshrc files to .zwc bytecode format
# This can speed up shell startup by 20-50ms
#
# Usage:
# dotfiles-compile.sh # Compile all
# dotfiles-compile.sh --clean # Remove compiled files
#
# Aliases: dfc-compile
# ============================================================================
set -e
DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}"
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
# Source shared colors
source "$DOTFILES_DIR/zsh/lib/colors.zsh" 2>/dev/null || {
DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_CYAN=$'\033[0;36m'
DF_NC=$'\033[0m' DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m'
DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m'
}
# ============================================================================
# MOTD-style header
# ============================================================================
_M_WIDTH=66
print_header() {
local user="${USER:-root}"
local hostname="${HOST:-$(hostname -s 2>/dev/null)}"
local script_name="dotfiles-compile"
local datetime=$(date '+%a %b %d %H:%M')
if declare -f df_print_header &>/dev/null; then
df_print_header "dotfiles-compile"
else
local user="${USER:-root}"
local hostname="${HOST:-$(hostname -s 2>/dev/null)}"
local datetime=$(date '+%a %b %d %H:%M')
local width=66
local hline="" && for ((i=0; i<width; i++)); do hline+="═"; done
# Colors
local _M_RESET=$'\033[0m'
local _M_BOLD=$'\033[1m'
local _M_DIM=$'\033[2m'
local _M_BLUE=$'\033[38;5;39m'
local _M_GREY=$'\033[38;5;242m'
# Build horizontal line
local hline=""
for ((i=0; i<_M_WIDTH; i++)); do hline+="═"; done
local inner=$((_M_WIDTH - 2))
# Header content
local h_left="${user}@${hostname}"
local h_center="${script_name}"
local h_right="${datetime}"
local h_pad=$(((inner - ${#h_left} - ${#h_center} - ${#h_right}) / 2))
local h_spaces=""
for ((i=0; i<h_pad; i++)); do h_spaces+=" "; done
echo ""
echo "${_M_GREY}${hline}${_M_RESET}"
echo "${_M_GREY}${_M_RESET} ${_M_BOLD}${_M_BLUE}${h_left}${_M_RESET}${h_spaces}${_M_DIM}${h_center}${h_spaces}${h_right}${_M_RESET} ${_M_GREY}${_M_RESET}"
echo "${_M_GREY}${hline}${_M_RESET}"
echo ""
echo ""
echo "${DF_GREY}${hline}${DF_NC}"
echo "${DF_GREY}${DF_NC} ${DF_BOLD}${DF_LIGHT_BLUE}${user}@${hostname}${DF_NC} ${DF_DIM}dotfiles-compile${DF_NC} ${datetime} ${DF_GREY}${DF_NC}"
echo "${DF_GREY}${hline}${DF_NC}"
echo ""
fi
}
compile_file() {
@@ -66,10 +41,10 @@ compile_file() {
if [[ -f "$file" ]]; then
if [[ ! -f "${file}.zwc" ]] || [[ "$file" -nt "${file}.zwc" ]]; then
zcompile "$file" 2>/dev/null && \
echo -e "${GREEN}${NC} Compiled: ${file##*/}" || \
echo -e "${YELLOW}${NC} Skipped: ${file##*/}"
echo -e "${DF_GREEN}${DF_NC} Compiled: ${file##*/}" || \
echo -e "${DF_YELLOW}${DF_NC} Skipped: ${file##*/}"
else
echo -e "${CYAN}${NC} Current: ${file##*/}"
echo -e "${DF_CYAN}${DF_NC} Current: ${file##*/}"
fi
fi
}
@@ -79,57 +54,46 @@ clean_compiled() {
local count=0
# Dotfiles
for zwc in "$DOTFILES_DIR"/**/*.zwc(N); do
rm -f "$zwc"
((count++))
done
# Home zsh files
rm -f ~/.zshrc.zwc ~/.zshenv.zwc ~/.zprofile.zwc 2>/dev/null
# Oh-my-zsh (optional)
# rm -f ~/.oh-my-zsh/**/*.zwc(N) 2>/dev/null
echo -e "${GREEN}${NC} Removed $count compiled files"
echo -e "${DF_GREEN}${DF_NC} Removed $count compiled files"
}
compile_all() {
echo -e "${CYAN}Compiling zsh files for faster startup...${NC}"
echo -e "${DF_CYAN}Compiling zsh files for faster startup...${DF_NC}"
echo
# Core files
echo "Core files:"
compile_file ~/.zshrc
compile_file ~/.zshenv
compile_file ~/.zprofile
echo
# Dotfiles zsh files
echo "Dotfiles:"
compile_file "$DOTFILES_DIR/zsh/.zshrc"
compile_file "$DOTFILES_DIR/zsh/aliases.zsh"
# Function files
for file in "$DOTFILES_DIR/zsh/functions"/*.zsh(N); do
compile_file "$file"
done
# Theme
for file in "$DOTFILES_DIR/zsh/themes"/*.zsh-theme(N); do
compile_file "$file"
done
echo
# Oh-my-zsh core (optional, can save ~10ms)
if [[ -d ~/.oh-my-zsh ]]; then
echo "Oh-My-Zsh (optional):"
compile_file ~/.oh-my-zsh/oh-my-zsh.sh
# compile_file ~/.oh-my-zsh/lib/*.zsh # Uncomment for more speed
echo
fi
echo -e "${GREEN}${NC} Compilation complete"
echo -e "${DF_GREEN}${DF_NC} Compilation complete"
echo
echo "To measure startup time:"
echo " time zsh -i -c exit"
@@ -145,9 +109,6 @@ show_help() {
echo " (none) Compile all zsh files"
echo " --clean Remove all compiled (.zwc) files"
echo " --help Show this help"
echo
echo "The compiled files (.zwc) are automatically used by zsh"
echo "and can speed up shell startup by 20-50ms."
}
# ============================================================================
@@ -157,13 +118,7 @@ show_help() {
print_header
case "${1:-}" in
--clean|-c)
clean_compiled
;;
--help|-h)
show_help
;;
*)
compile_all
;;
--clean|-c) clean_compiled ;;
--help|-h) show_help ;;
*) compile_all ;;
esac

View File

@@ -5,16 +5,17 @@
set -e
readonly DOTFILES_HOME="${DOTFILES_HOME:-.}"
readonly DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}"
readonly DOTFILES_VERSION="3.0.0"
# Color codes
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly CYAN='\033[0;36m'
readonly NC='\033[0m'
# Source shared colors
source "$DOTFILES_HOME/zsh/lib/colors.zsh" 2>/dev/null || {
# Fallback if colors.zsh not found
DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m'
DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m'
DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m'
DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m'
}
# Track results
TOTAL_CHECKS=0
@@ -26,39 +27,22 @@ WARNING_CHECKS=0
# MOTD-style header
# ============================================================================
_M_WIDTH=66
print_header() {
local user="${USER:-root}"
local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}"
local script_name="dotfiles-doctor"
local datetime=$(date '+%a %b %d %H:%M')
if declare -f df_print_header &>/dev/null; then
df_print_header "dotfiles-doctor"
else
local user="${USER:-root}"
local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}"
local datetime=$(date '+%a %b %d %H:%M')
local width=66
local hline="" && for ((i=0; i<width; i++)); do hline+="═"; done
# Colors
local _M_RESET=$'\033[0m'
local _M_BOLD=$'\033[1m'
local _M_DIM=$'\033[2m'
local _M_BLUE=$'\033[38;5;39m'
local _M_GREY=$'\033[38;5;242m'
# Build horizontal line
local hline=""
for ((i=0; i<_M_WIDTH; i++)); do hline+="═"; done
local inner=$((_M_WIDTH - 2))
# Header content
local h_left="${user}@${hostname}"
local h_center="${script_name}"
local h_right="${datetime}"
local h_pad=$(((inner - ${#h_left} - ${#h_center} - ${#h_right}) / 2))
local h_spaces=""
for ((i=0; i<h_pad; i++)); do h_spaces+=" "; done
echo ""
echo -e "${_M_GREY}${hline}${_M_RESET}"
echo -e "${_M_GREY}${_M_RESET} ${_M_BOLD}${_M_BLUE}${h_left}${_M_RESET}${h_spaces}${_M_DIM}${h_center}${h_spaces}${h_right}${_M_RESET} ${_M_GREY}${_M_RESET}"
echo -e "${_M_GREY}${hline}${_M_RESET}"
echo ""
echo ""
echo -e "${DF_GREY}${hline}${DF_NC}"
echo -e "${DF_GREY}${DF_NC} ${DF_BOLD}${DF_LIGHT_BLUE}${user}@${hostname}${DF_NC} ${DF_DIM}dotfiles-doctor${DF_NC} ${datetime} ${DF_GREY}${DF_NC}"
echo -e "${DF_GREY}${hline}${DF_NC}"
echo ""
fi
}
# ============================================================================
@@ -66,25 +50,25 @@ print_header() {
# ============================================================================
print_section() {
echo -e "\n${BLUE}${NC} $1"
echo -e "\n${DF_BLUE}${DF_NC} $1"
}
check_pass() {
((PASSED_CHECKS++))
((TOTAL_CHECKS++))
echo -e " ${GREEN}${NC} $1"
echo -e " ${DF_GREEN}${DF_NC} $1"
}
check_fail() {
((FAILED_CHECKS++))
((TOTAL_CHECKS++))
echo -e " ${RED}${NC} $1"
echo -e " ${DF_RED}${DF_NC} $1"
}
check_warn() {
((WARNING_CHECKS++))
((TOTAL_CHECKS++))
echo -e " ${YELLOW}${NC} $1"
echo -e " ${DF_YELLOW}${DF_NC} $1"
}
# ============================================================================
@@ -314,23 +298,23 @@ check_dotfiles_dir() {
print_summary() {
echo ""
printf "${CYAN}─%.0s${NC}" {1..70}; echo ""
printf "${DF_CYAN}─%.0s${DF_NC}" {1..70}; echo ""
if [[ $FAILED_CHECKS -eq 0 ]]; then
echo -e "${GREEN}${NC} All checks passed ($PASSED_CHECKS/$TOTAL_CHECKS)"
echo -e "${DF_GREEN}${DF_NC} All checks passed ($PASSED_CHECKS/$TOTAL_CHECKS)"
else
echo -e "${RED}${NC} Some checks failed"
echo -e " ${GREEN}Passed:${NC} $PASSED_CHECKS"
echo -e " ${RED}Failed:${NC} $FAILED_CHECKS"
echo -e "${DF_RED}${DF_NC} Some checks failed"
echo -e " ${DF_GREEN}Passed:${DF_NC} $PASSED_CHECKS"
echo -e " ${DF_RED}Failed:${DF_NC} $FAILED_CHECKS"
if [[ $WARNING_CHECKS -gt 0 ]]; then
echo -e " ${YELLOW}Warnings:${NC} $WARNING_CHECKS"
echo -e " ${DF_YELLOW}Warnings:${DF_NC} $WARNING_CHECKS"
fi
fi
echo ""
if [[ $FAILED_CHECKS -gt 0 ]]; then
echo -e "${YELLOW}💡 Tip:${NC} Run 'dotfiles-doctor.sh --fix' to attempt automatic fixes"
echo -e "${DF_YELLOW}💡 Tip:${DF_NC} Run 'dotfiles-doctor.sh --fix' to attempt automatic fixes"
echo ""
return 1
fi

View File

@@ -5,52 +5,36 @@
set -e
# Color codes
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly CYAN='\033[0;36m'
readonly MAGENTA='\033[0;35m'
readonly NC='\033[0m'
readonly DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}"
# Source shared colors
source "$DOTFILES_HOME/zsh/lib/colors.zsh" 2>/dev/null || {
DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m'
DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_MAGENTA=$'\033[0;35m'
DF_NC=$'\033[0m' DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m'
DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m'
}
# ============================================================================
# MOTD-style header
# ============================================================================
_M_WIDTH=66
print_header() {
local user="${USER:-root}"
local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}"
local script_name="dotfiles-stats"
local datetime=$(date '+%a %b %d %H:%M')
if declare -f df_print_header &>/dev/null; then
df_print_header "dotfiles-stats"
else
local user="${USER:-root}"
local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}"
local datetime=$(date '+%a %b %d %H:%M')
local width=66
local hline="" && for ((i=0; i<width; i++)); do hline+="═"; done
# Colors
local _M_RESET=$'\033[0m'
local _M_BOLD=$'\033[1m'
local _M_DIM=$'\033[2m'
local _M_BLUE=$'\033[38;5;39m'
local _M_GREY=$'\033[38;5;242m'
# Build horizontal line
local hline=""
for ((i=0; i<_M_WIDTH; i++)); do hline+="═"; done
local inner=$((_M_WIDTH - 2))
# Header content
local h_left="${user}@${hostname}"
local h_center="${script_name}"
local h_right="${datetime}"
local h_pad=$(((inner - ${#h_left} - ${#h_center} - ${#h_right}) / 2))
local h_spaces=""
for ((i=0; i<h_pad; i++)); do h_spaces+=" "; done
echo ""
echo -e "${_M_GREY}${hline}${_M_RESET}"
echo -e "${_M_GREY}${_M_RESET} ${_M_BOLD}${_M_BLUE}${h_left}${_M_RESET}${h_spaces}${_M_DIM}${h_center}${h_spaces}${h_right}${_M_RESET} ${_M_GREY}${_M_RESET}"
echo -e "${_M_GREY}${hline}${_M_RESET}"
echo ""
echo ""
echo -e "${DF_GREY}${hline}${DF_NC}"
echo -e "${DF_GREY}${DF_NC} ${DF_BOLD}${DF_LIGHT_BLUE}${user}@${hostname}${DF_NC} ${DF_DIM}dotfiles-stats${DF_NC} ${datetime} ${DF_GREY}${DF_NC}"
echo -e "${DF_GREY}${hline}${DF_NC}"
echo ""
fi
}
# ============================================================================
@@ -59,8 +43,8 @@ print_header() {
print_section() {
echo ""
echo -e "${BLUE}${NC} $1"
echo -e "${CYAN}─────────────────────────────────────────────────────────────${NC}"
echo -e "${DF_BLUE}${DF_NC} $1"
echo -e "${DF_CYAN}─────────────────────────────────────────────────────────────${DF_NC}"
}
# Get command history
@@ -83,8 +67,8 @@ show_dashboard() {
local unique=$(get_history | sort | uniq | wc -l)
echo ""
echo -e " ${CYAN}Total Commands:${NC} $total"
echo -e " ${CYAN}Unique Commands:${NC} $unique"
echo -e " ${DF_CYAN}Total Commands:${DF_NC} $total"
echo -e " ${DF_CYAN}Unique Commands:${DF_NC} $unique"
echo ""
print_section "Top 15 Commands"
@@ -93,7 +77,7 @@ show_dashboard() {
local percent=$((count * 100 / total))
local bar_length=$((percent / 5))
local bar=$(printf '█%.0s' $(seq 1 $bar_length))
printf " %-20s ${GREEN}%5d${NC} ${MAGENTA}%3d%%${NC} ${bar}\n" "$cmd" "$count" "$percent"
printf " %-20s ${DF_GREEN}%5d${DF_NC} ${DF_MAGENTA}%3d%%${DF_NC} ${bar}\n" "$cmd" "$count" "$percent"
done
echo ""
@@ -109,7 +93,7 @@ show_top_n() {
get_history | awk '{print $1}' | sort | uniq -c | sort -rn | head -"$n" | \
while read count cmd; do
local percent=$((count * 100 / total))
printf " ${YELLOW}%4d${NC} %-30s ${CYAN}%3d%%${NC}\n" "$count" "$cmd" "$percent"
printf " ${DF_YELLOW}%4d${DF_NC} %-30s ${DF_CYAN}%3d%%${DF_NC}\n" "$count" "$cmd" "$percent"
done
echo ""
@@ -124,7 +108,7 @@ show_suggestions() {
get_history | awk '{print $1}' | sort | uniq -c | sort -rn | head -20 | \
while read count cmd; do
if [[ $count -gt 50 ]]; then
printf " ${YELLOW}Suggestion:${NC} ${GREEN}alias ${cmd:0:2}='$cmd'${NC} (used $count times)\n"
printf " ${DF_YELLOW}Suggestion:${DF_NC} ${DF_GREEN}alias ${cmd:0:2}='$cmd'${DF_NC} (used $count times)\n"
fi
done
@@ -135,22 +119,22 @@ show_breakdown() {
print_section "Command Breakdown"
echo ""
echo -e " ${CYAN}Git Commands:${NC}"
echo -e " ${DF_CYAN}Git Commands:${DF_NC}"
get_history | grep "^git" | wc -l | xargs printf " %d\n"
echo -e " ${CYAN}Navigation (cd):${NC}"
echo -e " ${DF_CYAN}Navigation (cd):${DF_NC}"
get_history | grep "^cd" | wc -l | xargs printf " %d\n"
echo -e " ${CYAN}File Operations (ls):${NC}"
echo -e " ${DF_CYAN}File Operations (ls):${DF_NC}"
get_history | grep "^ls" | wc -l | xargs printf " %d\n"
echo -e " ${CYAN}Package Management (pacman/paru/yay):${NC}"
echo -e " ${DF_CYAN}Package Management (pacman/paru/yay):${DF_NC}"
get_history | grep -E "^(pacman|paru|yay)" | wc -l | xargs printf " %d\n"
echo -e " ${CYAN}Editing (vim/nvim):${NC}"
echo -e " ${DF_CYAN}Editing (vim/nvim):${DF_NC}"
get_history | grep -E "^(vim|nvim)" | wc -l | xargs printf " %d\n"
echo -e " ${CYAN}Dotfiles Commands (dotfiles-):${NC}"
echo -e " ${DF_CYAN}Dotfiles Commands (dotfiles-):${DF_NC}"
get_history | grep "^dotfiles-" | wc -l | xargs printf " %d\n"
echo ""
@@ -161,15 +145,14 @@ show_heatmap() {
echo ""
if [[ -f "$HOME/.zsh_history" ]]; then
# Extract hour from zsh history timestamp
grep "^:" "$HOME/.zsh_history" | awk -F'[: ]' '{print $2}' | \
date -f - "+%H" 2>/dev/null | sort | uniq -c | sort -k2n | while read count hour; do
local bar_length=$((count / 5))
local bar=$(printf '█%.0s' $(seq 1 $bar_length))
printf " ${CYAN}%02d:00${NC} ${MAGENTA}%5d${NC} ${GREEN}${bar}${NC}\n" "$hour" "$count"
printf " ${DF_CYAN}%02d:00${DF_NC} ${DF_MAGENTA}%5d${DF_NC} ${DF_GREEN}${bar}${DF_NC}\n" "$hour" "$count"
done
else
echo " ${YELLOW}${NC} Zsh history file required for hourly breakdown"
echo " ${DF_YELLOW}${DF_NC} Zsh history file required for hourly breakdown"
fi
echo ""
@@ -182,10 +165,10 @@ show_dirs() {
if [[ -f "$HOME/.zsh_history" ]]; then
grep "cd " "$HOME/.zsh_history" | awk '{print $NF}' | sort | uniq -c | \
sort -rn | head -15 | while read count dir; do
printf " ${CYAN}%4d${NC} ${YELLOW}%s${NC}\n" "$count" "$dir"
printf " ${DF_CYAN}%4d${DF_NC} ${DF_YELLOW}%s${DF_NC}\n" "$count" "$dir"
done
else
echo " ${YELLOW}${NC} Zsh history file required"
echo " ${DF_YELLOW}${DF_NC} Zsh history file required"
fi
echo ""
@@ -198,14 +181,14 @@ show_git_breakdown() {
local total=$(get_history | grep "^git" | wc -l)
if [[ $total -eq 0 ]]; then
echo " ${YELLOW}No git commands found${NC}"
echo " ${DF_YELLOW}No git commands found${DF_NC}"
return
fi
get_history | grep "^git " | awk '{print $2}' | sort | uniq -c | sort -rn | \
head -10 | while read count subcmd; do
local percent=$((count * 100 / total))
printf " ${YELLOW}git %-15s${NC} ${CYAN}%4d${NC} (${MAGENTA}%3d%%${NC})\n" \
printf " ${DF_YELLOW}git %-15s${DF_NC} ${DF_CYAN}%4d${DF_NC} (${DF_MAGENTA}%3d%%${DF_NC})\n" \
"$subcmd" "$count" "$percent"
done
@@ -242,7 +225,6 @@ main() {
show_git_breakdown
;;
export)
# Export as JSON
echo "{"
echo " \"total_commands\": $(get_history | wc -l),"
echo " \"unique_commands\": $(get_history | sort | uniq | wc -l),"

View File

@@ -2,57 +2,31 @@
# ============================================================================
# Update Dotfiles Script
# ============================================================================
# Updates dotfiles from the git repository and relinks files
#
# Usage:
# dotfiles-update.sh # Pull and re-run install
# dotfiles-update.sh --skip-deps # Pull and re-run install without deps
# dotfiles-update.sh --pull-only # Only git pull, don't re-run install
#
# Aliases: dfu, dfupdate
# ============================================================================
set -e
# ============================================================================
# Options
# ============================================================================
SKIP_DEPS=true # Default to skipping deps on updates
SKIP_DEPS=true
PULL_ONLY=false
for arg in "$@"; do
case "$arg" in
--skip-deps)
SKIP_DEPS=true
;;
--with-deps)
SKIP_DEPS=false
;;
--pull-only)
PULL_ONLY=true
;;
--skip-deps) SKIP_DEPS=true ;;
--with-deps) SKIP_DEPS=false ;;
--pull-only) PULL_ONLY=true ;;
--help|-h)
echo "Usage: dotfiles-update.sh [OPTIONS]"
echo
echo ""
echo "Options:"
echo " --skip-deps Skip dependency check (default for updates)"
echo " --with-deps Run full dependency check"
echo " --pull-only Only git pull, don't re-run install script"
echo " --help Show this help message"
echo
echo "Aliases:"
echo " dfu, dfupdate Update dotfiles"
echo
exit 0
;;
esac
done
# ============================================================================
# Load Configuration
# ============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DOTFILES_CONF="${SCRIPT_DIR}/../dotfiles.conf"
[[ -f "$DOTFILES_CONF" ]] || DOTFILES_CONF="$HOME/.dotfiles/dotfiles.conf"
@@ -60,78 +34,55 @@ DOTFILES_CONF="${SCRIPT_DIR}/../dotfiles.conf"
if [[ -f "$DOTFILES_CONF" ]]; then
source "$DOTFILES_CONF"
else
# Fallback defaults
DOTFILES_DIR="$HOME/.dotfiles"
DOTFILES_BRANCH="main"
DOTFILES_GITHUB_USER="adlee-was-taken"
DOTFILES_REPO_NAME="dotfiles"
DOTFILES_RAW_URL="https://raw.githubusercontent.com/${DOTFILES_GITHUB_USER}/${DOTFILES_REPO_NAME}/${DOTFILES_BRANCH}"
DOTFILES_RAW_URL="https://raw.githubusercontent.com/adlee-was-taken/dotfiles/main"
fi
# ============================================================================
# Colors
# ============================================================================
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
# Source shared colors
source "$DOTFILES_DIR/zsh/lib/colors.zsh" 2>/dev/null || {
DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_RED=$'\033[0;31m'
DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m'
DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m'
DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m'
}
# ============================================================================
# MOTD-style header
# ============================================================================
_M_WIDTH=66
print_header() {
local user="${USER:-root}"
local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}"
local script_name="dotfiles-update"
local datetime=$(date '+%a %b %d %H:%M')
if declare -f df_print_header &>/dev/null; then
df_print_header "dotfiles-update"
else
local user="${USER:-root}"
local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}"
local datetime=$(date '+%a %b %d %H:%M')
local width=66
local hline="" && for ((i=0; i<width; i++)); do hline+="═"; done
# Colors
local _M_RESET=$'\033[0m'
local _M_BOLD=$'\033[1m'
local _M_DIM=$'\033[2m'
local _M_BLUE=$'\033[38;5;39m'
local _M_GREY=$'\033[38;5;242m'
# Build horizontal line
local hline=""
for ((i=0; i<_M_WIDTH; i++)); do hline+="═"; done
local inner=$((_M_WIDTH - 2))
# Header content
local h_left="${user}@${hostname}"
local h_center="${script_name}"
local h_right="${datetime}"
local h_pad=$(((inner - ${#h_left} - ${#h_center} - ${#h_right}) / 2))
local h_spaces=""
for ((i=0; i<h_pad; i++)); do h_spaces+=" "; done
echo ""
echo -e "${_M_GREY}${hline}${_M_RESET}"
echo -e "${_M_GREY}${_M_RESET} ${_M_BOLD}${_M_BLUE}${h_left}${_M_RESET}${h_spaces}${_M_DIM}${h_center}${h_spaces}${h_right}${_M_RESET} ${_M_GREY}${_M_RESET}"
echo -e "${_M_GREY}${hline}${_M_RESET}"
echo ""
echo ""
echo -e "${DF_GREY}${hline}${DF_NC}"
echo -e "${DF_GREY}${DF_NC} ${DF_BOLD}${DF_LIGHT_BLUE}${user}@${hostname}${DF_NC} ${DF_DIM}dotfiles-update${DF_NC} ${datetime} ${DF_GREY}${DF_NC}"
echo -e "${DF_GREY}${hline}${DF_NC}"
echo ""
fi
}
print_success() {
echo -e "${GREEN}${NC} $1"
echo -e "${DF_GREEN}${DF_NC} $1"
}
print_warning() {
echo -e "${YELLOW}${NC} $1"
echo -e "${DF_YELLOW}${DF_NC} $1"
}
print_error() {
echo -e "${RED}${NC} $1"
echo -e "${DF_RED}${DF_NC} $1"
}
print_step() {
echo -e "${GREEN}==>${NC} $1"
echo -e "${DF_GREEN}==>${DF_NC} $1"
}
# ============================================================================
@@ -178,7 +129,7 @@ if [ $? -eq 0 ]; then
echo
print_success "Update complete!"
echo -e "Reload your shell: ${CYAN}reload${NC} or ${CYAN}source ~/.zshrc${NC}"
echo -e "Reload your shell: ${DF_CYAN}reload${DF_NC} or ${DF_CYAN}source ~/.zshrc${DF_NC}"
else
print_error "Failed to update dotfiles"
exit 1

View File

@@ -5,54 +5,38 @@
set -e
readonly DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}"
readonly VAULT_DIR="${HOME}/.dotfiles/vault"
readonly VAULT_FILE="${VAULT_DIR}/secrets.enc"
# Color codes
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly CYAN='\033[0;36m'
readonly NC='\033[0m'
# Source shared colors
source "$DOTFILES_HOME/zsh/lib/colors.zsh" 2>/dev/null || {
DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m'
DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m'
DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m'
DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m'
}
# ============================================================================
# MOTD-style header
# ============================================================================
_M_WIDTH=66
print_header() {
local user="${USER:-root}"
local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}"
local script_name="dotfiles-vault"
local datetime=$(date '+%a %b %d %H:%M')
if declare -f df_print_header &>/dev/null; then
df_print_header "dotfiles-vault"
else
local user="${USER:-root}"
local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}"
local datetime=$(date '+%a %b %d %H:%M')
local width=66
local hline="" && for ((i=0; i<width; i++)); do hline+="═"; done
# Colors
local _M_RESET=$'\033[0m'
local _M_BOLD=$'\033[1m'
local _M_DIM=$'\033[2m'
local _M_BLUE=$'\033[38;5;39m'
local _M_GREY=$'\033[38;5;242m'
# Build horizontal line
local hline=""
for ((i=0; i<_M_WIDTH; i++)); do hline+="═"; done
local inner=$((_M_WIDTH - 2))
# Header content
local h_left="${user}@${hostname}"
local h_center="${script_name}"
local h_right="${datetime}"
local h_pad=$(((inner - ${#h_left} - ${#h_center} - ${#h_right}) / 2))
local h_spaces=""
for ((i=0; i<h_pad; i++)); do h_spaces+=" "; done
echo ""
echo -e "${_M_GREY}${hline}${_M_RESET}"
echo -e "${_M_GREY}${_M_RESET} ${_M_BOLD}${_M_BLUE}${h_left}${_M_RESET}${h_spaces}${_M_DIM}${h_center}${h_spaces}${h_right}${_M_RESET} ${_M_GREY}${_M_RESET}"
echo -e "${_M_GREY}${hline}${_M_RESET}"
echo ""
echo ""
echo -e "${DF_GREY}${hline}${DF_NC}"
echo -e "${DF_GREY}${DF_NC} ${DF_BOLD}${DF_LIGHT_BLUE}${user}@${hostname}${DF_NC} ${DF_DIM}dotfiles-vault${DF_NC} ${datetime} ${DF_GREY}${DF_NC}"
echo -e "${DF_GREY}${hline}${DF_NC}"
echo ""
fi
}
# ============================================================================
@@ -60,16 +44,16 @@ print_header() {
# ============================================================================
print_success() {
echo -e "${GREEN}${NC} $1"
echo -e "${DF_GREEN}${DF_NC} $1"
}
print_error() {
echo -e "${RED}${NC} $1" >&2
echo -e "${DF_RED}${DF_NC} $1" >&2
}
print_section() {
echo ""
echo -e "${BLUE}${NC} $1"
echo -e "${DF_BLUE}${DF_NC} $1"
}
# ============================================================================
@@ -94,7 +78,6 @@ init_vault() {
chmod 700 "$VAULT_DIR"
if [[ ! -f "$VAULT_FILE" ]]; then
# Create empty encrypted file
echo "{}" | $(get_cipher) > "$VAULT_FILE"
print_success "Vault initialized"
else
@@ -147,24 +130,19 @@ vault_set() {
exit 1
fi
# Get value from stdin if not provided
if [[ -z "$value" ]]; then
read -s -p "Enter value for $key: " value
echo ""
fi
# Decrypt current vault
local current=$(decrypt_vault)
# Add new key-value pair (using jq if available, otherwise simple replacement)
if command -v jq &> /dev/null; then
local updated=$(echo "$current" | jq --arg k "$key" --arg v "$value" '.[$k] = $v')
else
# Simple fallback without jq
local updated="{\"$key\": \"$value\"}"
fi
# Encrypt and save
encrypt_vault "$updated"
print_success "Secret stored: $key"
}
@@ -182,7 +160,6 @@ vault_get() {
if command -v jq &> /dev/null; then
echo "$vault" | jq -r ".\"$key\" // \"\"" | grep -v "^$"
else
# Simple grep fallback
echo "$vault" | grep "\"$key\"" | cut -d'"' -f4
fi
}
@@ -194,12 +171,11 @@ vault_list() {
if command -v jq &> /dev/null; then
echo "$vault" | jq -r 'keys[]' | while read key; do
echo -e " ${CYAN}${NC} $key"
echo -e " ${DF_CYAN}${DF_NC} $key"
done
else
# Simple fallback
echo "$vault" | grep -o '"[^"]*":' | sed 's/"//g' | sed 's/:$//' | while read key; do
echo -e " ${CYAN}${NC} $key"
echo -e " ${DF_CYAN}${DF_NC} $key"
done
fi
@@ -280,23 +256,23 @@ vault_status() {
print_section "Vault Status"
if [[ ! -d "$VAULT_DIR" ]]; then
echo -e " ${YELLOW}${NC} Vault not initialized"
echo -e " ${DF_YELLOW}${DF_NC} Vault not initialized"
return
fi
if [[ ! -f "$VAULT_FILE" ]]; then
echo -e " ${YELLOW}${NC} Vault file not found"
echo -e " ${DF_YELLOW}${DF_NC} Vault file not found"
return
fi
local size=$(du -h "$VAULT_FILE" | cut -f1)
local modified=$(stat -c %y "$VAULT_FILE" 2>/dev/null | cut -d' ' -f1 || stat -f '%Sm' "$VAULT_FILE" 2>/dev/null)
echo -e " ${CYAN}Location:${NC} $VAULT_FILE"
echo -e " ${CYAN}Size:${NC} $size"
echo -e " ${CYAN}Modified:${NC} $modified"
echo -e " ${CYAN}Encryption:${NC} $(get_cipher)"
echo -e " ${CYAN}Permissions:${NC} $(stat -c '%a' $VAULT_FILE 2>/dev/null || stat -f '%a' "$VAULT_FILE")"
echo -e " ${DF_CYAN}Location:${DF_NC} $VAULT_FILE"
echo -e " ${DF_CYAN}Size:${DF_NC} $size"
echo -e " ${DF_CYAN}Modified:${DF_NC} $modified"
echo -e " ${DF_CYAN}Encryption:${DF_NC} $(get_cipher)"
echo -e " ${DF_CYAN}Permissions:${DF_NC} $(stat -c '%a' $VAULT_FILE 2>/dev/null || stat -f '%a' "$VAULT_FILE")"
echo ""
}
@@ -308,7 +284,6 @@ vault_status() {
main() {
print_header
# Initialize vault if not exists
if [[ ! -d "$VAULT_DIR" ]]; then
init_vault
fi

View File

@@ -2,17 +2,8 @@
# ============================================================================
# Dotfiles Version Checker
# ============================================================================
# Shows current and remote version info
#
# Usage:
# dotfiles-version.sh # Show version info
# dotfiles-version.sh --check # Check for updates (exit 1 if behind)
# ============================================================================
# ============================================================================
# Load Configuration
# ============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DOTFILES_CONF="${SCRIPT_DIR}/../dotfiles.conf"
[[ -f "$DOTFILES_CONF" ]] || DOTFILES_CONF="${SCRIPT_DIR}/dotfiles.conf"
@@ -27,82 +18,51 @@ else
DOTFILES_RAW_URL="https://raw.githubusercontent.com/adlee-was-taken/dotfiles/main"
fi
# ============================================================================
# Colors
# ============================================================================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
# ============================================================================
# MOTD-style header
# ============================================================================
_M_WIDTH=66
print_header() {
local user="${USER:-root}"
local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}"
local script_name="dotfiles-version"
local datetime=$(date '+%a %b %d %H:%M')
# Colors
local _M_RESET=$'\033[0m'
local _M_BOLD=$'\033[1m'
local _M_DIM=$'\033[2m'
local _M_BLUE=$'\033[38;5;39m'
local _M_GREY=$'\033[38;5;242m'
# Build horizontal line
local hline=""
for ((i=0; i<_M_WIDTH; i++)); do hline+="═"; done
local inner=$((_M_WIDTH - 2))
# Header content
local h_left="${user}@${hostname}"
local h_center="${script_name}"
local h_right="${datetime}"
local h_pad=$(((inner - ${#h_left} - ${#h_center} - ${#h_right}) / 2))
local h_spaces=""
for ((i=0; i<h_pad; i++)); do h_spaces+=" "; done
echo ""
echo -e "${_M_GREY}${hline}${_M_RESET}"
echo -e "${_M_GREY}${_M_RESET} ${_M_BOLD}${_M_BLUE}${h_left}${_M_RESET}${h_spaces}${_M_DIM}${h_center}${h_spaces}${h_right}${_M_RESET} ${_M_GREY}${_M_RESET}"
echo -e "${_M_GREY}${hline}${_M_RESET}"
echo ""
# Source shared colors
source "$DOTFILES_DIR/zsh/lib/colors.zsh" 2>/dev/null || {
DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_CYAN=$'\033[0;36m'
DF_NC=$'\033[0m' DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m'
DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m'
}
# ============================================================================
# Options
# ============================================================================
CHECK_ONLY=false
for arg in "$@"; do
case "$arg" in
--check|-c)
CHECK_ONLY=true
;;
--check|-c) CHECK_ONLY=true ;;
--help|-h)
echo "Usage: dotfiles-version.sh [OPTIONS]"
echo
echo ""
echo "Options:"
echo " --check Only check for updates (exit 1 if behind)"
echo " --help Show this help message"
echo
echo "Aliases:"
echo " dfv, dfversion Show version info"
echo
exit 0
;;
esac
done
# ============================================================================
# MOTD-style header
# ============================================================================
print_header() {
if declare -f df_print_header &>/dev/null; then
df_print_header "dotfiles-version"
else
local user="${USER:-root}"
local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}"
local datetime=$(date '+%a %b %d %H:%M')
local width=66
local hline="" && for ((i=0; i<width; i++)); do hline+="═"; done
echo ""
echo -e "${DF_GREY}${hline}${DF_NC}"
echo -e "${DF_GREY}${DF_NC} ${DF_BOLD}${DF_LIGHT_BLUE}${user}@${hostname}${DF_NC} ${DF_DIM}dotfiles-version${DF_NC} ${datetime} ${DF_GREY}${DF_NC}"
echo -e "${DF_GREY}${hline}${DF_NC}"
echo ""
fi
}
# ============================================================================
# Functions
# ============================================================================
@@ -132,7 +92,6 @@ get_local_date() {
}
get_remote_version() {
# Try to get version from remote dotfiles.conf
local remote_conf=$(curl -fsSL "${DOTFILES_RAW_URL}/dotfiles.conf" 2>/dev/null)
if [[ -n "$remote_conf" ]]; then
echo "$remote_conf" | grep -oP 'DOTFILES_VERSION="\K[^"]+' || echo "unknown"
@@ -176,7 +135,6 @@ compare_versions() {
if [[ "$local_v" == "$remote_v" ]]; then
echo "current"
else
# Simple semver comparison
local local_parts=(${local_v//./ })
local remote_parts=(${remote_v//./ })
@@ -220,44 +178,44 @@ main() {
print_header
echo -e "${CYAN}Local:${NC}"
echo -e " Version: ${GREEN}${local_version}${NC}"
echo -e "${DF_CYAN}Local:${DF_NC}"
echo -e " Version: ${DF_GREEN}${local_version}${DF_NC}"
echo -e " Commit: ${local_commit}"
echo -e " Date: ${local_date}"
echo -e " Path: ${DOTFILES_DIR}"
echo
echo -e "${CYAN}Remote:${NC}"
echo -e "${DF_CYAN}Remote:${DF_NC}"
echo -e " Version: ${remote_version}"
echo -e " Commit: ${remote_commit}"
echo -e " Branch: ${DOTFILES_BRANCH}"
echo
echo -e "${CYAN}Status:${NC}"
echo -e "${DF_CYAN}Status:${DF_NC}"
case "$version_status" in
current)
echo -e " Version: ${GREEN}✓ Up to date${NC}"
echo -e " Version: ${DF_GREEN}✓ Up to date${DF_NC}"
;;
behind)
echo -e " Version: ${YELLOW}⚠ New version available: ${remote_version}${NC}"
echo -e " Version: ${DF_YELLOW}⚠ New version available: ${remote_version}${DF_NC}"
;;
ahead)
echo -e " Version: ${CYAN} Local is ahead of remote${NC}"
echo -e " Version: ${DF_CYAN} Local is ahead of remote${DF_NC}"
;;
*)
echo -e " Version: ${YELLOW}? Cannot determine${NC}"
echo -e " Version: ${DF_YELLOW}? Cannot determine${DF_NC}"
;;
esac
if [[ "$commits_behind" -gt 0 ]]; then
echo -e " Commits: ${YELLOW}${commits_behind} commit(s) behind${NC}"
echo -e " Commits: ${DF_YELLOW}${commits_behind} commit(s) behind${DF_NC}"
echo
echo -e "${YELLOW}To update:${NC}"
echo -e "${DF_YELLOW}To update:${DF_NC}"
echo " dfu # Alias"
echo " dotfiles-update.sh # Full command"
elif [[ "$commits_behind" == "0" ]]; then
echo -e " Commits: ${GREEN}✓ Up to date${NC}"
echo -e " Commits: ${DF_GREEN}✓ Up to date${DF_NC}"
fi
echo

View File

@@ -12,24 +12,20 @@
# Only run in interactive shells
[[ -o interactive ]] || return 0
# ============================================================================
# MOTD Width, adjust if needed.
# ============================================================================
_M_WIDTH=66
# Source shared colors (with fallback)
source "${0:A:h}/../lib/colors.zsh" 2>/dev/null || \
source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null || {
typeset -g DF_RESET=$'\033[0m' DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m'
typeset -g DF_BLUE=$'\033[38;5;39m' DF_CYAN=$'\033[38;5;51m'
typeset -g DF_GREEN=$'\033[38;5;82m' DF_YELLOW=$'\033[38;5;220m'
typeset -g DF_GREY=$'\033[38;5;242m' DF_NC=$'\033[0m'
}
# ============================================================================
# Colors (ANSI escape codes)
# MOTD Width
# ============================================================================
_M_RESET=$'\033[0m'
_M_BOLD=$'\033[1m'
_M_DIM=$'\033[2m'
_M_BLUE=$'\033[38;5;39m'
_M_CYAN=$'\033[38;5;51m'
_M_GREEN=$'\033[38;5;82m'
_M_YELLOW=$'\033[38;5;220m'
_M_GREY=$'\033[38;5;242m'
typeset -g _M_WIDTH=66
# ============================================================================
# Info Gathering
@@ -64,7 +60,6 @@ _motd_disk() {
# Box Drawing - Fixed Width
# ============================================================================
_motd_line() {
local char="$1"
local i
@@ -76,7 +71,6 @@ _motd_line() {
}
_motd_pad() {
# Pad a plain string to exact width
local str="$1"
local width="$2"
local len=${#str}
@@ -101,14 +95,14 @@ show_motd() {
local load=$(_motd_load)
local mem=$(_motd_mem)
local disk=$(_motd_disk)
local local_ip=$(hostname -i | awk -F" " '{print $1}')
local local_ip=$(hostname -i 2>/dev/null | awk -F" " '{print $1}' || echo "N/A")
local hline=$(_motd_line '═')
local inner=$((_M_WIDTH - 2))
echo ""
# Top border
echo "${_M_GREY}${hline}${_M_RESET}"
echo "${DF_GREY}${hline}${DF_NC}"
# Header: hostname + datetime
local h_left="${hostname}"
@@ -117,21 +111,17 @@ show_motd() {
local h_pad=$(((inner - ${#h_left} - ${#h_center} - ${#h_right}) / 2 ))
local h_spaces=""
for ((i=0; i<h_pad; i++)); do h_spaces+=" "; done
echo "${_M_GREY}${_M_RESET} ${_M_BOLD}${_M_BLUE}${h_left}${_M_RESET}${h_spaces}${_M_YELLOW}${h_center}${h_spaces}${_M_RESET}${_M_BOLD}${h_right}${_M_RESET}${_M_GREY}${_M_RESET}"
echo "${DF_GREY}${DF_NC} ${DF_BOLD}${DF_BLUE}${h_left}${DF_NC}${h_spaces}${DF_YELLOW}${h_center}${h_spaces}${DF_NC}${DF_BOLD}${h_right}${DF_NC}${DF_GREY}${DF_NC}"
# Separator
echo "${_M_GREY}${hline}${_M_RESET}"
echo "${DF_GREY}${hline}${DF_NC}"
# Stats line - build with exact spacing
local s1="${_M_YELLOW}▲ up:${_M_RESET}${uptime}"
local s2="${_M_CYAN}◆ load:${_M_RESET}${load}"
local s3="${_M_GREEN}◇ mem:${_M_RESET}${mem}"
local s4="${_M_BLUE}${_M_RESET} ${disk}"
echo "${_M_GREY}${_M_DIM}${_M_RESET}${s1}${_M_GREY}${_M_DIM}〙⎯〘${s2}${_M_GREY}${_M_DIM}〙⎯〘${s3}${_M_GREY}${_M_DIM}〙⎯〘${s4}${_M_GREY}${_M_DIM}${_M_RESET}"
## Bottom border
#echo "${_M_GREY}╘${hline}𜲂${_M_RESET}"
# Stats line
local s1="${DF_YELLOW}▲ up:${DF_NC}${uptime}"
local s2="${DF_CYAN}◆ load:${DF_NC}${load}"
local s3="${DF_GREEN}◇ mem:${DF_NC}${mem}"
local s4="${DF_BLUE}${DF_NC} ${disk}"
echo "${DF_GREY}${DF_DIM}${DF_NC}${s1}${DF_GREY}${DF_DIM}〙⎯〘${s2}${DF_GREY}${DF_DIM}〙⎯〘${s3}${DF_GREY}${DF_DIM}〙⎯〘${s4}${DF_GREY}${DF_DIM}${DF_NC}"
echo ""
}
@@ -148,7 +138,7 @@ show_motd_mini() {
local uptime=$(_motd_uptime)
local mem=$(_motd_mem)
echo "${_M_DIM}──${_M_RESET} ${_M_BOLD}${hostname}${_M_RESET} ${_M_DIM}${_M_RESET} up:${uptime} ${_M_DIM}${_M_RESET} mem:${mem} ${_M_DIM}──${_M_RESET}"
echo "${DF_DIM}──${DF_NC} ${DF_BOLD}${hostname}${DF_NC} ${DF_DIM}${DF_NC} up:${uptime} ${DF_DIM}${DF_NC} mem:${mem} ${DF_DIM}──${DF_NC}"
}
# ============================================================================

View File

@@ -9,24 +9,15 @@
# pw otp <item> # Get OTP/TOTP code
# pw search <query> # Search items
# pw copy <item> # Copy password to clipboard
#
# Supported: LastPass (lpass)
#
# Add to .zshrc:
# source ~/.dotfiles/zsh/functions/password-manager.zsh
# ============================================================================
# ============================================================================
# Configuration
# ============================================================================
# Colors
typeset -g PW_GREEN=$'\033[0;32m'
typeset -g PW_BLUE=$'\033[0;34m'
typeset -g PW_YELLOW=$'\033[1;33m'
typeset -g PW_CYAN=$'\033[0;36m'
typeset -g PW_RED=$'\033[0;31m'
typeset -g PW_NC=$'\033[0m'
# Source shared colors (with fallback)
source "${0:A:h}/../lib/colors.zsh" 2>/dev/null || \
source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null || {
typeset -g DF_GREEN=$'\033[0;32m' DF_BLUE=$'\033[0;34m'
typeset -g DF_YELLOW=$'\033[1;33m' DF_CYAN=$'\033[0;36m'
typeset -g DF_RED=$'\033[0;31m' DF_NC=$'\033[0m'
}
# ============================================================================
# LastPass Functions
@@ -78,9 +69,8 @@ pw() {
local cmd="${1:-help}"
shift
# Check if lastpass is installed
if ! command -v lpass &>/dev/null; then
echo -e "${PW_RED}${PW_NC} LastPass CLI (lpass) not installed"
echo -e "${DF_RED}${DF_NC} LastPass CLI (lpass) not installed"
echo "Install with: yay -S lastpass-cli"
return 1
fi
@@ -133,7 +123,7 @@ pw() {
;;
help|--help|-h|*)
echo -e "${PW_BLUE}Password Manager CLI (LastPass)${PW_NC}"
echo -e "${DF_BLUE}Password Manager CLI (LastPass)${DF_NC}"
echo
echo "Usage: pw <command> [args]"
echo
@@ -174,7 +164,6 @@ alias pws='pw search'
# ============================================================================
if command -v fzf &>/dev/null; then
# Interactive password selection and copy
pwf() {
if ! command -v lpass &>/dev/null; then
echo "LastPass CLI not installed"
@@ -188,7 +177,6 @@ if command -v fzf &>/dev/null; then
fi
}
# Interactive OTP selection and copy
pwof() {
if ! command -v lpass &>/dev/null; then
echo "LastPass CLI not installed"

View File

@@ -8,164 +8,73 @@
# - Suggests existing aliases for frequently typed commands
# - "Did you mean?" for unknown commands
# - Package installation suggestions for missing commands
#
# Add to .zshrc:
# source ~/.dotfiles/zsh/functions/smart-suggest.zsh
# ============================================================================
# Source shared colors (with fallback)
source "${0:A:h}/../lib/colors.zsh" 2>/dev/null || \
source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null || {
typeset -g DF_CYAN=$'\033[0;36m' DF_YELLOW=$'\033[1;33m'
typeset -g DF_GREEN=$'\033[0;32m' DF_RED=$'\033[0;31m'
typeset -g DF_DIM=$'\033[2m' DF_NC=$'\033[0m'
}
# ============================================================================
# Configuration
# ============================================================================
# Enable/disable features
typeset -g SMART_SUGGEST_ENABLED=true
typeset -g SMART_SUGGEST_TYPOS=true
typeset -g SMART_SUGGEST_ALIASES=true
typeset -g SMART_SUGGEST_PACKAGES=true
typeset -g SMART_SUGGEST_HISTORY=true
# Tracking file for alias suggestions
typeset -g SMART_SUGGEST_TRACK_FILE="$HOME/.cache/smart-suggest-track"
# Colors
typeset -g SS_CYAN=$'\033[0;36m'
typeset -g SS_YELLOW=$'\033[1;33m'
typeset -g SS_GREEN=$'\033[0;32m'
typeset -g SS_RED=$'\033[0;31m'
typeset -g SS_DIM=$'\033[2m'
typeset -g SS_NC=$'\033[0m'
# ============================================================================
# Common Typo Database
# ============================================================================
typeset -gA TYPO_CORRECTIONS=(
# Git typos
[gti]="git"
[gitt]="git"
[got]="git"
[gut]="git"
[gi]="git"
[giit]="git"
[ggit]="git"
[gitst]="git st"
[gits]="git s"
[gitl]="git l"
[gitd]="git d"
[gitp]="git p"
[psuh]="push"
[psull]="pull"
[pul]="pull"
[puhs]="push"
[stauts]="status"
[statis]="status"
[statuus]="status"
[comit]="commit"
[commti]="commit"
[commt]="commit"
[chekcout]="checkout"
[chekout]="checkout"
[checkou]="checkout"
[branhc]="branch"
[barnch]="branch"
[bracnh]="branch"
[marge]="merge"
[merg]="merge"
[stsh]="stash"
[stahs]="stash"
[gti]="git" [gitt]="git" [got]="git" [gut]="git" [gi]="git"
[giit]="git" [ggit]="git" [gitst]="git st" [gits]="git s"
[gitl]="git l" [gitd]="git d" [gitp]="git p"
[psuh]="push" [psull]="pull" [pul]="pull" [puhs]="push"
[stauts]="status" [statis]="status" [statuus]="status"
[comit]="commit" [commti]="commit" [commt]="commit"
[chekcout]="checkout" [chekout]="checkout" [checkou]="checkout"
[branhc]="branch" [barnch]="branch" [bracnh]="branch"
[marge]="merge" [merg]="merge" [stsh]="stash" [stahs]="stash"
# Docker typos
[dokcer]="docker"
[doker]="docker"
[docekr]="docker"
[dcoker]="docker"
[dockr]="docker"
[docke]="docker"
[docker-compoes]="docker-compose"
[docker-compsoe]="docker-compose"
[dokcer]="docker" [doker]="docker" [docekr]="docker"
[dcoker]="docker" [dockr]="docker" [docke]="docker"
[docker-compoes]="docker-compose" [docker-compsoe]="docker-compose"
[dokcer-compose]="docker-compose"
# Common command typos
[sl]="ls"
[l]="ls"
[sls]="ls"
[lss]="ls"
[cta]="cat"
[catt]="cat"
[caat]="cat"
[grpe]="grep"
[gerp]="grep"
[gre]="grep"
[grepp]="grep"
[mkdri]="mkdir"
[mkdr]="mkdir"
[mdkir]="mkdir"
[mdir]="mkdir"
[rn]="rm"
[rmm]="rm"
[chmdo]="chmod"
[chomd]="chmod"
[chonw]="chown"
[cown]="chown"
[tarr]="tar"
[tart]="tar"
[wegt]="wget"
[wgte]="wget"
[weget]="wget"
[crul]="curl"
[crul]="curl"
[curll]="curl"
[pytohn]="python"
[pyhton]="python"
[pythn]="python"
[pyton]="python"
[pthon]="python"
[pytho]="python"
[ndoe]="node"
[noed]="node"
[noode]="node"
[npn]="npm"
[nmpm]="npm"
[nppm]="npm"
[yran]="yarn"
[yaarn]="yarn"
[yanr]="yarn"
[suod]="sudo"
[sudi]="sudo"
[sduo]="sudo"
[sudoo]="sudo"
[sssh]="ssh"
[shh]="ssh"
[sssh]="ssh"
[scpp]="scp"
[spcp]="scp"
[vmi]="vim"
[imv]="vim"
[viim]="vim"
[cde]="code"
[cdoe]="code"
[cod]="code"
[clera]="clear"
[cler]="clear"
[claer]="clear"
[ecoh]="echo"
[ehco]="echo"
[echoo]="echo"
[exti]="exit"
[ext]="exit"
[exitt]="exit"
[eixt]="exit"
[histroy]="history"
[hisotry]="history"
[hsitory]="history"
[histrory]="history"
[maek]="make"
[mkae]="make"
[amke]="make"
[makee]="make"
[ccd]="cd"
[cdd]="cd"
[ccd]="cd"
[sl]="ls" [l]="ls" [sls]="ls" [lss]="ls"
[cta]="cat" [catt]="cat" [caat]="cat"
[grpe]="grep" [gerp]="grep" [gre]="grep" [grepp]="grep"
[mkdri]="mkdir" [mkdr]="mkdir" [mdkir]="mkdir" [mdir]="mkdir"
[rn]="rm" [rmm]="rm" [chmdo]="chmod" [chomd]="chmod"
[chonw]="chown" [cown]="chown" [tarr]="tar" [tart]="tar"
[wegt]="wget" [wgte]="wget" [weget]="wget"
[crul]="curl" [curll]="curl"
[pytohn]="python" [pyhton]="python" [pythn]="python"
[pyton]="python" [pthon]="python" [pytho]="python"
[ndoe]="node" [noed]="node" [noode]="node"
[npn]="npm" [nmpm]="npm" [nppm]="npm"
[yran]="yarn" [yaarn]="yarn" [yanr]="yarn"
[suod]="sudo" [sudi]="sudo" [sduo]="sudo" [sudoo]="sudo"
[sssh]="ssh" [shh]="ssh" [scpp]="scp" [spcp]="scp"
[vmi]="vim" [imv]="vim" [viim]="vim"
[cde]="code" [cdoe]="code" [cod]="code"
[clera]="clear" [cler]="clear" [claer]="clear"
[ecoh]="echo" [ehco]="echo" [echoo]="echo"
[exti]="exit" [ext]="exit" [exitt]="exit" [eixt]="exit"
[histroy]="history" [hisotry]="history" [hsitory]="history"
[histrory]="history" [maek]="make" [mkae]="make"
[amke]="make" [makee]="make" [ccd]="cd" [cdd]="cd"
)
# ============================================================================
@@ -173,46 +82,25 @@ typeset -gA TYPO_CORRECTIONS=(
# ============================================================================
_ss_get_package_manager() {
if command -v apt-get &>/dev/null; then
echo "apt"
elif command -v dnf &>/dev/null; then
echo "dnf"
elif command -v pacman &>/dev/null; then
echo "pacman"
elif command -v brew &>/dev/null; then
echo "brew"
else
echo ""
if command -v apt-get &>/dev/null; then echo "apt"
elif command -v dnf &>/dev/null; then echo "dnf"
elif command -v pacman &>/dev/null; then echo "pacman"
elif command -v brew &>/dev/null; then echo "brew"
else echo ""
fi
}
# Common commands and their packages
typeset -gA COMMAND_PACKAGES=(
[htop]="htop"
[tree]="tree"
[jq]="jq"
[fd]="fd-find:apt fd:pacman fd:brew"
[rg]="ripgrep"
[bat]="bat"
[eza]="eza"
[exa]="exa"
[fzf]="fzf"
[tldr]="tldr"
[ncdu]="ncdu"
[duf]="duf"
[dust]="dust"
[procs]="procs"
[bottom]="bottom"
[btm]="bottom"
[lazygit]="lazygit"
[lazydocker]="lazydocker"
[neofetch]="neofetch"
[fastfetch]="fastfetch"
[httpie]="httpie"
[http]="httpie"
[htop]="htop" [tree]="tree" [jq]="jq"
[fd]="fd-find:apt fd:pacman fd:brew" [rg]="ripgrep"
[bat]="bat" [eza]="eza" [exa]="exa" [fzf]="fzf"
[tldr]="tldr" [ncdu]="ncdu" [duf]="duf" [dust]="dust"
[procs]="procs" [bottom]="bottom" [btm]="bottom"
[lazygit]="lazygit" [lazydocker]="lazydocker"
[neofetch]="neofetch" [fastfetch]="fastfetch"
[httpie]="httpie" [http]="httpie"
[delta]="git-delta:apt delta:pacman git-delta:brew"
[glow]="glow"
[navi]="navi"
[glow]="glow" [navi]="navi"
)
_ss_suggest_package() {
@@ -226,9 +114,7 @@ _ss_suggest_package() {
local pkg=""
# Check for PM-specific package name
if [[ "$pkg_info" == *":"* ]]; then
# Format: "pkg1:pm1 pkg2:pm2"
for entry in ${(s: :)pkg_info}; do
local p="${entry%%:*}"
local m="${entry##*:}"
@@ -237,7 +123,6 @@ _ss_suggest_package() {
break
fi
done
# Fallback to first package
[[ -z "$pkg" ]] && pkg="${${(s: :)pkg_info}[1]%%:*}"
else
pkg="$pkg_info"
@@ -264,12 +149,11 @@ _ss_track_command() {
[[ "$SMART_SUGGEST_ALIASES" != true ]] && return
local cmd="$1"
[[ ${#cmd} -lt 8 ]] && return # Skip short commands
[[ ${#cmd} -lt 8 ]] && return
mkdir -p "$(dirname "$SMART_SUGGEST_TRACK_FILE")"
echo "$cmd" >> "$SMART_SUGGEST_TRACK_FILE"
# Periodically check for alias suggestions
local count=$(grep -Fc "$cmd" "$SMART_SUGGEST_TRACK_FILE" 2>/dev/null || echo 0)
if [[ $count -ge 10 && $((count % 10)) -eq 0 ]]; then
@@ -281,23 +165,21 @@ _ss_suggest_alias_for() {
local cmd="$1"
local count="$2"
# Check if an alias already exists for this command
local existing=$(alias | grep -F "='$cmd'" | head -1 | cut -d= -f1)
if [[ -n "$existing" ]]; then
echo
echo -e "${SS_CYAN}💡 Tip:${SS_NC} You've typed '${SS_YELLOW}$cmd${SS_NC}' $count times"
echo -e " You already have an alias: ${SS_GREEN}$existing${SS_NC}"
echo -e "${DF_CYAN}💡 Tip:${DF_NC} You've typed '${DF_YELLOW}$cmd${DF_NC}' $count times"
echo -e " You already have an alias: ${DF_GREEN}$existing${DF_NC}"
else
# Generate suggested alias name
local suggested=$(echo "$cmd" | awk '{
for(i=1; i<=NF && i<=3; i++)
printf substr($i,1,1)
}')
echo
echo -e "${SS_CYAN}💡 Tip:${SS_NC} You've typed '${SS_YELLOW}$cmd${SS_NC}' $count times"
echo -e " Consider adding: ${SS_GREEN}alias $suggested='$cmd'${SS_NC}"
echo -e "${DF_CYAN}💡 Tip:${DF_NC} You've typed '${DF_YELLOW}$cmd${DF_NC}' $count times"
echo -e " Consider adding: ${DF_GREEN}alias $suggested='$cmd'${DF_NC}"
fi
}
@@ -315,34 +197,31 @@ command_not_found_handler() {
return 127
}
echo -e "${SS_RED}${SS_NC} Command not found: ${SS_YELLOW}$cmd${SS_NC}"
echo -e "${DF_RED}${DF_NC} Command not found: ${DF_YELLOW}$cmd${DF_NC}"
local suggestion_made=false
# Check for typo
if [[ "$SMART_SUGGEST_TYPOS" == true ]]; then
local correction="${TYPO_CORRECTIONS[$cmd]}"
if [[ -n "$correction" ]]; then
echo -e "${SS_CYAN}${SS_NC} Did you mean: ${SS_GREEN}$correction${SS_NC}?"
echo -e " ${SS_DIM}Run: $correction $args${SS_NC}"
echo -e "${DF_CYAN}${DF_NC} Did you mean: ${DF_GREEN}$correction${DF_NC}?"
echo -e " ${DF_DIM}Run: $correction $args${DF_NC}"
suggestion_made=true
fi
fi
# Check for similar commands
if [[ "$suggestion_made" != true ]]; then
local similar=$(compgen -c 2>/dev/null | grep -i "^${cmd:0:3}" | head -3 | tr '\n' ', ' | sed 's/,$//')
if [[ -n "$similar" ]]; then
echo -e "${SS_CYAN}${SS_NC} Similar commands: ${SS_GREEN}$similar${SS_NC}"
echo -e "${DF_CYAN}${DF_NC} Similar commands: ${DF_GREEN}$similar${DF_NC}"
suggestion_made=true
fi
fi
# Suggest package installation
if [[ "$SMART_SUGGEST_PACKAGES" == true ]]; then
local install_cmd=$(_ss_suggest_package "$cmd")
if [[ -n "$install_cmd" ]]; then
echo -e "${SS_CYAN}${SS_NC} To install: ${SS_GREEN}$install_cmd${SS_NC}"
echo -e "${DF_CYAN}${DF_NC} To install: ${DF_GREEN}$install_cmd${DF_NC}"
suggestion_made=true
fi
fi
@@ -351,42 +230,30 @@ command_not_found_handler() {
}
# ============================================================================
# Pre-exec Hook for Tracking
# Hooks
# ============================================================================
_ss_preexec_hook() {
local cmd="$1"
# Extract just the command (first word)
local first_word="${cmd%% *}"
# Track full commands for alias suggestions
_ss_track_command "$cmd"
}
# ============================================================================
# Post-command Hook for Suggestions
# ============================================================================
_ss_precmd_hook() {
local exit_code=$?
# Only suggest if last command failed
[[ $exit_code -eq 0 ]] && return
# Get last command
local last_cmd=$(fc -ln -1 2>/dev/null | sed 's/^[[:space:]]*//')
[[ -z "$last_cmd" ]] && return
local first_word="${last_cmd%% *}"
# Check if it's a git command with typo
if [[ "$first_word" == "git" && $exit_code -ne 0 ]]; then
local git_subcmd=$(echo "$last_cmd" | awk '{print $2}')
local correction="${TYPO_CORRECTIONS[$git_subcmd]}"
if [[ -n "$correction" ]]; then
echo -e "${SS_CYAN}${SS_NC} Did you mean: ${SS_GREEN}git $correction${SS_NC}?"
echo -e "${DF_CYAN}${DF_NC} Did you mean: ${DF_GREEN}git $correction${DF_NC}?"
fi
fi
}
@@ -395,18 +262,15 @@ _ss_precmd_hook() {
# Quick Fix Function
# ============================================================================
# Run the suggested correction
# Usage: !! <correction> or just press up and edit
fuck() {
local last_cmd=$(fc -ln -1 2>/dev/null | sed 's/^[[:space:]]*//')
local first_word="${last_cmd%% *}"
# Check for typo correction
local correction="${TYPO_CORRECTIONS[$first_word]}"
if [[ -n "$correction" ]]; then
local fixed_cmd="${last_cmd/$first_word/$correction}"
echo -e "${SS_GREEN}Running:${SS_NC} $fixed_cmd"
echo -e "${DF_GREEN}Running:${DF_NC} $fixed_cmd"
eval "$fixed_cmd"
else
echo "No automatic fix available"
@@ -419,28 +283,9 @@ fuck() {
# ============================================================================
_ss_setup() {
# Add preexec hook
autoload -Uz add-zsh-hook
add-zsh-hook preexec _ss_preexec_hook
add-zsh-hook precmd _ss_precmd_hook
}
# Initialize
[[ "$SMART_SUGGEST_ENABLED" == true ]] && _ss_setup
# ============================================================================
# Usage Examples (commented)
# ============================================================================
# $ gti status
# ✗ Command not found: gti
# → Did you mean: git?
# Run: git status
# $ dokcer ps
# ✗ Command not found: dokcer
# → Did you mean: docker?
# After typing "docker-compose up -d" 10 times:
# 💡 Tip: You've typed 'docker-compose up -d' 10 times
# Consider adding: alias dcu='docker-compose up -d'

View File

@@ -1,14 +1,13 @@
# ============================================================================
# Snapper Snapshot Functions for CachyOS/Arch with limine-snapper-sync
# ============================================================================
# Add these functions to your ~/.zshrc or ~/.dotfiles/zsh/.zshrc
# Colors for output
SNAP_GREEN='\033[0;32m'
SNAP_YELLOW='\033[1;33m'
SNAP_RED='\033[0;31m'
SNAP_BLUE='\033[0;34m'
SNAP_NC='\033[0m' # No Color
# Source shared colors (with fallback)
source "${0:A:h}/../lib/colors.zsh" 2>/dev/null || \
source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null || {
typeset -g DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m'
typeset -g DF_RED=$'\033[0;31m' DF_BLUE=$'\033[0;34m' DF_NC=$'\033[0m'
}
# ============================================================================
# Main Snapshot Function with Limine Validation
@@ -19,110 +18,81 @@ snap-create() {
local snap_config="root"
local limine_conf="/boot/limine.conf"
# Print header
echo -e "\n${SNAP_BLUE}╔════════════════════════════════════════════════════════════╗${SNAP_NC}"
echo -e "${SNAP_BLUE}${SNAP_NC} Snapper Snapshot Creation & Validation ${SNAP_BLUE}${SNAP_NC}"
echo -e "${SNAP_BLUE}╚════════════════════════════════════════════════════════════╝${SNAP_NC}\n"
echo -e "\n${DF_BLUE}╔════════════════════════════════════════════════════════════╗${DF_NC}"
echo -e "${DF_BLUE}${DF_NC} Snapper Snapshot Creation & Validation ${DF_BLUE}${DF_NC}"
echo -e "${DF_BLUE}╚════════════════════════════════════════════════════════════╝${DF_NC}\n"
# Check if description was provided
if [[ -z "$description" ]]; then
echo -e "${SNAP_YELLOW}${SNAP_NC} No description provided"
echo -e "${DF_YELLOW}${DF_NC} No description provided"
echo -n "Enter snapshot description: "
read description
if [[ -z "$description" ]]; then
echo -e "${SNAP_RED}${SNAP_NC} Description required. Aborting."
return 1
fi
[[ -z "$description" ]] && { echo -e "${DF_RED}${DF_NC} Description required. Aborting."; return 1; }
fi
# Check if limine.conf exists
if [[ ! -f "$limine_conf" ]]; then
echo -e "${SNAP_RED}${SNAP_NC} Limine config not found: $limine_conf"
return 1
fi
[[ ! -f "$limine_conf" ]] && { echo -e "${DF_RED}${DF_NC} Limine config not found: $limine_conf"; return 1; }
# Get limine.conf state before snapshot
echo -e "${SNAP_BLUE}==>${SNAP_NC} Checking limine.conf state before snapshot"
echo -e "${DF_BLUE}==>${DF_NC} Checking limine.conf state before snapshot"
local before_checksum=$(sudo md5sum "$limine_conf" | awk '{print $1}')
local before_entries=$(sudo grep -cP "^\\s*///\\d+\\s*│" "$limine_conf" || echo "0")
echo -e "${SNAP_GREEN}${SNAP_NC} Before: $before_entries snapshot entries"
echo -e "${SNAP_GREEN}${SNAP_NC} Before checksum: $before_checksum"
echo -e "${DF_GREEN}${DF_NC} Before: $before_entries snapshot entries"
echo -e "${DF_GREEN}${DF_NC} Before checksum: $before_checksum"
# Create the snapshot
echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Creating snapshot: \"$description\""
echo -e "\n${DF_BLUE}==>${DF_NC} Creating snapshot: \"$description\""
local snapshot_num=$(sudo snapper -c "$snap_config" create \
--description "$description" \
--print-number)
local snapshot_num=$(sudo snapper -c "$snap_config" create --description "$description" --print-number)
if [[ -z "$snapshot_num" ]]; then
echo -e "${SNAP_RED}${SNAP_NC} Failed to create snapshot"
return 1
fi
[[ -z "$snapshot_num" ]] && { echo -e "${DF_RED}${DF_NC} Failed to create snapshot"; return 1; }
echo -e "${SNAP_GREEN}${SNAP_NC} Snapshot created: #$snapshot_num"
echo -e "${DF_GREEN}${DF_NC} Snapshot created: #$snapshot_num"
# Trigger limine-snapper-sync service
echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Triggering limine-snapper-sync service..."
echo -e "\n${DF_BLUE}==>${DF_NC} Triggering limine-snapper-sync service..."
if sudo systemctl start limine-snapper-sync.service; then
echo -e "${SNAP_GREEN}${SNAP_NC} Service triggered successfully"
echo -e "${DF_GREEN}${DF_NC} Service triggered successfully"
else
echo -e "${SNAP_YELLOW}${SNAP_NC} Failed to trigger service (may run automatically)"
echo -e "${DF_YELLOW}${DF_NC} Failed to trigger service (may run automatically)"
fi
# Wait a moment for the service to complete
echo -e "${SNAP_BLUE}==>${SNAP_NC} Waiting for limine-snapper-sync to update limine.conf..."
echo -e "${DF_BLUE}==>${DF_NC} Waiting for limine-snapper-sync to update limine.conf..."
sleep 2
# Get limine.conf state after snapshot
echo -e "${SNAP_BLUE}==>${SNAP_NC} Validating limine.conf update"
echo -e "${DF_BLUE}==>${DF_NC} Validating limine.conf update"
local after_checksum=$(sudo md5sum "$limine_conf" | awk '{print $1}')
local after_entries=$(sudo grep -cP "^\\s*///\\d+\\s*│" "$limine_conf" || echo "0")
# Validate the update
local validation_passed=true
if [[ "$before_checksum" == "$after_checksum" ]]; then
echo -e "${SNAP_RED}${SNAP_NC} limine.conf was NOT updated (checksum unchanged)"
echo -e "${DF_RED}${DF_NC} limine.conf was NOT updated (checksum unchanged)"
validation_passed=false
else
echo -e "${SNAP_GREEN}${SNAP_NC} limine.conf was updated"
echo -e "${DF_GREEN}${DF_NC} limine.conf was updated"
fi
if [[ "$after_entries" -le "$before_entries" ]]; then
echo -e "${SNAP_RED}${SNAP_NC} No new snapshot entry added to limine.conf"
echo -e "${DF_RED}${DF_NC} No new snapshot entry added to limine.conf"
validation_passed=false
else
local new_entries=$((after_entries - before_entries))
echo -e "${SNAP_GREEN}${SNAP_NC} Added $new_entries new snapshot entry/entries"
echo -e "${DF_GREEN}${DF_NC} Added $new_entries new snapshot entry/entries"
fi
# Check for the specific snapshot in limine.conf
echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Searching for snapshot #$snapshot_num in limine.conf"
echo -e "\n${DF_BLUE}==>${DF_NC} Searching for snapshot #$snapshot_num in limine.conf"
# Format in limine.conf is: ///[number] │ [date]
if sudo grep -qP "^\\s*///$snapshot_num\\s*│" "$limine_conf"; then
echo -e "${SNAP_GREEN}${SNAP_NC} Found snapshot #$snapshot_num in limine.conf"
# Show the entry with description
echo -e "\n${SNAP_BLUE}Snapshot entry:${SNAP_NC}"
echo -e "${DF_GREEN}${DF_NC} Found snapshot #$snapshot_num in limine.conf"
echo -e "\n${DF_BLUE}Snapshot entry:${DF_NC}"
local entry_line=$(sudo grep -nP "^\\s*///$snapshot_num\\s*│" "$limine_conf" | head -n 1 | cut -d: -f1)
if [[ -n "$entry_line" ]]; then
# Show the snapshot header and description
sudo sed -n "${entry_line}p; $((entry_line+1))p" "$limine_conf" | sed 's/^/ /'
fi
[[ -n "$entry_line" ]] && sudo sed -n "${entry_line}p; $((entry_line+1))p" "$limine_conf" | sed 's/^/ /'
else
echo -e "${SNAP_RED}${SNAP_NC} Snapshot #$snapshot_num NOT found in limine.conf"
echo -e "${DF_RED}${DF_NC} Snapshot #$snapshot_num NOT found in limine.conf"
validation_passed=false
fi
# Print summary
echo -e "\n${SNAP_BLUE}╔════════════════════════════════════════════════════════════╗${SNAP_NC}"
echo -e "${SNAP_BLUE}${SNAP_NC} Summary ${SNAP_BLUE}${SNAP_NC}"
echo -e "${SNAP_BLUE}╚════════════════════════════════════════════════════════════╝${SNAP_NC}"
echo -e "\n${DF_BLUE}╔════════════════════════════════════════════════════════════╗${DF_NC}"
echo -e "${DF_BLUE}${DF_NC} Summary ${DF_BLUE}${DF_NC}"
echo -e "${DF_BLUE}╚════════════════════════════════════════════════════════════╝${DF_NC}"
echo -e "Snapshot Number: #$snapshot_num"
echo -e "Description: \"$description\""
echo -e "Config: $snap_config"
@@ -130,14 +100,14 @@ snap-create() {
echo -e "After entries: $after_entries"
if [[ "$validation_passed" == true ]]; then
echo -e "Status: ${SNAP_GREEN}✓ VALIDATED${SNAP_NC}"
echo -e "\n${SNAP_GREEN}${SNAP_NC} Snapshot created and limine.conf successfully updated!"
echo -e "Status: ${DF_GREEN}✓ VALIDATED${DF_NC}"
echo -e "\n${DF_GREEN}${DF_NC} Snapshot created and limine.conf successfully updated!"
return 0
else
echo -e "Status: ${SNAP_RED}✗ VALIDATION FAILED${SNAP_NC}"
echo -e "\n${SNAP_RED}${SNAP_NC} Snapshot created but limine.conf validation failed!"
echo -e "${SNAP_YELLOW}${SNAP_NC} Check if limine-snapper-sync service is running properly"
echo -e "${SNAP_YELLOW}Run:${SNAP_NC} sudo systemctl status limine-snapper-sync.service"
echo -e "Status: ${DF_RED}✗ VALIDATION FAILED${DF_NC}"
echo -e "\n${DF_RED}${DF_NC} Snapshot created but limine.conf validation failed!"
echo -e "${DF_YELLOW}${DF_NC} Check if limine-snapper-sync service is running properly"
echo -e "${DF_YELLOW}Run:${DF_NC} sudo systemctl status limine-snapper-sync.service"
return 1
fi
}
@@ -146,343 +116,135 @@ snap-create() {
# Helper Functions
# ============================================================================
# List recent snapshots
snap-list() {
local count="${1:-10}"
echo -e "${SNAP_BLUE}Recent $count snapshots:${SNAP_NC}\n"
echo -e "${DF_BLUE}Recent $count snapshots:${DF_NC}\n"
sudo snapper -c root list | tail -n "$((count + 1))"
}
# Show snapshot details
snap-show() {
if [[ -z "$1" ]]; then
echo -e "${SNAP_RED}${SNAP_NC} Usage: snap-show <snapshot_number>"
return 1
fi
[[ -z "$1" ]] && { echo -e "${DF_RED}${DF_NC} Usage: snap-show <snapshot_number>"; return 1; }
echo -e "${SNAP_BLUE}Snapshot #$1 details:${SNAP_NC}\n"
echo -e "${DF_BLUE}Snapshot #$1 details:${DF_NC}\n"
sudo snapper -c root list | grep "^\s*$1\s"
echo -e "\n${SNAP_BLUE}In limine.conf:${SNAP_NC}"
echo -e "\n${DF_BLUE}In limine.conf:${DF_NC}"
if sudo grep -qP "^\\s*///$1\\s*│" /boot/limine.conf; then
local entry_line=$(sudo grep -nP "^\\s*///$1\\s*│" /boot/limine.conf | head -n 1 | cut -d: -f1)
if [[ -n "$entry_line" ]]; then
sudo sed -n "${entry_line}p; $((entry_line+1))p" /boot/limine.conf
fi
[[ -n "$entry_line" ]] && sudo sed -n "${entry_line}p; $((entry_line+1))p" /boot/limine.conf
else
echo -e "${SNAP_YELLOW}${SNAP_NC} Not found in limine.conf"
echo -e "${DF_YELLOW}${DF_NC} Not found in limine.conf"
fi
}
# Delete snapshot with limine validation
snap-delete() {
if [[ -z "$1" ]]; then
echo -e "${SNAP_RED}${SNAP_NC} Usage: snap-delete <snapshot_number>"
return 1
fi
[[ -z "$1" ]] && { echo -e "${DF_RED}${DF_NC} Usage: snap-delete <snapshot_number>"; return 1; }
local snapshot_num="$1"
local limine_conf="/boot/limine.conf"
echo -e "${SNAP_BLUE}==>${SNAP_NC} Deleting snapshot #$snapshot_num"
echo -e "${DF_BLUE}==>${DF_NC} Deleting snapshot #$snapshot_num"
# Check before deletion (count snapshot entries using correct format)
local before_entries=$(sudo grep -cP "^\\s*///\\d+\\s*│" "$limine_conf" || echo "0")
# Delete the snapshot
sudo snapper -c root delete "$snapshot_num"
if [[ $? -eq 0 ]]; then
echo -e "${SNAP_GREEN}${SNAP_NC} Snapshot #$snapshot_num deleted"
echo -e "${DF_GREEN}${DF_NC} Snapshot #$snapshot_num deleted"
# Trigger sync service
echo -e "${SNAP_BLUE}==>${SNAP_NC} Triggering limine-snapper-sync..."
echo -e "${DF_BLUE}==>${DF_NC} Triggering limine-snapper-sync..."
sudo systemctl start limine-snapper-sync.service
# Wait for service to complete
sleep 2
# Check after deletion (use correct format)
local after_entries=$(sudo grep -cP "^\\s*///\\d+\\s*│" "$limine_conf" || echo "0")
if [[ "$after_entries" -lt "$before_entries" ]]; then
echo -e "${SNAP_GREEN}${SNAP_NC} limine.conf updated (removed entry)"
echo -e "${DF_GREEN}${DF_NC} limine.conf updated (removed entry)"
else
echo -e "${SNAP_YELLOW}${SNAP_NC} limine.conf may not have been updated"
echo -e "${DF_YELLOW}${DF_NC} limine.conf may not have been updated"
fi
# Verify snapshot is gone from limine.conf (use correct format)
if ! sudo grep -qP "^\\s*///$snapshot_num\\s*│" "$limine_conf"; then
echo -e "${SNAP_GREEN}${SNAP_NC} Snapshot #$snapshot_num removed from limine.conf"
echo -e "${DF_GREEN}${DF_NC} Snapshot #$snapshot_num removed from limine.conf"
else
echo -e "${SNAP_RED}${SNAP_NC} Snapshot #$snapshot_num still in limine.conf!"
echo -e "${DF_RED}${DF_NC} Snapshot #$snapshot_num still in limine.conf!"
fi
else
echo -e "${SNAP_RED}${SNAP_NC} Failed to delete snapshot #$snapshot_num"
echo -e "${DF_RED}${DF_NC} Failed to delete snapshot #$snapshot_num"
return 1
fi
}
# Check limine.conf for all snapshots
snap-check-limine() {
local limine_conf="/boot/limine.conf"
echo -e "${SNAP_BLUE}╔════════════════════════════════════════════════════════════╗${SNAP_NC}"
echo -e "${SNAP_BLUE}${SNAP_NC} Limine Snapshot Entries ${SNAP_BLUE}${SNAP_NC}"
echo -e "${SNAP_BLUE}╚════════════════════════════════════════════════════════════╝${SNAP_NC}\n"
echo -e "${DF_BLUE}╔════════════════════════════════════════════════════════════╗${DF_NC}"
echo -e "${DF_BLUE}${DF_NC} Limine Snapshot Entries ${DF_BLUE}${DF_NC}"
echo -e "${DF_BLUE}╚════════════════════════════════════════════════════════════╝${DF_NC}\n"
if [[ ! -f "$limine_conf" ]]; then
echo -e "${SNAP_RED}${SNAP_NC} Limine config not found: $limine_conf"
return 1
fi
[[ ! -f "$limine_conf" ]] && { echo -e "${DF_RED}${DF_NC} Limine config not found: $limine_conf"; return 1; }
# Get latest snapshot number (excluding snapshot 0)
local latest_snapshot=$(sudo snapper -c root list | tail -n +3 | grep -v "^\s*0\s" | tail -n 1 | awk '{print $1}')
[[ -z "$latest_snapshot" ]] && { echo -e "${DF_YELLOW}${DF_NC} No snapshots found in snapper"; return 1; }
if [[ -z "$latest_snapshot" ]]; then
echo -e "${SNAP_YELLOW}${SNAP_NC} No snapshots found in snapper"
return 1
fi
echo -e "${DF_BLUE}Latest snapshot:${DF_NC} #$latest_snapshot"
echo -e "${SNAP_BLUE}Latest snapshot:${SNAP_NC} #$latest_snapshot"
# Check if latest snapshot is in limine.conf
# Format in limine.conf is: ///49 │ 2025-12-14 01:15:33
echo -e "${SNAP_BLUE}==>${SNAP_NC} Checking if latest snapshot is in limine.conf"
echo -e "${DF_BLUE}==>${DF_NC} Checking if latest snapshot is in limine.conf"
if sudo grep -qP "^\\s*///$latest_snapshot\s*│" "$limine_conf"; then
echo -e "${SNAP_GREEN}${SNAP_NC} Latest snapshot #$latest_snapshot is present in limine.conf"
# Show the entry with description
echo -e "\n${SNAP_BLUE}Latest snapshot entry:${SNAP_NC}"
local entry_line=$(sudo grep -nP "^\\s*///$latest_snapshot\s*│" "$limine_conf" | head -n 1 | cut -d: -f1)
if [[ -n "$entry_line" ]]; then
# Show the snapshot header and description (next line)
sudo sed -n "${entry_line}p; $((entry_line+1))p" "$limine_conf" | sed 's/^/ /'
fi
echo -e "${DF_GREEN}${DF_NC} Latest snapshot #$latest_snapshot is present in limine.conf"
else
echo -e "${SNAP_RED}${SNAP_NC} Latest snapshot #$latest_snapshot is NOT in limine.conf"
echo -e "${SNAP_YELLOW}${SNAP_NC} This may indicate the sync service hasn't run yet"
echo -e "${DF_RED}${DF_NC} Latest snapshot #$latest_snapshot is NOT in limine.conf"
fi
# Count snapshot entries (lines matching ///[number] │)
echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Counting snapshot entries"
echo -e "\n${DF_BLUE}==>${DF_NC} Counting snapshot entries"
local entry_count=$(sudo grep -cP "^\\s*///\\d+\\s*│" "$limine_conf" || echo "0")
echo -e "${SNAP_BLUE}Total snapshot entries:${SNAP_NC} $entry_count\n"
# Show all snapshot entries
if [[ "$entry_count" -gt 0 ]]; then
echo -e "${SNAP_BLUE}Snapshot boot entries:${SNAP_NC}\n"
# Extract snapshot entries with their descriptions
local snap_nums=($(sudo grep -oP "^\\s*///\\K\\d+(?=\\s*│)" "$limine_conf" | sort -n))
local i=1
for snap_num in "${snap_nums[@]}"; do
local snap_line=$(sudo grep -nP "^\\s*///$snap_num\s*│" "$limine_conf" | head -n 1)
local line_num=$(echo "$snap_line" | cut -d: -f1)
local date_time=$(echo "$snap_line" | cut -d: -f2- | grep -oP "│\\s*\\K.*" || echo "")
# Get description from next line (starts with "comment:")
local desc=""
if [[ -n "$line_num" ]]; then
desc=$(sudo sed -n "$((line_num+1))p" "$limine_conf" | grep -oP "comment:\\s*\\K.*" || echo "")
fi
printf "%2d. Snapshot #%-3s %s %s\n" "$i" "$snap_num" "$date_time" "${desc:-(no description)}"
((i++))
done
# Show snapshot range
if [[ ${#snap_nums[@]} -gt 0 ]]; then
echo -e "\n${SNAP_BLUE}Snapshot range in boot menu:${SNAP_NC}"
echo -e " Oldest: #${snap_nums[1]}"
echo -e " Newest: #${snap_nums[-1]}"
fi
else
echo -e "${SNAP_YELLOW}${SNAP_NC} No snapshot entries found in limine.conf"
fi
# Compare with actual snapshots
echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Comparing with snapper list"
echo ""
# Count all snapshots except snapshot 0 (current system)
local snapper_count=$(sudo snapper -c root list | tail -n +3 | grep -v "^\s*0\s" | wc -l)
# Get oldest and newest snapshots
local oldest_snapshot=$(sudo snapper -c root list | tail -n +3 | grep -v "^\s*0\s" | head -n 1 | awk '{print $1}')
echo -e "Snapshots in snapper: $snapper_count (range: #$oldest_snapshot to #$latest_snapshot)"
echo -e "Entries in limine.conf: $entry_count"
if [[ "$snapper_count" -eq "$entry_count" ]]; then
echo -e "Status: ${SNAP_GREEN}✓ FULLY SYNCED${SNAP_NC}"
else
local diff=$((snapper_count - entry_count))
echo -e "Status: ${SNAP_YELLOW}⚠ PARTIALLY SYNCED${SNAP_NC}"
echo -e "Missing from boot menu: $diff snapshot(s)"
# Check if latest is present (most important)
if sudo grep -qP "^\\s*///$latest_snapshot\s*│" "$limine_conf"; then
echo -e "\n${SNAP_GREEN}${SNAP_NC} Latest snapshot IS available for boot (this is what matters most)"
else
echo -e "\n${SNAP_RED}${SNAP_NC} Latest snapshot NOT available for boot (run: snap-sync)"
fi
echo -e "\n${SNAP_BLUE}Note:${SNAP_NC} limine-snapper-sync typically limits boot entries to recent snapshots"
echo -e " This is normal and prevents boot menu clutter"
# Check the limit from comment line
local limit_comment=$(sudo grep -oP "comment:\\s*\\K\\d+\\s*/\\s*\\d+" "$limine_conf" | head -n 1)
if [[ -n "$limit_comment" ]]; then
echo -e " Current limit: $limit_comment snapshots"
fi
# Show which snapshots are missing if there aren't too many
if [[ $diff -le 20 ]]; then
echo -e "\n${SNAP_BLUE}Snapshots NOT in boot menu:${SNAP_NC}"
local all_snapshots=($(sudo snapper -c root list | tail -n +3 | grep -v "^\s*0\s" | awk '{print $1}'))
local limine_snapshots=($(sudo grep -oP "^\\s*///\\K\\d+(?=\\s*│)" "$limine_conf"))
local missing_count=0
for snap_num in "${all_snapshots[@]}"; do
# Check if this snapshot is in limine.conf
local found=false
for limine_snap in "${limine_snapshots[@]}"; do
if [[ "$snap_num" == "$limine_snap" ]]; then
found=true
break
fi
done
if [[ "$found" == false ]]; then
if [[ $missing_count -lt 10 ]]; then
local snap_info=$(sudo snapper -c root list | grep "^\s*$snap_num\s")
local snap_type=$(echo "$snap_info" | awk '{print $2}')
local snap_desc=$(echo "$snap_info" | awk -F'|' '{print $5}' | xargs)
echo -e " #$snap_num ($snap_type) - $snap_desc"
fi
((missing_count++))
fi
done
if [[ $missing_count -gt 10 ]]; then
echo -e " ... and $((missing_count - 10)) more"
fi
fi
fi
echo -e "${DF_BLUE}Total snapshot entries:${DF_NC} $entry_count\n"
}
# Show detailed snapshot information with types
snap-info() {
echo -e "${SNAP_BLUE}╔════════════════════════════════════════════════════════════╗${SNAP_NC}"
echo -e "${SNAP_BLUE}${SNAP_NC} Detailed Snapshot Information ${SNAP_BLUE}${SNAP_NC}"
echo -e "${SNAP_BLUE}╚════════════════════════════════════════════════════════════╝${SNAP_NC}\n"
echo -e "${SNAP_BLUE}All snapshots with types:${SNAP_NC}\n"
sudo snapper -c root list
echo -e "\n${SNAP_BLUE}Breakdown by type:${SNAP_NC}\n"
local single_count=$(sudo snapper -c root list | grep -c "single" || echo "0")
local pre_count=$(sudo snapper -c root list | grep -c "pre" || echo "0")
local post_count=$(sudo snapper -c root list | grep -c "post" || echo "0")
local total=$(sudo snapper -c root list | tail -n +3 | grep -v "^\s*0\s" | wc -l)
echo -e "Single snapshots: $single_count"
echo -e "Pre snapshots: $pre_count"
echo -e "Post snapshots: $post_count"
echo -e "Total snapshots: $total"
echo -e "\n${SNAP_BLUE}Snapshot types explained:${SNAP_NC}"
echo -e " ${SNAP_GREEN}single${SNAP_NC} - Manual snapshots (created by you)"
echo -e " ${SNAP_GREEN}pre${SNAP_NC} - Before system changes (e.g., package updates)"
echo -e " ${SNAP_GREEN}post${SNAP_NC} - After system changes"
}
# Manually trigger sync service
snap-sync() {
echo -e "${SNAP_BLUE}==>${SNAP_NC} Manually triggering limine-snapper-sync..."
echo -e "${DF_BLUE}==>${DF_NC} Manually triggering limine-snapper-sync..."
if sudo systemctl start limine-snapper-sync.service; then
echo -e "${SNAP_GREEN}${SNAP_NC} Service triggered successfully"
# Wait for completion
echo -e "${DF_GREEN}${DF_NC} Service triggered successfully"
sleep 2
# Show status
echo -e "\n${SNAP_BLUE}Service status:${SNAP_NC}"
echo -e "\n${DF_BLUE}Service status:${DF_NC}"
sudo systemctl status limine-snapper-sync.service --no-pager -l | tail -n 10
else
echo -e "${SNAP_RED}${SNAP_NC} Failed to trigger service"
echo -e "${DF_RED}${DF_NC} Failed to trigger service"
return 1
fi
}
# Validate limine-snapper-sync service is working
snap-validate-service() {
echo -e "${SNAP_BLUE}╔════════════════════════════════════════════════════════════╗${SNAP_NC}"
echo -e "${SNAP_BLUE}${SNAP_NC} Limine-Snapper-Sync Service Validation ${SNAP_BLUE}${SNAP_NC}"
echo -e "${SNAP_BLUE}╚════════════════════════════════════════════════════════════╝${SNAP_NC}\n"
echo -e "${DF_BLUE}╔════════════════════════════════════════════════════════════╗${DF_NC}"
echo -e "${DF_BLUE}${DF_NC} Limine-Snapper-Sync Service Validation ${DF_BLUE}${DF_NC}"
echo -e "${DF_BLUE}╚════════════════════════════════════════════════════════════╝${DF_NC}\n"
# Check if service unit exists
echo -e "${SNAP_BLUE}==>${SNAP_NC} Checking service unit"
echo -e "${DF_BLUE}==>${DF_NC} Checking service unit"
if systemctl list-unit-files | grep -q "limine-snapper-sync.service"; then
echo -e "${SNAP_GREEN}${SNAP_NC} limine-snapper-sync.service unit exists"
echo -e "${DF_GREEN}${DF_NC} limine-snapper-sync.service unit exists"
else
echo -e "${SNAP_RED}${SNAP_NC} limine-snapper-sync.service unit NOT found"
echo -e "\n${SNAP_YELLOW}Install with:${SNAP_NC} paru -S limine-snapper-sync"
echo -e "${DF_RED}${DF_NC} limine-snapper-sync.service unit NOT found"
echo -e "\n${DF_YELLOW}Install with:${DF_NC} paru -S limine-snapper-sync"
return 1
fi
# Check if service is enabled
echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Checking if service is enabled"
echo -e "\n${DF_BLUE}==>${DF_NC} Checking if service is enabled"
if systemctl is-enabled limine-snapper-sync.service &>/dev/null; then
echo -e "${SNAP_GREEN}${SNAP_NC} Service is enabled"
echo -e "${DF_GREEN}${DF_NC} Service is enabled"
else
echo -e "${SNAP_YELLOW}${SNAP_NC} Service is NOT enabled"
echo -e "${SNAP_YELLOW}Enable with:${SNAP_NC} sudo systemctl enable limine-snapper-sync.service"
echo -e "${DF_YELLOW}${DF_NC} Service is NOT enabled"
echo -e "${DF_YELLOW}Enable with:${DF_NC} sudo systemctl enable limine-snapper-sync.service"
fi
# Check service status
echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Checking service status"
if systemctl is-active limine-snapper-sync.service &>/dev/null; then
echo -e "${SNAP_GREEN}${SNAP_NC} Service is active"
else
echo -e "${SNAP_YELLOW}${SNAP_NC} Service is inactive (this is normal for oneshot services)"
fi
# Show recent service logs
echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Recent service logs (last 10 lines)"
echo -e "\n${DF_BLUE}==>${DF_NC} Recent service logs (last 10 lines)"
echo ""
sudo journalctl -u limine-snapper-sync.service -n 10 --no-pager | sed 's/^/ /'
# Check snapper config
echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Checking snapper configuration"
if [[ -f "/etc/snapper/configs/root" ]]; then
echo -e "${SNAP_GREEN}${SNAP_NC} Snapper root config exists"
else
echo -e "${SNAP_RED}${SNAP_NC} Snapper root config not found"
fi
# Check limine.conf
echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Checking limine.conf"
if [[ -f "/boot/limine.conf" ]]; then
echo -e "${SNAP_GREEN}${SNAP_NC} limine.conf exists"
local snap_entries=$(sudo grep -cP "^\\s*///\\d+\\s*│" /boot/limine.conf || echo "0")
echo -e " Snapshot entries: $snap_entries"
else
echo -e "${SNAP_RED}${SNAP_NC} limine.conf not found"
fi
echo -e "\n${SNAP_GREEN}${SNAP_NC} Validation complete"
echo -e "\n${DF_GREEN}${DF_NC} Validation complete"
}
# Quick snapshot aliases
@@ -492,16 +254,3 @@ alias snaprm='snap-delete'
alias snapshow='snap-show'
alias snapcheck='snap-check-limine'
alias snapsync='snap-sync'
alias snapinfo='snap-info'
# ============================================================================
# Usage Examples (commented out - uncomment to see examples)
# ============================================================================
# snap-create "Before system update"
# snap-list 20
# snap-show 42
# snap-delete 42
# snap-check-limine
# snap-sync
# snap-validate-service

View File

@@ -2,26 +2,16 @@
# SSH Session Manager with Tmux Integration
# ============================================================================
# Manage SSH connections with automatic tmux session handling
#
# Usage:
# ssh-save <name> <connection> # Save SSH connection
# ssh-connect <name> # Connect and attach/create tmux session
# ssh-list # List all saved connections
# ssh-delete <name> # Delete saved connection
# ssh-edit <name> # Edit connection details
# sshf # Fuzzy search and connect
#
# Features:
# - Automatic tmux session attach/create on remote host
# - Named sessions per connection
# - Connection profiles with SSH options
# - Auto-reconnect support
# - Dotfiles sync to remote (optional)
#
# Add to .zshrc:
# source ~/.dotfiles/zsh/functions/ssh-manager.zsh
# ============================================================================
# Source shared colors (with fallback)
source "${0:A:h}/../lib/colors.zsh" 2>/dev/null || \
source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null || {
typeset -g DF_GREEN=$'\033[0;32m' DF_BLUE=$'\033[0;34m'
typeset -g DF_YELLOW=$'\033[1;33m' DF_CYAN=$'\033[0;36m'
typeset -g DF_RED=$'\033[0;31m' DF_NC=$'\033[0m'
}
# ============================================================================
# Configuration
# ============================================================================
@@ -31,33 +21,14 @@ typeset -g SSH_AUTO_TMUX="${SSH_AUTO_TMUX:-true}"
typeset -g SSH_TMUX_SESSION_PREFIX="${SSH_TMUX_SESSION_PREFIX:-ssh}"
typeset -g SSH_SYNC_DOTFILES="${SSH_SYNC_DOTFILES:-ask}"
# Colors
typeset -g SSH_GREEN=$'\033[0;32m'
typeset -g SSH_BLUE=$'\033[0;34m'
typeset -g SSH_YELLOW=$'\033[1;33m'
typeset -g SSH_CYAN=$'\033[0;36m'
typeset -g SSH_RED=$'\033[0;31m'
typeset -g SSH_NC=$'\033[0m'
# ============================================================================
# Helper Functions
# ============================================================================
_ssh_print_step() {
echo -e "${SSH_BLUE}==>${SSH_NC} $1"
}
_ssh_print_success() {
echo -e "${SSH_GREEN}${SSH_NC} $1"
}
_ssh_print_error() {
echo -e "${SSH_RED}${SSH_NC} $1"
}
_ssh_print_info() {
echo -e "${SSH_CYAN}${SSH_NC} $1"
}
_ssh_print_step() { echo -e "${DF_BLUE}==>${DF_NC} $1"; }
_ssh_print_success() { echo -e "${DF_GREEN}${DF_NC} $1"; }
_ssh_print_error() { echo -e "${DF_RED}${DF_NC} $1"; }
_ssh_print_info() { echo -e "${DF_CYAN}${DF_NC} $1"; }
_ssh_init_profiles() {
if [[ ! -f "$SSH_PROFILES_FILE" ]]; then
@@ -65,10 +36,6 @@ _ssh_init_profiles() {
cat > "$SSH_PROFILES_FILE" << 'EOF'
# SSH Connection Profiles
# Format: name|user@host|port|key_file|options|description
#
# Example:
# prod|user@prod.example.com|22|~/.ssh/prod_key|-L 8080:localhost:80|Production server
# dev|user@dev.example.com|2222||ForwardAgent=yes|Development server
EOF
_ssh_print_success "Created SSH profiles file: $SSH_PROFILES_FILE"
fi
@@ -77,14 +44,8 @@ EOF
_ssh_parse_profile() {
local name="$1"
local line=$(grep "^${name}|" "$SSH_PROFILES_FILE" 2>/dev/null | head -1)
if [[ -z "$line" ]]; then
return 1
fi
# Parse: name|connection|port|key|options|description
[[ -z "$line" ]] && return 1
IFS='|' read -r profile_name connection port key_file ssh_opts description <<< "$line"
echo "$connection|$port|$key_file|$ssh_opts|$description"
}
@@ -93,88 +54,62 @@ _ssh_parse_profile() {
# ============================================================================
ssh-save() {
local name="$1"
local connection="$2"
local port="${3:-22}"
local key_file="${4:-}"
local options="${5:-}"
local description="${6:-}"
local name="$1" connection="$2" port="${3:-22}" key_file="${4:-}" options="${5:-}" description="${6:-}"
_ssh_init_profiles
if [[ -z "$name" || -z "$connection" ]]; then
[[ -z "$name" || -z "$connection" ]] && {
echo "Usage: ssh-save <name> <user@host> [port] [key_file] [options] [description]"
echo
echo "Examples:"
echo " ssh-save prod user@prod.com"
echo " ssh-save dev user@dev.com 2222 ~/.ssh/dev_key"
echo " ssh-save vpn user@vpn.com 22 '' '-D 9090' 'VPN server'"
return 1
fi
}
# Check if profile exists
if grep -q "^${name}|" "$SSH_PROFILES_FILE" 2>/dev/null; then
echo -e "${SSH_YELLOW}${SSH_NC} Profile '$name' already exists"
read -q "REPLY?Overwrite? [y/N]: "
echo
echo -e "${DF_YELLOW}${DF_NC} Profile '$name' already exists"
read -q "REPLY?Overwrite? [y/N]: "; echo
[[ ! "$REPLY" =~ ^[Yy]$ ]] && return 1
# Remove old entry
grep -v "^${name}|" "$SSH_PROFILES_FILE" > "${SSH_PROFILES_FILE}.tmp"
mv "${SSH_PROFILES_FILE}.tmp" "$SSH_PROFILES_FILE"
fi
# Save new profile
echo "${name}|${connection}|${port}|${key_file}|${options}|${description}" >> "$SSH_PROFILES_FILE"
_ssh_print_success "Saved SSH profile: $name"
echo " Connection: $connection"
[[ "$port" != "22" ]] && echo " Port: $port"
[[ -n "$key_file" ]] && echo " Key: $key_file"
[[ -n "$options" ]] && echo " Options: $options"
[[ -n "$description" ]] && echo " Description: $description"
}
ssh-list() {
_ssh_init_profiles
echo -e "${SSH_BLUE}╔════════════════════════════════════════════════════════════╗${SSH_NC}"
echo -e "${SSH_BLUE}${SSH_NC} SSH Connection Profiles ${SSH_BLUE}${SSH_NC}"
echo -e "${SSH_BLUE}╚════════════════════════════════════════════════════════════╝${SSH_NC}"
echo -e "${DF_BLUE}╔════════════════════════════════════════════════════════════╗${DF_NC}"
echo -e "${DF_BLUE}${DF_NC} SSH Connection Profiles ${DF_BLUE}${DF_NC}"
echo -e "${DF_BLUE}╚════════════════════════════════════════════════════════════╝${DF_NC}"
echo
local has_profiles=false
while IFS='|' read -r name connection port key options description; do
# Skip comments and empty lines
[[ "$name" =~ ^# ]] && continue
[[ -z "$name" ]] && continue
has_profiles=true
echo -e "${SSH_GREEN}${SSH_NC} ${SSH_CYAN}$name${SSH_NC}"
echo -e "${DF_GREEN}${DF_NC} ${DF_CYAN}$name${DF_NC}"
echo " Connection: $connection"
[[ "$port" != "22" && -n "$port" ]] && echo " Port: $port"
[[ -n "$key" ]] && echo " Key: $key"
[[ -n "$options" ]] && echo " Options: $options"
[[ -n "$description" ]] && echo " Description: $description"
echo
done < "$SSH_PROFILES_FILE"
if [[ "$has_profiles" != true ]]; then
[[ "$has_profiles" != true ]] && {
_ssh_print_info "No profiles saved yet"
echo
echo "Create a profile with:"
echo " ssh-save myserver user@example.com"
fi
echo "Create a profile with: ssh-save myserver user@example.com"
}
}
ssh-delete() {
local name="$1"
if [[ -z "$name" ]]; then
echo "Usage: ssh-delete <name>"
return 1
fi
[[ -z "$name" ]] && { echo "Usage: ssh-delete <name>"; return 1; }
_ssh_init_profiles
@@ -183,182 +118,74 @@ ssh-delete() {
return 1
fi
# Remove profile
grep -v "^${name}|" "$SSH_PROFILES_FILE" > "${SSH_PROFILES_FILE}.tmp"
mv "${SSH_PROFILES_FILE}.tmp" "$SSH_PROFILES_FILE"
_ssh_print_success "Deleted profile: $name"
}
ssh-edit() {
local name="$1"
if [[ -z "$name" ]]; then
# Edit entire file
${EDITOR:-vim} "$SSH_PROFILES_FILE"
return
fi
_ssh_init_profiles
local profile_data=$(_ssh_parse_profile "$name")
if [[ -z "$profile_data" ]]; then
_ssh_print_error "Profile '$name' not found"
return 1
fi
IFS='|' read -r connection port key_file ssh_opts description <<< "$profile_data"
echo -e "${SSH_CYAN}Editing profile: $name${SSH_NC}"
echo
read "new_connection?Connection [$connection]: "
new_connection="${new_connection:-$connection}"
read "new_port?Port [$port]: "
new_port="${new_port:-$port}"
read "new_key?Key file [$key_file]: "
new_key="${new_key:-$key_file}"
read "new_opts?SSH options [$ssh_opts]: "
new_opts="${new_opts:-$ssh_opts}"
read "new_desc?Description [$description]: "
new_desc="${new_desc:-$description}"
# Remove old and add new
grep -v "^${name}|" "$SSH_PROFILES_FILE" > "${SSH_PROFILES_FILE}.tmp"
echo "${name}|${new_connection}|${new_port}|${new_key}|${new_opts}|${new_desc}" >> "${SSH_PROFILES_FILE}.tmp"
mv "${SSH_PROFILES_FILE}.tmp" "$SSH_PROFILES_FILE"
_ssh_print_success "Updated profile: $name"
}
# ============================================================================
# SSH Connection with Tmux Integration
# ============================================================================
ssh-connect() {
local name="$1"
local session_name="${2:-${SSH_TMUX_SESSION_PREFIX}-${name}}"
if [[ -z "$name" ]]; then
echo "Usage: ssh-connect <profile_name> [tmux_session_name]"
echo
echo "Saved profiles:"
ssh-list
return 1
fi
[[ -z "$name" ]] && { echo "Usage: ssh-connect <profile_name>"; ssh-list; return 1; }
_ssh_init_profiles
# Parse profile
local profile_data=$(_ssh_parse_profile "$name")
if [[ -z "$profile_data" ]]; then
_ssh_print_error "Profile '$name' not found"
echo "Use 'ssh-save $name user@host' to create it"
return 1
fi
[[ -z "$profile_data" ]] && { _ssh_print_error "Profile '$name' not found"; return 1; }
IFS='|' read -r connection port key_file ssh_opts description <<< "$profile_data"
_ssh_print_step "Connecting to: $name"
[[ -n "$description" ]] && echo " $description"
# Build SSH command
local ssh_cmd="ssh"
# Add port
[[ -n "$port" && "$port" != "22" ]] && ssh_cmd="$ssh_cmd -p $port"
# Add key file
[[ -n "$key_file" ]] && ssh_cmd="$ssh_cmd -i $key_file"
# Add custom options
[[ -n "$ssh_opts" ]] && ssh_cmd="$ssh_cmd $ssh_opts"
# Add connection
ssh_cmd="$ssh_cmd $connection"
# Tmux integration
if [[ "$SSH_AUTO_TMUX" == "true" ]]; then
_ssh_print_info "Attaching to tmux session: $session_name"
# SSH with tmux attach or create
local tmux_cmd="tmux attach-session -t $session_name 2>/dev/null || tmux new-session -s $session_name"
# Execute
eval "$ssh_cmd -t '$tmux_cmd'"
else
# Direct SSH without tmux
eval "$ssh_cmd"
fi
}
# ============================================================================
# Fuzzy Search Integration (requires fzf)
# ============================================================================
sshf() {
if ! command -v fzf &>/dev/null; then
_ssh_print_error "fzf not installed"
echo "Install: git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf && ~/.fzf/install"
return 1
fi
_ssh_init_profiles
# Build selection list
local profiles=()
while IFS='|' read -r name connection port key options description; do
[[ "$name" =~ ^# ]] && continue
[[ -z "$name" ]] && continue
local display="$name$connection"
[[ -n "$description" ]] && display="$display ($description)"
profiles+=("$name|$display")
done < "$SSH_PROFILES_FILE"
if [[ ${#profiles[@]} -eq 0 ]]; then
_ssh_print_info "No profiles saved"
return 1
fi
[[ ${#profiles[@]} -eq 0 ]] && { _ssh_print_info "No profiles saved"; return 1; }
# Fuzzy select
local selection=$(printf '%s\n' "${profiles[@]}" | \
fzf --height=50% \
--layout=reverse \
--border=rounded \
--prompt='SSH > ' \
--preview='echo {}' \
--preview-window=hidden \
--delimiter='|' \
--with-nth=2)
fzf --height=50% --layout=reverse --border=rounded --prompt='SSH > ' \
--delimiter='|' --with-nth=2)
if [[ -n "$selection" ]]; then
local profile_name="${selection%%|*}"
ssh-connect "$profile_name"
fi
[[ -n "$selection" ]] && ssh-connect "${selection%%|*}"
}
# ============================================================================
# Quick Reconnect
# ============================================================================
ssh-reconnect() {
local name="${1:-last}"
if [[ "$name" == "last" ]]; then
# Get last connected profile from history
local last_profile=$(grep "ssh-connect" "$HISTFILE" 2>/dev/null | tail -1 | awk '{print $2}')
if [[ -z "$last_profile" ]]; then
_ssh_print_error "No previous connection found"
return 1
fi
[[ -z "$last_profile" ]] && { _ssh_print_error "No previous connection found"; return 1; }
name="$last_profile"
fi
@@ -366,58 +193,29 @@ ssh-reconnect() {
ssh-connect "$name"
}
# ============================================================================
# Dotfiles Sync to Remote
# ============================================================================
ssh-sync-dotfiles() {
local name="$1"
if [[ -z "$name" ]]; then
echo "Usage: ssh-sync-dotfiles <profile_name>"
return 1
fi
[[ -z "$name" ]] && { echo "Usage: ssh-sync-dotfiles <profile_name>"; return 1; }
local profile_data=$(_ssh_parse_profile "$name")
if [[ -z "$profile_data" ]]; then
_ssh_print_error "Profile '$name' not found"
return 1
fi
[[ -z "$profile_data" ]] && { _ssh_print_error "Profile '$name' not found"; return 1; }
IFS='|' read -r connection port key_file ssh_opts description <<< "$profile_data"
local dotfiles_dir="${DOTFILES_DIR:-$HOME/.dotfiles}"
if [[ ! -d "$dotfiles_dir" ]]; then
_ssh_print_error "Dotfiles directory not found: $dotfiles_dir"
return 1
fi
[[ ! -d "$dotfiles_dir" ]] && { _ssh_print_error "Dotfiles directory not found"; return 1; }
_ssh_print_step "Syncing dotfiles to: $connection"
# Build rsync command
local rsync_cmd="rsync -avz --exclude='.git' --exclude='*.local'"
[[ -n "$port" && "$port" != "22" ]] && rsync_cmd="$rsync_cmd -e 'ssh -p $port'"
[[ -n "$key_file" ]] && rsync_cmd="$rsync_cmd -e 'ssh -i $key_file'"
rsync_cmd="$rsync_cmd $dotfiles_dir/ $connection:.dotfiles/"
_ssh_print_info "Running: $rsync_cmd"
if eval "$rsync_cmd"; then
_ssh_print_success "Dotfiles synced successfully"
# Optionally run install script on remote
read -q "REPLY?Run install script on remote? [y/N]: "
echo
if [[ "$REPLY" =~ ^[Yy]$ ]]; then
local ssh_cmd="ssh"
[[ -n "$port" && "$port" != "22" ]] && ssh_cmd="$ssh_cmd -p $port"
[[ -n "$key_file" ]] && ssh_cmd="$ssh_cmd -i $key_file"
eval "$ssh_cmd $connection 'cd .dotfiles && ./install.sh --skip-deps'"
fi
else
_ssh_print_error "Failed to sync dotfiles"
return 1
@@ -435,26 +233,6 @@ alias sshd='ssh-delete'
alias sshr='ssh-reconnect'
alias sshsync='ssh-sync-dotfiles'
# ============================================================================
# Completion Helper
# ============================================================================
_ssh_manager_profiles() {
local profiles=()
while IFS='|' read -r name rest; do
[[ "$name" =~ ^# ]] && continue
[[ -z "$name" ]] && continue
profiles+=("$name")
done < "$SSH_PROFILES_FILE" 2>/dev/null
echo "${profiles[@]}"
}
# ZSH completion (if you want to add it)
# compdef '_arguments "1:profile:($(_ssh_manager_profiles))"' ssh-connect
# compdef '_arguments "1:profile:($(_ssh_manager_profiles))"' ssh-delete
# compdef '_arguments "1:profile:($(_ssh_manager_profiles))"' ssh-edit
# ============================================================================
# Initialization
# ============================================================================

View File

@@ -2,26 +2,16 @@
# Tmux Workspace Manager - Project Templates & Layouts
# ============================================================================
# Quick project workspace setup with pre-configured tmux layouts
#
# Usage:
# tw-create <name> [template] # Create workspace from template
# tw-attach <name> # Attach to workspace
# tw-list # List all workspaces
# tw-delete <name> # Delete workspace
# tw-save <name> # Save current layout as template
# tw <name> # Quick attach (or create if not exists)
#
# Templates:
# dev - Vim (50%) + terminal (25%) + logs (25%)
# ops - 4 panes: htop, logs, shell, monitoring
# ssh-multi - 4 panes for managing multiple servers
# debug - 2 panes: main (70%) + helper (30%)
# full - Just one full pane
#
# Add to .zshrc:
# source ~/.dotfiles/zsh/functions/tmux-workspaces.zsh
# ============================================================================
# Source shared colors (with fallback)
source "${0:A:h}/../lib/colors.zsh" 2>/dev/null || \
source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null || {
typeset -g DF_GREEN=$'\033[0;32m' DF_BLUE=$'\033[0;34m'
typeset -g DF_YELLOW=$'\033[1;33m' DF_CYAN=$'\033[0;36m'
typeset -g DF_RED=$'\033[0;31m' DF_NC=$'\033[0m'
}
# ============================================================================
# Configuration
# ============================================================================
@@ -30,33 +20,14 @@ typeset -g TW_TEMPLATES_DIR="${TW_TEMPLATES_DIR:-$HOME/.dotfiles/.tmux-templates
typeset -g TW_SESSION_PREFIX="${TW_SESSION_PREFIX:-work}"
typeset -g TW_DEFAULT_TEMPLATE="${TW_DEFAULT_TEMPLATE:-dev}"
# Colors
typeset -g TW_GREEN=$'\033[0;32m'
typeset -g TW_BLUE=$'\033[0;34m'
typeset -g TW_YELLOW=$'\033[1;33m'
typeset -g TW_CYAN=$'\033[0;36m'
typeset -g TW_RED=$'\033[0;31m'
typeset -g TW_NC=$'\033[0m'
# ============================================================================
# Helper Functions
# ============================================================================
_tw_print_step() {
echo -e "${TW_BLUE}==>${TW_NC} $1"
}
_tw_print_success() {
echo -e "${TW_GREEN}${TW_NC} $1"
}
_tw_print_error() {
echo -e "${TW_RED}${TW_NC} $1"
}
_tw_print_info() {
echo -e "${TW_CYAN}${TW_NC} $1"
}
_tw_print_step() { echo -e "${DF_BLUE}==>${DF_NC} $1"; }
_tw_print_success() { echo -e "${DF_GREEN}${DF_NC} $1"; }
_tw_print_error() { echo -e "${DF_RED}${DF_NC} $1"; }
_tw_print_info() { echo -e "${DF_CYAN}${DF_NC} $1"; }
_tw_check_tmux() {
if ! command -v tmux &>/dev/null; then
@@ -68,11 +39,7 @@ _tw_check_tmux() {
_tw_init_templates() {
mkdir -p "$TW_TEMPLATES_DIR"
# Create default templates if they don't exist
if [[ ! -f "$TW_TEMPLATES_DIR/dev.tmux" ]]; then
_tw_create_default_templates
fi
[[ ! -f "$TW_TEMPLATES_DIR/dev.tmux" ]] && _tw_create_default_templates
}
# ============================================================================
@@ -82,89 +49,44 @@ _tw_init_templates() {
_tw_create_default_templates() {
_tw_print_step "Creating default templates..."
# Development template - vim + terminal + logs
cat > "$TW_TEMPLATES_DIR/dev.tmux" << 'EOF'
# Development workspace
# Usage: tw-create myproject dev
# Split vertically (vim on left 50%, rest on right)
split-window -h -p 50
# Split right pane horizontally (terminal top, logs bottom)
split-window -v -p 50
# Select the first pane (vim)
select-pane -t 0
# Optional: Start vim in first pane
# send-keys -t 0 'vim' C-m
# Optional: Set pane titles
# select-pane -t 0 -T "Editor"
# select-pane -t 1 -T "Terminal"
# select-pane -t 2 -T "Logs"
EOF
# Operations template - 4 panes for monitoring
cat > "$TW_TEMPLATES_DIR/ops.tmux" << 'EOF'
# Operations workspace
# 4-pane layout for system monitoring
# Create 2x2 grid
# Operations workspace - 4 panes
split-window -h -p 50
split-window -v -p 50
select-pane -t 0
split-window -v -p 50
# Optional: Auto-start monitoring tools
# send-keys -t 0 'htop' C-m
# send-keys -t 1 'docker ps' C-m
# send-keys -t 2 '' C-m
# send-keys -t 3 'tail -f /var/log/syslog' C-m
select-pane -t 0
EOF
# SSH multi-server template
cat > "$TW_TEMPLATES_DIR/ssh-multi.tmux" << 'EOF'
# Multi-server SSH workspace
# 4 panes for managing multiple servers
# Create 2x2 grid
split-window -h -p 50
split-window -v -p 50
select-pane -t 0
split-window -v -p 50
# Enable pane synchronization (optional - uncomment to enable)
# set-window-option synchronize-panes on
select-pane -t 0
EOF
# Debug template - main + helper pane
cat > "$TW_TEMPLATES_DIR/debug.tmux" << 'EOF'
# Debug workspace
# Main pane (70%) + helper pane (30%)
split-window -h -p 30
select-pane -t 0
EOF
# Full template - single pane
cat > "$TW_TEMPLATES_DIR/full.tmux" << 'EOF'
# Full workspace
# Single full-screen pane (default tmux behavior)
# Full workspace - single pane
EOF
# Code review template - side-by-side comparison
cat > "$TW_TEMPLATES_DIR/review.tmux" << 'EOF'
# Code Review workspace
# Two equal panes side-by-side for comparison
split-window -h -p 50
select-pane -t 0
EOF
@@ -178,42 +100,29 @@ EOF
tw-templates() {
_tw_init_templates
echo -e "${TW_BLUE}╔════════════════════════════════════════════════════════════╗${TW_NC}"
echo -e "${TW_BLUE}${TW_NC} Available Tmux Templates ${TW_BLUE}${TW_NC}"
echo -e "${TW_BLUE}╚════════════════════════════════════════════════════════════╝${TW_NC}"
echo -e "${DF_BLUE}╔════════════════════════════════════════════════════════════╗${DF_NC}"
echo -e "${DF_BLUE}${DF_NC} Available Tmux Templates ${DF_BLUE}${DF_NC}"
echo -e "${DF_BLUE}╚════════════════════════════════════════════════════════════╝${DF_NC}"
echo
for template in "$TW_TEMPLATES_DIR"/*.tmux; do
[[ ! -f "$template" ]] && continue
local name=$(basename "$template" .tmux)
local description=$(grep "^#" "$template" | head -2 | tail -1 | sed 's/^# *//')
echo -e "${TW_GREEN}${TW_NC} ${TW_CYAN}$name${TW_NC}"
echo -e "${DF_GREEN}${DF_NC} ${DF_CYAN}$name${DF_NC}"
[[ -n "$description" ]] && echo " $description"
done
echo
echo "Create workspace: ${TW_CYAN}tw-create myproject dev${TW_NC}"
echo "Quick attach: ${TW_CYAN}tw myproject${TW_NC}"
echo "Create workspace: ${DF_CYAN}tw-create myproject dev${DF_NC}"
echo "Quick attach: ${DF_CYAN}tw myproject${DF_NC}"
}
tw-template-edit() {
local template_name="$1"
if [[ -z "$template_name" ]]; then
echo "Usage: tw-template-edit <template_name>"
echo
tw-templates
return 1
fi
[[ -z "$template_name" ]] && { echo "Usage: tw-template-edit <template_name>"; tw-templates; return 1; }
_tw_init_templates
local template_file="$TW_TEMPLATES_DIR/${template_name}.tmux"
${EDITOR:-vim} "$template_file"
${EDITOR:-vim} "$TW_TEMPLATES_DIR/${template_name}.tmux"
_tw_print_success "Template edited: $template_name"
}
@@ -225,26 +134,19 @@ tw-create() {
local workspace_name="$1"
local template="${2:-$TW_DEFAULT_TEMPLATE}"
if [[ -z "$workspace_name" ]]; then
echo "Usage: tw-create <workspace_name> [template]"
echo
tw-templates
return 1
fi
[[ -z "$workspace_name" ]] && { echo "Usage: tw-create <workspace_name> [template]"; tw-templates; return 1; }
_tw_check_tmux || return 1
_tw_init_templates
local session_name="${TW_SESSION_PREFIX}-${workspace_name}"
# Check if session already exists
if tmux has-session -t "$session_name" 2>/dev/null; then
_tw_print_error "Workspace '$workspace_name' already exists"
echo "Use: ${TW_CYAN}tw $workspace_name${TW_NC} to attach"
echo "Use: ${DF_CYAN}tw $workspace_name${DF_NC} to attach"
return 1
fi
# Check if template exists
local template_file="$TW_TEMPLATES_DIR/${template}.tmux"
if [[ ! -f "$template_file" ]]; then
_tw_print_error "Template '$template' not found"
@@ -254,14 +156,10 @@ tw-create() {
_tw_print_step "Creating workspace: $workspace_name (template: $template)"
# Create new tmux session (detached)
tmux new-session -d -s "$session_name"
# Apply template
_tw_print_step "Applying template: $template"
tmux source-file "$template_file" -t "$session_name"
# Set working directory if we're in a git repo or specific directory
if git rev-parse --git-dir &>/dev/null 2>&1; then
local git_root=$(git rev-parse --show-toplevel)
_tw_print_info "Setting workspace directory to: $git_root"
@@ -270,24 +168,17 @@ tw-create() {
_tw_print_success "Workspace created: $workspace_name"
# Attach if not already in tmux
if [[ -z "$TMUX" ]]; then
_tw_print_step "Attaching to workspace..."
tmux attach-session -t "$session_name"
else
_tw_print_info "Switch with: ${TW_CYAN}tmux switch-client -t $session_name${TW_NC}"
_tw_print_info "Switch with: ${DF_CYAN}tmux switch-client -t $session_name${DF_NC}"
fi
}
tw-attach() {
local workspace_name="$1"
if [[ -z "$workspace_name" ]]; then
echo "Usage: tw-attach <workspace_name>"
echo
tw-list
return 1
fi
[[ -z "$workspace_name" ]] && { echo "Usage: tw-attach <workspace_name>"; tw-list; return 1; }
_tw_check_tmux || return 1
@@ -295,12 +186,10 @@ tw-attach() {
if ! tmux has-session -t "$session_name" 2>/dev/null; then
_tw_print_error "Workspace '$workspace_name' not found"
echo
echo "Create it with: ${TW_CYAN}tw-create $workspace_name${TW_NC}"
echo "Create it with: ${DF_CYAN}tw-create $workspace_name${DF_NC}"
return 1
fi
# Attach or switch
if [[ -z "$TMUX" ]]; then
tmux attach-session -t "$session_name"
else
@@ -311,48 +200,38 @@ tw-attach() {
tw-list() {
_tw_check_tmux || return 1
echo -e "${TW_BLUE}╔════════════════════════════════════════════════════════════╗${TW_NC}"
echo -e "${TW_BLUE}${TW_NC} Active Tmux Workspaces ${TW_BLUE}${TW_NC}"
echo -e "${TW_BLUE}╚════════════════════════════════════════════════════════════╝${TW_NC}"
echo -e "${DF_BLUE}╔════════════════════════════════════════════════════════════╗${DF_NC}"
echo -e "${DF_BLUE}${DF_NC} Active Tmux Workspaces ${DF_BLUE}${DF_NC}"
echo -e "${DF_BLUE}╚════════════════════════════════════════════════════════════╝${DF_NC}"
echo
local has_workspaces=false
# List all tmux sessions
tmux list-sessions 2>/dev/null | while IFS=: read -r session_full rest; do
# Only show sessions with our prefix
if [[ "$session_full" == ${TW_SESSION_PREFIX}-* ]]; then
has_workspaces=true
local workspace_name="${session_full#${TW_SESSION_PREFIX}-}"
local attached=""
# Check if currently attached
if [[ -n "$TMUX" ]]; then
local current_session=$(tmux display-message -p '#S')
[[ "$current_session" == "$session_full" ]] && attached=" ${TW_GREEN}(current)${TW_NC}"
[[ "$current_session" == "$session_full" ]] && attached=" ${DF_GREEN}(current)${DF_NC}"
fi
echo -e "${TW_GREEN}${TW_NC} ${TW_CYAN}$workspace_name${TW_NC}$attached"
echo -e "${DF_GREEN}${DF_NC} ${DF_CYAN}$workspace_name${DF_NC}$attached"
echo " Session: $session_full"
fi
done
if [[ "$has_workspaces" != true ]]; then
_tw_print_info "No active workspaces"
echo
echo "Create one with: ${TW_CYAN}tw-create myproject${TW_NC}"
echo "Create one with: ${DF_CYAN}tw-create myproject${DF_NC}"
fi
}
tw-delete() {
local workspace_name="$1"
if [[ -z "$workspace_name" ]]; then
echo "Usage: tw-delete <workspace_name>"
echo
tw-list
return 1
fi
[[ -z "$workspace_name" ]] && { echo "Usage: tw-delete <workspace_name>"; tw-list; return 1; }
_tw_check_tmux || return 1
@@ -363,75 +242,45 @@ tw-delete() {
return 1
fi
# Kill session
tmux kill-session -t "$session_name"
_tw_print_success "Deleted workspace: $workspace_name"
}
# ============================================================================
# Save Current Layout as Template
# ============================================================================
tw-save() {
local template_name="$1"
if [[ -z "$template_name" ]]; then
echo "Usage: tw-save <template_name>"
echo
echo "Saves the current tmux window layout as a reusable template"
return 1
fi
[[ -z "$template_name" ]] && { echo "Usage: tw-save <template_name>"; return 1; }
_tw_check_tmux || return 1
if [[ -z "$TMUX" ]]; then
_tw_print_error "Must be run from inside tmux"
return 1
fi
[[ -z "$TMUX" ]] && { _tw_print_error "Must be run from inside tmux"; return 1; }
_tw_init_templates
local template_file="$TW_TEMPLATES_DIR/${template_name}.tmux"
if [[ -f "$template_file" ]]; then
[[ -f "$template_file" ]] && {
read -q "REPLY?Template '$template_name' exists. Overwrite? [y/N]: "
echo
[[ ! "$REPLY" =~ ^[Yy]$ ]] && return 1
fi
}
_tw_print_step "Saving current layout as template: $template_name"
# Get current window layout
local layout=$(tmux display-message -p '#{window_layout}')
local pane_count=$(tmux display-message -p '#{window_panes}')
# Create template with layout commands
cat > "$template_file" << EOF
# Custom template: $template_name
# Saved: $(date)
# Panes: $pane_count
# Note: This is a simplified layout recreation
# You may need to adjust split percentages and commands
EOF
# Generate split commands based on pane count
if (( pane_count == 2 )); then
echo "split-window -h -p 50" >> "$template_file"
elif (( pane_count == 3 )); then
cat >> "$template_file" << 'EOF'
split-window -h -p 50
split-window -v -p 50
EOF
echo "split-window -h -p 50" >> "$template_file"
echo "split-window -v -p 50" >> "$template_file"
elif (( pane_count == 4 )); then
cat >> "$template_file" << 'EOF'
split-window -h -p 50
split-window -v -p 50
select-pane -t 0
split-window -v -p 50
EOF
echo "split-window -h -p 50" >> "$template_file"
echo "split-window -v -p 50" >> "$template_file"
echo "select-pane -t 0" >> "$template_file"
echo "split-window -v -p 50" >> "$template_file"
fi
echo "" >> "$template_file"
@@ -439,27 +288,19 @@ EOF
_tw_print_success "Template saved: $template_name"
echo " File: $template_file"
echo " Edit: ${TW_CYAN}tw-template-edit $template_name${TW_NC}"
echo " Edit: ${DF_CYAN}tw-template-edit $template_name${DF_NC}"
}
# ============================================================================
# Quick Workspace (attach or create)
# ============================================================================
tw() {
local workspace_name="$1"
local template="${2:-$TW_DEFAULT_TEMPLATE}"
if [[ -z "$workspace_name" ]]; then
tw-list
return 0
fi
[[ -z "$workspace_name" ]] && { tw-list; return 0; }
_tw_check_tmux || return 1
local session_name="${TW_SESSION_PREFIX}-${workspace_name}"
# If session exists, attach. Otherwise create.
if tmux has-session -t "$session_name" 2>/dev/null; then
tw-attach "$workspace_name"
else
@@ -468,10 +309,6 @@ tw() {
fi
}
# ============================================================================
# Fuzzy Search (requires fzf)
# ============================================================================
twf() {
if ! command -v fzf &>/dev/null; then
_tw_print_error "fzf not installed"
@@ -480,7 +317,6 @@ twf() {
_tw_check_tmux || return 1
# Get list of sessions
local sessions=()
tmux list-sessions 2>/dev/null | while IFS=: read -r session_full rest; do
if [[ "$session_full" == ${TW_SESSION_PREFIX}-* ]]; then
@@ -489,57 +325,33 @@ twf() {
fi
done
if [[ ${#sessions[@]} -eq 0 ]]; then
_tw_print_info "No workspaces found"
return 1
fi
[[ ${#sessions[@]} -eq 0 ]] && { _tw_print_info "No workspaces found"; return 1; }
# Fuzzy select
local selection=$(printf '%s\n' "${sessions[@]}" | \
fzf --height=40% \
--layout=reverse \
--border=rounded \
--prompt='Workspace > ' \
--preview='tmux list-windows -t work-{} 2>/dev/null || echo "No preview"')
fzf --height=40% --layout=reverse --border=rounded --prompt='Workspace > ')
if [[ -n "$selection" ]]; then
tw-attach "$selection"
fi
[[ -n "$selection" ]] && tw-attach "$selection"
}
# ============================================================================
# Pane Synchronization Toggle
# ============================================================================
tw-sync() {
if [[ -z "$TMUX" ]]; then
_tw_print_error "Must be run from inside tmux"
return 1
fi
[[ -z "$TMUX" ]] && { _tw_print_error "Must be run from inside tmux"; return 1; }
local current=$(tmux show-window-option -v synchronize-panes 2>/dev/null)
if [[ "$current" == "on" ]]; then
tmux set-window-option synchronize-panes off
_tw_print_info "Pane synchronization: ${TW_RED}OFF${TW_NC}"
_tw_print_info "Pane synchronization: ${DF_RED}OFF${DF_NC}"
else
tmux set-window-option synchronize-panes on
_tw_print_info "Pane synchronization: ${TW_GREEN}ON${TW_NC}"
_tw_print_info "Pane synchronization: ${DF_GREEN}ON${DF_NC}"
fi
}
# ============================================================================
# Rename Workspace
# ============================================================================
tw-rename() {
local old_name="$1"
local new_name="$2"
if [[ -z "$old_name" || -z "$new_name" ]]; then
echo "Usage: tw-rename <old_name> <new_name>"
return 1
fi
[[ -z "$old_name" || -z "$new_name" ]] && { echo "Usage: tw-rename <old_name> <new_name>"; return 1; }
_tw_check_tmux || return 1
@@ -552,7 +364,6 @@ tw-rename() {
fi
tmux rename-session -t "$old_session" "$new_session"
_tw_print_success "Renamed: $old_name$new_name"
}

155
zsh/lib/colors.zsh Normal file
View File

@@ -0,0 +1,155 @@
# ============================================================================
# Shared Color Definitions for Dotfiles
# ============================================================================
# Source this file in scripts and functions to get consistent color support.
#
# Usage in zsh functions:
# source "${0:A:h}/../lib/colors.zsh" 2>/dev/null || source "$HOME/.dotfiles/zsh/lib/colors.zsh"
#
# Usage in bash scripts:
# source "$HOME/.dotfiles/zsh/lib/colors.zsh"
#
# All variables are prefixed with DF_ (dotfiles) to avoid conflicts.
# ============================================================================
# Prevent double-sourcing
[[ -n "$_DF_COLORS_LOADED" ]] && return 0
typeset -g _DF_COLORS_LOADED=1
# ============================================================================
# Standard Colors (ANSI escape codes)
# ============================================================================
typeset -g DF_RED=$'\033[0;31m'
typeset -g DF_GREEN=$'\033[0;32m'
typeset -g DF_YELLOW=$'\033[1;33m'
typeset -g DF_BLUE=$'\033[0;34m'
typeset -g DF_MAGENTA=$'\033[0;35m'
typeset -g DF_CYAN=$'\033[0;36m'
typeset -g DF_WHITE=$'\033[0;37m'
# Bold variants
typeset -g DF_BOLD_RED=$'\033[1;31m'
typeset -g DF_BOLD_GREEN=$'\033[1;32m'
typeset -g DF_BOLD_YELLOW=$'\033[1;33m'
typeset -g DF_BOLD_BLUE=$'\033[1;34m'
typeset -g DF_BOLD_MAGENTA=$'\033[1;35m'
typeset -g DF_BOLD_CYAN=$'\033[1;36m'
typeset -g DF_BOLD_WHITE=$'\033[1;37m'
# Text styles
typeset -g DF_BOLD=$'\033[1m'
typeset -g DF_DIM=$'\033[2m'
typeset -g DF_ITALIC=$'\033[3m'
typeset -g DF_UNDERLINE=$'\033[4m'
typeset -g DF_RESET=$'\033[0m'
typeset -g DF_NC=$'\033[0m' # Alias for reset (No Color)
# ============================================================================
# 256-Color Palette (used in theme and MOTD)
# ============================================================================
typeset -g DF_GREY=$'\033[38;5;242m'
typeset -g DF_LIGHT_GREY=$'\033[38;5;248m'
typeset -g DF_DARK_GREY=$'\033[38;5;239m'
typeset -g DF_ORANGE=$'\033[38;5;208m'
typeset -g DF_LIGHT_ORANGE=$'\033[38;5;220m'
typeset -g DF_PINK=$'\033[38;5;213m'
typeset -g DF_PURPLE=$'\033[38;5;141m'
typeset -g DF_LIGHT_BLUE=$'\033[38;5;39m'
typeset -g DF_LIGHT_GREEN=$'\033[38;5;82m'
typeset -g DF_BRIGHT_GREEN=$'\033[38;5;118m'
typeset -g DF_TEAL=$'\033[38;5;51m'
# ============================================================================
# Semantic Colors (for consistent UI)
# ============================================================================
typeset -g DF_SUCCESS="$DF_GREEN"
typeset -g DF_ERROR="$DF_RED"
typeset -g DF_WARNING="$DF_YELLOW"
typeset -g DF_INFO="$DF_CYAN"
typeset -g DF_HINT="$DF_DIM"
typeset -g DF_ACCENT="$DF_BLUE"
typeset -g DF_MUTED="$DF_GREY"
# ============================================================================
# Common Print Functions
# ============================================================================
# Print a step/section header
df_print_step() {
echo -e "${DF_GREEN}==>${DF_NC} $1"
}
# Print success message
df_print_success() {
echo -e "${DF_GREEN}${DF_NC} $1"
}
# Print error message (to stderr)
df_print_error() {
echo -e "${DF_RED}${DF_NC} $1" >&2
}
# Print warning message
df_print_warning() {
echo -e "${DF_YELLOW}${DF_NC} $1"
}
# Print info message
df_print_info() {
echo -e "${DF_CYAN}${DF_NC} $1"
}
# Print a section divider
df_print_section() {
echo ""
echo -e "${DF_BLUE}${DF_NC} $1"
echo -e "${DF_CYAN}─────────────────────────────────────────────────────────────${DF_NC}"
}
# ============================================================================
# MOTD-Style Header Function
# ============================================================================
# Prints a standardized header box for scripts
# Usage: df_print_header "script-name"
df_print_header() {
local script_name="${1:-script}"
local user="${USER:-root}"
local hostname="${HOST:-${HOSTNAME:-$(hostname -s 2>/dev/null)}}"
local datetime=$(date '+%a %b %d %H:%M')
local width=66
# Build horizontal line
local hline=""
for ((i=0; i<width; i++)); do hline+="═"; done
local inner=$((width - 2))
# Header content
local h_left="${user}@${hostname}"
local h_center="${script_name}"
local h_right="${datetime}"
local h_pad=$(((inner - ${#h_left} - ${#h_center} - ${#h_right}) / 2))
local h_spaces=""
for ((i=0; i<h_pad; i++)); do h_spaces+=" "; done
echo ""
echo -e "${DF_GREY}${hline}${DF_NC}"
echo -e "${DF_GREY}${DF_NC} ${DF_BOLD}${DF_LIGHT_BLUE}${h_left}${DF_NC}${h_spaces}${DF_DIM}${h_center}${h_spaces}${h_right}${DF_NC} ${DF_GREY}${DF_NC}"
echo -e "${DF_GREY}${hline}${DF_NC}"
echo ""
}
# ============================================================================
# Bash Compatibility
# ============================================================================
# For bash scripts, export as regular variables too
if [[ -n "$BASH_VERSION" ]]; then
export DF_RED DF_GREEN DF_YELLOW DF_BLUE DF_MAGENTA DF_CYAN DF_WHITE
export DF_BOLD DF_DIM DF_RESET DF_NC
export DF_GREY DF_LIGHT_BLUE DF_LIGHT_GREEN
export DF_SUCCESS DF_ERROR DF_WARNING DF_INFO
fi