macOS 个人开发环境配置。目录结构镜像 $HOME,可直接软链部署。
核心是一套 zsh 作登录 shell、fish 作交互 shell 的双 shell 架构 —— 这不是历史遗留,而是为了让 AI 编码工具(Claude Code / Codex)能正确识别并调用 shell,同时保留 fish 的交互体验。下面「设计说明」一节解释了取舍。
| 路径 | 用途 |
|---|---|
.zshenv |
zsh 环境变量:cargo、uv/~/.local/bin、conda/mamba 初始化 |
.zprofile |
Homebrew 初始化,并在 path_helper 重排 PATH 后重新确立 conda 优先级 |
.config/fish/config.fish |
conda shell 集成 + mamba 激活 base |
.config/fish/conf.d/ |
按功能拆分的自动加载片段:homebrew(含清华 TUNA 镜像)、cargo、uv.env、aliases、fish_frozen_theme(Dracula 配色) |
.config/fish/functions/ |
fish_prompt:带 git 分支与脏状态标记的提示符 |
.config/fish/completions/ |
codex、copilot 的 CLI 补全(自动生成) |
.config/ghostty/config |
终端:Maple Mono NF CN 字体、Catppuccin Mocha 主题、macos-option-as-alt、剪贴板保护、命令完成通知 |
.config/tmux/tmux.conf |
显式指定 default-shell 为 fish |
.config/karabiner/karabiner.json |
Caps Lock ↔ Left Control 互换;VSCode 内 Ctrl+P/N/F/B → 方向键 |
.config/gitpic/config.toml |
gitpic 图床:上传路径模板与 CDN 链接 |
.claude/statusline.sh |
Claude Code 状态栏:模型名、effort、context 占用条、花费、目录、git 分支 |
.claude/settings.json |
Claude Code 用户级配置:模型路由、effort、statusline。作示例参考,里面的 ANTHROPIC_BASE_URL 指向本机代理,换环境必须改 |
| 安装 | |
|---|---|
| Homebrew | brew.sh |
| fish | brew install fish |
| Ghostty | brew install --cask ghostty |
| Karabiner-Elements | brew install --cask karabiner-elements |
| tmux | brew install tmux |
| lsd, bat | brew install lsd bat(aliases.fish 会自动检测,未装则不生效) |
| Maple Mono NF CN | maple-font |
| miniforge3 | 装在 ~/miniforge3,提供 conda + mamba |
| Rust / uv | rustup 装到 ~/.cargo;uv 装到 ~/.local/bin |
配置里对 conda、cargo、uv、lsd、bat 都做了存在性检查,缺哪个只是对应功能不生效,不会报错。
git clone https://github.com/tarnish233/dotfiles.git ~/dotfiles
cd ~/dotfiles先备份已有配置(下面的 ln 会覆盖同名文件,且若目标是真实目录会产生嵌套链接):
for p in ~/.zshenv ~/.zprofile ~/.config/fish ~/.config/ghostty \
~/.config/tmux ~/.config/karabiner ~/.config/gitpic; do
[ -e "$p" ] && mv "$p" "$p.bak"
done再建立软链:
ln -sf "$PWD/.zshenv" ~/.zshenv
ln -sf "$PWD/.zprofile" ~/.zprofile
mkdir -p ~/.config
for d in "$PWD"/.config/*/; do
ln -sfn "${d%/}" ~/.config/
doneClaude Code 的两个文件单独链(按文件链,不要整目录链 —— ~/.claude 还存着会话历史、projects/、plugins/ 等状态,整目录替换会丢):
mkdir -p ~/.claude
for f in settings.json statusline.sh; do
[ -e ~/.claude/$f ] && mv ~/.claude/$f ~/.claude/$f.bak
ln -sf "$PWD/.claude/$f" ~/.claude/$f
donesettings.json 是示例,落地后按自己的环境改 —— 至少 ANTHROPIC_BASE_URL 和 ANTHROPIC_AUTH_TOKEN 是本机专属的。
最后确认登录 shell 是 zsh(交互 shell 由 Ghostty 的 command 指定为 fish,无需 chsh 到 fish):
chsh -s /bin/zsh # 原因见「设计说明」第 1 条Karabiner 注意:Karabiner-Elements 在 GUI 里改设置时会重写
karabiner.json,某些版本会用真实文件替换软链。如果发现改动不再同步到仓库,检查ls -l ~/.config/karabiner/karabiner.json是否还是软链。
这几条都是实测后的取舍,注释也写在对应文件里:
-
登录 shell 是
/bin/zsh,交互 shell 是 fish。 Claude Code 之类的工具通过登录 shell 判断自己实际使用的 POSIX shell,指向 fish 会出问题。所以登录 shell 留 zsh,由 Ghostty 的command = /opt/homebrew/bin/fish启动交互环境。tmux 的default-shell默认跟随$SHELL,而 macOS 上 Ghostty 经/usr/bin/login启动会话时,login会用passwd里的登录 shell 强制覆盖$SHELL—— 所以tmux.conf必须显式写死 fish,否则开出的是 zsh 窗格。 -
PATH 写在
.zshenv而不是.zshrc。 非交互的zsh -c ...只读.zshenv—— 而这正是 Codex 和 Claude Code 调用 shell 的方式。写在.zshrc里,这些工具会恰好在 agent 需要它们时找不到uv、ego-browser等命令。 -
用 mamba 的原生 hook,不用
conda init zsh。miniforge3/bin/conda是 Python 脚本,其shell.zshhook 约 320ms(zsh 启动总计约 840ms);arm64 的 mamba 二进制约 24ms。且conda init会写入.zshrc,非交互 shell 永远读不到(见第 2 条)。 -
.zprofile需要重新确立 conda 优先级。brew shellenv会调用/usr/libexec/path_helper重建 PATH,把~/miniforge3/bin从第 1 位降到第 14 位、落到/usr/bin之后,导致python3解析成系统自带的 3.9.6。conda 在.zshenv(先于.zprofile)设置,所以必须在重排之后补回来。 -
conda 初始化用
CONDA_PREFIX守卫。 保证每个进程树最多执行一次:从 Ghostty + fish 启动的会话已继承激活好的 base,嵌套 shell 逐层继承,只有无 conda 祖先的 zsh 才付那约 85ms。 -
macos-option-as-alt = true。 让 Option 作为 Alt/Meta 发送,否则 TUI 程序收不到 Option 系快捷键(如 Claude Code 的 Option+P 切模型、Option+O fast mode、Option+T thinking)。代价是 macOS 的 Option+字母 特殊字符输入(é ü © °)失效;改成left可只让左 Option 作 Alt、右 Option 保留特殊字符。
账号名、内网地址、私有主机等不适合入库的内容,写进 ~/.config/fish/conf.d/local.fish —— 该路径已在 .gitignore 中,fish 会照常自动加载。