WindowsでRhodeCode

2015.4.21 [WindowsでKallitheaを動かす](http://qiita.com/wonderful_panda/items/aa1506f2b18fb29c0f3e) の方に現時点でのセットアップ手順をまとめました。

-----

[Mercurial Advent Calender](http://connpass.com/event/1431/) 22日目のエントリです。

RhodeCodeというのは、MercurialとGitに対応したGitHubライクなリポジトリブラウザ(hg serve相当のサーバ機能もあり)な訳ですが、このRhodeCodeをWindowsで動かした時の手順とか設定とかの話です。
基本的には [ここ](http://packages.python.org/RhodeCode/setup.html) に書いてある通りなんですが。

## 前提条件とか
* アクセスするのは25人くらい。
* 既存の環境はできる限り汚したくない。
* リポジトリ内のファイルはUTF8とshift_jisが混在している。
* 今のところ日本語パスはないけど、一応日本語パスでも問題ないようにしておきたい。
* クライアントはすべてWindows。
* RhodeCodeとTracを同じポートで動かして、外からのアクセスはApacheでリバースプロキシを設定する。
* 認証はApache側で。
* Windowsサービス化する。

## virtualenv環境の準備

既存の環境に影響が出ないように、すべてvirtualenv内にインストールするようにします。

```console:console
C:\.virtualenvs>virtualenv galahad
C:\.virtualenvs>cd galahad
C:\.virtualenvs\galahad>Script\activate.bat
```

virtualenv内だけで有効な環境変数を設定するために、Lib\site-packages\sitecustomize.py を作成します。
Windows環境下で日本語パスをちゃんと扱うには `HGENCODING = cp932` としておく必要があるのと、RhodeCode用に個別のhgrcを読み込ませるために `HOME` を設定しています。

```python:sitecustomize.py
import os
os.environ["HGENCODING"] = "cp932"
os.environ["HOME"] = "C:/.virtualenvs/garahad/HOME"
os.environ["PYTHON_EGG_CACHE"] = "C:/.virtualenvs/garahad/.egg-cache"
```

## モジュールのインストール
インストール自体は `pip install rhodecode` でほぼ終わり。
non-asciiなファイルを扱うには追加で chardet が必要なのでそれも一緒に。

```console:console
(galahad)C:\.virtualenvs\galahad>pip install rhodecode
(galahad)C:\.virtualenvs\galahad>pip install chardet
```

サービス化するために、Python win32 extensionが必要になりますが、これはpypiにはないのでここからインストーラをダウンロードして、 `easy_install` でvirtualenvにインストールします。
http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/

```console:console
(galahad)C:\.virtualenvs\galahad>easy_install pywin32-218.win32-py2.7.exe
```

ちなみに、Mercurialをvirtualenv環境下にインストールするとそのままではhgコマンドが動かないので、 `Script\hg.bat` を少し修正しておきましょう。

```diff:hg.bat.patch
@@ -8,5 +8,5 @@
 rem install does not put python.exe on the PATH...
 rem %~dp0 is the directory of this script
 
-"%~dp0..\python" "%~dp0hg" %*
+"%~dp0python" "%~dp0hg" %*
 endlocal
```

## RhodeCode環境の構築
`paster make-config` と `paster setup-rhodecode` で、環境を構築します。
アクセスするのが高々25人程度ということで、とりあえずsqliteのままで。

```console:console
(galahad)C:\.virtualenvs\galahad>mkdir repositories
(galahad)C:\.virtualenvs\galahad>mkdir environ && cd environ
(galahad)C:\.virtualenvs\galahad\environ>paster make-config RhodeCode rhodecode.ini
(galahad)C:\.virtualenvs\galahad\environ>paster setup-rhodecode rhodecode.ini
...
Are you sure to destroy old database ? [y/n] y
...
Enter a valid absolute path to store repositories. All repositories in that path
will be added automatically: C:\.virtualenvs\garahad\repositories
...
Specify admin username:iwata
...
Specify admin password (min 6 chars):XXXXXX
Confirm password:XXXXXX
...
Specify admin email:iwata@dummy.local
...
```

ここでいったん、ちゃんと動くかどうか確認します。


```console:console
(galahad)C:\.virtualenvs\galahad\environ>paster serve rhodecode.ini
```

![rhodecode-initial](http://gyazo.com/35461ac9a4182aaae46a0b057f7300f8.png)

## 日本語パスのための設定
日本語パスを扱うために、以下の設定が必要です。

1. win32mbcs エクステンションをインストールする
RhodeCode環境にもwin32mbcsエクステンションが必要になります。
適当な場所に win32mbcs.py を置いて、 `$HOME\hgrc` に記述してください。

2. default_encodingの設定
RhodeCodeのコンフィグファイル内で、 `default_encoding = cp932` に設定します。

```ini:rhodecode.ini
[app:main]
use = egg:rhodecode
# 中略
default_encoding = cp932
```

日本語(というかnon-ascii文字)を含むファイルについては、基本的に chardet がよろしく処理してくれるのであまり気にする必要はありません。
cp932でもUTF8でも特に問題は無いと思います。

## サービス化
とりあえず、wsgi applicationにします。

```python:rhodecodeapp.py
import os

home = os.path.dirname(__file__)
CONFIG_FILE = os.path.join(home, 'rhodecode.ini')

from paste.deploy import loadapp
from paste.script.util.logging_config import fileConfig

fileConfig(CONFIG_FILE)
application = loadapp("config:" + CONFIG_FILE)
```

あとはこれを mod_wsgiでApacheに載せればいいんですが、今回は先住民との住み分けの問題でApacheに直接載せられないので、スタンドアロンなサービスにしてApacheからリバースプロキシすることにします。

～/galahad/hg => RhodeCode
～/galahad/trac  => Trac
という形でリバースプロキシ設定する予定なので、設定を簡単にするためにこっち側でも同じようにURLをマップしておきます。

で、サービスとして動かすために作ったのが以下のスクリプトです。

```python:service.py
import win32serviceutil
from cherrypy.wsgiserver import WSGIPathInfoDispatcher, CherryPyWSGIServer

class Galahad(win32serviceutil.ServiceFramework):
    _svc_name_ = "Galahad"
    _svc_display_name_ = "Galahad Service"
    _svc_description_ = "Finally, I have long-cherished Ice-sword."
    _svc_deps_ = None

    server = None

    def SvcDoRun(self):
        """サービス開始"""
        from rhodecodeapp import application as rhodecode_app
        # tracapp.py についての説明は省略
        from tracapp import application as trac_app  

        # Routingの設定
        dispatcher = WSGIPathInfoDispatcher({
                "/galahad/hg":    rhodecode_app,
                "/galahad/trac":  trac_app,
            })
        self.server = CherryPyWSGIServer(('127.0.0.1', 5000), dispatcher)
        self.server.start()

    def SvcStop(self):
        """サービス終了"""
        if self.server:
            self.server.stop()

if __name__ == "__main__":
    win32serviceutil.HandleCommandLine(Galahad)
```

[このへん](http://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest/deployment/windows.html) を参考にしました。
取り回しの簡単そうなCherryPyを使うことにしましたが、Werkzeug(何回見てもスペルが憶えられないw)とかでもよかったかも知れません。
※Windowsの場合、こういうときに手っ取り早くwsgiサーバを建てたいと思ったら何を使うのがいいんですかね？

サービスのインストールは以下のコマンドです

```console:console
(galahad)C:\.virtualenvs\galahad\environ>python service.py install
```

デフォルトの設定だと標準エラー出力にログメッセージが流れるようになっていますが、サービス化すると標準エラー出力が無くてBad File Descriptorになってしまうので、ファイルに出力するように設定を変えておきます。

```ini:rhodecode.ini
##############
## HANDLERS ##
##############

[handler_console]
class = handlers.RotatingFileHandler
args = ('C:/.virtualenvs/galahad/environ/log/service.log', 'a', 10000)
level = INFO
formatter = generic

[handler_console_sql]
class = handlers.RotatingFileHandler
args = ('C:/.virtualenvs/galahad/environ/log/service.log', 'a', 10000)
level = WARN
formatter = generic
```

## リバースプロキシと認証
[ここ](http://packages.python.org/RhodeCode/setup.html#proxy-pass-through-authentication) の記述に従って、Apache側で、リバースプロキシと認証の設定を行います。
REMOTE_USERをX-FORWARDED-USERに乗せ替えて飛ばしている訳ですね。

```config:httpd.conf
<Location /galahad>
    SetEnvIf X-Url-Scheme https HTTPS=1

    ProxyPass           http://127.0.0.1:5000/galahad
    ProxyPassReverse    http://127.0.0.1:5000/galahad

    AuthType Digest
    AuthName "Login to Galahad"
    AuthDigestProvider file
    AuthUserFile /path/to/digest/file
    BrowserMatch "MSIE|Chrome" AuthDigestEnableQueryStringHack=On
    Require valid-user
    
    RequestHeader unset X-Forwarded-User
    RewriteEngine On
    RewriteCond %{LA-U:REMOTE_USER} (.+)
    RewriteRule .* - [E=RU:%1]
    RequestHeader set X-Forwarded-User %{RU}e
</Location>
```

RhodeCode側の設定は以下のようになります。上のURLの記述からは分かりづらいですが、Proxy-Passthroughモードで動かすときは `container_auth_enabled` と `proxypass_auth_enabled` の両方を true に設定する必要があるようです。

```ini:rhodecode.ini
[app:main]
use = egg:rhodecode
# 中略
container_auth_enabled = true
proxypass_auth_enabled = true
```

これで晴れてRhodeCodeが動くようになりました。
リポジトリの作成と `clone`、`push`、`pull`がちゃんと動くことを確認して〆。

## いろいろ
Tracと共存させてみたところ、TracのURLに一度でもアクセスするとRhodeCodeがInternal Errorを返すようになるという現象がありました。
調べてみたところ、Tracが内部的に `locale.setlocale` を実行しているのが問題らしく、Trac側で `trac.locale = English` を設定することで回避。

RhodeCode上で、ファイルに対して`show as raw`や`download as raw`を実行すると、cp932のファイルであってもUTF8で返されるみたいです。さすがに `download as zip` はそんなことないので大した問題ではないですが。

## まとめのようなそうでもないような
RhodeCodeは細かいところまでよく出来てて、開発のモチベーションが上がりますね。
別にPull Request運用をする訳じゃないって所でも(うちもそうです)、Mercurial使うならセットアップして損はないんじゃないでしょうか。

でもまあ、こういうのはLinux系の方が楽ですねえ。