純規の暇人趣味ブログ

手を突っ込んで足を洗う

Gitoliteにgit-lfsを載せる

      2016/12/09    HimaJyun

Gitはソースコードを扱うための物であり、通常、バイナリファイルをバージョン管理する事は不得意です。

そこで、ファイルをポインタとして扱い、別途それ用のサーバに転送する事で良しなにしようと言うgit-lfsと言う物があります。

ただ、比較的最近に出来た物なので、「それ用のサーバ」として良い感じの物がありません。

なので、今回はGitoliteにそれ用のサーバ機能を載せられるものを作ってみました。

git-lfs

git-lfsはGitHub社が開発したgit用の拡張機能です。

拡張機能と言う位置付けではありますが、サーバ側だとGitHubはもちろんの事、BitBucketやGitLab、VisualStudioTeamServices(長い……)などが対応、クライアント側であればSourceTreeやVisualStudioが対応しており、ある種の標準に近いものになるのではないかと(個人的には)思います。(VisualStudioは対応と言っておきながらcloneしか出来ません、pushのやり方をご存知でしたら教えてください)

今回はこのgit-lfsのサーバ側の方としての機能をGitoliteにぶち込んでやろうと思います。

どう言う代物

多分ここを訪れる方の多くはgit-lfsについてはご存じで、先進的な物に敏感で、優秀な才能を持つ方達だと思いますので、詳しい説明は不要かと思いますが……

git-lfsはリモートレポジトリに対してファイルのポインタだけを送信し、バイナリファイルそのものは「それ用のサーバ」に転送します。

ただ、「それ用のサーバ」はHTTPでの通信となっており、ややこしめです。

そこで、今回はSmartHTTPに対応させたGitoliteを応用して、「それ用のサーバ」機能を作成してみました。

gitolite-lfs

その名も「gitolite-lfs(ネーミングセンスの欠片もない」です、これはPerlCGIを用いて作成されており、Gitoliteのアクセス制御をそのまま利用しています。

Perlは使った事ありませんでしたが、Gitoliteの機能を利用するために必要だったのでググりながら頑張ってみました。(比較的枯れているので「その書き方はもう古い!!今はこれだ!!」的なのはなくて楽でしたが)

機能

  • メモリセーフ(GB単位のファイルをアップロードしたとしても、それをメモリにため込んでOOM爆死したりするアホな事はしません)
  • X-Sendfile、X-Accel-Redirectに対応(使わなくても十分使える速度だと思いますが……)

Webサーバ側のタイムアウトさえきちんと設定すれば、10Gでも100Gでもアップロード出来ると思われます。(だいたいその規模になるとgit-lfsのSHAハッシュの計算に時間が掛かるようになるため、git addにおぞましい時間が掛かるようになりますけどね。)

前提条件

設定例を説明するために前提条件を設定しましょう、お好みに合わせて読み替えてください。

  • 「/var/git」にGitoliteがインストール済み(Gitoliteのインストールは別の記事をご覧ください。)
  • 「/var/git/repositories」にレポジトリが存在
  • 「gitolite-shell」は「/var/git/bin」に存在
  • gitolite-lfsは「/var/www/git」にインストール

インストール

GitoliteとWebサーバの双方と連携する必要があるので、少々インストールがややこしめです。

少しずつ説明していきましょう。

配置と設定

とりあえずファイルを配置して少しの設定を行いましょう。

まずは、cloneして、ファイルを移動させます。

git clone https://github.com/HimaJyun/gitolite-lfs
sudo chmod +x -R gitolite-lfs/src/
# /var/www/git/の部分はお好きに調整してください。
sudo mv gitolite-lfs/src/* /var/www/git/

これそのものの設定項目は少な目です。

まずは/var/www/git/gitolite-lfs.shを開きましょう。

以下の項目の設定が必要です。

# Gitoliteのホーム
export GITOLITE_HTTP_HOME="/var/git"
# Gitoliteのレポジトリが存在する場所
export GIT_PROJECT_ROOT="${GITOLITE_HTTP_HOME}/repositories"
# 常に1
export GIT_HTTP_EXPORT_ALL=1

/var/www/git/lfs/config.pl設定項目があります(極力設定しなくても動く様にはしていますが)

our %config = (
	# Gitoliteのホーム(前提条件の通りであればこのまま動作します。)
	GITOLITE_HOME => "$ENV{GIT_PROJECT_ROOT}/..",
	# Gitoliteのbinの場所(gitolite-shellのある場所)
	GITOLITE_BIN => "$ENV{GIT_PROJECT_ROOT}/../bin",
	# Gitoliteのlibの場所(bin/libです。)
	GITOLITE_LIB => "$ENV{GIT_PROJECT_ROOT}/../bin/lib",

	# lfsのレポジトリを保存する場所
	REPO_DIR => "$ENV{GIT_PROJECT_ROOT}/../lfs",

	# git-lfsのアップ/ダウンロード時のバッファサイズ、X-Sendfileを利用する場合、ダウンロードは無視されます。
	UPLOAD_BUFFER_SIZE => 1024,
	DOWNLOAD_BUFFER_SIZE => 1024,

	# X-Sendfile、またはX-Accel-Redirectを使用する場合のヘッダ名
	#X_SENDFILE => "X-Sendfile",
	# NginxでX-Accel-Redirectを使用する場合に設定が必要です。
	#NGX_ACCEL_PATH => "lfsdownload",
);

SSHのコマンドを追加する

git-lfsのイミワカンネー所はHTTPでの通信にしか対応していないせいでSSH経由でのアクセスの際に別途HTTPのURLを教えてやる必要がある事です、正直めんどくさい仕様ですが、Gitoliteは独自のコマンドも簡単に追加出来るので対応が楽です。

まずは/var/git/.gitolite.rcを開いて設定を変更します。

sudo editor /var/git/.gitolite.rc

LFS_URLを追加してください、値にはgit-lfsにアクセスするためのURLを指定します(トレイリングスラッシュ(末尾スラッシュ)必要)

LFS_URL => "http://example.com/",

下の方にスクロールすると「LOCAL_CODE」なる設定値があるはずなので、それをアンコメントします(2種類ありますが、GL_ADMIN_BASEとなっている方)

LOCAL_CODE => "$rc{GL_ADMIN_BASE}/local",

「ENABLE」に「'git-lfs-authenticate',」を追加します。

ENABLE => [
  'git-lfs-authenticate',
]

ここから普段使いのPC(gitolite-adminレポをclone出来るアカウント)で作業します。

gitolite-adminレポジトリをcloneしていない場合はcloneします。

git clone ssh://example.com/gitolite-admin.git
cd gitolite-admin

「local/commands」ディレクトリを作成します。

mkdir -p local/commands

コマンド用のスクリプトをDLします。

curl -o local/commands/git-lfs-authenticate https://raw.githubusercontent.com/HimaJyun/gitolite-lfs/master/commands/git-lfs-authenticate

addしてpushします(と、同時に実行権限を与えます)

git add local/commands/git-lfs-authenticate
git config core.filemode false
git update-index --add --chmod=+x local/commands/git-lfs-authenticate
git commit -am "Add git-lfs-authenticate command."
git push origin master

Apacheの設定

このgitolite-lfsを利用できるようにすると言う事は、同時にGitoliteをSmartHTTPに対応させると言う事でもあります。

Gitoliteは権限がややこしいのでsuEXECを使うのが一般的な手法です、少しずつ解説していきましょう。

suEXECの設定

何はともあれ、suEXECがなければ始まりません、インストールしましょう。

sudo apt-get install apache2-suexec-custom

suEXECはパーミッションの設定がややこしいのですが、ここはfindでまとめて調整してしまいます。

find /var/www/git/ | sudo xargs chown git:git
find /var/www/git/ | sudo xargs chmod 0700
# 以下はApacheが.htpasswdを読み取れるようにするために必要です、LDAP認証とかを利用しているのであれば不要です。
sudo chmod 0755 /var/www/git/
sudo chmod 0644 /var/www/git/.htpasswd

ついでにモジュールを有効にしておきましょう。

sudo a2enmod cgi
sudo a2enmod suexec

バーチャルホストの設定

GitoliteをSmartHTTPに対応させる時、/git/hoge.gitなどでアクセス出来る様にする設定を見かけた事がありますが、今のGitoliteでは不可能です。(スラッシュ区切りでレポジトリをサブディレクトリに入れられるようになったため)

ですので、ここはバーチャルホストで行きます。

<VirtualHost *:80>
	ServerName example.com

	DocumentRoot /var/www/git
	<Directory /var/www/git>
		AllowOverride None
		Options ExecCGI
		# Gitoliteはユーザの識別にBasic認証を通過したユーザ名を利用しているため、Basic認証は必須です。
		AuthType Basic
		AuthName "Git SmartHTTP"
		AuthUserFile "/var/www/git/.htpasswd"
		Require valid-user
		# git-credential-cacheを使わなくてもユーザ名、パスワードをやたらめったら要求されなくなります。
		SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0
	</Directory>

	# X-Sendfileを使用する場合の設定
	#XSendFile on
	#XSendFilePath /var/git/lfs
	# X-Sendfileを使用する場合は/var/www/git/lfs/config.plのX_SENDFILEを"X-Sendfile"に設定してください。

	# Gitoliteのユーザ名、グループ名
	SuexecUserGroup git git
	# / を /var/www/git/gitolite-lfs.sh/ に紐づけ(トレイリングスラッシュ必須)
	ScriptAlias / /var/www/git/gitolite-lfs.sh/

	ErrorLog ${APACHE_LOG_DIR}/error.log
	CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

X-Sendfileを利用する場合

X-Sendfileを利用する場合、git-lfsのデータがWebサーバの実行ユーザから読み取れる必要があります。

最初にmod_xsendfileをインストールしておきます。

sudo apt-get install libapache2-mod-xsendfile

まずは.gitolite.rcを開きましょう。

sudo editor /var/git/.gitolite.rc

上の方に「UMASK」という項目があります、デフォルトだと0077になっているはずなので、これを0027に変更してください。

以下のコマンドで権限を調整します。

# レポジトリが/var/git/lfs以外の場合、Gitoliteの実行ユーザがgit以外の場合は読み替えてください。
sudo install -d -m 0750 -o git -g git /var/git/lfs
sudo chmod g+rx -R /var/git/lfs/

あとはWebサーバの実行ユーザ(www-data)をGitoliteの実行ユーザのグループ(git)に追加します。

sudo gpasswd -a www-data git

先ほどの設定例のコメントにも書いてありますが、X-Sendfileを有効にする必要があります。

XSendFile on
XSendFilePath /var/git/lfs

また、/var/www/git/lfs/config.plのX_SENDFILEを"X-Sendfile"に設定してください。

Nginxの設定

上記のApacheでの設定と同じことをnginxでやりたい場合の設定例です。

ただ、nginxはその仕様から、この様な用途には不向きで、苦手です。

もしあなたが可能であるならば、nginxではなくApacheを使う事を検討して下さい。

CGIの設定

nginxは単体でCGIスクリプトを実行したりする機能がありません。(そして、FsatCGIスクリプトを起動する機能もありません)

そのため、fcgiwrapでこのCGIをFastCGIにラッピングして、spawn-fcgiで起動させる必要があります。

まずはfcgiwrapとspawn-fcgiをインストールしておきましょう。

sudo apt-get install spawn-fcgi fcgiwrap

それらを起動させるためのinitスクリプトも(一応)用意しましたのでご利用下さい。

cd /etc/init.d
sudo curl -O https://raw.githubusercontent.com/HimaJyun/gitolite-lfs/docs/Nginx/gitolite-lfs
sudo chmod +x gitolite-lfs

initスクリプト内に設定項目があるので設定を行いましょう。

sudo editor /etc/init.d/gitolite-lfs
# === config ===
# fcgiwrapのパス
fcgiwrap="/usr/sbin/fcgiwrap"
# PIDファイルのパス
pid="/run/git.pid"
# ソケットファイルのパス
sock="/run/git.sock"
# 子プロセスの数
proc=3
# Gitoliteの実行ユーザ
git_user="git"
# nginxの実行ユーザ
ngx_user="www-data"
# ==============

サービスを追加します。

sudo systemctl enable gitolite-lfs

デーモンを起動しましょう。

sudo systemctl start gitolite-lfs

バーチャルホストの設定

ここいらはApacheとそう変わりません。

server {
	listen 80;
	listen [::]:80;

	root /var/www/git;
	server_name example.com;

	location / {
		# 巨大なファイルのアップロードを許可します。
		client_max_body_size 10G;

		include fastcgi_params;

		# # Gitoliteはユーザの識別にBasic認証を通過したユーザ名を利用しているため、Basic認証は必須です。
		auth_basic "Git SmartHTTP";
		auth_basic_user_file /var/www/git/.htpasswd;

		# gitolite-lfs.shを指定します。
		fastcgi_param SCRIPT_FILENAME /var/www/git/gitolite-lfs.sh;
		fastcgi_param PATH_INFO $uri;
		fastcgi_param REMOTE_USER $remote_user;

		fastcgi_pass unix:/run/git.sock;
	}

	# X-Accel-Redirectを使う場合
	#location ^~ /lfsdownload/ {
	#	# Please set the actual path of the repository.
	#	alias /var/git/lfs/; # Set the trailing slash.
	#	internal;
	#}
	# /var/www/git/lfs/config.plのX_SENDFILEに"X-Accel-Redirect"を設定してください。
	# /var/www/git/config.plのNGX_ACCEL_PATHに"lfsdownload"を設定してください。

	access_log /var/log/nginx/access.log;
	error_log /var/log/nginx/error.log;
}

X-Accel-Redirectを利用する場合

Apacheと同じように、実行ユーザがgit-lfsのデータを読み取れる必要があります。

まずは.gitolite.rcを開きましょう。

sudo editor /var/git/.gitolite.rc

「UMASK」の値を「0027」に変更します。

ファイルの権限を調整します。

# レポジトリが/var/git/lfs以外の場合、Gitoliteの実行ユーザがgit以外の場合は読み替えてください。
sudo install -d -m 0750 -o git -g git /var/git/lfs
sudo chmod g+rx -R /var/git/lfs/

あとはWebサーバの実行ユーザ(www-data)をGitoliteの実行ユーザのグループ(git)に追加します。

sudo gpasswd -a www-data git

やはりApacheと同じようにX-Accel-Redirectを有効にする必要があります。

先ほどの設定例の中でコメントアウトされていた設定をアンコメントしてください。

また、/var/www/git/lfs/config.plのX_SENDFILEを"X-Accel-Redirect"に、NGX_ACCEL_PATHを"lfsdownload"に設定してください。

終わりに

これはどちらかと言うと自分向けに用意した様な物なので、少々扱い辛くなってしまっていますが、もし「Gitoliteでもgit-lfsが使いたい、でも良いサーバがない!!」みたいな場合は使ってみてください。

ちなみにですが、GitLabはgit-lfsをサポートしているので、性能面の問題(GitLabは少々重め)と、英語onlyな問題がクリア出来るのであればそちらの方がオススメかも知れません。

僕はGitサーバをRaspberry Pi上に置いており、Raspberry Piの性能でGitLabは厳しいでしょうからこの様にして解決しています。

 - サーバ運営