背景
愛用していた MBP15" が一ヶ月ほど前に突然亡くなり、急遽 MBP13" を買って環境構築を行ったので記録しておく。
(その後噂の薄くて軽くて新しい Macbook が出ただけでなく MBP13" までマイナーアップデートされたりしたが、悔しくはない。悔しくはないぞ!!)
Brewfile オワコン問題
開発環境の構築は Homebrew と Homebrew Cask を入れて Brewfile を書き、 brew bundle
すれば終わりかと思いきや、もう Brewfile はオワコンになってしまったらしい。
(3/25 追記) Brewfile がオワコンなのではなく Homebrew 本体から bundle コマンドが外されただけで、 元となった brewdle コマンドは健在で、もっと便利な brew-file もあるとのことです。 参考: Brewfileはオワコンではない
Ansible でできる?
しかし開発環境の構築は可能な限り手作業を減らしたい。Brewfile 相当のシェルスクリプトを書いても良いが、少し調べてみると Ansible に homebrew モジュール があり、 Ansibleでhomebrewを管理する ことができるらしい。実際に Macの環境構築をAnsibleでやることにした 方もいるようだ。
そして hnakamur さんが AnsibleでHomebrew, Cask, Atomエディターのパッケージを管理する 自動化タスクを再利用可能な形で Ansible Galaxy に公開されていることもわかった。 他にも osxc - simple configuration tool for os x というツールがあるらしい (これも Ansible ベース)。これらを踏まえて、自動化に取り組んでみる。
自動化の前に手で入れた(入れてしまった)もの
なお、下記のアプリは仕事上すぐに必要だったので、後述の自動化の仕組みに入れずに手で入れてしまった。
- dropbox
- 1Password
自動化準備
XCode
Homebrew を入れるためにまず Mac App Store から XCode をインストール。長い時間待ってダウンロードが終わったら一度立ち上げ、ライセンスに同意しておく。後にわかったことだが Mac Yosemite Rails 最新環境 詳解 構築手順 によると、立ち上げずとも下記コマンドでライセンス同意できるらしい (未確認)。
sudo xcodebuild -license
Homebrew
Xcode の Command Line Tool を入れ、その後から homebrew をワンライナーで入れる。
xcode-select --install ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
Homebrew インストール後に brew doctor
コマンドを叩き、古いと言われた場合は brew update
する。
brew doctor brew update
Ansible
Ansible でプロビジョニングを行うため python と ansible を入れる。二つとも Homebrew で入る。
brew install python brew install ansible
自動化開始
プロビジョニング用のディレクトリを作る。名前は何でも良いが .macbook-provisioning
としておく。
mkdir .macbook-provisioning cd .macbook-provisioning/
以降の作業は .macbook-provisioning
ディレクトリで行う。
inventory ファイル作成
ansible 用の inventory ファイル (実行対象ホスト指定ファイル) を作る。今回は手元マシンのプロビジョニングなので localhost だけあれば良い。ファイル名は hosts
とする。
echo 'localhost' > hosts
playbook ファイル作成
次に playbook (プロビジョニングの内容) を書く。ファイル名は localhost.yml
とする。(このファイルを含め github に置いておきました twada/macbook-provisioning)
- hosts: localhost connection: local gather_facts: no sudo: no vars: homebrew_taps: - homebrew/binary - homebrew/dupes - caskroom/cask - railwaycat/emacsmacport - sanemat/font homebrew_packages: - { name: readline } - { name: openssl } - { name: openssl, state: linked, install_options: force } - { name: python } - { name: ansible } - { name: coreutils } - { name: git } - { name: zsh, install_options: disable-etcdir } - { name: wget } - { name: curl } - { name: cmake } - { name: autoconf } - { name: automake } - { name: pkg-config } - { name: ctags } - { name: tree } - { name: lv } - { name: nkf } - { name: jq } - { name: go } - { name: direnv } - { name: peco } - { name: hub } - { name: tig } - { name: fish } - { name: rbenv } - { name: ruby-build } - { name: tofrodos } - { name: lha } - { name: flow } - { name: mysql } - { name: sqlite } - { name: redis } - { name: imagemagick } - { name: mercurial } - { name: packer } - { name: xz } - { name: socat } - { name: rlwrap } - { name: w3m } - { name: tmux } - { name: reattach-to-user-namespace } - { name: phantomjs } - { name: graphviz } - { name: autojump } - { name: gibo } - { name: source-highlight } homebrew_cask_packages: - { name: emacs-mac } - { name: iterm2 } - { name: firefox } - { name: google-chrome } - { name: adobe-reader } - { name: java } - { name: skype } - { name: slack } - { name: sourcetree } - { name: gitx } - { name: karabiner } - { name: seil } - { name: flux } - { name: dash } - { name: skitch } - { name: seashore } - { name: atom } - { name: kobito } - { name: webstorm } - { name: phpstorm } - { name: intellij-idea } - { name: vagrant } - { name: virtualbox } tasks: - name: homebrew の tap リポジトリを追加 homebrew_tap: tap={{ item }} state=present with_items: homebrew_taps - name: homebrew をアップデート homebrew: update_homebrew=yes # brew - name: brew パッケージをインストール homebrew: > name={{ item.name }} state={{ item.state | default('latest') }} install_options={{ item.install_options | default() | join(',') if item.install_options is not string else item.install_options }} with_items: homebrew_packages register: brew_result - name: brew パッケージの情報保存先ディレクトリを作成 file: path=brew_info state=directory - name: brew パッケージの情報を保存 shell: brew info {{ item }} > brew_info/{{ item }} with_items: brew_result.results | selectattr('changed') | map(attribute='item') | map(attribute='name') | list # cask - name: homebrew-cask のインストール homebrew: name=brew-cask state=latest - name: cask パッケージをインストール homebrew_cask: name={{ item.name }} state={{ item.state|default('installed') }} with_items: homebrew_cask_packages register: cask_result - name: cask パッケージの情報保存先ディレクトリを作成 file: path=cask_info state=directory - name: cask パッケージの情報を保存 shell: brew cask info {{ item }} > cask_info/{{ item }} with_items: cask_result.results | selectattr('changed') | map(attribute='item') | map(attribute='name') | list # oh-my-zsh - name: oh-my-zsh のインストール shell: curl -L https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh | sh args: creates: ~/.oh-my-zsh/ # Ricty - name: xquartz のインストール (for Ricty) homebrew_cask: name=xquartz - name: fontforge のインストール (for Ricty) homebrew: name=fontforge - name: Ricty のインストール homebrew: name=ricty - name: 生成されたフォントファイルをコピー shell: cp -f $(brew --cellar ricty)/*/share/fonts/Ricty*.ttf ~/Library/Fonts/ args: creates: ~/Library/Fonts/Ricty-Bold.ttf notify: run fc-cache handlers: - name: run fc-cache shell: fc-cache -vf
実行
あとは ansible-playbook コマンドを叩けば、 localhost.yml の内容が次々に実行され、アプリが大量にインストールされる。出力の量は -vv
オプションくらいがちょうど良い気がしている。
HOMEBREW_CASK_OPTS="--appdir=/Applications" ansible-playbook -i hosts -vv localhost.yml
ただ、実際には cask パッケージのいくつかでパスワードを聞かれるので、全自動とまではいかない。
(追記) HomebrewとAnsibleでMacの開発環境構築を自動化する | mawatari.jp によると、コマンド実行時に HOMEBREW_CASK_OPTS="--appdir=/Applications"
を指定した方がよさそう。オプションを指定しないと、アプリケーションによって /Applications
だったり、 ~/Applications
だったりにシンボリックリンクリンクが作られてしまうとのこと。
localhost.yml を軽く説明
- 構造や内容は hnakamur さんの Qiita エントリ をとてもとても参考にしつつ、何が起こるかを自分で把握したいので、またインストール後の処理や cask のオプションも後々明示的に指定できるようにしたかったので、結局ほぼ同じ内容を1ファイルで書いた (hnakamur さんすみません)。同様の理由で osxc も結局使わなかった。
- Ansible の homebrew モジュールのオプションに何が指定できるかは 本家ドキュメント と Ansibleでhomebrewを管理する - 理系学生日記 が参考になる。
- 各パッケージのデフォルトの
state
をlatest
にしているので、既にインストールされているパッケージでも新しいバージョンがある場合はアップデートされる。 - Ricty フォントのインストールは xquartz (cask), fontforge (brew), ricty (brew) の順に入れなければならないので、一発で入らず試行錯誤することになった (ここに書いた定義には、やっとたどり着いた)。
- oh-my-zsh や Ricty のインストール定義のところはファイルの存在チェックを行う creates オプション を使用しているので、一度インストールが終わったら次の回からはスキップされる。
- ATOK 派なので
google-japanese-ime
を入れていない - Emacs 派なので
vim
(ry - Emacs は
railwaycat/emacsmacport
を tap に加えて homebrew-cask で emacs-mac-port をインストールしている。 - ansible で実行すると、 homebrew を使っている人にはおなじみの
brew install
後の出力がないので不安を覚える。この出力内容はbrew info
で見れるので、ファイルにダンプしておく(本当はインストール時の標準出力をファイルに出しておきたい。詳しい人教えてください)。 - (3/24 追記) mawatari さんに プルリクエストいただいて いくつかの問題点を修正しました。 mawatari さんありがとうございます! 参考: HomebrewとAnsibleでMacの開発環境構築を自動化する | mawatari.jp
homebrew-cask の不安な点について
今回 homebrew だけでなく homebrew-cask も使いアプリケーションのインストールまで自動化したが、うまく動かないものや違和感を覚えるものもあることを記しておく。たとえば Chrome の info には次のように出てくる。
$ brew cask info google-chrome google-chrome: latest google-chrome https://www.google.com/chrome/ /opt/homebrew-cask/Caskroom/google-chrome/latest (389 files, 367M) https://github.com/caskroom/homebrew-cask/blob/master/Casks/google-chrome.rb ==> Contents Google Chrome.app (app) ==> Caveats The Mac App Store version of 1Password won't work with a Homebrew-Cask-linked Google Chrome. To bypass this limitation, you need to either: + Move Google Chrome to your /Applications directory (the app itself, not a symlink). + Install 1Password from outside the Mac App Store (licenses should transfer automatically, but you should contact AgileBits about it). $
しかし実際に使ってみると Max App Store で入れた 1Password と組み合わせても期待通り動いたりしているので、実際にやってみないと何ともいえないところがあるのかもしれない。他にも調べてみると homebrew-cask でうまく動かずに使うのをやめたり、ハックして運用で回避する等はよくあるようなので、下記リンクは参考になった(私の場合入れてしまった後なのだが)
入れるアプリにもよるだろうが、まだまだうまくいかないところがあるのだろう。このあたりは自己責任となりそうだ。
ghq + peco
仕事でも個人でも大量のソースコードの読み書きを手元で統一的に扱いたいので ghq を入れる。ディレクトリ構成も、この機会に lestrrat / antipop / miyagawa 方式にした。
- ghq + peco/percol
- ghqを使ったローカルリポジトリの統一的・効率的な管理について - delirious thoughts
- peco、ghq、gh-openの組み合わせが捗る - Webtech Walker
まず .zshrc に以下の設定を追加
export GOPATH=$HOME export PATH=$PATH:$GOPATH/bin
ghq をインストール
go get github.com/motemen/ghq
以下のコマンドで .gitconfig に設定を追加
git config --global user.name "ユーザー名" git config --global user.email "メールアドレス" git config --global ghq.root "~/src"
これで
cd $(ghq list -p | peco)
したり、
ghq get twada/power-assert
したりできるようになった。これは捗る!!
おわりに
あとは秘伝のタレ系の dotfiles を持ってきたりすれば終わり。ただそれら dotfiles もこの際に断捨離した方が良いかなと思っている。
これまで書いてきたオレオレ shell スクリプト等に比べると、やはり冪等性のあるプロビジョニングツールは二周目以降に強いと感じた。冪等性があると、設定に書かれた状態に向かって収束するように動作する。この習性がローカルの開発環境構築にも使えるというのは盲点だった。今回作成した仕組みは何回でも走らせられるので、追加したいパッケージがあるときは追記して実行すればいい。そうでなくとも定期的に実行しておけば環境を新しくしておけるので、かなり便利だ。
ということで、
Mac の開発環境構築自動化における定番である Brewfile がオワコンになっていたが Ansible を使ってまあまあ自動化できた話
でした。