Initialize dotfiles scaffold

This commit is contained in:
2026-03-08 16:03:47 +08:00
commit 87ac9027ed
20 changed files with 597 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
.DS_Store
*.swp
*.swo
*.tmp
# Machine-local shell state
zsh/90-machine.local.zsh
# Secrets should stay outside git
config/secrets/
# Optional sanitized backups
config/clash/*.bak
+45
View File
@@ -0,0 +1,45 @@
# dotfiles
Minimal, auditable dotfiles repo for shell, proxy wrappers, and local
configuration shims.
## Goals
- Keep most Codex edits inside a regular git repo.
- Reduce repeated permission prompts by routing common changes through
repo-owned scripts.
- Keep secrets and machine-specific values outside version control.
## Layout
- `install/`: one-time setup helpers.
- `zsh/`: shell fragments linked into `~/.zshrc.d/`.
- `bin/`: thin wrappers that can safely override binaries via `~/bin`.
- `scripts/`: narrow operational entrypoints suited for permission rules.
- `config/`: tracked config templates and migration targets.
## Quick Start
```bash
cd ~/work/codes/tools/macos/dotfiles
./install/doctor.sh
./install/link.sh
```
`install/link.sh` only creates symlinks and adds a managed bootstrap block to
`~/.zshrc` if needed. It does not delete existing configuration.
## Secrets
Do not commit secrets here.
- Put machine-local overrides in `zsh/90-machine.local.zsh` on disk.
- Keep API tokens in `~/.config/secrets/` or the macOS Keychain.
- Treat live Clash provider files as untracked machine state unless sanitized.
## Suggested Approval Model
- Long-lived approvals: repo-owned scripts in `scripts/` and `install/`.
- Per-run approvals: modifying `~/.ssh`, `~/.kube`, `~/.docker`, Keychain,
launch agents, or system defaults.
- Avoid broad approvals for `bash`, `zsh`, `python3`, or destructive commands.
+273
View File
@@ -0,0 +1,273 @@
# Recovery Checklist
这份文档的目标是让这台 macOS 机器在系统崩溃、整机重装或新机器迁移后,
尽可能快地恢复到可工作的开发状态。
## 当前覆盖范围
已经纳入 `dotfiles` 仓库管理的内容:
- `~/.zshrc.d/*.zsh` 的受管片段
- `~/bin` 下的受管 wrapper
- `codex` 默认走 Clash 的代理包装
- 一部分 shell / Clash / kubectl 的窄脚本入口
还没有完全版本化,仍需单独备份的内容:
- `~/.ssh/`
- `~/.gnupg/`
- `~/.config/secrets/`
- `~/.kube/config`
- `~/.docker/config.json`
- macOS Keychain 中的 token、证书、登录态
- Clash 订阅凭据和未脱敏的 provider 配置
- `~/.zshrc` 里尚未迁移进 `dotfiles` 的旧配置
- 浏览器数据、VS Code 扩展与个人设置
- OrbStack / Docker / 本地模型 / 数据目录
结论:
当前这套 `dotfiles` 能恢复“基础 shell 环境、代理入口、常用脚本入口”,
但还不能替代整机备份。
## 事前准备
每次大改环境前,确认以下内容有备份:
```bash
tar czf ~/Desktop/recovery-secrets-$(date +%Y%m%d).tgz \
~/.ssh \
~/.gnupg \
~/.config/secrets \
~/.kube/config \
~/.docker/config.json
```
另外导出或确认这些项目可恢复:
- Gitea 可访问:`http://192.168.1.83:33000/gao/dotfiles.git`
- 你常用的私有仓库地址和访问方式
- Clash 订阅来源或脱敏后的基础配置
- Keychain 里关键 token 是否有外部保存方案
## 恢复顺序
### 1. 系统初始化
完成 macOS 安装后,先做最小系统准备:
```bash
xcode-select --install
```
然后安装 Homebrew。
安装完成后,至少保证这些基础命令可用:
- `git`
- `curl`
- `jq`
- `zsh`
### 2. 拉取 dotfiles
```bash
mkdir -p ~/work/codes/tools/macos
git clone http://192.168.1.83:33000/gao/dotfiles.git ~/work/codes/tools/macos/dotfiles
cd ~/work/codes/tools/macos/dotfiles
```
如果当时局域网 Gitea 不可达,使用离线备份或其他远端镜像。
### 3. 接入 shell 与 wrapper
```bash
./install/doctor.sh || true
./install/link.sh
source ~/.zshrc
```
执行后应得到这些结果:
- `~/.zshrc` 含有 `dotfiles bootstrap` block
- `~/.zshrc.d/*.zsh` 链接到仓库内的 `zsh/`
- `~/bin/codex` 和 `~/bin/codex-direct` 链接到仓库内的 `bin/`
检查:
```bash
command -v codex
command -v codex-direct
bash ./install/doctor.sh
```
### 4. 恢复机密与本机状态
把这些内容恢复回 home 目录:
```bash
mkdir -p ~/.config/secrets
```
需要恢复或重新导入:
- `~/.ssh/`
- `~/.gnupg/`
- `~/.config/secrets/`
- `~/.kube/config`
- `~/.docker/config.json`
- Keychain 中的登录项、API token、证书
如果你要继续使用本机特有变量,复制本地覆盖文件:
```bash
cp zsh/90-machine.local.example.zsh zsh/90-machine.local.zsh
```
然后把下面这类值写入 `zsh/90-machine.local.zsh` 或 `~/.config/secrets/*.zsh`:
- `JAVA_HOME`
- `ANTHROPIC_BASE_URL`
- `ANTHROPIC_AUTH_TOKEN`
- 其他不应该进入 git 的 token 和内网地址
### 5. 恢复语言与开发运行时
这台机器当前依赖的运行时包括:
- Homebrew
- Oh My Zsh
- Miniforge / conda
- `nvm`
- `bun`
- `pnpm`
- `jenv`
- OpenJDK 21
- Flutter
建议顺序:
1. 安装 Oh My Zsh
2. 安装 Miniforge
3. 安装 `nvm` 并恢复 Node
4. 安装 `bun`
5. 安装 `pnpm`
6. 安装 `jenv` 和 OpenJDK 21
7. 恢复 Flutter 路径对应目录
恢复完成后检查:
```bash
zsh --version
node -v
pnpm -v
bun -v
java -version
python3 --version
```
### 6. 恢复 Clash 与 Codex 代理
当前 Codex 默认通过 Clash 的本地混合端口 `7890` 工作。
确保 ClashX 或等价客户端满足:
- `模式 = 规则`
- `混合代理端口 = 7890`
- `外部控制器 = 127.0.0.1:9090`
- OpenAI / ChatGPT 相关域名走同一个稳定节点
如果已经把 Clash 配置迁入 `dotfiles/config/clash/`,恢复后可直接重载:
```bash
./scripts/clash/reload.sh ~/.config/clash/clashx.yaml
```
检查代理与路由:
```bash
codex-proxy-status
./scripts/clash/check-openai-route.sh
```
### 7. 恢复容器与平台工具
按需恢复这些工具:
- OrbStack
- Docker CLI / buildx
- `kubectl`
- `gh`
- `ollama`
- `displayplacer`
检查:
```bash
docker ps
kubectl version --client
gh --version
ollama --version
```
### 8. 恢复工作目录
拉取你常用的工作仓库,至少包括:
- `~/work/codes/tools/macos`
- 业务项目仓库
- 私有基础设施仓库
如果 shell alias、脚本或路径依赖这些目录,必须恢复到相同路径。
## 验收清单
恢复完成后,至少跑一遍:
```bash
source ~/.zshrc
command -v codex
command -v kubectl
command -v java
command -v pnpm
bash ~/work/codes/tools/macos/dotfiles/install/doctor.sh
bash ~/work/codes/tools/macos/dotfiles/scripts/shell/lint-zsh.sh
```
再确认这些真实场景:
- `codex` 启动正常
- `codex` 不再直连外网,而是通过 Clash
- `codex-direct` 可用于临时绕过代理
- `kubectl` alias 仍然指向预期 wrapper
- Java / Node / Python / Bun 路径正确
## 建议的后续完善
要把恢复能力继续提高,下一步建议做这些事:
1. 把 `~/.zshrc` 里仍然存在的旧配置分批迁入 `dotfiles/zsh/*.zsh`
2. 把敏感 token 全部迁出 `~/.zshrc`,改放 `~/.config/secrets/` 或 Keychain
3. 补一个 `Brewfile`
4. 把 Clash 的可公开部分配置迁入 `dotfiles/config/clash/`
5. 给 `README.md` 增加 “新机器初始化” 和 “灾难恢复” 的入口链接
## 最小恢复命令块
如果你只想看最短路径,按下面跑:
```bash
xcode-select --install
# 安装 Homebrew、git、curl、jq、zsh
mkdir -p ~/work/codes/tools/macos
git clone http://192.168.1.83:33000/gao/dotfiles.git ~/work/codes/tools/macos/dotfiles
cd ~/work/codes/tools/macos/dotfiles
./install/link.sh
source ~/.zshrc
# 恢复 secrets、ssh、kube、docker、keychain
# 安装 nvm / bun / pnpm / miniforge / jenv / openjdk / clashx / orbstack
./install/doctor.sh
./scripts/clash/check-openai-route.sh
```
Executable
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
resolve_real_codex() {
local dir
IFS=':' read -r -a path_parts <<<"${PATH}"
for dir in "${path_parts[@]}"; do
[[ "${dir}" == "${script_dir}" ]] && continue
if [[ -x "${dir}/codex" ]]; then
printf '%s\n' "${dir}/codex"
return 0
fi
done
return 1
}
codex_bin="$(resolve_real_codex)"
export HTTP_PROXY="${CLASH_HTTP_PROXY:-http://127.0.0.1:7890}"
export HTTPS_PROXY="${CLASH_HTTPS_PROXY:-http://127.0.0.1:7890}"
export ALL_PROXY="${CLASH_ALL_PROXY:-socks5://127.0.0.1:7890}"
export http_proxy="${HTTP_PROXY}"
export https_proxy="${HTTPS_PROXY}"
export all_proxy="${ALL_PROXY}"
export NO_PROXY="${CLASH_NO_PROXY:-localhost,127.0.0.1,::1}"
export no_proxy="${NO_PROXY}"
exec "${codex_bin}" "$@"
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
resolve_real_codex() {
local dir
IFS=':' read -r -a path_parts <<<"${PATH}"
for dir in "${path_parts[@]}"; do
[[ "${dir}" == "${script_dir}" ]] && continue
if [[ -x "${dir}/codex" ]]; then
printf '%s\n' "${dir}/codex"
return 0
fi
done
return 1
}
codex_bin="$(resolve_real_codex)"
exec env -u HTTP_PROXY -u HTTPS_PROXY -u ALL_PROXY -u NO_PROXY \
-u http_proxy -u https_proxy -u all_proxy -u no_proxy \
"${codex_bin}" "$@"
+13
View File
@@ -0,0 +1,13 @@
# Clash Config Migration Notes
Keep sanitized, tracked Clash configs here and link them into
`~/.config/clash/` when ready.
Suggested migration flow:
1. Copy the live config into this directory.
2. Remove inline credentials or move them into provider-managed files.
3. Use `scripts/clash/reload.sh` after updating the live linked file.
Do not commit provider passwords, tokens, or subscription URLs unless this
repository is intentionally private and access-controlled.
+5
View File
@@ -0,0 +1,5 @@
[init]
defaultBranch = main
[pull]
ff = only
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
status=0
check_path() {
local path="$1"
if [[ -e "${path}" ]]; then
printf '[ok] %s\n' "${path}"
else
printf '[missing] %s\n' "${path}"
status=1
fi
}
echo "Checking dotfiles prerequisites..."
check_path "${repo_root}/zsh"
check_path "${repo_root}/bin"
check_path "${repo_root}/scripts"
check_path "${HOME}/.zshrc"
check_path "${HOME}/.zshrc.d"
check_path "${HOME}/bin"
if grep -Fq "# dotfiles bootstrap start" "${HOME}/.zshrc" 2>/dev/null; then
echo "[ok] ~/.zshrc contains managed bootstrap block"
else
echo "[warn] ~/.zshrc does not yet contain the managed bootstrap block"
fi
if command -v codex >/dev/null 2>&1; then
printf '[ok] codex resolves to %s\n' "$(command -v codex)"
else
echo "[warn] codex not found on PATH"
fi
exit "${status}"
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
bootstrap_start="# dotfiles bootstrap start"
bootstrap_end="# dotfiles bootstrap end"
zshrc="${HOME}/.zshrc"
"${repo_root}/scripts/shell/sync-zsh-links.sh"
if [[ ! -f "${zshrc}" ]]; then
touch "${zshrc}"
fi
if ! grep -Fq "${bootstrap_start}" "${zshrc}"; then
cat >>"${zshrc}" <<'EOF'
# dotfiles bootstrap start
for file in "$HOME"/.zshrc.d/*.zsh; do
[ -r "$file" ] && source "$file"
done
# dotfiles bootstrap end
EOF
echo "Added managed dotfiles bootstrap block to ${zshrc}"
else
echo "Managed dotfiles bootstrap block already present in ${zshrc}"
fi
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -euo pipefail
controller="${CLASH_CONTROLLER:-http://127.0.0.1:9090}"
if ! command -v jq >/dev/null 2>&1; then
echo "jq is required" >&2
exit 1
fi
echo "Current Proxy selection:"
curl -fsS "${controller}/proxies" | jq '.proxies.Proxy | {now, type}'
echo
echo "OpenAI-related active connections:"
curl -fsS "${controller}/connections" | jq '
.connections[]
| select(.metadata.host | test("openai|chatgpt|oaistatic|oaiusercontent"; "i"))
| {host: .metadata.host, chains: .chains, rule: .rule, payload: .rulePayload}
'
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail
controller="${CLASH_CONTROLLER:-http://127.0.0.1:9090}"
config_path="${1:-${HOME}/.config/clash/clashx.yaml}"
curl -fsS \
-X PUT \
-H 'Content-Type: application/json' \
-d "{\"path\":\"${config_path}\"}" \
"${controller}/configs"
echo
echo "Reloaded Clash config: ${config_path}"
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
exec kubectl get "$@"
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
exec kubectl logs "$@"
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
for file in "${repo_root}"/zsh/*.zsh; do
zsh -n "${file}"
echo "ok ${file}"
done
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
mkdir -p "${HOME}/.zshrc.d" "${HOME}/bin"
for file in "${repo_root}"/zsh/*.zsh; do
ln -sfn "${file}" "${HOME}/.zshrc.d/$(basename "${file}")"
done
for file in "${repo_root}"/bin/*; do
ln -sfn "${file}" "${HOME}/bin/$(basename "${file}")"
done
echo "Synced zsh and bin links from ${repo_root}"
+15
View File
@@ -0,0 +1,15 @@
export DOTFILES_ROOT="${HOME}/work/codes/tools/macos/dotfiles"
path_prepend() {
case ":${PATH}:" in
*":$1:"*) ;;
*) PATH="$1:${PATH}" ;;
esac
}
path_append() {
case ":${PATH}:" in
*":$1:"*) ;;
*) PATH="${PATH}:$1" ;;
esac
}
+5
View File
@@ -0,0 +1,5 @@
path_prepend "${HOME}/bin"
path_prepend "/opt/homebrew/bin"
path_prepend "/opt/homebrew/sbin"
export PATH
+40
View File
@@ -0,0 +1,40 @@
export CLASH_HTTP_PROXY="${CLASH_HTTP_PROXY:-http://127.0.0.1:7890}"
export CLASH_HTTPS_PROXY="${CLASH_HTTPS_PROXY:-http://127.0.0.1:7890}"
export CLASH_ALL_PROXY="${CLASH_ALL_PROXY:-socks5://127.0.0.1:7890}"
export CLASH_NO_PROXY="${CLASH_NO_PROXY:-localhost,127.0.0.1,::1}"
codex() {
HTTP_PROXY="${CLASH_HTTP_PROXY}" \
HTTPS_PROXY="${CLASH_HTTPS_PROXY}" \
ALL_PROXY="${CLASH_ALL_PROXY}" \
http_proxy="${CLASH_HTTP_PROXY}" \
https_proxy="${CLASH_HTTPS_PROXY}" \
all_proxy="${CLASH_ALL_PROXY}" \
NO_PROXY="${CLASH_NO_PROXY}" \
no_proxy="${CLASH_NO_PROXY}" \
command codex "$@"
}
codex-direct() {
local codex_bin
codex_bin="$(whence -p codex)"
env -u HTTP_PROXY -u HTTPS_PROXY -u ALL_PROXY -u NO_PROXY \
-u http_proxy -u https_proxy -u all_proxy -u no_proxy \
"${codex_bin}" "$@"
}
clash-on() {
export HTTP_PROXY="${CLASH_HTTP_PROXY}"
export HTTPS_PROXY="${CLASH_HTTPS_PROXY}"
export ALL_PROXY="${CLASH_ALL_PROXY}"
export http_proxy="${CLASH_HTTP_PROXY}"
export https_proxy="${CLASH_HTTPS_PROXY}"
export all_proxy="${CLASH_ALL_PROXY}"
export NO_PROXY="${CLASH_NO_PROXY}"
export no_proxy="${CLASH_NO_PROXY}"
}
clash-off() {
unset HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY
unset http_proxy https_proxy all_proxy no_proxy
}
+1
View File
@@ -0,0 +1 @@
alias codex-proxy-status='printf "HTTP_PROXY=%s\nHTTPS_PROXY=%s\nALL_PROXY=%s\n" "${HTTP_PROXY:-}" "${HTTPS_PROXY:-}" "${ALL_PROXY:-}"'
+6
View File
@@ -0,0 +1,6 @@
# Copy this file to zsh/90-machine.local.zsh for machine-specific overrides.
#
# Examples:
# export JAVA_HOME="/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home"
# export ANTHROPIC_BASE_URL="https://example.internal/api"
# export CLASH_HTTP_PROXY="http://127.0.0.1:7890"