Munki und AutoPKG

Um meine Software zu verteilen nutze ich Munki. Damit die enthaltenen Pakete auch immer aktuell sind und ich nicht jede einzelne Herstellerseite absurfen muss, kommt AutoPKG zum Einsatz. Diese Software lädt die Pakete von diversen Websites runter, fügt noch ggf. Installscripte hinzu und kopiert sie in das Munki-Repo. Nun muss ich also nur regelmäßig ein Script starten und das tut den Rest. Eine Installationsanleitung ist bei Github zu finden.

Für jede Software gibt es Recipes, also Rezepte (u.a. für den Download). Um diese den eigenen Bedürfnissen anzupassen, kann man Overrides erstellen.

$ autopkg search QuickLookCSV

Name                             Repo             Path                                    
----                             ----             ----                                    
QuickLookCSV.pkg.recipe          jps3-recipes     QuickLookCSV/QuickLookCSV.pkg.recipe    
QuickLookCSV.download.recipe     jps3-recipes     QuickLookCSV/QuickLookCSV.download.recipe
QuickLookCSV.munki.recipe        jps3-recipes     QuickLookCSV/QuickLookCSV.munki.recipe  

To add a new recipe repo, use 'autopkg repo-add <repo name>'

$ autopkg repo-add jps3-recipes
Attempting git clone...

Adding /Users/admin/Library/AutoPkg/RecipeRepos/com.github.autopkg.jps3-recipes to RECIPE_SEARCH_DIRS...
Updated search path:
  '.'
  '~/Library/AutoPkg/Recipes'
  '/Library/AutoPkg/Recipes'
[...]

$ autopkg make-override QuickLookCSV.munki.recipe
Override file saved to /Users/admin/Library/AutoPkg/RecipeOverrides/QuickLookCSV.munki.recipe

Um nun alle Recipes auszuführen, führe ich folgenden Befehl aus:

$ autopkg run /Users/admin/Library/AutoPkg/RecipeOverrides/*.recipe MakeCatalogs.munki

Wenn ich nun die AutoPKG-Installation zu einem neuen Rechner migieren möchte, kopieren ich nur die RecipeOverrides auf den neuen Rechner und führe danach folgendes Script aus:

#!/bin/sh
autopkg repo-add "recipes"
for recipe in ~/Library/AutoPkg/RecipeOverrides/*
do
    repo=$(cat "${recipe}" | grep ParentRecipe -A 1 | tail -n 1 | tr -d "[:blank:]" | sed "s/<string>(.*)</string>/1/" | sed "s/.*.github.(.*)..*..*/1/")
    repoarray+=("${repo}")
done

IFS=$'n' repoarray=($(sort <<<"${repoarray[*]}"))
IFS=$'n' repoarray=($(uniq <<<"${repoarray[*]}"))

for repo in "${repoarray[@]}"
do
    echo "${repo}"
    autopkg repo-add "${repo}-recipes"
done

Das Script liest aus jeder Override-Datei das Parent-Recipe und dessen Repository aus und fügt es per autopkg repo-add xxx hinzu. So wird auch automatisch ein Update des bisherigen Repos durchgeführt.

Munki mit Git

Ich setze für meine Softwareverteilung auf Macs Munki ein. Das ist eine Software, die serverseitig nur einen Webserver und ein paar XML-Dateien benötigt. Der Client fragt dann regelmäßig am Server an und erhält so Updates. Die Funktion und Einrichtung von Munki ist aber im offiziellen Wiki viel besser dokumentiert.

In diesem Beitrag soll es um die Versionierung des Munki-Repositories mit Git und anschließender Veröffentlichung auf einem Server gehen.

Ich habe einen Webserver lokal auf meinem iMac laufen. Hier werden neue Pakete in Munki importiert, Software getestet und die ganze Verteilung geprüft. Ein weiterer Webserver läuft im Internet, der das gleiche Munki-Repo anbietet, sodass alle meine (und befreundete) Macs den Update-Server erreichen und Software installieren können.

Ich gehe davon aus, dass deine Munki-Installation funktioniert und so oder so ähnlich wie im Demonstration Setup konfiguriert ist.

Munki-Repository mit Git versionieren

Um nun das Repo zu versionieren, muss zuerst ein Git-Repository erstellt werden. Dazu wechselt du im Terminal in dein Munki-Repo und gibst folgenden Befehl ein:

$ git init

Danach müssen noch sämtliche Dateien eingecheckt werden, die versioniert werden sollen. Da das mit Binärdaten aber eher nicht so toll funktioniert, ignoriere ich den Ordner pkgs per .gitignore im Hauptverzeichnis meines Git-Repos.

$ cat .gitignore
/pkgs/
.DS_Store
$ git add .
$ git commit -m "First commit"

So sind alle Dateien – außer die Installer-Dateien –  eingecheckt und somit versioniert. Wenn ich nun ein Softwarepaket verändere (im Beispiel den Katalog von testing auf production ändere), dann kann ich dies also in Zukunft nachvollziehen und im Zweifel rückgängig machen.

Bildschirmfoto 2015-09-04 um 14.32.01

Remote-Repository einrichten

Nun möchte ich dieses lokal konfigurierte und getestete Repository auf meinem Server im Internet bereitstellen. Dies ist relativ einfach mittels Remote-Repository zu erreichen.

Hierzu muss auf dem Webserver ein Git-Server laufen und ein Headles-Repository eingerichtet sein. Wie ein Git-Server installiert und eingerichtet wird, erfährst du an anderer Stelle.

$ mkdir munki-test.git
$ cd munki-test.git
$ git --bare init

Danach wird das Remote-Repository dem lokalen Git-Repository hinzugefügt.

Bildschirmfoto 2015-09-04 um 14.48.07

Und nun kann das lokale Branch an den Remote-Server gepusht werden.

Bildschirmfoto 2015-09-04 um 23.48.34

Die Eigenheit von einem Bare-Repository ist, dass es nur die Git-Metadaten vorhält und nicht einen Branch auscheckt. Somit liegt das Munki-Repository nicht im direkten Zugriff auf dem Server. Das Git-Repo muss nach dem Push noch in Richtung Webserver ausgecheckt werden.

git --work-tree=/var/www/munki_repo --git-dir=/home/git/munki-test.git checkout -f

Da dieser Befehl nach jedem Push (also Upload zum Server) passieren muss, kann man es über die sog. Git Hooks lösen. In dem Git-Ordner auf dem Server sollte eigentlich schon ein Unterordner namens hooks mit ein paar Beispiel-Dateien liegen. Hier wird eine neue Datei namens post-receive angelegt, die in etwa so aussehen kann:

#!/bin/sh
git --work-tree=/var/www/munki_repo --git-dir=/home/git/munki-test.git checkout -f

Eingangs hatte ich geschrieben, dass ich die Binaries nicht mittracke. Wenn ich das nicht tue, werden sie natürlich auch nicht mit hochgeladen und auf dem Webserver bereitgestellt. Dies mache ich separat mit einem rsync. Und damit es auch automatisch läuft, nutze ich in meinem lokalen Repository den pre-push-hook.

$ cat pre-push
#!/bin/sh

remote="$1"
url="$2"

z40=0000000000000000000000000000000000000000

IFS=' '
while read local_ref local_sha remote_ref remote_sha
do
    if [ "$local_sha" = $z40 ]
    then
        # Handle delete
        :
    else
        if [ "$remote_sha" = $z40 ]
        then
            # New branch, examine all commits
            range="$local_sha"
        else
            # Update to existing branch, examine new commits
            range="$remote_sha..$local_sha"
        fi

        ### do the rsync
        rsync -hrLvzp --size-only --ignore-errors --stats --timeout=60 --delete-after --progress --exclude-from="/Users/admin/munki/syncfolders.exclude" /Users/admin/munki/munki_repo/pkgs/ username@server:/var/www/munki_repo/pkgs
        if [ $? != 0 ]; then
            echo "rsync error"
            exit 1
        fi
        ssh username@server "chown -R www-data:www-data /var/www/munki_repo/pkgs"

        # Check for WIP commit
        commit=`git rev-list -n 1 --grep '^WIP' "$range"`
        if [ -n "$commit" ]
        then
            echo "Found WIP commit in $local_ref, not pushing"
            exit 1
        fi
    fi
done

exit 0

Nun wird also der Ordner pkgs per rsync zum Server synchronisiert, das Git-Repo zum Server gepusht und serverseitig das Git-Repo in den www-Ordner ausgecheckt.

Dieses ganze Szenario habe ich bisher nur in eine Richtung getestet. Also von meinem Client zum Server. Git ist ja für die verteilte Versionskontrolle bekannt. Besonders das rsync-Script ist aktuell nicht darauf ausgelegt, mehrere Quellen (also 2-3 Rechner von Munki-Admins, die Updates bereitstellen) zu bedienen.