#!/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" rm -fP -- "$public_key" "$private_key" 2>/dev/null \ || rm -f -- "$public_key" "$private_key" \ || true 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_packages() { log "Installing packages with Homebrew Bundle" brew bundle --file=/dev/stdin <<-'EOF' brew "curl" brew "ffmpeg" brew "git" brew "gnupg" brew "luarocks" brew "mise" brew "pinentry-mac" brew "stow" brew "tig" brew "zsh" cask "1password@7" cask "bartender" cask "firefox@developer-edition" cask "flux-app" cask "font-maple-mono-nf" cask "font-sf-pro" cask "ghostty" cask "hammerspoon" cask "hazeover" cask "libreoffice" cask "macdown" cask "qlcolorcode" cask "qlmarkdown" cask "qlstephen" cask "raycast" cask "the-unarchiver" cask "todoist-app" EOF } 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 "First, log into GitHub using a passkey." log "Visit: https://github.com/login" log "Use your iPhone or another device with a passkey to authenticate." pause "After logging in, press enter to continue." mkdir -p "$HOME/.ssh" && chmod 700 "$HOME/.ssh" 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" local ssh_output ssh_output="$(ssh -T -o BatchMode=yes -o StrictHostKeyChecking=accept-new \ git@github.com 2>&1 || true)" if ! grep -qi "successfully authenticated" <<<"$ssh_output"; 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" if ! gpg --import "$public_key"; then die "Failed to import GPG public key" fi if ! gpg --import "$private_key"; then die "Failed to import GPG private key" fi cleanup_gpg_keys trap - EXIT INT TERM } DOTFILES_DIR="$HOME/.dotfiles" REPO_URL="https://github.com/majjoha/dotfiles.git" 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 brew_install_packages 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 [[ -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 --restow . if confirm "Setup GitHub SSH key?"; then ensure_github_ssh_key fi if confirm "Import GPG keys?"; then ensure_gpg_keys fi if command -v mise >/dev/null 2>&1; then if [[ -z "${MISE_GITHUB_TOKEN:-}" ]] && \ [[ -z "${GITHUB_TOKEN:-}" ]] && \ [[ -z "${GITHUB_API_TOKEN:-}" ]]; then log "GitHub token not found. Setting a token avoids rate limiting." log "Create a token at: https://github.com/settings/tokens/new" log "No scopes are required." pause "After creating the token, press enter to continue." printf 'Paste your GitHub token (input will be hidden): ' read -rs GITHUB_TOKEN printf '\n' export GITHUB_TOKEN if [[ -z "$GITHUB_TOKEN" ]]; then log "No token provided. Continuing without token (may hit rate limits)." else log "GitHub token set successfully." fi fi 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 "Installing MacDown custom theme" mkdir -p "$HOME/Library/Application Support/MacDown/Styles" curl -fsSL \ https://gist.githubusercontent.com/majjoha/a2952805774a2af73b75f0d992ed7cbe/raw/majjoha.css \ -o "$HOME/Library/Application Support/MacDown/Styles/majjoha.css" 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