Alle lieben Github (Linux-Magazin, Januar 2013)

Jeder kennt Github als Entwicklungsschmiede und Archiv für Open-Source-Code. Der Service bietet aber auch eine einfach zu bedienende Web-Oberfläche und eröffnet damit ganz andere Möglichkeiten, wie zum Beispiel als Content-Management-System für einfache Webseiten zu fungieren.

Auf Github.com arbeitet die Open-Source-Gemeinde an ihren Projekten, und die ehemalige Startup-Firma aus San Francisco hat auf diese Weise einige Berühmtheit erlangt. Mittlerweile haben sich sogar beachtliche finanzielle Erfolge eingestellt. Und nicht nur das: Die Firma gilt im erweiterten Silicon Valley schon als Vorzeigebeispiel dafür, wie man talentierte Entwickler anlockt ([4]).

Punktfiles bunkern

So nimmt es nicht Wunder, dass die in der offenen Basisversion kostenlos angebotenen Repositories, der damit verbundene Plattenplatz und zuverlässiges Web-Hosting für allerlei Daten herhalten, die nur im weiteren Sinne als Open-Source-Projekte gelten dürften. Wer hat sich nicht schon darüber geärgert, dass auf einem neu bezogenen Rechner über Jahre gepflegte Konfigurationsdateien für die Shell (z.B. .bashrc) oder den Lieblingseditor (.vimrc) dort nicht installiert waren? Da in diesen Dateien selten Geheimnisse liegen, hat es sich eingebürgert, diese auf Github in einem Repository namens dotfiles einzubunkern. Landet man in einer sträflich unterkonfigurierten Umgebung, sind die heimischen Dateien nur einen Download auf einer leicht zu findenden Internetseite entfernt.

Schlummernde Möglichkeiten

Aber es geht noch mehr. Nicht jeder Github-Nutzer weiß zum Beispiel, dass Entwickler dort auch ganz normale Webseiten hosten können. Verzichtet man auf dynamisch generierte Inhalte und beschränkt sich auf HTML, JavaScript und Fotos, lässt sich mit einem kostenlosen Github-Account durchaus eine private Webseite erstellen und online halten. Auf Github gehostete Inhalte sind damit allerdings öffentlich als Open Source zugänglich, aber das sind sie auf einer statischen Webseite implizit sowieso.

Abbildung 1: Auf Github.com können Technikmuffel Textdateien auch direkt im Browser editieren und hosten.

Dazu legt der Github-Nutzer in einem neu angelegten Repository einen neuen Branch namens gh-pages an, stellt die Datei index.html hinein und führt ein git commit mit anschließendem push aus. Anschließend darf er unter http://username.github.com/reponame staunend mit ansehen, wie seine neu erstellte Website auf einmal im Internet steht. Kostenlos, und zuverlässig von Githubs Infrastruktur am Laufen gehalten.

Abbildung 2: Die automatisch von Github aus der Source im Branch gh-pages generierte Projektseite.

Abbildung 1 zeigt den im Github-Repository perlsnapshot im Zweig gh-pages angelegten "Source Code" einer statischen HTML-Seite. Github generiert daraus automatisch, unmittelbar nach einem Push, eine auf einem öffentlich zugänglichen Webserver liegende und unter einer recht kurzen URL abrufbare Projektseite (Abbildung 2).

Los geht's

Um den erforderlichen Branch gh-pages in einem neu generierten Repo anzulegen, wird dieses zunächst mit

    git clone git@github.com/user/repo.git

geklont und im Branch master ein einziger Commit (z.B. einer README-Datei) ausgeführt. Den für die Projektseite notwendigen Branch gh-pages erzeugt dann ein nachfolgendes

    git checkout -b gh-pages

In diesem Branch legt man die Datei index.html und eventuell von dort referenezierte Bilder ab (zum Beispiel in einem Unterverzeichnis fig), und zimmert sie mit git add und git commit im Repo fest. Die neuesten Branch-Daten landen anschließend mit

    git push origin gh-pages

bei Github, das daraufhin unmittelbar die automatische Webseitengenerierung anstößt.

GUI oder Kommando?

Nun weiß zwar heutzutage jeder Software-Entwickler, der sein Geld wert ist, wie man git von der Kommandozeile aus bedient und Updates ins Github-Repo hochlädt, doch technikfeindlichem Personal wäre das nur schwer beizubringen. Glücklicherweise bietet Github eine leicht zu bedienende Weboberfläche, die auf Knopfdruck Textdateien im Repo verändert.

Ein Klick auf den "Edit"-Button in Abbildung 1 und schon zeigt der Browser ein editierbares Textwindow an, in dem der User Änderungen am HTML-Code der Webseite vornehmen darf. Nach Abschluss der Korrektur drückt der User "Save" und trägt optional noch einen Änderungskommentar ins Nachrichtenfeld ein, worauf Github einen Commit im Github-Repository mit der aktuellen Textversion auslöst und die Änderung damit samt Historie festhält. Das Verfahren funktioniert allerdings nur bei Textdateien, Fotos checkt man entweder von der Kommandozeile oder mittels einer der auch für Linux verfügbaren grafischen Git-Frontends ein.

Wo ist der Haken?

Dynamische Webseiten à la PHP kennt das Verfahren zweifellos nicht, da der Github-Webserver keinen entsprechenden Interpreter konfiguiert. Wem aber HTML zu unhandlich ist und wer lieber kompakte Text-Sourcen wie in einem Content-Management-System verwaltet, die dann automatisch mit HTML umrankt werden, dem kann geholfen werden.

Nach jedem erfolgreichen push von lokalen Commits ins öffentliche Github-Repo springt der Github-Server entsprechend vorkonfigurierte "Service-Hooks" an. So erfährt zum Beispiel der CI-Service travis-ci.org mittels eines post-receive-Webhooks sofort von Änderungen im Entwicklungsbaum und kann die Testsuite anwerfen, um zu sehen, ob der Build mit dem neuesten Code noch funktioniert ([5]).

Abbildung 3: Der "Webhook" auf Github schickt eine Nachricht an einen beliebigen Webserver, falls Änderungen am Repo erfolgten.

Unter "Admin" und der Sektion "Service Hooks" darf der Repo-Besitzer einen sogenannten "Webhook" definieren, den Github jedes Mal anspringt, falls neue Commits mittels "git push" im Online-Repo gelandet sind (Abbildung 3). So erfahren fremde Webseiten allgemein von den Änderungen. Als sogenannte "Payload" (bezahlte Fracht) legt Github jedes Mal JSON-formatierte Meta-Daten bei, die darüber Auskunft geben, in welchem Repo Änderungen eingingen, welche Dateien betroffen waren und wie die SHA1-IDs vor und nach dem Commit aussehen. Dabei können in einem Push durchaus nicht nur ein sondern sogar mehrere Commits ankommen, und für jeden findet sich ein Eintrag im Feld commits, das einen Array von Einzel-Commits enthält.

Abbildung 4: Die "Payload" des Commits im JSON-Format signalisiert der Website, welche Dateien sich im Repo verändert haben.

Abbildung 4 zeigt die JSON-Daten nach einer Commit/Push-Kombination der Projektseite index.html. Ein in Perl geschriebenes CGI-Skript wie das in Listing 1 nimmt den Request entgegen, fieselt mit einem JSON-Parser interessante Details heraus und startet entsprechende Aktionen.

CM für Arme

Abbildung 5: Diese vereinfachte Textdatei editiert der technikfeindliche User.

Abbildung 6: Der Template-Header enthält den HTML-Markup, um den sich der User nicht mehr kümmern muss.

Listing 1 implementiert ein simples Content-Management-System, das seine Sourcen auf Github liegen hat. Es baut mittels Perls Template-Modul (erhältlich auf dem CPAN) Textbausteine in Templates zusammen und bietet sie auf dem eigenen Webserver an. Auch technisch unbedarfte User können nun die Text-Dateien 001.txt, 002.txt, usw. auf der Github-UI editieren (Abbildung 5). Sie enthalten Anweisungen zum Einbinden von Template Dateien, die einleitende und abschließende HTML-Sequenzen einbinden (Abbildung 6), ohne dass sich der User mit technischen Details abquälen müsste. Klickt der User den Save-Button, speichert Github die Änderungen im Repo ab und feuert den Webhook auf das CGI-Skript auf einen privaten Webserver ab. Dieses findet über die JSON-Daten heraus, welche .txt-Dateien verändert wurden, frischt seinen lokalen Klon des Repositories auf und wirft für diese den Template-Generator an. Das Diagramm in Abbildung 7 zeigt den Ablauf.

Abbildung 7: Githubs Webhook benachrichtigt die Website, die wiederum die neuen Source-Dateien von Github abholt und daraus die formatierten Webseiten generiert (Bitte Diagramm draus machen).

Zeit ist Geld

Der Webhoster könnte zwar bei jedem Push alle Textdateien durchforsten und neue Seiten generieren, aber bei tausenden von Dateien spart der Webhook mit den detaillierten Änderungsinformationen enorm Zeit.

Das Skript in Listing 1 wird ausführbar im CGI-Verzeichnis des Webservers abgelegt und das Github-Repo im Admin-Bereich wie in Abbildung 3 gezeigt mit der URL als Webhook konfiguriert.

Es definiert in den Zeilen 11 bis 18 den lesenden URL zum Original Github-Repo ($github_repo) und die Pfade zum lokalen Repo-Klon ($repo_dir) und dem Zielverzeichnis ($target_dir), von dem aus der Webserver die fertigen HTML-Dateien an andockende Browser ausliefert.

Listing 1: github-push-receiver.cgi

    01 #!/usr/bin/perl -w
    02 use strict;
    03 use CGI qw( :all );
    04 use Log::Log4perl qw(:easy);
    05 use JSON qw( from_json );
    06 use Sysadm::Install qw( :all );
    07 use File::Basename;
    08 
    09 my( $home ) = glob "~";
    10 
    11 my $target_dir =
    12   "$home/htdocs/perlsnapshot";
    13 my $repo_dir =
    14   "$home/perlsnapshot.git";
    15 my $github_repo =
    16     # read-only
    17   "git://github.com/mschilli" .
    18   "/perlsnapshot.git";
    19 
    20 Log::Log4perl->easy_init( { 
    21   file => ">>$home/gpr.log",
    22   level => $DEBUG,
    23 });
    24 
    25 print header( 'text/txt' );
    26 
    27 my $json = param( 'payload' );
    28 
    29 if( !defined $json ) {
    30     LOGDIE "Parameter payload missing";
    31 }
    32 
    33 if( ! -d $repo_dir ) {
    34   cd dirname $repo_dir;
    35   tap qw( git clone ), $github_repo,
    36       basename $repo_dir;
    37   cdback;
    38 }
    39 
    40   # Get latest update from Github
    41 cd $repo_dir;
    42 tap "git", "pull", "origin", "master";
    43 cdback;
    44 
    45 my $payload = from_json( $json );
    46 
    47 cd $repo_dir;
    48 
    49 for my $commit ( 
    50   @{ $payload->{ commits } } ) {
    51 
    52   INFO "Commit found: $commit->{ id }";
    53 
    54   for my $file ( 
    55     @ { $commit->{ modified } } ) {
    56 
    57     INFO "Modified: $file";
    58 
    59     if( $file =~ m#^(\d{3}\.txt)$# ) {
    60 
    61       ( my $html_file = $file ) =~
    62         s/\.txt$/.html/;
    63 
    64       sysrun "tpage $file " .
    65              ">$target_dir/$html_file";
    66     }
    67   }
    68 }
    69 
    70 print "OK\n";
    71 DEBUG "Finished";

Damit Interessierte nachvollziehen können, was das CGI-Skript so treibt, und wo eventuell noch Fehler auftreten, schaltet Zeile 20 das Log4perl-Modul an und lässt die Applikation die Arbeitsschritte in der Log-Datei gpr.log im Home-Verzeichnis des Webserver-Users mitprotokollieren. Wie Abbildung 8 zeigt, loggen so nicht nur die explizit im Code sichtbaren Log4perl-Anweisungen (DEBUG, INFO, usw.), sondern auch die im Modul Sysadm::Install versteckten. So schreibt das Skript mit, welche git-Befehle mittels tap und sysrun ausgeführt wurden, welchen Return-Code sie zurücklieferten und was sie auf Stdout und Stderr zum Besten gaben.

Abbildung 8: Die Logdatei zeigt an, was nach dem Push auf dem Webhoster passiert ist.

Aufgefrischter Klon

Um an den Inhalt der aktualisierten Textdateien zu gelangen, muss das Skript in Zeile 35 einen lokalen Klon des Github-Repos anlegen, falls dieser noch nicht von vorhergehenden Skript-Aufrufen her existiert. Andernfalls kommt Zeile 42 zum Tragen und führt einen Update zur neuesten Version durch. Die Funktionen cd und cdback des CPAN-Moduls Sysadm::Install helfen dabei in bestimmte Verzeichnisse hinein und nach getaner Arbeit wieder heraus zu springen. Die aus dem Modul JSON exportierte Funktion from_json dekodiert die im CGI-Parameter payload ankommenden JSON-Daten und legt sie als Perl-Datenstruktur in $payload ab.

Beim Auswerten der eingehenden Daten ist darauf zu achten, dass keineswegs sicher gestellt ist, dass der JSON-Salat von Github kommt. Auch böswillige Gnome könnten das Web-offene CGI-Skript nutzen, um einen Angriff auf den Webserver zu lancieren. Deshalb ist es wichtig, sämtliche Daten (wie die Namen der modifizierten Github-Dateien) auf Herz und Nieren zu Prüfen, bevor das Skript sie weiter verarbeitet oder sie sogar Kommandozeilentools übergibt. Der Schalter -T würde sicherstellen, dass die Daten zumindest mittels regulärerer Ausdrücke inspiziert wurden, doch auch dieses Verfahren findet nicht alle Lücken, die Verantwortung liegt letztendlich beim Programmierer.

Die For-Schleife ab Zeile 49 iteriert über alle Commits der auf vorher auf Github eingegangenen Push-Daten. Alle editierten Dateien legt Github im JSON-Array unter dem Eintrag modified ab, und die For-Schleife ab Zeile 54 nudelt sie alle durch. Passt eine Datei auf das Schema nnn.txt, wirft Zeile 64 den Template-Expander tpage an. Dieser liegt dem Modul Template bei und kombiniert, entsprechend der Template-Anweisung [% PROCESS %] die Header-, Footer- und Textdateien zu fertigen HTML-Dateien. Letztere landen wegen der Redirekt-Anweisung im Shell-Kommando im Verzeichnis htdocs des Webservers. Eventuell ebenfalls eingespeicherte Fotos muss der Nutzer selbst dorthin kopieren. Alternativ könnte das ebenfalls Template beiliegende Skript ttree den gesamten Baum kopieren.

Mit etwas mehr Aufwand lassen sich so beliebige Webseiten aus Github-Repositories generieren. Die Vorteile einer Versionsverwaltung liegen auf der Hand und die leicht zu bedienene Browser-Oberfläche lädt alle Berechtigten dazu ein, kleine Druckfehler sofort auf Github zu korrigieren. Kurze Zeit später erscheinen sie wie durch Zauberhand im Web.

Infos

[1]

Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2013/01/Perl

[2]

Github: Instantly Beautiful Project Pages, https://github.com/blog/1081-instantly-beautiful-project-pages

[3]

Github Post-Receive Hooks, https://help.github.com/articles/post-receive-hooks

[4]

"First Contact", The Github Hiring Experience, https://github.com/blog/1269-the-github-hiring-experience

[5]

"Erweiterte Testansicht", Michael Schilli, http://www.linux-magazin.de/Heft-Abo/Ausgaben/2012/06/Perl-Snapshot

Michael Schilli

arbeitet als Software-Engineer bei Yahoo in Sunnyvale, Kalifornien. In seiner seit 1997 laufenden Kolumne forscht er jeden Monat nach praktischen Anwendungen der Skriptsprache Perl. Unter mschilli@perlmeister.com beantwortet er gerne Ihre Fragen.