Personal development environment managed with GNU Stow and driven through a single main.sh entrypoint. One command bootstraps a fresh macOS or Linux (Debian/Ubuntu) machine with shell, editor, terminal, git, and AI agent skill configurations.
- Quick Start
- How It Works
- Cross-Platform Architecture
- Repository Structure
- Stow Packages
- What Gets Installed
- Profiles
- Agent Skills
- Shell Configuration
- Tmux Configuration
- Alacritty Configuration
- VS Code / Cursor Configuration
- Utility Commands
- Custom Shell Functions
- Adding New Configurations
git clone git@github.com:baksha97/dotfiles.git ~/dotfiles
cd ~/dotfiles
./main.sh setup # full setup (auto-detects profile, defaults to "personal")
./main.sh setup work # full setup with "work" profile./main.sh setup [profile] # bootstrap the system (auto-detects OS and profile on re-run)
./main.sh brew backup [profile] # dump current Homebrew state to Brewfile.<profile>
./main.sh alacritty-icon # replace the Alacritty app iconThis repository uses GNU Stow to manage dotfiles. Each directory inside stow/ is a "stow package" whose internal structure mirrors where the files should live relative to $HOME. Stow creates symlinks from your home directory into this repo, so every config file is version-controlled in one place.
The --adopt flag is used during setup, which means if you already have a config file at the target location, Stow moves it into the repo (adopting it) and creates the symlink. After running setup, a git diff will show any differences between your existing configs and the repo versions.
stow/
├── zsh/
│ └── .zshrc → ~/.zshrc
├── tmux/
│ └── .tmux.conf → ~/.tmux.conf
├── git/
│ ├── .gitconfig → ~/.gitconfig
│ └── .gitignore → ~/.gitignore
├── alacritty/
│ ├── .config/alacritty/ → ~/.config/alacritty/
│ └── .config/linearmouse/ → ~/.config/linearmouse/ (macOS mouse config)
└── ...
The setup scripts mirror the .zshrc.d/ pattern: adding a new tool or stow package requires only dropping one file — no edits to existing scripts.
meta/scripts/install.d/shared/— cross-platform tool installers (must work on macOS + Linux)meta/scripts/install.d/linux/— Linux-only tools (apt-specific, Linux paths)meta/scripts/install.d/linux-gui/— GUI apps (headful only)meta/scripts/stow.d/— one stow manifest per package
Both macOS and Linux follow the same setup structure:
source libs → resolve profile → platform packages → install.d loop → setup-common.sh
macOS (setup-macos.sh): Homebrew installs most tools via brew bundle, then shared/ scripts run — most hit their command -v guard and skip since brew already installed the tool.
Linux (setup-linux.sh): apt installs base packages, then shared/ and linux/ scripts are merged and sorted by filename so numbering controls global install order. This ensures dependencies are respected (e.g. linux/70-node runs before shared/80-vercel).
shared/: Scripts must work on both macOS and Linux without platform guards. No[[ "$(uname)" == "Darwin" ]] && return 0, nocommand -v apt-get || return 0. If a script would fail on macOS, it belongs inlinux/.linux/: Linux-only install methods — apt repos, Linux font paths, shell functions without a binary to check.
Inline OS detection for binary download URLs is fine in shared/ (e.g. [[ "$(uname)" == "Darwin" ]] && OS="darwin") — that's selecting the right binary, not guarding execution.
Scripts across both directories share a single number line:
| Range | Purpose | Examples |
|---|---|---|
| 10–40 | Standalone tools (no deps) | lazygit, zoxide, yq, uv |
| 50–60 | Linux-specific standalone tools | nerd-fonts, docker-compose, tailscale |
| 70–72 | Node.js ecosystem setup | node (linux), nvm (linux) |
| 74–76 | Package managers depending on node | pnpm, agent-browser |
| 80–90 | npm-installed CLIs | vercel, gemini-cli |
| 95–99 | Late installers | opencode, claude |
Every setup component is designed for safe re-runs:
| Component | Guard mechanism |
|---|---|
install.d/ scripts |
command -v tool && return 0 + fallback path check for ~/.local/bin installers |
| npm-installed tools | command -v npm || { echo "Skipped..."; return 0; } prerequisite guard |
| Nerd fonts (linux) | Per-font marker files (.installed-FontName-vX.Y.Z) |
| nvm (linux) | [ -s "$HOME/.nvm/nvm.sh" ] && return 0 (shell function, not binary) |
stow --adopt |
Idempotent — re-stowing adopted files is a no-op |
| Git profile | cmp -s guard — skips copy if unchanged |
| Skills symlinks | Recreated each run (rm + ln -s) |
| Version detection | gh_latest_version — always latest, never hardcoded |
dotfiles/
├── main.sh # Single entrypoint for all commands
├── stow/ # GNU Stow packages (symlinked to $HOME)
│ ├── alacritty/ # Alacritty terminal emulator config
│ │ └── .config/
│ │ ├── alacritty/
│ │ │ ├── alacritty.toml
│ │ │ └── themes/ # 130+ color themes
│ │ ├── linearmouse/ # macOS mouse configuration
│ │ └── git/ # Git ignore templates
│ ├── claude/ # Claude Code settings, commands, agents, scripts
│ ├── git/ # Git config and profiles
│ │ ├── .gitconfig
│ │ ├── .gitignore # Global gitignore
│ │ └── profiles/
│ │ ├── personal # Name + email for personal projects
│ │ └── work # Name + email for work projects
│ ├── powerlevel10k/ # Powerlevel10k prompt theme
│ │ └── .p10k.zsh
│ ├── tmux/ # tmux terminal multiplexer config
│ │ └── .tmux.conf
│ ├── vscode/ # VS Code / Cursor editor settings
│ │ ├── settings.json
│ │ └── keybindings.json
│ └── zsh/ # Zsh shell config
│ ├── .zshrc # Sources all .zshrc.d/*.zsh (just the loop)
│ └── .zshrc.d/ # Modular zsh configs (00-first, 50-default, 99-last)
└── meta/ # Support files (not stowed)
├── skills/ # AI coding agent skills (symlinked to tool paths)
├── homebrew/ # Homebrew package management (macOS only)
│ ├── Brewfile.personal
│ └── Brewfile.work
├── packages/ # Linux package lists
│ └── linux.packages # apt packages for Debian/Ubuntu setup
└── scripts/ # Implementation scripts
├── lib/ # Shared utilities (sourced first by platform scripts)
│ ├── arch.sh # ARCH_GO / ARCH_MUSL detection
│ ├── sudo.sh # SUDO prefix detection
│ ├── github.sh # gh_latest_version() helper
│ └── profile.sh # resolve_profile() — auto-detect or accept explicit
├── install.d/ # Per-tool installers (one file = one tool)
│ ├── shared/ # Cross-platform (must work on macOS + Linux)
│ ├── linux/ # Linux-only tools
│ └── linux-gui/ # GUI apps (headful environments only)
├── stow.d/ # Per-package stow manifests (one file = one package)
├── setup-macos.sh # macOS bootstrap (Homebrew + shared loop)
├── setup-linux.sh # Linux bootstrap (apt + merged install loop)
├── setup-common.sh # Shared stow/git/skills setup
├── backup.sh # Brewfile dump
└── alacritty-icon.sh # Icon replacement
All stow packages live under stow/. The table below shows where each package's files end up:
| Package | Contents | Symlink Target |
|---|---|---|
zsh |
.zshrc, .zshrc.d/ |
$HOME |
powerlevel10k |
.p10k.zsh |
$HOME |
tmux |
.tmux.conf |
$HOME |
alacritty |
.config/alacritty/, .config/linearmouse/, .config/git/ |
$HOME |
git |
.gitconfig, .gitignore |
$HOME |
bin |
.local/bin/hcli (homelab CLI wrapper) |
$HOME |
claude |
settings.json, status-line.sh, commands/, agents/, scripts/ |
~/.claude/ |
vscode |
settings.json, keybindings.json |
Platform-specific VS Code User/ directory |
VS Code target paths:
- macOS:
~/Library/Application Support/Code/User - Linux:
~/.config/Code/User
The setup command performs these steps in order:
- Resolve profile — auto-detects from previous setup or uses explicit argument (defaults to
personal) - Source lib utilities — profile, sudo, arch, github helpers
- Show hidden files in Finder (macOS) or Nautilus (Linux)
- Platform-specific package installation:
- macOS: Install Homebrew (if missing), then
brew bundlefrommeta/homebrew/Brewfile.<profile> - Linux: Install apt packages from
meta/packages/linux.packages, change shell to zsh
- macOS: Install Homebrew (if missing), then
- Install.d loop — each script installs one tool with idempotency guards
- macOS: loops
shared/only (brew already installed most tools) - Linux: merges
shared/+linux/sorted by filename for dependency ordering
- macOS: loops
- Install SDKMAN! for JVM SDK management
- Stow all packages — each
stow.d/script backs up and links one package - Set git profile — copies the chosen identity into
~/.gitconfig-profile - Symlink Agent Skills — makes skills discoverable by Copilot, Cursor, and other agents
- Linux SDKMAN packages — installs Gradle and Kotlin (Linux only)
Cross-platform tools (shared/ — run on macOS + Linux):
| Tool | macOS source | Linux source |
|---|---|---|
| lazygit | brew → skip | shared/10 (GitHub binary) |
| zoxide | brew → skip | shared/20 (install script) |
| yq | brew → skip | shared/30 (GitHub binary) |
| uv | brew → skip | shared/40 (install script) |
| pnpm | brew → skip | shared/74 (install script) |
| agent-browser | brew → skip | shared/76 (npm) |
| vercel | brew → skip | shared/80 (npm) |
| gemini-cli | shared/90 (npm) |
shared/90 (npm) |
| claude | shared/99 (install script) |
shared/99 (install script) |
Linux-only tools (linux/):
| Tool | Source |
|---|---|
| gh CLI | linux/10 (apt repo) |
| fzf (latest) | linux/20 (GitHub binary) |
| just | linux/30 (install script) |
| docker | linux/40 (get.docker.com) |
| docker-compose | linux/50 (GitHub plugin) |
| nerd-fonts | linux/50 (GitHub tarballs, latest version) |
| tailscale | linux/60 (install script) |
| node | linux/70 (NodeSource apt) |
| nvm | linux/72 (install script) |
| opencode | linux/95 (install script) |
| Category | Packages |
|---|---|
| CLI tools | fzf, zoxide, tmux, stow, curl, ffmpeg, jq, yq, typos-cli |
| Dev runtimes | node, nvm, pnpm, uv |
| Containers | docker, colima, act (local GitHub Actions) |
| AI tools | agent-browser, vercel-cli |
| Fonts | JetBrains Mono, Fira Code, Mononoki, Roboto Mono, and more (all Nerd Font variants) |
| Apps | Alacritty, VS Code, Google Chrome, Rectangle, Spotify, Multipass |
apt packages (meta/packages/linux.packages):
- Core:
git,git-lfs,curl,zsh,tmux,stow,fzf,jq - Media:
ffmpeg,scrcpy - Tools:
rclone,aria2,ansible,exiftool - Utilities:
fontconfig,unzip,zip,ca-certificates
GUI apps (install.d/linux-gui/ — headful environments only):
- VS Code, VS Code Insiders, Google Chrome (amd64 only), Firefox, VLC, Alacritty, Android Studio
Profiles control git identity and (on macOS) which Homebrew packages are installed. Linux uses the same package lists regardless of profile.
On re-runs, the active profile is auto-detected by comparing .gitconfig-profile against stow/git/profiles/*. Pass a profile name explicitly only to switch profiles. Fresh machines default to personal.
Available profiles:
personal— Full macOS setup with GUI appswork— Work environment setup
./main.sh setup # auto-detects profile (defaults to "personal" on fresh machines)
./main.sh setup work # uses "work" profile (switches if different)Each profile has its own complete Brewfile at meta/homebrew/Brewfile.<profile>. Core CLI tools are in both profiles; only genuinely profile-specific tools differ. During setup, only the matching profile's Brewfile is installed. The backup command dumps the current machine's Homebrew state into the active profile's Brewfile:
./main.sh brew backup # dumps to meta/homebrew/Brewfile.personal
./main.sh brew backup work # dumps to meta/homebrew/Brewfile.work- Create
stow/git/profiles/<name>with[user]name and email fields - macOS only: Create
meta/homebrew/Brewfile.<name>with the desired packages - Run
./main.sh setup <name>
Agent Skills are SKILL.md packages that give AI coding agents specialized domain knowledge. The setup script symlinks the meta/skills/ directory so multiple tools discover them automatically:
| Tool | Discovery Path |
|---|---|
| Claude Code | ~/.claude/skills |
| Copilot CLI | ~/.copilot/skills |
| Cursor IDE | ~/.cursor/skills |
| Common Agent Path | ~/.agents/skills |
| Skill | Purpose |
|---|---|
doc-coauthoring |
Structured workflow for co-authoring documentation |
dotfiles |
Expert guidance for managing this dotfiles repo |
skill-creator |
Create, modify, and evaluate agent skills |
To add a new skill, create a directory under meta/skills/ containing a SKILL.md file. It will be picked up automatically without re-running setup.
The .zshrc is a single loop that sources all files from ~/.zshrc.d/ — the same file-loop composition pattern used by the setup scripts. Files use numbered prefixes to control load order: 00- runs first (p10k, PATH), 50- is the default tier, and 99- runs last (SDKMAN, zoxide). Zinit is the plugin manager.
| Plugin | Purpose |
|---|---|
| Powerlevel10k | Fast, customizable prompt theme |
| zsh-syntax-highlighting | Fish-like syntax highlighting |
| zsh-completions | Additional completion definitions |
| zsh-autosuggestions | Fish-like autosuggestions from history |
| fzf-tab | Replace default completion with fzf |
OMZ git snippet |
Git aliases and completions |
OMZ command-not-found snippet |
Suggests packages for unknown commands |
- fzf — fuzzy finder for files, history, and completions
- zoxide — smarter
cdthat learns your most-used directories (aliased tocd, loaded via99-zoxide.zsh) - SDKMAN! — JVM SDK version management (loaded via
99-sdkman.zsh)
| Binding | Action |
|---|---|
Ctrl+P |
Search history backward |
Ctrl+N |
Search history forward |
Alt+W |
Kill region |
| Alias | Command |
|---|---|
ls |
ls --color |
vim |
nvim |
c |
clear |
Prefix is rebound to Ctrl+A. Vim-style navigation and copy mode are enabled.
| Binding | Action |
|---|---|
Prefix + | |
Split pane horizontally |
Prefix + - |
Split pane vertically |
Prefix + r |
Reload tmux config |
Prefix + h/j/k/l |
Resize panes |
Prefix + m |
Toggle pane zoom |
v (copy mode) |
Begin selection |
y (copy mode) |
Copy selection |
| Plugin | Purpose |
|---|---|
| vim-tmux-navigator | Seamless navigation between tmux panes and Vim splits |
| tmux-resurrect | Persist sessions across restarts |
| tmux-continuum | Auto-save sessions every 15 minutes |
| tmux-tokyo-night | Tokyo Night color theme |
Alacritty is configured with JetBrains Mono Nerd Font at size 16, block cursor, generous padding (25x20), and 256-color support. A library of 130+ color themes is included under stow/alacritty/.config/alacritty/themes/ — uncomment an import line in alacritty.toml to switch themes.
URL hints are enabled with Ctrl+Shift+U for clickable links.
The alacritty stow package also includes:
- LinearMouse config (
.config/linearmouse/linearmouse.json) — macOS mouse customization - Git ignore templates (
.config/git/ignore) — global git ignore patterns
- Font: JetBrains Mono Nerd Font (editor and terminal)
- Minimap: Disabled
- Tab size: 2 spaces
- Copilot: Auto-completions and next-edit suggestions enabled
- Formatters: Prettier for JSON/YAML, yamlfmt for Docker Compose and GitHub Actions
| Binding | Context | Action |
|---|---|---|
Shift+Enter / Ctrl+Enter |
Terminal | Send line continuation |
Ctrl+Shift+A |
Terminal | Focus next terminal |
Ctrl+Shift+B |
Terminal | Focus previous terminal |
Ctrl+Shift+J |
Global | Toggle bottom panel |
Ctrl+Shift+N |
Terminal | New terminal |
Ctrl+Shift+W |
Terminal | Kill terminal |
Ctrl+E |
Global | Toggle sidebar / focus file explorer |
N |
File explorer | New file |
Shift+N |
File explorer | New folder |
R |
File explorer | Rename file |
D |
File explorer | Delete file |
Dumps the current Homebrew state (formulae, casks, taps, VS Code extensions) into the active profile's Brewfile:
./main.sh brew backup # dumps to meta/homebrew/Brewfile.personal
./main.sh brew backup work # dumps to meta/homebrew/Brewfile.workReplaces the default Alacritty icon with a custom one from macOSicons. Backs up the original icon before replacing.
./main.sh alacritty-iconLink a project's agent skills into the project-local discovery paths (.claude/skills/, .agents/skills/) without modifying dotfiles. Run from the repo root — the source directory is auto-discovered from common conventions:
| Priority | Path | Convention |
|---|---|---|
| 1 | .ai-agent/skills/ |
Generic agent skills |
| 2 | .claude/skills/ |
Claude Code |
| 3 | .agents/skills/ |
Generic agents |
| 4 | .github/skills/ |
GitHub ecosystem |
| 5 | .copilot/skills/ |
GitHub Copilot |
# From a project repo root — auto-discovers the skills directory
link-skills
# Explicit path
link-skills path/to/skills
# Remove only this project's skill links (global dotfiles skills untouched)
unlink-skillslink-skills creates relative per-skill symlinks within the project. unlink-skills uses readlink to only remove links it created.
Creates a git worktree for a branch. If the branch exists on origin, it tracks the remote. Otherwise, it creates a new branch from origin/main and pushes it.
gct feature/my-branch
# Creates ../repo-name-feature-my-branch/ and cd's into itRemoves a git worktree and returns to the main repo root. With no arguments, removes the worktree you're currently in.
grmt # remove current worktree
grmt /path/to/worktree # remove specific worktreeCreate a single file — no existing scripts need editing:
# Cross-platform (must work on macOS + Linux without platform guards):
meta/scripts/install.d/shared/<NN>-toolname.sh
# Linux-only (apt-specific, Linux paths, etc.):
meta/scripts/install.d/linux/<NN>-toolname.shTemplate for shared/:
#!/bin/bash
# toolname — short description
command -v toolname &>/dev/null && return 0
[ -f "$HOME/.local/bin/toolname" ] && return 0 # fallback for ~/.local/bin installers
echo "Installing toolname..."
curl -fsSL https://toolname.dev/install.sh | bashTemplate for npm tools in shared/ (number ≥74):
#!/bin/bash
# toolname — short description
command -v toolname &>/dev/null && return 0
command -v npm &>/dev/null || { echo " Skipped toolname (npm not found)"; return 0; }
echo "Installing toolname..."
# Avoid unnecessary sudo when npm global prefix is user-writable (e.g. Homebrew node)
NPM_PREFIX="$(npm prefix -g 2>/dev/null)"
if [[ -w "$NPM_PREFIX" ]]; then
npm install -g toolname
else
$SUDO npm install -g toolname
fiUse $SUDO, $ARCH_GO, $ARCH_MUSL (set by lib/) and gh_latest_version OWNER REPO (from lib/github.sh) as needed. Never hardcode versions.
- Create
stow/<tool>/mirroring$HOMEstructure (e.g.,stow/neovim/.config/nvim/init.lua) - Create
meta/scripts/stow.d/<NN>-tool.sh:
#!/bin/bash
# tool — short description
stow_backup "$HOME/.toolrc" # backs up real files, removes symlinks
stow_package tool # stows against $HOME by defaultFor non-$HOME targets (like claude or vscode), set STOW_TARGET before calling stow_package. For --no-folding packages, pass the flag directly: stow_package tool --no-folding.
- Run
./main.sh setupor manually run the stow command.