upstart, pty-keeper, reptyr, socat - ターミナルアプリをリモートサーバでデーモン化する方法(Earthquakeをサーバで実行するようにした)

試行錯誤で6日かかったンゴ……普通に嵌ってしんどかった……。1 release/dayが途絶えて残念なり……。一応、毎日勤勉に取り組んでたんですけどねぇ。でも最後はかなりシンプルになって良かった。似たような方法でターミナルアプリは全て同じ方法でデーモン化して接続できるようになると思います。

例はEarthquakeです、というか、Earthquakeをデーモン化するためにいろいろ調べた。

要件

  1. Earthquakeをデーモン化したい
  2. ローカルから1コマンドで接続
  3. ssh認証したい

1は先日event_chainsave_imageってEarthquakeプラグイン作ったので、サーバに常駐させときたいなぁということです。ローカルはラップトップなので閉じてることあるから。

2は基本的には常時接続なんですが、やっぱ気が向いた時にすぐ見れなきゃまずいっしょ。

3は外から誰でも接続できると嫌なんで。危ない。

ソリューション

とりあえず、ざっくり解決法書きます。解説は後で。

リモートサーバ

reptyrとsocatをインストールします。UbuntuでやってるのでOSに合わせて適度に読み替えてください。

$ sudo apt-get install reptyr
$ sudo apt-get install socat

reptyrは別のpty(pseoudo tty)で動いてるプロセスを手元のptyで乗っ取るソフトウェアです。 socatはnetcatの高機能版みたいなもんで、ネットワーク用途に限らない色々なことができます。今回はコマンド実行して終了するんじゃなくて、リモートサーバへの接続を維持するために使ってます。

そして、拙作のpty-keeperをダウンロードします。

$ wget https://gist.github.com/babie/7297810/raw/ae3793ae39b3165e0c50f7ca4a05fb00921a9cdd/pty-keeper
$ chmod +x pty-keeper

適当な場所に置いてね。

ptyを持たせて子プロセスを起動しながら、自分から子プロセスのIOへ読み書きができなくなっても、子プロセスが生きている限りは死なない、っつーソフトウェアが見つからなかったので作りました。

要点は、PTY.spawnでpty付きでspawnするのと、SIGCHLD,SIGCLDは子プロセスが死んだ時だけじゃなくて、ptyが変更された時なんかも飛んでくるので、ホントに死んだかどうか確かめるってとこっすね。汎用的にしなければ10行です。

んで、Upstart用設定ファイル/etc/init/earthquake.confを作ります。

これでサーバ起動時に自動で立ち上がります。ユーザーやパスは環境に合わせて適度に改変してください。

HOMEはEarthquakeが設定ディレクトリを探すために、LANGはEarthquakeへ文字入力するときに日本語が化けるのを防ぐのに、COLUMNSはpty及びreadlineがデフォルト80を採用しそれ以上の入力すると強制的に行頭に戻されてつらいので、PATHはEarthquake及びpty-keeperがRubyを探すために必要です。

--no-daemonオプションを付けてるのは、upstartはプロセスIDを追跡する記述があって、expect forkはfork一回、expect daemonはfork二回という風に対応してるのですが、RubyProcess.daemonupstartが追えないプロセスIDの変わり方してるみたいなんで付けざるを得ませんでした。pty-keeper自体はconsole noneでどのtty/ptyにも接続してないので問題ありません。

プロセスの起動・終了を何に任せるかはいろんな方法がありますよね。

昔ながらのstart-stop-daemon使って/etc/init.d/earthquake書いてたんだけど、ほら、sysvinitってrespawn(プロセスを監視して自動再起動)ないじゃないっすかー。んでその後、daemon使ってやってたんすけど、--ptyオプションがうまく使えなかった。これデバッグ用でupstart(init)は元々pty持ってないからかな?んでググると、なんとUbuntuでは今upstartが普通っぽいじゃないですかー。こいつはrespawnもうまく捌いてくれるしかっこいいなということでこれ使いました。

最初は直接earthquakeを呼び出すearthquake.conf書いてたんですが、デーモン化はうまくいくんですが、文字入力がダメ。なんか行単位のバッファになってるみたいなんすよね。socatのオプションでicanon=0rawなんかを試したんですがうまくいきませんでした。

次にemptyを介したearthquake.confを書いたんですが、これはデーモン化も文字入力も問題なかったんですけど、earthquakeプロセスが2つできちゃう。デフォルトのEarthquake使うならこれでも良かったんですが、私が使ってるプラグインにタイムラインを監視してイベントに応じてアクションするってのがあるんすよね。具体的にはfavった画像つきツイートの画像を保存するって奴なんですが、画像が2個保存されて宜しくない。どうしてプロセス2つになるかというと、reptyrがpty乗っ取ったと同時に読み書きできる子プロセスがいなくなったemptyが自殺してupstartが再起動かけるって具合です。このempty、Ubuntuでのパッケージ名がempty-expectとなっているように、ptyを生成してコマンドを起動してそいつと対話するexpectなんですわ。なので子プロセス自体が生きていてもIOが読み書きできなくなったら速やかに死ぬようになってんですね。そもそもの目的がそれなんだからしょうがない、他の用途に使う俺が悪かった。

あ、この設定ファイル、密かにrbenv環境のupstart設定の書き方にもなってますね。

手動で立ち上げるときは

$ sudo start earthquake

です。この辺はinitctl辺りを調べてください。

ローカルホスト

.zshrcに以下を追記

別にaliasでもいいと思うんですけど、私はwhichですぐ見れるようにfunctionにしてます。設定再読み込みも忘れずに。

moshじゃなくてsshなのは、moshは接続を安定させるために複数のコネクションを張るので、プロセスが複数実行されて悲しいことになります。

socatは2つのブロックの引数を取り、STDINがこっちの標準入力、SYSTEMがあちらでshを実行することを意味してます。
STDINのオプション、echo=0は文字が二重に表示されるのを防ぐため、icanon=0は非カノニカルモードにして行単位ではなく文字単位で書き込みするために使ってます。詳しくはTERMIOSを読んで下さい。
SYSTEMは引数なしのコマンドを実行するならEXECでもいいんですが、今回は引数に加えてshの実行も必要だったのでこれにしました。オプションのptyはリモートサーバでpty作るかどうか、stderrはSTDERRもSTDOUTにリダイレクトしてくれるやつ、ignbrk=1はこれまたTERMIOS関連でシグナルのC(Ctrl-c)を無視するかどうかですね。どっちにしろsocat/reptyrの終了でearthquakeも再起動するんで今回はあんま意味ないんですが。

reptyrにオプション-sを付けてますが、これはプロセスにtty/ptyが接続されてない時に使うもので不要なんですが、オプションなしの既存のptyを乗っ取るより、オプションありの新しくpty作って繋ぎ変える方が速くて安定してるみたいなので付けました。

あかんかったわ。-sオプションつけると複数行や長文の入力に難あり。取り除いたら無事できました。遅延じゃろか。

これで、

$ earthquake

とすればリモートで動いているEarthquakeに接続しTwitterを楽しめます。

次回予告

はてなダイアリーに予約投稿がなくてつらいので何とかしたいと思います。んだけど、MacBook Proが届いて環境設定しないとアカンのでちょっと遅れるかも。