After switching to Mac, my dev environment went through several iterations before settling into its current form. Writing this down as a personal reference and a guide for anyone setting up their own environment.
Shell: Oh My Zsh + Dracula
The terminal is a developer’s home base — get the shell comfortable first.
# Install Oh My Zshsh "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
# Dracula themecurl -L https://raw.githubusercontent.com/dracula/zsh/master/dracula.zsh-theme -o ~/.oh-my-zsh/themes/dracula.zsh-themeKey ~/.zshrc config:
ZSH_THEME="dracula"plugins=(git zsh-autosuggestions zsh-completions)
# Syntax highlighting (manual install required)source ~/.zsh/zsh-syntax-highlighting/zsh-syntax-highlighting.zshThree plugins, each with a job:
- zsh-autosuggestions — Auto-complete from history, gray suggestions, press → to accept
- zsh-completions — Additional completion rules
- zsh-syntax-highlighting — Commands turn green (valid) or red (invalid) as you type
I use iTerm2 as the terminal emulator, with Shell Integration for directory jumping and command history navigation.
Package Management: Homebrew First
On macOS, Homebrew is virtually the only choice. Most tools in one command:
brew install ripgrep lsd ffmpeg gh hyperfine himalaya cloudflaredI added an alias for easy daily updates:
alias bup="brew update && brew upgrade && brew cleanup"My own tools are distributed through a Homebrew tap too:
brew install EasyXdc/tap/ports-rsPlenty of third-party taps as well — steipete/tap for gifgrep, imsg, peekaboo, and other small utilities.
Language Runtimes: Each Its Own Version Manager
No mixing — every language has its own management tool.
Node.js — Volta
Volta handles version management + global package management. Faster than nvm, instant version switching:
curl https://get.volta.sh | bashvolta install node@25volta install pnpmGlobal packages managed by Volta as well, currently installed:
claude-code— Anthropic AI coding assistantcodex— OpenAI Codex CLIccline— Code completion toolopenclaw/openclaw-qqbot— QQ botspnpm— Frontend package managermysql-mcp-server— MCP database service
Java — SDKMAN
curl -s "https://get.sdkman.io" | bashsdk install java 21.0.10-amznsdk install java 17.0.18-amznsdk default java 21.0.10-amznSwitch Java versions per project. sdk use java 17... for temporary, sdk default for default.
Rust — rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shStable toolchain, primary language for CLI tools. Cargo also manages dev tools like rust-analyzer and clippy.
Python — Homebrew + Mamba
System Python stays untouched. Homebrew installs Python 3.12. Conda/Mamba uses lazy loading to avoid slowing down shell startup:
_mamba_lazy_init() { unset -f mamba conda _mamba_lazy_init eval "$(/opt/homebrew/bin/mamba shell hook --shell zsh 2>/dev/null)" eval "$(/opt/homebrew/bin/conda shell.zsh hook 2>/dev/null)"}
mamba() { _mamba_lazy_init; mamba "$@"; }conda() { _mamba_lazy_init; conda "$@"; }Initialization only happens on first call — no drag on normal shell startup.
Go — Homebrew
brew install goCurrently 1.26, mainly used for small tools and backend services.
Git Config
[user] name = EasyXdc email = [email protected][credential] helper = !gh auth git-credentialUsing gh auth for Git credential management — no separate SSH key or token needed for GitHub push/pull over HTTPS. SSH still uses keys, but HTTPS operations go through gh credential for a smoother experience.
SSH config stores common server aliases — ssh Company for direct connection, plus RemoteForward to proxy local ports to remote servers.
Networking: Clash + Cloudflare WARP
Proxy via Clash, config files at ~/.config/clash/, rules and subscriptions self-managed.
Cloudflare WARP as backup — more convenient in some scenarios (e.g. accessing Cloudflare-protected sites).
The two complement each other: Clash handles everyday proxy needs, WARP handles specific network issues.
Editor: VS Code + Cursor
Primary editor is VS Code with ~80 extensions, mainly in these categories:
- AI Assistants: Claude Code, CodeGeeX
- Database: Database Client JDBC
- Development: ESLint, Git History, LeetCode Helper
- Theme/Icons: One Dark Italic, Great Icons
- Utilities: CodeSnap (screenshots), Comment Translate, JSON Tools, Excel Viewer
Cursor as an AI-first editor for occasional use, suited for heavy AI-assisted workflows.
Docker + K8s
Docker Desktop provides the container runtime, kubectl manages clusters. Docker CLI completion added at the end of .zshrc.
System Utilities
A few tools that make macOS more pleasant:
- CleanMyMac — Cleanup and system maintenance
- iStat Menus — Real-time system stats in the menu bar (CPU, memory, network, temperature)
- Parallels Desktop — Windows/Linux VMs, occasional testing use
File Structure Habits
~/├── .config/ # Tool configs (clash, gh, neofetch, uv...)├── .oh-my-zsh/ # Shell config and plugins├── .volta/ # Node versions and global packages├── .sdkman/ # Java version management├── .rustup/ # Rust toolchain├── .ssh/config # SSH aliases and port forwarding├── Desktop/│ └── PersonalDocument/│ └── MyCode/ # Code projects live here└── Downloads/ # Temporary filesConfig files mostly live under ~/.config/, following XDG conventions. A few legacy tools (like Oh My Zsh) still use ~/.
Core Design Principles
Looking back, the guiding principles of this setup are:
- Each language manages its own versions — Volta, SDKMAN, rustup each do their own thing, no interference
- Lazy loading over eager loading — Mamba lazy init, SDKMAN conditional loading, shell stays fast
- Homebrew unifies CLI tooling — One
brew installdoes it, upgrades are easy - Centralized config management —
~/.zshrcis the entry point, other configs at their standard paths - Layered networking — Clash for proxy, WARP for specific networks, no mixing
There’s no “best setup” — only “the setup that fits you best.” Writing this down mainly so I don’t forget when setting up a new machine, and hopefully it helps someone else too.