Last active
April 13, 2026 15:47
-
-
Save fuzzbuster/a117689823ba87c27437a44129253dcb to your computer and use it in GitHub Desktop.
bootstrap.sh - macOS 輕量終端開發環境一鍵安裝腳本
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # | |
| # bootstrap.sh - macOS 輕量終端開發環境一鍵安裝腳本 | |
| # 包含:Ghostty + zellij + yazi + lazygit + fastfetch + Neovim/LazyVim + 現代工具 + oh-my-zsh | |
| # 語言環境:Python(uv/pipx)、Go、Rust(rustup)、Java(Liberica 20 Full+JavaFX)、Node(pnpm+bun)、Ruby | |
| # | |
| # 用法:curl -fsSL https://gist.githubusercontent.com/fuzzbuster/a117689823ba87c27437a44129253dcb/raw/1b2e930ad152a7ceb9fbb7df581b304e2d1685a0/macos_bootstrap.sh | bash | |
| # 或 bash bootstrap.sh | |
| # | |
| # 可重複執行,不會重複安裝或破壞既有配置 | |
| set -euo pipefail | |
| # 顏色定義 | |
| RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' | |
| echo_info() { echo -e "${GREEN}[INFO]${NC} $1"; } | |
| echo_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } | |
| echo_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; exit 1; } | |
| has_cmd() { command -v "$1" >/dev/null 2>&1; } | |
| # 小工具:確保 tap / clone / brew 安裝可重用 | |
| ensure_tap() { | |
| local tap="$1" | |
| [[ "$HAVE_BREW" == true ]] || { echo_warn "brew not available, cannot tap $tap"; return; } | |
| brew tap | grep -q "^${tap}$" || { echo_info "Tapping ${tap}..."; brew tap "${tap}" || echo_warn "Failed to tap ${tap}"; } | |
| } | |
| ensure_clone() { | |
| local name="$1" url="$2" dir="$3" | |
| has_cmd git || { echo_warn "git not installed, cannot clone $name"; return; } | |
| [[ -d "$dir" ]] || { echo_info "Cloning ${name}..."; git clone --quiet "${url}" "${dir}" || echo_warn "Failed to clone ${url}"; } | |
| } | |
| # 安裝 brew 軟體包(formula),若不存在則安裝,可接受額外參數 | |
| ensure_brew_pkg() { | |
| local pkg="$1" | |
| shift | |
| [[ "$HAVE_BREW" == true ]] || { echo_warn "brew not available, skipping install of $pkg"; return; } | |
| brew list --formula "$pkg" >/dev/null 2>&1 && return | |
| echo_info "Installing brew package: $pkg" | |
| if (($#)); then brew install "$pkg" "$@" || echo_warn "Failed to install $pkg" | |
| else brew install "$pkg" || echo_warn "Failed to install $pkg"; fi | |
| } | |
| # 安裝 brew cask,若尚未存在則安裝 | |
| ensure_brew_cask() { | |
| local cask="$1" | |
| [[ "$HAVE_BREW" == true ]] || { echo_warn "brew not available, cannot install cask $cask"; return; } | |
| brew list --cask "$cask" >/dev/null 2>&1 && return | |
| echo_info "Installing brew cask: $cask" | |
| brew install --cask "$cask" || echo_warn "Failed to install cask $cask" | |
| } | |
| # 將指定內容加入 ~/.zshrc(以唯一標記避免重複) | |
| ensure_zshrc_block() { | |
| local marker="$1" content="$2" | |
| grep -qF "$marker" "$ZSHRC" 2>/dev/null && return | |
| echo_info "Appending block to .zshrc: $marker" | |
| cat <<EOF >>"$ZSHRC" | |
| $content | |
| EOF | |
| } | |
| # 檢查 .zshrc 的 plugins=(...) 單行配置中是否已包含指定外掛 | |
| # 使用 shell 分詞而非正則,避免首個外掛被誤判導致重複追加。 | |
| zshrc_has_plugin() { | |
| local plugin="$1" | |
| local plugins_line plugins_body existing | |
| plugins_line="$(grep -E '^plugins=\(' "$ZSHRC" 2>/dev/null | head -n 1 || true)" | |
| [[ -z "$plugins_line" ]] && return 1 | |
| plugins_body="${plugins_line#plugins=(}" | |
| plugins_body="${plugins_body%)}" | |
| for existing in $plugins_body; do | |
| [[ "$existing" == "$plugin" ]] && return 0 | |
| done | |
| return 1 | |
| } | |
| # 追加外掛到 .zshrc 的 plugins=(...) 單行配置 | |
| append_zshrc_plugin() { | |
| local plugin="$1" | |
| if grep -q '^plugins=()' "$ZSHRC" 2>/dev/null; then sed -i '' "s/^plugins=()$/plugins=($plugin)/" "$ZSHRC" | |
| else sed -i '' "/^plugins=(/ s/)/ $plugin)/" "$ZSHRC"; fi | |
| } | |
| ensure_symlink_if_target_exists() { | |
| local target="$1" link="$2" missing_warn="$3" | |
| if [[ -e "$target" ]]; then | |
| [[ -L "$link" ]] || ln -sf "$target" "$link" | |
| else | |
| echo_warn "$missing_warn" | |
| fi | |
| } | |
| copy_if_missing() { | |
| local src="$1" dest="$2" missing_warn="$3" | |
| [[ -f "$dest" ]] && return | |
| if [[ -f "$src" ]]; then | |
| cp "$src" "$dest" || true | |
| else | |
| echo_warn "$missing_warn" | |
| fi | |
| } | |
| sync_community_plugins() { | |
| local plugin_info plugin_name plugin_dir | |
| for plugin_info in "${COMMUNITY_PLUGINS[@]}"; do | |
| plugin_name="${plugin_info%% *}" | |
| plugin_dir="$HOME/.oh-my-zsh/custom/plugins/$plugin_name" | |
| ensure_clone "$plugin_name" "${plugin_info#* }" "$plugin_dir" | |
| if [[ ! -d "$plugin_dir" ]]; then | |
| echo_warn "Community plugin directory missing, skipping .zshrc entry: $plugin_name" | |
| continue | |
| fi | |
| if ! zshrc_has_plugin "$plugin_name"; then | |
| append_zshrc_plugin "$plugin_name" | |
| fi | |
| done | |
| } | |
| # 確保指定檔案存在;若不存在則以 stdin 的內容建立它 | |
| # 用法:ensure_file /path/to/file <<'EOF' … EOF | |
| ensure_file() { | |
| local file="$1" | |
| if [[ ! -f "$file" ]]; then | |
| echo_info "Creating file: $file" | |
| cat >"$file" | |
| else | |
| echo_info "File already exists, skipping: $file" | |
| fi | |
| } | |
| # 1. 確保安裝 Xcode Command Line Tools -> Homebrew 會自動處理安裝 | |
| # if ! xcode-select -p &>/dev/null; then | |
| # echo_info "Installing Xcode Command Line Tools..." | |
| # xcode-select --install | |
| # until xcode-select -p &>/dev/null; do sleep 5; done | |
| # else | |
| # echo_info "Xcode Command Line Tools already installed." | |
| # fi | |
| # 2. 安裝或更新 Homebrew | |
| ZPROFILE="$HOME/.zprofile" | |
| BREW_BIN="" | |
| if has_cmd brew; then | |
| BREW_BIN="$(command -v brew)" | |
| echo_info "Homebrew already installed." | |
| else | |
| echo_info "Installing Homebrew..." | |
| /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" | |
| fi | |
| [[ -z "$BREW_BIN" && -x "/opt/homebrew/bin/brew" ]] && BREW_BIN="/opt/homebrew/bin/brew" | |
| [[ -z "$BREW_BIN" && -x "/usr/local/bin/brew" ]] && BREW_BIN="/usr/local/bin/brew" | |
| # 將 brew shellenv 寫入 ~/.zprofile(僅寫一次)並套用到目前 shell | |
| if [[ -n "$BREW_BIN" ]]; then | |
| printf -v BREW_SHELLENV_LINE "eval \"\$(%s shellenv)\"" "$BREW_BIN" | |
| touch "$ZPROFILE" | |
| grep -qxF "$BREW_SHELLENV_LINE" "$ZPROFILE" || printf "%s\n" "$BREW_SHELLENV_LINE" >> "$ZPROFILE" | |
| if grep -qxF "$BREW_SHELLENV_LINE" "$ZPROFILE"; then | |
| eval "$("$BREW_BIN" shellenv)" | |
| echo_info "Homebrew shellenv is ready in current shell and ~/.zprofile." | |
| else | |
| echo_warn "Failed to persist Homebrew shellenv to ~/.zprofile" | |
| fi | |
| fi | |
| # 安裝檢查後快取 brew 的可用性和前綴 | |
| if has_cmd brew; then | |
| HAVE_BREW=true | |
| BREW_PREFIX="$(brew --prefix)" | |
| else | |
| HAVE_BREW=false | |
| BREW_PREFIX="" | |
| fi | |
| # 2.5 檢查並安裝 Brewfile(若存在外部配置,可用 `brew bundle dump` 生成) | |
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| BREWFILE_SCRIPT="$SCRIPT_DIR/Brewfile" | |
| # 檢查脚本所在目錄是否存在 Brewfile | |
| if [[ -f "$BREWFILE_SCRIPT" ]]; then | |
| if [[ "$HAVE_BREW" == true ]]; then | |
| echo_info "Found Brewfile in script directory: $BREWFILE_SCRIPT" | |
| echo_info "Installing packages from Brewfile..." | |
| brew bundle --file="$BREWFILE_SCRIPT" || echo_warn "部分 Brewfile 套件安裝失敗" | |
| else | |
| echo_warn "Brewfile found but Homebrew is unavailable, skipping Brewfile install" | |
| fi | |
| fi | |
| # 3. 安裝核心終端工具 + 常用輔助工具 + Neovim | |
| echo_info "Installing core terminal tools, utilities and Neovim..." | |
| # 批量安裝 Homebrew 套件與 Cask | |
| CORE_BREW=(zellij yazi lazygit fastfetch ripgrep fzf zoxide fd bat eza lsd | |
| starship chsrc gh git-delta tldr httpie glow jq poppler ffmpeg imagemagick | |
| openssh curl neovim mas dockutil tmux p7zip resvg jd coreutils) | |
| LANG_BREW=([email protected] pipx uv go node pnpm ruby) | |
| BREW_PACKAGES=("${CORE_BREW[@]}" "${LANG_BREW[@]}") | |
| BREW_CASKS=(ghostty font-jetbrains-mono-nerd-font font-0xproto-nerd-font) | |
| if [[ "$HAVE_BREW" == true ]]; then | |
| echo_info "Installing Homebrew packages..." | |
| # 更新清單並安裝所有分類中的套件 | |
| brew update || true | |
| brew install "${BREW_PACKAGES[@]}" || echo_warn "部分 brew 套件安裝失敗" | |
| echo_info "Installing Homebrew casks..." | |
| brew install --cask "${BREW_CASKS[@]}" || echo_warn "部分 brew cask 安裝失敗" | |
| else | |
| echo_warn "Homebrew is unavailable, skipping package and cask installation" | |
| fi | |
| echo_info "Core terminal tools, utilities and Neovim installed/updated (or reported warnings)." | |
| # 3.1 安裝 tmux 並設定 oh-my-tmux | |
| echo_info "Ensuring tmux is installed and configuring oh-my-tmux..." | |
| ensure_clone "oh-my-tmux" https://github.com/gpakosz/.tmux.git "$HOME/.tmux" | |
| # 建立符號鏈結到主配置 | |
| ensure_symlink_if_target_exists \ | |
| "$HOME/.tmux/.tmux.conf" \ | |
| "$HOME/.tmux.conf" \ | |
| "oh-my-tmux repo not ready, skipping ~/.tmux.conf symlink" | |
| # 複製本地化配置樣板(若尚未存在) | |
| copy_if_missing \ | |
| "$HOME/.tmux/.tmux.conf.local" \ | |
| "$HOME/.tmux.conf.local" \ | |
| "oh-my-tmux local config template not found, skipping ~/.tmux.conf.local copy" | |
| # 3.5 安裝常見程式語言環境(包含 LazyVim 必要依賴) | |
| echo_info "Installing programming language environments and LazyVim dependencies..." | |
| # 以上各套件已在 BREW_PACKAGES 中統一列出,這裡只處理額外步驟 | |
| if has_cmd pip3; then | |
| pip3 install --user pynvim &>/dev/null || echo_warn "Failed to install pynvim, run manually: pip3 install --user pynvim" | |
| fi | |
| # bun: 使用 oven-sh/bun tap,然後透過 Homebrew 安裝 bun;失敗時提示使用官方安裝腳本 | |
| if ! has_cmd bun; then | |
| echo_info "Ensuring bun is installed via Homebrew..." | |
| ensure_tap oven-sh/bun | |
| ensure_brew_pkg bun | |
| if ! has_cmd bun; then | |
| echo_warn "brew install bun may have failed; you can install manually: curl -fsSL https://bun.sh/install | bash" | |
| fi | |
| else | |
| echo_info "bun already installed, skipping" | |
| fi | |
| if has_cmd npm; then | |
| npm install -g neovim &>/dev/null || echo_warn "Failed to install neovim npm package, run manually: npm install -g neovim" | |
| fi | |
| # Rust - 透過 Homebrew 安裝 rustup 管理工具鏈 | |
| if ! has_cmd rustup; then | |
| echo_info "Installing rustup via Homebrew..." | |
| ensure_brew_pkg rustup | |
| echo_info "Initializing default stable Rust toolchain..." | |
| rustup default stable || echo_warn "Please run 'rustup default stable' manually after reloading your shell" | |
| else | |
| echo_info "rustup already installed. Updating stable Rust toolchain..." | |
| rustup update stable || echo_warn "Failed to update stable Rust toolchain" | |
| fi | |
| # 檢查是否缺少 stable toolchain,避免 Rust 命令無法使用 | |
| if has_cmd rustup && ! rustup toolchain list | grep -q stable; then | |
| echo_warn "No stable Rust toolchain found. Please run: rustup default stable" | |
| fi | |
| # Java - Liberica JDK 20 Full (內含 JavaFX) | |
| ensure_tap bell-sw/liberica | |
| ensure_brew_cask liberica-jdk20-full | |
| echo_info "Programming language environments and LazyVim dependencies installed/updated." | |
| # 4. 安裝 oh-my-zsh(僅第一次執行安裝,重複執行跳過) | |
| if [[ ! -d "$HOME/.oh-my-zsh" ]]; then | |
| echo_info "Installing oh-my-zsh framework..." | |
| KEEP_ZSHRC=yes RUNZSH=no sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" | |
| else | |
| echo_info "oh-my-zsh already installed, skipping." | |
| fi | |
| # 5. 配置 ~/.zshrc(外掛、fzf 智慧預覽、fastfetch 自動顯示等) | |
| ZSHRC="$HOME/.zshrc" | |
| # 註解預設 ZSH 主題,交由 Starship 接管終端提示符 | |
| if grep -qE '^ZSH_THEME=' "$ZSHRC" 2>/dev/null; then | |
| sed -i '' 's/^ZSH_THEME=/# ZSH_THEME=/' "$ZSHRC" | |
| echo_info "Commented out default ZSH_THEME for Starship prompt." | |
| fi | |
| RECOMMENDED_OFFICIAL="git fzf zoxide starship colored-man-pages extract" | |
| COMMUNITY_PLUGINS=( | |
| "zsh-autosuggestions https://github.com/zsh-users/zsh-autosuggestions" | |
| "zsh-syntax-highlighting https://github.com/zsh-users/zsh-syntax-highlighting" | |
| ) | |
| # 確保 plugins=() 區塊存在於 .zshrc,避免新增外掛時出錯 | |
| if ! grep -q '^plugins=(' "$ZSHRC" 2>/dev/null; then | |
| echo 'plugins=()' >> "$ZSHRC" | |
| fi | |
| # 加入官方推薦外掛到 plugins 區塊(重複執行不重複添加) | |
| for plugin in $RECOMMENDED_OFFICIAL; do | |
| if ! zshrc_has_plugin "$plugin"; then | |
| append_zshrc_plugin "$plugin" | |
| fi | |
| done | |
| # 克隆社群外掛並加入 plugins 區塊(放在最後,保證執行順序) | |
| sync_community_plugins | |
| # fzf 智慧預覽配置:檔案用 bat 高亮,目錄用 eza/ls 顯示(僅第一次添加) | |
| fzf_block=$(cat <<'EOF' | |
| # fzf: smart preview (bat for files, eza/ls for directories) | |
| if command -v fd >/dev/null; then | |
| export FZF_DEFAULT_COMMAND='fd --type f --strip-cwd-prefix --hidden --follow --exclude .git' | |
| export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND" | |
| export FZF_ALT_C_COMMAND="fd --type d --strip-cwd-prefix --hidden --follow --exclude .git" | |
| fi | |
| export FZF_DEFAULT_OPTS=' | |
| --height 45% --layout=reverse --border --inline-info | |
| --preview "[[ \$(file --mime {}) =~ binary ]] && | |
| echo {} is a binary file || | |
| (bat --style=numbers --color=always --line-range :500 {} 2>/dev/null || | |
| eza -l --color=always --icons --git {} 2>/dev/null || | |
| ls -la --color=always {} ) 2>/dev/null' | |
| EOF | |
| ) | |
| ensure_zshrc_block "# fzf: smart preview" "$fzf_block" | |
| # fastfetch 自動顯示:新終端開啟時顯示系統資訊(zellij 內跳過,避免重複顯示) | |
| fastfetch_block=$(cat <<'EOF' | |
| # Auto-display system info with fastfetch (skip inside zellij session) | |
| if [[ -z "$ZELLIJ" ]]; then | |
| fastfetch --logo-type kitty-direct --logo-width 40 --logo-height 20 --logo-padding-top 5 | |
| fi | |
| EOF | |
| ) | |
| ensure_zshrc_block "# Auto-display system info with fastfetch" "$fastfetch_block" | |
| # 6. 模組化配置:拆分 PATH / Alias / Function 到獨立檔案 | |
| ZSH_DEV_DIR="$HOME/.config/zshdev" | |
| PATH_FILE="$ZSH_DEV_DIR/00_paths.zsh" | |
| ALIAS_FILE="$ZSH_DEV_DIR/01_aliases.zsh" | |
| FUNC_FILE="$ZSH_DEV_DIR/02_functions.zsh" | |
| LOADER_FILE="$ZSH_DEV_DIR/zshdev.loader.zsh" | |
| mkdir -p "$ZSH_DEV_DIR" 2>/dev/null | |
| ensure_file "$PATH_FILE" <<EOF | |
| # 開發工具 PATH 集中管理 | |
| # Go 開發環境 | |
| export GOPATH="$HOME/go" | |
| export PATH="$GOPATH/bin:$PATH" | |
| # Rust (Homebrew 安裝的 rustup 路徑) | |
| [ -n "$BREW_PREFIX" ] && export PATH="$BREW_PREFIX/opt/rustup/bin:$PATH" || true | |
| # Node (pnpm 全域套件安裝路徑) | |
| export PATH="$HOME/.pnpm-global/bin:$PATH" # 如有自訂 global dir 可在此調整 | |
| # bun 通常會自動加入 ~/.bun/bin,若未生效可手動加入 source ~/.bun/env | |
| # Java - Liberica JDK 20 Full (Homebrew cask 預設安裝路徑) | |
| export JAVA_HOME="/Library/Java/JavaVirtualMachines/liberica-jdk-20.jdk/Contents/Home" | |
| export PATH="$JAVA_HOME/bin:$PATH" | |
| # Ruby (Homebrew 安裝的 Ruby,優先於系統預設) | |
| [ -n "$BREW_PREFIX" ] && export PATH="$BREW_PREFIX/opt/ruby/bin:$PATH" | |
| [ -n "$BREW_PREFIX" ] && export LDFLAGS="-L$BREW_PREFIX/opt/ruby/lib" | |
| [ -n "$BREW_PREFIX" ] && export CPPFLAGS="-I$BREW_PREFIX/opt/ruby/include" | |
| # Neovim 相關:Mason 安裝的工具路徑(LazyVim 插件依賴) | |
| export PATH="$HOME/.local/share/nvim/mason/bin:$PATH" | |
| EOF | |
| ensure_file "$ALIAS_FILE" <<'EOF' | |
| # 終端工具 Alias 集中管理 | |
| # 核心終端工具快捷指令 | |
| alias lg="lazygit" | |
| alias zj="zellij" | |
| alias y="yy" # 對應 yy 函數(yazi 退出後自動切換目錄) | |
| # Neovim/LazyVim 快捷别名(替換系統預設 vim/vi,統一使用 Neovim) | |
| alias nv="nvim" | |
| alias vim="nvim" | |
| alias vi="nvim" | |
| alias nvimrc="nvim ~/.config/nvim/lua/config/" # 快速開啟 LazyVim 核心配置目錄 | |
| alias nvimplug="nvim ~/.config/nvim/lua/plugins/" # 快速開啟 LazyVim 外掛配置目錄 | |
| # Python 快捷指令(優先使用 uv 管理) | |
| command -v uv >/dev/null && alias py="uv python" && alias venv="uv venv" | |
| # 自定義擴展區域(可自行新增/修改,不影響其他配置) | |
| # alias cargo-up='rustup update' | |
| # alias bun-up='bun upgrade' | |
| # alias ll='eza -l --icons --git' | |
| # alias la='eza -la --icons --git' | |
| EOF | |
| ensure_file "$FUNC_FILE" <<'EOF' | |
| # 自定義 Shell 函數集中管理 | |
| # yazi 增強函數:退出檔案管理器時,自動切換到最後瀏覽的目錄 | |
| function yy() { | |
| local tmp="$(mktemp -t "yazi-cwd.XXXXXX")" | |
| yazi "$@" --cwd-file="$tmp" | |
| if [ -f "$tmp" ]; then | |
| local cwd="$(cat -- "$tmp")" | |
| [ -n "$cwd" ] && [ "$cwd" != "$PWD" ] && cd -- "$cwd" | |
| fi | |
| rm -f -- "$tmp" | |
| } | |
| function lazy-update() { | |
| echo "Updating LazyVim core and all plugins..." | |
| nvim --headless "+Lazy sync" +qa | |
| echo "LazyVim update completed successfully!" | |
| } | |
| # 全域更新函數(可解註解使用):一鍵更新所有終端工具 + LazyVim | |
| # function update-all() { | |
| # brew update && brew upgrade | |
| # rustup update stable | |
| # bun upgrade | |
| # pnpm update -g | |
| # lazy-update # 加入 LazyVim 外掛更新 | |
| # } | |
| EOF | |
| ensure_file "$LOADER_FILE" <<'EOF' | |
| # ZSH 開發配置加載器(PATH → Alias → Function) | |
| [ -f "$HOME/.config/zshdev/00_paths.zsh" ] && source "$HOME/.config/zshdev/00_paths.zsh" | |
| [ -f "$HOME/.config/zshdev/01_aliases.zsh" ] && source "$HOME/.config/zshdev/01_aliases.zsh" | |
| [ -f "$HOME/.config/zshdev/02_functions.zsh" ] && source "$HOME/.config/zshdev/02_functions.zsh" | |
| # 可擴展自定義配置(例如 03_custom.zsh),解註解即可加載 | |
| # [ -f "$HOME/.config/zshdev/03_custom.zsh" ] && source "$HOME/.config/zshdev/03_custom.zsh" | |
| EOF | |
| loader_block=$(cat <<EOF | |
| # Load modular zsh development config (PATH/Alias/Function) | |
| [ -f "$LOADER_FILE" ] && source "$LOADER_FILE" | |
| EOF | |
| ) | |
| ensure_zshrc_block "# Load modular zsh development config" "$loader_block" | |
| # 7. 配置 LazyVim 環境:拉取官方 Starter 模板 | |
| LAZYVIM_CONFIG="$HOME/.config/nvim" | |
| LAZYVIM_STARTER="https://github.com/LazyVim/starter.git" | |
| if [[ ! -d "$LAZYVIM_CONFIG" ]]; then | |
| echo_info "Installing LazyVim - cloning official Starter template..." | |
| git clone --quiet "$LAZYVIM_STARTER" "$LAZYVIM_CONFIG" | |
| rm -rf "$LAZYVIM_CONFIG/.git" | |
| echo_info "LazyVim Starter template cloned to: $LAZYVIM_CONFIG" | |
| else | |
| echo_info "LazyVim config directory already exists: $LAZYVIM_CONFIG (skipping clone, keep your custom config)" | |
| fi | |
| mkdir -p "$HOME/.local/share/nvim" "$HOME/.cache/nvim" "$HOME/.config/nvim/after" 2>/dev/null | |
| # 8. Ghostty 基本設定檔:适配 Neovim 快捷鍵 | |
| GHOSTTY_CONFIG="$HOME/.config/ghostty/config" | |
| if [[ ! -f "$GHOSTTY_CONFIG" ]]; then | |
| echo_info "Creating basic Ghostty config (Neovim keybinding compatible)..." | |
| mkdir -p "$(dirname "$GHOSTTY_CONFIG")" | |
| cat <<'EOF' >"$GHOSTTY_CONFIG" | |
| font-family = "JetBrainsMono Nerd Font" | |
| font-size = 14 | |
| theme = "Catppuccin Mocha" | |
| background-opacity = 0.92 | |
| background-blur = 20 | |
| window-padding-x = 8 | |
| window-padding-y = 8 | |
| macos-option-as-alt = true | |
| EOF | |
| else | |
| echo_info "Ghostty config file already exists: $GHOSTTY_CONFIG (skipping)" | |
| fi | |
| # 9. 清理與安裝完成提示 | |
| [[ "$HAVE_BREW" == true ]] && brew cleanup --quiet 2>/dev/null || true | |
| # 安裝完成主提示 | |
| echo_info "Bootstrap installation completed successfully! 🎉" | |
| echo_info "To apply all configs: run 'source ~/.zshrc' or restart your terminal" | |
| # 常見問題警告 | |
| echo_warn "Starship/LazyVim icons not displaying? → Ensure Ghostty uses 'JetBrainsMono Nerd Font'" | |
| echo_warn "Incorrect JAVA_HOME? → Run 'ls /Library/Java/JavaVirtualMachines/' and edit $PATH_FILE" | |
| # 配置文件位置說明 | |
| echo_info "=== Modular Config File Locations ===" | |
| echo_info " PATH Env: $PATH_FILE" | |
| echo_info " Custom Aliases: $ALIAS_FILE" | |
| echo_info " Shell Functions: $FUNC_FILE" | |
| echo_info " Config Loader: $LOADER_FILE" | |
| echo_info " LazyVim Core Config: $LAZYVIM_CONFIG/lua/config/" | |
| echo_info " LazyVim Plugin Config: $LAZYVIM_CONFIG/lua/plugins/" | |
| # 核心快捷指令說明 | |
| echo_info "=== Core Shortcut Commands ===" | |
| echo_info " lg → lazygit (git UI client)" | |
| echo_info " y / yy → yazi (file manager, auto cd on exit)" | |
| echo_info " zj → zellij (terminal multiplexer)" | |
| echo_info " z → zoxide (smart cd command)" | |
| echo_info " nv/vim/vi → Neovim/LazyVim (replace system default vim)" | |
| echo_info " nvimrc → Open LazyVim core config directory" | |
| echo_info " nvimplug → Open LazyVim plugin config directory" | |
| echo_info " lazy-update → Update LazyVim core and all plugins" | |
| # LazyVim 首次啟動注意事項 | |
| echo_info "=== First Time LazyVim Setup ===" | |
| echo_info "1. Run 'nv' to start Neovim (auto installs all LazyVim plugins, 1-2 mins via network)" | |
| echo_info "2. Restart Neovim after plugin installation for full functionality" | |
| echo_info "3. LazyVim is preconfigured with fd/ripgrep/eza/bat for fast search and preview" | |
| # 最後歡迎語 | |
| echo_info "Enjoy your clean, fast and modern macOS terminal + Neovim/LazyVim development environment!" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # macos_defaults.sh - 套用自定義 macOS 預設設定 | |
| # | |
| # 此腳本參考自 alllex 的 gist(mac-fre.sh, | |
| # https://gist.github.com/alllex/e0b126ca69316460cefd7e23abe0eafa), | |
| # 並合併了額外的客製化調整(鍵盤重複、Finder 預設、截圖設定、 | |
| # 遙測關閉、選單列時鐘等)。變更前會備份現有設定;同一項目 | |
| # 只寫入一次以避免重複。# | |
| # 用法:sudo ./macos_defaults.sh | |
| # 或 NEW_MAC=true sudo ./macos_defaults.sh # 首次設定時使用 | |
| set -euo pipefail | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| # 1. 前置檢查 | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| MIN_VERSION="14.0" | |
| CURRENT=$(sw_vers -productVersion) | |
| if [[ "$(printf '%s\n' "$MIN_VERSION" "$CURRENT" | sort -V | head -n1)" != "$MIN_VERSION" ]]; then | |
| echo "Requires macOS $MIN_VERSION+. You have $CURRENT." | |
| exit 1 | |
| fi | |
| # 關閉「系統設定」,避免它覆蓋稍後的調整 | |
| osascript -e 'tell application "System Settings" to quit' 2>/dev/null || true | |
| # 要求管理員密碼並保持 sudo 權限活躍 | |
| sudo -v | |
| while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null & | |
| # 備份目前的預設設定 | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| # 備份目前的預設設定 | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| BACKUP_DIR="$SCRIPT_DIR/backup" | |
| mkdir -p "$BACKUP_DIR" | |
| BACKUP_FILE="$BACKUP_DIR/defaults-$(date +%Y%m%d-%H%M%S).plist" | |
| echo "[INFO] backing up current defaults to $BACKUP_FILE" | |
| # 匯出所有網域:dash 表示全部,接著指定輸出檔案 | |
| defaults export - "$BACKUP_FILE" | |
| # 寫入前檢查當前值,只有不同才寫入(避免重複條目) | |
| write_default() { | |
| local domain="$1" key="$2" type="$3" value="$4" | |
| local current | |
| current=$(defaults read "$domain" "$key" 2>/dev/null || echo "__unset__") | |
| if [[ "$current" == "$value" ]]; then | |
| return | |
| fi | |
| defaults write "$domain" "$key" -$type "$value" | |
| } | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| # 2. 一般介面/使用者體驗 | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| # 加快動畫速度 | |
| write_default NSGlobalDomain NSAutomaticWindowAnimationsEnabled bool false | |
| # 關閉按住按鍵的選字功能(對程式開發有用) | |
| write_default NSGlobalDomain ApplePressAndHoldEnabled bool false | |
| # 更快速的鍵盤重複與短延遲(客製片段 + gist) | |
| write_default NSGlobalDomain KeyRepeat int 1 | |
| write_default NSGlobalDomain InitialKeyRepeat int 10 | |
| # 關閉智慧引號、長破折號、自動大寫、自動句號 | |
| write_default NSGlobalDomain NSAutomaticQuoteSubstitutionEnabled bool false | |
| write_default NSGlobalDomain NSAutomaticDashSubstitutionEnabled bool false | |
| write_default NSGlobalDomain NSAutomaticCapitalizationEnabled bool false | |
| write_default NSGlobalDomain NSAutomaticPeriodSubstitutionEnabled bool false | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| # 3. 觸控板/滑鼠 | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| #(來自 gist)啟用點按即點選 | |
| write_default com.apple.driver.AppleBluetoothMultitouch.trackpad Clicking bool true | |
| write_default NSGlobalDomain com.apple.trackpad.scaling float 2.0 | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| # 4. Finder 調整 | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| # 顯示 ~/Library 資料夾 | |
| chflags nohidden ~/Library | |
| # 避免重覆寫入同樣設定(已在自訂片段出現) | |
| write_default com.apple.finder NewWindowTarget string "PfLo" | |
| write_default com.apple.finder NewWindowTargetPath string "file://$HOME/Downloads/" | |
| write_default NSGlobalDomain AppleShowAllExtensions bool true | |
| write_default com.apple.finder ShowPathbar bool true | |
| write_default com.apple.finder _FXSortFoldersFirst bool true | |
| write_default com.apple.finder FXDefaultSearchScope string "SCcf" | |
| write_default com.apple.finder FXEnableExtensionChangeWarning bool false | |
| write_default NSGlobalDomain com.apple.springing.enabled bool true | |
| write_default NSGlobalDomain com.apple.springing.delay float 0.1 | |
| write_default com.apple.desktopservices DSDontWriteNetworkStores bool true | |
| write_default com.apple.desktopservices DSDontWriteUSBStores bool true | |
| write_default com.apple.finder FXPreferredViewStyle string "Nlsv" | |
| # 顯示所有檔案以及路徑/狀態列(自訂片段) | |
| write_default com.apple.finder AppleShowAllFiles bool true | |
| write_default com.apple.finder ShowStatusBar bool true | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| # 5. Dock / 儀表板 | |
| # 使用預先提供的設定: | |
| # Dock 瞬間彈出 + 無動畫 (原文片段在前面註解中) | |
| # 見:https://gist.github.com/alllex/e0b126ca69316460cefd7e23abe0eafa | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| # Dock 瞬間彈出 + 無動畫 | |
| write_default com.apple.dock autohide bool true | |
| write_default com.apple.dock autohide-delay float 0 | |
| write_default com.apple.dock autohide-time-modifier float 0 | |
| write_default com.apple.dock launchanim bool false | |
| killall Dock 2>/dev/null || true | |
| # 加速任務控制、隱藏最近項目等(來自gist) | |
| write_default com.apple.dock expose-animation-duration float 0.1 | |
| write_default com.apple.dock expose-group-by-app bool true | |
| write_default com.apple.dock mru-spaces bool false | |
| write_default com.apple.dock autohide-time-modifier float 0.4 # overridden earlier but safe | |
| write_default com.apple.dock showhidden bool true | |
| write_default com.apple.dock show-recents bool false | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| # 6. 截圖 | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| write_default com.apple.screencapture disable-shadow bool true | |
| write_default com.apple.screencapture type string jpg | |
| write_default com.apple.screencapture location string "${HOME}/Pictures/Screenshots" | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| # 7. 遙測 / 隱私 | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| write_default com.apple.AdLib forceLimitAdTracking bool true | |
| write_default com.apple.Assistant "Assistance Allowed" bool false | |
| write_default com.apple.Safari UniversalSearchEnabled bool false | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| # 8. 選單列時鐘 | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| write_default com.apple.menuextra.clock DateFormat string "EEE d MMM h:mm a" | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| # 9. 其他雜項(來自 gist 的補充) | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| # 來自 alllex的gist 其他實用默認值快照 | |
| # 禁用自動存儲檢查、啟用 WebKit 開發工具等 | |
| write_default com.apple.appstore WebKitDeveloperExtras bool true | |
| write_default com.apple.appstore ShowDebugMenu bool true | |
| write_default com.apple.SoftwareUpdate AutomaticCheckEnabled bool true | |
| write_default com.apple.SoftwareUpdate AutomaticDownload int 1 | |
| write_default com.apple.SoftwareUpdate CriticalUpdateInstall int 1 | |
| write_default com.apple.SoftwareUpdate ConfigDataInstall int 1 | |
| write_default com.apple.commerce AutoUpdate bool true | |
| write_default com.apple.commerce AutoUpdateRestartRequired bool true | |
| # 防止裝置插入時照片應用程式開啟 | |
| defaults -currentHost write com.apple.ImageCapture disableHotPlug -bool true | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| # 10. 重新啟動受影響的應用 | |
| # ──────────────────────────────────────────────────────────────────────────────── | |
| echo "[INFO] restarting affected apps..." | |
| killall Finder Dock SystemUIServer cfprefsd 2>/dev/null || true | |
| echo "Done. Some changes require logout or restart to take full effect." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment