Pow をインストールするとシステムに何をされるのか確認する

Pow37signals 謹製の Rack サーバです。Rails 開発で愛用している人も多いのではないでしょうか。Pow をインストールすると、

  • (1) http://my_app_name.dev/ にアクセスすると
  • (2) Pow が Rack アプリケーションを自動的に起動して
  • (3) ~/.pow/my_app_name にある Rack アプリケーションを利用できる

のでとても便利です。しかも、Pow のインストールは

$ curl get.pow.cx | sh

だけで完結するので簡単です。しかし、魔法のような (1)-(3) を見ていると、システムに何か複雑な操作をしているのではないかと不安になります。そこで、インストールスクリプトを順に追って、Pow をインストールするとシステムに何をされるのか確認することにします。この記事を書いている時点のインストールスクリプトget.pow.cx · GitHub に保存しています。以下、このインストールスクリプトに沿って解説をすすめていきます。

インストールスクリプトは何をするか


それでは、順に見ていくことにしましょう。

1-37 行目

まず、目につくのが Pow のロゴ AA です(右図)。これがほとんどの人に気づかれないのはもったいないと思います。このロゴがシステムに何かするわけではありません。しかし、こういう見えないところで頑張るのは私の好みなので紹介した次第です。

41-59行目

Pow は Max OS X 10.6 以降でないと動作しないので、実行環境を確認しています。まだシステムには何もしていません。ここで、Pow のインストール先ディレクトリと Pow 本体のパスを変数に設定しています。

  • POW_ROOT="$HOME/Library/Application Support/Pow"
  • POW_BIN="$POW_ROOT/Current/bin/pow"
64行目: インストールディレクトリの作成
mkdir -p "$POW_ROOT/Hosts" "$POW_ROOT/Versions"

インストール先ディレクトリとして、$POW_ROOT/Hosts と $POW_ROOT/Versions を作成しています。$POW_ROOT="$HOME/Library/Application Support/Pow" であることは先に説明しました。

69-71行目: 既にインストールしてある Pow を削除
cd "$POW_ROOT/Versions"
rm -rf "$POW_ROOT/Versions/$VERSION"

以降の作業は $POW_ROOT/Versions で行われます。

75行目: Pow 本体をダウンロード
curl -s http://get.pow.cx/versions/$VERSION.tar.gz | tar xzf -

$POW_ROOT/Versions に展開されます。

80-82行目: ダウンロードしたバージョンを Current としてシンボリックリンクを作成
cd "$POW_ROOT"
rm -f Current
ln -s Versions/$VERSION Current

最新のバージョンに Current という名前にシンボリックリンクを張るのは Mac OS X の流儀ですね。といっても、私はこの流儀が書かれているドキュメントを見たことがありません。もし知っている人がいたら誰か教えてください。

87-88行目: ホームディレクトリに $POW_ROOT/Hosts へのシンボリックリンクを ~/.pow として作成
cd "$HOME"
[[ -a .pow ]] || ln -s "$POW_ROOT/Hosts" .pow
93行目: pow 起動スクリプトの設定
echo "*** Installing local configuration files..."
"$POW_BIN" --install-local

pow --install-local を実行して、pow 起動スクリプトを設定しています。pow --install-local が何をするかは pow/command.js at master · basecamp/pow · GitHub から installer.coffee を追いかけると分かります。簡単に言うと、/Users/passoingloop/Library/LaunchAgents/cx.pow.powd.plist *1 を作成して、ユーザのログイン時に

$SHELL --login -c "'/Users/passingloop/Library/Application Support/Pow/Versions/0.3.2/bin/pow'"

が実行されるようにしているだけです。

99行目: root 権限が必要かチェック
"$POW_BIN" --install-system --dry-run >/dev/null && NEEDS_ROOT=0 || NEEDS_ROOT=1

root 権限が必要なら NEEDS_ROOT=1, 不要なら NEEDS_ROOT=0 を設定します。root 権限が必要かどうかをチェックするこのテクニックは、他のインストーラを作成するときにも使えそうです。

104-108行目: ポートフォワーディング設定
if [ $NEEDS_ROOT -eq 1 ]; then
  echo "*** Installing system configuration files as root..."
  sudo "$POW_BIN" --install-system
  sudo launchctl load -Fw /Library/LaunchDaemons/cx.pow.firewall.plist 2>/dev/null
fi

を実行して、起動時に 80 番ポートへのアクセスを 20559 番に転送するよう設定します。pow --install-system が何をするかは、--install-local と同じように pow/command.js at master · basecamp/pow · GitHub から installer.coffee を追いかけると分かります。簡単に言うと、/etc/resolver/dev と /Library/LaunchDaemons/cx.pow.firewall.plist を作成して、後者を sudo launchctl load -Fw します。sudo launchctl load -Fw ... は起動時に PID=1 の launchd から cx.pow.firewall.plist を実行する設定です。cx.pow.firewall.plist には実際に launchd が実行するコマンド、

ipfw add fwd 127.0.0.1,20559 tcp from any to me dst-port 80 in && sysctl -w net.inet.ip.forwarding=1

が書かれています。/etc/resolver/dev には *.dev の名前解決をローカルホストの 20560 番ポートで動いている DNS サーバで解決する設定が書かれています。

113-115行目: Pow の起動
echo "*** Starting the Pow server..."
launchctl unload "$HOME/Library/LaunchAgents/cx.pow.powd.plist" 2>/dev/null || true
launchctl load -Fw "$HOME/Library/LaunchAgents/cx.pow.powd.plist" 2>/dev/null

インストールした Pow をいよいよ起動します。既に起動している Pow があれば先に停止します。

以降

Pow が正しく起動したかどうかの確認です。

インストールスクリプトがシステムに何をしたか

これまで確認したことをまとめると、インストールスクリプトは、

  • インストールディレクトリを作成する
    • $HOME/Library/Application Support/Pow
  • インストールディレクトリに Pow 本体をダウンロードして展開
  • ホームディレクトリにシンボリックリンク ~/.pow を作成
    • 参照先は $HOME/Library/Application Support/Pow/Hosts
  • /Users/passoingloop/Library/LaunchAgents/cx.pow.powd.plist を作成
    • このファイルはログイン時に読み込まれ実行される → ログイン時に Pow が起動
  • /etc/resolver/dev を作成
    • *.dev なホスト名はローカルホストの 20560 番ポートで解決
  • /Library/LaunchDaemon/cx.pow.firefall.plist を作成
  • launchctl を実行して ↑ をロード。起動時に実行されるようにする
    • 起動時に 80 番ポートへのアクセスを 20559 番ポートに転送する設定
  • Pow を起動

となります。

書き出してみると、Pow の魔法の秘密に近づけるような気がします。つまり、Pow は 20559 番ポートで HTTP サーバとして、20560 番ポートで DNS サーバとして起動します。my_app_name.dev という ホスト名は Pow DNS サーバで 127.0.0.1 に名前解決されます。ローカルホストの 80 番ポートへのアクセスは、インストールスクリプトが設定したポートフォワーディングにより 20559 番ポートへと転送され、Pow HTTP サーバがリクエストを受け取ります。あとは、バーチャルホストを使って Pow が各 Rack アプリケーションにリクエストを振り分けるだけです。このとき、Rack サーバが起動していなければ、Pow が起動してくれるというわけです。

おわりに

本エントリでは、Pow をインストールするといシステムにどのような影響があるかを、インストールスクリプトを順に追って確認してみました。また、確認することで Pow の魔法の一端を垣間見ることができました。Pow をイストールしてもシステムに害は無いようなので、どんどん curl get.pow.cx | sh しましょう。

ただし、私は一点だけ気になるところがありました。それはポートフォワーディングのため実行する ipfw コマンドで、from any となっている点です。FW を適切に運用している環境では問題ないのでしょうが、私の場合、そういう環境ばかりというわけにはいかず気持ち悪いので、from any を from me と変更したスクリプトを使っていることを最後につけ加えます。

*1:passingloop は各自のユーザ名で読み換えてください