前面的話
如果想與他人使用,除了使用Git來完成日常工作之外,還需要一個遠程的Git倉庫。盡管從技術上可以從個人的倉庫里推送和拉取修改內容,但並不鼓勵這樣做,因為一不留心就很容易弄混其他人的進度。因此,更好的合作方式是建立一個大家都可以訪問的共享倉庫,從那里推送和拉取數據。我們將這個倉庫稱為"Git服務器";代理一個Git倉庫只需要花費很少的資源,幾乎從不需要整個服務器來支持它的運行
遠程倉庫通常只是一個裸倉庫(bare repository)——即一個沒有當前工作目錄的倉庫。因為該倉庫只是一個合作媒介,所以不需要從硬盤上取出最新版本的快照;倉庫里存放的僅僅是Git的數據。簡單地說,裸倉庫就是你工作目錄中.git子目錄內的內容
本文將詳細介紹服務器上的Git
協議
Git可以使用四種主要的協議來傳輸數據:本地傳輸,SSH協議,Git協議和HTTP協議。下面分別介紹一下哪些情形應該使用(或避免使用)這些協議
值得注意的是,除了HTTP協議外,其他所有協議都要求在服務器端安裝並運行Git
【本地協議】
最基本的就是本地協議(Local protocol),所謂的遠程倉庫在該協議中的表示,就是硬盤上的另一個目錄。這常見於團隊每一個成員都對一個共享的文件系統(例如NFS)擁有訪問權,或者比較少見的多人共用同一台電腦的情況。后面一種情況並不安全,因為所有代碼倉庫實例都儲存在同一台電腦里,增加了災難性數據損失的可能性
$ git init ~/git-server --bare 將當前的倉庫初始化為一個裸倉庫,裸倉庫的意思是沒有工作目錄。中央服務器並不需要工作目錄,它是一個被動的接收作用,如果有工作目錄的話,反而會造成錯亂
如果你使用一個共享的文件系統,就可以在一個本地文件系統中克隆倉庫,推送和獲取。克隆的時候只需要將遠程倉庫的路徑作為URL使用
$ git clone /opt/git/project.git
或者這樣:
$ git clone file:///opt/git/project.git
如果在URL開頭明確使用file://,那么Git會以一種略微不同的方式運行。如果你只給出路徑,Git會嘗試使用硬鏈接或直接復制它所需要的文件。如果使用了file://,Git會調用它平時通過網絡來傳輸數據的工序,而這種方式的效率相對較低。使用file://前綴的主要原因是當你需要一個不包含無關引用或對象的干凈倉庫副本的時候——一般指從其他版本控制系統導入的,或類似情形。我們這里僅僅使用普通路徑,這樣更快
要添加一個本地倉庫作為現有Git項目的遠程倉庫,可以這樣做
$ git remote add local_proj /opt/git/project.git
然后就可以像在網絡上一樣向這個遠程倉庫推送和獲取數據了
基於文件倉庫的優點在於它的簡單,同時保留了現存文件的權限和網絡訪問權限。如果你的團隊已經有一個全體共享的文件系統,建立倉庫就十分容易了。你只需把一份裸倉庫的副本放在大家都能訪問的地方,然后像對其他共享目錄一樣設置讀寫權限就可以了
這也是從別人工作目錄中獲取工作成果的快捷方法。假如你和你的同事在一個項目中合作,他們想讓你檢出一些東西的時候,運行類似git pull /home/john/project通常會比他們推送到服務器,而你再從服務器獲取簡單得多
這種方法的缺點是,與基本的網絡連接訪問相比,難以控制從不同位置來的訪問權限。如果你想從家里的筆記本電腦上推送,就要先掛載遠程硬盤,這和基於網絡連接的訪問相比更加困難和緩慢
另一個很重要的問題是該方法不一定就是最快的,尤其是對於共享掛載的文件系統。本地倉庫只有在你對數據訪問速度快的時候才快。在同一個服務器上,如果二者同時允許Git訪問本地硬盤,通過NFS訪問倉庫通常會比SSH慢
【SSH協議】
Git使用的傳輸協議中最常見的可能就是SSH了。這是因為大多數環境已經支持通過SSH對服務器的訪問—即便還沒有,架設起來也很容易。SSH也是唯一一個同時支持讀寫操作的網絡協議。另外兩個網絡協議(HTTP和Git)通常都是只讀的,所以雖然二者對大多數人都可用,但執行寫操作時還是需要SSH。SSH同時也是一個驗證授權的網絡協議;而因為其普遍性,一般架設和使用都很容易
通過SSH克隆一個Git倉庫,可以像下面這樣給出ssh://的URL:
$ git clone ssh://user@server/project.git
或者不指明某個協議——這時Git會默認使用SSH:
$ git clone user@server:project.git
如果不指明用戶,Git會默認使用當前登錄的用戶名連接服務器
使用SSH的好處有很多。首先,如果你想擁有對網絡倉庫的寫權限,基本上不可能不使用SSH。其次,SSH架設相對比較簡單——SSH守護進程很常見,很多網絡管理員都有一些使用經驗,而且很多操作系統都自帶了它或者相關的管理工具。再次,通過SSH進行訪問是安全的——所有數據傳輸都是加密和授權的。最后,和Git及本地協議一樣,SSH也很高效,會在傳輸之前盡可能壓縮數據
SSH的限制在於你不能通過它實現倉庫的匿名訪問。即使僅為讀取數據,人們也必須在能通過SSH訪問主機的前提下才能訪問倉庫,這使得SSH不利於開源的項目。如果你僅僅在公司網絡里使用,SSH可能是你唯一需要使用的協議。如果想允許對項目的匿名只讀訪問,那么除了為自己推送而架設SSH協議之外,還需要支持其他協議以便他人訪問讀取
【Git協議】
接下來是Git協議。這是一個包含在Git軟件包中的特殊守護進程;它會監聽一個提供類似於SSH服務的特定端口(9418),而無需任何授權。打算支持Git協議的倉庫,需要先創建git-daemon-export-ok文件,它是協議進程提供倉庫服務的必要條件,但除此之外該服務沒有什么安全措施。要么所有人都能克隆Git倉庫,要么誰也不能。這也意味着該協議通常不能用來進行推送。你可以允許推送操作;然而由於沒有授權機制,一旦允許該操作,網絡上任何一個知道項目URL的人將都有推送權限。不用說,這是十分罕見的情況
Git協議是現存最快的傳輸協議。如果你在提供一個有很大訪問量的公共項目,或者一個不需要對讀操作進行授權的龐大項目,架設一個Git守護進程來供應倉庫是個不錯的選擇。它使用與SSH協議相同的數據傳輸機制,但省去了加密和授權的開銷
Git協議消極的一面是缺少授權機制。用Git協議作為訪問項目的唯一方法通常是不可取的。一般的做法是,同時提供SSH接口,讓幾個開發者擁有推送(寫)權限,其他人通過git://擁有只讀權限。Git協議可能也是最難架設的協議。它要求有單獨的守護進程,需要定制,需要設定xinetd或類似的程序,而這些工作就沒那么輕松了。該協議還要求防火牆開放9418端口,而企業級防火牆一般不允許對這個非標准端口的訪問。大型企業級防火牆通常會封鎖這個少見的端口
【HTTP/S協議】
最后還有HTTP協議。HTTP或HTTPS協議的優美之處在於架設的簡便性。基本上,只需要把Git的裸倉庫文件放在HTTP的根目錄下,配置一個特定的post-update掛鈎(hook)就可以搞定。此后,每個能訪問Git倉庫所在服務器上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的獲取和克隆正常工作。這條命令在你用SSH向倉庫推送內容時運行;之后,其他人就可以用下面的命令來克隆倉庫:
$ git clone http://example.com/gitproject.git
在本例中,我們使用了Apache設定中常用的/var/www/htdocs路徑,不過你可以使用任何靜態web服務——把裸倉庫放在它的目錄里就行。 Git的數據是以最基本的靜態文件的形式提供的
通過HTTP進行推送操作也是可能的,不過這種做法不太常見,並且牽扯到復雜的WebDAV設定。通過HTTP推送的好處之一是你可以使用任何WebDAV服務器,不需要為Git設定特殊環境;所以如果主機提供商支持通過WebDAV更新網站內容,你也可以使用這項功能
使用HTTP協議的好處是易於架設。幾條必要的命令就可以讓全世界讀取到倉庫的內容。花費不過幾分鍾。HTTP協議不會占用過多服務器資源。因為它一般只用到靜態的HTTP服務提供所有數據,普通的Apache服務器平均每秒能支撐數千個文件的並發訪問——哪怕讓一個小型服務器超載都很難
你也可以通過HTTPS提供只讀的倉庫,這意味着你可以加密傳輸內容;你甚至可以要求客戶端使用特定簽名的SSL證書。一般情況下,如果到了這一步,使用SSH公共密鑰可能是更簡單的方案;不過也存在一些特殊情況,這時通過HTTPS使用帶簽名的SSL證書或者其他基於HTTP的只讀連接授權方式是更好的解決方案
HTTP還有個額外的好處:HTTP是一個如此常見的協議,以至於企業級防火牆通常都允許其端口的通信
HTTP協議的消極面在於,相對來說客戶端效率更低。克隆或者下載倉庫內容可能會花費更多時間,而且HTTP傳輸的體積和網絡開銷比其他任何一個協議都大。因為它沒有按需供應的能力,傳輸過程中沒有服務端的動態計算,因而HTTP協議經常會被稱為傻瓜(dumb)協議
服務器部署
開始架設Git服務器前,需要先把現有倉庫導出為裸倉庫——即一個不包含當前工作目錄的倉庫。做法直截了當,克隆時用--bare選項即可。裸倉庫的目錄名一般以.git結尾,像這樣
$ git clone --bare my_project my_project.git Cloning into bare repository 'my_project.git'... done.
clone操作基本上相當於git init加git fetch,所以這里出現的其實是git init的輸出,先由它建立一個空目錄,而之后傳輸數據對象的操作並無任何輸出,只是悄悄在幕后執行。現在my_project.git目錄中已經有了一份Git目錄數據的副本
整體上的效果大致相當於:
$ cp -Rf my_project/.git my_project.git
但在配置文件中有若干小改動,不過對用戶來講,使用方式都一樣,不會有什么影響。它僅取出Git倉庫的必要原始數據,存放在該目錄中,而不會另外創建工作目錄
【把裸倉庫移到服務器上】
有了裸倉庫的副本后,剩下的就是把它放到服務器上並設定相關協議。假設一個域名為git.example.com的服務器已經架設好,並可以通過 SSH 訪問,打算把所有 Git 倉庫儲存在 /opt/git 目錄下。只要把裸倉庫復制過去
$ scp -r my_project.git user@git.example.com:/opt/git
現在,所有對該服務器有SSH訪問權限,並可讀取/opt/git目錄的用戶都可以用下面的命令克隆該項目
$ git clone user@git.example.com:/opt/git/my_project.git
如果某個SSH用戶對/opt/git/my_project.git目錄有寫權限,那他就有推送權限。如果到該項目目錄中運行git init命令,並加上--shared選項,那么Git會自動修改該倉庫目錄的組權限為可寫
實際上,--shared可以指定其他行為,只是默認為將組權限改為可寫並執行g+sx,所以最后會得到rws
$ ssh user@git.example.com $ cd /opt/git/my_project.git $ git init --bare --shared
值得注意的是,這的確是架設一個少數人具有連接權的Git服務的全部,只要在服務器上加入可以用SSH登錄的帳號,然后把裸倉庫放在大家都有讀寫權限的地方
【小型安裝】
如果設備較少或者只想在小型開發團隊里嘗試Git,那么一切都很簡單。架設Git服務最復雜的地方在於賬戶管理。如果需要倉庫對特定的用戶可讀,而給另一部分用戶讀寫權限,那么訪問和許可的安排就比較困難
SSH連接
如果已經有了一個所有開發成員都可以用SSH訪問的服務器,架設第一個服務器將變得異常簡單,幾乎什么都不用做。如果需要對倉庫進行更復雜的訪問控制,只要使用服務器操作系統的本地文件訪問許可機制就行了
如果需要團隊里的每個人都對倉庫有寫權限,又不能給每個人在服務器上建立賬戶,那么提供SSH連接就是唯一的選擇了。我們假設用來共享倉庫的服務器已經安裝了SSH服務,而且你通過它訪問服務器
有好幾個辦法可以讓團隊的每個人都有訪問權。第一個辦法是給每個人建立一個賬戶,直截了當但略過繁瑣。反復運行adduser並給所有人設定臨時密碼可不是好玩的
第二個辦法是在主機上建立一個git賬戶,讓每個需要寫權限的人發送一個SSH公鑰,然后將其加入git賬戶的~/.ssh/authorized_keys文件。這樣一來,所有人都將通過git賬戶訪問主機。這絲毫不會影響提交的數據,訪問主機用的身份不會影響提交對象的提交者信息
另一個辦法是讓SSH服務器通過某個LDAP服務,或者其他已經設定好的集中授權機制,來進行授權。只要每個人都能獲得主機的shell訪問權,任何可用的SSH授權機制都能達到相同效果
生成SSH公鑰
大多數Git服務器都會選擇使用SSH公鑰來進行授權。系統中的每個用戶都必須提供一個公鑰用於授權,沒有的話就要生成一個。生成公鑰的過程在所有操作系統上都差不多。首先先確認一下是否已經有一個公鑰了。SSH公鑰默認儲存在賬戶的主目錄下的~/.ssh目錄
$ cd ~/.ssh
$ ls
authorized_keys2 id_dsa known_hosts
config id_dsa.pub
關鍵是看有沒有用something和something.pub來命名的一對文件,這個something通常就是id_dsa或id_rsa。有.pub后綴的文件就是公鑰,另一個文件則是密鑰。假如沒有這些文件,或者干脆連.ssh目錄都沒有,可以用ssh-keygen來創建。該程序在Linux/Mac系統上由SSH包提供,而在Windows上則包含在MSysGit包里:
$ ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/Users/schacon/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /Users/schacon/.ssh/id_rsa. Your public key has been saved in /Users/schacon/.ssh/id_rsa.pub. The key fingerprint is: 43:c5:5b:5f:b1:f1:50:43:ad:20:a6:92:6a:1f:9a:3a schacon@agadorlaptop.local
它先要求你確認保存公鑰的位置(.ssh/id_rsa),然后它會讓你重復一個密碼兩次,如果不想在使用公鑰的時候輸入密碼,可以留空
現在,所有做過這一步的用戶都得把它們的公鑰給你或者Git服務器的管理員(假設SSH服務被設定為使用公鑰機制)。他們只需要復制.pub文件的內容然后發郵件給管理員。公鑰的樣子大致如下
$ cat ~/.ssh/id_rsa.pub ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3 Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XA t3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert/En mZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbx NrRFi9wrf+M7Q== schacon@agadorlaptop.local
架設服務器
現在我們過一邊服務器端架設SSH訪問的流程。本例將使用authorized_keys方法來給用戶授權。我們還將假定使用類似Ubuntu這樣的標准Linux發行版。首先,創建一個名為'git'的用戶,並為其創建一個.ssh目錄
$ sudo adduser git
$ su git
$ cd
$ mkdir .ssh
接下來,把開發者的 SSH 公鑰添加到這個用戶的 authorized_keys 文件中。假設你通過電郵收到了幾個公鑰並存到了臨時文件里。重復一下,公鑰大致看起來是這個樣子:
$ cat /tmp/id_rsa.john.pub ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCB007n/ww+ouN4gSLKssMxXnBOvf9LGt4L ojG6rs6hPB09j9R/T17/x4lhJA0F3FR1rP6kYBRsWj2aThGw6HXLm9/5zytK6Ztg3RPKK+4k Yjh6541NYsnEAZuXz0jTTyAUfrtU3Z5E003C4oxOj6H0rfIF1kKI9MAQLMdpGW1GYEIgS9Ez Sdfd8AcCIicTDWbqLAcU4UpkaX8KyGlLwsNuuGztobF8m72ALC/nLF6JLtPofwFBlgc+myiv O7TCUSBdLQlgMVOFq1I2uPWQOkOWQAHukEOmfjy2jctxSDBQ220ymjaNsHT4kgtZg2AYYgPq dAv8JggJICUvax2T9va5 gsg-keypair
只要把它們逐個追加到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 /opt/git
$ mkdir project.git
$ cd project.git
$ git --bare init
這時,Join,Josie或者Jessica就可以把它加為遠程倉庫,推送一個分支,從而把第一個版本的項目文件上傳到倉庫里了。值得注意的是,每次添加一個新項目都需要通過shell登入主機並創建一個裸倉庫目錄。我們不妨以gitserver作為git用戶及項目倉庫所在的主機名。如果在網絡內部運行該主機,並在DNS中設定gitserver指向該主機,那么以下這些命令都是可用的:
# 在 John 的電腦上 $ cd myproject $ git init $ git add . $ git commit -m 'initial commit' $ git remote add origin git@gitserver:/opt/git/project.git $ git push origin master
這樣,其他人的克隆和推送也一樣變得很簡單:
$ git clone git@gitserver:/opt/git/project.git $ cd project $ vim README $ git commit -am 'fix for the README file' $ git push origin master
用這個方法可以很快捷地為少數幾個開發者架設一個可讀寫的Git服務
作為一個額外的防范措施,你可以用Git自帶的git-shell工具限制git用戶的活動范圍。只要把它設為git用戶登入的shell,那么該用戶就無法使用普通的bash或者csh什么的shell程序。編輯/etc/passwd文件
$ sudo vim /etc/passwd
在文件末尾,你應該能找到類似這樣的行
git:x:1000:1000::/home/git:/bin/sh
把bin/sh改為/usr/bin/git-shell,或者用which git-shell查看它的實際安裝路徑。該行修改后的樣子如下
git:x:1000:1000::/home/git:/usr/bin/git-shell
現在git用戶只能用SSH連接來推送和獲取Git倉庫,而不能直接使用主機shell。嘗試普通SSH登錄的話,會看到下面這樣的拒絕信息
$ ssh git@gitserver fatal: What do you think I am? A shell? Connection to gitserver closed.
公共訪問
匿名的讀取權限該怎么實現呢?也許除了內部私有的項目之外,你還需要托管一些開源項目。或者因為要用一些自動化的服務器來進行編譯,或者有一些經常變化的服務器群組,而又不想整天生成新的SSH密鑰。總之,你需要簡單的匿名讀取權限
或許對小型的配置來說最簡單的辦法就是運行一個靜態web服務,把它的根目錄設定為Git倉庫所在的位置,然后開啟post-update掛鈎。這里繼續使用之前的例子。假設倉庫處於/opt/git目錄,主機上運行着Apache服務。重申一下,任何web服務程序都可以達到相同效果;作為范例,我們將用一些基本的Apache設定來展示大體需要的步驟
首先,開啟掛鈎:
$ cd project.git $ mv hooks/post-update.sample hooks/post-update $ chmod a+x hooks/post-update
post-update掛鈎是做什么的呢?其內容大致如下:
$ cat .git/hooks/post-update #!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". # exec git-update-server-info
意思是當通過SSH向服務器推送時,Git將運行這個git-update-server-info命令來更新匿名HTTP訪問獲取數據時所需要的文件
接下來,在Apache配置文件中添加一個VirtualHost條目,把文檔根目錄設為Git項目所在的根目錄。這里我們假定DNS服務已經配置好,會把對.gitserver的請求發送到這台主機:
<VirtualHost *:80> ServerName git.gitserver DocumentRoot /opt/git <Directory /opt/git/> Order allow, deny allow from all </Directory> </VirtualHost>
另外,需要把/opt/git目錄的Unix用戶組設定為www-data,這樣web服務才可以讀取倉庫內容,因為運行CGI腳本的Apache實例進程默認就是以該用戶的身份起來的:
$ chgrp -R www-data /opt/git
重啟Apache之后,就可以通過項目的URL來克隆該目錄下的倉庫了
$ git clone http://git.gitserver/project.git
這一招可以讓你在幾分鍾內為相當數量的用戶架設好基於HTTP的讀取權限。另一個提供非授權訪問的簡單方法是開啟一個Git守護進程,不過這將要求該進程作為后台進程常駐
GitWeb
現在我們的項目已經有了可讀可寫和只讀的連接方式,不過如果能有一個簡單的web界面訪問就更好了。Git自帶一個叫做GitWeb的CGI腳本,運行效果可以到http://git.kernel.org這樣的站點體驗下
如果想看看自己項目的效果,不妨用Git自帶的一個命令,可以使用類似lighttpd或webrick這樣輕量級的服務器啟動一個臨時進程。如果是在Linux主機上,通常都預裝了lighttpd,可以到項目目錄中鍵入git instaweb來啟動。如果用的是Mac,Leopard預裝了Ruby,所以webrick應該是最好的選擇。如果要用lighttpd以外的程序來啟動git instaweb,可以通過--httpd選項指定:
$ git instaweb --httpd=webrick [2009-02-21 10:02:21] INFO WEBrick 1.3.1 [2009-02-21 10:02:21] INFO ruby 1.8.6 (2008-03-03) [universal-darwin9.0]
這會在1234端口開啟一個HTTPD服務,隨之在瀏覽器中顯示該頁,十分簡單。關閉服務時,只需在原來的命令后面加上--stop選項就可以了
$ git instaweb --httpd=webrick --stop
如果需要為團隊或者某個開源項目長期運行GitWeb,那么CGI腳本就要由正常的網頁服務來運行。一些Linux發行版可以通過apt或yum安裝一個叫做gitweb的軟件包,不妨首先嘗試一下。我們將快速介紹一下手動安裝GitWeb的流程。首先,你需要Git的源碼,其中帶有GitWeb,並能生成定制的CGI腳本
$ git clone git://git.kernel.org/pub/scm/git/git.git $ cd git/ $ make GITWEB_PROJECTROOT="/opt/git" \ prefix=/usr gitweb $ sudo cp -Rf gitweb /var/www/
注意,通過指定GITWEB_PROJECTROOT變量告訴編譯命令Git倉庫的位置。然后,設置Apache以CGI方式運行該腳本,添加一個VirtualHost配置:
<VirtualHost *:80> ServerName gitserver DocumentRoot /var/www/gitweb <Directory /var/www/gitweb> Options ExecCGI +FollowSymLinks +SymLinksIfOwnerMatch AllowOverride All order allow,deny Allow from all AddHandler cgi-script cgi DirectoryIndex gitweb.cgi </Directory> </VirtualHost>
不難想象,GitWeb可以使用任何兼容CGI的網頁服務來運行;如果偏向使用其他web服務器,配置也不會很麻煩。現在,通過http://gitserver就可以在線訪問倉庫了,在http://git.server上還可以通過HTTP克隆和獲取倉庫的內容
Gitosis
把所有用戶的公鑰保存在authorized_keys文件的做法,只能湊和一陣子,當用戶數量達到幾百人的規模時,管理起來就會十分痛苦。每次改刪用戶都必須登錄服務器不去說,這種做法還缺少必要的權限管理,每個人都對所有項目擁有完整的讀寫權限
幸好我們還可以選擇應用廣泛的Gitosis項目。簡單地說,Gitosis就是一套用來管理authorized_keys文件和實現簡單連接限制的腳本。有趣的是,用來添加用戶和設定權限的並非通過網頁程序,而只是管理一個特殊的Git倉庫。你只需要在這個特殊倉庫內做好相應的設定,然后推送到服務器上,Gitosis就會隨之改變運行策略,聽起來就很酷
Gitosis 的安裝算不上傻瓜化,但也不算太難。用Linux服務器架設起來最簡單。以下例子中,我們使用裝有Ubuntu 8.10系統的服務器
Gitosis的工作依賴於某些Python工具,所以首先要安裝Python的setuptools包,在Ubuntu上稱為python-setuptools
$ apt-get install python-setuptools
接下來,從Gitosis項目主頁克隆並安裝:
$ git clone https://github.com/tv42/gitosis.git $ cd gitosis $ sudo python setup.py install
這會安裝幾個供Gitosis使用的工具。默認Gitosis會把/home/git作為存儲所有Git倉庫的根目錄,這沒什么不好,不過我們之前已經把項目倉庫都放在/opt/git里面了,所以為方便起見,我們可以做一個符號連接,直接划轉過去,而不必重新配置:
$ ln -s /opt/git /home/git/repositories
Gitosis將會幫我們管理用戶公鑰,所以先把當前控制文件改名備份,以便稍后重新添加,准備好讓Gitosis自動管理authorized_keys文件:
$ mv /home/git/.ssh/authorized_keys /home/git/.ssh/ak.bak
接下來,如果之前把git用戶的登錄shell改為git-shell命令的話,先恢復'git'用戶的登錄shell。改過之后,大家仍然無法通過該帳號登錄,因為authorized_keys文件已經沒有了。不過不用擔心,這會交給Gitosis來實現。所以現在先打開/etc/passwd文件,把這行:
git:x:1000:1000::/home/git:/usr/bin/git-shell
改回:
git:x:1000:1000::/home/git:/bin/sh
好了,現在可以初始化Gitosis了。你可以用自己的公鑰執行gitosis-init命令,要是公鑰不在服務器上,先臨時復制一份:
$ sudo -H -u git gitosis-init < /tmp/id_dsa.pub Initialized empty Git repository in /opt/git/gitosis-admin.git/ Reinitialized existing Git repository in /opt/git/gitosis-admin.git/
這樣該公鑰的擁有者就能修改用於配置Gitosis的那個特殊Git倉庫了。接下來,需要手工對該倉庫中的post-update腳本加上可執行權限:
$ sudo chmod 755 /opt/git/gitosis-admin.git/hooks/post-update
基本上就算是好了。如果設定過程沒出什么差錯,現在可以試一下用初始化Gitosis的公鑰的擁有者身份SSH登錄服務器,應該會看到類似下面這樣:
$ ssh git@gitserver PTY allocation request failed on channel 0 ERROR:gitosis.serve.main:Need SSH_ORIGINAL_COMMAND in environment. Connection to gitserver closed.
說明Gitosis認出了該用戶的身份,但由於沒有運行任何Git命令,所以它切斷了連接。那么,現在運行一個實際的Git命令——克隆Gitosis的控制倉庫:
# 在你本地計算機上
$ git clone git@gitserver:gitosis-admin.git
這會得到一個名為gitosis-admin的工作目錄,主要由兩部分組成:
$ cd gitosis-admin $ find . ./gitosis.conf ./keydir ./keydir/scott.pub
gitosis.conf文件是用來設置用戶、倉庫和權限的控制文件。keydir目錄則是保存所有具有訪問權限用戶公鑰的地方——每人一個。在keydir里的文件名(比如上面的scott.pub)應該跟你的不一樣——Gitosis會自動從使用gitosis-init腳本導入的公鑰尾部的描述中獲取該名字
看一下gitosis.conf文件的內容,它應該只包含與剛剛克隆的gitosis-admin相關的信息:
$ cat gitosis.conf [gitosis] [group gitosis-admin] members = scott writable = gitosis-admin
它顯示用戶scott——初始化Gitosis公鑰的擁有者——是唯一能管理gitosis-admin項目的人
現在我們來添加一個新項目。為此我們要建立一個名為mobile的新段落,在其中羅列手機開發團隊的開發者,以及他們擁有寫權限的項目。由於'scott'是系統中的唯一用戶,我們把他設為唯一用戶,並允許他讀寫名為iphone_project的新項目:
[group mobile] members = scott writable = iphone_project
修改完之后,提交gitosis-admin里的改動,並推送到服務器使其生效:
$ git commit -am 'add iphone_project and mobile group' [master 8962da8] add iphone_project and mobile group 1 file changed, 4 insertions(+) $ git push origin master Counting objects: 5, done. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 272 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) To git@gitserver:gitosis-admin.git fb27aec..8962da8 master -> master
在新工程iphone_project里首次推送數據到服務器前,得先設定該服務器地址為遠程倉庫。但你不用事先到服務器上手工創建該項目的裸倉庫——Gitosis會在第一次遇到推送時自動創建:
$ git remote add origin git@gitserver:iphone_project.git $ git push origin master Initialized empty Git repository in /opt/git/iphone_project.git/ Counting objects: 3, done. Writing objects: 100% (3/3), 230 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) To git@gitserver:iphone_project.git * [new branch] master -> master
請注意,這里不用指明完整路徑(實際上,如果加上反而沒用),只需要一個冒號加項目名字即可——Gitosis會自動幫你映射到實際位置
要和朋友們在一個項目上協同工作,就得重新添加他們的公鑰。不過這次不用在服務器上一個一個手工添加到~/.ssh/authorized_keys文件末端,而只需管理keydir目錄中的公鑰文件。文件的命名將決定在gitosis.conf中對用戶的標識。現在我們為John,Josie和Jessica添加公鑰:
$ cp /tmp/id_rsa.john.pub keydir/john.pub $ cp /tmp/id_rsa.josie.pub keydir/josie.pub $ cp /tmp/id_rsa.jessica.pub keydir/jessica.pub
然后把他們都加進'mobile'團隊,讓他們對iphone_project具有讀寫權限:
[group mobile] members = scott john josie jessica writable = iphone_project
如果你提交並推送這個修改,四個用戶將同時具有該項目的讀寫權限
Gitosis也具有簡單的訪問控制功能。如果想讓John只有讀權限,可以這樣做:
[group mobile] members = scott josie jessica writable = iphone_project [group mobile_ro] members = john readonly = iphone_project
現在John可以克隆和獲取更新,但Gitosis不會允許他向項目推送任何內容。像這樣的組可以隨意創建,多少不限,每個都可以包含若干不同的用戶和項目。甚至還可以指定某個組為成員之一(在組名前加上@ 前綴),自動繼承該組的成員:
[group mobile_committers] members = scott josie jessica [group mobile] members = @mobile_committers writable = iphone_project [group mobile_2] members = @mobile_committers john writable = another_iphone_project
如果遇到意外問題,試試看把loglevel=DEBUG加到[gitosis]的段落(把日志設置為調試級別,記錄更詳細的運行信息)。如果一不小心搞錯了配置,失去了推送權限,也可以手工修改服務器上的/home/git/.gitosis.conf文件——Gitosis實際是從該文件讀取信息的。它在得到推送數據時,會把新的gitosis.conf存到該路徑上。所以如果你手工編輯該文件的話,它會一直保持到下次向gitosis-admin推送新版本的配置內容為止
Gitolite
Gitolite是在Git之上的一個授權層,依托sshd或者httpd來進行認證
認證是確定用戶是誰,授權是決定該用戶是否被允許做他想做的事情
Gitolite允許你定義訪問許可而不只作用於倉庫,而同樣於倉庫中的每個branch和tag name。你可以定義確切的人(或一組人)只能push特定的"refs"(或者branches或者tags)而不是其他人。
【安裝】
安裝Gitolite非常簡單,甚至不用讀自帶的那一大堆文檔。你需要一個unix服務器上的賬戶;許多linux變種和solaris 10都已經試過了。不需要root訪問,假設git,perl,和一個openssh兼容的ssh服務器已經裝好了。在下面的例子里,我們會用git賬戶在gitserver進行
Gitolite是不同於“服務”的軟件——其通過ssh訪問,而且每個在服務器上的userid都是一個潛在的“gitolite主機”。我們在這里描述最簡單的安裝方法
開始,在你的服務器上創建一個名為git的用戶,然后以這個用戶登錄。從你的工作站拷貝你的SSH公鑰(也就是你用ssh-keygen默認生成的~/.ssh/id_dsa.pub文件),重命名為<yourname>.pub(我們這里使用scott.pub作為例子)。然后執行下面的命令:
$ git clone git://github.com/sitaramc/gitolite $ gitolite/install -ln # assumes $HOME/bin exists and is in your $PATH $ gitolite setup -pk $HOME/scott.pub
最后一個命令在服務器上創建了一個名為gitolite-admin的Git倉庫
最后,回到你的工作站,執行git clone git@gitserver:gitolite-admin。然后你就完成了!Gitolite現在已經安裝在了服務器上,在你的工作站上,你也有一個名為gitolite-admin的新倉庫。你可用通過更改這個倉庫以及推送到服務器上來管理你的Gitolite配置。
【配置文件和訪問規則】
安裝結束后,你切換到gitolite-admin倉庫(放在你的HOME目錄)然后看看都有啥:
$ cd ~/gitolite-admin/ $ ls conf/ keydir/ $ find conf keydir -type f conf/gitolite.conf keydir/scott.pub $ cat conf/gitolite.conf repo gitolite-admin RW+ = scott repo testing RW+ = @all
注意"scott"(之前用gl-setup命令時候的pubkey名稱)有讀寫權限而且在gitolite-admin倉庫里有一個同名的公鑰文件
添加用戶很簡單。為了添加一個名為alice的用戶,獲取她的公鑰,命名為alice.pub,然后放到在你工作站上的gitolite-admin克隆的keydir目錄。添加,提交,然后推送更改。這樣用戶就被添加了
gitolite配置文件的語法在conf/example.conf里,我們只會提到一些主要的。
你可以給用戶或者倉庫分組。分組名就像一些宏;定義的時候,無所謂他們是工程還是用戶;區別在於你使用“宏”的時候
@oss_repos = linux perl rakudo git gitolite @secret_repos = fenestra pear @admins = scott @interns = ashok @engineers = sitaram dilbert wally alice @staff = @admins @engineers @interns
你可以控制許可在”ref“級別。在下面的例子里,實習生可以push "int"分支。工程師可以push任何有"eng-"開頭的branch,還有refs/tags下面用"rc"開頭的后面跟數字的。而且管理員可以隨便更改(包括rewind)對任何參考名
repo @oss_repos RW int$ = @interns RW eng- = @engineers RW refs/tags/rc[0-9] = @engineers RW+ = @admins
在RWorRW+之后的表達式是正則表達式(regex)對應着后面的push用的參考名字(ref)。所以我們叫它“參考正則”(refex)
同樣,你可能猜到了,Gitolite字頭refs/heads/是一個便捷句法如果參考正則沒有用refs/開頭
一個這個配置文件語法的重要功能是,所有的倉庫的規則不需要在同一個位置。你能報所有普通的東西放在一起,就像上面的對所有oss_repos的規則那樣,然后建一個特殊的規則對后面的特殊案例,就像:
repo gitolite
RW+ = sitaram
那條規則剛剛加入規則集的gitolite倉庫
這次你可能會想要知道訪問控制規則是如何應用的,我們簡要介紹一下
在gitolite里有兩級訪問控制。第一是在倉庫級別;如果你已經讀或者寫訪問過了任何在倉庫里的參考,那么你已經讀或者寫訪問倉庫了
第二級,應用只能寫訪問,通過在倉庫里的branch或者tag。用戶名如果嘗試過訪問(W或+),參考名被更新為已知。訪問規則檢查是否出現在配置文件里,為這個聯合尋找匹配(但是記得參考名是正則匹配的,不是字符串匹配的)。如果匹配被找到了,push就成功了。不匹配的訪問會被拒絕。
【帶“拒絕”的高級訪問控制】
目前,我們只看過了許可是R,RW,或者RW+這樣子的。但是gitolite還允許另外一種許可:"-"代表“拒絕”。這個給了你更多的能力,當然也有一點復雜,因為不匹配並不是唯一的拒絕訪問的方法,因此規則的順序變得無關了
在前面的情況中,我們想要工程師可以rewind任意branch除了master和integ。這里是如何做到的
RW master integ = @engineers - master integ = @engineers RW+ = @engineers
你再一次簡單跟隨規則從上至下知道你找到一個匹配你的訪問模式的,或者拒絕。非rewind push到master或者integ 被第一條規則允許。一個rewind push到那些refs不匹配第一條規則,掉到第二條,因此被拒絕。任何push(rewind或非rewind)到參考或者其他master或者integ不會被前兩條規則匹配,即被第三條規則允許
【通過改變文件限制 push】
此外限制用戶push改變到哪條branch的,你也可以限制哪個文件他們可以碰的到。比如,可能Makefile (或者其他哪些程序) 真的不能被任何人做任何改動,因為好多東西都靠着它呢,或者如果某些改變剛好不對就會崩潰。你可以告訴 gitolite:
repo foo RW = @junior_devs @senior_devs - VREF/NAME/Makefile = @junior_devs
這是一個強力的功能寫在 conf/example.conf里
【個人分支】
Gitolite也支持一個叫“個人分支”的功能 (或者叫“個人分支命名空間”)在合作環境里非常有用。
在git世界里許多代碼交換通過"pull"請求發生。然而在合作環境里,委任制的訪問是“絕不”,一個開發者工作站不能認證,你必須push到中心服務器並且叫其他人從那里pull
這個通常會引起一些branch名稱簇變成像VCS里一樣集中化,加上設置許可變成管理員的苦差事
Gitolite讓你定義一個“個人的”或者“亂七八糟的”命名空間字首給每個開發人員(比如,refs/personal/<devname>/*);看在doc/3-faq-tips-etc.mkd里的"personal branches"一段獲取細節
【“通配符”倉庫】
Gitolite允許你定義帶通配符的倉庫(其實還是perl正則式),比如隨便整個例子的話assignments/s[0-9][0-9]/a[0-9][0-9]。這是一個非常有用的功能,需要通過設置$GL_WILDREPOS = 1; 在rc文件中啟用。允許你安排一個新許可模式("C")允許用戶創建倉庫基於通配符,自動分配擁有權對特定用戶——創建者,允許他交出R和RW許可給其他合作用戶等等。這個功能在doc/4-wildcard-repositories.mkd文檔里
【其他功能】
我們用一些其他功能的例子結束這段討論,這些以及其他功能都在"faqs, tips, etc"和其他文檔里
記錄:Gitolite記錄所有成功的訪問。如果你太放松給了別人rewind許可(RW+)和其他孩子弄沒了"master",記錄文件會救你的命,如果其他簡單快速的找到SHA都不管用。
訪問權報告:另一個方便的功能是你嘗試用ssh連接到服務器的時候發生了什么。Gitolite告訴你哪個 repos你訪問過,那個訪問可能是什么。這里是例子:
hello scott, this is git@git running gitolite3 v3.01-18-g9609868 on git 1.7.4.4 R anu-wsd R entrans R W git-notes R W gitolite R W gitolite-admin R indic_web_input R shreelipi_converter
委托:真正的大安裝,你可以把責任委托給一組倉庫給不同的人然后讓他們獨立管理那些部分。這個減少了主管理者的負擔,讓他瓶頸更小。這個功能在他自己的文檔目錄里的doc/下面
鏡像:Gitolite可以幫助你維護多個鏡像,如果主服務器掛掉的話在他們之間很容易切換
Git守護進程
對於提供公共的,非授權的只讀訪問,我們可以拋棄HTTP協議,改用Git自己的協議,這主要是出於性能和速度的考慮。Git協議遠比HTTP協議高效,因而訪問速度也快,所以它能節省很多用戶的時間
重申一下,這一點只適用於非授權的只讀訪問。如果建在防火牆之外的服務器上,那么它所提供的服務應該只是那些公開的只讀項目。如果是在防火牆之內的服務器上,可用於支撐大量參與人員或自動系統(用於持續集成或編譯的主機)只讀訪問的項目,這樣可以省去逐一配置SSH公鑰的麻煩
但不管哪種情形,Git協議的配置設定都很簡單。基本上,只要以守護進程的形式運行該命令即可:
git daemon --reuseaddr --base-path=/opt/git/ /opt/git/
這里的--reuseaddr選項表示在重啟服務前,不等之前的連接超時就立即重啟。而--base-path選項則允許克隆項目時不必給出完整路徑。最后面的路徑告訴Git守護進程允許開放給用戶訪問的倉庫目錄。假如有防火牆,則需要為該主機的9418端口設置為允許通信
以守護進程的形式運行該進程的方法有很多,但主要還得看用的是什么操作系統。在Ubuntu主機上,可以用Upstart腳本達成。編輯該文件:
/etc/event.d/local-git-daemon
加入以下內容:
start on startup stop on shutdown exec /usr/bin/git daemon \ --user=git --group=git \ --reuseaddr \ --base-path=/opt/git/ \ /opt/git/ respawn
出於安全考慮,強烈建議用一個對倉庫只有讀取權限的用戶身份來運行該進程——只需要簡單地新建一個名為git-ro的用戶
新建用戶默認對倉庫文件不具備寫權限,但這取決於倉庫目錄的權限設定。務必確認git-ro對倉庫只能讀不能寫,並用它的身份來啟動進程。這里為了簡化,后面我們還是用之運行Gitosis的用戶'git'
這樣一來,當你重啟計算機時,Git進程也會自動啟動。要是進程意外退出或者被殺掉,也會自行重啟。在設置完成后,不重啟計算機就啟動該守護進程,可以運行:
initctl start local-git-daemon
而在其他操作系統上,可以用xinetd,或者sysvinit系統的腳本,或者其他類似的腳本——只要能讓那個命令變為守護進程並可監控
接下來,我們必須告訴Gitosis哪些倉庫允許通過Git協議進行匿名只讀訪問。如果每個倉庫都設有各自的段落,可以分別指定是否允許Git進程開放給用戶匿名讀取。比如允許通過Git協議訪問iphone_project,可以把下面兩行加到gitosis.conf文件的末尾:
[repo iphone_project]
daemon = yes
在提交和推送完成后,運行中的Git守護進程就會響應來自9418端口對該項目的訪問請求。
如果不考慮Gitosis,單單起了Git守護進程的話,就必須到每一個允許匿名只讀訪問的倉庫目錄內,創建一個特殊名稱的空文件作為標志:
$ cd /path/to/project.git
$ touch git-daemon-export-ok
該文件的存在,表明允許 Git 守護進程開放對該項目的匿名只讀訪問。
Gitosis還能設定哪些項目允許放在GitWeb上顯示。打開GitWeb的配置文件/etc/gitweb.conf,添加以下四行
$projects_list = "/home/git/gitosis/projects.list"; $projectroot = "/home/git/repositories"; $export_ok = "git-daemon-export-ok"; @git_base_url_list = ('git://gitserver');
接下來,只要配置各個項目在Gitosis中的gitweb參數,便能達成是否允許GitWeb用戶瀏覽該項目。比如,要讓iphone_project項目在GitWeb里出現,把repo的設定改成下面的樣子:
[repo iphone_project] daemon = yes gitweb = yes
在提交並推送過之后,GitWeb就會自動開始顯示iphone_project項目的細節和歷史