DEV Community

Cover image for Automated macOS Setup with Dotfiles
Danylo Mikula
Danylo Mikula

Posted on

Automated macOS Setup with Dotfiles

A comprehensive automation solution for setting up macOS development environments. This dotfiles repository handles everything from Homebrew package installation to SSH/GPG key generation and GitHub integration – all through an interactive bootstrap script.

Repository: github.com/danylomikula/dotfiles


The Problem

Every DevOps engineer knows the pain:

  • New laptop arrives – 4-8 hours of manual setup
  • Switching between personal/work machines – different configs everywhere
  • Team onboarding – "just install these 50 things..."
  • Disaster recovery – scrambling to remember every tool and setting

Traditional approaches fall short:

  • Manual installation: Error-prone, inconsistent, undocumented
  • Basic dotfiles: Only manage config files, not installation
  • Homebrew Brewfile: Doesn't handle SSH/GPG keys or directory structures
  • Ansible playbooks: Overkill for personal use, slow iteration

The Solution: Intelligent Bootstrap Script

I built a comprehensive dotfiles repository that handles everything through a single bootstrap.sh script with interactive prompts using gum for a beautiful TUI experience.

What It Does

Core Installation:

  • Homebrew + analytics disabled
  • Nerd Fonts (Hack, Ubuntu Mono, Fira Code, Courier Prime)
  • Oh-My-Zsh with plugins (autosuggestions, syntax highlighting, you-should-use)
  • Modern CLI tools: eza, zoxide, fzf, starship, zellij
  • Python via pyenv with latest version auto-installed

Applications:

  • Productivity: Zen, Obsidian, Maccy, BetterDisplay, Grammarly
  • Communication: Signal, Telegram, Slack, Discord
  • Development: VSCode, Alacritty, Fork, Lens
  • DevOps/Cloud: AWS CLI, kubectl, helm, terraform, vault, argocd, ansible
  • Security: Mullvad VPN, Tailscale, GPG Suite
  • Utilities: AldDente, Bartender, AppCleaner, Shottr

Git Configuration:

  • Global .gitconfig with sane defaults
  • Separate directories for personal/work repos with automatic context switching
  • includeIf directives for email/name per directory
  • Global .gitignore setup
  • Branch sorting by commit date, auto column UI

SSH Key Generation:

  • Ed25519 keys with modern crypto
  • Automatic GitHub CLI integration
  • Adds key to GitHub via API
  • Tests SSH connection to GitHub

GPG Key Generation:

  • Ed25519 + Curve25519 keys for signing/encryption
  • Configures pinentry-mac for macOS Keychain integration
  • Automatic GitHub integration via API
  • Sets global signing key in Git

Dotfiles Management:

  • GNU Stow for symlink management
  • Configs for: zsh, starship, alacritty, zellij, k9s, docker, git, gh
  • Alacritty themes auto-cloned (200+ colorschemes)

AI Agents Configuration:

  • Configures Codex and Claude for development workflow
  • Installs Context7 MCP server for automatic library documentation
  • Sets up global Copilot instructions at ~/.config/Code/User/prompts/context7.instructions.md
  • Enables AI agents to automatically fetch library docs during code generation
  • Configured via standalone configure-ai-agents.sh script

Architecture

Directory Structure

dotfiles/
├── bootstrap.sh              # Main installation script
├── configure-git.sh          # Standalone Git config tool
├── configure-ai-agents.sh    # Standalone AI agents setup
├── generate-gpg-key.sh       # Standalone GPG key generator
├── generate-ssh-key.sh       # Standalone SSH key generator
├── alacritty/
│   └── .config/alacritty/
│       ├── alacritty.toml
│       └── themes/           # 200+ themes via git submodule
├── docker/.docker/
│   └── config.json
├── git/
│   ├── .gitconfig
│   └── .gitignore_global
├── github-cli/.config/gh/
├── k9s/.config/k9s/
├── starship/.config/
│   └── starship.toml
├── zellij/.config/zellij/
│   └── config.kdl
└── zshrc/
    └── .zshrc
Enter fullscreen mode Exit fullscreen mode

Key Design Decisions

1. Interactive Prompts with Gum

Instead of environment variables or config files, I use charmbracelet/gum for beautiful TUI prompts:

gum style --foreground "#00FF00" --bold "Do you want to generate a GPG key?"
CHOICE=$(gum choose "Yes" "No")
Enter fullscreen mode Exit fullscreen mode

This makes the script:

  • Self-documenting (you see what's being configured)
  • Flexible (skip sections you don't need)
  • Beginner-friendly (no prior knowledge required)

2. Conditional Directory Creation

The script offers to create separate Git directories:

~/git/
├── personal/    # Personal projects with personal email
└── work/        # Work projects with work email
Enter fullscreen mode Exit fullscreen mode

Git automatically switches context using includeIf:

[includeIf "gitdir:~/git/personal/**"]
    path = ~/git/personal/.gitconfig

[includeIf "gitdir:~/git/work/**"]
    path = ~/git/work/.gitconfig
Enter fullscreen mode Exit fullscreen mode

3. Modern Crypto Defaults

  • SSH: Ed25519 (fast, secure, small keys)
  • GPG: Ed25519 for signing + Curve25519 for encryption
  • No RSA 2048/4096 bloat

4. GitHub API Integration

Keys aren't just generated–they're automatically added to GitHub:

# SSH key
gh ssh-key add "$SSH_KEY_PATH.pub" --title "$(hostname)"

# GPG key  
gh gpg-key add <(gpg --armor --export "$KEY_ID")
Enter fullscreen mode Exit fullscreen mode

5. Stow for Symlink Management

GNU Stow creates symlinks from dotfiles/ to $HOME:

stow zshrc starship alacritty zellij k9s docker git github-cli
Enter fullscreen mode Exit fullscreen mode

This keeps your actual configs in Git while making them available system-wide.

6. AI Agents with Context7 MCP

The configure-ai-agents.sh script sets up AI development assistants:

  • Codex CLI: Command-line AI agent for shell tasks
  • Claude Integration: Configured for VS Code with Copilot
  • Context7 MCP Server: Provides automatic library documentation via Model Context Protocol
  • Global Instructions: Creates ~/.config/Code/User/prompts/context7.instructions.md with rule to always use Context7 for docs

This enables AI agents to automatically fetch up-to-date documentation when generating code, reducing hallucinations and improving accuracy.

Usage

Quick Start (Full Bootstrap)

git clone https://github.com/danylomikula/dotfiles.git ~/dotfiles
cd ~/dotfiles
chmod +x bootstrap.sh
./bootstrap.sh
Enter fullscreen mode Exit fullscreen mode

The script will:

  1. Install Homebrew and packages (5-10 min)
  2. Configure Oh-My-Zsh and plugins
  3. Sync dotfiles via Stow
  4. Prompt for Git configuration (optional)
  5. Offer to generate SSH key (optional)
  6. Offer to generate GPG key (optional)
  7. Offer to configure AI agents (optional)

Standalone Scripts

Each script can run independently:

# Just configure Git
./configure-git.sh

# Just generate SSH key
./generate-ssh-key.sh

# Just generate GPG key  
./generate-gpg-key.sh

# Just configure AI agents
./configure-ai-agents.sh
Enter fullscreen mode Exit fullscreen mode

Customization

Add/Remove Packages:

Edit the brew install lines in bootstrap.sh:

# Add your tools here
brew install --cask your-app-here
Enter fullscreen mode Exit fullscreen mode

Add New Dotfiles:

  1. Create directory: mkdir -p newtool/.config/newtool
  2. Add config: newtool/.config/newtool/config.yaml
  3. Stow it: stow newtool

Change Alacritty Theme:

# ~/.config/alacritty/alacritty.toml
[general]
import = [
    "~/.config/alacritty/themes/themes/tokyo-night.toml"
]
Enter fullscreen mode Exit fullscreen mode

Customize AI Agents:

Edit ~/.config/Code/User/prompts/context7.instructions.md:

---
applyTo: "**"
name: "Global-Context7-Rule"
---

Always use context7 when I need code generation, setup steps, or library docs.
Enter fullscreen mode Exit fullscreen mode

You can add more rules or change the applyTo pattern to scope instructions to specific file types.

Real-World Usage

New Machine Setup

Time to productive workstation: ~15 minutes (mostly package downloads)

# On new Mac
git clone https://github.com/danylomikula/dotfiles.git ~/dotfiles
cd ~/dotfiles
./bootstrap.sh

# Answer prompts:
# - Configure Git? Yes
# - Personal dir? ~/git/personal
# - Work dir? ~/git/work  
# - Generate SSH? Yes
# - Generate GPG? Yes
# - Add to GitHub? Yes
# - Configure AI agents? Yes
Enter fullscreen mode Exit fullscreen mode

Result: Fully configured machine with:

  • All dev tools installed
  • SSH key in GitHub
  • GPG signing configured
  • Git context switching working
  • Terminal customized
  • AI agents with Context7 MCP ready
  • Ready to git clone and start working

Disaster Recovery

Laptop dies or needs rebuild:

# On replacement machine
git clone https://github.com/danylomikula/dotfiles.git ~/dotfiles
cd ~/dotfiles
./bootstrap.sh
Enter fullscreen mode Exit fullscreen mode

Back to 100% productivity in under an hour (including restoring data from backups).


Technical Deep Dive

GPG Key Generation with Batch Mode

I use GPG's batch mode to avoid interactive prompts:

GPG_BATCH_FILE=$(mktemp)
cat > "$GPG_BATCH_FILE" <<EOF
%echo Generating a GPG key
Key-Type: eddsa
Key-Curve: ed25519
Subkey-Type: ecdh
Subkey-Curve: cv25519
Name-Real: ${GPG_OWNER_NAME}
Name-Email: ${GPG_OWNER_EMAIL}
Expire-Date: 0
Passphrase: ${GPG_PASSWORD}
%commit
%echo done
EOF

gpg --batch --gen-key "$GPG_BATCH_FILE"
rm "$GPG_BATCH_FILE"
Enter fullscreen mode Exit fullscreen mode

This creates:

  • Primary key: Ed25519 for signing
  • Subkey: Curve25519 for encryption
  • No expiration: Suitable for long-term code signing

Detecting New GPG Key After Generation

Since we generate the key non-interactively, we need to find the new key ID:

# Capture existing keys before generation
EXISTING_KEYS=$(gpg --list-secret-keys --keyid-format=long \
  | awk '/^sec/ {print $2}' | cut -d'/' -f2)

# Generate key...

# Find the new key
NEW_KEYS=$(gpg --list-secret-keys --keyid-format=long \
  | awk '/^sec/ {print $2}' | cut -d'/' -f2)

KEY_ID=$(comm -13 <(echo "$EXISTING_KEYS" | sort) \
                   <(echo "$NEW_KEYS" | sort) | head -n 1)
Enter fullscreen mode Exit fullscreen mode

This uses comm to find the set difference–the new key ID.

GitHub CLI Integration

Adding keys to GitHub via API:

# Authenticate (opens browser for OAuth)
gh auth login

# Add SSH key
gh ssh-key add "$HOME/.ssh/id_ed25519.pub" \
  --title "$(hostname)" --type authentication

# Test connection
ssh -T git@github.com

# Add GPG key  
gh gpg-key add <(gpg --armor --export "$KEY_ID")

# Configure Git to use it
git config --global user.signingkey "$KEY_ID"
git config --global commit.gpgsign true
git config --global tag.gpgSign true
Enter fullscreen mode Exit fullscreen mode

No manual copying of keys into GitHub UI!

Pinentry Configuration for macOS

macOS Keychain integration requires pinentry-mac:

brew install pinentry-mac

# Configure GPG agent
echo "pinentry-program $(which pinentry-mac)" \
  >> ~/.gnupg/gpg-agent.conf

# Restart agent
killall gpg-agent
Enter fullscreen mode Exit fullscreen mode

Now GPG passphrase prompts use macOS Keychain–enter once, cached securely.

AI Agents Configuration Deep Dive

The configure-ai-agents.sh script sets up CLI and desktop AI coding assistants with Context7 MCP integration.

1. Tools Installation:

brew install node codex           # Codex CLI
brew install --cask claude-code   # Claude desktop app
Enter fullscreen mode Exit fullscreen mode

2. Codex CLI Configuration:

Creates ~/.codex/AGENTS.md with global instructions:

# Global instructions

## context7 instructions
- Always use context7 when I need code generation, setup or configuration 
  steps, or library/API documentation. This means you should automatically 
  use the Context7 MCP tools to resolve library id and get library docs 
  without me having to explicitly ask.
Enter fullscreen mode Exit fullscreen mode

Creates ~/.codex/config.toml with MCP server configuration:

model = "gpt-5.1-codex"
model_reasoning_effort = "high"

[mcp_servers.context7]
command = "npx"
args = ["-y", "@upstash/context7-mcp"]
Enter fullscreen mode Exit fullscreen mode

3. Claude Desktop Configuration:

Creates ~/.claude/CLAUDE.md with Context7 usage instructions:

# Context7 MCP usage
- Always use context7 when I need code generation, setup or configuration 
  steps, or library/API documentation. This means you should automatically 
  use the Context7 MCP tools to resolve library id and get library docs 
  without me having to explicitly ask.
Enter fullscreen mode Exit fullscreen mode

Enables MCP server for Claude:

claude mcp add context7 -- npx -y @upstash/context7-mcp
Enter fullscreen mode Exit fullscreen mode

4. How It Works:

Both agents can now automatically fetch library documentation via Context7 MCP:

# Codex CLI usage
$ codex "Add authentication with Supabase"
[Codex automatically fetches Supabase docs via Context7 MCP]
"Installing supabase-js and configuring auth..."

# Claude Desktop usage
User: "Add authentication with Supabase"
Claude: [fetches Supabase docs via Context7]
        "I'll help you set up Supabase authentication..."
Enter fullscreen mode Exit fullscreen mode

This eliminates manual documentation lookups and reduces hallucinations by grounding AI responses in actual library docs.

Conclusion

This dotfiles setup has saved me countless hours across:

  • 5+ fresh installs on new machines
  • Daily context switching between personal/work
  • Team onboarding

Resources


Have questions or improvements? Open an issue or PR on the dotfiles repo!

Top comments (0)