Setting up a new Mac.
How I use dotfiles to make a fresh Mac feel like mine.
Unboxing a new Mac and peeling the protective film from its display is hard to beat. Making it feel like mine is a close second.
A fresh Mac is nice. Remembering how I set up the last one is less nice. Which shell settings did I change? Where do my screenshots go? What was that command-line tool I installed and then started using everywhere?
My macOS dotfiles repository is where I keep those answers. It brings together the apps, configuration files, and macOS preferences that make the computer feel like mine, with a setup command to put them in place.
This is a tour of that setup and a guide to trying it yourself. You can browse it for ideas, fork it and change things, or use the files to start your own repository. You don’t have to want my whole setup to find something useful in it.
Why put a Mac setup in Git?
A lot of the work of setting up a computer happens in small, forgettable steps. Install an app. Change a shortcut. Adjust the terminal. Fix the same setting again in another editor.
I want those choices somewhere I can see them. The Brewfile records what gets installed, configuration files record how tools behave, and scripts record the system preferences. Git gives those choices a history: I can review a change, see what the previous setting was, and carry the setup to another Mac.
The turnkey part is that the repository also knows how to apply those files and settings. I don’t have to translate a folder of configuration into a second checklist of installation instructions. There’s a command to report the current state, a dry run to preview work, and a full setup that runs the phases in order.
It still leaves plenty to do by hand. Signing into accounts, setting up SSH keys, and opening apps for the first time belong to the machine. Shell history, browser profiles, credentials, caches, and other private or generated state stay out of Git.
The tools, and what they’re there for
Finding things in the terminal
The Brewfile is the app and tool list. Homebrew installs from that file, so there’s one place to add something or decide it no longer belongs.
A few of the terminal tools fit together especially well:
- ripgrep searches inside files.
rg 'post-meta' srcis the sort of command I can use to find a style or component in this blog. - fd finds files by name.
fd '\.astro$' srclists Astro files without needing to write a longerfindcommand. - fzf turns a list into a fuzzy picker. The shell also uses it for completion, so choosing a path doesn’t always mean typing it in full.
- zoxide makes revisiting directories easier. Once it has learned a path,
z blogcan get me back there with less typing. - bat adds syntax highlighting when reading a file, and eza provides the directory listings. The shell aliases
catandlsto these tools;catpgives me a plain bat view without paging.
Starship handles the prompt, and Atuin gives me searchable command history stored locally. Zsh autosuggestions and syntax highlighting add feedback while I type. The shell configuration lives under ~/.config/zsh, with its pieces split into files for aliases, integrations, completion, and keybindings.
A terminal that keeps the working context
Ghostty is configured with the Vesper theme and JetBrainsMono Nerd Font. New windows, tabs, and splits inherit the active working directory. That’s a small setting with a useful effect: opening another terminal surface doesn’t send me back home when I’m already in a project.
Option acts as Alt, which lets the terminal use the same modifier for shell editing and tmux shortcuts. The shell includes Option-Backspace for deleting the word to the left of the cursor. Its cursor also changes shape between Vim insert and normal mode, giving me a visible cue about how the next key will behave.
Tmux keeps terminal sessions and panes together; Sesh makes those sessions easier to choose. In this configuration, the tmux prefix is Ctrl+Space. Press the prefix, release it, then press b to open the Sesh picker, or Tab to return to the previous session. Prefix followed by h, j, k, or l moves between panes.
There are direct Option shortcuts too: Option+h/j/k/l moves between panes, and Option+s opens the session picker. New tmux panes inherit the current directory, and copying a selection in tmux sends it to the macOS clipboard.
Yazi covers file browsing in the terminal. The shell wrapper is y: browse to a directory, press q, and the shell follows you there. Press Q when you want to leave Yazi without changing the shell’s directory.
Editors and development tooling
The setup has separate phases for Neovim, VS Code, and Zed. They share the Vesper palette, while their own configuration files handle settings and keybindings. VS Code also has a reviewed extension list and Ghostty configured as its external terminal.
Keeping these files in the checkout gives me somewhere to make changes that will survive the next setup. Zed accounts, provider settings, and runtime state stay local rather than being copied into the repository.
Mise installs Node.js, and uv supplies Python tooling. The npm phase installs the CLI list, including pnpm, coding-agent tools, and the devcontainer CLI. If you only want an editor configuration, you can inspect and run that phase without adopting the rest of the list.
Apps outside the terminal
The GUI app list includes 1Password for credentials, Raycast for launching and actions, CleanShot for screenshots, and Chrome for browsing. It also includes utilities such as Homerow for keyboard navigation and Stats for system information, along with communication apps and hardware utilities.
An entry in the Brewfile installs the app; it doesn’t mean every part of that app is configured. Accounts, permissions, and first-run choices still need attention. The complete list is there to browse, and it’s one of the first files I’d trim in a fork.
The preferences that make it feel like my Mac
The macOS defaults script groups settings by keyboard, Finder, Dock, trackpad, screenshots, menu bar, and general UI. These are personal choices, and they’re worth reading before applying.
Keyboard: keep what I type
Key repeat is fast, with a short initial delay. Holding a key repeats it instead of opening the accented-character menu, which suits Vim-style movement.
Automatic spelling correction, capitalization, periods, smart quotes, and smart dashes are off. When I type a command or a snippet of code, I want the characters I entered to stay that way. If you use the accented-character menu or like automatic punctuation, those are settings to keep in your version.
Finder: make the files easier to see
Hidden files and filename extensions are visible. Finder shows its path and status bars, starts in list view, and searches the current folder by default. That makes configuration files easier to find and gives a search a more predictable starting point.
Drive icons are hidden from the Desktop, while ordinary files and folders remain visible. Mounting an installer doesn’t need to add another icon to the workspace.
Dock and movement: fewer interruptions
The Dock hides automatically and appears without a delay or slide animation. Recent apps are hidden, minimized windows go into their app’s icon, and Spaces keep their order rather than rearranging themselves by use.
The setup also reduces motion and shortens several transitions. The aim is to get from one place to another with less waiting and visual movement. Dark mode is part of this setup too.
Tap-to-click and three-finger drag are enabled on the trackpad. Those are easy preferences to miss when setting up a new machine, so they live here with the rest.
Screenshots and small bits of context
The built-in screenshot tool saves PNG files to ~/.local/share/pictures/screenshots by default, without window shadows or the floating thumbnail preview. Keeping screenshots in a known folder makes them easier to find later and keeps them off the Desktop. The folder can be changed through XDG_SCREENSHOTS_DIR.
The menu bar shows the date, weekday, and battery percentage. Save and print panels open expanded, and new documents default to local storage rather than iCloud. None of these settings is a big project on its own, but together they account for a lot of the “this feels right” part of setup.
Chrome: a quieter starting point
The Chrome phase blocks site notifications, suppresses the default-browser prompt, and disables Chrome’s built-in password manager in favor of 1Password. These policies apply to the current user. Restart Chrome after applying them.
If you use Chrome’s password manager, change or skip this phase. It’s another place where my defaults should become your decision.
Make the repository yours
Start by finishing the macOS setup assistant and connecting to the internet. Open Terminal and install Apple’s Command Line Tools if they aren’t already installed:
xcode-select --install
Wait for installation to finish. We need Git to get the repository.
Then choose one of the following approaches. They all lead to a local checkout you can inspect and edit before running setup.
Fork it to keep a connection to my version
Fork the repository on GitHub, then clone your fork. Replace YOUR-USERNAME with your GitHub username:
mkdir -p ~/.local/share
git clone https://github.com/YOUR-USERNAME/dotfiles-macos.git \
~/.local/share/dotfiles-macos
cd ~/.local/share/dotfiles-macos
Optionally add my repository as upstream so you can fetch future changes to review:
git remote add upstream https://github.com/bharatsuri1/dotfiles-macos.git
Your origin points to your fork, where your changes belong. Fetching upstream changes doesn’t apply them to the machine; review and merge the parts you want before running setup again.
Clone it and point it at your own repository
If you prefer a separate repository while keeping the existing history, clone mine first:
mkdir -p ~/.local/share
git clone https://github.com/bharatsuri1/dotfiles-macos.git \
~/.local/share/dotfiles-macos
cd ~/.local/share/dotfiles-macos
Create an empty repository in your account, then replace the remote with its URL:
git remote set-url origin https://github.com/YOUR-USERNAME/YOUR-REPO.git
git remote -v
If you’re only trying things locally and don’t want a remote yet, use git remote remove origin instead. You still have local commits and history, and can add your own remote later.
Start with the files and a fresh history
For a clean starting point, use the clone commands above, then remove the Git metadata from that new checkout only:
cd ~/.local/share/dotfiles-macos
rm -rf .git
git init -b main
git add .
git commit -m 'chore: start my Mac setup'
Removing .git leaves the setup files in place but discards the clone’s history and remotes. Initializing Git again gives you a new history of your own. When you’re ready, add your empty repository as origin:
git remote add origin https://github.com/YOUR-USERNAME/YOUR-REPO.git
Whichever route you choose, keep the checkout in place after setup. Managed configuration files are symlinks back into it, so this folder becomes part of the working setup.
Review, preview, then apply
Before running setup, read the README, trim the Brewfile, and look through config/ and lib/macos-setup/. Review the shell aliases and coding-agent permission settings along with the visible themes and shortcuts. All of these files are part of the setup you’re adopting.
Set your own Git identity if you haven’t already:
git config --global user.name 'Your Name'
git config --global user.email 'you@example.com'
The repository’s bootstrap script sets my identity and other Git defaults and can update the checkout from its configured remote. For an adapted copy, use the local CLI below. If you later adopt the bootstrap too, change its repository URL and identity defaults to yours first.
From your checkout, inspect the current state:
./bin/macos-setup status
Preview the full setup:
./bin/macos-setup --dry-run apply
If that matches what you want, run:
./bin/macos-setup apply
The full command runs all the phases in dependency order. Follow the prompts as it proceeds. To try a smaller part, preview and apply that phase instead. For example, these commands only target macOS preferences:
./bin/macos-setup --dry-run macos-defaults
./bin/macos-setup macos-defaults
Other phases include homebrew, shell-tools, shell, config, mise, npm-tools, nvim, vscode, zed, yazi, chrome-defaults, and herdr. Check the README for what each manages and its prerequisites. The Homebrew phase installs the whole Brewfile, so trim that file before using it for a smaller setup.
Existing configuration targets are moved to timestamped backups under ~/.local/state/dotfiles-macos/backups/ before being replaced by links. Those backups cover configuration files; they aren’t a rollback of every package installation or system preference.
Keep using it after setup
Once setup finishes, open a new terminal session and run status again from the checkout. Open your apps, sign in, finish their first-run prompts, and try a familiar project.
From then on, edit managed configuration in the repository. Because the files are linked, edits to an existing linked file are available at its installed path; the tool may still need a reload or restart. Changes to the Brewfile or macOS defaults need their setup phase run again. New configuration files may need to be linked by the relevant phase too.
For shell settings that belong to just one Mac, use ~/.zshenv.local or ~/.config/zsh/local.zsh. Those files stay outside Git. That gives machine-specific paths and overrides somewhere to live without becoming defaults for every computer.
When you make a change worth keeping, review it with git diff and commit it. Push to your own remote when you want that version available for another machine. If you keep an upstream connection, treat incoming changes the same way: read them, choose what belongs, and apply the relevant phase.
That’s what I want from this repository: a setup I can use today and understand when I come back to it. If you borrow a preference, find a useful tool, or turn the whole thing into your own setup, it has done something useful for you too.