"Of the theme that I have declared to you, I will now that ye make in harmony together a Great Music." — Eru Ilúvatar, The Silmarillion
A declarative dotfiles and system bootstrap for macOS (and potentially Linux).
Inspired by the Ainulindalë, where Eru Ilúvatar shaped the world through the harmony of the Great Music, this bootstrap system shapes your development environment with purpose and harmony.
- Pragmatic over idealistic: Prefers practical solutions over theoretical perfection
- Simple over complex: Avoids unnecessary complexity and over-engineering
- Maintainable: Easy to understand, modify, and debug
- Idempotent: Safe to run multiple times
- Selective: Run everything or just specific tasks
# Clone into ~/.config
git clone <your-repo> ~/.config
cd ~/.config
# Full installation
./eru.sh install
# See what would happen without doing it
./eru.sh install --dry-run
# Run specific tasks
./eru.sh install packages wm
# Upgrade packages
./eru.sh upgrade packages~/.config/
├── eru.sh # Main bootstrap script
├── brew/ # Homebrew packages
│ ├── Brewfile # Common packages for all machines
│ ├── $USER.Brewfile # User-specific packages (gitignored)
│ └── $HOSTNAME.Brewfile # Machine-specific packages (gitignored)
├── macos/
│ └── defaults.sh # macOS system preferences
├── gnupg/ # GnuPG configuration (symlinked to ~/.gnupg)
│ ├── ...
│ └── README.md
├── ssh/ # SSH configuration (symlinked to ~/.ssh)
│ ├── ...
│ └── README.md
├── fish/ # Fish shell configuration
├── git/ # Git configuration
├── alacritty/ # Alacritty configuration
├── ...
└── emacs/ # Emacs configuration
The bootstrap system is organized into independent tasks:
Install or update Homebrew itself.
./eru.sh install homebrew
./eru.sh upgrade homebrewInstall packages from Brewfiles. Automatically processes:
brew/Brewfile- Common packagesbrew/$USER.Brewfile- User-specific (e.g.,d12frosted.Brewfile)brew/$HOSTNAME.Brewfile- Hostname-specific (e.g.,macbook-pro.Brewfile)
./eru.sh install packages
./eru.sh upgrade packagesConfigure macOS system defaults (Dock, Finder, keyboard, etc.).
./eru.sh install macosSet up fish shell as the default shell.
./eru.sh install shellConfigure window manager (yabai + skhd):
- Sets up yabai sudoers for scripting additions
- Patches skhd LaunchAgent plist
- Restarts services
./eru.sh install wmSet up development tools:
- Generate SSH keys (if not present)
- Fix GPG permissions
- Configure git, etc.
./eru.sh install devtoolsCreate symlinks for configuration files that need to live outside ~/.config.
Some programs (like GnuPG and SSH) don't support XDG Base Directory spec and expect configs in specific locations. This task symlinks configs from ~/.config/ to their expected locations:
- GnuPG:
~/.config/gnupg/*→~/.gnupg/* - SSH:
~/.config/ssh/config→~/.ssh/config
Setup:
./eru.sh install symlinksFeatures:
- Automatically backs up existing files before symlinking
- Skips files that are already correctly symlinked
- Fixes GnuPG permissions (600 for files, 700 for directories)
See gnupg/README.md and ssh/README.md for details.
Set up Emacs configuration. Supports subtasks:
./eru.sh install emacs # All Emacs setup
./eru.sh install emacs:config # Just config
./eru.sh install emacs:db # Just database syncinstall- Install and configureupgrade- Update existing installations
--dry-run- Show what would be done without doing it--force- Skip dependency checks and continue anyway--help- Show help message
# Full installation with dry-run first
./eru.sh install --dry-run
./eru.sh install
# Install just packages and window manager
./eru.sh install packages wm
# Upgrade packages only
./eru.sh upgrade packages
# Reset yabai/skhd configuration
./eru.sh install wm
# Run Emacs-specific subtask
./eru.sh install emacs:config
# Force install even if dependencies missing
./eru.sh install wm --force-
Clone the repository
git clone <your-repo> ~/.config cd ~/.config
-
Create your Brewfiles
# Copy and customize user-specific packages cp brew/example-user.Brewfile brew/$USER.Brewfile # Optional: hostname-specific packages cp brew/example-hostname.Brewfile brew/$(hostname -s).Brewfile
-
Edit configurations
- Update
brew/Brewfilewith common packages - Customize
macos/defaults.shto your preferences - Add your application configs (fish, git, etc.)
- Update
-
Run bootstrap
# See what will happen ./eru.sh install --dry-run # Do it! ./eru.sh install
Tasks have implicit dependencies. The bootstrap script performs basic checks:
homebrew (standalone)
↓
packages (requires: homebrew)
↓
wm (requires: packages → yabai, skhd)
shell (requires: packages → fish)
devtools (requires: packages → gnupg, ssh)
emacs (requires: packages → emacs-plus)
When running individual tasks, you'll get helpful error messages if dependencies are missing.
Edit brew/Brewfile:
brew "your-package"
cask "your-app"Edit brew/$USER.Brewfile:
# Personal tools
brew "your-personal-tool"Edit brew/$HOSTNAME.Brewfile:
# Work laptop only
brew "kubectl"
cask "zoom"Then run:
./eru.sh install packagesThe repository includes GitHub Actions workflows that test:
- ✅ ShellCheck linting
- ✅ Dry-run mode on all tasks
- ✅ Homebrew installation
- ✅ Package installation
- ✅ Help and error handling
- ✅ Script syntax validation
Run tests locally:
# Lint
shellcheck eru.sh macos/defaults.sh
# Syntax check
bash -n eru.sh
# Dry run
./eru.sh install --dry-run- Define the task function in
eru.sh:
function task_mytask() {
task_start "mytask" "Setting up my task"
# Your logic here
task_complete "mytask" "My task configured"
}- Register the task:
declare -A TASKS=(
...
[mytask]="task_mytask"
)- Add to default tasks (optional):
DEFAULT_TASKS=(... mytask)Edit macos/defaults.sh and add your defaults write commands:
# Example: Disable animations
defaults write NSGlobalDomain NSAutomaticWindowAnimationsEnabled -bool falseRun:
./eru.sh install macosrm ~/.cache/eru/eru.lockUse --force to skip checks (use with caution):
./eru.sh install mytask --forceAlways available:
./eru.sh install --dry-runThe script automatically adds Homebrew to PATH, but if you're getting "brew not found":
# Apple Silicon
eval "$(/opt/homebrew/bin/brew shellenv)"
# Intel
eval "$(/usr/local/bin/brew shellenv)"This setup deliberately avoids Nix/nix-darwin/home-manager because:
- macOS is not designed for Nix
- Emacs is not designed for Nix
- Complexity cost > reproducibility benefits (for this use case)
- Homebrew is simpler and "just works" on macOS
If you want Nix, that's cool! But this repo is explicitly for a simpler approach.
Use it however you want. No warranty. Your configs, your responsibility.
