#!/bin/bash # Dotfiles Installation Script - Cross-Platform # Optimiert für Fish + Ghostty + Neovim Scratch Setup set -e # Exit on error DOTFILES_DIR="$HOME/gits/dotfiles" BACKUP_DIR="$HOME/.config-backup-$(date +%Y%m%d-%H%M%S)" # Colors GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' BLUE='\033[0;34m' NC='\033[0m' # Package manager and OS detection PACKAGE_MANAGER="" OS="" log() { echo -e "${GREEN}[INFO]${NC} $1"; } warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } error() { echo -e "${RED}[ERROR]${NC} $1"; } detect_os() { log "Detecting operating system..." if [[ "$OSTYPE" == "darwin"* ]]; then OS="macos" PACKAGE_MANAGER="brew" elif [[ -f /etc/arch-release ]]; then OS="arch" PACKAGE_MANAGER="pacman" elif [[ -f /etc/debian_version ]]; then OS="debian" PACKAGE_MANAGER="apt" elif [[ -f /etc/fedora-release ]]; then OS="fedora" PACKAGE_MANAGER="dnf" else error "Unsupported operating system" exit 1 fi log "Detected: $OS with $PACKAGE_MANAGER" } install_package_manager() { case $PACKAGE_MANAGER in "brew") if ! command -v brew &> /dev/null; then log "Installing Homebrew..." /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" fi ;; "pacman") log "Pacman detected - updating package database..." sudo pacman -Sy ;; "apt") log "APT detected - updating package database..." sudo apt update ;; "dnf") log "DNF detected - updating package database..." sudo dnf check-update || true ;; esac } install_package() { local package=$1 log "Installing $package..." case $PACKAGE_MANAGER in "brew") brew install "$package" ;; "pacman") sudo pacman -S --noconfirm "$package" ;; "apt") sudo apt install -y "$package" ;; "dnf") sudo dnf install -y "$package" ;; esac } install_cask() { local app=$1 log "Installing $app..." case $PACKAGE_MANAGER in "brew") brew install --cask "$app" ;; "pacman") # Use yay for AUR packages if command -v yay &> /dev/null; then yay -S --noconfirm "$app" fi ;; *) warn "GUI app installation for $app not supported on $OS" ;; esac } header() { echo -e "${BLUE} ╔════════════════════════════════════╗ ║ QA Engineer Dev Setup ║ ║ TypeScript/Playwright + Go ║ ╚════════════════════════════════════╝${NC}" } check_prerequisites() { log "Checking prerequisites..." detect_os install_package_manager # Check dotfiles directory if [ ! -d "$DOTFILES_DIR" ]; then error "Dotfiles directory not found: $DOTFILES_DIR" log "Please ensure your dotfiles are at: $DOTFILES_DIR" exit 1 fi } backup_configs() { log "Creating backup..." mkdir -p "$BACKUP_DIR" # Backup existing configs [ -d ~/.config/fish ] && cp -r ~/.config/fish "$BACKUP_DIR/" [ -d ~/.config/ghostty ] && cp -r ~/.config/ghostty "$BACKUP_DIR/" [ -d ~/.config/nvim ] && cp -r ~/.config/nvim "$BACKUP_DIR/" [ -d ~/.config/neofetch ] && cp -r ~/.config/neofetch "$BACKUP_DIR/" [ -d ~/.config/btop ] && cp -r ~/.config/btop "$BACKUP_DIR/" [ -d ~/.config/htop ] && cp -r ~/.config/htop "$BACKUP_DIR/" [ -d ~/.config/yabai ] && cp -r ~/.config/yabai "$BACKUP_DIR/" [ -d ~/.config/mise ] && cp -r ~/.config/mise "$BACKUP_DIR/" [ -f ~/.config/starship.toml ] && cp ~/.config/starship.toml "$BACKUP_DIR/" [ -f ~/.tmux.conf ] && cp ~/.tmux.conf "$BACKUP_DIR/" [ -d ~/.config/tmux ] && cp -r ~/.config/tmux "$BACKUP_DIR/" log "Backup created: $BACKUP_DIR" } install_1password_cli() { log "Installing 1Password CLI..." if command -v op &> /dev/null; then log "1Password CLI is already installed." return fi case $OS in "macos") install_cask 1password-cli ;; "arch") if command -v yay &> /dev/null; then yay -S --noconfirm 1password-cli else sudo pacman -S --noconfirm 1password-cli fi ;; "debian") log "Configuring APT repository for 1Password..." curl -sS https://downloads.1password.com/linux/keys/1password.asc | sudo gpg --dearmor --output /usr/share/keyrings/1password-archive-keyring.gpg echo 'deb [arch=amd64 signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] https://downloads.1password.com/linux/debian/amd64 stable main' | sudo tee /etc/apt/sources.list.d/1password.list sudo apt update && sudo apt install -y 1password-cli ;; "fedora") log "Configuring DNF repository for 1Password..." sudo rpm --import https://downloads.1password.com/linux/keys/1password.asc sudo sh -c 'echo -e "[1password]\nname=1Password Stable Channel\nbaseurl=https://downloads.1password.com/linux/rpm/stable/\$basearch\nenabled=1\ngpgcheck=1\ngpgkey=https://downloads.1password.com/linux/keys/1password.asc" > /etc/yum.repos.d/1password.repo' sudo dnf install -y 1password-cli ;; esac } install_core_tools() { log "Installing core development tools..." case $OS in "macos") # Essential CLI tools install_package git install_package neovim install_package fish install_package starship install_package fzf install_package ripgrep install_package fd install_package bat install_package btop install_package neofetch install_package tmux # Development install_package go install_package python3 install_package mise # Terminal install_cask ghostty # Optional tools install_package direnv install_package lazygit # Window manager log "Tapping koekeishiya/formulae for yabai..." brew tap koekeishiya/formulae install_package yabai ;; "arch") # Check if yay is installed if ! command -v yay &> /dev/null; then log "Installing yay AUR helper..." sudo pacman -S --needed base-devel git git clone https://aur.archlinux.org/yay.git /tmp/yay cd /tmp/yay && makepkg -si --noconfirm cd - && rm -rf /tmp/yay fi # Install via yay yay -S --noconfirm \ git neovim fish starship fzf ripgrep fd bat btop neofetch tmux \ go python mise \ ghostty direnv lazygit ;; "debian"|"fedora") # Basic tools install_package git install_package fish install_package fzf install_package ripgrep install_package fd-find install_package bat install_package neofetch install_package tmux # Neovim (from source if needed) if ! command -v nvim &> /dev/null; then warn "Installing Neovim from source..." install_neovim_from_source fi # Starship curl -sS https://starship.rs/install.sh | sh # Development install_package golang install_package python3 # Install mise if ! command -v mise &> /dev/null; then log "Installing mise version manager..." curl https://mise.run | sh fi warn "Ghostty needs manual installation on $OS" ;; esac install_1password_cli log "Core tools installed!" } install_neovim_from_source() { log "Building Neovim from source..." # Install build dependencies case $OS in "debian") sudo apt install -y ninja-build gettext cmake unzip curl build-essential ;; "fedora") sudo dnf install -y ninja-build cmake gcc make unzip gettext curl ;; esac # Clone and build cd /tmp git clone https://github.com/neovim/neovim cd neovim git checkout stable make CMAKE_BUILD_TYPE=Release sudo make install cd ~ rm -rf /tmp/neovim log "Neovim installed from source" } setup_fish() { log "Setting up Fish shell..." # Install Fisher (Fish plugin manager) if ! fish -c "type -q fisher" 2>/dev/null; then log "Installing Fisher..." fish -c "curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source && fisher install jorgebucaran/fisher" fi # Remove existing fish config (if not symlink) if [ ! -L ~/.config/fish ] && [ -d ~/.config/fish ]; then rm -rf ~/.config/fish fi # Create symlink to dotfiles ln -sf "$DOTFILES_DIR/.config/fish" ~/.config/fish # Install Fish plugins log "Installing Fish plugins..." fish -c "fisher install jorgebucaran/nvm.fish" || true fish -c "fisher install PatrickF1/fzf.fish" || true fish -c "fisher install franciscolourenco/done" || true # Set as default shell local fish_path=$(which fish) if ! grep -q "$fish_path" /etc/shells 2>/dev/null; then echo "$fish_path" | sudo tee -a /etc/shells fi if [ "$SHELL" != "$fish_path" ]; then log "Setting Fish as default shell..." chsh -s "$fish_path" fi log "Fish shell configured!" } setup_neovim() { log "Setting up Neovim..." # Remove existing nvim config (if not symlink) if [ ! -L ~/.config/nvim ] && [ -d ~/.config/nvim ]; then rm -rf ~/.config/nvim fi # Create symlink ln -sf "$DOTFILES_DIR/.config/nvim" ~/.config/nvim log "Neovim configured! Run 'nvim' to install plugins on first start." } setup_other_configs() { log "Setting up other configurations..." # Ghostty if [ -d "$DOTFILES_DIR/.config/ghostty" ]; then rm -rf ~/.config/ghostty ln -sf "$DOTFILES_DIR/.config/ghostty" ~/.config/ghostty fi # Starship ln -sf "$DOTFILES_DIR/.config/starship.toml" ~/.config/starship.toml # Neofetch if [ -d "$DOTFILES_DIR/.config/neofetch" ]; then rm -rf ~/.config/neofetch ln -sf "$DOTFILES_DIR/.config/neofetch" ~/.config/neofetch fi # Btop if [ -d "$DOTFILES_DIR/.config/btop" ]; then mkdir -p ~/.config/btop ln -sf "$DOTFILES_DIR/.config/btop/btop.conf" ~/.config/btop/btop.conf [ -d "$DOTFILES_DIR/.config/btop/themes" ] && ln -sf "$DOTFILES_DIR/.config/btop/themes" ~/.config/btop/themes fi # Htop (fallback wenn btop abstürzt) if [ -d "$DOTFILES_DIR/.config/htop" ]; then mkdir -p ~/.config/htop ln -sf "$DOTFILES_DIR/.config/htop/htoprc" ~/.config/htop/htoprc fi # Yabai (macOS only) if [ "$OS" = "macos" ] && [ -d "$DOTFILES_DIR/.config/yabai" ]; then rm -rf ~/.config/yabai ln -sf "$DOTFILES_DIR/.config/yabai" ~/.config/yabai fi # Mise if [ -d "$DOTFILES_DIR/.config/mise" ]; then rm -rf ~/.config/mise ln -sf "$DOTFILES_DIR/.config/mise" ~/.config/mise fi # Tmux if [ -d "$DOTFILES_DIR/.config/tmux" ]; then rm -rf ~/.config/tmux ln -sf "$DOTFILES_DIR/.config/tmux" ~/.config/tmux # Symlink tmux.conf to home directory for backward compatibility rm -f ~/.tmux.conf ln -sf "$DOTFILES_DIR/.config/tmux/tmux.conf" ~/.tmux.conf fi log "Configurations symlinked!" } install_playwright() { log "Installing Playwright..." local MISE_CMD="mise" if [ -f "$HOME/.local/bin/mise" ]; then MISE_CMD="$HOME/.local/bin/mise" fi if command -v "$MISE_CMD" &> /dev/null; then log "Using mise node runtime for global packages..." "$MISE_CMD" exec -- npm install -g @playwright/test "$MISE_CMD" exec -- npx playwright install log "Playwright installed!" elif command -v npm &> /dev/null; then log "Using system npm..." npm install -g @playwright/test npx playwright install log "Playwright installed!" else warn "npm / mise not found, skipping Playwright" fi } install_optional_ai_tools() { # Check if stdin is a terminal (tty) to avoid hanging in automated setups if [ ! -t 0 ]; then log "Non-interactive shell detected, skipping optional AI tools installation." return 0 fi log "Checking Google Antigravity CLI status..." if ! command -v agy &> /dev/null; then warn "Google Antigravity CLI (agy) is not installed in the PATH." log "To install it, run the official installer: curl -sS https://antigravity.google/install.sh | bash" else log "Google Antigravity CLI (agy) is already installed." fi echo -e "\n${YELLOW}=== Optional AI CLI Tools ===${NC}" # Prompt helper prompt_yes_no() { local prompt_msg=$1 local response read -p "$prompt_msg [y/N]: " response case "$response" in [yY][eE][sS]|[yY]) return 0 ;; *) return 1 ;; esac } # 1. Claude Code if prompt_yes_no "Do you want to install Claude Code (@anthropic-ai/claude-code)?"; then log "Installing Claude Code..." local MISE_CMD="mise" [ -f "$HOME/.local/bin/mise" ] && MISE_CMD="$HOME/.local/bin/mise" if command -v "$MISE_CMD" &> /dev/null; then "$MISE_CMD" exec -- npm install -g @anthropic-ai/claude-code elif command -v npm &> /dev/null; then npm install -g @anthropic-ai/claude-code else warn "npm / mise not found, cannot install Claude Code." fi fi # 2. Simon Willison's LLM CLI (supports OpenAI, Claude, DeepSeek via plugins) if prompt_yes_no "Do you want to install Simon Willison's LLM CLI (supports ChatGPT, Claude, DeepSeek)?"; then log "Installing LLM CLI..." case $PACKAGE_MANAGER in "brew") brew install llm ;; *) if command -v pip3 &> /dev/null; then pip3 install llm elif command -v pip &> /dev/null; then pip install llm else warn "pip not found, cannot install llm cli." fi ;; esac # Install plugins if llm is installed if command -v llm &> /dev/null; then log "Installing plugins for llm (llm-claude, llm-gpt3, llm-deepseek)..." llm install llm-claude llm install llm-gpt3 llm install llm-deepseek || true fi fi # 3. tgpt (Zero-config terminal GPT chat client) if prompt_yes_no "Do you want to install tgpt (Zero-config Terminal ChatGPT)?"; then log "Installing tgpt..." case $PACKAGE_MANAGER in "brew") brew install tgpt ;; *) curl -sSL https://raw.githubusercontent.com/aandrew-me/tgpt/main/install.sh | bash -s -- -y ;; esac fi # 4. Ollama (for running DeepSeek locally offline) if prompt_yes_no "Do you want to install Ollama (to run DeepSeek and other models locally)?"; then log "Installing Ollama..." case $OS in "macos") install_cask ollama ;; *) curl -fsSL https://ollama.com/install.sh | sh ;; esac log "To run DeepSeek locally after Ollama is running, execute: ollama run deepseek-coder" fi } main() { header check_prerequisites backup_configs install_core_tools setup_fish setup_neovim setup_other_configs # Install runtimes via mise local MISE_CMD="mise" if [ -f "$HOME/.local/bin/mise" ]; then MISE_CMD="$HOME/.local/bin/mise" fi if command -v "$MISE_CMD" &> /dev/null; then log "Installing language runtimes configured in mise..." "$MISE_CMD" install fi install_playwright install_optional_ai_tools echo -e "${GREEN} ╔══════════════════════════════════════════════════════════╗ ║ Setup Complete! ║ ║ ║ ║ ✓ $OS detected ║ ║ ✓ Fish shell configured ║ ║ ✓ Neovim (Scratch) configured ║ ║ ✓ Ghostty terminal configured ║ ║ ✓ Development tools installed ║ ║ ✓ Dotfiles symlinked ║ ║ ║ ║ Next steps: ║ ║ 1. Restart your terminal ║ ║ 2. Run 'nvim' to install plugins ║ ║ 3. Enjoy your setup! ║ ║ ║ ║ Backup: $BACKUP_DIR ║ ╚══════════════════════════════════════════════════════════╝${NC}" } main "$@"