using Microsoft.EntityFrameworkCore; using StellaOps.Auth.Abstractions; using StellaOps.Auth.ServerIntegration; using StellaOps.Integrations.Persistence; using StellaOps.Integrations.Plugin.GitHubApp; using StellaOps.Integrations.Plugin.Harbor; using StellaOps.Integrations.Plugin.InMemory; using StellaOps.Integrations.Plugin.Gitea; using StellaOps.Integrations.Plugin.Jenkins; using StellaOps.Integrations.Plugin.Nexus; using StellaOps.Integrations.Plugin.DockerRegistry; using StellaOps.Integrations.Plugin.GitLab; using StellaOps.Integrations.Plugin.Vault; using StellaOps.Integrations.Plugin.Consul; using StellaOps.Integrations.Plugin.EbpfAgent; using StellaOps.Integrations.WebService; using StellaOps.Integrations.WebService.AiCodeGuard; using StellaOps.Integrations.WebService.Infrastructure; using StellaOps.Integrations.WebService.Security; using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Infrastructure.Postgres.Migrations; using StellaOps.Infrastructure.Postgres.Options; using StellaOps.Localization; using StellaOps.Router.AspNet; using System.Net.Http.Headers; var builder = WebApplication.CreateBuilder(args); // Add services builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new() { Title = "StellaOps Integration Catalog API", Version = "v1" }); }); // Database var connectionString = builder.Configuration.GetConnectionString("IntegrationsDb") ?? builder.Configuration.GetConnectionString("Default") ?? "Host=localhost;Database=stellaops_integrations;Username=postgres;Password=postgres"; builder.Services.Configure(options => { options.ConnectionString = connectionString; options.SchemaName = IntegrationDbContext.DefaultSchemaName; }); builder.Services.AddDbContext(options => options.UseNpgsql(connectionString)); builder.Services.AddStartupMigrations( IntegrationDbContext.DefaultSchemaName, "Integrations.Persistence", typeof(IntegrationDbContext).Assembly); // Repository builder.Services.AddScoped(); // HttpClient factory (used by AuthRef resolver for Vault) builder.Services.AddHttpClient(VaultAuthRefResolver.HttpClientName, client => { var vaultAddr = builder.Configuration["VAULT_ADDR"] ?? "http://vault.stella-ops.local:8200"; client.BaseAddress = new Uri(vaultAddr.TrimEnd('/') + "/"); client.Timeout = TimeSpan.FromSeconds(15); }); builder.Services.AddHttpClient(S3CompatibleConnectorPlugin.HttpClientName, client => { client.Timeout = TimeSpan.FromSeconds(30); client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("StellaOps", "1.0")); }); builder.Services.AddHttpClient(FeedMirrorConnectorPluginBase.HttpClientName, client => { client.Timeout = TimeSpan.FromSeconds(30); client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("StellaOps", "1.0")); }); // Plugin loader builder.Services.AddSingleton(sp => { var logger = sp.GetRequiredService>(); var loader = new IntegrationPluginLoader(logger, sp); // Load from plugins directory var pluginsDir = builder.Configuration.GetValue("Integrations:PluginsDirectory") ?? Path.Combine(AppContext.BaseDirectory, "plugins"); if (Directory.Exists(pluginsDir)) { loader.LoadFromDirectory(pluginsDir); } // Also load from current assembly (for built-in plugins) loader.LoadFromAssemblies( [ typeof(Program).Assembly, typeof(GitHubAppConnectorPlugin).Assembly, typeof(HarborConnectorPlugin).Assembly, typeof(InMemoryConnectorPlugin).Assembly, typeof(GiteaConnectorPlugin).Assembly, typeof(JenkinsConnectorPlugin).Assembly, typeof(NexusConnectorPlugin).Assembly, typeof(DockerRegistryConnectorPlugin).Assembly, typeof(GitLabConnectorPlugin).Assembly, typeof(VaultConnectorPlugin).Assembly, typeof(ConsulConnectorPlugin).Assembly, typeof(EbpfAgentConnectorPlugin).Assembly ]); return loader; }); builder.Services.AddSingleton(TimeProvider.System); // Infrastructure builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Core service builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration); builder.Services.AddStellaOpsLocalization(builder.Configuration); builder.Services.AddTranslationBundle(System.Reflection.Assembly.GetExecutingAssembly()); // Authentication and authorization builder.Services.AddStellaOpsResourceServerAuthentication(builder.Configuration); builder.Services.AddAuthorization(options => { options.AddStellaOpsScopePolicy(IntegrationPolicies.Read, StellaOpsScopes.IntegrationRead); options.AddStellaOpsScopePolicy(IntegrationPolicies.Write, StellaOpsScopes.IntegrationWrite); options.AddStellaOpsScopePolicy(IntegrationPolicies.Operate, StellaOpsScopes.IntegrationOperate); options.AddStellaOpsScopePolicy(IntegrationPolicies.SecretAuthorityRead, StellaOpsScopes.IntegrationRead); options.AddStellaOpsScopePolicy(IntegrationPolicies.SecretAuthorityWrite, StellaOpsScopes.IntegrationWrite); }); // Unified audit emission (posts audit events to Timeline service) builder.Services.AddAuditEmission(builder.Configuration); // Stella Router integration var routerEnabled = builder.Services.AddRouterMicroservice( builder.Configuration, serviceName: "integrations", version: System.Reflection.CustomAttributeExtensions.GetCustomAttribute(System.Reflection.Assembly.GetExecutingAssembly())?.InformationalVersion ?? "1.0.0", routerOptionsSection: "Router"); builder.Services.AddStellaOpsTenantServices(); builder.TryAddStellaOpsLocalBinding("integrations"); var app = builder.Build(); app.LogStellaOpsLocalHostname("integrations"); // Configure pipeline if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseStellaOpsCors(); app.UseStellaOpsLocalization(); app.UseIdentityEnvelopeAuthentication(); app.UseAuthentication(); app.UseAuthorization(); app.UseStellaOpsTenantMiddleware(); app.TryUseStellaRouter(routerEnabled); // Map endpoints app.MapIntegrationEndpoints(); app.MapSecretAuthorityEndpoints(); // Health endpoint app.MapGet("/health", () => Results.Ok(new { Status = "Healthy", Timestamp = DateTimeOffset.UtcNow })) .WithTags("Health") .WithName("HealthCheck") .WithDescription("Returns the liveness status and current UTC timestamp for the Integration Catalog service. Used by the Router gateway and container orchestrator for health polling.") .AllowAnonymous(); // Database schema created by SQL migrations (001_initial_schema.sql) // Run via: stella-ops migration run Integrations --category startup app.TryRefreshStellaRouterEndpoints(routerEnabled); await app.LoadTranslationsAsync(); app.Run(); public partial class Program { }