Zahnräder
Gears of industry, (CC BY-NC-ND 2.0), House Photography

Deployment einer Jekyll-Site via Git Bare Repository1 und post-receive Hook2 auf einen Webserver.

Zielstellung ist, dass ein git push auf das Remote Repository3 das weiter unten beschriebene Skript triggert. Dieses generiert aus dem Repository mit all seinen Änderungen, wie z.B. Markdown, Config, Layout, Include und den Abhängigkeiten im Gemfile4 das entsprechende HTML, dass dann der Welt in der DocumentRoot5 deines Webservers, in meinem Fall einem Uberspace ausgeliefert wird.

Vorbereitungen auf dem Zielserver

Wir starten nach erfolgreichem SSH-Login auf unserem Uberspace-Host in unserem Home-Verzeichnis und legen Ordner für unser Repository an,

mkdir repos

wechseln dort hinein

cd repos

und erstellen den Ordner für das Repository auf das wir später pushen wollen. In diesem Fall entspricht der Ordnername der Domain. Ab hier solltest du das exemplarische florian.latzel.io durch deine Domain ersetzen.

mkdir florian.latzel.io

und wechseln hinein.

cd florian.latzel.io

Jetzt initialisieren wir den Ordner als Bare Repository1.

git init --bare

[DEPRECATED] The –path flag is deprecated …

Vor einiger Zeit tauchte die folgende Meldung beim Deployment auf:

[DEPRECATED] The --path flag is deprecated because it relies on being remembered across bundler invocations, which bundler will no longer do in future versions. Instead please use bundle config set --local path '~/.gem', and stop using this flag

Ich habe das --path aus dem Skript entfernt und statt dem Vorschlag folgendes gemacht:

bundle config set --global path '~/.gem'

Your application has set path to “~/.gem”. This will override the global value you are currently setting

Statt einer lokalen .bundle/config mit nur einer Zeile geht hierdurch die folgende Zeile nach ~/.bundle/config:

BUNDLE_PATH: "~/.gem"

Das Deployment Skript

Der Code

Hier der Stand vom 03. September 2021:

#!/bin/bash

# Deployment of Jekyll-Sites via Git Bare Repository and post-receive hook.
#
# USAGE:
# - git hook: Place this script as .hooks/post-receive within your git repository
# - standalone: ${SCRIPT} /path/to/git-bare-repository
#
# See https://florian.latzel.io/jekyll-deployment-via-bare-repository-und-post-receive-hook.html
# for setup and a more detailed description (german)
#
# set -x

if [ -z "$1" ]; then
  read oldrev newrev ref
  pushed_branch=${ref#refs/heads/}
elif [ -d "$1" ] && { cd $1 ; git rev-parse --is-bare-repository ; } >/dev/null; then
  pushed_branch=""
else
  exit
fi

config=$(mktemp)
git show HEAD:deploy.conf > $config || exit
source $config

[ -z "$pushed_branch" ] && build_branch=$pushed_branch
[ "$pushed_branch" != "$build_branch" ] && exit
tmp=$(mktemp -d)
git clone $git_repo $tmp
cd $tmp
bundle install || bundle install --redownload
JEKYLL_ENV=${env:-production} \
  bundle exec jekyll build --source $tmp --destination $www $build_prefix
rm -rf $tmp $config

Beschreibung des Codes

  1. “Debug Modus”, entfernen der Raute um mehr Ausgaben zu sehen (Zeile 12)
  2. Indikator für Git-Hook: Überprüfung ob keine Parameter übergeben wurden(Zeile 14)
  3. Einlesen des gepushten Branches(Zeile 15 und 16) um ihn später vergleichen zu könnnen
  4. Test ob es sich um ein Verzeichnis handelt, Verzeichnswechsel dort hinein und testen ob es sich um ein Bare-Repository handelt (Zeile 20), sonst Exit. Die Standardausgabe, also true wird nach /dev/null geschrieben während der Fehlerkanal bewusst ausgegeben wird (Zeile 20)
  5. Zusweisung eines leeren Strings auf Variable pushed_branch (Zeile 18)
  6. Anlegen einer temporären Variablen config (Zeile 23)
  7. Umleiten des Inhalts der Datei deploy.conf aus dem Bare-Repository, die unsere Konfiguration für das Deployment enthält in die Variable $config, falls die Datei existiert sonst exit (Zeile 24)
  8. Einlesen der Konfiguration aus der Variablen $config(Zeile 25)
  9. Falls Variable $pushed_branch leer ist (vergl. Zeile 18), dann bekommt die Variable build_branch den gleichen Wert den $pushed_branch enthält (Zeile 27)
  10. Die Variablen $pushed_branch und $build_branch werden auf Ungleichheit verglichen, ist das der Fall, dann wird das Skript mit exit verlassen (Zeile 28).
  11. Anlegen eines temporären Verzeichnises Names tmp (Zeile 29)
  12. Klonen des Bare-Repos1 das das temporäre Verzeichnis tmp (Zeile 30).
  13. Verzeichniswechsel in das temporäre Verzeichnis $tmp (Zeile 31)
  14. Installation der im Gemfile spezifizierten Abhängigkeiten via bundle install4. Falls bundle install fehlschlägt, wird das nochmal mit der Option --redownload6 versucht (Zeile 32)
  15. Setzen von der Variable JEKYLL_ENV und Generierung des HTML aus $tmp in die mit $www spezifizierte Document Root via jekyll build
  16. Löschung des temporären Verzeichnisses $tmp und der Datei $config. Dat wor et!

Nutzung als post-receive Git Hook

Wenn der komplette Prozess der Übertragung (Push) abgeschlossen ist, greift serverseitig der sogenannte post-receive Hook2 und führt das gleichnamige Skript (sofern vorhanden) aus.

Das Jekyll uberspace deployment Skript welches wir als post-receive Hook2 nutzen findest du hier auf github.

Einrichtung des post-receive Hooks

Als Erstes entfernen wir den Default post-receive Hook aus dem Bare-Repository um ihn später durch einen symbolischen Link auf unser Skript zu ersetzen.

rm ~/repos/florian.latzel.io/hooks/post-receive

Dann clonen wir das Jekyll Deployment Skript, dort passiert später die ganze Magie.

git clone https://github.com/fl3a/jekyll_deployment.git ~/repos 

Jetzt legen wir einen Symlink namens post-receive im Bare-Repo an, der auf das gleichnamige Deployment Skript verweist:

ln -s ~/repos/jekyll_deployment/post-receive ~/repos/florian.latzel.io/hooks/post-receive

Dann muss das Skript noch ausführbar gemacht werden:

chmod +x ~/repos/jekyll_deployment/post-receive

Last but not least, muss die Konfiguration über die weiter unten beschrieben Datei deploy.conf erfolgen

Nötige Schritte im lokalen Git-Repository

Das waren die Schritte auf deinem Uberspace, weiter gehts in deinen lokalen Git Repository.
Das oben erstellte Bare-Repository fügst du deinem lokalen Repository so als sog. Remote-Repository3 namens uberspace hinzu:

git remote add uberspace fl3a@bellatrix.uberspace.de:repos/florian.latzel.io

Fertig, jetzt noch der push vom master nach uberspace.

git push uberspace master

Nach der Übertragung der Daten solltest du jetzt die Ausgaben von git clone, bundle install und jekyll build sehen.

Standalone: Aufruf mit dem Jekyll Repo als Argument

Das gleiche Skipt kann auch für die direkte Ausführung auf dem Zielsystem genutzt werden. Das ist manchmal ganz nützlich um das Deployment direkt und ohne einen Push anzustoßen z.B. um den Fehler auszumachen, wenn bundler4 mal wieder zickt.

Es erfolgt die Konfiguration auch hier, wie in der post-receive Variante über die weiter unten beschriebenen Datei deploy.conf.

Um das Skript Standalone nutzen zu können, setzen wir einen Symlink in den Suchpfad:

ln -s ~/repos/jekyll_deployment/post-receive ~/bin/jekyll_deployment

Der Aufruf der Standalone-Variante erfolgt mit dem Jekyll-Repository als Argument:

jekyll_deployment ~/repos/florian.latzel.io

deploy.conf - Anpassung der Variablen

Die Datei deploy.conf dient als Konfiguration für die Post-Receice-Hook als auch für die Standalone Variante.
Mit dieser Konfiguration verfährst du wie folgt:

  1. Kopiere hierzu deploy.conf in die Hauptebene deines lokalen Jekyll Repositories
  2. Passe die Variablen auf die Bedürfnisse deines Zielsystems hin an
  3. Committe diese Datei anschließend

Sofern du alles wie oben beschrieben umgesetzt hast und du auch auf uberspace bist😙, sind nur subdomain und domain anzupassen. Alle anderen Variablen sind vorbelegt, werden zusammengesetzt oder sind optional.

  • build_branch, Der Branch der gebaut werden soll z.B. master.
  • subdomain, optional. z.B. preview. oder leer. Falls gesetzt, achte auf den . am Ende!
    Wird mit u.g. Domain zu preview.netzaffe.de.
  • domain, Name der Domain, z.B. netzaffe.de.
  • git_repo, Pfad zum Jekyll Bare Repository auf dem Server
    z.B. ${HOME}/repos/${domain}
  • www, Pfad zur Document root auf dem Server, wo das generierte HTML ausgeliefert wird,
    z.B www=/var/www/virtual/${USER}/${subdomain}${domain}.
    Dieses Pfadschema ist uberspace spezifisch und natürlich anpassbar.
  • env, Wert für JEKYLL_ENV, default production
  • build_option, Option die bundle excec jekyll build angefügt wird,
    z.B. --incremental

Learnings

  • Get file from git repo https://stackoverflow.com/questions/610208/how-to-retrieve-a-single-file-from-a-specific-revision-in-git
  • Eine Erkenntnis zu exit7 [sic]

    Es ist eine verbreitete Unsitte als letzten Befehl eines Scripts ‘exit 0’ zu verwenden. Ein Skript das zu Ende ist, ist zu Ende und braucht keinen ausdrücklichen Abbruch, vor allem keinen, der den letzten Fehler kaschiert.

  • Darauf bin ich beim Recherchieren zu exit gestoßen: traps8
  • git ours9, darauf bin ich gestoßen, während ich den Hook überarbeitet habe und in Richtung Config in Repository gegangen bin und an eine Config je Branch gedacht habe. Kannte ich nicht, das will ich mal ausprobieren.
  • Noch keine README im Repository, aber über goldene Türklinken nachdenken. jekyll_deployment.sh ist im Vergleich zu post-receive nicht so schön und schlank, aber die grundlegenede Funktionalität ist in beiden Skipten gleich… Auslagern🧠, es geht schöner⌨️10! Verlieren wir uns nicht in Schönheit und bringen wir diesen Post erstmal ins Netz, Florian☝️. Es handelt sich hierbei ja eigentlich um ein Abfallprodukt, dass aus einer Ur-Version basiert.

Credits

Dieser Artikel basiert im wesentlichen auf Jekyll Auf Uberspace Mit Git von Daniel Wittberger und Jekyll Auf Uberspace von Franz aka laerador. Danke!

Dieser Artikel ist eine aktuelle Essenz, die sich nur auf das Deployment bezieht. Seit 2012, 2013 hat sich einiges in Jekyll und auf Uberspace etc. getan und das Skript hat noch etwas Liebe erfahren.

Verwandte Artikel