Aus Linux-Magazin 04/2023

Ein Bot baut nach jeder Änderung automatisch Debian-Pakete

© cameracraft / 123rf.com

Dank der Arbeit der Distributoren lässt sich die meiste Software im Nu via Paketmanager installieren, aktualisieren oder deinstallieren. Wer seine eigene Software ähnlich unkompliziert handhaben will, verpackt sie in Pakete – am besten automatisiert.

Agile Softwareentwicklung erfordert, das Projekt nach einer Änderung automatisch zu kompilieren, zu testen und den Entwicklern direkt Feedback zu geben, ob die Veränderung funktioniert. Auch bei persönlichen Projekten, die oft weniger strukturiert ablaufen, lassen sich kurze Entwicklungszyklen realisieren, indem Automatisierung dem Entwickler lästige Arbeit abnimmt.

Von dem im letzten Beitrag entwickelten Watchbot bekommen wir bereits eine Benachrichtigung, wenn ein Git-Repository verändert wurde. Damit aus dieser Benachrichtigung ein neues Paket zum Installieren mit dem Paketmanager entsteht, brauchen wir nun einen Bot, der auf Zuruf des Watchbots das Repository herunterlädt und Pakete daraus baut. Wir nennen ihn Debbot, weil er Debian-Pakete schnürt.

Das richtige Werkzeug

Um ein Debian-Paket zu bauen, muss der Quellbaum ein Verzeichnis namens »debian/« mit Regeln für den Paketbau, das Changelog und weitere Dateien enthalten. Man nennt das Ganze einen debianisierten Sourcetree. Da ein Repository nicht ohne weiteres automatisch debianisiert ist, setzen wir voraus, dass die Repos, auf die der Debbot zugreift, bereits entsprechend vorbereitet sind.

Es stehen mehrere Tools zur Wahl, um Debian-Pakete zu bauen. Das einfachste und zugleich unpraktischste ist Dpkg-buildpackage. Um damit ein Paket zu bauen, muss man nur in das Quellverzeichnis wechseln und dort »dpkg-buildpackage« ausführen. Unpraktisch ist das deshalb, weil der Befehl das laufende System direkt für den Paketbau nutzt und benötigte Pakete somit auf dem System installiert sein müssen. Pakete für andere, ebenfalls auf Debian basierende Distributionen lassen sich mit Dpkg-buildpackage also genauso wenig bauen wie Pakete für andere Prozessorarchitekturen.

Können oder wollen Sie zum Bau benötigte Pakete nicht direkt installieren oder möchten Pakete für andere Distributionen und Architekturen erstellen, finden Sie in Pbuilder einen Verbündeten. Mit ihm lassen sich Pakete in sogenannten Buildroots bauen, also in Einwegumgebungen speziell für den Paketbau. Da ein Buildroot nach vollendeter Arbeit in die Tonne wandert, kann Pbuilder zum Bau benötigte Pakete ohne Bedenken installieren. Bevor er aber Pakete bauen kann, müssen Sie ein »base.tgz« erstellen, das als Grundlage für Buildroots herhält. Der Befehl aus der ersten Zeile von Listing 1 erstellt ein »base.tgz« für das neueste stabile Debian.

Listing 1

Pbuilder

$ sudo pbuilder create --distribution stable --mirror http://ftp.debian.org/debian
$ sudo pbuilder create --distribution stable --mirror http://ftp.debian.org/debian --debootstrapopts "--keyring=/usr/share/keyrings/debian-archive-keyring.gpg" --debootstrapopts "--exclude=ubuntu-keyring,devuan-keyring,devuan-baseconf"
$ dpkg-source --build /Pfad/zum/Repository
$ sudo pbuilder build --buildresult Ziel Quellpaket.dsc

Falls Sie Pbuilder auf einem Debian-Derivat wie Devuan oder Ubuntu ausführen, müssen Sie ihm mitteilen, dass es nicht den Keyring der laufenden Distribution benutzen soll, sondern den von Debian. Dazu installieren Sie das Paket debian-archive-keyring und hängen dem Pbuilder-Aufruf die passenden Parameter an (Zeile 2). Anschließend lässt sich mit dem Befehl aus Zeile 4 ein Paket im Buildroot bauen.

Die ».dsc«-Datei, die Sie Pbuilder dazu übergeben, bezeichnet man auch als Quellpaket. Sie enthält eine Liste der Abhängigkeiten für den Bau sowie den Namen und den Hash des Tarballs, der den Quellcode des Pakets enthält. Anders als Quellpakete für RPM-basierte Distributionen besteht ein Debian-Quellpaket aus mehreren Dateien, die Sie mit dem Kommando aus Zeile 3 aus einem Repository erzeugen. Das Quellpaket entsteht im aktuellen Verzeichnis.

Stabile Verpackung

Die zwei Schritte, mit denen Sie Debian-Pakete aus einem Git-Repository bauen, führt die Funktion »build_deb_from_src()« zusammen (Listing 2). Als erstes Argument erwartet sie den Pfad eines Arbeitsverzeichnisses. Das enthält in einem Unterverzeichnis namens »sources/« den Quellcode und darf von der Funktion benutzt werden, um temporäre Dateien zwischenzuspeichern. Als zweites Argument übergeben Sie der Funktion den Pfad, unter dem sie die Ergebnisse – also Pakete und Log-Dateien – ablegen soll.

Listing 2

Paketbau mit Pbuilder

build_deb_from_src() {
  local builddir="$1"
  local resultdir="$2"
  local -i err
  local dsc
  err=1
  if ! prepare_buildroot &>> "$resultdir/build.log"; then
    log_error "Konnte Buildroot nicht erzeugen"
    return 1
  fi
  if ! ( cd "$builddir" && dpkg-source --build sources ) &>> "$resultdir/build.log"; then
    log_error "Konnte Quellpaket nicht bauen"
    return 1
  fi
  while read -r dsc; do
    sudo pbuilder build --buildresult "$resultdir" "$dsc" &>> "$resultdir/build.log"
    err="$?"
    if (( err != 0 )); then
      log_error "Bau von $dsc fehlgeschlagen"
      break
    fi
  done < <(find "$builddir" -type f -name "*.dsc")
  return "$err"
}

Die Funktion ruft zunächst eine Hilfsfunktion »prepare_buildroot()« auf, die ein Buildroot erzeugt, falls unter »/var/cache/pbuilder/base.tgz« keines existiert. Da dieser Helfer recht trivial ausfällt, drucken wir ihn hier nicht ab. Anschließend ruft »build_deb_from_src()« aus dem Arbeitsverzeichnis heraus Dpkg-source auf und erzeugt so das Quellpaket. Dessen Namen ermitteln Sie, indem Sie über die Ausgabe von Find iterieren und alle gefundenen Quellpakete kompilieren. In der Regel genügt dazu ein einzelner Schleifendurchlauf. Findet sich kein Quellpaket oder schlägt der Paketbau fehl, teilt die Funktion das dem Aufrufer über den Rückgabewert mit.

Sowohl zum Vorbereiten des Buildroots als auch zum Paketbau ruft man Pbuilder mit »sudo« auf. Daher gilt es, Sudo so zu konfigurieren, dass der Prozess beim Aufruf von »sudo pbuilder« nicht nach seinem Passwort gefragt wird.

Diese Funktion wickeln Sie nun in eine weitere Funktion »build()«, die ein Arbeitsverzeichnis erstellt, den Quellcode herunterlädt und mit der soeben geschriebenen Funktion die Pakete baut. Diese Funktion implementiert eine automatische Versionierung als Feature. Warum, wird spätestens dann klar, wenn Sie versuchen, die gebauten Pakete zu installieren: Die Version eines Debian-Pakets wird in dessen Changelog festgelegt.

Da Sie aber nicht mit jedem Commit den Changelog verändern, würde Debbot ohne automatische Versionierung mehrere unterschiedliche Pakete mit derselben Versionsnummer erzeugen. Weil der Paketmanager neuere Pakete anhand der Paketversion erkennt, wären dann keine Updates mehr möglich. Tatsächlich würden Sie schon beim Versuch scheitern, das doppelte Paket in Ihr Apt-Repository einzufügen.

Der Debbot braucht daher eine Funktion »add_changelog()«, die einen Eintrag in das Changelog des Pakets einfügt und so verhindert, dass mehrere Pakete dieselbe Versionsnummer erhalten. Ob Pakete automatisch versioniert werden, legen Sie über die Befehlszeile fest. Die Option dafür erhält den Namen »-a« beziehungsweise »–auto-versioning«. Die gebauten Pakete und die Log-Dateien landen in einem sogenannten Kontextverzeichnis (dazu später mehr), das der Aufrufer übergibt (Listing 3).

Listing 3

Aus Git wird DEB

build() {
  local contextdir="$1"
  local repository="$2"
  local branch="$3"
  local ref="$4"
  local builddir="$5"
  local -i auto_versioning
  auto_versioning=$(opt_get "auto-versioning")
  if ! ( git clone "$repository" "$builddir/sources" && cd "$builddir/sources" && git checkout "$ref" ) &>> "$contextdir/prep.log"; then
    log_error "Konnte $repository#$ref nicht nach $builddir/sources klonen"
    return 1
  fi
  if (( auto_versioning > 0 )) && ! add_changelog "$builddir/sources" "$branch" "$ref" &>> "$contextdir/prep.log"; then
    log_error "Konnte Changelog nicht bearbeiten"
    return 1
  fi
  build_deb_from_src "$builddir" "$contextdir"
  return "$?"
}

TIPP

Näheres über die Changelogs von Debian-Paketen können Sie der Manpage des Befehls »deb-changelog« [1] entnehmen.

Automatische Versionierung

Die Funktion »add_changelog()« (Listing 4) erzeugt einen Eintrag im Changelog, dem man entnehmen kann, dass das Paket automatisch gebaut wurde. Damit keine zwei Pakete dieselbe Versionsnummer tragen und somit später gebaute Pakete als neuer erkannt werden, lassen Sie Debbot die Paketversion erhöhen.

Listing 4

Eintrag ins Changelog

add_changelog() {
  local workdir="$1"
  local branch="$2"
  local ref="$3"
  local changelog
  local package
  local verrev
  local other
  local new_changelog
  changelog="$workdir/debian/changelog"
  if ! read -r package verrev other < "$changelog" ||
     ! next_version="${verrev%-*}-$(date +"%s"))"; then
    return 1
  fi
  if ! new_changelog=$(
       printf '%s %s %s\n\n' "$package" "$next_version" "$other"
       printf '  * Automatisch gebaut von %s [%s]\n\n' "$branch" "$ref"
       printf ' -- Debbot <%s@%s>  %s\n\n\n' "$USER" "$HOSTNAME" "$(date -R)"
       cat "$changelog") ||
       ! echo "$new_changelog" > "$changelog"; then
    return 1
  fi
  return 0
}

Die Paketversion ist eine Zeichenkette nach dem Schema »5.10.158-2«. Der Teil vor dem Bindestrich – die Version – gibt der Entwickler der verpackten Software vor. Der Teil hinter dem Bindestrich nennt sich Revision und wird vom Maintainer festgelegt, also demjenigen, der das Paket baut. Um keine zwei Pakete mit derselben Revision zu bauen, verwenden Sie die Unix-Zeit als Revisionsnummer.

Den Namen des Pakets, die aktuelle Version und Revision, die Distributionsvariante (bei Debian etwa »stable«, »bullseye«, »sid« oder Ähnliches) und weitere, optionale Angaben über das Paket befinden sich durch Leerzeichen getrennt in der ersten Zeile des Changelogs:

$ toolbox (0.3.5-1) stable; prior$$
ity=medium

Da Sie nur die Revision anpassen wollen, liest »add_changelog()« die vier Werte mit »read« in die drei Variablen »package«, »verrev« und »other« ein (Listing 4, Zeile 11). Die String-Expansion »${verrev%-*}« in Zeile 12 entfernt die alte Revisionsnummer und fügt die Unix-Zeit als neue an. Die doppelte geschlossene Klammer hinter dem Aufruf von »date« ist kein Tippfehler, sondern gehört wie oben gesehen zum Format des Changelogs.

Die Verwendung der Unix-Zeit wirft gewisse Probleme auf: So könnten zwei parallel laufende Debbot-Instanzen zwei Pakete mit derselben Versionsnummer bauen. Durch Schaltsekunden kann es außerdem passieren, dass zwei Sekunden denselben Unix-Zeitstempel tragen. Die Wahrscheinlichkeit dieser Ereignisse ist allerdings sehr gering. Da sowohl der Paketmanager als auch der Bot, der das Apt-Repository verwaltet – zu ihm kommen wir in einem späteren Beitrag – das doppelte Paket verwerfen würden, machen wir uns darum keine weiteren Gedanken.

Unterbau

Damit Debbot Pakete auf Zuruf von Watchbot baut, schreiben Sie nun das Grundgerüst von Debbot. Wie üblich definieren Sie in »main()« die Optionen, die der Benutzer auf der Befehlszeile übergeben kann. Als Erstes soll er mit »–subscribe-to« festlegen können, auf welchem Topic der Bot auf Commit-Nachrichten wartet. Nach dem Paketbau soll auch Debbot selbst eine Benachrichtigung senden. Das Topic kann der Benutzer mit »–publish-to« festlegen.

Gilt es, viele Pakete abzuarbeiten, sollen mehrere Debbots starten können, um die Arbeiten parallel abzuwickeln. Damit sie sich dabei nicht in die Quere kommen und Pakete mehrfach bauen, kann der Benutzer einem Bot mit »–team« ein Team zuweisen. Ein Bot, der Pakete für ein bestimmtes Apt-Repository produziert, ist meist nur an Änderungen in einem bestimmten Branch interessiert. Der Benutzer kann daher mit der Option »–build-branch« bestimmen, aus welchem Zweig der Bot Pakete bauen soll. Zu guter Letzt gibt es noch die bereits erwähnte Option »–auto-versioning«, über die der Benutzer die automatische Versionierung einschalten kann.

Wie schon im letzten Beitrag für den Watchbot praktiziert, starten Sie mit »inst_start()« einen neuen Debbot. Die Optionen von der Befehlszeile übergeben Sie diesmal nicht direkt, sondern greifen bei Bedarf mit »opt_get()« darauf zu. Da sich dieser Teil des Bots nur unerheblich vom Watchbot aus dem letzten Beitrag unterscheidet, können Sie ihn dem Begleitmaterial zum Beitrag entnehmen.

Gruppenarbeit

Um Abhängigkeiten zwischen Teilen des Build-Systems zu vermeiden, kommunizieren unsere Bots niemals direkt, sondern ausschließlich über Topics. Abonnieren zwei Bots dasselbe Topic, erhalten beide eine Benachrichtigung für denselben Commit und bauen unabhängig voneinander dasselbe Paket (Abbildung 1).

Abbildung 3: Debbot war erfolgreich.

Abbildung 3: Debbot war erfolgreich.

Vom Paket zum Apt-Repo

Die gebauten Pakete können Sie nun direkt mit Dpkg installieren. Dazu müssen Sie sie aber erst händisch auf den Zielrechner übertragen. Dazu wollen wir auch ein Apt-Repository bereitstellen, das die Installation auf anderen Computern einfacher macht. Es soll aber nur Pakete veröffentlichen, die wir auch selbst gebaut haben und die nach dem Bau nicht manipuliert wurden. Der nächste Beitrag stellt deshalb einen Bot vor, der die von Debbot gebauten Pakete signiert. Anhand der Signatur entscheidet dann der Bot, der das Apt-Repository verwaltet, ob er ein Paket veröffentlicht oder ablehnt. (jcb/jlu)

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 7 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben