Repos: the first-party plugins
Rendered from spec at76d46f3 (2026-10-25) · its source · Edit this page
Status: Accepted
One page for every bundled part’s repository. Each is a first-party plugin, written in the same manifest as anybody’s, pinned by the default bundle and embedded in the binary.
Implements: ADR-0045, for F14-R7 and F14-R15. The manifest is plugin.toml and the pin is bundle.toml.
Which repositories
Every bundled part has a repository of its own, named plugin- followed by the id of
the stack service it takes the place of, which is also its plugin id. The container,
the configuration directory, the settings and the journal therefore keep their names
when a part’s source moves. 30-repos/repos.toml lists each with plugin = "<id>" and
says with image whether it builds an image.
| Repository | The part | Builds an image |
|---|---|---|
plugin-prowlarr |
the indexer manager, with its adapter | yes |
plugin-flaresolverr |
the solver that gets past an indexer’s bot protection | no |
plugin-nzbhydra2 |
the Usenet indexer search, with its adapter | yes |
plugin-sabnzbd |
the Usenet download client, with its adapter | yes |
plugin-gluetun |
the egress guard | no |
plugin-qbittorrent |
the torrent client, with its adapter | yes |
plugin-sonarr |
the television curator, with its adapter | yes |
plugin-radarr |
the film curator, with its adapter | yes |
plugin-lidarr |
the music curator, with its adapter | yes |
plugin-bindery |
the book curator | no |
plugin-bazarr |
the subtitle fetcher, with its adapter | yes |
plugin-recyclarr |
quality sync | no |
plugin-unpackerr |
archive extraction | no |
plugin-seerr |
the request service, with its adapter | yes |
plugin-calibre-web-automated |
the book library | no |
plugin-audiobookshelf |
the audiobook server | no |
plugin-navidrome |
the music server | no |
plugin-homepage |
the dashboard | no |
plugin-jellyfin |
the media server, with its adapter | yes |
plugin-door |
the guarded door | yes |
plugin-caddy |
the proxy | yes |
Two parts keep the repositories they already have:
lemonfiber-decline and
lemonfiber-request-gate each declare their service in a
plugin.toml of their own beside the image they build (REPO-R94).
What each holds
| Path | What it is |
|---|---|
plugin.toml |
The manifest: the part’s service, its claims, asks and proofs |
config/<id>/ |
The part’s starting files, written into its configuration directory (ARCH-R271) |
recordings/<id>/ |
The recordings its claims and proofs name, each taken from the pinned image |
conformance/ |
Where the plugin speaks a contract, the cases it is held to |
targets.toml |
The core release, or the core commit on main, it is validated and proved against (OPS-R89) |
The paths are the ones the media stack held these files at, so the catalogue’s copy of
a part reproduces them and every materialised file keeps its path (ARCH-R267). A part
with no adapter declares one service, the bundle assembles it into the stack the core
ships, and its in-process adapter is the core’s; the release whose bundle installs the
part deletes that adapter (ARCH-R266).
The adapters in front of Sonarr, Radarr, Lidarr and Prowlarr take the client they share
from adapter-kit-servarr at a commit, and hold only what their
own upstream alone answers (REPO-R99).
What its CI runs
- The manifest, against the schema the core it targets publishes.
- Its claims and proofs, against its recordings, and every image digest resolved on both platforms.
- Each image under its posture, the image started under what its manifest declares, healthy and answering its probes (ARCH-R250), and each run proof run live.
- Its recordings, taken again from the commit: the adapter built from the plugin’s own
Dockerfile, andlemonfiber plugin record --check --adapter <that image>at the lemonfibertargets.tomlnames held to every recording committed beside the manifest (ARCH-R285). A change to what the adapter answers is recorded withlemonfiber plugin record --adapter <that image>in the same pull request, and the pull request that moves the adapter’s pin to the image itsmainpublished holds every recording to that pin. The plugin holds no recorder of its own. - The organisation’s shared gates and hooks, as
shared/adoption.tomlstates them. - Its upstream pins, moved every week by
upstream-pinswhere its manifest runs an upstream’s image (below).
A repository that builds an image also builds and publishes it from every commit on its
main and from each train tag (OPS-R90, OPS-R91). manifest-pin moves the
manifest’s pin to each, with every recording taken again from the new image as an
upstream’s pin is below, and a commit that changes nothing but the pin, its recordings,
its proofs and its targets.toml publishes no image. The guarded door’s and the
proxy’s images are Caddy’s, rebuilt with the file capability removed, so they start
under standard (ADR-0045).
How its upstream pins move
A plugin whose manifest runs an image not under ghcr.io/lemonfiber/ calls spec’s
reusable upstream-pins workflow on a weekly schedule
(ADR-0049).
lemonfiber’s own images move when their repository publishes one (OPS-R90).
| Step | What happens |
|---|---|
| Which tag | The newest the registry publishes in the pinned tag’s spelling and major, so v3.41.3 follows v3.40.0, a fixed build such as 4.0.21.3020-ls327 follows 4.0.20.3014-ls326, and a major is never crossed (E1-R10) |
| Which digest | The tag’s multi-architecture index, publishing linux/amd64 and linux/arm64 (E1-R1); a digest that moved under the same tag is a rebuild, and is taken |
| What is written | Each service running the image gets the new digest and tag, and no other line of plugin.toml changes, through the rewrite manifest-pin uses for lemonfiber’s own images |
| Its recordings | Taken again by lemonfiber plugin record at the lemonfiber targets.toml names, and proofs.json written from them (ARCH-R285) |
| Who writes it | The image runs and lemonfiber is built in a job holding no secret; the job holding the release App’s token works the move out again from the registry and commits only plain files: the manifest with exactly that move, proofs.json, and recordings the manifest names taken from that pin |
| Its pull request | One rolling branch per image, upstream-pin/<the first service running it>, opened or edited through rolling-pull-request (OPS-R87) |
| When it merges | Armed where no recording still names the image it replaces, and merged once the required checks pass on its commit; otherwise disarmed, naming each recording left behind |
The callers and the media stack’s own pins workflow never run together: the change
that turns the callers on stops it, so one mover holds each upstream’s pin.
Releasing
Only a repository that builds an image is a stream the train cuts: every version that
cuts the core cuts it and tags it before the core (OPS-R86). One whose image is the
upstream’s is never tagged. The bundle pins it by revision and the digest of its tree,
and the pin moves by a reviewed pull request on the catalogue
(lemonfiber-plugins).
Requirements
| ID | Requirement |
|---|---|
| REPO-R94 | Every bundled part MUST have a repository of its own named plugin-<id>, <id> being both its plugin id and the id of the stack service it takes the place of, listed in 30-repos/repos.toml with plugin = "<id>" and image saying whether it builds an image; decline and the request gate MUST instead declare their service in a plugin.toml in lemonfiber-decline and lemonfiber-request-gate. |
| REPO-R95 | A first-party plugin’s repository MUST hold plugin.toml at its root, its starting files under config/<id>/, the recordings its claims and proofs name under recordings/<id>/, conformance/ where it speaks a contract, and targets.toml. |
| REPO-R96 | A first-party plugin’s CI MUST validate its manifest against the schema the core it targets publishes, judge its claims and proofs against its recordings, resolve every image digest on both platforms, and run each image under the posture its manifest declares, recording each run proof again and refusing a recording the run does not reproduce. |
| REPO-R97 | A first-party plugin’s repository that builds an image MUST be a stream the train tags; one that builds none MUST NOT be tagged, and the bundle MUST pin it by revision and tree digest alone. |
| REPO-R98 | A first-party plugin’s repository whose manifest runs an image not under ghcr.io/lemonfiber/ MUST call spec’s upstream-pins workflow on a weekly schedule, and that workflow MUST move each such image’s pin to the newest tag its registry publishes in the pinned tag’s spelling and major, resolved to the digest of a multi-architecture index publishing linux/amd64 and linux/arm64 (E1-R1), and to a digest that moved under the same tag, and MUST NOT cross a major (E1-R10); each image MUST move on a rolling branch of its own with every recording taken again by lemonfiber plugin record at the lemonfiber its targets.toml names and proofs.json written from them, opened or edited through rolling-pull-request (OPS-R85, OPS-R87); the image MUST run and lemonfiber MUST be built in a job holding no secret, and the job holding the release App’s token MUST run nothing that job wrote, MUST work the move out again from the manifest on main and the registry, and MUST commit only plain files, read through no link: that manifest with exactly that move, proofs.json, and recordings the manifest names taken from exactly that pin; the pull request MUST be armed to merge only where no recording still names the image it replaces, decided by that job alone, and disarmed, naming each, where one does. |
Affected repos (GOV-R7): every plugin-<id> repository listed above, lemonfiber-decline and lemonfiber-request-gate.
Related
- ADR-0045 — every bundled part has its repository now, and its adapter later
- bundle.md — how the bundle pins each of these
- adapter-kit-servarr — the client the Servarr adapters share
- lemonfiber-plugins — where the bundle lives, and the CI that holds each copy to its origin
- releasing —
OPS-R86,OPS-R90,OPS-R91 - ADR-0049 — a moved pin is recorded again by the core