161 lines
7.7 KiB
C#
161 lines
7.7 KiB
C#
using Scalar.Aspire;
|
|
|
|
var builder = DistributedApplication.CreateBuilder(args);
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Platform infrastructure (Keycloak, Vault, MinIO, Nginx, Dnsmasq) is
|
|
// managed by infra/docker-compose.yml — NOT Aspire.
|
|
// Run `docker compose up -d` from the infra/ folder before starting this host.
|
|
//
|
|
// Fixed dev URLs (hardcoded to match infra/docker-compose.yml):
|
|
// Keycloak → http://localhost:8080
|
|
// Vault → http://localhost:8200
|
|
// MinIO → http://localhost:9000
|
|
//
|
|
// ControlPlane owns: opc-postgres (opcdb + giteadb), RabbitMQ, Gitea.
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
// Shared paths
|
|
var clientAssetsPath = Path.GetFullPath(Path.Combine(builder.AppHostDirectory, "..", "ClientAssets"));
|
|
var nginxConfDPath = Path.GetFullPath(Path.Combine(builder.AppHostDirectory, "..", "infra", "nginx", "conf.d"));
|
|
var vaultKeysFile = Path.GetFullPath(Path.Combine(builder.AppHostDirectory, "..", "infra", "vault", "data", "init.json"));
|
|
|
|
#region CONTROLPLANE POSTGRES
|
|
// ControlPlane owns this — isolated from platform infra postgres.
|
|
// Override via: dotnet user-secrets set "Parameters:cp-postgres-password" "yourpassword"
|
|
var cpPostgresPassword = builder.AddParameter("cp-postgres-password", "controlplane-dev", secret: true);
|
|
var cpPostgres = builder.AddPostgres("opc-postgres", password: cpPostgresPassword)
|
|
.WithLifetime(ContainerLifetime.Persistent)
|
|
.WithDataVolume("opc-postgres-data")
|
|
.WithHostPort(5433)
|
|
.WithPgAdmin();
|
|
|
|
var controlPlaneDb = cpPostgres.AddDatabase("opcdb");
|
|
var giteaDb = cpPostgres.AddDatabase("giteadb");
|
|
#endregion
|
|
|
|
#region GITEA
|
|
// Gitea is ControlPlane's code management component — owns its own DB on opc-postgres.
|
|
var gitea = builder.AddContainer("gitea", "gitea/gitea", "latest")
|
|
.WithHttpEndpoint(port: 3000, targetPort: 3000, name: "http")
|
|
.WithEndpoint(port: 2222, targetPort: 22, name: "ssh")
|
|
.WithVolume("clarity-gitea-data", "/data")
|
|
.WithEnvironment("GITEA__database__DB_TYPE", "postgres")
|
|
.WithEnvironment("GITEA__database__HOST", "host.docker.internal:5433")
|
|
.WithEnvironment("GITEA__database__NAME", "giteadb")
|
|
.WithEnvironment("GITEA__database__USER", "postgres")
|
|
.WithEnvironment("GITEA__database__PASSWD", "controlplane-dev")
|
|
.WithEnvironment("GITEA__server__DOMAIN", "opc.clarity.test")
|
|
.WithEnvironment("GITEA__server__ROOT_URL", "http://opc.clarity.test")
|
|
.WithEnvironment("GITEA__server__SSH_DOMAIN", "opc.clarity.test")
|
|
.WithEnvironment("GITEA__server__SSH_PORT", "2222")
|
|
.WithEnvironment("GITEA__service__DISABLE_REGISTRATION", "true")
|
|
.WaitFor(giteaDb)
|
|
.WithLifetime(ContainerLifetime.Persistent);
|
|
#endregion
|
|
|
|
#region RABBITMQ
|
|
var rabbitPassword = builder.AddParameter("rabbitmq-password", "clarity-rabbit", secret: true);
|
|
var rabbit = builder.AddRabbitMQ("rabbitmq", password: rabbitPassword)
|
|
.WithLifetime(ContainerLifetime.Persistent)
|
|
.WithManagementPlugin();
|
|
#endregion
|
|
|
|
#region CONTROLPLANE API
|
|
var api = builder.AddProject<Projects.ControlPlane_Api>("controlplane-api")
|
|
.WithReference(rabbit)
|
|
.WaitFor(rabbit)
|
|
.WithReference(controlPlaneDb)
|
|
.WaitFor(controlPlaneDb)
|
|
.WithEnvironment("Gitea__BaseUrl", gitea.GetEndpoint("http"))
|
|
.WithEnvironment("ClientAssets__Folder", clientAssetsPath)
|
|
.WithEnvironment("Docker__RepoRoot", Path.GetFullPath(Path.Combine(builder.AppHostDirectory, "..", ".."))) // ClarityStack/ root — needed for Directory.*.props
|
|
.WithExternalHttpEndpoints();
|
|
#endregion
|
|
|
|
#region PROVISIONING WORKER
|
|
builder.AddProject<Projects.ControlPlane_Worker>("controlplane-worker")
|
|
.WithReference(rabbit)
|
|
.WaitFor(rabbit)
|
|
// Vault — fixed dev address from infra/docker-compose.yml
|
|
.WithEnvironment("Vault__Address", "http://localhost:8200")
|
|
.WithEnvironment("Vault__ContainerAddress", "http://vault:8200")
|
|
.WithEnvironment("Vault__KeysFile", vaultKeysFile)
|
|
// Keycloak — fixed dev address from infra/docker-compose.yml
|
|
.WithEnvironment("Keycloak__AuthServerUrl", "http://localhost:8080")
|
|
.WithEnvironment("Keycloak__ContainerUrl", "https://keycloak.clarity.test")
|
|
.WithEnvironment("Keycloak__Realm", "master")
|
|
.WithEnvironment("Keycloak__Resource", "admin-cli")
|
|
.WithEnvironment("Keycloak__AdminUser", "admin")
|
|
.WithEnvironment("Keycloak__AdminPassword", "Admin1234!")
|
|
// Gateway
|
|
.WithEnvironment("Gateway__TenantBaseUrl", "https://{subdomain}.clarity.test")
|
|
// ClarityInfraOptions
|
|
.WithEnvironment("Clarity__Domain", "clarity.test")
|
|
.WithEnvironment("Clarity__Network", "clarity-net")
|
|
.WithEnvironment("Clarity__KeycloakPublicUrl", "https://keycloak.clarity.test")
|
|
.WithEnvironment("Clarity__KeycloakInternalUrl", "http://keycloak:8080")
|
|
.WithEnvironment("Clarity__VaultInternalUrl", "http://vault:8200")
|
|
.WithEnvironment("Clarity__NginxCertPath", "/etc/nginx/certs/clarity.test.crt")
|
|
.WithEnvironment("Clarity__NginxCertKeyPath", "/etc/nginx/certs/clarity.test.key")
|
|
// Nginx conf.d — points to infra/nginx/conf.d so platform nginx picks up tenant configs
|
|
.WithEnvironment("Nginx__ConfDPath", nginxConfDPath)
|
|
.WithEnvironment("ClientAssets__Folder", clientAssetsPath)
|
|
// Platform Postgres connection string for tenant database provisioning (infra/docker-compose.yml)
|
|
.WithEnvironment("ConnectionStrings__platformdb",
|
|
"Host=localhost;Port=5432;Username=postgres;Password=postgres")
|
|
.WithReference(controlPlaneDb)
|
|
.WaitFor(controlPlaneDb);
|
|
#endregion
|
|
|
|
#region CONTROLPLANE UI
|
|
builder.AddViteApp("controlplane-ui", "../clarity.controlplane")
|
|
.WithReference((IResourceBuilder<IResourceWithServiceDiscovery>)api)
|
|
.WaitFor(api);
|
|
#endregion
|
|
|
|
#region CLARITY-NET — connect RabbitMQ to platform network
|
|
// Ensures RabbitMQ (the one container Aspire owns) is reachable from tenant containers
|
|
// on clarity-net. All other platform containers are already on clarity-net via docker-compose.
|
|
builder.Eventing.Subscribe<AfterResourcesCreatedEvent>(async (@event, ct) =>
|
|
{
|
|
const string network = "clarity-net";
|
|
|
|
await Task.Delay(TimeSpan.FromSeconds(4), ct);
|
|
|
|
var (inspectCode, _) = await DockerOutputAsync($"network inspect {network}", ct);
|
|
if (inspectCode != 0)
|
|
await DockerOutputAsync($"network create {network}", ct);
|
|
|
|
var (idCode, idOut) = await DockerOutputAsync("ps --filter name=rabbitmq --format {{.ID}}", ct);
|
|
if (idCode == 0 && !string.IsNullOrWhiteSpace(idOut))
|
|
{
|
|
var containerId = idOut.Trim().Split('\n')[0].Trim();
|
|
await DockerOutputAsync($"network connect --alias rabbitmq {network} {containerId}", ct);
|
|
}
|
|
});
|
|
#endregion
|
|
|
|
#region SCALAR API DOCS
|
|
var scalar = builder.AddScalarApiReference();
|
|
scalar.WithApiReference(api);
|
|
#endregion
|
|
|
|
builder.Build().Run();
|
|
|
|
static async Task<(int ExitCode, string Output)> DockerOutputAsync(string args, CancellationToken ct)
|
|
{
|
|
var psi = new System.Diagnostics.ProcessStartInfo("docker", args)
|
|
{
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false
|
|
};
|
|
using var proc = System.Diagnostics.Process.Start(psi)!;
|
|
var output = await proc.StandardOutput.ReadToEndAsync(ct);
|
|
await proc.WaitForExitAsync(ct);
|
|
return (proc.ExitCode, output);
|
|
}
|
|
|
|
|