对于几个人的小团队,可以自行在公司内网搭建git服务器,实现版本控制。配合gitolite实现权限控制。
我搭建的服务器框架大致如下图:
那么以下的搭建操作就是基于这个图进行配置。每个节点都是ubuntu 20.04发行版,图示有4个节点
节点 | 功能 |
---|---|
Server | 中心化的git仓库,本文假设IP为192.168.100.100 |
Alice | 网管,负责创建仓库或者各种访问权限 |
Bob | 项目组长,负责Code-Review和Merge分支到master,拥有修改仓库权限 |
Carl | 项目开发者,只能在自己分支上面修改 |
搭建服务器
搭建服务器操作在Server和Alice节点进行
Alice端
生成一个rsa密钥对
cd /tmp
ssh-keygen -t rsa -b 4096 -C "alice"
假设生成的公私钥为
# 私钥
~/.ssh/alice
# 公钥
~/.ssh/alice.pub
将私钥写入当前用户ssh配置文件中
vi ~/.ssh/config
# 添加
Host server
User git
Hostname 192.168.100.100
Port 22
ServerAliveInterval 30
IdentityFile ~/.ssh/alice
将公钥上传到Server备用
scp ~/.ssh/alice.pub root@server:/tmp/
Server端
留意gitolite的README,提到依赖的软件,有最低版本的要求
- git 1.6.6 or later
- perl 5.8.8 or later
- openssh 5.0 or later
创建git账户并切换到新帐户
adduser git
su git
cd ~
创建空的ssh配置目录
mkdir -p ~/.ssh
克隆gitolite仓库
git clone https://github.com/sitaramc/gitolite
cd gitolite
创建一个目录存放gitolite二进制文件,然后安装
mkdir -p ~/bin
./install -to ~/bin
设置Alice的公钥,这样Alice就成为了gitolite管理员
~/bin/gitolite setup -pk /tmp/alice.pub
执行上面一条命令后,/tmp/alice.pub 被拷贝到~/.gitolite/keydir目录下,并且仓库gitolite-admin克隆到本地后,keydir目录也有一份alice.pub。
所有仓库存放在~/repositories
下,gitolite会自动修改~/.ssh/authorized_keys
实现不同用户的访问。
因此单独使用一个git用户的目的是,不希望用户手动修改authorized_keys里面的内容,而是通过gitolite来间接修改它。
仓库创建与权限
修改访问权限在Alice节点进行
克隆admin仓库,因为服务器只有Alice的公钥,其它用户无权访问。
cd ~
git clone git@server:gitolite-admin
cd gitolite-admin
直接编辑这个conf文件实现权限管理
conf/gitolite.conf
详细的权限和仓库创建可以参考官方README:
http://gitolite.com/gitolite/conf
https://github.com/sitaramc/gitolite#adding-users-and-repos
比如我修改为
repo foo
RW+ = bob
- master = carl
- refs/tags/v[0-9] = carl
RW+ carl = carl
R = carl
那么达到的效果是:
- 创建了一个名字为foo的仓库
- RW+表示可读可写可overwrite,Bob拥有仓库最大权限
- 减号说明Carl没有master分支和tags的读写权限
- Carl只能在自己分支(carl分支)上面进行修改,拥有carl分支的最大权限
- Carl可以读取其它分支,这时候就可以读master分支了
注意等号后面的名字是跟ssh公钥文件名字对应的,如果gitolite-admin/keydir目录下的公钥文件名字是
carl_ssh_key.pub
那么等号后面的内容就不是carl,而是carl_ssh_key
为了与conf/gitolite.conf中的帐户对应,创建ssh公私鈅要保存为正确的文件名。
# 输出id_rsa的时候,保存为~/my_gitolite_keys/bob
ssh-keygen -t rsa -b 4096 -C "bob"
# 输出id_rsa的时候,保存为~/my_gitolite_keys/carl
ssh-keygen -t rsa -b 4096 -C "carl"
把~/my_gitolite_keys/中对应的公私鈅交给Bob和Carl,下面测试步骤,要用到公私鈅
将公钥文件添加到gitolite-admin仓库中
cp ~/my_gitolite_keys/*.pub ~/gitolite-admin/keydir/
修改conf和生成密钥完毕,就可以commit,然后将新配置push给server端
cd ~/gitolite-admin
git add *
git commit -m "add user Bob, Carl; generate keys"
git push
那么server端在push结束后自动执行perl脚本,实现权限管理。
测试
测试git在Bob和Carl节点进行
将由Alice交给Bob和Carl的公私鈅,分别存放到各自节点的.ssh目录下
节点 | 公私鈅存放路径 |
---|---|
Bob | 公钥 ~/.ssh/bob.pub 私钥 ~/.ssh/bob |
Carl | 公钥 ~/.ssh/carl.pub 私钥 ~/.ssh/carl |
Bob节点
将私钥写入当前用户ssh配置文件中
vi ~/.ssh/config
# 添加
Host server
User git
Hostname 192.168.100.100
Port 22
ServerAliveInterval 30
IdentityFile ~/.ssh/bob
本地克隆服务器上的foo仓库,测试修改代码
cd ~
git clone git@server:foo.git
cd foo
echo "hello world" > README.md
git add README.md
git commit -m "print hello world"
git push
这样即验证了Bob有读写服务器master分支的权限
Carl节点
将私钥写入当前用户ssh配置文件中
vi ~/.ssh/config
# 添加
Host server
User git
Hostname 192.168.100.100
Port 22
ServerAliveInterval 30
IdentityFile ~/.ssh/carl
本地克隆服务器上的foo仓库,测试修改代码
cd ~
git clone git@server:foo.git
cd foo
echo "try to modify branch master" > README.md
git add README.md
git commit -m "invalid commit"
git push
这么做push的话会被拒绝,即验证了Carl没有写服务器master分支的权限。
下面测试在carl分支的工作流程
情况1: 如果上游没有carl分支,可以添加一个carl分支并Push到服务器上
git checkout -b carl
echo "create branch carl" > README.md
git add README.md
git commit -m "branch: carl created"
git push --set-upstream origin carl
情况2: 如果上游已经存在carl分支,直接切换到carl分支
git checkout carl
经常性使用git pull以拉取服务器上最新的代码版本。
其它设置
安全项
修改默认的shell为gitolite专用的,而不是默认的bash,这样让用户无法通过sshkey登陆到终端,且在root用户调用su git也无法切换(提示GL_USER not set)
sudo vi /etc/passwd
# 修改git用户的shell程序为/home/git/bin/gitolite-shell
不允许git用户使用密码登陆
sudo vi /etc/ssh/sshd_config
# 增加以下
Match User git
PasswordAuthentication no
# 重启ssh服务
sudo systemctl restart sshd.service
语言项
避免每次git push都产生如下警告
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = "en_US:en",
LC_ALL = (unset),
LC_CTYPE = "zh_CN.UTF-8",
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8")
首先重新生成locale
sudo dpkg-reconfigure locales
然后在sshd_config中禁止客户端传递环境变量给服务器,因为服务器一般才使用一种语言,而不是采用客户端的语言环境。
vi /etc/ssh/sshd_config
# 注释掉这一行
# AcceptEnv LANG LC_*
保存并退出,重启ssh服务
sudo systemctl restart sshd.service
git-hooks
设置pre-receive, post-receive等钩子,实现语法检测或者邮件推送。参考这篇文章进行设置。
在RC文件中取消注释数行:
vi ~/.gitolite.rc
# 取消注释这一行,表示可以从admin仓库中增加local文件,注意不要取消错了,有两行很相似
LOCAL_CODE => "$rc{GL_ADMIN_BASE}/local",
# 取消注释这一行,表示启用单独仓库配置hook
'repo-specific-hooks'
保存,然后在克隆好的gitolite-admin仓库根目录下建立文件夹
cd /path/to/your/gitolite-admin-cloned
mkdir -p local/hooks/repo-specific
cd local/hooks/repo-specific
在此目录创建对应的hook可执行文件即可,比如我使用这个cppcheck检测Qt工程。文件名不要使用git标准的pre-receive, post-receive, post-update等
#!/bin/bash
# works only in gitolite 3.6+
# hook as pre-receive
REQUIRE_BINS=("cppcheck" "git")
for b in "${REQUIRE_BINS[@]}"; do
type $b >/dev/null 2>&1 || { echo >&2 "$b is required, but was not found. exited"; exit 1; }
done
# https://github.com/danmar/cppcheck/blob/main/cfg/qt.cfg
QT_CFG=qt.cfg
if [ -z $GL_ADMIN_BASE ];then
QT_CFG=$PWD/$QT_CFG
else
QT_CFG=$GL_ADMIN_BASE/local/cppcheck-libs/$QT_CFG
fi
[[ -f $QT_CFG || -L $QT_CFG ]] || { echo >&2 "Cannot find $QT_CFG. exited"; exit 1; }
NULL_SHA1="0000000000000000000000000000000000000000" # 40 0's
TMP_DIR=$( mktemp -d /tmp/cppcheck-XXXXX )
EXIT_CODE=0
while read oldrev newrev ref; do
if [ "$oldrev" = "$NULL_SHA1" ]; then
oldrev="$newrev^"
fi
echo -n "new commit "
git log --pretty=oneline --abbrev-commit -n 1 $newrev
echo -n "old commit "
git log --pretty=oneline --abbrev-commit -n 1 $oldrev
for filename in $( git diff --name-only $oldrev $newrev | grep -E '*\.(c|cpp|cc|cxx|h|hpp)$' ); do
short_file_name=$( basename $filename )
git show $newrev:$filename > $TMP_DIR/$short_file_name
echo "checking $filename ..."
cppcheck --error-exitcode=1 --quiet --library=$QT_CFG $TMP_DIR/$short_file_name
[ "x$?" = "x0" ] || EXIT_CODE=1
done
done
[ -d "$TMP_DIR" ] && rm -rf $TMP_DIR
exit $EXIT_CODE
保存以上内容为my-hook,并设置为可执行权限
echo 'echo good; exit 0' > my-hook
chmod +x my-hook
下载qt.cfg作为cppcheck的语法文件
mkdir -p /path/to/your/gitolite-admin-cloned/local/cppcheck-libs
wget https://github.com/danmar/cppcheck/blob/main/cfg/qt.cfg
mv qt.cfg /path/to/your/gitolite-admin-cloned/local/cppcheck-libs/
修改conf文件,指定hook
vi /path/to/your/gitolite-admin-cloned/conf/gitolite.conf
# 设置hook
repo foo
option hook.pre-receive = my-hook
提交并push到远端gitolite-admin,下次推送foo仓库即可生效。