Macの環境を自動構築してみる

## 背景

数年前からずっとMac Bookが欲しいと考えていました。
Apple Storeを覗いてスペックを見たりしながら夢を膨らませていました。
つい先日、満を持して、Mac Book Air M1を購入しました。
新しいPCを購入したあとにやることといえば．．．
そう、環境構築です。
自分専用の使いやすい環境を構築する。
とても楽しいワクワクする作業です。

手作業でポチポチインストールするのも楽しいので環境構築は苦ではなく、むしろ大好きです。
でもそれではこれまでと何も変わりません。
せっかくなので、これまでの仕事で経験してきたIaCの技術を使ってアプリケーションの自動インストールを行ってみようと考えました。

幸い、まだMac Book Air M1が届くまで時間があるようです。
ならばこの到着までの時間を使って、調査やAnsibleの準備を進めてみようと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589067/84f01644-4cac-7f34-c291-4446b4de3fc0.png)

## 参考

[AnsibleでMacの環境構築をしてみた](https://rightcode.co.jp/blog/information-technology/ansible-mac-environment-setup)
[community.general.mas – Manage Mac App Store applications with mas-cli](https://docs.ansible.com/ansible/latest/collections/community/general/mas_module.html)
[チェックモード (「ドライラン」)](https://docs.ansible.com/ansible/2.9_ja/user_guide/playbooks_checkmode.html)
[いつの間にかbrew caskが使用できなくなっていた話](https://www.mathkuro.com/mac/brew-cask-command-error/)

## (番外編)Macとの出会い

就職してからほとんどWindowsに触れていました。
クライアントもサーバも。
職場のPCはともかく、自宅のPCもメーカーPC、自作PC、ショップPCと渡り歩いてきました。
次第にスペック不足に陥ったり、PCが不調になったりと、パーツ交換を繰り返しながら使っていましたが、どうにも手間がかかるなという思いから、買い替えを決意。
どうせプライベートだし、Windowsに固執する必要はない、40歳のときに思い切ってMac(Mac mini Late 2012)に切り替えました。
購入直後からいきなり分解しHDDをSSDに換装、メモリも8GBから16GBに増設したりと、これまでの自作PCの経験を活かしカスタマイズ。
そこからはMacに慣れることや、MacでWindowsを動かす方法を調べたりと、楽しいPCライフが始まり、今に至ります。

## 調査してわかったこと

自動構築はAnsibleを使うようです。
Ansibleでアプリケーションをインストールするのは数多くの先人たちが情報を蓄積してくださっています。
調べたところ、Macはbrewコマンド、brew-caskコマンド、そして、masコマンドを使うことでCLIでアプリケーションをインストールすることができるようです。

Ansibleからはこれらコマンドに対して、インストールしたいアプリケーション情報を引数で渡してあげると良いみたいです。

## masコマンドでインストーするアプリケーションのIDとは？

例えばLINEですが、調べてみると、brewでもbrew-caskでもインストール出来ないようでmasコマンドでインストールするようです。
masコマンドにはアプリケーションのIDが必要です。
では、LINEのIDはどうやったらわかるのでしょうか？

答えは簡単でした。

MacのApp Storeで「LINE」を検索してもわからなかったのですが、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589067/98fbf593-34b3-6684-e4ca-c461df6bd233.png)

ブラウザで検索すれば、URLにIDが表示されていました。
どうやら「LINE」は「539883307」のようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589067/a46dac26-8fa7-4328-d417-10e4286f3766.png)

なので、インストールするコマンドはこの様になるようです。

```
mas install 539883307
```

## Ansibleを実行する前にやっておくこと

アプリケーションの自動インストールはAnsibleでできそうだということがわかりました。
でもその前に、準備することがあります。
アカウントの設定とかもですが、Ansibleを実行する環境は事前に構築しておく必要があります。

ターミナルを起動し、以下のコマンドを実行して環境を整えておきます。
(これも自動化できればよいのですが．．．)

* Command line tools for Xcode

```
$ xcode-select --install
```

* Homebrew

```
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
```

* Homebrew-Cask

```
$ brew install cask
```

* mas-cli

```
$ brew install mas
```

* Ansible

```
$ brew install ansible
```

* Ansible Community(Ansibleでmas-cliを使用するために必要な追加モジュールのインストール)

```
$ ansible-galaxy collection install community.general
- downloading role 'collection', owned by
 [WARNING]: - collection was NOT installed successfully: Content has no field named 'owner'

ERROR! - you can use --ignore-errors to skip failed roles and finish processing the list.
```

このエラーがどうやっても解消できませんでした．．．
Ansibleで「community.general.mas」を使用していたので必要だと思うのですが、どうやってもインストールできませんでした。
仕方がありませんので、こちらのモジュールは使用せず、Ansibleでは純粋に「mas-cli」を使用することにします。

## インストールしたいアプリケーションとインストール方法の調査

今回はクライアント用途のMac Book Air M1へのインストールなのでアプリケーションもそんなにたくさんはありません。
とりあえずは以下のアプリケーションをインストールします。
それぞれのインストール方法も合わせてチェックしておきました。

|アプリケーション|インストール方法|
|----|----|
|Python|brew install python|
|Docker|brew install docker<br>brew cask install docker|
|iTerm2|brew cask install iterm2|
|Typora|brew cask install typora|
|LINE|mas install 539883307|
|CleanArchiver|brew install cleanarchiver|
|VLC|brew cask install vlc|
|Google Chrome|brew cask install google-chrome|
|TweetDeck|mas install 485812721|
|DUET|brew cask install duet|

## Ansible Playbookの作成

どうやらベストプラクティスなのか、以下のような構成を多く見ました。
私もこちらの構成に習って構築してみることにします。

### 構成

```
repositorys
 └──/setup-mac
     ├── README.md
     ├── inventory
     ├── roles
     │   ├── homebrew
     │   │   ├── tasks
     │   │   │   └── main.yml
     │   │   └── vars
     │   │       └── main.yml
     │   ├── homebrew-cask
     │   │   ├── tasks
     │   │   │   └── main.yml
     │   │   └── vars
     │   │       └── main.yml
     │   └── mas
     │       ├── tasks
     │       │   └── main.yml
     │       └── vars
     │           └── main.yml
     └── site.yml
```

### ドライラン

いきなりたくさんのアプリケーションをインストールするのもリスクなので、まずは各モードで一つづつ確認。
確認は母艦のMac miniで実行します。
こちらには既に色々なアプリケーションをインストール済みなので、再インストールになるのが嫌なので、Ansibleのチェックモードで確認します。

## Ansible Playbookのチェック「ドライラン」

```
＄ ansible playbook -i inventory site.yml --check
```

homebrewは問題なし。

```
TASK [homebrew : homebrew install] *********************************************
changed: [xxx.xxx.xxx.xxx] => (item={u'state': u'present', u'name': u'cleanarchiver'})

PLAY RECAP *********************************************************************
xxx.xxx.xxx.xxx                : ok=1    changed=1    unreachable=0    failed=0
```

homebrew-caskはエラー．．．

```
TASK [homebrew-cask : homebrew cask install] ***********************************
failed: [xxx.xxx.xxx.xxx] (item={u'state': u'present', u'name': u'typora'}) => {"failed": true, "item": {"name": "typora", "state": "present"}, "msg": "Error: Unknown command: cask"}
```

どうやらcaskの指定はオプション扱いになったようです。
なので、Ansibleのcommandで「brew install --cask」を実行する形に変更してエラーを回避しました。

```
$ ansible-playbook --check -i inventory site.yml

PLAY [mac setup] ***************************************************************

TASK [homebrew-cask : homebrew cask install] ***********************************
skipping: [xxx.xxx.xxx.xxx] => (item={u'state': u'present', u'name': u'typora'})

PLAY RECAP *********************************************************************
xxx.xxx.xxx.xxx                : ok=0    changed=0    unreachable=0    failed=0
```

masは「mas-cli」を使用することにしたのでエラーなく実行できたようです。
前述のように、本来は「community.general.mas」を使用したかったところですが．．．
残念ですが今回はこれで良しとします。

```
$ ansible-playbook --check -i inventory site.yml

PLAY [mac setup] ***************************************************************

TASK [mas : mas install] *******************************************************
skipping: [192.168.3.3] => (item={u'485812721 state': u'present', u'id': None})

PLAY RECAP *********************************************************************
192.168.3.3                : ok=0    changed=0    unreachable=0    failed=0
```

結果、各ファイルは以下のようになりました。

### /homebrew/tasks/main.yml

```
---
# file: ~/setup-mac/roles/homebrew/tasks/main.yml
 
- block:
  - name: homebrew tap
    homebrew_tap:
      name: "{{ item }}"
      state: present
    with_items: "{{ taps }}"
 
  - name: homebrew install
    homebrew:
      name: "{{ item.name }}"
      state: "{{ item.state }}"
    with_items: "{{ packages }}"
 
  tags:
    - homebrew

```

### /homebrew/vars/main.yml

```
---
# file: ~/setup-mac/roles/homebrew/vars/main.yml
 
taps:
  - homebrew/cask
  - homebrew/cask-versions
  - homebrew/cask-fonts
  - homebrew/core

packages:
  - { name: python, state: present }
#  - { name: cleanarchiver, state: present }
#  - { name: docker, state: present }

```

### /homebrew-cask/tasks/main.yml

```
---
# file: ~/setup-mac/roles/homebrew_cask/tasks/main.yml
 
- block:
 
  - name: homebrew cask install
    command: brew install --cask "{{ item.name }}"
    with_items: "{{ packages }}"
 
  tags:
    - homebrew_cask
```

### /homebrew-cask/vars/main.yml

```
---
# file: ~/setup-mac/roles/homebrew_cask/vars/main.yml
 
packages:
#  - { name: docker, state: present }
  - { name: iterm2, state: present }
  - { name: typora, state: present }
  - { name: vlc, state: present }
  - { name: google-chrome, state: present }
  - { name: duet, state: present }
  - { name: google-japanese-ime, state: present }
  - { name: cleanarchiver, state: present }
```

### /mas/tasks/main.yml

```
---
# file: ~/setup-mac/roles/mas/tasks/main.yml
 
- block:
 
  - name: mas install
    command: mas install "{{ item.id }}"
    with_items: "{{ packages }}"

  tags:
    - mas
```

### /mas/vars/main.yml

```
---
# file: ~/setup-mac/roles/homebrew/vars/main.yml
 
packages:
  - { id: ,539883307 state: present } # LINE
  - { id: ,485812721 state: present } # TweetDeck
  - { id: ,600925318 state: present } # Parallels Client
```

### リモートリポジトリへ登録

今回はリポジトリは使用しませんが、資材の保管先として有志との活動で構築したgitlabを使用します。
予め、gitlab上にリポジトリを作成しておき、空のリポジトリをローカルにcloneし、資材をリモートリポジトリにpushしておきます。
実はこの活動を有志に相談したときに、次のテーマの構想が出てきたため、そのための事前準備として登録しておこうという魂胆です。

```
$ git clone http://xxx.xxx.xxx.xxx/example/setup-mac.git
$ cd setup-mac
$ git config --global user.name "example example"
$ git config --global user.email "example@example.com"
$ git add .
$ git commit -m "first commit"
$ git push
Username for 'http://xxx.xxx.xxx.xxx': example
Password for 'http://example@xxx.xxx.xxx.xxx':
Counting objects: 6, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 469 bytes | 469.00 KiB/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To http://xxx.xxx.xxx.xxx/example/setup-mac.git
   359cfc6..ca5b4de  main -> main
```

## Ansible Playbookの実行

```
＄ ansible playbook -i inventory site.yml
```

無事にインストール完了！と言いたいところですが問題点が2つ。
cleanarchiverとdockerがインストールできませんでした。
terminalで直接実行してもだめです。
調べてみたところ、どうやらM1チップはちょっと癖があるようで、Intelチップと同じことが出来ないらしいです。
今回はDockerは手動でDocker Desktopをインストールしてコンテナを利用可能にしておきました。
そして、このあと、コンテナの利用でも問題が発覚することになります．．．
cleanarchiverは．．．今度調べてみます。

## おわりに

今回の検証でMacへのアプリケーションのインストールはAnsibleで行えることがわかりました。
brewなのか、brew caskなのか、masなのか、masならIDを調べる、この段取りでなんとかなりそうだとわかりました。

もし、Mac Book Air M1の調子が悪くなり、再インストールすることになったとしても、このAnsible Playbookがあればすぐに同じ環境を構築することが出来ます。

さらに、アプリケーションを追加インストールしたければ、各vars/main.ymlに情報を追記してansible-playbookを再実行すればよいだけです。

Ansible Playbookは本来ならGitHubなどに登録しておき、必要に応じてgit cloneすればよいのですが、今回は完全に個人用だし、Ubuntu Serverにでも保存しておき、必要に応じて更新して再利用可能な状態にしておき、大事に保管しておきます。