15 KiB
Executable File
# 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:
src/backend/Stella.Ops.Plugin.Binaries/<MyPlugin>/
├── 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:
dotnet new stellaops-plugin-schedule \
-n MyPlugin.Schedule \
--output src
Result:
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:
<PropertyGroup>
<StellaPluginOut>$(SolutionDir)src/backend/Stella.Ops.Plugin.Binaries/$(MSBuildProjectName)</StellaPluginOut>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\StellaOps.Common\StellaOps.Common.csproj"
PrivateAssets="all" />
</ItemGroup>
<Target Name="CopyStellaPlugin" AfterTargets="Publish">
<MakeDir Directories="$(StellaPluginOut)" />
<Copy SourceFiles="$(PublishDir)$(AssemblyName).dll;$(PublishDir)$(AssemblyName).dll.sig"
DestinationFolder="$(StellaPluginOut)" />
</Target>
## 5 Dependency‑Injection Entry‑point
Back‑end auto‑discovers restart‑time bindings through two mechanisms:
- Service binding metadata for simple contracts.
IDependencyInjectionRoutineimplementations 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.
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:
namespace StellaOps.DependencyInjection;
public sealed class IoCConfigurator : IDependencyInjectionRoutine
{
public IServiceCollection Register(IServiceCollection services, IConfiguration cfg)
{
services.AddSingleton<IJob, MyJob>(); // schedule job
services.Configure<MyPluginOptions>(cfg.GetSection("Plugins:MyPlugin"));
return services;
}
}
## 6 Schedule Plug‑ins
### 6.1 Minimal Job
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
services.AddCronJob<MyJob>("0 15 * * *"); // everyday
15:00 Cron syntax follows Hangfire rules
## 7 Scanner Adapters
Implement IScannerRunner. Register inside Configure:
services.AddScanner<MyAltScanner>("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
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
docker cp MyPlugin.zip <backend>:/opt/plugins/ && docker restart <backend>
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:
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
- Attribute Required: Plugins without
[StellaPluginVersion]are rejected - Minimum Version: Host version must be ≥
MinimumHostVersion - Maximum Version: Host version must be ≤
MaximumHostVersion(if specified) - Strict Major Version: If
MaximumHostVersionis not specified, the plugin is assumed to only support the same major version asMinimumHostVersion
Examples
// 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:
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:
using StellaOps.DependencyInjection.Validation;
// Register options with automatic startup validation
services.AddOptionsWithValidation<MyPluginOptions, MyPluginOptionsValidator>(
MyPluginOptions.SectionName);
// Or with data annotations
services.AddOptionsWithDataAnnotations<MyPluginOptions>(
MyPluginOptions.SectionName);
Create validators using the base class:
public sealed class MyPluginOptionsValidator : OptionsValidatorBase<MyPluginOptions>
{
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:
# 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
-
Add the version attribute to your plugin's AssemblyInfo.cs:
[assembly: StellaPluginVersion("1.0.0", MinimumHostVersion = "2.0.0", MaximumHostVersion = "2.99.99")] -
If your plugin must support multiple major host versions, explicitly set
MaximumHostVersion:// Supports host 1.x through 3.x [assembly: StellaPluginVersion("1.0.0", MinimumHostVersion = "1.0.0", MaximumHostVersion = "3.99.99")] -
Rebuild and re-sign your plugin.
Opt-out (Not Recommended)
If you must load legacy plugins without version attributes:
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 |