wizard (25446B)
1 #!/usr/bin/env zsh 2 # Dasho's Dotfiles Wizard 🧙 3 # A magical, self-contained interactive wizard for managing dotfiles 4 # Works on fresh Linux/macOS systems with just internet access 5 6 set -eo pipefail 7 8 # Color Palette (Soft Gradients & Pastels) 9 # ---------------------------------------- 10 # 183 - Soft Lavender (headers, prompts) 11 # 219 - Light Pink (selected items, cursor) 12 # 147 - Muted Purple (list items) 13 # 117 - Pale Cyan (info messages) 14 # 114 - Soft Green (success messages) 15 # 210 - Coral (error messages) 16 # 222 - Peach (warnings) 17 # 242 - Warm Gray (subtle text) 18 19 # Colors for fallback (before gum is available) 20 RED='\033[0;31m' 21 GREEN='\033[0;32m' 22 YELLOW='\033[1;33m' 23 BLUE='\033[0;34m' 24 MAGENTA='\033[0;35m' 25 CYAN='\033[0;36m' 26 BOLD='\033[1m' 27 RESET='\033[0m' 28 29 # Paths 30 SCRIPT_DIR="$(cd "$(dirname "${(%):-%x}")" && pwd)" 31 ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" 32 PRIVATE_DIR="$ROOT/private" 33 34 # ============================================================================ 35 # SELF-BOOTSTRAP: Install wizard dependencies 36 # ============================================================================ 37 38 has_command() { 39 command -v "$1" >/dev/null 2>&1 40 } 41 42 install_homebrew() { 43 echo "${BLUE}📦 Installing Homebrew...${RESET}" 44 if [[ "$OSTYPE" == "darwin"* ]]; then 45 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 46 if [[ -x /opt/homebrew/bin/brew ]]; then 47 eval "$(/opt/homebrew/bin/brew shellenv)" 48 elif [[ -x /usr/local/bin/brew ]]; then 49 eval "$(/usr/local/bin/brew shellenv)" 50 fi 51 else 52 # Linux 53 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 54 if [[ -x /home/linuxbrew/.linuxbrew/bin/brew ]]; then 55 eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 56 fi 57 fi 58 } 59 60 wizard_bootstrap() { 61 echo "${MAGENTA}${BOLD}" 62 echo "╔════════════════════════════════════════════════════════════╗" 63 echo "║ ║" 64 echo "║ 🧙 Dasho's Dotfiles Wizard 🧙 ║" 65 echo "║ ║" 66 echo "║ Making magic happen on your machine... ║" 67 echo "║ ║" 68 echo "╚════════════════════════════════════════════════════════════╝" 69 echo "${RESET}" 70 echo "" 71 echo "${CYAN}Checking for wizard dependencies...${RESET}" 72 echo "" 73 74 # Check/install Homebrew first 75 if ! has_command brew; then 76 echo "${YELLOW}⚠️ Homebrew not found. Installing...${RESET}" 77 install_homebrew 78 else 79 echo "${GREEN}✓${RESET} Homebrew" 80 fi 81 82 # Install essential tools 83 local tools=("gum" "glow" "age" "jq") 84 local to_install=() 85 86 for tool in "${tools[@]}"; do 87 if ! has_command "$tool"; then 88 echo "${YELLOW}⚠️ $tool not found${RESET}" 89 to_install+=("$tool") 90 else 91 echo "${GREEN}✓${RESET} $tool" 92 fi 93 done 94 95 if [[ ${#to_install[@]} -gt 0 ]]; then 96 echo "" 97 echo "${BLUE}📦 Installing missing tools: ${to_install[*]}${RESET}" 98 brew install "${to_install[@]}" 99 echo "" 100 echo "${GREEN}✓ All wizard dependencies installed!${RESET}" 101 else 102 echo "" 103 echo "${GREEN}✓ All wizard dependencies present!${RESET}" 104 fi 105 106 echo "" 107 sleep 1 108 } 109 110 # ============================================================================ 111 # GUM-POWERED UI HELPERS 112 # ============================================================================ 113 114 show_header() { 115 clear 116 gum style \ 117 --border double \ 118 --border-foreground 183 \ 119 --padding "1 2" \ 120 --margin "1 0" \ 121 --align center \ 122 --foreground 219 \ 123 "🧙 Dasho's Dotfiles Wizard 🧙" \ 124 "" \ 125 "Your magical companion for dotfiles mastery" 126 } 127 128 show_success() { 129 gum style \ 130 --foreground 114 \ 131 "✓ $1" 132 } 133 134 show_error() { 135 gum style \ 136 --foreground 210 \ 137 "✗ $1" 138 } 139 140 show_info() { 141 gum style \ 142 --foreground 117 \ 143 "ℹ $1" 144 } 145 146 spinner_run() { 147 local title="$1" 148 shift 149 gum spin --spinner dot --title "$title" -- "$@" 150 } 151 152 wait_for_key() { 153 gum style --foreground 242 --italic "Press any key to continue..." 154 read -k1 -s 155 return 0 156 } 157 158 confirm() { 159 command gum confirm \ 160 --prompt.foreground 183 \ 161 --selected.foreground 219 \ 162 "$@" 163 } 164 165 # ============================================================================ 166 # ERROR HANDLING WITH RETRY/SKIP OPTIONS 167 # ============================================================================ 168 169 handle_error() { 170 local error_msg="$1" 171 local context="$2" 172 173 show_error "$error_msg" 174 echo "" 175 176 gum style --foreground 183 --italic "What would you like to do?" 177 178 local choice=$(gum choose \ 179 --cursor.foreground 219 \ 180 --selected.foreground 183 \ 181 --cursor "→ " \ 182 "🔄 Retry" \ 183 "⏭️ Skip and continue" \ 184 "🛑 Exit wizard" \ 185 "📋 Show error details") 186 187 case "$choice" in 188 *Retry*) 189 return 0 190 ;; 191 *Skip*) 192 show_info "Skipping: $context" 193 sleep 1 194 return 1 195 ;; 196 *Exit*) 197 echo "" 198 gum style --foreground 9 "Exiting wizard. See you next time! 👋" 199 exit 1 200 ;; 201 *details*) 202 gum style --foreground 8 "$context" 203 echo "" 204 wait_for_key 205 handle_error "$error_msg" "$context" 206 return $? 207 ;; 208 esac 209 } 210 211 safe_run() { 212 local description="$1" 213 shift 214 215 local output 216 local exit_code 217 218 while true; do 219 output=$("$@" 2>&1) && exit_code=0 || exit_code=$? 220 221 if [[ $exit_code -eq 0 ]]; then 222 return 0 223 else 224 if handle_error "$description failed" "$output"; then 225 continue # Retry 226 else 227 return 1 # Skip 228 fi 229 fi 230 done 231 } 232 233 # ============================================================================ 234 # CORE OPERATIONS 235 # ============================================================================ 236 237 run_bootstrap() { 238 show_header 239 gum style --foreground 183 --bold "🚀 Bootstrap: Fresh Machine Setup" 240 echo "" 241 242 gum style --foreground 183 "This will install Homebrew (if needed), install packages from Brewfile," 243 gum style --foreground 183 "and link all your dotfiles to the right places." 244 echo "" 245 246 if ! confirm "Ready to bootstrap this machine?"; then 247 return 248 fi 249 250 echo "" 251 spinner_run "Installing Homebrew if needed..." bash "$ROOT/bin/bootstrap" 252 253 if [[ $? -eq 0 ]]; then 254 echo "" 255 show_success "Bootstrap completed successfully!" 256 echo "" 257 gum style --foreground 222 "⚠️ Remember to restart your shell or run: source ~/.zshrc" 258 else 259 safe_run "Bootstrap" bash "$ROOT/bin/bootstrap" 260 fi 261 262 echo "" 263 wait_for_key 264 } 265 266 run_backup() { 267 show_header 268 gum style --foreground 183 --bold "💾 Backup: Save Brewfile & Package List" 269 echo "" 270 271 gum style --foreground 183 "Updates Brewfile and brew/leaves.txt with currently installed packages." 272 echo "" 273 274 if ! confirm "Run backup now?"; then 275 return 276 fi 277 278 echo "" 279 spinner_run "Backing up Brewfile and brew leaves..." bash "$ROOT/bin/backup" 280 281 echo "" 282 show_success "Backup completed!" 283 284 if has_command glow && [[ -f "$ROOT/Brewfile" ]]; then 285 echo "" 286 if confirm "View the updated Brewfile?"; then 287 glow "$ROOT/Brewfile" -p 288 fi 289 fi 290 291 echo "" 292 wait_for_key 293 } 294 295 run_backup_secrets() { 296 show_header 297 gum style --foreground 183 --bold "🔐 Backup Secrets: Encrypt GPG/SSH/Age/Skate/Oh-My-Zsh" 298 echo "" 299 300 gum style --foreground 183 "Creates an encrypted archive with:" 301 gum style --foreground 147 " • GPG keys" 302 gum style --foreground 147 " • SSH keys and config" 303 gum style --foreground 147 " • Age identities" 304 gum style --foreground 147 " • Skate database" 305 gum style --foreground 147 " • Oh-My-Zsh custom configs" 306 echo "" 307 308 local default_name="keys-$(date +%Y%m%d).tar.gz.age" 309 local archive_name=$(gum input \ 310 --placeholder "$default_name" \ 311 --prompt "Archive name: " \ 312 --value "$default_name" \ 313 --prompt.foreground 183 \ 314 --cursor.foreground 219) 315 316 [[ -z "$archive_name" ]] && archive_name="$default_name" 317 318 local archive_path="$PRIVATE_DIR/$archive_name" 319 320 echo "" 321 spinner_run "Creating encrypted backup..." bash "$ROOT/bin/backup-secrets" "$archive_path" 322 323 echo "" 324 show_success "Secrets backed up to: $archive_name" 325 326 echo "" 327 wait_for_key 328 } 329 330 run_restore_secrets() { 331 show_header 332 gum style --foreground 183 --bold "🔓 Restore Secrets: Decrypt & Restore" 333 echo "" 334 335 if [[ ! -d "$PRIVATE_DIR" ]] || [[ -z "$(ls -A "$PRIVATE_DIR"/*.age 2>/dev/null)" ]]; then 336 show_error "No encrypted archives found in $PRIVATE_DIR" 337 echo "" 338 wait_for_key 339 return 340 fi 341 342 local archives=($(ls "$PRIVATE_DIR"/*.age 2>/dev/null)) 343 344 if [[ ${#archives[@]} -eq 0 ]]; then 345 show_error "No .age archives found" 346 echo "" 347 wait_for_key 348 return 349 fi 350 351 gum style --foreground 183 "Select an archive to restore:" 352 echo "" 353 354 local selected=$(gum choose \ 355 --cursor.foreground 219 \ 356 --selected.foreground 183 \ 357 --cursor "→ " \ 358 "${archives[@]##*/}") 359 local archive_path="$PRIVATE_DIR/$selected" 360 361 echo "" 362 gum style --foreground 222 "⚠️ This will restore GPG, SSH, Age, Skate, and Oh-My-Zsh configs" 363 364 if ! confirm "Restore from $selected?"; then 365 return 366 fi 367 368 echo "" 369 spinner_run "Restoring secrets..." bash "$ROOT/bin/restore-secrets" "$archive_path" 370 371 echo "" 372 show_success "Secrets restored successfully!" 373 374 echo "" 375 wait_for_key 376 } 377 378 run_link() { 379 show_header 380 gum style --foreground 183 --bold "🔗 Link: Symlink Dotfiles" 381 echo "" 382 383 gum style --foreground 183 "Creates symlinks for:" 384 gum style --foreground 147 " • ~/.zshrc" 385 gum style --foreground 147 " • ~/.config/direnv/direnvrc" 386 gum style --foreground 147 " • ~/.gitconfig" 387 gum style --foreground 147 " • ~/.config/nvim/init.lua" 388 gum style --foreground 147 " • ~/.config/redbrick/*" 389 gum style --foreground 147 " • ~/,config/yazi/*" 390 echo "" 391 392 if ! confirm "Create/update symlinks?"; then 393 return 394 fi 395 396 echo "" 397 398 mkdir -p "$HOME/.config/direnv" 399 mkdir -p "$HOME/.config/git" 400 mkdir -p "$HOME/.config/nvim" 401 mkdir -p "$HOME/.config/redbrick" 402 mkdir -p "$HOME/.config/yazi" 403 404 ln -snf "$ROOT/zsh/zshrc" "$HOME/.zshrc" && show_success "Linked ~/.zshrc" 405 ln -snf "$ROOT/config/direnv/direnvrc" "$HOME/.config/direnv/direnvrc" && show_success "Linked direnvrc" 406 ln -snf "$ROOT/config/git/config" "$HOME/.gitconfig" && show_success "Linked gitconfig" 407 ln -snf "$ROOT/config/nvim/init.lua" "$HOME/.config/nvim/init.lua" && show_success "Linked nvim config" 408 ln -snf "$ROOT/config/redbrick" "$HOME/.config/redbrick" && show_success "Linked redbrick config" 409 ln -snf "$ROOT/config/yazi/yazi.toml" "$HOME/.config/yazi/yazi.toml" && show_success "Linked yazi config" 410 411 echo "" 412 show_success "All dotfiles linked!" 413 414 echo "" 415 wait_for_key 416 } 417 418 run_check() { 419 show_header 420 gum style --foreground 183 --bold "🔍 Check: Verify Dependencies" 421 echo "" 422 423 local tools=("direnv" "skate" "age" "just" "brew" "gum" "glow" "git" "zsh") 424 local missing=() 425 426 for tool in "${tools[@]}"; do 427 if has_command "$tool"; then 428 show_success "$tool" 429 else 430 show_error "$tool (missing)" 431 missing+=("$tool") 432 fi 433 done 434 435 echo "" 436 437 if [[ ${#missing[@]} -eq 0 ]]; then 438 gum style --foreground 10 --bold "✨ All dependencies present! You're all set!" 439 else 440 gum style --foreground 222 "Missing tools: ${missing[*]}" 441 echo "" 442 if confirm "Install missing tools via Homebrew?"; then 443 echo "" 444 spinner_run "Installing missing tools..." brew install "${missing[@]}" 445 echo "" 446 show_success "Tools installed!" 447 fi 448 fi 449 450 echo "" 451 wait_for_key 452 } 453 454 # ============================================================================ 455 # GIT OPERATIONS 456 # ============================================================================ 457 458 check_git_repo() { 459 if ! git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then 460 show_header 461 show_error "Not a git repository" 462 gum style --foreground 147 "The directory $ROOT is not a git repository." 463 gum style --foreground 147 "Please initialize a repository first." 464 echo "" 465 if confirm "Initialize a new git repository here?"; then 466 git -C "$ROOT" init 467 show_success "Git repository initialized at $ROOT" 468 fi 469 echo "" 470 wait_for_key 471 return 1 472 fi 473 return 0 474 } 475 476 git_menu() { 477 if ! check_git_repo; then 478 return 479 fi 480 481 while true; do 482 show_header 483 gum style --foreground 183 --bold "🌳 Git Operations" 484 echo "" 485 486 local choice=$(gum choose \ 487 --cursor.foreground 219 \ 488 --selected.foreground 183 \ 489 --cursor "→ " \ 490 "📊 Status" \ 491 "💾 Commit changes" \ 492 "🔄 Sync (pull + push)" \ 493 "⬆️ Push" \ 494 "⬇️ Pull" \ 495 "🔧 Configure remote" \ 496 "🔑 GitHub authentication" \ 497 "← Back to main menu") 498 499 case "$choice" in 500 *Status*) 501 git_status 502 ;; 503 *Commit*) 504 git_commit 505 ;; 506 *Sync*) 507 git_sync 508 ;; 509 *Push*) 510 git_push 511 ;; 512 *Pull*) 513 git_pull 514 ;; 515 *remote*) 516 git_configure_remote 517 ;; 518 *authentication*) 519 github_auth 520 ;; 521 *Back*) 522 break 523 ;; 524 esac 525 done 526 } 527 528 git_status() { 529 show_header 530 gum style --foreground 183 --bold "📊 Git Status" 531 echo "" 532 533 cd "$ROOT" 534 git status 535 536 echo "" 537 wait_for_key 538 } 539 540 git_commit() { 541 show_header 542 gum style --foreground 183 --bold "💾 Commit Changes" 543 echo "" 544 545 cd "$ROOT" 546 547 local status_output=$(git status --porcelain) 548 549 if [[ -z "$status_output" ]]; then 550 show_info "No changes to commit" 551 echo "" 552 wait_for_key 553 return 554 fi 555 556 echo "Changed files:" 557 git status --short 558 echo "" 559 560 if ! confirm "Stage all changes?"; then 561 show_info "Commit cancelled" 562 echo "" 563 wait_for_key 564 return 565 fi 566 567 git add -A 568 569 echo "" 570 local commit_msg=$(gum input \ 571 --placeholder "Enter commit message" \ 572 --prompt "Message: " \ 573 --prompt.foreground 183 \ 574 --cursor.foreground 219) 575 576 if [[ -z "$commit_msg" ]]; then 577 show_error "Commit message required" 578 echo "" 579 wait_for_key 580 return 581 fi 582 583 echo "" 584 spinner_run "Committing changes..." git commit -m "$commit_msg" 585 586 echo "" 587 show_success "Changes committed!" 588 589 echo "" 590 if confirm "Push to remote?"; then 591 git_push 592 else 593 wait_for_key 594 fi 595 } 596 597 git_sync() { 598 show_header 599 gum style --foreground 183 --bold "🔄 Sync: Pull + Push" 600 echo "" 601 602 cd "$ROOT" 603 604 # Check if remote is configured 605 if ! git remote get-url origin >/dev/null 2>&1; then 606 show_error "No remote repository configured" 607 gum style --foreground 147 "Configure a remote first in the Git menu." 608 echo "" 609 wait_for_key 610 return 611 fi 612 613 if spinner_run "Pulling from remote..." git pull 2>&1; then 614 show_success "Pulled successfully" 615 else 616 echo "" 617 show_error "Git pull failed" 618 gum style --foreground 147 "Check your internet connection and authentication." 619 echo "" 620 wait_for_key 621 return 622 fi 623 624 echo "" 625 626 local status_output=$(git status --porcelain) 627 628 if [[ -n "$status_output" ]]; then 629 gum style --foreground 222 "You have uncommitted changes." 630 if confirm "Commit and push?"; then 631 git_commit 632 return 633 fi 634 fi 635 636 if spinner_run "Pushing to remote..." git push 2>&1; then 637 echo "" 638 show_success "Synced successfully!" 639 else 640 echo "" 641 show_error "Git push failed" 642 gum style --foreground 147 "Check your authentication and permissions." 643 echo "" 644 wait_for_key 645 return 646 fi 647 648 echo "" 649 wait_for_key 650 } 651 652 git_push() { 653 show_header 654 gum style --foreground 183 --bold "⬆️ Push to Remote" 655 echo "" 656 657 cd "$ROOT" 658 659 # Check if remote is configured 660 if ! git remote get-url origin >/dev/null 2>&1; then 661 show_error "No remote repository configured" 662 gum style --foreground 147 "Configure a remote first in the Git menu." 663 echo "" 664 wait_for_key 665 return 666 fi 667 668 if spinner_run "Pushing to remote..." git push 2>&1; then 669 echo "" 670 show_success "Pushed successfully!" 671 else 672 echo "" 673 show_error "Git push failed" 674 gum style --foreground 147 "Check authentication and permissions." 675 echo "" 676 wait_for_key 677 return 678 fi 679 680 echo "" 681 wait_for_key 682 } 683 684 git_pull() { 685 show_header 686 gum style --foreground 183 --bold "⬇️ Pull from Remote" 687 echo "" 688 689 cd "$ROOT" 690 691 # Check if remote is configured 692 if ! git remote get-url origin >/dev/null 2>&1; then 693 show_error "No remote repository configured" 694 gum style --foreground 147 "Configure a remote first in the Git menu." 695 echo "" 696 wait_for_key 697 return 698 fi 699 700 if spinner_run "Pulling from remote..." git pull 2>&1; then 701 echo "" 702 show_success "Pulled successfully!" 703 else 704 echo "" 705 show_error "Git pull failed" 706 gum style --foreground 147 "Check your internet connection and authentication." 707 echo "" 708 wait_for_key 709 return 710 fi 711 712 echo "" 713 wait_for_key 714 } 715 716 git_configure_remote() { 717 show_header 718 gum style --foreground 183 --bold "🔧 Configure Git Remote" 719 echo "" 720 721 cd "$ROOT" 722 723 local current_remote=$(git remote get-url origin 2>/dev/null || echo "") 724 725 if [[ -n "$current_remote" ]]; then 726 gum style "Current remote: $current_remote" 727 echo "" 728 else 729 show_info "No remote configured" 730 echo "" 731 fi 732 733 local action=$(gum choose \ 734 --cursor.foreground 219 \ 735 --selected.foreground 183 \ 736 --cursor "→ " \ 737 "➕ Add/Update remote" \ 738 "🗑️ Remove remote" \ 739 "← Back") 740 741 case "$action" in 742 *Add*) 743 echo "" 744 local remote_url=$(gum input \ 745 --placeholder "https://github.com/username/repo.git" \ 746 --prompt "Remote URL: " \ 747 --prompt.foreground 183 \ 748 --cursor.foreground 219) 749 750 if [[ -z "$remote_url" ]]; then 751 show_error "Remote URL required" 752 else 753 if [[ -n "$current_remote" ]]; then 754 git remote set-url origin "$remote_url" 755 else 756 git remote add origin "$remote_url" 757 fi 758 show_success "Remote configured: $remote_url" 759 fi 760 ;; 761 *Remove*) 762 git remote remove origin 2>/dev/null 763 show_success "Remote removed" 764 ;; 765 esac 766 767 echo "" 768 wait_for_key 769 } 770 771 github_auth() { 772 show_header 773 gum style --foreground 183 --bold "🔑 GitHub Authentication" 774 echo "" 775 776 gum style --foreground 183 "Choose authentication method:" 777 echo "" 778 779 local method=$(gum choose \ 780 --cursor.foreground 219 \ 781 --selected.foreground 183 \ 782 --cursor "→ " \ 783 "🌐 Device flow (browser)" \ 784 "🔑 Personal Access Token" \ 785 "📋 Check current auth" \ 786 "← Back") 787 788 case "$method" in 789 *Device*) 790 github_device_flow 791 ;; 792 *Token*) 793 github_token_setup 794 ;; 795 *Check*) 796 github_check_auth 797 ;; 798 esac 799 } 800 801 github_device_flow() { 802 show_header 803 gum style --foreground 183 --bold "🌐 GitHub Device Flow Authentication" 804 echo "" 805 806 if ! has_command gh; then 807 gum style --foreground 222 "GitHub CLI (gh) not found" 808 echo "" 809 if confirm "Install GitHub CLI?"; then 810 spinner_run "Installing gh..." brew install gh 811 echo "" 812 else 813 return 814 fi 815 fi 816 817 # Check if GITHUB_TOKEN is set 818 if [[ -n "${GITHUB_TOKEN:-}" ]]; then 819 show_info "GITHUB_TOKEN environment variable is set" 820 echo "" 821 gum style --foreground 147 "GitHub CLI will use this token automatically." 822 gum style --foreground 147 "To use device flow instead, unset GITHUB_TOKEN first:" 823 gum style --foreground 242 " unset GITHUB_TOKEN" 824 echo "" 825 wait_for_key 826 return 827 fi 828 829 gum style --foreground 183 "This will open GitHub in your browser for authentication." 830 echo "" 831 832 if ! confirm "Continue?"; then 833 return 834 fi 835 836 echo "" 837 838 if gh auth login 2>&1; then 839 echo "" 840 show_success "Authentication complete!" 841 842 echo "" 843 if confirm "Configure git to use gh credentials?"; then 844 gh auth setup-git 2>/dev/null && show_success "Git configured to use gh credentials" || show_info "Git already configured" 845 fi 846 else 847 local gh_exit=$? 848 echo "" 849 show_error "GitHub authentication failed" 850 gum style --foreground 147 "You may already be authenticated, or there was a network issue." 851 echo "" 852 fi 853 854 echo "" 855 wait_for_key 856 } 857 858 github_token_setup() { 859 show_header 860 gum style --foreground 183 --bold "🔑 GitHub Personal Access Token Setup" 861 echo "" 862 863 gum style --foreground 183 "To create a token:" 864 gum style --foreground 147 " 1. Go to: https://github.com/settings/tokens" 865 gum style --foreground 147 " 2. Click 'Generate new token (classic)'" 866 gum style --foreground 147 " 3. Select scopes: repo, workflow, write:packages" 867 gum style --foreground 147 " 4. Generate and copy the token" 868 echo "" 869 870 if ! confirm "Have you created a token?"; then 871 return 872 fi 873 874 echo "" 875 local token=$(gum input \ 876 --password \ 877 --placeholder "ghp_..." \ 878 --prompt "Token: " \ 879 --prompt.foreground 183 \ 880 --cursor.foreground 219) 881 882 if [[ -z "$token" ]]; then 883 show_error "Token required" 884 echo "" 885 wait_for_key 886 return 887 fi 888 889 # Store in git config 890 cd "$ROOT" 891 local remote_url=$(git remote get-url origin 2>/dev/null || echo "") 892 893 if [[ -z "$remote_url" ]]; then 894 show_error "No git remote configured. Please configure remote first." 895 else 896 # Update URL to use token 897 if [[ "$remote_url" =~ ^https://github.com/ ]]; then 898 local new_url=$(echo "$remote_url" | sed "s|https://github.com/|https://${token}@github.com/|") 899 git remote set-url origin "$new_url" 900 show_success "Token configured for this repository" 901 else 902 show_error "Remote is not an HTTPS GitHub URL" 903 fi 904 fi 905 906 echo "" 907 wait_for_key 908 } 909 910 github_check_auth() { 911 show_header 912 gum style --foreground 183 --bold "📋 GitHub Authentication Status" 913 echo "" 914 915 if has_command gh; then 916 gh auth status 917 else 918 show_info "GitHub CLI not installed" 919 fi 920 921 echo "" 922 echo "Git config:" 923 git config --get user.name && show_success "Name: $(git config --get user.name)" || show_error "No user.name set" 924 git config --get user.email && show_success "Email: $(git config --get user.email)" || show_error "No user.email set" 925 926 echo "" 927 wait_for_key 928 } 929 930 # ============================================================================ 931 # INSTALL OH-MY-ZSH 932 # ============================================================================ 933 934 install_omz() { 935 show_header 936 gum style --foreground 183 --bold "🎨 Install Oh-My-Zsh" 937 echo "" 938 939 if [[ -d "$HOME/.oh-my-zsh" ]]; then 940 show_info "Oh-My-Zsh already installed" 941 echo "" 942 wait_for_key 943 return 944 fi 945 946 gum style --foreground 183 "This will install Oh-My-Zsh with recommended settings." 947 echo "" 948 949 if ! confirm "Install Oh-My-Zsh?"; then 950 return 951 fi 952 953 echo "" 954 sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended 955 956 echo "" 957 show_success "Oh-My-Zsh installed!" 958 959 echo "" 960 wait_for_key 961 } 962 963 # ============================================================================ 964 # MAIN MENU 965 # ============================================================================ 966 967 main_menu() { 968 while true; do 969 show_header 970 971 echo "" 972 gum style --foreground 183 --italic "What magical task shall we perform today?" 973 echo "" 974 975 local choice=$(gum choose \ 976 --cursor.foreground 219 \ 977 --selected.foreground 183 \ 978 --cursor "✨ " \ 979 --height 12 \ 980 "🚀 Bootstrap (fresh machine setup)" \ 981 "💾 Backup (Brewfile & packages)" \ 982 "🔐 Backup secrets (GPG/SSH/Age/Skate/OMZ)" \ 983 "🔓 Restore secrets" \ 984 "🔗 Link dotfiles" \ 985 "🔍 Check dependencies" \ 986 "🌳 Git operations" \ 987 "🎨 Install Oh-My-Zsh" \ 988 "🚪 Exit") 989 990 case "$choice" in 991 *Bootstrap*) 992 run_bootstrap 993 ;; 994 *"Backup ("*) 995 run_backup 996 ;; 997 *"Backup secrets"*) 998 run_backup_secrets 999 ;; 1000 *Restore*) 1001 run_restore_secrets 1002 ;; 1003 *Link*) 1004 run_link 1005 ;; 1006 *Check*) 1007 run_check 1008 ;; 1009 *Git*) 1010 git_menu 1011 ;; 1012 *Oh-My-Zsh*) 1013 install_omz 1014 ;; 1015 *Exit*) 1016 clear 1017 gum style \ 1018 --foreground 219 \ 1019 --align center \ 1020 --padding "1 2" \ 1021 --border rounded \ 1022 --border-foreground 183 \ 1023 "✨ Thank you for using Dasho's Dotfiles Wizard! ✨" \ 1024 "" \ 1025 "May your configs be ever in your favor! 🧙" 1026 echo "" 1027 exit 0 1028 ;; 1029 esac 1030 done 1031 } 1032 1033 # ============================================================================ 1034 # ENTRY POINT 1035 # ============================================================================ 1036 1037 # First-time bootstrap of wizard itself 1038 wizard_bootstrap 1039 1040 # Launch main menu 1041 main_menu