vagrant upするだけで、anyenvを使って、phpとrubyが導入済みの開発環境を立ち上げる方法

# はじめに
自宅で遊び用に[Virtualbox](https://www.virtualbox.org/ "Virtualbox")で運用しているFedoraが、ちょっとミスをして起動しなくなってしまいました。そのため、再インストールの必要が出たのですが、いちいち「OSイメージダウンロード」「GUIやCUIでインストーラーで設定」「インストール後にネットワーク設定を色々」「必要なツールをyumでインストール」などが、かなり面倒なので、[Vagrant](https://www.vagrantup.com/ "Vagrant")を使ってしまおうと思い立ったのが始まりです。

さらに、「せっかくVagrantを入れるんだから、OSを入れたからyumやら何やらであれこれ入れるんじゃなく、コマンド一発で[anyenv](https://github.com/riywo/anyenv "anyenv")やPHPやRubyを入れたい」と思って検索してみると、どうも断片的な記事がほとんどで、最初から最後まで網羅的に説明してる記事が見つからず、少し苦労しました。そのため、同じ目的を持ってる他の人にも役に立つ（かも）と思い、自分のメモ書きも兼ねて公開してみました。

# 記事の概要および前提
* ホストはWindows 10、ゲストはUbuntu 18.04となります。
 * OSにこだわりは無いので、使い慣れてるRedhat系を考えていましたが、途中で上手く行かないところや、気に入らないところがあったので、最終的にUbuntuを選択しました。
 * 上手く行かなかった点は、最後で簡単に説明します。
* [Virtualbox](https://www.virtualbox.org/ "Virtualbox")と[Vagrant](https://www.vagrantup.com/ "Vagrant")は、既にインストール済みであり、簡単にVagrantを使用した事がある人が前提とします。そのため、インストール方法や、細かいVagrantのコマンドの内容などは、この記事では説明しません。
 * 自分は「[Vagrant + VirtualBoxでWindows上に開発環境をサクッと構築する](https://qiita.com/ozawan/items/160728f7c6b10c73b97e "Vagrant + VirtualBoxでWindows上に開発環境をサクッと構築する")」を参考とさせていただきました。
 * 共有フォルダを使う時は、vagrant-vbguest もインストールします。
* **目的は、PHPとRubyが動く環境をコマンド一発で構築するところまで持っていく事です**。
  * 単純にaptや、phpenv、rbenvで入れても良かったですが、やはりバージョン切り替え機能が欲しかったのと、将来的にPythonやnode.jsを入れる可能性もあるので、せっかくなので[anyenv](https://github.com/riywo/anyenv "anyenv")で入れてみる。
  * [ansible](https://www.ansible.com/ "ansible")や[Itamae](https://itamae.kitchen/ "Itamae")などの構成管理ツールでインストール及び環境設定をしていくのが今風なのでしょうが、今回は仕事とは無関係の、自宅の個人環境を構築するのが主な目的なので、今回はすべてシェルスクリプトでインストールさせます（前述通り、環境が壊れたことが発端なので、さくっと復活させたい事もあり、習得に時間がかかりそうなansibleは外しました）。

# 環境設定
### 環境変数
boxのダウンロード場所をDドライブに変更します。この辺は好みなので、不要な人は飛ばしてください。

個人の好みですが、あまりCドライブに（厳密に言うと、`C:\Users\ユーザー名\.vagrant.d\boxes`のようなユーザーディレクトリ内に）大きなファイルを起きたくないというのがあるので、Dドライブにboxがダウンロードされるようにするため、環境変数`VAGRANT_HOME`を以下のように設定。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/31051/4d214a51-b814-488a-05dc-9be97ad83f90.png)

VirtualBox側も、`デフォルトの仮想マシンフォルダー`を以下のようにDドライブに設定してます。これで、「boxをダウンロード」するドライブも、「仮想マシンを作る」ドライブも、すべてDドライブとなります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/31051/37d27138-f0f1-dfec-53b9-5f9cb6e073dc.png)

# Vagrant構築

### box
boxには、「[ubuntu/bionic64](https://app.vagrantup.com/ubuntu/boxes/bionic64 "ubuntu/bionic64")」を選択しました。
最初は「[bento/ubuntu-18.04](https://app.vagrantup.com/bento/boxes/ubuntu-18.04 "bento/ubuntu-18.04")」を使おうかと思ったのですが、これはなぜか`apt-get upgrade`実行時、``Setting up keyboard-configuration (1.178ubuntu2.7) ...``というログで止まってしまう現象が起こり、解決しなかったのでboxを`ubuntu/bionic64`変更しました。

### Vagrantfile
任意のフォルダに移動して、コマンドプロンプトで以下を実行すると、`Vagrantfile`ができます。

```powershell
vagrant init ubuntu/bionic64
```
こいつを、以下のように編集します。不要なコメントは除外してます。
`synced_folder`は、自分がプログラムのソースを置く基本フォルダです。必要に応じて変えてください。
この中で、`config.vm.provision`で設定しているシェルスクリプト群がありますが、これが今回の肝となります。

```ruby:Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/bionic64"
  config.vm.network "private_network", ip: "192.168.33.12"
  config.vm.synced_folder "C:\\dev", "/vagrant_dev"

  config.vm.provider "virtualbox" do |vb|
    vb.name = "ubuntu-18.04"
    vb.memory = "2048"
  end

  config.vm.provision "shell", path: "setup_root.sh"
  config.vm.provision "shell", privileged: false, path: "setup_vagrant.sh"
  config.vm.provision "shell", privileged: false, path: "setup_anyenv.sh"
  config.vm.provision "shell", privileged: false, path: "setup_anyenv_etc.sh"
  config.vm.provision "shell", privileged: false, path: "setup_anyenv_sh.sh"
end
```

### シェルスクリプト

`config.vm.provision`で指定されてる部分は、`vagrant up`をしても、最初の一回だけ実行され、2回目以降は実行されません。ただ、`vagrant provision`や`vagrant reload --provision`と指定すれば、その中も実行されます。なので、anyenvのインストール箇所などは、フォルダの存在チェックをして、存在すればインストール処理をスキップする処理を入れてます。

##### setup_root.sh

`config.vm.provision "shell", path: "setup_root.sh"`で実行されるスクリプトです。`privileged: false`を設定してないので、これはrootで実行されます。
anyenv、PHP、Rubyのインストールに必要なものを主に`apt-get`でインストールしてます。ただ、tree、zip、unzipは、個人的に必要なので入れてるだけで、anyenvを入れるだけなら不要（らしい）です。
何度も実行しては、エラーを元に付け足し差し替えを繰り返していたので、不要な物がありそうですし、重複してる物もありますが、とりあえず自分はこれで成功してますので、このまま載せてます。

```shell:setup_root.sh
#!/bin/bash
apt-get update -y
apt-get upgrade -y
apt-get install -y autoconf bison build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm-dev
apt-get install -y libreadline-dev zlib1g-dev
apt-get install -y libsqlite3-dev
apt-get install -y git build-essential libssl-dev
apt-get install -y curl g++ gcc build-essential libcurl4-gnutls-dev libcurl4-nss-dev libcurl4-gnutls-dev libjpeg-dev re2c libxml2-dev libtidy-dev libxslt-dev libmcrypt-dev libreadline-dev libssl-dev zlib1g-dev libyaml-dev libffi-dev bison
apt-get install -y libxml2 libxml2-dev libssl-dev pkg-config zlib1g-dev libbz2-dev libjpeg-dev libpng-dev libreadline-dev libtidy-dev libcurl4-openssl-dev libxslt1.1 libxslt-dev

apt-get install tree
apt-get install zip unzip
apt-get update -y
apt-get upgrade -y
```

##### setup_vagrant.sh

ここ以降の4つのシェルスクリプトは、`privileged: false`と設定しているので、rootではなく、vagrantユーザーで実行されます。

なお、**このスクリプトの部分は、anyenvのインストールとは関係ありません**。ここでは、色設定を指定してます。
今回のboxでインストールしたUbuntuは、ホームディレクトリに.dircolorsが存在しません。
そのため、`ls -la`などのコマンド時にも、色分けがされず、かなり見づらいです。
なので、ファイルの存在チェックをして、.dircolorsが存在しなければ、ファイルを作成します（存在チェックをしないと、provision時に、常に上書きされてしまうので）。

さらに、`.bash_profile`にも、初期状態では`source .bashrc`の記述が無く（というか、どうも初期では`.bash_profile`自体が存在しないらしく）、いちいち`source .bashrc`を実行しないと`.dircolors`が反映されないので、`source .bashrc`の記述を`.bash_profile`に吐き出す処理も追加しました。

こちらも、そのまま吐き出す処理を実行すると、いくつも`source .bashrc`の記述が連続して書き込まれてしまうので、記述の存在チェックをしています。
シェルスクリプトあまり書いたことがなかったので、とりあえず以下の記事を参考にさせていただき、`grep`して`source .bashrc`の記述を検索して、1が帰るかどうかで判定してます。
> [unixコマンドの戻り値は](https://qiita.com/yamao2253/items/05adf89f39c1a6862147 "unixコマンドの戻り値は") (https://qiita.com/yamao2253/items/05adf89f39c1a6862147)

```shell:setup_vagrant.sh
#!/bin/bash
if [ ! -e '/home/vagrant/.dircolors' ]; then
	dircolors -p > /home/vagrant/.dircolors
fi

if [ ! -e '/home/vagrant/.bash_profile' ]; then
	touch /home/vagrant/.bash_profile
fi

grep 'source .bashrc' /home/vagrant/.bash_profile
if [ $? -eq 1 ]; then
	echo 'source .bashrc' >> /home/vagrant/.bash_profile
fi
```

##### setup_anyenv.sh

ここで、いよいよanyenvをインストールします。provision時に、不要なインストールをする羽目にならないように、ディレクトリの存在チェックをしています。

```shell:setup_anyenv.sh
#!/bin/bash
if [ ! -e '/home/vagrant/.anyenv' ]; then
	git clone https://github.com/riywo/anyenv ~/.anyenv
	echo 'export PATH="$HOME/.anyenv/bin:$PATH"' >> ~/.bash_profile
	echo 'eval "$(anyenv init -)"' >> ~/.bash_profile
	source ~/.bash_profile
	exec $SHELL -l
fi
```

##### setup_anyenv_etc.sh

次に、anyenv経由でrbenvとphpenvをインストールします。なお、なぜ「setup_anyenv.sh」で一緒にインストールしてしまわないのかと疑問に思われるかもしれませんが、どうもVagrantのprovision処理では、シェルスクリプトに`exec $SHELL -l`が含まれてると、そこで処理を終了してしまうようなのです。
> [Vagrantfile の Shell プロビジョンで anyenv のインストールなど `exec $SHELL -l` するような処理を書くとき](https://qiita.com/ndxbn/items/5365e5eabd9e54c3f0a6 "Vagrantfile の Shell プロビジョンで anyenv のインストールなど `exec $SHELL -l` するような処理を書くとき") (https://qiita.com/ndxbn/items/5365e5eabd9e54c3f0a6)

なので、ファイルを分けることで対応しています。
`exec $SHELL -l`をせず、`source ~/.bash_profile`だけでも問題ない気もしますが、「[anyenv公式](https://github.com/riywo/anyenv "anyenv")」に沿う形にしています。

ここでも、例によってディレクトリの存在確認をすることで、rbenvとphpenvを何度もインストールする事態になることを防いでいます。

```shell:setup_anyenv_etc.sh
#!/bin/bash
if [ ! -e '/home/vagrant/.anyenv/envs/rbenv' ]; then
	anyenv install rbenv
fi
if [ ! -e '/home/vagrant/.anyenv/envs/phpenv' ]; then
	anyenv install phpenv
fi

source ~/.bash_profile
exec $SHELL -l
```

##### setup_anyenv_sh.sh

最後の処理です。`setup_anyenv_etc.sh`のときと同様の理由で、`exec $SHELL -l`での終了を回避するため、別ファイルにしています。
PHPとRubyのバージョンは、とりあえずこの記事が公開された時点での安定版の最新となります。
バージョン番号は、好みで変えて良いかと思います。別バージョンを入れたいなら、installコマンドを繰り返せば良いです。

```shell:setup_anyenv_sh.sh
#!/bin/bash
ruby -v 1>/dev/null 2>/dev/null
if [ $? -ne 0 ] ; then
	rbenv install 2.5.1
	rbenv global 2.5.1
fi

php -v &> /dev/null
if [ $? -ne 0 ] ; then
	phpenv install 7.2.10
	phpenv rehash
	phpenv global 7.2.10
fi
```

### vagrant up

シェルスクリプトの設置も終わったら、あとはvagrantfileのある場所で、`vagrant up`を実行すれば良いだけです。

```powershell
vagrant up
```

あとは、以下のようなログが流れていくので、しばらく待てば完了するかと思います。

```ruby
==> default: Attempting graceful shutdown of VM...
==> default: Destroying VM and associated drives...
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'ubuntu/bionic64'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'ubuntu/bionic64' is up to date...
==> default: Setting the name of the VM: ubuntu-18.04
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...


# ずらずらとログが流れていきます。


    default: [xdebug]: Installing xdebug configuration in /home/vagrant/.anyenv/envs/phpenv/versions/7.2.10/etc/conf.d/xdebug.ini
    default: [xdebug]: Cleaning up.
    default: [Info]: Enabling Opcache...
    default: [Info]: Done
    default: [Info]: The Log File is not empty, but the Build did not fail. Maybe just warnings got logged. You can review the log in /tmp/php-build.7.2.10.20181014003512.log
    default: [Success]: Built 7.2.10 successfully.
    default: 7.2.10
```
途中でエラーなど起こらなければ、以上で完了です。`vagrant ssh`や、[Rlogin](http://nanno.dip.jp/softlib/man/rlogin/ "Rlogin")などでログインし、PHPやRubyが動くことを確認し、OKなら開発環境の構築は完了です。

```
vagrant@ubuntu-bionic:~$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]

vagrant@ubuntu-bionic:~$ php -v
PHP 7.2.10 (cli) (built: Oct 14 2018 00:49:15) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.10, Copyright (c) 1999-2018, by Zend Technologies
    with Xdebug v2.6.1, Copyright (c) 2002-2018, by Derick Rethans
vagrant@ubuntu-bionic:~$ ls -la
```

# ミスした箇所
注意点と言うか、アホなものも含めて、色々とミスしていたので、以下にまとめておきます。
####  `synced_folder`で、ゲスト側に`/dev`という設定をしていた
どういう事かと言うと、`config.vm.synced_folder "C:\\dev", "/dev"`と設定していました（なぜなのかは、良く覚えてません）。
当然ですが、`/dev`はLinuxにデフォルトで存在するディレクトリなので、`vagrant up`しても上手く動きません。

ただ、問題はログにはエラーが出てこず、コマンドプロンプトに流れるログを見る限りでは、普通に動いてるように見えるというのが、曲者でした。
当初は`vagrant ssh`を叩いても、アクセスする事ができなかったので、「ネットワーク関係が原因？」と、しばらく悩んでしまいました。

#### シェルスクリプトでは、先頭を`#!/bin/sh`としない。
Ubuntuには不慣れだったので知らなかったのですが、最初、私は各シェルスクリプトの1行目に、普段の慣れから「`#!/bin/sh`」って書いてしまってました。
ところが、CentOS時には成功していたシェルスクリプトが、なぜか上手く動いてくれず、少し悩みました。
どうやら調べてみると、Ubuntu（というかDebian系）では、`/bin/sh`は独自実装の「dash」のシンボリックリンクらしく、`php -v &> /dev/null`の部分などが、きちんと機能しなくなってました。「`#!/bin/bash`」とすると、bashとして実行されます。

ちなみに、CentOSなどRedhat系なら、「#!/bin/sh」で動きます。

> [CentOSの/bin/shとUbuntuの/bin/shの違い](http://blog.aqutras.com/entry/2016/03/11/104801 "CentOSの/bin/shとUbuntuの/bin/shの違い") (http://blog.aqutras.com/entry/2016/03/11/104801)

#### private_networkで指定したIPを使っても、[Rlogin](http://nanno.dip.jp/softlib/man/rlogin/ "Rlogin")などでログインできない。

単純なミスと言うか、ホスト名に「192.168.33.12」と指定して、ポートに「2222」としていたので、ログインできませんでした。
SSHクライアントから、vagrantにアクセスする場合の記事を探し、まずは「127.0.0.1」にしてポートに「2222」としている記事を見て、それを実行して成功しました。
次に、ホスト名を「private_networkで設定したIP、192.168.33.12」に書き換えたのですが、ポート番号の変更をするという事に気づかず、接続できなかったのです。あれこれ色々とググって調べて、最終的には、色々と良く考えると「ポート番号を変えればよいのでは？」という事に気づき、自己解決しました。

ホスト名に「192.168.33.12」と指定して、ポートに「22」とし、さらに秘密鍵を指定すれば、普通にログインできます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/31051/3e465002-9960-0c3d-b234-8dfe2952f5d3.png)

#### 各シェルスクリプトの改行コードはLFで。

当然ですが、シェルスクリプトはUbuntu上で動くので、改行コードはLFにする必要があります。
CRLFだと、シェルスクリプトがバグる事があります（Windows上でスクリプト書いていたら、LF指定したつもりでしたが、いつの間にかCRLFになってて、スクリプトが変な動きをしました）。

# 最後に

この箇所は自分用のメモ書きや感想が主なので、飛ばしても問題ありません。

### Ubuntuにした理由

最初で少し説明しましたが、はじめはCentOSやFedoraを使う予定でした。
特に、自宅ではyumでPHPの最新バージョンが手軽に入るという理由で、好んでFedoraを使ったいたので、今回もFedoraにするつもりでした（anyenvを使う今回のパターンだと、yumで最新PHPが入るのは、あまりメリットではありませんが、慣れていたので）。
しかし、Fedora（[bento/fedora-28](https://app.vagrantup.com/bento/boxes/fedora-28 "bento/fedora-28")）はいざ進めてみると、「yum update」でカーネルなどの更新が起こると、VagrantとVirtualboxで、GuestAdditionsのバージョンが合わなくなります。さらに本来なら、自動で「vagrant-vbguest」が実行されてバージョンが合うようになるはずですが、これも上手く動かない事態になりました。

あれこれ調べてみましたが、どうやっても解消しないので、Fedoraは諦めてCentOS（[bento/centos-7.4](https://app.vagrantup.com/bento/boxes/centos-7.4 "bento/centos-7.4")）にしました。
こちらは割とスムーズに進んだのですが、yumはpythonのバージョン2で動いているので、将来的にpythonのバージョン3を入れるとyum関係でトラブルを起こすことから断念。
この問題自体は、割と簡単に解決できるっぽいですが、設定ファイルを書き換える必要があるようなので、「コマンド1つで実行環境を整える」という今回の目的には、少し合わなくなります。`.bash_profile`の「追記」程度ならともかく、シェルスクリプトの「書換」は少し怖いので、考えた末に別のOSを使うこととしました。

ということで、前々から興味のあったUbuntuを選択。
正直、コマンドなどで微妙にRedhat系と違いがあって、慣れない部分も多いですが、会社では今後、Ubuntuサーバが増えていくかもという話が出ているので、ちまちま慣れていこうかと思います。

### 感想
Vagrantを入れる前、自宅で遊ぶための開発環境は、Virtualboxに直接Fedoraやらをインストールして、yumで地道に必要ツール群をインストールするという原始的な方法で構築していたのですが、サーバで遊んでいると、壊れるということは稀にあります。これまでも壊れたたびに、面倒くさい再インストール再構築をやっていたのですが、今回はこれを機に自宅でもVagrantを使って環境構築をしてみようかなと思い立って、実行に移してみました。

業務でもVagrantは使ったことがありますが、基本はインフラチームや、Vagrantfileが書けるエンジニアが作り上げた環境を渡され、こちらはコマンドを実行するだけということが多かったので、勉強がてら試してみることにしました。
インフラ系はやってみると面白いのですが、どうも上手くいかない時はとことん上手くいかないので、少し苦労します。最終的には、とりあえず個人で使う環境としては、納得行くものができたかなと思います。

Vagrantを最初から試行錯誤して使ってみた感想としては、単純にOSをインストールするだけでも、かなり楽になるということですね。今回は、さらにシェルスクリプトで、あれこれインストールさせる所まで自動化できたので、一度設定してしまえば、壊れることも恐れる必要がなくなります。

Vagrantはスナップショットを取るのも楽ですし、さ今後は、躊躇なくガンガンと新しいことを試す事ができそうなので、楽しみです。