Restructure solution layout by module

This commit is contained in:
master
2025-10-28 15:10:40 +02:00
parent 95daa159c4
commit d870da18ce
4103 changed files with 192899 additions and 187024 deletions

View File

@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
namespace StellaOps.Scheduler.WebService.Options;
/// <summary>
/// Configuration controlling Authority-backed authentication for the Scheduler WebService.
/// </summary>
public sealed class SchedulerAuthorityOptions
{
public bool Enabled { get; set; } = false;
/// <summary>
/// Allows the service to run without enforcing Authority authentication (development/tests only).
/// </summary>
public bool AllowAnonymousFallback { get; set; }
/// <summary>
/// Authority issuer URL exposed via OpenID discovery.
/// </summary>
public string Issuer { get; set; } = string.Empty;
public bool RequireHttpsMetadata { get; set; } = true;
public string? MetadataAddress { get; set; }
public int BackchannelTimeoutSeconds { get; set; } = 30;
public int TokenClockSkewSeconds { get; set; } = 60;
public IList<string> Audiences { get; } = new List<string>();
public IList<string> RequiredScopes { get; } = new List<string>();
public IList<string> RequiredTenants { get; } = new List<string>();
public IList<string> BypassNetworks { get; } = new List<string>();
public void Validate()
{
if (!Enabled)
{
return;
}
if (string.IsNullOrWhiteSpace(Issuer))
{
throw new InvalidOperationException("Scheduler Authority issuer must be configured when Authority is enabled.");
}
if (!Uri.TryCreate(Issuer.Trim(), UriKind.Absolute, out var uri))
{
throw new InvalidOperationException("Scheduler Authority issuer must be an absolute URI.");
}
if (RequireHttpsMetadata && !uri.IsLoopback && !string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("Scheduler Authority issuer must use HTTPS unless targeting loopback development.");
}
if (BackchannelTimeoutSeconds <= 0)
{
throw new InvalidOperationException("Scheduler Authority back-channel timeout must be greater than zero seconds.");
}
if (TokenClockSkewSeconds < 0 || TokenClockSkewSeconds > 300)
{
throw new InvalidOperationException("Scheduler Authority token clock skew must be between 0 and 300 seconds.");
}
}
}

View File

@@ -0,0 +1,19 @@
namespace StellaOps.Scheduler.WebService.Options;
public sealed class SchedulerCartographerOptions
{
public CartographerWebhookOptions Webhook { get; set; } = new();
}
public sealed class CartographerWebhookOptions
{
public bool Enabled { get; set; }
public string? Endpoint { get; set; }
public string? ApiKeyHeader { get; set; }
public string? ApiKey { get; set; }
public int TimeoutSeconds { get; set; } = 10;
}

View File

@@ -0,0 +1,109 @@
namespace StellaOps.Scheduler.WebService.Options;
/// <summary>
/// Scheduler WebService event options (outbound + inbound).
/// </summary>
using System;
public sealed class SchedulerEventsOptions
{
public GraphJobEventsOptions GraphJobs { get; set; } = new();
public SchedulerInboundWebhooksOptions Webhooks { get; set; } = new();
}
public sealed class GraphJobEventsOptions
{
/// <summary>
/// Enables emission of legacy <c>scheduler.graph.job.completed@1</c> events.
/// </summary>
public bool Enabled { get; set; }
}
public sealed class SchedulerInboundWebhooksOptions
{
public SchedulerWebhookOptions Feedser { get; set; } = SchedulerWebhookOptions.CreateDefault("feedser");
public SchedulerWebhookOptions Vexer { get; set; } = SchedulerWebhookOptions.CreateDefault("vexer");
}
public sealed class SchedulerWebhookOptions
{
private const string DefaultSignatureHeader = "X-Scheduler-Signature";
public SchedulerWebhookOptions()
{
SignatureHeader = DefaultSignatureHeader;
}
public bool Enabled { get; set; } = true;
/// <summary>
/// Require a client certificate to be presented (mTLS). Optional when HMAC is configured.
/// </summary>
public bool RequireClientCertificate { get; set; }
/// <summary>
/// Shared secret (Base64 or raw text) for HMAC-SHA256 signatures. Required if <see cref="RequireClientCertificate"/> is false.
/// </summary>
public string? HmacSecret { get; set; }
/// <summary>
/// Header name carrying the webhook signature (defaults to <c>X-Scheduler-Signature</c>).
/// </summary>
public string SignatureHeader { get; set; }
/// <summary>
/// Maximum number of accepted requests per sliding window.
/// </summary>
public int RateLimitRequests { get; set; } = 60;
/// <summary>
/// Sliding window duration in seconds for the rate limiter.
/// </summary>
public int RateLimitWindowSeconds { get; set; } = 60;
/// <summary>
/// Optional label used for logging/diagnostics; populated via <see cref="CreateDefault"/>.
/// </summary>
public string Name { get; set; } = string.Empty;
public static SchedulerWebhookOptions CreateDefault(string name)
=> new()
{
Name = name,
SignatureHeader = DefaultSignatureHeader,
RateLimitRequests = 120,
RateLimitWindowSeconds = 60
};
public void Validate()
{
if (!Enabled)
{
return;
}
if (string.IsNullOrWhiteSpace(SignatureHeader))
{
throw new InvalidOperationException($"Scheduler webhook '{Name}' must specify a signature header when enabled.");
}
if (!RequireClientCertificate && string.IsNullOrWhiteSpace(HmacSecret))
{
throw new InvalidOperationException($"Scheduler webhook '{Name}' must configure either HMAC secret or mTLS enforcement.");
}
if (RateLimitRequests <= 0)
{
throw new InvalidOperationException($"Scheduler webhook '{Name}' must configure a positive rate limit.");
}
if (RateLimitWindowSeconds <= 0)
{
throw new InvalidOperationException($"Scheduler webhook '{Name}' must configure a rate limit window greater than zero seconds.");
}
}
public TimeSpan GetRateLimitWindow() => TimeSpan.FromSeconds(RateLimitWindowSeconds <= 0 ? 60 : RateLimitWindowSeconds);
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
namespace StellaOps.Scheduler.WebService.Options;
/// <summary>
/// Scheduler host configuration defaults consumed at startup for cross-cutting concerns
/// such as plug-in discovery.
/// </summary>
public sealed class SchedulerOptions
{
public PluginOptions Plugins { get; set; } = new();
public void Validate()
{
Plugins.Validate();
}
public sealed class PluginOptions
{
/// <summary>
/// Base directory resolving relative plug-in paths. Defaults to solution root.
/// </summary>
public string? BaseDirectory { get; set; }
/// <summary>
/// Directory containing plug-in binaries. Defaults to <c>plugins/scheduler</c>.
/// </summary>
public string? Directory { get; set; }
/// <summary>
/// Controls whether sub-directories are scanned for plug-ins.
/// </summary>
public bool RecursiveSearch { get; set; } = false;
/// <summary>
/// Ensures the plug-in directory exists on startup.
/// </summary>
public bool EnsureDirectoryExists { get; set; } = true;
/// <summary>
/// Explicit plug-in discovery patterns (supports globbing).
/// </summary>
public IList<string> SearchPatterns { get; } = new List<string>();
/// <summary>
/// Optional ordered plug-in assembly names (without extension).
/// </summary>
public IList<string> OrderedPlugins { get; } = new List<string>();
public void Validate()
{
foreach (var pattern in SearchPatterns)
{
if (string.IsNullOrWhiteSpace(pattern))
{
throw new InvalidOperationException("Scheduler plug-in search patterns cannot contain null or whitespace entries.");
}
}
foreach (var assemblyName in OrderedPlugins)
{
if (string.IsNullOrWhiteSpace(assemblyName))
{
throw new InvalidOperationException("Scheduler ordered plug-in entries cannot contain null or whitespace values.");
}
}
}
}
}