One command to get your full dev environment on a new machine.
git clone https://github.com/ahamdah/dotfiles.git ~/dotfiles
cd ~/dotfiles && make install| Platform | Status |
|---|---|
| macOS | ✅ Full support (Homebrew) |
| Ubuntu / Debian | ✅ Full support (apt + cargo) |
| WSL2 (Windows) | ✅ Full support |
| Tool | Purpose |
|---|---|
| Zsh + Oh My Zsh | Shell |
| zsh-autosuggestions | Fish-like history suggestions |
| zsh-syntax-highlighting | Command syntax coloring |
| Starship | Installed but unused — zsh prompt is the custom OMZ gruvbox theme |
| Neovim + lazy.nvim | Editor with LSP, Treesitter, completion |
| eza | Modern ls with icons |
| bat | Modern cat with syntax highlighting |
| ripgrep | Fast grep replacement (rg) |
| fd | Fast find replacement |
| fzf | Fuzzy finder (Ctrl+R, Ctrl+T, Alt+C) |
| zoxide | Smart cd with frecency |
| lazygit | Terminal Git UI (lg) |
| delta | Beautiful git diffs |
| gh | GitHub CLI |
| tmux + TPM | Terminal multiplexer |
| nvm | Node version manager |
| Rust | Cargo + toolchain |
| glow | Markdown rendered in the terminal (md) — via Brewfile |
| duti | Sets default apps per file type — via Brewfile, used by make macos |
| .NET 8 SDK (LTS) | dotnet pinned to the 8.x keg — via Brewfile |
Note:
zsh-autosuggestionsandzsh-syntax-highlightingare not bundled with Oh My Zsh.make linkclones them automatically if missing.
make install # Install all tools + symlink dotfiles
make link # Symlinks only (also installs zsh plugins if missing)
make update # Update all tools + pull latest dotfiles
make check # Validate all shell scripts and JSON configs
make macos # Apply macOS system defaults (macOS only)
make brew-bundle # Install all apps from Brewfile (macOS only)
make clean # Remove all symlinksdotfiles/
├── shell/
│ ├── .zshrc ← Entry point (sources modules)
│ ├── .zshenv ← Non-interactive PATH (cron, scripts)
│ ├── exports.zsh ← PATH & environment variables
│ ├── aliases.zsh ← Aliases (OS-aware)
│ ├── plugins.zsh ← Oh My Zsh plugins & theme (ZSH_THEME="gruvbox")
│ ├── gruvbox.zsh-theme ← Custom Gruvbox Dark OMZ prompt
│ ├── keybindings.zsh ← bindkey & ZLE widgets
│ ├── functions.zsh ← mkcd, extract, fcd, serve, fs, fo…
│ └── .tmux.conf ← Tmux config (Gruvbox Dark theme)
│
├── config/
│ ├── starship.toml ← Starship prompt config (unused, kept for reference)
│ ├── iterm2/
│ │ ├── DynamicProfiles/gruvbox.json ← iTerm2 Gruvbox Dark profile
│ │ └── preferences/com.googlecode.iterm2.plist ← Full iTerm2 settings (General, Key Mappings, …)
│ ├── nvim/ ← Neovim (lazy.nvim + LSP + Gruvbox)
│ │ ├── init.lua ← options, keymaps, autocmds + lazy.nvim bootstrap
│ │ ├── lazy-lock.json ← pinned plugin revisions (commit this)
│ │ ├── lua/plugins/ ← one file per plugin spec
│ │ └── after/ftplugin/ ← per-filetype overrides (go.lua)
│ ├── git/
│ │ ├── .gitconfig ← Global git config + aliases + delta
│ │ ├── .gitconfig-work ← Work identity, pulled in per-repo via includeIf
│ │ └── .gitignore_global
│ └── vscode/
│ ├── settings.json
│ ├── keybindings.json
│ └── install-extensions.sh ← portable extension installer
│
├── scripts/
│ ├── setup.sh ← Full installer (macOS + Linux)
│ ├── link.sh ← Symlinks + zsh plugin auto-install
│ ├── update.sh ← Update all tools in one command
│ ├── macos.sh ← macOS system defaults
│ └── brew-bundle/
│ └── Brewfile ← Full macOS app manifest
│
├── .github/
│ └── workflows/
│ └── check.yml ← CI: validates scripts on every push
├── Makefile ← Entry point
├── Dockerfile ← Ubuntu container for testing
└── README.md
| Symlink | Points to |
|---|---|
~/.zshenv |
shell/.zshenv |
~/.zshrc |
shell/.zshrc |
~/.tmux.conf |
shell/.tmux.conf |
~/.config/starship.toml |
config/starship.toml |
$ZSH_CUSTOM/themes/gruvbox.zsh-theme |
shell/gruvbox.zsh-theme |
~/Library/Application Support/iTerm2/DynamicProfiles/gruvbox.json |
config/iterm2/DynamicProfiles/gruvbox.json (macOS only) |
~/.config/nvim |
config/nvim/ |
~/.gitconfig |
config/git/.gitconfig |
~/.gitignore_global |
config/git/.gitignore_global |
~/.gitconfig-work |
config/git/.gitconfig-work |
VS Code settings.json |
config/vscode/settings.json |
VS Code keybindings.json |
config/vscode/keybindings.json |
.gitconfig commits as the personal identity by default. Work repos swap in
the rased.ai address automatically — no per-clone git config --local needed,
so a fresh clone on a new machine is already correct.
The switch is driven by two [includeIf] rules at the bottom of .gitconfig,
both pointing at ~/.gitconfig-work:
| Trigger | Matches |
|---|---|
hasconfig:remote.*.url:**/rased-org/** |
Any repo whose remote is in the rased-org GitHub org, wherever it's cloned |
gitdir:~/Development/work/ |
Anything under ~/Development/work/, if that layout is ever adopted |
The hasconfig rule is the one that does the work: it keys off the remote, not
the directory, so moving or re-cloning a repo can't strand it on the wrong
address. Includes are evaluated in file order and later values win, which is why
those two blocks must stay at the bottom of .gitconfig.
Check which identity a repo resolved to:
git whoami # alias for: git config user.email
git config --show-origin user.email # also shows which file supplied itTo add another work org, add its pattern alongside the existing hasconfig
rule. To onboard a new machine, nothing extra — make link creates
~/.gitconfig-work and the rules fire on their own.
Note: a missing include
pathis not an error in git. If~/.gitconfig-workdoesn't exist, work repos silently fall back to the personal address rather than failing loudly — so if commits show the wrong author, check that symlink first.
make macos points iTerm2's entire preferences domain (General settings, Key Mappings, etc. — not just the Dynamic Profile) at config/iterm2/preferences/com.googlecode.iterm2.plist via:
defaults write com.googlecode.iterm2 PrefsCustomFolder -string "$DOTFILES_DIR/config/iterm2/preferences"
defaults write com.googlecode.iterm2 LoadPrefsFromCustomFolder -bool true
defaults write com.googlecode.iterm2 NoSyncNeverRemindPrefsChangesLostForFile_selection -int 0 # 0 = save on quit
defaults write com.googlecode.iterm2 NoSyncNeverRemindPrefsChangesLostForFile -bool true
After that (and a full quit + relaunch of iTerm2 to pick it up), any setting changed in the iTerm2 GUI is auto-exported back to that file on quit — so git status in this repo shows what changed, and a new machine gets identical settings for free after make macos.
| Function | What it does |
|---|---|
mkcd <dir> |
mkdir + cd in one step |
extract <file> |
Universal archive extractor |
fcd |
fzf-powered cd |
fgb |
fzf git branch checkout |
fgl |
fzf git log browser |
serve [port] |
Quick HTTP server in current directory |
killport <port> |
Kill process on a port |
envload [file] |
Source a .env file into the shell |
fo |
fzf file opener → Neovim |
fs <query> |
ripgrep + fzf file content search |
| Action | Keybinding |
|---|---|
| Prefix | Ctrl+a |
| Vertical split | Prefix + | |
| Horizontal split | Prefix + - |
| Navigate panes | Prefix + h/j/k/l |
| Navigate windows | Prefix + m/n |
| Install plugins | Prefix + I |
| Reload config | Prefix + r |
| Copy mode | Prefix + Enter |
| Copy selection | v then y |
# 1. Reload shell
exec zsh
# 2. Install tmux plugins (inside a tmux session)
# Ctrl+a then I
# 3. Install VS Code extensions
bash config/vscode/install-extensions.sh
# 4. Install macOS apps
make brew-bundle
# 5. Apply macOS system defaults ← must come AFTER step 4
make macos
# 6. Open Neovim (plugins auto-install via lazy.nvim)
nvimOrder matters for steps 4 and 5.
macos.shbinds every source and config file type to VS Code usingduti, which arrives with the Brewfile. That block is guarded bycommand -v duti, so running it first just skips silently — no error, no default-app bindings.make installoffers to apply macOS defaults partway through, before the Brewfile has been installed; that early pass is fine, but runmake macosagain afterwards (step 5) to pick up the parts that neededduti. Re-running is safe — every step is idempotent.