🐷

Claude CodeでMacbookの設定をAnsibleとchezmoiに移行してみた

に公開

ある日、Macbookのキーボード設定が壊れてしまいました。日本語配列キーボードなのに英語配列だと認識されてしまいます。Claudeに尋ねて色々試しましたが駄目でした。Karabiner-Elementsでキーバインドを変更しても反映されません。Macbook上でアカウントを新規作成すると日本語配列が復活したので、おそらく今のアカウント設定が壊れてしまったのでしょう。

新規アカウントで設定し直してもよかったのですが、使わなくなったアプリケーションが残っていたり、ディスク容量が7割をこえていたので、この機に再セットアップすることにしました。
再セットアップ自体は簡単ですが、その後の設定が面倒です。macOS特有の機能は邪魔なので軒並みオフにしていますし、キーボードやトラックパッドなどの設定もチューニングしています。アプリケーションを一つずつインストールするのも手間がかかります。

Macbookの設定をAnsibleに移行する

そこでAnsibleによるInfrastructure as Code(IaC)化をすることにしました。Ansibleを選択した理由は以下です。

  1. Macbookの設定をコード化できる
  2. 冪等性がある
  3. コマンドのインストールが簡単
  4. 以前に使ったことがあり慣れている

1つ目について、Ansibleにはosx_defaultsモジュールが提供されているので、macOS特有の設定をコード化できます。アプリケーションはHomebrew Caskによるインストールで同じくコードとして落とし込めます。

2つ目について、dotfilesリポジトリに install.sh のようなインストールスクリプトは作っていたのですが、セットアップ後の初回実行用なのでほとんどメンテナンスしていません。何度も実行できるようにif文とかで条件分岐を入れるのは面倒ですが、Ansibleは冪等性が考慮されているので何度同じように実行しても安心です。

3つ目について、セットアップするためのツールをインストールするために、あれこれインストールするのは本末転倒です。AnsibleはPython製ですが、Macbookにpython3が入っていますし、Homebrewでインストールできます。

Claude Codeで現Macbookの設定状態をAnsibleに落とし込む

AnsibleでMacbookの設定をコード化できるものの、今の状態を落とし込むのは面倒です。そこで、Claude Codeにdefaults readコマンドを実行してもらい、今のMacbookの状態をAnsibleとして実行できるようにコード化してもらうことにしました。Homebrewでインストールしたものは $(brew --prefix)/bin、アプリケーションは /Applications ディレクトリをスキャンしてYAMLファイルにまとめてもらいました。

Ansibleの設定

先ほど書いたように、Claude Codeを使って現状のMacbookの設定状態をAnsibleに反映しました。設定ファイルの作成もすべてClaude Codeにお任せです。以下のようなディレクトリ構成になりました。

ansible
├── ansible.cfg
├── group_vars
│   └── all.yml
├── mac.yml
├── requirements.yml
└── roles
    ├── apps
    │   └── tasks
    │       └── main.yml
    ├── homebrew
    │   └── tasks
    │       └── main.yml
    ├── macos_defaults
    │   ├── handlers
    │   │   └── main.yml
    │   └── tasks
    │       └── main.yml
    └── mise
        └── tasks
            └── main.yml
  • group_vars/all.yml: パッケージリスト
    • homebrew_packages: Homebrew でインストールするパッケージ一覧
    • homebrew_cask_packages: Homebrew Caskでインストールするパッケージ一覧
    • homebrew_cask_packages_sudo: Homebrew Caskでインストールするパッケージでsudoが必要なもの
    • mise_global_tools: miseによる管理一覧
    • pipx_packages: pipxでインストールされるもの
  • roles/apps: シェル環境で必要な設定
  • roles/homebrew: Homebrew設定
  • roles/macos_defaults: macOS特有の設定
  • roles/mise: miseの設定

実行コマンド

$ ansible-playbook mac.yml -K
# sudoパスワードを2回入力する ※ 要改善
BECOME password:
sudo パスワードを入力してください(sudo が必要な Cask をスキップする場合は空欄) []:

以下に、実装例の一部を紹介します。

homebrewの実装例

group_vars/all.yml

---
homebrew_packages:
  - bat
  - direnv
  - eza
  - ripgrep
  - sk # skim
homebrew_cask_packages:
  - ghostty
  - intellij-idea
  - visual-studio-code
homebrew_cask_packages_sudo:
  - docker-desktop

roles/homebrew/tasks/main.yml

---
- name: Homebrew パッケージをインストール
  community.general.homebrew:
    name: "{{ homebrew_packages }}"
    state: present
    update_homebrew: true

- name: Homebrew Cask アプリをインストール
  community.general.homebrew_cask:
    name: "{{ item }}"
    state: present
  loop: "{{ homebrew_cask_packages }}"
  ignore_errors: true

- name: sudo が必要な Homebrew Cask アプリをインストール
  community.general.homebrew_cask:
    name: "{{ item }}"
    state: present
    sudo_password: "{{ sudo_password }}"
  loop: "{{ homebrew_cask_packages_sudo }}"
  when: sudo_password != ""
  ignore_errors: true

macos_defaultsの実装例

roles/macos_defaults/handlers/main.yaml

---
- name: Finder を再起動
  shell: killall Finder
  ignore_errors: true

- name: Dock を再起動
  shell: killall Dock
  ignore_errors: true

- name: SystemUIServer を再起動
  shell: killall SystemUIServer
  ignore_errors: true

- name: cfprefsd を再起動
  shell: killall cfprefsd
  ignore_errors: true

roles/macos_defaults/tasks/main.yml

---
# ---- Finder ----
- name: Finder で隠しファイルを表示する
  community.general.osx_defaults:
    domain: com.apple.finder
    key: AppleShowAllFiles
    type: string
    value: "true"
  notify: Finder を再起動

# ---- Dock ----
- name: Dock を自動非表示にする
  community.general.osx_defaults:
    domain: com.apple.dock
    key: autohide
    type: bool
    value: true
  notify: Dock を再起動

# ---- デスクトップ ----
- name: ネットワークドライブに .DS_Store を作らない
  community.general.osx_defaults:
    domain: com.apple.desktopservices
    key: DSDontWriteNetworkStores
    type: string
    value: "true"

# ---- キーボード ----
- name: キーリピートの間隔を短くする
  community.general.osx_defaults:
    domain: NSGlobalDomain
    key: KeyRepeat
    type: float
    value: 2

# ---- トラックパッド----
- name: トラックパッドの速度を設定する
  community.general.osx_defaults:
    domain: NSGlobalDomain
    key: com.apple.trackpad.scaling
    type: float
    value: 2.5

# ---- メニューバー 時計 ----
- name: メニューバーの時計に AM/PM を表示する
  community.general.osx_defaults:
    domain: com.apple.menuextra.clock
    key: ShowAMPM
    type: int
    value: 1
  notify: SystemUIServer を再起動

# ---- キーボードショートカット (AppleSymbolicHotKeys) ----
- name: Spotlight ショートカットを無効にする (key 27)
  ansible.builtin.shell: >-
    defaults write com.apple.symbolichotkeys AppleSymbolicHotKeys -dict-add 27
    '<dict><key>enabled</key><false/><key>value</key><dict><key>parameters</key><array><integer>64</integer><integer>33</integer><integer>1048576</integer></array><key>type</key><string>standard</string></dict></dict>'
  changed_when: true
  notify: cfprefsd を再起動

dotfilesをchezmoiに移行する

シェル環境は dotfiles リポジトリで管理しています。この機にdotfilesの管理を chezmoi に移行しました。今までは dotfiles リポジトリから各ドットファイルにシンボリックリンクを貼っていましたが、chezmoi なら chezmoi apply で反映できるので管理が楽です。 chezmoiの慣習に倣って dot_xxxx というファイル名にリネームしました。そのあたりもすべてClaude Codeにお任せしました。

変更が終わったら、chezmoi apply で変更完了です。

シェル環境で変えたこと

今回はすべての設定を移行したわけではなく、一部のツールを変えてみました。

  • ターミナル
    • iTerm2からGhosttyに移行しました。iTerm2の設定もClaude CodeでGhostty仕様に変えてもらいました
  • インクリメンタルサーチ
    • Ghosttyでtmux有効したときにpecoがpanicで落ちるので、skimに変更しました
  • Rust製ツール: エイリアスでそのまま使えるようにしています

bootstrapスクリプトで一発インストールする

新規セットアップしたMacbookにこれらの設定をゼロから構築するためのbootstrapスクリプトを用意しました。次のコマンド一つですべて動くようにしています。途中でsudoパスワードの入力やGitHub CLI(gh)のログインを求められるので、完全自動化になっていません。

/bin/bash -c "$(curl -fsSL https://gist.githubusercontent.com/ohsawa0515/411eecc5dd76196bcc6d31ba31e9a924/raw/bootstrap.sh)"

最後に

動かすために3回ぐらいMacbookを初期化しました。昔EC2インスタンスをゼロから構築していたのを思い出して懐かしい気持ちになりました。
時間はかかりましたが、環境の吸い上げからコード化、コマンド実行までClaude Codeがやってくれたので、片手間でできてとても楽ちんでした。

Discussion