5db3e48262
* feat: add opt-in plugin auto-updater Automatically update installed plugins in the background, off by default and opt-in. Builds on the existing verified install pipeline — an auto-update is just an automatic reinstall of a newer catalog package_url. Two-tier toggle on Settings > Plugins: an instance-wide default (SQLite plugin_settings) plus a per-plugin override (tri-state auto_update on the plugin record, carried forward on upgrade). The AutoUpdatePoller (~90-min cadence + a boot sweep) upgrades only plugins that are currently active, opted in, and report update_available; disabled/errored plugins stay on their installed version. New endpoints: GET/PUT /api/plugins/settings and PUT /api/plugins/:id/auto-update. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(plugins): recheck auto-update eligibility at install and fix test mock Address PR #1903 review feedback: - Re-check each plugin's current status + effective opt-in immediately before InstallFromURL (eligibleForAutoUpdate), closing the TOCTOU window between the candidate snapshot / catalog fetch and the install so a plugin disabled or opted out mid-sweep is never reinstalled and reactivated. - Correct the marketplace spec to describe the real failure behavior (shared with manual updates) instead of promising a version rollback the install pipeline does not perform. - Fix the frontend CI failure: the plugins page test mocked the whole plugins-api module without the new settings/auto-update functions, so useAutoUpdateSettings threw at mount. - Add an aria-label to the per-plugin auto-update Switch and Reset control. - Add a use-auto-update-settings hook test (load, failure, optimistic setDefault, rollback) and an eligibleForAutoUpdate unit test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
78 lines
3.4 KiB
Go
78 lines
3.4 KiB
Go
package plugins
|
|
|
|
import "github.com/kandev/kandev/internal/plugins/store"
|
|
|
|
// InstallRequest is the JSON body of POST /api/plugins/install when
|
|
// installing from a URL: {"url": "https://.../plugin-1.0.0.tar.gz"}. The
|
|
// same endpoint also accepts a multipart/form-data upload with a "package"
|
|
// field instead of this body — see Controller.install.
|
|
type InstallRequest struct {
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
// InstallResponse is the body of a successful POST /api/plugins/install.
|
|
// Warning is set (and the plugin's status left as "error") when the
|
|
// package was installed but its initial spawn/handshake failed — the
|
|
// operator can inspect Plugin.Status and retry via POST /:id/enable.
|
|
type InstallResponse struct {
|
|
Plugin *store.Record `json:"plugin"`
|
|
Warning string `json:"warning,omitempty"`
|
|
}
|
|
|
|
// UpdateConfigRequest is the PATCH /api/plugins/:id body: the full
|
|
// operator-editable config, replacing whatever was previously stored (see
|
|
// store.Store.SetConfig).
|
|
type UpdateConfigRequest struct {
|
|
Config map[string]any `json:"config"`
|
|
}
|
|
|
|
// UpdateSettingsRequest is the PUT /api/plugins/settings body: the
|
|
// instance-wide plugin preferences. AutoUpdateDefault is required (a bare
|
|
// bool, not a pointer) — the endpoint sets the default outright.
|
|
type UpdateSettingsRequest struct {
|
|
AutoUpdateDefault bool `json:"auto_update_default"`
|
|
}
|
|
|
|
// SetAutoUpdateRequest is the PUT /api/plugins/:id/auto-update body: the
|
|
// per-plugin override. AutoUpdate is a tri-state — true/false force the plugin
|
|
// on/off, and a null (or omitted) value clears the override so the plugin
|
|
// inherits the instance-wide default again.
|
|
type SetAutoUpdateRequest struct {
|
|
AutoUpdate *bool `json:"auto_update"`
|
|
}
|
|
|
|
// SyncResult is the body of a successful POST /api/plugins/sync (and the
|
|
// return value of Service.Sync / Service.bootScan): what the filesystem
|
|
// scan under the plugins directory found and did this run, per
|
|
// docs/specs/plugins/spec.md ("Filesystem sideloading & sync").
|
|
type SyncResult struct {
|
|
// Added lists the plugin ids of directory sideloads
|
|
// (<pluginsDir>/<id>/<version>/manifest.yaml found with no existing
|
|
// {id}.yml record) registered this run. Always registered
|
|
// StatusDisabled — sideloads are unverified and are never auto-spawned.
|
|
Added []string `json:"added"`
|
|
// Installed lists the plugin ids of dropped *.tar.gz packages installed
|
|
// (and, on a healthy spawn, activated) via the normal verified install
|
|
// pipeline (Service.Install) this run. Each source tarball is deleted
|
|
// from the plugins directory once its install succeeds.
|
|
Installed []string `json:"installed"`
|
|
// Missing lists the plugin ids of existing records whose InstallPath no
|
|
// longer exists on disk. Each is stopped (if running) and transitioned
|
|
// to StatusError.
|
|
Missing []string `json:"missing"`
|
|
// Errors lists every per-item failure encountered this run: a rejected
|
|
// dir-sideload candidate (invalid/mismatched manifest.yaml, or a
|
|
// lower-priority version dir skipped in favor of the lexically greatest
|
|
// when more than one is found for the same unregistered id), or a
|
|
// dropped tarball that failed pkgtar's verify/validate pipeline and was
|
|
// left in place.
|
|
Errors []SyncError `json:"errors"`
|
|
}
|
|
|
|
// SyncError is one entry of SyncResult.Errors: the filesystem path the
|
|
// problem was found at, and a human-readable reason.
|
|
type SyncError struct {
|
|
Path string `json:"path"`
|
|
Reason string `json:"reason"`
|
|
}
|