init plugins
This commit is contained in:
@@ -936,6 +936,45 @@ impl Default for ContentSearchConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM plugin runtime configuration (M0 walking skeleton).
|
||||
///
|
||||
/// The runtime is doubly gated: it is only compiled when the `plugins` cargo
|
||||
/// feature is enabled, and only activated when `enabled` is `true`. The limits
|
||||
/// below are conservative starting defaults, not part of the plugin ABI — each
|
||||
/// deployment may tune them.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PluginConfig {
|
||||
/// Master switch. When disabled, no plugins are loaded and the lifecycle
|
||||
/// bridge hook is never registered. Env: `OXICLOUD_ENABLE_PLUGINS`.
|
||||
pub enabled: bool,
|
||||
/// Directory scanned for plugins at startup; each plugin is a subdirectory
|
||||
/// containing `plugin.toml` + its `.wasm`. Default: `{storage_path}/.plugins`.
|
||||
/// Env: `OXICLOUD_PLUGINS_DIR`.
|
||||
pub plugins_dir: Option<PathBuf>,
|
||||
/// Wall-clock timeout for a single `handle` invocation. A runaway plugin
|
||||
/// cannot stall the upload path beyond this. Default: 250.
|
||||
/// Env: `OXICLOUD_PLUGIN_TIMEOUT_MS`.
|
||||
pub invocation_timeout_ms: u64,
|
||||
/// Max linear memory per plugin instance, in WASM pages (64 KiB each).
|
||||
/// Default: 256 (≈ 16 MiB). Env: `OXICLOUD_PLUGIN_MAX_MEMORY_PAGES`.
|
||||
pub max_memory_pages: u32,
|
||||
/// Hard cap on the serialized event payload handed to a plugin. Default:
|
||||
/// 256 KiB. Env: `OXICLOUD_PLUGIN_MAX_INPUT_BYTES`.
|
||||
pub max_input_bytes: usize,
|
||||
}
|
||||
|
||||
impl Default for PluginConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
plugins_dir: None,
|
||||
invocation_timeout_ms: 250,
|
||||
max_memory_pages: 256,
|
||||
max_input_bytes: 256 * 1024,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Global application configuration
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AppConfig {
|
||||
@@ -977,6 +1016,8 @@ pub struct AppConfig {
|
||||
pub i18n: I18nConfig,
|
||||
/// Content-search configuration (embedded full-text index)
|
||||
pub content_search: ContentSearchConfig,
|
||||
/// WASM plugin runtime configuration
|
||||
pub plugins: PluginConfig,
|
||||
}
|
||||
|
||||
/// Server-side i18n knobs.
|
||||
@@ -1029,6 +1070,7 @@ impl Default for AppConfig {
|
||||
magic_link: MagicLinkConfig::default(),
|
||||
i18n: I18nConfig::default(),
|
||||
content_search: ContentSearchConfig::default(),
|
||||
plugins: PluginConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1318,6 +1360,33 @@ impl AppConfig {
|
||||
config.content_search.max_text_bytes = val;
|
||||
}
|
||||
|
||||
// WASM plugin runtime
|
||||
if let Ok(v) = env::var("OXICLOUD_ENABLE_PLUGINS").map(|v| v.parse::<bool>())
|
||||
&& let Ok(val) = v
|
||||
{
|
||||
config.plugins.enabled = val;
|
||||
}
|
||||
if let Ok(dir) = env::var("OXICLOUD_PLUGINS_DIR")
|
||||
&& !dir.trim().is_empty()
|
||||
{
|
||||
config.plugins.plugins_dir = Some(PathBuf::from(dir.trim()));
|
||||
}
|
||||
if let Ok(v) = env::var("OXICLOUD_PLUGIN_TIMEOUT_MS").map(|v| v.parse::<u64>())
|
||||
&& let Ok(val) = v
|
||||
{
|
||||
config.plugins.invocation_timeout_ms = val;
|
||||
}
|
||||
if let Ok(v) = env::var("OXICLOUD_PLUGIN_MAX_MEMORY_PAGES").map(|v| v.parse::<u32>())
|
||||
&& let Ok(val) = v
|
||||
{
|
||||
config.plugins.max_memory_pages = val;
|
||||
}
|
||||
if let Ok(v) = env::var("OXICLOUD_PLUGIN_MAX_INPUT_BYTES").map(|v| v.parse::<usize>())
|
||||
&& let Ok(val) = v
|
||||
{
|
||||
config.plugins.max_input_bytes = val;
|
||||
}
|
||||
|
||||
if let Ok(v) = env::var("OXICLOUD_EXPOSE_SYSTEM_USERS").map(|v| v.parse::<bool>())
|
||||
&& let Ok(val) = v
|
||||
{
|
||||
|
||||
+75
-9
@@ -457,13 +457,28 @@ impl AppServiceFactory {
|
||||
authz.clone(),
|
||||
));
|
||||
|
||||
// Built before the upload/management services so the plugin lifecycle
|
||||
// bridge (which looks file metadata up by id) can be wired into the
|
||||
// dispatcher they receive. It depends only on repos + core, never on
|
||||
// the upload service, so the reorder is safe.
|
||||
let file_retrieval_service = Arc::new(FileRetrievalService::new_with_cache(
|
||||
repos.file_read_repository.clone(),
|
||||
core.file_content_cache.clone(),
|
||||
core.image_transcode_service.clone(),
|
||||
authz.clone(),
|
||||
));
|
||||
|
||||
// Effective lifecycle dispatcher: the core hooks (thumbnails, metadata)
|
||||
// plus, when the plugins feature is enabled, the WASM plugin bridge.
|
||||
let file_lifecycle = self.effective_file_lifecycle(core, &file_retrieval_service);
|
||||
|
||||
let file_upload_service = Arc::new(
|
||||
FileUploadService::new_with_read(
|
||||
repos.file_write_repository.clone(),
|
||||
repos.file_read_repository.clone(),
|
||||
)
|
||||
.with_content_cache(core.file_content_cache.clone())
|
||||
.with_file_lifecycle_hook(core.file_lifecycle.clone())
|
||||
.with_file_lifecycle_hook(file_lifecycle.clone())
|
||||
.with_instant_upload(
|
||||
authz.clone(),
|
||||
core.dedup_service.clone(),
|
||||
@@ -485,13 +500,6 @@ impl AppServiceFactory {
|
||||
),
|
||||
);
|
||||
|
||||
let file_retrieval_service = Arc::new(FileRetrievalService::new_with_cache(
|
||||
repos.file_read_repository.clone(),
|
||||
core.file_content_cache.clone(),
|
||||
core.image_transcode_service.clone(),
|
||||
authz.clone(),
|
||||
));
|
||||
|
||||
// FileManagementService — ref_count handled by PG trigger, no dedup port needed
|
||||
let file_management_service = Arc::new(
|
||||
FileManagementService::with_trash(
|
||||
@@ -502,7 +510,7 @@ impl AppServiceFactory {
|
||||
Some(core.file_content_cache.clone()),
|
||||
authz.clone(),
|
||||
)
|
||||
.with_file_lifecycle_hook(core.file_lifecycle.clone()),
|
||||
.with_file_lifecycle_hook(file_lifecycle.clone()),
|
||||
);
|
||||
|
||||
let file_use_case_factory = Arc::new(AppFileUseCaseFactory::new(
|
||||
@@ -549,6 +557,64 @@ impl AppServiceFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the file lifecycle dispatcher handed to the upload/management
|
||||
/// services. By default this is just the core dispatcher (thumbnails,
|
||||
/// metadata). When the `plugins` feature is built and enabled, it wraps the
|
||||
/// core dispatcher together with the WASM plugin bridge so plugins observe
|
||||
/// `file.uploaded` events without any of the core hooks being aware of them.
|
||||
fn effective_file_lifecycle(
|
||||
&self,
|
||||
core: &CoreServices,
|
||||
file_retrieval: &Arc<FileRetrievalService>,
|
||||
) -> Arc<dyn crate::application::ports::file_lifecycle::FileLifecycleHook> {
|
||||
#[cfg(feature = "plugins")]
|
||||
if self.config.plugins.enabled
|
||||
&& let Some(manager) = self.create_plugin_manager()
|
||||
{
|
||||
use crate::application::adapters::plugin_lifecycle_hook::PluginLifecycleHook;
|
||||
use crate::application::ports::plugin_ports::PluginDispatchPort;
|
||||
|
||||
let dispatch: Arc<dyn PluginDispatchPort> = manager;
|
||||
let bridge = Arc::new(PluginLifecycleHook::new(dispatch, file_retrieval.clone()));
|
||||
let composite = FileLifecycleService::new()
|
||||
.with_hook(core.file_lifecycle.clone())
|
||||
.with_hook(bridge);
|
||||
return Arc::new(composite);
|
||||
}
|
||||
|
||||
let _ = file_retrieval;
|
||||
core.file_lifecycle.clone()
|
||||
}
|
||||
|
||||
/// Discovers and loads WASM plugins when the `plugins` feature is enabled and
|
||||
/// `OXICLOUD_ENABLE_PLUGINS=true`. Plugins live under
|
||||
/// `OXICLOUD_PLUGINS_DIR` (default `{storage_path}/.plugins`); a missing or
|
||||
/// empty directory simply yields no plugins.
|
||||
#[cfg(feature = "plugins")]
|
||||
fn create_plugin_manager(
|
||||
&self,
|
||||
) -> Option<Arc<crate::infrastructure::services::plugins::ExtismPluginManager>> {
|
||||
if !self.config.plugins.enabled {
|
||||
return None;
|
||||
}
|
||||
let dir = self
|
||||
.config
|
||||
.plugins
|
||||
.plugins_dir
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.config.storage_path.join(".plugins"));
|
||||
let manager = crate::infrastructure::services::plugins::ExtismPluginManager::load_from_dir(
|
||||
self.config.plugins.clone(),
|
||||
&dir,
|
||||
);
|
||||
tracing::info!(
|
||||
target: "oxicloud::plugins",
|
||||
loaded = manager.loaded_count(),
|
||||
"plugin manager initialized"
|
||||
);
|
||||
Some(Arc::new(manager))
|
||||
}
|
||||
|
||||
/// Creates the audio metadata service (extracts ID3 tags from audio files)
|
||||
pub fn create_audio_metadata_service(
|
||||
&self,
|
||||
|
||||
Reference in New Issue
Block a user