#!/usr/bin/env bash set -euo pipefail log() { printf '[setup] %s\n' "$*" } die() { printf 'Error: %s\n' "$*" >&2 exit 1 } pause() { printf '%s\n' "$*" read -r _ } confirm() { local reply printf '%s [y/N]: ' "$*" read -r reply [[ "$reply" == "y" || "$reply" == "Y" ]] } cleanup_gpg_keys() { local private_key="$HOME/gpg-private.key" local public_key="$HOME/gpg-public.key" if [[ -f "$private_key" ]] || [[ -f "$public_key" ]]; then log "Cleaning up GPG key files" if command -v srm >/dev/null 2>&1; then srm -f "$public_key" "$private_key" 2>/dev/null || true else rm -f "$public_key" "$private_key" fi fi } ensure_xcode_cli() { if xcode-select -p >/dev/null 2>&1; then return 0 fi log "Installing Xcode Command Line Tools" if ! xcode-select --install 2>/dev/null; then log "Xcode tools installation already in progress or complete" fi pause "Wait for installation to complete, then press enter." if ! xcode-select -p >/dev/null 2>&1; then die "Xcode Command Line Tools still not available." fi } ensure_homebrew() { if command -v brew >/dev/null 2>&1; then return 0 fi log "Installing Homebrew" /bin/bash -c "$(curl -fsSL \ https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" export PATH="/opt/homebrew/bin:$PATH" if ! command -v brew >/dev/null 2>&1; then die "Homebrew installed but brew is not available in PATH." fi } brew_install_formulae() { local formula for formula in "${BREW_FORMULAE[@]}"; do log "Installing $formula" brew install "$formula" done } brew_install_casks() { local cask for cask in "${BREW_CASKS[@]}"; do log "Installing $cask" brew install --cask "$cask" done } ensure_github_ssh_key() { local key_path="$HOME/.ssh/github" local key_comment key_comment=$(git config --global user.email 2>/dev/null || true) key_comment=${key_comment:-"github-key"} if [[ -f "$key_path" ]]; then log "GitHub SSH key already exists at $key_path" return 0 fi log "Generating GitHub SSH key at $key_path" ssh-keygen -t ed25519 -C "$key_comment" -f "$key_path" log "Adding key to keychain" ssh-add --apple-use-keychain "$key_path" log "Copying public key to clipboard" pbcopy <"$key_path.pub" pause "Add the key at https://github.com/settings/ssh/new then press enter." log "Testing GitHub SSH connection" if ! ssh -T git@github.com >/dev/null 2>&1; then die "GitHub SSH connection failed. Verify the key was added." fi log "GitHub SSH connection successful" } ensure_gpg_keys() { local private_key="$HOME/gpg-private.key" local public_key="$HOME/gpg-public.key" if [[ ! -f "$public_key" ]]; then log "GPG public key not found at $public_key, skipping import" return 0 fi if [[ ! -f "$private_key" ]]; then log "GPG private key not found at $private_key, skipping import" return 0 fi trap cleanup_gpg_keys EXIT INT TERM log "Importing GPG keys" gpg --import "$public_key" gpg --import "$private_key" cleanup_gpg_keys trap - EXIT INT TERM } DOTFILES_DIR="$HOME/.dotfiles" REPO_URL="https://github.com/majjoha/dotfiles.git" BREW_FORMULAE=( curl git gnupg imagemagick lua mise pinentry-mac stow tig tmux tree zsh ) BREW_CASKS=( 1password@7 amethyst bartender firefox@developer-edition flux font-maple-mono-nf ghostty hammerspoon hazeover libreoffice macdown okta-verify qlcolorcode qlmarkdown qlstephen raycast the-unarchiver todoist-app ) ensure_xcode_cli if [[ ! -d "$DOTFILES_DIR/.git" ]]; then if [[ -d "$DOTFILES_DIR" && -n "$(ls -A "$DOTFILES_DIR")" ]]; then die "DOTFILES_DIR ($DOTFILES_DIR) exists and is not empty." fi log "Cloning dotfiles into $DOTFILES_DIR" git clone "$REPO_URL" "$DOTFILES_DIR" fi cd "$DOTFILES_DIR" || die "Failed to enter $DOTFILES_DIR" ensure_homebrew log "Installing Homebrew formulae" brew_install_formulae if command -v mise >/dev/null 2>&1; then log "Trusting dotfiles directory with mise" if ! mise trust "$DOTFILES_DIR"; then die "mise trust failed. Check permissions and try again." fi fi if confirm "Install GUI apps (${#BREW_CASKS[@]} casks)?"; then brew_install_casks fi if [[ -f .gitmodules ]]; then log "Updating git submodules" git submodule update --init --recursive fi xdg_state=${XDG_STATE_HOME:-"$HOME/.local/state"} xdg_cache=${XDG_CACHE_HOME:-"$HOME/.cache"} xdg_data=${XDG_DATA_HOME:-"$HOME/.local/share"} log "Ensuring XDG directories" mkdir -p -- \ "$xdg_state/zsh" \ "$xdg_cache/zsh" \ "$xdg_data/zlua" log "Stowing dotfiles" stow . ensure_github_ssh_key if confirm "Import GPG keys?"; then ensure_gpg_keys fi if command -v mise >/dev/null 2>&1; then log "Installing mise tools" mise install fi if [[ -f "$DOTFILES_DIR/.bin/initialize-macos-defaults" ]]; then log "Initializing macOS defaults" "$DOTFILES_DIR/.bin/initialize-macos-defaults" fi log "Setup complete." cat <<'EOF' == Post-installation == 1. Enable space switching shortcuts: System Settings → Keyboard → Keyboard Shortcuts → Mission Control → Enable 'Switch to Desktop X' for desired spaces 2. Configure Firefox Developer Edition: - Open about:config - Set toolkit.legacyUserProfileCustomizations.stylesheets = true - Close Firefox - Create chrome/userChrome.css in ~/Library/Application Support/Firefox/Profiles/*/ - Add the following to userChrome.css: /* Hides the native tabs */ #TabsToolbar { visibility: collapse; } EOF