服务器上的 Git
-
协议
Git 可以使用四种不同的协议来传输资料:本地协议(Local),HTTP 协议,SSH(Secure Shell)协议及 Git协议。
-
本地协议(Local protocol)是最基本的,其中的远程版本库就是同一主机上的另一个目录。
-
增加一个本地版本库到现有的 Git 项目,就可以通过新的远程仓库名 local_proj 像在网络上一样从远端版本库推送和拉取更新了。可以执行如下的命令:
$ git remote add local_proj /srv/git/project.git
-
克隆一个本地版本库,可以执行如下的命令:
$ git clone /srv/git/project.git
-
或者可以执行这个命令:
$ git clone file:///srv/git/project.git
-
如果在 URL 开头明确的指定 file://,那么 Git 的行为会略有不同。 如果仅是指定路径,Git 会尝试使用硬链接(hard link)或直接复制所需要的文件。 如果指定 file://,Git 会触发平时用于网路传输资料的进程,那样传输效率会更低。 指定 file:// 的主要目的是取得一个没有外部参考(extraneous references) 或对象(object)的干净版本库副本——通常是在从其他版本控制系统导入后或一些类似情况需要这么做。
-
优点
基于文件系统的版本库的优点是简单,并且直接使用了现有的文件权限和网络访问权限。只需要像设置其他共享目录一样,把一个裸版本库的副本放到大家都可以访问的路径,并设置好读/写的权限,就可以了。
-
缺点
缺点是通常共享文件系统比较难配置,并且比起基本的网络连接访问,这不方便从多个位置访问。如果你想从家里推送内容,必须先挂载一个远程磁盘,相比网络连接的访问方式,配置不方便,速度也慢。在同一个服务器上,如果允许 Git 访问本地硬盘,一般的通过 NFS 访问版本库要比通过 SSH 访问慢。每一个用户都有“远程”目录的完整shell权限,没有方法可以阻止他们修改或删除 Git 内部文件和损坏仓库。这个协议并不保护仓库避免意外的损坏。
-
-
HTTP 协议
Git 通过 HTTP 通信有两种模式。 在 Git 1.6.6 版本之前只有一个方式可用,十分简单并且通常是只读模式的。Git 1.6.6 版本引入了一种新的、更智能的协议,让 Git 可以像通过 SSH 那样智能的协商和传输数据。 之后几年,这个新的 HTTP 协议因为其简单、智能变的十分流行。 新版本的 HTTP 协议一般被称为 智能 HTTP 协议,旧版本的一般被称为 哑 HTTP 协议。
-
智能 HTTP 协议
智能 HTTP 的运行方式和 SSH 及 Git 协议类似,只是运行在标准的 HTTP/S 端口上并且可以使用各种 HTTP 验证机制, 这意味着使用起来会比 SSH 协议简单的多,比如可以使用 HTTP 协议的用户名/密码授权,免去设置SSH 公钥。智能 HTTP 协议或许已经是最流行的使用 Git 的方式了,它即支持像 git:// 协议一样设置匿名服务, 也可以像SSH 协议一样提供传输时的授权和加密。 而且只用一个 URL 就可以都做到,省去了为不同的需求设置不同的URL。 如果你要推送到一个需要授权的服务器上(一般来讲都需要),服务器会提示你输入用户名和密码。 从服务器获取数据时也一样。
-
哑(Dumb) HTTP 协议
如果服务器没有提供智能 HTTP 协议的服务,Git 客户端会尝试使用更简单的“哑” HTTP 协议。 哑 HTTP 协议里 web 服务器仅把裸版本库当作普通文件来对待,提供文件服务。 哑 HTTP 协议的优美之处在于设置起来简单。 基本上,只需要把一个裸版本库放在 HTTP 根目录,设置一个叫做 post-update 的挂钩就可以了。 此时,只要能访问 web 服务器上你的版本库,就可以克隆你的版本库。 下面是设置从 HTTP 访问版本库的方法:
$ cd /var/www/htdocs/ $ git clone --bare /path/to/git_project gitproject.git $ cd gitproject.git $ mv hooks/post-update.sample hooks/post-update $ chmod a+x hooks/post-update
-
Git 自带的 post-update 挂钩会默认执行合适的命令(git update-server-info),来确保通过 HTTP 的获取和克隆操作正常工作。只需要把裸版本库放到正确的目录下就可以,Git 的数据是以基本的静态文件形式提供的。这条命令会在你通过 SSH 向版本库推送之后被执行;然后别人就可以通过类似下面的命令来克隆:
$ git clone https://example.com/gitproject.git
-
优点
只需要一个 URL 以及服务器只在需要授权时提示输入授权信息,这两个简便性让终端用户使用Git 变得非常简单。也可以在 HTTPS 协议上提供只读版本库的服务,在传输数据的时候就可以加密数据;或者,让客户端使用指定的 SSL 证书。HTTPS 协议被广泛使用,一般的企业防火墙都会允许这些端口的数据通过。
-
缺点
在一些服务器上,架设 HTTPS 协议的服务端会比 SSH 协议的棘手一些。
在 HTTP 上使用需授权的推送,管理凭证会比使用 SSH 密钥认证麻烦一些。
-
-
SSH 协议
架设 Git 服务器时常用 SSH 协议作为传输协议。 因为大多数环境下服务器已经支持通过 SSH 访问 —— 即使没有也很容易架设。 SSH 协议也是一个验证授权的网络协议;并且,因为其普遍性,架设和使用都很容易。
通过 SSH 协议克隆版本库,你可以指定一个 ssh:// 的 URL:
$ git clone ssh://[user@]server/project.git
或者使用一个简短的 scp 式的写法:
$ git clone [user@]server:project.git
在上面两种情况中,如果你不指定可选的用户名,那么 Git 会使用当前登录的用的名字。
-
优势
首先,SSH 架设相对简单 —— SSH 守护进程很常见,多数管理员都有使用经验,并且多数操作系统都包含了它及相关的管理工具。
其次,通过 SSH 访问是安全的 —— 所有传输数据都要经过授权和加密。 最后,与 HTTPS 协议、Git 协议及本地协议一样,SSH 协议很高效,在传输前也会尽量压缩数据。
-
缺点
SSH 协议的缺点在于它不支持匿名访问 Git 仓库。 如果你使用 SSH,那么即便只是读取数据,使用者也 必须 通过 SSH 访问你的主机, 这使得 SSH 协议不利于开源的项目,毕竟人们可能只想把你的仓库克隆下来查看。 如果你只在公司网络使用,SSH 协议可能是你唯一要用到的协议。 如果你要同时提供匿名只读访问和 SSH 协议,那么你除了为自己推送架设 SSH 服务以外, 还得架设一个可以让其他人访问的服务。
-
-
Git 协议
这是包含在 Git 里的一个特殊的守护进程;它监听在一个特定的端口(9418),类似于SSH服务,但是访问无需任何授权。
要让版本库支持 Git 协议,需要先创建一个 git-daemon-export-ok 文件—— 它是 Git 协议守护进程为这个版本库提供服务的必要条件 —— 但是除此之外没有任何安全措施。要么谁都可以克隆这个版本库,要么谁也不能。这意味着,通常不能通过 Git 协议推送。由于没有授权机制,一旦你开放推送操作,意味着网络上知道这个项目 URL 的人都可以向项目推送数据。
-
优点
目前,Git 协议是 Git 使用的网络传输协议里最快的。如果你的项目有很大的访问量,或者你的项目很庞大并且不需要为写进行用户授权,架设 Git 守护进程来提供服务是不错的选择。 它使用与 SSH 相同的数据传输机制,但是省去了加密和授权的开销。
-
缺点
Git 协议缺点是缺乏授权机制。 把 Git 协议作为访问项目版本库的唯一手段是不可取的。 一般的做法里,会同时提供 SSH 或者 HTTPS 协议的访问服务,只让少数几个开发者有推送(写)权限,其他人通过 git:// 访问只有读权限。 Git 协议也许也是最难架设的。 它要求有自己的守护进程,这就要配置 xinetd、systemd 或者其他的程序,这些工作并不简单。 它还要求防火墙开放 9418 端口,但是企业防火墙一般不会开放这个非标准端口。而大型的企业防火墙通常会封锁这个端口。
-
在服务器上搭建 Git
-
在开始架设 Git 服务器前,需要把现有仓库导出为裸仓库——即一个不包含当前工作目录的仓库。
通过克隆现有的仓库来创建一个新的裸仓库,需要在克隆命令后加上 --bare 选项。 按照惯例,裸仓库的目录名以 .git 结尾,就像这样:
$ git clone --bare my_project my_project.git
整体上效果大致相当于:
$ cp -Rf my_project/.git my_project.git
虽然在配置文件中有若干不同,但是对于你的目的来说,这两种方式都是一样的。 它只取出 Git 仓库自身,不要工作目录,然后特别为它单独创建一个目录。
-
把裸仓库放到服务器上
假设一个域名为
git.example.com
的服务器已经架设好,并可以通过 SSH 连接, 你想把所有的 Git 仓库放在 /srv/git 目录下。 假设服务器上存在 /srv/git/ 目录,你可以通过以下命令复制你的裸仓库来创建一个新仓库:$ scp -r my_project.git user@git.example.com:/srv/git
其他可通过 SSH 读取此服务器上 /srv/git 目录的用户,可运行以下命令来克隆你的仓库。
$ git clone user@git.example.com:/srv/git/my_project.git
如果一个用户,通过使用 SSH 连接到一个服务器,并且其对 /srv/git/my_project.git 目录拥有可写权限,那么他将自动拥有推送权限。
如果到该项目目录中运行 git init 命令,并加上 --shared 选项, 那么 Git 会自动修改该仓库目录的组权限为可写。 注意,运行此命令的工程中不会摧毁任何提交、引用等内容。
$ ssh user@git.example.com $ cd /srv/git/my_project.git $ git init --bare --shared
根据现有的 Git 仓库创建一个裸仓库,然后把它放上你和协作者都有 SSH 访问权的服务器就可以在同一项目上展开合作了。值得注意的是,这的确是架设一个几个人拥有连接权的 Git 服务的全部—— 只要在服务器上加入可以用 SSH 登录的帐号,然后把裸仓库放在大家都有读写权限的地方。
生成 SSH 公钥
首先,你需要确认自己是否已经拥有密钥。 默认情况下,用户的 SSH 密钥存储在其 ~/.ssh 目录下。 进入该目录并列出其中内容,确认自己是否已拥有密钥:
$ cd ~/.ssh
$ ls
寻找一对以 id_dsa 或 id_rsa 命名的文件,其中一个带有 .pub 扩展名。 .pub 文件是你的公钥,另一个则是与之对应的私钥。 如果找不到这样的文件(或者根本没有 .ssh 目录),你可以通过运行 ssh-keygen 程序来创建它们。 在 Linux/macOS 系统中,ssh-keygen 随 SSH 软件包提供;在 Windows 上,该程序包含于 MSysGit 软件包中。
$ ssh-keygen -o
首先 ssh-keygen 会确认密钥的存储位置(默认是 .ssh/id_rsa),然后它会要求你输入两次密钥口令。 如果你不想在使用密钥时输入口令,将其留空即可。 然而,如果你使用了密码,那么请确保添加了 -o 选项,它会以比默认格式更能抗暴力破解的格式保存私钥。 你也可以用 ssh-agent 工具来避免每次都要输入密码。
$ cat ~/.ssh/id_rsa.pub
配置服务器
使用 authorized_keys 方法来对用户进行认证。
-
首先,创建一个操作系统用户 git,并为其建立一个 .ssh 目录。
$ sudo adduser git $ su git $ cd $ mkdir .ssh && chmod 700 .ssh $ touch .ssh/authorized_keys && chmod 600 .ssh/authorized_keys
以下操作可通过 ssh-copy-id 命令自动完成,这样就不必手动复制并安装公钥了。
ssh-copy-id 用来将本地公钥复制到远程主机。如果不传入 -i 参数,ssh-copy-id 使用默认 ~/.ssh/identity.pub 作为默认公钥。如果多次运行 ssh-copy-id ,该命令不会检查重复,会在远程主机中多次写入 authorized_keys.ssh-copy-id仅仅支持SSH运行在22端口的情况,服务器的SSH端口改成10022端口时运行ssh-copy-id就会报错了。
# vi ~/.ssh/config
加上内容:
Host server
Hostname ip
Port 10022
也可以单独只加入Port一行配置,那样就是一个全局配置,保存后再运行ssh-copy-id命令就不会报错了。
不修改config文件,按如下方式也可以:
ssh-copy-id -i ~/.ssh/id_rsa.pub “-p 10022 user@server” -
添加SSH公钥。
为系统用户 git 的 authorized_keys 文件添加一些开发者 SSH 公钥。 假设我们已经获得了
若干受信任的公钥,并将它们保存在临时文件中。$ cat /tmp/id_rsa.john.pub
将这些公钥加入系统用户 git 的 .ssh 目录下 authorized_keys 文件的末尾:
$ cat /tmp/id_rsa.john.pub >> ~/.ssh/authorized_keys $ cat /tmp/id_rsa.josie.pub >> ~/.ssh/authorized_keys $ cat /tmp/id_rsa.jessica.pub >> ~/.ssh/authorized_keys
为开发者新建一个空仓库。可以借助带 --bare 选项的 git init 命令来做到这一点,该命令在初始化仓库时不会创建工作目录:
$ cd /srv/git $ mkdir project.git $ cd project.git $ git init --bare
-
推送项目到仓库
假定 myproject 是已有项目且其中已包含文件:
# on John's computer $ cd myproject $ git init $ git add . $ git commit -m 'initial commit' $ git remote add origin git@gitserver:/srv/git/project.git $ git push origin master
此时,其他开发者可以克隆此仓库,并推回各自的改动:
$ git clone git@gitserver:/srv/git/project.git $ cd project $ vim README $ git commit -am 'fix for the README file' $ git push origin master
-
限制用户 git 的活动在与 Git 相关的范围内。
目前所有(获得授权的)开发者用户都能以系统用户 git 的身份登录服务器从而获得一个普通shell。 如果你想对此加以限制,则需要修改 /etc/passwd 文件中(git 用户所对应)的 shell 值。
借助一个名为 git-shell 的受限 shell 工具,该工具随 Git 软件包一同提供。如果将 git-shell 设置为用户 git 的登录 shell(login shell), 那么该用户便不能获得此服务器的普通 shell 访问权限。 若要使用 git-shell,需要用它替换掉 bash 或 csh,使其成为该用户的登录 shell。 为进行上述操作,首先你必须确保 git-shell 的完整路径名已存在于 /etc/shells文件中:
$ cat /etc/shells # see if git-shell is already in there. If not... $ which git-shell # make sure git-shell is installed on your system. $ sudo -e /etc/shells # and add the path to git-shell from last command
使用 chsh -s 命令修改任一系统用户的 shell:
$ sudo chsh git -s $(which git-shell)
这样,用户 git 就只能利用 SSH 连接对 Git 仓库进行推送和拉取操作,而不能登录机器并取得普通 shell。 如果试图登录,你会发现尝试被拒绝,像这样:
$ ssh git@gitserver
此时,用户仍可通过 SSH 端口转发来访问任何可达的 git 服务器。 如果你想要避免它,可编辑authorized_keys 文件并在所有想要限制的公钥之前添加以下选项:
no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty
结果查看:
$ cat ~/.ssh/authorized_keys
现在,网络相关的 Git 命令依然能够正常工作,但是开发者用户已经无法得到一个普通 shell 了。 正如输出信息所提示的,你也可以在 git 用户的主目录下建立一个目录,来对 git-shell 命令进行一定程度的自定义。 比如,你可以限制掉某些本应被服务器接受的 Git 命令,或者对刚才的 SSH 拒绝登录信息进行自定义,这样,当有开发者用户以类似方式尝试登录时,便会看到你的信息。 要了解更多有关自定义 shell 的信息,请运行
git help shell
。