Gitolite是一款Perl语言开发的Git服务管理工具,通过公钥对用户进行认证,并能够通过配置文件对写操作进行基于分支和路径的精细授权。Gitolite采用的是SSH协议并且使用SSH公钥认证,因此无论是管理员还是普通用户,都需要对SSH非常熟悉。在开始之前,请确认您已经通读过第29章“使用SSH协议”。
Gitolite的官方网址是:http://github.com/sitaramc/gitolite。从提交日志里可以看出作者是Sitaram Chamarty,最早的提交开始于 2009年8月。作者是受到了Gitosis的启发,开发了这款功能更为强大和易于安装的软件。Gitolite的命名,作者的原意是Gitosis和lite的组合,不过因为Gitolite的功能越来越强大,已经超越了Gitosis,因此作者笑称Gitolite可以看作是Github-lite——轻量级的Github。
我是在2010年8月才发现Gitolite这个项目的,并尝试将公司基于Gitosis的管理系统迁移至Gitolite。在迁移和使用过程中,增加和改进了一些实现,如:通配符版本库的创建过程,对创建者的授权,版本库名称映射等。本文关于Gitolite的介绍也是基于我改进的版本[1]。
原作者的版本库地址:
笔者改进后的Gitolite分支:
Gitolite的实现机制和使用特点概述如下:
Gitolite安装在服务器(server)某个帐号之下,例如git帐号。
管理员通过git命令检出名为gitolite-admin的版本库。
$ git clone git@server:gitolite-admin.git
管理员将所有Git用户的公钥保存在gitolite-admin库的keydir目录下,并编辑conf/gitolite.conf文件为用户授权。
当管理员提交对gitolite-admin库的修改并推送到服务器之后,服务器上gitolite-admin版本库的钩子脚本将执行相应的设置工作。
新用户的公钥自动追加到服务器端安装帐号主目录下的.ssh/authorized_keys文件中,并设置该用户的shell为gitolite的一条命令gl-auth-command。在.ssh/authorized_keys文件中增加的内容示例如下: [2]
command="/home/git/bin/gl-auth-command jiangxin",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2...(公钥内容来自于 jiangxin.pub)...
更新服务器端的授权文件~/.gitolite/conf/gitolite.conf。
编译授权文件为~/.gitolite/conf/gitolite.conf-compiled.pm。
若用ssh命令登录服务器(以git用户登录)时,因为公钥认证的相关设置(使用gl-auth-command作为shell),不能进入shell环境,而是打印服务器端git库授权信息后马上退出。即用户不会通过git用户进入服务器的shell,也不会对系统的安全造成威胁。
$ ssh git@bj
hello jiangxin, the gitolite version here is v1.5.5-9-g4c11bd8
the gitolite config gives you the following access:
R gistore-bj.ossxp.com/.*$
C R W ossxp/.*$
@C @R W users/jiangxin/.+$
Connection to bj closed.
用户可以用git命令访问授权的版本库。
若管理员授权,用户可以远程在服务器上创建新版本库。
下面介绍Gitolite的部署和使用。
安装Gitolite(2.1版本)对服务器的要求是:
和其他Unix上软件包一样Gitolite既可通过操作系统本身提供的二进制发布包方式安装,也可通过克隆Gitolite源码库从源代码安装Gitolite。
注解
老版本的Gitolite提供了一种从客户端发起安装的模式,但该安装模式需要管理员维护两套不同公钥/私钥对(一个公钥用于无口令登录服务器以安装和更新软件,另外一个公钥用于克隆和推送gitolite-admin版本库),稍嫌复杂,在2.1之后的Gitolite取消了这种安装模式。
Gitolite搭建的Git服务器是以SSH公钥认证为基础的,无论是普通Git用户还是Gitolite的管理员都通过公钥认证访问Gitolite服务器。在Gitolite的安装过程中需要提供管理员公钥,以便在Gitolite安装完毕后管理员能够远程克隆gitolite-admin版本库(仅对管理员授权),对Gitolite服务器进行管理——添加新用户和为用户添加授权。
为此在安装Gitolite之前,管理员需要在客户端(用于远程管理Gitolite服务器的客户端)创建用于连接Gitolite服务器的SSH公钥(如果尚不存在的话),并把公钥文件拷贝到服务器上。
在客户端创建SSH公钥/私钥对。
如果管理员在客户端尚未创建公钥/私钥对,使用下面的命令会在用户主目录下创建名为~/.ssh/id_rsa的SSH私钥和名为~/.ssh/id_rsa.pub的公钥文件:
$ ssh-keygen
将公钥文件从客户端复制到服务器端,以便安装Gitolite时备用。
可以使用ftp或U盘拷贝等方式从客户端向服务器端传送文件,不过用scp命令是非常方便的,例如服务器地址为server,相应的拷贝命令为:
$ scp ~/.ssh/id_rsa.pub server:/tmp/admin.pub
常见的Linux发行版都包含了Gitolite软件包,安装Gitolite使用如下命令:
Debian/Ubuntu:
$ sudo aptitude install gitolite
RedHat:
$ sudo yum install gitolite
安装完毕后会自动创建一个专用系统账号如gitolite。在Debian平台上创建的gitolite账号使用/var/lib/gitolite作为用户主目录,而非/home/gitolite。
$ getent passwd gitolite
gitolite:x:114:121:git repository hosting,,,:/var/lib/gitolite:/bin/bash
安装完毕,运行如下命令完成对Gitolite的配置:
切换至新创建的gitolite用户账号。
$ sudo su - gitolite
运行gl-setup命令,并以客户端复制过来的公钥文件路径作为参数。
$ gl-setup /tmp/admin.pub
Debian等平台会在安装过程中(或运行sudo dpkg-reconfigure gitolite命令时),开启配置界面要求用户输入Gitolite专用账号、Git版本库根目录、管理员公钥文件名,然后自动执行gl-setup完成设置。
如果想在系统中部署多个Gitolite实例,希望部署最新的Gitolite版本,或者希望安装自己或他人对Gitolite的定制版本,就要采用从源代码进行Gitolite部署。
创建专用系统账号。
首先需要在服务器上创建Gitolite专用帐号。因为所有用户都要通过此帐号访问Git版本库,为方便易记一般选择更为简练的git作为专用帐号名称。
$ sudo adduser --system --group --shell /bin/bash git
注意添加的用户要能够远程登录,若系统只允许特定用户组(如ssh用户组)的用户才可以通过SSH协议登录,就需要将新建的git用户添加到该特定的用户组中。执行下面的命令可以将git用户添加到ssh用户组。
$ sudo adduser git ssh
取消git用户的口令,以便只能通过公钥对git账号进行认证,增加系统安全性。
$ sudo passwd --delete git
切换到新创建的用户账号,后续的安装都以该用户身份执行。
$ sudo su - git
在服务器端下载Gitolite源码。一个更加“Git”的方式就是克隆Gitolite的版本库。
克隆官方的Gitolite版本库如下:
$ git clone git://github.com/sitaramc/gitolite.git
也可以克隆定制后的Gitolite版本库,如我在GitHub上基于Gitolite官方版本库建立的分支版本:
$ git clone git://github.com/ossxp-com/gitolite.git
安装Gitolite。
运行源码目录中的src/gl-system-install执行安装。
$ cd gitolite
$ src/gl-system-install
如果像上面那样不带参数的执行安装程序,会将Gitolite相关命令安装到~/bin目录中,相当于执行:
$ src/gl-system-install $HOME/bin $HOME/share/gitolite/conf $HOME/share/gitolite/hooks
运行gl-setup完成设置。
若Gitolite安装到~/bin目录下(即没有安装到系统目录下),需要设置PATH环境变量以便gl-setup能够正常运行。
$ export PATH=~/bin:$PATH
然后运行gl-setup命令,并以客户端复制过来的公钥文件路径作为参数。
$ ~/bin/gl-setup /tmp/admin.pub
当Gitolite安装完成后,就会在服务器端版本库根目录下创建一个用于管理Gitolite的版本库。若以git用户安装,则该Git版本库的路径为:~git/repositories/gitolite-admin.git。
在客户端用ssh命令连接服务器server的git用户,如果公钥认证验证正确的话,Gitolite将此SSH会话的用户认证为admin用户,显示admin用户的权限。如下:
$ ssh -T git@server
hello admin, this is gitolite v2.1-7-ge5c49b7 running on git 1.7.7.1
the gitolite config gives you the following access:
R W gitolite-admin
@R_ @W_ testing
从上面命令的倒数第二行输出可以看出用户admin对版本库gitolite-admin拥有读写权限。
为了对Gitolite服务器进行管理,需要在客户端克隆gitolite-admin版本库,使用如下命令:
$ git clone git@server:gitolite-admin.git
$ cd gitolite-admin/
在客户端克隆的gitolite-admin目录下有两个子目录conf/和keydir/,包含如下文件:
文件:keydir/admin.pub。
目录keydir下初始时只有一个用户公钥,即管理员admin的公钥。
文件:conf/gitolite.conf。
该文件为授权文件。初始内容为:
repo gitolite-admin
RW+ = admin
repo testing
RW+ = @all
默认授权文件中只设置了两个版本库的授权:
gitolite-admin
即本版本库。此版本库用于Gitolite管理,只有admin用户有读写和强制更新的权限。
testing
默认设置的测试版本库。设置为任何人都可以读写及强制更新。
增加新用户,就是允许新用户能够通过其公钥访问Git服务。只要将新用户的公钥添加到gitolite-admin版本库的keydir目录下,即完成新用户的添加,具体操作过程如下。
管理员从用户获取公钥,并将公钥按照username.pub格式进行重命名。
管理员进入gitolite-admin本地克隆版本库中,复制新用户公钥到keydir目录。
$ cp /path/to/dev1.pub keydir/
$ cp /path/to/dev2.pub keydir/
$ cp /path/to/jiangxin.pub keydir/
执行git add命令,将公钥添加到版本库。
$ git add keydir
执行git commit,完成提交。
$ git commit -m "add user: jiangxin, dev1, dev2"
执行git push,同步到服务器,才真正完成新用户的添加。
$ git push
Counting objects: 8, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 1.38 KiB, done.
Total 6 (delta 0), reused 0 (delta 0)
remote: Already on 'master'
remote:
remote: ***** WARNING *****
remote: the following users (pubkey files in parens) do not appear in the config file:
remote: dev1(dev1.pub),dev2(dev2.pub),jiangxin(jiangxin.pub)
在git push的输出中,以remote标识的输出是服务器端执行post-update钩子脚本的错误输出,用于提示新增的三个用户(公钥)在授权文件中没有被引用。接下来会介绍如何修改授权文件,以及如何为用户添加授权。
服务器端的git主目录下的.ssh/authorized_keys文件会随着新增用户公钥而更新,即添加三条新的记录。如下:
$ cat ~git/.ssh/authorized_keys
# gitolite start
command="/home/git/bin/gl-auth-command admin",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty <用户admin的公钥...>
command="/home/git/bin/gl-auth-command dev1",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty <用户dev1的公钥...>
command="/home/git/bin/gl-auth-command dev2",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty <用户dev2的公钥...>
command="/home/git/bin/gl-auth-command jiangxin",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty <用户jiangxin的公钥...>
# gitolite end
新用户添加完毕,接下来需要为新用户添加授权,这个过程也比较简单,只需修改conf/gitolite.conf配置文件,提交并推送。具体操作过程如下:
管理员进入gitolite-admin本地克隆版本库中,编辑conf/gitolite.conf。
$ vi conf/gitolite.conf
授权指令比较复杂,先通过建立新用户组尝试一下更改授权文件。
考虑到之前增加了三个用户公钥,服务器端发出了用户尚未在授权文件中出现的警告。现在就在这个示例中解决这个问题。
可以在其中加入用户组@team1,将新添加的用户jiangxin、dev1、dev2都归属到这个组中。
只需要在conf/gitolite.conf文件的文件头加入如下指令即可。用户名之间用空格分隔。
@team1 = dev1 dev2 jiangxin
编辑完毕退出。可以用git diff命令查看改动:
还修改了版本库testing的授权,将@all用户组改为新建立的@team1用户组。
$ git diff
diff --git a/conf/gitolite.conf b/conf/gitolite.conf
index 6c5fdf8..f983a84 100644
--- a/conf/gitolite.conf
+++ b/conf/gitolite.conf
@@ -1,5 +1,7 @@
+@team1 = dev1 dev2 jiangxin
+
repo gitolite-admin
RW+ = admin
repo testing
- RW+ = @all
+ RW+ = @team1
编辑结束,提交改动。
$ git add conf/gitolite.conf
$ git commit -q -m "new team @team1 auth for repo testing."
执行git push,同步到服务器,授权文件的更改才真正生效。
可以注意到,推送后的输出中没有了警告。
$ git push
Counting objects: 7, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 398 bytes, done.
Total 4 (delta 1), reused 0 (delta 0)
remote: Already on 'master'
To git@server:gitolite-admin.git
bd81884..79b29e4 master -> master
下面看一个不那么简单的授权文件。为方便描述添加了行号。
1 @manager = jiangxin wangsheng
2 @dev = dev1 dev2 dev3
3
4 repo gitolite-admin
5 RW+ = jiangxin
6
7 repo ossxp/[a-z].+
8 C = @manager
9 RW+ = CREATOR
10 RW = WRITERS
11 R = READERS @dev
12
13 repo testing
14 RW+ = @manager
15 RW master = @dev
16 RW refs/tags/v[0-9] = dev1
17 - refs/tags/ = @all
在上面的示例中,演示了很多授权指令:
下面针对授权指令进行详细的讲解。
在conf/gitolite.conf授权文件中,可以定义用户组或版本库组。组名称以@字符开头,可以包含一个或多个成员。成员之间用空格分开。
例如定义管理员组:
@admin = jiangxin wangsheng
组可以嵌套:
@staff = @admin @engineers tester1
除了作为用户组外,同样的语法也适用于版本库组。版本库组和用户组的定义没有任何区别,只是在版本库授权指令中处于不同的位置。即位于授权指令中的版本库位置代表版本库组,位于授权指令中的用户位置代表用户组。
一个版本库可以包含多条授权指令,这些授权指令组成了一个版本库的权限控制列表(ACL)。例如:
repo testing
RW+ = jiangxin @admin
RW = @dev @test
R = @all
每一个版本库授权都以一条repo指令开始。指令repo后面是版本库列表,版本之间用空格分开,还可以包括版本库组。示例如下:
repo sandbox/test1 sandbox/test2 @test_repos
注意版本库名称不要添加.git后缀,在版本库创建或权限匹配过程中会自动添加.git后缀。用repo指令定义的版本库会自动在服务器上创建,但使用正则表达式定义的通配符版本库除外。
通配符版本库就是在repo指令定义的版本库名称中使用了正则表达式。通配符版本库针对的不是某一个版本库,而是匹配一组版本库,这些版本库可能已经存在或尚未创建。例如下面的repo指令定义了一组通配符版本库。
repo redmine/[a-zA-Z].+
通配符版本库匹配时会自动在版本库名称前面加上前缀^,在后面添加后缀$。即通配符版本库对版本库名称进行完整匹配而非部分匹配,这一点和后面将要介绍的正则引用(refex)大不一样。
有时repo指令定义普通版本库和通配符版本库的界限并不是那么清晰,像下面这条repo指令:
repo ossxp/.+
因为点号(.)和加号(+)也可以作为普通字符出现在版本库名称中,这条指令会导致Gitolite创建ossxp目录,并在目录下创建名为.+.git的版本库。因此在定义通配符版本库时要尽量写得“复杂点”以免造成误判。
小技巧
我对Gitolite进行了一点改进,能够减少对诸如ossxp/.+通配符版本库误判的可能。并提供在定义通配符版本库时使用^前缀和$后缀,以减少误判。如使用如下方式定义通配符版本库:repo ^myrepo。
在repo指令之后是缩进的一条或多条授权指令。授权指令的语法如下:
<权限> [零个或多个正则表达式匹配的引用] = <user> [<user> ...]
每条指令必须指定一个权限,称为授权关键字。包括传统的授权关键字:C、R、RW和RW+,以及将分支创建和分支删除分离出来的扩展授权关键字:RWC、RW+C、RWD、RW+D、RWCD、RW+CD。
传统的授权关键字包括:
C
C代表创建版本库,仅在对通配符版本库进行授权时方可使用。用于设定谁可以创建名称与通配符匹配的版本库。
R
R代表只读权限。
RW
RW代表读写权限。如果在同一组(针对同一版本库)授权指令中没有出现代表创建分支的扩展授权关键字,则RW还包括创建分支的权限,而不仅是在分支中的读写。
RW+
RW+除了具有读写权限外,还可以强制推送(执行非快进式推送)。如果在同一组授权指令中没有出现代表分支删除的扩展授权关键字,则RW+还同时包含了创建分支和删除分支的授权。
-
-含义为禁用。因为禁用规则只在第二阶段授权生效[3],所以一般只用于撤销特定用户对特定分支或整个版本库的写操作授权。
扩展的授权关键字将创建分支和删除分支的权限从传统授权关键字中分离出来,从而新增了六个授权关键字。在一个版本库的授权指令中一旦发现创建分支和/或删除分支的授权使用了下列新的扩展授权关键字后,原有的RW和RW+不再行使对创建分支和/或删除分支的授权。
RWC
RWC代表读写授权、创建新引用(分支、里程碑等)的授权。
RW+C
RW+C代表读写授权、强制推送和创建新引用的授权。
RWD
RWD代表读写授权、删除引用的授权。
RW+D
RW+D代表读写授权、强制推送和删除引用的授权。
RWCD
RWCD代表读写授权、创建新引用和删除引用的授权。
RW+CD
RW+CD代表读写授权、强制推送、创建新引用和删除引用的授权。
授权关键字后面(等号前面)是一个可选的正则引用(refex)或正则引用列表(用空格分隔)。
授权关键字后面(等号前面)也可以包含一个以NAME/为前缀的表达式,但这个表达式并非引用,而是路径。支持基于路径的写操作授权。
授权指令以等号(=)为标记分为前后两段,等号后面的是用户列表。用户之间用空格分隔,并且可以使用用户组。
Gitolite的授权实际分为两个阶段。第一个阶段称为前Git阶段,即在Git命令执行前,由SSH连接触发的gl-auth-command命令执行的授权检查。包括:
版本库的读。
如果用户拥有版本库或版本库的任意分支具有下列权限之一:R、RW、RW+(或其他扩展关键字),则整个版本库(包含所有分支)对用户均可读,否则版本库不可读取。
最让人迷惑的就是只为某用户分配了对某个分支的读授权(R),而该用户实际上能够读取版本库的任意分支。之所以Gitolite对读授权不能细化到分支甚至目录,只能针对版本库进行粗放的非零即壹的读操作授权,是因为读授权只在版本库授权的第一个阶段进行检查,而在此阶段还获取不到版本库的分支。
版本库的写。
版本库的写授权实际上要在两个阶段分别进行检查。本阶段,即第一阶段仅检查用户是否拥有下列权限之一:RW、RW+或C授权,具有这些授权则通过第一阶段的写权限检查。第二个阶段的授权检查由Git版本库的钩子脚本触发,能够实现基于分支和路径的写操作授权,以及对分支创建、删除和是否可强制更新进行授权检查,具体见第二阶段授权过程描述。
版本库的创建。
仅对正则表达式定义的通配符版本库有效。即拥有C授权的用户可以创建和相应的正则表达式匹配的版本库。创建版本库(尤其是通过执行git push命令创建版本库)不免要涉及到执行新创建的版本库的钩子脚本,所以需要为版本库设置一条创建者可读写的授权。如:
RW = CREATOR
Gitolite对授权的第二个阶段的检查,实际上是通过update钩子脚本进行的。因为版本库的读操作不执行update钩子,所以读操作只在授权的第一个阶段(前Git阶段)就完成了检查,授权的第二个阶段仅对写操作进行更为精细的授权检查。
Gitolite的授权非常强大也很复杂,因此从版本库授权的实际案例来学习是非常行之有效的方式。
授权文件如下:
1 @admin = jiangxin
2 @dev = dev1 dev2 badboy jiangxin
3 @test = test1 test2
4
5 repo testing
6 RW+ = @admin
7 R = @test
8 - = badboy
9 RW = @dev test1
关于授权的说明:
用户jiangxin对版本库具有写的权限,并能够强制推送。
由于用户jiangxin属于用户组@admin,通过第6行授权指令而具有读写权限,以及强制推送、创建和删除引用的权限。
用户test1对版本库具有写的权限。
第7行定义了test1所属的用户组@test具有只读权限。第9行定义了test1用户具有读写权限。Gitolite的实现是对读权限和写权限分别进行判断并汇总(并集),从而test1用户具有读写权限。
用户badboy对版本库只具有读操作的权限,没有写操作权限。
第8行的指令以减号(-)开始,是一条禁用指令。禁用指令只在授权的第二阶段起作用,即只对写操作起作用,不会对badboy用户的读权限施加影响。在第9行的指令中,badboy所在的@dev组拥有读写权限。但禁用规则会对写操作起作用,导致badboy只有读操作权限,而没有写操作。
上面在Gitolite配置文件中对testing版本库进行的授权,当通过推送更新至Gitolite服务器上时,如果服务器端尚不存在一个名为testing的版本库,Gitolite会自动初始化一个空白的testing版本库。
授权文件如下:
1 @administrators = jiangxin admin
2 @dev = dev1 dev2 badboy
3 @test = test1 test2
4
5 repo sandbox/[a-z].+
6 C = @administrators
7 RW+ = CREATOR
8 R = @test
9 - = badboy
10 RW = @dev test1
这个授权文件的版本库名称中使用了正则表达式,匹配在sandbox目录下的任意以小写字母开头的版本库。因为通配符版本库并非指代一个具体版本库,因而不会在服务器端自动创建,而是需要管理员手动创建。
创建和通配符匹配的版本库,Gitolite的原始实现是克隆即创建。例如管理员jiangxin创建名为sandbox/repos1.git版本库,执行下面命令:
jiangxin$ git clone git@server:sandbox/repos1.git
这种克隆即创建的方式很容易因为录入错误而导致意外创建错误的版本库。我改进的Gitolite需要通过推送来创建版本库。下面的示例通过推送操作(以jiangxin用户身份),远程创建版本库sandbox/repos1.git。
jiangxin$ git remote add origin git@server:sandbox/repos1.git
jiangxin$ git push origin master
对创建完成的sandbox/repo1.git版本库进行授权检查,会发现:
用户jiangxin对版本库具有读写权限,而用户admin则不能读取sandbox/repo1.git版本库。
第6行的授权指令同时为用户jiangxin和admin赋予了创建与通配符相符的版本库的权限。但因为版本库sandbox/repo1.git是由jiangxin而非admin创建的,所以第7条的授权指令只为版本库的创建者jiangxin赋予了读写权限。
Gitolite通过在服务器端该版本库目录下创建一个名为gl-creater的文件记录了版本库的创建者。
和之前的例子相同的是:
如果采用接下来的示例中的版本库权限设置,版本库sandbox/repo1.git的创建者jiangxin还可以使用setperms命令为版本库添加授权。具体用法参见下面的示例。
授权文件如下:
1 @administrators = jiangxin admin
2
3 repo users/CREATOR/[a-zA-Z].*
4 C = @all
5 RW+ = CREATOR
6 RW = WRITERS
7 R = READERS @administrators
关于授权的说明:
第4条指令,设置用户可以在自己的名字空间(/usrs/<userid>/)下,自己创建版本库。例如下面就是用户dev1执行git push命令在Gitolite服务器上自己的名字空间下创建版本库。
dev1$ git push git@server:users/dev1/repos1.git master
第5条指令,设置版本库创建者对版本库具有完全权限。
即用户dev1拥有对其自建的users/dev1/repos1.git拥有最高权限。
第7条指令,让管理员组@administrators的用户对于users/下用户自建的版本库拥有读取权限。
那么第6、7条授权指令中出现的WRITERS和READERS是如何定义的呢?实际上这两个变量可以看做是两个用户组,不过这两个用户组不是在Gitolite授权文件中设置,而是由版本库创建者执行ssh命令创建的。
版本库users/dev1/repos1.git的创建者dev1可以通过ssh命令连接服务器,使用setperms命令为自己的版本库设置角色。命令setperms的唯一一个参数就是版本库名称。当执行命令时,会自动进入一个编辑界面,手动输入角色定义后,按下^D(Ctrl+D)结束编辑。如下所示:
dev1$ ssh git@server setperms users/dev1/repos1.git
READERS dev2 dev3
WRITERS jiangxin
^D
即在输入setperms指令后,进入一个编辑界面,输入^D(Ctrl+D)结束编辑。也可以将角色定义文件保存到文件中,用setperms指令加载。如下:
dev1$ cat > perms << EOF
READERS dev2 dev3
WRITERS jiangxin
EOF
dev1$ ssh git@server setperms users/dev1/repos1.git < perms
New perms are:
READERS dev2 dev3
WRITERS jiangxin
当版本库创建者dev1对版本库users/dev1/repos1.git进行了如上设置后,Gitolite在进行授权检查时会将setperms设置的角色定义应用到授权文件中。故此版本库users/dev1/repos1.git中又补充了新的授权:
版本库users/dev1/repos1.git的建立者dev1可以使用getperms查看自己版本库的角色设置。如下:
dev1$ ssh git@server getperms users/dev1/repos1.git
READERS dev2 dev3
WRITERS jiangxin
如果在用户自定义授权中需要使用READERS和WRITERS之外的角色,管理员可以通过修改gitolite.rc文件中的变量$GL_WILDREPOS_PERM_CATS实现。该变量的默认设置如下:
$GL_WILDREPOS_PERM_CATS = "READERS WRITERS";
传统模式的引用授权指的是在授权指令中只采用R、RW和RW+的传统授权关键字,而不包括后面介绍的扩展授权指令。传统的授权指令没有把分支的创建和分支删除权限细分,而是和写操作及强制推送操作混杂在一起。
授权文件:
1 @administrators = jiangxin admin
2 @dev = dev1 dev2 badboy
3 @test = test1 test2
4
5 repo test/repo1
6 RW+ = @administrators
7 RW master refs/heads/feature/ = @dev
8 R = @test
关于授权的说明:
扩展模式的引用授权,指的是该版本库的授权指令出现了下列授权关键字中的一个或多个:RWC、RWD、RWCD、RW+C、RW+D、RW+CD,将分支的创建权限和删除权限从读写权限中分离出来,从而可对分支进行更为精细的权限控制。
即引用的创建和删除使用了单独的授权关键字,和写权限和强制推送权限分开。
下面是一个采用扩展授权关键字的授权文件:
1 repo test/repo2
2 RW+C = @administrators
3 RW+ = @dev
4 RW = @test
5
6 repo test/repo3
7 RW+CD = @administrators
8 RW+C = @dev
9 RW = @test
通过上面的配置文件,对于版本库test/repo2.git具有如下的授权:
第2行,用户组@administrators中的用户,具有创建和删除引用的权限,并且能强制推送。
其中创建引用来自授权关键字中的C,删除引用来自授权关键中的+,因为该版本库授权指令中没有出现D,因而删除应用授权沿用传统授权关键字。
第3行,用户组@dev中的用户,不能创建引用,但可以删除引用,并且可以强制推送。
因为第2行授权关键字中字符C的出现,使得创建引用采用扩展授权关键字,因而用户组@dev不具有创建引用的权限。
第4行,用户组@test中的用户,拥有读写权限,但是不能创建引用,不能删除引用,也不能强制推送。
通过上面的配置文件,对于版本库test/repo3.git具有如下的授权:
第7行,用户组@administrators中的用户,具有创建和删除引用的权限,并且能强制推送。
其中创建引用来自授权关键字中的C,删除引用来自授权关键中的D。
第8行,用户组@dev中的用户,可以创建引用,并能够强制推送,但不能删除引用。
因为第7行授权关键字中字符C和D的出现,使得创建和删除引用都采用扩展授权关键字,因而用户组@dev不具有删除引用的权限。
第9行,用户组@test中的用户,可以推送到任何引用,但是不能创建引用,不能删除引用,也不能强制推送。
授权文件片段:
1 RW refs/tags/v[0-9] = jiangxin
2 - refs/tags/v[0-9] = @dev
3 RW refs/tags/ = @dev
关于授权的说明:
前面我们介绍过通过CREATOR特殊关键字实现用户自建版本库的功能。与之类似,Gitolite还支持在一个版本库中用户自建分支的功能。
用户在版本库中自建分支用到的关键字是USER而非CREATOR。即当授权指令的引用表达式中出现的USER关键字时,在授权检查时会动态替换为用户ID。例如授权文件片段:
1 repo test/repo4
2 RW+CD = @administrators
3 RW+CD refs/heads/u/USER/ = @all
4 RW+ master = @dev
关于授权的说明:
Gitolite也实现了对路径的写操作的精细授权,并且非常巧妙的是实现此功能所增加的代码可以忽略不计。这是因为Gitolite把路径当作是特殊格式的引用的授权。
在授权文件中,如果一个版本库的授权指令中的正则引用字段出现了以NAME/开头的引用,则表明该授权指令是针对路径进行的写授权,并且该版本库要进行基于路径的写授权判断。
示例:
1 repo foo
2 RW = @junior_devs @senior_devs
3
4 RW NAME/ = @senior_devs
5 - NAME/Makefile = @junior_devs
6 RW NAME/ = @junior_devs
关于授权的说明:
Gitolite维护的版本库默认位于安装用户主目录下的repositories目录中 ,即如果安装用户为git,则版本库都创建在/home/git/repositories目录之下。可以通过配置文件.gitolite.rc修改默认的版本库的根路径。
$REPO_BASE="repositories";
有多种创建版本库的方式。一种是在授权文件中用repo指令设置版本库(未使用正则表达式的版本库)的授权,当对gitolite-admin版本库执行git push操作时,自动在服务端创建新的版本库。另外一种方式是在授权文件中用正则表达式定义的通配符版本库,不会即时创建(也不可能被创建),而是被授权的用户在远程创建后推送到服务器上完成创建。
尝试在授权文件conf/gitolite.conf中加入一段新的版本库授权指令,而这个版本库尚不存在。新添加到授权文件中的内容为:
repo testing2
RW+ = @all
然后将授权文件的修改提交并推送到服务器,会看到授权文件中添加新授权的版本库testing2被自动创建。
$ git push
Counting objects: 7, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 375 bytes, done.
Total 4 (delta 1), reused 0 (delta 0)
remote: Already on 'master'
remote: creating testing2...
remote: Initialized empty Git repository in /home/git/repositories/testing2.git/
To gitadmin.bj:gitolite-admin.git
278e54b..b6f05c1 master -> master
注意其中带remote标识的输出,可以看到版本库testing2.git被自动初始化了。
此外使用版本库组的语法(即用@创建的组,用作版本库),也会被自动创建。例如下面的授权文件片段设定了一个包含两个版本库的组@testing,当将新配置文件推送到服务器上时,会自动创建testing3.git和testing4.git。
@testing = testing3 testing4
repo @testing
RW+ = @all
通配符版本库是用正则表达式语法定义的版本库,所指的并非某一个版本库而是和正则表达式相匹配的一组版本库。要想使用通配符版本库,需要在服务器端Gitolite的安装用户(如git)主目录下,修改配置文件.gitolite.rc,使其包含如下配置:
$GL_WILDREPOS = 1;
使用通配符版本库,可以对一组版本库进行授权,非常有效。但是版本库的创建则不像前面介绍的那样,不会在授权文件推送到服务器时创建,而是由拥有版本库创建授权(C)的用户手工进行创建。
对于用通配符设置的版本库,用C指令指定能够创建此版本库的管理员(拥有创建版本库的授权)。例如:
repo ossxp/[a-z].+
C = jiangxin
RW = dev1 dev2
用户jinagxin可以创建路径符合正则表达式ossxp/[a-z].+的版本库,用户dev1和dev2对版本库具有读写(但是没有强制更新)权限。
本地建库。
$ mkdir somerepo
$ cd somerepo
$ git init
$ git commit --allow-empty
使用git remote指令设置远程版本库。
jiangxin$ git remote add origin git@server:ossxp/somerepo.git
运行git push完成在服务器端版本库的创建。
jiangxin$ git push origin master
使用该方法创建版本库后,创建者jiangxin的用户ID将被记录在版本库目录下的gl-creater文件中。该帐号具有对该版本库最高的权限。该通配符版本库的授权指令中如果出现关键字CREATOR将会用创建者的用户ID替换。
实际上Gitolite的原始实现是通过克隆即可创建版本库。即当克隆一个不存在的、名称匹配通配符版本库的、且拥有创建权限(C),Gitolite会自动在服务器端创建该版本库。但是我认为这不是一个好的实践,会经常因为在克隆时把URL写错,从而导致在服务器端创建垃圾版本库。因此我重新改造了Gitolite通配符版本库创建的实现方法,使用推送操作实现版本库的创建,而克隆一个不存在的版本库会报错、退出。
在Gitolite搭建时,已经存在并使用的版本库需要导入到Gitolite中。如果只是简单地把这些裸版本库(以.git为后缀不带工作区的版本库)复制到Gitolite的版本库根目录下,针对这些版本库的授权可能不能正常工作。这是因为Gitolite管理的版本库都配置了特定的钩子脚本,以实现基于分支和/或路径的授权,直接拷贝到Gitolite中的版本库没有正确地设置钩子脚本。而且Gitolite还利用版本库中的gl-creater记录版本库创建者,用gl-perms记录版本库的自定义授权,而这些也是拷贝过来的版本库不具备的。
对于少量的版本库,直接修修改gitolite-admin的授权文件、添加同名的版本库授权、提交并推送,就会在Gitolite服务器端完成同名版本库的初始化。然后在客户端进入到相应版本库的工作区,执行git push命令将原有版本库的各个分支和里程碑导入到Gitolite新建的版本库中。
$ git remote add origin git@server:<repo-name>.git
$ git push --all origin
$ git push --tags origin
如果要导入的版本库较多,逐一在客户端执行git push操作很繁琐。可以采用下面的方法。
如果版本库非常多,就连在gitolite-admin的授权文件中添加版本库授权也是难事,还可以采用下面的办法:
Gitolite托管在GitHub上,任何人都可以基于原作者Sitaramc的工作进行定制。我对Gitolite的定制版本在http://github.com/ossxp-com/gitolite, 包含的扩展和改进有:
通配符版本库的创建方式和授权。
原来的实现是克隆即创建(克隆者需要被授予C的权限)。同时还要通过另外的授权语句为用户设置RW权限,否则创建者没有读和写权限。
新的实现是通过推送创建版本库(推送者需要被授予C权限)。不必再为创建者赋予RW等权限,创建者自动具有对版本库最高的授权。
避免通配符版本库的误判。
若将通配符版本库误判为普通版本库名称,会导致在服务器端创建错误的版本库。新的设计可以在通配符版本库的正则表达式之前添加^或之后添加$字符避免误判。
改变默认配置。
默认安装即支持通配符版本库。
版本库重定向。
Gitosis的一个很重要的功能——版本库名称重定向,没有在Gitolite中实现。我为Gitolite增加了这个功能。
在Git服务器架设的初期,版本库的命名可能非常随意,例如redmine的版本库直接放在根下:redmine-0.9.x.git、redmine-1.0.x.git,...随着redmine项目越来越复杂,可能就需要将其放在子目录下进行管理,例如放到ossxp/redmine/目录下。只需要在Gitolite的授权文件中添加下面一行map语句,就可以实现版本库名称的重定向。使用旧地址的用户不必重新检出,可以继续使用。
map (redmine.*) = ossxp/redmine/$1
Git版本库控制系统往往并不需要设计特别的容灾备份,因为每一个Git用户就是一个备份。但是下面的情况,就很有必要考虑容灾了。
可以在两台或多台安装了Gitolite服务的服务器之间实现版本库的镜像。数据镜像的最小单位为版本库,对于任意一个Git版本库可以选择在其中一个服务器上建立主版本库(只能有一个主版本库),在其他服务器上建立的为镜像库。镜像库只接受来自主版本库的数据同步而不接受来自用户的推送。
首先要为每一台服务器架设Gitolite服务,并建议所有的服务器上Gitolite服务都架设在同一用户(如git)之下。如果Gitolite服务安装到不同的用户账号下,就必需通过文件~/.ssh/config建立SSH别名,以便能够使用正确的用户名连接服务器。
接下来为每个服务器设置一个名称,服务器之间数据镜像时就使用各自的名称进行连接。假设我们要配置的两个Gitolite服务器的其中一个名为server1,另一个名为server2。
打开server1上Gitolite的配置文件~/.gitolite.rc,进行如下设置:
$GL_HOSTNAME = 'serer1';
$GL_GITCONFIG_KEYS = "gitolite.mirror.*";
设置$GL_HOSTNAME为本服务器的别名,如serer1。
设量$GL_GITCONFIG_KEYS以便允许在Gitolite授权文件中为版本库动态设置配置变量。
例如本例设置了GL_GITCONFIG_KEYS为gitolite.mirror.*后,允许在gitolite-admin管理库的conf/gitolite.conf中用config指令对版本库添加配置变量。
repo testing
config gitolite.mirror.master = "server1"
config gitolite.mirror.slaves = "server2 server3"
同样对server2进行设置,只不过将$GL_HOSTNAME设置为serer2。
接下来每一个服务器为Gitolite的安装用户创建公钥/私钥对。
$ sudo su - git
$ ssh-keygen
然后把公钥拷贝到其他服务器上,并以本服务器名称命名。例如:
再运行gl-tool设置其他服务器到本服务器上的公钥认证。例如在server1上执行命令:
$ gl-tool add-mirroring-peer server2.pub
当完成上述设置后,就可以从一个服务器发起到另外服务器的SSH连接,连接过程无需口令认证并显示相关信息。例如从server1发起到server2的连接如下:
$ ssh git@server2 info
Hello server1, I am server2
做了前面的准备工作后,就可以开始启用版本库镜像了。下面通过一个示例介绍如何建立版本库镜像,将服务器server1上的版本库testing要镜像到服务器server2上。
首先要修改server1和server2的Gitolite管理库gitolite-admin,为testing版本库添加配置变量,如下:
repo testing
config gitolite.mirror.master = "server1"
config gitolite.mirror.slaves = "server2"
两个服务器server1和server2都要做出同样的修改,提交改动并推送到服务器上。当推送完成,两个服务器上的testing版本库的config就会被更新,包含类似如下的设置:
[gitolite "mirror"]
master = server1
slaves = server2
当向服务器server1的testing版本库推送新的提交时,就会自动同步到server2上。
$ git push git@server1:testing.git master
[master c0b097a] test
Counting objects: 1, done.
Writing objects: 100% (1/1), 185 bytes, done.
Total 1 (delta 0), reused 0 (delta 0)
remote: (29781&) server1 ==== (testing) ===> server2
To git@server1:testing.git
d222699..c0b097a master -> master
如果需要将服务器server1上所有版本库,包括gitolite-admin版本库都同步到server2上,不必对版本库逐一设置,可以采用下面的简便方法。
修改server1和server2的Gitolite管理版本库gitolite-admin,在配置文件conf/gitolite.conf最开始插入如下设置。
repo @all
config gitolite.mirror.master = "server1"
config gitolite.mirror.slaves = "server2"
然后分别提交并推送。要说明的是gitolite-admin版本库此时尚未建立同步,直到服务器server1的gitolite-admin版本库推送新的提交,才开始gitolite-admin版本库的同步。
也可以在server1服务器端执行命令开始同步。例如:
$ gl-mirror-shell request-push gitolite-admin
Gitolite官方版本在版本库同步时有个局限,要求在镜像服务器上必需事先存在目标版本库并正确设置了gitolite.mirror.*参数,才能同步成功。例如允许用户自行创建的通配符版本库,必需在主服务器上和镜像服务器上分别创建,之后版本库同步才能正常执行。我在GitHub上的Gitolite分支项目提交了一个补丁解决了这个问题。
关于Gitolite版本库镜像的更详悉资料,参见http://sitaramc.github.com/git olite/doc/mirroring.html。
Gitolite和git-daemon的整合很简单,就是由Gitolite创建的版本库会在版本库目录中创建一个空文件git-daemon-export-ok。
Gitolite和Gitweb的整合则提供了两个方面的内容。一个是可以设置版本库的描述信息,用于在Gitweb的项目列表页面中显示。另外一个是自动生成项目的列表文件供Gitweb参考,避免Gitweb使用低效率的目录递归搜索查找Git版本库列表。
可以在授权文件中设定版本库的描述信息,并在gitolite-admin管理库更新时创建到版本库的description文件中。
reponame = "one line of description"
reponame "owner name" = "one line of description"
对于通配符版本库,使用这种方法则很不现实。Gitolite提供了SSH子命令供版本库的创建者使用。
$ ssh git@server setdesc path/to/repos.git
$ ssh git@server getdesc path/to/repos.git
至于生成Gitweb所用的项目列表文件,默认创建在用户主目录下的projects.list文件中。对于所有启用Gitweb的[repo]小节所设定的版本库,以及通过版本库描述隐式声明的版本库都会加入到版本库列表中。
Gitolite源码的doc目录包含用markdown标记语言编写的手册,可以直接在Github上查看。也可以使用markdown的文档编辑工具将.mkd文档转换为 html 文档。转换工具很多,有rdiscount、Bluefeather、Maruku、BlueCloth2,等等。
在这些参考文档中,用户可以发现Gitolite包含的更多的小功能或秘籍,包括:
版本库设置。
授权文件通过git config指令为版本库进行附加的设置。例如:
repo gitolite
config hooks.mailinglist = gitolite-commits@example.tld
config hooks.emailprefix = "[gitolite] "
config foo.bar = ""
config foo.baz =
多级管理员授权。
可以为不同的版本库设定管理员,操作gitolite-admin库的部分授权文件。具体参考:doc/5-delegation.mkd。
自定义钩子脚本。
因为Gitolite占用了几个钩子脚本,如果需要对同名钩子进行扩展,Gitolite提供了级联的钩子脚本,将定制放在级联的钩子脚本里。
例如:通过自定义gitolite-admin的post-update.secondary脚本,以实现无须登录服务器即可更改.gitolite.rc文件。具体参考:doc/shell-games.mkd。
关于钩子脚本的创建和维护,具体参考:doc/hook-propagation.mkd。
管理员自定义命令。
通过设置配置文件中的$GL_ADC_PATH变量,在远程执行该目录下的可执行脚本,如:rmrepo。
具体参考:doc/admin-defined-commands.mkd。
创建匿名的SSH认证。
允许匿名用户访问Gitolite提供的Git服务。即建立一个和Gitolite服务器端帐号同ID同主目录的用户,设置其的特定shell,并且允许口令为空。
具体参考:doc/mob-branches.mkd。
可以通过名为@all的版本库进行全局的授权。
但是不能在@all版本库中对@all用户组进行授权。
版本库或用户非常之多(几千个)的时候,需要使用大配置文件模式。
因为Gitolite的授权文件要先编译才能生效,而编译文件的大小是和用户及版本库数量的乘积成正比的。选择大配置文件模式则不对用户组和版本库组进行扩展。
具体参考:doc/big-config.mkd。
授权文件支持包含语句,可以将授权文件分成多个独立的单元。
执行外部命令,如rsync。
Subversion版本库支持。
如果在同一个服务器上以svn+ssh方式运行Subversion服务器,可以使用同一套公钥,同时为用户提供Git和Subversion服务。
HTTP口令文件维护。通过名为htpasswd的SSH子命令实现。
[1] | 对Gitolite的各项改动采用了Topgit特性分支进行维护,以便和上游最新代码同步更新。还要注意如果在Gitolite使用中发现问题,要区分是由上游软件引发的还是我的改动引起的,而不要把我的错误算在Sitaram头上。 |
[2] | 公钥的内容为一整行,因排版需要做了换行处理。 |
[3] | 可以为版本库设置配置变量gitolite-options.deny-repo在第一个授权阶段启用禁用规则检查。 |
[4] | 参见第8部分41.2.2“Git模板”相关内容。 |