Packaging
A plugin is distributed as a gzip compressed tar archive (.tar.gz) with plugin.json at its root. A release can ship one portable package, one package per platform, or both.
Building a Package
nginx-ui plugin pack builds a package from a plugin directory, and signs it when you pass a key:
nginx-ui plugin pack ./mydns io.github.example.mydns-1.0.0-linux-amd64.tar.gz --key mydns.keyAny tool that writes a tar archive works too, as long as the result follows this page. nginx-ui plugin lint <package> checks it.
Layout
plugin.jsonis at the root of the archive, or exactly one directory down (the layouttar czf x.tar.gz plugin-dir/produces).- The root holds
README.mdandLICENSE. They are for the person installing the plugin and for anyone auditing it: a missingREADME.mdis an error, a missingLICENSEa warning. What changed in a version belongs in its release notes, see Releases. - The package contains every file
server.executablesdeclares. - It contains no symbolic or hard links.
Limits
| Limit | Value |
|---|---|
| Entries (files and directories) | 10,000 |
| Total size of the files, uncompressed | 256 MiB |
Nginx UI stops extracting as soon as a package exceeds a limit. A native plugin that ships executables for many platforms exceeds the size limit quickly: ship one package per platform instead.
Safe Paths
Every archive entry and every path in the manifest is a safe relative path:
/separates segments, never\;- not empty and no NUL byte;
- not absolute, neither
/foonor a Windows drive such asC:foo; - already clean: no
.segments and no//; - not
.or.., and not starting with../.
Nginx UI refuses a package with any other path instead of fixing it, and makes sure nothing it extracts ends up outside the plugin directory.
Per-Platform Packages
A per-platform package contains the executable for one platform. Its file name ends in that platform, and server.executables declares exactly that platform:
io.github.example.mydns-1.0.0-linux-arm64.tar.gz"server": { "executables": { "linux-arm64": "dist/linux-arm64/mydns" } }All packages of one release carry the same manifest except for server.executables, and the same files except for the executables and the signature files, so what a person approves is the same whichever package their platform gets.
A portable package may declare any number of platforms and contains the executable of every platform it declares. A package without server, or with an interpreted server.command, runs everywhere.
See Naming for how file names are built.
Executables
Nginx UI marks every file that server.executables or a path in server.command points at as executable after extracting it, whatever the archive says, and leaves the permission bits of other files alone. A platform the package does not ship is left out of server.executables rather than declared without its file.
Reproducible Builds
Build packages deterministically where the toolchain allows: stable file order, no timestamps beyond what the format needs. Then the checksum of a package means something, and anyone can confirm a package was built from the published source. Write plugin.json with sorted keys if you generate it.
Installing
When it installs a package, Nginx UI:
- extracts it within the limits above;
- validates the manifest, and discards everything when it fails;
- checks the signature and derives the trust level;
- shows the person the plugin's id, version, permissions and trust level, and asks for approval;
- installs it, replacing the previous version when there was one.