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が届いて環境設定しないとアカンのでちょっと遅れるかも。

event_chain - Earthquakeのユーザーストリームで流れてくるイベントに応じて任意のコマンドを実行できるプラグインを作った

チョリ〜〜〜〜ッス!ブログ記事は遅れてるけど一応1日1リリース継続中。

先日のfav_machineは、①favイベントだけに反応し、②画像を保存するだけ、だったのですが、①は全てのイベントに、②は任意のコマンド(勝手プラグインコマンドも含む)に対応して、汎用性を持たせてみました。

前の記事のsave_imageと組み合わせるとfav_machine相当のことができます。

あと名前はお弁当チェーンみたいでいいかな?と思ってこれにしました。

インストール

⚡ :plugin_install https://gist.github.com/babie/7215590

設定

Earthquake.config[:event_chain] = {
  :favorite => [':retweet %{id}', ':save_image %{id}'],
  :favorited => [':update @%{him} thx 4 ur fav!', ':follow %{him}'],
}

例は、自分がfavったらついでにリツイートも実行するのと、自分がfavられたらその相手に返事をしfollowする、という意味になります。

ハッシュで設定します。keyがイベント名で、valueがイベントの際に実行するコマンドの配列になります。配列の中の順番は特に意味はないですが、内部的にはeachで順番に回してます。ので途中で例外が上がると止まる。

イベント名は、

  • block
  • unblock
  • favorite
  • favorited
  • unfavorite
  • unfavorited
  • follow
  • followed
  • unfollow

などが使えます。ストリーミングAPIのイベントを元としていて、自分がしたか・されたかによって能動態・受動態に分けました。そのままだとめっちゃややこしかったので。

コマンドは文字列で書きます。頭の:を忘れないようにしてください。例に%{him}というのが含まれてますが、よく使う変数を埋め込めます。変数には、 * me : 自分 * him : 相手 * her : himと同じ * it : himと同じ * id : ツイートエイリアス($xx) があります。コマンドに応じて埋め込んでください。
また、コマンド文字列はevalされるので、式や上記にない変数を埋め込めます。例えば':retweet #{id2var(item["id"])}'という風にも書けます。設定ファイルを読み込むときに評価されないようシングルクォート相当(%q)で囲んでください。ダブルクォート相当だと読み込み時に解釈されるので死ぬ。

使い方

一度設定したら自動です。

コード

ちょっとcallerのとこがちょっとおもろかった。コマンドにはconfirmメソッドを使ってyesかnoを問い合わせるものがありますので、このプラグインから呼ばれた時は常時trueにして手動入力なしでいけるようにしています。最初 Readline::HISTORY[-1](最後の入力履歴)を見ようかなと想ったんですが、inputに渡されるテキストをconfirmまで持ってくるのがダルいなということで、こういう風にしました。あと、prependを使ったほうがお作法的によろしいのでしょうが、Earthquakeがどのバージョンで使われるか知らないのと、元々ActiveSupportを組み込んであるのでalias_method_chainを使いました。便利。

passiviseは単語の受動態を作るメソッドです。こんな単語知らないけど適当に。ActiveSupport風にStringクラスをオープンして拡張しても良かったけどダルかった。

今後

リツイートはイベントとして流れてこないので、対応した方がいいかもなー。要望があれば。

次回予告

Earthquakeでちょっと考えてることがあって、調査していいソフトウェアがあったらその使い方、なかったら作って記事書きます。どちらにしても、Earthquakeについては次回で最後になると思います。

save_image - 指定したツイートの画像を保存するEarthquakeプラグインを書いた

チョリーッス!昨日はブログ書かなかったけど挫折したんじゃないよ!マジで。先日のfav_machineを2つに分けたから同時に公開したかったのだ。マジでマジで。

fav_machineはふぁぼったツイートの画像を自動で保存するだけで、コマンドは作らなかったので必ずふぁぼらないと保存できなかったんだけど、今回はちゃんとコマンドにして切り分けてみました。そして、次の記事で解説するevent_chainと合わせるとfav_machine相当以上のことができます。

インストール

⚡ :plugin_install https://gist.github.com/babie/7215585

設定

~/.earthquake/configに、

Earthquake.config[:save_image] = {:dir => "path/to/your_save_dir"}

と書けば、保存ディレクトリを指定できます。デフォルトは~/.earthquake/imageになります。

使い方

Earthquake内で、

⚡ :save_image $xx

としてください。$xxはツイートのidを指すEarthquakeが用意したエイリアスです。

コード

基本はfav_machineから切り出しただけっす。画像がなかったらエラーメッセージ表示したり、確認プロンプトだしたりちょっぴり拡張してます。

今後

なんか拡張することあるじゃろか?単機能のこれで良さそう。

次回予告

fav_machineよりもっと柔軟性を持たせたevent_chainを紹介するよ。若干面白いことしてるので楽しみにしていてね。この後すぐ!

update_profile - Twitterのユーザー情報を更新するEarthquakeプラグインをアップデートした

いやー、ギリギリ間に合った〜。いやー、数分ぐらい間に合ってないけど記事のURLと日付は見かけ上間に合った〜。ん?いやいや、さっきまで寝てて急遽10分で以前のプラグインの修正でお茶を濁したとかそういうわけじゃないですよ?やだなー、2日前の今後の予定に修正するかもって書いてたじゃないっすかー、やだもー。

インストール

修正前と同じっす。更新も同じく

:plugin_install https://gist.github.com/babie/7076697

設定

なし。

使い方

ちょっとこの辺変わりました。

:description
{
    "description" => "ちん強 推していく"
}
⚡ :update_description Comic X-EROSまだ読んでない
⚡ :description
{
    "description" => "Comic X-EROSまだ読んでない"
}

みたいな感じ。

表示は名前そのまま、更新は:update_を頭につけることにしました。この方が行儀がいいかなと思って。:update_が付いてればまず更新系だとわかるし。つっても流石に:update_profile_にはしなかった。長いし別に被らないし。あとユーザー情報表示を簡素にしたかった。

:me:bioは廃止です。前者は他のプラグインでよく使われるコマンド名だというのと、後者は各自エイリアスを張ってもらうのが筋かな、と。

あと、descriptionの更新の複数行入力も廃止しました。入力は受け付けるけど、ウェブクライアント・スマートフォンクライアント共に表示に特に反映されるわけではないので。

コード

descriptionの特別扱いがないのですっきりしましたね。特にどうということもない感じ。

今後

拡張予定なし!これはこれで完成でしょう。

次回予告

多分、fav_machineをいじるわ。

ばいなり〜

lists - Twitterのリストを表示するEarthquakeプラグインを書いた

1日1リリース、4日目〜。三日坊主にはならんかったな。良かった。
えー、昨日はコマンドライン書くかもって言ってたけど、Earthquake関連をまとめて終わらせちゃおうと思って、やっぱこれにしました。記事はまとまってた方が探しやすいしね。リストは:recent babie/funnyとかでも見れるんだけど、これだと巡回するのがめんどいなと思って作ってみました。

インストール

おなじみEarthquake内コマンドで、

⚡ :plugin_install https://gist.github.com/babie/7169374

でっす。

設定

ないっす。ZeroConfでござる。

使い方

例えば自分のリストにbabie/funnyってリストがあったとして、

:list funny

と、自分のユーザー名を省略できます。

もちろん他人の公開リストも見れて、

:list a_matsuda/ruby-rails-ja

てな具合。

んで、*(ワイルドカード)を使用すると、

:list *

自分の購読している全リストを巡回してざざざっと表示します。

このプラグインのチャームポイントは、コンピレーションがちゃんと効いていて、リストだけ候補に挙がるとこです!!

コード

こんな感じですわ。

最初作った時は:listsとか:list :allとか:list_allとかコマンド名に迷いがあったんだけど、コンピレーションが同じプラグイン内でぶつかるのもうざいので、すっきり:listにまとめました。

あと、jugyo/twitter_oauthlists.rbが古いっぽいわ。Twitter API 1.1に対応してないのかな?fork元のmoomerman/twitter_oauthがあんま更新してなくてforkして、Earthquakeでもこの辺は使ってないから問題にならなかったんでしょう。プルリク出しとかなきゃな。なので、クラス開いて上書きしてますわ。

Readline.completion_procの書き換えはイマイさんのno6v/friend_completion.rbが参考になったわ。サンクス。Earthquake標準で用意されてるコンピレーション候補追加DSLcompletion do ... endは、基本的に候補付け足すもの(?)で他の候補を抑制する方法がよくわからんかったから使わなかった。

今後

:create_listとか:update_listとか充実させるかなぁ?んー、今はいらんな。必要になったら拡張するわ。

次回予告

これで俺のEarthquake環境は大体整ったので別のカテゴリに移るか、もしかしたら先日のfav_machineupdate_profileを改良するかもしれんわ。その場合、記事は全面書き換えた方がいいんかね?ま、適当に処理しまっす。

update_profile - Twitterユーザー情報を更新するEarthquakeプラグインを書いた

Updated update_profile - Twitterのユーザー情報を更新するEarthquakeプラグインをアップデートしたをお読みください!

今日で1日1リリース、3日目!
寝込んでて9:30から作業始めたんですけど間に合いました!

予告通り、Earthquakeプラグインを作りました。

今日のプラグインは、 名前やバイオを手軽に更新したかったでござる。:bio ほげほげみたいな感じで。

インストール

:plugin_install https://gist.github.com/babie/7076697

設定

ZeroConfでございます。

使い方

:name 我々はボーグだ
⚡ :description お前たちは同化される。抵抗は無意味だ。
⚡ :url http://ja.memory-alpha.org/wiki/%E5%90%8C%E5%8C%96:location 浮遊機械都市
⚡ :me
{
...
              "name" => "我々はボーグだ",
...
       "description" => "お前たちは同化される。抵抗は無意味だ。",
               "url" => "http://t.co/ks4Wv00N9P",
          "location" => "浮遊機械都市",
...
}

みたいな感じっす。:bio:descriptionエイリアスになっております。長いので。上では特に付け足しませんでしたが、yes/no聞かれるんで安心して打ってください。
:me:user #{twitter.info['screen_name']}の略です。確認するのにめんどかったので入れた。
:screen_nameの更新はnoを優先で入れようか迷ったけど、重大すぎるので入れるのやめました。

コード

update_profile.rb

descriptionの複数行入力はEarthquakeの:updateコマンドからパクってきたんだけど、前々回のauto_restart プラグインみたいに入力がおかしかったのでsttyコマンドを挟んでます。

  • stty icrnlは「復帰キャラクタを改行キャラクタに変換する」で、\rを\nに変換
  • stty brkintは「ブレークによって割り込みシグナルを発生する」で、\Dを表示するんじゃなくてINTシグナルに変換

して対処してますん。
なんか俺の端末がおかしいっぽいな。プログラム起動するとstty設定忘れるんじゃろか?

class_evalはプラグイン読み込みに便利ですねぇ。

今後

現在は:name ほげほげとかで更新できるようになってるけど、:name(引数なし)の時はただの表示にした方がいいかも。んで更新は:update_name ほげほげに変える。オリジナルコマンドとの整合性的に言っても。長いのが嫌で現状こうなってんだけど、そこは各ユーザーにエイリアス張ってもらう方がいいのかもしれん。
あと:meは別プラグインに分けた方がいいだろうなぁ。名前被ってるプラグインコマンドも散見されるし:myselfにリネームしようかなぁ。

次回予告

3日坊主回避できるかな?
まだEarthquakeプラグインのネタ残ってんだけど、ちょっと趣向を変えてコマンドラインでも作ってみようかな。

fav_machine - favしたツイートの画像を保存するEarthquakeプラグインを書いた

Updated
このプラグインをsave_imageevent_chainに分割して、もっと汎用的にしました。使ってる人いないと思うけど、いればそちらをオススメします。

はい、予告どおりEarthquakeプラグインでございます。

はい、名前はアレです、おっさんなので。このプラグイン入れて常時起動しておけば、Earthquake上はもちろん、スマートフォンなんかのTwitterクライアントでfavしてもローカルに保存されます。Bitcasaのディレクトリなんか保存先に指定すればいいんじゃないでしょうか。

ちょっと話は逸れますが、特定のユーザーやキーワードを監視してツイートや画像を延々とBitcasaフォルダに溜め続けてBitcasaアカウントにそのフォルダのアクセス権与えるサービス(有料)を企画したんですが、Twitter API利用規約に「再配布禁止」って書いてあったので諦めました。そりゃそうだ。迂回方法なー。
ちなみにBitcasa APIは2013年10月24日現在まだありません。

インストール

Earthquake内で、

:plugin_install https://gist.github.com/babie/7076697

してください。

設定

Earthquake.config[:fav_machine] = {
  :dir => "~/.earthquake/fav_machine",
  :screen_names => ["babie"]
}

のように~/.earthquake/configに設定します。デフォルトで、~/.earthquake/fav_machineディレクトリが自動で作られ、自分がfavった画像が入ります。よく考えたら他人がfavった情報ってuser_streamに入ってなかったっけ?:screee_namesいらんかったかも。てへ。

使い方

マッスィーンなので自動です。

コード

ネスト深くてごちゃっとしてますねぇ。

Object#tryActiveSupportによる拡張です。この場合はeachできるならするって意味です。画像ついてないツイートのときはmediaが入ってないから。

んで、今のところ pic.twitter.com 画像URLのみサポートですわ。必要になったらnokogiri使ってtwitpicとかpixivとかもサポートするかも。需要ありそうだな。

ついさっきまで、デフォルト設定が「俺(babie)がふぁぼった画像を保存する」という、わりとひどい仕様になってたので、ユーザーの名前突っ込むように直したわ。てへ。

今後の拡張予定

あれだ、もっとマッスィーンっぽく色々できた方がいいかも。:retweet をチェインするとか。ちょっと考えるわ。

次回予告

明日もまたEarthquakeプラグインの予定です。ばいなり〜