docs consolidation

This commit is contained in:
StellaOps Bot
2025-12-24 12:38:14 +02:00
parent 7503c19b8f
commit 9a08d10b89
215 changed files with 2188 additions and 9623 deletions

View File

@@ -1,428 +1,124 @@
#10 · Plugin SDK Guide — **StellaOps**
*(v1.5  11Jul2025 · template install, no reload, IoC)*
---
##0Audience & Scope
Guidance for developers who extend StellaOps with schedule jobs, scanner adapters, TLS providers, notification channels, etc. Everything here is OSS; commercial variants simply ship additional signed plugins.
---
##1Prerequisites
| 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 plugin shells out to containers |
---
##2Repository & Build Output
Every plugin is hosted in **`git.stellaops.org`**.
At publish time it must copy its signed artefacts to:
~~~text
src/backend/Stella.Ops.Plugin.Binaries/<MyPlugin>/
├── MyPlugin.dll
└── MyPlugin.dll.sig
~~~
The backend scans this folder on startup, verifies the **Cosign** signature, confirms the `[StellaPluginVersion]` gate, then loads the DLL inside an **isolated AssemblyLoadContext** to avoid dependency clashes
---
##3Project 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/
~~~
---
##4MSBuild Wiring
Add this to **`MyPlugin.Schedule.csproj`** so the signed DLL + `.sig` land in the canonical plugin folder:
~~~xml
<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>
~~~
---
##5DependencyInjection Entrypoint
Backend autodiscovers restarttime bindings through two mechanisms:
1. **Service binding metadata** for simple contracts.
2. **`IDependencyInjectionRoutine`** implementations when you need full control.
###5.1Service 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 (PostgreSQL connections, 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.2Dependency 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<IJob, MyJob>(); // schedule job
services.Configure<MyPluginOptions>(cfg.GetSection("Plugins:MyPlugin"));
return services;
}
}
~~~
---
##6Schedule Plugins
###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 plugin!");
await Task.Delay(500, ct);
}
}
~~~
###6.2 Cron Registration
```csharp
services.AddCronJob<MyJob>("0 15 * * *"); // everyday
```
15:00
Cron syntax follows Hangfire rules 
##7Scanner Adapters
Implement IScannerRunner.
Register inside Configure:
```csharp
services.AddScanner<MyAltScanner>("alt"); // backend
```
selects by --engine alt
If the engine needs a sidecar container, include a Dockerfile in your repo and document resource expectations.
##8Packaging & 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.
##9Deployment
```bash
docker cp MyPlugin.zip <backend>:/opt/plugins/ && docker restart <backend>
```
Check /health "plugins":["MyPlugin.Schedule@2.0.0"].
(Hotreload was removed to keep the core process simple and memorysafe.)
##10Configuration Patterns
| Need | Pattern |
| ------------ | --------------------------------------------------------- |
| Settings | Plugins:MyPlugin:* in appsettings.json. |
| Secrets | Redis secure:<plugin>:<key> (encrypted per TLS provider). |
| Dynamic cron | Implement ICronConfigurable; UI exposes editor. |
##11Testing & CI
| Layer | Tool | Gate |
| ----------- | -------------------------- | ------------------- |
| Unit | xUnit + Moq | ≥50% lines |
| Integration | Testcontainers run in CI | Job completes <5s |
| Style | dotnet | format 0 warnings |
Use the prebaked workflow in StellaOps.Templates as starting point.
##12Publishing to the Community Marketplace
Tag Git release pluginvX.Y.Z and attach the signed ZIP.
Submit a PR to stellaops/community-plugins.json with metadata & git URL.
On merge, the plugin shows up in the UI Marketplace.
##13Common Pitfalls
| Symptom | Root cause | Fix |
| ------------------- | -------------------------- | ------------------------------------------- |
| NotDetected | .sig missing | cosign sign |
| VersionGateMismatch | Backend 2.1 vs plugin 2.0 | Recompile / bump attribute |
| FileLoadException | Duplicate | StellaOps.Common Ensure PrivateAssets="all" |
| Redis | timeouts Large writes | Batch or use PostgreSQL |
---
## 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, MyPluginOptionsValidator>(
MyPluginOptions.SectionName);
// Or with data annotations
services.AddOptionsWithDataAnnotations<MyPluginOptions>(
MyPluginOptions.SectionName);
```
Create validators using the base class:
```csharp
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:
```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 |
# Plugin SDK Guide
This guide explains how StellaOps loads, validates, and wires restart-time plugins. It is intentionally cross-cutting: module-specific plugin contracts (Authority identity providers, Concelier connectors, Scanner analyzers, CLI command modules, etc.) live in the corresponding module dossiers under `docs/modules/`.
## 1) What a "plugin" means in StellaOps
StellaOps uses plugins to extend behavior without losing:
- Determinism (stable ordering, stable identifiers, replayable outputs).
- Offline posture (no hidden outbound calls; explicit trust roots and caches).
- Security boundaries (no client-controlled identity injection; signed artifacts where enforced).
Most services load plugins at process start (restart-time). Hot-reload is not a goal: restart-time loading keeps memory and dependency isolation predictable.
## 2) Service plugin loading model (restart-time)
Service plugins are loaded by the `StellaOps.Plugin` library:
- Discovery occurs in a configured plugin directory.
- Assemblies are loaded in an isolated `AssemblyLoadContext`.
- A compatibility gate runs before DI registration:
- version attribute presence and host compatibility
- optional signature verification
### 2.1 Plugin directory and discovery patterns
Default behavior (from `StellaOps.Plugin.Hosting.PluginHostOptions`):
- Base directory: `AppContext.BaseDirectory` (unless overridden).
- Plugin directory:
- `<PrimaryPrefix>.PluginBinaries` when `PrimaryPrefix` is set, otherwise `PluginBinaries`.
- Discovery glob(s):
- `<prefix>.Plugin.*.dll` for each configured prefix.
Hosts may override the directory and/or add explicit `searchPatterns` in their config. Use module operations docs to see the authoritative configuration for a given service.
### 2.2 Deterministic ordering
The loader is deterministic:
- When no explicit order is configured, discovered plugin assemblies are sorted by filename (case-insensitive).
- When an explicit order is configured, that order is applied first and the remainder stays sorted.
## 3) Version compatibility requirements
Plugins should declare the assembly-level attribute:
```csharp
using StellaOps.Plugin.Versioning;
[assembly: StellaPluginVersion("1.2.3", MinimumHostVersion = "1.0.0")]
```
The host can enforce:
- Required attribute presence (`RequireVersionAttribute`).
- Compatibility bounds (`MinimumHostVersion` / `MaximumHostVersion`).
- Strict major compatibility when `MaximumHostVersion` is not set (`StrictMajorVersionCheck`).
## 4) Dependency injection wiring
StellaOps supports two DI registration mechanisms.
### 4.1 Simple bindings via `ServiceBindingAttribute`
Annotate implementations with `ServiceBindingAttribute`:
```csharp
using Microsoft.Extensions.DependencyInjection;
using StellaOps.DependencyInjection;
[ServiceBinding(typeof(IMyContract), ServiceLifetime.Singleton, RegisterAsSelf = true)]
public sealed class MyPluginService : IMyContract
{
}
```
### 4.2 Advanced wiring via `IDependencyInjectionRoutine`
For full control, include a concrete `IDependencyInjectionRoutine` implementation. The host discovers and runs all routines in loaded plugin assemblies:
```csharp
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.DependencyInjection;
public sealed class MyPluginDi : IDependencyInjectionRoutine
{
public IServiceCollection Register(IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<IMyContract, MyPluginService>();
return services;
}
}
```
## 5) Signing and verification (Cosign)
When enabled, the host can verify plugin assemblies using Cosign (`cosign verify-blob`). The signature file is expected adjacent to the assembly:
- `MyPlugin.dll`
- `MyPlugin.dll.sig`
Verification is performed by `StellaOps.Plugin.Security.CosignPluginVerifier` and controlled by host configuration (for example `EnforceSignatureVerification` plus verifier options).
Offline note: verification can be performed without transparency log access when the host is configured accordingly (for example by ignoring tlog or using an offline receipt flow).
## 6) Repo layout (this monorepo)
In this repository, plugin binaries are typically staged under module-specific `*.PluginBinaries` directories (examples):
- `src/StellaOps.Authority.PluginBinaries/`
- `src/Concelier/StellaOps.Concelier.PluginBinaries/`
The authoritative loader configuration is owned by the host module and documented in its operations/architecture docs.
## 7) Testing expectations
Plugins should ship tests that protect determinism and compatibility:
- Stable ordering of outputs and collections.
- Stable timestamps (UTC ISO-8601).
- Fixture-backed inputs for offline operation.
- Compatibility checks for host version boundaries.
Reference tests for the generic plugin host live under:
- `src/__Libraries/__Tests/StellaOps.Plugin.Tests/`
## 8) Where to go next
- Authority plugins and operations: `docs/modules/authority/`
- Concelier connectors and operations: `docs/modules/concelier/`
- Scanner analyzers and operations: `docs/modules/scanner/`
- CLI command modules: `docs/modules/cli/`