- Install brew from https://brew.sh/
- Install the applications from brew using the brewfiles:
brew bundle --file=brewfiles/common.Brewfile
brew bundle --file=brewfiles/macos.Brewfile
brew bundle --file=brewfiles/ai.BrewfileOr use the helper script to auto-select by OS:
./bootstrap.sh./bootstrap.sh also installs qmd globally via npm (npm install -g @tobilu/qmd) after Brewfile packages are installed.
-
macOS (
Darwin) ->brewfiles/macos.Brewfile -
Fedora Linux ->
brewfiles/fedora-dev.Brewfile -
brewfiles/common.Brewfileis always applied first -
brewfiles/ai.Brewfileis applied last -
For a Fedora workstation (pure development profile), use: (manual alternative to
./bootstrap.sh)
brew bundle --file=brewfiles/common.Brewfile
brew bundle --file=brewfiles/fedora-dev.Brewfile
brew bundle --file=brewfiles/ai.BrewfileDesign notes:
- Put shared tools in
brewfiles/common.Brewfile(e.g.,fzf). - Put OS-specific tools in profile files (
brewfiles/macos.Brewfilefor macOS,brewfiles/fedora-dev.Brewfilefor Fedora). - Put AI tooling in
brewfiles/ai.Brewfile(currentlyanomalyco/tap/opencode). - If you want to replace a shared package later, update only
brewfiles/common.Brewfile.
Notes:
- System utilities are managed in
brewfiles/macos.Brewfile(currentlystats,hiddenbar, andraycast). raycastis used as the launcher/window-management tool.- Raycast config is stored in OneDrive; remember to back it up regularly.
This repo includes custom Raycast scripts under raycast/scripts/.
get-bitwarden-2fa.sh: fetches TOTP from Bitwarden CLI and copies it to clipboard. It caches a BW session token in~/Library/Caches/raycast-bitwarden-sessionto reduce repeated unlock prompts.lock-mac-and-bitwarden.sh: locks Bitwarden first (bw lock), clears the cached session token file, then locks the Mac session.
Recommended hardening:
- In Raycast, bind your lock shortcut (for example
cmd+l) tolock-mac-and-bitwarden.shinstead of the built-in Lock command. - Keep the old lock command unbound to avoid bypassing Bitwarden lock.
- Load zsh configs and plugins via:
./init.sh zshImplementation note: zsh bootstrap logic is maintained in zsh/init.sh, and init.sh only dispatches to it for the zsh app.
./init.sh zsh now:
- installs Oh My Zsh if missing (unattended)
- clones custom plugins listed in
zsh/plugins.confinto${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins - syncs plugin activation list to
~/.zsh_plugins - symlinks all repo
zsh/scripts/*.zshfiles into${ZSH_CUSTOM:-~/.oh-my-zsh/custom} - symlinks repo
zsh/.zshrcto~/.zshrc
zsh/.zshrc also initializes Homebrew shell environment for both macOS and Linux
(brew shellenv), so custom scripts can safely use commands installed via Homebrew.
It also prepends ~/.local/bin to PATH (when present), so user-installed CLI tools
such as uv tool shims are discoverable by default.
To add a plugin, update only zsh/plugins.conf with one line per plugin:
plugin-name|https://github.com/org/repo.git
For Oh My Zsh built-in plugins (no separate git repo), keep the URL empty:
git|
Then rerun:
./init.sh zshThis also loads zsh/scripts/bracketed-paste.zsh, which enables bracketed-paste-magic to prevent pasted commands from showing raw control prefixes like [200~.
zsh/scripts/ssh-agent.zsh is also loaded automatically and will:
- reuse an existing
ssh-agentfrom${XDG_STATE_HOME:-~/.local/state}/ssh-agent/agent-<host>.envwhen available - start a new
ssh-agentautomatically when needed - auto-add keys only when the agent has no identities
Per-host key customization (recommended):
~/.ssh/agent-keys: shared key list for all hosts~/.ssh/agent-keys.<hostname>: host-specific key list (hostname defaults to${HOST%%.*})
Each file supports one key path per line and # comments. Example:
~/.ssh/id_ed25519
~/.ssh/work
# optional per-machine key
~/.ssh/personal
Optional env overrides:
SSH_AGENT_HOSTNAME: override host tag used in env/key file namesSSH_AGENT_ENV_FILE: override agent env file pathSSH_AGENT_AUTO_KEYS: explicit key list string (shell words)
macOS SOCKS5 proxy helper is also included in zsh/scripts/socks5-proxy.zsh:
- command:
s5proxy <on|off|status|dns-status> - server:
192.168.0.201:7893(preconfigured) - target network service: Wi-Fi only (set
MAC_SOCKS5_PROXY_SERVICEto override) - credentials from env vars (required for
on):MAC_SOCKS5_PROXY_USER/MAC_SOCKS5_PROXY_PASS(orSOCKS5_PROXY_USER/SOCKS5_PROXY_PASS)
- optional DNS override while proxy is on (restored on
off):- default is proxy host itself (
192.168.0.201) - override with
MAC_SOCKS5_PROXY_DNS_SERVERS(space-separated), e.g."1.1.1.1 8.8.8.8"
- default is proxy host itself (
- if macOS shows
error -25299, it means the same proxy credential already exists in Keychain and can be safely ignored
Example:
export MAC_SOCKS5_PROXY_USER="your-user"
export MAC_SOCKS5_PROXY_PASS="your-pass"
# Optional: force DNS while proxy is enabled
# export MAC_SOCKS5_PROXY_DNS_SERVERS="1.1.1.1 8.8.8.8"
s5proxy on-
Install Squirrel via Homebrew (included in
brewfiles/macos.Brewfile) or manually.
# Install Plum
cd ~
git clone https://github.com/rime/plum.git plum
# Install all rime-ice files
cd ~/plum
bash rime-install iDvel/rime-ice:others/recipes/fullTo update rime-ice later, re-run
bash rime-install iDvel/rime-ice:others/recipes/fullfrom~/plum.
- Initialize local Rime config from this repo:
./init.sh rimeThis command will:
- symlink files from
Rime/to~/Library/Rime/(includingcustom_phrase_double.txt, excludinginstallation.yamlanduser.yaml) - write
~/Library/Rime/installation.yamlwith:- a per-machine
installation_id(default:<os>-YYYYmmdd-HHMMSS, e.g.macos-20260408-141530) sync_dirdefaults to~/OneDrive*/RimeSync(requires a OneDrive link under$HOMEand existingRimeSync)
- a per-machine
- create
${sync_dir}/${installation_id}if missing
Validation behavior:
- if
~/OneDrive*is missing,./init.sh rimeexits with error - if
~/OneDrive*/RimeSyncis missing,./init.sh rimeexits with error - use
RIME_SYNC_DIRto override explicitly when needed
Optional overrides:
RIME_INSTALLATION_ID="mac-your-id" RIME_SYNC_DIR="$HOME/Library/CloudStorage/OneDrive-个人/RimeSync" ./init.sh rimeFor Windows, use the PowerShell variant:
.\init.ps1 <app>Behavior mirrors the generic link flow of init.sh (with Windows-native config location):
- links
<repo>/<app>/->%APPDATA%/<app> copilotlinks to~/.copilotsshsyncs onlyssh/configto~/.ssh/config(keeps keys and known_hosts untouched)opencodelinks to~/.config/opencodenviminitializes submodulenvim/(if configured) and links to%LOCALAPPDATA%/nvimpwshlinks to your PowerShell profile directory (~/Documents/PowerShell)weztermlinks config dir to~/.config/weztermand ensures~/.wezterm.luapoints to that configgitlinksgit/.gitconfigandgit/.gitignore_globalto home
Notes:
- If destination already exists and is not the desired symlink, it is moved to a timestamped backup first.
zshandrimeare intentionally not supported ininit.ps1; useinit.shfor those app types.
- Reload Rime: click the Squirrel menu bar icon -> Deploy (重新部署).
The Rime config folder defaults to
~/Library/Rimeon macOS.
Some tools require environment variables to be set. Add them to your shell config (e.g., ~/.zshrc or a zsh/scripts/*.zsh file):
| Variable | Used by | Purpose |
|---|---|---|
CONTEXT7_API_KEY |
Copilot, OpenCode | API key for the Context7 MCP server |
export CONTEXT7_API_KEY="your-api-key"- Copilot reads it via
copilot/mcp-config.json(passed as an HTTP header to the Context7 MCP endpoint). - OpenCode (daemonized MCP mode) reads it via
mcp/.envwhen you runmcp/mcp.sh start.
This repo provides env templates so you can keep a reusable setup across machines.
Sync templates to your home directory:
./init.sh envThis command copies:
env/templates/.local/bin/env->~/.local/bin/envenv/templates/.config/env.d/*.sh->${XDG_CONFIG_HOME:-~/.config}/env.d/*.sh
If destination files already exist and differ, they are moved to timestamped backups (.bak.<timestamp>) before copying.
Default behavior is on-demand loading:
source ~/.local/bin/envto load helper functionsenvlistto list available modulesenvload 20-app-opencodeto load a single module
Optional auto-load (selected modules only):
export ENV_AUTO_MODULES="20-app-opencode 20-app-mcp"
source ~/.local/bin/envFor per-machine differences, prefer setting ENV_AUTO_MODULES in ~/.zshrc.local (not tracked in this repo):
# ~/.zshrc.local
export ENV_AUTO_MODULES="20-app-opencode"zsh/.zshrc already loads ~/.zshrc.local before ~/.local/bin/env, so each machine can enable a different module set.
Included templates:
20-app-opencode.sh: OpenCode-related non-secret env vars20-app-mcp.sh: local MCP host defaults (MCP_ENDPOINT_HOST)
Template naming convention:
00-*.sh: base/shared settings10-*.sh: toolchain/path settings20-app-*.sh: app-specific settings (e.g.20-app-opencode.sh)90-*.sh: machine-local overrides
For secrets, do not store plaintext in env templates. Prefer Bitwarden (~/.bw-env + bwenv/bwup).
To avoid each OpenCode process spawning its own MCP server instances, this repo now supports shared local MCP daemons under mcp/.
Files:
mcp/mcp.sh: manages MCP daemons with subcommands (start|stop|status|restart).mcp/servers.conf: server list and start commands (name|enabled|port|stdio_command).mcp/lib.sh: shared helper functions used by the scripts.mcp/sync-opencode-mcp.sh: syncs enabled server endpoints intoopencode/opencode.json.
Current local endpoints (used by opencode/opencode.json):
- Azure MCP:
http://{env:MCP_ENDPOINT_HOST}:8781/mcp - awesome-copilot MCP:
http://{env:MCP_ENDPOINT_HOST}:8782/mcp - Context7 MCP:
http://{env:MCP_ENDPOINT_HOST}:8783/mcp
You can override the endpoint host with MCP_ENDPOINT_HOST (for example your host LAN IP), so VMs can reach the MCP daemons.
- Create an env file for MCP-only secrets:
cp mcp/.env.example mcp/.env- Edit
mcp/.envand set:
CONTEXT7_API_KEY=your-api-key
# Optional: host/IP for generated endpoint URLs.
# Keep 127.0.0.1 for local-only access.
# Set this to your host LAN IP when OpenCode runs inside a VM.
MCP_ENDPOINT_HOST=127.0.0.1
- Edit
mcp/servers.confif you need to customize ports, enable/disable servers, or replace commands:
# name|enabled|port|stdio_command
azure|1|8781|uvx --from msmcp-azure azmcp server start
awesome-copilot|1|8782|docker run -i --rm ghcr.io/microsoft/mcp-dotnet-samples/awesome-copilot:latest
context7|1|8783|npx -y @upstash/context7-mcp --api-key ${CONTEXT7_API_KEY}
- Set
enabledto0to disable a server. - If you change ports, update
opencode/opencode.jsonURLs accordingly.
- Sync OpenCode MCP endpoints from
servers.conf:
bash mcp/sync-opencode-mcp.sh- This writes
opencode/opencode.jsonmcpentries asremoteURLs for enabled servers. - By default, host is written as
{env:MCP_ENDPOINT_HOST}so one linked config can be reused across different hosts. - Override with a fixed literal host by setting
OPENCODE_MCP_ENDPOINT_HOSTwhen syncing.
- Start daemons:
bash mcp/mcp.sh start- Check status:
bash mcp/mcp.sh status- Stop daemons when needed:
bash mcp/mcp.sh stop- Restart daemons when needed:
bash mcp/mcp.sh restartNotes:
- Runtime files are stored in
mcp/run/(*.pid,logs/*.log) and ignored by git. - This keeps shell env loading on-demand: MCP-related env vars are only loaded when starting MCP daemons.
- You can override the servers config path with
MCP_SERVERS_FILE, for example:
MCP_SERVERS_FILE=mcp/servers.conf.example bash mcp/mcp.sh status- For VM access, set
MCP_ENDPOINT_HOSTto a reachable host IP (for example192.168.x.x) before runningmcp/sync-opencode-mcp.sh. - Ensure your VM networking mode and host firewall allow inbound access to MCP ports (
8781-8783by default).
Example with fixed host at sync time:
OPENCODE_MCP_ENDPOINT_HOST=127.0.0.1 bash mcp/sync-opencode-mcp.shIf you use Bitwarden CLI (bw) to store secrets, zsh can auto-load selected values into env vars during shell startup.
- Ensure dependencies exist:
brew install bitwarden-cli jq- Add mappings to
~/.bw-env(one per line):
# ENV_VAR|item-id-or-name|source
OPENAI_API_KEY|my-openai-key|password
GITHUB_TOKEN|gh-pat|field:token
MY_USERNAME|some-login|username
Supported source values:
password(default)notesusernametotpfield:<Custom Field Name>
- Unlock Bitwarden vault before starting zsh (or run
bwenvafter unlocking):
export BW_SESSION="$(bw unlock --raw)"- Run
./init.sh zshto sync scripts, then open a new shell.
Notes:
- Loader script:
zsh/scripts/bitwarden-env.zsh - Default config file path:
~/.bw-env(override withBW_ENV_FILE) - Auto-load is disabled by default (
BW_ENV_AUTOLOAD=0) - Manual reload command in shell:
bwenv - Convenience command for unlock + load:
bwup
This repository primarily mirrors ~/.config/ and can be reused across different machines.
Link an app directory from this repo into your host's config directory:
./init.sh opencodeBy default, this creates a symlink from <repo>/<app>/ to ${XDG_CONFIG_HOME:-~/.config}/<app>.
Exceptions:
copilotlinks to~/.copilotsshsyncs onlyssh/configto~/.ssh/config(keeps keys and known_hosts untouched)
SSH hosts are managed in ssh/config in this repository.
./init.sh sshThis links only ssh/config from the repo to ~/.ssh/config so host aliases (for example testing and openclaw-test) stay versioned and consistent across machines.
Other files in ~/.ssh (such as private keys and known_hosts) are intentionally not managed by this repo.
If the destination already exists and isn't the desired symlink, it is moved aside to *.bak.<timestamp>.
Manage global Git config and ignore rules from this repo:
./init.sh gitThis creates the following symlinks:
git/.gitconfig->~/.gitconfiggit/.gitignore_global->~/.gitignore_global
Included defaults:
- enforce Unix line endings (
core.eol=lf,core.autocrlf=input) - global ignore file (
core.excludesfile=~/.gitignore_global) - common quality-of-life settings (
fetch.prune,rebase.autoStash,push.autoSetupRemote, etc.) git-deltaintegration for paging and interactive diffs (core.pager=delta,interactive.diffFilter=delta --color-only)merge.conflictStyle=zdiff3for clearer conflict context
When you run ./init.sh git, the script also checks whether delta is installed and prints a hint if missing (brew install git-delta).
Example: OpenCode
OpenCode configuration lives in opencode/.
opencode/AGENTS.md: default agent instructions (copied from~/AGENTS.md).opencode/opencode.json: includessuperpowers@git+https://github.com/obra/superpowers.gitinplugin, so skills auto-install on OpenCode restart.
AstroNvim config lives in nvim/ and is linked as your Neovim config:
./init.sh nvimOn Windows PowerShell:
.\init.ps1 nviminit.ps1 will initialize the nvim git submodule first (when declared in .gitmodules), then link it into your Neovim config directory.
init.sh now follows the same behavior: it initializes the nvim git submodule first (when declared in .gitmodules), then links it.
This creates a symlink from nvim/ to ${XDG_CONFIG_HOME:-~/.config}/nvim.
Plugin versions are locked in nvim/lazy-lock.json and should be committed to keep plugin behavior consistent across machines.
- Add startup management integration (e.g.,
launchdon macOS) for MCP daemons if auto-start on login is desired.