Deployment mit git, GitHub und Webhooks

deployment

Das freie Versionsverwaltungssystem Git eignet sich nicht nur für umfangreiches Codemanagment. Zudem bietet es die Möglichkeit, automatisch auf verschiedensten Umgebungen Code auszuliefern – dank der in GitHub integrierten Webhooks. Eine Anleitung für EntwicklerInnen.

Was genau passieren soll

Wir haben folgendes Setup:

  • eine lokale Entwicklungsumgebung
  • ein remote Testsystem
  • der Code läuft über GitHub

Wenn in der lokalen Entwicklungsumgebung ein git push origin master ausgeführt werden soll, dann sollte auf dem remote Testsystem ein git pull origin master getriggert werden.

git pull über http & www-data

Zu Beginn richten wir das lokale Testsystem ein. Das müssen wir so vorbereiten, dass der Befehl git pull auch über einen http-Zugriff ausgeführt werden kann. Damit das geht, müssen wir ein paar Dateien anlegen. Folgende Ordner-Struktur kann ich dafür empfehlen:

Aus Sicherheitsgründen sollte der Webhook-Ordner nicht in den versionierten Code integriert werden. Zum Grund hierfür gleich mehr.

Zunächst legen wir ein Shell-Script im Webhook-Ordner an. Dieses macht im Grunde nichts anderes, als in das entsprechende Verzeichnis zu gehen, um dort den Pull-Befehl auszuführen:

Die Datei heisst bei mir git-pull.sh – ihr solltet sie über chmod a+x git-pull.sh ausführbar machen.

Als nächstes müssen wir überprüfen, ob der www-data-User (auf euren Maschinen kann der Webserver ein anderer User sein) die Rechte besitzt, den git pull Befehl auszuführen. Dafür wechseln wir in das Webhook-Verzeichnis und führen folgenden Befehl aus:

Dieser Befehl führt unser Script als User www-data aus. Wir gaukeln dem System sozusagen genau die Rechte vor, die es aus einem HTTP-Zugriff bekommt. In den meisten Fällen erscheint dabei folgende Fehlermeldung:

error: cannot open .git/FETCH_HEAD: Permission denied

Das bedeutet, dass bestimmte Verzeichnisse des Repositories für den User www-data nicht erreichbar sind. Das müssen wir ändern. Dafür wechseln wir in das entsprechende Projekt-Verzeichnis und führen den nachfolgenden Befehl aus:

Dieser Befehl ändert den Besitzer des Git-Verzeichnisses auf jenen des Benutzers und auf die Gruppe vom User des Webservers. Ein weiterer Test sollte keine weiteren Fehler mehr hervorrufen.

Zusätzlich ist es eine gute Idee, dass jede neu angelegte Datei die Besitzrechte vererbt bekommt:

Wenn das erledigt ist, können wir eine PHP-Datei im Webhook-Verzeichnis anlegen. Ich nenne diese überraschenderweise git-pull.php:

Wir wechseln also in das Webhook-Verzeichnis (weil PHP das ja nicht wissen kann) und führen das Shell-Script aus. Mehr ist dabei erst einmal nicht zu tun. Wechseln wir also zum Browser unserer Wahl und rufen die PHP-Datei auf. Wenn ihr alles richtig gemacht habt, dann sollte dort jetzt “Everything is up to date” oder Ähnliches stehen. Damit könnt ihr euch sicher sein, dass ihr alles richtig gemacht habt.

Jetzt müssen wir noch den Webhook einrichten. Hier gibt es einen ausführlichen Guide dafür. Um die Sache aber kurz zusammenzufassen:

  1. Geht auf https://github.com/[user]/[repo]/settings/hooks/
  2. Fügt einen Webhook über den Button oben rechts hinzu
  3. Gebt die Adresse des PHP-Scripts an
  4. Gebt pro forma einen Secret-Key ein – etwas Kryptisches, wie 47c74185ced5f6fdc418acd22eccf8a52943da91. Dazu kommen wir später.
  5. Wir brauchen das Event nur beim Push-Event. Alle anderen Events sind für uns nicht relevant.
  6. Aktiviert den Webhook und speichert ihn

Sobald der Webhook aktiv ist, wird eine Payload an das PHP-Script gesendet. Ihr könnt über die Hook-Details den Request und die Response einsehen.

Sicherheit

Grundsätzlich sind wir damit fertig. Dennoch ist es mehr als sinnvoll, noch ein paar Sicherheitsbarrieren einzubauen. Als erste und einfachste Barriere nutze ich HTTP-Auth, die das Verzeichnis grundsätzlich schützt. Wichtig ist, dass der HTTP-Aufruf im Webhook-Setup von github entsprechend geändert wird.

Meine fertige Struktur sieht dann normalerweise so aus:

Als weitere Schutzmaßnahme nutze ich den Secret-Key vom Request und überprüfe diesen über das PHP-Script. Sollte der Secret nicht matchen, dann bricht das Script natürlich ab:

Findige Entwicklerinnen finden bestimmt noch mehr Möglichkeiten das Setup abzusichern.

Fazit

Natürlich könnte man auch Capistrano oder ähnliche Werkzeuge als Deployment-Software nutzen. Ich bin jedoch ein Fan davon, mit möglichst wenigen Tools zu arbeiten, um maximal integrativ zu sein. Mit git, GitHub und deren Webhooks lassen sich automatische Deployments von kleinen Projekten einrichten – einfach und recht flott.

Beitrag teilen

Author Avatar

Thomas ist Senior-WordPress-Entwickler bei der Inpsyde GmbH, der aber auch über den Tellerand hinaus schaut und sprachunabhängig entwickeln kann. Der 1985 geborene Wahl-Düsseldorfer macht in seiner Freizeit Musik, spielt Spiele, kocht gern und verbringt seine Zeit am liebsten mit seiner Frau.

Auch interessant:

broken

Vom Kundenwunsch zum Quellcode. 10 Fragen an unseren Plugin-Entwickler Andre Peiffer.

von Michael Firnkes

Wie übersetzt man Kundenanforderungen in Quellcode? Was sind die Herausforderungen dabei? Und gibt es etwas, das einen WordPress-Entwickler aus der Fassun ...

Weiterlesen
MarketPress Adventskalender 10

Adventskalender Tag 10 – Rekursion in Hooks verhindern

von Thomas Herzog

Der heutige Adventskalenderbeitrag richtet sich speziell an die Entwickler unter uns, die mit Rekursionen zu kämpfen haben. Angenommen, wir befinden uns i ...

Weiterlesen
MarketPress Adventskalender 07

Adventskalender Tag 7 – Mini-Plugin: Bekannte Spam-IPs in WordPress blockieren

von Christian Brückner

Bereits am Dienstag in unserem dritten Türchen hat Toscho einige nützliche SQL-Statements aufgezeigt um die Kommentare in WordPress zu analysieren und si ...

Weiterlesen
MarketPress Adventskalender 2

Adventskalender Tag 2 – PHP Debuggen in der Browser-Console

von Frank

Es gibt diverse Libraries und Tutorials im Netz um das Debuggen aus PHP heraus in die Console des Browsers zu verlagern. Nicht selten kommen dabei recht ko ...

Weiterlesen

Kommentare

7 Kommentare

  1. #1

    Ich hab einen Cronjob mit git-pull 🙂

  2. #2

    Hallo,

    irgendwo habe ich einen gedanklichen Klemmer.
    Im Skript auf dem Entwicklungsserver wird ein ‘pull‘ über den remote ‘origin‘ und dem branch ‘master‘ durchgeführt. Was ist wenn mehrere feature branch vorhanden sind ?
    Siehe gitflow

    Ist es nicht besser ein remote anzulegen welcher 2 URL hat ?
    > git remote add set-url

    Mit freundlichen Grüßen

    Stephan

  3. #4

    Hey Thomas,
    vielen Dank für dein Skript. Aktuell klemmt das von Dir erstellte PHP-Skript noch ein wenig, bzw. der Fehler leigt bei mir.

    Dazu hab ich mehrere Fragen:

    In meinen tcpdump sehe ich das von Gitlab über die Webhooks der Secret-Token im Post gesendet wird. Aber wie der den von deinem PHP-Skript analysiert bzw. geparsed. Ich erhalte ein “X-Hub-Signature’ is missing.”
    Auszug aus meinem tcpdump:
    POST /git-webhook/git-pull.php HTTP/1.1
    Content-Type: application/json
    X-Gitlab-Event: Push Hook
    X-Gitlab-Token: HalloTcpdumpKannstDuMeinenSecretTokenSniffen?
    Connection: close
    Host: git-lab-client-02
    Content-Length: 2504

    Eine X-Hub-Signature’ enthält der POST nicht. eine Anpassung in deinem Skript auf X-Gitlab-Token: führte jebenfalls nicht zum Erfolg.
    Ich versuchte mir ein simples PHP-Skript zu bauen, indem nur mal abgefragt wird ob im HEADER/POST ein X-Gitlab-Token enthalten ist.
    <?php
    if(isset($_SERVER[ 'X-Powered-By:' ]))
    echo "X-Gitlab-Event: angegeben”;
    else
    echo “kein X-Gitlab-Event: angegeben”;
    ?>
    Selbst das führte nicht zum Erfolgt. Wo ist mein Denkfehler?
    Grüße Andreas

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

Du kannst folgende HTML Tags verwenden: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">