自宅鯖があぼーんした

事の起こり

今日の朝起きたらtwitterのリロードができなくてブラウザが腐ったのかと思った。 ブラウザ再起動しても状況改善せず。 よくよく気づいてみたら妙に部屋が静かだ・・・ 見てみたらサーバちゃん息してないの・・・ サーバマシンはPPPoE接続とルーティングもやらせていたのでそれも止まってしまったという状態。

サーバの状況

起動してみたらfsckしろよクソって言われた。 (最近ずっと怪しかったけど)ディスクやべーのかなぁ、 (ずっと気にかかってたけど)とうとう逝ったのかなぁ、 (ここ数年ずっとそうだけど)カネねえなぁとか思いながらfsck実行してみる。 fsckが全然終わらなくてそのままにして仕方なく仕事行く。

仕事終わって家に帰ったら妙な臭いがする。 サーバの電源落ちてる。

なぜ落ちてたか

もっかい電源入れてみる。 なんか定期的にブンって音がするような気がする。 箱の中覗いてみたらCPU1のファンが数秒に1回、数センチしか動いてない!! CPU2のファンなんて全く動いてない!!! ケースの一部が透明になってるのが初めて役に立った。

ファンを指で回してみようと思ったけど硬くて動かない。 ホコリとか詰まって硬くなってるのかな? ファン外してみたらCPU表面が焦げてるような感じ。 臭いの理由と電源落ちてた理由はこれか・・・

シングルユーザモードでの起動はギリギリ大丈夫なようだ

ネットワーク復旧作業

とりあえず起動してfsckだけ終わらせた。 /homeは巨大なのでとりあえず無視。 /usrが読めるようになったのでとりあえずPPPoEの設定が読めた。 無線LAN構築に使ってるAPがルータにもなるのでPPPoEを喋らせてみる。 設定ドキュメントがなかなか難しくて困った。 なんで家電化された機器はあんなに設定が難しいんだろう・・・ ネットワーク構成変更中にAPをもともと繋いでたはずのLANケーブルが消失する。 どこいったんや・・・ 死んだサーバを繋ぐケーブルがない。

fsckが終わってディスクのマウントできるようになった。 通常起動もできるはずだが、起動プロセス中にリブートかかる。 ここから先は無理っぽい。

障害範囲

  • ネットワーク全般
    • APをルータにして復帰
  • web
    • 全部herokuに移してしまおうかと画策
    • この時期コミケ準備会とか見に来るっぽいからまじヤバイ タイミング最悪
    • mysqlのデータdumpだるい
  • ストレージ
    • 一部復旧できないかも
    • 大量のエロ画像の運命は!?

サーバ復旧作業予定

  • 新しくCPUファン買って付け直せばなんとか起動してくれるかなぁ・・・
    • AM2時代のOpteron用ファンなんて見つかるのかね
  • webサーバはクラウドに持って行きたいなぁ
  • ストレージだけ手元に持たないといけないからそれどうしよう
  • 確か1TBのディスクが入ってるからそれ経由して他のマシンのHDDにデータ移動できるかな
    • RAIDがむしろ邪魔
  • 圧倒的にカネが足りない

突然ネットワークが使えなくなって気づいたこと

  • LANケーブル何本か余分に持っていたほうがいい
  • テザリングさえあれば何とかなる
  • タブレット超便利
  • 視覚よりも聴覚・嗅覚
  • nasneはネットワークが切れてPS3から見えてなくても問題なく動く
    • 繋げた瞬間に録画済み動画がどかっと増えた
    • 障害中のもバッチリ撮れてた
    • 上手く設計できてると感心した
  • ファンが回らなくなったのは初めて サーバ止めずに掃除とかできんの?
  • トラブル楽しい
    • 嘘。死ね

Gem-src

gem-src がすごく便利。

よくあるパターン

1. gemのソースコードを読みたい時

gemを使ってるとソースをがっつり追いたくなる時がありますが、そんな時どうしてますか? 最近編み出したのが、とりあえずインストールされたgemをgitに突っ込むというワザです。

rvmを使っているのでこんな感じに。

$ cd ~/.rvm/gems/ruby-1.9.4-p194@hoge/gems/hoge_gem
$ git init; git add .
$ git grep hogehoge

これでも十分に便利ですけど、gemのディレクトリまで行くのが面倒ですよねー。

2. gemに Pull Request したい時

gemの名前は知っていても誰が作ったかなんて覚えてませんよね。 hubコマンドもユーザ名がわからないと使えません。 (なぜか hub clone rails はできるんだけどなんでだろう・・・) 結局githubで検索してリポジトリを探さないといけません。

gem-srcを使う

gem-srcを使うと、gem install した時に同時にgithubからソースコードを git clone してくれます。 その前に .gemrc にダウンロードするディレクトリを設定しておきましょう。

$ echo "gemsrc_clone_root: ~/src/gems" >> ~/.gemrc

この状態でgem install してみましょう。

$ gem install ero_getter
Fetching: zipruby-0.3.6.gem (100%)
Building native extensions.  This could take a while...
Fetching: ero_getter-1.6.0.gem (100%)
Cloning into '/home/masaki/src/gems/ero_getter'...
remote: Counting objects: 665, done.
remote: Compressing objects: 100% (310/310), done.
remote: Total 665 (delta 306), reused 634 (delta 278)
Receiving objects: 100% (665/665), 274.95 KiB | 165 KiB/s, done.
Resolving deltas: 100% (306/306), done.
Successfully installed zipruby-0.3.6
Successfully installed ero_getter-1.6.0
2 gems installed

$ ls ~/src/gem
./  ../  ero_getter/  gem-src/

入りましたね!

$ cd ~/src/gem/ero_getter
$ git remote show origin
* remote origin
  Fetch URL: http://github.com/masarakki/ero_getter
    Push  URL: http://github.com/masarakki/ero_getter

hubでforkした時と同じ状態なのでそのままhubコマンドでforkも Pull Request もできそうです。

$ hub fork
$ hub pull-request

やったーforkできたよー多分 Pull Request もできるよー

条件

gemのメタデータにあるhomepageの項目がgithubっぽかったらcloneするらしいです。 なのでみんなhomepageはgithubのurlにしましょう。

ゆっくりにアクセントをつけてみた

あらすじ

前回、 棒読みちゃんと同じAquesTalkという音声合成エンジンを使い、 Linuxでも棒読みができるアプリケーションyukkuriを作った俺。

ところがこのyukkuriには致命的な欠陥があったのだ!!

実際に喋らせてみた

棒読みちゃん以上に棒読みだった。 完全に真っ平ら。ぺったんこ。 実は棒読みちゃんは棒読みじゃなかった

どうやらAquesTalkは自分でアクセント記号を付けないといけないようです。 棒読みちゃんにもアクセント付きに変換された文字列が表示されます。

アクセントの付け方

やはりこのままmecabを使いたいので 「mecab アクセント」 でぐぐってみたところ、 Unidicというmecab用アクセント辞書があるようです。 これを組み込めばアクセントがうまく処理できそうです。

アクセントの仕様はかなり複雑なので一部から実装していきます。 実際にニコ生で使って気になるアクセントがあったらテストを書いて修正していくという方法を取ります。

アクセントのテストデータ

アクセントの処理を実装するにあたって、もちろんテストデータが必要になります。

方法1. 棒読みちゃんに喋らせてみてアクセント付き文字列を使う

残念ながらLinuxではWineを使っても棒読みちゃんが動かなかったので断念。

方法2. TASETのデモページを使う

CRFを用いたアクセント結合推定 というなんか難しいサイトの デモページ で文章にアクセントをつけることができます。 ただし変換にかなり時間がかかり、実際に喋らせてみるとなんかちょっと変です。

方法3. 聞く

2で作ったアクセントを実際に喋らせてみて、 自然に聞こえるように微調整するのが一番良さそうです。

ただし、細かい部分では正しい日本語の発音なんてのは知らないので、 こんなもんなのかで済ませます。

結果

たぶん棒読みちゃんより正確なアクセントが付けれるようになった。 確認はコードを実行してみるか、 ニコ生の放送を見てくださいな。

Yukkurid

ゆっくりしていってね!

yukkuridとは?

棒読みちゃん+デーモン

ニコ生をするのに欠かせないの棒読みちゃんというWindowsアプリがありますが、 もちろんWindowsアプリなのでLinuxでは動きませんしWineで動くとしても動かしたくないですよね。

でもやっぱり棒読み機能がないと不便なので作ってみました。

yukkurid

棒読み部分

棒読みちゃんは内部でAquesTalk2という読み上げライブラリを使用していて、 これはWindows版だけでなくMacOS版とLinux版のライブラリも配布されています。 (ただし有料)

棒読みにはそんなに機能は必要無いので、ほとんどサンプルファイルをそのままコンパイルして、 十分実用なバイナリになりました。

デーモン部分

sinatraでちょちょっと作って終了です。 POSTで受け取ったデータを、漢字が読めない棒読みちゃんのためにmecabで読みに変換して、 棒読みちゃんに渡して吐かれたwavをALSAに食わせる。 ワンライナーで瞬殺です。

これでPOSTで送信するとコンピュータがゆっくり喋ってくれるようになりました!

次の展望

デーモンにさえすれば、実際にニコ生のコメントをchrome extensionから送信して、 コンピュータが喋ってくれるようになるはずです。 EroGetterの時もそうだったけどいちいちデーモンにするのがめんどくさいですがしかたないですね・・・

というわけでchrome extensionのコメビュを作るか既存のをどっかからパクってきて繋ぎこみたいと思います。

あとは今だと喋ってる間ブロックするので非同期化と、それにともなってちゃんとしたキューイングを。

ところで

非商用の個人の開発ライセンスを買ったのですが、

  • アプリの機能として、外部ソフトから当社製品ライブラリの機能を呼び出し可能なインターフェースを持たないこと。

ってどういうことなんだろう? これをラップした raques.gem みたいなのは作っちゃダメなのかな? .soと.hは自分で用意してねっていう形でならok? といっても今コンパイル済みバイナリを ./bin/yukkuri に置いてるけど、これも.so無いと動かないよね? かといって.so同梱できるようなライセンスじゃないよね? そんなんしたら商売上がったりだよね? あんまC方面わからんからこのライセンスが一体何をしてよくて何をしていけないのかわからない。

RailsログでJSONの一部を隠す方法

Rails のログ出力には filter_parameters という一部のパラメータを隠す便利な機能があります。

デフォルトでは config/application.rb に

1
  config.filter_parameters += [:password]

と書かれています。

ここにフィルタしたいパラメータを追加すれば [FILTERED] という文字列に置き換えられます

例えば開発者が user_id を見てはいけない社内ルールがある場合

1
  config.filter_parameters += [:password, :user_id]

とすればいいわけです。

ためしてみる

GETの場合

GET /posts?password=abc123

を叩いた場合ログに

Started GET "/posts?password=[FILTERED]" for 127.0.0.1 at 2012-09-20 05:16:44 +0900
Processing by PostsController#index as HTML
  Parameters: {"password"=>"[FILTERED]"}

とURLもパラメータも [FILTERED] に変えられて出力されます。

POSTの場合

POST /posts
data: password=abc123

を叩いた場合ログは

Started POST "/posts" for 127.0.0.1 at 2012-09-20 05:18:12 +0900
Processing by PostsController#create as */*
  Parameters: {"password"=>"[FILTERED]"}

上手いことフィルタされますね。

Rails の標準的なPOSTパラメータ形式の :post => {:password => “abc123”} はどうでしょうか

Started POST "/posts" for 127.0.0.1 at 2012-09-20 05:22:33 +0900
Processing by PostsController#create as */*
  Parameters: {"post"=>{"password"=>"[FILTERED]", "title"=>"unko"}}

これもきちんとフィルタできてます。

特殊な事情

ある特殊な事情から「パラメータは json というキーに json 文字列で入ってくる」というプロジェクトがある場合どうなるでしょうか? ほんとにそんなプロジェクトがあるんでしょうか? ファックですね。 どうせ原因はこのAPIを叩いてくるphpが低脳だからに違いありません。

POST "/post"
data: json={\"password\":\"abc123\"}

という状況です。 ためしてみましょう。

Started POST "/posts" for 127.0.0.1 at 2012-09-20 05:30:56 +0900
Processing by PostsController#create as */*
  Parameters: {"json"=>"{\"password\":\"abc123\"}"}

残念、丸見えです! かと言って :json を全部フィルタするのは開発の妨げになってしまいます。

一番かっこ悪い解決方法

filter_parameters には、|key, value| を引数に取る lambda を指定することもできるようです。 サンプルでは

1
2
3
lambda do |k,v|
  v.reverse! if k =~ %rsecret/
end

と書かれていますが、reverse! と破壊的メソッドを使っているのがすごく嫌な予感がします。 どうやら value のオブジェクトを破壊的に変更しないといけないようです。

こんな感じで書いてみました。

ログの出力結果は

Started POST "/posts" for 127.0.0.1 at 2012-09-20 05:49:22 +0900
Processing by PostsController#create as */*
  Parameters: {"json"=>"{:password=>\"[FILTERED]\"}"}

上手くいってます!!

ポイントは v.replace でオブジェクト自体は変えずに文字列を変更してる点です。 これを

1
v = json.to_s

とやってしまうと上手く動きません。

とりあえずこれで泣く泣く json の入力すべてを捨てる必要はなくなりました。 やった!!

問題点

  • [FILTERED] が定義されているクラスのファイルを require してもクラスが存在しないと言われる? ロード順の問題?
  • 普通に指定されたフィルタしたいパラメータを lambda 内で再利用できない
    • lambda での指定は最後に評価されるので
    • before_filter (まさに!) があれば解決?

ほんとにやりたい一番かっこいい解決方法

:json が渡ってきた場合は filter とかする前に JSON.parse してハッシュにしたい。 そうすれば普通に filter_parameters += [:password, :user_id] を指定するだけで、 :post => {:password => “abc123”} がフィルタできたのと同様にフィルタできるはず。

次はこれを調べる。

EroGetter With Your Favorite Site!

EroGetter対応サイトを増やす

サイト定義を書くことで対応サイトを増やすことができます。

準備するもの

  • まともな(ry
  • 対象サイト
    • 記事urlの正規表現
    • サイト名
  • エロにかける情熱

今回追加するサイトは http://example.com/archives/\d.html えろいぐざんぷるどっとこむ とします。

ダウンロード

git clone git://github.com/masarakki/ero_getter.git
cd ero_getter
bundle install
git checkout -b add_example_dot_com

クラス名の決定

サイト名が えろいぐざんぷるどっとこむ なのでクラス名は EroExample とします。 通例に従って、ファイル名などは ero_example のようなスネークケースになります。

サンプルファイルの追加

mkdir spec/samples/ero_example
wget http://example.com/archives/1234567.html -o spec/samples/ero_example/sample.html

その1〜 のように連番になっているサイトの場合は先頭、中間、最後のファイルをそれぞれ別名で保存してください。

テストの追加

spec/downloader/ にある他のサイト定義のファイルをコピーして、 spec/downloader/ero_example_spec.rb を作ります。 連番サイトは nijigazou_sokuhou_spec.rb、 そうでないサイトはそれ以外のファイルがいいでしょう。

ファイルを開いてそれっぽいところを変更します。

1
2
3
4
5
6
7
8
9
10
11
describe EroExample do                                     # クラス名を変更
  subject { @dl }
  let(:url) { 'http://example.com/archives/1234567.html' } # urlを変更
  before do
    FileUtils.stub(:mkdir_p)
    @dl = EroExample.new(url)                              # クラスを変更
    fake(:get, url, 'ero_example/sample.html')             # サンプルファイルを変更
  end
  its(:sub_directory) { should == '1234567' }              # サブディレクトリ名を変更
  its("targets.count") { should == 26 }                    # サンプルファイルの画像数に変更
end
rspec spec/downloader/ero_example_spec.rb

を実行してFailがひとつも出なくなるようにサイト定義をいじります。

別のターミナルを立てて

bundle exec guard start

でguardを動かしてCIすることもできます。

サイト定義の追加

lib/downloader/ero_example.rb に、 EroGetter::Base を継承したクラスを作ります。 対象サイトがLivedoorBlogの場合は、 EroGetter::Livedoor が使える場合もあります。

1
2
3
4
5
6
7
8
9
10
11
class EroExample < EroGetter::Base
  name 'えろいぐざんぷるどっとこむ'
  url %r{http://example.com/archives/\d+.html}
  target "a > img.ero-file" do |tag|
     tag.parent[:href] if path.parent[:href] =~ /jpe?g|gif|png/
  end
  sub_directory do
     url.match(/(\d).html/)[1]
  end
  filename {|attr| "%04d%s" % [attr[:index], attr[:ext]] }
end

肝になるのは target のところです。 targetで取得したい画像のurlを集める方法を指定します。

targetの引数には取得したいタグをcssで指定します。 そのタグに対してブロックの処理をしてurlを取得します。 最終的に、 画像のurlの文字列 を集めなければならないことに注意してください。 (ただタグを返すだけだとurl文字列ではないということです)

この例の場合、aタグ直下の、クラスにero-fileが付けられたimgタグを全部持ってきて、 その親要素のhref属性が画像ファイルなら、そのhrefを文字列として返す、という条件になります

rspecを走らせて、取得した画像の件数が一致するまでtargetを調整しましょう。

連番サイト

その1〜 など連番で記事が作られるサイトは、ひとつの記事から全てのページを取得する設定が可能です。

lib/downloader/nijigazou_sokuhou.rb を参考にしましょう。

1
2
3
4
5
6
7
  connection ['a[rel=prev]', 'a[rel=next]'] do |path|
    path.text.match(Regexp.escape(title_part))
  end

  def title_part
     title.match(/(.+?)(その.+)?$/)[1]
  end

connection で前後のページへのリンクをcssの配列で指定します。 ブロックを渡してtrue/falseを返すことで、そのリンクを本当に取得するかどうかを調整できます。 この場合、タイトルの「その〜」より前の部分が一致したら本当に取得する事になります。

rspecも spec/downloader/nijigazou_sokuhou_spec.rb を参考に、 前後ページが正しく取得できるか、次がなければnilになるか、をテストしましょう。

Pull Request

あとはPull Requestすれば取り込まれます。 Pull Requestの前にはコミットをひとつにまとめるとかっこいいですよ。

git rebase -i HEAD~10 (十分大きい数字)

コミットIDの前の文字列を s にすると、その前のコミットと合成されます。 作業をひとつのコミットにまとめて、コミットメッセージを適切なものに直し、 pushしてPull Requestで完成です!

Enjoy!

How to Use EroGetter

快適エロ生活はEroGetterで。

EroGetterとは?

エロ画像収集に特化したダウンローダーです。 汎用ダウンローダーを使う場合に必要になる、余計なURLを除外したり、保存場所を指定したりといった面倒な部分を極力排除してあります。 サーバとしても動作するので自宅サーバに立てておけば会社にいながら今夜のオカズを集めておくこともできます。

サイト定義を追加してプルリクエストすることにより対応サイトを増やすことができます。

使い方

収集だけが目的の場合はこれだけでokです

用意するもの

  • まともにRubyが動くまともなOSのコンピュータ
    • つまりWindowsでの動作報告を待っています
  • ruby-1.9.3のインストール
  • Google Chrome / Chromium

Chromeエクステンションのインストール

git clone git://github.com/masarakki/ero_getter_chrome_extension.git
cd ero_getter_chrome_extension
bundle install
#( src/ero_getter.coffee の url: を変更してero_getter_serverのホストを変更可能 )
make

Chromeエクステンションのページで ero_getter_chrome_extension のディレクトリを指定してインストール。 Chromeのアドレスバーの右側におっぱいアイコンが追加されたら成功です。

サーバのダウンロード

git clone git://github.com/masarakki/ero_getter_server.git
cd ero_getter_server
bundle install

バックグラウンドタスクの実行

rake backend:start

サーバの起動

rackup -p 9393

http://localhost:9393/ にアクセスするとサーバステータス、キュー、対象サイトのリストなどが見れます。

あとは対象画像サイトの記事ページでおっぱいアイコンをクリックすれば $HOME/ero_getter 以下に保存されます。

更新

git pull origin master

更新があったら

bundle install
rake backend:restart
kill -INT `cat rack.pid`
rackup -p 9393

とりあえず今回は使い方だけのまとめで終了ー

Remove Uploaded Files in Rails (With Unicorn)

Rails + unicorn環境でファイルアップロードを扱うと /tmp/RackMultipart… というファイルが作られなかなか消えません。 UploadedFileクラスの中身はTempfileなので

1
params[:file].close(true)

とかやってみてもやっぱり消えません。 どうやらunicornを再起動して少し経つとGCが起動して消えるような動作をします。

これに対処するため某社では5分に一度unicornを再起動しているようです。 これではまるで「僕なら、10リクエストごとにApacheを再起動しますね」と言ってるバカと一緒じゃないですか。 そんなのはよくないですよね。

というわけで色々やってみた結果 「すぐには消えないけど高々プロセス数以下のファイルしか残らない方法」 を見つけました

1
2
3
4
5
def remove_uploaded(file)
  tmp_path = file.instance_variable_get(:@tempfile).path
  File.unlink(tmp_path) if File.exists?(tmp_path)
  file.instance_variable_set(:@tempfile, nil)
end

このままッ!! nilを! こいつの! @tempfileに……… つっこんで! 殴り抜けるッ!

これで/tmpが無限に肥大化することはなくなるようです

ヘルパーメソッドのspecってどう書くの?

twitter-bootstrapのよくあるパターンを簡単にかけるヘルパーを集めたgem を作ろうとしてみた けどrspecの上手い書き方がわからない

例えばフォームの実行ボタン類を置くエリアを作るヘルパー

helper

1
2
3
4
5
6
7
8
require 'action_view'
module BootstrapHelpers
  include ActionView::Helpers::TagHelper
  def actions(&block)
    content_tag(:div, :class => 'form-actions', &block)
  end
end
ActionView::Helpers.send(:include, BootstrapHelpers)
使い方(haml)
1
2
= actions do
  f.button :submit, :class => "btn btn-primary"

一応これで使えるには使えるが もちろんこれのrspecを書きたい

helper_spec.rb

1
2
3
4
5
6
7
8
9
10
describe BootstrapHelpers
  include BootstrapHelpers

  it "div.form-actionsで囲まれる" do
     tag = actions do
        'hoge'
     end
     tag.should match /class="form-actions"/
  end
end

みたいな感じで書けると思ったけど 実行してみると

1
undefined method `output_buffer=' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_1:0x0000000241e5b8>

どうやらblockを渡すと capture(&block) される中でoutput_buffer=ねーよって言われるらしい となるとblock渡すヘルパーはことごとくspecが書けないことになる

これ以上 helper_spec に余計なこと書かずにこのspecを実行できるようにするにはどうすればいいのだろうか? (include BootstrapHelpers の1行だけでも十分余計である)

それともそもそも書き方の方針が大外れしてんのかねぇ

Octopressのテスト

とりあえずoctopressのセットアップしてみた

しかしこのリポジトリを他のコンピュータでpullしてきてrake deployしても更新が上手く行かない あと _layout/custom/* をいじってもなかなか反映されない

とりあえず色々使ってみて試すしかないかー 文字小さくて読むの辛い