Install plugins from the interface: upload a package, pick from the official catalogue, later add custom catalogues #38
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Depends on
Reference
Postulo/postulo#38
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Observation
What exists today
docs/PLUGINS.md(lines 87–94):uv pip install my-postulo-myboardinto the same environment, restart, and the source appears; uninstalling the package removes it. The registry (plugins/registry.py) loads entry points at startup, and a broken plugin disables itself and is logged rather than taking capture down.uv sync --locked), runs as the non-rootpostulouser, and the only writable place is the/app/datavolume. Apip installinside the running container lands in the image layer and is gone at the next upgrade. #5 asked this question ("how plugins reach a container") and left it open; this issue answers it.Shape
1. A plugins directory on the data volume.
POSTULO_PLUGINS_DIR, default/app/data/pluginsin the image anddata/pluginsotherwise, added tosys.pathat startup (site.addsitedir, so.pthfiles work), with aplugins.jsonbeside it recording what is installed and where it came from: name, version, source (upload, catalogue name, URL), SHA-256, when, by whom. Because the record lives on the volume, plugins survive an image upgrade: the entrypoint runsmanage.py plugins syncat boot, which reinstalls anything the record lists that the directory lacks — a new Python minor in the image, say.2. Three sources, in the order they arrive.
postulo.*entry points it declares, the Postulo version range it says it supports, its dependencies. Refused outright: anything that is not pure Python (py3-none-any; the image has no compiler and should not), anything with no Postulo entry point, anything whose dependencies would change the version of a package Postulo itself pins — the install runs with the core's lock as a constraint, so a plugin can never downgrade or upgrade the core's own dependencies, and the refusal names the package.postulo-pluginsrepository on Forgejo, its raw URL the default ofPOSTULO_PLUGIN_CATALOGUES— listing, per plugin: name, description, maintainer, licence, repository, and per version a wheel URL, SHA-256, compatible Postulo range and the entry-point kinds it provides. The Plugins page shows it with Install, Update when the index has a newer compatible version, Remove. The index is signed (minisign or plain Ed25519, the project's public key shipped in the release), and every wheel is checked against the SHA-256 the signed index carries — so a mirror or a hijacked download host cannot ship code. Fetched only when the administrator opens the page or presses Check for updates, never in the background by default: Postulo makes no request on its own. Being in the catalogue means the project has looked at the plugin — contract, licence, that it does nothing with the network or secrets beyond what it says — and that is stated as a review, not a guarantee.3. Activation. Entry points are read from
sys.path, and the registry already supports a refresh (available_sources(refresh=True)), so a plugin that only registers entry points — a source, a notifier, a store — can be activated without a restart once its directory is on the path. A plugin that adds a Django app (models, URLs, templates) needs one; the page says so and offers Restart now, which asks gunicorn to exit gracefully and lets Compose bring it back. The record on the volume makes that safe.4. The security posture, said plainly on the page. Installing a plugin is running someone's code inside Postulo, with access to everything on the instance. Only administrators can do it; the confirmation screen shows what is about to be installed; the catalogue is signed; a per-plugin Disable switch stops it without removing it; a failing plugin is isolated exactly as today. No automatic updates. An update is a code change and an administrator clicks it; a notification that updates exist can go through #4 for those who want it.
5. The same code from the command line.
manage.py plugins list | install <wheel or name> | update | remove | sync, which is what the entrypoint calls and what a GitOps-minded operator scripts.POSTULO_PLUGINS="postulo-apprise postulo-paperless"in the environment installs from the catalogue at first boot. TheFROM postuloDockerfile approach from #5 stays documented for anyone who wants an immutable image with plugins baked in.6. Documentation.
docs/PLUGINS.mdgains Publishing to the catalogue — a pull request topostulo-pluginswith the metadata and the wheel URL from the author's own release, plus what the review looks at. Installing Postulo gains a Plugins section. #5, #15, #16 and #34 are the first catalogue entries; #17, #18 and #19 run outside Postulo and are listed as companion tools with links, not installable packages.Classification
Enhancement. Not breaking:
uv pip installkeeps working exactly as documented; the directory and the record are new; nothing changes for an instance with no plugins.Depends on
Open questions