 Okay, also ich halte jetzt meinen Vortrag Paketmanagement vom Scratch und daran beschreibe ich, wie ich eben dazu kam, an eigenen Paketmanagen zu entwickeln. Also erst mal über mich. Ich studiere Elektrotechnik am KIT hier in Karlsruhe. Ich bin freundfreier Software und momentan verwende ich einige meiner Freizeit dafür, um an der Mathe Library in Dean JavaScript geschrieben ist, namens SmartJS mitzuarbeiten. Sonst eigentlich nicht so viel Interessantes. Erst mal, was ist überhaupt ein Paketmanager und warum wollte ich meinen eigenen schreiben? Also unter Paketmanagement verstehe ich erstmal ein Programm, das einem hilft, erstens den Bild von der Software zu automatisieren, damit man nicht die ganzen Befehle von Hand eintippen muss. Und dann auch noch das Ganze zu paketieren und die Installation zu automatisieren und vor allem auch die Deinstallation zu Managen und Dependency ist natürlich. Gut, warum mache ich da mein eigenes? Also ich habe angefangen, mich mit Linux from Scratch zu beschäftigen. Das ist eine Linux-Distribution, die eigentlich in Form eines Buches kommt. In dem beschrieben wird, wie man direkt aus den Quellen von den verschiedenen Projekten seine Linux-Distribution zusammen kompiliert. Also ganz ohne Paketmanagement oder sonst irgendwas. Und das ging ziemlich häufig schief. Also ich habe mehrere Anläufe gebraucht, habe ständig wieder von vorne anfangen müssen. Also dachte ich mir, brauche ich ein Paketmanager, dann kann ich einzelne Schritte rückgängig machen. Und ich hätte dafür ein Bestehenden nehmen können, aber da ich ja eh Linux from Scratch als einfach zum Lernen benutzt habe, um zu lernen, wie ein Linux-System aufgebaut ist, habe ich mir gedacht, warum nicht auch gleich lernen, wie Paketmanagement funktioniert und einen eigenen Paketmanager schreiben. Ich meine, immerhin bei Slackware hat das ja auch einigermaßen gut funktioniert, also kann ich das auch selber machen. Ich habe mich für die Sprache, in der ich das gemacht habe, für Bash entschieden, weil die Toolchain von Linux from Scratch, also man baut erst eine Toolchain und mit dieser Toolchain baut man dann das eigentliche System und die enthielt halt entweder Perl oder halt Bash und ich wollte nicht mit Perl rumhantieren, dann habe ich halt die Bash genommen. Ach ja, noch was Fragen bitte am besten gleichstellen, dann kann ich mich gleich auf, also wenn es sich gleich auf das Thema bezieht, was ich gerade bespreche. Ansonsten, wenn es irgendwelche Sachen sind, die jetzt nicht direkt zu dem passen, was ich sage, dann bitte am Ende. Gut, erst mal so ein paar Fragen. Wer hat von euch schon mal selber Software kompiliert? Okay, das ist gut. Wer hat schon selber Pakete geschrieben? Schon weniger. Wer von denen die Pakete geschrieben haben, haben einen Devian Paket geschrieben. Okay, und wer hat schon mal seinen eigenen Paketmanager gemacht? Okay, also wenn ihr alle schon mal Software kompiliert habt, dann dürft es jetzt relativ schnell gehen. Also was relativ freudig verwendet wird von Programmen, es sind die sogenannten Auto Tools und da lässt man einfach dieses Konfigurskript laufen, gibt dem noch ein Install prefix mit, also zum Beispiel wenn es nach Slash Use installieren soll, macht danach ein Make und ein Make Install und die meisten, also wenn man mit Autoconf arbeitet, dann hat dieses Make File auch diese Desk der Variable, mit der man eben sagen kann, wohin das Programm installiert wird, das wird später noch wichtig. Genauso manche Programme verwenden Seamake, da ist halt die Sonntags ein bisschen anders und manche bringen auch nur Make Files, sondern muss man die Dateien am Ende selber von Hand kopieren. Was macht das? Ach so, das ist ein Fehler von dem Tool, mit dem ich meine Präsentation gemacht habe, das sollte eigentlich mit Unterstrichen verbunden sein, ist mir wohl durchgeflutscht. Da ich ja meinen eigenen Paketmanager schreiben wollte, muss ich mich erstmal informieren, wie man denn überhaupt Paketmanagement betreiben kann. Also habe ich mir verschiedene Konzepte angeschaut. Und zwar ist diese Liste größtenteils entnommen aus Linux vom Scratch, wo einfach durch ein paar Konzepte durchgegangen wird. Also der einfachste Ansatz wäre, man merkt sich einfach alles, was man so installiert hat und dann ist das Paketmanagement, aber das skaliert relativ schlecht. Ein weiterer Ansatz wäre, dass man auf dem gesamten Installationsprozess ein S-Trace ausführt. Das heißt, man schneidet jeden einzelnen System Call mit und weiß damit, welche Datei wo angelegt wurde, auch relativ aufwändig. Ein einfacherer Ansatz ist, dass man Timestamps verwendet, um herauszufinden, was zu dem gewissen Zeitpunkt installiert wurde. Also wenn jetzt nur eine Installation zehn Sekunden braucht, dann kann ich alle Dateien, die in Änderungsdatum von diesem Zeitraum haben, als zu diesem Paketzugehörig betrachten. Und ich finde die ganzen Dateien, die zu einem Paket gehören, anhand von einem Feindbefehl, der dann die Änderungszeit nimmt. Schwierig, wenn man in relativ kurzen Abständen mehrere Pakete installiert und man bräuchte auch nochmal zusätzlich eine Liste, um sich das ganze, um das Mapping zu haben, von welches Paket ist es eigentlich, das zu dieser Zeit erstellt wurde. Und man kriegt auch dann Probleme, wenn man anfängt, sein System woanders hinzukopieren. Also auch nicht unbedingt optimal. Was auch noch eine Möglichkeit ist, ist, dass man für jedes Programm ein eigenes Verzeichnis macht. Also das kennt man ja unter Windows teilweise oder unter macOS diese Apps. Oder eben in der Linux-Welt machen das zum Beispiel NixOS und Greeks. Die haben aber einen großen, also dieses Konzept hat einen großen Nachteil. Die meisten Bildsysteme sind nicht wirklich darauf ausgelegt, dass man jedes Programm in einem eigenen Ordner macht. Gerade was das Linken gegen die Libraries angeht, man müsste da, man muss dann bei vielen die Bildsysteme patchen und LD Library Preload setzen, damit das sich alles richtig gegeneinander baut. Das heißt, der Auffand ist einfach zu groß. Ein anderer auch ganz schöner Ansatz ist Union Mounts zu verwenden. Also Union Mount ist, wenn man mehrere Dateisysteme aufeinanderstapelt. Man hat zum Beispiel ein Base-Dateisystem. Und wenn man ein neues Programm installieren möchte, dann macht man eine neue Partition oder einen neuen Ordner. Macht davon ein Bindmount auf das Dateisystem drauf. Und alle Schreibänderungen, die man dann macht bei der Installation, landen in diesem separaten Ordner. So ein Ansatz machen tatsächlich Tiny Core und Puppy. Das ist ein ziemlich cooler Ansatz, aber wenn man jetzt davon ausgeht, dass man 100 Pakete hat, hätte man halt 100 aufeinander gestapelte Dateisysteme. Und außerdem war zu dem Zeitpunkt, als ich den Paketmanager geschrieben hatte, OverlayFS noch nicht im Kernel. Das heißt, ich hätte damit Union FS rummachen müssen. Und das wäre dann auch zu viel Aufwand gewesen. Also das auch nicht. Ein Ansatz, der in der Linux From Scratch Community auch relativ beniebt ist, ist der Paketnutzeransatz. Man legt für jedes einzelne Paket einen eigenen Nutzer an und führt dann die Installation mit diesem Nutzer aus. Das heißt, man kann nachher die Zugehörigkeit zu dem Paket herausfinden, indem man einfach alle Dateien von diesem Nutzer auflistet. Das hat den Nachteil, dass man unglaublich viele Nutzer rumliegen hat. Und man kommt spätestens dann an Probleme, wenn man Programme braucht, die das ZUID-Bit setzen und Root sein müssen. Also beispielsweise Xorg. Bis vor Kurzem, und auch wenn man nicht SystemDilogIndy verwendet, musste man Xorg immer als Root starten. Und dafür war die Xorg Executable in der Regel eine ZUID-Binary, die als Root angelegt war. Und solche Sachen muss man bei dem Paketnutzeransatz nochmal separat handeln. Was auch in letzter Zeit recht beliebt ist, es einfach Container zu machen. Also es gibt ja diese verschiedenen Namespacing-Geschichten in Linux-Körnel. Docker verwendet sowas zum Beispiel. Schlaure Ansätze sind das, was Juwuntu Snappy und XdgApp vom GNOME-Projekt machen. Dass man Libraries in Container-Pakt und Anwendungen in Container-Pakt und die eben zusammen kombiniert. Damit hat man die Möglichkeit, mehrere Libraries gleichzeitig installiert zu haben. Und also ohne dass die sich gegenseitig in Konflikt geraten. Ist aber alles noch in den Kinderschuhen mehr oder weniger. Also Docker nicht, aber die Ansätze kannte ich auch tatsächlich noch nicht, als ich mit dem Paketmanager angefangen habe. Also habe ich mich auch nicht dafür entschieden. Und man kann natürlich auch mehrere von den oben gesehen, also von den bisher gezeigten kombinieren. Also zum Beispiel Puppy kann auch Ubuntu-Pakete installieren und so zeigt es. Der Ansatz den aber tatsächlich die meisten Distributionen verwenden, bzw. die meisten Paketmanager, ist der sogenannte Fake-Root-Ansatz. Man kompiliert einfach sein Programm ganz normal mit seinem Configure und Make. Gibt aber bei der Installation bei dem Make-Install einfach dieses Desktop hiermit an und legt einen eigenen Ordner an, in den das dann reininstalliert wird. Also nicht ins laufende System reininstalliert, sondern in einen separaten Ordner. Davon kann man dann ein Tarbol machen und Metadaten hinzufügen und dann hat man sein Paket und das kann man weiter verteilen, woanders installieren und wie auch immer managen. Also das machen zum Beispiel Slackware, die Debian-Pakete, RPM, Entschließungs-GEN2. Also GEN2 macht davon meistens dann kein Tarbol, sondern macht nur die Installation in den Ordner und kopiert es dann ins laufende System rein. Aber der Ansatz ist überall gleich. Das heißt, die meisten Bildsysteme sind auch darauf ausgelegt und man muss relativ wenig Aufwand betreiben, um das selber umzusetzen. Man kann auch bei anderen abgucken. Okay, so, da ich Arch Linux nutzer bin, habe ich mich intensiv von Arch Linux-Paketmanager Petman inspirieren lassen und deren Package-Bild-Konzept umgesetzt. Das ganze Paket ist durch genau eine Datei definiert, ein sogenanntes Package-Bild und das ist im Prinzip einfach nur ein Best Script. Und was man da machen kann, ist, man kann anfangen, Variabeln zu definieren. Also hier habe ich eine Versionierung eingeführt, damit, wenn ich verschiedene Versionen von dem Paketmanager habe, dass da nicht Konflikte auftreten, weil man alte Package-Bilds hat. Dann kommt noch einiges, auf das ich später eingehen werde. Man kann Array angeben mit den URLs, die hier runtergeladen werden. Und dafür, da kann man ja dann auch, weil das ein ganz normales Best Script ist, die Variablen von hier verwenden. Und zur Überprüfung, dass der Download auch korrekt war, kann man dann eine Check-Summe angeben und hier später kommen dann Funktionen. Das ist so implementiert, dass der Paketmanager einfach nur mit Source dieses Package-Bild in sich reinlädt. Er hat intern schon vordefinierte Funktionen und also diese Variablen und Funktionen, die da drin stehen, sind vordefiniert, werden beim Rheinladen überladen bzw. überschrieben. Hat den Vorteil, dass wenn in dem Default schon Sachen gemacht werden, wenn der Default schon genau das macht, was man möchte, zum Beispiel Make, dann kann man das einfach weglassen und muss es nicht mehr extra in sein Package-Bild reinschreiben. Das spart dann Code in den Package-Bilds. Also was ist der erste Schritt, was ein Paketmanager tun soll? Der erste Schritt ist, dass der Bild automatisiert werden soll. Dafür habe ich in diesem Package-Bild, das ich gerade erläutert habe, einfach mehrere Funktionen definiert. Es gibt einmal die Prepare-Phase. Da steht dann das Entpacken drin und zwar werden vom Paketmanager einige Variablen vordefiniert, zum Beispiel hier dieses Bildtier und dieses Source ist der. Source ist der Ordner, in den automatisch der Quake-Code runtergeladen wird und man kann dann einfach den Tabefee ausführen, der diese Quake-Code entpackt und in den Bild der Ordner reinmacht. In dem Fall muss man dann noch manual reingehen und hier kann man dann zum Beispiel noch Patches anwenden und das Configure ausführen. Also das ist die Vorbereitungsphase. Die nächste Funktion in dem Package-Bild ist dann die Bildfunktion. Da steht in der Regel einfach nur ein Make drin. Da kann aber auch einfach nur ein Aufruf am Compiler drin stehen, wenn man nur einfach ein C-Code hat zum Beispiel. Die nächste Funktion wäre dann eine Checkfunktion, die führt diese Unit-Tests aus, wenn es welche gibt, Make-Check und letztendlich die Installation. Hier gibt es die Variable $Fake-Route und das ist dieser Ordner, dieses Fake-Route, der für diesen Ansatz benutzt wird, in den alles reininstalliert wird. Das heißt, in meinem Package-Manager muss gar nicht viel Logik implementiert sein, sondern das steht alles in dem Package letztendlich drin. Funktioniert auch also relativ einfach, das dann umzusetzen. Also der erste Prototyp, der einfach nur diese Funktionen ausgeführt hat, der war, ich glaube, 200 Zeilungen. Das nächste, was man aber gerne haben möchte, ist, dass man zurückverfolgen kann, welche Dateien zu einem Paket gehören. Das heißt, ich muss anfangen, Metadaten zu speichern und in dem Fall dadurch, dass ich ja einen separaten Ordner habe, in dem alles drin liegt, kann ich einfach Feindbefehle ausführen. In dem Fall aufgespültet, das hätte man auch in einer Zelle machen können. Erst finde alle Dateien und schreibe es in eine Datein namens Fileswein in diesem Fake-Route, also das ist dann in dem Paket einfach mit drin. Dasselbe mit Sim-Links in der separaten Datein namens Links und dasselbe, oh Gott, da habe ich einen tatsächlich Fehler gemacht, weil hier sollte eigentlich Links stehen und nicht das, egal. Und dasselbe auch noch für Ordner, dann hat man eine Liste mit allen installierten Ordnern und zusätzlich, damit man das Paket auf verifizieren kann, nachdem man das installiert hat. Oder wenn man es runtergeladen hat, als Binärpaket, dass man verifizieren kann, ob das Paket in Ordnung ist, gibt es noch eine Liste mit SHA-1-Checksum für jede Datei in dem ganzen Paket. So, jetzt hat man diese Dateiliste und möchte daraus ein Paket erstellen. Da das ja einfach ein Ordner ist, kann man ganz einfach ein Tarbol davon machen. In dem Fall führt man das mit Fake-Route auf. Fake-Route hat die Eigenschaft, dass das, was danach ausgeführt wird, denken lässt, es sei Route. Das hat zur Folge, dass wenn man den Build-Prozess und dieses Make-Install als normaler Nutzer macht, dass dann trotzdem am Ende die Dateien in diesem Tarbol vom Nutzer-Route sind. Und zwar ohne, dass man im ganzen Prozess jeweils Route-Rechte gebraucht hat. Das ist dann nur das, was in dem Tarbol am Ende drinsteht. Und hier eben noch Flats gleich Null, damit man parallel komprimieren kann. Zur Installation könnte man erst mal naiv denken. Man entpackt einfach diesen Tarbol in seinem Route-Verzeichnis. Das hat aber einen entscheidenden Nachteil. Nämlich in meinem Fall war es so, dass userlib64 ein Simlink auf slash-user-slash-lib war. Das heißt, wenn ich jetzt einfach ein Make-Install in das Route-Datei-System gemacht hätte, dann wäre alles, was im Ordner userlib64 gelandet wäre, direkt in userlib gelandet. Macht auch Sinn, wenn man kein Betriebssystem baut, das sowohl 64 als auch 32 BitLibraries hat. Dadurch, dass aber die Make-Install-Phase nicht in das echte Datei-System rein passiert, sondern in dieses Fake-Route legt er die Ordner neu an. Also ist userlib64 dann am Ende gar kein Simlink auf userlib, sondern ein eigener Ordner. Wenn man davon jetzt ein Tarbol macht und den im Route entpackt, überschreibt dieser Ordnerlib64 den Simlink auf userlib und es gibt auf einmal zwei verschiedene Ordner, userlib und userlib64, die verschiedene Libraries enthalten und alles, was vorher gegen userlib64 Library-Name gelingt hat, findet die Library nicht mehr und damit ist das System kaputt. Sprich, Tar an sich alleine funktioniert nicht. Also habe ich mir überlegt, um es zu installieren, entpack ich erst den Tarbol in diesem Fake-Route Ordner und benutze dann einfach Async, um das in Slash rein zu installieren. In dem Fall R für Rekursiv, L für R, um Simlinks als Simlinks zu kopieren. Die Berechtigungen und die Gruppen sollen erhalten werden. Die Besitzer von den Dateien sollen erhalten werden. Berätete Dateien sollen aufinstalliert werden können. Das ist gerade fürs Basisystem wichtig. Zum Beispiel DevNull und DevZero sind ja Gerätetateien, die man manuell in seinem System anlegen muss am Anfang. Die werden nicht dynamisch von Udev erstellt. Und hier ist dann das Wichtige, warum ich überhaupt Async verwendet habe. Dieser Carfleck sorgt dafür, dass ein Simlink auf einem Ordner gefolgt wird und nicht überschrieben wird mit einem Ordner. Um gerade das zu verhindern, dass man seine Library-Ordner aufsplittet. Ich kann jetzt schon ein Paket Bauen automatisiert und ich kann es installieren. Jetzt ist es aber so, dass man manchmal noch mehr machen muss, als nur das Paket zu installieren, sondern zum Beispiel Caches-Updaten, Eigencaches, Nutzern anlegen usw. und sofort. Das heißt, man braucht weitere Funktionen, die vor und nach der Installation ausgeführt werden. Zum Beispiel, dafür gibt es diese Funktion LFSME pre-install und host-install. Die werden beide als Route ausgeführt. Das ist noch so ein Punkt. Während man das Paket erstellt, kann man ja keine Sachen als Route machen. Man möchte eigentlich das Compilieren und das Make-Install als normaler Nutzer ausführen. Das heißt, für bestimmte Dinge hat man die Privilegien nicht. Das heißt, das muss man dann separat in dem pre-install und post-install machen. In dem Fall habe ich das post-install zum Beispiel genutzt, um zu überprüfen, ob eine umgebungsvariable gesetzt ist. Das habe ich gebraucht für die Toolchain, damit die Stelle an der etwas installiert wird korrekt ist. Für das post-install kann man zum Beispiel Info-Pages hinzufügen. Da muss man diesen install-info-Befehl ausführen und Nutzer anlegen. Zum Beispiel, wenn man SSH installiert, den SSH-Nutzer anlegen und entsprechende Dateien diesen Nutzer zuweisen. Man kann nicht einfach irgendeine User-ID in Etsy Pass-WD reinschreiben, weil die können ja schon von einem anderen Paket erstellt worden sein. Das heißt, man muss es wirklich bei der Installation machen, um keine Konflikte zu haben. Wenn man Pakete installieren kann, dann will man sie auch wieder deinstallieren können. Das ist dann auch relativ einfach anhand der Metadaten, die man hat. Man erstellt einfach eine Liste mit allen installierten Anwendungen, also mit den Dateien in allen installierten Paketen, und guckt dann, also geht dann von dem Paket, was man deinstallieren möchte, alle Dateien durch. Sind die von einem Gehirn, die schon noch zu irgendeinem anderen Paket? Wenn sie nicht zu irgendeinem anderen Paket gehören, dann deinstalliert man sie. Das selbe für Ordner. Und SimLinks, da habe ich das dann so gelöst, dass von den Links, die in der Links-Datei drin sind in dem Paket, diejenigen gelöscht werden, die auf nichts mehr zeigen. Da ist mir, als ich diese Präsentation gemacht habe, dann aufgefallen, dass es vielleicht gar nicht so schlau ist, weil wenn man jetzt beispielsweise ein Paket hat, das nur ein SimLink von, also User-Bin-CC auf User-Bin-GCC erstellt, um den GCC auch über CC verfügbar zu haben, dann würde dieser Algorithmus beim deinstallieren, diesen SimLink nicht entfernen, und das ist mir erst jetzt aufgefallen. Das ist vielleicht ein bisschen suboptimal, aber in der Praxis hat das alles ganz gut funktioniert. Und auch hier vor dem deinstallieren möchte man zum Beispiel noch mal die Info-Pages wieder entfernen und eventuell nach dem deinstallieren auch noch irgendwas machen. Da ist mir jetzt kein konkretes Beispiel für eingefallen. Eine Voraussetzung für das deinstallieren habe ich noch nicht erwähnt. Man muss ja eine Liste haben mit allen Dateien, die überhaupt installiert worden sind. Der erste naive Ansatz, den ich gegangen bin, beziehungsweise der pragmatische Ansatz ist, man erstellt einfach sein Paket, installiert es, und dann hat man irgendwo auf seiner Platte ein Verzeichnis, das heißt, installed, und da tut man einfach alle Pakete rein. Dann hat man eine Liste mit allen installierten Paketen. Was aber viel schöner wäre, wäre einen globalen Index zu haben, der alle installierten Pakete und alle Dateien enthält. Das ermöglicht einem dann später auch nach dependency zu gucken, ob die installiert sind und so weiter und so fort. Und dafür bin ich dann relativ einfach einen Ansatz gegangen. Ich mache dann nämlich einfach nur einen Ordner in installprefix-indexter. Das ist in der Regel, also in der Standard-Einstellung, ist das slash var slash LFSME. Und dort ist erstmal der Datei drin, um den Index zu versionieren. Da steht eine Zahl drin, zum Beispiel zwei, um dafür zu sorgen, dass nicht die falsche Paketmanager-Version auf den Index zugreift und ihn dadurch kaputt macht. Und dann kriegt jedes Paket seinen eigenen Ordner. Und dort drin nochmal ein Ordner mit der Versionsnummer. Und da sind die Metadaten dann einfach alle aufgelistet, inklusive dem Package-Weld selbst. Das heißt, den Index zu haben reicht schon aus, um das Paket wieder neu zu bauen. Man muss also das Paket, wenn man das einmal gebaut und installiert hat, nicht mehr bei sich haben. Also man muss es nicht aufbewahren, man hat in dem System, in dem die Pakete installiert sind, alles Nötige zur Verfügung, um das Paket wieder neu bauen zu können. Und man hätte auch so machen können, dass man ein Ordner macht für jedes Paket inklusive der Versionsnummer, also Package-Name-Package-Version. Das ist aber schwieriger zu pasen. Dann müsste ich zum Beispiel anfangen zu definieren, dass in meinem Paket-Namen keine Bindestriche drin vorkommen, wenn ich jetzt Paket-Name-Versionsnummer machen würde. Und so tue ich mir einfach leichter, dass die Namen des Pakets und die Versionsnummer voneinander zu unterscheiden. Und es kann auch Fälle geben, wie zum Beispiel Pfeifen, bei denen dann mehrere Versionen von dem Paket gleichzeitig installiert sind. Das würde zwar mit dem anderen Ansatz auch gehen, aber ich fand das so eigentlich ganz nett. Und damit man mit diesem Index auch was anfangen kann, gibt es den LFSME-Index-List-Befil. Und der gibt einfach den gesamten Paketindex aus. Und man kann auch nach installierten Paketen suchen, indem man eine Rack-Exter hinteran gibt. Und das ist relativ leicht zu implementieren, indem man einfach das hier nimmt, den Output davon an grep weiter gibt und die Rack-Expression nach der gesucht wird einfach hier bei dem grep reinschreibt. Das heißt, dadurch, dass das Ganze in Bash implementiert ist, war das hier im Prinzip ein No-Brainer, das so zu implementieren. Und wenn man dann angefangen hat, verschiedene Pakete zu installieren und also ich habe dann angefangen, mein Basis-System damit zu paketieren, irgendwann xorg drauf zu haben, dann bis zu einem GNOME hoch. Und da kommen schon so um die 1.000 Pakete zusammen, bis man das soweit hat. Und es gibt viele Pakete davon, die tatsächlich Dateien haben, die in anderen auch enthalten sind. Und bei dem Ansatz, wie ich ihn bis zu dem Zeitpunkt hatte, wäre es einfach so gewesen, dass bei der Installation jedes neue Paket, die Dateien von dem vorherigen Paket überschreiben. Und das kann dann dazu führen, dass Sachen nicht mehr so funktionieren, wie sie sollen. Und man findet vielleicht auch Fehler in seinem eigenen Billscript, nicht mehr richtig. Also man findet Fehler in seinem eigenen Billscript. Wenn man einfach guckt, gibt es diese Datei eigentlich schon. Also habe ich ein Konflikt-Management eingebaut. Das guckt einfach, existiert diese Datei schon im Datei-System. Wenn ja, breche ab. Und dann findet man Fehler in den Billskripten und Fälle, in denen Dateien sonst einfach überschrieben würden, was dann wiederum so Dinge nach sich zieht, wie das, wenn man die Checksum prüft, dass sie dann nicht mehr stimmen. Und das zieht einen ganzen Rattenschwanz hinter sich her. Ein Problem ist dabei aber, dass wenn man bei Linux from scratch zum Beispiel die Toolchain erstellt, manche Dateien überschrieben werden müssen. Zum Beispiel erstellt man GCC in zwei Phasen. Man kompiliert erst einen GCC mit dem GCC, das Systems von dem, was man die Toolchain macht. Und danach mit diesem neuen GCC kompiliert man nochmal den, der letztendlich für das Betriebssystem verwendet wird. Und dabei werden manche Dateien einfach überschrieben. Und da das nicht mehr funktioniert, wenn man diese Konflikt-Detection eingeführt hat, musste ich mir was anderes ausdenken, nämlich ein Array, der einfach eine Liste mit zu überschreiben in Dateien enthält. Und, ja, so. Ja, also, was jetzt noch fehlt, also wir können jetzt Sachen installieren, bauen, deinstallieren, überprüfen, was installiert ist, Konflikte erkennen und so weiter. Aber was jetzt noch fehlt, ein wichtiges Feature, sind Abhängigkeiten. Und das ist an sich schon, also es ist im Prinzip ein relativ schwieriges Problem. Da mein Paketmanager kein Konzept von Repositories kennt, kann man auch keine Pakete automatisch nachziehen, wenn sie fehlen. Also habe ich mich dazu entschieden, eigentlich kann ich das Abhängigkeitsmanagement, ja, dem Nutzer, der die Pakete installiert, überlassen. Ich würde aber gerne zumindest überprüfen, beide Installationen sind denn die Abhängigkeiten von dem Paket installiert, weil sonst kommen irgendwelche Fehler, von denen man sich fragt, woher sie kommen. Weil manche Programme lassen sich auch dann kompilieren, wenn die Abhängigkeit nicht da ist, aber dann fehlen irgendwelche Funktionen. Und sowas wollte ich gerne auffangen. Das heißt, anstatt Abhängigkeiten automatisch aufzulösen und automatisch nachzuinstallieren, habe ich zumindest eine Funktion eingebaut, die prüft, ist denn die Abhängigkeit installiert und eine Fehlermeldung ausgibt, wenn nicht. Und einem sagt, ja, installiert es bitte selber nach. Das ist umgesetzt über so diesen Dependencies Array. Und das ist ein Array, der unterscheidet nicht zwischen Make Dependencies und also Build Dependencies und Install Dependencies, also bzw. nicht Install Dependencies, sondern also diese Run-Time-Dependencies, weil da das davon ausgeht, dass man so ein Linux von Scratch macht und alles eh aus den Quellen installiert, will man sowieso immer die Build Dependencies mit dabei haben und es ist auch einfacher zu implementieren. Und das unterstützt Konflikte. Ich habe dann gleich dafür gesorgt, dass ein Paket mit sich selbst in Konflikt steht. Das wird man in jedem dieser Pakete, die ich erstellt habe, finden, dass ein Paket mit sich selbst in Konflikt steht. Wenn es also schon da ist, wird es nicht noch mal installiert. Das ist vor allem dann nützlich, wenn in diesen Post-Install-Funktionen irgendwas angelegt wird, was zu Konflikten führen würde, wenn man das zweimal hintereinander machen würde. Das heißt, anstatt es dann drüber zu installieren, müsste man es erst deinstallieren. Dann kann man das Post-Remove und Pre-Remove-Script ausgeführt werden, was dann das alles wieder aufräumt und dann kann man das wieder erneut installieren. Genauso habe ich dann auch angefangen, solche Operatoren einzuführen. Also zum Beispiel für Python, nur Python größer gleich drei oder nur genau dieses Linux. Also wenn man jetzt irgendwelche Module hat, die wollen, also wenn man ein Kernel-Modul für den Linux-Könel kompiliert hat, dann will man auch, dass das genau nur mit diesem Kernel zusammen installiert wird, zum Beispiel. Und die Implementierung davon ist relativ interessant. Man kann nämlich das hier anhand von dem Operator aufsplitten und dann anfangen die Installierte, also die Versionsnummer von dem installierten Paket und von dem, was hier drin steht, also der drei zum Beispiel oder die in 4.1 in der Liste zu schreiben und der Sort-Fehl hat ein Parameter, der es einem ermöglicht, Versionsnummer zu sortieren. Da kann dann auch mit Dingen umgehen, wie Versionsnummer 1.1a oder 1.1b. Also OpenSSL macht zum Beispiel so was 1.01i oder jsclarade das aktuellste, so was in der Richtung. Und damit muss man die Logik nicht selber implementieren, sondern man nutzt einfach den Sort-Befil dafür und das passiert dann alles von alleine, sozusagen. Und ja, das ist so viel zu Abhängigkeiten. Also mit der Auflösung wollte ich mich, wie gesagt, nicht beschäftigen, sondern nur überprüfen, ob sie denn installiert sind. Und ich habe dann noch ein paar weitere Funktionen gemacht, zum Beispiel will man manchmal nicht, dass wenn man die neue Version installiert, dass dann die Konfigurationsdatei verloren geht. Deswegen kann man eine Liste erstellen von Dateien, die automatisch geback-up werden sollen. Und oft hat man auch den Fall, dass man irgendwo im Datei-System Datei hat und man weiß gar nicht, wo kommt überhaupt her. Dafür gibt es den Owner-Befil, der geht einfach die Pakete durch und gibt an zu welchen Paketen die Datei gehört. Also gerade, wenn man das auf einem Ordner ausführt, dann können das auch mehrere Pakete sein. Also zum Beispiel User-Bin wird von fast allen Paketen, also wird fast alle Pakete auflisten, wenn man diesen Befehl darauf ausführt. Ja, gibt es dazu bisher irgendwelche Fragen? Dann würde ich jetzt gerne eine Demo machen. Und zwar habe ich ein kleines Linux from Scratch System eingerichtet in einem Container, den fahre ich kurz hoch. Da ist der Nutzer Root drin. Diesen Container habe ich auch auf meine Webseite geladen. Den könnt ihr euch später runterladen und selber mal ausprobieren. Okay, Uppsala. Ist das in Ordnung? Gut, erstmal zeige ich diesen Indexlist-Befil. Der listet einfach alle Pakete auf, die in dem System installiert sind. Und wenn ich jetzt zum Beispiel nach einem Paket suche, ich will jetzt mal suchen nach Linux, dann sehe ich hier, ich habe Linux Headers und Linux, sieht man das von hier? Oder stehe ich im Weg? Ich kann auch das so machen. Nein, das ist blöd. Gut, dann kann ich auch den Index mal von innen zeigen, sozusagen, ich gehe mal in den Ordner, in dem der Index drin ist. Man sieht hier, hier ist die Datei-Version, in dem Fall Index-Version 2. Und für die einzelnen Pakete jeweils eigene Ordner. Ich gehe jetzt mal in den Pfeifenordner. Ach so, ich habe Pfeifen gar nicht installiert. Wim zum Beispiel hat jetzt die Version 7.4 installiert und hier sind diese jeweiligen Dateien drin. Das ist der Ordner, der Dateien, Simlings und so weiter, wenn man das Paket spielt. Aber dazu gleich noch später. Ich kann jetzt auch die Integrität von den einzelnen Programmen überprüfen. Also zum Beispiel, wenn ich jetzt wissen will, ob von Wim die ganzen Dateien alle noch korrekt sind, kann ich das einfach ausführen. Der geht einfach nur diese SHA-1 zum Datei durch und in dem Fall ist alles in Ordnung. Gut, es ist auch gar nicht so schwierig, eigene Pakete dafür zu erstellen und das möchte ich jetzt auch mal durchgehen. In dem Fall nehme ich jetzt einfach mal Wayland. Ich kopiere die Vorlage. Also Package-Spielpunkt Proto habe ich das genannt, ganz nach Arch Linux Vorbild zu dem Namen Wayland 1.5.0. So, hier sieht man jetzt gleich die Versionsnummer ist 5 von den Package-Spiels. Die lasse ich jetzt so. Den Name setze ich auf Wayland. Versionsnummer 1.5.0. Backupen will ich nichts. Es überschreibt auch nichts. dependency, das muss man jetzt halt nachschauen oder auswendig wissen, das ist in dem Fall lib.ffi und steht in Konflikt mit sich selbst. Hier müsste jetzt eigentlich der volle Name, also der volle Pfad hin für das Paket, noch so eine kleine Zusatzinfo, wenn das jetzt in der HTTBS-Adresse ist und man hat ein System, das noch keine root-Zertifikate installiert hat, dann verweigert das Herunterladen, also der Wegetver, wenn ich zum Herunterladen verweigert, das Herunterladen von der Datei, weil das SSL-Zertifikaten nicht verifizieren kann und dafür gibt es einen extra Flag, der heißt dann NoSearchag. Aber in dem Fall werde ich keine URL angeben, sondern direkt den Namen der Datei, weil ich habe das schon runtergeladen und ich weiß die auch nicht auswendig. Hier kann ich einfach diese Variablen verwenden und die Dateiendung ist TXZ. Das hier muss später noch berechnet werden. Hier ist so ein kleiner Hinweis, wie man dieses Packagebild benutzt, also dass man Sachen, die sich nicht verändern, einfach weglassen kann. Den Hinweis kann ich jetzt mal wegmachen. Hier muss ich ein X draus machen, weil es ein TXZ ist. Das entpackt das Ganze in diesen Bilderordner. Im Fall von Wayland habe ich Configure mit PrefixUser, DisableStatic und DisableDocumentation in meinem Fall. Build ist ein einfaches Make. Das heißt, das kann ich löschen, weil wenn ich keine Änderung angebe, dann wird einfach der Default genommen. Das ist schon Make, MakeCheck genauso. Install auch genauso. Und da ich auch keine Preinstall, Pre-Remove, Post-Install, Post-Remove-Aktionen betreiben möchte, kann ich die auch einfach löschen. Das heißt, mein Paket ist jetzt fertig. Soll ich das noch mal vorhin nicht zeigen? Egal. So, jetzt habe ich mein Paket. Jetzt will ich erstmal den Quake-Out runterladen. Angenommen, da würde jetzt eine falsche Negoel drin stehen, dann würde das runtergeladen werden. Dazu gibt es den LEV-SME-Bild, ne, nicht Bild, Download-Befil. Und in dem Fall, da es schon runtergeladen ist, sieht man das jetzt nicht mehr, wie es runtergeladen wird, aber es beschwert sich, dass die SHA1 Summe nicht stimmt. Und das kann man dann auch beheben, indem man die erstmal berechnet. Das muss man einfach nur noch kopieren und in das Package-Bild reinmachen. Und wenn man das Ganze jetzt nochmal macht, dann sieht man, dass die Datei okay ist. Als nächstes müsste man das Paket bauen. Und hier sieht man, aha, LibFFI ist noch nicht installiert. Das heißt, das muss man jetzt erstmal noch installieren. Das habe ich ja auch vorbereitet. Das wird jetzt installiert. Das hatte auch dieses Data-Gnu als Dependency, hat das gefunden, hat das Ganze jetzt installiert. Kann man auch nachprüfen mit LFSME-Index-List LibFFI. Ist da gelistet. Wenn ich jetzt Wailand wieder bauen möchte, dann funktioniert es diesmal. Das war es. Das erstellt jetzt die Datei Wailand150.pkg. Und das ist einfach nur ein TAR-Bolm, der mit TAR-XZ verpackt ist, wie vorher schon beschrieben. Also ich kann jetzt mit TAR-TEF Wailand150.pkg mit den Inhalt ausgeben lassen. Das selbe geht auch direkt über den Paket-Manager mit LFSME-List Wailand150.pkg. Jetzt muss ich es eigentlich nur noch installieren. Und das war es. Also so einfach. Und da dieses Package-Bild-Format an Arch Linux angelegt ist, kann man auch relativ einfach auf die Arch Linux-Webseite gehen und sich ein beliebiges Paket von dort kopieren. Nehmen wir mal die Bash. Da ist jetzt noch ein bisschen mehr dabei. Das ist jetzt ein schlechtes Beispiel. Aber im Prinzip kann man das Format von Arch Linux 1 zu 1 nachmachen. Also die haben ja auch eine Repair-Methode. Da ist das Entpacken von dem Quake-Code nicht drin. Das muss man da manuell machen. Aber wenn man halt nicht weiter weiß, wenn man nicht weiß, welche Dependencies man braucht, dann kann man das einfach von dort abgucken. Jetzt habe ich ehrlich gesagt noch relativ viel Zeit, weil ich doch schneller durch bin, als ich das geplant hatte. Gibt es noch irgendwelche Fragen oder Dinge, die ich zeigen soll? Ja, Linux vom Scratch ist eine Linux-Distribution, die besteht nur aus einem Buch, in dem beschrieben wird, wo man den Quake-Code runterladen kann und wie man aus dem Quake-Code direkt von Hand sein, ein bootbares Linux-System mit Compiler, mit Bash, in dem Fall System, die uns weiter bauen kann. Also das ist eine Schritt für Schritt Anleitung, wie man aus den Quellen sein eigenes Betriebssystem, sein eigenes GNU-Linux-Betriebssystem zusammenbaut. Eigentlich müsste man das GNU from scratch nennen, weil Linux besteht genau aus einer HTML-Seite, in der ein bisschen was beschrieben ist, aber der Rest beschäftigt sich hauptsächlich mit der Installation von GNU Tools und der Toolchain, die auffassender als GNU Tools besteht. Aber das ist so ein Namenskonflikt. Ja, ist sonst noch irgendwas? Ja? Also mit dem Google-Gruf vielleicht noch was sagen. Kannst du ja bitte in der Beschäftigung was wissen? Wie sind es so Debian-Pakete im Gegensatz dazu aufgebaut? Was sind denn die Problembespiele, dass es ganz anders bleibt? Also peinlicherweise habe ich mal versucht, ein Debian-Paket zu erstellen und bin dabei gescheitert. Bei Debian muss man erstmal so ein Helper-Programm ausführen, um sich so ein Verzeichnungsbaum zu erstellen, in dem verschiedene Dateien für Metadaten drin sind. Ein Makefile, das den eigentlichen Bild und die Installation steuert usw. Man muss dann erstmal in dem Metadatenfall die einzelnen Metadaten in korrekter Debian-Manier eintragen. Muss die anderen Dateien eventuell anpassen und muss dann dieses eine Makefile schreiben. Und das war mir irgendwie nach zwei Stunden zu blöd. Mein Ziel war es, es möglichst einfach zu implementieren. Und dann dachte ich mir, der Ansatz mit einer einzigen Datei, die das ganze Paket beschreibt, ist deutlich eingängiger als diese Debian-Pakete. Aber da ich nicht hundertprozentig weiß, wie dieses DPKG und Get-Intern funktionieren, kann ich da nicht viel mehr drüber sagen. Es ist nur so, ich halte es für nicht besonders einsteigerfreundlich. Ich kann auch nochmal den Quake-Code zeigen. Das ist noch so eine Geschichte, der ist nicht besonders schön. Vor allem, im Nachhinein hätte ich mich für was anderes entschieden, als Bash, weil Lua hat kaum Dependencies und hätte ich auch in die Toolchain einfach mit reinnehmen können. Zumal ich für mein Paket-Manager auch Async gebraucht habe, was auch nicht in der ursprünglichen Toolchain drin war. Und ich muss das dann alles erweitern. Das ist so ein 1.145-Zeilen langes Bash-Script, in dem der Reihe nach einzelne Funktionen ausgerufen werden. Er überprüft hier, in welchem Modus er ist. Der Modus ist hauptsächlich das, was man hier angibt. Eines von diesen Befehlen, Backup List, Backup Match, Birt, Check Devs usw. Das wird in einem großen Switchcase unterschieden. Und der führt dann die einzelnen Fehler aus. Und das Programm hat auch eigentlich kein State in Memories, sondern das wird alles in Dateien gespeichert, die dann mit SED, Grab, AWK, Cut miteinander verwuschtelt werden. Falls jemand Interesse daran hat, selber mal was mit eigenem Paket-Management zu machen, ich empfehle nicht an meinem Code rumzumachen, sondern einfach von vorne anzufangen. Was wollte ich jetzt noch? Irgendwas ist mir noch entfallen, aber ich kann zumindest mal diese Folie hier aufsetzen. Die Quellen, die ich verwendet habe, ich habe natürlich sehr viele Quellen nicht aufgelistet, weil ich mich da wirklich mehrere Monate lang mit beschäftigt habe und von daher überhaupt nicht weiß, wo die einzelnen Sachen alle herkommen. Aber so hauptsächlich im Linux from scratch, die hat eine Seite über Paket-Management, da werden die meisten dieser Konzepte, die ich vorher erklärt habe, zumindest mal erwähnt. Und es gibt ein, also das hier beschäftigt sich, okay, das ist eine gute Frage, womit beschäftigt sich das überhaupt? Genau, das beschäftigt sich mit diesem Paket-Nutzer-Ansatz, den ich vorher beschrieben habe, bei dem jedes einzelne Paket einen eigenen Nutzer bekommt. Und wer Interesse an dem Quellcode hat, findet den auf GitHub unter der Adresse. Und hier ist eine Liste mit Paketen, die ich dafür erstellt habe, also Linux from scratch 7.6. Und zusätzlich zu Linux from scratch gibt es noch beyond Linux from scratch, da sind dann Sachen drin wie xorg, sudo, OpenSSL, auch Webbrowser, alles Mögliche. Ich bin mit dem 7.6 noch nicht so weit, aber dieses 7.5 hatte ich so weit, dass es einen genauen 3-System hatte, aber so weit werde ich wahrscheinlich nicht nochmal kommen, weil das hat vier Monate gedauert. Aber ich würde nicht empfehlen, das 7.5 in der Form zu reproduzieren, weil das war ziemlich verpackt. Also man sollte auch dankbar sein den Leuten, die Distributionen gemacht haben, weil das ist alles gar nicht so einfach, so auf der Software so zu installieren, dass sie am Ende auch gut miteinander zusammenarbeitet. Ja, Moment. Ich kann eigentlich direkt das anklicken. Und der Link hier, das ist das, was man hier sieht. Da kann man diesen Container runterladen, den ich hier verwendet habe, um den Paketmanager zu demonstrieren. Das kann man dann mit System.dn spawn starten oder in Chainshoot reinmachen, wenn man möchte. Und da sind auch nochmal die Links auf GitHub und auf die Präsentation als Download oder direkt zum Online angucken. Ja, mehr gibt es dann auch erst. Ach ja, genau. Jetzt ist mir eingefallen, was ich noch erwähnen wollte. Eine Funktion, die in meinem Paketmanager noch fehlt, ist die Funktion eines Upgrades. Dadurch, dass ich ja dieses Konfliktmanagement drin habe, kann ich ein Paket nicht einfach durch die neue Version ersetzen. Und deswegen ist der Ansatz erstmal altes Paket deinstallieren, neues Paket installieren, bauen und installieren. Problematisch wird der Ansatz aber bei so Sachen wie G-Lipsy oder dem Paketmanager selbst. Ich kann nicht einfach G-Lipsy deinstallieren und dann wieder installieren in der neuen Version, weil sobald ich G-Lipsy deinstalliert habe, funktionieren alle in C geschriebenen Programme nicht mehr und ich kann das nicht wieder deinstallieren. Das heißt, es fehlt noch eine Funktion, mit der man ein neues Paket über das alte drüber installieren kann. Und das wäre auch gar nicht so schwierig zu implementieren. Man müsste halt ein Diff machen der Dateien, die im Index drin sind und dann eben erst mal alles drüber installieren und dann alles, was das alte Paket hatte, aber das neue Paket nicht mehr hat, dann wieder rauslöschen. Das ist aber noch nicht implementiert. Mehr gibt es eigentlich auch nicht zu sagen.