アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

さくらのVPS を改めて使いはじめる 11 - Git、Gitolite、GitHub その 2

さくらのVPS(v3) 2GB プランへの環境構築メモ 11。今回は Git 関連の続き。Gitolite と GitHub の push をフックする方法などについて書く。

Gitolite への push をフックする

前回 Gitolite をセットアップしてリポジトリを Redmine へ表示するところまで書いた。しかしそのままでは Gitolite の push がミラーリングされたリポジトリに反映されず内容は古いままとなる。この問題への対処方法は 2 通りある。

  1. ミラーリングされたリポジトリ上で git fetch を定期実行
  2. Gitolite 上で push を検知して、ミラーリング先リポジトリに対して git push --mirror を実行

方法 1 なら crontab を利用することになるだろう。たとえば 10 分間隔ぐらいで git fetch させるなど。しかし push が全くないときでも無駄なコマンド実行が発生するうえ実行されるまで push が反映されないためイマイチ。

そのため今回は方法 2 で検討する。話の前提として前回の記事により以下のような環境が構築されているものとする。

リポジトリ ユーザー:グループ 説明
/home/gituser/repositories/testing.git gituser:gituser Gitolite のテスト用リポジトリ。
/var/lib/git/testing.git XXXX:XXXX Gitolite のテスト用リポジトリに対するミラーリング リポジトリ。

XXXX というのは普段 SSH 接続でログインしているユーザーとグループになる。例えば test というユーザーとグループなら test:test のようになる。

ミラーリング リポジトリの設定

ミラーリングされたリポジトリを参照したいユーザーが XXXX なら gpasswd コマンドで gituser グループに追加する。所属しているグループは id コマンドで確認。リポジトリを Redmine 上のプロジェクトに関連づけるなら Redmine 実行ユーザーを指定すること。このシリーズにならって環境構築していた場合は SSH 接続でログインしているユーザーと一緒のはず。

$ sudo gpasswd -a XXXX gituser
Adding user XXXX to group gituser
$ id XXXX
uid=500(XXXX) gid=500(XXXX) 所属グループ=500(XXXX),10(wheel),501(gituser)

ミラーリングしたリポジトリの所有者とグループを gituser、権限を 755 (グループに読み取りを許可) に変更。

$ sudo chown -R gituser:gituser /var/lib/git/testing.git/
$ sudo chmod -R 755 /var/lib/git/testing.git/

この時点で gituser グループに所属するユーザー全員が /var/lib/git/testing.git/ 以下の内容を読み取れるようになる。

このリポジトリを Redmine プロジェクトに関連づけているなら、そちらの表示も確認すること。もしリポジトリ画面で 404 になる場合はグループやパーミッションの設定が Redmine の稼働ユーザーからアクセス不能になった可能性がある。

ミラーリング元からこのリポジトリに対して git push --mirror が実行された場合、testing.git/objects などにディレクトリが追加されるのだがパーミッションは 700 になる。つまり所有者しかアクセスできない。というわけで以下の記事を参考に config ファイルの設定を変更する。

ミラーリング リポジトリ直下に移動して git config コマンドで core セクションの sharedRepositorygroup を指定。

$ cd /var/lib/git/testing.git
$ sudo git config core.sharedRepository group

設定が反映されたことを確認。

$ git config --list
core.repositoryformatversion=0
core.filemode=true
core.bare=true
core.sharedrepository=group
remote.origin.fetch=+refs/*:refs/*
remote.origin.mirror=true
remote.origin.url=/home/gituser/repositories/testing.git

ばっちり反映されている。これで Gitolite からの push を受け入れる体制が整った。

Gitolite の push をフックしてミラーに push

Git フックを利用して Gitolite 上のリポジトリに push がおこなわれたらミラーリング リポジトリ側にも反映されるようにする。

はじめに hook 時のパーミッション設定を変更。gituser の HOME にある .gitolite.rc の REPO_UMASK を編集する。gituser になってからファイルを開く。

$ sudo su gituser
$ cd
$ vi .gitolite.rc

REPO_UMASK を設定している部分を探す。デフォルトのパーミッションは 0077 になっているので元をコメントアウト、 0027 の設定を追加してからファイルを保存する。この作業は一度だけ実施すればよい。

#$REPO_UMASK = 0077;
$REPO_UMASK = 0027;

gituser の状態で testing.git 内の hooks ディレクトリに post-receive というファイルを新規作成する。

$ sudo su - gituser
$ cd repositories/testing.git/hooks
$ vi post-receive

このファイルにはリポジトリに対する操作に応じた処理をシェル スクリプトとして記述できる。今回はミラーリング リポジトリとなる /var/lib/git/testing.git に変更を git push --mirror する処理を書いて保存。

#!/bin/sh
/usr/bin/git push --mirror /var/lib/git/testing.git

スクリプト ファイルなので実行属性を付ける。

$ chmod 700 post-receive

正しく設定できているなら testing.git へ push が行われるたびにミラー側へも変更が反映されるはず。

GitHub への push をフックして VPS に送信

GitHub 上のリポジトリに変更がおこなわれた時は Post-Receive Hooks でハンドリングできる。今回はこの機能を利用してリポジトリへの push 時に VPS へ更新を通知してみる。

GitHub のクローン リポジトリ

前回は GitHub から clone したリポジトリに対して Redmine の動作ユーザー (= SSH ログインしているユーザー) を所有者に設定した。今回は Gitolite 用のミラーリング リポジトリの設定でこのユーザーを gituser グループに所属させる。

今後は Git 系の操作を gituser または gituser グループに統一。GitHub に Test というリポジトリがあると仮定して、以下のようにクローンする。

$ cd /var/lib/git
$ sudo git clone --mirror git://github.com/akabekobeko/Test.git
$ sudo chown -R gituser:gituser Test.git
$ sudo chmod -R 755 Test.git/

これで gituser グループに所属するユーザーでもリポジトリを読めるようになる。ただし git fetch などの更新系は gituser のみに許可する。

PHP 実行ユーザーに sudo を許可

今回 GitHub からの更新通知による VPS 上のリポジトリ反映は PHP スクリプトでおこなう予定である。反映は git fetch コマンドで実行するのだが、これを PHP から呼び出す方法としては以下が考えられる。

  1. suEXEC を有効にしてスクリプトの実行ユーザーを git 操作可能なユーザーにする
  2. PHP の実行ユーザーに sudo を許可し、その -u オプションでユーザー指定する

前者は設定が面倒なうえスクリプトや CGI 全体の実行にかかわってくるので後者を採用する。

本シリーズのsudo と SSH ポート変更の回で取り上げた visudo コマンドを実行。

$ sudo visudo

すると vi で sudoers ファイルが開かれるので Defaults requiretty の行をコメントアウトする。これが有効だとシェルにログインできないユーザーは sudo を利用出来ない。PHP 実行ユーザーとなる apache はこれに該当するので設定を無効にする。

セキュリティ的には好ましくないのだが VPS 全体として SSH ログインを必須にし、かつそのユーザーを限定しているのでよしとしておく。

#
# Disable "ssh hostname sudo <cmd>", because it will show the password in clear.
#         You have to run "ssh -t hostname sudo <cmd>".
#
#Defaults    requiretty

次に apache ユーザーへ sudo を許可。といっても代替ユーザーは gituser、実行可能なコマンドは git に限定しておく。この設定は wheel グループの後ろあたりに記述。

## Allows people in group wheel to run all commands
%wheel  ALL=(ALL)       ALL

## Same thing without a password
# %wheel        ALL=(ALL)       NOPASSWD: ALL

apache   ALL=(gituser)    NOPASSWD: /usr/bin/git

設定できたらファイルを保存して編集を終了。

更新通知時に実行するスクリプト

GitHub の clone リポジトリと PHP 実行環境が整ったので GitHub からの更新通知を処理するスクリプトを書く。GitHub からの変更通知は POST 形式で送信される。これを VPS 上で受信したときにスクリプトが実行されるように設定しておく。

Git リポジトリ ルート直下に scripts というディレクトリを作成、この中にスクリプトを置く。スクリプトの言語は何でもよいのだが今回は PHP を選んでみた。ファイル名は github-post-receive.php としておく。

$ cd /var/lib/git/
$ sudo mkdir scripts
$ sudo chown gituser:gituser scripts
$ cd scripts
$ vi github-post-receive.php

ファイルを以下のように記述して保存する。

<?php
$githubIPs   = array( "207.97.227.253", "50.57.128.197", "108.171.174.178" );
$errorStatus = "HTTP/1.1 500 Internal Server Error";
if( !in_array( $_SERVER[ "REMOTE_ADDR" ], $githubIPs ) )
{
    header( $errorStatus );
    die;
}

$payload = json_decode( $_REQUEST[ "payload" ] );
if( $payload == NULL )
{
    header( $errorStatus );
    die;
}

if( !chdir( "/var/lib/git/" . $payload->repository->name . ".git" ) )
{
    header( $errorStatus );
    die;
}

shell_exec( "sudo -u gituser git fetch" );
?>

GitHub からのアクセスであることをチェックする。GitHub が POST に使用する IP アドレスは固定なのでそれを判定。IP アドレスは GitHub リポジトリの WebHook URLs 設定画面に記載されているものを指定すること。

json_decode により POST のリクエストボディに含まれる payload パラメータの JSON をデコード。この中には push 時の様々な情報 ( ユーザー、リポジトリ名、...etc ) が格納されている。続けて JSON 内のリポジトリ名を元に clone 先へ移動し、gituser として git fetch を実行している。

スクリプトが完成したら所有者とグループを apache にして Web に公開する。いたずらにアクセスされると厄介なので、シンボリックリンクには UUID を指定し URL を類推されにくくしておく。

$ sudo chown apache:apache github-post-receive.php
$ sudo chmod 644 github-post-receive.php
$ uuidgen
a2267948-9f75-46ba-85df-058199cdfb7d
$ sudo ln -s /var/lib/git/scripts /var/www/html/a2267948-9f75-46ba-85df-058199cdfb7d

UUID は uuidgen コマンドで生成。コマンドを実行すると UUID が出力されるのでそれをコピーしてリンク名に使用。このだとスクリプトへの URL は以下のようになる。

http://ホスト名/a2267948-9f75-46ba-85df-058199cdfb7d/github-post-receive.php

Web ブラウザーからアクセスすると真っ白なページが表示される。Firebug などで HTTP ステータスを確認すると 500 エラーになるはず。

GitHub リポジトリの設定

スクリプトが用意できたので GitHub 側から呼び出せる用にする。GitHub にログインして push をフックしたいリポジトリの設定画面を開く。そして上段にある Admin ボタンを押す。

Admin ボタン

左側のメニューから「Service Hooks」→「WebHook URLs」を選択。

Service Hooks

画面右側に URL の入力欄があるので push 時に呼び出すものを指定。URL を入力したら、Update Settings ボタンを押して設定を保存する。

WebHook URLs

保存に成功すると、以下のような画面が表示される。

設定成功

スクリプトが正しく実行されることを確認するため GitHub リポジトリへ何度か push してみる。以下はスクリプトによって更新された VPS 上のリポジトリを Redmine で表示してみたところ。

Redmine のリポジトリ表示

...今回はここまで。次回は phpMyAdmin あたりを取り上げるかもしれない。