Docker で Hackage Mirror Server を構築する

# Docker で Hackage Mirror Server を構築する

どうも僕です。
最近、Hackage 調子悪いですね。
こうも落ちてばかりいると業務に支障をきたしてしまいます。
多くの場合は sandbox 使い回しでなんとかなるのかも知れませんが、新規パッケージ、サーバーから最新版を落としたい場合には辛いものがあります。
なので今回は Dockerを使って Hackage Server を内部に構築する話をしたいと思います。
もちろん本家からミラーリングも行います。

## Hackage Server のイメージをビルドする

Hackage Server (Hackage2)のソースは公開されており、githubにあります。
[hackage-server](https://github.com/haskell/hackage-server)

まずはcloneしてきましょう。

```shell
$ git clone https://github.com/haskell/hackage-server.git
$ cd hackage-server
```

このままビルドしてしまいたいところなのですが、新しい ghc (haskell-platform) ではうまくビルドできません。
どうも haskell-platform 2013 が前提のようです。

少し古めの環境というのもありますし、環境を構築するのにいろいろと気をつかうのも大変です。
なので今回は Docker を使ってビルド、および起動まで行います。

リポジトリ内を見ればわかりますが、既に Dockerfile が用意されています。
みんな考えることは同じようです:)

一応既に DockerHub にもイメージがあがっているのですが、これは内容が古くうまくミラーができません。
(zsol/hackage-serverは0.3)
そのため最新版の Hackage Server をビルドする必要があります。
この記事を書いた時点では 0.4.3 です。

基本的には用意されている Dockerfile でイメージを作成すれば良いだけですが、今回は少しアレンジしています。

```
from zsol/haskell-platform-2013.2.0.0

env HOME /home/haskell

# dependencies
run sudo apt-get update && sudo apt-get -y dist-upgrade
run sudo apt-get -y install unzip libicu48 libicu-dev
run cabal update
run curl -LO https://github.com/haskell/hackage-server/archive/master.zip

run unzip master.zip

workdir /home/haskell/hackage-server-master

run cabal install --only-dependencies
run cabal configure && cabal build

volume ["/home/haskell/hackage-server-master/state"]

cmd echo "Binaries are in ./dist/build/hackage-*"

```

オリジナル版ではパッケージデータをホストにマウントするVOLUMEがないので追加しています。
この Dockerfile を使ってイメージを作成しましょう。

```shell
$ sudo docker build -t="hackage-server" .
....
....
```

実行すると hackage-server のソースのダウンロードからビルドまでを行ってくれます。
結果、hackage-server というタグのイメージが出来上がります。

## Hackage Server のイメージを起動

それでは Hackage Server を起動してみましょう。
起動スクリプト(run-docker.sh)もリポジトリ内にあります。

```bash
#!/bin/sh

set -e

ADMIN_USER=admin
ADMIN_PASS=admin
BUILD_RUN_TIME=30
BUILD_INTERVAL=5

DOCKER_IMAGE=hackage-server

server_id=$(sudo docker run -d -p 8080 ${DOCKER_IMAGE} sh -c "./dist/build/hackage-server/hackage-server init --static-dir=datafiles --admin=\"${ADMIN_USER}:${ADMIN_PASS}\" ; ./dist/build/hackage-server/hackage-server run --static-dir=datafiles")

server_ip=$(sudo docker inspect $server_id | grep IPAddress | cut -d'"' -f4)
server_local_port=$(sudo docker port $server_id 8080)

sleep 2 # TODO: poll until web server comes up

sudo docker run -d ${DOCKER_IMAGE} sh -c "echo -e \"${ADMIN_USER}\n${ADMIN_PASS}\" | ./dist/build/hackage-build/hackage-build init http://${server_ip}:8080; ./dist/build/hackage-build/hackage-build build --run-time=${BUILD_RUN_TIME} --interval=${BUILD_INTERVAL} --continuous"

echo "You can access your local hackage at http://localhost:${server_local_port}"

```

DOCKER_IMAGEはビルドしたイメージ名を設定します。
管理者ユーザーなどは任意で変更して下さい。
また、ミラーした結果をコンテナ外に永続化したい場合には`-v`で上記の Dockerfile で設定した VOLUME のパスを設定して下さい。


```
$ ./run-docker.sh
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
You can access your local hackage at http://localhost:0.0.0.0:xxxxx
```

実行するとコンテナIDとURL(ポート番号)が表示されます。
ブラウザでアクセスして Hackage の画面が表示されるか確認しましょう。

## Hackage をミラーリングする

起動しただけの状態ではアップロードはできるかも知れませんが、パッケージも何もありません。
このままでは意味がないので Hackage をミラーリングしましょう。
まず、ミラーを行うため、ミラーグループにユーザーを追加します。

http://localhost:8080/packages/mirrorers/

次にミラーリングツールを取得、設定します。
hackage-serverのリポジトリにはミラーリングするためのツール hackage-mirror も含まれています。
これを使用してミラーリングを行います。ミラーリングはコンテナ外から行います。

hackage-mirror も hackage-server 同様にコンテナ内にビルドされています。
そのため、コンテナ内から hackage-mirror を取得します。
先ほどのコンテナIDを使用し、コンテナ内からコピーします

```shell
sudo docker cp xxxxxxxxxxx:/home/haskell/hackage-server-master/dist/build/hackage-mirror/hackage-mirror ~/tmp/ 
```

使い方は hackage-mirror のヘルプを見れば大体わかります
次にミラーリング設定ファイルを記述します。

```
http://hackage.haskell.org/
http://admin:admin@localhost:xxxxxx
```

ミラー元、ミラー先を記述してあるだけのシンプルな設定です。
ミラー先には先ほど立てた Hackage Server のURLを指定します。

あとはこのファイルを指定し、hackage-mirror を実行するだけです。

```
$ ./hackage-mirror ./mirror.cfg
44823 packages to mirror.
mirroring 3d-graphics-examples-0.0.0.0
mirroring 4Blocks-0.1
mirroring 4Blocks-0.2
...
...

```

実行し、しばらくするとミラーリングが始まります。
ミラーリングされたパッケージからWebの方にも表示されます。