Eigenen Git-Server einrichten

Im Unterricht möchte ich die Schülerinnen und Schüler an einem gemeinsamen Projekt arbeiten lassen. Dazu benötigen sie eine Versionsverwaltung.1

Alternativen

In der Vergangenheit habe ich deshalb mit Gitlab herum experimentiert. Es war aber zu aufwendig, den Server stabil zu betreiben. Mehr oder weniger zufällig bin ich auf Gogs - Go Git Server gestoßen. Es gibt einen Fork des Projekts namens Gitea - Git with a cup of tea. Ich habe Gitea den Vorzug gegeben, da die Entwicklung von Gogs in der Vergangenheit eingeschlafen war. Gitea hat eine Menge Mitarbeiter, Gogs nur zwei. Allerdings sieht die Dokumentation von Gogs aufgeräumter aus. Momentan scheint der Unterschied zwischen Gogs und Gitea minimal zu sein.

Ubuntu Server 16.04 LTS bereitstellen

Den Gitea-Server kann man natürlich auf einem Ubuntu-Server installieren. Dazu habe ich die folgenden Schritte nach der Grundinstallation des Ubuntu-Servers mit dem OpenSSH-Server durchgeführt. Ob man den Server nativ oder virtualisiert betreibt bleibt dem eigenen Geschmack überlassen. Man kann Gogs und Gitea auch in einem Docker-Container betreiben. Die Installation ist dann ebenfalls super einfach.

Virtuelle Maschine mit Ubuntu Server 16.04 LTS aufsetzen

Meistens betreibe ich meine Server als KVM - Kernel based Virtual Machines. Die virtuelle Maschine ist dann schnell mit der folgenden Anweisung aufgesetzt.

virt-install --connect qemu:///system \
  --name gitserver \
  --ram 1024 \
  --disk path=/var/lib/libvirt/images/gitserver.qcow2,size=100,format=qcow2 \
  --vcpus 1 \
  --network bridge=br0 \
  --graphics none \
  --console pty,target_type=serial \
  --location 'http://archive.ubuntu.com/ubuntu/dists/xenial/main/installer-amd64/' \
  --extra-args 'console=ttyS0,115200n8 serial'

Natürlich kann man auch Xen, VirtualBox, VMware oder sonst eine Virtualisierungslösung verwenden.

Git installieren und Benutzer git anlegen

Auf unserem Git-Server benötigen wir natürlich git. Das muss noch installiert werden. Damit könnte man schon seine Versionsverwaltung auf dem Server betreiben. Das bietet aber keinerlei Komfort.

sudo apt install git

Der Gitea-Server soll unter dem (normalen) Benutzer git laufen. Den muss man mit dem folgenden Kommando noch anlegen.

sudo adduser git

MariaDB installieren und Datenbank anlegen

Gogs und Gitea benötigen eine Datenbank. Sie können dabei MySQL (MariaDB), PostgreSQL, SQLite3, MSSQL und TiDB verwenden. Eine Empfehlung habe ich nicht gefunden. Da ich mit MySQL gut vertraut bin, habe ich mich für MySQL (MariaDB) entschieden und mit der folgenden Anweisung auf dem Server installiert.

sudo apt install mariadb-server

Mit den beiden folgenden SQL-Anweisungen wird die Datenbank für Gitea eingerichtet und dem Benutzer git die vollen Rechte auf der Datenbank gegeben.

CREATE DATABASE IF NOT EXISTS gitea CHARACTER SET utf8 COLLATE utf8_general_ci;
GRANT ALL ON gitea.* to 'git'@'localhost' identified by '<Passwort>';

Schreibt man die SQL-Anweisungen von oben in die create_gitea.sql, so kann man die Datenbank für Gitea mit dem folgenden Kommando einrichten.

sudo mysql -uroot < create_gitea.sql

Gitea installieren und konfigurieren

Nach den Vorbereitungen kann man jetzt Gitea installieren und konfigurieren. Mir erschien die Installation als Binärdatei am einfachsten. Dazu braucht man als Benutzer git nur die ausführbare Datei in ein passendes Verzeichnis herunterzuladen und zu starten:

su git
cd ~
mkdir gitea
cd gitea
wget -O gitea https://dl.gitea.io/gitea/1.0.0/gitea-1.0.0-linux-amd64
chmod +x gitea
./gitea web

Jetzt läuft der Gitea-Server und kann konfiguriert werden.

Konfiguration

Die Konfiguration erfolgt im Web-Browser unter der auf der Konsole angezeigten Adresse (bei mir http://git:3000/).

gitea_install_1.png

Abbildung 1: Konfiguration oberer Bereich

gitea_install_2.png

Abbildung 2: Konfiguration mittlerer Bereich

Den Mail-Server habe ich erst einmal nicht konfiguriert (siehe Abbildung 3).

gitea_install_3.png

Abbildung 3: Konfiguration unterer Bereich

Jetzt ist der Gitea-Server fertig konfiguriert.

Benutzer aus Active Directory verwenden

Die Schülerinnen und Schüler haben ein Konto im Schulnetzwerk. Damit diese Konten für die Anmeldung am Gitea-Server verwendet werden muss man noch die Authentikation mit dem Active Directory konfigurieren.

Also fügen wir einen Authentikator hinzu:

gitea_active_directory_1.png

Abbildung 4: LDAP (AD) als Authentikator hinzufügen

gitea_active_directory_2.png

Abbildung 5: LDAP (AD) konfigurieren

Der Gitea-Server ist jetzt fertig konfiguriert. Wir beenden den laufenden Prozess mit CTRL-C und melden uns als Benutzer git ab.

Systemd

Damit der Gitea-Server beim Starten der Server-Maschine auch gestartet wird muss man das dem Systemd mitteilen. Dazu legt man die Datei /lib/systemd/system/gitea.service mit dem folgenden Inhalt an.

[Unit]
Description=Gitea (Git with a cup of tea)
After=syslog.target
After=network.target
After=mysqld.service
#After=postgresql.service
#After=memcached.service
#After=redis.service

[Service]
# Modify these two values and uncomment them if you have
# repos with lots of files and get an HTTP error 500 because
# of that
###
#LimitMEMLOCK=infinity
#LimitNOFILE=65535
Type=simple
User=git
Group=git
WorkingDirectory=/home/git/gitea
ExecStart=/home/git/gitea/gitea web
Restart=always
Environment=USER=git HOME=/home/git

[Install]
WantedBy=multi-user.target

Jetzt noch schnell den Service aktivieren und über Systemd starten.

sudo systemctl enable gitea.service
sudo systemctl start gitea.service

Jetzt ist unser Gitea-Server fertig.

Fußnoten:

1

Einfach ein Projekt auf Github dafür einzurichten scheidet aus. Da ein eigenes Konto auf Github einrichten müsste. Unterricht soll aber in einer kontrollierten Umgebung stattfinden.

Physics with Nikola

I would like to use LaTeX notation in physics texts. Thankfully, there is MathJax. MathJax renders LaTeX formulas in HTML documents for quite some time.

A simple mathematical formula:

\( a^2 + b^2 = c^2 \)

But physics require the rendering of a value with a unit. I use the siunitx package in LaTeX for this. These commands where not available in MathJax for a long time. There is a MathJax extension available since one year. This extension provides some of the functionality of the LaTeX package. Especially, the \sisetup command is not working. The format of the output is not customizable as in LaTeX.

\begin{equation} \Delta s = \SI{4.5e-2}{\meter} \end{equation}

Zertifikate von Let's Encrypt verwenden

Installation von acme-tiny

Mein Favorit der Let's Encrypt Clients ist acme-tiny. Es ist ein sehr kurzes Python-Skript. Das erleichtert einen Code-Review. Die Vorarbeiten für die Zertifikatserstellung werden leicht mit OpenSSL durchgeführt. Es benötigt keine root-Rechte und funktioniert gut. In der folgenden Beschreibung gehen wir davon aus, dass der Benutzer lets als normaler Benutzer auf einem OpenBSD-System mit nginx als Web-Server existiert.

Erstellen der Verzeichnisstruktur

Im Verzeichnis des Benutzers lets wird folgende Verzeichnisstruktur angelegt.

mkdir bin
mkdir src
mkdir reqs
mkdir certs

Clonen des Repositories

Damit man das Skript leicht installieren und eventuell auch aktualisieren kann wird das Repository in src geclont.

cd src
git clone https://github.com/diafygi/acme-tiny.git
cd

Das Skript kopiert man nach dem Code Review nach bin.

cp src/acme-tiny/acme_tiny.py bin
chmod +x bin/acme_tiny.py

Erstellen des privaten Schlüssels für den Let's Encrypt Account

Für den Account benötigt man ein Schlüsselpaar. Das kann man leicht mit dem folgenden Kommando erstellen. Aus Sicherheitsgründen sollte es auch nur für den Benutzer lesbar sein.

openssl genrsa 4096 > account.key
chmod 400 account.key

Erstellen eines Certificate Signing Requests (CSR)

Für jede Domain, für die Let's Encrypt ein Zertifikat ausstellen soll benötigt man einen Certificate Signing Request. Im Certificate Signing Request wird der öffentliche Schlüssel der Domain zum Unterschreiben an Let's Encrypt gesandt. Also müssen wir am besten für jede Domain einen eigenes Domain-Schlüsselpaar und einen Request erstellen.

openssl genrsa 4096 > example.key
openssl req -new -sha256 -key example.key -subj "/CN=example.com" > ./requests/example.csr

Jetzt wird der Schlüssel nur noch vom Server benötigt. Ich habe ihn als root (sudo/doas) in das Verzeichnis /etc/ssl/private verschoben.

Man kann auch ein Zertifikat für mehrere Domains in einem Zertifikat erstellen lassen. Das nutze ich zur Zeit aber nicht.

Konfiguration des Verzeichnisses für die Challenges

Let's Encrypt verifiziert mit einer Challenge, dass man die Kontrolle über die Domain für das Zertifikat hat. Dazu muss der Client eine Datei anlegen können, die der Let's Encrypt Server unter der Adresse http://domain/.well-known/acme-challenge/... abrufen kann. Dazu muss der Web-Server ein entsprechendes Verzeichnis haben.

server {
    listen 80;
    server_name example.com;

    location /.well-known/acme-challenge/ {
        alias /var/www/challenges/;
        try_files $uri =404;
    }

    ...
}

Dieser Schritt hat mich den meisten Ärger gekostet, da meine Konfiguration des nginx als Reverse Proxy trotz scheinbar korrekter Pfadangabe irgendwie in den falschen Verzeichnissen gesucht hat. Eine Umstellung der Konfigurationsdateien hat da Wunder gewirkt. Man sollte nicht zu stark an den nginx-Konfigurationsdateien herumoptimieren.

Jetzt hat man die Vorarbeiten abgeschlossen und kann die Zertifikate anfordern.

Anfordern eines Zertifikats

Der folgende Aufruf fordert das Zertifikat für die Domain an.

python acme_tiny.py --account-key ./account.key --csr ~/requests/example.csr --acme-dir /var/www/challenges/ > ~/certs/example.crt

Für die Server benötigt man meistens eine Zertifikatskette. In der Zertifikatskette sind die Zertifikate der Domain und der unterschreibenden Certification Authority aneinandergehängt. Diese Kette kann man mit den folgenden Schritten erstellen.

curl -fsS https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem -o ~/certs/intermediate.pem
cat ~/certs/example.crt intermediate.pem > /etc/ssl/lets/example-chained.pem

Hier sieht man auch einen Kniff, den ich angewendet habe, um möglichst eine Trennung der Rechte zu erreichen. Der private Schlüssel der Domain (example.key), der nur zur Erstellung des Certificate Requests und später von Server verwendet wird liegt in einem Verzeichnis, auf das nur root zugreifen kann. Das Verzeichnis mit den Zertifikaten (/etc/ssl/lets/ gehört hingegen dem Benutzer lets, der nur einfacher Anwender ist. Alle dürfen auf das Verzeichnis nur lesend zugreifen.

Erneuern des Zertifikat

Da das Zertifikate nur 90 Tage gültig ist, muss man vor Ablauf der Frist ein neues anfordern. Das kann man wie oben beschrieben mit dem gleichen Aufruf von acme_tiny.py durchführen. Unter den unix-artigen Betriebssystemen kann man aber auch ein einfaches Skript erstellen und von cron ausführen lassen.

Das folgende Skript funktioniert unter OpenBSD und muss etwas für andere Unix-Artige angepasst werden. Nennen wir es renew_cert.sh.

#!/bin/sh
# Renew certificates with Let's Encrypt

base="/home/lets"
alias acme_tiny="$base/bin/acme_tiny.py"
chaineddir="/etc/ssl/acme"

accountkey="$base/account.key"
reqsdir="$base/reqs"
certsdir="$base/certs"
challengedir="/var/www/challenges"
intermediate_cert="$certsdir/intermediate.pem"
intermediate_cert_url="https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem"

echo "Downloading intermediate certificate..."
# get intermediate certificate with curl
# -f: fail without writing html garbage to output file
# -s: silent mode without progress meter
# -S: output error message if download fails
curl -fsS $intermediate_cert_url -o $intermediate_cert && echo "Success."

# The script continues even if the download of the intermediate
# certificate fails. The old intermediate certificate should still
# exist and be useable.

for csr in $reqsdir/*.csr; do
  domain=`basename $csr .csr`
  echo "Requesting certificate for $domain..."
  acme_tiny --account-key $accountkey --csr $csr --acme-dir $challengedir > $certsdir/$domain.crt || (echo "Failed!" ; exit)
  echo "Success."
  cat $certsdir/$domain.crt $intermediate_cert >$chaineddir/$domain-chained.pem
done

echo "Reloading web server configuration..."
doas /usr/sbin/rcctl reload nginx || (echo "Failed!"; exit)
echo "Success."

Damit der Benutzer lets auch die Webserver-Konfiguration neu laden kann muss er entsprechende Rechte dafür haben. Das lässt sich unter OpenBSD schön für genau diesen Zweck eingeschränkt in der Datei `/etc/doas.conf` mit der folgenden Zeile konfigurieren. Unter anderen Betriebssystemen muss man entsprechend `sudo` konfigurieren und verwenden.

permit nopass lets as root cmd /usr/sbin/rcctl args reload nginx

Jetzt kann man noch den cron entsprechend konfigurieren, damit er einmal im Monat ein neues Zertifikat anfordert. Ich mag es, wenn ich dann Post bekomme.

MAILTO=cert@example.com
0 0 1 * * $HOME/bin/renew_certs.sh