Git Repositories hosten

In diesem Kapitel möchte ich Kurz darauf eingehen, wie man seine Repositories nun der Öffentlichkeit verfügbar macht. Hier gibt es - wie bei Git so üblich - viele Möglichkeiten zum Ziel. Es ist wichtig ein paar Konzepte zu verstehen:

Bare Repository vs. Working Copy

Zwei Begriffe möchte ich hier erklären.

Die Arbeitskopie:
Die Arbeitskopie ist das Projektverzeichnis mit allen ausgecheckten (also verwalteten) Projektdateien. Es enthält zusätzlich zu den Projektdateien noch das .git Verzeichnis, das alle Kontrolldateien enthält.

Das Bare Repository:
Tatsächlich ist das „eigentliche“ Repository immer das Verzeichnis „.git“ in der Arbeitskopie. Dieses Verzeichnis enthält schon alle benötigten Informationen! Beispielsweise können wir einfach das .git Verzeichnis einer Arbeitskopie in ein leeres Verzeichnis kopieren und so auch die Arbeitskopie wiederherstellen. Angenommen ihr seid in einer Git Arbeitskopie sieht das so aus:

$ mkdir /tmp/empty
$ cp -r .git /tmp/empty/
$ cd /tmp/empty
$ git reset --hard HEAD


Das .git Verzeichnis kann auch für sich alleine stehen, und kann von alle Operationen referenziert werden. Wenn das .git Repository für sich alleine steht, nennt man es Bare Repository.

Merke: Der Inhalt eines Bare Repositories ist der gleiche wie der Inhalt des .git Verzeichnisses einer Arbeitskopie.

Ihr könnt das Bare Repository nennen wie ihr wollt, es gibt aber die Konvention Repositories die gehostet werden projektname.git zu nennen.

In diesem Artikel werde ich das Verzeichnis des Repositories mit dem Symbol $GIT_DIR bezeichnen, es steht sowohl für ein .git-Verzeichnis eines ausgecheckten repositories als auch für ein Bare Repository.

Um ein leeres Repository mit Arbeitskopie zu erstellen, muss in ein leeres Verzeichnis wechseln und git init ausführen. Aber Achtung! Repositories mit Arbeitskopien zu hosten ist eine schlechte Idee. Wenn dorthin gepusht wird, hat derjenige dem die Arbeitskopie gehört ziemliche Probleme und es kann zu unerwarteten Problemen kommen.
Deswegen sollte man immer nur sogenannte „Bare Repositories“ hosten. Ein Bare Repository ist ein Repository ohne Arbeitskopie.

Um ein leeres Bare Repository zu erstellen, muss man es mit git init –bare initialisieren.
Ihr könnt gleich darauf euer Vorhandenes Repository dorthin pushen.

Objects und Refs

Weiters sollte man wissen, dass ein Git Repository hauptsächlich 1) aus zwei Dingen besteht:

  • objects
  • refs

objects sind alle „Objekte“ eines Repositories. Diese Objekte sind eines der folgenden:

  • blob (Dateien)
  • tree (Verzeichnisse)
  • commit

Der Namen eines Objekts ist immer sein SHA1-Hash-Wert, und die Objekte sind im Verzeichnis $GIT_DIR/objects abgespeichert.

Ein tree Objekt stellt eine Verzeichnisebene dar und enthält Referenzen auf blob-Objekte (die in dieser Verzeichnisebene gespeicherten Dateien) oder auf weitere tree-Objekte (Unterverzeichnisse).

Ein commit-Objekt enthält Referenzen auf den (oder die 2) ) vorhergehenden Commit (den sogenannten „parent commits“) und auf ein tree-Objekt.

refs sind Referenzen auf diese Objekte und einfache Dateien im $GIT_DIR/refs Verzeichnis. Sie beinhalten nur den Hash des Commits, auf den sie referenzieren. Und so funktioniert auch das repository: Kennt man ein bestimmtes commit-Objekt, so kann man daraus die ganze Geschichte des Codes zurückverfolgen. Branches sind somit nichts anderes als Referenzen auf commit-Objekte.

Wer mehr über Objects und refs erfahren möchte, für den könnte das Buch Pro Git interessant sein.


Wie sind nun die objects und refs fürs Hosten relevant? Ganz einfach. Alle, die an einem Projekt mitarbeiten, müssen Objects erstellen dürfen. Bestimmte Leute müssen refs bearbeiten und erstellen können, und hier geschieht auch die genaue Steuerung der Zugriffsrechte.

Methoden

Hier sind einige Methoden aufgelistet, wie ihr ein Git Repository hosten könnt.
Ich habe zu jeder Methode ein kleine Tabelle hinzugefügt, die eine Zusammenfassung der Eigenschaften beschreibt.

Methode 0 - Ein lokales Repository

Zur Installation benötigt -
Zum Betreiben benötigt git
Öffentlicher Lesezugriff Nein
Öffentlicher Schreibzugriff Nein
Benötigte Rechte für User Lokaler Account
Schwierigkeit Leicht


Diese nicht-Methode ist eigentlich nützlicher als sie aussehen mag. Dazu kopiert einfach das Repository an einen Ort auf eurem Dateisystem (entweder mit cp -r .git ein/pfad oder mit git clone –bare).

Dies ist nun schon alles, ihr könnt es jetzt verwenden. Ihr könnt es ganz normal, wie ein entferntes Repository verwenden und es als remote hinzufügen, dorthin pushen oder von dort pullen.

Wozu es vielleicht nützlich sein könnte: Habt ihr mehrere Entwickler auf einer Maschine (sei es eine die ihr irgendwo stehen habt oder aber auch ein entfernter Server auf den ihr nur über Shell-Accounts Zugriff habt), können diese Entwickler so sicher Änderungen austauschen. Aber auch einfach für euch persönlich als „Backup“ repository kann es nützlich sein.
Was ich gerne mache ist ein Repository auf einem USB-Stick, mit dem ich dann unterwegs arbeiten kann. Sind die remotes meines Repos auf meinem Hauptrechner richtig gesetzt, kann ich so die Änderungen mit einem einfachen pull abholen.

Methode 1 - Ein "dummes" Git Repository

Zur Installation benötigt git
Zum Betreiben benötigt http oder ftp server
Öffentlicher Lesezugriff Ja
Öffentlicher Schreibzugriff Nein
Benötigte Rechte für User -
Schwierigkeit Leicht


Mit dieser Methode könnt ihr ganz leicht öffentliche lese-Repositories hosten. Dazu müsst ihr einfach nur folgende Dinge tun:

  • Euer bare-Repository erstellen und/oder kopieren
  • In diesem Repository git update-server-info
  • Das repository an einen Ort kopieren wo euer FTP/HTTP Server das sieht. Das wäre z.B. /var/www bei einer Standard-Apache-Installation oder /home/ftp bei einer Standard-vsftpd installation.

Schon seid ihr fertig. Ihr könnt auf das Repository über die URL zugreifen. Heißt das Verzeichnis z.B. „eurprojekt.git“, und hat euer Server die domain „example.org“ könnt ihr auf das Repository über die URL http://example.org/euerprojekt.git bzw. ftp://example.org/euerprojekt.git zugreifen.

Allerdings müsst ihr wissen, dass ihr jedes mal wenn ihr am Repository etwas ändert ihr nochmal git update-server-info aufrufen müsst. Wozu man git-update-server-info(1) braucht: normalerweise werden die refs und die objects beim pullen von einem Server gepackt (und beim pushen auf den Server entpackt). Da die FTP und HTTP Server aber keine Ahnung von Git haben, können sie das nicht tun - es müssen den Clients zusätzliche Informationen bereitgestellt werden damit diese wissen welche refs und objects es auf dem Server gibt.

Da es nicht sehr praktikabel ist update-server-info nach jedem commit aufzurufen kann man sich hier der githooks(5) bedienen. Das sind kleine Skripte, die zu bestimmten Zeitpunkten bei bestimmten Aktionen automatisch von git aufgerufen werden. In diesem Fall ist das post-update hook interessant.
Hier kann man update-server-info nach jedem update aufrufen lassen. Dazu kopiert die Datei $GIT_DIR/hooks/post-update.sample einfach nach $GIT_DIR/hooks/post-update, oder erstellt die Datei mit dem Inhalt

#!/bin/sh
exec git update-server-info

einfach selbst.

Methode 2 - Git Daemon

Zur Installation benötigt -
Zum Betreiben benötigt git-daemon(1)
Öffentlicher Lesezugriff Ja
Öffentlicher Schreibzugriff Nein
Benötigte Rechte für User -
Schwierigkeit Leicht


Dies ist ebenfalls eine der einfachsten Methoden ein Repository zu hosten. Um das Repository zu hosten braucht ihr einfach nur den git-daemon(1); dieser sollte bei jeder Git-Installation mit dabei sein.

Um es zu hosten, markiert ihr das zu hostende Repository mit einer Datei mit dem Namen „git-daemon-export-ok“. Der Inhalt ist egal, aber ihr müsst sie in das bare Repository legen (das bedeutet wenn es eine ausgecheckte Arbeitskopie ist, dann muss die Datei im .git/-Verzeichnis liegen).

Startet dann den Daemon mit git daemon, und schon ist euer Repository gehostet. Wenn euer Server die Domain „example.org“ hat, und ihr das Repository unter /gitroot/proj.git abgelegt habt, könnt ihr nun darauf mit der URL git://example.org/gitroot/proj.git zugreifen.

Das war die Kurzversion. Es gibt einige Einstellungen die ihr alle in der manpage findet.

Um den Server dauerhaft (also auch nach Neustart) zu installieren, muss man ihn von den Startskripten (z.B. init-Skripte) eurer Distribution starten. Debian und Ubuntu stellen dazu das Paket git-daemon-run bereit.
Für die derzeitig aktuelle Version des Pakets (1:1.7.1-1) besteht das Einrichten lediglich daraus, dass man in /var/cache/git einen symbolischen link zu den zu exportierenden repositories verzeichnissen erstellt:

# ln -s /gitroot/proj.git /var/cache/git/

Schon könnt ihr auf das Repository über die URL git://example.org/git/proj.git zugreifen.

Methode 3a - Mehrere Shell Accounts

Zur Installation benötigt -
Zum Betreiben benötigt git, sshd
Öffentlicher Lesezugriff Ja
Öffentlicher Schreibzugriff Ja
Benötigte Rechte für User Shell Account
Schwierigkeit Mittel


Für diese Methode braucht jeder Nutzer auf dem Server, der das Repository hosten soll einen Shell Account. Dazu muss man auf dem Server für jeden User einen Account anlegen - entweder mit adduser(8) oder mit eurem Lieblingsbenutzerverwaltungstool.
Wenn ihr den Usern außer Git auf diesem Server nichts erlauben wollt, könnt ihr die Login-Shell der User auf die git-shell(1) beschränken (das macht ihr indem ihr in der /etc/passwd das letzte Feld des users ändert). Das erlaubt nur die Ausführung von git-receive-pack und git-upload-pack, alles andere ist nicht erlaubt.

Legt nun ein Verzeichnis für das Repository an, zum Beispiel /gitroot/proj.git. Wechselt in das zu hostende Verzeichnis, und legt dort ein Bare Repository an:

# mkdir -p /gitroot/proj.git
# chown -R euerlogin:projekgruppe /gitroot/proj.git
$ cd /gitroot/proj.git
$ git init --bare --shared=group


Stellt sicher, dass der User Zugriffsrechte (Schreib- oder Leserechte, je nachdem wie ihr es einrichten wollt) hat. Wenn das Repository nur einem User zur Verfügung gestellt werden soll, gebt ihr nur dem User die Rechte. Wenn ihr allerdings das Repository für mehrere Benutzer verfügbar machen wollt, müsst ihr das Repository einer Unix-Gruppe zuordnen, und alle Entwickler dieser Gruppe hinzufügen; die Rechteverwaltung entspricht hier genau der Rechteverwaltung von Unix.
Neue Dateien werden standardmäßig mit den Rechten der umask(2) des jeweiligen erstellt. Dies kann zu Problemen führen, wenn Schreibrechte für die Gruppe nicht gesetzt werden. Dazu kann man git init die option --shared=group übergeben. Somit wird sichergestellt, das alle von git erstellten Dateien die benötigten Zugriffsrechte haben.

Schon ist das Repository gehostet. Wenn euer Server die domain example.org hat, können die User nun über die URL ssh://euerlogin@example.org/gitroot/proj.git zugreifen.

Nachteil dieser Methode ist, dass ihr wenig Kontrolle darüber habt was die User mit dem Repository anstellen.

Methode 3b - Ein Shell Account

Zur Installation benötigt -
Zum Betreiben benötigt git, sshd
Öffentlicher Lesezugriff Ja
Öffentlicher Schreibzugriff Ja
Benötigte Rechte für User Öffentlicher SSH-Schlüssel eingetragen
Schwierigkeit Mittel

Alternativ dazu, jedem User einen eigenen Account zu geben kann man auch nur einen Account erstellen (traditionell „git“ genannt), und die Öffentlichen SSH-Schlüssel in das authorized_keys file einzutragen.

Dazu folgt man den Anweisungen von oben um ein Repository einzurichten, als Benutzername für den einen Accout verwenden wir „git“.

Anschließend generiert jeder Entwickler ein SSH-Schlüsselpaar (sofern er noch keines hat), mit folgendem Befehl:

ssh-keygen -t rsa -C "entwickler@example.com"

Dann schickt er den Öffentlichen Schlüssel, der Standardmäßig die Datei ~/.ssh/id_rsa.pub ist an den Systemadministrator.

Der Systemadministrator fügt nun den Öffentlichen Schlüssel jedes Entwicklers zum authorized_keys file des Users „git“ hinzu:

# cat /tmp/id_rsa.john.pub >> /home/git/.ssh/authorized_keys
# cat /tmp/id_rsa.josie.pub >> /home/git/.ssh/authorized_keys
# cat /tmp/id_rsa.jessica.pub >> /home/git/.ssh/authorized_keys

Nun kann jeder eingetragene Entwickler mit der Adresse ssh://git@example.org/gitroot/proj.git auf das Repository zugreifen.

Methode 3c - Mehrere Shell Accounts mit githooks(5) zur Zugriffskontrolle

Zur Installation benötigt -
Zum Betreiben benötigt git, sshd
Öffentlicher Lesezugriff Ja
Öffentlicher Schreibzugriff Ja
Benötigte Rechte für User Shell Account
Schwierigkeit Schwierig

Githooks sind Skripte, die in einem Git Verzeichnis liegen und zu bestimmten Zeitpunkten bei bestimmten Aktionen aufgerufen werden. Hier kann man einiges an Spielzeug einbauen, beispielsweise kann man den post-receive-hook dazu verwenden über bestimmte Ereignisse per E-Mail benachrichtigt zu werden. Welche hooks es gibt und wie sie verwendet werden, kann man auf der githooks(5) manpage nachlesen.

Unter anderem kann man die hook-Skripte zur Zugriffskontrolle benutzen. In diesem Fall ist für uns ein bestimmter hook interessant:

Der „update“ hook. Er wird aufgerufen nachdem git-receive-pack aufgerufen wurde (also die Objekte hochgeladen wurden) aber bevor die refs upgedatet werden.

Als Argument wird dem Hook folgendes übergeben:

  • Der Name des upzudatenden refs
  • Der Name des alten, im ref gespeicherten Objekts
  • Der Name des neuen, im ref zu speichernden Objekt

Gibt das Skript 0 zurück, wird der update des refs erlaubt. Gibt das Skript nicht 0 zurück, wird der update des refs verweigert.

Hier sieht man auch eine gute Möglichkeit zur Zugriffskontrolle: man schreibt ein Skript das überprüft ob die operation erlaubt ist und einen entsprechenden Rückgabewert liefert und legt es dann ins repository mit dem Namen $GIT_DIR/hooks/update. (Selbstverständlich muss der Interpreter für das Skript auf dem System installiert sein)

Glücklicherweise gibt es ein kleines Beispielskript von einem Git Entwickler, das recht gut für unsere Zwecke funktioniert. Das Original ist auf der Git-Webseite zu finden.

Hier ist eine minimal veränderte 3) und mit zusätzlichen Kommentaren besetzte Version des Skriptes:

#!/bin/sh
 
umask 002
 
# If you are having trouble with this access control hook script
# you can try setting this to true.  It will tell you exactly
# why a user is being allowed/denied access.
 
verbose=false
 
# Default shell globbing messes things up downstream
GLOBIGNORE=*
 
grant() {
  $verbose && echo >&2 "-Grant-     $1"
  echo grant
  exit 0
}
 
deny() {
  $verbose && echo >&2 "-Deny-      $1"
  echo deny
  exit 1
}
 
info() {
  $verbose && echo >&2 "-Info-      $1"
}
 
# Implement generic branch and tag policies.
# - Tags should not be updated once created.
# - Branches should only be fast-forwarded unless their pattern starts with '+'
case "$1" in
  refs/tags/*)
    # if the old tag exists git rev-parse will NOT fail, and therefore
    # deny will be exucuted.
    git rev-parse --verify -q "$1" && 
    deny >/dev/null "You can't overwrite an existing tag"
    ;;
  refs/heads/*)
    # No rebasing or rewinding
    if expr "$2" : '0*$' >/dev/null; then # if the old branch is 0000 (aka did not exist)
      info "The branch '$1' is new..."
    else
      # updating -- make sure it is a fast-forward
      mb=$(git-merge-base "$2" "$3") #find best common ancestor aka "merge-base"
      case "$mb,$2" in
        # if the merge-base is the old commit, it's called fast-forward
        "$2,$mb") info "Update is fast-forward" ;; 
        # if the merge-base it's something else, 
        # the merge is non-trivial, set noff variable to true
        *) noff=y; info "This is not a fast-forward update.";; 
      esac
    fi
    ;;
  *)
    deny >/dev/null \
    "Branch is not under refs/heads or refs/tags.  What are you trying to do?"
    ;;
esac
 
# Implement per-branch controls based on username
allowed_users_file=$GIT_DIR/info/allowed-users
username=$(id -u -n)
info "The user is: '$username'"
 
if test -f "$allowed_users_file" # if there's a file called allowed-users
then
  # cat the allowed-users file, filter empty lines and comment lines
  rc=$(cat $allowed_users_file | grep -v '^#' | grep -v '^$' |
    # pipe it to read
    while read heads user_patterns # read pairs of heads and user patterns
    do
      # does this rule apply to us?
      head_pattern=${heads#+} # remove prefixed plus if it's there
       # match updated ref against ref pattern
      matchlen=$(expr "$1" : "${head_pattern#+}")
      # if the ref pattern doesnt match completely, skip this line
      test "$matchlen" = ${#1} || continue
 
      # if non-ff, $heads must be with the '+' prefix
      test -n "$noff" &&
      # if it's not a fast forward merge and there is no + prefixing the 
      # pattern, skip this line
      test "$head_pattern" = "$heads" && continue 
 
      info "Found matching head pattern: '$head_pattern'"
      for user_pattern in $user_patterns; do # for every user in the line,
    info "Checking user: '$username' against pattern: '$user_pattern'"
    # match against the current user
    matchlen=$(expr "$username" : "$user_pattern") 
    if test "$matchlen" = "${#username}"
    then
      grant "Allowing user: '$username' with pattern: '$user_pattern'"
    fi
      done
      deny "The user is not in the access list for this branch"
    done
  )
  case "$rc" in # grant or deny depending on the outcome of the above loop
    grant) grant >/dev/null "Granting access based on $allowed_users_file" ;;
    deny)  deny  >/dev/null "Denying  access based on $allowed_users_file" ;;
    *) ;;
  esac
fi
 
allowed_groups_file=$GIT_DIR/info/allowed-groups
groups=$(id -G -n)
info "The user belongs to the following groups:"
info "'$groups'"
 
# same as above but with groups instead of users
if test -f "$allowed_groups_file"
then
  rc=$(cat $allowed_groups_file | grep -v '^#' | grep -v '^$' |
    while read heads group_patterns
    do
      # does this rule apply to us?
      head_pattern=${heads#+}
      matchlen=$(expr "$1" : "${head_pattern#+}")
      test "$matchlen" = ${#1} || continue
 
      # if non-ff, $heads must be with the '+' prefix
      test -n "$noff" &&
      test "$head_pattern" = "$heads" && continue
 
      info "Found matching head pattern: '$head_pattern'"
      for group_pattern in $group_patterns; do
    for groupname in $groups; do
      info "Checking group: '$groupname' against pattern: '$group_pattern'"
      matchlen=$(expr "$groupname" : "$group_pattern")
      if test "$matchlen" = "${#groupname}"
      then
        grant "Allowing group: '$groupname' with pattern: '$group_pattern'"
      fi
        done
      done
      deny "None of the user's groups are in the access list for this branch"
    done
  )
  case "$rc" in
    grant) grant >/dev/null "Granting access based on $allowed_groups_file" ;;
    deny)  deny  >/dev/null "Denying  access based on $allowed_groups_file" ;;
    *) ;;
  esac
fi
 
# if thread of execution has come to this point, apparantly no rules have been
# matched and the update is denied.
deny >/dev/null "There are no more rules to check.  Denying access"

Diese Datei erwartet, dass im Git Verzeichnis eine Datei liegt, die die Gruppen und/oder Usernamen ihren Befugnissen zuordnet. Diese sind $GIT_DIR/info/allowed-users und/oder $GIT_DIR/info/allowed-groups.

Eine solche Datei sieht zum Beispiel so aus:

refs/heads/master   junio bigboss
+refs/heads/pu      junio
refs/heads/cogito$  pasky
refs/heads/tmp/.*   .*
refs/tags/v[0-9].*  junio

Jede Zeile enthält:

  • Ein optionales plus
  • Eine Regex 4), die auf den ref passt, den die user updaten oder erstellen dürfen
  • Ein oder mehrere Regex für jeden User, der den ref updaten oder erstellen darf

In diesem Beispiel darf nur junio und bigboss auf den master branch zugreifen, pasky nur auf den „cogito“ branch, junio darf auf den pu branch sogar nicht fast-forward-updates machen, jeder darf unter refs/heads/tmp branches erstellen und nur junio darf Versions-Tags setzen.

Zusammenfassend muss man also für diese Methode die folgenden schritte durchführen:

  • Accounts für Entwickler erstellen
  • Repository einrichten (siehe oben)
  • das obige Beispielskript als $GIT_DIR/hooks/update abspeichern
  • eine $GIT_DIR/info/allowed-users und/oder $GIT_DIR/info/allowed-groups datei anlegen und nach den eigenen bedürfnissen konfigurieren
  • Testen, ob das ganze auch tut was es soll.

Methode 4 - gitosis

Zur Installation benötigt gitosis, python, python-setuptools
Zum Betreiben benötigt git, sshd, gitosis, python
Öffentlicher Lesezugriff Ja
Öffentlicher Schreibzugriff Ja
Benötigte Rechte für User Öffentlicher SSH-Schlüssel eingetragen
Schwierigkeit Mittel


Gitosis ist ein Satz von Python-Skripten, die das Management von mehreren Git Repositories vereinfachen. Es arbeitet auf der Basis von öffentlichen SSH-Schlüsseln und ist besonders in der Weise, dass die Konfiguration ebenfalls über ein Git-Repository erstellt.

Man erhält es entweder wie gewohnt über den Paketmanager eurer Distribution, oder man checkt das Git Repository aus, und installiert es dann wie folgt:

admin@example.com$ git clone git://eagain.net/gitosis.git
admin@example.com$ cd gitosis
admin@example.com$ sudo python setup.py install

Dazu wird ein Python Interpreter und die Python-Setuptools benötigt.

Anschließend benötigt gitosis einen eigenen Benutzer und ein Homeverzeichnis um zu laufen. Wenn der Paketmanager nicht bereits einen Benutzer erstellt hat (Achtung :!: Unter Debian und Ubuntu heißt der Benutzer „gitosis“ und nicht „git“), kann man das händisch machen. In diesem Beispiel wird der Benutzer „git“ heißen, und sein Homeverzeichnis soll in /srv/gitosis liegen. Das kann man mit adduser machen:

root@example.com# adduser \
            --system \
            --shell /bin/sh \
            --gecos 'git repository hosting' \
            --group \
            --disabled-password \
            --home /srv/gitosis \
            gitosis


Selbstverständlich muss das Homeverzeichnis für den Benutzer schreib- und lesbar sein. Nun muss der User „git“ den Befehl „gitosis-init“ aufrufen. Allerdings haben wir ihn seines logins beschnitten indem wir kein Passwort gesetzt haben. Jetzt schafft das Programm „sudo“ abhilfe, das Programme mit anderen Benutzerdaten ausführen kann, wenn der Ausführende genügend (in diesem Fall root-) Rechte besitzt. Der Aufruf sieht so aus:

admin@example.com$ cd /srv/gitosis
admin@example.com$ sudo -H -u git gitosis-init < admins-ssh-key.pub

Der switch „-u git“ bedeutet, dass das Programm als User „git“ ausgeführt werden soll „'-H“ bedeutet, sudo soll auch die HOME Variable auf das richtige Homeverzeichnis von „git“ setzen. gitosis-init erwartet von der Standardeingabe den öffentlichen SSH-Schlüssel des Administrators der Repositories ( Der Administrator kann seinen Key mithilfe von „cat ~/.ssh/id_rsa.pub“ auf der Konsole anzeigen lassen). Hier wird der Schlüssel gleich aus der datei admins-ssh-key.pub eingelesen.

gitosis-init erstellt nun zwei Verzeichnisse, einerseits das „gitosis“-Verzeichnis, andererseits das „repositories“-Verzeichnis, in denen die Repositories gespeichert werden.

Diese Verzeichnisse lassen wir aber unangetastet! Die weitere Konfiguration erfolgt nämlich nicht mehr auf dem Server, sondern über ein git repository auf das nur der Administrator zugreifen darf.
Der Administrator muss jetzt auf seinem Rechner zu Hause nämlich ein bestimmtes Repository klonen, das geht mit folgendem Kommando:

admin@admins-pc$ git clone git@example.com:gitosis-admin.git

Dazu muss selbtverständlich der SSH-Schlüsses des Administrators zum vorher auf dem Server eigetragenen Schlüssel passen.

Jetzt hat der Admin ein lokales Repository namens „gitosis-admin“, es enthält eine Konfigurationsdatei Namens „gitosis.conf“ und ein Verzeichnis mit dem Namen „keys“.

Wie ihr euch wahrscheinlich schon denkt kommen in das „keys/“-Verzeichnis die öffentlichen Schlüssel der beteiligten Entwickler:

admin@admins-pc$ ls keydir
administrator.pub anna.pub bernd.pub

Beim ersten clone enthält das Verzeichnis nur den Key des Administrators, ich habe hier noch zwei Schlüsseldataien hinzugefügt - „anna.pub“ und „bernd.pub“.

Eine gitosis.conf Datei sieht beispielweise so aus:

[gitosis]
 
[group gitosis-admin]
writable = gitosis-admin
members = fat-lobyte
 
[group tolle-gruppe]
writable = tollesprojekt
members = anna bernd
 
[group normale-gruppe]
writable = normalesprojekt
members = bernd

Hier ist fat-lobyte der Admin, anna und bernd können auf „tollesprojekt.git“ zugreifen und bernd hat noch etwas auf der Seite laufen und darf noch auf „normalesprojekt.git“ zugreifen.

Zu beachten ist, dass die Namen der Dateien für die Schlüssel den Benutzernamen zugeordnert werden. Beispielsweise gibt es den Benutzer „anna“ nur dann, wenn im „keys/“-Verzeichnis eine Schlüsseldatei mit dem Namen „anna.pub“ liegt.

Nachdem ihr die Schlüssel eurer Entwickler hinzugefügt habt und die „gitosis.conf“ geändert habt, müsst ihr sie commiten und auf den Server pushen:

admin@admins-pc$ git add keys/
admin@admins-pc$ git commit -a -m "Anna und Bernds Schlüssel und Projekt hinzugefügt" 
admin@admins-pc$ git push 

Die 1. Zeile stellt alle neuen Schlüssel im keys-Verzeichnis für den Commit bereit. 2. Zeile: Änderungen commiten, „-a“ heißt das alle bekannten geänderten Dateien auch commitet werden 3. Zeile: Änderungen hochladen

Die Änderungen werden sofort nach eurem Push wirksam (natürlich nur sofern der Push funktioniert), denn auf dem gitosis-admin.git Repository auf dem Server wird der „post-update“ Hook aufgerufen, der alles nötige in die Wege leitet.

Bis jetzt wurden allerdings nur die Erlaubnisse für die repositories gesetzt. Die Repositories auch tatsächlich zu erstellen liegt in der Verantwortung der User. Das funktioniert ganz einfach durch einen Push auf das neue Repository:

anna@annaspc$ git remote add origin git@example.com:tollesprojekt.git
anna@annaspc$ git push origin master <andere branches...>

Schon ist das Repository erstellt. Anna kann jetzt allerdings nur auf das Repository tollesprojekt.git zugreifen. Versucht sie auf bernds eigenes Projekt zuzugreifen, gibts eine Fehlermeldung:

anna@annaspc$ git remote add origin git@example.com:normalesprojekt.git
anna@annaspc$ git push origin master
ERROR:gitosis.serve.main:Repository read access denied
fatal: The remote end hung up unexpectedly

Somit ist man auch schon fertig.

Hier nochmals eine Zusammenfassung der notwendigen Schritte, und wer sie wo ausführen muss:

  • Administrator auf dem Server: gitosis installieren.
  • Administrator auf dem Server: Benutzer hinzufügen
  • Administrator auf dem Server: gitosis-init mit dem richtigen Benutzer und dem richtigen SSH-Key aufrufen
  • Administrator auf seinem Arbeitsplatz: gitosis-admin Repository auschecken
  • Administrator auf seinem Arbeitsplatz: gitosis konifigurieren, Änderungen speichern und auf den Server pushen
  • Benutzer auf ihren Arbeitsplätzen: Ihre Repositories pushen, um sie auf dem Server zu erstellen

Methode 5 - gitolite

Zur Installation benötigt git, gitolite, perl
Zum Betreiben benötigt git, sshd, gitolite, perl
Öffentlicher Lesezugriff Ja
Öffentlicher Schreibzugriff Ja
Benötigte Rechte für User Öffentlicher SSH-Schlüssel eingetragen
Schwierigkeit Mittel

Man kann gitolite quasi als eine Weiterentwicklung von gitosis betrachten. Auch hier erfolgt die Steuerung über ein Git Repository auf dem Arbeitsplatzrechner des Admins.
Der große Vorteil von gitolite gegenüber gitosis ist allerdings, dass man die Benutzerrechte sehr viel genauer einstellen kann, denn während man bei gitosis nur den Zugriff zu ganzen Repositories steuern kann, kann man mit gitolite den Zugriff auf einzelne Branches und Versions-Tags steuern (ganz ähnlich dem update-hook aus der Methode 3c).

Gitolite ist ein einigermaßen komplexer Satz an Skripten, zumindest hat sich der Autor einiges dabei gedacht.
Ich stelle hier nur eine minimale Zusammenschau des Installations- und Konfigurationsvorganges zusammen, denn Gitolite ist sehr ausführlich und dokumentiert. Bitte tut euch selbst und dem Autor einen Gefallen und lest die Dokumentation! Anscheinend ist er schon ein bisschen genervt, wie eine commit-message verrät:

*major* doc revamp
 
people will NOT read documentation, especially the bloody install
documentation.  I'm about ready to throw in the towel and declare
gitolite unsupported, take-it-or-leave-it.
 
But I'm making one last attempt to refocus the install doc to better
suit the "I know I'm very smart and I dont have to read docs so it's
clearly your fault that I am not able to install gitolite" crowd.
 
[...]

Also, bitte lest die Dokumentation, beispielsweise hier: http://github.com/sitaramc/gitolite/tree/pu/doc

Um gitolite zu installieren wird Perl (sowohl auf dem Server als auch auf dem Arbeitsplatz des Administrators) benötigt. Man muss zunächst auf dem Server einen Benutzer mit eigenem Homeverzeichnis erstellen:

root@example.com# adduser \
    --system \
    --group \
    --home /srv/gitolite \
    --disabled-password \
    --gecos 'gitolite repository hosting' gitolite \
    --shell '/bin/sh'

Jetzt muss der Administrator irgendwie seinen öffentlichen SSH-Schlüssel in die ~/.ssh/authorized_keys-Datei des Benutzers gitolite bekommen (gegebenenfalls hier nachlesen).
Dies ist notwendig, denn sonst funktioniert das Installationsskript von gitolite nicht.
Macht das beispielsweise so :

admin@admins-pc$ scp ~/.ssh/id_rsa.pub admin@example.com:
admin@admins-pc$ ssh admin@example.com
admin@example.com$ su -
root@example.com# mkdir /srv/gitolite/.ssh
root@example.com# cp ~/id_rsa.pub /srv/gitolite/.ssh/authorized_keys
root@example.com# chmod -R go-rwx /srv/gitolite/.ssh
root@example.com# chown -R gitolite:gitolite /srv/gitolite/.ssh

Der Admin muss jetzt überprüfen ob er sich tatsächlich als User gitolite einloggen kann, ohne das Passwort einzugeben, z.B. so:

admin@admins-pc$ ssh gitolite@example.com pwd
/srv/gitolite

Das ist notwendig, ansonsten kann das Installationsskript von gitolite nicht arbeiten.

Jetzt muss man ähnlich wie bei gitosis zuerst das Repository auschecken. Dies geschieht aber auf dem Arbeitsplatz des Administrators! Anchließend startet man das Installationsskript:

admin@admins-pc$ git clone git://github.com/sitaramc/gitolite
admin@admins-pc$ cd gitolite/src
admin@admins-pc$ ./gl-easy-install -q gitolite example.com admin

Der Schalter -q gibt an, dass das Skript „leise“ sein soll und wenige Ausgaben machen soll, außerdem werden einige Fragen übersprungen.
Anschließend kommt der Name des Git-benutzers (in unserem Fall gitolite), der Hostname des Servers, und der Name, den sich der der Administrator gibt, in dieser Reihenfolge. Es lohnt sich das Skript ./gl-easy-install ohne Parameter aufzurufen und dann den Hilfetext zu lesen.

Das Installerskript macht nun zunächst einige Ausgaben, die ihr lesen solltet:

*Your* URL for cloning any repo from this server will be
    gitolite:reponame.git
 
Note: If you are upgrading and you set a host nickname during initial
      setup, please use that host nickname instead of gitolite
      above.
 
*Other* users you set up will have to use
    gitolite@example.com:reponame.git
However, if your server uses a non-standard ssh port, they should use
    ssh://gitolite@example.com/reponame.git


Achtung! Das Installerskript erstellt ein neues(!) SSH-Schlüsselpaar, das gleich benannt ist wie der Name den sich der Admin gegeben hat (in diesem Fall ~/.ssh/admin und ~/.ssh/admin.pub). Dieses benötigt ihr, um die Repositories zu verwalten.
Außerdem wird in eurem Homeverzeichnis ein Repository erstellt, mit dem ihr die Repos auf dem Server verwalten könnt; es heißt gitolite-admin. Ihr könnt das Verzeichnis aber auch woanders hinlegen.

In diesem Verzeichnis sind nun ähnlich wie bei gitosis die Schlüssel und die Konfiguration:

admin@admins-pc$ cd ~/gitolite-admin
admin@admins-pc$ ls
conf  keydir
admin@admins-pc$ ls conf/ keydir/
conf/:
gitolite.conf
 
keydir/:
admin.pub anna.pub bernd.pub

Wie erwartert enthält das keys/-Verzeichnis die öffentlichen Schlüssel der Entwickler.
Der Dateiname des Schlüssels wird den Benutzernamen zugeordnert. Beispielsweise gibt es den Benutzer „anna“ nur dann, wenn im „keys/“-Verzeichnis eine Schlüsseldatei mit dem Namen „anna.pub“ liegt.
Zuerst enthält das Verzeichnis nur den Key des Administrators, ich habe hier noch zwei Schlüsseldataien hinzugefügt - „anna.pub“ und „bernd.pub“.

FIXME


Hat dir diese Antwort geholfen? Wenn nicht oder wenn du Verbesserungs- bzw. Erweiterungsvorschläge hast, dann schreib das bitte einfach auf die Diskussionsseite.

1)
woraus genau es besteht könnt ihr auf gitrepository-layout(5) nachlesen
2)
ist der commit ein merge-commit, so hat er mehrere vorhergehende commits
3)
/bin/bash durch /bin/sh ersetzt und funktionseklarationen geändert damit das Skript auch mit dash funtioniert
4)
Regulärer Ausdruck, siehe regex(7)