Windows Programm als Paket bereitstellen

Update: Inwzischen gibt es https://www.winepak.org/. Das scheint ungefähr das zu machen, was in diesem Artikel beschrieben wird.

Motivation

Es gibt da eine Schule, die setzt Linux auf den Arbeitsplatzrechnern ein. Das schließt die Arbeitsplätze für die Schüler der Grundschule mit ein. Auf die alte Lernsoftware soll nicht verzichtet werden. Mit wine ist es möglich, die Lernwerkstatt 8 unter Ubuntu zu starten. Die Lernwerkstatt soll natürlich auf jedem Arbeitsplatz verfügbar sein. Am Dieser Artikel beschreibt das Erstellen eines Debian Paketes mit Windows Software am Beispiel der "Lernwerkstatt 8".

Zielsetzung

Ein beliebiges Windows Programm soll als Debian Paket bereitstellt und systemweit installiert werden, sodass es von jedem Benutzer mit Hilfe von Wine ausgeführt werden kann.

Das Problem ist der sogenannte WINEPREFIX. wine legt beim ersten Start eine Art virtuelle Windows-Installation im Verzeichnis $WINEPREFIX an. Darin enthalten sind unter anderem die Windows Registry und ein virtuelles Laufwerk C:. Viele Programme wollen unbedingt auf diese Daten direkt zugreifen. Daher benötigt der Benutzer Schreibzugriff auf den WINEPREFIX. Deswegen zeigt der WINEPREFIX standardmäßig auf ~/.wine.

Ich könnte jedem interessierten Benutzer einen passenden WINEPREFIX in sein Heimatverzeichnis kopieren, doch das macht die Festplatte unnötig voll. Jeder Benutzer hätte dann seine eigene Windows-Installation nur für dieses eine Programm. Ich hingegen möchte für eine systemweite Installation einen WINEPREFIX in /opt erstellen, der root gehört. Da wine vollen Zugriff auf die darin enthaltenen Dateien braucht, muss der WINEPREFIX dem ausführenden Benutzer gehören. Hier kann UnionFS helfen.

UnionFS

UnionFS kann genutzt werden, um ein (nur lesbares) Verzeichnis unter einem anderen Einhängepunkt bereitzustellen. Außerdem kann zusätzlich ein zweites Verzeichnis in denselben Einhängepunkt kombiniert werden. Auf diese weise kann die der WINEPREFIX mit dem installierten Programm von jedem gelesen, aber nicht systemweit geändert werden. Gleichzeitig kann jeder Benutzer in den WINEPREFIX schreiben. Diese Änderungen werden aber nur für genau den Benutzer gespeichert.

image/svg+xml Anwendung Union File System nur-lesen Verzeichnis(System) schreibbares Verzeichnis(Benutzer) .cache/winebottles/bottle .cache/winebottles/bottle /opt/winebottles/bottle

Ich möchte die "Lernwerkstatt 8" in einem separaten WINEPREFIX verteilen. Diesen WINEPREFIX möchte ich anschließend unter /opt/winebottle/lernwerkstatt8 für alle Benutzer bereitstellen. Dieser WINEPREFIX muss deswegen root gehören. Mittels UnionFS lasse ich /opt/winebottles/lernwerkstatt8 in ~/.cache/winebottles/lernwerkstatt8 bereitstellen. Ich wähle dafür das Verzeichnis .cache, weil hier keine persistenten Daten gespeichert werden sollen. Auch das Verzeichnis /var/run/user/ bietet sich für sowas an (gvfs nutzt das zum Beispiel). UnionFS kann dafür sorgen, dass ~/.cache/winebottles/lernwerkstatt8 scheinbar dem ausführenden Benutzer gehört. Natürlich kann der dennoch nicht auf /opt/winebottles/lernwerkstatt8 schreiben. Daher wird ein zweites, für den Benutzer schreibbares Verzeichnis definiert. Ich habe mich für ~/.local/winebottles/lernwerkstatt8 entschieden. Jeder Schreibzugriff auf ~/.cache/winebottles/lernwerkstatt8 wird nun auf ~/.local/winebottles/lernwerkstatt8 umgelenkt. Alle Daten, welche nur gelesen werden, verbleiben in /opt/winebottles/lernwerkstatt8 und werden nicht unnötig dupliziert.

Programm installieren

Ich wechsle in ein temporäres Verzeichnis. Ich starte wine und lasse dabei ein neuen, leeren WINEPREFIX erzeugen:

>$ WINEPREFIX=lernwerkstatt8 winecfg

Ich ändere die Einstellungen so, wie die Anwendung sie braucht. Bei alter, schräger Software ist oftmals das Emulieren eines virtuellen Desktops nötig. Außerdem möchte ich vielleicht die in wine sichtbaren Laufwerke ändern. Dann installiere ich das eigentliche Programm:

>$ WINEPREFIX=lernwerkstatt8 WINEDLLOVERRIDES=winemenubuilder.exe=d wine Setup.exe

Hierbei unterdrücke ich mit WINEDLLOVERRIDES=winemenubuilder.exe=d gerne das automatische Anlegen von Programmstartern für jede neue Verknüpfung in wines Startmenü, weil ich das sowieso manuell machen muss. Ich teste das installierte Programm und nehme gegebenenfalls noch weitere Änderungen vor.

Vorbereiten für Systemweite Installation

Zum späteren Erstellen eines Debian Paketes verschiebe ich meinen WINEPREFIX lernwerkstatt8 in eine solche Verzeichnisstruktur:

$ winebottle-lernwerkstatt8
├── DEBIAN
│   └── control
├── usr
│   └── share
│       ├── applications
│       └── icons
└── opt
    └── winebottles
        └── lernwerkstatt8
            ├── dosdevices
            │   └── c: -> ../drive_c
            └── drive_c
                ├── Program Files
                │   └── eigentliche Inhalte
                │             ⋮
                └── users
                    ├── hermann
                    └── Public

In diesem Beispiel der Verzeichnisstruktur existiert im Verzeichnis users noch ein Verzeichnis hermann. Das sollte entfernt werden, weil es für alle anderen Benutzer irrelevant ist. Hierin enthaltene, aber wichtige Dateien sollten in Public verschoben werden. Innerhalb von wine wirkt dieser Ordner "für alle Benutzer".

Icons für Programmstarter können nach /usr/share/icons verschoben werden. Icons werden von einem aktivierten winemenubuilder nach der Programminstallation automatisch extrahiert, landen aber in ~/.local/share/icons. Ein offizieller Programmstarter kann in /usr/share/applications neu angelegt werden. Zum Start wird aber nicht wine direkt aufgerufen, sondern ein Startscript, welches vorher noch UnionFS einrichtet.

Startscript

#!/bin/sh

BOTTLE_NAME="lernwerkstatt8"
BOTTLE_BASE_PATH="/opt/winebottles/"
MOUNT_BASE_PATH="${HOME}/.cache/winebottles/"
WRITABLE_BASE_PATH="${HOME}/.local/share/winebottles/"

mkdir -p ${WRITABLE_BASE_PATH}${BOTTLE_NAME}
mkdir -p ${MOUNT_BASE_PATH}${BOTTLE_NAME}
unionfs-fuse -o cow,use_ino,uid=`id -u`,gid=`id -g` ${WRITABLE_BASE_PATH}${BOTTLE_NAME}=RW:${BOTTLE_BASE_PATH}${BOTTLE_NAME}=RO ${MOUNT_BASE_PATH}${BOTTLE_NAME}
# application specific commands start here
cd "${MOUNT_BASE_PATH}${BOTTLE_NAME}/dosdevices/c:/Program Files/Lernwerkstatt 8/"
wine "C:\\Program Files\\Lernwerkstatt 8\\Lernwerkstatt.exe"
# application specific commands end here
sleep 5s
if ! fusermount -u ${MOUNT_BASE_PATH}${BOTTLE_NAME} ; then
  sleep 15s
  fusermount -u -z ${MOUNT_BASE_PATH}${BOTTLE_NAME}
fi

Wenn das Paket oder wine aktualisiert wird, kann es zu Problemen kommen. Vielleicht braucht ein neues wine andere Einstellungen. Es ist sinnvoll, alle wichtigen Einstellungen innerhalb des Scripts und unmittelbar vor dem Programmstart mit Hilfe von Kommandozeilenparametern vorzunehmen. Das Emulieren eines virtuellen Desktops muss nicht zwangsläufig mittels winecfg geschehen, sondern kann auch über einen Programmstart mit wine explorer /desktop=Lernwerkstatt,800x600 Lernwerkstatt.exe erreicht werden. Das Benutzerverzeichnis enthält vielleicht teilweise Dateien, welche zur alten Version gehören. Diesem Script fehlt eine Überprüfung der Paketversion und einer Aufräumfunktion für relevante Dateien im Benutzerverzeichnis.

Paket Erstellen

Der gesamte oben abgebildete Verzeichnisbaum inklusive WINEPREFIX soll root gehören.

Inhalt der Datei control für die Lernwerstatt 8:

Package: winebottle-lernwerkstatt8
Priority: extra
Section: education
Maintainer: hermann@hehoe.de
Architecture: all
Version: 0.1~testing-3
Depends: wine,unionfs-fuse
Provides: winebottle-lernwerkstatt8
Description: Lernwerkstatt 8
Installed-Size: 679556

Installed-Size gibt die ungefähre Größe des Paketinhaltes nach der Installation wieder. Einheit ist Kilobyte (Byte durch 1024 geteilt und aufgerundet).

Das Debian Paket wird mit Hilfe von dpkg-deb gepackt:

>$ dpkg-deb -b winebottle-lernwerkstatt8

Paket bereitstellen

Im Netzwerk auf einem Webserver. Repository erzeugen mittels dpkg-scanpackages, siehe https://wiki.ubuntuusers.de/eigene_Paketquelle_anlegen.