Skip to content

jamand/dotfiles

Repository files navigation

dotfiles

My personal dotfiles for macOS, managed with GNU Stow and Homebrew.

The goal is to automate setting up a new Mac from scratch: shell, editor, Git, dev tools, and apps. Also I want to avoid having a "version drift" between machines and forget how I set up my daily working environment. Each tool's config lives in its own "package" that can be installed independently.

Built for my specific workflow as a Cloud Engineer (backend/infra, Go, Node, Terraform, Neovim + VS Code, iTerm2), but designed to be modular enough to fork and adapt.

Early stage: This repo is actively evolving. Expect rough edges and breaking changes. If you'd like to use it, fork it and adapt to your needs (after careful review, at your own risk!). Issues and PRs for bugs are welcome.

Quick start

For the repo and replace CHANGEME with your GitHub handle:

git clone https://github.com/CHANGEME/dotfiles.git ~/dotfiles
cd ~/dotfiles
cp config.example config.local  # Fill in your personal values
./install.sh --help

First-time setup (existing configs on your machine)

If you have existing configs for e.g. zsh you would like to consider, run the following. You can use the diff to consider the changes you need. Make sure to also manually adopt other config files for your tool if required (this will only adopt files that are defined in this repo):

./install.sh --dry-run    # Preview what would happen
./install.sh --adopt      # Pull existing configs into the repo, then symlink
git diff                  # Review what was adopted

Commands

If in doubt, run:

./install --help

Here is a quick overview:

./install.sh                              # Interactive install (gum picker)
./install.sh -p zsh -p git -p starship    # Non-interactive: install specific packages
./install.sh -p zsh,git,starship         #  Same, comma-separated
./install.sh -p zsh,git --macos           # Install packages and apply macOS defaults
./install.sh update                       # Pull latest, update brew deps, re-stow packages
./install.sh uninstall                    # Remove symlinks for selected packages

Options

-p, --package PKG   # Packages to install (repeatable, comma-separated)
-n, --dry-run       # Preview changes without installing
--adopt             # Adopt existing files into the repo (first-time setup)
--macos             # Apply macOS system defaults (skipped by default)
-h, --help          # Show all options

Without -p, the script runs interactively using gum for package selection. With -p, all prompts are skipped.

Personal configuration

On first run, install.sh prompts for your name, email, and GPG key, saving them to config.local (gitignored). These values are used by per-package pre-hooks to generate local config files (e.g., ~/.gitconfig.user).

To configure manually, copy the example file:

cp config.example config.local
Variable Used in Purpose
GIT_NAME .gitconfig Git author name
GIT_EMAIL .gitconfig Git author email
GIT_SIGNING_KEY .gitconfig GPG signing key ID

Packages

Each package is a self-contained folder with its configs, a Brewfile for dependencies, and optional prehooks.sh / posthooks.sh for setup steps.

Package What it configures Brewfile Hooks
editorconfig Global .editorconfig (LF, UTF-8, spaces) - -
zsh Shell config, aliases, functions, completions zsh -
git Git config (GPG signing, delta pager), SSH config, global gitignore git, git-delta, lazygit, gh, gnupg Pre-hook: generates ~/.gitconfig.user
starship Prompt theme (gruvbox dark) starship -
nvim Neovim (LazyVim) neovim, luarocks -
vscode VS Code settings, extensions visual-studio-code, claude-code Post-hook: installs extensions
iterm2 iTerm2 preferences, 0xProto Nerd Font iterm2, font-0xproto-nerd-font Post-hook: configures prefs folder sync
docker Docker client config (ghcr.io, osxkeychain) rancher, docker-compose -
k8s Kubernetes tools kubectl, helm, k9s, k3d -
terraform OpenTofu CLI config opentofu Post-hook: creates plugin cache dir
go Go environment variables go -
node Node.js and pnpm node@24, pnpm -
proton Proton suite + SSH agent + GPG key via Pass CLI pass-cli, protonvpn, proton-mail, proton-drive, proton-pass Post-hook: logs in and imports GPG key
apps Personal apps (no configs, just installs) obsidian, ollama, localsend, pika, tailscale-app, spotify, opera, anki -

How it works

Stow

Each top-level folder is a "package". Its contents mirror $HOME. Running stow zsh creates symlinks from ~/dotfiles/zsh/.zshrc to ~/.zshrc, etc.

stow git              # Symlink a package
stow --restow git     # Update symlinks
stow -D git           # Remove symlinks

Per-package Brewfiles

Each package declares its own dependencies. The install script runs brew bundle only for packages you select.

Pre-hooks and post-hooks

Packages can include:

  • prehooks.sh — runs before stowing (e.g., generating local config files)
  • posthooks.sh — runs after stowing (e.g., configuring iTerm2 prefs sync, installing VS Code extensions)

Secret scanning

lefthook runs gitleaks on every commit to prevent accidentally committing secrets by checking against a large list of known patterns. Still, you should be careful to not push credentials.

Keeping configs in sync

Some tools don't auto-save to a custom folder. Lefthook warns on commit if things are out of sync, and shell functions let you update manually (so you are in control):

Tool Auto-sync Manual sync command
iTerm2 Yes (prefs folder sync) -
VS Code extensions Warn on commit dotfiles-sync-extensions

SSH and GPG keys via Proton Pass

SSH and GPG keys are managed through Proton Pass CLI — keys never touch the filesystem as plain files.

  • SSH: pass-cli runs as an SSH agent (started in .zshrc). Keys are loaded from your vault on demand.
  • GPG: The proton hook imports your signing key from Proton Pass into the local GPG keyring.

On a fresh machine, select the proton package during install. The hook will prompt you to log in and import your GPG key automatically.

Apps not managed by Homebrew

  • Xcode (App Store only — use xcode-select --install for CLI tools)
  • Keynote, Numbers, Pages (App Store)

Adding a new package

  1. Create the folder structure mirroring $HOME, e.g.:
    mkdir -p newpkg/.config/newtool
  2. Add your config files inside
  3. (Optional) Add a Brewfile for dependencies
  4. (Optional) Add a prehooks.sh for pre-stow setup
  5. (Optional) Add a posthooks.sh for post-stow setup
  6. Add a .stow-local-ignore if the package has a Brewfile or hooks:
    Brewfile
    prehooks\.sh
    posthooks\.sh
    \.stow-local-ignore
    
  7. Add the package name to ALL_PACKAGES in install.sh
  8. Run stow newpkg

TODO

  • Ask for sudo/credentials once (keep-alive for brew cask installs)
  • End-to-end testing on a fresh installation
  • More apps + settings
  • Improve structure (dependencies,...)

Resources

About

My personal dotfiles (MacOS, Cloud Engineer)

Topics

Resources

License

Stars

Watchers

Forks

Contributors