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]).
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.
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).
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.
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.
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.
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). |
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.
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. |
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.
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2013/01/Perl
Github: Instantly Beautiful Project Pages, https://github.com/blog/1081-instantly-beautiful-project-pages
Github Post-Receive Hooks, https://help.github.com/articles/post-receive-hooks
"First Contact", The Github Hiring Experience, https://github.com/blog/1269-the-github-hiring-experience
"Erweiterte Testansicht", Michael Schilli, http://www.linux-magazin.de/Heft-Abo/Ausgaben/2012/06/Perl-Snapshot