Hugo-Website automatisch per SSH deployen mit GitHub Actions

Erfahren Sie, wie Sie Ihre Hugo-Webseite automatisch auf dem eigenen Server veröffentlichen.
     

Zum Glück gibt es eine Lösung für dieses Problem: Github Actions!

Vorbereitungen

Wir legen einen User an und erstellen SSH-Keys für ebendiesen User. Für dieses Beispiel nenne ich den User github.

# User anlegen
adduser --system --shell /bin/bash --gecos 'GitHub Deploy' --group --disabled-password --home /home/github github

# SSH-Key generieren
sudo -u github ssh-keygen -t rsa

# SSH-Key für den Login hinterlegen
sudo -u github cat /home/github/.ssh/id_rsa.pub >> /home/github/.ssh/authorized_keys

Nun kann man sich unter Nutzung des Private Keys per SSH mit diesem User einloggen.

In diesem Tutorial gehe ich außerdem davon aus, dass eure Hugo-Files im Root-Verzeichnis des Github-Repositories liegen. Das Repository kann natürlich privat sein (und sollte es vermutlich auch…). Die Ordnerstruktur eures Repository sollte also ungefähr so aussehen:

Wie ihr seht, habe ich den Ordner public/, in dem die eigentliche Website veröffentlicht wird, nicht im Repository.

Geheimniskrämerei mit Secrets

Bevor wir mit der Action starten, hinterlegen wir zuerst ein paar Secrets – also geheime Variablen. Hierzu wechseln wir in unserem Repository zum Menüpunkt Settings und dann zu Secrets -> Actions.

Wir legen insgesamt drei Secrets an. Die Überschrift entspricht hier jeweils dem Namen des Secrets.

VPS_DEPLOY_KEY

Hier hinterlegen wir den SSH Private Key des oben angelegten Users. Diesen erhalten wir per cat /home/github/.ssh/id_rsa.

Den kompletten Private Key kopieren wir ins Feld Value unseres Secrets mit dem Namen VPSDEPLOYKEY.

KNOWN_HOSTS

Damit uns niemand einen falschen SSH-Host unterschieben kann, hinterlegen wir die bekannten SSH-Fingerprints unseres Servers – hiermti vermeiden wir eine MITM-Attacke.

Die Fingerprints kann man ganz einfach herausfinden, indem man ssh-keyscan web.example.org ausführt – natürlich muss hier euer Hostname eingetragen werden. Wir erhalten folgenden Output:

# 10.0.2.15:22 SSH- 2.0-OpenSSH 8.3p1 Ubuntu-lubuntuo.1
10.0.2.15 ssh-rsaAAAAB3NzaC1yc2EAAAADAQABAAABgQDmDW jjOgc4GaZzIVVNIvhuh 71DmE2eV
YPnFtJM7zB jdRIWSgB8BnmO9zaYMkaXIM2nmNTX6JMcRR /GWy×h8wvGBEougs1K43ty4rurwftAwTGOF GeiUKdxaqMWLUwjlD/TqAcHTqjFUkUatoltv2AiZKyB5MkGAtS//mTZpXcIHsKi1gVMeE/SN2Zfk/Sf WVTvjig_×flkIv5AM1UgAYgWOEz72+D57N09gbgxqMRMLS7GDaMILmO9NwJVVqvM5091ELWFfPOjK46
00IZXUpYCEv38he6×LGSlcdRuZwaLOJOmWQCaoSHOtPCBRq0X1PirGULJBS7ihJUV2/puheZTpDUe80/ 5aXE6dIVTAA81PKOZtbKdLgFNE jT\mR73pGM2+tE0jJP1cyrN5sNO194hmWM7aFhopOXKMonttOF5YGo
Os700 fiVe4CZ95inFXZ9V00FGKTK5C1GT1feCBH64wKYp2sDh3p707A6 KDMmsOMxzlpwo+0vU
U=
# 10.0.2.15:22 SSH-2.0-OpenSSH_8.3p1 Ubuntu- 1ubuntuo. 1
# 10.0.2.15:22 SSH-2.0-OpenSSH_8.3p1 Ubuntu- 1ubuntuo. 1
10.0.2.15 ecdsa-sha2-nistp256 AAAAE2ViZHNhLXNoYTItbmlzdHANTYAAAAIbMlzdHAyNTYAAA BBBAZUol2XsPiplWpGwGHXSXPX0V00054K5+DY7pmpc3WJiIBHOM1K6zVRlukM97bF9Bdq+05×LOl kotAlUtZO=
# 10.0.2.15:22 SSH-2.0-OpenSSH_8.3p1 Ubuntu- 1ubuntuo. 1
10.0.2.15 ssh-ed25519 AAAAC3NzaC11ZDI1NTE5AAAAIGZczoY91cUECrdMgP /zy6StPPH7kb5uiI
ZH5CSGOPYC
# 10.0.2.15:22 SSH- 2.0-OpenSSH 8.3p1 Ubuntu-lubuntuo.1

Den kompletten Output dieses Befehls fügen wir nun ins Feld Value unseres Secrets mit dem Namen KNOWN_HOSTS ein.

REMOTE_DEST

Hierbei handelt es sich, wie der Name des Secrets schon sagt, um das Ziel des in der Action ausgeführten rsync-Befehls. In meinem Fall lautet der Inhalt dieses Secrets:

github@web.example.org:/var/www/example.org

Natürlich müsst ihr das an eure Begebenheiten anpassen.

Das fügen wir nun ins Feld Value unseres Secrets mit dem Namen REMOTE_DEST ein.

Ready, Set, Action!

Jetzt können wir endlich die lang ersehnte Github Action anlegen!

In unserem Repository erstellen wir den Ordner .github, darin den Unterordner workflows und darin die Datei deploy-website.yaml mit folgendem Inhalt:

name: Deploy Website

on:
  workflow_dispatch:
  push:

jobs:
  build:
    name: Build and Deploy
    runs-on: ubuntu-latest
    steps:
      - name: ???? Checkout
        uses: actions/checkout@main
        with:
          submodules: true     

      - name: ✨ Setup Hugo
        run: |
          mkdir ~/hugo
          cd ~/hugo
          curl -s https://api.github.com/repos/gohugoio/hugo/releases/latest | grep "browser_download_url.*hugo_[^extended].*_Linux-64bit\.tar\.gz" | cut -d ":" -f 2,3 | tr -d \" | wget -qi - -O hugo.tar.gz
          tar -xvzf hugo.tar.gz
          sudo mv hugo /usr/local/bin          

      - name: ????️ Build
        run: hugo --minify         

      - name: ???? Install SSH Key
        run: |
          install -m 600 -D /dev/null ~/.ssh/id_rsa
          echo "${{ secrets.VPS_DEPLOY_KEY }}" > ~/.ssh/id_rsa
          echo "${{ secrets.KNOWN_HOSTS }}" > ~/.ssh/known_hosts          

      - name: ???? Deploy
        run: rsync --archive --delete --stats -e 'ssh' 'public/' ${{ secrets.REMOTE_DEST }}

Was hier passiert ist eigentlich relativ selbsterklärend, trotzdem möchte ich das Ganze kurz mit euch durchgehen:

  1. Checkout: wir holen uns die aktuellste Version unseres Git-Repository und checken alle Dateien aus – inklusive etwaiger Submodules (Hugo-Themes z.B.)

  2. Setup Hugo: wir versuchen, die aktuellste Version das Static Site Generators herauszufinden, diese herunterzuladen und zu entpacken – danach schieben wir die Datei in den Ordner /usr/local/bin

  3. Build: wir erstellen unsere Website – hier können neben minify natürlich noch weitere Hugo-Optionen hinterlegt werden

  4. Install SSH-Key: wir hinterlegen unseren SSH-Key und die Known-Hosts-Datei

  5. Deploy: wir veröffentlichen unsere Website per rsync auf unserem Server

Im Prinzip machen wir also genau das, was wir auch per Hand machen würden – nur überlassen wir das Ganze jetzt eben Github.

Übrigens: mit der Option push im Header der Datei sagen wir Github, dass die Action bei jedem Push ins Repository ausgeführt werden soll. Hiermit automatisieren wir die ganze Geschichte also, sodass bei jeder Änderung am Github-Repository auch unsere Webseite neu veröffentlicht wird.

Fazit

Sofern ihr alles richtig gemacht habt, wird nun bei jedem Push zu eurem Github-Repository die neueste Version eurer Website auf eurem Server veröffentlicht.

Der Weg zu einem zufriedenstellenden Workflow mit Hugo war zwar etwas holprig, aber jetzt bin ich wirklich zufrieden. Ich muss nichts mehr händisch machen, um neue Inhalte zu veröffentlichen und kann meine Beiräge in jedem Texteditor schreiben und von überall veröffentlichen, ohne den ganzen Ballast von WordPress mitschleppen zu müssen.