2010-01-04 [長年日記]
_ xig_installer なんてものを書いてみた
まとめ
- twitter を IRC に変換して使いたい
- tig.rb を使いたいけどどこからどう入れたらいいかよく分からない
- なんか tig.rb をもっとうまく扱いたい
人向けのツールを作ったよ。
まずはなんでこんなものを作ったのか、理由から。
~/bin になんでも突っ込むのをやめたい
現在自分の twitter 環境は
自宅サーバ上の(tiarra + TwitterIRCGateway) + クライアントPCの LimeChat
という構成になっているんだけど、実はサーバ上のプログラムは
すべて ~/bin 以下に放り込まれている状態
になっている。とりあえず動かし始めるときにはこういうのよくやるんだけど、一昨年末(!)から始めたホームディレクトリの整理の一環で、~/bin 以下も基本的に全利用ホスト共通にしたいと考えている。当然、いちばんのネックは
サーバ用の ~/bin の内容をクライアントに持ってきたくない
というところ。
gem の奥底の実行ファイルを呼ぶのがなんかイヤ
すでに書いたけど twitter への大半のアクセスに TwitterIRCGateway を使っている。ただ実はずいぶん前からこれの Ruby 派生版である tig.rb に乗り換えようとしている。
TwitterIRCGateway は自分にとって Twitter を気軽にかつディープに利用するようになるうえで絶対に外すことのできないプロダクトで、とても感謝している。
だけれど、やはりどうも FreeBSD + Mono という環境のせいなのか、
- よく分からない落ち方をする
- 稀に無限死亡モード(起こしても起こしてもすぐ死ぬ)に入る
- dll の入れ替えを手動で行うアップデート方法がなんかイヤ
などの細かい不満があった。一言で言うと
どうも管理しにくい
ということである。そこで Ruby だけで動くし、gem で install できる net-irc に付属している tig.rb に乗り換えようかということなんだが、
肝心の tig.rb は gem でインストールした examples/ 以下で、gem の深い階層の下の方のファイルを呼び出すのはなんかダサイし扱いにくい*1
ということで、net-irc 以下の examples/ 以下にある *ig.rb の実行ファイル、つまり何かと IRC の Gateway として機能する実行ファイルを PATH の通ったところに install してしまうツールを用意することにした。
前置きが長くなったけど、そんなわけで xig_installer というツールを書いた。
紹介
wtnabe's xig_installer at master - GitHub
と
xig_installer | gemcutter | awesome gem hosting
に置いてある。
ぶっちゃけると実にくだらないツールで、gem の奥底にある *ig.rb の一覧を作り、それを gem を実行している Ruby 本体と同じパスに対して install, upgrade, uninstall するだけ。小判鮫すぎるうえにたいしたことはしてない。
でもこれだけで実行時には普通に PATH の通っているところにある tig.rb を叩けばよくなる。これが嬉しい。
使い方は簡単。
- Rubyをインストール
- Rubygems をインストール
- gem install gemcutter
- gem install xig_installer
これで net-irc も依存関係でインストールされるので、あとは
(rehash) xig_installer install tig.rb
とすると Ruby を置いてあるパスに tig.rb がコピーされる。Ruby のインストールに管理者権限が必要なら
sudo xig_installer install tig.rb
にする必要がある。
その他の機能は
xig_install help
で確認してほしい。
tig.rb の使い方はとりあえず割愛ということでごめんなさい。
*1 gem バージョンじゃなくて git clone したものを使うでしょ、という指摘はとりあえず置いておく。
2010-01-08 [長年日記]
_ OSX 10.4 で ffmpeg を使う 2010
2009年の振り返りを華麗にスルーした wtnabe です。いや、そのうち振り返ろうとは思ってますよ。マジでマジで。
話変わって今回もまたなんでそんな今さらなネタ!?という内容です。
結論 - macports の ffmpeg で ok
結論を先に言うと OSX 10.4 でも MacPorts (1.8.2) から ffmpeg (0.5) をインストールすれば*1 MPEG2 から DV へ変換でき、正しく QuickTime に解釈させて iMovie に取り込むことが可能になります。
ではその顛末をどうぞ。
背景 - 古めの iMac でビデオ編集
去年からちょこちょこ仕事でムービーの編集とか DVD のマスタリングを行っています。仕事とは言え売りもんを作ってるわけじゃなくて、あくまで内部保管目的ですが。で、カメラとか HDD の容量の問題がいろいろあって、この作業を
古めの Intel iMac (OSX 10.4 + HDD 160GB + iMovie HD 6)
で行っております。で、これまではカメラが DV だったので iMovie で取り込むだけでよかったんですが、MPEG2 で撮れるカメラからデータだけ持ち込まれる、というケースが発生。まぁ今どきのカメラなら MPEG2 とか MPEG4 とか当たり前ですね。でもこれは困る訳です。
iMovie は MPEG2 を編集できません。
しかもカメラ直結で iMovie に取り込めるんならいいんですが、データだけ渡されてしまうのです。
ffmpegX が動かない!
あーじゃあ ffmpeg で変換ね、ということで
ffmpegX a DVD, SVCD, VCD, CVD, VOB, DivX, XviD, H.264, PSP, iPod, MP4, MOV encoder for Mac OSX
から落として実行。(バージョンは 0.0.9y)最近は英語環境作らなくても ffmpegX で変換できて便利だよねーなんて思っていたら、
ffmpegX がソース movie の判別に失敗する
じゃないですか。えーっ。元のムービーが何か分からなければ正しく変換できるわけないやん。もしかしたらと思って一つ前の 0.0.9x を試してみましたが現象は一緒。
制限
ちなみに OSX 10.5 (PPC) + 0.0.9y で意図通り MPEG2 -> DV 変換を行えるのは確認済みであります。
じゃあその変換できる機械でやりゃいいじゃんと思うかもしれないけど、この PPC マシンは自分のメインマシンだし何より遅いのです。Windows マシンはあるのでそれで変換するという方法もなくはないけど、Windows マシンは HDD にまったく余裕がなく、さらに DV に変換しちゃったでかいデータを編集作業用の機械に持って行くのも一苦労な環境なのです。
笑え。
ffmpeg そのものを試すがコンパイル済みのものはダメ
GUI なんかなくたって変換さえできりゃいいよと思い立ち、ffmpegX のパッケージの中を覗いて ffmpeg コマンドを直叩き、すると変換の実行を試みるがほどなく落ちるorz 一つ前の 0.0.9x 内の ffmpeg コマンドは 0.0.9y のものよりは保つが、やはり途中で落ちるorz
いざ MacPorts へ
八方ふさがりかと思いかけたけど、これ IntelMac だし、もしかしたら MacPorts に ffmpeg あるかも、と思ったらこれがビンゴ。よーしということでここから以下の手順を踏むことになります。
- install DVD から XCode Tools をインストール
- The MacPorts Project -- Home からMacPorts をインストール
- sudo port install ffmpeg
- mp4v2 がコンパイル不能。どうも生成される Makefile がおかしいらしく、#21007 (mp4v2 build fails on Tiger (libtool: more than one: -current_version option specified)) – MacPorts から patch を取ってきて当てる
- libtheora に install DVD から入れた XCode は古いから上げれと言われる。ADC から 10.4 に入れられる XCode 2.5*2をダウンロード*3 -> インストール
- 全部インストールできた!
ここで初日は時間切れ。
QuickTime が解釈しない!
無事インストールできた ffmpeg で変換を試みる。えい。おぉ、変換できてるできてる!
……。
あれ?
変換済みの dv ファイルを QuickTime で開くといきなり「ファイルの最後に達しました」と言われる。もしかして変換後のデータ壊れてる?
ffmpeg は target ごとに default bitrate をよしなにしてくれない
試しに VLC で開くと再生できる。…けど。なんだこの荒さ?
あー bitrate の問題なのね。man を見ると確かに
Video Options
-b bitrate
Set the video bitrate in bit/s (default = 200 kb/s).
って書いてある。target フォーマットは DV だって言ってるんだから DV 用の bitrate にしてくれりゃいいのに。
というわけで
ffmpeg -i foo.mpg foo.dv -target dv -b 25000k
で変換したら QuickTime で読めたよ!
QuickTime で読めるということは iMovie に取り込めるということ。いやーめでたしめでたし。長い道のりだった。
2010-01-09 [長年日記]
_ iPod touchからのdelicous post復活
FLDR の pin を purge するときに delicious に投げられないと困るのだが、いつからか、それ以前には動いていたはずの bookmarklet が動かなくなって、なんでだろうなんでだろうと思っていた。ちゃんと注意して delicious を利用する記事を読み直していたらアプリのことが書いてあった。
何のことはない、必要なアプリを意図せずアンインストールしていただけだった。そうか、Mobile Safari だけで bookmarklet を動かすことはできないのか?
以下、昨日*1の Twitter のログ。
22:35:11 >wtnabe< できた。何もかも解明。1) 長いこと iPod touch で delicious に投げられないと思っていたのは必要なアプリをアンインストール していたから。 22:36:44 >wtnabe< 2) これは、アプリのインストールとbookmarkletの設定を 非同期に調べて非同期に実行したため、bookmarklet の実行にアプリが必要と いうことがまったく分かってなかったから。アプリの名前は Delicious Bookmarks 22:37:21 >wtnabe< 3) なぜあるときこれをアンインストールしたかというと、 これはブックマークの同期に時間が掛かりすぎてこけてしまうことがあったた め、同期目的では Yummy を入れたから。 22:40:44 >wtnabe< ということで同期の設定を切ってbookmarklet動作用アプ リとして復帰してもらった。なんかちょっとアホくさいけど仕方ないな。
ということで
- Yummy
- 同期、参照用
- Delicious Bookmarks
- bookmark post用
の2アプリ体制になってしまった。
cf.
DeliciousのiPhoneアプリ"Bookmarks"でどこでもブックマークできるようになった - Blog.IKUBON.com
*1 日記の日付上
2010-01-12 [長年日記]
_ 実行中の Ruby の install ディレクトリを知る
xig_installer のために実行中の Ruby のパスを知りたいと思ったが、当初は方法がよく分からず、とりあえず放置していた。これをちゃんと調べ直した。
ENVじゃなかった
- Twitter / wtnabe: ruby -e 'p ENV["_"]' って自分自 ...
- Twitter / eban: @wtnabe 合ってないです。bashとかが設定し ...
- Twitter / wtnabe: 実行中の Ruby のパスは ruby -e 're ...
2010-01-07
17:05:43 >wtnabe< ruby -e 'p ENV["_"]' って自分自身を実行している Ruby インタプリタを得る方法という理解は合ってますか? 17:14:06 <eban> @wtnabe 合ってないです。bashとかが設定してるので、shだ とだめ 17:35:11 >wtnabe< @eban あざっす。ちなみに純粋に Ruby スクリプトだけで 知る方法ってあるんですかね?
ごそごそ調べると require 'rbconfig' が必要らしい。
2010-01-08
11:15:51 >wtnabe< 実行中の Ruby のパスは ruby -e 'require "rbconfig"; p Config::CONFIG["bindir"]' ですか?
Config -> RbConfig 移行問題
- Twitter / wtnabe: Ruby 1.8 は Config で 1.9 は ...
- Twitter / Kazuhiro NISHIYAMA: @wtnabe ConfigでもRbConfigでも ...
- Twitter / Kazuhiro NISHIYAMA: @wtnabe 廃止予定だと思うので、新しいのだけで ...
- Twitter / Siena.: @wtnabe require 'rbconfig' ...
2010-01-12
18:56:55 >wtnabe< Ruby 1.8 は Config で 1.9 は RbConfig なのか。えーと どう書けばいいのかな。 19:00:59 <znz> @wtnabe ConfigでもRbConfigでも見えるからどっちでもいい。 もうサポートされてない古いのも対応したければConfigしかなさそうだけど。 19:10:01 >wtnabe< @znz Config はいずれ廃止予定とかではないんですかね? 個人的には 1.8.5 以降だけ相手にすればいいのでギリギリどっちでもオッケー なんすけど。 19:12:59 <znz> @wtnabe 廃止予定だと思うので、新しいのだけで良ければ RbConfigがいいのではないかと。 19:12:59 <nsiena> @wtnabe require 'rbconfig' 後に定義状況を const_defined? で調べて RbConfig = Config とかではだめです? 19:14:29 >wtnabe< @nsiena あーそんなんでいいですね。ありがとうございま す。
まとめ
最終的に出来上がった部分がコレ。
require 'rbconfig' RbConfig = Config unless defined? RbConfig RbConfig::CONFIG['bindir']
cf. Commit 651136d20e9925d9a86d224272acae13dad84c31 to wtnabe's xig_installer - GitHub
RbConfig は Ruby 1.8.5 以降で定義されているので、まぁ現役で使われている Ruby のほとんどはわざわざこの書き方にしなくても RbConfig を読めば大丈夫だと思う。
参考
ちなみにちょっと前にこんな議論があった。インストールディレクトリではなくて実行パスだけど、 basename 噛ませばいいだけだし、似たようなもんかと。
- [ruby-dev:39755] RbConfig.rubybin
- [ruby-dev:39755] RbConfig.rubybin - Tanaka Akira - org.ruby-lang.ruby-dev - MarkMail
MarkMail って使いやすいなぁ。すごいなぁ。
2010-01-13 [長年日記]
_ lftp mirror でドットファイルを対象にする
以前作った簡単なWebサイト向けRakefile で lftp mirror を使って一部のサイトの更新をしてるんだけど、ドットファイルが正しくミラーリングの対象になっていなかったので、この問題を修正した。正確には
全部アップするときにはドットファイルは対象になるけど、remote の list を取得するときに無視されてしまうので、問答無用でアップするだけで、local で削除しても反映されない。
という状態で、これは具体的には
.htaccess で認証を掛けていたものを外すことができない
というかなりおバカな状態になっていた。これへの対処は
set ftp:list-options -a
と設定を変更するコマンドを与えること。細かい方法はリンク先の Rakefile を grep してもらうとして、基本的には
lftp -e 'set ftp:list-options -a; mirror --delete FROM TO; bye'
って感じで使う。
うん、これで安心して使えるようになった。
_ ngrep で localhost 宛のパケットを覗く
みんな大好き ngrep. でも ngrep はそのままでは localhost へのアクセスの様子は抽出できない。man にはこのように書かれているので、
-d dev By default ngrep will select a default interface to listen on.
Use this option to force ngrep to listen on interface dev.
例えば
sudo ngrep -d lo port 80 (Linuxはこうだと思う)
とか
sudo ngrep -d lo0 port 80 (BSD系はこうだと思う)
とすれば localhost に立てた Apache への通信の様子をみることができる。
ちなみに default interface の決まり方、localhost を意味する loopback interface についている名前、これらは使っている OS によって異なるので、実際にどうなっているかは各自で調べてちょ。
2010-01-14 [長年日記]
_ Ruby の SSL の証明書検証の失敗でハマっていた
基本的な流れ
- TIG から tig.rb に移行しようと思い立った
- tig.rb が先日の DNS 障害の件を受けて SSL の証明書をちゃんと検証するようになった
- 自宅サーバの FreeBSD 上の Ruby で CA証明書が認識されておらず、結果 twitter.com のサーバ証明書を検証できずにコケていた。
tig.rb のアップデート - 冬通りに消え行く制服ガールは、夢物語にリアルを求めない。 - subtech
を読む前にハマってました><
だらだらと言い訳
- 本番と検証で3種類のRubyを使っていた
- ports ( Ruby 1.8.7 ) on FreeBSD
- MacPorts ( Ruby 1.8.7 ) on MacOSX
- Fink ( Ruby 1.8.6 ) on MacOSX
- ports のものだけ証明書がインストールできてなかった
- たまたま ports のものだけ tiarra と組み合わせていたので tiarra にあらぬ疑い
- ここでしばらくハマる
- 最終的には RFC と tig.rb の中身を読んで tiarra を除外、エラーメッセージを検索して Net::HTTP に矛先が
最初はバグを疑った。
14:25:09 >wtnabe< http://bit.ly/4tHhtN この patch 適用したら OpenSSL::SSL::SSLError 例外に出会えた。
- Twitter / wtnabe: http://bit.ly/4tHhtN この pa ...
- Bug#564168: net/http.rb: Error on page fetch exception (regression) - linux.debian.bugs.dist | Google グループ
このパッチを当ててみた。すると OpenSSL 周りの例外が出てきたので、とにかく SSL の問題だということで落ち着く。tig.rb を読むと SSL の verify_mode をセットしている部分があったので試しにこれを外してみたら動いた。
Twitter / wtnabe: さっきのパッチ当てた状態で tig.rb を htt ...
14:38:26 >wtnabe<さっきのパッチ当てた状態で tig.rb を http://gist.github.com/276924 こうしたら動いた。ここまできて残りは証明 書か。とりあえず tiarra のせいでもなんでもなかった。
Twitter / wtnabe: net/http を元に戻しても動いた。ということで ...
net/http を元に戻しても動いた。ということで openssl で使う証明書の問題 だった。もしくは tig.rb で証明書をまともにチェックしないようにしちゃう か、どっちか。
この状態で Net::HTTP を元に戻してみたがやはり正常に動作した。ということは本当に単なる SSL の問題。
その後、
Twitter / : @wtnabe tig.rb動かしてるのがFreeB ...
18:18:54 <otsune> @wtnabe tig.rb動かしてるのがFreeBSDだったので security/ca_root_nssいれた
と @otsune に情報をいただいたので、security/ca_root_nss を入れたけど、うまく動かず。最終的にはインストールされていたパスの問題で、
Twitter / wtnabe: VERIFY_PEER でも /usr/local/ ...
00:05:34 >wtnabe< VERIFY_PEER でも /usr/local/openssl/cert.pem@ -> /usr/local/share/cert/ca-root-nss.crt の link で動いたっぽい。
ということで tig.rb を元に戻して link 張って解決。長かった!
結局、インストールしていた証明書を正しく認識していなかっただけ。でも Net::HTTP のエラーの出方も分かりにくいよねぇ?
ちなみに他にもハマっていた人が。
Twitter / Yugui (Yuki Sonoda): このRubyはどのOpenSSLとリンクされるんだー ...
※ Ruby 1.9 の方は Ruby 1.9 - Feature #2579: Net::HTTP.start("www.ruby-lang.org", use_ssl: true) で SSL 利用を可能に - Ruby Issue Tracking System で証明書の検証をデフォルトで行うようになる新 API が入りました。
2010-01-15 [長年日記]
_ God + tig.rb へ移行した
ありがとう TwitterIRCGateway
Twitterに日常的にアクセスするようになってから、ずっと IRC に変換して利用しています。何度かあちこちに書いてる気がするけど、個人的には IRC と Twitter は繋ぎっぱなしで気が向いたときだけ見るとか、呼ばれたことに気づいたときだけ返事する、という使い方がとてもよく似ていると感じています。そしてこの接し方は自分への負担がとても少ないのです。だから自分の中ではまだしばらくはこの使い方以外想像ができません。
この IRC への変換にはずっと TwitterIRCGateway を利用してきました。ありがとうございます。Twitter を通じて交流が広がったのは間違いなく TIG のおかげです。
FreeBSD + Mono + TIG は少々気難しかった
そんなお世話になりっ放しな TIG でしたが、自宅サーバ上で動かしているため、
FreeBSD + Mono + TIG
という構成になってしまいます。これがやはり少々気難しいのですね。その辺の話は xig_installer なんてものを書いてみた にも書いたのですが、もともと .NET がよく分かっていないこともあって、
- いつ落ちるのか分からない不安
- 何が悪いのか、何を直せばいいのかよく分からない不安
がずっとあったのは確かです。まぁ、TIG が落ちようが Twitter が落ちようが、そんなに困らないことはすでに分かっていたんですが(笑)
そこで構成を変えようと、実はずいぶん前から思っていました。
こんにちは God + tig.rb
以下、やったことへのリンク。
- Godで初めてのプロセス監視
- topless で Activity Monitor もどき
- xig_installer なんてものを書いてみた
- Ruby の SSL の証明書検証の失敗でハマっていた
- Godにプロセスの起動順序を教えたい
実は自分の使っていた IRC クライアントとの相性の問題で乗り換えられなかった時期も長かったのですが、今は LimeChat + tiarra でまったく問題なく使えています。快適です。
tid がめっちゃ便利
その後、分かったことです。
なんで irc クライアントで Twitter 的に正しい reply を打てるのか不思議だったのですが、
- tig.rb 上で、twitter の status id と紐づけられる id が生成される
- tid というのはどうも typable map id の略っぽい?
- (あるいは tig.rb id ?)
こんな風に irc クライアント上に表示されます。
wtnabe アイディア出しするときは広い画面が欲しくなるなー [ipya]
ちょっとよくあるフッタと区別がつきにくいように見えますが、実際には(対応していれば)色分けされているので、そこまで見にくくはないです。
これを使って
/me reply <tid> HOGE
って打つと irc クライアントから in_reply_to を付加して reply できます。こんな使い方。
/me reply uno 簡単な言葉の吐き出しと並べ替えに OmniGraffle を使ってる のです。そのまま整理していくのにも便利だし。面倒なことを考えるときは手 書きになります。
すると他の Twitter クライアントから見てもちゃんと繋がりの分かる reply を打てます。今までの自分の reply には ID がついてなくて見にくいなと思っていた方々、ごめんなさい、今後はたぶん大丈夫です。
同じ要領で
/me fav <tid>
とすることで fav れるので、irc クライアントから目も手も離さずに twitter 上に self fav のメモ書きを作っていけます。実はこれがかなり便利。
TIG にもこの機能は入ってるっぽいんですが、よく分かってませんでした。でも調子に乗って self fav ってばかりいると後でまとめるのが大変なので、そこは考えどころですね。
Twitter が落ちてても困らないとか書いていながら Twitter に必死になりすぎじゃね?と思ったかもしれませんが、今まで挙げたやつ、全部他にも応用が利くからいいんですよ!
2010-01-19 [長年日記]
_ Godにプロセスの起動順序を教えたい
起動順を制御したい背景
先日、God + tig.rb 環境に移行したわけだけど*1、実際には自分の irc 周りの環境は下の図のようになっている。
twitter
|
internet
|
+----+ +---+--+
|ircd| |tig.rb|
++--++ +---+--+
| | |
+--+ +-+ +---+
| | |
+---+--+ +-+--+-+
|nadoka| |tiarra|
+------+ +---+--+
|
LimeChat ( Mac or iPod )
拙い図だけど四角で囲んである部分は自宅サーバ内で動いているプログラムである。制御したいプログラムだけ抜き出すと、
| tiarra | proxy |
| nadoka | bot |
| tig.rb | twitter gateway |
という構成になっている*2。
そしてここからが大事なんだけど、
起動の順番としては tiarra がいちばん最後
である。
God 以前
God 化する前、これらは単に sh スクリプトから順番に起動するだけだった。
nadoka sleep 2 tig.rb sleep 2 tiarra
である。サーバサーバなんて偉そうに言っといて中身はこんなもの。途中で何かの拍子にどれかのプロセスが落ちたらそれだけまた起こし直す。全部手動で、厳密には daemon プロセスではなく
ただずっと起きっぱなし
の状態になっていた。サーバ管理的にはあまりに稚拙だが、こと起動順に関しては
書いた通りの順番に起動する
という分かりやすいものだった。しかし God 化してしまうとこうはいかない。
God の起動の流れ
0.8.0 で確認したところ、以下のようになっている。
- God.watch の指定を読み込めるだけ読み込む
- watch の name を key にとる Hash に放り込まれる
- 読み込み終わったら Hash から一つずつ取り出し、autostart を指定してあったら(default で true)ただちにプロセスの起動を行う
ということは少なくとも Ruby 1.8 では
God.load の読み込み順、God.watch の出現順とプロセスの起動順は無関係
である。
はて困ったな。
無理矢理解決してみた。
ここでは単にプロセスの起動順序だけを問題にしたいので tiarra や nadoka など実際に使ったプログラムではなく、先日の xig_installer で動かしやすくなった tig.rb と wig.rb で試したみた。
上の gist のファイルを適当な名前で保存して
god -c CONFIG
で起動すると
必ず wig.rb の方を後で起動することができる。
順番は
ps ax | grep ruby
して PID を確認すると分かる。
ポイント
- あとから起動したいものの God.watch の記述の中で autostart = false を加える
- watch 定義のあとに Thread.new {} の中で無理矢理待ちたいプロセスの起動を待つ
待ちたいプロセスの様子は God.status[NAME][:state] で確認することができる。
実は、こんな書き方でいいのか分かってない。本当はもっといい書き方、正しい書き方があるのかもしれない。探したけどまだまだ God の情報は少ないし、英語になるとどういう言葉でこれを探せばいいのかも分からない。
でも目的は達成できている。
参考 - God.load の流れ
God.load は指定された設定ファイルを読み込むものなんだけど、内部で Dir.glob を使っているので一つずつファイル名を指定しなくても
God.load File.dirname( __FILE__ ) + '/*.god'
なんて書き方でまとめて読み込むことができる。最終的には Kernel.load が呼ばれるので Ruby の文法に則っていないと、この時点で弾かれる。
最初、読み込み順でプロセスの起動順を制御できるかと思ったけど違ったのでこの部分の読み込みはあんまり活かしようがないのであった。残念。
2010-01-20 [長年日記]
_ Godで簡単daemonize
今さらながら
なぜ daemontools を使うのか - kazuhoのメモ置き場
これを読んで、
通常のプログラムをそのままデーモン化できるから
という記述にうんうんと頷いていた。*1
今回は、daemontools もいいかもしれないけど God を使って daemon 作ろうぜって話。これは monit には真似できないよ。
cf. M/Monit | Wiki
Godでオレオレデーモンを作る
例えば以前作った Rswatch っていうログを監視するツールなんかも、一応 daemon のように動くけど、これ落ちたらどうすんのとか、システム起動時に自動的に起きるようにするのどうしよう、とかいろいろ管理しにくくて仕方なかった。デーモンを作ることは、実は 1)目的の動作を作り込むこと、2)デーモン化する処理を作り込むこと、の二つに分けられる。そう、2 は目的じゃない。期待する動作ではあるけれど。
God を使えば 2 の部分はすべて God 任せにできる。
また自分の中では daemon と言うとサーバとかこういう監視ツールをすぐに思い浮かべちゃうんだけど、別にそんな役に立つものばかりが daemon というわけじゃない。ここでは
とりあえず終了しないもの
は全部 daemon と呼ぶことにする。例えば twitter の bot なんかは daemon である。
Godを使った基本的なdaemon化の流れ
実は Ruby だけの話であれば別に God を使わなくても Process.daemon でも daemons でもいいのだが、これは後述することにして God の話を進める。
スクリプトを用意
まずは終了しないプログラムを作る。こんなものでもいい。
#! /usr/bin/env ruby require 'logger' i = 0 l = Logger.new( File.dirname( __FILE__ ) + '/counter.log' ) loop do l.info( i ) i += 1 sleep 1 end
これを例えば counter.rb という名前で用意する。これを実行すると当然1秒ごとに延々と数字を数え上げていく動きをする。
tail -f counter.log
で、その様子を確認できる。わざわざ絶対パスにしているのは、
daemon 化したときにカレントディレクトリが変わってしまうから。
これはもうそういうものなので肝に命じておく。
godファイルを用意
同じディレクトリに以下の内容で God の設定ファイルを作る。
God.watch do |w|
w.name = 'counter'
w.interval = 5.seconds
w.start = "ruby #{File.dirname( __FILE__ ) + '/counter.rb'}"
w.behavior( :clean_pid_file )
w.start_if do |start|
start.condition( :process_running ) do |c|
c.running = false
end
end
end
これを counter.god のような名前で保存する。スクリプトのパスを絶対パスにしているのはさっきと同じ理由。あるいは実行するプラグラムを PATH の通ったところに置けばもう少し話は簡単。
xig_installer を作ったのは実はこのためである。
実行ファイルの場所が変わってしまったら God の設定ファイルをいちいち書き換えなくてはならない。これは面倒くさいし、まず忘れる。
※ ここでは ruby に PATH が通っていることを期待した書き方になっているが、FreeBSD + ports のようにデフォルトの PATH が /usr/bin で追加するプログラムがすべて /usr/local/bin に入る環境は、この部分もフルパスにしておくべき。
god 経由で起こす
まず -D を付けて起こす。
god -D -c counter.god
するとスクリプトを daemon 化する様子、状態を監視している様子がそのままターミナルに表示される。正常に daemon 化できて監視できていれば ^C で終了する。
このとき、daemon 化したスクリプトはそのまま動き続ける
が、慌てず
god -c counter.god
と打って god プロセスの daemon 化を行う。すると
今動いているプロセスをそのまま監視対象にできる
ので、この状態で
god stop counter
と打てば止められる。あるいは
god terminate
とすれば丸ごと全部終了できる。
どうだろう。
単に終了しないプログラムを作っただけなのに God を噛ませることでちゃんと daemon 化できている。*2
また、ここまでの例はすべて Ruby で書いているが、daemon 化したいプログラムは何で書かれていても関係ない。Godにプロセスの起動順序を教えたい などで触れている tiarra は Perl 製だし、なんでもよい。
おまけ - Rubyでdaemonを作って管理するその他の方法
死活監視やリソース消費を監視して自動的に再起動、などの機能が要らない場合は以下のリンク先も参考になる。
- Ruby: daemon (Japanese)
- すごく丁寧で、Ruby 1.8 向けも含めて参考になる
- 僕たちが待ち望んでいたRubyスクリプトをデーモン化する方法 - (rubikitch loves (Emacs Ruby CUI))
- Ruby 1.8 も含めて楽をする方法
ただしこれらの情報だけでは作った daemon をコントロールすることができない。Rubyスクリプト限定でいいなら以下を使うと God より楽に管理できる。
これを使えば基本的には
Daemons.run(デーモン化したいスクリプトのファイル名)
だけで済む。
ま、もちろん監視はできないけど。
2010-01-22 [長年日記]
_ イントラgem server運用開始
2009.10.29
09:14:00 >wtnabe< gemcutter サーバをイントラに立てられると便利だろうな
2009.12.03
21:14:52 >wtnabe< イントラに gemcutter サーバがあると便利だよね
酒を飲んだとかでなく、平気で同じことをくり返し言うようになってきている。まいりましたな。
それはともかく、gem server が簡単すぎてビビる件 を書いてから知らん間にずいぶん時間が経ってしまったが、やっと活用し始めた。
2010.01.22
11:37:34 >wtnabe< イントラに gem push したいしたいと思ってたけど、とり あえず Rakefile に scp する task 足した。permission を考えたくないから DAV 経由にした方がいいかなぁ。
どうしたかというと、cutagem で作った Rakefile に直接こんな task を書き足してお茶を濁した。我ながらひどい。
desc "push gem to intra gem server"
task :push_gem do
name = "#{NAME}-#{VERS}.gem"
sh %{scp name HOST:/PATH/TO/GEM_SERVER/gems}
end
こんなん。サーバ側では
/etc/cron.hourly/generate_gem_index
#! /bin/sh GEM_ROOT=/PATH/TO/GEM_SERVER gem generate_index -d $GEM_ROOT chown -R Webサーバにしたんだっけ $GEM_ROOT
こんな感じで回してる。
scp で投げるのやめて DAV で投げられるようにすれば権限とか owner とか面倒なこと考える必要なくなるような気もするけど、あんま考えてない。どうせしばらく自分しか push しないだろうし。
あとはイントラで他に動いてるホストの
/etc/gemrc
に、ひっそり
:sources: - http://HOST/PATH
で、gem を探すサーバを書き足してやる。
ちなみにイントラのgemはライブラリではなくshlauncherで固めたツール群の配信を想定している。
2010-01-24 [長年日記]
_ freebsd-update で 6.4-RELEASE に
自宅サーバの FreeBSD は基本的にはお気楽実験用なんだけど、DNS cache サーバを兼ねているので事故るとまずい。そういう意味も含めて古めのものを追っかけてるんだけど、今まで使っていた 6.3 が 2010年1月いっぱいでサポートされなくなるので 6.4 に上げた。
freebsd-update
手順はこんな感じ。
freebsd-update -r RELEASE upgrade freebsd-update -r RELEASE install shutdown -r now freebsd-update install
library が上がって ports 上げろって言われたので以下の作業を追加。
portupgrade -af freebsd-update install
念のためもう一度再起動しといた。ports の full rebuild は途中で設定を何個か聞かれながらで丸半日以上掛かったかな?
そうしたら ircd-hybrid, nadoka, tiarra の起動周りがイマイチ、nadoka がうまくいったりいかなかったりしてる。もしかして nadoka が ircd より先に起きたりしてるのかもしれない。どうにか手動で順番もきっちり意識して再起動して意図した動きに。やっぱ ircd も God 管理にしなきゃダメだなぁ。
Trac が 0.11 で動かなくなった
※ 解決済み。なぜか mod_fastcgi が無効になってた。これを戻しただけで動いた。
Trac, pysvn 周りはいつもちょっとハマるので面倒で放置していたんだけど、portupgrade -af で一緒に上がってしまって案の定動かなくなった。
どうも PYTHON_EGG_CACHE が設定できていなくて cache を作るディレクトリが見つからないみたいなんだけど、
Apache + FastCGI
で動く設定が出てこない。なんか FastCgiConfig --initial-env じゃなくて PythonOption で渡すみたいなんだけど、edgewall の方にキチっと書かれてないなぁ。Python 読める人が自力で解釈してブログに書いてあるのはありがたいんだけど、0.11 が出てずいぶん経つのに情報が整理されてないのは Python 製のツールっぽくない印象。
とりあえず自宅で Trac は重要じゃないから今度考えよう。
2010-01-26 [長年日記]
_ Mechanizeで無茶をする
自分にとって Mechanize による自動化はたいがい無理を通す行為である。分かりやすく言えば API なんかない、あるいはあっても足りないみたいな状態で、それでもどうにか自動化したいから Mechanize を使う。
Mechanize が持っている標準的な機能だけで済んでいる場合はまだかなりマシで、実際のところ無理というか「無茶」なレベルに突入してしまうことが、なぜかそれなりにあったりする。具体的には HTML が壊れているのでパースに失敗して、あるはずの要素がなくなっていたりする場合などである。
今回はそんな無茶の一部をご紹介。
パーサを Hpricot に変える
ずばり基本でしょう。
Mechanize は 0.9 以降デフォルトパーサを Hpricot から Nokogiri に切り替えているが、そもそも Nokogiri は HTML 用にできていない。XML 用の道具に、 Hpricot によく似たインターフェイスを付けたものである。HTML は自由度の高い書式で、XML 用のノコギリでは歯こぼれを起こすことがよくある。
そこでこの設定(0.9.0 〜 0.9.2)。
require 'hpricot' WWW::Mechanize.html_parser = Hpricot
cf.
- Mechanize の parser を Hpricot にする
- hpricot's hpricot at master - GitHub
- hpricot | gemcutter | awesome gem hosting
0.9.3 (以降?)はサブクラスの利用に注意
html_parser がインスタンスのアクセッサとして定義されたのでインスタンスごとに parser をセットできるようになったのはいいんだけど、Mechanizeクラスオブジェクトのインスタンス変数を self.class で参照して自身に書き戻しているので、
サブクラスに反映されない
状態になっている。(少なくとも Ruby 1.8.7 では parser が nil になって動かなかった。)定義部分は以下のような感じ。
class Mechanize
...
@html_parser = Nokogiri::HTML
class << self; attr_accessor :html_parser, :log end
def initialize
...
@html_parser = self.class.html_parser
end
...
end
動かすとこんな感じ。
$ cat sub_mechanize.rb class SubMechanize < WWW::Mechanize end $ irb irb(main):001:0> require 'mechanize' => true irb(main):002:0> a = WWW::Mechanize.new => (snip)irb(main):003:0> a.html_parser => Nokogiri::HTML irb(main):004:0> require './sub_mechanize' => true irb(main):005:0> b = SubMechanize.new => (snip) irb(main):006:0> b.html_parser => nil
恐らく Mechanize のインスタンスについては html_parser のセット方法に互換性をとりつつインスタンスごとに設定できるようにしたかったためにこうなったんだろうけど、いつもデバッグしやすいように独自のサブクラスを噛ましていた*1ので、まったく parse できない現象にハマってしまった。仕方ないのでサブクラスの中で独自に定義することにした。
class SubMechanize < WWW::Mechanize
def initialize
super
@html_parser = Hpricot
end
end
こんなんでいいのかな。
cf. RubyのMechanizeの0.9.3が6月8日に出てたっぽい - きたももんががきたん。
Field を作る
これはまだ初級。
Form#add_field!( name, value )
無駄に JavaScript に分離したフォームの場合、必要な field が HTML 上に存在しないことがよくある。その値を無理矢理 form 上に反映するために使う。
FileUpload を作る
これに気づいたときにはけっこう嬉しかった。add_field! では Field は作れても FileUpload は作れないから。
どうやるかというと、Form オブジェクトに対して instance_eval を使う。
Form#enctype = 'multipart/form-data'
Form#instance_eval {
@file_uploads << WWW::Mechanize::Form::FileUpload.new( name[, filename] )
}
もともと file_upload が存在しない form として解釈した場合は enctype が違うことがある(というか指定してないかも)ので手で変更してあげると吉。
Form を作る
instance_eval() を思い出してしまえば簡単。もう form そのものを解釈できませんでしたという凶悪な HTML 向け。
Page#instance_eval {
@forms << WWW::Mechanize::Form.new( node[, mech, page] )
}
node には Hpricot::Elem オブジェクトを入れてあげれば ok.
このとき、Hpricot::Elem になれば元の文字列はなんでもよいので、
Form.new( Hpricot( String#scan( /<form.*?>.*?<\/form>/m ).to_s ).at( 'form' ) )
みたいなこともできる。HTML が壊れているので正規表現でいったん form だけ引っこ抜いて、それを Hpricot オブジェクトに戻してやって Form を作る。途中の整形も思いのまま。
Page を作る
Page を作るのはちょっと手が掛かる。以前作ったものを gist に置いてあるので参考になれば嬉しい。
*1 http://gist.github.com/76140