Quickly set up a new Mac with all development tools, configurations, and secrets using this automated dotfiles repository.
# 1. Clone this repository
git clone https://github.com/yourusername/dotfiles.git ~/dotfiles
cd ~/dotfiles
# 2. (Optional but recommended) Run pre-flight checks
./bin/preflight.sh
# 3. Run installation with your vault password
./install.sh your_vault_password
# 4. (Optional but recommended) Validate installation
./bin/validate-install.sh
# 5. Restart your terminal or source config
source ~/.zshrcImportant Notes:
- Do NOT run with sudo - the script will request sudo access only when needed
- Vault password is REQUIRED as the first argument
- The script handles Xcode, packages, decryption, and repository cloning automatically
- Run
preflight.shfirst to catch potential issues before installation - Run
validate-install.shafter to confirm everything installed correctly
- Development tools (git, node, python, etc.)
- Terminal utilities (tmux, vim, etc.)
- Desktop applications (browsers, IDEs, etc.)
- Node.js: Multiple versions via NVM (10, 12, 14, 16, 18, 20, 22)
- Python: Multiple versions via pyenv (3.7-3.13)
- Each version gets its own virtual environment
- Python 3.13 designated for Neovim's Python provider (nvim-provider)
- Includes pip-tools, pipx, poetry, uv in each environment
- Package managers: npm, yarn, pnpm, pip, pipx, poetry, uv
- Google Fonts (curated selection)
- Nerd Fonts (for terminal icons)
- Powerline Fonts (for fancy prompts)
- Zsh: Primary shell with custom configuration
- Bash: Backup shell configuration
- Custom prompt with git integration
- Environment-specific configurations
- Git configuration and aliases
- Vim/NeoVim setup with plugins
- Python provider for plugin support (pynvim)
- Configured in
vim/init.vim
- VSCode/Cursor settings
- WebStorm/IntelliJ settings
The vault system manages encrypted SSH keys, credentials, and environment variables.
vault/ # Encrypted files (committed to git)
βββ VTJGc2RHVm... # Encrypted filename (base64 encoded)
βββ VTJGc2RHVm... # Another encrypted file
βββ VTJGc2RHVm.../ # Encrypted subdirectories
βββ VTJGc2RHVm... # Encrypted files in subdirs
vault-key/ # Decrypted files (gitignored - never public)
βββ password.txt # Auto-loaded and generated by shell
βββ config # SSH config (actual filename)
βββ main # SSH keys (actual filename)
βββ main.pub # SSH keys (actual filename)
βββ *.env # Environment variables
βββ *.repos.txt # Repository lists
βββ install.sh # Custom installations
Security Note:
vault/filenames are encrypted and base64 encoded (e.g.,VTJGc2RHVm...)- This is intentional - even filenames reveal no information in public repos
vault-key/has actual filenames but is gitignored (never pushed)- Directory structure is preserved during encryption/decryption
Decrypt vault files:
./bin/vault yourpassword
# Password is saved to vault-key/password.txt
# Future shells auto-load this passwordWhat happens during decryption:
- Automatic Backup: All existing
vault-key/files are moved to timestamped backup folder - Safe Decryption: Encrypted
vault/files are decrypted tovault-key/ - Password Saved: Your password is written to
vault-key/password.txt - Permissions Set: SSH keys get proper 600 permissions automatically
- Nothing Lost: Original files preserved in
vault-key/vault_backups/vault-key/YYYYMMDD_HHMMSS/
Encrypt vault files:
./bin/vault -e newpassword
# Updates password.txt and encrypts all filesWhat happens during encryption:
- Automatic Backup: All existing
vault/files are moved to timestamped backup folder - Password Preserved: Current
vault-key/password.txtis copied to backup folder - New Password: Your new password is written to
vault-key/password.txt - Safe Encryption: All
vault-key/files are encrypted tovault/ - Nothing Lost:
- Old encrypted files in
vault/vault_backups/vault/YYYYMMDD_HHMMSS/ - Old password.txt preserved with those backups (so you can decrypt them later)
- Old encrypted files in
- Permissions Set: New encrypted files get proper permissions
Why backups include password.txt: Each backup folder contains the password that was used to create those encrypted files. This means:
- You can always decrypt a specific backup set using its password.txt
- If you rotate passwords, old backups remain accessible
- You never lose access to historical encrypted files
- Backups are stored in
vault/vault_backups/..and are gitignored by /vault-key directory
Auto-loading (automatic):
# Your shell config automatically loads password on startup
source ~/.zshrc # Shows: "Vault password loaded from..."
echo $VAULT_PASSWORD # Password available as environment variable
./bin/vault # Works without password argument!Backup Structure:
vault-key/
vault_backups/
vault/
20240101_120000/ # Timestamped backup folder
VTJGc2RHVmtYMTk... # Even password.txt is encrypted! (base64 encoded)
VTJGc2RHVmtYMS8... # Encrypted filename 1 (base64 encoded)
VTJGc2RHVmtYMSs... # Encrypted filename 2 (base64 encoded)
VTJGc2RHVmtYMSt.../ # Encrypted subdirectory
VTJGc2RHVm... # Encrypted file in subdir
vault-key/
vault_backups/
vault-key/
20240101_120000/ # Timestamped backup folder
password.txt # Readable password file
config # Actual SSH config (readable name)
main # Actual SSH keys (readable name)
org-name.env # Actual environment file (readable name)
Why Encrypted Filenames?
- This repository is public, so even filenames must not reveal sensitive information
vault/files use base64-encoded encrypted filenames for ALL files (e.g.,VTJGc2RHVmtYMS8...)- Even
password.txthas its filename encrypted invault/ - Only
vault-key/(gitignored) contains readable filenames - The vault script maintains the mapping between encrypted and decrypted names
When you modify files in
vault-key/be sure to runvault -e <password>so the changes make it into your dotfiles repo when you commit/push changes (e.g. get encrypted to /vault). Seevault -hfor more info.
-
Symlinks (
vault-key/symlinks.txt):- SSH keys (private and public) linked to
~/.ssh/ - Config files linked to appropriate locations
- SSH keys (private and public) linked to
-
SSH Setup (
vault-key/ssh-keys.txt):- Automatically adds SSH keys to ssh-agent
- Uses macOS keychain for persistence across reboots
- Only adds keys listed in configuration file
- Auto-runs on every shell startup
-
Custom Installations (
vault-key/install.sh):- Organization-specific tools
- Additional configurations
-
Repository Cloning (
*.repos.txt):- Automatically clones development repositories with
--npm-installflag - Smart package installation (detects Node version, package manager, installs dependencies)
- Supports multiple organizations
- Automatically clones development repositories with
The system automatically manages SSH keys with zero manual intervention.
-
Configuration (
vault-key/ssh-keys.txt):# List SSH private key filenames (as they appear in ~/.ssh/ after symlinking) main org-name-one org-name-two -
Automatic Processing:
- During installation: Keys added to ssh-agent with macOS keychain integration
- On every shell startup: Keys automatically re-added if agent restarts
- Persistent across reboots: macOS keychain stores passphrases securely
-
What You Need To Do:
- Nothing! Just list your key filenames in
ssh-keys.txt - The system handles everything else automatically
- Nothing! Just list your key filenames in
The bin/ssh-setup.sh script handles:
- Starting ssh-agent if not running
- Finding existing agent processes
- Adding keys to agent with
--apple-use-keychainfor persistence - Proper error handling and reporting
Example Output:
Starting SSH setup...
SSH agent is running (PID: 12345)
Adding SSH key: main
β Successfully added key: main
Adding SSH key: org-name-one
β Successfully added key: org-name-one
SSH setup completed: 2/2 keys added successfullyKeys not being added:
# Run manually to see detailed output
~/dotfiles/bin/ssh-setup.sh
# Check agent is running
ssh-add -lPermission errors:
# Ensure correct permissions (handled automatically)
chmod 600 ~/.ssh/your_key
chmod 644 ~/.ssh/your_key.pubSwitch between different work environments:
This is useful for NPM registries, AWS profiles, and other environment variables.
# List all available environments
workenv --list
# Switch to an environment
workenv org-name-one # Load org-name-one environment variables
workenv org-name-two # Switch to org-name-two environment
# Check current environment
workenv --envEach environment can have:
- Different NPM registries and tokens
- Different AWS profiles
- Different SSH keys
- Different environment variables
Environment files are stored in vault-key/ and must end with .env extension.
dotfiles/
βββ install.sh # Main installation script
βββ bin/ # Utility scripts
β βββ preflight.sh # Pre-installation system checks
β βββ validate-install.sh # Post-installation validation
β βββ vault # Encryption/decryption tool
β βββ repo.sh # Repository cloning with smart npm install
β βββ ssh-setup.sh # SSH agent and key management
β βββ symlink.sh # Symlink manager
β βββ fonts.sh # Font installer
β βββ workenv.sh # Environment switcher
βββ install/ # Installation modules
β βββ brew.sh # Homebrew packages
β βββ node.sh # Node.js versions
β βββ python.sh # Python versions
β βββ osx.sh # macOS preferences
β βββ vagrant.sh # VirtualBox & Vagrant
βββ bash/ # Bash configuration
βββ zsh/ # Zsh configuration
βββ vim/ # Vim/NeoVim configuration
βββ vscode/ # VSCode settings
βββ vault/ # Encrypted secrets
Pre-flight checks before installation - validates system requirements.
./bin/preflight.shChecks Performed:
- macOS version and architecture
- Disk space availability (minimum 5GB)
- Internet connectivity
- Xcode Command Line Tools status
- Homebrew installation status
- Required tools (git, openssl, zsh, bash)
- File permissions (home directory, /usr/local)
- Existing configuration conflicts
- Vault directory status
Output:
- β Green checkmarks for passed checks
- β Yellow warnings for non-critical issues
- β Red X for failed requirements
- Summary with pass/warn/fail counts
- Recommendations for fixes
When to run: Before first installation or when troubleshooting issues
Post-installation validation - confirms successful setup.
./bin/validate-install.shValidates:
- Homebrew installation and health
- Vault decryption and password loading
- Symlink creation and targets
- SSH configuration and agent status
- Node.js versions via NVM
- Python versions via pyenv (including nvim-provider)
- Shell configuration (zsh, DOTFILES, HOMEBREW_PREFIX)
- Vim/Neovim setup
- workenv function availability
- Repository cloning status
Output:
- Detailed status of each component
- Summary with recommendations
- Next steps if issues found
When to run: After installation completes, before using the system
Main orchestrator - runs all installation steps in order.
Requirements:
- Must NOT run with sudo (script will request sudo only when needed)
- Vault password is REQUIRED as first argument
./install.sh your_vault_passwordExecution Order:
- Check/install Xcode Command Line Tools (uses sudo if needed)
- Install Homebrew packages (brew.sh)
- Decrypt vault files with provided password
- Create symlinks (dotfiles + vault-key symlinks including SSH keys)
- Setup SSH keys (adds to ssh-agent with macOS keychain integration)
- Install Node.js and Python versions
- Run vault-specific installations
- Install fonts
- Apply macOS system settings
- Set ZSH as default shell (uses sudo)
- Install Base16 themes
- Source bash_profile
- Clone repositories from *.repos.txt files with smart package installation
Sudo Usage:
- Xcode Command Line Tools installation
- Adding shells to /etc/shells
- That's it! Everything else runs as your user
Manages encryption/decryption of sensitive files.
vault password # Decrypt vault/ to vault-key/
vault -e password # Encrypt vault-key/ to vault/
vault --list password # List encrypted files
vault --list-backups # List backup filesFeatures:
- Automatic password.txt management
- Timestamped backups (never overwrites)
- Proper permissions (600) on SSH keys
- Shell auto-loading support
Clones repositories from .repos.txt files with intelligent package installation.
repo.sh --file repos.txt --default-directory ~/dev
repo.sh --file repos.txt --npm-install # Auto-install with smart detection
repo.sh --file repos.txt --dry-run # Test without cloningFeatures:
- Skips repositories already cloned
- Smart Package Installation (with
--npm-install):- Detects Node.js version from
.nvmrcorpackage.jsonengines.node - Automatically installs missing Node.js versions via NVM
- Detects package manager from lock files (pnpm-lock.yaml β pnpm, yarn.lock β yarn, package-lock.json β npm)
- Automatically installs missing package managers globally
- Gracefully skips repos without package.json
- Detects Node.js version from
- Supports $HOME and ~ expansion in paths
Manages SSH agent and adds keys automatically.
ssh-setup.sh # Run manually
# Or automatically via shell configFeatures:
- Starts ssh-agent if not running
- Finds existing agent processes
- Adds keys from
vault-key/ssh-keys.txt - Uses macOS keychain for persistence
- Auto-runs on shell startup
Creates symlinks for configuration files.
symlink.sh -v # Create all symlinks (verbose)
symlink.sh -k # Keep backups of existing files
symlink.sh --dry-run # Test without changesInstalls fonts from multiple sources.
fonts.sh --google-fonts-light --nerd-fonts-light
fonts.sh --google-fonts-select='Roboto, JetBrains Mono'
fonts.sh --list-google-fonts # See available fontsSwitches between organization environments.
workenv --list # List all available environments
workenv org_name # Switch to org_name environment
workenv --env # Show current environment name
workenv --help # Show usageFeatures:
- Auto-discovers
.envfiles invault-key/directory - Shows environment names and file locations with
--list - Unloads previous environment variables before loading new ones
- Updates
~/.workrcto persist environment selection
All scripts work on both:
- Intel Macs: Uses
/usr/localHomebrew prefix - Apple Silicon Macs: Uses
/opt/homebrewHomebrew prefix
Automatic detection handles both architectures transparently.
Files ending in .symlink are automatically linked to $HOME:
bash/bashrc.symlink β ~/.bashrc
zsh/zshrc.symlink β ~/.zshrc
- Zsh: Primary shell, loaded first
- Bash: Backup shell configuration
- Both auto-load vault password if available
- Both detect and use correct Homebrew prefix
System packages - Edit install/brew.sh:
brew install your-packageShell configuration - Edit zsh or bash files:
zsh/config.zsh # Zsh-specific config
bash/bashrc.symlink # Bash-specific config- Add files to
vault-key/ - Encrypt:
./bin/vault -e password - Commit encrypted
vault/files to git - Never commit
vault-key/(it's gitignored)
Create vault-key/org-name.repos.txt:
git@github.com:org/repo1.git $HOME/dev/org-one
git@github.com:org/repo2.git $HOME/dev/org-two
Repos are automatically cloned during installation with smart package installation:
- Detects Node.js version from
.nvmrcorpackage.json - Auto-installs correct Node.js version via NVM
- Detects package manager (pnpm/yarn/npm) from lock files
- Auto-installs missing package managers
- Runs
npm install(or equivalent) automatically
The system installs multiple Python versions via pyenv, with Python 3.13 specially configured as Neovim's Python provider.
Automatic Configuration:
install/python.shcreates anvim-providervirtual environment using Python 3.13- Installs
pynvimpackage required by Neovim plugins vim/init.vimreferences this environment:g:python3_host_prog
Verify Configuration:
# In Neovim, check health status
:checkhealth provider
# Should show:
# Python 3 provider (optional)
# - INFO: Using: ~/.pyenv/versions/nvim-provider/bin/python
# - INFO: Python version: 3.13.xIf you need to use a different Python version for Neovim:
-
Update
install/python.sh(line 69):# Change from: if [ "$short" = "3.13" ]; then # To your desired version, e.g.: if [ "$short" = "3.12" ]; then
-
Run the installation script:
./install/python.sh # This creates the new nvim-provider environment -
Update
vim/init.vim(only if path changed):" Usually stays the same: let g:python3_host_prog = '~/.pyenv/versions/nvim-provider/bin/python'
-
Restart Neovim and verify:
:checkhealth provider
Each Python version gets its own virtual environment:
- Python 3.13:
nvim-provider(for Neovim) - Python 3.12:
py3_12_x(general use) - Python 3.11:
py3_11_x(general use) - etc.
Each environment includes:
- Latest pip
- pip-tools (pip-compile, pip-sync)
- pipx (install Python CLI tools in isolation)
- poetry (Python dependency management)
- uv (fast Python package installer)
Neovim can't find Python provider:
# Check if nvim-provider exists
pyenv versions | grep nvim-provider
# If missing, recreate it
./install/python.sh
# Verify path in Neovim
:echo g:python3_host_progpynvim not installed:
# Activate the environment and install
pyenv activate nvim-provider
pip install pynvim
# Or recreate with install/python.sh
./install/python.sh# Make sure to run with sudo
sudo ./install.sh# Check if password file exists
cat ~/dotfiles/vault-key/password.txt
# Manually source shell config
source ~/.zshrc
# Verify environment variable
echo $VAULT_PASSWORD# Verify Homebrew prefix detection
echo $HOMEBREW_PREFIX # Should show /opt/homebrew
# Check architecture
uname -m # Should show arm64The Google Fonts API changed - we now use their GitHub repository. First installation may take a few minutes to clone the fonts repository.
# Check SSH keys are decrypted
ls -la ~/.ssh/
# Verify SSH config
cat ~/.ssh/config
# Test git connection
ssh -T git@github.com- Fork the repository
- Create a feature branch
- Make your changes
- Test on both Intel and Apple Silicon if possible
- Submit a pull request
MIT License - See LICENSE file for details
- β One-command setup: Single script installs everything
- β Cross-architecture: Works on Intel and Apple Silicon Macs
- β Encrypted secrets: Vault system for SSH keys and credentials
- β Multi-organization: Switch between work environments easily
- β Auto-loading passwords: No need to type vault password repeatedly
- β Automatic backups: Never overwrites, always backs up first
- β Repository cloning: Automatically clones your development repos
- β Font installation: Google, Nerd, and Powerline fonts
- β Shell agnostic: Works with both Zsh and Bash
After installation completes:
- Restart terminal or run
source ~/.zshrc - Verify tools are installed:
node --version python --version git --version
- Check vault if you decrypted it:
ls ~/.ssh/ # Should show your SSH keys
- Switch environments if needed:
workenv your-org
Happy Hacking! π