Skip to content

About

A light QGIS plugin server

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

49 Commits

Folders and files

Repository files navigation

A ligth QGIS plugin server

A QGIS plugin repository server: it maintains a package catalog (plugins.xml / plugins.json) populated by uploading archives over HTTP.

The XML catalog is compatible with the QGIS Desktop plugin manager: the .../plugins.xml URL can be added directly as a repository in QGIS.

Features:

  • Automatic extraction of metadata (metadata.txt) from the uploaded archive;
  • Catalog updates safe from concurrent access;
  • Plugin filtering by QGIS version, tag, and status (experimental, deprecated, server plugin);
  • Catalog available as XML (QGIS Desktop) and JSON;
  • HTTP cache handling (ETag / Last-Modified);
  • Optional TLS and client certificate authentication (mTLS).

Important: the server does not serve the archives themselves. It writes the files to the plugins.repository directory and publishes download URLs built from plugins.download_url. A static file server (nginx, apache, …) serving that same directory must respond on download_url.

From source (Rust 2024 edition required):

cargo build --release
# the binary is in ./target/release/yapt-server

Docker image:

make -C .docker build

Limitations

This project is in active development, some features may be missing or incomplete.

Security

The server implements no application-level authentication: the POST and DELETE routes are open. In production, it must be placed behind a reverse proxy providing authentication, or use client certificate authentication (server.tls_client_ca_file) which, when enabled, requires a valid certificate for every connection.

Plugins database

At the moment, the server use a flat file for storing plugin informations. This will change in the near future by implementing support for different database formats.

It means that, at the moment, your shouldn't use that server for hosting thousands of plugins.

Quick start

mkdir -p /pub

cat > server.toml <<EOF
[server]
listen = "127.0.0.1:8070"

[plugins]
repository = "/pub"
download_url = "https://packages.example.org/pub/"
EOF

yapt-server serve -C server.toml

Upload a plugin, then read the catalog:

curl -X POST -F "file=@myplugin.1.0.0.zip" http://localhost:8070/plugins/
curl "http://localhost:8070/plugins.xml?qgis=3.40"

In QGIS: Plugins → Manage and Install Plugins → Settings → Add, with the URL http://localhost:8070/plugins.xml?qgis=3.40.

Command line

yapt-server <COMMAND>
Command Description
serve [-C FILE] Start the server
config [-C FILE] Print the effective configuration as JSON
createdb --url URL [--root DIR] [--output FILE] (Re)build the catalog from the archives present on disk

Without -C/--conf, the configuration is built solely from default values and environment variables.

createdb scans the <root>/*/*.zip archives (the parent directory name gives the plugin slug) and writes the catalog to --output (./plugins.json by default):

yapt-server createdb --root /pub --output /pub/plugins.json \
    --url https://packages.example.org/pub/

Configuration

TOML file

[server]
# Listening interface
listen = "127.0.0.1:8070"
enable_tls = false
# Required if enable_tls is set
tls_cert_file = "<path>"
tls_key_file = "<path>"
# Optional: enable client certificate authentication (mTLS)
tls_client_ca_file = "<path>"
# Defaults to the number of available physical cpus
num_workers = 4

[plugins]
# Path of the directory where archives are stored.
# A relative path is resolved against the configuration file's
# directory. The directory must exist.
repository = "/pub"
# Base url used for downloading plugins
download_url = "https://packages.3liz.org/pub/"
# Optional: temporary directory used during upload
# (defaults to the platform temporary directory)
tempdir = "/tmp"
# Optional: maximum upload size, in bytes
# (defaults to actix-multipart's default limit)
upload_limit = 104857600

[logging]
level = "info"  # error | warn | info | debug | trace

All keys are optional; unknown keys are rejected at startup. yapt-server config -C server.toml lets you check the effective configuration.

Environment variables

Environment variables must have the form CONF_SECTION[__SECTION]__KEY and override the configuration file.

Examples:

CONF_SERVER__LISTEN=0.0.0.0:9876
CONF_PLUGINS__REPOSITORY=/pub
CONF_PLUGINS__DOWNLOAD_URL=https://packages.3liz.org/pub/
CONF_LOGGING__LEVEL=debug

Two additional variables control logging:

  • QGIS_PLUGIN_SERVER_LOG: global log filter, in env_logger format (e.g. warn,yapt_server=debug). It enables logging from external libraries; the server's own modules remain forced to the level set by logging.level. When this variable is set, the module name is added to each log line.
  • SERVER_LOG_STYLE: controls colorization (auto, always, never).

HTTP API

Routes ending with / require it: /plugins returns 404, /plugins/ is the valid route.

Catalog

Method Route Description
GET, HEAD /plugins.xml XML catalog (QGIS Desktop)
GET, HEAD /plugins.json JSON catalog
GET, HEAD /plugins/ JSON catalog (alias)
GET, HEAD /plugins/<slug>/plugins.xml XML catalog for a single plugin
GET, HEAD /plugins/<slug>/plugins.json JSON catalog for a single plugin
GET, HEAD /plugins/<slug>/ JSON catalog for a single plugin

Query parameters:

Parameter Value Default Description
qgis version — Required. Target QGIS version (3.40, 3.40.2, …). Only compatible plugins (qgisMinimumVersion/qgisMaximumVersion) are returned
pre true/false false Include experimental versions
deprecated true/false false Include deprecated plugins
server true/false false Return only server plugins
all true/false false Return all versions, not only the latest ones (JSON only)
tags text — Fuzzy search on plugin name and tags

Booleans must be written explicitly: ?pre=true. ?pre alone or ?pre=1 returns 400.

Without all=true, the catalog contains at most two versions per plugin: the latest stable version and, if pre=true, the latest experimental version — this is the behavior expected by QGIS Desktop.

A missing or invalid qgis parameter returns 400 Bad Request.

Examples:

# XML catalog for QGIS 3.40, experimental versions included
curl "http://localhost:8070/plugins.xml?qgis=3.40&pre=true"

# Server plugins only, as JSON
curl "http://localhost:8070/plugins.json?qgis=3.40&server=true"

# All versions of a plugin
curl "http://localhost:8070/plugins/lizmap-server/plugins.json?qgis=3.40&all=true&pre=true"

JSON response:

{ "plugins": [ { "name": "Lizmap server", "version": "2.16.0", "slug": "lizmap-server", "downloadUrl": "..." } ] }

Cache

Responses carry the ETag (weak) and Last-Modified headers, computed from the catalog's last modification date. A request with an up-to-date If-None-Match receives 304 Not Modified. HEAD requests can be used to check catalog freshness without downloading it; on a /plugins/<slug>/… route, HEAD returns 404 if the plugin is unknown.

Uploading a plugin

POST /plugins/[?pre=true|false]

multipart/form-data body:

Field Required Description
file yes The plugin .zip archive
sig no Archive signature
checksum no Checksum file
checksum_sig no Checksum signature

Optional X-Upload-Agent header: its value is stored in the catalog's uploadedBy field.

The pre parameter forces the plugin's experimental status and overrides the experimental field of metadata.txt; if absent, the metadata is used as is.

The server:

  1. extracts metadata.txt from the archive and derives the slug from it (slugify(name), e.g. Lizmap server → lizmap-server);
  2. writes the archive (and accompanying files) to <repository>/<slug>/<uploaded-file-name>;
  3. records the version in the catalog and rewrites <repository>/plugins.json.

Response 200 OK with a Location header containing the public download URL (<download_url>/<slug>/<file_name>).

An existing stable version cannot be overwritten (400 Bad Request, "You cannot overwrite a stable plugin version"); an experimental version can be replaced.

curl -i -X POST \
    -H "X-Upload-Agent: gitlab-ci" \
    -F "file=@lizmap-server.2.16.0.zip" \
    -F "checksum=@lizmap-server.2.16.0.zip.sha256" \
    "https://plugins.example.org/plugins/"

Deleting a plugin

DELETE /plugins/<slug>/?version=<requirement>

version is a semver requirement (=0.1.0, <1.0.0, >=1.0, <2.0, …). Beware: version=0.1.0 is interpreted as ^0.1.0 and therefore deletes all 0.1.x versions; use =0.1.0 for an exact deletion.

The matching archives are removed from disk and the catalog is rewritten. The response lists the versions actually removed:

curl -X DELETE "https://plugins.example.org/plugins/lizmap-server/?version=%3D2.16.0"
{"removed":[["lizmap-server","2.16.0"]]}

An invalid requirement returns 400; a requirement that matches nothing returns 200 with an empty list.

Repository layout

<repository>/
├── plugins.json                 # catalog, reloaded at startup
├── lizmap-server/
│   ├── lizmap-server.2.16.0.zip
│   └── lizmap-server.2.16.0.zip.sha256
└── edigeo-processing/
    └── edigeo-processing.0.1.0.zip

At startup, the server loads <repository>/plugins.json if it exists. If the catalog is lost or out of sync with the files on disk, it can be rebuilt with createdb (see above).

Plugin metadata

The archive must contain a single root directory with a metadata.txt file ([general] section).

Required fields: name, description, version, qgisMinimumVersion, homepage, author, repository, commitSha1.

Optional fields used: qgisMaximumVersion, icon, tracker, tags, experimental, deprecated, server.

An invalid archive or incomplete metadata results in a 400 Bad Request.

Non-semver versions are accepted (a "best effort" conversion is applied: v2.4.0, 0.6-beta3, 23.2a, …), but they cannot be targeted by DELETE version requirements.

Docker

The image listens on port 9876 and runs yapt-server serve without a configuration file: configuration is therefore done through environment variables.

docker run --rm \
    -p 9876:9876 \
    -v /srv/plugins:/pub \
    -e CONF_PLUGINS__REPOSITORY=/pub \
    -e CONF_PLUGINS__DOWNLOAD_URL=https://packages.example.org/pub/ \
    qgis-plugin-server

Development

cargo test
cargo build --release
make package      # binary tar.gz archive
make wheel        # Python wheel (maturin)

The xml feature (enabled by default) provides the plugins.xml routes; it can be disabled with cargo build --no-default-features.

About

A light QGIS plugin server

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages