Zum Inhalt springen

Packen, signieren, ausliefern

Ein Plugin wird als ein Archiv ausgeliefert, das seine eigene Signatur trägt. Die Signatur sagt wer, niemals was.

manifest.json <- genau eine, in der Wurzel
dist/ <- nur wenn Sie ein Frontend deklarieren; der genannte Eintrag liegt hier
runtime/ <- der Baum eines Sidecars, in beliebiger Form

Ein reines Backend-Paket liefert kein dist/, und das ist richtig. Wird ein solches Paket mit layout_invalid abgelehnt, läuft die Gegenseite auf einem Host älter als SDK 0.8.0.

Grenze Wert
Paket gepackt 50 MB
Paket entpackt, gesamt 200 MB
Eine Datei 50 MB
Einträge 2000
Pfadtiefe 32 Segmente
manifest.json 256 KB

Die Eintragszahl ist die, die einen Python-Baum beißt: viele kleine Dateien, nicht wenige große.

Zusätzlich abgelehnt wird ein Eintrag, der beim Entpacken aus dem Zielverzeichnis herausführt, eine Verknüpfung ist oder nach Normalisierung mit einem anderen kollidiert (unsafe_entry), sowie ein Archiv mit auffälligem Kompressionsverhältnis (resource_limit).

Ein zip-Aufruf reicht nicht. Übernehmen Sie plugins/tcp/scripts/pack.mjs und passen Sie an, was es einsammelt. Was dieses Skript tut und ein zip nicht:

  • Einträge nach ihren UTF-8-Bytes sortieren. Nicht Array.sort() — das ordnet nach UTF-16-Codeeinheiten und ist ab U+FFFF anderer Meinung. Alles andere an Ihrem Paket kann richtig sein, und ein Dateiname mit einem Emoji liefert trotzdem einen anderen Digest als erwartet.
  • Rohe Bytes lesen, nie Zeichenketten. Ein Packer, der eine .py dekodiert und neu kodiert, erzeugt ein Paket, das sich installieren lässt und dann beim Start stirbt.
  • Feste Zeitstempel. Gleiche Eingabe, gleiche Bytes — sonst wandert der Digest bei jedem Bau, und „erst prüfen, dann ausführen” bedeutet nichts mehr.
  • Tests und __pycache__ weglassen. Eine .pyc trägt einen absoluten Quellpfad und einen Zeitstempel; sie zu packen macht das Archiv maschinenabhängig.
node scripts/pack.mjs # Version aus package.json
node scripts/pack.mjs --version 0.2.0
node scripts/pack.mjs --tag acme.tcp-v0.2.0 # der Freigabeweg

Die Auflösung der Version ist --tag vor --version vor package.json. Eine Version wird nie ins Skript getippt.

Zeilenenden. Wenn Ihre Werkzeuge unter Windows CRLF schreiben, unterscheidet sich Ihr Paket Byte für Byte von einem, das unter Linux gebaut wurde. Beide funktionieren, aber sie sind nicht dasselbe Artefakt und ihre Digests unterscheiden sich. Halten Sie .gitattributes auf LF und bauen Sie aus einem sauberen Checkout.

Ein Paket ist Bytes plus eine Ed25519-Signatur über deren SHA-256-Digest.

node dist/cli/sign-package-cli.js --package build/acme.tcp-0.1.0.zip --key your-key.pem

Im Arbeitsbereich während der Entwicklung:

tsx packages/sdk/src/cli/sign-package-cli.ts --package build/acme.tcp-0.1.0.zip --key your-key.pem

Flags: --package und --key sind Pflicht, --out überschreibt den Ausgabepfad, --detached wählt die alte Form.

Das Werkzeug liest nie einen Schlüssel aus einer Umgebungsvariablen und schreibt ihn nirgendwohin. Auf die Ausgabe gehen nur Pfade und der Digest-Fingerabdruck — ein öffentlicher Wert.

  • Eingebettet (Standard). Die Signatur wird als fester Eintrag META-INF/signature in das Archiv gelegt; heraus kommt eine Datei <id>-<version>.tspkg. Das ist die Form, die ein Betreiber hochlädt.
  • Getrennt (--detached). Eine eigenständige <paket>.sig neben dem unveränderten Archiv. Nicht veraltet: das Freigaberegal des Betreibers hält weiterhin Paare aus <id>-<version>.zip und <id>-<version>.zip.sig.

Der Server akzeptiert beide nach Inhalt, nie nach Dateiendung oder Aufrufflag.

Ein bereits eingebettetes Paket lässt sich nicht erneut signieren — signieren Sie das Archiv vor dem Einbetten.

Welcher Schlüssel — das ist wichtiger als der Befehl

Abschnitt betitelt „Welcher Schlüssel — das ist wichtiger als der Befehl“
  • Ein Paket, das mit dem Betreiberschlüssel signiert ist, läuft überall.
  • Ein Paket mit Ihrem eigenen Schlüssel läuft nur dort, wo der Betreiber mandanteneigene Schlüssel zugelassen hat — und für ein Backend ist das noch einmal eine eigene Einstellung.

Trägt Ihr Paket Backend-Code und ist mit Ihrem eigenen Schlüssel signiert, wird es heute mit signature_scope_denied abgelehnt, solange die Instanz nicht die abgeschottete Backend-Laufzeit fährt. Ein reines Frontend-Paket mit Ihrem Schlüssel installiert sich normal. Fragen Sie den Betreiber, bevor Sie darauf einen Plan bauen.

Ein Mandanten-Administrator hinterlegt eigene Schlüssel unter Einstellungen → Plugins → Signaturschlüssel: dort wird der öffentliche Teil (public.pem) registriert. Der private Teil bleibt bei Ihnen und wird nie hochgeladen.

Zwei Wege hinein, und sie sind für verschiedene Leute.

Hochladen — die Plugin-Verwaltung hat einen Hochladedialog. Ziehen Sie die .tspkg hinein. So testen Sie, und so installiert ein Kunde etwas, das Sie nur für ihn gebaut haben.

Katalog — das Freigaberegal des Betreibers. Eine Markierung im Produkt-Repository baut, signiert und veröffentlicht eine Freigabe, ein Skript legt sie aufs Regal, und sie erscheint im Katalog jedes Mandanten zum Installieren mit einem Klick. Der Katalog bietet ausschließlich Pakete an, die gegen den Herstellerschlüssel geprüft sind — nie ein mandantensigniertes.

Installieren ist nicht Einschalten. Ein Paket liegt installiert und untätig, bis jemand es für einen Mandanten einschaltet — und dann startet ein Backend.

Aus dem Katalog — der normale Weg. Liegt im Katalog eine neuere Version eines installierten Plugins, zeigt die Plugin-Liste unter Einstellungen → Plugins den Schritt als Pfeil (0.1.0 → 0.2.0), und das Zeilenmenü bietet Auf 0.2.0 aktualisieren… an. Der Dialog nennt den Schritt, fragt einmal nach und holt die neue Version vom Regal — keine Datei, keine Signatur. Das Plugin behält seine Daten und seine Konfiguration. Gibt es nichts Neueres, steht dort Aktuell; bei einem Plugin, das der Katalog nicht führt, Nicht im Katalog. Der Reiter Katalog bietet dieselbe Aktualisierung in seiner eigenen Zeile an.

Ein Plugin nie zum Aktualisieren entfernen. Entfernen kann seine Daten löschen — dafür ist es da. Eine Aktualisierung braucht es nie.

Paket hochladen… im selben Zeilenmenü ist der zweite Weg, für Versionen, die der Katalog nicht führt — eigene Plugins oder ein für einen Kunden signiertes Paket. Er ist auch der Weg für eine Aktualisierung, die der Ein-Klick-Weg als mit Datenverlust ablehnt: die Katalog-Aktualisierung bestätigt nie stellvertretend eine Änderung, die Daten zerstört, und der Hochladeweg zeigt den Plan zur Bestätigung.

Eine Aktualisierung muss eine echt höhere Version tragen; Rückschritte und Rückwärtsmigrationen gibt es nicht. Dasselbe gilt, wenn die aktive Version gegen den aktuellen Vertrauensanker nicht mehr prüft: Patch-Version erhöhen, neu signieren, aktualisieren — die Version unterscheidet zwei Uploads, nicht die Signatur.

Vor dem Vollzug zeigt das System einen Plan und stuft jede Änderung ein: ohne Folgen, prüfpflichtig, mit Datenverlust, nicht zulässig. Was Zeilen kostet, wird beziffert und muss ausdrücklich bestätigt werden.

Beim Entfernen gibt es drei getrennte Absichten: das Plugin vollständig entfernen (Tabellen, Verzeichniseintrag, Dateien), nur die Daten dieses Mandanten löschen, oder nur den Verzeichniseintrag entfernen und die Tabellen stehen lassen.

Startet das Backend der neuen Version nicht, wird die Aktualisierung nicht angewandt und die bisherige Version bleibt aktiv (backend_unstartable).

Installieren, einschalten, dann in das Serverprotokoll schauen. Ein Sidecar, der hochkam, sagt es:

sidecar ready · runtimeVersion 3.12.10 · routes 5

Wenn nicht:

Was Sie sehen Was es heißt
entry_digest_mismatch Der Baum hat sich zwischen Packen und Starten geändert. Meist hat der Interpreter Bytecode hineingeschrieben — starten Sie Python mit -B.
runtime_missing / runtime_too_old Der von Ihrem Manifest geforderte Interpreter fehlt oder ist älter.
runtime_unsupported_language Ihr requires.language ist keines, das der Host starten kann.
entry_outside_package Ihr entry-Pfad verlässt das Paket. Feuert auch für eine Verknüpfung, die nach außen zeigt.
signature_scope_denied Die Signatur ist in Ordnung. Die Richtlinie des Betreibers erlaubt einem Schlüssel Ihrer Art nicht, dies auszuführen.
permission_denied bei einem Schreiben Ein Recht fehlt — prüfen Sie das Tor der Fähigkeit, nicht nur capability:invoke.
backend_entry_missing Die im Manifest genannte Einstiegsdatei liegt nicht im Archiv.
start_timeout Ihr Backend hat sich nicht innerhalb von 10 s gemeldet.
  • plugins/tcp — Manifest mit Sidecar und Konnektor-Typ, Python-Backend, Pack-Skript, Tests, die das Manifest von der Platte lesen, und scripts/peer.py als Gegenstelle am anderen Ende des Kabels.
  • docs/design/PRODLOG-179-sidecar-kanal.md — die Frames auf den Leitungen, Byte für Byte.
  • docs/design/PRODLOG-179-konnektor-plugin-vertrag.md — die fünf Routen und zwei Ereignisse eines Konnektors.