2014年12月11日木曜日

『管理ページチラ見せナイト#2』に登壇してまいりました。

管理画面AdventCalender11日目は、フジテレビでソーシャルゲーム『ゲゲゲの鬼太郎 妖怪横丁』の開発を行なっている忠相が担当いたします。



少し前の10月21日に、管理ページチラ見せ♡ナイト#2に登壇してまいりました。


色々とまとめ記事はあるのですが、geechs magazineさんの記事が読みやすくてオススメです。

↑↑の写真も、こちらからお借りしちゃいました。

登壇させて頂くことになったのは、第一回のチラ見せナイトにお話を聞く側で参加したのがきっかけでした。

「面白いイベントだなー。こういうのを企画する人とお近づきになれたら嬉しいなー」と思いまして、主催者の西小倉さんにFacebookでメッセージを送ってみたところ、すぐに返信が。

で、色々とお話させて頂いた結果、第二回のチラ見せナイトで、ワタクシがフジテレビで作ったゲームの管理ページについて話をさせて頂くことに。

いやー、社内プレゼンとかは何度も経験があるのだけど、外部の方の前で、しかもすぐに満席になるような人気イベントでの登壇なんて生まれて初めてだったので、予想以上に緊張しちゃいました。

ま、結果としては、1、2度は笑いがとれたので、ぼちぼち成功だったのではないかと。

以下が、スライド資料と発表を文字起こししたものです。

少々長いので、お暇な方はどうぞ。。。




加藤:
皆さん、こんばんは。フジテレビから来ました、加藤忠相(ただすけ)と申します。皆さんどうぞよろしくお願いします。

今日はフジテレビで開発、運営している『ゲゲゲの鬼太郎妖怪横丁』というゲームの管理ページと、あとはサーバーの負荷や売上をモニタリングするページの紹介をさせて頂きます。

今日のアジェンダはこんな感じです。

ちょっとチラ見せまでが長いんですが、チラ見せではキャプチャーではなくて実際の管理ページと実際のモニタリングページをを見て頂こうと思っていますので、少しの間我慢して下さい。

トイレとか行きたい人は今のうちにどうぞ。

まずとっても大切な前置きがあります。

「本日の発表内容は全て加藤忠相個人の見解であり、株式会社フジテレビジョンの立場や意見を代弁するものではありません。」

よくあるヤツですね。

こう言っておかないと「フジテレビがまた変なことを言っているぞ!!」とか言われてデモ行進とかされると困っちゃうので・・・

今日話すのは僕個人の見解なんで、そこだけは忘れないようにお願いします。

早速ですが、フジテレビがゲーム開発しているというのを知らない方が多いんですよね。

私が所属しているのは「フジテレビ コンテンツ事業局 コンテンツ映像センター ゲーム&インキュベーション事業部 フジテレビゲームスタジオ」という長い名前の部署なんですが、ここで主に、「ネットでガチャガチャ もやしもん」、「ネットでガチャガチャ テルマエ・ロマエ」、「マングローブと不思議なクマたち」、それと今日お話させて頂く「ゲゲゲの鬼太郎 妖怪横丁」と4つのゲームの開発、運用を行なっております。

実際の職場の写真はこんな感じです。



このフロアでゲームの企画会議をやって、デザイナーさんが絵を描いて、プログラマーがコードを書いて、みんなでテストやってリリースということをやっています。

場所は、以前はフジテレビ本社屋の24階などを使っていたのですが、少々手狭になってきたので、今はダイバーシティ東京のオフィスタワーの20階を使っています。



さて、今日お話する「ゲゲゲの鬼太郎 妖怪横丁」なのですが、こんな感じのゲームです。

http://yahoo-mbga.jp/game/12015799/detail

プラットフォームはYahoo!モバゲーで、PCのブラウザゲームです。

ちょっと自慢させて頂くと、Yahoo!モバゲー内で人気のあるゲームを決めるYahoo!モバゲーアワードというものがあるのですが、そこで2013年はルーキー賞、今年2014年はベストタイトル賞を獲得させて頂きました。

ただ、Yahoo!モバゲーというプラットフォーム自体が、ちょっとマイナーだったりするんですよね。

ちなみにこの中に、このゲームをやったことある方っていらっしゃいますか?

いないですよね。

ああ、いなくてよかったです。いると話しにくい話もあるんで。

どんな感じのゲームなのか、ちょっとだけデモプレイさせて下さい。

いわゆる、箱庭系のゲームですね。

こんな感じで横丁というものがあって、お店があって、このお店にエサを置くと妖怪が集まってくるという流れです。

では、実際に何人くらいでこのゲームを作っているかというと、とても少ないです。

主要メンバーは7名です。

先日、一人修行中の者が入ったんで8人になったんですが、その8人で企画を考えたり、絵を書いたり、プログラムを書いたり、テストをしたりしています。

一部、外部の会社さんに絵を書いてもらったり、問い合わせの一次回答をしてもらったりはしていますが、基本的にはこの8人が全てです。

ほぼベンチャーですね。

ですので、管理ページのコンテンツはとても少ないです。

基本的な機能はデータの参照系ですね。

DBのマスターデータの更新などはphpMyAdminを使って行いますし、設定ファイルの更新はSSHでサーバにログインして、viでやったりしています。

基本的に鬼太郎開発チームはディレクターさんもデザイナーさんもプログラマーもすぐ近くの席なので、例えばディレクターさんが管理ページの仕様を忘れちゃったとしても、振り返ってすぐに実際に管理ページを作ったプログラマーに聞ける状況なんです。

それと、グラフなどのデータの可視化については、csv出力をして、エクセルなどで行なってもらう方針になっています。

ですので、基本的な管理ページの設計思想としては、「使いやすさよりも機能重視」「見た目のデザインよりも実装スピード重視」です。

さて、残り時間があと5分になってきたので、そろそろ実際の管理ページを見て頂きましょう。

個人的にはいろいろお見せたいものがあったんですがあまり時間もないので、いくつか絞って説明いたします。

クリックしたらマズいボタンがいっぱいあるので緊張しますね。

例えばこれは、「魂」というゲーム内通貨が、ゲーム内にどれくらい出回っているのかを見る機能です。

以前は魂の総数だけ表示していたのですが、現在はヘビーユーザ、ライトユーザというような感じでユーザをセグメント分けして、それぞれどのようなユーザがどれくらい保持しているのかまで、管理ページで見られるようにしています。

後はですね、これはディレクターから依頼もされていないのに自分で勝手に作ったのですが、APIがリクエストを受け付けてからレスポンスを返すまでにかかった時間の平均値を、一時間ごとに集計したページです。

例えば、今日の20時から21時までで見てみると、APIへの総アクセス数が200万くらいで、平均的に0.06秒でレスポンスを返せているというのが、この表から知ることができます。

イベントリリース直後などにサーバの負荷が上がってゲーム全体がもっさりしてしまった時など、この情報をもとに原因となっているAPIを見つけ出して、対応を行うというようなことをしています。

時間もないんで、最後に各ユーザごとの情報が見れるページですね。

ここで、各ユーザのレベルなどのゲームの状況が見られるようになっているのですが、ユーザに対する補填も出来るようになっています。

基本的にそのようなことはないのが一番なのですが、何か不具合が発生し、ユーザにご迷惑をおかけしてしまった場合、ここの機能を使って、アイテムをユーザに配布し、さらにお詫びのメッセージもゲーム内に表示させることができます。

続きまして、モニタリングページですね。

最後にこれを見て頂いて、終わりにしようかと思います。

売上とかDAUとかはダミーのデータなんですが、サーバのロードアベレージやアクセス数などの情報は今現在の本当の本番サーバの状況です。

このAPIのレスポンス秒数もガチです。

ここでは、今月の予算と売上を表示しています。

これはサーバのロードアベレージ、メモリ使用率、ディスク使用率ですね。

最後は水木しげる先生に出演頂いた、鬼太郎のコマーシャル動画です。

この動画をここで流す意味はあまりないのですが、なんとなくスタッフのモチベーションが上がる気がしたので、これも私が勝手に作りました。

実際にはこんな風に大きなモニターに表示させて、仕事中のスタッフが常に意識できるようにしています。



では、時間もなくなってきたので、以上となります。

最後に、フジテレビでは、一緒に楽しく、楽しく、楽しく仕事をしてくれる人を探しています。
Wantedlyなどのアカウントはないので、興味がある方はワタクシのFacebookまで連絡を下さい。
「加藤忠相」で検索すればすぐに見つかるかと思います。

ご静聴、ありがとうございました。

司会2:質問ある方いらっしゃいますか。
Q1:
貴重なお話ありがとうございました。

私も似たり寄ったりの仕事をしているんですが、管理画面がまともにあるところって、たまたまかもしれませんがあまり見たことがないんです。

ゲーム本体の方の開発にかかるコストと管理ページ開発のためのコストって、どれくらいの比率になっていますか?

加藤:
基本となる売上とかのKPIが見れる機能は丸一日くらいかけて作って、後は、ディレクターさんから「こういうデータが欲しい」っていう依頼がちょくちょくきて、面倒になったから管理ページに組み込んだとかそういう感じですね。

きちんと工数とかを計算してから設計、実装っていうわけではないんです。

Q1:
じゃあ最初は簡単な機能のみを作って、あとは必要なところから追加してっていう感じですか?

加藤:
そうですね。

ただ、ユーザからの問い合わせ回答に必要な機能は、優先して最初に作るようにしています。

そうしないと、問い合わせの調査依頼がプログラマーのところまで来るようになってしまって、その対応が忙しくて管理ページに機能追加できないっていう、負のスパイラルが発生してしまうので。

Q1:
なるほど。負のスパイラルに正にはまっている状態なので。今の話はとても参考になりました。

ありがとうございます。

加藤:ありがとうございました。

Q2:
貴重なお話ありがとうございました。

管理画面で使ってるグラフなどを表示する処理って、自分で作ろうと思うとかなり大変だと思うんですけど、どのように作っていますか?

加藤:
jqPlotを使っています。

Q2:ありがとうございました。

加藤:
では、これで終わりにしますが、自分はこんな格好でまだ後ろの方をうろうろしていると思うので、何かあったら聞いてください。

ありがとうございました。

2013年4月4日木曜日

Mountain LionでPHPが動く環境を作る

まずはインストールからかと思いきや、ApahceもPHPもインストールされていた。
mac:~ tadasuke$ sudo apachectl start
とやると、とりあえず起動するっぽいので、ブラウザから
http://localhost
とアクセスしてやると、
It works!
が表示された。

ただ、ドキュメントルートが
/Library/WebServer/Documents
となっていてイケていないので、設定を変更してやる。

設定ファイルは
/etc/apache2/httpd.conf
にあったので、
#DocumentRoot "/Library/WebServer/Documents"
DocumentRoot "/web/tadasuke/http"
こんな感じに変更。

で、
Macintosh:~ tadasuke$ sudo apachectl restart
で再起動して改めてアクセスするとエラー発生。

どうやら、
/Library/WebServer/Documents
以外のディレクトリはアクセスができない設定になっている模様。

細かい対策は色々あるのだけど、とりあえず面倒なので
<Directory />
    Options FollowSymLinks
    AllowOverride None
    Order deny,allow
#    Deny from all
    Allow from all
</Directory>
としてやって、アクセスを許可してみたところ、意図したとおり表示された。

ふぅ。

PHPについてはもっと簡単で、同じくhttpd.confで
#LoadModule php5_module libexec/apache2/libphp5.so
となっているので、ここのコメントアウトをとってやればよいだけ。

思いの外簡単にできました。。。

2013年3月21日木曜日

git pushでのミスを防ぐ

通常は
git push origin hoge
という、もはや何百回タイプしたか分からないやり方でプッシュをしている。

通常であれば問題ないのだけど、何かトラブルなどがあった場合、間違えて
git push origin master
などとしてしまい、余計に被害を拡大してしまったことが何度かあった。

これをどうにかして防ぎたいなーと思って色々調べてみたところ、引数なしのgit pushは危険なので気をつけましょうというとても参考になるページを発見した。

Gitの設定で、
git config --global push.default current
とすると、引数に何も設定せずに
git push
としただけで、カレントブランチのみをpushしてくれる。

本当は
git config --global push.default simple
とすると、カレントブランチと同名のブランチがリモートにあった場合のみpushしてくれるのでバッチリなのだけど、バージョンが1.7.11以降でないと使えないらしく、自分の環境の1.7.4だったので断念。

でも、とりあえずこれで今までよりは安全にpushができるようになったかな。

2013年2月3日日曜日

脳内メモリとコーディング

ソーシャルゲームの開発現場に入ってから、過去にない量のコードを書いている。

過去に働いていたウォーターフォール型の現場では2,3週間コードを1行も書かない日が続くなんてことはザラにあったのだが、ここ数年は3日連続でコードを書かかなかった記憶はほぼない。

夏休み、正月休みなどでも結局何かしらのコードは書いているし・・・
別に愚痴を言っているわけではなく「コードを書いている時間=至福の時間」の自分にとっては、これ以上ない幸せな現場で働かせて頂いている。

さて、そのようにほぼ毎日コードを書いていると、色々と気付くことがある。

下記はあくまでもワタクシ個人の場合であって、プログラマーを代表しての発言ではないことを先にお伝えしておきます。

コードを書く際に重要なのは、脳内メモリだ。

これから書くコードの内容、重要なポイント、気をつけなければならない点などを過去の記憶やPC上のドキュメントなどから呼び出して脳内メモリに展開し、それからコーディングを開始する必要がある。

この作業が実はとても重要で、良い感じにメモリに展開できればかなりのハイスピードでコードを書き続けることができるのだが、上手く展開できないときは、いちいち脳内ハードディスクの『記憶』領域やPC内、ネット上のドキュメントにアクセスしなければならないので、著しくスピードが落ちる。

逆にこのメモリ展開が上手く言った時などは、自分のようなヘボ脳内CPUの持ち主でもタイピングが追いつかないレベルでコードが浮かんでくる。

なので、この『脳内メモリ展開』さえ上手くいってくれれば常にハイパフォーマンスを発揮できるわけだが、これがなかなか難しい。

脳内メモリはメモリなので、容量が少なく、上書きされやすい。
ようするに、外部からの割り込み処理に極めて弱いのだ。

このあたりが自分が自宅作業が苦手な理由でもあるのだけど、せっかく色々な情報を脳内メモリに展開でき、「さーこれからコーディング!!」となったあたりで話しかけらるなどの外部からの割り込み処理があると、せっかくの脳内メモリが上書きされてしまう。

で、また新たに展開できたころにさらに割り込み処理が起こる。

なんてことが繰り返されると、さすがに心が折れてくる。

また、もう一つ、この脳内メモリ型コーディングの問題点としては、自分が書いたコードを忘れやすいということがある。

「実装したと思っていた機能を実装していなかった」
というのならまだ分かるのだが、それなりにやっかいな処理をバッチリ書いたのに、そのことがさっぱり記憶になく、後になって「あれっ?できてるじゃん!!」と驚いたことは過去に何度もある。

というわけで、色々と問題点があるコーディング技法?なのだけど、自分としてはこれ以外のやり方を持っていないので、引き続き頑張りますよー。

ただ、さすがにコードを書いたことをすっかり忘れているというのは色々な意味で危険ではあるので、何かしら対策を考えます・・・

2013年1月31日木曜日

Zend_QueueでActiveMQを使う_その2

さて、その1の続き。

ひたすらZendのソースを追ってみたところ、その1で作ったMy_Queue_Stomp_Clientクラスを使うようにするにはZend_Queueのインスを作成する際のパラメータで、オブジェクトを渡してやれば良いことが判明。
$stompClient = new My_Queue_Stomp_Client( Zend_Queue_Adapter_Activemq::DEFAULT_SCHEME );
$options = array(
    'name'          => $name
  , 'driverOptions' => array( 'stompClient' => $stompClient )
);
$activeMq = new Zend_Queue( 'Activemq', $options );
後は今までどおり
$msgQueue = $activeMq -> receive( 1 );
とやれば、キューを一つ取り出すことができた。

とりあえずこれで最低限使える状態にはなったのだけど、このやり方だとキューを1つしか取り出すことができない。
マニュアルを読んだところ
$msgQueue = $activeMq -> receive( 5 );
のようにすると、キューを5つ取り出すことができるらしい。
というわけで早速試してみたところ、またも微妙に意図した動きをしてくれない。
キューが5件以上ある時は問題なく5件取得できるし、キューが1件もない時はすぐに終了してくれるのだけど、キューが5件未満だった場合、いつまで待ってもレスポンスが返ってこない。
またもZendのソースを追ってみたところ、今度はかなり難解だった。
というわけで今度は素直に断念して、以下のようなコードで対応することに。
$insertDataCount = 0;
$queueArray = array();
while ( 1 ) {
  
  // ActiveMQから一件ずつ取得
  $msgQueue = $activeMq -> receive( 1 );
  $queue    = $msgQueue -> current();

  // 取得できなければ終了
  if ( is_null( $queue ) === TRUE ) {
    break;
  } else {
    ;
  }
    
  // Bodyを抽出
  $message = $queue -> body;
    
  // 内部変数にプール
  $queueArray[] = $message;
    
  // キューを削除
  $activeMq -> deleteMessage( $queue );
    
  // 制限数を超えたら終了
  $insertDataCount++;
  if ( $insertDataCount >= 10000 ) {
    break;
  } else {
    ;
  }
    
}


久々にちゃんとZendのソースを読むのはかなり疲れる作業だったけど、色々と仕組みがわかってとても勉強になりましたとさ。

2013年1月30日水曜日

Zend_QueueでActiveMQを使う_その1

DBサーバの負荷を軽減するため、キューサーバを利用し、非同期処理をすることになった。
そこで、Zend_Queueを使い、ActiveMQに対してキューの出し入れをする。

キューを送信するのはマニュアルに従って、
$activeMq = new Zend_Queue( 'Activemq', array( 'name' => '/queue/hoge' ) );
$activeMq -> send( $message );
のような感じに書いたら、とりあえず問題なく動いてくれた。

こいつは予想外に簡単♪と思い今度は受信するために
$activeMq = new Zend_Queue( 'Activemq', array( 'name' => '/queue/hoge' ) );
$msgQueue = $activeMq -> receive( 1 );
とやってみたところ、どうにも挙動がおかしい。
ActiveMQにキューが1件以上ある場合はすぐにレスポンスが返ってくるのだけど、キューが1件もないと5秒程度レスポンスが返ってこない。

Zend_Queueのコンストラクタのパラメータに"timeout"というものが設定できるのだが、その値を変更しても挙動は何も変わらない。
というわけで、Zendの中身を見てみることにした。

Zend_QueueでAcitveMQと接続する流れとしては、
$msgQueue = $activeMq -> receive( 1 );
    public function receive($maxMessages=null, $timeout=null)
    {
        if (($maxMessages !== null) && !is_integer($maxMessages)) {
            require_once 'Zend/Queue/Exception.php';
            throw new Zend_Queue_Exception('$maxMessages must be an integer or null');
        }

        if (($timeout !== null) && !is_integer($timeout)) {
            require_once 'Zend/Queue/Exception.php';
            throw new Zend_Queue_Exception('$timeout must be an integer or null');
        }

        // Default to returning only one message
        if ($maxMessages === null) {
            $maxMessages = 1;
        }

        // Default to standard timeout
        if ($timeout === null) {
            $timeout = $this->getOption(self::TIMEOUT);
        }

        return $this->getAdapter()->receive($maxMessages, $timeout);
    public function receive($maxMessages=null, $timeout=null, Zend_Queue $queue=null)
    {
        if ($maxMessages === null) {
            $maxMessages = 1;
        }
        if ($timeout === null) {
            $timeout = self::RECEIVE_TIMEOUT_DEFAULT;
        }
        if ($queue === null) {
            $queue = $this->_queue;
        }

        // read
        $data = array();

        // signal that we are reading
        if (!$this->_isSubscribed($queue)){
            $this->_subscribe($queue);
        }

        if ($maxMessages > 0) {
            if ($this->_client->canRead()) {
    public function canRead()
    {
        return $this->getConnection()->canRead();
 public function canRead()
    {
        $read   = array($this->_socket);
        $write  = null;
        $except = null;

        return stream_select(
            $read,
            $write,
            $except,
            $this->_options['timeout_sec'],
            $this->_options['timeout_usec']
        ) == 1;
        // see http://us.php.net/manual/en/function.stream-select.php
    }
と、ここまで追って、やっとPHPの標準メソッドに到着。
ふぅ。

調べてみると、このstream_selectというメソッドの第四、第五引数で接続の待ち時間を設定するらしい。
というわけで、この値の初期値を調べてみると、
    const READ_TIMEOUT_DEFAULT_USEC = 0; // 0 microseconds
    const READ_TIMEOUT_DEFAULT_SEC = 5; // 5 seconds
となっていて、これが原因で5秒間レスポンスが返ってこないことが判明。
というわけで、後はこの値を変えてやればいいのだけど、これが絶妙に難しい。
Connection.php側では、
    public function open($scheme, $host, $port, array $options = array())
    {
        $str = $scheme . '://' . $host;
        $this->_socket = fsockopen($str, $port, $errno, $errstr);

        if ($this->_socket === false) {
            // aparently there is some reason that fsockopen will return false
            // but it normally throws an error.
            require_once 'Zend/Queue/Exception.php';
            throw new Zend_Queue_Exception("Unable to connect to $str; error = $errstr ( errno = $errno )");
        }

        stream_set_blocking($this->_socket, 0); // non blocking

        if (!isset($options['timeout_sec'])) {
            $options['timeout_sec'] = self::READ_TIMEOUT_DEFAULT_SEC;
        }
        if (! isset($options['timeout_usec'])) {
            $options['timeout_usec'] = self::READ_TIMEOUT_DEFAULT_USEC;
        }

        $this->_options = $options;

        return true;
    }
となっているので、このメソッドの第四引数で設定できるようになっているのだけど、呼び出し元が
    public function addConnection($scheme, $host, $port, $class = 'Zend_Queue_Stomp_Client_Connection')
    {
        if (!class_exists($class)) {
            require_once 'Zend/Loader.php';
            Zend_Loader::loadClass($class);
        }

        $connection = new $class();

        if ($connection->open($scheme, $host, $port)) {

となっていて、そもそも第四引数が存在しない。
そして、Connection.php側では上記メソッド以外で該当の変数を変更する手段が用意されていない・・・

対応方法としては
  • Connection.phpのREAD_TIMEOUT_DEFAULT_SECの値を書き換える。
  • 継承したクラスを作って頑張る
の2つ。
簡単なのは前者なのだけど、Zendのソースを書き換えて使うというのはかなり抵抗があるので、後者にチャレンジすることに。

継承したクラスを作るのは簡単で、
require_once 'Zend/Queue/Stomp/Client.php';

class My_Queue_Stomp_Client extends Zend_Queue_Stomp_Client {
 
 /**
  * (non-PHPdoc)
  * @see Zend_Queue_Stomp_Client::addConnection()
  */
 public function addConnection($scheme, $host, $port, $class = 'Zend_Queue_Stomp_Client_Connection') {
  
  OutputLog::outLog( OutputLog::INFO, __METHOD__, __LINE__, 'START' );
  
        if (!class_exists($class)) {
            require_once 'Zend/Loader.php';
            Zend_Loader::loadClass($class);
        }

        $connection = new $class();

        // とりあえず0.2秒レスポンスを待つことに
        if ($connection->open($scheme, $host, $port, array( 'timeout_sec' => 0, 'timeout_usec' => 200000 ) ) ) {
            $this->setConnection($connection);
            return true;
        }

        $connection->close();
        return false;
    }
}
のようにしてやればOK。
問題はどうやってこいつを使うようにするか。

かなり長くなってきたので、続きはまた今度。

2012年8月28日火曜日

Gitのリポジトリを後から共有設定する

さてさて、どうにかこうにかイカした開発環境を作れてホッとしていたのだが、実際に運用してみたところ、上手く動かないケースが出てきた。

hogeユーザが
git push origin master
とやって共有リポジトリにプッシュした後、tadasukeユーザが
git pull orgin master
で更新を落としてくることはできるのだけど、さらに修正して
git push origin master
とやると、パーミッションエラー的なメッセージが出てプッシュできない。

共有リポジトリの
/var/git_repos/game.git
のパーミッションは775に設定していて、hogeとtadasukeは同じグループなのに…

どうもpushしたタイミングでファイルのパーミッションが書き換わってしまうらしい。

pushする度にファイルのパーミッションを変更するのは面倒すぎるので色々調べてみたところ、リポジトリを作る際に
--shared=true
オプションを設定するとリポジトリが共有設定になって、同じグループのユーザであればpushしまくっても問題なくなるらしい。

ただ、すでにリポジトリは作成済みなわけで、新たに作り直すのはできることならご遠慮願いたい。

で、さらに調べたところ、
/var/git_repos/game.git/config
の[core]のところに
sharedrepository = 1
と書いてやれば、共有リポジトリになることが判明。

もしかすると
sharedrepository = true
のほうが、正しい書き方かもしれないけど・・・

というわけで、今度こそイカした開発環境ができましたー。