#!/usr/bin/env bash set -euo pipefail OS="$(uname)" ARCH="$(uname -m)" echo "==> OS: $OS / ARCH: $ARCH" # ────────────────────────────────────────────────────────────────── # 헬퍼 함수 # ────────────────────────────────────────────────────────────────── ensure() { # PATH 및 알려진 경로에서 검색 if command -v "$1" &>/dev/null \ || [ -x "/usr/local/go/bin/$1" ] \ || [ -x "$HOME/.cargo/bin/$1" ] \ || [ -x "$HOME/go/bin/$1" ] \ || [ -x "$HOME/.local/bin/$1" ] \ || [ -x "$HOME/.local/kitty.app/bin/$1" ]; then echo " ✓ $1 이미 설치됨" return 1 fi echo " → $1 설치 중..." return 0 } # ────────────────────────────────────────────────────────────────── # macOS # ────────────────────────────────────────────────────────────────── install_macos() { echo "==> macOS 환경 설치 시작" # Homebrew if ! command -v brew &>/dev/null; then echo "==> Homebrew 설치 중..." /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" fi brew update BREW_PACKAGES=( # 기본 도구 git curl wget unzip make stow # 셸 & 터미널 fish tmux # 에디터 neovim # 검색 & 탐색 fzf ripgrep fd bat eza tree # Git 도구 gh git-delta lazygit # 데이터 처리 jq yq # 인프라 kubectl helm vault # 개발 런타임 node uv go rust # SSH trzsz-ssh # 모니터링 htop ) for pkg in "${BREW_PACKAGES[@]}"; do brew list "$pkg" &>/dev/null || brew install "$pkg" done # cask 앱 brew list kitty &>/dev/null || brew install --cask kitty # Claude Code if ! command -v claude &>/dev/null; then npm install -g @anthropic-ai/claude-code fi # fish를 기본 셸로 등록 FISH_PATH="$(brew --prefix)/bin/fish" if ! grep -q "$FISH_PATH" /etc/shells; then echo "$FISH_PATH" | sudo tee -a /etc/shells fi echo "==> macOS 설치 완료" } # ────────────────────────────────────────────────────────────────── # Linux (Debian/Ubuntu) # ────────────────────────────────────────────────────────────────── install_linux() { echo "==> Linux 환경 설치 시작" sudo apt-get update # ── apt 일괄 설치 ── APT_PACKAGES=( # 기본 도구 git curl wget unzip make gcc stow # 셸 & 터미널 fish tmux # 검색 & 탐색 fzf ripgrep fd-find tree # Git 도구 gh git-delta # 데이터 처리 jq # 모니터링 htop # 클립보드 xclip # Python 기본 python3 python3-pip python3-venv ) sudo apt-get install -y "${APT_PACKAGES[@]}" # ── fd 심링크 (Debian은 fdfind) ── if command -v fdfind &>/dev/null && ! command -v fd &>/dev/null; then sudo ln -sf "$(which fdfind)" /usr/local/bin/fd fi # ── bat (Debian은 batcat) ── if ! command -v bat &>/dev/null; then sudo apt-get install -y bat 2>/dev/null || sudo apt-get install -y batcat if command -v batcat &>/dev/null && ! command -v bat &>/dev/null; then sudo ln -sf "$(which batcat)" /usr/local/bin/bat fi fi # ── eza ── if ensure eza; then sudo apt-get install -y eza 2>/dev/null || { sudo mkdir -p /etc/apt/keyrings wget -qO- https://raw.githubusercontent.com/eza-community/eza/main/deb.asc | sudo gpg --dearmor -o /etc/apt/keyrings/gierens.gpg echo "deb [signed-by=/etc/apt/keyrings/gierens.gpg] http://deb.gierens.de stable main" | sudo tee /etc/apt/sources.list.d/gierens.list sudo apt-get update && sudo apt-get install -y eza } fi # ── neovim (최신) ── if ensure nvim; then curl -Lo /tmp/nvim.tar.gz https://github.com/neovim/neovim/releases/latest/download/nvim-linux-x86_64.tar.gz sudo tar -C /usr/local --strip-components=1 -xzf /tmp/nvim.tar.gz rm -f /tmp/nvim.tar.gz fi # ── kitty ── if ! command -v kitty &>/dev/null && [ ! -x "$HOME/.local/kitty.app/bin/kitty" ]; then echo " → kitty 설치 중..." curl -L https://sw.kovidgoyal.net/kitty/installer.sh | sh /dev/stdin fi # ── yq ── if ensure yq; then local yq_arch="amd64" [ "$ARCH" = "aarch64" ] && yq_arch="arm64" curl -Lo /tmp/yq "https://github.com/mikefarah/yq/releases/latest/download/yq_linux_${yq_arch}" sudo install /tmp/yq /usr/local/bin/yq rm -f /tmp/yq fi # ── lazygit ── if ensure lazygit; then LAZYGIT_VERSION=$(curl -s "https://api.github.com/repos/jesseduffield/lazygit/releases/latest" | jq -r '.tag_name' | sed 's/^v//') local lg_arch="x86_64" [ "$ARCH" = "aarch64" ] && lg_arch="arm64" curl -Lo /tmp/lazygit.tar.gz "https://github.com/jesseduffield/lazygit/releases/download/v${LAZYGIT_VERSION}/lazygit_${LAZYGIT_VERSION}_Linux_${lg_arch}.tar.gz" tar -xzf /tmp/lazygit.tar.gz -C /tmp lazygit sudo install /tmp/lazygit /usr/local/bin/lazygit rm -f /tmp/lazygit.tar.gz /tmp/lazygit fi # ── Node.js (이미 없으면) ── if ensure node; then curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - sudo apt-get install -y nodejs fi # ── uv (Python 패키지 매니저) ── if ensure uv; then curl -LsSf https://astral.sh/uv/install.sh | sh fi # ── Go ── if ensure go; then local go_arch="amd64" [ "$ARCH" = "aarch64" ] && go_arch="arm64" GO_VERSION=$(curl -s 'https://go.dev/VERSION?m=text' | head -1) curl -Lo /tmp/go.tar.gz "https://go.dev/dl/${GO_VERSION}.linux-${go_arch}.tar.gz" sudo rm -rf /usr/local/go sudo tar -C /usr/local -xzf /tmp/go.tar.gz rm -f /tmp/go.tar.gz fi # ── Rust ── if ensure cargo; then curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y fi # ── HashiCorp Vault CLI ── if ensure vault; then wget -qO- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list sudo apt-get update && sudo apt-get install -y vault fi # ── tssh (trzsz-ssh) ── if ensure tssh; then curl -Ls https://raw.githubusercontent.com/trzsz/trzsz-ssh/main/install.sh | sudo bash fi # ── Claude Code ── if ensure claude; then npm install -g @anthropic-ai/claude-code fi echo "==> Linux 설치 완료" } # ────────────────────────────────────────────────────────────────── # fisher & fish 플러그인 # ────────────────────────────────────────────────────────────────── install_fish_plugins() { echo "==> Fisher 및 fish 플러그인 설치 중..." if command -v fish &>/dev/null; then FISH_BIN=fish elif [ -x /opt/homebrew/bin/fish ]; then FISH_BIN=/opt/homebrew/bin/fish else echo " !! fish를 찾을 수 없습니다. 스킵." return fi # stow된 플러그인 파일은 fisher와 충돌하므로, # fish 플러그인은 stow가 아닌 fisher로 관리한다. # fish_plugins 파일만 dotfiles에서 가져오고, 실제 설치는 fisher가 처리. FISH_PLUGIN_DIRS=(completions conf.d functions) for dir in "${FISH_PLUGIN_DIRS[@]}"; do find "$HOME/.config/fish/$dir" -maxdepth 1 -type l -delete 2>/dev/null || true done $FISH_BIN -c ' if not functions -q fisher curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source fisher install jorgebucaran/fisher end fisher update ' echo "==> fish 플러그인 설치 완료" } # ────────────────────────────────────────────────────────────────── # stow 적용 # ────────────────────────────────────────────────────────────────── apply_dotfiles() { echo "==> dotfiles 심링크 적용 중..." SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" cd "$SCRIPT_DIR" # fish 플러그인 파일은 fisher가 관리하므로 stow 충돌 방지 mkdir -p "$HOME/.config/fish" stow -v --adopt -t "$HOME" fish git kitty nvim tmux git checkout -- . echo "==> dotfiles 적용 완료" } # ────────────────────────────────────────────────────────────────── # OS별 gitconfig.local # ────────────────────────────────────────────────────────────────── setup_gitconfig_local() { if [ ! -f "$HOME/.gitconfig.local" ]; then echo "==> .gitconfig.local 생성 중..." if [ "$OS" = "Darwin" ]; then cat > "$HOME/.gitconfig.local" <<'EOF' [credential] helper = osxkeychain EOF else cat > "$HOME/.gitconfig.local" <<'EOF' [credential] helper = store EOF fi fi } # ────────────────────────────────────────────────────────────────── # 메인 # ────────────────────────────────────────────────────────────────── case "$OS" in Darwin) install_macos ;; Linux) install_linux ;; *) echo "지원하지 않는 OS: $OS"; exit 1 ;; esac apply_dotfiles install_fish_plugins setup_gitconfig_local echo "" echo "========================================" echo " 설치 완료! 설치된 도구 목록:" echo "========================================" echo "" echo " 셸/터미널 : fish, tmux, kitty" echo " 에디터 : neovim" echo " 검색/탐색 : fzf, ripgrep, fd, bat, eza, tree" echo " Git : git, gh, delta, lazygit" echo " 데이터 : jq, yq" echo " 런타임 : node, python3, uv, go, rust" echo " 인프라 : kubectl, helm, vault" echo " AI : claude (Claude Code)" echo " 기타 : tssh, htop, stow" echo "" echo " 새 셸을 열거나 'source ~/.config/fish/config.fish' 를 실행하세요."