diff --git a/.config/install/Brewfile b/.config/brew/Brewfile similarity index 80% rename from .config/install/Brewfile rename to .config/brew/Brewfile index ed12fdd..61972bb 100644 --- a/.config/install/Brewfile +++ b/.config/brew/Brewfile @@ -1,10 +1,5 @@ # Taps -tap "homebrew/bundle" -tap "homebrew/cask-versions" -tap "homebrew/services" -tap "laishulu/homebrew" tap "microsoft/git" -tap "nikitabobko/tap" # CLI Tools brew "bat" @@ -24,16 +19,16 @@ brew "git" brew "git-delta" brew "go" brew "gum" +brew "highlight" brew "htop" brew "httpie" brew "jq" brew "jsonlint" brew "lua-language-server" brew "luarocks" -brew "macism" brew "neovim" brew "node" -brew "nowplaying-cli" +brew "nowplaying-cli" if OS.mac? brew "nvm" brew "postgresql" brew "prettierd" @@ -44,9 +39,9 @@ brew "ripgrep" brew "ripgrep-all" brew "rust" brew "selene" -brew "starship" brew "shellcheck" brew "sqlite3" +brew "starship" brew "stow" brew "tcl-tk" brew "tmux" @@ -57,23 +52,24 @@ brew "yarn" brew "yazi" brew "zlib" brew "zoxide" +brew "zsh" # Cask Applications -cask "aerospace" +cask "aerospace" if OS.mac? cask "figma" cask "font-3270-nerd-font" cask "font-jetbrains-mono-nerd-font" cask "font-monaspice-nerd-font" -cask "font-sf-pro" +cask "font-sf-pro" if OS.mac? cask "ghostty" cask "google-chrome" -cask "imageoptim" +cask "imageoptim" if OS.mac? cask "insomnia" -cask "keycastr" +cask "keycastr" if OS.mac? cask "kitty" cask "obsidian" cask "pgadmin4" -cask "sf-symbols" +cask "sf-symbols" if OS.mac? cask "spotify" cask "visual-studio-code" cask "zoom" diff --git a/.config/gh-dash/config.yml b/.config/gh-dash/config.yml index cb452e8..7997aee 100644 --- a/.config/gh-dash/config.yml +++ b/.config/gh-dash/config.yml @@ -1,6 +1,8 @@ prSections: - title: Opened - filters: is:open + filters: is:open is:pr involves:@me -author:@me + - title: Mine + filters: is:open is:pr author:@me layout: author: hidden: true @@ -9,8 +11,6 @@ prSections: layout: repo: width: 6 - - title: Involved - filters: is:open involves:@me -author:@me issuesSections: - title: My Issues filters: is:open author:@me diff --git a/.config/gh/extensions b/.config/gh/extensions new file mode 100644 index 0000000..4f6858e --- /dev/null +++ b/.config/gh/extensions @@ -0,0 +1 @@ +dlvhdr/gh-dash diff --git a/.config/ghostty/config b/.config/ghostty/config index 9b5987a..ebb1079 100644 --- a/.config/ghostty/config +++ b/.config/ghostty/config @@ -1,4 +1,4 @@ -theme = Catppuccin Macchiato +theme = Rose Pine Moon shell-integration = zsh shell-integration-features = cursor diff --git a/.config/install.sh b/.config/install.sh new file mode 100755 index 0000000..8d8adaa --- /dev/null +++ b/.config/install.sh @@ -0,0 +1,459 @@ +#!/usr/bin/env bash + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +MAGENTA='\033[0;35m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +# Configuration +DOTFILES="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd .. && pwd)" +BREWFILE="$DOTFILES/.config/brew/Brewfile" +NO_GUI=false +IN_CODESPACES=false + +# Detect GitHub Codespaces +if [ -n "$CODESPACES" ]; then + IN_CODESPACES=true +fi + +# Parse command line arguments +parse_args() { + for arg in "$@"; do + case $arg in + --no-gui) + NO_GUI=true + shift + ;; + --help|-h) + show_help + exit 0 + ;; + *) + ;; + esac + done + + # Auto-enable --no-gui in Codespaces + if [ "$IN_CODESPACES" = true ]; then + NO_GUI=true + fi +} + +# Show help message +show_help() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --no-gui Skip installation of GUI applications (casks)" + echo " --help, -h Show this help message" + echo "" + echo "Example:" + echo " $0 # Install everything" + echo " $0 --no-gui # Install only CLI tools" +} + +# Helper functions +print_header() { + echo -e "\n${BLUE}==>${NC} ${1}" +} + +print_success() { + echo -e "${GREEN}✓${NC} ${1}" +} + +print_warning() { + echo -e "${YELLOW}!${NC} ${1}" +} + +print_error() { + echo -e "${RED}✗${NC} ${1}" +} + +print_info() { + echo -e "${CYAN}ℹ${NC} ${1}" +} + +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Detect OS +detect_os() { + case "$(uname -s)" in + Darwin*) + OS="macos" + print_success "Detected macOS" + ;; + Linux*) + OS="linux" + print_success "Detected Linux" + if [ "$IN_CODESPACES" = true ]; then + print_info "Running in GitHub Codespaces" + fi + ;; + *) + print_error "Unsupported operating system" + exit 1 + ;; + esac +} + +# Install Homebrew +install_homebrew() { + if command_exists brew; then + print_success "Homebrew already installed" + return + fi + + print_header "Installing Homebrew..." + + case "$OS" in + macos) + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + + # Add Homebrew to PATH for this session + if [ -d "/opt/homebrew" ]; then + eval "$(/opt/homebrew/bin/brew shellenv)" + else + eval "$(/usr/local/bin/brew shellenv)" + fi + ;; + linux) + if [ "$IN_CODESPACES" = true ]; then + NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + else + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + fi + + # Add Homebrew to PATH for Linux + if [ -d /home/linuxbrew/.linuxbrew ]; then + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + + # Add to shell profiles + # shellcheck disable=SC2016 + if ! grep -q "linuxbrew" "$HOME/.profile" 2>/dev/null; then + echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> "$HOME/.profile" + fi + + if [ -f "$HOME/.zshrc" ]; then + # shellcheck disable=SC2016 + if ! grep -q "linuxbrew" "$HOME/.zshrc"; then + echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> "$HOME/.zshrc" + fi + fi + fi + ;; + esac + + print_success "Homebrew installed" +} + +# Install oh-my-zsh +install_oh_my_zsh() { + if [ -d "$HOME/.oh-my-zsh" ]; then + print_success "oh-my-zsh already installed" + return + fi + + if ! command_exists zsh; then + print_warning "zsh not installed, skipping oh-my-zsh" + return + fi + + print_header "Installing oh-my-zsh..." + sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended + print_success "oh-my-zsh installed" +} + +# Set zsh as default shell +set_zsh_default() { + if ! command_exists zsh; then + print_warning "zsh not found, will be installed via Brewfile" + return + fi + + if [ "$SHELL" = "$(command -v zsh)" ]; then + print_success "zsh is already the default shell" + return + fi + + print_header "Setting zsh as default shell..." + local ZSH_PATH + ZSH_PATH="$(command -v zsh)" + + # Add zsh to /etc/shells if not present + if ! grep -q "$ZSH_PATH" /etc/shells 2>/dev/null; then + echo "$ZSH_PATH" | sudo tee -a /etc/shells >/dev/null + fi + + # Change default shell + if command_exists chsh; then + if chsh -s "$ZSH_PATH"; then + print_success "Default shell set to zsh (takes effect on next login)" + else + print_warning "Could not change default shell. Try manually: chsh -s $ZSH_PATH" + fi + else + print_warning "chsh not available. Run manually: chsh -s $ZSH_PATH" + fi +} + +# Install packages from Brewfile +install_brew_packages() { + if [ ! -f "$BREWFILE" ]; then + print_error "Brewfile not found at: $BREWFILE" + exit 1 + fi + + if [ "$NO_GUI" = true ]; then + print_header "Installing CLI packages only (skipping GUI applications)..." + # Filter out cask lines from Brewfile + local temp_brewfile="/tmp/brewfile-no-gui-$$" + grep -v '^cask' "$BREWFILE" > "$temp_brewfile" + brew bundle --file="$temp_brewfile" + rm -f "$temp_brewfile" + print_success "CLI packages installed" + else + print_header "Installing all packages from Brewfile..." + brew bundle --file="$BREWFILE" + print_success "All packages installed" + fi + + # Ensure Homebrew binaries are in PATH (important for CI) + if command_exists brew; then + eval "$(brew shellenv)" + fi +} + +# Install tmux plugin manager +install_tpm() { + local tpm_dir="$HOME/.tmux/plugins/tpm" + + if [ -d "$tpm_dir" ]; then + print_success "tmux plugin manager already installed" + return + fi + + print_header "Installing tmux plugin manager..." + git clone --depth=1 https://github.com/tmux-plugins/tpm "$tpm_dir" + print_success "tmux plugin manager installed" +} + +# Install UV (Python package manager) +install_uv() { + if command_exists uv; then + print_success "UV already installed" + return + fi + + print_header "Installing UV..." + if curl -LsSf https://astral.sh/uv/install.sh | sh; then + print_success "UV installed" + else + print_warning "UV installation failed (may not be available in this environment)" + fi +} + +# Install Claude CLI +install_claude_cli() { + if command_exists claude; then + print_success "Claude CLI already installed" + return + fi + + print_header "Installing Claude CLI..." + if curl -fsSL https://claude.ai/install.sh | sh; then + print_success "Claude CLI installed" + else + print_warning "Claude CLI installation failed (may not be available in this environment)" + fi +} + +# Install OpenCode +install_opencode() { + if command_exists opencode; then + print_success "OpenCode already installed" + return + fi + + print_header "Installing OpenCode..." + if curl -fsSL https://opencode.ai/install | sh; then + print_success "OpenCode installed" + else + print_warning "OpenCode installation failed (may not be available in this environment)" + fi +} + +# Install GitHub CLI extensions +install_gh_extensions() { + if ! command_exists gh; then + print_warning "gh CLI not installed, skipping extensions" + return + fi + + # Check if we're authenticated (required for extension installation) + if ! gh auth status >/dev/null 2>&1; then + print_warning "gh not authenticated, skipping extensions (set GH_TOKEN in CI)" + return + fi + + local extensions_file="$DOTFILES/.config/gh/extensions" + + if [ ! -f "$extensions_file" ]; then + print_info "No gh extensions file found at $extensions_file" + return + fi + + print_header "Installing GitHub CLI extensions..." + + local installed=0 + local skipped=0 + + # Read extensions file line by line + while IFS= read -r extension || [ -n "$extension" ]; do + # Skip empty lines and comments + [[ -z "$extension" || "$extension" =~ ^# ]] && continue + + # Trim whitespace + extension=$(echo "$extension" | xargs) + + # Check if already installed + if gh extension list 2>/dev/null | grep -q "$extension"; then + print_success "$extension already installed" + skipped=$((skipped + 1)) + else + echo " Installing $extension..." + if gh extension install "$extension"; then + print_success "$extension installed" + installed=$((installed + 1)) + else + print_warning "Failed to install $extension" + fi + fi + done < "$extensions_file" + + if [ $installed -eq 0 ] && [ $skipped -eq 0 ]; then + print_info "No extensions to install" + else + echo " Installed: $installed, Already present: $skipped" + fi +} + +# Stow dotfiles +stow_dotfiles() { + if ! command_exists stow; then + print_error "stow is not installed. Please install it first." + return 1 + fi + + print_header "Stowing dotfiles..." + + # Backup existing dotfiles that might conflict + local backup_dir + backup_dir="$HOME/.dotfiles-backup-$(date +%Y%m%d-%H%M%S)" + local needs_backup=false + + # Common dotfiles that might exist and aren't symlinks + local common_files=(".zshrc" ".zshenv" ".zprofile" ".gitconfig" ".tmux.conf" ".editorconfig") + + for file in "${common_files[@]}"; do + if [ -e "$HOME/$file" ] && [ ! -L "$HOME/$file" ]; then + if [ "$needs_backup" = false ]; then + mkdir -p "$backup_dir" + print_warning "Backing up existing dotfiles to $backup_dir" + needs_backup=true + fi + mv "$HOME/$file" "$backup_dir/" + echo " Backed up $file" + fi + done + + # Get parent directory and dotfiles name for stow + local parent_dir + local dotfiles_name + parent_dir="$(dirname "$DOTFILES")" + dotfiles_name="$(basename "$DOTFILES")" + + # Unstow first (in case of previous installation) + stow --dir="$parent_dir" --target="$HOME" -D "$dotfiles_name" 2>/dev/null || true + + # Stow the dotfiles directory with explicit flags + stow --dir="$parent_dir" --target="$HOME" --verbose "$dotfiles_name" + + print_success "Dotfiles stowed" + + if [ "$needs_backup" = true ]; then + print_warning "Your original dotfiles have been backed up to: $backup_dir" + fi +} + +# Print installation summary +print_summary() { + echo "" + echo -e "${GREEN}╔════════════════════════════════════════╗" + echo "║ Installation Complete! 🎉 ║" + echo "╚════════════════════════════════════════╝${NC}" + echo "" + echo -e "${CYAN}Configuration:${NC}" + echo " Dotfiles dir: $DOTFILES" + echo " Platform: $OS" + if [ "$IN_CODESPACES" = true ]; then + echo " Environment: GitHub Codespaces" + fi + if [ "$NO_GUI" = true ]; then + echo " GUI apps: Skipped" + else + echo " GUI apps: Installed" + fi + echo "" + print_warning "Please restart your terminal or run: source ~/.zshrc" + echo "" + + if [ "$IN_CODESPACES" = true ]; then + print_info "In Codespaces, you may need to reload your window for changes to take effect" + fi + + if [ "$OS" = "macos" ]; then + echo -e "${CYAN}Next steps:${NC}" + echo " • Configure your terminal theme" + echo " • Set up Git credentials: git config --global user.name 'Your Name'" + echo " • Set up Git email: git config --global user.email 'your@email.com'" + echo "" + fi +} + +# Main installation flow +main() { + parse_args "$@" + + echo -e "${MAGENTA}" + echo "╔════════════════════════════════════════╗" + echo "║ Dotfiles Setup & Installation ║" + echo "║ ║" + echo "║ This will install development tools ║" + echo "║ and configure your environment ║" + echo "╚════════════════════════════════════════╝" + echo -e "${NC}" + + detect_os + install_homebrew + install_brew_packages + install_gh_extensions + install_oh_my_zsh + set_zsh_default + install_tpm + install_uv + install_claude_cli + install_opencode + stow_dotfiles + print_summary +} + +# Run main function +main "$@" diff --git a/.config/install/install.sh b/.config/install/install.sh deleted file mode 100644 index e3ec4f9..0000000 --- a/.config/install/install.sh +++ /dev/null @@ -1,53 +0,0 @@ -# Get dotfiles dir (so run this script from anywhere) -export DOTFILES_DIR EXTRA_DIR -DOTFILES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd .. && pwd)" - -# Install oh-my-zsh if not installed -if [ ! -d "$HOME/.oh-my-zsh" ]; then - echo "Installing oh-my-zsh..." - sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" - echo "oh-my-zsh installation complete." -else - echo "oh-my-zsh already installed, skipping." -fi - -# Install Homebrew if not installed -if ! command -v brew &> /dev/null; then - echo "Installing Homebrew..." - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - echo "Homebrew installation complete." -else - echo "Homebrew already installed, skipping." -fi - -# Install brew packages & casks from brewfile -echo "Installing brew packages from Brewfile..." -brew bundle --file="$DOTFILES_DIR/install/brewfile" -echo "Brew packages installation complete." - -# Install UV if not installed -if ! command -v uv &> /dev/null; then - echo "Installing UV..." - sh -c "$(curl -fsSL https://astral.sh/uv/install.sh)" - echo "UV installation complete." -else - echo "UV already installed, skipping." -fi - -# Install Claude CLI if not installed -if ! command -v claude &> /dev/null; then - echo "Installing Claude CLI..." - curl -fsSL https://claude.ai/install.sh | sh - echo "Claude CLI installation complete." -else - echo "Claude CLI already installed, skipping." -fi - -# Install OpenCode if not installed: https://opencode.ai/docs -if ! command -v opencode &> /dev/null; then - echo "Installing OpenCode..." - curl -fsSL https://opencode.ai/install | sh - echo "OpenCode installation complete." -else - echo "OpenCode already installed, skipping." -fi diff --git a/.config/zsh/oh-my-zsh.zsh b/.config/zsh/oh-my-zsh.zsh index 24394d8..e1c47d1 100644 --- a/.config/zsh/oh-my-zsh.zsh +++ b/.config/zsh/oh-my-zsh.zsh @@ -1,7 +1,6 @@ #!/bin/env zsh - # Disable auto-update checks zstyle ':omz:plugins:nvm' lazy yes @@ -17,33 +16,33 @@ plugins=( z zsh-eza zsh-interactive-cd - zsh-autosuggestions - zsh-syntax-highlighting ) +# Auto-install custom plugins from GitHub if not present +# This runs on shell startup and ensures all plugins are available github_plugins=( - wfxr/forgit + zsh-users/zsh-autosuggestions + zsh-users/zsh-syntax-highlighting ) -# for plugin in $github_plugins; do -# plugin_name=${plugin#*/} -# # clone the plugin from github if it doesn't exist -# if [[ ! -d ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/$plugin_name ]]; then -# mkdir -p ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins -# git clone --depth 1 --recursive https://github.com/$plugin.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/$plugin_name -# fi -# # load the plugin -# for initscript in ${plugin_name}.zsh ${plugin_name}.plugin.zsh ${plugin_name}.sh; do -# if [[ -f ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/$plugin_name/$initscript ]]; then -# source ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/$plugin_name/$initscript -# break -# fi -# done -# done -# +for plugin in $github_plugins; do + plugin_name=${plugin#*/} + # clone the plugin from github if it doesn't exist + if [[ ! -d ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/$plugin_name ]]; then + mkdir -p ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins + git clone --depth 1 --recursive https://github.com/$plugin.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/$plugin_name + fi + # load the plugin + for initscript in ${plugin_name}.zsh ${plugin_name}.plugin.zsh ${plugin_name}.sh; do + if [[ -f ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/$plugin_name/$initscript ]]; then + source ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/$plugin_name/$initscript + break + fi + done +done + source $ZSH/oh-my-zsh.sh -# -# unset github_plugins -# unset plugin -# unset initscript -# + +unset github_plugins +unset plugin +unset initscript diff --git a/.gitconfig b/.gitconfig index 1cbbc6c..2ad07ed 100644 --- a/.gitconfig +++ b/.gitconfig @@ -10,6 +10,7 @@ [core] editor = nvim sparseCheckout = true + fsmonitor = false pager = delta --dark --diff-so-fancy multiPackIndex = true [rebase] diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..0cfeb8c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,328 @@ +name: Test Dotfiles + +on: + push: + branches: [master] + pull_request: + branches: [master] + workflow_dispatch: + +jobs: + syntax-validation: + name: Syntax Validation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install validation tools + run: | + sudo apt-get update + sudo apt-get install -y shellcheck zsh yamllint + + # Install taplo for TOML validation + curl -fsSL https://github.com/tamasfe/taplo/releases/latest/download/taplo-linux-x86_64.gz | gunzip -c > /tmp/taplo + sudo install -m 755 /tmp/taplo /usr/local/bin/taplo + + - name: Run syntax validation + run: | + chmod +x ./test.sh + ./test.sh + + test-macos: + name: Test on macOS + runs-on: macos-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Cache Homebrew packages + uses: actions/cache@v4 + with: + path: | + ~/Library/Caches/Homebrew + /opt/homebrew + key: ${{ runner.os }}-brew-${{ hashFiles('.config/brew/Brewfile') }} + restore-keys: | + ${{ runner.os }}-brew- + + - name: Run install script + run: | + # Run install script in non-interactive mode with --no-gui + bash .config/install.sh --no-gui + env: + CI: true + NONINTERACTIVE: 1 + + - name: Debug stow results + run: | + echo "=== Checking dotfiles in home directory ===" + ls -la ~/ | grep -E "^l.*\." || echo "No symlinks found" + echo "" + echo "=== Checking specific dotfiles ===" + for file in .zshrc .zshenv .gitconfig; do + if [ -L ~/$file ]; then + echo "✓ ~/$file is a symlink -> $(readlink ~/$file)" + elif [ -e ~/$file ]; then + echo "✗ ~/$file exists but is not a symlink" + else + echo "✗ ~/$file does not exist" + fi + done + + - name: Verify Homebrew environment + run: | + if [ -d "/opt/homebrew" ]; then + eval "$(/opt/homebrew/bin/brew shellenv)" + else + eval "$(/usr/local/bin/brew shellenv)" + fi + echo "Homebrew prefix: $(brew --prefix)" + echo "PATH: $PATH" + + - name: Verify zsh configuration loads + run: | + # Source Homebrew environment first + if [ -d "/opt/homebrew" ]; then + eval "$(/opt/homebrew/bin/brew shellenv)" + else + eval "$(/usr/local/bin/brew shellenv)" + fi + + # Try to load zsh config + if [ -f ~/.zshrc ]; then + zsh -c "source ~/.zshenv 2>/dev/null; source ~/.zshrc && echo 'zsh config loaded successfully'" || echo "zsh config has errors but continuing" + else + echo "~/.zshrc not found" + exit 1 + fi + + - name: Verify git configuration + run: | + if [ -L ~/.gitconfig ]; then + echo "✓ .gitconfig is a symlink" + else + echo "✗ .gitconfig is not a symlink" + ls -la ~/.gitconfig || echo "✗ .gitconfig does not exist" + exit 1 + fi + + echo "Git aliases:" + git config --list | grep "^alias\." | head -5 || echo "No git aliases found" + + if git config --list | grep -q "alias\.d=diff"; then + echo "✓ git alias 'd' configured correctly" + else + echo "✗ git alias 'd' not found" + exit 1 + fi + + - name: Verify tmux configuration exists + run: | + if [ -d "/opt/homebrew" ]; then + eval "$(/opt/homebrew/bin/brew shellenv)" + else + eval "$(/usr/local/bin/brew shellenv)" + fi + + if [ -L ~/.config/tmux ]; then + echo "✓ .config/tmux is a symlink" + ls -la ~/.config/tmux/tmux.conf || echo "tmux.conf not found" + else + echo "Note: .config/tmux may not be symlinked yet" + fi + + - name: List installed tools + run: | + if [ -d "/opt/homebrew" ]; then + eval "$(/opt/homebrew/bin/brew shellenv)" + else + eval "$(/usr/local/bin/brew shellenv)" + fi + + echo "=== Installed CLI Tools ===" + for cmd in zsh git stow fzf rg fd bat eza starship gh yazi; do + if command -v $cmd >/dev/null 2>&1; then + version=$($cmd --version 2>&1 | head -1 || echo "version unknown") + echo "✓ $cmd: $version" + else + echo "✗ $cmd: not installed" + fi + done + + test-ubuntu: + name: Test on Ubuntu + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Cache Homebrew packages + uses: actions/cache@v4 + with: + path: | + ~/.cache/Homebrew + /home/linuxbrew/.linuxbrew + key: ${{ runner.os }}-brew-${{ hashFiles('.config/brew/Brewfile') }} + restore-keys: | + ${{ runner.os }}-brew- + + - name: Run install script + run: | + # Run install script in non-interactive mode with --no-gui + bash .config/install.sh --no-gui + env: + CI: true + NONINTERACTIVE: 1 + + - name: Debug stow results + run: | + echo "=== Checking dotfiles in home directory ===" + ls -la ~/ | grep -E "^l.*\." || echo "No symlinks found" + echo "" + echo "=== Checking specific dotfiles ===" + for file in .zshrc .zshenv .gitconfig; do + if [ -L ~/$file ]; then + echo "✓ ~/$file is a symlink -> $(readlink ~/$file)" + elif [ -e ~/$file ]; then + echo "✗ ~/$file exists but is not a symlink" + else + echo "✗ ~/$file does not exist" + fi + done + + - name: Verify Homebrew environment + run: | + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + echo "Homebrew prefix: $(brew --prefix)" + echo "PATH: $PATH" + + - name: Verify zsh configuration loads + run: | + # Source Homebrew environment first + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + + # Try to load zsh config + if [ -f ~/.zshrc ]; then + zsh -c "source ~/.zshenv 2>/dev/null; source ~/.zshrc && echo 'zsh config loaded successfully'" || echo "zsh config has errors but continuing" + else + echo "~/.zshrc not found" + exit 1 + fi + + - name: Verify git configuration + run: | + if [ -L ~/.gitconfig ]; then + echo "✓ .gitconfig is a symlink" + else + echo "✗ .gitconfig is not a symlink" + ls -la ~/.gitconfig || echo "✗ .gitconfig does not exist" + exit 1 + fi + + echo "Git aliases:" + git config --list | grep "^alias\." | head -5 || echo "No git aliases found" + + if git config --list | grep -q "alias\.d=diff"; then + echo "✓ git alias 'd' configured correctly" + else + echo "✗ git alias 'd' not found" + exit 1 + fi + + - name: List installed tools + run: | + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + + echo "=== Installed CLI Tools ===" + for cmd in zsh git stow fzf rg fd bat eza starship gh yazi; do + if command -v $cmd >/dev/null 2>&1; then + version=$($cmd --version 2>&1 | head -1 || echo "version unknown") + echo "✓ $cmd: $version" + else + echo "✗ $cmd: not installed" + fi + done + + test-stow-structure: + name: Test Stow Structure + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install stow + run: sudo apt-get update && sudo apt-get install -y stow + + - name: Verify .stowrc exists + run: | + if [ -f .stowrc ]; then + echo "✓ .stowrc found" + cat .stowrc + else + echo "✗ .stowrc not found" + exit 1 + fi + + - name: Verify .stow-local-ignore exists + run: | + if [ -f .stow-local-ignore ]; then + echo "✓ .stow-local-ignore found" + echo "Ignored patterns:" + head -10 .stow-local-ignore + else + echo "✗ .stow-local-ignore not found" + exit 1 + fi + + - name: Test stow dry-run from parent directory + run: | + cd .. + echo "Testing stow operations (dry-run) from parent directory..." + stow -n -v dotfiles 2>&1 | tee stow-output.log + + # Check for conflicts + if grep -i "conflict" stow-output.log; then + echo "⚠️ Stow conflicts detected" + cat stow-output.log + exit 1 + else + echo "✓ No stow conflicts detected" + fi + + - name: Test actual stow + run: | + cd .. + # Actually create symlinks + stow -v dotfiles + + # Verify symlinks were created + echo "=== Verifying symlinks ===" + [ -L ~/.zshrc ] && echo "✓ .zshrc symlinked" || echo "✗ .zshrc not symlinked" + [ -L ~/.zshenv ] && echo "✓ .zshenv symlinked" || echo "✗ .zshenv not symlinked" + [ -L ~/.gitconfig ] && echo "✓ .gitconfig symlinked" || echo "✗ .gitconfig not symlinked" + + # Check .config directory + if [ -L ~/.config ] || [ -d ~/.config ]; then + echo "✓ .config exists" + ls -la ~/.config/ | head -10 + else + echo "✗ .config not found" + fi + + - name: Verify stow respects .stow-local-ignore + run: | + # These should NOT be symlinked + if [ -L ~/.git ] || [ -e ~/.git ]; then + echo "✗ .git was stowed (should be ignored)" + exit 1 + else + echo "✓ .git correctly ignored" + fi + + if [ -L ~/README.md ] || [ -e ~/README.md ]; then + echo "✗ README.md was stowed (should be ignored)" + exit 1 + else + echo "✓ README.md correctly ignored" + fi diff --git a/.gitignore b/.gitignore index 9595dab..5de39e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,12 @@ /* !/.git[a-z]* +!/.github/ !/delta.gitconfig !.editorconfig !.stowrc !.stow-local-ignore !/*.zsh +!/test.sh !/.config/ /.config/* @@ -16,7 +18,8 @@ !/.config/gh !/.config/gh-dash !/.config/ghostty -!/.config/install/ +!/.config/brew/ +!/.config/install.sh !/.config/kitty !/.config/nvim !/.config/opencode/ diff --git a/.zshenv b/.zshenv index d920300..12438d5 100644 --- a/.zshenv +++ b/.zshenv @@ -1,14 +1,38 @@ #!/usr/bin/env zsh +# Dynamically detect dotfiles directory from this file's location +# This works both locally and in CI where the repo path may differ +if [ -L "${(%):-%x}" ]; then + # If .zshenv is a symlink (stow), resolve to find dotfiles root + # readlink gives us the target: dotfiles/.zshenv + # We need to get the directory containing that file + local real_path + real_path="$(readlink "${(%):-%x}" 2>/dev/null)" + if [ -n "$real_path" ]; then + # Handle both absolute and relative symlinks + if [[ "$real_path" = /* ]]; then + # Absolute path: /full/path/to/dotfiles/.zshenv -> /full/path/to/dotfiles + DOTFILES="${real_path:h}" + else + # Relative path: dotfiles/.zshenv -> resolve from $HOME + DOTFILES="$HOME/${real_path:h}" + fi + else + DOTFILES="$HOME/dotfiles" + fi +else + # Fallback to default location + DOTFILES="$HOME/dotfiles" +fi + # Path to your oh-my-zsh installation. ZSH="$HOME/.oh-my-zsh" TIMING=0 EDITOR="nvim" VISUAL="nvim" NVM_DIR="$HOME/.nvm" -DOTFILES="$HOME/dotfiles" -DOTFILES_ZSH="$HOME/dotfiles/.config/zsh" -STARSHIP_CONFIG="$DOTFILES/.config/starship/config.toml" +DOTFILES_ZSH="$DOTFILES/.config/zsh" +STARSHIP_CONFIG="$DOTFILES/.config/starship.toml" SNACKS_GHOSTTY=true # DISABLE_AUTO_UPDATE="true" diff --git a/.zshrc b/.zshrc index c493546..82aaf17 100644 --- a/.zshrc +++ b/.zshrc @@ -38,3 +38,4 @@ eval "$(starship init zsh)" # End profiling # zprof +export PATH="$HOME/.local/bin:$PATH" diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..48a9a0d --- /dev/null +++ b/test.sh @@ -0,0 +1,280 @@ +#!/bin/bash + +# Dotfiles Syntax Validation Script +# Tests configuration files for syntax errors before committing + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +ERRORS=0 +WARNINGS=0 + +echo -e "${BLUE}🧪 Testing dotfiles configuration...${NC}\n" + +# Helper function to check if command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Test 1: Shell script syntax with shellcheck +echo -e "${BLUE}→ Checking shell scripts...${NC}" +if command_exists shellcheck; then + SHELL_ERRORS=0 + + # Check install.sh + if [ -f ".config/install.sh" ]; then + if shellcheck .config/install.sh; then + echo -e "${GREEN}✓ .config/install.sh passed shellcheck${NC}" + else + echo -e "${RED}✗ .config/install.sh has shellcheck issues${NC}" + SHELL_ERRORS=$((SHELL_ERRORS + 1)) + fi + fi + + # Check test.sh itself + if [ -f "test.sh" ]; then + if shellcheck test.sh; then + echo -e "${GREEN}✓ test.sh passed shellcheck${NC}" + else + echo -e "${RED}✗ test.sh has shellcheck issues${NC}" + SHELL_ERRORS=$((SHELL_ERRORS + 1)) + fi + fi + + if [ $SHELL_ERRORS -gt 0 ]; then + ERRORS=$((ERRORS + SHELL_ERRORS)) + fi +else + echo -e "${YELLOW}⚠ shellcheck not installed, skipping shell script checks${NC}" + WARNINGS=$((WARNINGS + 1)) +fi +echo + +# Test 2: Zsh configuration syntax +echo -e "${BLUE}→ Checking zsh configuration...${NC}" +if command_exists zsh; then + ZSH_ERRORS=0 + + # Check main zsh files + for file in .zshrc .zshenv .zprofile; do + if [ -f "$file" ]; then + if zsh -n "$file" 2>&1; then + echo -e "${GREEN}✓ $file syntax valid${NC}" + else + echo -e "${RED}✗ $file has syntax errors${NC}" + ZSH_ERRORS=$((ZSH_ERRORS + 1)) + fi + fi + done + + # Check .config/zsh/ files + if [ -d ".config/zsh" ]; then + ZSH_FILES=$(find .config/zsh -name "*.zsh" -type f) + ZSH_COUNT=0 + ZSH_FAILED=0 + + for file in $ZSH_FILES; do + ZSH_COUNT=$((ZSH_COUNT + 1)) + if ! zsh -n "$file" 2>/dev/null; then + if [ $ZSH_FAILED -eq 0 ]; then + echo -e "${RED}✗ Zsh files with issues:${NC}" + fi + echo -e " ${RED}• $file${NC}" + ZSH_FAILED=$((ZSH_FAILED + 1)) + fi + done + + if [ $ZSH_FAILED -eq 0 ] && [ $ZSH_COUNT -gt 0 ]; then + echo -e "${GREEN}✓ All $ZSH_COUNT .config/zsh files valid${NC}" + elif [ $ZSH_FAILED -gt 0 ]; then + echo -e "${RED}✗ $ZSH_FAILED/$ZSH_COUNT zsh files have issues${NC}" + ZSH_ERRORS=$((ZSH_ERRORS + 1)) + fi + fi + + if [ $ZSH_ERRORS -gt 0 ]; then + ERRORS=$((ERRORS + 1)) + fi +else + echo -e "${YELLOW}⚠ zsh not installed, skipping zsh checks${NC}" + WARNINGS=$((WARNINGS + 1)) +fi +echo + +# Test 3: Git configuration +echo -e "${BLUE}→ Checking git configuration...${NC}" +if command_exists git; then + GIT_ERRORS=0 + + # Check .gitconfig + if [ -f ".gitconfig" ]; then + if git config -f .gitconfig --list >/dev/null 2>&1; then + echo -e "${GREEN}✓ .gitconfig syntax valid${NC}" + + # Verify key sections exist + if git config -f .gitconfig user.name >/dev/null 2>&1; then + echo -e "${GREEN} ✓ user.name configured${NC}" + else + echo -e "${YELLOW} ⚠ user.name not set${NC}" + fi + + if git config -f .gitconfig --get-regexp "^alias\." >/dev/null 2>&1; then + ALIAS_COUNT=$(git config -f .gitconfig --get-regexp "^alias\." | wc -l) + echo -e "${GREEN} ✓ $ALIAS_COUNT git aliases defined${NC}" + fi + else + echo -e "${RED}✗ .gitconfig has syntax errors${NC}" + GIT_ERRORS=$((GIT_ERRORS + 1)) + fi + fi + + # Check delta.gitconfig + if [ -f "delta.gitconfig" ]; then + if git config -f delta.gitconfig --list >/dev/null 2>&1; then + echo -e "${GREEN}✓ delta.gitconfig syntax valid${NC}" + else + echo -e "${RED}✗ delta.gitconfig has syntax errors${NC}" + GIT_ERRORS=$((GIT_ERRORS + 1)) + fi + fi + + if [ $GIT_ERRORS -gt 0 ]; then + ERRORS=$((ERRORS + 1)) + fi +else + echo -e "${YELLOW}⚠ git not installed, skipping git config checks${NC}" + WARNINGS=$((WARNINGS + 1)) +fi +echo + +# Test 4: TOML configuration (AeroSpace) +echo -e "${BLUE}→ Checking TOML configurations...${NC}" +if command_exists taplo; then + TOML_ERRORS=0 + + if [ -f ".config/aerospace/aerospace.toml" ]; then + if taplo check .config/aerospace/aerospace.toml 2>&1; then + echo -e "${GREEN}✓ aerospace.toml syntax valid${NC}" + else + echo -e "${RED}✗ aerospace.toml has syntax errors${NC}" + TOML_ERRORS=$((TOML_ERRORS + 1)) + fi + fi + + if [ -f ".config/starship.toml" ]; then + if taplo check .config/starship.toml 2>&1; then + echo -e "${GREEN}✓ starship.toml syntax valid${NC}" + else + echo -e "${RED}✗ starship.toml has syntax errors${NC}" + TOML_ERRORS=$((TOML_ERRORS + 1)) + fi + fi + + if [ $TOML_ERRORS -gt 0 ]; then + ERRORS=$((ERRORS + 1)) + fi +elif command_exists toml-cli; then + TOML_ERRORS=0 + + if [ -f ".config/aerospace/aerospace.toml" ]; then + if toml-cli get .config/aerospace/aerospace.toml "" >/dev/null 2>&1; then + echo -e "${GREEN}✓ aerospace.toml syntax valid${NC}" + else + echo -e "${RED}✗ aerospace.toml has syntax errors${NC}" + TOML_ERRORS=$((TOML_ERRORS + 1)) + fi + fi + + if [ -f ".config/starship.toml" ]; then + if toml-cli get .config/starship.toml "" >/dev/null 2>&1; then + echo -e "${GREEN}✓ starship.toml syntax valid${NC}" + else + echo -e "${RED}✗ starship.toml has syntax errors${NC}" + TOML_ERRORS=$((TOML_ERRORS + 1)) + fi + fi + + if [ $TOML_ERRORS -gt 0 ]; then + ERRORS=$((ERRORS + 1)) + fi +else + echo -e "${YELLOW}⚠ taplo/toml-cli not installed, skipping TOML checks${NC}" + WARNINGS=$((WARNINGS + 1)) +fi +echo + +# Test 5: Tmux configuration +echo -e "${BLUE}→ Checking tmux configuration...${NC}" +if command_exists tmux; then + if [ -f ".config/tmux/tmux.conf" ]; then + # Basic syntax check - try to parse it + if tmux -f .config/tmux/tmux.conf list-keys >/dev/null 2>&1; then + echo -e "${GREEN}✓ tmux.conf syntax valid${NC}" + else + echo -e "${YELLOW}⚠ tmux.conf may have issues (or TPM plugins not installed)${NC}" + WARNINGS=$((WARNINGS + 1)) + fi + fi +else + echo -e "${YELLOW}⚠ tmux not installed, skipping tmux config checks${NC}" + WARNINGS=$((WARNINGS + 1)) +fi +echo + +# Test 6: YAML configuration (GitHub workflows) +echo -e "${BLUE}→ Checking YAML configurations...${NC}" +if command_exists yamllint; then + YAML_ERRORS=0 + + if [ -d ".github/workflows" ]; then + YAML_FILES=$(find .github/workflows -name "*.yml" -o -name "*.yaml" -type f 2>/dev/null) + YAML_COUNT=0 + YAML_FAILED=0 + + for file in $YAML_FILES; do + YAML_COUNT=$((YAML_COUNT + 1)) + if ! yamllint -d relaxed "$file" 2>/dev/null; then + if [ $YAML_FAILED -eq 0 ]; then + echo -e "${RED}✗ YAML files with issues:${NC}" + fi + echo -e " ${RED}• $file${NC}" + YAML_FAILED=$((YAML_FAILED + 1)) + fi + done + + if [ $YAML_FAILED -eq 0 ] && [ $YAML_COUNT -gt 0 ]; then + echo -e "${GREEN}✓ All $YAML_COUNT YAML files valid${NC}" + elif [ $YAML_FAILED -gt 0 ]; then + echo -e "${RED}✗ $YAML_FAILED/$YAML_COUNT YAML files have issues${NC}" + YAML_ERRORS=$((YAML_ERRORS + 1)) + fi + fi + + if [ $YAML_ERRORS -gt 0 ]; then + ERRORS=$((ERRORS + 1)) + fi +else + echo -e "${YELLOW}⚠ yamllint not installed, skipping YAML checks${NC}" + WARNINGS=$((WARNINGS + 1)) +fi +echo + +# Summary +echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +if [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then + echo -e "${GREEN}✓ All tests passed!${NC}" + exit 0 +elif [ $ERRORS -eq 0 ]; then + echo -e "${YELLOW}⚠ Tests passed with $WARNINGS warning(s)${NC}" + echo -e "${YELLOW} (Some validation tools not installed)${NC}" + exit 0 +else + echo -e "${RED}✗ Tests failed with $ERRORS error(s) and $WARNINGS warning(s)${NC}" + exit 1 +fi