pub struct ServerPluginInstaller { /* private fields */ }Expand description
AppState-backed PluginInstaller. See the module docs for the full
design rationale (borrowing, path derivation, atomicity).
Implementations§
Source§impl ServerPluginInstaller
impl ServerPluginInstaller
pub fn new(state: Data<AppState>) -> Self
Sourcepub async fn stop_services_for_upgrade(&self, plugin_id: &str) -> Vec<String>
pub async fn stop_services_for_upgrade(&self, plugin_id: &str) -> Vec<String>
Same-id upgrade ordering (issue #479 “Install-flow deltas” /
“Same-id upgrade ordering bug risk”): plugin_source::stage_plugin_source
swaps plugin_dir’s ENTIRE contents (old bundle moved to a
.backup-* dir, staged bundle renamed into place) BEFORE
install() — and therefore Self::register_services — ever runs.
A still-running old service process holding the old binary open
during that swap is at best running stale code post-swap and at
worst (Windows) blocks the rename outright. The minimal seam that
fixes the ordering without restructuring stage_plugin_source/
install(): the HTTP update_plugin handler calls this BEFORE
stage_plugin_source, using the URL path’s target id (known up
front for an upgrade, unlike a fresh install) to look up the
CURRENTLY-installed row’s registered.service_ids and stop each one.
Net effect: stop (old binary) → swap (new binary) → start (new
binary, via register_services inside install()), exactly the
sequencing the issue calls for.
Best-effort: returns exactly the ids that were actually running and
got stopped (not e.g. an already-stopped or unknown id), so a
subsequently-failed upgrade can restart precisely those — see
Self::restart_services_after_failed_upgrade. A plugin with no
prior install (or no services) returns an empty vec and stops
nothing.
Sourcepub async fn restart_services_after_failed_upgrade(
&self,
plugin_id: &str,
stopped: &[String],
)
pub async fn restart_services_after_failed_upgrade( &self, plugin_id: &str, stopped: &[String], )
Counterpart to Self::stop_services_for_upgrade: called after a
FAILED upgrade whose StagedPlugin::rollback() already restored
plugin_dir to the pre-upgrade bundle’s bytes — re-reads that
(now-restored) OLD plugin.json and restarts exactly the services in
stopped that it still declares as enabled. Best-effort/
log-and-continue: a failure here leaves the affected service stopped
(a degraded-but-safe outcome — never silently double-runs an old and
a new instance) rather than panicking the request.
Trait Implementations§
Source§impl PluginInstaller for ServerPluginInstaller
impl PluginInstaller for ServerPluginInstaller
Source§fn install<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
manifest: &'life1 PluginManifest,
plugin_dir: &'life2 Path,
source: PluginSource,
disposition: InstallDisposition,
installed_at: DateTime<Utc>,
) -> Pin<Box<dyn Future<Output = PluginResult<InstalledPlugin>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn install<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
manifest: &'life1 PluginManifest,
plugin_dir: &'life2 Path,
source: PluginSource,
disposition: InstallDisposition,
installed_at: DateTime<Utc>,
) -> Pin<Box<dyn Future<Output = PluginResult<InstalledPlugin>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
InstallDisposition::Upgrade, upgrade) a plugin
already unpacked at plugin_dir (source handling — copying a local
dir, unpacking a .tar.gz, fetching+verifying a URL + selecting the
per-platform artifact — happens BEFORE this is called; by the time
install runs, plugin_dir already contains plugin.json plus the
skills//prompts//workflows//bin/ layout the manifest declares). Read moreSource§fn uninstall<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = PluginResult<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn uninstall<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = PluginResult<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
install registered for id (stop + remove MCP
servers, remove prompt presets, remove workflow files), then remove
the provenance entry and delete plugin_dir from disk. Safe by
construction: the provenance registered set only ever names
plugin-created entries (invariant 1 in the module docs).Source§fn list<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = PluginResult<Vec<InstalledPlugin>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn list<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = PluginResult<Vec<InstalledPlugin>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
installed.json).