{"id":3623,"date":"2024-12-31T16:15:45","date_gmt":"2024-12-31T15:15:45","guid":{"rendered":"https:\/\/informatik.htwk-leipzig.de\/seminar\/?p=3623"},"modified":"2025-01-15T22:26:24","modified_gmt":"2025-01-15T21:26:24","slug":"nix-nixos","status":"publish","type":"post","link":"https:\/\/informatik.htwk-leipzig.de\/seminar\/ankuendigungen\/2024\/nix-nixos\/","title":{"rendered":"Nix &amp; NixOS"},"content":{"rendered":"<h1>Nix &amp; NixOS<\/h1>\n<p><em>Lesezeit: Acht Minuten<\/em><\/p>\n<p>Ein Problem so alt wie die Softwareentwicklung selbst: Nach einem Update funktioniert *nix* mehr.<br \/>\nDieses Problem ist Ihnen sicher schon einmal begegnet.<br \/>\nSei es ein einfaches Grafiktreiber-Update, welches dazu f\u00fchrt, dass die eigene Maschine nicht mehr grafisch bootet oder ein Update beliebiger Anwender-Software, nach welchem jene nicht mehr wie gewohnt funktioniert.<\/p>\n<p>Selbst bei gr\u00fcndlichster Testung steht jede Software vor der zentralen Frage: Funktioniert die Software beim Anwender?<\/p>\n<p>In diesem Blog-Beitrag betrachten wir, wie ein revolution\u00e4rer, deklarativer Ansatz von <strong><em>Nix<\/em><\/strong> und <em><strong>NixOS<\/strong><\/em> diese Frage \u00fcberfl\u00fcssig macht und warum betriebliche Informationssysteme davon profitieren k\u00f6nnen.<\/p>\n<h2>\u00d6ko-System: Nix ist nicht gleich Nix<\/h2>\n<p dir=\"auto\">Wer schon einmal mit Nix in Ber\u00fchrung gekommen ist, der denkt an einen Paket-Manager. Das ist auch vollkommen richtig, denn der Paket-Manager\u00a0<em><strong>Nix<\/strong><\/em>\u00a0ist der zentrale Bestandteil des Nix-\u00d6ko-Systems. Zus\u00e4tzlich gibt es aber auch\u00a0<em><strong>Nix<\/strong><\/em>: eine dom\u00e4nenspezifische Sprache zur Beschreibung von Build-Instruktionen. Zur Differenzierung bezeichnen wir diese im Nachfolgenden auch mit dem etwas un\u00fcblicheren Namen der\u00a0<em><strong>Nix Expression Language.<\/strong><\/em>\u00a0Aufbauend auf dem Paket-Manager Nix gibt es noch eine Linux-Distribution\u00a0<em><strong>NixOS<\/strong><\/em>, welche die funktionale Idee des Paket-Managers auf System-Ebene \u00fcbernimmt. Erw\u00e4hnenswert, aber in diesem Beitrag nicht weiter betrachtet ist\u00a0<em><strong>Nixpkgs<\/strong><\/em>: das Paket-Repository f\u00fcr Nix-Pakete.<\/p>\n<div class=\"markdown-heading\" dir=\"auto\">\n<div class=\"markdown-heading\" dir=\"auto\">\n<h2 class=\"heading-element\" dir=\"auto\">Nix ist deklarativ &#8211; alles ist deklarativ<\/h2>\n<\/div>\n<div class=\"markdown-heading\" dir=\"auto\">\n<div class=\"markdown-heading\" dir=\"auto\">\n<h3 class=\"heading-element\" dir=\"auto\">Nix Expression Language<\/h3>\n<\/div>\n<p dir=\"auto\">Die Nix Expression Language ist eine touring-vollst\u00e4ndige dom\u00e4nenspezifische Sprache zur Beschreibung der Erstellung von Paketen. Angelehnt an die funktionale Programmiersprache Haskell ist diese ebenfalls deklarativ und rein funktional &#8211; aber dynamisch typisiert.<\/p>\n<p dir=\"auto\">Betrachten wir das Universum als Menge von Paketen\u00a0P\u00a0(z.B.\u00a0<code>gcc<\/code>), Konfigurationen\u00a0C\u00a0(z.B. Flaggen f\u00fcr\u00a0<code>gcc<\/code>) und Daten\u00a0D\u00a0(z.B. Quellcode-Dateien). Ein Nix-Paket ist dann Funktionswert einer Transformation\u00a0t:2P\u00d7C\u2192P. Jedes Paket h\u00e4ngt also von bestimmten Eingaben (Dependencies) ab und produziert eine Ausgabe (Derivation &#8211; Nix-Name f\u00fcr ein Paket).<\/p>\n<p dir=\"auto\">Aufgrund der rein funktionalen Natur von Nix ist nicht verwunderlich, dass allgemeiner alle Nix-Ausdr\u00fccke B\u00e4ume sind. Bl\u00e4tter sind Konfigurationen\u00a0C\u00a0oder Daten\u00a0D\u00a0und Knoten sind Transformationen\u00a0t:2P\u00d7C0,1\u00d72D\u2192P\u2223C\u2223D. Da identische Teilb\u00e4ume aus Performance-Gr\u00fcnden allerdings untereinander geteilt werden (engl.\u00a0<em>sharing<\/em>), sind die Ausdr\u00fccke in Wirklichkeit kreisfreie, gerichtete Graphen (engl.\u00a0<em>directed acyclic graph<\/em>\u00a0&#8211; kurz\u00a0<em>DAG<\/em>).<\/p>\n<p dir=\"auto\">Aufgeschrieben werden Nix-Ausdr\u00fccke in Dateien mit Endung\u00a0<code>.nix<\/code>.<\/p>\n<div class=\"markdown-heading\" dir=\"auto\">\n<h3 class=\"heading-element\" dir=\"auto\">Nix<\/h3>\n<\/div>\n<p dir=\"auto\">Gebaut werden die Pakete in einer dedizierten System-Umgebung, dem sogenannten\u00a0<em><strong>Nix-Store<\/strong><\/em>. Unter Linux entspricht dieser dem Pfad\u00a0<code>\/nix\/store<\/code>\u00a0im Root-Dateisystem und unter MacOS sogar einer eigenen System-Partition. Im Folgenden betrachten wir ausschlie\u00dflich die Verwendung von Nix unter Linux. Der Speicher-Ort eines Pakets wird durch die Addition des Hashes eines Pakets zum Pfad des Nix-Stores bestimmt. Ein Paket\u00a0p\u00a0wird mit einer Hash-Funktion\u00a0h:P\u2192N\u00a0also unter\u00a0<code>\/nix\/store\/h(p)<\/code>\u00a0gespeichert.<\/p>\n<p dir=\"auto\">Wie die Expression Language ist auch der Nix-Store rein funktional, was bedeutet, dass alle Pakete unver\u00e4nderlich (engl.\u00a0<em>immutable<\/em>) sind. Zusammen mit der Hash-Funktion gilt folgende Eigenschaft:<\/p>\n<p dir=\"auto\">\u2200p1,p2\u2208P:(h(p1)\u2260h\u2032(p1\u2032)\u2227p2=t(P,c)\u2227p1\u2208P)\u2192Rebuild(p2,h\u2032)<\/p>\n<p dir=\"auto\">\u00c4ndert sich eine Dependency eines Paketes\u00a0p, dann muss dieses neu gebaut werden. Das neu gebaute Paket\u00a0p\u2032\u00a0erh\u00e4lt dann einen neuen Hash und somit eine neue Speicherstelle. Das alte Paket\u00a0p\u00a0an der alten Stelle bleibt dabei dennoch erhalten, sodass der Nix-Store referentielle Transparenz aufweist. Diese Eigenschaft garantiert zusammen mit den ausschlie\u00dflich puren Funktionen der Nix Expression Language, dass Pakete tats\u00e4chlich Funktionswerte mathematischer Funktionen sind, also ausschlie\u00dflich von ihren Eingaben abh\u00e4ngen und somit vollst\u00e4ndig reproduzierbar sind.<\/p>\n<div class=\"markdown-heading\" dir=\"auto\">\n<div class=\"markdown-heading\" dir=\"auto\">\n<h3 class=\"heading-element\" dir=\"auto\">NixOS<\/h3>\n<\/div>\n<p dir=\"auto\">Nix implementiert pures und rein funktionales Paket-Management. Diese Art von Management kann allerdings auch auf System-Ebene gehoben werden. Genau das macht\u00a0<em><strong>NixOS<\/strong><\/em>, eine Linux-Distribution auf Basis des Paket-Managers Nix. So wie bei Nix jedes Paket durch eine Build-Instruktion in einer\u00a0<code>.nix<\/code>-Datei beschrieben werden kann, kann auch ein ganzes Betriebssystem in einer einzigen\u00a0<code>.nix<\/code>-Datei beschrieben werden. Betrachten wir beispielhaft folgendes Betriebssystem, auf welchem sowohl ein OpenSSH- als auch ein Apache-Server laufen:<\/p>\n<p dir=\"auto\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-3624 size-large\" src=\"https:\/\/informatik.htwk-leipzig.de\/seminar\/wp-content\/uploads\/2024\/12\/nixosSystemPackageTree-1024x669.png\" alt=\"\" width=\"910\" height=\"595\" srcset=\"https:\/\/informatik.htwk-leipzig.de\/seminar\/wp-content\/uploads\/2024\/12\/nixosSystemPackageTree-1024x669.png 1024w, https:\/\/informatik.htwk-leipzig.de\/seminar\/wp-content\/uploads\/2024\/12\/nixosSystemPackageTree-300x196.png 300w, https:\/\/informatik.htwk-leipzig.de\/seminar\/wp-content\/uploads\/2024\/12\/nixosSystemPackageTree-768x502.png 768w, https:\/\/informatik.htwk-leipzig.de\/seminar\/wp-content\/uploads\/2024\/12\/nixosSystemPackageTree.png 1044w\" sizes=\"auto, (max-width: 910px) 100vw, 910px\" \/><\/p>\n<p dir=\"auto\"><em>Abbildung: Beispielhafte und vereinfachte System-Konfiguration in NixOS<\/em><\/p>\n<\/div>\n<p dir=\"auto\">Wie auch bei Nix wird hier deutlich, dass jedes System ein DAG aus Paketen, Konfigurationen und Daten ist. Zus\u00e4tzlich kommen hier noch Skripte hinzu, um ausf\u00fchrbare Dienste zu erm\u00f6glichen.<\/p>\n<\/div>\n<div class=\"markdown-heading\" dir=\"auto\">\n<h2 class=\"heading-element\" dir=\"auto\">Vorteile von NixOS in betrieblichen Informationssystemen<\/h2>\n<\/div>\n<div dir=\"auto\">\n<p dir=\"auto\">Durch diese deklarative und rein funktionale Natur von Nix und NixOS ergeben sich einige Vorteile, die insbesondere in betrieblichen Informationssystemen von Nutzen sind. Im Folgenden betrachten wir einige davon.<\/p>\n<div class=\"markdown-heading\" dir=\"auto\">\n<h3 class=\"heading-element\" dir=\"auto\">Sichere Updates durch Rollbacks<\/h3>\n<\/div>\n<p dir=\"auto\">Systemupdates werden durch \u00c4nderungen der Konfigurationsdatei durchgef\u00fchrt. Um die \u00c4nderungen zu \u00fcbernehmen, muss das System mit\u00a0<code>nixos-rebuild<\/code>\u00a0neu gebaut werden. Da die Konfiguration der entsprechenden Paketversionen durch eine Konfigurationsdatei beschrieben ist, kann jede &#8222;Generation&#8220; des Systems in einer Historie festgehalten werden. Sollten Aktualisierungen zu Problemen oder Instabilit\u00e4t f\u00fchren, kann mithilfe von\u00a0<code>nixos-rebuild --rollback switch<\/code>\u00a0auf eine \u00e4ltere Generation zur\u00fcckgewechselt und die vorherige Konfiguration wiederhergestellt werden. Au\u00dferdem besteht die M\u00f6glichkeit neue Konfigurationen mit\u00a0<code>nixos-rebuild build-vm<\/code>\u00a0vorher in einer VM auszutesten. Damit kann Downtime in produktiven Umgebungen durch fehlerhafte Updates minimiert werden.<\/p>\n<p dir=\"auto\">Um Rollbacks zu gew\u00e4hrleisten, speichert NixOS standardm\u00e4\u00dfig alle vorherigen Systemzust\u00e4nde. Dies kann mitunter zu einem hohen Speicherverbrauch f\u00fchren, was insbesondere in ressourcenbegrenzten Umgebungen wie der Cloud problematisch sein kann.<\/p>\n<div class=\"markdown-heading\" dir=\"auto\">\n<h3 class=\"heading-element\" dir=\"auto\">Skalierbarkeit und Automatisierung<\/h3>\n<\/div>\n<div dir=\"auto\">\n<p dir=\"auto\">\u00c4hnlich wie klassische Docker-basierte Systeme wie beispielsweise GitHub Actions, bietet das auf Nix basierende Tool\u00a0<a href=\"https:\/\/hydra.nixos.org\/build\/281326333\/download\/1\/hydra\/\" rel=\"nofollow\"><em>Hydra<\/em><\/a>\u00a0eine Umgebung zum Ausf\u00fchren von automatisierten Pipelines. Dabei wird vollst\u00e4ndig auf Docker verzichtet, sodass stattdessen ausf\u00fchrbare System-Images direkt mit Nix-Konfigurationen deklariert werden.<\/p>\n<p dir=\"auto\">Alternativ ist es auch m\u00f6glich, NixOS in einer Docker-Umgebung zu verwenden, um die Vorteile beider Systeme zu kombinieren. Damit k\u00f6nnen deklarative Umgebungen, die sich garantiert immer gleich verhalten in weit verbreiteten Systemen eingesetzt werden, in denen NixOS nicht direkt unterst\u00fctzt wird. Ein Beispiel daf\u00fcr sind Cloud-Applikationen, die Docker in Verbindung mit Kubernetes zum Skalieren verwenden.<\/p>\n<p dir=\"auto\">F\u00fcr die Verwendung unter Amazons EC2 k\u00f6nnen virtuelle System-Images direkt aus Nix-Konfigurationen heraus gebaut werden. Der Befehl\u00a0<code>nix-build<\/code>\u00a0nimmt daf\u00fcr als Argument einerseits die Nix-Konfigurationsdatei und andererseits das bereitgestellte EC2 Base-Image f\u00fcr die passende Architektur.<\/p>\n<div class=\"markdown-heading\" dir=\"auto\">\n<h3 class=\"heading-element\" dir=\"auto\">Sicherheit<\/h3>\n<\/div>\n<p dir=\"auto\">Betriebliche Informationssysteme k\u00f6nnen durch den Einsatz deklarativer Konfigurationen zuverl\u00e4ssiger h\u00f6heren Sicherheitsstandards entsprechen. In einer deklarativen Umgebung wird der gew\u00fcnschte Endzustand explizit beschrieben und das System automatisch so konfiguriert, dass dieser erreicht wird. Dadurch werden unbeabsichtigte Nebenwirkungen oder nicht dokumentierte Zust\u00e4nde, wie sie bei klassischen imperativen Ans\u00e4tzen (direkte Befehle, manuelle Konfiguration) auftreten k\u00f6nnen, vermieden.<\/p>\n<p dir=\"auto\">Au\u00dferdem k\u00f6nnen Sicherheitspr\u00fcfungen in einer einzelnen Umgebung durchgef\u00fchrt werden und auf alle anderen Umgebungen \u00fcbertragen werden, auf denen diese Konfiguration zum Einsatz kommt. Durch die zustandslose Konfiguration kann somit garantiert werden, dass alle Umgebungen den gleichen Sicherheitsstandards entsprechen.<\/p>\n<p dir=\"auto\">Weiterhin ist es m\u00f6glich, durch die Versionierung von Systemzust\u00e4nden direkt nachzuvollziehen, wer was wann wie ge\u00e4ndert hat und welche Auswirkungen diese \u00c4nderungen hatten. So k\u00f6nnen Breaking-Changes, oder \u00c4nderungen an sicherheitskritischen Komponenten direkt isoliert und nachvollzogen werden.<\/p>\n<p dir=\"auto\">Alle \u00c4nderungen, die am System vorgenommen werden, sind zudem atomar. Jede \u00c4nderung wird also vollst\u00e4ndig \u00fcbernommen oder gar nicht. In klassischen Systemen kann es dazu kommen, dass bei fehlgeschlagenen Installationen das System in einem halbfertigen, potenziell unsicheren Zustand zur\u00fcckgelassen wird. Diese Zust\u00e4nde werden hier vermieden, womit die damit verbundenen Sicherheitsl\u00fccken nicht auftreten k\u00f6nnen.<\/p>\n<div class=\"markdown-heading\" dir=\"auto\">\n<div class=\"markdown-heading\" dir=\"auto\">\n<h2 class=\"heading-element\" dir=\"auto\">Anwendungsbeispiel Pinterest<\/h2>\n<\/div>\n<p dir=\"auto\">Ein prominentes Beispiel, bei welchem NixOS erfolgreich in einem betrieblichen System zum Einsatz kommt, stellte das Pinterest Engineering Team in einem\u00a0<a href=\"https:\/\/medium.com\/pinterest-engineering\/continuous-integration-for-ios-with-nix-and-buildkite-ef5b36c5292d\" rel=\"nofollow\">Blog-Beitrag<\/a>\u00a0vor. Konkret ging es um die Pipelines zum automatisierten Bauen und Testen der Pinterest iOS-App. Die Ausgangssituation, die viele Probleme verursachte, sah wie folgt aus:<\/p>\n<ul dir=\"auto\">\n<li>Groovy und Bash Skripte f\u00fcr Jenkins Pipelines waren \u00fcberall im Repository verteilt und schwer zu warten<\/li>\n<li>Es gab es eine gro\u00dfe Menge an unversioniertem Code in Form von Jenkins-Konfigurationen<\/li>\n<li>Zahlreiche Beschwerden von Entwicklern \u00fcber die CI Umgebung\n<ul dir=\"auto\">\n<li><em>&#8222;My build passes locally, but fails on CI.&#8220;<\/em><\/li>\n<li><em>&#8222;It\u2019s difficult to reproduce what\u2019s running on CI on my machine.&#8220;<\/em><\/li>\n<li><em>&#8222;It\u2019s difficult to add new pipelines or build new machines.&#8220;<\/em><\/li>\n<li><em>&#8222;Builds are not fast enough.&#8220;<\/em><\/li>\n<li><em>&#8222;It\u2019s difficult to tell why my build failed.&#8220;<\/em><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p dir=\"auto\">Aus diesen Problemen heraus, entschied sich Pinterest&#8217;s Engineering Team unter anderem dazu, zu Nix-basiertem Deployment zu wechseln. Konkret bedeutete das die Vereinheitlichung von Dev und CI Umgebungen. Anstelle komplizierter und verteilter Bash-Setup-Skripte wurde auf eine Nix-Config Datei umgestellt. Mit dieser Konfiguration konnten dann sowohl lokale Entwicklungsumgebungen als auch die CI Maschinen sehr einfach aufgebaut werden. Durch diese \u00c4nderung hatten die Entwickler nun die Garantie, dass Fehler, die in der CI auftraten, auch lokal reproduziert waren.<\/p>\n<p dir=\"auto\">Die Ergebnisse der Builds sind immer gleich, unabh\u00e4ngig von Ausf\u00fchrungszeit oder -Umgebung, au\u00dferdem gibt es keinen unklaren System-Zustand oder unvorhersehbare Nebeneffekte. Ein weiterer Vorteil der Umstellung auf Nix ist die Vereinheitlichung der Abh\u00e4ngigkeiten. Sollten Entwickler Pakete aktualisieren, so geschieht das \u00fcber die Nix-Config, die automatisch auch die CI Umgebung aktualisiert.<\/p>\n<p dir=\"auto\">Parallel dazu wechselte das Team auch auf\u00a0<a href=\"https:\/\/buildkite.com\/docs\/pipelines\" rel=\"nofollow\">Buildkite<\/a>, um die Pipelines zu verwalten. Buildkite ist eine Alternative zu Jenkins, die es erm\u00f6glicht CI\/CD Prozesse auf der eigenen, selbst gehosteten Infrastruktur auszuf\u00fchren und dennoch ein zentrales, cloud-basiertes Web-Interface zur Verwaltung bietet. Das erm\u00f6glichte den beliebigen Einsatz der zuvor durch Nix vereinheitlichten Systeme als Buildkite-Agenten. Buildkite setzt standardm\u00e4\u00dfig auf parallel laufende Pipeline-Stages, um Build-Zeiten zu reduzieren. Diese Eigenschaft konnte dank einfacher Parallelisierung einzelner Build-Schritte von Nix-Paketen maximal ausgesch\u00f6pft werden. Insgesamt wurde das System dadurch deutlich stabiler, schneller, einfacher zu warten und skalierbarer.<\/p>\n<div class=\"markdown-heading\" dir=\"auto\">\n<h2 class=\"heading-element\" dir=\"auto\">Fazit<\/h2>\n<\/div>\n<p dir=\"auto\">Nix und NixOS adressieren ein altes Problem der Softwareentwicklung: Fehleranf\u00e4lligkeit durch schwache Reproduzierbarkeit. Der deklarative und rein funktionale Ansatz von Nix erm\u00f6glicht maximale Reproduzierbarkeit, sodass Software in beliebigen Umgebungen konsistent und zuverl\u00e4ssig bereitgestellt werden kann.<\/p>\n<p dir=\"auto\">Es ist wahrscheinlich, dass Nix und NixOS in Zukunft vermehrt genutzt werden, da ihre Vorteile im Hinblick auf Zuverl\u00e4ssigkeit und Sicherheit immer mehr Beachtung finden.<\/p>\n<div class=\"markdown-heading\" dir=\"auto\">\n<h2 class=\"heading-element\" dir=\"auto\">Quellen<\/h2>\n<\/div>\n<ul dir=\"auto\">\n<li>Eelco Dolstra. \u201eThe Purely Functional Software Deployment Model\u201c. Diss. Utrecht, The Netherlands: Utrecht University, Jan. 2006.<\/li>\n<li>Eelco Dolstra und Armijn Hemel. \u201ePurely functional system configuration management\u201c. In: Proceedings of the 11th USENIX Workshop on Hot Topics in Operating Systems. HOTOS\u201907. San Diego, CA: USENIX Association, 2007.<\/li>\n<li>Pablo Ovelleiro Corral. \u201eNix and NixOS for DevOps\u201c. Okt. 2022. URL:\u00a0<a href=\"https:\/\/www.inovex.de\/de\/blog\/nix-and-nixos-for-devops\/\" rel=\"nofollow\">https:\/\/www.inovex.de\/de\/blog\/nix-and-nixos-for-devops\/<\/a>\u00a0(Zugriff 27.12.2024)<\/li>\n<li>Ryan Yin. \u201eAdvantages &amp; Disadvantages of NixOS\u201c. Dez. 2024. URL:\u00a0<a href=\"https:\/\/nixos-and-flakes.thiscute.world\/introduction\/advantages-and-disadvantages\" rel=\"nofollow\">https:\/\/nixos-and-flakes.thiscute.world\/introduction\/advantages-and-disadvantages<\/a>\u00a0(Zugriff 27.12.2024)<\/li>\n<\/ul>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Nix &amp; NixOS Lesezeit: Acht Minuten Ein Problem so alt wie die Softwareentwicklung selbst: Nach einem Update funktioniert *nix* mehr.<\/p>\n","protected":false},"author":178,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-3623","post","type-post","status-publish","format-standard","hentry","category-ankuendigungen"],"_links":{"self":[{"href":"https:\/\/informatik.htwk-leipzig.de\/seminar\/wp-json\/wp\/v2\/posts\/3623","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/informatik.htwk-leipzig.de\/seminar\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/informatik.htwk-leipzig.de\/seminar\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/informatik.htwk-leipzig.de\/seminar\/wp-json\/wp\/v2\/users\/178"}],"replies":[{"embeddable":true,"href":"https:\/\/informatik.htwk-leipzig.de\/seminar\/wp-json\/wp\/v2\/comments?post=3623"}],"version-history":[{"count":2,"href":"https:\/\/informatik.htwk-leipzig.de\/seminar\/wp-json\/wp\/v2\/posts\/3623\/revisions"}],"predecessor-version":[{"id":3626,"href":"https:\/\/informatik.htwk-leipzig.de\/seminar\/wp-json\/wp\/v2\/posts\/3623\/revisions\/3626"}],"wp:attachment":[{"href":"https:\/\/informatik.htwk-leipzig.de\/seminar\/wp-json\/wp\/v2\/media?parent=3623"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/informatik.htwk-leipzig.de\/seminar\/wp-json\/wp\/v2\/categories?post=3623"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/informatik.htwk-leipzig.de\/seminar\/wp-json\/wp\/v2\/tags?post=3623"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}