Apple Silicon Mac開発環境移行ガイド： rsyncとBrewで作る最小限の開発環境

新しいMacの配布に備えて、開発環境の移行手順を残しておきます。
Intel MacやRosettaのファイルが混在している環境では、標準の移行ツールだと予期せぬ不具合が発生する恐れがあるので、バックアップツールは使わずにやっていきたいと思います。

基本的には、下記手順で考えてます。

- rsyncで必要そうなファイルを転送
- brew管理下のファイルリストを出力＆復元
- グローバルインストールしてるパッケージリストを出力＆復元


# rsyncでファイル転送
新しいMacのIPアドレスとユーザ名を調べてからrsyncで送りつけよう。
実行前に**修正ポイント**を踏まえて、編集してから実行して下さい。

## 修正ポイントと注意

- USERとIP_ADDRESSを自分の新しい環境に書き換え
  - `whoami`, `ip a show en0`等を駆使
- DIRSは、必要に応じて各自編集
- **`~/Library/Application Support` はファイルがロックされて上手くいかない可能性があるので、問題が出たら手動でコピーしてください:bow:**
- Mac標準のrsyncはv2.6.9と古いので、brewでインストールしておく
- rsyncはsshが必要なので、転送先の~/authorized_keysに転送元の公開キーを書いておく
- `Application Support`は`Application Support.bk`としてコピー



## スクリプト

事前にbrewでrsyncとparallelを入れておいてください

```bash
brew install rsync parallel
```

```bash:run-rsync.sh
# 新しいMac側のUSERとIP_ADDRESSを設定
USER="MYUSERNAME"
IP_ADDRESS="192.168.x.xxx"

# 転送したいディレクトリの配列
DIRS=(
  "~/Documents"
  "~/Downloads"
  "~/Desktop"
  "~/Pictures"
  "~/Movies"
  "~/Music"
  "~/.config"
  "~/src"

  # 開発環境設定
  "~/.ssh"
  "~/.aws"
  "~/.anyenv"
  "~/.asdf"
  "~/.mise"
  "~/.vscode"

  # 言語環境
  "~/.nodebrew"
  "~/.npm"
  "~/.yarn"
  "~/.rbenv"
  "~/.pyenv"
  "~/.goenv"
  "~/.cargo"
  "~/.gradle"
  "~/.m2"
  "~/.composer"
  "~/.bundle"
  "~/.local"

  # DB
  "/opt/homebrew/var/db"
  "/opt/homebrew/var/mysql"
  "/opt/homebrew/var/postgresql@14"

  # コンテナ関連
  "~/.docker"
  "~/OrbStack"

  # その他の開発ツール
  "~/bin"
  "~/certs"
)

BK_DIRS=(
  # アプリケーション設定
  "~/Library/Application Support"
  "~/Library/Preferences"

  # メール関連
  "~/Library/Mail"
  "~/Library/Containers/com.apple.mail"
)

FILES=(
  "$HOME/.zshenv"
  "$HOME/.zsh_history"
  "$HOME/.bashrc"
  "$HOME/.gitconfig"
  "$HOME/.gitconfig_local"
  "$HOME/.my.cnf"
  "$HOME/.tmux.conf"
  "$HOME/.Brewfile"
)

# 一時ファイルを作成
EXCLUDE_FILE=$(mktemp /tmp/exclude-list.XXXXXX)

# スクリプト終了時に一時ファイルを削除
trap 'rm -f "$EXCLUDE_FILE"' EXIT

# exclude listを一時ファイルに作成
cat >"$EXCLUDE_FILE" <<EOF
.DS_Store
dist
build
node_modules
Trash
.Trash
vendor
EOF

export USER IP_ADDRESS EXCLUDE_FILE

sync_directory() {
  local dir=$1
  local expanded_dir=$(eval echo ${dir})
  if [ ! -e "$expanded_dir" ]; then
    echo "WARNING: ${dir} が存在しません"
    return
  fi
  echo "転送中: ${dir}/"
  rsync -avzP --exclude-from="$EXCLUDE_FILE" "${expanded_dir}/" "${USER}@${IP_ADDRESS}:${dir}/"

}

export -f sync_directory

sync_directory_with_bk() {
  local dir=$1
  local expanded_dir=$(eval echo ${dir})
  if [ ! -e "$expanded_dir" ]; then
    echo "WARNING: ${dir} が存在しません"
    return
  fi

  # パスの最後のディレクトリ名を取得し、.bkを付加
  local dirname=$(basename "$dir")
  local parent_dir=$(dirname "$dir")
  local bk_path="${parent_dir}/${dirname}.bk"

  echo "転送中: ${dir}/ → ${bk_path}/"
  rsync -avzP --exclude-from="$EXCLUDE_FILE" "${expanded_dir}/" "${USER}@${IP_ADDRESS}:${bk_path}/"
}

export -f sync_directory_with_bk

# 並列実行
printf "%s\n" "${DIRS[@]}" | parallel -j 4 sync_directory

# バックアップディレクトリの転送（.bk付き）
printf "%s\n" "${BK_DIRS[@]}" | parallel -j 4 sync_directory_with_bk

# 個別ファイル
expanded_files=()
for file in "${FILES[@]}"; do
  expanded_files+=($(eval echo ${file}))
done

rsync -avzP "${expanded_files[@]}" "${USER}@${IP_ADDRESS}:~/"

```

# brew管理下のツールの移行
母艦のMacでbrewを使ってる方がほとんどかと思います。
brew bundle dumpでMacにインストールされているパッケージやアプリケーションの一覧を.Brewfileとして出力します。

## バックアップ元

```bash:.Brewfileに出力
brew bundle dump --global --force
```

## リストア先
リストア先のMacに~/.Brewfileをおいてから作業してね

```bash:brew bundleで復元
# 新しいMacにHomebrewをインストール
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Rosettaのインストール（必要な場合）
softwareupdate --install-rosetta

# .Brewfileから復元
brew bundle --global
```

# 各パッケージマネージャーをインストール
個人的に使うやつなので、必要な人は適当に入れてください

```bash
npm install -g pnpm
npm install -g yarn
curl -fsSL https://bun.sh/install | bash
curl https://mise.run | sh
```

# globalインストールされてるパッケージをバックアップ＆リストア

## npm

npmのglobal管理されてるパッケージリストをバックアップして、新しいMacでリストアします。

```bash
# バックアップ
npm list -g --json > ~/.config/global-package.json

# リストア
brew install jq
jq -r '.dependencies | to_entries | .[] | "\(.key)@\(.value.version)"' ~/.config/global-package.json | xargs npm install -g
```

## pip

直近はグローバルでインストールしてないけど、コマンドだけメモ
良い子はvenvで環境分けようね。

```bash
# バックアップ
pip list --format=json > requirements.txt

# リストア
pip install -r requirements.txt
```

## pipx

pipxで実行してた人は、こちらも同じようにリストを作ってからリストア

```bash
# バックアップ
brew install pipx
pipx list --json | jq -r '.venvs | keys[]' > ~/.config/pipx-package.txt

# リストア
cat ~/.config/pipx-package.txt | xargs pipx install
```