Warning
Before using the following dotfiles, fork this repository, review the content and remove things you don't want or need. Don't blindly use my settings unless you know what you are doing. Use at your own risk.
The home directory is version-controlled. The .gitignore acts as an allowlist, so files are tracked only once opted-in and nothing leaks in by accident. Git hooks run natively, with no extra tooling.
.install.sh clones the repo, copies every managed file into the home, and sets up Homebrew, runtimes, packages, and all other managed tools. It confirms before overwriting and backs up whatever it replaces, so re-runs are safe.
Brewfile pins formulae, casks, and App Store apps. The wrapped brew command keeps it always in sync, and brew fresh updates, upgrades, cleans, dumps, and runs health checks in one go.
Oh My Zsh runs alongside local plugins and themes under .zsh. Plugins extend the shell with guided profile setup, runtime version switching and LTS installation, completions, and more. The custom node theme displays active runtime versions and symbols for available LTS upgrades.
Node, Ruby, and Python are pinned through nodenv, rbenv, and pyenv. The version managers read the tracked .node-version, .ruby-version, and .python-version files, so the machine and repos stay on the same versions.
The .agents directory is the source for instructions, rules, skills, and hook scripts shared by Claude Code, Codex, and Copilot. Tracked tool entrypoints use relative symlinks back to that source, while tool-specific settings stay separate.
Managed files are linked into the locations each tool expects. When an application rewrites a linked file in place, dotfiles init restores the link and dotfiles doctor reports the drift.
The home Downloads, Movies, and Music folders are replaced with relative symbolic links to iCloud Drive, so their contents sync across devices while paths stay stable and portable.
Run the install script to bootstrap the configuration:
sh -c "$(curl -fsSL https://raw.githubusercontent.com/gabrielecanepa/dotfiles/HEAD/.install.sh)"The script runs the following main actions:
- Installs the configuration by shallow-cloning this repository into a temporary directory (removed automatically on exit, error or interrupt) and copying each managed file into the home directory
- Backs up the replaced files into a timestamped backup folder in the default state directory
- Sets up Homebrew, packages, Oh My Zsh, runtimes, global dependencies, macOS defaults, Codex, Visual Studio Code, and iCloud symlinks
If not already present, generate a new SSH key and copy it to the clipboard:
ssh-keygen -t ed25519 -C "<comment>"
tr -d '\n' < ~/.ssh/id_ed25519.pub | pbcopyAdd the key to GitHub, GitLab and any other services you use.
Install Homebrew:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"Install Oh My Zsh and some useful community plugins:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/HEAD/tools/install.sh)"
plugins=(
zsh-users/zsh-autosuggestions
zsh-users/zsh-completions
zsh-users/zsh-syntax-highlighting
wbingli/zsh-claudecode-completion
)
for plugin in ${plugins[@]}; do
git clone https://github.com/${plugin}.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/${plugin}
done
# Restart
zshThe .install.sh script (see Installation) installs the configuration automatically.
Note
Before overwriting an existing file, the script asks for confirmation (default no).
Every replaced file is moved into a timestamped backup folder in the default state directory. To restore a file, just copy it back from that folder:
backup="${XDG_STATE_HOME:-$HOME/.local/state}/dotfiles/backup"
ls "$backup"
cp "$backup/<timestamp>/.zshrc" ~/.zshrcTo clone the repository locally:
gh repo clone gabrielecanepa/dotfiles
# or
git clone git@github.com:gabrielecanepa/dotfiles.gitInstall the packages listed in Brewfile:
brew bundle --file ~/.homebrew/BrewfileNote
Skip this step if you cloned the repository with gh repo clone or git clone above, as your home directory is already version-controlled.
After adding your SSH key to GitHub, initialize ~ as a git repo that tracks the remote:
git -C ~ init -b main
git -C ~ remote add origin git@github.com:gabrielecanepa/dotfiles.git
git -C ~ fetch --depth 1 origin main && git -C ~ reset --hard FETCH_HEADThis repository wires its own checks with local Git hooks running Oxfmt, ShellCheck, commitlint and other code quality tools on commit and push.
Use the custom profile plugin to create your shell configuration with a guided prompt:
profile installInstall the Node.js version in use with nodenv. A tracked install hook enables Corepack automatically, so pnpm follows the active Node version:
nodenv install $(cat ~/.node-version) --skip-existingLink the local nodenv version to the tracked .node-version file:
nodenv global $(cat ~/.node-version)
rm -f $NODENV_ROOT/version && ln -sf ~/.node-version $NODENV_ROOT/versionpnpm is installed by Corepack, enabled by the tracked install hook.
Node-based tooling (Commitlint, Oxfmt, TypeScript and its language server) and every other global npm package are tracked in .npm/package.json by the custom npm-global plugin: the wrapped npm command re-dumps the manifest after every global install or removal, a second nodenv install hook restores it into every new Node version, and a bare npm install -g reinstalls it at any time.
Native tooling like ShellCheck still ships through the Brewfile.
Install and link the Ruby version in use with rbenv:
rbenv install $(cat ~/.ruby-version) --skip-existing
rm -f $RBENV_ROOT/version && ln -sf ~/.ruby-version $RBENV_ROOT/versionInstall and link the Python version in use with pyenv:
pyenv install $(cat ~/.python-version) --skip-existing
rm -f $PYENV_ROOT/version && ln -sf ~/.python-version $PYENV_ROOT/version.codex/system.toml is the portable, shared system configuration linked to /etc/codex/config.toml. Create the symlink with:
# Needs root privileges
sudo mkdir -p /etc/codex
sudo ln -sfn "$HOME/.codex/system.toml" /etc/codex/config.tomlCodex may add project trust, hook state, and other machine-specific values to the private user configuration. Keep these definitions entirely in that file.
Use symlinks to backup keybindings, settings and snippets of Visual Studio Code.
Warning
The following operations will permanently replace some system folders with symlinks to the corresponding files in the repository. Make sure to back up your data before proceeding.
for config in snippets keybindings.json settings.json; do
file=~/Library/Application\ Support/Code/User/$config
rm -rf $file
ln -sf ~/.vscode/user/$config $file
doneVS Code rewrites settings.json in place on update or Settings Sync, replacing the symlink with a regular file. Running dotfiles init re-links it, and dotfiles doctor reports the drift.
Extensions are tracked in .vscode/extensions.json, and dotfiles init installs any listed extension that is missing from the machine.
To avoid emitting beeps in Electron-based applications when using the keyboard combinations ^⌘←, ^⌘↓ and ^⌘ (see this issue) create the keybinding settings file:
[[ ! -d ~/Library/KeyBindings ]] && mkdir ~/Library/KeyBindings
touch ~/Library/KeyBindings/DefaultKeyBinding.dictAnd populate it with the following content:
{
"^@\UF701" = "noop";
"^@\UF702" = "noop";
"^@\UF703" = "noop";
}
Use the following snippet to replace the home Downloads, Movies and Music folders with symbolic links pointing at their corresponding (new or existing) folder on iCloud. This allows continuous synchronization between cloud and local machine.
Warning
The following operations permanently replaces system folders with symbolic links to iCloud Drive. Make sure to back up your data before proceeding.
for folder in Applications Developer Downloads Movies Music Pictures; do
# create icloud drive folder
cloud_folder=~/Library/Mobile\ Documents/com~apple~CloudDocs/$folder
[[ ! -d $cloud_folder ]] && mkdir $cloud_folder
case $folder in
# symlink to system folder
Applications)
rm -rf ~/Applications
ln -sf /Applications ~/Applications
ln -sf $cloud_folder /Applications/iCloud
;;
# symlink to cloud folder
Downloads|Movies|Music)
[[ -d ~/$folder ]] && mv ~/$folder/* $cloud_folder 2>/dev/null
rm -rf ~/$folder && ln -sf ${cloud_folder#$HOME/} ~/$folder
;;
esac
done