Plugin storage, install API, and directory watcher #71
Labels
No Label
area:auth
area:docs
area:export
area:ops
area:storage
area:supply-chain
auth
backend
blocked
collab
deployment
docs
effort:L
effort:M
effort:S
frontend
plugins
qa
vs-nfd
vs-nfd:blocker
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: stwaidele/dorfteich#71
Loading…
Reference in New Issue
Block a user
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?
Context
Site Admins install plugin ZIPs via GUI or by dropping them into the plugins volume (vision + ADR 0008).
Scope
Backend:
plugins+pond_pluginsmodels;POST /admin/plugins(ZIP upload: structure check, manifest validation, CSS sanitization for style plugins, bundle size limit, apiVersion gate) unpacking to the plugins volume; static serving of plugin assets under/plugins/:id/:version/(immutable cache headers); directory watcher registering dropped ZIPs with the same validation (invalid → quarantine subfolder + log); update flow (same id, higher version) and uninstall (blocked whilerequired).Acceptance criteria
Technical notes
Dependencies
Depends on #52, #70.
Size: ~2 days
Conventions: English code/comments, clear human-readable code, no hard-coded UI strings (ADR 0012, add
deanden), permission checks only via the shared guard (docs/architecture/permissions.md). Read the referenced ADRs before starting.Done — pipeline green (all 8 contexts) and verified live on Test (readyz
ok, apiUp (healthy), watcherwatching plugin dropzoneat/data/plugins/_dropzone).Backend for installing plugin ZIPs (ADR 0008, plugin-architecture.md §Lifecycle, security.md §Plugins), consuming the #70 SDK.
Acceptance criteria
POST /admin/plugins+ watcherprocessDropped)required; afterwards assets are gone and metadata is tombstoned (removedAt)What landed
plugins+pond_plugins+PluginInstanceMode; migration20260710130000_plugins(applied cleanly on Test).PluginPackageService(pure): fflate unzip → structure / manifest (SDK) / apiVersion / kind-bundle-styles / CSS-sanitation (no@import, externalurl(),expression()) / zip-slip + unpacked-size guards.PluginStorageService:<PLUGINS_DIR>/<id>/<version>/, atomic writeVersion, traversal-safe asset resolution, dropzone/quarantine.PluginsService: install/update (higher-version-only, preserves instance mode) / uninstall (required-guarded, soft-delete + files removed) / list / get.POST/GET/DELETE /admin/plugins(SiteAdminGuard); public version-pinnedGET /plugins/:id/:version/*rest(immutable + nosniff).PluginWatcherService: dropzone watch with the same validation; invalid → quarantine + log; never crashes boot (setup failure disables drop-to-install and logs a warning).PLUGINS_DIR=/data/plugins(writable, node-owned) so it works out of the box; repo compose adds thepluginsvolume for persistence.Follow-up (needs a shared-host write to authorize): the Test/Int stage compose files on the VPS are not synced by CD, so they still lack the
pluginsvolume mount — installed plugins there are currently ephemeral (image default dir) until the volume is added, same pattern as the #62/#67 sidecars.Fix history in this issue: the initial push crash-looped on Test because the watcher's
mkdiron the non-writable default path aborted bootstrap; hardened + image default fixed it (commitsd7aa1fb,f3938b7).