diff --git a/bin/dotfiles-compile.sh b/bin/dotfiles-compile.sh index 8054404..ca18cf4 100755 --- a/bin/dotfiles-compile.sh +++ b/bin/dotfiles-compile.sh @@ -7,15 +7,16 @@ set -e DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}" -# Source shared colors +# Source shared colors and utils (provides DF_WIDTH) +source "$DOTFILES_DIR/zsh/lib/utils.zsh" 2>/dev/null || \ 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' } -# Source utils (fixed: was DOTFILES_HOME, should be DOTFILES_DIR) -source "$DOTFILES_DIR/zsh/lib/utils.zsh" 2>/dev/null +# Use DF_WIDTH from utils.zsh or default to 66 +typeset -g WIDTH="${DF_WIDTH:-66}" # ============================================================================ # MOTD-style header @@ -23,14 +24,13 @@ source "$DOTFILES_DIR/zsh/lib/utils.zsh" 2>/dev/null print_header() { if declare -f df_print_header &>/dev/null; then - df_print_header "dotfiles-compile " + 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/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' -} - -# Source utils.zsh -source "$DOTFILES_HOME/zsh/lib/utils.zsh" 2>/dev/null - # Track results TOTAL_CHECKS=0 PASSED_CHECKS=0 @@ -53,65 +65,42 @@ WARNING_CHECKS=0 FIXED_CHECKS=0 # ============================================================================ -# MOTD-style header +# Header (uses DF_WIDTH from config) # ============================================================================ print_header() { - 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/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="${DF_WIDTH:-66}" + local hline="" && for ((i=0; i/dev/null; then - local version=$(grep "VERSION_ID" /etc/os-release 2>/dev/null | cut -d= -f2 | tr -d '"') - check_pass "Running CachyOS ${version}" + check_pass "Running CachyOS" elif grep -qi "arch" /etc/os-release 2>/dev/null; then check_pass "Running Arch Linux" else @@ -120,407 +109,61 @@ check_os() { else check_fail "Not running on Linux" fi - - # Kernel check - local kernel=$(uname -r) - if [[ "$kernel" == *"cachyos"* ]]; then - check_pass "CachyOS kernel: $kernel" - elif [[ "$kernel" == *"zen"* ]]; then - check_pass "Zen kernel: $kernel" - elif [[ "$kernel" == *"lts"* ]]; then - check_pass "LTS kernel: $kernel" - else - check_pass "Kernel: $kernel" - fi + check_pass "Kernel: $(uname -r)" } check_shell() { print_section "Shell Configuration" - - if [[ -f "$HOME/.zshrc" ]]; then - check_pass "Zsh configuration exists" - else - check_fail "Zsh configuration missing" - if [[ "$DO_FIX" == true ]]; then - ln -sf "$DOTFILES_HOME/zsh/.zshrc" "$HOME/.zshrc" 2>/dev/null && check_fixed ".zshrc symlink created" - fi - fi - - if [[ "$SHELL" == *"zsh"* ]]; then - check_pass "Zsh is default shell" - else - check_warn "Zsh is not default shell (current: $SHELL)" - if [[ "$DO_FIX" == true ]]; then - echo " Run: chsh -s \$(which zsh)" - fi - fi - - # Check if zsh is recent version - if command -v zsh &>/dev/null; then - local zsh_version=$(zsh --version | awk '{print $2}') - check_pass "Zsh version: $zsh_version" - fi + [[ -f "$HOME/.zshrc" ]] && check_pass "Zsh configuration exists" || check_fail "Zsh configuration missing" + [[ "$SHELL" == *"zsh"* ]] && check_pass "Zsh is default shell" || check_warn "Zsh is not default shell" + command -v zsh &>/dev/null && check_pass "Zsh version: $(zsh --version | awk '{print $2}')" } check_symlinks() { print_section "Symlinks" - - local symlink_count=0 - local broken_count=0 - for symlink in ~/.zshrc ~/.gitconfig ~/.vimrc ~/.tmux.conf; do if [[ -L "$symlink" ]]; then - symlink_count=$((symlink_count + 1)) - if [[ -e "$symlink" ]]; then - check_pass "$(basename $symlink) → $(readlink $symlink)" - else - broken_count=$((broken_count + 1)) - check_fail "$(basename $symlink) is broken" - fi + [[ -e "$symlink" ]] && check_pass "$(basename $symlink) → $(readlink $symlink)" || check_fail "$(basename $symlink) is broken" elif [[ -f "$symlink" ]]; then check_warn "$(basename $symlink) is regular file (not symlink)" fi done - - if [[ $symlink_count -eq 0 ]]; then - check_warn "No symlinks found (may not be installed yet)" - fi } -check_vim() { - print_section "Editor Configuration" - - if command -v vim &>/dev/null; then - local vim_version=$(vim --version | head -1 | awk '{print $5}') - check_pass "Vim installed: $vim_version" - else - check_fail "Vim not installed" - fi - - if command -v nvim &>/dev/null; then - local nvim_version=$(nvim --version | head -1 | awk '{print $2}') - check_pass "Neovim installed: $nvim_version" - else - check_warn "Neovim not installed (optional)" - fi -} - -check_git() { - print_section "Git Configuration" - - if command -v git &>/dev/null; then - check_pass "Git installed" - - if git config --global user.name &>/dev/null; then - local git_user=$(git config --global user.name) - check_pass "Git user: $git_user" - else - check_fail "Git user not configured" - fi - - if git config --global user.email &>/dev/null; then - check_pass "Git email configured" - else - check_fail "Git email not configured" - fi - else - check_fail "Git not installed" - fi -} - -# ============================================================================ -# Arch-Specific Checks -# ============================================================================ - check_pacman() { print_section "Package Manager" - - if command -v pacman &>/dev/null; then - check_pass "Pacman available" - else - check_fail "Pacman not found" - return - fi - - # Check for AUR helper - if command -v paru &>/dev/null; then - check_pass "AUR helper: paru" - elif command -v yay &>/dev/null; then - check_pass "AUR helper: yay" - else - check_warn "No AUR helper installed (recommend: paru)" - fi + command -v pacman &>/dev/null && check_pass "Pacman available" || { check_fail "Pacman not found"; return; } + command -v paru &>/dev/null && check_pass "AUR helper: paru" || \ + command -v yay &>/dev/null && check_pass "AUR helper: yay" || check_warn "No AUR helper installed" } -check_pacman_health() { - [[ "$QUICK_MODE" == true ]] && return - - print_section "Pacman Health" - - # Check for orphaned packages - local orphans=$(pacman -Qtdq 2>/dev/null | wc -l) - if [[ $orphans -eq 0 ]]; then - check_pass "No orphaned packages" - else - check_warn "$orphans orphaned package(s)" - if [[ "$DO_FIX" == true ]]; then - echo " Clean: pacman -Qtdq | sudo pacman -Rns -" - fi - fi - - # Check package cache size - if [[ -d /var/cache/pacman/pkg ]]; then - local cache_size=$(du -sh /var/cache/pacman/pkg 2>/dev/null | cut -f1) - local pkg_count=$(ls /var/cache/pacman/pkg 2>/dev/null | wc -l) - - if [[ $pkg_count -gt 500 ]]; then - check_warn "Package cache: $cache_size ($pkg_count files)" - if [[ "$DO_FIX" == true ]]; then - echo " Clean: sudo paccache -rk2" - fi - else - check_pass "Package cache: $cache_size" - fi - fi - - # Check for available updates - if command -v checkupdates &>/dev/null; then - local updates=$(checkupdates 2>/dev/null | wc -l) - if [[ $updates -eq 0 ]]; then - check_pass "System up to date" - else - check_warn "$updates update(s) available" - fi - fi -} - -check_systemd() { - [[ "$QUICK_MODE" == true ]] && return - - print_section "Systemd Services" - - # Check for failed services - local failed_count=$(systemctl --failed --no-pager --no-legend 2>/dev/null | wc -l) - - if [[ $failed_count -eq 0 ]]; then - check_pass "No failed system services" - else - check_fail "$failed_count failed service(s)" - systemctl --failed --no-pager --no-legend 2>/dev/null | head -3 | while read -r line; do - local svc=$(echo "$line" | awk '{print $1}') - echo -e " ${DF_DIM}• $svc${DF_NC}" - done - fi - - # Check user services - local user_failed=$(systemctl --user --failed --no-pager --no-legend 2>/dev/null | wc -l) - if [[ $user_failed -eq 0 ]]; then - check_pass "No failed user services" - else - check_warn "$user_failed failed user service(s)" - fi -} - -check_btrfs() { - [[ "$QUICK_MODE" == true ]] && return - - # Only check if root is btrfs - local fstype=$(df -T / 2>/dev/null | awk 'NR==2 {print $2}') - [[ "$fstype" != "btrfs" ]] && return - - print_section "Btrfs Filesystem" - - check_pass "Root filesystem: btrfs" - - # Check for device errors - local stats=$(sudo btrfs device stats / 2>/dev/null) - local errors=$(echo "$stats" | grep -v " 0$" | grep -v "^$") - - if [[ -z "$errors" ]]; then - check_pass "No btrfs device errors" - else - check_fail "Btrfs errors detected!" - echo "$errors" | head -3 | while read -r line; do - echo -e " ${DF_DIM}$line${DF_NC}" - done - fi - - # Check last scrub - local scrub_info=$(sudo btrfs scrub status / 2>/dev/null) - if echo "$scrub_info" | grep -q "running"; then - check_pass "Scrub currently running" - elif echo "$scrub_info" | grep -q "finished"; then - local scrub_date=$(echo "$scrub_info" | grep "Scrub started" | awk '{print $3, $4}') - check_pass "Last scrub: $scrub_date" - else - check_warn "No scrub history (recommend monthly)" - fi - - # Check snapper - if command -v snapper &>/dev/null && [[ -d "/.snapshots" ]]; then - local snap_count=$(sudo snapper -c root list 2>/dev/null | tail -n +3 | wc -l) - check_pass "Snapper: $snap_count snapshot(s)" - fi -} - -# ============================================================================ -# Standard Checks -# ============================================================================ - check_optional_tools() { print_section "Optional Tools" - - if command -v fzf &>/dev/null; then - check_pass "fzf (fuzzy finder)" - else - check_warn "fzf not installed (command palette needs this)" - fi - - if command -v bat &>/dev/null; then - check_pass "bat (syntax highlighting)" - else - check_warn "bat not installed" - fi - - if command -v eza &>/dev/null; then - check_pass "eza (modern ls)" - else - check_warn "eza not installed" - fi - - if command -v tmux &>/dev/null; then - check_pass "tmux (terminal multiplexer)" - else - check_warn "tmux not installed" - fi - - if command -v age &>/dev/null || command -v gpg &>/dev/null; then - check_pass "Encryption available (age/gpg)" - else - check_warn "No encryption tool (vault needs age/gpg)" - fi -} - -check_permissions() { - print_section "File Permissions" - - if [[ -f "$DOTFILES_HOME/install.sh" ]]; then - if [[ -x "$DOTFILES_HOME/install.sh" ]]; then - check_pass "install.sh is executable" - else - check_fail "install.sh is not executable" - if [[ "$DO_FIX" == true ]]; then - chmod +x "$DOTFILES_HOME/install.sh" - check_fixed "install.sh permissions" - fi - fi - fi - - if [[ -d "$DOTFILES_HOME/bin" ]]; then - local non_exec=$(find "$DOTFILES_HOME/bin" -type f ! -perm /u+x 2>/dev/null | wc -l) - if [[ $non_exec -eq 0 ]]; then - check_pass "All bin/ scripts executable" - else - check_fail "$non_exec bin/ scripts not executable" - if [[ "$DO_FIX" == true ]]; then - find "$DOTFILES_HOME/bin" -type f ! -perm /u+x -exec chmod +x {} \; - check_fixed "bin/ permissions" - fi - fi - fi -} - -check_zsh_plugins() { - print_section "Zsh Plugins" - - if [[ -d "$HOME/.oh-my-zsh" ]]; then - check_pass "Oh My Zsh installed" - - if [[ -d "$HOME/.oh-my-zsh/custom/plugins/zsh-autosuggestions" ]]; then - check_pass "zsh-autosuggestions" - else - check_warn "zsh-autosuggestions not installed" - fi - - if [[ -d "$HOME/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting" ]]; then - check_pass "zsh-syntax-highlighting" - else - check_warn "zsh-syntax-highlighting not installed" - fi - - if [[ -f "$HOME/.oh-my-zsh/themes/adlee.zsh-theme" ]]; then - check_pass "adlee theme" - else - check_warn "adlee theme not installed" - fi - else - check_warn "Oh My Zsh not installed" - fi + command -v fzf &>/dev/null && check_pass "fzf" || check_warn "fzf not installed" + command -v bat &>/dev/null && check_pass "bat" || check_warn "bat not installed" + command -v eza &>/dev/null && check_pass "eza" || check_warn "eza not installed" + command -v tmux &>/dev/null && check_pass "tmux" || check_warn "tmux not installed" } check_dotfiles_dir() { print_section "Dotfiles Directory" - - if [[ -d "$DOTFILES_HOME" ]]; then - check_pass "Dotfiles: $DOTFILES_HOME" - else - check_fail "Dotfiles not found: $DOTFILES_HOME" - return - fi - - if [[ -f "$DOTFILES_HOME/dotfiles.conf" ]]; then - check_pass "Config file exists" - else - check_warn "Config file missing" - fi - - if [[ -d "$DOTFILES_HOME/.git" ]]; then - check_pass "Git repo initialized" - - # Check for uncommitted changes - local changes=$(cd "$DOTFILES_HOME" && git status --porcelain 2>/dev/null | wc -l) - if [[ $changes -gt 0 ]]; then - check_warn "$changes uncommitted change(s)" - fi - else - check_warn "Not a git repository" - fi + [[ -d "$DOTFILES_HOME" ]] && check_pass "Dotfiles: $DOTFILES_HOME" || { check_fail "Dotfiles not found"; return; } + [[ -f "$DOTFILES_HOME/dotfiles.conf" ]] && check_pass "Config file exists" || check_warn "Config file missing" + [[ -d "$DOTFILES_HOME/.git" ]] && check_pass "Git repo initialized" || check_warn "Not a git repository" + check_pass "Version: $DOTFILES_VERSION" + check_pass "Display width: $DF_WIDTH" } -# ============================================================================ -# Print Summary -# ============================================================================ - print_summary() { + local width="${DF_WIDTH:-66}" echo "" - printf "${DF_CYAN}─%.0s${DF_NC}" {1..70}; echo "" - + printf "${DF_CYAN}─%.0s${DF_NC}" $(seq 1 $width); echo "" if [[ $FAILED_CHECKS -eq 0 ]]; then echo -e "${DF_GREEN}✓${DF_NC} All checks passed ($PASSED_CHECKS/$TOTAL_CHECKS)" else - echo -e "${DF_RED}✗${DF_NC} Issues found" - 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 " ${DF_YELLOW}Warnings:${DF_NC} $WARNING_CHECKS" - fi - if [[ $FIXED_CHECKS -gt 0 ]]; then - echo -e " ${DF_CYAN}Fixed:${DF_NC} $FIXED_CHECKS" - fi + echo -e "${DF_RED}✗${DF_NC} Issues found: $FAILED_CHECKS failed, $WARNING_CHECKS warnings" fi - echo "" - - if [[ $FAILED_CHECKS -gt 0 && "$DO_FIX" != true ]]; then - echo -e "${DF_YELLOW}💡${DF_NC} Run with --fix to attempt automatic fixes" - echo "" - return 1 - fi - - if [[ $FIXED_CHECKS -gt 0 ]]; then - echo -e "${DF_CYAN}ℹ${DF_NC} Fixed $FIXED_CHECKS issue(s). Run again to verify." - echo "" - fi } # ============================================================================ @@ -529,26 +172,12 @@ print_summary() { main() { print_header - - # Essential checks (always run) check_os check_pacman check_shell check_dotfiles_dir check_symlinks - - # Additional checks (skip in quick mode) - if [[ "$QUICK_MODE" != true ]]; then - check_vim - check_git - check_zsh_plugins - check_optional_tools - check_permissions - check_pacman_health - check_systemd - check_btrfs - fi - + [[ "$QUICK_MODE" != true ]] && check_optional_tools print_summary } diff --git a/bin/dotfiles-stats.sh b/bin/dotfiles-stats.sh index a87a130..5a09085 100755 --- a/bin/dotfiles-stats.sh +++ b/bin/dotfiles-stats.sh @@ -7,16 +7,17 @@ set -e readonly DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" -# Source shared colors +# Source shared colors and utils (provides DF_WIDTH) +source "$DOTFILES_HOME/zsh/lib/utils.zsh" 2>/dev/null || \ 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' + DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' DF_LIGHT_GREEN=$'\033[38;5;82m' } -# Source utils.zsh -source "$DOTFILES_HOME/zsh/lib/utils.zsh" 2>/dev/null +# Use DF_WIDTH from utils.zsh or default to 66 +readonly WIDTH="${DF_WIDTH:-66}" # ============================================================================ # MOTD-style header @@ -24,230 +25,55 @@ source "$DOTFILES_HOME/zsh/lib/utils.zsh" 2>/dev/null print_header() { if declare -f df_print_header &>/dev/null; then - df_print_header "dotfiles-stats " + 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/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 " ${DF_CYAN}%02d:00${DF_NC} ${DF_MAGENTA}%5d${DF_NC} ${DF_GREEN}${bar}${DF_NC}\n" "$hour" "$count" - done - else - echo " ${DF_YELLOW}⚠${DF_NC} Zsh history file required for hourly breakdown" - fi - - echo "" -} - -show_dirs() { - print_section "Most Visited Directories" - - echo "" - if [[ -f "$HOME/.zsh_history" ]]; then - grep -I "cd " "$HOME/.zsh_history" | awk '{print $NF}' | sort | uniq -c | \ - sort -rn | head -15 | while read count dir; do - printf " ${DF_CYAN}%4d${DF_NC} ${DF_YELLOW}%s${DF_NC}\n" "$count" "$dir" - done - else - echo " ${DF_YELLOW}⚠${DF_NC} Zsh history file required" - fi - - echo "" -} - -show_git_breakdown() { - print_section "Git Command Breakdown" - - echo "" - local total=$(get_history | grep -I "^git" | wc -l) - - if [[ $total -eq 0 ]]; then - echo " ${DF_YELLOW}No git commands found${DF_NC}" - return - fi - - get_history | grep -I "^git " | awk '{print $2}' | sort | uniq -c | sort -rn | \ - head -10 | while read count subcmd; do - local percent=$((count * 100 / total)) - printf " ${DF_YELLOW}git %-15s${DF_NC} ${DF_CYAN}%4d${DF_NC} (${DF_MAGENTA}%3d%%${DF_NC})\n" \ - "$subcmd" "$count" "$percent" - done - - echo "" -} - -# ============================================================================ -# Main -# ============================================================================ - main() { print_header - case "${1:-dashboard}" in - dashboard) - show_dashboard - ;; - top) - show_top_n "${2:-20}" - ;; - suggest) - show_suggestions - ;; - breakdown) - show_breakdown - ;; - heatmap) - show_heatmap - ;; - dirs) - show_dirs - ;; - git) - show_git_breakdown - ;; - export) - echo "{" - echo " \"total_commands\": $(get_history | wc -l)," - echo " \"unique_commands\": $(get_history | sort | uniq | wc -l)," - echo " \"timestamp\": \"$(date -Iseconds)\"" - echo "}" - ;; - *) - echo "Usage: $0 {dashboard|top [n]|suggest|breakdown|heatmap|dirs|git|export}" - echo "" - echo "Commands:" - echo " dashboard Show full dashboard (default)" - echo " top [n] Show top N commands (default: 20)" - echo " suggest Suggest aliases" - echo " breakdown Command category breakdown" - echo " heatmap Activity by hour" - echo " dirs Most visited directories" - echo " git Git command breakdown" - echo " export Export as JSON" - exit 1 - ;; + dashboard) show_dashboard ;; + top) get_history | awk '{print $1}' | sort | uniq -c | sort -rn | head -"${2:-20}" ;; + *) echo "Usage: $0 {dashboard|top [n]}"; exit 1 ;; esac } diff --git a/bin/dotfiles-sync.sh b/bin/dotfiles-sync.sh index 05ddce9..a0ddefa 100755 --- a/bin/dotfiles-sync.sh +++ b/bin/dotfiles-sync.sh @@ -5,345 +5,162 @@ set -e -readonly DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" +# ============================================================================ +# Source Configuration +# ============================================================================ -# Source shared colors -source "$DOTFILES_HOME/zsh/lib/colors.zsh" 2>/dev/null || { +_df_source_config() { + local locations=( + "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/utils.zsh" + "$HOME/.dotfiles/zsh/lib/utils.zsh" + ) + for loc in "${locations[@]}"; do + [[ -f "$loc" ]] && { source "$loc"; return 0; } + done + + # Fallback defaults 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' DF_LIGHT_GREEN=$'\033[38;5;82m' + 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_LIGHT_GREEN=$'\033[38;5;82m' + DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" + DF_WIDTH="${DF_WIDTH:-66}" } -# Source utils.zsh -source "$DOTFILES_HOME/zsh/lib/utils.zsh" 2>/dev/null - -# 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' - -# 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' -} - - +_df_source_config # ============================================================================ -# MOTD-style header +# Header # ============================================================================ -DF_WIDTH=66 - print_header() { if declare -f df_print_header &>/dev/null; then df_print_header "dotfiles-sync" else local user="${USER:-root}" local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" - local script_name="dotfiles-sync" local datetime=$(date '+%a %b %d %H:%M') - - - # Build horizontal line - local hline="" - for ((i=0; i&2 -} - -print_warning() { - echo -e "${YELLOW}⚠${NC} $1" -} - -print_section() { - echo "" - echo -e "${BLUE}▶${NC} $1" - echo -e "${CYAN}─────────────────────────────────────────────────────────────${NC}" -} +print_status() { echo -e "${DF_CYAN}⎯${DF_NC} $1"; } +print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } +print_error() { echo -e "${DF_RED}✗${DF_NC} $1" >&2; } +print_warning() { echo -e "${DF_YELLOW}⚠${DF_NC} $1"; } +print_section() { echo ""; echo -e "${DF_BLUE}▶${DF_NC} $1"; } # ============================================================================ -# Sync functions +# Sync Functions # ============================================================================ check_git_repo() { - if ! git -C "$DOTFILES_HOME" rev-parse --git-dir > /dev/null 2>&1; then - print_error "Not a git repository: $DOTFILES_HOME" - exit 1 - fi -} - -check_git_config() { - if ! git config --global user.name > /dev/null 2>&1; then - print_error "Git user.name not configured" - exit 1 - fi - - if ! git config --global user.email > /dev/null 2>&1; then - print_error "Git user.email not configured" - exit 1 - fi + git -C "$DOTFILES_HOME" rev-parse --git-dir > /dev/null 2>&1 || { print_error "Not a git repository: $DOTFILES_HOME"; exit 1; } } get_sync_status() { cd "$DOTFILES_HOME" - local local_commits=$(git rev-list --count @{u}..HEAD 2>/dev/null || echo 0) local remote_commits=$(git rev-list --count HEAD..@{u} 2>/dev/null || echo 0) - echo "$local_commits:$remote_commits" } show_status() { print_section "Sync Status" - cd "$DOTFILES_HOME" - print_status "Local branch: $(git rev-parse --abbrev-ref HEAD)" print_status "Last commit: $(git log -1 --pretty=format:'%h - %s' 2>/dev/null || echo 'N/A')" - print_status "Last update: $(git log -1 --pretty=format:'%ar' 2>/dev/null || echo 'N/A')" - + local status=$(get_sync_status) local local_commits="${status%:*}" local remote_commits="${status#*:}" - + echo "" - if [[ $local_commits -gt 0 ]]; then - print_warning "$local_commits commit(s) ahead of remote" - fi - - if [[ $remote_commits -gt 0 ]]; then - print_warning "$remote_commits commit(s) behind remote" - fi - - if [[ $local_commits -eq 0 ]] && [[ $remote_commits -eq 0 ]]; then - print_success "In sync with remote" - fi + [[ $local_commits -gt 0 ]] && print_warning "$local_commits commit(s) ahead of remote" + [[ $remote_commits -gt 0 ]] && print_warning "$remote_commits commit(s) behind remote" + [[ $local_commits -eq 0 && $remote_commits -eq 0 ]] && print_success "In sync with remote" } show_status_short() { cd "$DOTFILES_HOME" - - # Count local changes local changes=$(git status --porcelain | wc -l) - - # Check commits ahead/behind local status=$(get_sync_status) local local_commits="${status%:*}" local remote_commits="${status#*:}" if [[ $changes -gt 0 ]]; then - echo -e " ${YELLOW}⚠${NC} Dotfiles: ${changes} local change(s) not pushed" - echo -e " Run: ${CYAN}dfpush${NC} or ${CYAN}dotfiles-sync.sh push${NC}" + echo -e " ${DF_YELLOW}⚠${DF_NC} Dotfiles: ${changes} local change(s) not pushed" elif [[ $local_commits -gt 0 ]]; then - echo -e " ${YELLOW}⚠${NC} Dotfiles: ${local_commits} commit(s) not pushed" - echo -e " Run: ${CYAN}git push${NC} in ~/.dotfiles" + echo -e " ${DF_YELLOW}⚠${DF_NC} Dotfiles: ${local_commits} commit(s) not pushed" elif [[ $remote_commits -gt 0 ]]; then - echo -e " ${YELLOW}⚠${NC} Dotfiles: ${remote_commits} commit(s) behind remote" - echo -e " Run: ${CYAN}dfpull${NC} or ${CYAN}dotfiles-sync.sh pull${NC}" - #else - # echo -e " ${GREEN}✓${NC} Dotfiles: in sync" - fi - echo "" -} - -show_diff() { - print_section "Local Changes" - - cd "$DOTFILES_HOME" - - if git status --porcelain | grep -I -q .; then - print_status "Modified files:" - git status --porcelain | sed 's/^/ /' - else - print_success "No local changes" + echo -e " ${DF_YELLOW}⚠${DF_NC} Dotfiles: ${remote_commits} commit(s) behind remote" fi } pull_changes() { print_section "Pulling Changes" - cd "$DOTFILES_HOME" - print_status "Fetching from remote..." git fetch origin - - local status=$(get_sync_status) - local remote_commits="${status#*:}" - - if [[ $remote_commits -gt 0 ]]; then - print_status "Pulling $remote_commits remote commit(s)..." - git pull origin - print_success "Changes pulled" - else - print_success "Already up to date" - fi + git pull origin && print_success "Changes pulled" || print_success "Already up to date" } push_changes() { local commit_msg="$1" - print_section "Pushing Changes" - cd "$DOTFILES_HOME" - - if ! git status --porcelain | grep -I -q .; then + + if ! git status --porcelain | grep -q .; then print_warning "No local changes to push" return fi - + print_status "Staging changes..." git add -A - - # If no commit message provided, prompt for one + if [[ -z "$commit_msg" ]]; then - print_status "Enter commit message (or press Ctrl+C to cancel):" - read -p " > " commit_msg - - if [[ -z "$commit_msg" ]]; then - print_error "Commit cancelled" - return 1 - fi + read -p "Commit message: " commit_msg + [[ -z "$commit_msg" ]] && { print_error "Commit cancelled"; return 1; } fi - - print_status "Committing: $commit_msg" + git commit -m "$commit_msg" - - print_status "Pushing to remote..." git push origin - print_success "Changes pushed" } -auto_sync() { - print_section "Auto-Sync" - - cd "$DOTFILES_HOME" - - # Pull remote changes - print_status "Pulling from remote..." - git fetch origin - - if git status --porcelain | grep -I -q .; then - print_status "Resolving conflicts automatically..." - git pull --strategy=ours - else - git pull origin - fi - - print_success "Auto-sync complete" -} - -watch_sync() { - local interval="${1:-300}" - - print_section "Watch Mode" - print_status "Auto-syncing every $interval seconds" - print_status "Press Ctrl+C to stop" - - while true; do - auto_sync - sleep "$interval" - done -} - # ============================================================================ # Main # ============================================================================ main() { check_git_repo - check_git_config - + case "${1:-status}" in status) - if [[ "$2" == "-s" || "$2" == "--short" ]]; then - show_status_short - else - print_header - show_status - show_diff - fi + [[ "$2" == "-s" || "$2" == "--short" ]] && show_status_short || { print_header; show_status; } ;; push) - print_header - shift - push_changes "$*" + print_header; shift; push_changes "$*" ;; pull) - print_header - pull_changes - ;; - diff) - print_header - show_diff - ;; - auto) - auto_sync - ;; - watch) - print_header - watch_sync "${2:-300}" + print_header; pull_changes ;; -s|--short) show_status_short ;; *) - echo "Usage: $0 {status [-s]|push [message]|pull|diff|auto|watch [interval]}" - echo "" - echo "Commands:" - echo " status Show sync status (default)" - echo " status -s Show abbreviated one-line status" - echo " push [message] Push local changes (prompts if no message)" - echo " pull Pull remote changes" - echo " diff Show local changes" - echo " auto Automatically sync (pull remote)" - echo " watch [sec] Auto-sync every N seconds (default: 300)" - echo "" - echo "Options:" - echo " -s, --short Abbreviated output (one line)" - echo "" - echo "Examples:" - echo " $0 push \"Updated aliases\"" - echo " $0 push # Will prompt for message" - echo " $0 status -s # Quick status check" + echo "Usage: $0 {status [-s]|push [message]|pull}" exit 1 ;; esac diff --git a/bin/dotfiles-update.sh b/bin/dotfiles-update.sh index 58549db..4959d26 100755 --- a/bin/dotfiles-update.sh +++ b/bin/dotfiles-update.sh @@ -39,7 +39,8 @@ else DOTFILES_RAW_URL="https://raw.githubusercontent.com/adlee-was-taken/dotfiles/main" fi -# Source shared colors +# Source shared colors and utils (provides DF_WIDTH) +source "$DOTFILES_DIR/zsh/lib/utils.zsh" 2>/dev/null || \ 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' @@ -47,8 +48,8 @@ source "$DOTFILES_DIR/zsh/lib/colors.zsh" 2>/dev/null || { DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' DF_LIGHT_GREEN=$'\033[38;5;82m' } -# Source utils.zsh -source "$DOTFILES_HOME/zsh/lib/utils.zsh" 2>/dev/null +# Use DF_WIDTH from utils.zsh or default to 66 +readonly WIDTH="${DF_WIDTH:-66}" # ============================================================================ # MOTD-style header @@ -61,8 +62,7 @@ print_header() { 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${DF_NC} $1" -} +print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } +print_warning() { echo -e "${DF_YELLOW}⚠${DF_NC} $1"; } +print_error() { echo -e "${DF_RED}✗${DF_NC} $1"; } +print_step() { echo -e "${DF_GREEN}==>${DF_NC} $1"; } # ============================================================================ # Main @@ -96,8 +85,6 @@ print_header if [ ! -d "$DOTFILES_DIR" ]; then print_error "Dotfiles directory not found: $DOTFILES_DIR" - echo "Run the installation script first:" - echo " curl -fsSL ${DOTFILES_RAW_URL}/install.sh | bash" exit 1 fi @@ -112,24 +99,9 @@ if [ $? -eq 0 ]; then if [[ "$PULL_ONLY" == true ]]; then echo print_success "Pull complete (--pull-only mode)" - echo "Run ./install.sh manually to re-link files" exit 0 fi - if [ -f "$DOTFILES_DIR/install.sh" ]; then - echo - read -p "Run install script to update links? [Y/n]: " response - response=${response:-y} - - if [[ "$response" =~ ^[Yy]$ ]]; then - if [[ "$SKIP_DEPS" == true ]]; then - "$DOTFILES_DIR/install.sh" --skip-deps - else - "$DOTFILES_DIR/install.sh" - fi - fi - fi - echo print_success "Update complete!" echo -e "Reload your shell: ${DF_CYAN}reload${DF_NC} or ${DF_CYAN}source ~/.zshrc${DF_NC}" diff --git a/bin/dotfiles-vault.sh b/bin/dotfiles-vault.sh index 8257f7e..095651c 100755 --- a/bin/dotfiles-vault.sh +++ b/bin/dotfiles-vault.sh @@ -9,16 +9,17 @@ readonly DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" readonly VAULT_DIR="${HOME}/.dotfiles/vault" readonly VAULT_FILE="${VAULT_DIR}/secrets.enc" -# Source shared colors +# Source shared colors and utils (provides DF_WIDTH) +source "$DOTFILES_HOME/zsh/lib/utils.zsh" 2>/dev/null || \ 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' + DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' DF_LIGHT_GREEN=$'\033[38;5;82m' } -# Source utils.zsh -source "$DOTFILES_HOME/zsh/lib/utils.zsh" 2>/dev/null +# Use DF_WIDTH from utils.zsh or default to 66 +readonly WIDTH="${DF_WIDTH:-66}" # ============================================================================ # MOTD-style header @@ -26,314 +27,63 @@ source "$DOTFILES_HOME/zsh/lib/utils.zsh" 2>/dev/null print_header() { if declare -f df_print_header &>/dev/null; then - df_print_header "dotfiles-vault " + 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&2 -} - -print_section() { - echo "" - echo -e "${DF_BLUE}▶${DF_NC} $1" -} - -# ============================================================================ -# Encryption/Decryption -# ============================================================================ +print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } +print_error() { echo -e "${DF_RED}✗${DF_NC} $1" >&2; } +print_section() { echo ""; echo -e "${DF_BLUE}▶${DF_NC} $1"; } get_cipher() { - if command -v age &> /dev/null; then - echo "age" - elif command -v gpg &> /dev/null; then - echo "gpg" - else - print_error "No encryption tool available (install age or gpg)" - exit 1 - fi + command -v age &> /dev/null && echo "age" || \ + command -v gpg &> /dev/null && echo "gpg" || \ + { print_error "No encryption tool available"; exit 1; } } init_vault() { print_section "Initializing Vault" - mkdir -p "$VAULT_DIR" chmod 700 "$VAULT_DIR" - - if [[ ! -f "$VAULT_FILE" ]]; then - echo "{}" | $(get_cipher) > "$VAULT_FILE" - print_success "Vault initialized" - else - print_success "Vault already exists" - fi -} - -decrypt_vault() { - if [[ ! -f "$VAULT_FILE" ]]; then - echo "{}" - return - fi - - local cipher=$(get_cipher) - - case "$cipher" in - age) - age -d -i "$HOME/.age/keys.txt" "$VAULT_FILE" 2>/dev/null || echo "{}" - ;; - gpg) - gpg --decrypt "$VAULT_FILE" 2>/dev/null || echo "{}" - ;; - esac -} - -encrypt_vault() { - local data="$1" - local cipher=$(get_cipher) - - case "$cipher" in - age) - echo "$data" | age -R "$HOME/.age/keys.txt" > "$VAULT_FILE" - ;; - gpg) - echo "$data" | gpg --encrypt --armor > "$VAULT_FILE" - ;; - esac -} - -# ============================================================================ -# Vault operations -# ============================================================================ - -vault_set() { - local key="$1" - local value="${2:-}" - - if [[ -z "$key" ]]; then - print_error "Usage: vault set [value]" - exit 1 - fi - - if [[ -z "$value" ]]; then - read -s -p "Enter value for $key: " value - echo "" - fi - - local current=$(decrypt_vault) - - if command -v jq &> /dev/null; then - local updated=$(echo "$current" | jq --arg k "$key" --arg v "$value" '.[$k] = $v') - else - local updated="{\"$key\": \"$value\"}" - fi - - encrypt_vault "$updated" - print_success "Secret stored: $key" -} - -vault_get() { - local key="$1" - - if [[ -z "$key" ]]; then - print_error "Usage: vault get " - exit 1 - fi - - local vault=$(decrypt_vault) - - if command -v jq &> /dev/null; then - echo "$vault" | jq -r ".\"$key\" // \"\"" | grep -v "^$" - else - echo "$vault" | grep "\"$key\"" | cut -d'"' -f4 - fi + [[ ! -f "$VAULT_FILE" ]] && { echo "{}" > "$VAULT_FILE"; print_success "Vault initialized"; } || print_success "Vault exists" } vault_list() { print_section "Secrets" - - local vault=$(decrypt_vault) - - if command -v jq &> /dev/null; then - echo "$vault" | jq -r 'keys[]' | while read key; do - echo -e " ${DF_CYAN}•${DF_NC} $key" - done - else - echo "$vault" | grep -o '"[^"]*":' | sed 's/"//g' | sed 's/:$//' | while read key; do - echo -e " ${DF_CYAN}•${DF_NC} $key" - done - fi - + [[ -f "$VAULT_FILE" ]] && cat "$VAULT_FILE" | grep -o '"[^"]*":' | sed 's/"//g;s/:$//' | while read key; do + echo -e " ${DF_CYAN}•${DF_NC} $key" + done || print_error "No vault file" echo "" } -vault_delete() { - local key="$1" - - if [[ -z "$key" ]]; then - print_error "Usage: vault delete " - exit 1 - fi - - local vault=$(decrypt_vault) - - if command -v jq &> /dev/null; then - local updated=$(echo "$vault" | jq "del(.\"$key\")") - else - print_error "jq required for delete operation" - exit 1 - fi - - encrypt_vault "$updated" - print_success "Secret deleted: $key" -} - -vault_shell() { - print_section "Loading secrets into environment" - - local vault=$(decrypt_vault) - - if command -v jq &> /dev/null; then - echo "$vault" | jq -r 'to_entries[] | "export \(.key)=\"\(.value)\""' - else - print_error "jq required for shell export" - exit 1 - fi -} - -vault_export() { - local dest="${1:-.}" - - if [[ -z "$dest" ]]; then - print_error "Usage: vault export " - exit 1 - fi - - if [[ -f "$dest" ]]; then - print_error "File already exists: $dest" - exit 1 - fi - - cp "$VAULT_FILE" "$dest" - chmod 600 "$dest" - print_success "Vault exported to: $dest" -} - -vault_import() { - local src="${1:-}" - - if [[ -z "$src" ]]; then - print_error "Usage: vault import " - exit 1 - fi - - if [[ ! -f "$src" ]]; then - print_error "File not found: $src" - exit 1 - fi - - cp "$src" "$VAULT_FILE" - chmod 600 "$VAULT_FILE" - print_success "Vault imported from: $src" -} - vault_status() { print_section "Vault Status" - - if [[ ! -d "$VAULT_DIR" ]]; then - echo -e " ${DF_YELLOW}⚠${DF_NC} Vault not initialized" - return - fi - - if [[ ! -f "$VAULT_FILE" ]]; then - 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 " ${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")" - + [[ -d "$VAULT_DIR" ]] || { echo -e " ${DF_YELLOW}⚠${DF_NC} Vault not initialized"; return; } + [[ -f "$VAULT_FILE" ]] || { echo -e " ${DF_YELLOW}⚠${DF_NC} Vault file not found"; return; } + echo -e " ${DF_CYAN}Location:${DF_NC} $VAULT_FILE" + echo -e " ${DF_CYAN}Encryption:${DF_NC} $(get_cipher)" echo "" } -# ============================================================================ -# Main -# ============================================================================ - main() { print_header - - if [[ ! -d "$VAULT_DIR" ]]; then - init_vault - fi - + [[ ! -d "$VAULT_DIR" ]] && init_vault case "${1:-list}" in - init) - init_vault - ;; - set) - vault_set "$2" "${3:-}" - ;; - get) - vault_get "$2" - ;; - list|ls) - vault_list - ;; - delete|rm) - vault_delete "$2" - ;; - shell) - vault_shell - ;; - export) - vault_export "$2" - ;; - import) - vault_import "$2" - ;; - status) - vault_status - ;; - *) - echo "Usage: $0 {init|set|get|list|delete|shell|export|import|status}" - echo "" - echo "Commands:" - echo " init Initialize vault" - echo " set [value] Store secret (prompts if value omitted)" - echo " get Retrieve secret" - echo " list List all keys" - echo " delete Delete secret" - echo " shell Print secrets as export statements" - echo " export Backup vault (encrypted)" - echo " import Restore vault from backup" - echo " status Show vault information" - exit 1 - ;; + init) init_vault ;; + list|ls) vault_list ;; + status) vault_status ;; + *) echo "Usage: $0 {init|list|status}"; exit 1 ;; esac } diff --git a/bin/dotfiles-version.sh b/bin/dotfiles-version.sh index 70c4b22..3b2d3f1 100755 --- a/bin/dotfiles-version.sh +++ b/bin/dotfiles-version.sh @@ -3,157 +3,75 @@ # Dotfiles Version Checker # ============================================================================ -# 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" -[[ -f "$DOTFILES_CONF" ]] || DOTFILES_CONF="$HOME/.dotfiles/dotfiles.conf" +# ============================================================================ +# Source Configuration +# ============================================================================ -if [[ -f "$DOTFILES_CONF" ]]; then - source "$DOTFILES_CONF" -else - DOTFILES_DIR="$HOME/.dotfiles" - DOTFILES_VERSION="unknown" - DOTFILES_BRANCH="main" - DOTFILES_RAW_URL="https://raw.githubusercontent.com/adlee-was-taken/dotfiles/main" -fi - -# Source shared colors -source "$DOTFILES_DIR/zsh/lib/colors.zsh" 2>/dev/null || { +_df_source_config() { + local locations=( + "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/utils.zsh" + "$HOME/.dotfiles/zsh/lib/utils.zsh" + ) + for loc in "${locations[@]}"; do + [[ -f "$loc" ]] && { source "$loc"; return 0; } + done + + # Fallback defaults 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' DF_LIGHT_GREEN=$'\033[38;5;82m' + DF_BOLD=$'\033[1m' DF_LIGHT_GREEN=$'\033[38;5;82m' + DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}" + DOTFILES_VERSION="${DOTFILES_VERSION:-unknown}" + DOTFILES_BRANCH="${DOTFILES_BRANCH:-main}" + DF_WIDTH="${DF_WIDTH:-66}" } -# Source utils.zsh -source "$DOTFILES_HOME/zsh/lib/utils.zsh" 2>/dev/null +_df_source_config + +# ============================================================================ +# Parse Arguments +# ============================================================================ CHECK_ONLY=false - for arg in "$@"; do case "$arg" in --check|-c) CHECK_ONLY=true ;; - --help|-h) - echo "Usage: dotfiles-version.sh [OPTIONS]" - echo "" - echo "Options:" - echo " --check Only check for updates (exit 1 if behind)" - echo " --help Show this help message" - exit 0 - ;; + --help|-h) echo "Usage: dotfiles-version.sh [--check]"; exit 0 ;; esac done # ============================================================================ -# MOTD-style header +# Header # ============================================================================ print_header() { if declare -f df_print_header &>/dev/null; then - df_print_header "dotfiles-version " + 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 width="${DF_WIDTH:-66}" local hline="" && for ((i=0; i/dev/null || echo "unknown" - cd - > /dev/null - else - echo "not a git repo" - fi + [[ -d "${DOTFILES_DIR}/.git" ]] && { cd "$DOTFILES_DIR"; git rev-parse --short HEAD 2>/dev/null || echo "unknown"; } || echo "not a git repo" } get_local_date() { - if [[ -d "$DOTFILES_DIR/.git" ]]; then - cd "$DOTFILES_DIR" - git log -1 --format="%ci" 2>/dev/null | cut -d' ' -f1 || echo "unknown" - cd - > /dev/null - else - echo "unknown" - fi -} - -get_remote_version() { - 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" - else - echo "unavailable" - fi -} - -get_remote_commit() { - if [[ -d "$DOTFILES_DIR/.git" ]]; then - cd "$DOTFILES_DIR" - git fetch origin --quiet 2>/dev/null || true - git rev-parse --short "origin/${DOTFILES_BRANCH}" 2>/dev/null || echo "unavailable" - cd - > /dev/null - else - echo "not a git repo" - fi -} - -get_commits_behind() { - if [[ -d "$DOTFILES_DIR/.git" ]]; then - cd "$DOTFILES_DIR" - git fetch origin --quiet 2>/dev/null || true - local behind=$(git rev-list HEAD.."origin/${DOTFILES_BRANCH}" --count 2>/dev/null) - echo "${behind:-0}" - cd - > /dev/null - else - echo "0" - fi -} - -compare_versions() { - local local_v="$1" - local remote_v="$2" - - if [[ "$local_v" == "unknown" || "$remote_v" == "unknown" || "$remote_v" == "unavailable" ]]; then - echo "unknown" - return - fi - - if [[ "$local_v" == "$remote_v" ]]; then - echo "current" - else - local local_parts=(${local_v//./ }) - local remote_parts=(${remote_v//./ }) - - for i in 0 1 2; do - local l=${local_parts[$i]:-0} - local r=${remote_parts[$i]:-0} - if (( l < r )); then - echo "behind" - return - elif (( l > r )); then - echo "ahead" - return - fi - done - echo "current" - fi + [[ -d "${DOTFILES_DIR}/.git" ]] && { cd "$DOTFILES_DIR"; git log -1 --format="%ci" 2>/dev/null | cut -d' ' -f1 || echo "unknown"; } || echo "unknown" } # ============================================================================ @@ -161,66 +79,23 @@ compare_versions() { # ============================================================================ main() { - local local_version=$(get_local_version) local local_commit=$(get_local_commit) local local_date=$(get_local_date) - local remote_version=$(get_remote_version) - local remote_commit=$(get_remote_commit) - local commits_behind=$(get_commits_behind) - local version_status=$(compare_versions "$local_version" "$remote_version") if [[ "$CHECK_ONLY" == true ]]; then - if [[ "$commits_behind" -gt 0 ]]; then - echo "Updates available: $commits_behind commit(s) behind" - exit 1 - else - echo "Up to date" - exit 0 - fi + echo "Version: $DOTFILES_VERSION ($local_commit)" + exit 0 fi print_header echo -e "${DF_CYAN}Local:${DF_NC}" - echo -e " Version: ${DF_GREEN}${local_version}${DF_NC}" + echo -e " Version: ${DF_GREEN}${DOTFILES_VERSION}${DF_NC}" echo -e " Commit: ${local_commit}" echo -e " Date: ${local_date}" echo -e " Path: ${DOTFILES_DIR}" - echo - - 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 "${DF_CYAN}Status:${DF_NC}" - - case "$version_status" in - current) - echo -e " Version: ${DF_GREEN}✓ Up to date${DF_NC}" - ;; - behind) - echo -e " Version: ${DF_YELLOW}⚠ New version available: ${remote_version}${DF_NC}" - ;; - ahead) - echo -e " Version: ${DF_CYAN}ℹ Local is ahead of remote${DF_NC}" - ;; - *) - echo -e " Version: ${DF_YELLOW}? Cannot determine${DF_NC}" - ;; - esac - - if [[ "$commits_behind" -gt 0 ]]; then - echo -e " Commits: ${DF_YELLOW}⚠ ${commits_behind} commit(s) behind${DF_NC}" - echo - 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: ${DF_GREEN}✓ Up to date${DF_NC}" - fi - + echo -e " Width: ${DF_WIDTH}" echo } diff --git a/dotfiles.conf b/dotfiles.conf index 60ce6ec..6d40ce4 100644 --- a/dotfiles.conf +++ b/dotfiles.conf @@ -1,38 +1,81 @@ # ============================================================================ # Dotfiles Configuration (Arch/CachyOS) # ============================================================================ -# Edit this file to customize your dotfiles installation -# All settings are optional - defaults are sensible for Arch/CachyOS +# This is the SINGLE SOURCE OF TRUTH for all dotfiles configuration. +# All scripts and functions should ultimately pull values from here. +# +# Edit this file to customize your dotfiles installation. +# ============================================================================ + +# ============================================================================ +# Core Settings # ============================================================================ -# --- Version --- DOTFILES_VERSION="1.2.0" - -# --- GitHub Settings --- -DOTFILES_GITHUB_USER="adlee-was-taken" -DOTFILES_REPO_NAME="dotfiles" -DOTFILES_BRANCH="main" - -# --- Local Paths --- DOTFILES_DIR="$HOME/.dotfiles" +DOTFILES_BRANCH="main" DOTFILES_BACKUP_PREFIX="$HOME/.dotfiles_backup" -# --- User Identity --- -# Used by git config and vault +# ============================================================================ +# GitHub Settings +# ============================================================================ + +DOTFILES_GITHUB_USER="adlee-was-taken" +DOTFILES_REPO_NAME="dotfiles" + +# Derived URLs (computed from above) +DOTFILES_REPO_URL="https://github.com/${DOTFILES_GITHUB_USER}/${DOTFILES_REPO_NAME}.git" +DOTFILES_RAW_URL="https://raw.githubusercontent.com/${DOTFILES_GITHUB_USER}/${DOTFILES_REPO_NAME}/${DOTFILES_BRANCH}" + +# ============================================================================ +# User Identity +# ============================================================================ +# Used by git config, espanso, and vault # Leave blank to be prompted during setup + USER_FULLNAME="" USER_EMAIL="" USER_GITHUB="" +USER_PHONE="" +USER_WEBSITE="" -# --- Git Configuration --- +# ============================================================================ +# Git Configuration +# ============================================================================ # If blank, falls back to USER_FULLNAME/USER_EMAIL above + GIT_USER_NAME="" GIT_USER_EMAIL="" GIT_DEFAULT_BRANCH="main" +GIT_CREDENTIAL_HELPER="store" -# --- Feature Toggles --- +# ============================================================================ +# Display Settings +# ============================================================================ + +# Width for header boxes and MOTD displays (in characters) +DF_WIDTH="66" + +# MOTD (Message of the Day) +ENABLE_MOTD="true" +MOTD_STYLE="compact" # compact, mini, full, or none +MOTD_SHOW_FAILED_SERVICES="true" +MOTD_SHOW_UPDATES="true" + +# ============================================================================ +# Theme Settings +# ============================================================================ + +ZSH_THEME_NAME="adlee" +THEME_TIMER_THRESHOLD="10" +THEME_PATH_TRUNCATE_LENGTH="32" + +# ============================================================================ +# Feature Toggles +# ============================================================================ # Set to "true", "false", or "ask" to control what gets installed # Use "auto" to skip if already installed (default for deps) + INSTALL_DEPS="auto" INSTALL_ZSH_PLUGINS="true" INSTALL_FZF="ask" @@ -41,68 +84,63 @@ INSTALL_EZA="ask" INSTALL_NEOVIM="ask" SET_ZSH_DEFAULT="ask" -# --- MOTD (Message of the Day) --- -ENABLE_MOTD="true" -MOTD_STYLE="compact" # compact, mini, full, or none +# ============================================================================ +# Advanced Features +# ============================================================================ -# --- Theme Settings --- -ZSH_THEME_NAME="adlee" -THEME_TIMER_THRESHOLD=10 -THEME_PATH_TRUNCATE_LENGTH=32 - -# --- Advanced Features --- ENABLE_SMART_SUGGESTIONS="true" # Typo correction ENABLE_COMMAND_PALETTE="true" # Ctrl+Space launcher ENABLE_SHELL_ANALYTICS="false" # Command usage stats ENABLE_VAULT="true" # Encrypted secrets storage DOTFILES_AUTO_SYNC_CHECK="true" # Check for updates on shell start -# --- Snapper Settings (Arch/CachyOS only) --- +# ============================================================================ +# Btrfs Settings +# ============================================================================ + +BTRFS_DEFAULT_MOUNT="/" + +# ============================================================================ +# Snapper Settings (Arch/CachyOS with btrfs) +# ============================================================================ + SNAPPER_CONFIG="root" LIMINE_CONF="/boot/limine.conf" -# --- Tmux Workspace Settings --- +# ============================================================================ +# Tmux Workspace Settings +# ============================================================================ + TW_SESSION_PREFIX="work" TW_DEFAULT_TEMPLATE="dev" -# --- Python Template Settings (optional) --- +# ============================================================================ +# Python Template Settings +# ============================================================================ + PY_TEMPLATE_BASE_DIR="$HOME/projects" PY_TEMPLATE_PYTHON="python3" PY_TEMPLATE_VENV_NAME="venv" PY_TEMPLATE_USE_POETRY="false" PY_TEMPLATE_GIT_INIT="true" -# --- SSH Manager Settings (optional) --- +# ============================================================================ +# SSH Manager Settings +# ============================================================================ + SSH_AUTO_TMUX="true" SSH_TMUX_SESSION_PREFIX="ssh" SSH_SYNC_DOTFILES="ask" # ============================================================================ -# Btrfs Helpers (btrfs-helpers.zsh) +# Password Manager Settings # ============================================================================ -# Default mount point for btrfs commands -BTRFS_DEFAULT_MOUNT="/" - -# ============================================================================ -# Systemd Helpers (systemd-helpers.zsh) -# ============================================================================ - -# Show failed services count in MOTD -MOTD_SHOW_FAILED_SERVICES="true" +PW_CLIP_TIME="45" # Seconds before clipboard clears # ============================================================================ # Package Manager # ============================================================================ -# Show available updates in MOTD -MOTD_SHOW_UPDATES="true" - # Preferred AUR helper: paru, yay, or auto (auto-detect) AUR_HELPER="auto" - -# ============================================================================ -# Derived URLs (generally don't edit these) -# ============================================================================ -DOTFILES_REPO_URL="https://github.com/${DOTFILES_GITHUB_USER}/${DOTFILES_REPO_NAME}.git" -DOTFILES_RAW_URL="https://raw.githubusercontent.com/${DOTFILES_GITHUB_USER}/${DOTFILES_REPO_NAME}/${DOTFILES_BRANCH}" diff --git a/setup/setup-espanso.sh b/setup/setup-espanso.sh index 49bcad5..6bc40b4 100755 --- a/setup/setup-espanso.sh +++ b/setup/setup-espanso.sh @@ -28,23 +28,25 @@ fi # Colors # ============================================================================ -# Source shared colors (with fallback) -source "${0:A:h}/../lib/colors.zsh" 2>/dev/null || \ +# Source shared colors and utils (provides DF_WIDTH) +source "$DOTFILES_DIR/zsh/lib/utils.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/utils.zsh" 2>/dev/null || \ +source "$DOTFILES_DIR/zsh/lib/colors.zsh" 2>/dev/null || \ source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null || { typeset -g DF_NC=$'\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_RED=$'\033[38;5;196m' DF_GREY=$'\033[38;5;242m' DF_NC=$'\033[0m' typeset -g DF_LIGHT_BLUE=$'\033[38;5;39m' DF_LIGHT_GREEN=$'\033[38;5;82m' - } +# Use DF_WIDTH from utils.zsh or default to 66 +readonly WIDTH="${DF_WIDTH:-66}" +# ============================================================================ # MOTD-style header # ============================================================================ -DF_WIDTH=66 - print_header() { local user="${USER:-root}" local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" @@ -52,8 +54,8 @@ print_header() { local datetime=$(date '+%a %b %d %H:%M') local hline="" - for ((i=0; i/dev/null || \ -source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null || { - typeset -g DF_NC=$'\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_RED=$'\033[38;5;196m' DF_GREY=$'\033[38;5;242m' DF_NC=$'\033[0m' - typeset -g DF_LIGHT_BLUE=$'\033[38;5;39m' DF_LIGHT_GREEN=$'\033[38;5;82m' +_df_source_config() { + local locations=( + "${DOTFILES_DIR:-$HOME/.dotfiles}/zsh/lib/utils.zsh" + "$HOME/.dotfiles/zsh/lib/utils.zsh" + ) + for loc in "${locations[@]}"; do + [[ -f "$loc" ]] && { source "$loc"; return 0; } + done + + # Fallback defaults + 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' DF_LIGHT_GREEN=$'\033[38;5;82m' + DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}" + DOTFILES_VERSION="${DOTFILES_VERSION:-1.0.0}" + DF_WIDTH="${DF_WIDTH:-66}" } - - -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -CYAN='\033[0;36m' -MAGENTA='\033[0;35m' -BOLD='\033[1m' -DIM='\033[2m' -NC='\033[0m' +_df_source_config # ============================================================================ -# MOTD-style header +# Header # ============================================================================ -DF_WIDTH=66 - print_header() { - local user="${USER:-root}" - local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" - local script_name="setup-wizard" - local datetime=$(date '+%a %b %d %H:%M') + if declare -f df_print_header &>/dev/null; then + df_print_header "setup-wizard" + else + local user="${USER:-root}" + local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" + local datetime=$(date '+%a %b %d %H:%M') + local width="${DF_WIDTH:-66}" + local hline="" && for ((i=0; i/dev/null; then - HAS_GUM=true - return 0 - fi - return 1 -} - -install_gum() { - echo -e "${DF_CYAN}Installing gum for beautiful prompts...${DF_NC}" - - if [[ "$OSTYPE" == "darwin"* ]]; then - brew install gum - elif command -v pacman &>/dev/null; then - sudo pacman -S --noconfirm gum - elif command -v apt-get &>/dev/null; then - sudo mkdir -p /etc/apt/keyrings - curl -fsSL https://repo.charm.sh/apt/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg - echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | sudo tee /etc/apt/sources.list.d/charm.list - sudo apt update && sudo apt install -y gum - elif command -v dnf &>/dev/null; then - echo '[charm] -name=Charm -baseurl=https://repo.charm.sh/yum/ -enabled=1 -gpgcheck=1 -gpgkey=https://repo.charm.sh/yum/gpg.key' | sudo tee /etc/yum.repos.d/charm.repo - sudo dnf install -y gum - else - echo -e "${DF_YELLOW}Could not auto-install gum. Using fallback prompts.${DF_NC}" - return 1 - fi - - HAS_GUM=true -} - -# ============================================================================ -# Wrapper Functions (gum with fallback) -# ============================================================================ - -wizard_header() { - local title="$1" - if [[ "$HAS_GUM" == true ]]; then - gum style \ - --border double \ - --border-foreground 99 \ - --padding "1 3" \ - --margin "1" \ - --align center \ - --width 60 \ - "$title" - else - echo - echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${DF_NC}" - echo -e "${BLUE}║${DF_NC} ${BOLD}$title${DF_NC}" - echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${DF_NC}" - echo - fi -} - -wizard_spin() { - local title="$1" - shift - if [[ "$HAS_GUM" == true ]]; then - gum spin --spinner dot --title "$title" -- "$@" - else - echo -n "$title... " - "$@" &>/dev/null - echo -e "${DF_GREEN}done${DF_NC}" - fi -} +command -v gum &>/dev/null && HAS_GUM=true wizard_confirm() { local prompt="$1" local default="${2:-yes}" - if [[ "$HAS_GUM" == true ]]; then - if [[ "$default" == "yes" ]]; then - gum confirm --default=yes "$prompt" - else - gum confirm --default=no "$prompt" - fi + [[ "$default" == "yes" ]] && gum confirm --default=yes "$prompt" || gum confirm --default=no "$prompt" else local yn_prompt="[Y/n]" [[ "$default" == "no" ]] && yn_prompt="[y/N]" - read -p "$prompt $yn_prompt: " response response=${response:-${default:0:1}} [[ "$response" =~ ^[Yy] ]] @@ -186,72 +76,14 @@ wizard_confirm() { wizard_input() { local prompt="$1" local default="$2" - local placeholder="${3:-$default}" - if [[ "$HAS_GUM" == true ]]; then - gum input --placeholder "$placeholder" --value "$default" --prompt "$prompt: " + gum input --placeholder "$default" --value "$default" --prompt "$prompt: " else read -p "$prompt [$default]: " response echo "${response:-$default}" fi } -wizard_choose() { - local prompt="$1" - shift - local options=("$@") - - if [[ "$HAS_GUM" == true ]]; then - echo "$prompt" >&2 - printf '%s\n' "${options[@]}" | gum choose - else - echo "$prompt" - local i=1 - for opt in "${options[@]}"; do - echo " $i) $opt" - ((i++)) - done - read -p "Enter choice [1-${#options[@]}]: " choice - echo "${options[$((choice-1))]}" - fi -} - -wizard_multichoose() { - local prompt="$1" - shift - local options=("$@") - - if [[ "$HAS_GUM" == true ]]; then - echo "$prompt" >&2 - printf '%s\n' "${options[@]}" | gum choose --no-limit - else - echo "$prompt (comma-separated numbers)" - local i=1 - for opt in "${options[@]}"; do - echo " $i) $opt" - ((i++)) - done - read -p "Enter choices: " choices - IFS=',' read -ra nums <<< "$choices" - for num in "${nums[@]}"; do - echo "${options[$((num-1))]}" - done - fi -} - -wizard_write() { - local prompt="$1" - local placeholder="${2:-Type here...}" - - if [[ "$HAS_GUM" == true ]]; then - gum write --placeholder "$placeholder" --header "$prompt" - else - echo "$prompt" - echo "(Enter text, then Ctrl+D when done)" - cat - fi -} - # ============================================================================ # Wizard Steps # ============================================================================ @@ -259,204 +91,27 @@ wizard_write() { step_welcome() { clear print_header - wizard_header "🚀 Welcome to Dotfiles Setup Wizard" - - echo -e "${DF_DIM}This wizard will help you configure your dotfiles installation." - echo -e "You can re-run this wizard anytime with: dotfiles --wizard${DF_NC}" + echo -e "${DF_BOLD}Welcome to Dotfiles Setup Wizard${DF_NC}" + echo -e "${DF_DIM}Version: $DOTFILES_VERSION | Width: $DF_WIDTH${DF_NC}" echo - - if ! wizard_confirm "Ready to begin?"; then - echo -e "${DF_YELLOW}Setup cancelled.${DF_NC}" - exit 0 - fi + wizard_confirm "Ready to begin?" || { echo "Cancelled."; exit 0; } } step_user_info() { - wizard_header "👤 Personal Information" - - echo -e "${DF_DIM}This information is used for git config, templates, etc.${DF_NC}" - echo - - USER_FULLNAME=$(wizard_input "Full Name" "${USER_FULLNAME:-$(git config --global user.name 2>/dev/null || echo '')}") - USER_EMAIL=$(wizard_input "Email" "${USER_EMAIL:-$(git config --global user.email 2>/dev/null || echo '')}") + echo -e "\n${DF_BLUE}▶${DF_NC} Personal Information" + USER_FULLNAME=$(wizard_input "Full Name" "${USER_FULLNAME:-}") + USER_EMAIL=$(wizard_input "Email" "${USER_EMAIL:-}") USER_GITHUB=$(wizard_input "GitHub Username" "${USER_GITHUB:-}") - USER_WEBSITE=$(wizard_input "Website (optional)" "${USER_WEBSITE:-}") -} - -step_shell_choice() { - wizard_header "🐚 Shell Configuration" - - SHELL_CHOICE=$(wizard_choose "Which shell do you primarily use?" \ - "zsh" \ - "bash" \ - "fish" \ - "other") - - if [[ "$SHELL_CHOICE" == "zsh" ]]; then - ZSH_FRAMEWORK=$(wizard_choose "ZSH framework preference?" \ - "none (pure zsh)" \ - "oh-my-zsh" \ - "prezto" \ - "zinit" \ - "antigen") - fi -} - -step_modules() { - wizard_header "📦 Module Selection" - - echo -e "${DF_DIM}Select which modules to install:${DF_NC}" - echo - - SELECTED_MODULES=$(wizard_multichoose "Choose modules (space to select):" \ - "git - Git configuration and aliases" \ - "zsh - ZSH configuration" \ - "vim - Vim/Neovim configuration" \ - "tmux - Terminal multiplexer" \ - "ssh - SSH configuration" \ - "espanso - Text expansion" \ - "scripts - Utility scripts" \ - "macos - macOS preferences" \ - "linux - Linux preferences") -} - -step_secrets() { - wizard_header "🔐 Secrets Management" - - echo -e "${DF_DIM}How would you like to manage secrets (API keys, tokens, etc.)?${DF_NC}" - echo - - SECRETS_METHOD=$(wizard_choose "Secrets management:" \ - "vault - Encrypted local vault" \ - "1password - 1Password CLI integration" \ - "bitwarden - Bitwarden CLI integration" \ - "none - No secrets management") - - if [[ "$SECRETS_METHOD" == "vault"* ]]; then - if wizard_confirm "Initialize secrets vault now?"; then - "${DOTFILES_DIR}/bin/dotfiles-vault.sh" init || true - fi - fi -} - -step_git_config() { - wizard_header "📝 Git Configuration" - - if wizard_confirm "Configure Git with your information?"; then - git config --global user.name "$USER_FULLNAME" - git config --global user.email "$USER_EMAIL" - echo -e "${DF_GREEN}✓${DF_NC} Git configured" - fi - - if wizard_confirm "Set up SSH key for GitHub?" "no"; then - if [[ ! -f "$HOME/.ssh/id_ed25519" ]]; then - ssh-keygen -t ed25519 -C "$USER_EMAIL" -f "$HOME/.ssh/id_ed25519" - echo -e "${DF_GREEN}✓${DF_NC} SSH key generated" - echo - echo -e "${DF_CYAN}Add this key to GitHub:${DF_NC}" - cat "$HOME/.ssh/id_ed25519.pub" - echo - wizard_confirm "Press Enter when done..." - else - echo -e "${DF_YELLOW}SSH key already exists${DF_NC}" - fi - fi -} - -step_backup() { - wizard_header "💾 Backup Existing Files" - - local backup_dir="$HOME/.dotfiles-backup-$(date +%Y%m%d-%H%M%S)" - - if wizard_confirm "Backup existing dotfiles before installation?"; then - mkdir -p "$backup_dir" - - local files_to_backup=(.zshrc .bashrc .vimrc .gitconfig .tmux.conf) - local backed_up=0 - - for file in "${files_to_backup[@]}"; do - if [[ -f "$HOME/$file" && ! -L "$HOME/$file" ]]; then - cp "$HOME/$file" "$backup_dir/" - ((backed_up++)) - fi - done - - if [[ $backed_up -gt 0 ]]; then - echo -e "${DF_GREEN}✓${DF_NC} Backed up $backed_up files to $backup_dir" - else - echo -e "${DF_DIM}No existing files to backup${DF_NC}" - rmdir "$backup_dir" 2>/dev/null || true - fi - fi -} - -step_install() { - wizard_header "⚡ Installation" - - echo -e "${DF_DIM}Ready to install with these settings:${DF_NC}" - echo - echo " User: $USER_FULLNAME <$USER_EMAIL>" - echo " Shell: $SHELL_CHOICE" - echo " Secrets: $SECRETS_METHOD" - echo - - if wizard_confirm "Proceed with installation?"; then - echo - wizard_spin "Installing dotfiles" sleep 2 - wizard_spin "Linking configuration files" sleep 1 - wizard_spin "Setting up shell" sleep 1 - - echo - echo -e "${DF_GREEN}✓${DF_NC} Installation complete!" - else - echo -e "${DF_YELLOW}Installation cancelled.${DF_NC}" - exit 0 - fi } step_summary() { - wizard_header "✨ Setup Complete!" - - echo -e "${DF_GREEN}Your dotfiles have been configured successfully!${DF_NC}" + echo -e "\n${DF_GREEN}✓${DF_NC} Setup Complete!" echo - echo -e "${BOLD}Quick Commands:${DF_NC}" - echo " dotfiles sync - Sync with remote repository" - echo " dotfiles update - Update dotfiles" - echo " dotfiles doctor - Check installation health" - echo " dotfiles vault - Manage secrets" + echo " Name: $USER_FULLNAME" + echo " Email: $USER_EMAIL" + echo " GitHub: $USER_GITHUB" echo - echo -e "${DF_DIM}Restart your terminal or run 'source ~/.zshrc' to apply changes.${DF_NC}" -} - -# ============================================================================ -# Save Configuration -# ============================================================================ - -save_config() { - local config_file="${DOTFILES_DIR}/dotfiles.conf" - - cat > "$config_file" << EOF -# Dotfiles Configuration -# Generated by setup-wizard on $(date) - -DOTFILES_DIR="$DOTFILES_DIR" -DOTFILES_VERSION="${DOTFILES_VERSION:-1.0.0}" - -# User Information -USER_FULLNAME="$USER_FULLNAME" -USER_EMAIL="$USER_EMAIL" -USER_GITHUB="$USER_GITHUB" -USER_WEBSITE="$USER_WEBSITE" - -# Shell Configuration -SHELL_CHOICE="$SHELL_CHOICE" -ZSH_FRAMEWORK="${ZSH_FRAMEWORK:-none}" - -# Secrets Management -SECRETS_METHOD="$SECRETS_METHOD" -EOF - - echo -e "${DF_GREEN}✓${DF_NC} Configuration saved to $config_file" + echo -e "${DF_DIM}Run 'source ~/.zshrc' to apply changes.${DF_NC}" } # ============================================================================ @@ -464,31 +119,9 @@ EOF # ============================================================================ main() { - # Check for gum - if ! check_gum; then - if wizard_confirm "Install 'gum' for a better experience?" "yes"; then - install_gum || true - fi - fi - - # Run wizard steps step_welcome step_user_info - step_shell_choice - step_modules - step_secrets - step_git_config - step_backup - - # Save configuration - save_config - - # Install - step_install step_summary } -# Run if executed directly -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - main "$@" -fi +[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@" diff --git a/zsh/functions/motd.zsh b/zsh/functions/motd.zsh index d65643f..8d08bce 100644 --- a/zsh/functions/motd.zsh +++ b/zsh/functions/motd.zsh @@ -14,26 +14,28 @@ # Only run in interactive shells [[ -o interactive ]] || return 0 -# 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 || { +# ============================================================================ +# Source Configuration +# ============================================================================ +# utils.zsh sources config.zsh which sources dotfiles.conf +# This gives us DF_WIDTH, MOTD_STYLE, colors, and all other settings + +source "${0:A:h}/../lib/utils.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/utils.zsh" 2>/dev/null || { + # Fallback if utils.zsh not available 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_RED=$'\033[38;5;196m' DF_GREY=$'\033[38;5;242m' DF_NC=$'\033[0m' + typeset -g DF_LIGHT_BLUE=$'\033[38;5;39m' DF_LIGHT_GREEN=$'\033[38;5;82m' + typeset -g DF_WIDTH="${DF_WIDTH:-66}" + typeset -g MOTD_STYLE="${MOTD_STYLE:-compact}" } # ============================================================================ -# MOTD Width +# Optimized Info Gathering (using /proc directly) # ============================================================================ -typeset -g _M_WIDTH=66 - -# ============================================================================ -# Optimized Info Gathering (using /proc directly - faster than spawning processes) -# ============================================================================ - -# Uptime from /proc (no subprocess) _motd_uptime() { local uptime_seconds=$(cut -d. -f1 /proc/uptime 2>/dev/null) [[ -z "$uptime_seconds" ]] && { echo "?"; return; } @@ -51,87 +53,43 @@ _motd_uptime() { fi } -# Load from /proc (no subprocess) -_motd_load() { - cut -d' ' -f1 /proc/loadavg 2>/dev/null || echo "?" -} +_motd_load() { cut -d' ' -f1 /proc/loadavg 2>/dev/null || echo "?"; } -# Memory from /proc (single awk call) _motd_mem() { awk '/MemTotal/ {total=$2} /MemAvailable/ {avail=$2} END { if (total > 0) { used=(total-avail)/1024/1024 total_gb=total/1024/1024 printf "%.1fG/%.0fG", used, total_gb - } else { - print "N/A" - } + } else { print "N/A" } }' /proc/meminfo 2>/dev/null || echo "N/A" } -# Disk usage (single df call) -_motd_disk() { - df -h / 2>/dev/null | awk 'NR==2 {print $3 "/" $2}' || echo "N/A" -} +_motd_disk() { df -h / 2>/dev/null | awk 'NR==2 {print $3 "/" $2}' || echo "N/A"; } -# CPU governor (Arch-specific, direct file read) -_motd_governor() { - local gov=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 2>/dev/null) - [[ -n "$gov" ]] && echo "$gov" -} - -# Kernel version (simplified) -_motd_kernel() { - local kernel=$(uname -r) - # Strip architecture suffix for cleaner display - echo "${kernel%%-*}" -} - -# CachyOS scheduler detection -_motd_scheduler() { - if grep -q "cachyos" /proc/version 2>/dev/null; then - if grep -q "bore" /proc/version 2>/dev/null; then - echo "BORE" - elif grep -q "eevdf" /proc/version 2>/dev/null; then - echo "EEVDF" - else - echo "CachyOS" - fi - fi -} - -# Failed systemd services count (cached) _motd_failed_services() { - # Use cache to avoid slow systemctl calls on every prompt local cache_file="/tmp/.motd-failed-${UID}" - local cache_age=300 # 5 minutes - + local cache_age=300 if [[ -f "$cache_file" ]]; then local file_age=$(($(date +%s) - $(stat -c %Y "$cache_file" 2>/dev/null || echo 0))) - if (( file_age < cache_age )); then - cat "$cache_file" - return - fi + (( file_age < cache_age )) && { cat "$cache_file"; return; } fi - local count=$(systemctl --failed --no-pager --no-legend 2>/dev/null | wc -l) echo "$count" > "$cache_file" 2>/dev/null echo "$count" } -# Package updates (from environment, set by aliases.zsh) -_motd_updates() { - echo "${UPDATE_PKG_COUNT:-0}" -} +_motd_updates() { echo "${UPDATE_PKG_COUNT:-0}"; } # ============================================================================ -# Box Drawing - Fixed Width +# Box Drawing - Uses DF_WIDTH from config # ============================================================================ _motd_line() { local char="$1" + local width="${DF_WIDTH:-66}" local line="" - for ((i=0; i<_M_WIDTH; i++)); do line+="$char"; done + for ((i=0; i/dev/null | awk -F" " '{print $1}') local h_right="${datetime}" local h_pad=$(((inner - ${#h_left} - ${#h_center} - ${#h_right}) / 2)) local h_spaces="" for ((i=0; i 0 )); then - alerts+="${DF_RED}⚠ ${failed} failed service(s)${DF_NC} " + # Failed services warning + if [[ "${MOTD_SHOW_FAILED_SERVICES:-true}" == "true" ]]; then + local failed=$(_motd_failed_services) + (( failed > 0 )) && echo " ${DF_RED}⚠${DF_NC} ${failed} failed service(s)" fi - # Check for updates - local updates=$(_motd_updates) - if (( updates > 0 )); then - alerts+="${DF_YELLOW}⇑ ${updates} update(s)${DF_NC}" - fi - - [[ -n "$alerts" ]] && echo " $alerts" - - #echo "" + echo "" } # ============================================================================ @@ -205,32 +150,10 @@ show_motd() { # ============================================================================ show_motd_mini() { - [[ -n "$_MOTD_SHOWN" && "$1" != "--force" ]] && return 0 - typeset -g _MOTD_SHOWN=1 - local hostname="${HOST:-$(hostname -s 2>/dev/null)}" local uptime=$(_motd_uptime) - local mem=$(_motd_mem) local load=$(_motd_load) - local disk=$(_motd_disk) - local hline=$(_motd_line '═') - local failed=$(_motd_failed_services) - - local alert="" - (( failed > 0 )) && alert=" ${DF_RED}[${failed} failed]${DF_NC}" - - - # Stats line - local s1="${DF_GREY}「${DF_YELLOW}▲ ${DF_NC}${uptime}${DF_GREY}」" - local s2="${DF_GREY}「${DF_CYAN}◆ ${DF_NC}${load}${DF_GREY}」" - local s3="${DF_GREY}「${DF_GREEN}◇ ${DF_NC}${mem}${DF_GREY}」" - local s4="${DF_GREY}「${DF_BLUE}⊡ ${DF_NC}${disk}${DF_GREY}」" - - if [ "$EUID" -ne 0 ];then - echo "${DF_GREY}──${DF_NC} ${DF_BOLD}${DF_LIGHT_BLUE}${hostname} ${DF_NC}${DF_GREY}─${DF_NC}${s1}─${s2}─${s3}─${s4}${DF_GREY}──${DF_NC}" - else - echo "${DF_GREY}──${DF_NC} ${DF_BOLD}${DF_RED}${hostname} ${DF_NC}${DF_GREY}─${DF_NC}${s1}─${s2}─${s3}─${s4}${DF_GREY}──${DF_NC}" - fi + echo "${DF_LIGHT_BLUE}${hostname}${DF_NC} │ up ${uptime} │ load ${load}" } # ============================================================================ @@ -238,82 +161,41 @@ show_motd_mini() { # ============================================================================ show_motd_full() { - [[ -n "$_MOTD_SHOWN" && "$1" != "--force" ]] && return 0 - typeset -g _MOTD_SHOWN=1 - - local hostname="${HOST:-$(hostname -s 2>/dev/null)}" - local datetime=$(date '+%A, %B %d %Y %H:%M:%S') - local uptime=$(_motd_uptime) - local load=$(_motd_load) - local mem=$(_motd_mem) - local disk=$(_motd_disk) - local kernel=$(_motd_kernel) - local governor=$(_motd_governor) - local scheduler=$(_motd_scheduler) - local hline=$(_motd_line '═') - - # echo "" - echo "${DF_GREY}╒${hline}╕${DF_NC}" - echo "${DF_GREY}│${DF_NC} ${DF_BOLD}${DF_BLUE}✦ ${hostname}${DF_NC}" - echo "${DF_GREY}│${DF_NC} ${DF_DIM}${datetime}${DF_NC}" - echo "${DF_GREY}├$(_motd_line '─')┤${DF_NC}" + show_motd --force - # System info - echo "${DF_GREY}│${DF_NC} ${DF_CYAN}Kernel:${DF_NC} ${kernel}" - [[ -n "$scheduler" ]] && echo "${DF_GREY}│${DF_NC} ${DF_CYAN}Scheduler:${DF_NC} ${scheduler}" - [[ -n "$governor" ]] && echo "${DF_GREY}│${DF_NC} ${DF_CYAN}Governor:${DF_NC} ${governor}" + local width="${DF_WIDTH:-66}" - echo "${DF_GREY}├$(_motd_line '─')┤${DF_NC}" + echo "${DF_CYAN}System Details${DF_NC}" + printf "${DF_GREY}─%.0s${DF_NC}" $(seq 1 $width); echo "" - # Resources - echo "${DF_GREY}│${DF_NC} ${DF_YELLOW}▲ Uptime:${DF_NC} ${uptime}" - echo "${DF_GREY}│${DF_NC} ${DF_CYAN}◆ Load:${DF_NC} ${load}" - echo "${DF_GREY}│${DF_NC} ${DF_GREEN}◇ Memory:${DF_NC} ${mem}" - echo "${DF_GREY}│${DF_NC} ${DF_BLUE}⊡ Disk:${DF_NC} ${disk}" + echo " ${DF_DIM}Kernel:${DF_NC} $(uname -r)" + echo " ${DF_DIM}Shell:${DF_NC} ${SHELL##*/} ${ZSH_VERSION:-}" - # Alerts section - local failed=$(_motd_failed_services) - local updates=$(_motd_updates) - - if (( failed > 0 || updates > 0 )); then - echo "${DF_GREY}├$(_motd_line '─')┤${DF_NC}" - (( failed > 0 )) && echo "${DF_GREY}│${DF_NC} ${DF_RED}⚠ ${failed} failed systemd service(s)${DF_NC}" - (( updates > 0 )) && echo "${DF_GREY}│${DF_NC} ${DF_YELLOW}⇑ ${updates} package update(s) available${DF_NC}" + if [[ -f /etc/os-release ]]; then + local distro=$(grep '^PRETTY_NAME=' /etc/os-release | cut -d'"' -f2) + echo " ${DF_DIM}OS:${DF_NC} ${distro}" fi - echo "${DF_GREY}╘${hline}╛${DF_NC}" - #echo "" + if command -v pacman &>/dev/null; then + local pkg_count=$(pacman -Q 2>/dev/null | wc -l) + echo " ${DF_DIM}Packages:${DF_NC} ${pkg_count}" + fi + + echo "" } # ============================================================================ -# Aliases +# Auto-display based on MOTD_STYLE from config # ============================================================================ -alias motd='show_motd --force' -alias motd-mini='show_motd_mini --force' -alias motd-full='show_motd_full --force' - -# ============================================================================ -# Quick System Overview (callable anytime) -# ============================================================================ - -sysbrief() { - echo -e "${DF_CYAN}Uptime:${DF_NC} $(_motd_uptime)" - echo -e "${DF_CYAN}Load:${DF_NC} $(_motd_load)" - echo -e "${DF_CYAN}Memory:${DF_NC} $(_motd_mem)" - echo -e "${DF_CYAN}Disk:${DF_NC} $(_motd_disk)" - - local kernel=$(_motd_kernel) - echo -e "${DF_CYAN}Kernel:${DF_NC} ${kernel}" - - local governor=$(_motd_governor) - [[ -n "$governor" ]] && echo -e "${DF_CYAN}Governor:${DF_NC} ${governor}" - - local scheduler=$(_motd_scheduler) - [[ -n "$scheduler" ]] && echo -e "${DF_CYAN}Scheduler:${DF_NC} ${scheduler}" - - local failed=$(_motd_failed_services) - if (( failed > 0 )); then - echo -e "${DF_RED}Failed:${DF_NC} ${failed} service(s)" - fi +_motd_auto() { + case "${MOTD_STYLE:-compact}" in + full) show_motd_full ;; + mini) show_motd_mini ;; + none|off|false) ;; + *) show_motd ;; + esac } + +# Run on source if ENABLE_MOTD is true +[[ "${ENABLE_MOTD:-true}" == "true" ]] && _motd_auto diff --git a/zsh/lib/config.zsh b/zsh/lib/config.zsh new file mode 100644 index 0000000..4f714e5 --- /dev/null +++ b/zsh/lib/config.zsh @@ -0,0 +1,154 @@ +# ============================================================================ +# Dotfiles Configuration Loader +# ============================================================================ +# This file loads dotfiles.conf and sets up all configuration variables. +# It serves as the bridge between dotfiles.conf and the rest of the system. +# +# Source this file to get access to all configuration: +# source "${0:A:h}/config.zsh" +# +# This file: +# 1. Finds and sources dotfiles.conf +# 2. Sets sensible defaults for any missing values +# 3. Exports variables for use in subshells/scripts +# ============================================================================ + +# Prevent double-sourcing +[[ -n "$_DF_CONFIG_LOADED" ]] && return 0 + +# ============================================================================ +# Find and Source dotfiles.conf +# ============================================================================ + +_df_find_config() { + local locations=( + "${DOTFILES_DIR}/dotfiles.conf" + "${DOTFILES_HOME}/dotfiles.conf" + "$HOME/.dotfiles/dotfiles.conf" + "${0:A:h}/../../dotfiles.conf" + ) + + for loc in "${locations[@]}"; do + [[ -f "$loc" ]] && { echo "$loc"; return 0; } + done + return 1 +} + +_DF_CONFIG_FILE=$(_df_find_config) + +if [[ -n "$_DF_CONFIG_FILE" && -f "$_DF_CONFIG_FILE" ]]; then + source "$_DF_CONFIG_FILE" + typeset -g _DF_CONFIG_LOADED=1 +else + # Config file not found - set critical defaults + typeset -g _DF_CONFIG_LOADED=1 +fi + +# ============================================================================ +# Set Defaults for Any Missing Values +# ============================================================================ +# These defaults ensure scripts work even if dotfiles.conf is incomplete + +# Core Settings +typeset -g DOTFILES_VERSION="${DOTFILES_VERSION:-1.0.0}" +typeset -g DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}" +typeset -g DOTFILES_HOME="${DOTFILES_HOME:-$DOTFILES_DIR}" # Alias for compatibility +typeset -g DOTFILES_BRANCH="${DOTFILES_BRANCH:-main}" +typeset -g DOTFILES_BACKUP_PREFIX="${DOTFILES_BACKUP_PREFIX:-$HOME/.dotfiles_backup}" + +# GitHub Settings +typeset -g DOTFILES_GITHUB_USER="${DOTFILES_GITHUB_USER:-adlee-was-taken}" +typeset -g DOTFILES_REPO_NAME="${DOTFILES_REPO_NAME:-dotfiles}" +typeset -g DOTFILES_REPO_URL="${DOTFILES_REPO_URL:-https://github.com/${DOTFILES_GITHUB_USER}/${DOTFILES_REPO_NAME}.git}" +typeset -g DOTFILES_RAW_URL="${DOTFILES_RAW_URL:-https://raw.githubusercontent.com/${DOTFILES_GITHUB_USER}/${DOTFILES_REPO_NAME}/${DOTFILES_BRANCH}}" + +# Display Settings +typeset -g DF_WIDTH="${DF_WIDTH:-66}" +typeset -g ENABLE_MOTD="${ENABLE_MOTD:-true}" +typeset -g MOTD_STYLE="${MOTD_STYLE:-compact}" +typeset -g MOTD_SHOW_FAILED_SERVICES="${MOTD_SHOW_FAILED_SERVICES:-true}" +typeset -g MOTD_SHOW_UPDATES="${MOTD_SHOW_UPDATES:-true}" + +# Theme Settings +typeset -g ZSH_THEME_NAME="${ZSH_THEME_NAME:-adlee}" +typeset -g THEME_TIMER_THRESHOLD="${THEME_TIMER_THRESHOLD:-10}" +typeset -g THEME_PATH_TRUNCATE_LENGTH="${THEME_PATH_TRUNCATE_LENGTH:-32}" + +# Feature Toggles +typeset -g ENABLE_SMART_SUGGESTIONS="${ENABLE_SMART_SUGGESTIONS:-true}" +typeset -g ENABLE_COMMAND_PALETTE="${ENABLE_COMMAND_PALETTE:-true}" +typeset -g ENABLE_SHELL_ANALYTICS="${ENABLE_SHELL_ANALYTICS:-false}" +typeset -g ENABLE_VAULT="${ENABLE_VAULT:-true}" +typeset -g DOTFILES_AUTO_SYNC_CHECK="${DOTFILES_AUTO_SYNC_CHECK:-true}" + +# Btrfs Settings +typeset -g BTRFS_DEFAULT_MOUNT="${BTRFS_DEFAULT_MOUNT:-/}" + +# Snapper Settings +typeset -g SNAPPER_CONFIG="${SNAPPER_CONFIG:-root}" +typeset -g LIMINE_CONF="${LIMINE_CONF:-/boot/limine.conf}" + +# Tmux Settings +typeset -g TW_SESSION_PREFIX="${TW_SESSION_PREFIX:-work}" +typeset -g TW_DEFAULT_TEMPLATE="${TW_DEFAULT_TEMPLATE:-dev}" + +# Python Template Settings +typeset -g PY_TEMPLATE_BASE_DIR="${PY_TEMPLATE_BASE_DIR:-$HOME/projects}" +typeset -g PY_TEMPLATE_PYTHON="${PY_TEMPLATE_PYTHON:-python3}" +typeset -g PY_TEMPLATE_VENV_NAME="${PY_TEMPLATE_VENV_NAME:-venv}" +typeset -g PY_TEMPLATE_USE_POETRY="${PY_TEMPLATE_USE_POETRY:-false}" +typeset -g PY_TEMPLATE_GIT_INIT="${PY_TEMPLATE_GIT_INIT:-true}" + +# SSH Settings +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}" + +# Password Manager Settings +typeset -g PW_CLIP_TIME="${PW_CLIP_TIME:-45}" + +# Package Manager +typeset -g AUR_HELPER="${AUR_HELPER:-auto}" + +# Git Settings (with fallbacks to user identity) +typeset -g GIT_USER_NAME="${GIT_USER_NAME:-$USER_FULLNAME}" +typeset -g GIT_USER_EMAIL="${GIT_USER_EMAIL:-$USER_EMAIL}" +typeset -g GIT_DEFAULT_BRANCH="${GIT_DEFAULT_BRANCH:-main}" + +# ============================================================================ +# Export for Bash Scripts +# ============================================================================ +# Bash scripts can't see typeset -g, so we export key variables + +export DOTFILES_VERSION DOTFILES_DIR DOTFILES_HOME DOTFILES_BRANCH +export DOTFILES_GITHUB_USER DOTFILES_REPO_NAME DOTFILES_REPO_URL DOTFILES_RAW_URL +export DF_WIDTH MOTD_STYLE +export ZSH_THEME_NAME + +# ============================================================================ +# Helper Function: Get Config Value +# ============================================================================ +# Usage: df_config "VARIABLE_NAME" "default_value" + +df_config() { + local var_name="$1" + local default="$2" + local value="${(P)var_name}" + echo "${value:-$default}" +} + +# ============================================================================ +# Helper Function: Show Config Summary +# ============================================================================ + +df_show_config() { + echo "Dotfiles Configuration" + echo "======================" + echo "Config File: ${_DF_CONFIG_FILE:-not found}" + echo "Version: $DOTFILES_VERSION" + echo "Directory: $DOTFILES_DIR" + echo "Branch: $DOTFILES_BRANCH" + echo "Display Width: $DF_WIDTH" + echo "MOTD Style: $MOTD_STYLE" + echo "Theme: $ZSH_THEME_NAME" +} diff --git a/zsh/lib/utils.zsh b/zsh/lib/utils.zsh index 156f8a1..77ac8f3 100644 --- a/zsh/lib/utils.zsh +++ b/zsh/lib/utils.zsh @@ -1,89 +1,86 @@ # ============================================================================ # Shared Utility Functions for Zsh Dotfiles # ============================================================================ -# Common helper functions used across multiple function files -# Note: colors.zsh provides: DF_* color variables and df_print_func_name +# Common helper functions used across multiple function files. +# +# This file sources config.zsh (which sources dotfiles.conf) and colors.zsh, +# so sourcing this single file gives you access to everything. # # Source this file in function files: # source "${0:A:h}/../lib/utils.zsh" # ============================================================================ -# Ensure colors are loaded first (provides DF_* vars and df_print_func_name) -source "${0:A:h}/colors.zsh" 2>/dev/null || \ -source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null +# Prevent double-sourcing +[[ -n "$_DF_UTILS_LOADED" ]] && return 0 +typeset -g _DF_UTILS_LOADED=1 # ============================================================================ -# Common Print Functions +# Source Configuration and Colors +# ============================================================================ +# Order matters: config first (sets DF_WIDTH, etc.), then colors + +# Find lib directory +_df_lib_dir="${0:A:h}" +[[ ! -d "$_df_lib_dir" ]] && _df_lib_dir="$HOME/.dotfiles/zsh/lib" + +# Source config (provides all settings from dotfiles.conf) +source "${_df_lib_dir}/config.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/config.zsh" 2>/dev/null || { + # Fallback: set critical defaults if config.zsh not found + typeset -g DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}" + typeset -g DOTFILES_HOME="${DOTFILES_HOME:-$DOTFILES_DIR}" + typeset -g DOTFILES_VERSION="${DOTFILES_VERSION:-unknown}" + typeset -g DF_WIDTH="${DF_WIDTH:-66}" +} + +# Source colors +source "${_df_lib_dir}/colors.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null || { + # Fallback colors if colors.zsh not found + typeset -g DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' + typeset -g DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + typeset -g DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' + typeset -g DF_LIGHT_GREEN=$'\033[38;5;82m' DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' +} + +unset _df_lib_dir + +# ============================================================================ +# MOTD-Style Header Functions # ============================================================================ -# Print a step/section header -df_print_step() { - echo -e "${DF_GREEN}==>${DF_NC} $1" +# Prints a standardized header box for functions +# Usage: df_print_func_name "Function Name" +df_print_func_name() { + local func_name="${1:-func}" + local datetime=$(date '+%a %b %d %H:%M') + local width="${DF_WIDTH:-66}" + + # Build horizontal line + local hline="" + for ((i=0; i&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 Header -# ============================================================================ - -# Prints a standardized header box for scripts -# Usage: df_print_header "script-name" -# Usage: df_print_func_name "func-name" - -df_print_func_name() { - local func_name="${1:-func}" - local datetime=$(date '+%a %b %d %H:%M') - local width=66 - - # Build horizontal line - local hline="" - for ((i=0; i${DF_NC} $1"; } - -# Print a success message (green checkmark) df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } - -# Print an error message (red X) -df_print_error() { echo -e "${DF_RED}✗${DF_NC} $1"; } - -# Print a warning message (yellow warning sign) +df_print_error() { echo -e "${DF_RED}✗${DF_NC} $1" >&2; } df_print_warning() { echo -e "${DF_YELLOW}⚠${DF_NC} $1"; } - -# Print an info message (cyan info icon) df_print_info() { echo -e "${DF_CYAN}ℹ${DF_NC} $1"; } - -# Print a section header (cyan label) df_print_section() { echo -e "${DF_CYAN}$1:${DF_NC}"; } - -# Print indented content df_print_indent() { echo " $1"; } # ============================================================================ # Command Dependency Checking # ============================================================================ -# Check if a command exists df_cmd_exists() { command -v "$1" &>/dev/null; } -# Require a command, show install hint if missing df_require_cmd() { local cmd="$1" local package="${2:-$1}" @@ -154,7 +136,6 @@ df_require_cmd() { # User Confirmation # ============================================================================ -# Ask for yes/no confirmation (defaults to no) df_confirm() { local prompt="$1" read -q "REPLY?$prompt [y/N]: " @@ -162,7 +143,6 @@ df_confirm() { [[ "$REPLY" =~ ^[Yy]$ ]] } -# Confirm with warning prefix df_confirm_warning() { df_print_warning "$1" df_confirm "Continue?" @@ -172,16 +152,10 @@ df_confirm_warning() { # File/Directory Helpers # ============================================================================ -# Check if in a git repo df_in_git_repo() { git rev-parse --git-dir &>/dev/null 2>&1; } - -# Get git root directory df_git_root() { git rev-parse --show-toplevel 2>/dev/null; } - -# Ensure directory exists df_ensure_dir() { [[ ! -d "$1" ]] && mkdir -p "$1"; } -# Ensure file exists with optional default content df_ensure_file() { local file="$1" content="${2:-}" if [[ ! -f "$file" ]]; then