Modules

Publishing

Signing

[signing].key_file in gansey.toml (or GANSEY_SIGNING_KEY) names a 32-byte ed25519 seed the site lends to modules with sign = true. The key stays in its file (mode 600; the site will not read a key others can) and is never handed to a script: a module can ask for the public key and for a signature over one of its stored files. gansey signing-key new --out <file> makes one; gansey signing-key show prints the public half. Without a key, ctx.sign returns Err and the site still starts.

Switching a module off

Modules → a module → the switch, or gansey mod disable <id> / gansey mod enable <id>. A switched-off module stays in its folder with its tables, but init does not run, so nothing it registers is active: its routes return 404, its widgets drop out of the builder, its admin screens, hooks and jobs stop. File changes do not bring it back. The list of switched-off modules is stored per site in the settings row modules.disabled, so a restart keeps the choice. The CLI writes the same row; a running server picks the change up the next time it reloads the module (or restarts).

Installing from the registry

  • gansey mod add <registry-id[@version]> and Modules → Registry install packages from the hub; paid packages need the site's licence key.
  • A package from the registry is checked before anything unpacks it: its SHA-256 and its ed25519 signature must match the hub's signing key, the same key the site pinned for core updates ([updates].public_key, or the hub's /api/v1/releases/public-key on first use). A package that fails is discarded. The install route's url (a direct download from the hub) needs the hub's X-Checksum-Sha256 and X-Signature-Ed25519 headers; anything unsigned is refused there. The licence key goes only to the hub and the downloads its registry lists, never to an address typed in.
  • gansey mod remove <id> [--purge] removes the folder, and with --purge its tables, store and queue.

Your own modules

A module does not have to come from the hub. Anyone can write one and share it as a folder, a package file, a git repository or a download address, and a site can install it without us:

gansey mod add ../opening-hours                          # a folder
gansey mod add opening-hours-1.0.0.tar.gz                # a package file
gansey mod add https://example.test/you/opening-hours.git#v1.0.0
gansey mod add [email protected]:you/modules.git --ref main --path opening-hours
gansey mod add https://example.test/opening-hours-1.0.0.tar.gz
  • A repository is any https://, ssh:// or git:// address, git's user@host:path, or anything after git+. #branch-or-tag or --ref picks the branch, tag or commit (the default branch otherwise); --path names the folder holding module.toml when it is not at the top. The server runs git clone without prompting, so it needs git, and a private repository needs a key or credentials set up for the user the site runs as. Links in a repository arrive as plain files.
  • A folder is packed as gansey mod pack would, so hidden files such as .git stay behind. A package file or download may hold the module folder at its top or one level down (a repository host's download of a tag puts it there); it is rebuilt from its plain files, and one holding links or more than one module is refused.
  • None of these is checked against the hub's key (a download the hub serves with its signature headers is), so the command says the package goes in on your say-so. Core still holds the module to its manifest's permissions.
  • In the admin, Modules → Add your own takes an uploaded package, a repository (with an optional branch, tag or commit and folder) or a package URL. The package is fetched and set aside, and the screen shows its name, version, where it came from, the core it needs, every permission it asks for (marking any the installed version does not have) and whether the hub signed it. A second click installs it; nothing is installed before then, and a package left waiting more than an hour is cleared away the next time one is fetched. The admin only clones network addresses, never a path on the server.
  • Each install records where it came from in the module's folder (.origin.json), and the Modules screen shows it: a module that was not signed is marked unsigned and listed under Yours, whatever its manifest's source says.
  • [modules].allow_unsigned = false (GANSEY_MODULES_ALLOW_UNSIGNED) keeps a site's admin to packages its hub signed: Add your own disappears and the routes below refuse anything unsigned. A host sets it for sites it runs. The command line is not affected.
  • The routes behind the screen, all needing manage_modules: POST /api/modules/fetch with { git, ref?, path? } or { url }, and POST /api/modules/upload with a multipart file, answer { token, package, origin, replaces }; POST /api/modules/install with { staged: token } installs it, once. The activity log records the install with where it came from and whether it was signed.

Packing and publishing

  • A package for the registry is a gzip tarball of the module folder; gansey mod pack <folder> --out <dir> writes <id>-<version>.tar.gz and prints its SHA-256. Packing is deterministic, so the same folder always gives the same checksum.
  • gansey mod publish <folder> packs the folder and sends it to the hub (--hub, GANSEY_HUB_URL or [updates].hub_url) with the publish token from the hub's Packages screen (--token or GANSEY_REGISTRY_TOKEN). Release CI sends its OIDC token instead (trusted publishing): the hub checks it came from the shared module release workflow, for the tag v<version>, in the repository that first published the module. The hub reads the version, core range and permissions from the package's own module.toml, hashes and signs it; the command checks the stored checksum and the signature (against GANSEY_RELEASE_PUBLIC_KEY, or the hub's published key) before it succeeds. A version the registry already has is refused: bump version. --if-newer skips quietly instead, unless the version is newer than every one the registry holds, for scripts that publish whatever a folder holds. Official modules are published by a v<version> tag in each module's own repository (gansey-module-<id>).