Every module has the same shape: metadata that identifies it, a version, a declaration of what it supports, and one class per resource it implements.
Installed modules sit under the SDK's module directory:
/userland/sdk/module/installed
Each module is a directory containing its metadata and its resource classes.
A module must identify itself. At minimum that means a title, a description and an
author — these are what an operator sees when choosing a module, so make the description say
what the module actually does rather than restating the title.
The module version is a numeric identifier used to detect that a newer release exists. It has no meaning beyond ordering: increment it whenever you publish a change you want existing installations to pick up.
properties.json declares what the module exposes. Two things belong here:
Resources — which parts of the lifecycle this module implements. A resource that is not declared falls back to the platform's default behaviour where one exists, so you only declare what you actually override.
Supported operating systems — supportedOperatingSystem, an array of strings. Any
operating-system identifier is accepted, including the linux shorthand, which marks the
module compatible with every supported Linux flavour and distribution. The panel uses this to
decide which nodes may host the game.
A resource is one capability — process handling, setup, and so on. Each is a class extending the abstract template for that resource type. The template must be implemented; a resource class that does not extend its template will not be executed.
This is what keeps modules interchangeable: the panel knows it can call start() on any
service module's process resource, whatever game is behind it.
Every resource is constructed with a gateway parameter. The gateway carries the external
data the caller passed in — most importantly, which thing is being acted upon.
For a service module that means an instance.id, which is how a resource knows which of the
many instances running that game it has been invoked for:
Resolve it once in the constructor and hold the resulting object, rather than re-resolving it in every method.
Creating your first module walks through building one of these from nothing.