教你搭建你自己的Git伺服器

roc_guo發表於2023-01-10

直到現在,我們主要討論的還是以一個使用者的身份與 Git 進行互動。這篇文章中我將討論 Git 的管理,並且設計一個靈活的 Git 框架。你可能會覺得這聽起來是 “高階 Git 技術” 或者 “只有狂熱粉才能閱讀”的一句委婉的說法,但是事實是這裡面的每個任務都不需要很深的知識或者其他特殊的訓練,就能基本理解 Git 的工作原理,有可能需要一丁點關於   的知識。
教你搭建你自己的Git伺服器教你搭建你自己的Git伺服器

共享Git伺服器

建立你自己的共享 Git 伺服器意外地簡單,而且在很多情況下,遇到的這點麻煩是完全值得的。不僅僅是因為它保證你有許可權檢視自己的程式碼,它還可以透過擴充套件為 Git 的使用敞開了一扇大門,例如個人 Git 鉤子、無限制的資料儲存、和持續整合與分發(CI & CD)。

如果你知道如何使用 Git 和 SSH,那麼你已經知道怎麼建立一個 Git 伺服器了。Git 的設計方式,就是讓你在建立或者 clone 一個倉庫的時候,就完成了一半伺服器的搭建。然後允許用 SSH 訪問倉庫,而且任何有許可權訪問的人都可以使用你的倉庫作為 clone 的新倉庫的基礎。

但是,這是一個小的點對點環境(ad-hoc)。按照一些方案你可以建立一些帶有同樣的功能的設計優良的 Git 伺服器,同時有更好的擴充性。

首要之事:確認你的使用者們,現在的使用者以及之後的使用者都要考慮。如果你是唯一的使用者那麼沒有任何改動的必要。但是如果你試圖邀請其他的程式碼貢獻者使用,那麼你應該允許一個專門的分享系統使用者給你的開發者們。

假定你有一個可用的伺服器(如果沒有,這不成問題,Git 會幫忙解決,  的 樹莓派 3 是個不錯的開始),然後第一步就是隻允許使用 SSH 金鑰認證的 SSH 登入。這比使用密碼登入安全得多,因為這可以免於暴力破解,也可以透過直接刪除使用者金鑰而禁用使用者。

一旦你啟用了 SSH 金鑰認證,建立 gituser 使用者。這是給你的所有授權的使用者們的公共使用者:

        $ su -c 'adduser gituser'

然後切換到剛建立的 gituser 使用者,建立一個 ~/.ssh 的框架,並設定好合適的許可權。這很重要,如果許可權設定得太開放會使自己所保護的 SSH 沒有意義。

        $ su - gituser
        $ mkdir .ssh && chmod 700 .ssh
        $ touch .ssh/authorized_keys
        $ chmod 600 .ssh/authorized_keys

authorized_keys 檔案裡包含所有你的開發者們的 SSH 公鑰,你開放許可權允許他們可以在你的 Git 專案上工作。他們必須建立他們自己的 SSH 金鑰對然後把他們的公鑰給你。複製公鑰到 gituser 使用者下的 authorized_keys 檔案中。例如,為一個叫 Bob 的開發者,執行以下 :

        $ cat ~/path/to/id_rsa.bob.pub >> /home/gituser/.ssh/authorized_keys

只要開發者 Bob 有私鑰並且把相對應的公鑰給你,Bob 就可以用 gituser 使用者訪問伺服器。

但是,你並不是想讓你的開發者們能使用伺服器,即使只是以 gituser 的身份訪問。你只是想給他們訪問 Git 倉庫的許可權。因為這個特殊的原因,Git 提供了一個限制的  ,準確的說是 git-shell。以 root 身份執行以下 ,把 git-shell 新增到你的系統中,然後設定成 gituser 使用者的預設 shell。

        # grep git-shell /etc/shells || su -c "echo `which git-shell` >> /etc/shells"
        # su -c 'usermod -s git-shell gituser'

現在 gituser 使用者只能使用 SSH 來 push 或者 pull Git 倉庫,並且無法使用任何一個可以登入的 shell。你應該把你自己新增到和 gituser 一樣的組中,在我們的樣例伺服器中這個組的名字也是 gituser。

舉個例子:

        # usermod -a -G gituser seth

僅剩下的一步就是建立一個 Git 倉庫。因為沒有人能在伺服器上直接與 Git 互動(也就是說,你之後不能 SSH 到伺服器然後直接操作這個倉庫),所以建立一個空的倉庫 。如果你想使用這個放在伺服器上的倉庫來完成工作,你可以從它的所在處 clone 下來,然後在你的 home 目錄下進行工作。

嚴格地講,你不是必須建立這個空的倉庫;它和一個正常的倉庫一樣工作。但是,一個空的倉庫沒有工作分支(working tree) (也就是說,使用 checkout 並沒有任何分支顯示)。這很重要,因為不允許遠端使用者們 push 到一個有效的分支上(如果你正在 dev 分支工作然後突然有人把一些變更 push 到你的工作分支,你會有怎麼樣的感受?)。因為一個空的倉庫可以沒有有效的分支,所以這不會成為一個問題。

你可以把這個倉庫放到任何你想放的地方,只要你想要放開許可權給使用者和使用者組,讓他們可以在倉庫下工作。千萬不要儲存目錄到比如說一個使用者的 home 目錄下,因為那裡有嚴格的許可權限制。儲存到一個常規的共享地址,例如 /opt 或者 /usr/local/share。

以 root 身份建立一個空的倉庫:

 	# git init --bare /opt/jupiter.git
        # chown -R gituser:gituser /opt/jupiter.git
 	# chmod -R 770 /opt/jupiter.git

現在任何一個使用者,只要他被認證為 gituser 或者在 gituser 組中,就可以從 jupiter.git 庫中讀取或者寫入。在本地機器嘗試以下操作:

 	$ git clone gituser@example.com:/opt/jupiter.git jupiter.clone
 	Cloning into 'jupiter.clone'...
 	Warning: you appear to have cloned an empty repository.

謹記:開發者們一定要把他們的 SSH 公鑰加入到 gituser 使用者下的 authorized_keys 檔案裡,或者說,如果他們有伺服器上的使用者(如果你給了他們使用者),那麼他們的使用者必須屬於 gituser 使用者組。

Git鉤子

執行你自己的 Git 伺服器最讚的一件事之一就是可以使用 Git 鉤子。Git 託管服務有時提供一個鉤子類的介面,但是他們並不會給你真正的 Git 鉤子來讓你訪問檔案系統。Git 鉤子是一個 ,它將在一個 Git 過程的某些點執行;鉤子可以執行在當一個倉庫即將接收一個 commit 時、或者接受一個 commit 之後,或者即將接收一次 push 時,或者一次 push 之後等等。

這是一個簡單的系統:任何放在 .git/hooks 目錄下的 、使用標準的命名體系,就可按設計好的時間執行。一個指令碼是否應該被執行取決於它的名字; pre-push 指令碼在 push 之前執行,post-receive 指令碼在接受 commit 之後執行等等。這或多或少的可以從名字上看出來。

指令碼可以用任何語言寫;如果在你的系統上有可以執行的指令碼語言,例如輸出 ‘hello world’ ,那麼你就可以這個語言來寫 Git 鉤子指令碼。Git 預設帶了一些例子,但是並不有啟用。

想要動手試一個?這很簡單。如果你沒有現成的 Git 倉庫,首先建立一個 Git 倉庫:

 	$ mkdir jupiter
 	$ cd jupiter
 	$ git init .

然後寫一個 “hello world” 的 Git 鉤子。因為我為了支援老舊系統而使用 tsch,所以我仍然用它作為我的指令碼語言,你可以自由的使用自己喜歡的語言(Bash,Python,Ruby,Perl,Rust,Swift,Go):

 	$ echo "#/!/bin/tcsh" > .git/hooks/post-commit
 	$ echo "echo 'POST-COMMIT SCRIPT TRIGGERED'" >> ~/jupiter/.git/hooks/post-commit
 	$ chmod +x ~/jupiter/.git/hooks/post-commit

現在測試它的輸出:

 	$ echo "hello world" > foo.txt
 	$ git add foo.txt
 	$ git commit -m 'first commit'
 	! POST-COMMIT SCRIPT TRIGGERED
 	[master (root-commit) c8678e0] first commit
 	1 file changed, 1 insertion(+)
 	create mode 100644 foo.txt

現在你已經實現了:你的第一個有功能的 Git 鉤子。

有名的push-to-web鉤子

Git 鉤子最流行的用法就是自動 push 更改的程式碼到一個正在使用中的產品級 Web 伺服器目錄下。這是擺脫 FTP 的很好的方式,對於正在使用的產品保留完整的版本控制,整合並自動化內容的釋出。

如果操作正確,網站釋出工作會像以前一樣很好的完成,而且在某種程度上,很精準。Git 真的好棒。我不知道誰最初想到這個主意,但是我是從 Emacs 和 Git 方面的專家,IBM 的 Bill von Hagen 那裡第一次聽到它的。他的文章包含關於這個過程的權威介紹:Git 改變了分散式網頁開發的遊戲規則。

Git 變數

每一個 Git 鉤子都有一系列不同的變數對應觸發鉤子的不同 Git 行為。你需不需要這些變數,主要取決於你寫的程式。如果你只是需要一個當某人 push 程式碼時候的通用郵件通知,那麼你就不需要什麼特殊的東西,甚至也不需要編寫額外的指令碼,因為已經有現成的適合你的樣例指令碼。如果你想在郵件裡檢視 commit 資訊和 commit 的作者,那麼你的指令碼就會變得相對麻煩些。

Git 鉤子並不是被使用者直接執行,所以要弄清楚如何收集可能會混淆的重要資訊。事實上,Git 鉤子指令碼類似於其他的指令碼,像 BASH、Python、C++ 等等一樣從標準輸入讀取引數。不同的是,我們不會給它提供這個輸入,所以,你在使用的時候,需要知道可能的輸入引數。

在寫 Git 鉤子之前,看一下 Git 在你的專案目錄下 .git/hooks 目錄中提供的一些例子。舉個例子,在這個 pre-push.sample 檔案裡,註釋部分說明了如下內容:

 	# $1 -- 即將 push 的遠端倉庫的名字
 	# $2 -- 即將 push 的遠端倉庫的 URL
 	# 如果 push 的時候,並沒有一個命名的遠端倉庫,那麼這兩個引數將會一樣。
 	# 提交的資訊將以下列形式按行傳送給標準輸入
 	# <local ref> <local sha1> <remote ref> <remote sha1>

並不是所有的例子都是這麼清晰,而且關於鉤子獲取變數的文件依舊缺乏(除非你去讀 Git 的原始碼)。但是,如果你有疑問,你可以從線上其他使用者的嘗試中學習,或者你只是寫一些基本的指令碼,比如 echo $1, $2, $3 等等。

分支檢測示例

我發現,對於生產環境來說有一個共同的需求,就是需要一個只有在特定分支被修改之後,才會觸發事件的鉤子。以下就是如何跟蹤分支的示例。

首先,Git 鉤子本身是不受版本控制的。 Git 並不會跟蹤它自己的鉤子,因為對於鉤子來說,它是 Git 的一部分,而不是你倉庫的一部分。所以,Git 鉤子可以監控你的 Git 伺服器上的一個空倉庫的 commit 記錄和 push 記錄,而不是你本地倉庫的一部分。

我們來寫一個 post-receive(也就是說,在 commit 被接受之後觸發)鉤子。第一步就是需要確定分支名:

 	#!/bin/tcsh
 	foreach arg ( $< )
 	  set argv = ( $arg )
 	  set refname = $1
 	end

這個 for 迴圈用來讀入第一個引數 $1 ,然後迴圈用第二個引數 $2 去覆蓋它,然後用第三個引數 $3 再這樣。在 Bash 中有一個更好的方法,使用 read 命令,並且把值放入陣列裡。但是,這裡是 tcsh,並且變數的順序可以預測的,所以,這個方法也是可行的。

當我們有了 commit 記錄的 refname,我們就能使用 Git 去找到這個分支的供人看的名字:

 	set branch = `git rev-parse --symbolic --abbrev-ref $refname`
 	echo $branch #DEBUG

然後把這個分支名和我們想要觸發的事件的分支名關鍵字進行比較:

 	if ( "$branch" == "master" ) then
 	  echo "Branch detected: master"
 	  git /
 	    --work-tree=/path/to/where/you/want/to/copy/stuff/to /
 	    checkout -f $branch || echo "master fail"
 	else if ( "$branch" == "dev" ) then
 	  echo "Branch detected: dev"
 	  Git /
 	    --work-tree=/path/to/where/you/want/to/copy/stuff/to /
 	    checkout -f $branch || echo "dev fail"
 	  else
 	    echo "Your push was successful."
 	    echo "Private branch detected. No action triggered."
 	endif

給這個指令碼分配可執行許可權:

        $ chmod +x ~/jupiter/.git/hooks/post-receive

現在,當一個使用者提交到伺服器的 master 分支,那些程式碼就會被複制到一個生產環境的目錄,提交到 dev 分支則會被複制到另外的地方,其他分支將不會觸發這些操作。

同時,創造一個 pre-commit 指令碼也很簡單。比如,判斷一個使用者是否在他們不該 push 的分支上 push 程式碼,或者對 commit 資訊進行解析等等。

Git 鉤子也可以變得複雜,而且它們因為 Git 的工作流的抽象層次不同而變得難以理解,但是它們確實是一個強大的系統,讓你能夠在你的 Git 基礎設施上針對所有的行為進行對應的操作。如果你是一個 Git 重度使用者,或者一個全職 Git 管理員,那麼 Git 鉤子是值得學習的,只有當你熟悉這個過程,你才能真正掌握它。

在我們這個系列下一篇也是最後一篇文章中,我們將會學習如何使用 Git 來管理非文字的二進位制資料,比如音訊和圖片。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901823/viewspace-2931624/,如需轉載,請註明出處,否則將追究法律責任。

相關文章