Moin,

Ich möchte Euch heute mal grob erklären, wie man seine Webseite mithilfe eines Hooks für Git automatisch auf dem gleichen Server deployen kann.
Auf meinem V-Server läuft Debian mit Gitolite, weshalb ich mich auf die Eigenschaften von dieser Distribution und diesem Hosting Tool beziehen werde. Vermutlich sind aber viele der angesprochenen Aktionen bei anderen Lösungen identisch bzw. müssen nur leicht abgeändert werden.

Bitte Lesen Ich weise darauf hin, dass ich keinerlei Haftung für jeglichen auftretenden Fehler, gelöschte Dateien oder defekte an eurem System übernehme! Die folgenden Scripte habe ich in eigener Arbeit und Recherche erstellt und nur auf meinem System ausprobiert. Solltet Ihr einen gravierenden Fehler feststellen, eine „Sicherheitslücke“ finden oder eine Verbesserungsmöglichkeit haben, so teilt es mir in den Kommentaren, via Mail, Twitter oder auf andere Kommunikationswegen mit!

Ich gehe davon aus, dass Gitolite (oder ähnliche Software) bereits auf eurem Server läuft, Ihr dort ein Repository zum Testen angelegt habt und es lokal bereits geklont habt. In dem Tutorial arbeiten wir mit dem Repository test.

Einrichten des serverseitigen Hooks

Bei Gitolite befinden sich die Repositories im Verzeichnis /home/git/repositories, in unserem Fall müssen wir in das Verzeichnis /home/git/repositories/test.git wechseln. Dort befinden sich einige Ordner, uns interessiert jedoch nur hooks.
Hier gibt es bereits einige Beispiele (*.sample), wir erstellen aber einen neuen Hook.

In die Datei post-receive fügen wir folgenden Code ein:

#!/bin/bash

umask 002

htdocs="/var/www/PROJECT_NAME"
deploymentBranch="deploy"

echo "> post-receive hook"

while read oldrev newrev refname
do
        branch=$(git rev-parse --symbolic --abbrev-ref $refname)
        echo "> branch is $branch"
        if [ "$branch" == "$deploymentBranch" ]; then
                echo "> deployment hook"
                echo "> checking out branch to $htdocs"
                GIT_WORK_TREE=$htdocs git checkout -f $deploymentBranch
        fi
done

Dieser Hook überprüft, auf welcher Branch der erhaltene Commit eintrifft und macht ein checkout auf dieser Branch. Fangen wir aber mal am Anfang an.

Ein solcher Git Hook ist in diesem Fall ein normales Shell-Script, welches mit dem Shebang #!/bin/bash eingeleitet wird.

Darauf folgt dann mit umask 002 das Setzen der Rechte von allen neu erstellten Ordnern und Dateien. Dies bewirkt, dass alle Dateien und Ordner mit den rechten rwxrwxr-x erstellt werden. Den Grund hierfür werdet Ihr noch später erfahren.

Hinweis Auf das Rechtesystem von Linux werde ich nicht genauer eingehen, da dies den Rahmen sprengen würde.

Mit htdocs="/var/www/PROJECT_NAME können wir das Hook-Script ganz einfach wiederverwenden und den Wert PROJECT_NAME durch den des jeweiligen Projektes – oder Ordners in den deployed werden soll – ersetzen.
Auch deploymentBranch="deploy" bezweckt den einfach Wechsel des Branch-Namens auf welchem deployed werden soll.

Nun kommt der erste echo-Befehl. Von diesen folgen noch einige und diese dienen letztendlich eher dem Zweck des Debuggens. Mit etwas Englischkenntnissen sollten diese keine Hürde darstellen.

Kommen wir nun zum interessantesten Teil, der While-Schleife. Wenn Ihr etwas in das Remote-Repository pusht, dann kann dieser Push auch mehrere Commits auf verschiedenen Branches enthalten. Deshalb gehen wir über die erhaltenen Commits und überprüfen diese.

branch=$(git rev-parse --symbolic --abbrev-ref $refname) dient als Trick, um den Branch des aktuell betrachteten Commits herauszufinden.

Die Bedingung if [ "$branch" == "$deploymentBranch" ]; then macht nun den Vergleich zwischen dem Wert in der Variable branch und in diesem Fall dem Wert deploy, welcher für die Variable deploymentBranch gesetzt ist.

Nun kommt der letzte Schritt des Hooks, der checkout vom Branch in das Deployment-Verzeichnis. Der Befehl GIT_WORK_TREE=$htdocs git checkout -f $deploymentBranch sagt Git, dass es in dem Verzeichnis /var/www/PROJECT_NAME arbeiten soll und dort ein checkout von der Branch deploy machen soll. Der Parameter -f bewirkt, dass das checkout erzwungen wird, d.h. jegliche Änderungen an von Git verfolgten Dateien werden überschrieben.

Vorbereiten der Nutzung des Hooks

Bevor wir nun den Hook testen können, müssen noch einige Schritte getan werden.

  1. Damit sowohl der Webserver als auch der Git Nutzer (im Falle von Gitolite ist dies git) Zugriff auf die Dateien und Ordner hat, müssen wir den Benutzer git der Gruppe www-data hinzufügen.
    Dies geschieht mit Hilfe von folgendem Befehl:
    useradd -a -G www-data git

Hinweis Unter Umständen kann der Befehl zum Hinzufügen von Benutzergruppen sich je nach Distribution unterscheiden

  1. Das Verzeichnis /var/www/PROJECT_NAME muss das Sticky-Bit gesetzt haben, damit die besitzende Gruppe für jede neue Datei und jeden neuen Ordner gilt. Dies können wir über folgenden Befehl realisieren:
    chown git:www-data /var/www/PROJECT_NAME && chmod gu=rwx /var/www/PROJECT_NAME && chmod g+s /var/www/PROJECT_NAME
  2. Bearbeiten der Hook-Konfiguration
    Hierfür begeben wir uns wieder in unseren post-receive-Hook und ändern htdocs und deploymentBranch zu den gewünschten Werten
  3. Nun sollte der Hook soweit bereit sein getestet zu werden. Hierfür müssen wir ihn nur noch ausführbar machen und zur Sicherheit limitieren wir dies auf den Benutzer git:
    chown git:git post-receive && chmod u=rwx post-receive && chmod go-rws post-receive

Testen des Hooks

Kommen wir nun endlich zum Testen. Wir arbeiten in der Branch deploy und erstellen eine index.php mit folgendem Inhalt in eurem lokalen Repository:

<?php
echo "Git Hooks Rulez!";
?>

Nun erstellt einen Commit mit dieser Datei. Solltet Ihr über die Kommandozeile arbeiten, so solltet Ihr bei einem Push den Output von unserem Hook erhalten, arbeitet Ihr z.B. mit SourceTree, dann setzt ein Häckchen bei Show Full Output (ich gehe von der englischen Version aus).

In SourceTree erhaltet Ihr eine ähnliche Ausgabe wie diese:

Pushing to git@domain.com:test

remote: > post-receive hook[K
remote: > branch is deploy[K
remote: > deployment hook[K
remote: > checking out branch to /var/www/test[K

Nun dürftet Ihr die Ausgabe Git Hooks Rulez! bei einem Aufruf der Seite erhalten. Der Hook ist nun voll einsatzfähig.

Beispiele für Anpassungen des Hooks

Jedoch gestaltet es sich nicht unbedingt immer so einfach und es gehört mehr zum Deployment als nur das reine „Kopieren“ der Daten.

Am Beispiel von dem Framework Laravel werde ich ein paar Anpassungen am Hook vornehmen. In der Regel wird es bei einem Laravel-Projekt um zwei Dinge gehen, die nach einer aktualisierung des Codes nötig ist:

  1. Das Installieren der Abhängigkeiten
    Laravel arbeitet mit dem Abhängigkeitsmanager Composer, welcher anhand der composer.lock-Datei weiß, welche Version von welcher Abhängigkeit installiert werden muss.
  2. Die Migration der Datenbank
    Nachdem die Abhängigkeiten installiert wurden – dies ist wichtig, da die Migrationen ggf. auf Änderungen der Abhängigkeiten angewiesen sind – kann nun die Datenbank migriert werden. D.h Änderungen an bereits vorhandenen Tabellen oder das erstellen von neuen Tabellen muss ausgeführt werden.

Beide Schritte sind zum Glück leicht zu ermöglichen und die Rückgabe wird auch an den Benutzer zurückgegeben, d.h. sollte ein Befehl fehlschlagen, erhaltet Ihr die Rückmeldung über den Output auf der Kommandozeile oder in SourceTree.

Hier also ein Beispiel inkl. der beiden Schritte.

#!/bin/bash

umask 002

htdocs="/var/www/test"
deploymentBranch="deploy"

echo "> post-receive hook"

while read oldrev newrev refname
do
        branch=$(git rev-parse --symbolic --abbrev-ref $refname)
        echo "> branch is $branch"
        if [ "$branch" == "$deploymentBranch" ]; then
                echo "> deployment hook"
                echo "> checking out branch to $htdocs"
                GIT_WORK_TREE=$htdocs git checkout -f $deploymentBranch

                echo "> installing composer dependencies"
                composer -d="$htdocs" install

                echo "> migrating database changes"
                php $htdocs/artisan migrate -n
        fi
done

Der Befehl composer -d="$htdocs" install führt die Installationsroutine von Composer in dem angegebenen Verzeichnis (-d) aus.

php $htdocs/artisan migrate -n führt das Artisan-Tool von Laravel aus, welches die Migration durchführt. Der Parameter -n sorgt dafür, dass das Tool keinerlei Fragen stellt, sondern einfach nur die Migrationen durchführt.

Ende

Ich bedanke mich für das – hoffentlich – aufmerksame Lesen und hoffe, dass jeder etwas für sich mitnehmen konnte aus diesem Beitrag.
Wie bereits oben erwähnt, würde ich mich sehr über Feedback freuen und stehe gerne bei Fragen zur Verfügung.