ssh越しにAtom他GUIエディタを使いたい？それ、同期ツールunisonでできるよ

追記：これを書いた当時は開発者ごとにEC2インスタンスが配られてる環境でした。Amazon LinuxやRDSとかも使っていたので、ローカルでやるのは面倒という状況だったので、いかにEC2上でローカルと同じように開発するかを追求したという背景です。

# EC2とかでもAtom使いたい・・！

VimとかEmacsとかのCUIエディタでの開発からの脱却を狙っていて、
ローカルエディタで開発したいが・・

- ssh先のサーバでしか開発できない
- 手元のMacに依存関係のセットアップするのがとても面倒

実際やってみたが・・

- 変更都度rsyncとかscpとか許せない
- sshfsは遅すぎて使いものにならない
- エディタの機能でssh上のファイルを開くとGit連携とかのいろんな機能が使えずつらい
  - （以前上がっていた[Atomプラグインを使用する方法](http://dev.classmethod.jp/tool/remote-editing-by-using-atom/)だと一部機能が使えないはずです）
- エディタだけじゃなく**シェルなどもなるべくローカルのものを使いたい**

# シンプルなソリューション：unison

エディタの自由のためにはファイルを自動で双方向同期するのが一番手っ取り早かったです。
[unison](http://www.cis.upenn.edu/~bcpierce/unison/)を使うとssh先のサーバとDropboxのように同期することができます。
rsyncと違って、前回の同期状態を保存していたり、ファイルの変更をリアルタイムに監視したり、競合を手動で解決したりできます。
最近も更新されていますが、製品自体とても枯れてて安心です。

また、副作用的に、ローカルシェルで全てのファイルを編集できるようになるので、EC2上に最低限の環境しか構築しなくて済むようになります。

1行でまとめると**もっと評価されるべき**。

# セットアップ

サーバ・クライアント双方でセットアップが必要です。

## Mac

homebrewに最新版が上がっていました。

**追記：** 以前はunison-fsmonitorがPython 3でしか動かなかったのですが、今は逆にPython 2でしか動かなくなってしまいました・・。

```
brew install unison
# ファイルシステム変更監視用のscriptが同梱されていないので別途持ってくる
mkdir -p ~/bin
curl -L https://github.com/jumpstarter-io/unox/raw/master/unox.py > ~/bin/unison-fsmonitor
chmod +x ~/bin/unison-fsmonitor
# ~/binをPATHに通してください

# pyenv install 2.x.xなど何らかの方法でpython3を用意する
# まだ特にセットアップしてない人はanyenvがオススメです http://qiita.com/luckypool/items/f1e756e9d3e9786ad9ea

# pyenvな人はインストールしたpythonを使うように設定します
cd /path/to/your/repo
pyenv local 2.x.x

# 依存ライブラリを導入
pip install MacFSEvents
```

## Amazon Linux (EC2)

少なくとも2015年前半においては、ものすごく古いバージョンしかyum installできなかったので、ソースからビルドしました。
お使いのディストリで2.48.3が配信されていれば単にインストールするだけで良いと思います。

ここだけちょっと面倒です。
他力本願ですがrpmをどなたか作っていただきたく・・ｗ

**追記：** 面倒すぎたので確認したところ外部yum repoを使う方法もありました。

### ocamlのインストール：外部yum repoを入れる方法

下記の手順に従って導入します。
http://rnowling.github.io/software/engineering/2015/04/02/installing-ocaml.html

Amazon Linuxに対応するCent OSのバージョンに合わせて追加するrepoのURLを変更してください。

### ocamlのインストール：ocamlbrewを使う方法

```
# 最新版を入れるためにソースからビルドするため、言語環境のOCamlをセットアップする
curl -kL https://raw.github.com/hcarty/ocamlbrew/master/ocamlbrew-install | bash
# エラーになリますが無視で
# https://github.com/hcarty/ocamlbrew/issues/25#issuecomment-97130288

# gitで管理していないような.**shrcファイルに環境変数を追加しておく
vim ~/.zshrc_local # 自分はこのファイルを.zshrcからsourceしている
```

下記を貼り付け

```
__ocaml_path__="$HOME/ocamlbrew/ocaml-4.XX.X" # ~/ocamlbrewをlsしてみて適切なバージョン番号に置き換えてください
PATH="$__ocaml_path__/bin:$PATH"
export OPAMROOT="$__ocaml_path__/.opam"
source "$OPAMROOT/opam-init/init.zsh" > /dev/null 2> /dev/null || true
```

編集したファイルをsourceし、下記のコマンドでセットアップを完了します。

```
opam switch 4.XX.X # （上記の「適切なバージョン番号」を使います
```

### （共通）unisonをインストールする

ocamlがインストールできたのでunisonをインストールします。

```
# unisonのocaml依存関係を入れておく
opam install oasis utop Batteries ocamlscript

# http://www.cis.upenn.edu/~bcpierce/unison/download.html からCurrent Stable Versionのものをwgetなどで落としてきて、tar -xfなどで展開する。
# unison-2.48.3に展開したとする
cd ~/unison-2.48.3
make
# デフォルトで~/binにインストールされる
mkdir -p ~/bin
make install
# 監視用のスクリプトはinstallされないので別途コピーする
cp unison-fsmonitor ~/bin/
# パスが通ってない場合は通す（ただしsshで実行コマンドを直指定しても読み込まれる箇所（.zshenvや.bashrc）に書かなくてはならない）
echo 'export PATH="$HOME/bin:$PATH" >> ~/.zshenv
```

# 使い方

## コマンド

使用例

```
unison ssh://host/dir /path/to/local/dir -ignore 'Path vendor' -ignore 'Name {tmp,log}' -repeat watch -auto
```

基本は`unison A B`です。
`-repeat watch`はファイルシステム監視を有効にして、`-auto`はコンフリクトがなければ勝手に同期するためのフラグです（いわゆる--yes）。
-ignoreは開発する環境に合わせて付け替えます。
例に載ってるvendorは、railsのgem群が多すぎて辛かったのとmacとlinuxでバイナリが非互換だったのがあり（「罠」を参照のこと）、ignoreしています（手元でbundle installしている）。

## 設定ファイル

~/.unison/foobar.prf に 設定ファイルを置くと、`unison foobar -repeat watch [OPTIONS]`で同期できます。
**注：ignore設定に罠（バグ）があります。「罠」を参照のこと。**

```
root = ssh://host/dir
root = /path/to/local/dir

ignore = Path vender
ignore = Name {tmp,log}
auto = true
```

## コンフリクト

調子にのって両方のホストでファイルを編集してから`unison -repeat watch`コマンド叩くと、下記のようにCONFLICT表示になります。

```
Looking for changes
  Waiting for changes from server
Reconciling changes
changed  <-?-> changed    conflict.txt
Propagating updates
UNISON 2.48.3 started propagating changes at 10:33:42.14 on 04 Aug 2015
[CONFLICT] Skipping conflict.txt
  contents changed on both sides
```

罠っぽいのが`-repeat watch`の場合conflictが発生しても（そのファイルをスキップして）何食わぬ顔で同期し続けるので、`unison -repeat watch`コマンドを叩いた直後は一応確認しておいたほうが良いです。

解消するには、`-repeat watch`なしでunisonコマンドを叩きます。

```
ssh-host       local
changed  <-?-> changed    conflict.txt  []
```

上記の表示が出て、現在のファイルに対するアクションを1文字（y/nに似てる）で聞いてきます。
とりあえず利用可能なアクションを表示するには`?`を押します。
よく使いそうなのは下記です。

```
  d                     show differences
  > or .                propagate from from ssh-host to local
  < or ,                propagate from from local to ssh-host
```

## 罠

- ignoreをprfファイルに書いても反映されない（同期はされないけどファイルの監視が実行される？）模様（コマンドラインならいけた）
- Rails限定：macでtherubyracerというgemをビルドするためには特定バージョンにしなくてはならない http://3.1415.jp/d3wpyqjr/
- Rails限定：vendor/bundle以下は同期から外さないと、ライブラリのバイナリが無いと言われたり、ファイル監視の上限を超えたというエラーが出たりする
- ignoreしたパス（gemの中身）を編集してしまいローカルにしか反映されていない、みたいな罠を踏んだorz...
- 1回だけ、両方のホストでgitの操作をしてしまったところ、gitのobjectファイルが片方に存在して片方に存在しないみたいな状況が発生しました（git logするとエラー） → gitで複数のワーキングディレクトリを[作ってた](http://qiita.com/yuya_presto/items/dcebbebc6b3d9cf6f542)のに、コピーの方しか同期してなかったからでした。この場合は元となった.gitディレクトリがちゃんと同期されている必要があります。

## 近況

とりあえず1年近くうまく使えているのでVim→Atomにほぼ完全に乗り換えることができました。