urMQTT auf einem echten VPS: ISO, Handshake in 0,24 ms, eigene Festplatte und gehärteter Kernel
Was aus den drei Versprechen des ersten urMQTT-Textes (ein einziges ISO, rohe Festplatte ohne Dateisystem, nachweisbar sicher) nach den ersten vier Entwicklungstagen geworden ist, mit Messungen, Tabellen und einer Liste dessen, was noch nicht fertig ist.

Im ersten Text über urMQTT haben wir einen Unikernel beschrieben, der selbst der MQTT-Broker ist, und zwar bis zum Meilenstein M17. Am Ende standen drei Versprechen unter der Überschrift "Was als Nächstes kommt": ein einziges ISO, das auf mehreren Plattformen startet, eine rohe Festplatte ohne Dateisystem und Sicherheit, die sich beweisen lässt, statt nur behauptet zu werden. Nichts davon war damals gebaut. Dieser Text verfolgt, was aus jedem Versprechen geworden ist, in derselben Reihenfolge wie zuvor: was wir geplant haben, was wir gebaut haben, was uns überrascht hat und was noch offen ist. Nebenbei zeigen wir auch, wie lange das gedauert hat, denn das gehört zur Geschichte.
Ein Hinweis vorab: Das Projekt ist weiterhin Pre-Alpha. Die Zahlen in diesem Text messen Code und Tests, nicht die Produktionsreife, und die erste Veröffentlichung ist bewusst auf VPS mit KVM beschränkt. Bare Metal bleibt für später.
Wie das in der Zeit aussah
Der erste Commit entstand am 28. Oktober 2026 um 23:48 Uhr (M0). Bis zum 31. Oktober um 23:00 Uhr sind es 119 Commits im Repository, verteilt auf die Kalendertage als 3, 39, 38 und 39. Wie das möglich ist, sagen wir gleich dazu: Das Projekt wird mit Claude Code gebaut, und die Commits tragen Co-Authored-By: Claude-Trailer. Vier Kalendertage Historie bedeuten nicht, dass eine einzelne Person in vier Tagen 90 000 Zeilen geschrieben hat; sie bedeuten, dass Plan, Implementierung, Tests und ADRs in kurzen, geschlossenen Schleifen entstehen und jeder Schritt mit demselben Prüfritual endet wie zuvor. Die Zahlen unten sagen etwas über die Menge an Code und Tests aus, nicht darüber, wie viel Arbeitsaufwand in der Realität dahintersteckt.
Das Diagramm zeigt drei Kurven, die fast gemeinsam wachsen, und genau das ist die Absicht: Tests und festgehaltene Entscheidungen begleiten den Code, statt ihm hinterherzuhinken. Der Anstieg ist am 29. Oktober am steilsten, als der gesamte Weg vom Kernel bis zum Broker entsteht. Danach kommen Zeilen langsamer hinzu, aber die Tests wachsen schneller als der Code (27 Tests auf 1,6 Tausend Zeilen am Anfang, 739 auf 89,8 Tausend heute).
| Meilenstein | Commit | Zeit | Rust-Zeilen | Tests | ADRs |
|---|---|---|---|---|---|
| M0 | 9412a9c | 28.10. 23:48 | 1 592 | 27 | 13 |
| M5 | a61083b | 29.10. 01:32 | 9 314 | 88 | 24 |
| M9 | 4be8cc9 | 29.10. 02:21 | 15 729 | 149 | 28 |
| M11 | 4800904 | 29.10. 03:43 | 22 737 | 180 | 30 |
| M13 | a558d5e | 29.10. 05:22 | 26 556 | 193 | 32 |
| M17 | 7df4f98 | 29.10. 08:48 | 33 504 | 232 | 36 |
| S1 | ecf72ec | 30.10. 00:46 | 41 910 | 299 | 45 |
| X8 | fb1504a | 30.10. 19:08 | 56 341 | 456 | 59 |
| PERF | 76f7c36 | 31.10. 14:19 | 72 511 | 618 | 76 |
| heute | HEAD | 31.10. 23:00 | 89 790 | 739 | 104 |
Als Zeilen zählen alle .rs-Dateien außerhalb von vendor/ und fuzz/, als Tests die #[test]-Funktionen. Was welcher Abschnitt hinzugefügt hat, nach Datum aus der Git-Historie:
| Datum | Meilensteine | Was hinzugekommen ist |
|---|---|---|
| 29.10. | M0 bis M17 | Kernel, Netzwerk, TLS, Broker, SQLite, Mehrkernbetrieb, eigenes TCP, Härtung, reproduzierbares Release |
| 29.10. abends und 30.10. | X0 bis X10, S1, H2, H3 (X0 beginnt am 29.10. um 20:54) | Port auf x86_64, universelles ISO, eine Festplatte und selbstinstallierendes ISO, TLS-Prüfungen, angreifende Clients |
| 31.10. | O1 bis O4, T1, PERF, H4, H5, H9, S2 bis S6 | Einblick und Konsole über MQTT, Live-Modus und Diagnose, SNTP, Beschleunigungen, Härtung, Verifikation, Lieferkette, Speicherung |
Versprechen 1: ein ISO für mehrere Plattformen
Was wir geplant haben. Eine einzelne Datei, die auf aarch64-UEFI, x86_64-UEFI und x86_64-BIOS startet. Was wir gebaut haben. Den Port auf x86_64 (X0 bis X10) und ein universelles ISO mit dem Limine-Bootloader, ein hybrides ISO, von dem alle diese Kombinationen starten; die Smoke-Tests starten es auf vier virtuellen Maschinen. Das Menü hat drei Einträge: urMQTT (Live-Modus, läuft aus dem RAM, schreibt nichts auf die Festplatte), die Installation auf die Festplatte der Maschine, auf der es gestartet wurde (die Installation schreibt GPT und Daten und schaltet die Maschine dann aus) und Diagnose (Hardware-Erkennung und Messungen).

Das erste echte Ziel war ein VPS und keine physische Maschine: Wir haben entschieden, dass die erste Veröffentlichung nur VPS (KVM) unterstützt und Bare Metal danach kommt. Getestet haben wir auf einem Contabo-VPS mit AMD-EPYC-Prozessor (Familie 23, Zen 1), 4 vCPUs und 8 GiB RAM, mit virtio-net-Netzwerk und virtio-scsi-Festplatte. Der erste Versuch hat uns überrascht: Das als virtuelle CD eingebundene ISO startete den Standardeintrag, das Produktions-Image weigerte sich, von einem schreibgeschützten Medium zu laufen, und setzte sich zurück. So bekamen wir eine Endlosschleife aus Menü, Kernel und Reset, ohne irgendetwas Lesbares. Heute hinterlässt ein fataler Fehler im Produktions-Image seine Meldung 60 Sekunden lang auf der Konsole, bevor der Reset erfolgt, und von einem optischen Medium wird ausgegeben, dass von einem schreibgeschützten Medium gestartet wurde, und dann angehalten. Der Live-Modus im RAM (O3) hat dieses Problem später für den üblichen Fall umgangen.
Der Diagnose-Eintrag ist aus dieser Erfahrung entstanden: Eine Maschine, die wir nicht sehen, müssen wir fragen können, was sie hat. Er gibt CPU, RAM, Netzwerk, Festplatten und PCI-Geräte aus, misst Speicherdurchsatz, Festplatte und Kryptografie und gibt dieselben Messungen als JSON auf dem seriellen Port aus. Das Schreiben auf die Festplatte ist nur im nicht zugewiesenen Bereich erlaubt und erst, nachdem WIPE eingegeben wurde; in der Aufnahme unten wurde das übersprungen.

Was offen ist. Echte Hardware. Nach der ersten Veröffentlichung folgt Bare Metal (USB-Boot, NVMe und AHCI, Netzwerkkarte eines echten Rechners) zusammen mit IOMMU und Secure Boot, denn ohne IOMMU kann ein bösartiges oder fehlerhaftes Gerät den gesamten Speicher beschreiben. In dieser Version behaupten wir dazu nichts.
Handshake: von 1,2 ms auf 0,24 ms
Die erste Diagnosemessung auf dem VPS lieferte eine unangenehme Zahl: Der mTLS-Handshake, nur die Kryptografie auf der Serverseite, dauerte 1,2 ms. Auf aarch64 nutzten wir bereits die Hardware-Instruktionen für AES und SHA, aber x86_64 hatte für Public-Key-Verfahren nichts Vergleichbares: RustCrypto ist portabel und constant-time, und damit auch langsam. Drei Änderungen haben das gesenkt.
- Für P-256 haben wir den
nistz256-Assembler aus OpenSSL und ring übernommen (ADR-130), dazu Tabellen mit fester Basis für das Signieren und die Prüfung von CA-Signaturen (ADR-110, ADR-111). - X25519 wurde auf vier 64-Bit-Limbs mit MULX/ADX-Instruktionen umgeschrieben (ADR-131).
- Danach haben wir auch den Rest profiliert (ADR-132, ADR-200).
| Messung auf dem VPS (Server, Mikrosekunden) | Vorher | Nachher |
|---|---|---|
| mTLS-Handshake, gesamte Kryptografie | 1200 | etwa 240 (237 in der Messung aus dem Diagramm, 244 in der Aufnahme) |
| X25519 | 146 | 48 |
| ECDSA-Signatur | 673 | 34 |
| ECDSA-Verifikation | 309 | 90 (107 in der Aufnahme) |
| Handshake, altes memcpy, derselbe Boot | 278,8 | 229,4 (-17,7%) |
Die Zahlen unterscheiden sich von Lauf zu Lauf um etwa 10%, deshalb haben wir eine Spanne statt eines einzelnen Wertes notiert. Insgesamt ist das ungefähr fünfmal weniger CPU-Zeit für den Handshake.
Was wir über memcpy gelernt haben. Als die Kryptografie gefallen war, zeigte das Profil etwas, womit wir nicht gerechnet hatten: Die mem*-Funktionen (memcpy, memmove, memset) verbrauchten 7% des Handshakes, 13 bis 39% des TLS-Record-Pfads (39% bei 64 B, 13% bei 4 KiB) und 17 bis 34% der PUBLISH-Verteilung. Wir haben sie durch SSE2-Assembler für x86_64 ersetzt (ADR-200), mit Differenztests gegen die Referenzimplementierung für alle Größen, Ausrichtungen und Überlappungen. Pro Aufruf sind das 55 bis 75 Zyklen gegenüber 7 bis 12 bei 1 bis 64 Bytes.
| Pfad | Anteil mem* vorher | Danach schneller |
|---|---|---|
| Handshake | 7% | 17,7% |
| TLS-Record 64 B | 39% | 26,4% |
| TLS-Record 256 B | 37% | 19,6% |
| TLS-Record 1 KiB | 24% | 16,1% |
| TLS-Record 4 KiB | 13% | 6,6% |
| Nachrichtenverteilung | 17 bis 34% | 7 bis 34% |
So liest man das Diagramm: Je kleiner der Record, desto größer der Anteil der Zeit, der ins Kopieren fließt, und desto größer der Gewinn. Für kleine Nachrichten, die den Großteil des MQTT-Verkehrs ausmachen, ist das genau das, was zählt.
Was das nicht bedeutet. Auf nativem x86_64 haben wir OpenSSL 3.5.5 (ADR-130) nur als Referenz für den Assembler gemessen, nicht als Produktvergleich. Den Vergleich Linux plus Mosquitto auf demselben VPS (Aufgabe X11) haben wir noch nicht gemacht. Deshalb behaupten wir nicht, dass urMQTT schneller als Linux ist: Es ist nicht gemessen. Die Zahlen oben vergleichen urMQTT mit sich selbst.
Die Überlast war das zweite Ergebnis. Auf dem VPS, von einem einzelnen Mac aus (RTT 35 ms), schafften 128 Clients 618 Handshakes pro Sekunde, 256 Clients 393 und 512 Clients 67, mit Tausenden abgewiesenen Verbindungen. Das war kein Plateau, sondern ein Zusammenbruch. Die Ursachen haben wir im Code gefunden (zwei davon wurden in QEMU/HVF bestätigt, auf dem VPS ließen sie sich nicht reproduzieren), mehrere gleichzeitig: Eine abgewiesene Verbindung schickte den Client über das nächste Segment mit gültigem SYN-Cookie zurück, die Abweisung per RST sagte dem Client, sofort erneut zu versuchen, und die Annahme war O(Warteschlange). Die Korrektur (ADR-080 bis ADR-082) verwirft ein SYN, das nicht angenommen würde, merkt sich abgewiesene Flows, hält die Annahme in O(1) und zählt die Handshakes pro Kern. Möglich (Hypothese, nicht geprüft) ist, dass ein Teil der Ursache auch auf der Client-Seite lag: Das Lastwerkzeug schloss zuerst, sodass ein einzelner Mac mit 16 384 ephemeren Ports und 30 Sekunden TIME_WAIT auf etwa 550 Verbindungen pro Sekunde begrenzt war. Die echte Obergrenze des Servers haben wir daher noch nicht gemessen.
Versprechen 2: rohe Festplatte, aber sorgfältig
Was wir geplant haben. Das eigene Layout ohne Dateisystem beibehalten und all das hinzufügen, was ein Dateisystem sonst liefert: Ausrichtung, Wachstum, Discard, Sicherungskopien und Prüfungen. Warum kein Dateisystem. Darüber haben wir diskutiert, und die Entscheidung wurde bestätigt: urMQTT schreibt eine Datenbank, ein Telemetrie-Journal und eigene Metadaten, braucht weder Verzeichnisse noch Berechtigungen noch Dateinamen, und jede zusätzliche Schicht wäre noch mehr Code im vertrauenswürdigen Teil und noch ein Weg, auf dem ein Schreibvorgang unbemerkt verschwinden kann. Die Festplatte ist GPT mit einer ESP-Partition und einer urMQTT-Datenpartition, auf der das eigene Layout liegt.
Was wir gebaut haben (S2 bis S6).
- Layout v2 (S2). Festplattentopologie, Ausrichtung auf 1 MiB oder die optimale I/O-Größe, ganze physische Blöcke, ein A/B-Paar von Superblöcken mit Generation (ADR-140). Layout v1 lehnt der Kernel mit einer klaren Meldung ab, und es wird vom Offline-Werkzeug
urmqtt-disk migratemigriert (ADR-141): Standard ist ein Probelauf, es wird kopiert und erst dann umgeschaltet, mit Rollback und Fehlerinjektion bei jedem Schreibvorgang. - Wachstum (S3). Beim Start kann die Festplatte wachsen (sie schrumpft nie), und auf virtio-blk auch zur Laufzeit, über den Konfigurationsänderungs-Interrupt (ADR-142, ADR-143).
- Discard und Write-Zeroes (S4). Thin provisionierte Festplatten geben Speicherplatz an den Host zurück. Im Test fiel eine qcow2-Datei auf dem Host von 62 auf 16 MiB, nachdem Telemetriesegmente abgelaufen waren, und die Wiederherstellung traut dem Inhalt verworfener Bereiche nie.
- Sicherungskopie der Datenbank auf der Festplatte (S5). Eine A/B-Kopie mit der Online-Backup-API von SQLite in Schritten von 16 Seiten: der längste Schritt 4,6 ms gegenüber 445 ms, wenn in einem Aufruf kopiert wird. Commits während des Kopierens hatten ein Maximum von 2,4 ms gegenüber 2,2 ms ohne Kopie. Schlägt
quick_checkbeim Start fehl, wird die Datenbank aus der Kopie wiederhergestellt. - Scrubbing und begrenzter Start (S6). Der Scrubber liest die Festplatte mit begrenztem I/O-Budget und meldet beschädigte Einträge, und der Start liest nur das jeweils neueste Segment jeder Partition, sodass die Startzeit nicht mit der Größe der Telemetrieregion wächst. Damit haben wir ein Problem gelöst, das wir vorab ausgerechnet hatten: Auf einer großen Festplatte würde eine vollständige Wiederherstellung von Minuten bis zu Dutzenden von Minuten dauern.
Die gesamte Gruppe der Smoke-Tests für die Speicherung läuft auf beiden Architekturen durch: 79 erfolgreich, 0 fehlgeschlagen, auf aarch64 unter HVF und auf x86_64 unter TCG. Wir haben auch gemessen, dass S4 die Datenbank nicht verlangsamt hat: 53,3 Tausend Einfügungen pro Sekunde in Batches vorher und nachher, ohne messbaren Unterschied.
Was überrascht hat. Wie viel von einem "einfachen" Layout auf Randfälle entfällt: virtio-scsi und virtio-blk melden die Topologie auf unterschiedliche Weise, und ein unvollständiger Schreibvorgang mitten in der Migration muss immer mindestens eine gültige Version hinterlassen. Deshalb hat jeder Schreibvorgang auf dem Weg der Migration und des Wachstums die Fehlerinjektion durchlaufen.
Was offen ist. SQLite-Seiten haben keine Prüfsumme, sodass die Datenbank ein geändertes Bit innerhalb eines Zeilenwerts nicht bemerkt; Telemetrieeinträge und der Inhalt der Kopien haben ein CRC und werden erkannt. Die Festplattenverschlüsselung (S7) ist nicht fertig.
Versprechen 3: nachweisbar sicher, so weit wir es schaffen
Härtung (H4). Der Allokator löscht freigegebenen Speicher und prüft ihn bei der Wiederverwendung, erkennt doppelte Freigaben, verknüpft Zeiger der Freiliste per XOR und nullt Schlüssel bei der Freigabe (ADR-170). Stack-Schutz (-Zstack-protector=strong, 1315 Funktionen, und SQLite mit -fstack-protector-strong, ADR-171). Fehlerberichte in der Produktion enthalten weder Register noch Adressen (ADR-172). KASLR auf beiden Architekturen: Der Kernel ist PIE, und Limine verschiebt ihn um etwa 19 Bit, zufällig platziert werden Heap, Stacks und das MMIO-Fenster (ADR-173). Auf aarch64 markiert MTE den Heap (ADR-174), aber nur unter TCG: HVF und x86 haben es nicht. kCFI haben wir bewertet und nicht übernommen, weil der Compiler im festgelegten Nightly mit einem internen Fehler abstürzt; IOMMU wurde zusammen mit Bare Metal zurückgestellt (ADR-175, ADR-176).
Der Preis ist gemessen, nicht angenommen. Auf dem Handshake-Pfad (dp-bench, aarch64 HVF, volle TLS-1.3- + mTLS- + CONNECT-Handshakes):
| Schritt | 1 Kern | 4 Kerne | Änderung |
|---|---|---|---|
| ohne Härtung | 1941 | 7620 | |
| + Allokator | 1898 | 7429 | -2,2% bis -2,5% |
| + Stack-Schutz | 1845 | 7243 | zusammen -4,9% bis -5% |
| + PIE und Markierungen | etwa 1800 | etwa 6750 | auf einem ausgelasteten Rechner, verrauscht |
Insgesamt liegt der Preis auf dem Handshake-Pfad also bei etwa 5% für die sauber gemessenen Teile, und mit PIE und Markierungen kommen etwa 7% auf einem und etwa 11% auf vier Kernen heraus, doch diese Messung ist verrauscht. Wir halten das für akzeptabel, haben es aber nicht auf dem x86_64-VPS wiederholt.
Es lohnt sich, das mit der Zahl aus dem ersten Text zu vergleichen (862 Handshakes pro Sekunde auf einem Kern). Dasselbe Werkzeug liefert heute, mit der Hardware-Verschlüsselung auf aarch64 (X0b) und einer Reihe von Beschleunigungen, die seitdem hinzugekommen sind, 1941 vor der Härtung. Den Beitrag jeder einzelnen Änderung haben wir nicht isoliert, deshalb behaupten wir zur Ursache nichts.
Wie es geprüft wurde, und was nicht. H5 hat Miri für eine Teilmenge der Host-Crates, Kani-Beweise, Sanitizer, einen Modell-Fuzzer für die ACL und Abdeckungsberichte hinzugefügt. Kani hat record_len in TLS, parse im TCP und zwei Arithmetiken des Timer-Rads bewiesen. Nicht bewiesen (Zeitüberschreitung) sind beide MQTT-Decoder, beide Konfigurationsparser und das Verhalten des Timer-Rads; der CI-Job für Kani blockiert deshalb noch nicht. Miri kann keinen Assembler ausführen. Lieferkette (H9): ein CycloneDX-SBOM mit 215 Komponenten, cargo-vet läuft durch mit 39 vollständig geprüften und 140 Ausnahmen, und cargo-deny ist so konfiguriert, dass Schwachstellenhinweise als CI-Fehler behandelt werden (diese CI-Jobs wurden bisher nur lokal geprüft, nicht auf GitHub, und der Negativtest wurde nicht ausgeführt), dazu VULNERABILITY-HANDLING.md. Der ehrliche Stand: Von 67 in den Kernel gelinkten Versionen von Drittanbieter-Crates sind 53 Ausnahmen und keine Audits. Vet beweist, dass sich die Menge der Abhängigkeiten nicht ohne sichtbaren Unterschied ändern kann, nicht, dass die TCB geprüft wurde. Ein eigenes Audit eines Crates haben wir nicht geschrieben.
SNTP (T1) ist in diesem Sinne eine kleine Sicherheitsgeschichte: Es ist optional, nur zu privaten IPv4-Servern, mit einem maximalen Sprung und der Bedingung, dass die Quellen übereinstimmen (Median, Mehrheit innerhalb einer Sekunde). Eine Uhr, die jeder auf dem Weg verstellen kann, ist keine Uhr, der wir bei der Gültigkeit von Zertifikaten trauen dürfen.
Auf dem Mac: aarch64 nativ und x86_64 per Emulation
Wir entwickeln auf dem Mac, und der hat zwei Wege, denselben Code auszuführen, die sich stark unterscheiden.
| aarch64 unter HVF | x86_64 unter TCG | |
|---|---|---|
| Was das ist | native Ausführung, Geschwindigkeit nahe am echten Prozessor | QEMU emuliert die Instruktionen |
| Handshake im Gast | etwa 0,58 ms (erstmals gemessen um M13); dp-bench heute 1941/s pro Kern |
etwa 3,3 ms |
| TLS-Smoke-Szenarien | schnell | etwa 15 Minuten pro Szenario |
| MTE | nein (gibt es unter HVF nicht) | x86 hat es nicht; die Heap-Markierung funktioniert nur für einen aarch64-Gast unter TCG |
| Wofür es dient | echte Messungen | Korrektheit: der x86-Pfad wird geprüft, die Zahlen sind keine Performance |
Die Regel ist einfach: Zahlen unter TCG sind Emulation, und wir zitieren sie nicht als Performance. Die x86-Performance messen wir nur auf einem echten VPS. Deshalb stammen die Krypto-Zahlen oben vom Contabo-VPS und nicht vom Mac.
Lektionen
- Messen Sie auf einer echten Maschine, bevor Sie optimieren. Unser erster Ansatz war, das zu optimieren, was wir für langsam hielten; erst die Diagnose auf dem VPS zeigte 1,2 ms und wo genau die Zeit hingeht.
- Überlast ist ein eigenes Feature. Ein System, das unter Last schnell ist, kann darüber zusammenbrechen; Abweisen muss billiger sein als Annehmen.
- Das Profil überrascht. Der größte Einzelgewinn für kleine Nachrichten lag nicht in der Kryptografie, sondern im Kopieren von Speicher.
- Eine Festplatte ohne Dateisystem erfordert mehr Arbeit, nicht weniger. Was ein Dateisystem sonst liefert (Ausrichtung, Wachstum, Discard, Kopien, Prüfungen), müssen wir selbst schreiben und prüfen, jeden Schritt mit Fehlerinjektion.
- Ehrlichkeit bei den Zahlen. Speichermessungen auf einem geteilten VPS schwanken zwischen den Läufen um das bis zu 10-Fache und sagen nichts über den Code aus, deshalb nennen wir sie nicht. Die Krypto-Zahlen sind stabil bis auf etwa 10%.
- Entwicklungsgeschwindigkeit ist keine Reife. Tests und ADRs wachsen mit dem Code, aber vier Tage sind kein echter Betrieb.
Was als Nächstes kommt
- H6: SQLite auf EL0-Ebene isoliert, sodass ein Fehler in der Datenbank nicht den Kernel öffnet.
- H7: der Rest der Zeitsicherheit (NTS und Behandlung von Sprüngen); SNTP ist erst der erste Schritt.
- X11: Vergleich mit Linux und Mosquitto auf demselben VPS, erst dann ist es sinnvoll, von relativer Geschwindigkeit zu sprechen.
- Release: die erste öffentliche Veröffentlichung für VPS (KVM), mit signierten Artefakten und Anleitungen.
- Nach dem Release: Bare Metal (X12 bis X16), IOMMU und Secure Boot sowie Festplattenverschlüsselung.
Der Code liegt auf github.com/zeljan-alduk/urMQTT, und docs/PROGRESS.md bleibt die einzige Quelle der Wahrheit darüber, was tatsächlich funktioniert. Pre-Alpha, wie zuvor; Fehlerberichte und Sicherheitsmeldungen nach den Hinweisen in SECURITY.md sind willkommen.
Kommentare
Kommentarregeln