# 10 · Plug‑in SDK Guide — **Stella Ops** *(v 1.5 — 11 Jul 2025 · template install, no reload, IoC)* --- ## 0 Audience & Scope Guidance for developers who extend Stella Ops with schedule jobs, scanner adapters, TLS providers, notification channels, etc. Everything here is OSS; commercial variants simply ship additional signed plug‑ins. --- ## 1 Prerequisites | Tool | Min Version | | ----------------------- | ----------------------------------------------------------------- | | .NET SDK | {{ dotnet }} | | **StellaOps templates** | install once via `bash dotnet new install StellaOps.Templates::*` | | **Cosign** | 2.3 + — used to sign DLLs | | xUnit | 2.6 | | Docker CLI | only if your plug‑in shells out to containers | --- ## 2 Repository & Build Output Every plug‑in is hosted in **`git.stella‑ops.org`**. At publish time it must copy its signed artefacts to: ~~~text src/backend/Stella.Ops.Plugin.Binaries// ├── MyPlugin.dll └── MyPlugin.dll.sig ~~~ The back‑end scans this folder on start‑up, verifies the **Cosign** signature, confirms the `[StellaPluginVersion]` gate, then loads the DLL inside an **isolated AssemblyLoadContext** to avoid dependency clashes --- ## 3 Project Scaffold Generate with the installed template: ~~~bash dotnet new stellaops-plugin-schedule \ -n MyPlugin.Schedule \ --output src ~~~ Result: ~~~text src/ ├─ MyPlugin.Schedule/ │ ├─ MyJob.cs │ └─ MyPlugin.Schedule.csproj └─ tests/ └─ MyPlugin.Schedule.Tests/ ~~~ --- ## 4 MSBuild Wiring Add this to **`MyPlugin.Schedule.csproj`** so the signed DLL + `.sig` land in the canonical plug‑in folder: ~~~xml $(SolutionDir)src/backend/Stella.Ops.Plugin.Binaries/$(MSBuildProjectName) ~~~ --- ## 5 Dependency‑Injection Entry‑point Back‑end auto‑discovers restart‑time bindings through two mechanisms: 1. **Service binding metadata** for simple contracts. 2. **`IDependencyInjectionRoutine`** implementations when you need full control. ### 5.1 Service binding metadata Annotate implementations with `[ServiceBinding]` to declare their lifetime and service contract. The loader honours scoped lifetimes and will register the service before executing any custom DI routines. ~~~csharp using Microsoft.Extensions.DependencyInjection; using StellaOps.DependencyInjection; [ServiceBinding(typeof(IJob), ServiceLifetime.Scoped, RegisterAsSelf = true)] public sealed class MyJob : IJob { // IJob dependencies can now use scoped services (Mongo sessions, etc.) } ~~~ Use `RegisterAsSelf = true` when you also want to resolve the concrete type. Set `ReplaceExisting = true` to override default descriptors if the host already provides one. ### 5.2 Dependency injection routines For advanced scenarios continue to expose a routine: ~~~csharp namespace StellaOps.DependencyInjection; public sealed class IoCConfigurator : IDependencyInjectionRoutine { public IServiceCollection Register(IServiceCollection services, IConfiguration cfg) { services.AddSingleton(); // schedule job services.Configure(cfg.GetSection("Plugins:MyPlugin")); return services; } } ~~~ --- ## 6 Schedule Plug‑ins ### 6.1 Minimal Job ~~~csharp using StellaOps.Scheduling; // contract [StellaPluginVersion("2.0.0")] public sealed class MyJob : IJob { public async Task ExecuteAsync(CancellationToken ct) { Console.WriteLine("Hello from plug‑in!"); await Task.Delay(500, ct); } } ~~~ ### 6.2 Cron Registration ```csharp services.AddCronJob("0 15 * * *"); // everyday ``` 15:00 Cron syntax follows Hangfire rules  ## 7 Scanner Adapters Implement IScannerRunner. Register inside Configure: ```csharp services.AddScanner("alt"); // backend ``` selects by --engine alt If the engine needs a side‑car container, include a Dockerfile in your repo and document resource expectations. ## 8 Packaging & Signing ```bash dotnet publish -c Release -p:PublishSingleFile=true -o out cosign sign --key $COSIGN_KEY out/MyPlugin.Schedule.dll # sign binary only sha256sum out/MyPlugin.Schedule.dll > out/.sha256 # optional checksum zip MyPlugin.zip out/* README.md ``` Unsigned DLLs are refused when StellaOps:Security:DisableUnsigned=false. ## 9 Deployment ```bash docker cp MyPlugin.zip :/opt/plugins/ && docker restart ``` Check /health – "plugins":["MyPlugin.Schedule@2.0.0"]. (Hot‑reload was removed to keep the core process simple and memory‑safe.) ## 10 Configuration Patterns | Need | Pattern | | ------------ | --------------------------------------------------------- | | Settings | Plugins:MyPlugin:* in appsettings.json. | | Secrets | Redis secure:: (encrypted per TLS provider). | | Dynamic cron | Implement ICronConfigurable; UI exposes editor. | ## 11 Testing & CI | Layer | Tool | Gate | | ----------- | -------------------------- | ------------------- | | Unit | xUnit + Moq | ≥ 50 % lines | | Integration | Testcontainers ‑ run in CI | Job completes < 5 s | | Style | dotnet | format 0 warnings | Use the pre‑baked workflow in StellaOps.Templates as starting point. ## 12 Publishing to the Community Marketplace Tag Git release plugin‑vX.Y.Z and attach the signed ZIP. Submit a PR to stellaops/community-plugins.json with metadata & git URL. On merge, the plug‑in shows up in the UI Marketplace. ## 13 Common Pitfalls | Symptom | Root cause | Fix | | ------------------- | -------------------------- | ------------------------------------------- | | NotDetected | .sig missing | cosign sign … | | VersionGateMismatch | Backend 2.1 vs plug‑in 2.0 | Re‑compile / bump attribute | | FileLoadException | Duplicate | StellaOps.Common Ensure PrivateAssets="all" | | Redis | timeouts Large writes | Batch or use Mongo | --- ## 14 Plugin Version Compatibility (v2.0) **IMPORTANT:** All plugins **must** declare a `[StellaPluginVersion]` attribute. Plugins without this attribute will be rejected by the host loader. Declare your plugin's version and host compatibility requirements: ```csharp using StellaOps.Plugin.Versioning; // In AssemblyInfo.cs or any file at assembly level [assembly: StellaPluginVersion("1.2.0", MinimumHostVersion = "1.0.0", MaximumHostVersion = "2.0.0")] ``` | Property | Purpose | Required | |----------|---------|----------| | `pluginVersion` (constructor) | Your plugin's semantic version | **Yes** | | `MinimumHostVersion` | Lowest host version that can load this plugin | Recommended | | `MaximumHostVersion` | Highest host version supported | Recommended for cross-major compatibility | | `RequiresSignature` | Whether signature verification is mandatory (default: true) | No | ### Version Compatibility Rules 1. **Attribute Required:** Plugins without `[StellaPluginVersion]` are rejected 2. **Minimum Version:** Host version must be ≥ `MinimumHostVersion` 3. **Maximum Version:** Host version must be ≤ `MaximumHostVersion` (if specified) 4. **Strict Major Version:** If `MaximumHostVersion` is not specified, the plugin is assumed to only support the same major version as `MinimumHostVersion` ### Examples ```csharp // Plugin works with host 1.0.0 through 2.x (explicit range) [assembly: StellaPluginVersion("1.0.0", MinimumHostVersion = "1.0.0", MaximumHostVersion = "2.99.99")] // Plugin works with host 2.x only (strict - no MaximumHostVersion means same major version) [assembly: StellaPluginVersion("1.0.0", MinimumHostVersion = "2.0.0")] // Plugin version 3.0.0 with no host constraints (uses plugin major version as reference) [assembly: StellaPluginVersion("3.0.0")] ``` --- ## 15 Plugin Host Configuration (v2.0) Configure the plugin loader with security-first defaults in `PluginHostOptions`: ```csharp var options = new PluginHostOptions { // Version enforcement (all default to true for security) HostVersion = new Version(2, 0, 0), EnforceVersionCompatibility = true, // Reject incompatible plugins RequireVersionAttribute = true, // Reject plugins without [StellaPluginVersion] StrictMajorVersionCheck = true, // Reject plugins crossing major version boundaries // Signature verification (opt-in, requires infrastructure) EnforceSignatureVerification = true, SignatureVerifier = new CosignPluginVerifier(new CosignVerifierOptions { PublicKeyPath = "/keys/cosign.pub", UseRekorTransparencyLog = true, AllowUnsigned = false }) }; var result = await PluginHost.LoadPluginsAsync(options, logger); // Check for failures if (result.HasFailures) { foreach (var failure in result.Failures) { logger.LogError("Plugin {Path} failed: {Reason} - {Message}", failure.AssemblyPath, failure.Reason, failure.Message); } } ``` ### Host Options Reference | Option | Default | Purpose | |--------|---------|---------| | `HostVersion` | null | The host application version for compatibility checking | | `EnforceVersionCompatibility` | **true** | Reject plugins that fail version checks | | `RequireVersionAttribute` | **true** | Reject plugins without `[StellaPluginVersion]` | | `StrictMajorVersionCheck` | **true** | Reject plugins that don't explicitly support the host's major version | | `EnforceSignatureVerification` | false | Reject plugins without valid signatures | | `SignatureVerifier` | null | The verifier implementation (e.g., `CosignPluginVerifier`) | ### Failure Reasons | Reason | Description | |--------|-------------| | `LoadError` | Assembly could not be loaded (missing dependencies, corrupt file) | | `SignatureInvalid` | Signature verification failed | | `IncompatibleVersion` | Plugin version constraints not satisfied | | `MissingVersionAttribute` | Plugin lacks required `[StellaPluginVersion]` attribute | --- ## 16 Fail-Fast Options Validation (v2.0) Use the fail-fast validation pattern to catch configuration errors at startup: ```csharp using StellaOps.DependencyInjection.Validation; // Register options with automatic startup validation services.AddOptionsWithValidation( MyPluginOptions.SectionName); // Or with data annotations services.AddOptionsWithDataAnnotations( MyPluginOptions.SectionName); ``` Create validators using the base class: ```csharp public sealed class MyPluginOptionsValidator : OptionsValidatorBase { protected override string SectionPrefix => "Plugins:MyPlugin"; protected override void ValidateOptions(MyPluginOptions options, ValidationContext context) { context .RequireNotEmpty(options.BaseUrl, nameof(options.BaseUrl)) .RequirePositive(options.TimeoutSeconds, nameof(options.TimeoutSeconds)) .RequireInRange(options.MaxRetries, nameof(options.MaxRetries), 0, 10); } } ``` --- ## 17 Available Templates (v2.0) Install and use the official plugin templates: ```bash # Install from local templates directory dotnet new install ./templates # Or install from NuGet dotnet new install StellaOps.Templates # Create a connector plugin dotnet new stellaops-plugin-connector -n MyCompany.AcmeConnector # Create a scheduled job plugin dotnet new stellaops-plugin-scheduler -n MyCompany.CleanupJob ``` Templates include: - Plugin entry point with version attribute - Options class with data annotations - Options validator with fail-fast pattern - DI routine registration - README with build/sign instructions --- ## 18 Migration Guide: v2.0 to v2.1 ### Breaking Change: Version Attribute Required As of v2.1, all plugins **must** include a `[StellaPluginVersion]` attribute. Plugins without this attribute will be rejected with `MissingVersionAttribute` failure. **Before (v2.0):** Optional, plugins without attribute loaded with warning. **After (v2.1):** Required, plugins without attribute are rejected. ### Migration Steps 1. Add the version attribute to your plugin's AssemblyInfo.cs: ```csharp [assembly: StellaPluginVersion("1.0.0", MinimumHostVersion = "2.0.0", MaximumHostVersion = "2.99.99")] ``` 2. If your plugin must support multiple major host versions, explicitly set `MaximumHostVersion`: ```csharp // Supports host 1.x through 3.x [assembly: StellaPluginVersion("1.0.0", MinimumHostVersion = "1.0.0", MaximumHostVersion = "3.99.99")] ``` 3. Rebuild and re-sign your plugin. ### Opt-out (Not Recommended) If you must load legacy plugins without version attributes: ```csharp var options = new PluginHostOptions { RequireVersionAttribute = false, // Allow unversioned plugins (NOT recommended) StrictMajorVersionCheck = false // Allow cross-major version loading }; ``` --- ## Change Log | Version | Date | Changes | |---------|------|---------| | v2.1 | 2025-12-14 | **Breaking:** `[StellaPluginVersion]` attribute now required by default. Added `RequireVersionAttribute`, `StrictMajorVersionCheck` options. Added `MissingVersionAttribute` failure reason. | | v2.0 | 2025-12-14 | Added StellaPluginVersion attribute, Cosign verification options, fail-fast validation, new templates | | v1.5 | 2025-07-11 | Template install, no hot-reload, IoC conventions |