install.sh 19.7 K raw
1
#!/bin/bash
2
3
# Darkmatter Terminal Setup - Remote configuration installer
4
# Downloads and installs terminal configuration from GitHub
5
# Requires: Homebrew (https://brew.sh)
6
7
set -euo pipefail  # Exit on any error, undefined variables, and pipe failures
8
9
# Configuration - Update these URLs to match your GitHub repo
10
GITHUB_RAW_BASE="https://raw.githubusercontent.com/stevedylandev/darkmatter/main"
11
TEMP_DIR="/tmp/darkmatter_install"
12
DEBUG_MODE="${DEBUG:-false}"
13
14
# Colors for output
15
RED='\033[0;31m'
16
GREEN='\033[0;32m'
17
YELLOW='\033[1;33m'
18
BLUE='\033[0;34m'
19
PURPLE='\033[0;35m'
20
NC='\033[0m' # No Color
21
22
# Function to print colored output
23
print_status() {
24
    echo -e "${BLUE}[INFO]${NC} $1"
25
}
26
27
print_success() {
28
    echo -e "${GREEN}[SUCCESS]${NC} $1"
29
}
30
31
print_warning() {
32
    echo -e "${YELLOW}[WARNING]${NC} $1"
33
}
34
35
print_error() {
36
    echo -e "${RED}[ERROR]${NC} $1"
37
}
38
39
print_debug() {
40
    if [ "$DEBUG_MODE" = "true" ]; then
41
        echo -e "${PURPLE}[DEBUG]${NC} $1"
42
    fi
43
}
44
45
# Enhanced error handler
46
error_handler() {
47
    local line_number="$1"
48
    local error_code="$2"
49
    local command="$BASH_COMMAND"
50
51
    print_error "Script failed at line $line_number with exit code $error_code"
52
    print_error "Failed command: $command"
53
    print_error "Current function: ${FUNCNAME[1]:-main}"
54
55
    # Show some context
56
    if [ -f "$0" ]; then
57
        print_error "Context around line $line_number:"
58
        sed -n "$((line_number-2)),$((line_number+2))p" "$0" | nl -ba
59
    fi
60
61
    cleanup
62
    exit $error_code
63
}
64
65
# Set up error trap
66
trap 'error_handler ${LINENO} $?' ERR
67
68
# Check if required tools are available
69
check_dependencies() {
70
    print_status "Checking dependencies..."
71
    print_debug "Checking for brew command..."
72
73
    if ! command -v brew &> /dev/null; then
74
        print_error "Homebrew is not installed. Please install it first:"
75
        print_error "https://brew.sh"
76
        exit 1
77
    fi
78
    print_success "Homebrew found at: $(which brew)"
79
80
    print_debug "Checking for curl command..."
81
    if ! command -v curl &> /dev/null; then
82
        print_error "curl is not installed. Please install it first."
83
        exit 1
84
    fi
85
}
86
87
# Create temporary directory for downloads
88
setup_temp_dir() {
89
    print_status "Setting up temporary directory..."
90
    print_debug "Removing existing temp dir: $TEMP_DIR"
91
92
    if [ -d "$TEMP_DIR" ]; then
93
        rm -rf "$TEMP_DIR" || {
94
            print_error "Failed to remove existing temp directory: $TEMP_DIR"
95
            exit 1
96
        }
97
    fi
98
99
    print_debug "Creating temp dir: $TEMP_DIR"
100
    mkdir -p "$TEMP_DIR" || {
101
        print_error "Failed to create temporary directory: $TEMP_DIR"
102
        exit 1
103
    }
104
105
    print_success "Temporary directory created: $TEMP_DIR"
106
    print_debug "Temp directory permissions: $(ls -ld "$TEMP_DIR")"
107
}
108
109
# Test GitHub connectivity
110
test_github_connection() {
111
    print_status "Testing GitHub connectivity..."
112
    print_debug "Testing connection to: $GITHUB_RAW_BASE"
113
114
    # Test with a simple HEAD request
115
    if curl -I -f -s --connect-timeout 10 --max-time 30 "$GITHUB_RAW_BASE/.zshrc" > /dev/null; then
116
        print_success "Successfully connected to GitHub repository"
117
    else
118
        local exit_code=$?
119
        print_error "Failed to connect to GitHub repository"
120
        print_error "URL tested: $GITHUB_RAW_BASE/.zshrc"
121
        print_error "curl exit code: $exit_code"
122
123
        # Try to give more specific error information
124
        case $exit_code in
125
            6) print_error "Could not resolve host - check your internet connection" ;;
126
            7) print_error "Failed to connect to host - check the repository URL" ;;
127
            22) print_error "HTTP error - the file might not exist or repository might be private" ;;
128
            28) print_error "Timeout - check your internet connection" ;;
129
            *) print_error "Unknown curl error - check the repository URL and your internet connection" ;;
130
        esac
131
132
        exit 1
133
    fi
134
}
135
136
# Download file from GitHub with detailed error reporting
137
download_file() {
138
    local filename="$1"
139
    local url="$GITHUB_RAW_BASE/$filename"
140
    local dest="$TEMP_DIR/$filename"
141
142
    print_status "Downloading $filename..."
143
    print_debug "From: $url"
144
    print_debug "To: $dest"
145
146
    # Create directory if needed
147
    local dest_dir=$(dirname "$dest")
148
    if [ ! -d "$dest_dir" ]; then
149
        mkdir -p "$dest_dir" || {
150
            print_error "Failed to create directory: $dest_dir"
151
            return 1
152
        }
153
    fi
154
155
    # Download with verbose error reporting
156
    local curl_output
157
    curl_output=$(curl -L -f -s -w "HTTP_CODE:%{http_code};SIZE:%{size_download};TIME:%{time_total}" -o "$dest" "$url" 2>&1) || {
158
        local exit_code=$?
159
        print_error "Failed to download $filename"
160
        print_error "URL: $url"
161
        print_error "curl exit code: $exit_code"
162
        print_error "curl output: $curl_output"
163
        return 1
164
    }
165
166
    print_debug "curl response: $curl_output"
167
168
    # Verify file was downloaded and has content
169
    if [ ! -f "$dest" ]; then
170
        print_error "File was not created: $dest"
171
        return 1
172
    fi
173
174
    local file_size=$(wc -c < "$dest" 2>/dev/null || echo "0")
175
    if [ "$file_size" -eq 0 ]; then
176
        print_error "Downloaded file is empty: $filename"
177
        return 1
178
    fi
179
180
    print_success "Downloaded $filename (${file_size} bytes)"
181
    print_debug "File content preview:"
182
    if [ "$DEBUG_MODE" = "true" ]; then
183
        head -3 "$dest" || true
184
    fi
185
186
    return 0
187
}
188
189
# Download all configuration files
190
download_configs() {
191
    print_status "Downloading configuration files from GitHub..."
192
193
    local files=(
194
        ".zshrc"
195
        "config"
196
        "dark.tmTheme"
197
    )
198
199
    local success_count=0
200
    local total_files=${#files[@]}
201
202
    print_debug "Starting download loop for ${total_files} files"
203
204
    for file in "${files[@]}"; do
205
        print_debug "=== Processing file $((success_count + 1))/$total_files: $file ==="
206
207
        # Disable error exit temporarily for this specific operation
208
        set +e
209
        download_file "$file"
210
        local download_result=$?
211
        set -e
212
213
        print_debug "Download result for $file: $download_result"
214
215
        if [ $download_result -eq 0 ]; then
216
            success_count=$((success_count + 1))
217
            print_success "Successfully downloaded: $file (count: $success_count)"
218
        else
219
            print_error "Failed to download $file (exit code: $download_result)"
220
            print_error "This will cause installation issues"
221
            # Continue with other files to see what we can get
222
        fi
223
224
        print_debug "Completed processing $file, moving to next file"
225
    done
226
227
    print_debug "Download loop completed"
228
    print_status "Downloaded $success_count/$total_files configuration files"
229
230
    if [ $success_count -eq $total_files ]; then
231
        print_success "All configuration files downloaded successfully"
232
    else
233
        print_error "Failed to download some configuration files ($success_count/$total_files succeeded)"
234
        print_error "Installation cannot continue without all config files"
235
236
        # Show what files we do have
237
        print_status "Files in temp directory:"
238
        ls -la "$TEMP_DIR" || true
239
240
        exit 1
241
    fi
242
}
243
244
# Download and install font
245
download_and_install_font() {
246
    print_status "Downloading CommitMono Nerd Font..."
247
248
    local font_filename="CommitMonoNerdFont-Regular.otf"
249
    local font_url="$GITHUB_RAW_BASE/assets/$font_filename"
250
    local font_dest="$TEMP_DIR/$font_filename"
251
252
    print_debug "Font download URL: $font_url"
253
254
    # Create assets subdirectory in temp
255
    mkdir -p "$TEMP_DIR/assets"
256
    font_dest="$TEMP_DIR/assets/$font_filename"
257
258
    if curl -L -f -s  -o "$font_dest" "$font_url" 2>/dev/null; then
259
        local file_size=$(wc -c < "$font_dest" 2>/dev/null || echo "0")
260
        print_success "Downloaded CommitMono Nerd Font (${file_size} bytes)"
261
262
        # Verify it's actually a font file (should be reasonably large)
263
        if [ "$file_size" -lt 10000 ]; then
264
            print_warning "Font file seems too small, might be an error page"
265
            print_debug "Font file content preview:"
266
            if [ "$DEBUG_MODE" = "true" ]; then
267
                head -3 "$font_dest" || true
268
            fi
269
        fi
270
    else
271
        print_warning "Failed to download font from $font_url"
272
        print_warning "Continuing without font installation..."
273
        return 1
274
    fi
275
276
    # Install the font
277
    print_status "Installing CommitMono Nerd Font..."
278
279
    local user_fonts_dir="$HOME/Library/Fonts"
280
    print_debug "Font installation directory: $user_fonts_dir"
281
282
    if [ ! -d "$user_fonts_dir" ]; then
283
        mkdir -p "$user_fonts_dir" || {
284
            print_error "Failed to create fonts directory: $user_fonts_dir"
285
            return 1
286
        }
287
    fi
288
289
    local dest_file="$user_fonts_dir/$font_filename"
290
291
    if [ -f "$dest_file" ]; then
292
        print_warning "Font $font_filename already installed, skipping"
293
    else
294
        cp "$font_dest" "$dest_file" || {
295
            print_error "Failed to copy font to $dest_file"
296
            return 1
297
        }
298
        print_success "Installed font: $font_filename"
299
300
        # Clear font cache on macOS
301
        print_status "Refreshing font cache..."
302
        if command -v atsutil &> /dev/null; then
303
            sudo atsutil databases -remove 2>/dev/null || print_debug "atsutil databases -remove failed"
304
            atsutil server -shutdown 2>/dev/null || print_debug "atsutil server -shutdown failed"
305
            atsutil server -ping 2>/dev/null || print_debug "atsutil server -ping failed"
306
        else
307
            print_debug "atsutil not found, skipping font cache refresh"
308
        fi
309
310
        print_success "Font installation complete!"
311
        print_status "You may need to restart applications to see the new font"
312
    fi
313
}
314
315
# Install packages via Homebrew with better error handling
316
install_packages() {
317
    print_status "Installing packages via Homebrew..."
318
319
    local packages=(
320
        "zsh"
321
        "zsh-autosuggestions"
322
        "zsh-syntax-highlighting"
323
        "starship"
324
        "eza"
325
        "zoxide"
326
        "aichat"
327
        "btop"
328
        "fzf"
329
        "bat"
330
    )
331
332
    local cask_packages=(
333
        "ghostty"
334
    )
335
336
    # Install regular packages
337
    for package in "${packages[@]}"; do
338
        print_status "Installing $package..."
339
        print_debug "Checking if $package is already installed..."
340
341
        if brew list "$package" &>/dev/null; then
342
            print_warning "$package is already installed"
343
        else
344
            print_debug "Installing $package with brew..."
345
            if brew install "$package"; then
346
                print_success "Installed $package"
347
            else
348
                print_error "Failed to install $package"
349
                # Don't exit, try to continue with other packages
350
            fi
351
        fi
352
    done
353
354
    # Install cask packages
355
    for package in "${cask_packages[@]}"; do
356
        print_status "Installing $package (cask)..."
357
        print_debug "Checking if cask $package is already installed..."
358
359
        if brew list --cask "$package" &>/dev/null; then
360
            print_warning "$package is already installed"
361
        else
362
            print_debug "Installing cask $package with brew..."
363
            if brew install --cask "$package"; then
364
                print_success "Installed $package"
365
            else
366
                print_warning "Failed to install $package (cask) - this might not be critical"
367
                # Ghostty might not be available, but don't fail the whole script
368
            fi
369
        fi
370
    done
371
}
372
373
# Backup existing config files
374
backup_configs() {
375
    print_status "Backing up existing configuration files..."
376
377
    local timestamp=$(date +%Y%m%d_%H%M%S)
378
    local backup_dir="$HOME/.config_backup_$timestamp"
379
380
    print_debug "Backup directory: $backup_dir"
381
382
    mkdir -p "$backup_dir" || {
383
        print_error "Failed to create backup directory: $backup_dir"
384
        exit 1
385
    }
386
387
    local backed_up=false
388
389
    if [ -f "$HOME/.zshrc" ]; then
390
        cp "$HOME/.zshrc" "$backup_dir/" && {
391
            print_success "Backed up .zshrc to $backup_dir"
392
            backed_up=true
393
        } || print_warning "Failed to backup .zshrc"
394
    fi
395
396
    if [ -f "$HOME/.config/ghostty/config" ]; then
397
        mkdir -p "$backup_dir/.config/ghostty"
398
        cp "$HOME/.config/ghostty/config" "$backup_dir/.config/ghostty/" && {
399
            print_success "Backed up ghostty config to $backup_dir"
400
            backed_up=true
401
        } || print_warning "Failed to backup ghostty config"
402
    fi
403
404
    if [ "$backed_up" = false ]; then
405
        print_status "No existing configuration files to backup"
406
        rmdir "$backup_dir" 2>/dev/null || true
407
    fi
408
}
409
410
check_darkmatter_in_zshrc() {
411
    local zshrc_file="$1"
412
413
    if [ ! -f "$zshrc_file" ]; then
414
        return 1  # File doesn't exist
415
    fi
416
417
    # Look for our marker comment
418
    if grep -q "# Darkmatter Terminal Configuration" "$zshrc_file" 2>/dev/null; then
419
        return 0  # Already present
420
    else
421
        return 1  # Not present
422
    fi
423
}
424
425
426
# Copy downloaded configuration files to their destinations
427
install_configs() {
428
    print_status "Installing configuration files..."
429
430
    local install_errors=0
431
432
    # Handle .zshrc installation
433
    if [ -f "$TEMP_DIR/.zshrc" ]; then
434
        if [ -f "$HOME/.zshrc" ]; then
435
            # Existing .zshrc found
436
            print_status "Existing .zshrc found, checking for Darkmatter configuration..."
437
438
            if check_darkmatter_in_zshrc "$HOME/.zshrc"; then
439
                print_warning "Darkmatter configuration already exists in .zshrc"
440
                print_status "Skipping .zshrc modification to avoid duplicates"
441
            else
442
                print_status "Appending Darkmatter configuration to existing .zshrc"
443
444
                # Add a separator and our config
445
                {
446
                    echo ""
447
                    echo "# ============================================="
448
                    echo "# Darkmatter Terminal Configuration"
449
                    echo "# Added by Darkmatter installer on $(date)"
450
                    echo "# ============================================="
451
                    echo ""
452
                    cat "$TEMP_DIR/.zshrc"
453
                } >> "$HOME/.zshrc" && {
454
                    print_success "Successfully appended Darkmatter configuration to .zshrc"
455
                } || {
456
                    print_error "Failed to append to existing .zshrc"
457
                    ((install_errors++))
458
                }
459
            fi
460
        else
461
            # No existing .zshrc, create new one
462
            print_status "No existing .zshrc found, creating new one"
463
            if cp "$TEMP_DIR/.zshrc" "$HOME/"; then
464
                print_success "Installed .zshrc"
465
            else
466
                print_error "Failed to install .zshrc"
467
                ((install_errors++))
468
            fi
469
        fi
470
    else
471
        print_error ".zshrc not found in downloaded files: $TEMP_DIR/.zshrc"
472
        ((install_errors++))
473
    fi
474
475
    # Install ghostty config (keep existing logic for replacement)
476
    if [ -f "$TEMP_DIR/config" ]; then
477
        print_debug "Installing ghostty config to $HOME/.config/ghostty/"
478
        mkdir -p "$HOME/.config/ghostty"
479
480
        if [ -f "$HOME/.config/ghostty/config" ]; then
481
            print_status "Existing Ghostty config found, it will be replaced"
482
            print_status "(Previous config was backed up)"
483
        fi
484
485
        if cp "$TEMP_DIR/config" "$HOME/.config/ghostty/"; then
486
            print_success "Installed Ghostty config"
487
        else
488
            print_error "Failed to install Ghostty config"
489
            ((install_errors++))
490
        fi
491
    else
492
        print_error "Ghostty config not found in downloaded files: $TEMP_DIR/config"
493
        ((install_errors++))
494
    fi
495
496
    # Install aichat theme
497
    if [ -f "$TEMP_DIR/dark.tmTheme" ]; then
498
        print_debug "Installing aichat theme to $HOME/Library/Application Support/aichat/"
499
        mkdir -p "$HOME/Library/Application Support/aichat"
500
501
        if [ -f "$HOME/Library/Application Support/aichat/dark.tmTheme" ]; then
502
            print_status "Existing aichat theme found, it will be replaced"
503
        fi
504
505
        if cp "$TEMP_DIR/dark.tmTheme" "$HOME/Library/Application Support/aichat/"; then
506
            print_success "Installed aichat theme"
507
        else
508
            print_error "Failed to install aichat theme"
509
            ((install_errors++))
510
        fi
511
    else
512
        print_error "aichat theme not found in downloaded files: $TEMP_DIR/dark.tmTheme"
513
        ((install_errors++))
514
    fi
515
516
    if [ $install_errors -gt 0 ]; then
517
        print_error "$install_errors configuration files failed to install"
518
        exit 1
519
    fi
520
}
521
522
# Set zsh as default shell
523
set_default_shell() {
524
    print_status "Checking default shell..."
525
526
    local current_shell="${SHELL}"
527
    local zsh_path
528
    zsh_path=$(which zsh) || {
529
        print_error "zsh not found in PATH"
530
        exit 1
531
    }
532
533
    print_debug "Current shell: $current_shell"
534
    print_debug "zsh path: $zsh_path"
535
536
    if [ "$current_shell" != "$zsh_path" ]; then
537
        print_status "Setting zsh as default shell..."
538
539
        # Add zsh to /etc/shells if not present
540
        if ! grep -q "$zsh_path" /etc/shells 2>/dev/null; then
541
            print_debug "Adding $zsh_path to /etc/shells"
542
            echo "$zsh_path" | sudo tee -a /etc/shells > /dev/null || {
543
                print_error "Failed to add zsh to /etc/shells"
544
                exit 1
545
            }
546
        fi
547
548
        # Change default shell
549
        print_debug "Changing default shell to $zsh_path"
550
        if chsh -s "$zsh_path"; then
551
            print_success "Default shell set to zsh"
552
            print_warning "Please restart your terminal or run 'exec zsh' to use the new shell"
553
        else
554
            print_error "Failed to change default shell"
555
            exit 1
556
        fi
557
    else
558
        print_success "zsh is already the default shell"
559
    fi
560
}
561
562
# Cleanup temporary files
563
cleanup() {
564
    if [ -d "$TEMP_DIR" ]; then
565
        print_debug "Cleaning up temporary files from: $TEMP_DIR"
566
        rm -rf "$TEMP_DIR" || print_warning "Failed to clean up temporary directory"
567
        print_success "Cleanup complete"
568
    fi
569
}
570
571
# Show debug information
572
show_debug_info() {
573
    if [ "$DEBUG_MODE" = "true" ]; then
574
        print_debug "=== Debug Information ==="
575
        print_debug "Script: $0"
576
        print_debug "Working directory: $(pwd)"
577
        print_debug "User: $USER"
578
        print_debug "Home: $HOME"
579
        print_debug "Shell: $SHELL"
580
        print_debug "PATH: $PATH"
581
        print_debug "GitHub base URL: $GITHUB_RAW_BASE"
582
        print_debug "Temp directory: $TEMP_DIR"
583
        print_debug "========================="
584
    fi
585
}
586
587
# Main installation function
588
main() {
589
    echo "🌑 Darkmatter Setup Installer (Remote)"
590
    echo "======================================"
591
    echo
592
593
    # Enable debug mode if requested
594
    if [ "${1:-}" = "--debug" ] || [ "${1:-}" = "-d" ]; then
595
        DEBUG_MODE="true"
596
        print_status "Debug mode enabled"
597
    fi
598
599
    show_debug_info
600
601
    # Check if GITHUB_RAW_BASE needs to be updated
602
    if [[ "$GITHUB_RAW_BASE" == *"your-username/your-repo"* ]]; then
603
        print_error "Please update the GITHUB_RAW_BASE variable in this script"
604
        print_error "Set it to your actual GitHub repository URL"
605
        print_error "Example: https://raw.githubusercontent.com/username/repo-name/main"
606
        exit 1
607
    fi
608
609
    print_status "Starting installation process..."
610
611
    check_dependencies
612
    setup_temp_dir
613
    install_packages
614
    test_github_connection
615
    download_configs
616
    download_and_install_font
617
    backup_configs
618
    install_configs
619
    set_default_shell
620
    cleanup
621
622
    echo
623
    print_success "Installation complete! 🌌"
624
    echo
625
    print_status "Next steps:"
626
    echo "  1. Restart your terminal or run: exec zsh"
627
    echo "  2. Open Ghostty to use your new terminal setup"
628
    echo "  3. Enjoy your Darkmatter terminal experience!"
629
    echo
630
}
631
632
# Handle script interruption
633
trap cleanup EXIT
634
635
# Run main function with all arguments
636
main "$@"