ミニマルに始めるDotfiles自動化計画


# Dotfilesを自動化
Github上にあるファイルを起動して、Dotfilesのインストールとかをしたいけどよく分からないよ...っていう人向けの記事です。OS X向け

![screen.jpg](https://qiita-image-store.s3.amazonaws.com/0/38647/318e3bb4-077f-b1ca-879a-e48af7083659.jpeg)

# Dotfilesについて
`.bash_profile`や`.vimrc`といった設定ファイルのことをdotfileと呼んでいます。
Githubで管理されることが多いです。
Dotfilesについて知らない人は以前投稿した[dotfilesをgithubで管理](http://qiita.com/himinato/items/7f5461814e8ed8916870)を参照してください。

# 自動化について
以下の３つの処理を自動化します

* ダウンロード
* ダウンロード後のdotfileにシンボリックリンクを張る
* 各種インストール作業などの初期処理

## ダウンロードについて
簡単さを求めてbashで書くとこんな感じになりました。

```bash
#!/bin/bash

DOT_DIRECTORY="${HOME}/dotfiles"
DOT_TARBALL="https://github.com/himinato/dotfiles/tarball/master"
REMOTE_URL="git@github.com:himinato/dotfiles.git"
# ディレクトリがなければダウンロード（と解凍）する
if [ ! -d ${DOT_DIRECTORY} ]; then
  echo "Downloading dotfiles..."
  mkdir ${DOT_DIRECTORY}

  if type "git" > /dev/null 2>&1; then
    git clone --recursive "${REMOTE_URL}" "${DOT_DIRECTORY}"
  else
    curl -fsSLo ${HOME}/dotfiles.tar.gz ${DOT_TARBALL}
    tar -zxf ${HOME}/dotfiles.tar.gz --strip-components 1 -C ${DOT_DIRECTORY}
    rm -f ${HOME}/dotfiles.tar.gz
  fi

  echo $(tput setaf 2)Download dotfiles complete!. ✔︎$(tput sgr0)
fi
```

買ったばかりのMacなど`git`コマンドすら入っていないことがありますのでコマンドが使えるかによって場合分けします。
なければ`curl`でGithubから圧縮ファイルを取得して解凍します。
`DOT_TARBALL`に設定しているURLを自分のリポジトリにすればわかりますが、`tar.gz`で取得できるのです。

```bash
# コマンドの有無は今後よく使うので
has() {
  type "$1" > /dev/null 2>&1
}
# 関数を作って、こんな風に使いましょう
if has "brew"; then
  ...
fi
```

ダウンロードはこれだけです。
ここからちょこっと改良して`-f`オプションを指定するとDotfilesを上書き保存するようにしました。
小さな一歩はいずれ大きなストライドになるので、小さくはじめていきましょう。（最初から大きな一歩を踏み出せる人は踏み出して結構）

## Deployについて
段落の名前がいきなり`Deploy`になっていますが、シンボリックリンクを貼ることをDeployと名付けている人が多いようなのでそれに則っています。

以前はこんな感じでした。

```bash:link.sh
#!/bin/sh
ln -sf ~/dotfiles/.vimrc ~/.vimrc
ln -sf ~/dotfiles/.bash_profile ~/.bash_profile
```

これ時代遅れらしいです。
リンクするファイルが増えるたびに編集とかダルい...と生まれたのが、

```bash
cd ${DOT_DIRECTORY}

for f in .??*
do
  # 無視したいファイルやディレクトリはこんな風に追加してね
  [[ ${f} = ".git" ]] && continue
  [[ ${f} = ".gitignore" ]] && continue
  ln -snfv ${DOT_DIRECTORY}/${f} ${HOME}/${f}
done
echo $(tput setaf 2)Deploy dotfiles complete!. ✔︎$(tput sgr0)
```

Dotfiles以下のすべてのdotfileに対してシンボリックリンクを貼っていきます。
こういう方法あるだろうと思ってましたか？昔の私？

デプロイについてもこれだけです。

## Initializeについて
ちょっと長くなりますが、

```bash
if has "brew"; then
  echo "$(tput setaf 2)Already installed Homebrew ✔︎$(tput sgr0)"
else
  echo "Installing Homebrew..."
  ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
fi

if has "brew"; then
  echo "Updating Homebrew..."
  brew update && brew upgrade
  [[ $? ]] && echo "$(tput setaf 2)Update Homebrew complete. ✔︎$(tput sgr0)"

  brew tap 'caskroom/cask'
  brew tap 'homebrew/dupes'
  brew tap 'sanemat/font'

  local list_formulae
  local -a missing_formulae
  local -a desired_formulae=(
    'brew-cask'
    'coreutils'
    'ffmpeg --with-faac --with-fdk-aac --with-ffplay --with-fontconfig --with-freetype --with-frei0r --with-libass --with-libbluray --with-libcaca --with-libquvi --with-libsoxr --with-libvidstab --with-libvorbis --with-libvpx --with-opencore-amr --with-openjpeg --with-openssl --with-opus --with-rtmpdump --with-speex --with-theora --with-tools --with-x265'
    'git'
    'go'
    'imagemagick'
    'lua'
    'mysql'
    'ruby-build'
    'rbenv'
    'the_platinum_searcher'
    'tmux'
    'zsh'
    'vim --with-lua'
  )

  local installed=`brew list`

  # desired_formulaeで指定していて、インストールされていないものだけ入れましょ
  for index in ${!desired_formulae[*]}
  do
    local formula=`echo ${desired_formulae[$index]} | cut -d' ' -f 1`
    if [[ -z `echo "${installed}" | grep "^${formula}$"` ]]; then
      missing_formulae=("${missing_formulae[@]}" "${desired_formulae[$index]}")
    else
      echo "Installed ${formula}"
      [[ "${formula}" = "ricty" ]] && local installed_ricty=true
    fi
  done

  if [[ "$missing_formulae" ]]; then
    list_formulae=$( printf "%s " "${missing_formulae[@]}" )

    echo "Installing missing Homebrew formulae..."
    brew install $list_formulae

    [[ $? ]] && echo "$(tput setaf 2)Installed missing formulae ✔︎$(tput sgr0)"
  fi

  # コマンド類の初期処理
  ln -sfv /usr/local/opt/mysql/*.plist ~/Library/LaunchAgents

  local -a missing_formulae=()
  local -a desired_formulae=(
    'alfred'
    'clipmenu'
    'google-chrome'
    'vagrant'
    'virtualbox'
  )
  # cask
  local installed=`brew cask list`

  for index in ${!desired_formulae[*]}
  do
    local formula=`echo ${desired_formulae[$index]} | cut -d' ' -f 1`
    if [[ -z `echo "${installed}" | grep "^${formula}$"` ]]; then
      missing_formulae=("${missing_formulae[@]}" "${desired_formulae[$index]}")
    else
      echo "Installed ${formula}"
    fi
  done

  if [[ "$missing_formulae" ]]; then
    list_formulae=$( printf "%s " "${missing_formulae[@]}" )

    echo "Installing missing Homebrew formulae..."
    brew cask install $list_formulae

    [[ $? ]] && echo "$(tput setaf 2)Installed missing formulae ✔︎$(tput sgr0)"
  fi

  echo "Cleanup Homebrew..."
  brew cleanup
  echo "$(tput setaf 2)Cleanup Homebrew complete. ✔︎$(tput sgr0)"
fi

# 必要なファイル類を持ってくる
[ ! -d ${HOME}/antigen ] && git clone https://github.com/zsh-users/antigen.git ${HOME}/antigen

# Brewで入れたプログラム言語管理コマンドの初期処理
if has "rbenv"; then
  # 最新のRubyを入れる
  latest=`rbenv install --list | grep -v - | tail -n 1`
  current=`rbenv versions | tail -n 1 | cut -d' ' -f 2`
  if [ ${current} != ${latest} ]; then
    rbenv install ${latest}
    rbenv global ${latest}
  fi
fi

# シェルをzshにする
[ ${SHELL} != "/bin/zsh"  ] && chsh -s /bin/zsh
echo "$(tput setaf 2)Initialize complete!. ✔︎$(tput sgr0)"
```

結構削ってこんな感じ、ここは使うツールなどによって千差万別だと思うのでお好みのスクリプト作ってください。
これ使える！！って部分があったらパクってあげてください。

## 自動化をまとめると

```bash:dotfiles

#!/bin/bash
set -e
DOT_DIRECTORY="${HOME}/dotfiles"
DOT_TARBALL="https://github.com/himinato/dotfiles/tarball/master"
REMOTE_URL="git@github.com:himinato/dotfiles.git"

has() {
  type "$1" > /dev/null 2>&1
}

# 使い方なのだよ
usage() {
  name=`basename $0`
  cat <<EOF
Usage:
  $name [arguments] [command]
Commands:
  deploy
  initialize
Arguments:
  -f $(tput setaf 1)** warning **$(tput sgr0) Overwrite dotfiles.
  -h Print help (this message)
EOF
  exit 1
}

# オプション -fは上書き、-hはヘルプ表示
while getopts :f:h opt; do
  case ${opt} in
    f)
      OVERWRITE=true
      ;;
    h)
      usage
      ;;
  esac
done
shift $((OPTIND - 1))


# Dotfilesがない、あるいは上書きオプションがあればダウンロード
if [ -n "${OVERWRITE}" -o ! -d ${DOT_DIRECTORY} ]; then
  echo "Downloading dotfiles..."
  rm -rf ${DOT_DIRECTORY}
  mkdir ${DOT_DIRECTORY}

  # ... 中略

  echo $(tput setaf 2)Download dotfiles complete!. ✔︎$(tput sgr0)
fi

link_files() {
  # ... Deploy処理
}

initialize() {
  # ... Initialize処理
}

# 引数によって場合分け
command=$1
[ $# -gt 0 ] && shift

# 引数がなければヘルプ
case $command in
  deploy)
    link_files
    ;;
  init*)
    initialize
    ;;
  *)
    usage
    ;;
esac

exit 0
```

こんな感じになります。
あとはGithubに公開しておけば、以下のようにダウンロードとInitializeを同時に行うなど柔軟に対応できます

```bash
$ bash -c "$(curl -fsSL raw.github.com/himinato/dotfiles/master/dotfiles)" -s init
```

## 最後に

今の設定はこんな感じです。
[dotfiles repository](https://github.com/himinato/dotfiles)

なんでOS X限定なんだよ！！って思ったかもしれないですけど、手持ちでよく使うのがMacだからです。
最近他に使うとしてもUbuntuですが、サーバとして立ち上げるのでAnsibleで環境構築したほうがいいです。むしろ設定ファイルなんかはAnsibleで利用出来る変数群のおかげでIPアドレスを埋め込んだりとかが圧倒的に楽チン

まだDotfiles自動化をしていないそこのあなた！！
年末の大掃除とともにDotfilesも見直しませんか？