lsyncdをrsync以外の用途で使う

# はじめに

lsyncdはLinuxで使えるinotifyのラッパーである。

[lsyncdのほぼすべての紹介記事](https://www.google.co.jp/search?q=lsyncd&ie=UTF-8)は、ファイルを作成したり変更したりした時に自動的にどこかにrsyncしてくれる、という目的を達成するためのものであるが、実際のところlsyncdはinotifyイベント（ファイル/ディレクトリの作成、変更、属性変更、削除、移動。あとinotify関係ないけどlsyncdの起動）を拾った時に、rsyncだけなどではなくどんなコマンドでも実行させることができるのである。

公式ドキュメントにおいて、そのもっとも単純な例は次のページに書かれたものである。

* (Layer 3) https://github.com/axkibe/lsyncd/wiki/Lsyncd%202.1.x%20%E2%80%96%20Layer%203%20Config%20%E2%80%96%20Simple%20onAction

上の例だと細かい制御はできていないが、さらに次のドキュメントを見るとluaでいろんな処理を書けることがわかる。

* (Layer 2) https://github.com/axkibe/lsyncd/wiki/Lsyncd%202.1.x%20%E2%80%96%20Layer%202%20Config%20%E2%80%96%20Advanced%20onAction
* (Layer 1) https://github.com/axkibe/lsyncd/wiki/Lsyncd%202.1.x%20%E2%80%96%20Layer%201%20Config%20%E2%80%96%20Inlets

# やってみる

CentOS 7だとEPELにあるので、次のようにインストールできる。

```
# yum install epel-release
# yum install lsyncd
```

設定ファイルは /etc/lsyncd.conf である。
ここに設定やluaのコードを書く。
今回は[私の前回の記事](http://qiita.com/yunano/items/65b8d3989d15c71287df)で作ったansible_best_practice.shを動かしてみることにする。

```lua:/etc/lsyncd.conf
settings {
  logfile = "/var/log/lsyncd.log",
  nodaemon = false,
}

exec = function(event)
  local src = event.source
  if string.match(event.sourcePathname, "^" .. src .. "/%%[^/]+$") then
    spawnShell(event, "bash " .. src .. "ansible_best_practice.sh -dH -f " .. src .. " -t " .. src)
  end
end

ansible_best_practice = {
  maxProcesses = 1,
  delay = 0,
  onCreate = exec,
  onDelete = exec,
  onMove = exec,
}

sync {
  ansible_best_practice,
  source = "/space/ansible",
}
```

上記 /etc/lsyncd.conf は次の4つのパートに分かれているが、説明すると、

* settings {}
lsyncdの全体設定。上記ではログファイルのパスと、デーモンモードで起動することを設定している。

* exec = function(event) ～ end
今回、ファイルを作成した時、削除した時、移動した時ですべて同じ処理をさせるので、その処理を括りだしたもの。
引数であるeventから取得できる値は先に紹介したLayer 2やLayer 3のドキュメントに記載されている。基本lsyncdがrsync用だからか、ディレクトリパスが格納された変数もそれぞれで最後にスラッシュ(/)が付いたり付かなかったりして面白い。
ここでやっていることは「監視対象ディレクトリ直下に%で始まるファイルを作成・削除・移動した時、ansible_best_practice.shを実行する」である。
「監視対象ディレクトリ直下に%で始まるファイル」を判別しているのがifの箇所であり、「..」はluaにおける文字列連結の演算子、「%%」はluaの正規表現記法では%がエスケープ用の文字だから「%%」で%1文字を表す。
実行させたいコマンドは、実行したい箇所で「spawnShell」の第2引数にすれば実行される。
ここでは使っていないが「log("Normal", "出力させたい内容")」でログファイルに出力されるので必要なら使うと良い。

* ansible_best_practice = {}
ansible_best_practiceという名前の設定セットを定義している箇所である。名前は任意で決めて良い。
maxProcessesは同時に実行可能な最大プロセス数であり、delayはinotifyイベントが発生して何秒後に実行するかである。
onCreate（ファイル追加）、onDelete（ファイル削除）、onMove（ファイル移動）時に、先程書いた同じexecを実行させることにしている。別々の処理をさせるなら代わりにここにfunction(event)で始まる関数を書いても良い。
他にイベントにはonModify（ファイル変更）、onAttrib（ファイル属性変更）、onStartup（lsynd起動時）もあるが、今回は特に何もさせたくないので何も記述していない。

* sync {}
監視対象のディレクトリ(source)と、そのディレクトリでinotifyイベントが発生した時にどの設定セットを適用するかを記述する。ここは先程書いたansible_best_practice設定セットを適用することにしている。
sourceに書いたディレクトリの直下だけではなくサブディレクトリも監視される。
なお、上記設定ではsync {}を1つしか書いていないが、複数書けばそれぞれの監視対象ディレクトリに別々の設定セットを適用することができる。

設定ファイル /etc/lsyncd.conf が書けたらlsyncdを起動すれば準備完了である。
CentOS 7でサービスとして起動するには次の通りである。

```
# systemctl enable lsyncd.service
# systemctl start lsyncd.service
```

これで後はイベント発生時に自動でコマンドを実行してくれる。

なお、spawnShellで実行したコマンドのリターンコードが0以外の場合、lsyncdのサービスが終了してしまう。
設定セットでexitcodes（Layer 3ドキュメント参照）を適切に設定しておけば良いのだが、面倒だった、もとい特にそれで私には問題なかったのでやっていないからだ。
一番簡単な回避法は実行するコマンドの最後に" || :"とか付けてリターンコードを常に0にすることだ。

# どういう用途で使えるか

すぐに思いついたのは、あんまりコンピュータ詳しくない人に「ファイルはFTPでここに放り込んでおいて」って言っておいて、ファイルが送られてきたら自動でバージョン管理システムと連携する、とか。

ぐぐったらやっぱりinotify使ってやってる人いるね。
http://memocache.blogspot.jp/2009/12/git.html

# 参考

lsyncdにはOS X用のものもあるらしい。
http://qiita.com/suisho/items/22ad9efcac90127f87a1