From 5b971d0b95d786de3b4c2da25290c6a33a2053ee Mon Sep 17 00:00:00 2001 From: MPCoreDeveloper Date: Sat, 5 Sep 2026 07:38:46 +0200 Subject: [PATCH 1/2] feat: add .NET Aspire hosting package + server container image Implements the SharpCoreDB-side prerequisites from the SCDMS Aspire design (docs/aspire.md, SCDMS issue #10). - New SharpCoreDB.Aspire.Hosting NuGet package: container resource with HTTPS gRPC endpoint (5001) and HTTPS REST API endpoint (8443), WithServerContainer/WithJwtSecret extensions. - Docker workflow publishing ghcr.io/mpcoredeveloper/sharpcoredb-server (linux/amd64 + linux/arm64) on v* tags. - Dockerfile version via ARG VERSION (was hardcoded 1.5.0); image name updated. - Solution (.sln/.slnx), Directory.Packages.props and NuGet publish-layer registration. - docs/server/ASPIRE_INTEGRATION.md + server README link. --- .github/workflows/ci.yml | 1 + .github/workflows/docker-publish.yml | 66 +++++++++++ .github/workflows/publish-manual.yml | 1 + Directory.Packages.props | 2 + SharpCoreDB.sln | 17 ++- SharpCoreDB.slnx | 2 + docs/server/ASPIRE_INTEGRATION.md | 108 ++++++++++++++++++ docs/server/README.md | 1 + .../NuGet.README.md | 47 ++++++++ .../SharpCoreDB.Aspire.Hosting.csproj | 56 +++++++++ .../SharpCoreDbAspireExtensions.cs | 93 +++++++++++++++ .../SharpCoreDbServerResource.cs | 41 +++++++ src/SharpCoreDB.Server/Dockerfile | 5 +- 13 files changed, 437 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/docker-publish.yml create mode 100644 docs/server/ASPIRE_INTEGRATION.md create mode 100644 src/SharpCoreDB.Aspire.Hosting/NuGet.README.md create mode 100644 src/SharpCoreDB.Aspire.Hosting/SharpCoreDB.Aspire.Hosting.csproj create mode 100644 src/SharpCoreDB.Aspire.Hosting/SharpCoreDbAspireExtensions.cs create mode 100644 src/SharpCoreDB.Aspire.Hosting/SharpCoreDbServerResource.cs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1126e9a4..850ff49a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -281,6 +281,7 @@ jobs: push_layer "Layer 0 (core)" \ "SharpCoreDB.[0-9]*.nupkg" \ + "SharpCoreDB.Aspire.Hosting.*.nupkg" \ "SharpCoreDB.Client.Protocol.*.nupkg" \ "SharpCoreDB.Server.Protocol.*.nupkg" diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 00000000..73c51384 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,66 @@ +name: Docker + +on: + push: + tags: [ 'v*' ] + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ghcr.io/mpcoredeveloper/sharpcoredb-server + +jobs: + build-and-push: + name: Build & push SharpCoreDB server image + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Compute image tags + id: meta + shell: pwsh + run: | + $version = if ("${{ github.ref_type }}" -eq 'tag') { "${{ github.ref_name }}".TrimStart('v') } else { "${{ github.sha }}".Substring(0, 7) } + $tags = @("${{ env.IMAGE_NAME }}:$version") + if ("${{ github.ref_type }}" -eq 'tag') { + $tags += "${{ env.IMAGE_NAME }}:latest" + } + Write-Host "Version: $version" + Write-Host "Tags: $($tags -join ', ')" + "version=$version" >> $env:GITHUB_OUTPUT + "tags=$($tags -join ',')" >> $env:GITHUB_OUTPUT + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + file: src/SharpCoreDB.Server/Dockerfile + push: true + platforms: linux/amd64, linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + build-args: | + VERSION=${{ steps.meta.outputs.version }} + labels: | + org.opencontainers.image.title=SharpCoreDB Server + org.opencontainers.image.description=SharpCoreDB network database server (gRPC + HTTPS) + org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} + org.opencontainers.image.version=${{ steps.meta.outputs.version }} + org.opencontainers.image.revision=${{ github.sha }} diff --git a/.github/workflows/publish-manual.yml b/.github/workflows/publish-manual.yml index 7df256bf..595daec9 100644 --- a/.github/workflows/publish-manual.yml +++ b/.github/workflows/publish-manual.yml @@ -95,6 +95,7 @@ jobs: push_layer "Layer 0 (core)" \ "SharpCoreDB.[0-9]*.nupkg" \ + "SharpCoreDB.Aspire.Hosting.*.nupkg" \ "SharpCoreDB.Client.Protocol.*.nupkg" \ "SharpCoreDB.Server.Protocol.*.nupkg" diff --git a/Directory.Packages.props b/Directory.Packages.props index 94967e37..eeaa5aa9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,6 +7,7 @@ + @@ -30,6 +31,7 @@ + diff --git a/SharpCoreDB.sln b/SharpCoreDB.sln index 8dfe4679..8e1739a2 100644 --- a/SharpCoreDB.sln +++ b/SharpCoreDB.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 VisualStudioVersion = 18.1.11312.151 @@ -185,6 +185,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderManagement.CqrsDemo", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCoreDB.AppHost", "src\SharpCoreDB.AppHost\SharpCoreDB.AppHost.csproj", "{532DCF54-42EE-41C6-AC93-4DB59425C017}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCoreDB.Aspire.Hosting", "src\SharpCoreDB.Aspire.Hosting\SharpCoreDB.Aspire.Hosting.csproj", "{5D4E3C2B-1A09-4E8F-B6C7-D8E9F0A1B2C3}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCoreDB.Functional", "src\SharpCoreDB.Functional\SharpCoreDB.Functional.csproj", "{B7C3FE06-9C25-46F1-BDE4-C37A51192CDD}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCoreDB.Functional.Dapper", "src\SharpCoreDB.Functional.Dapper\SharpCoreDB.Functional.Dapper.csproj", "{4D7E752E-F1E9-4580-A60F-6F3F31CBD8A8}" @@ -731,6 +733,18 @@ Global {532DCF54-42EE-41C6-AC93-4DB59425C017}.Release|x64.Build.0 = Release|Any CPU {532DCF54-42EE-41C6-AC93-4DB59425C017}.Release|x86.ActiveCfg = Release|Any CPU {532DCF54-42EE-41C6-AC93-4DB59425C017}.Release|x86.Build.0 = Release|Any CPU + {5D4E3C2B-1A09-4E8F-B6C7-D8E9F0A1B2C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D4E3C2B-1A09-4E8F-B6C7-D8E9F0A1B2C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D4E3C2B-1A09-4E8F-B6C7-D8E9F0A1B2C3}.Debug|x64.ActiveCfg = Debug|Any CPU + {5D4E3C2B-1A09-4E8F-B6C7-D8E9F0A1B2C3}.Debug|x64.Build.0 = Debug|Any CPU + {5D4E3C2B-1A09-4E8F-B6C7-D8E9F0A1B2C3}.Debug|x86.ActiveCfg = Debug|Any CPU + {5D4E3C2B-1A09-4E8F-B6C7-D8E9F0A1B2C3}.Debug|x86.Build.0 = Debug|Any CPU + {5D4E3C2B-1A09-4E8F-B6C7-D8E9F0A1B2C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D4E3C2B-1A09-4E8F-B6C7-D8E9F0A1B2C3}.Release|Any CPU.Build.0 = Release|Any CPU + {5D4E3C2B-1A09-4E8F-B6C7-D8E9F0A1B2C3}.Release|x64.ActiveCfg = Release|Any CPU + {5D4E3C2B-1A09-4E8F-B6C7-D8E9F0A1B2C3}.Release|x64.Build.0 = Release|Any CPU + {5D4E3C2B-1A09-4E8F-B6C7-D8E9F0A1B2C3}.Release|x86.ActiveCfg = Release|Any CPU + {5D4E3C2B-1A09-4E8F-B6C7-D8E9F0A1B2C3}.Release|x86.Build.0 = Release|Any CPU {B7C3FE06-9C25-46F1-BDE4-C37A51192CDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B7C3FE06-9C25-46F1-BDE4-C37A51192CDD}.Debug|Any CPU.Build.0 = Debug|Any CPU {B7C3FE06-9C25-46F1-BDE4-C37A51192CDD}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -969,6 +983,7 @@ Global {16246662-205B-4B38-9C72-5386FD85A4B4} = {2F8A8533-DAA8-4CF9-A6C0-2F663AF7FD2E} {7BBCA0B4-D868-C04E-8748-21DE9A02486F} = {16246662-205B-4B38-9C72-5386FD85A4B4} {532DCF54-42EE-41C6-AC93-4DB59425C017} = {F8B5E3A4-1C2D-4E5F-8B9A-1D2E3F4A5B6C} + {5D4E3C2B-1A09-4E8F-B6C7-D8E9F0A1B2C3} = {F8B5E3A4-1C2D-4E5F-8B9A-1D2E3F4A5B6C} {B7C3FE06-9C25-46F1-BDE4-C37A51192CDD} = {F8B5E3A4-1C2D-4E5F-8B9A-1D2E3F4A5B6C} {4D7E752E-F1E9-4580-A60F-6F3F31CBD8A8} = {F8B5E3A4-1C2D-4E5F-8B9A-1D2E3F4A5B6C} {67976C63-C341-4D9D-A805-812B565E7CC9} = {F8B5E3A4-1C2D-4E5F-8B9A-1D2E3F4A5B6C} diff --git a/SharpCoreDB.slnx b/SharpCoreDB.slnx index 39ca8a5d..b434619c 100644 --- a/SharpCoreDB.slnx +++ b/SharpCoreDB.slnx @@ -32,6 +32,7 @@ + @@ -92,6 +93,7 @@ + diff --git a/docs/server/ASPIRE_INTEGRATION.md b/docs/server/ASPIRE_INTEGRATION.md new file mode 100644 index 00000000..1ac29bfa --- /dev/null +++ b/docs/server/ASPIRE_INTEGRATION.md @@ -0,0 +1,108 @@ +# SharpCoreDB Server × .NET Aspire Integration + +> **Status:** SharpCoreDB-side prerequisites implemented · **Date:** 2026-09-05 +> +> This repository delivers the two SharpCoreDB prerequisites defined by the SCDMS design doc +> [`docs/aspire.md`](https://github.com/MPCoreDeveloper/SCDMS/blob/main/docs/aspire.md) +> (SCDMS issue #10). Once the `SCDMS.Aspire.Hosting` package is built in the SCDMS repository, +> SharpCoreDB server + SCDMS can run as one Aspire application with all SCDMS ⇄ SharpCoreDB +> traffic over **gRPC**. + +## Goal (from the SCDMS design doc) + +Run SharpCoreDB server + SCDMS as a single Aspire app, like pgAdmin next to PostgreSQL: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var sharpCoreDb = builder.AddSharpCoreDB("db") + .WithServerContainer(); // SharpCoreDB server container + +builder.AddSCDMS("admin") + .WithGrpcReference(sharpCoreDb) // SCDMS container linked via gRPC + .WithHttpEndpoint(port: 8080, name: "http"); + +builder.Build().Run(); +``` + +## What this repository now provides + +### 1. Server container image → `ghcr.io/mpcoredeveloper/sharpcoredb-server` + +- `.github/workflows/docker-publish.yml` builds and pushes the server image on every `v*` tag + and via `workflow_dispatch`. +- Multi-architecture: `linux/amd64` + `linux/arm64` (Docker Buildx + QEMU). +- Tags: `` (tag without the leading `v`) and `latest`. +- Build context is the repository root; the Dockerfile lives at + `src/SharpCoreDB.Server/Dockerfile`. + +### 2. `SharpCoreDB.Aspire.Hosting` NuGet package + +The package (project `src/SharpCoreDB.Aspire.Hosting/`) registers the server container in an +Aspire application: + +```csharp +using SharpCoreDB.Aspire.Hosting; + +var builder = DistributedApplication.CreateBuilder(args); + +var db = builder.AddSharpCoreDB("db") // ghcr.io/mpcoredeveloper/sharpcoredb-server:latest + .WithServerContainer() + .WithImageTag("2.0.0.2") // optional pinned tag + .WithJwtSecret("a-secret-of-at-least-32-characters"); + +builder.Build().Run(); +``` + +The resource (`SharpCoreDbServerResource`) implements `IResourceWithConnectionString` and always +exposes **two** HTTPS endpoints: + +| Endpoint | Container port | Purpose | +|---|---|---| +| `grpc` | 5001 | Primary gRPC protocol (HTTP/2), matching `Server.GrpcPort` | +| `https` | 8443 | HTTPS REST API + `/health`, matching `Server.HttpsApiPort` | + +The connection string of the resource points at the gRPC endpoint: +`Host=;Port=;SSL=True`. + +## TLS in development (important) + +SharpCoreDB server **enforces TLS 1.2+** and refuses to start on plain HTTP +(`Server.Security.TlsEnabled` must stay `true` and a certificate path is required). Both +endpoints are therefore modeled with `WithHttpsEndpoint` in the hosting package. + +- **Production / behind a reverse proxy:** terminate TLS at a publicly trusted proxy (see the + SCDMS `samples/docker/` pattern) and mount the real certificate into `/app/certs`. +- **Local Aspire development:** the container still needs a certificate. Use the Aspire + development certificate (trust it in the consuming client) or mount a development PFX and + point `Server__Security__TlsCertificatePath` at it. gRPC clients in development may need + certificate validation disabled for the local `grpc` endpoint. + +## Environment configuration + +| Setting | Container env var | Notes | +|---|---|---| +| JWT secret | `Server__Security__JwtSecretKey` | Min. 32 chars; use `WithJwtSecret(...)` | +| TLS cert | `Server__Security__TlsCertificatePath` | Default `./certs/server.pfx`; mount into `/app/certs` | +| HTTPS API | `Server__EnableHttpsApi` / `Server__HttpsApiPort` | Defaults `true` / `8443` | +| gRPC | `Server__EnableGrpc` / `Server__GrpcPort` | Defaults `true` / `5001` | + +## Consuming the resource from another hosting package + +`WithGrpcReference` (to be added by `SCDMS.Aspire.Hosting`) can resolve the gRPC endpoint and +forward `SCDMS__DefaultServer*` settings: + +```csharp +var grpcEndpoint = sharpCoreDb.GetEndpoint("grpc"); +scdms.WithEnvironment("SCDMS__DefaultServerHost", grpcEndpoint) + .WithEnvironment("SCDMS__DefaultServerPort", grpcEndpoint.Property(EndpointProperty.Port)) + .WithEnvironment("SCDMS__DefaultServerUseSsl", "true"); +``` + +## Related + +- Server quick start: [`QUICKSTART.md`](QUICKSTART.md) +- Server configuration reference: [`CONFIGURATION_SCHEMA.md`](CONFIGURATION_SCHEMA.md) +- Server Docker Compose: `src/SharpCoreDB.Server/docker-compose.yml` +- SCDMS Aspire design doc (the plan this implements): + https://github.com/MPCoreDeveloper/SCDMS/blob/main/docs/aspire.md diff --git a/docs/server/README.md b/docs/server/README.md index 6a837d4f..44d9b497 100644 --- a/docs/server/README.md +++ b/docs/server/README.md @@ -33,6 +33,7 @@ In server mode, SharpCoreDB runs as a network service instead of an in-process e - **Linux systemd:** `installers/linux/install.sh` - **macOS launchd:** `installers/macos/install.sh` - **Docker:** `src/SharpCoreDB.Server/docker-compose.yml` +- **.NET Aspire:** `ASPIRE_INTEGRATION.md` — `SharpCoreDB.Aspire.Hosting` package + server container image For complete production installation instructions, see `INSTALLATION.md`. diff --git a/src/SharpCoreDB.Aspire.Hosting/NuGet.README.md b/src/SharpCoreDB.Aspire.Hosting/NuGet.README.md new file mode 100644 index 00000000..aeeb1643 --- /dev/null +++ b/src/SharpCoreDB.Aspire.Hosting/NuGet.README.md @@ -0,0 +1,47 @@ +# SharpCoreDB.Aspire.Hosting + +.NET Aspire hosting integration for the **SharpCoreDB network server** container image +(`ghcr.io/mpcoredeveloper/sharpcoredb-server`). + +## Quick start + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var db = builder.AddSharpCoreDB("db") + .WithServerContainer() + .WithImageTag("2.0.0.2") + .WithJwtSecret("replace-with-a-secret-of-at-least-32-characters"); + +builder.Build().Run(); +``` + +The server container exposes two HTTPS endpoints (TLS 1.2+ is enforced, plain HTTP is not supported): + +| Endpoint name | Container port | Purpose | +|---|---|---| +| `grpc` | 5001 | Primary gRPC protocol (HTTP/2) | +| `https` | 8443 | HTTPS REST API + `/health` | + +The resource implements `IResourceWithConnectionString`; the connection string points at the +`grpc` endpoint (`Host=…;Port=…;SSL=True`). + +## Extension methods + +- `AddSharpCoreDB(name)` — adds the container resource. +- `WithServerContainer()` — documentation alias for the container-based hosting intent. +- `WithJwtSecret(secret)` — forwards `Server__Security__JwtSecretKey` (min. 32 chars). +- `WithImageTag(tag)` — pick a published image tag instead of `latest`. + +## Prerequisites + +- The image is published to `ghcr.io/mpcoredeveloper/sharpcoredb-server` by the repository + Docker workflow on every `v*` tag (`linux/amd64` + `linux/arm64`). + +## Development notes (TLS) + +In local Aspire development the container keeps enforcing TLS. Trust the Aspire development +certificate in the consuming app, or connect over the Aspire network with certificate +validation disabled for the `grpc` endpoint. See +[docs/server/ASPIRE_INTEGRATION.md](https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/server/ASPIRE_INTEGRATION.md) +for the full guide. diff --git a/src/SharpCoreDB.Aspire.Hosting/SharpCoreDB.Aspire.Hosting.csproj b/src/SharpCoreDB.Aspire.Hosting/SharpCoreDB.Aspire.Hosting.csproj new file mode 100644 index 00000000..def19242 --- /dev/null +++ b/src/SharpCoreDB.Aspire.Hosting/SharpCoreDB.Aspire.Hosting.csproj @@ -0,0 +1,56 @@ + + + + + false + + + + net10.0 + 14.0 + enable + enable + true + + + SharpCoreDB.Aspire.Hosting + 2.0.0.2 + .NET Aspire hosting integration for the SharpCoreDB network server + + + SharpCoreDB.Aspire.Hosting + MPCoreDeveloper + SharpCoreDB + SharpCoreDB.Aspire.Hosting + database;aspire;hosting;grpc;container;sharpcoredb;net10 + MIT + https://github.com/MPCoreDeveloper/SharpCoreDB + https://github.com/MPCoreDeveloper/SharpCoreDB + git + NuGet.README.md + SharpCoreDB.jpg + true + snupkg + v2.0.0.2 (2026-09-05): first .NET Aspire hosting package for SharpCoreDB.Server - container image ghcr.io/mpcoredeveloper/sharpcoredb-server with HTTPS gRPC (5001) and HTTPS REST API (8443) endpoints. Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/server/ASPIRE_INTEGRATION.md + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/src/SharpCoreDB.Aspire.Hosting/SharpCoreDbAspireExtensions.cs b/src/SharpCoreDB.Aspire.Hosting/SharpCoreDbAspireExtensions.cs new file mode 100644 index 00000000..ae667f0a --- /dev/null +++ b/src/SharpCoreDB.Aspire.Hosting/SharpCoreDbAspireExtensions.cs @@ -0,0 +1,93 @@ +// +// Copyright (c) 2026 MPCoreDeveloper. All rights reserved. +// Licensed under the MIT License. +// + +using Aspire.Hosting; +using Aspire.Hosting.ApplicationModel; + +namespace SharpCoreDB.Aspire.Hosting; + +/// +/// .NET Aspire extension methods that register a SharpCoreDB network server container. +/// +public static class SharpCoreDbAspireExtensions +{ + /// Published OCI image for the SharpCoreDB server. + public const string ServerImage = "ghcr.io/mpcoredeveloper/sharpcoredb-server"; + + /// Default image tag used when no explicit tag is supplied. + public const string DefaultImageTag = "latest"; + + /// Minimum length (in characters) required for the JWT signing secret. + public const int MinJwtSecretLength = 32; + + /// Default HTTPS gRPC port inside the container (see src/SharpCoreDB.Server/Dockerfile). + public const int DefaultGrpcTargetPort = 5001; + + /// Default HTTPS REST API port inside the container (see src/SharpCoreDB.Server/Dockerfile). + public const int DefaultHttpsApiTargetPort = 8443; + + /// + /// Adds a SharpCoreDB network server container to the Aspire application. + /// The resource exposes a primary HTTPS gRPC endpoint (named + /// , container port 5001) and an + /// HTTPS REST API endpoint (named , + /// container port 8443). Use as a + /// documentation-friendly alias and to configure the JWT secret. + /// + /// The distributed application builder. + /// The resource name. + /// Optional container image tag (defaults to latest). + /// Optional fixed host port for the gRPC endpoint (default: allocated by Aspire). + /// Optional fixed host port for the HTTPS REST API endpoint (default: allocated by Aspire). + /// The SharpCoreDB server resource builder. + public static IResourceBuilder AddSharpCoreDB( + this IDistributedApplicationBuilder builder, + string name, + string? imageTag = null, + int? grpcPort = null, + int? httpsApiPort = null) + { + var resource = new SharpCoreDbServerResource(name); + + return builder.AddResource(resource) + .WithImage(ServerImage) + .WithImageTag(imageTag ?? DefaultImageTag) + .WithHttpsEndpoint(targetPort: DefaultGrpcTargetPort, port: grpcPort, name: SharpCoreDbServerResource.GrpcEndpointName) + .WithHttpsEndpoint(targetPort: DefaultHttpsApiTargetPort, port: httpsApiPort, name: SharpCoreDbServerResource.HttpsApiEndpointName); + } + + /// + /// Documentation-friendly alias that makes the container-based hosting intent explicit + /// (compatible with the SCDMS Aspire design, docs/aspire.md issue #10). + /// + /// The SharpCoreDB server resource builder. + /// The unchanged resource builder. + public static IResourceBuilder WithServerContainer( + this IResourceBuilder builder) => builder; + + /// + /// Sets the JWT signing secret that is forwarded to the container as + /// Server__Security__JwtSecretKey. The server refuses to start without a secret of at + /// least characters. + /// + /// The SharpCoreDB server resource builder. + /// The JWT signing secret (at least characters). + /// The SharpCoreDB server resource builder. + /// Thrown when is null, empty or shorter than characters. + public static IResourceBuilder WithJwtSecret( + this IResourceBuilder builder, + string secret) + { + ArgumentException.ThrowIfNullOrWhiteSpace(secret); + if (secret.Length < MinJwtSecretLength) + { + throw new ArgumentException( + $"The JWT secret must be at least {MinJwtSecretLength} characters.", nameof(secret)); + } + + builder.Resource.JwtSecretKey = secret; + return builder.WithEnvironment("Server__Security__JwtSecretKey", secret); + } +} diff --git a/src/SharpCoreDB.Aspire.Hosting/SharpCoreDbServerResource.cs b/src/SharpCoreDB.Aspire.Hosting/SharpCoreDbServerResource.cs new file mode 100644 index 00000000..17ecbe2e --- /dev/null +++ b/src/SharpCoreDB.Aspire.Hosting/SharpCoreDbServerResource.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) 2026 MPCoreDeveloper. All rights reserved. +// Licensed under the MIT License. +// + +using Aspire.Hosting.ApplicationModel; + +namespace SharpCoreDB.Aspire.Hosting; + +/// +/// A container resource representing the SharpCoreDB network server image. +/// The server always exposes two HTTPS endpoints: a primary gRPC endpoint +/// (container port 5001) and an HTTPS REST API endpoint (container port 8443). +/// Plain HTTP is not supported by the server (TLS 1.2+ is enforced). +/// +public sealed class SharpCoreDbServerResource(string name) + : ContainerResource(name), IResourceWithConnectionString +{ + /// Name of the primary HTTPS gRPC endpoint. + public const string GrpcEndpointName = "grpc"; + + /// Name of the HTTPS REST API endpoint. + public const string HttpsApiEndpointName = "https"; + + /// + /// Gets or sets the JWT signing secret forwarded to the container as + /// Server__Security__JwtSecretKey. Must be at least 32 characters. + /// + public string? JwtSecretKey { get; set; } + + /// + public ReferenceExpression ConnectionStringExpression => + ReferenceExpression.Create( + $"Host={GrpcEndpoint.Property(EndpointProperty.Host)};Port={GrpcEndpoint.Property(EndpointProperty.Port)};SSL=True"); + + /// Gets a reference to the HTTPS gRPC endpoint of the container. + public EndpointReference GrpcEndpoint => new(this, GrpcEndpointName); + + /// Gets a reference to the HTTPS REST API endpoint of the container. + public EndpointReference HttpsApiEndpoint => new(this, HttpsApiEndpointName); +} diff --git a/src/SharpCoreDB.Server/Dockerfile b/src/SharpCoreDB.Server/Dockerfile index 59d26dd1..82bcd560 100644 --- a/src/SharpCoreDB.Server/Dockerfile +++ b/src/SharpCoreDB.Server/Dockerfile @@ -1,6 +1,6 @@ # ── SharpCoreDB Server ── # Multi-stage build for .NET 10 production container -# Image: sharpcoredb/server:1.5.0 +# Image: ghcr.io/mpcoredeveloper/sharpcoredb-server # ── Stage 1: Build ── FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build @@ -37,6 +37,7 @@ RUN dotnet publish src/SharpCoreDB.Server/SharpCoreDB.Server.csproj \ # ── Stage 2: Runtime ── FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime +ARG VERSION=2.0.0.2 WORKDIR /app # Install runtime dependencies for health checks @@ -47,7 +48,7 @@ RUN apt-get update && \ # Labels LABEL maintainer="MPCoreDeveloper" \ org.opencontainers.image.title="SharpCoreDB Server" \ - org.opencontainers.image.version="1.5.0" \ + org.opencontainers.image.version="${VERSION}" \ org.opencontainers.image.description="SharpCoreDB network database server (gRPC + HTTPS)" \ org.opencontainers.image.source="https://github.com/MPCoreDeveloper/SharpCoreDB" From 9e0af526821a3a94dd776cf87121f7c62ca45c3e Mon Sep 17 00:00:00 2001 From: MPCoreDeveloper Date: Sat, 5 Sep 2026 08:54:02 +0200 Subject: [PATCH 2/2] ci: pin GitHub action SHAs for supply-chain security Resolves SonarCloud githubactions:S7637 findings (VULNERABILITY) on the new Docker workflow by pinning actions to full commit SHAs. --- .github/workflows/docker-publish.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 73c51384..06093fac 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Compute image tags id: meta @@ -36,20 +36,20 @@ jobs: "tags=$($tags -join ',')" >> $env:GITHUB_OUTPUT - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Log in to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: . file: src/SharpCoreDB.Server/Dockerfile