自宅のWindowsを開発機にする その2: Macの開発環境を作業状態ごとWSLに移す

この記事でわかること
- cloneし直しではなくrsyncで作業状態ごと移した理由
- 除外設定で転送量を48GBから13GBにした話
- volta/pyenv/tfenvをmiseにまとめてリポジトリごとに自動切替
- マシン専用のGitHub鍵と、ディレクトリごとのgitのコミット者切替
その1の状態(Macから ssh desktop でWSLに入れる)
この記事は「自宅のWindowsを開発機にする」連載のその2です。その1で Mac とスマホから WSL に入れるようになったので、今回はそこに Mac の開発環境をそのまま持っていきます。
移すもの
Mac のデスクトップには、個人開発の private(約9GB)と、業務委託先ごとのフォルダが入った Job(約48GB、git リポジトリが20個ちょっと)があります。移し先は WSL の ~/dev/private と ~/dev/Job にして、中の形は Mac と揃えました。「あのリポジトリどこだっけ」をなくしたかったので。
clone し直さない理由
最初は GitHub から clone し直せばきれいかなと思ったんですが、棚卸ししてみたらこんな感じでした。
- commit 前の変更が100ファイル超、未 push の commit が100超あるリポジトリがある
- git worktree をいくつも切って並行作業している
.envなど git に入っていない設定ファイルが各所にある
clone だとこれが全部消えるので、作業状態ごと rsync でコピーすることにしました。捨てていいものだけ除外して、あとで入れ直す方針です。
rsync の除外設定
.DS_Store
node_modules/
.venv/
venv/
__pycache__/
.pytest_cache/
.mypy_cache/
.ruff_cache/
.next/
.turbo/
.terraform/
.cache/これだけで Job の 48GB のうち 35GB が除外対象でした。実際に送るのは 13GB ほどで、ほとんど node_modules と仮想環境だったんですよね...
rsync -aH --partial --info=stats2 --exclude-from=excludes.txt ~/Desktop/Job/ desktop:dev/Job/ちなみに Mac に最初から入っている /usr/bin/rsync は今だと openrsync(protocol 29)なので、Homebrew で普通の rsync を入れてから使いました。速度は Wi-Fi と小さいファイルの多さのせいで 4MB/s くらいしか出ず、Tailscale の経路が中継(DERP)になってないか疑ったんですが、tailscale status で見ると direct でした。寝かせておけば終わるので、裏で流しっぱなしにしています。
ツールは mise にまとめる
リポジトリごとに必要なものを洗い出してみると、バージョン指定がなかなかバラバラでした。
- Node: 16 系から 23 系まで(
.nvmrc/.node-version/engines) - Python: 3.11、3.12、3.13(poetry と uv が混在)
- Terraform: 1.7 から 1.14 まで
- パッケージマネージャ: npm / pnpm 8・9 / yarn 1 / bun
Mac では volta、pyenv、tfenv の3つで回していたんですが、WSL 側は mise 一本にしました。.nvmrc や .python-version、.terraform-version も読んでくれるので、フォルダに入るだけでバージョンが切り替わります。
curl -fsSL https://mise.run | sh
mise settings set idiomatic_version_file_enable_tools node,python,terraform
mise use -g node@22 [email protected] [email protected] bun@latest pnpm@latest
mise use -g tflint@latest tfsec@latest ecspresso@2
# ~/dev 配下の mise.toml は毎回「信頼しますか」と聞かれないように
mise settings add trusted_config_paths /home/koki/devpnpm と yarn は corepack に任せて、packageManager の指定に追従させています。初回に「ダウンロードしていいか」と聞いてきて止まるので、COREPACK_ENABLE_DOWNLOAD_PROMPT=0 も入れました。poetry、pre-commit、ruff は uv tool install です。
sudo が要るもの(apt で入れるビルドツール、DB クライアント、LibreOffice と日本語フォント、ffmpeg、Docker Engine、gh / gcloud / az / aws)は1本のスクリプトにまとめて、WSL 側の tmux で走らせてパスワードだけ手で入れました。Docker は Windows の Docker Desktop ではなく、WSL の中に Docker Engine を直接入れています。systemd が動いていれば普通の Ubuntu と同じように入るので。
依存パッケージの入れ直し
除外した node_modules や .venv は、lockfile を見て入れ直すスクリプトでまとめて入れました。やっていることは単純です。
package-lock.json→npm ci(ダメならnpm install)pnpm-lock.yaml→pnpm install --frozen-lockfileyarn.lock→yarn install --frozen-lockfilebun.lock→bun installuv.lock→uv syncpoetry.lock→poetry install --no-root
各ディレクトリに入ったら mise install してから動かすので、古い Node を指定しているリポジトリもそのバージョンで入ります。失敗しても止めずに最後に一覧を出すようにしていて、private の分は8か所とも一発で通りました。
GitHub はマシン専用の鍵で
WSL には GitHub 用の鍵がなかったので、Mac の鍵をコピーするのではなく、デスクトップ専用の鍵を作って登録しました。どっちかのマシンを手放すときに、その鍵だけ消せば済むので。
# desktop 側
ssh-keygen -t ed25519 -N "" -C "koki@kt-desktop" -f ~/.ssh/id_ed25519
# Mac 側(gh に鍵登録の権限を足してから)
gh auth refresh -h github.com -s admin:public_key
gh ssh-key add kt-desktop.pub --title kt-desktopgh auth refresh はブラウザでの承認が挟まるので、ここも tmux で走らせてワンタイムコードだけ自分で入れています。ちなみに Claude Code の auto mode では、「GitHub アカウントに鍵を足す」操作は認証情報を増やすものとして一度止められて、自分が明示的に OK してから進みました。これはちゃんと止まってくれていいなと思っています。
git のコミット者をディレクトリごとに切り替える
業務委託先のうち1社だけは会社のメールアドレスで commit したかったので、includeIf で切り替えています。
[user]
name = j19015
email = (個人のアドレス)
[includeIf "gitdir:~/dev/Job/<業務委託先A>/"]
path = ~/.gitconfig-<業務委託先A>Mac 側は全部個人のアドレスで commit していたので、移植を機に整理できたのはちょっとよかったです。
コピーが終わったあとにやったこと
Job のコピーは、除外後 14.5GB を Wi-Fi で 4〜6MB/s、だいたい40分で終わりました。で、コピーしただけだとちゃんとは動かなくて、ここからが地味に長かったんですよね。
worktree のパスを付け替える
git worktree は、worktree 側の .git ファイルと、本体側の .git/worktrees/*/gitdir にお互いの絶対パスが書かれています。ここに Mac のパスが入ったままなので、全部 WSL のパスに置き換えました。対象は38ファイルでした。
grep -rlF '/Users/<ユーザー>/Desktop/' --include=gitdir ~/dev/*/.git ~/dev/*/*/*/.git 2>/dev/null
# worktree 側の .git ファイルと、本体側の gitdir を sed で置換
sed -i 's|/Users/<ユーザー>/Desktop/|/home/koki/dev/|g' <対象ファイル>
git worktree list # 全部 prunable にならずに出ればOKgit worktree repair でも直せるんですが、数が多かったのでまとめて置換しています。あと、本体の .git/config の core.hooksPath にも Mac のパスが残っていたので、これも見落とさないほうがいいです。
Mac 由来の git 設定を直す
気づかなかったところが2つありました。
core.ignorecase = trueが31リポジトリに残っていた。 Mac のファイルシステムは大文字小文字を区別しないので git が勝手に true にしているんですが、Linux でこのままだと「大文字小文字だけのリネーム」を見落とします。false に戻しました- グローバルの gitignore を移し忘れていた。 Mac の
~/.config/git/ignoreに**/.claude/settings.local.jsonを入れていたので、desktop でだけ.claude/が未追跡に出てきて、あれっとなりました
for r in $(find ~/dev -name .git -maxdepth 4 -prune | xargs -n1 dirname); do
git -C "$r" config --local core.ignorecase false
done本当に同じ状態か確かめる
最後に、Mac と desktop の全リポジトリで git status --short を取って突き合わせました。差分は、除外した .DS_Store と .turbo/、あと出力の並び順だけ。未 commit の変更もちゃんと同じ状態で移っていたので、ここでようやく安心できました。
Job の依存の入れ直し
上のスクリプトを Job にも流したら、117か所中101か所が一発で通りました。落ちた原因は3種類です。
- corepack の既定の pnpm が新しすぎた。 最新の pnpm 12 が選ばれて、pnpm 8 時代の lockfile(v6)を読めませんでした。そこは
npx pnpm@8で入れています - 設定だけの
pnpm-workspace.yamlが pnpm 9 だとエラーになる。 中身がallowBuildsだけのファイルで、pnpm 9 だとpackages field missingで落ちます。Mac で実際に使っていたのは 10.28.2 だったのでそれに合わせました。決め手は Mac 側のnode_modules/.modules.yamlで、ここに使った pnpm のバージョンが残っているんですよね - uv が Python 3.14 を選んでビルドに失敗する。 Python のバージョン指定がないプロジェクトだと、uv が一番新しい 3.14 を選んで、pyarrow や pydantic-core のビルドで落ちました。
uv sync --python 3.13で解決です
最終的には全部通りました。pnpm の既定は corepack と mise の両方で 10.28.2 に固定しています。ちなみに並行で流していたらデスクトップのファンが唸り出して、ちょっと焦ったんですが、負荷を見たら16コア中3〜4コア分くらいでした笑
移植で学んだこと
- 差分を見るときは「元のマシンで実際に使っていたバージョン」が一番の手がかり。lockfile や
.modules.yamlに残っている - ファイルシステムごとに git が勝手に決める設定(
core.ignorecase)があるので、OS をまたぐときは要注意 - 最後に
git statusを突き合わせると、移植が正しくできたか簡単に確かめられる
次回(その3)は、Claude Code の設定・メモリ・複数アカウントの切替を移した話です。


