diff --git a/.aspire/settings.json b/.aspire/settings.json new file mode 100644 index 0000000..9929d96 --- /dev/null +++ b/.aspire/settings.json @@ -0,0 +1,3 @@ +{ + "appHostPath": "../DysonNetwork.Control/DysonNetwork.Control.csproj" +} \ No newline at end of file diff --git a/.env b/.env new file mode 100644 index 0000000..13c2b1d --- /dev/null +++ b/.env @@ -0,0 +1,35 @@ +# Default container port for ring +RING_PORT=8080 + +# Default container port for pass +PASS_PORT=8080 + +# Default container port for drive +DRIVE_PORT=8080 + +# Default container port for sphere +SPHERE_PORT=8080 + +# Default container port for develop +DEVELOP_PORT=8080 + +# Parameter cache-password +CACHE_PASSWORD=KS3jSPaU9e + +# Parameter queue-password +QUEUE_PASSWORD=8xEECa4ckz + +# Container image name for ring +RING_IMAGE=ring:latest + +# Container image name for pass +PASS_IMAGE=pass:latest + +# Container image name for drive +DRIVE_IMAGE=drive:latest + +# Container image name for sphere +SPHERE_IMAGE=sphere:latest + +# Container image name for develop +DEVELOP_IMAGE=develop:latest diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index f4f3bac..f78b693 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -1,4 +1,4 @@ -name: Build and Push Microservices +name: Aspire Publish Workflow on: push: @@ -7,7 +7,7 @@ on: workflow_dispatch: jobs: - build-sphere: + publish: runs-on: ubuntu-latest permissions: contents: read @@ -15,174 +15,46 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Setup NBGV - uses: dotnet/nbgv@master - id: nbgv - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push DysonNetwork.Sphere Docker image - uses: docker/build-push-action@v6 - with: - file: DysonNetwork.Sphere/Dockerfile - context: . - push: true - tags: ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-sphere:latest - platforms: linux/amd64 - build-pass: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout repository - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 with: - fetch-depth: 0 - - name: Setup NBGV - uses: dotnet/nbgv@master - id: nbgv - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push DysonNetwork.Pass Docker image - uses: docker/build-push-action@v6 - with: - file: DysonNetwork.Pass/Dockerfile - context: . - push: true - tags: ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-pass:latest - platforms: linux/amd64 + dotnet-version: "9.0.x" - build-ring: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Setup NBGV - uses: dotnet/nbgv@master - id: nbgv - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push DysonNetwork.Pusher Docker image - uses: docker/build-push-action@v6 - with: - file: DysonNetwork.Ring/Dockerfile - context: . - push: true - tags: ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-ring:latest - platforms: linux/amd64 - build-drive: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Setup NBGV - uses: dotnet/nbgv@master - id: nbgv - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push DysonNetwork.Drive Docker image - uses: docker/build-push-action@v6 - with: - file: DysonNetwork.Drive/Dockerfile - context: . - push: true - tags: ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-drive:latest - platforms: linux/amd64 + - name: Install Aspire CLI + run: dotnet tool install -g Aspire.Cli --prerelease - build-gateway: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Setup NBGV - uses: dotnet/nbgv@master - id: nbgv - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push DysonNetwork.Gateway Docker image - uses: docker/build-push-action@v6 - with: - file: DysonNetwork.Gateway/Dockerfile - context: . - push: true - tags: ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-gateway:latest - platforms: linux/amd64 + - name: Build and Publish Aspire Application + run: aspire publish --project ./DysonNetwork.Control/DysonNetwork.Control.csproj --output publish - build-develop: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout repository - uses: actions/checkout@v3 + - name: Tag and Push Images + run: | + IMAGES=( "sphere" "pass" "ring" "drive" "develop" ) + + for image in "${IMAGES[@]}"; do + IMAGE_NAME="ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-$image:alpha" + SOURCE_IMAGE_NAME="$image:latest" # Aspire's default local image name + + echo "Tagging and pushing $SOURCE_IMAGE_NAME to $IMAGE_NAME..." + docker tag $SOURCE_IMAGE_NAME $IMAGE_NAME + docker push $IMAGE_NAME + done + + - name: Upload Aspire Publish Directory + uses: actions/upload-artifact@v3 with: - fetch-depth: 0 - - name: Setup NBGV - uses: dotnet/nbgv@master - id: nbgv - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 + name: aspire-publish-output + path: ./publish/ + + - name: Upload Docker Compose file + uses: actions/upload-artifact@v3 with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push DysonNetwork.Develop Docker image - uses: docker/build-push-action@v6 - with: - file: DysonNetwork.Develop/Dockerfile - context: . - push: true - tags: ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-develop:latest - platforms: linux/amd64 + name: docker-compose-output + path: ./publish/docker-compose.yml diff --git a/DysonNetwork.Control/AppHost.cs b/DysonNetwork.Control/AppHost.cs new file mode 100644 index 0000000..ee70630 --- /dev/null +++ b/DysonNetwork.Control/AppHost.cs @@ -0,0 +1,77 @@ +using Aspire.Hosting.Yarp.Transforms; + +var builder = DistributedApplication.CreateBuilder(args); + +// Database was configured separately in each service. +// var database = builder.AddPostgres("database"); + +var cache = builder.AddRedis("cache"); +var queue = builder.AddNats("queue").WithJetStream(); + +var ringService = builder.AddProject("ring") + .WithReference(queue) + .WithHttpHealthCheck() + .WithEndpoint(5001, 5001, "https", name: "grpc"); +var passService = builder.AddProject("pass") + .WithReference(cache) + .WithReference(queue) + .WithReference(ringService) + .WithHttpHealthCheck() + .WithEndpoint(5001, 5001, "https", name: "grpc"); +var driveService = builder.AddProject("drive") + .WithReference(cache) + .WithReference(queue) + .WithReference(passService) + .WithReference(ringService) + .WithHttpHealthCheck() + .WithEndpoint(5001, 5001, "https", name: "grpc"); +var sphereService = builder.AddProject("sphere") + .WithReference(cache) + .WithReference(queue) + .WithReference(passService) + .WithReference(ringService) + .WithHttpHealthCheck() + .WithEndpoint(5001, 5001, "https", name: "grpc"); +var developService = builder.AddProject("develop") + .WithReference(cache) + .WithReference(passService) + .WithReference(ringService) + .WithHttpHealthCheck() + .WithEndpoint(5001, 5001, "https", name: "grpc"); + +// Extra double-ended references +ringService.WithReference(passService); + +builder.AddYarp("gateway") + .WithHostPort(5000) + .WithConfiguration(yarp => + { + var ringCluster = yarp.AddCluster(ringService.GetEndpoint("http")); + yarp.AddRoute("/ws", ringCluster); + yarp.AddRoute("/ring/{**catch-all}", ringCluster) + .WithTransformPathRemovePrefix("/ring") + .WithTransformPathPrefix("/api"); + var passCluster = yarp.AddCluster(passService.GetEndpoint("http")); + yarp.AddRoute("/.well-known/openid-configuration", passCluster); + yarp.AddRoute("/.well-known/jwks", passCluster); + yarp.AddRoute("/id/{**catch-all}", passCluster) + .WithTransformPathRemovePrefix("/id") + .WithTransformPathPrefix("/api"); + var driveCluster = yarp.AddCluster(driveService.GetEndpoint("http")); + yarp.AddRoute("/api/tus", driveCluster); + yarp.AddRoute("/drive/{**catch-all}", driveCluster) + .WithTransformPathRemovePrefix("/drive") + .WithTransformPathPrefix("/api"); + var sphereCluster = yarp.AddCluster(sphereService.GetEndpoint("http")); + yarp.AddRoute("/sphere/{**catch-all}", sphereCluster) + .WithTransformPathRemovePrefix("/sphere") + .WithTransformPathPrefix("/api"); + var developCluster = yarp.AddCluster(developService.GetEndpoint("http")); + yarp.AddRoute("/develop/{**catch-all}", developCluster) + .WithTransformPathRemovePrefix("/develop") + .WithTransformPathPrefix("/api"); + }); + +builder.AddDockerComposeEnvironment("docker-compose"); + +builder.Build().Run(); diff --git a/DysonNetwork.Control/DysonNetwork.Control.csproj b/DysonNetwork.Control/DysonNetwork.Control.csproj new file mode 100644 index 0000000..970a972 --- /dev/null +++ b/DysonNetwork.Control/DysonNetwork.Control.csproj @@ -0,0 +1,30 @@ + + + + + + Exe + net9.0 + enable + enable + a68b3195-a00d-40c2-b5ed-d675356b7cde + DysonNetwork.Control + + + + + + + + + + + + + + + + + + + diff --git a/DysonNetwork.Control/Properties/launchSettings.json b/DysonNetwork.Control/Properties/launchSettings.json new file mode 100644 index 0000000..ee4fd34 --- /dev/null +++ b/DysonNetwork.Control/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17025;http://localhost:15057", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21175", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22189" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15057", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19163", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20185" + } + } + } +} diff --git a/DysonNetwork.Control/appsettings.json b/DysonNetwork.Control/appsettings.json new file mode 100644 index 0000000..1bdb86e --- /dev/null +++ b/DysonNetwork.Control/appsettings.json @@ -0,0 +1,11 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "cache": "localhost:6379" + } +} diff --git a/DysonNetwork.Develop/DysonNetwork.Develop.csproj b/DysonNetwork.Develop/DysonNetwork.Develop.csproj index 1577bf3..49cbd38 100644 --- a/DysonNetwork.Develop/DysonNetwork.Develop.csproj +++ b/DysonNetwork.Develop/DysonNetwork.Develop.csproj @@ -31,6 +31,7 @@ + diff --git a/DysonNetwork.Develop/Program.cs b/DysonNetwork.Develop/Program.cs index df62582..3dae1e0 100644 --- a/DysonNetwork.Develop/Program.cs +++ b/DysonNetwork.Develop/Program.cs @@ -1,17 +1,16 @@ using DysonNetwork.Develop; using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Http; -using DysonNetwork.Shared.Registry; using DysonNetwork.Develop.Startup; -using DysonNetwork.Shared.Stream; +using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); +builder.AddServiceDefaults(); + builder.ConfigureAppKestrel(builder.Configuration); -builder.Services.AddRegistryService(builder.Configuration); -builder.Services.AddStreamConnection(builder.Configuration); builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppAuthentication(); builder.Services.AddAppSwagger(); @@ -22,6 +21,8 @@ builder.Services.AddDriveService(); var app = builder.Build(); +app.MapDefaultEndpoints(); + using (var scope = app.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); diff --git a/DysonNetwork.Develop/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Develop/Startup/ServiceCollectionExtensions.cs index 216ac2e..0ce5715 100644 --- a/DysonNetwork.Develop/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Develop/Startup/ServiceCollectionExtensions.cs @@ -20,11 +20,6 @@ public static class ServiceCollectionExtensions services.AddDbContext(); services.AddSingleton(SystemClock.Instance); services.AddHttpContextAccessor(); - services.AddSingleton(_ => - { - var connection = configuration.GetConnectionString("FastRetrieve")!; - return ConnectionMultiplexer.Connect(connection); - }); services.AddSingleton(); services.AddHttpClient(); diff --git a/DysonNetwork.Develop/appsettings.json b/DysonNetwork.Develop/appsettings.json index 1d407a2..b81e2d8 100644 --- a/DysonNetwork.Develop/appsettings.json +++ b/DysonNetwork.Develop/appsettings.json @@ -10,10 +10,7 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "App": "Host=localhost;Port=5432;Database=dyson_network_dev;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", - "FastRetrieve": "localhost:6379", - "Etcd": "etcd.orb.local:2379", - "Stream": "nats.orb.local:4222" + "App": "Host=localhost;Port=5432;Database=dyson_network_dev;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" }, "KnownProxies": [ "127.0.0.1", @@ -24,8 +21,6 @@ }, "Service": { "Name": "DysonNetwork.Develop", - "Url": "https://localhost:7192", - "ClientCert": "../Certificates/client.crt", - "ClientKey": "../Certificates/client.key" + "Url": "https://localhost:7192" } } diff --git a/DysonNetwork.Drive/DysonNetwork.Drive.csproj b/DysonNetwork.Drive/DysonNetwork.Drive.csproj index 6e54599..d77a199 100644 --- a/DysonNetwork.Drive/DysonNetwork.Drive.csproj +++ b/DysonNetwork.Drive/DysonNetwork.Drive.csproj @@ -66,6 +66,7 @@ + diff --git a/DysonNetwork.Drive/Program.cs b/DysonNetwork.Drive/Program.cs index eef5c20..91fe0c7 100644 --- a/DysonNetwork.Drive/Program.cs +++ b/DysonNetwork.Drive/Program.cs @@ -5,18 +5,18 @@ using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Http; using DysonNetwork.Shared.PageData; using DysonNetwork.Shared.Registry; -using DysonNetwork.Shared.Stream; using Microsoft.EntityFrameworkCore; using tusdotnet.Stores; var builder = WebApplication.CreateBuilder(args); +builder.AddServiceDefaults(); + // Configure Kestrel and server options builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxValue); // Add application services -builder.Services.AddRegistryService(builder.Configuration); -builder.Services.AddStreamConnection(builder.Configuration); + builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); @@ -39,6 +39,8 @@ builder.Services.AddTransient(); var app = builder.Build(); +app.MapDefaultEndpoints(); + // Run database migrations using (var scope = app.Services.CreateScope()) { @@ -51,8 +53,6 @@ var tusDiskStore = app.Services.GetRequiredService(); // Configure application middleware pipeline app.ConfigureAppMiddleware(tusDiskStore, builder.Environment.ContentRootPath); -app.MapGatewayProxy(); - app.MapPages(Path.Combine(app.Environment.WebRootPath, "dist", "index.html")); // Configure gRPC diff --git a/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs index 05f220e..66e7369 100644 --- a/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs @@ -17,11 +17,6 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration) { services.AddDbContext(); // Assuming you'll have an AppDatabase - services.AddSingleton(_ => - { - var connection = configuration.GetConnectionString("FastRetrieve")!; - return ConnectionMultiplexer.Connect(connection); - }); services.AddSingleton(SystemClock.Instance); services.AddHttpContextAccessor(); services.AddSingleton(); // Uncomment if you have CacheServiceRedis diff --git a/DysonNetwork.Drive/appsettings.json b/DysonNetwork.Drive/appsettings.json index 872ee07..f025412 100644 --- a/DysonNetwork.Drive/appsettings.json +++ b/DysonNetwork.Drive/appsettings.json @@ -10,10 +10,7 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "App": "Host=localhost;Port=5432;Database=dyson_drive;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", - "FastRetrieve": "localhost:6379", - "Etcd": "etcd.orb.local:2379", - "Stream": "nats.orb.local:4222" + "App": "Host=localhost;Port=5432;Database=dyson_drive;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" }, "Authentication": { "Schemes": { @@ -131,8 +128,6 @@ ], "Service": { "Name": "DysonNetwork.Drive", - "Url": "https://localhost:7092", - "ClientCert": "../Certificates/client.crt", - "ClientKey": "../Certificates/client.key" + "Url": "https://localhost:7092" } } diff --git a/DysonNetwork.Gateway/Controllers/WellKnownController.cs b/DysonNetwork.Gateway/Controllers/WellKnownController.cs deleted file mode 100644 index 9fddcf3..0000000 --- a/DysonNetwork.Gateway/Controllers/WellKnownController.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System.Text; -using System.Text.Json.Serialization; -using dotnet_etcd.interfaces; -using Microsoft.AspNetCore.Mvc; -using Yarp.ReverseProxy.Configuration; - -namespace DysonNetwork.Gateway.Controllers; - -[ApiController] -[Route("/.well-known")] -public class WellKnownController( - IConfiguration configuration, - IProxyConfigProvider proxyConfigProvider, - IEtcdClient etcdClient) - : ControllerBase -{ - public class IpCheckResponse - { - [JsonPropertyName("remote_ip")] public string? RemoteIp { get; set; } - [JsonPropertyName("x_forwarded_for")] public string? XForwardedFor { get; set; } - [JsonPropertyName("x_forwarded_proto")] public string? XForwardedProto { get; set; } - [JsonPropertyName("x_forwarded_host")] public string? XForwardedHost { get; set; } - [JsonPropertyName("x_real_ip")] public string? XRealIp { get; set; } - } - - [HttpGet("ip-check")] - public ActionResult GetIpCheck() - { - var ip = HttpContext.Connection.RemoteIpAddress?.ToString(); - - var xForwardedFor = Request.Headers["X-Forwarded-For"].FirstOrDefault(); - var xForwardedProto = Request.Headers["X-Forwarded-Proto"].FirstOrDefault(); - var xForwardedHost = Request.Headers["X-Forwarded-Host"].FirstOrDefault(); - var realIp = Request.Headers["X-Real-IP"].FirstOrDefault(); - - return Ok(new IpCheckResponse - { - RemoteIp = ip, - XForwardedFor = xForwardedFor, - XForwardedProto = xForwardedProto, - XForwardedHost = xForwardedHost, - XRealIp = realIp - }); - } - - [HttpGet("domains")] - public IActionResult GetDomainMappings() - { - var domainMappings = configuration.GetSection("DomainMappings").GetChildren() - .ToDictionary(x => x.Key, x => x.Value); - return Ok(domainMappings); - } - - [HttpGet("services")] - public IActionResult GetServices() - { - var local = configuration.GetValue("LocalMode"); - var response = etcdClient.GetRange("/services/"); - var kvs = response.Kvs; - - var serviceMap = kvs.ToDictionary( - kv => Encoding.UTF8.GetString(kv.Key.ToByteArray()).Replace("/services/", ""), - kv => Encoding.UTF8.GetString(kv.Value.ToByteArray()) - ); - - if (local) return Ok(serviceMap); - - var domainMappings = configuration.GetSection("DomainMappings").GetChildren() - .ToDictionary(x => x.Key, x => x.Value); - foreach (var (key, _) in serviceMap.ToList()) - { - if (!domainMappings.TryGetValue(key, out var domain)) continue; - if (domain is not null) - serviceMap[key] = "https://" + domain; - } - - return Ok(serviceMap); - } - - [HttpGet("routes")] - public IActionResult GetProxyRules() - { - var config = proxyConfigProvider.GetConfig(); - var rules = config.Routes.Select(r => new - { - r.RouteId, - r.ClusterId, - Match = new - { - r.Match.Path, - Hosts = r.Match.Hosts != null ? string.Join(", ", r.Match.Hosts) : null - }, - Transforms = r.Transforms?.Select(t => t.Select(kv => $"{kv.Key}: {kv.Value}").ToList()) - }).ToList(); - - var clusters = config.Clusters.Select(c => new - { - c.ClusterId, - Destinations = c.Destinations?.Select(d => new - { - d.Key, - d.Value.Address - }).ToList() - }).ToList(); - - return Ok(new { Rules = rules, Clusters = clusters }); - } -} \ No newline at end of file diff --git a/DysonNetwork.Gateway/Dockerfile b/DysonNetwork.Gateway/Dockerfile deleted file mode 100644 index b9273da..0000000 --- a/DysonNetwork.Gateway/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base -USER $APP_UID -WORKDIR /app -EXPOSE 8080 -EXPOSE 8081 - -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build -ARG BUILD_CONFIGURATION=Release -WORKDIR /src -COPY ["DysonNetwork.Gateway/DysonNetwork.Gateway.csproj", "DysonNetwork.Gateway/"] -RUN dotnet restore "DysonNetwork.Gateway/DysonNetwork.Gateway.csproj" -COPY . . -WORKDIR "/src/DysonNetwork.Gateway" -RUN dotnet build "./DysonNetwork.Gateway.csproj" -c $BUILD_CONFIGURATION -o /app/build - -FROM build AS publish -ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "./DysonNetwork.Gateway.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "DysonNetwork.Gateway.dll"] \ No newline at end of file diff --git a/DysonNetwork.Gateway/DysonNetwork.Gateway.csproj b/DysonNetwork.Gateway/DysonNetwork.Gateway.csproj deleted file mode 100644 index 7403f3c..0000000 --- a/DysonNetwork.Gateway/DysonNetwork.Gateway.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - net9.0 - enable - enable - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - diff --git a/DysonNetwork.Gateway/Program.cs b/DysonNetwork.Gateway/Program.cs deleted file mode 100644 index 7c981ea..0000000 --- a/DysonNetwork.Gateway/Program.cs +++ /dev/null @@ -1,36 +0,0 @@ -using DysonNetwork.Gateway.Startup; -using DysonNetwork.Shared.Http; -using Microsoft.AspNetCore.HttpOverrides; - -var builder = WebApplication.CreateBuilder(args); - -builder.Host.UseContentRoot(Directory.GetCurrentDirectory()); -builder.WebHost.ConfigureKestrel(options => -{ - options.Limits.MaxRequestBodySize = long.MaxValue; - options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2); - options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30); -}); - -// Add services to the container. -builder.Services.AddGateway(builder.Configuration); -builder.Services.AddControllers(); - -var app = builder.Build(); - -app.ConfigureForwardedHeaders(app.Configuration); - -app.UseRequestTimeouts(); -app.UseCors(opts => - opts.SetIsOriginAllowed(_ => true) - .WithExposedHeaders("*") - .WithHeaders("*") - .AllowCredentials() - .AllowAnyHeader() - .AllowAnyMethod() -); - -app.MapControllers(); -app.MapReverseProxy(); - -app.Run(); diff --git a/DysonNetwork.Gateway/Properties/launchSettings.json b/DysonNetwork.Gateway/Properties/launchSettings.json deleted file mode 100644 index 4551407..0000000 --- a/DysonNetwork.Gateway/Properties/launchSettings.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/launchsettings.json", - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": false, - "applicationUrl": "http://localhost:5094", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": false, - "applicationUrl": "https://localhost:7034;http://0.0.0.0:5094", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs b/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs deleted file mode 100644 index 4c4bc91..0000000 --- a/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs +++ /dev/null @@ -1,239 +0,0 @@ -using System.Text; -using dotnet_etcd.interfaces; -using Yarp.ReverseProxy.Configuration; -using Yarp.ReverseProxy.Forwarder; - -namespace DysonNetwork.Gateway; - -public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable -{ - private readonly object _lock = new(); - private readonly IEtcdClient _etcdClient; - private readonly IConfiguration _configuration; - private readonly ILogger _logger; - private readonly CancellationTokenSource _watchCts = new(); - private CancellationTokenSource _cts; - private IProxyConfig _config; - - public RegistryProxyConfigProvider( - IEtcdClient etcdClient, - IConfiguration configuration, - ILogger logger - ) - { - _etcdClient = etcdClient; - _configuration = configuration; - _logger = logger; - _cts = new CancellationTokenSource(); - _config = LoadConfig(); - - // Watch for changes in etcd - _etcdClient.WatchRange("/services/", _ => - { - _logger.LogInformation("Etcd configuration changed. Reloading proxy config."); - ReloadConfig(); - }, cancellationToken: _watchCts.Token); - } - - public IProxyConfig GetConfig() => _config; - - private void ReloadConfig() - { - lock (_lock) - { - var oldCts = _cts; - _cts = new CancellationTokenSource(); - _config = LoadConfig(); - oldCts.Cancel(); - oldCts.Dispose(); - } - } - - private IProxyConfig LoadConfig() - { - _logger.LogInformation("Generating new proxy config."); - var response = _etcdClient.GetRange("/services/"); - var kvs = response.Kvs; - - var serviceMap = kvs.ToDictionary( - kv => Encoding.UTF8.GetString(kv.Key.ToByteArray()).Replace("/services/", ""), - kv => Encoding.UTF8.GetString(kv.Value.ToByteArray()) - ); - - var clusters = new List(); - var routes = new List(); - - var domainMappings = _configuration.GetSection("DomainMappings").GetChildren() - .ToDictionary(x => x.Key, x => x.Value); - - var pathAliases = _configuration.GetSection("PathAliases").GetChildren() - .ToDictionary(x => x.Key, x => x.Value); - - var directRoutes = _configuration.GetSection("DirectRoutes").Get>() ?? - []; - - _logger.LogInformation("Indexing {ServiceCount} services from Etcd.", kvs.Count); - - var gatewayServiceName = _configuration["Service:Name"]; - - // Add direct routes - foreach (var directRoute in directRoutes) - { - if (serviceMap.TryGetValue(directRoute.Service, out var serviceUrl)) - { - var existingCluster = clusters.FirstOrDefault(c => c.ClusterId == directRoute.Service); - if (existingCluster is null) - { - var cluster = new ClusterConfig - { - ClusterId = directRoute.Service, - Destinations = new Dictionary - { - { "destination1", new DestinationConfig { Address = serviceUrl } } - }, - }; - clusters.Add(cluster); - } - - var route = new RouteConfig - { - RouteId = $"direct-{directRoute.Service}-{directRoute.Path.Replace("/", "-")}", - ClusterId = directRoute.Service, - Match = new RouteMatch { Path = directRoute.Path }, - }; - routes.Add(route); - _logger.LogInformation(" Added Direct Route: {Path} -> {Service}", directRoute.Path, - directRoute.Service); - } - else - { - _logger.LogWarning(" Direct route service {Service} not found in Etcd.", directRoute.Service); - } - } - - foreach (var serviceName in serviceMap.Keys) - { - if (serviceName == gatewayServiceName) - { - _logger.LogInformation("Skipping gateway service: {ServiceName}", serviceName); - continue; - } - - var serviceUrl = serviceMap[serviceName]; - - // Determine the path alias - string? pathAlias; - pathAlias = pathAliases.TryGetValue(serviceName, out var alias) - ? alias - : serviceName.Split('.').Last().ToLowerInvariant(); - - _logger.LogInformation(" Service: {ServiceName}, URL: {ServiceUrl}, Path Alias: {PathAlias}", serviceName, - serviceUrl, pathAlias); - - // Check if the cluster already exists - var existingCluster = clusters.FirstOrDefault(c => c.ClusterId == serviceName); - if (existingCluster == null) - { - var cluster = new ClusterConfig - { - ClusterId = serviceName, - Destinations = new Dictionary - { - { "destination1", new DestinationConfig { Address = serviceUrl } } - } - }; - clusters.Add(cluster); - _logger.LogInformation(" Added Cluster: {ServiceName}", serviceName); - } - else if (existingCluster.Destinations is not null) - { - // Create a new cluster with merged destinations - var newDestinations = new Dictionary(existingCluster.Destinations) - { - { - $"destination{existingCluster.Destinations.Count + 1}", - new DestinationConfig { Address = serviceUrl } - } - }; - - var mergedCluster = new ClusterConfig - { - ClusterId = serviceName, - Destinations = newDestinations - }; - - // Replace the existing cluster with the merged one - var index = clusters.IndexOf(existingCluster); - clusters[index] = mergedCluster; - - _logger.LogInformation(" Updated Cluster {ServiceName} with {DestinationCount} destinations", - serviceName, mergedCluster.Destinations.Count); - } - - // Host-based routing - if (domainMappings.TryGetValue(serviceName, out var domain) && domain is not null) - { - var hostRoute = new RouteConfig - { - RouteId = $"{serviceName}-host", - ClusterId = serviceName, - Match = new RouteMatch - { - Hosts = [domain], - Path = "/{**catch-all}" - }, - }; - routes.Add(hostRoute); - _logger.LogInformation(" Added Host-based Route: {Host}", domain); - } - - // Path-based routing - var pathRoute = new RouteConfig - { - RouteId = $"{serviceName}-path", - ClusterId = serviceName, - Match = new RouteMatch { Path = $"/{pathAlias}/{{**catch-all}}" }, - Transforms = new List> - { - new() { { "PathRemovePrefix", $"/{pathAlias}" } }, - new() { { "PathPrefix", "/api" } }, - }, - Timeout = TimeSpan.FromSeconds(5) - }; - routes.Add(pathRoute); - _logger.LogInformation(" Added Path-based Route: {Path}", pathRoute.Match.Path); - } - - return new CustomProxyConfig( - routes, - clusters, - new Microsoft.Extensions.Primitives.CancellationChangeToken(_cts.Token) - ); - } - - private class CustomProxyConfig( - IReadOnlyList routes, - IReadOnlyList clusters, - Microsoft.Extensions.Primitives.IChangeToken changeToken - ) - : IProxyConfig - { - public IReadOnlyList Routes { get; } = routes; - public IReadOnlyList Clusters { get; } = clusters; - public Microsoft.Extensions.Primitives.IChangeToken ChangeToken { get; } = changeToken; - } - - public record DirectRouteConfig - { - public required string Path { get; set; } - public required string Service { get; set; } - } - - public virtual void Dispose() - { - _cts.Cancel(); - _cts.Dispose(); - _watchCts.Cancel(); - _watchCts.Dispose(); - } -} \ No newline at end of file diff --git a/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs deleted file mode 100644 index 147983f..0000000 --- a/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Net.Security; -using System.Security.Cryptography.X509Certificates; -using DysonNetwork.Shared.Registry; -using Yarp.ReverseProxy.Configuration; -using Yarp.ReverseProxy.Transforms; - -namespace DysonNetwork.Gateway.Startup; - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddGateway(this IServiceCollection services, IConfiguration configuration) - { - services.AddRequestTimeouts(); - - services - .AddReverseProxy() - .ConfigureHttpClient((context, handler) => - { - // var caCert = X509CertificateLoader.LoadCertificateFromFile(configuration["CaCert"]!); - handler.SslOptions = new SslClientAuthenticationOptions - { - RemoteCertificateValidationCallback = (sender, cert, chain, errors) => true - }; - }) - .AddTransforms(context => - { - context.CopyRequestHeaders = true; - context.AddOriginalHost(); - context.AddForwarded(action: ForwardedTransformActions.Set); - context.AddXForwarded(action: ForwardedTransformActions.Set); - }); - - services.AddRegistryService(configuration, addForwarder: false); - services.AddSingleton(); - - return services; - } -} diff --git a/DysonNetwork.Gateway/VersionController.cs b/DysonNetwork.Gateway/VersionController.cs deleted file mode 100644 index 6040eae..0000000 --- a/DysonNetwork.Gateway/VersionController.cs +++ /dev/null @@ -1,20 +0,0 @@ -using DysonNetwork.Shared.Data; -using Microsoft.AspNetCore.Mvc; - -namespace DysonNetwork.Gateway; - -[ApiController] -[Route("/api/version")] -public class VersionController : ControllerBase -{ - [HttpGet] - public IActionResult Get() - { - return Ok(new AppVersion - { - Version = ThisAssembly.AssemblyVersion, - Commit = ThisAssembly.GitCommitId, - UpdateDate = ThisAssembly.GitCommitDate - }); - } -} diff --git a/DysonNetwork.Gateway/appsettings.json b/DysonNetwork.Gateway/appsettings.json deleted file mode 100644 index 3357c9a..0000000 --- a/DysonNetwork.Gateway/appsettings.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "LocalMode": true, - "CaCert": "../Certificates/ca.crt", - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "ConnectionStrings": { - "Etcd": "etcd.orb.local:2379" - }, - "Etcd": { - "Insecure": true - }, - "Service": { - "Name": "DysonNetwork.Gateway", - "Url": "https://localhost:7034" - }, - "DomainMappings": { - "DysonNetwork.Pass": "id.solsynth.dev", - "DysonNetwork.Drive": "drive.solsynth.dev", - "DysonNetwork.Ring": "push.solsynth.dev", - "DysonNetwork.Sphere": "sphere.solsynth.dev" - }, - "PathAliases": { - "DysonNetwork.Pass": "id", - "DysonNetwork.Drive": "drive" - }, - "DirectRoutes": [ - { - "Path": "/ws", - "Service": "DysonNetwork.Ring" - }, - { - "Path": "/api/tus", - "Service": "DysonNetwork.Drive" - }, - { - "Path": "/.well-known/openid-configuration", - "Service": "DysonNetwork.Pass" - }, - { - "Path": "/.well-known/jwks", - "Service": "DysonNetwork.Pass" - } - ] -} diff --git a/DysonNetwork.Gateway/version.json b/DysonNetwork.Gateway/version.json deleted file mode 100644 index 9fbf8d3..0000000 --- a/DysonNetwork.Gateway/version.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": "1.0", - "publicReleaseRefSpec": ["^refs/heads/main$"], - "cloudBuild": { - "setVersionVariables": true - } -} diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.csproj b/DysonNetwork.Pass/DysonNetwork.Pass.csproj index c5e9687..88f6e6b 100644 --- a/DysonNetwork.Pass/DysonNetwork.Pass.csproj +++ b/DysonNetwork.Pass/DysonNetwork.Pass.csproj @@ -49,6 +49,7 @@ + diff --git a/DysonNetwork.Pass/Email/EmailService.cs b/DysonNetwork.Pass/Email/EmailService.cs index 77b68e0..71714c3 100644 --- a/DysonNetwork.Pass/Email/EmailService.cs +++ b/DysonNetwork.Pass/Email/EmailService.cs @@ -1,5 +1,3 @@ -using dotnet_etcd; -using dotnet_etcd.interfaces; using DysonNetwork.Shared.Proto; using Microsoft.AspNetCore.Components; diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index 6a344fa..fc96c59 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -4,20 +4,16 @@ using DysonNetwork.Pass.Startup; using DysonNetwork.Shared.Http; using DysonNetwork.Shared.PageData; using DysonNetwork.Shared.Registry; -using DysonNetwork.Shared.Stream; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); +builder.AddServiceDefaults(); + // Configure Kestrel and server options builder.ConfigureAppKestrel(builder.Configuration); -// Add metrics and telemetry -builder.Services.AddAppMetrics(); - // Add application services -builder.Services.AddRegistryService(builder.Configuration); -builder.Services.AddStreamConnection(builder.Configuration); builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); @@ -41,6 +37,8 @@ builder.Services.AddTransient(); var app = builder.Build(); +app.MapDefaultEndpoints(); + // Run database migrations using (var scope = app.Services.CreateScope()) { @@ -51,8 +49,6 @@ using (var scope = app.Services.CreateScope()) // Configure application middleware pipeline app.ConfigureAppMiddleware(builder.Configuration, builder.Environment.ContentRootPath); -app.MapGatewayProxy(); - app.MapPages(Path.Combine(builder.Environment.WebRootPath, "dist", "index.html")); // Configure gRPC diff --git a/DysonNetwork.Pass/Startup/MetricsConfiguration.cs b/DysonNetwork.Pass/Startup/MetricsConfiguration.cs deleted file mode 100644 index 78e33cd..0000000 --- a/DysonNetwork.Pass/Startup/MetricsConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using OpenTelemetry.Metrics; -using OpenTelemetry.Trace; -using Prometheus; -using Prometheus.SystemMetrics; - -namespace DysonNetwork.Pass.Startup; - -public static class MetricsConfiguration -{ - public static IServiceCollection AddAppMetrics(this IServiceCollection services) - { - // Prometheus - services.UseHttpClientMetrics(); - services.AddHealthChecks(); - services.AddSystemMetrics(); - services.AddPrometheusEntityFrameworkMetrics(); - services.AddPrometheusAspNetCoreMetrics(); - services.AddPrometheusHttpClientMetrics(); - - // OpenTelemetry - services.AddOpenTelemetry() - .WithTracing(tracing => - { - tracing - .AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddOtlpExporter(); - }) - .WithMetrics(metrics => - { - metrics - .AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddRuntimeInstrumentation() - .AddOtlpExporter(); - }); - - return services; - } -} diff --git a/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs index 858b568..cda0085 100644 --- a/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs @@ -34,11 +34,6 @@ public static class ServiceCollectionExtensions services.AddLocalization(options => options.ResourcesPath = "Resources"); services.AddDbContext(); - services.AddSingleton(_ => - { - var connection = configuration.GetConnectionString("FastRetrieve")!; - return ConnectionMultiplexer.Connect(connection); - }); services.AddSingleton(SystemClock.Instance); services.AddHttpContextAccessor(); services.AddSingleton(); diff --git a/DysonNetwork.Pass/appsettings.json b/DysonNetwork.Pass/appsettings.json index 8b9275e..066d1a3 100644 --- a/DysonNetwork.Pass/appsettings.json +++ b/DysonNetwork.Pass/appsettings.json @@ -10,10 +10,7 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "App": "Host=localhost;Port=5432;Database=dyson_pass;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", - "FastRetrieve": "localhost:6379", - "Etcd": "etcd.orb.local:2379", - "Stream": "nats.orb.local:4222" + "App": "Host=localhost;Port=5432;Database=dyson_pass;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" }, "Authentication": { "Schemes": { @@ -83,9 +80,7 @@ ], "Service": { "Name": "DysonNetwork.Pass", - "Url": "https://localhost:7058", - "ClientCert": "../Certificates/client.crt", - "ClientKey": "../Certificates/client.key" + "Url": "https://localhost:7058" }, "Etcd": { "Insecure": true diff --git a/DysonNetwork.Ring/Connection/WebSocketService.cs b/DysonNetwork.Ring/Connection/WebSocketService.cs index 7454798..8c0c78d 100644 --- a/DysonNetwork.Ring/Connection/WebSocketService.cs +++ b/DysonNetwork.Ring/Connection/WebSocketService.cs @@ -1,6 +1,5 @@ using System.Collections.Concurrent; using System.Net.WebSockets; -using dotnet_etcd.interfaces; using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Proto; using Grpc.Core; @@ -11,17 +10,14 @@ public class WebSocketService { private readonly IConfiguration _configuration; private readonly ILogger _logger; - private readonly IEtcdClient _etcdClient; private readonly IDictionary _handlerMap; public WebSocketService( IEnumerable handlers, - IEtcdClient etcdClient, ILogger logger, IConfiguration configuration ) { - _etcdClient = etcdClient; _logger = logger; _configuration = configuration; _handlerMap = handlers.ToDictionary(h => h.PacketType); @@ -59,8 +55,10 @@ public class WebSocketService } catch (Exception ex) { - _logger.LogWarning(ex, "Error while closing WebSocket for {AccountId}:{DeviceId}", key.AccountId, key.DeviceId); + _logger.LogWarning(ex, "Error while closing WebSocket for {AccountId}:{DeviceId}", key.AccountId, + key.DeviceId); } + data.Cts.Cancel(); ActiveConnections.TryRemove(key, out _); } @@ -140,45 +138,24 @@ public class WebSocketService { try { - // Get the service URL from etcd for the specified endpoint - var serviceKey = $"/services/{packet.Endpoint}"; - var response = await _etcdClient.GetAsync(serviceKey); + var serviceUrl = "https://" + packet.Endpoint; - if (response.Kvs.Count > 0) + var callInvoker = GrpcClientHelper.CreateCallInvoker(serviceUrl); + var client = new RingHandlerService.RingHandlerServiceClient(callInvoker); + + try { - var serviceUrl = response.Kvs[0].Value.ToStringUtf8(); - - var clientCertPath = _configuration["Service:ClientCert"]!; - var clientKeyPath = _configuration["Service:ClientKey"]!; - var clientCertPassword = _configuration["Service:CertPassword"]; - - var callInvoker = - GrpcClientHelper.CreateCallInvoker( - serviceUrl, - clientCertPath, - clientKeyPath, - clientCertPassword - ); - var client = new RingHandlerService.RingHandlerServiceClient(callInvoker); - - try + await client.ReceiveWebSocketPacketAsync(new ReceiveWebSocketPacketRequest { - await client.ReceiveWebSocketPacketAsync(new ReceiveWebSocketPacketRequest - { - Account = currentUser, - DeviceId = deviceId, - Packet = packet.ToProtoValue() - }); - } - catch (RpcException ex) - { - _logger.LogError(ex, $"Error forwarding packet to endpoint: {packet.Endpoint}"); - } - - return; + Account = currentUser, + DeviceId = deviceId, + Packet = packet.ToProtoValue() + }); + } + catch (RpcException ex) + { + _logger.LogError(ex, $"Error forwarding packet to endpoint: {packet.Endpoint}"); } - - _logger.LogWarning($"No service registered for endpoint: {packet.Endpoint}"); } catch (Exception ex) { @@ -197,4 +174,4 @@ public class WebSocketService CancellationToken.None ); } -} +} \ No newline at end of file diff --git a/DysonNetwork.Ring/DysonNetwork.Ring.csproj b/DysonNetwork.Ring/DysonNetwork.Ring.csproj index ae8a231..dc52c8e 100644 --- a/DysonNetwork.Ring/DysonNetwork.Ring.csproj +++ b/DysonNetwork.Ring/DysonNetwork.Ring.csproj @@ -42,6 +42,7 @@ + diff --git a/DysonNetwork.Ring/Program.cs b/DysonNetwork.Ring/Program.cs index 2425a43..eda4ae7 100644 --- a/DysonNetwork.Ring/Program.cs +++ b/DysonNetwork.Ring/Program.cs @@ -3,17 +3,16 @@ using DysonNetwork.Ring.Startup; using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Http; using DysonNetwork.Shared.Registry; -using DysonNetwork.Shared.Stream; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); +builder.AddServiceDefaults(); + // Configure Kestrel and server options builder.ConfigureAppKestrel(builder.Configuration); // Add application services -builder.Services.AddRegistryService(builder.Configuration); -builder.Services.AddStreamConnection(builder.Configuration); builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); @@ -32,6 +31,8 @@ builder.Services.AddAppScheduledJobs(); var app = builder.Build(); +app.MapDefaultEndpoints(); + // Run database migrations using (var scope = app.Services.CreateScope()) { diff --git a/DysonNetwork.Ring/Services/PusherServiceGrpc.cs b/DysonNetwork.Ring/Services/PusherServiceGrpc.cs index bb21a56..80a3e08 100644 --- a/DysonNetwork.Ring/Services/PusherServiceGrpc.cs +++ b/DysonNetwork.Ring/Services/PusherServiceGrpc.cs @@ -2,7 +2,6 @@ using DysonNetwork.Ring.Connection; using DysonNetwork.Ring.Email; using DysonNetwork.Ring.Notification; using DysonNetwork.Shared.Proto; -using DysonNetwork.Shared.Registry; using Google.Protobuf.WellKnownTypes; using Grpc.Core; using System.Text.Json; diff --git a/DysonNetwork.Ring/Services/QueueBackgroundService.cs b/DysonNetwork.Ring/Services/QueueBackgroundService.cs index bb3b357..a2f0f1b 100644 --- a/DysonNetwork.Ring/Services/QueueBackgroundService.cs +++ b/DysonNetwork.Ring/Services/QueueBackgroundService.cs @@ -2,7 +2,6 @@ using System.Text.Json; using DysonNetwork.Ring.Email; using DysonNetwork.Ring.Notification; using DysonNetwork.Shared.Proto; -using DysonNetwork.Shared.Registry; using DysonNetwork.Shared.Stream; using Google.Protobuf; using NATS.Client.Core; diff --git a/DysonNetwork.Ring/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Ring/Startup/ServiceCollectionExtensions.cs index 63677b4..0480fcd 100644 --- a/DysonNetwork.Ring/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Ring/Startup/ServiceCollectionExtensions.cs @@ -21,11 +21,6 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration) { services.AddDbContext(); - services.AddSingleton(_ => - { - var connection = configuration.GetConnectionString("FastRetrieve")!; - return ConnectionMultiplexer.Connect(connection); - }); services.AddSingleton(SystemClock.Instance); services.AddHttpContextAccessor(); services.AddSingleton(); diff --git a/DysonNetwork.Ring/appsettings.json b/DysonNetwork.Ring/appsettings.json index 228bc9f..2ab4e9e 100644 --- a/DysonNetwork.Ring/appsettings.json +++ b/DysonNetwork.Ring/appsettings.json @@ -9,10 +9,7 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "App": "Host=localhost;Port=5432;Database=dyson_pusher;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", - "FastRetrieve": "localhost:6379", - "Etcd": "etcd.orb.local:2379", - "Stream": "nats.orb.local:4222" + "App": "Host=localhost;Port=5432;Database=dyson_pusher;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" }, "Notifications": { "Push": { @@ -45,9 +42,7 @@ ], "Service": { "Name": "DysonNetwork.Ring", - "Url": "https://localhost:7259", - "ClientCert": "../Certificates/client.crt", - "ClientKey": "../Certificates/client.key" + "Url": "https://localhost:7259" }, "Etcd": { "Insecure": true diff --git a/DysonNetwork.ServiceDefaults/DysonNetwork.ServiceDefaults.csproj b/DysonNetwork.ServiceDefaults/DysonNetwork.ServiceDefaults.csproj new file mode 100644 index 0000000..9f8259b --- /dev/null +++ b/DysonNetwork.ServiceDefaults/DysonNetwork.ServiceDefaults.csproj @@ -0,0 +1,26 @@ + + + + net9.0 + enable + enable + true + + + + + + + + + + + + + + + + + + + diff --git a/DysonNetwork.ServiceDefaults/Extensions.cs b/DysonNetwork.ServiceDefaults/Extensions.cs new file mode 100644 index 0000000..4b8dc80 --- /dev/null +++ b/DysonNetwork.ServiceDefaults/Extensions.cs @@ -0,0 +1,137 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + private const string HealthEndpointPath = "/health"; + private const string AlivenessEndpointPath = "/alive"; + + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + // Allow unencrypted grpc + AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); + + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); + + builder.AddNatsClient("queue"); + builder.AddRedisClient("cache", configureOptions: opts => + { + opts.AbortOnConnectFail = false; + }); + + return builder; + } + + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) + where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddSource(builder.Environment.ApplicationName) + .AddAspNetCoreInstrumentation(tracing => + // Exclude health check requests from tracing + tracing.Filter = context => + !context.Request.Path.StartsWithSegments(HealthEndpointPath) + && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath) + ) + .AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) + where TBuilder : IHostApplicationBuilder + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) + where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks(HealthEndpointPath); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Auth/Startup.cs b/DysonNetwork.Shared/Auth/Startup.cs index 3cd02ed..e0caf8c 100644 --- a/DysonNetwork.Shared/Auth/Startup.cs +++ b/DysonNetwork.Shared/Auth/Startup.cs @@ -1,5 +1,5 @@ -using dotnet_etcd.interfaces; using DysonNetwork.Shared.Proto; +using DysonNetwork.Shared.Registry; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -11,33 +11,7 @@ public static class DysonAuthStartup this IServiceCollection services ) { - services.AddSingleton(sp => - { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; - - return GrpcClientHelper - .CreateAuthServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); - }); - - services.AddSingleton(sp => - { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; - - return GrpcClientHelper - .CreatePermissionServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); - }); + services.AddAuthService(); services.AddAuthentication(options => { diff --git a/DysonNetwork.Shared/DysonNetwork.Shared.csproj b/DysonNetwork.Shared/DysonNetwork.Shared.csproj index 341d5bf..a79c88a 100644 --- a/DysonNetwork.Shared/DysonNetwork.Shared.csproj +++ b/DysonNetwork.Shared/DysonNetwork.Shared.csproj @@ -7,11 +7,11 @@ - + all @@ -27,7 +27,6 @@ - @@ -40,4 +39,8 @@ + + + + diff --git a/DysonNetwork.Shared/Http/KestrelConfiguration.cs b/DysonNetwork.Shared/Http/KestrelConfiguration.cs index bd6bf1b..e8534aa 100644 --- a/DysonNetwork.Shared/Http/KestrelConfiguration.cs +++ b/DysonNetwork.Shared/Http/KestrelConfiguration.cs @@ -12,7 +12,7 @@ public static class KestrelConfiguration { public static WebApplicationBuilder ConfigureAppKestrel( this WebApplicationBuilder builder, - IConfiguration configuration, + IConfiguration configuration, long maxRequestBodySize = 50 * 1024 * 1024 ) { @@ -20,22 +20,53 @@ public static class KestrelConfiguration builder.WebHost.ConfigureKestrel(options => { options.Limits.MaxRequestBodySize = maxRequestBodySize; - - var configuredUrl = Environment.GetEnvironmentVariable("ASPNETCORE_URLS"); - if (!string.IsNullOrEmpty(configuredUrl)) return; - var certPath = configuration["Service:ClientCert"]!; - var keyPath = configuration["Service:ClientKey"]!; + // gRPC + options.ListenAnyIP(5001, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http2; + + var selfSignedCert = _CreateSelfSignedCertificate(); + listenOptions.UseHttps(selfSignedCert); + }); - // Load PEM cert and key manually - var certificate = X509Certificate2.CreateFromPemFile(certPath, keyPath); - // Now pass the full cert - options.ListenAnyIP(5001, listenOptions => { listenOptions.UseHttps(certificate); }); + var httpPorts = configuration.GetValue("HTTP_PORTS", "5000") + .Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(p => int.Parse(p.Trim())) + .ToArray(); - // Optional: HTTP fallback - options.ListenAnyIP(8080); + // Regular HTTP + foreach (var httpPort in httpPorts) + options.ListenAnyIP(httpPort, + listenOptions => { listenOptions.Protocols = HttpProtocols.Http1AndHttp2; }); }); return builder; } + + static X509Certificate2 _CreateSelfSignedCertificate() + { + using var rsa = RSA.Create(2048); + var certRequest = new CertificateRequest( + "CN=dyson.network", // Common Name for the certificate + rsa, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + + // Add extensions (e.g., for server authentication) + certRequest.CertificateExtensions.Add( + new X509EnhancedKeyUsageExtension( + new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, // Server Authentication + false)); + + // Set validity period (e.g., 1 year) + var notBefore = DateTimeOffset.UtcNow.AddDays(-1); + var notAfter = notBefore.AddYears(1); + + var certificate = certRequest.CreateSelfSigned(notBefore, notAfter); + + // Export to PKCS#12 and load using X509CertificateLoader + var pfxBytes = certificate.Export(X509ContentType.Pfx); + return X509CertificateLoader.LoadPkcs12(pfxBytes, password: null); + } } \ No newline at end of file diff --git a/DysonNetwork.Shared/Proto/GrpcClientHelper.cs b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs index 65ca1d6..55ff54e 100644 --- a/DysonNetwork.Shared/Proto/GrpcClientHelper.cs +++ b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs @@ -2,171 +2,13 @@ using System.Net; using Grpc.Net.Client; using System.Security.Cryptography.X509Certificates; using Grpc.Core; -using dotnet_etcd.interfaces; namespace DysonNetwork.Shared.Proto; public static class GrpcClientHelper { - public static CallInvoker CreateCallInvoker( - string url, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) + public static CallInvoker CreateCallInvoker(string url) { - var handler = new HttpClientHandler(); - handler.ClientCertificates.Add( - clientCertPassword is null - ? X509Certificate2.CreateFromPemFile(clientCertPath, clientKeyPath) - : X509Certificate2.CreateFromEncryptedPemFile(clientCertPath, clientCertPassword, clientKeyPath) - ); - handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true; - var httpClient = new HttpClient(handler); - httpClient.DefaultRequestVersion = HttpVersion.Version20; - httpClient.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher; - return GrpcChannel.ForAddress(url, new GrpcChannelOptions { HttpClient = httpClient }).CreateCallInvoker(); - } - - private static async Task GetServiceUrlFromEtcd(IEtcdClient etcdClient, string serviceName) - { - var response = await etcdClient.GetAsync($"/services/{serviceName}"); - return response.Kvs.Count == 0 - ? throw new InvalidOperationException($"Service '{serviceName}' not found in Etcd.") - : response.Kvs[0].Value.ToStringUtf8(); - } - - public static async Task CreateAccountServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass"); - return new AccountService.AccountServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task - CreateBotAccountReceiverServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass"); - return new BotAccountReceiverService.BotAccountReceiverServiceClient(CreateCallInvoker(url, clientCertPath, - clientKeyPath, - clientCertPassword)); - } - - public static async Task CreateActionLogServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass"); - return new ActionLogService.ActionLogServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task CreateAuthServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass"); - return new AuthService.AuthServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task CreatePermissionServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass"); - return new PermissionService.PermissionServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task CreatePaymentServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass"); - return new PaymentService.PaymentServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task CreateRingServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Ring"); - return new RingService.RingServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task CreateFileServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Drive"); - return new FileService.FileServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task CreateFileReferenceServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Drive"); - return new FileReferenceService.FileReferenceServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task CreatePublisherServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Sphere"); - return new PublisherService.PublisherServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task CreateCustomAppServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Develop"); - return new CustomAppService.CustomAppServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); + return GrpcChannel.ForAddress(url).CreateCallInvoker(); } } \ No newline at end of file diff --git a/DysonNetwork.Shared/Proto/pusher.proto b/DysonNetwork.Shared/Proto/ring.proto similarity index 100% rename from DysonNetwork.Shared/Proto/pusher.proto rename to DysonNetwork.Shared/Proto/ring.proto diff --git a/DysonNetwork.Shared/Registry/GatewayReverseProxy.cs b/DysonNetwork.Shared/Registry/GatewayReverseProxy.cs deleted file mode 100644 index 5e7a1c0..0000000 --- a/DysonNetwork.Shared/Registry/GatewayReverseProxy.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Diagnostics; -using System.Net; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Yarp.ReverseProxy.Forwarder; - -namespace DysonNetwork.Shared.Registry; - -public static class GatewayReverseProxy -{ - /// - /// Provides reverse proxy for DysonNetwork.Gateway. - /// Give the ability to the contained frontend to access other services via the gateway. - /// - /// The asp.net core application - /// The modified application - public static WebApplication MapGatewayProxy(this WebApplication app) - { - var httpClient = new HttpMessageInvoker(new SocketsHttpHandler - { - UseProxy = false, - AllowAutoRedirect = true, - AutomaticDecompression = DecompressionMethods.All, - UseCookies = true, - EnableMultipleHttp2Connections = true, - ActivityHeadersPropagator = new ReverseProxyPropagator(DistributedContextPropagator.Current), - ConnectTimeout = TimeSpan.FromSeconds(15), - }); - - var transformer = new GatewayReverseProxyTransformer(); - var requestConfig = new ForwarderRequestConfig(); - - app.Map("/cgi/{**catch-all}", async (HttpContext context, IHttpForwarder forwarder) => - { - var registry = context.RequestServices.GetRequiredService(); - var gatewayUrl = await registry.GetServiceUrl("DysonNetwork.Gateway"); - if (gatewayUrl is null) - { - context.Response.StatusCode = 404; - await context.Response.WriteAsync("Gateway not found"); - return; - } - - var error = await forwarder.SendAsync( - context, - gatewayUrl, - httpClient, - requestConfig, - transformer - ); - if (error != ForwarderError.None) - { - var errorFeature = context.GetForwarderErrorFeature(); - var exception = errorFeature?.Exception; - context.Response.StatusCode = 502; - context.Response.ContentType = "text/plain"; - await context.Response.WriteAsync($"Gateway remote error: {exception?.Message}"); - } - }); - - return app; - } -} - -public class GatewayReverseProxyTransformer : HttpTransformer -{ - private const string Value = "/cgi"; - - public override ValueTask TransformRequestAsync( - HttpContext httpContext, - HttpRequestMessage proxyRequest, - string destinationPrefix, - CancellationToken cancellationToken - ) - { - httpContext.Request.Path = httpContext.Request.Path.StartsWithSegments(Value, out var remaining) - ? remaining - : httpContext.Request.Path; - - return Default.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, cancellationToken); - } -} \ No newline at end of file diff --git a/DysonNetwork.Shared/Registry/RegistryHostedService.cs b/DysonNetwork.Shared/Registry/RegistryHostedService.cs deleted file mode 100644 index a1d8383..0000000 --- a/DysonNetwork.Shared/Registry/RegistryHostedService.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace DysonNetwork.Shared.Registry; - -public class RegistryHostedService( - ServiceRegistry serviceRegistry, - IConfiguration configuration, - ILogger logger -) - : IHostedService -{ - private CancellationTokenSource? _cts; - - public async Task StartAsync(CancellationToken cancellationToken) - { - var serviceName = configuration["Service:Name"]; - var serviceUrl = configuration["Service:Url"]; - var insecure = configuration.GetValue("Etcd:Insecure"); - var remote = configuration.GetConnectionString("Etcd"); - - if (insecure) - logger.LogWarning("Etcd is configured to use insecure channel."); - - if (string.IsNullOrEmpty(serviceUrl) || string.IsNullOrEmpty(serviceName)) - { - logger.LogWarning("Service URL or Service Name was not configured. Skipping Etcd registration."); - return; - } - - logger.LogInformation( - "Registering service {ServiceName} at {ServiceUrl} with Etcd ({Remote}).", - serviceName, - serviceUrl, - remote - ); - try - { - _cts = new CancellationTokenSource(); - await serviceRegistry.RegisterService(serviceName, serviceUrl, cancellationToken: _cts.Token); - logger.LogInformation("Service {ServiceName} registered successfully.", serviceName); - } - catch (Exception ex) - { - logger.LogError(ex, "Failed to register service {ServiceName} with Etcd.", serviceName); - } - } - - public async Task StopAsync(CancellationToken cancellationToken) - { - _cts?.Cancel(); - - // The lease will expire automatically if the service stops ungracefully. - var serviceName = configuration["Service:Name"]; - if (serviceName is not null) - await serviceRegistry.UnregisterService(serviceName); - logger.LogInformation("Service registration hosted service is stopping."); - } -} \ No newline at end of file diff --git a/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs b/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs index bd1d997..d948adf 100644 --- a/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs +++ b/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs @@ -1,4 +1,3 @@ -using dotnet_etcd.interfaces; using DysonNetwork.Shared.Proto; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -9,153 +8,94 @@ public static class ServiceInjectionHelper { public static IServiceCollection AddRingService(this IServiceCollection services) { - services.AddSingleton(sp => - { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; + services + .AddGrpcClient(o => o.Address = new Uri("https://_grpc.ring")) + .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() + { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } + ); - return GrpcClientHelper - .CreateRingServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); - }); - return services; } - + + public static IServiceCollection AddAuthService(this IServiceCollection services) + { + services + .AddGrpcClient(o => o.Address = new Uri("https://_grpc.pass")) + .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() + { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } + ); + + services + .AddGrpcClient(o => o.Address = new Uri("https://_grpc.pass")) + .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() + { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } + ); + + return services; + } + public static IServiceCollection AddAccountService(this IServiceCollection services) { - services.AddSingleton(sp => - { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; - - return GrpcClientHelper - .CreateAccountServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); - }); + services + .AddGrpcClient(o => o.Address = new Uri("https://_grpc.pass") ) + .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() + { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } + ); services.AddSingleton(); - - services.AddSingleton(sp => - { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; - return GrpcClientHelper - .CreateBotAccountReceiverServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); - }); - - services.AddSingleton(sp => - { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; + services + .AddGrpcClient(o => + o.Address = new Uri("https://_grpc.pass") + ) + .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() + { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } + ); - return GrpcClientHelper - .CreateActionLogServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); - }); - - services.AddSingleton(sp => - { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; + services.AddGrpcClient(o => o.Address = new Uri("https://_grpc.pass")) + .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() + { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } + ); + + services.AddGrpcClient(o => o.Address = new Uri("https://_grpc.pass")) + .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() + { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } + ); - return GrpcClientHelper - .CreatePaymentServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); - }); - return services; } - + public static IServiceCollection AddDriveService(this IServiceCollection services) { - services.AddSingleton(sp => - { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; + services.AddGrpcClient(o => o.Address = new Uri("https://_grpc.drive")) + .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() + { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } + ); - return GrpcClientHelper - .CreateFileServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); - }); - - services.AddSingleton(sp => - { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; + services.AddGrpcClient(o => o.Address = new Uri("https://_grpc.drive")) + .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() + { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } + ); - return GrpcClientHelper - .CreateFileReferenceServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); - }); - return services; } - + public static IServiceCollection AddPublisherService(this IServiceCollection services) { - services.AddSingleton(sp => - { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; + services.AddGrpcClient(o => o.Address = new Uri("https://_grpc.sphere")) + .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() + { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } + ); - return GrpcClientHelper - .CreatePublisherServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); - }); - return services; } public static IServiceCollection AddDevelopService(this IServiceCollection services) { - services.AddSingleton(sp => - { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; + services.AddGrpcClient(o => o.Address = new Uri("https://_grpc.develop")) + .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() + { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } + ); - return GrpcClientHelper - .CreateCustomAppServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); - }); - return services; } - } \ No newline at end of file +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Registry/ServiceRegistry.cs b/DysonNetwork.Shared/Registry/ServiceRegistry.cs deleted file mode 100644 index 8c2e0e4..0000000 --- a/DysonNetwork.Shared/Registry/ServiceRegistry.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Text; -using dotnet_etcd.interfaces; -using Etcdserverpb; -using Google.Protobuf; -using Microsoft.Extensions.Logging; - -namespace DysonNetwork.Shared.Registry; - -public class ServiceRegistry(IEtcdClient etcd, ILogger logger) -{ - public async Task RegisterService( - string serviceName, - string serviceUrl, - long leaseTtlSeconds = 60, - CancellationToken cancellationToken = default - ) - { - var key = $"/services/{serviceName}"; - var leaseResponse = await etcd.LeaseGrantAsync( - new LeaseGrantRequest { TTL = leaseTtlSeconds }, - cancellationToken: cancellationToken - ); - await etcd.PutAsync(new PutRequest - { - Key = ByteString.CopyFrom(key, Encoding.UTF8), - Value = ByteString.CopyFrom(serviceUrl, Encoding.UTF8), - Lease = leaseResponse.ID - }, cancellationToken: cancellationToken); - - _ = Task.Run(async () => - { - try - { - await etcd.LeaseKeepAlive(leaseResponse.ID, cancellationToken); - } - catch (Exception ex) - { - logger.LogError($"Lease keep-alive failed: {ex.Message}"); - } - }, cancellationToken); - } - - public async Task UnregisterService(string serviceName) - { - var key = $"/services/{serviceName}"; - await etcd.DeleteAsync(key); - } - - public async Task GetServiceUrl(string serviceName) - { - var key = $"/services/{serviceName}"; - var response = await etcd.GetAsync(key); - return response.Kvs.Count == 0 ? null : response.Kvs[0].Value.ToStringUtf8(); - } -} \ No newline at end of file diff --git a/DysonNetwork.Shared/Registry/Startup.cs b/DysonNetwork.Shared/Registry/Startup.cs deleted file mode 100644 index e5cdeac..0000000 --- a/DysonNetwork.Shared/Registry/Startup.cs +++ /dev/null @@ -1,28 +0,0 @@ -using dotnet_etcd.DependencyInjection; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace DysonNetwork.Shared.Registry; - -public static class RegistryStartup -{ - public static IServiceCollection AddRegistryService( - this IServiceCollection services, - IConfiguration configuration, - bool addForwarder = true - ) - { - services.AddEtcdClient(options => - { - options.ConnectionString = configuration.GetConnectionString("Etcd"); - options.UseInsecureChannel = configuration.GetValue("Etcd:Insecure"); - }); - services.AddSingleton(); - services.AddHostedService(); - - if (addForwarder) - services.AddHttpForwarder(); - - return services; - } -} \ No newline at end of file diff --git a/DysonNetwork.Shared/Stream/Connector.cs b/DysonNetwork.Shared/Stream/Connector.cs deleted file mode 100644 index 66fed40..0000000 --- a/DysonNetwork.Shared/Stream/Connector.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using NATS.Client.Core; - -namespace DysonNetwork.Shared.Stream; - -public static class Connector -{ - public static IServiceCollection AddStreamConnection(this IServiceCollection services, IConfiguration configuration) - { - var connectionString = configuration.GetConnectionString("Stream"); - if (connectionString is null) - throw new ArgumentNullException(nameof(connectionString)); - services.AddSingleton(_ => new NatsConnection(new NatsOpts() - { - Url = connectionString - })); - - return services; - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Chat/ChatRoom.cs b/DysonNetwork.Sphere/Chat/ChatRoom.cs index 73eb46e..639493a 100644 --- a/DysonNetwork.Sphere/Chat/ChatRoom.cs +++ b/DysonNetwork.Sphere/Chat/ChatRoom.cs @@ -2,9 +2,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; using DysonNetwork.Shared.Data; -using DysonNetwork.Shared.Proto; using NodaTime; -using Account = DysonNetwork.Pass.Account.Account; namespace DysonNetwork.Sphere.Chat; @@ -75,7 +73,7 @@ public class ChatMember : ModelBase public Guid ChatRoomId { get; set; } public ChatRoom ChatRoom { get; set; } = null!; public Guid AccountId { get; set; } - [NotMapped] public Account? Account { get; set; } + [NotMapped] public AccountReference? Account { get; set; } [NotMapped] public AccountStatusReference? Status { get; set; } [MaxLength(1024)] public string? Nick { get; set; } @@ -108,7 +106,7 @@ public class ChatMemberTransmissionObject : ModelBase public Guid Id { get; set; } public Guid ChatRoomId { get; set; } public Guid AccountId { get; set; } - [NotMapped] public Account Account { get; set; } = null!; + [NotMapped] public AccountReference Account { get; set; } = null!; [MaxLength(1024)] public string? Nick { get; set; } diff --git a/DysonNetwork.Sphere/Chat/ChatRoomService.cs b/DysonNetwork.Sphere/Chat/ChatRoomService.cs index 0a4af47..a3a0ada 100644 --- a/DysonNetwork.Sphere/Chat/ChatRoomService.cs +++ b/DysonNetwork.Sphere/Chat/ChatRoomService.cs @@ -2,7 +2,7 @@ using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; using NodaTime; -using Account = DysonNetwork.Pass.Account.Account; +using Account = DysonNetwork.Shared.Data.AccountReference; namespace DysonNetwork.Sphere.Chat; diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj index cc01ed1..1b7b4f2 100644 --- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj @@ -55,7 +55,6 @@ - @@ -160,8 +159,8 @@ + - diff --git a/DysonNetwork.Sphere/Poll/Poll.cs b/DysonNetwork.Sphere/Poll/Poll.cs index e7778d6..fac9b6a 100644 --- a/DysonNetwork.Sphere/Poll/Poll.cs +++ b/DysonNetwork.Sphere/Poll/Poll.cs @@ -2,7 +2,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json; using System.Text.Json.Serialization; -using DysonNetwork.Pass.Account; using DysonNetwork.Shared.Data; using NodaTime; @@ -64,5 +63,5 @@ public class PollAnswer : ModelBase public Guid AccountId { get; set; } public Guid PollId { get; set; } [JsonIgnore] public Poll? Poll { get; set; } - [NotMapped] public Account? Account { get; set; } + [NotMapped] public AccountReference? Account { get; set; } } diff --git a/DysonNetwork.Sphere/Poll/PollController.cs b/DysonNetwork.Sphere/Poll/PollController.cs index d54ea7b..e56e2ce 100644 --- a/DysonNetwork.Sphere/Poll/PollController.cs +++ b/DysonNetwork.Sphere/Poll/PollController.cs @@ -1,8 +1,8 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json; +using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Registry; -using DysonNetwork.Sphere.Publisher; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -117,7 +117,7 @@ public class PollController( { var protoValue = answeredAccounts.FirstOrDefault(a => a.Id == answer.AccountId.ToString()); if (protoValue is not null) - answer.Account = Pass.Account.Account.FromProtoValue(protoValue); + answer.Account = AccountReference.FromProtoValue(protoValue); } } diff --git a/DysonNetwork.Sphere/Program.cs b/DysonNetwork.Sphere/Program.cs index 8700d83..bdec808 100644 --- a/DysonNetwork.Sphere/Program.cs +++ b/DysonNetwork.Sphere/Program.cs @@ -2,7 +2,6 @@ using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Http; using DysonNetwork.Shared.PageData; using DysonNetwork.Shared.Registry; -using DysonNetwork.Shared.Stream; using DysonNetwork.Sphere; using DysonNetwork.Sphere.PageData; using DysonNetwork.Sphere.Startup; @@ -11,15 +10,13 @@ using Microsoft.Extensions.FileProviders; var builder = WebApplication.CreateBuilder(args); +builder.AddServiceDefaults(); + // Configure Kestrel and server options builder.ConfigureAppKestrel(builder.Configuration); -// Add metrics and telemetry -builder.Services.AddAppMetrics(); - // Add application services -builder.Services.AddRegistryService(builder.Configuration); -builder.Services.AddStreamConnection(builder.Configuration); + builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); @@ -42,6 +39,8 @@ builder.Services.AddTransient(); var app = builder.Build(); +app.MapDefaultEndpoints(); + // Run database migrations using (var scope = app.Services.CreateScope()) { @@ -52,8 +51,6 @@ using (var scope = app.Services.CreateScope()) // Configure application middleware pipeline app.ConfigureAppMiddleware(builder.Configuration); -app.MapGatewayProxy(); - app.UseDefaultFiles(); app.UseStaticFiles(new StaticFileOptions { diff --git a/DysonNetwork.Sphere/Publisher/Publisher.cs b/DysonNetwork.Sphere/Publisher/Publisher.cs index 9f826d1..52b56fd 100644 --- a/DysonNetwork.Sphere/Publisher/Publisher.cs +++ b/DysonNetwork.Sphere/Publisher/Publisher.cs @@ -7,7 +7,7 @@ using Microsoft.EntityFrameworkCore; using NodaTime; using NodaTime.Serialization.Protobuf; using VerificationMark = DysonNetwork.Shared.Data.VerificationMark; -using Account = DysonNetwork.Pass.Account.Account; +using Account = DysonNetwork.Shared.Data.AccountReference; namespace DysonNetwork.Sphere.Publisher; diff --git a/DysonNetwork.Sphere/Publisher/PublisherController.cs b/DysonNetwork.Sphere/Publisher/PublisherController.cs index fcf3fb2..70e8784 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherController.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherController.cs @@ -34,7 +34,7 @@ public class PublisherController( var account = await accounts.GetAccountAsync( new GetAccountRequest { Id = publisher.AccountId.Value.ToString() } ); - publisher.Account = Pass.Account.Account.FromProtoValue(account); + publisher.Account = AccountReference.FromProtoValue(account); return Ok(publisher); } diff --git a/DysonNetwork.Sphere/Publisher/PublisherService.cs b/DysonNetwork.Sphere/Publisher/PublisherService.cs index 1165b24..27ba956 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherService.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherService.cs @@ -382,7 +382,7 @@ public class PublisherService( public async Task LoadMemberAccount(PublisherMember member) { var account = await accountsHelper.GetAccount(member.AccountId); - member.Account = Pass.Account.Account.FromProtoValue(account); + member.Account = AccountReference.FromProtoValue(account); return member; } @@ -394,7 +394,7 @@ public class PublisherService( return members.Select(m => { if (accounts.TryGetValue(m.AccountId, out var account)) - m.Account = Pass.Account.Account.FromProtoValue(account); + m.Account = AccountReference.FromProtoValue(account); return m; }).ToList(); } diff --git a/DysonNetwork.Sphere/Realm/Realm.cs b/DysonNetwork.Sphere/Realm/Realm.cs index af8aafd..8d733d4 100644 --- a/DysonNetwork.Sphere/Realm/Realm.cs +++ b/DysonNetwork.Sphere/Realm/Realm.cs @@ -1,7 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; -using DysonNetwork.Pass.Account; using DysonNetwork.Shared.Data; using DysonNetwork.Sphere.Chat; using Microsoft.EntityFrameworkCore; @@ -48,7 +47,7 @@ public class RealmMember : ModelBase public Guid RealmId { get; set; } public Realm Realm { get; set; } = null!; public Guid AccountId { get; set; } - [NotMapped] public Account? Account { get; set; } + [NotMapped] public AccountReference? Account { get; set; } [NotMapped] public AccountStatusReference? Status { get; set; } public int Role { get; set; } = RealmMemberRole.Normal; diff --git a/DysonNetwork.Sphere/Realm/RealmController.cs b/DysonNetwork.Sphere/Realm/RealmController.cs index e3bf31c..09efa48 100644 --- a/DysonNetwork.Sphere/Realm/RealmController.cs +++ b/DysonNetwork.Sphere/Realm/RealmController.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using NodaTime; using Google.Protobuf.WellKnownTypes; -using Microsoft.AspNetCore.Http.HttpResults; namespace DysonNetwork.Sphere.Realm; diff --git a/DysonNetwork.Sphere/Realm/RealmService.cs b/DysonNetwork.Sphere/Realm/RealmService.cs index 6afc257..9ee2153 100644 --- a/DysonNetwork.Sphere/Realm/RealmService.cs +++ b/DysonNetwork.Sphere/Realm/RealmService.cs @@ -1,5 +1,6 @@ using DysonNetwork.Shared; using DysonNetwork.Shared.Cache; +using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Registry; using DysonNetwork.Sphere.Localization; @@ -73,7 +74,7 @@ public class RealmService( public async Task LoadMemberAccount(RealmMember member) { var account = await accountsHelper.GetAccount(member.AccountId); - member.Account = Pass.Account.Account.FromProtoValue(account); + member.Account = AccountReference.FromProtoValue(account); return member; } @@ -85,7 +86,7 @@ public class RealmService( return members.Select(m => { if (accounts.TryGetValue(m.AccountId, out var account)) - m.Account = Pass.Account.Account.FromProtoValue(account); + m.Account = AccountReference.FromProtoValue(account); return m; }).ToList(); } diff --git a/DysonNetwork.Sphere/Startup/MetricsConfiguration.cs b/DysonNetwork.Sphere/Startup/MetricsConfiguration.cs deleted file mode 100644 index ed427da..0000000 --- a/DysonNetwork.Sphere/Startup/MetricsConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using OpenTelemetry.Metrics; -using OpenTelemetry.Trace; -using Prometheus; -using Prometheus.SystemMetrics; - -namespace DysonNetwork.Sphere.Startup; - -public static class MetricsConfiguration -{ - public static IServiceCollection AddAppMetrics(this IServiceCollection services) - { - // Prometheus - services.UseHttpClientMetrics(); - services.AddHealthChecks(); - services.AddSystemMetrics(); - services.AddPrometheusEntityFrameworkMetrics(); - services.AddPrometheusAspNetCoreMetrics(); - services.AddPrometheusHttpClientMetrics(); - - // OpenTelemetry - services.AddOpenTelemetry() - .WithTracing(tracing => - { - tracing - .AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddOtlpExporter(); - }) - .WithMetrics(metrics => - { - metrics - .AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddRuntimeInstrumentation() - .AddOtlpExporter(); - }); - - return services; - } -} diff --git a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs index 69ad0bc..7077a81 100644 --- a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs @@ -31,11 +31,6 @@ public static class ServiceCollectionExtensions services.AddLocalization(options => options.ResourcesPath = "Resources"); services.AddDbContext(); - services.AddSingleton(_ => - { - var connection = configuration.GetConnectionString("FastRetrieve")!; - return ConnectionMultiplexer.Connect(connection); - }); services.AddSingleton(SystemClock.Instance); services.AddHttpContextAccessor(); services.AddSingleton(); diff --git a/DysonNetwork.Sphere/Sticker/Sticker.cs b/DysonNetwork.Sphere/Sticker/Sticker.cs index dd125e0..c145656 100644 --- a/DysonNetwork.Sphere/Sticker/Sticker.cs +++ b/DysonNetwork.Sphere/Sticker/Sticker.cs @@ -1,7 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; -using DysonNetwork.Pass.Account; using DysonNetwork.Shared.Data; using Microsoft.EntityFrameworkCore; @@ -46,5 +45,5 @@ public class StickerPackOwnership : ModelBase public Guid PackId { get; set; } public StickerPack Pack { get; set; } = null!; public Guid AccountId { get; set; } - [NotMapped] public Account Account { get; set; } = null!; + [NotMapped] public AccountReference Account { get; set; } = null!; } diff --git a/DysonNetwork.Sphere/WebReader/WebArticle.cs b/DysonNetwork.Sphere/WebReader/WebArticle.cs index cfa9049..1945804 100644 --- a/DysonNetwork.Sphere/WebReader/WebArticle.cs +++ b/DysonNetwork.Sphere/WebReader/WebArticle.cs @@ -1,7 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; -using DysonNetwork.Pass.Account; using DysonNetwork.Shared.Data; namespace DysonNetwork.Sphere.WebReader; @@ -54,5 +53,5 @@ public class WebFeedSubscription : ModelBase public Guid FeedId { get; set; } public WebFeed Feed { get; set; } = null!; public Guid AccountId { get; set; } - [NotMapped] public Account Account { get; set; } = null!; + [NotMapped] public AccountReference Account { get; set; } = null!; } \ No newline at end of file diff --git a/DysonNetwork.Sphere/appsettings.json b/DysonNetwork.Sphere/appsettings.json index 44f42c2..1157d91 100644 --- a/DysonNetwork.Sphere/appsettings.json +++ b/DysonNetwork.Sphere/appsettings.json @@ -10,10 +10,7 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", - "FastRetrieve": "localhost:6379", - "Etcd": "etcd.orb.local:2379", - "Stream": "nats.orb.local:4222" + "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" }, "GeoIp": { "DatabasePath": "./Keys/GeoLite2-City.mmdb" @@ -39,8 +36,6 @@ }, "Service": { "Name": "DysonNetwork.Sphere", - "Url": "https://localhost:7099", - "ClientCert": "../Certificates/client.crt", - "ClientKey": "../Certificates/client.key" + "Url": "https://localhost:7099" } } diff --git a/DysonNetwork.sln b/DysonNetwork.sln index 4a515f3..30ddd02 100644 --- a/DysonNetwork.sln +++ b/DysonNetwork.sln @@ -16,10 +16,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Ring", "DysonN EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Drive", "DysonNetwork.Drive\DysonNetwork.Drive.csproj", "{8DE0B783-8852-494D-B90A-201ABBB71202}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Gateway", "DysonNetwork.Gateway\DysonNetwork.Gateway.csproj", "{19EB0086-4049-4B78-91C4-EAC37130A006}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Develop", "DysonNetwork.Develop\DysonNetwork.Develop.csproj", "{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Control", "DysonNetwork.Control\DysonNetwork.Control.csproj", "{7FFED190-51C7-4302-A8B5-96C839463458}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.ServiceDefaults", "DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj", "{877AAD96-C257-4305-9F1C-C9D9C9BEE615}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -46,13 +48,17 @@ Global {8DE0B783-8852-494D-B90A-201ABBB71202}.Debug|Any CPU.Build.0 = Debug|Any CPU {8DE0B783-8852-494D-B90A-201ABBB71202}.Release|Any CPU.ActiveCfg = Release|Any CPU {8DE0B783-8852-494D-B90A-201ABBB71202}.Release|Any CPU.Build.0 = Release|Any CPU - {19EB0086-4049-4B78-91C4-EAC37130A006}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {19EB0086-4049-4B78-91C4-EAC37130A006}.Debug|Any CPU.Build.0 = Debug|Any CPU - {19EB0086-4049-4B78-91C4-EAC37130A006}.Release|Any CPU.ActiveCfg = Release|Any CPU - {19EB0086-4049-4B78-91C4-EAC37130A006}.Release|Any CPU.Build.0 = Release|Any CPU {C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Release|Any CPU.ActiveCfg = Release|Any CPU {C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Release|Any CPU.Build.0 = Release|Any CPU + {7FFED190-51C7-4302-A8B5-96C839463458}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7FFED190-51C7-4302-A8B5-96C839463458}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7FFED190-51C7-4302-A8B5-96C839463458}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7FFED190-51C7-4302-A8B5-96C839463458}.Release|Any CPU.Build.0 = Release|Any CPU + {877AAD96-C257-4305-9F1C-C9D9C9BEE615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {877AAD96-C257-4305-9F1C-C9D9C9BEE615}.Debug|Any CPU.Build.0 = Debug|Any CPU + {877AAD96-C257-4305-9F1C-C9D9C9BEE615}.Release|Any CPU.ActiveCfg = Release|Any CPU + {877AAD96-C257-4305-9F1C-C9D9C9BEE615}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index 6c84793..72dd347 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -6,6 +6,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -21,6 +22,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -32,8 +34,10 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -64,6 +68,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -81,6 +86,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -90,6 +96,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -132,6 +139,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/compose.yaml b/compose.yaml deleted file mode 100644 index fee2329..0000000 --- a/compose.yaml +++ /dev/null @@ -1,86 +0,0 @@ -services: - etcd: - image: bitnami/etcd:latest - ports: - - "2379:2379" - - "2380:2380" - environment: - - ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379 - - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 - - ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380 - - ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd:2380 - - ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster - - ETCD_INITIAL_CLUSTER_STATE=new - - ETCD_INITIAL_CLUSTER=etcd=http://etcd:2380 - healthcheck: - test: ["CMD", "etcdctl", "get", "/health"] - interval: 5s - timeout: 5s - retries: 5 - - gateway: - image: xsheep2010/dyson-gateway:latest - ports: - - "8000:8080" - environment: - - ConnectionStrings__Etcd=http://etcd:2379 - - Etcd__Insecure=true - - Service__Name=DysonNetwork.Gateway - - Service__Url=http://gateway:8080 - depends_on: - etcd: - condition: service_healthy - - drive: - image: xsheep2010/dyson-drive:latest - ports: - - "8001:8080" - environment: - - ConnectionStrings__Etcd=http://etcd:2379 - - Etcd__Insecure=true - - Service__Name=DysonNetwork.Drive - - Service__Url=http://drive:8080 - depends_on: - etcd: - condition: service_healthy - - pass: - image: xsheep2010/dyson-pass:latest - ports: - - "8002:8080" - environment: - - ConnectionStrings__Etcd=http://etcd:2379 - - Etcd__Insecure=true - - Service__Name=DysonNetwork.Pass - - Service__Url=http://pass:8080 - depends_on: - etcd: - condition: service_healthy - - ring: - image: xsheep2010/dyson-ring:latest - ports: - - "8003:8080" - environment: - - ConnectionStrings__Etcd=http://etcd:2379 - - Etcd__Insecure=true - - Service__Name=DysonNetwork.Ring - - Service__Url=http://ring:8080 - depends_on: - etcd: - condition: service_healthy - - sphere: - image: xsheep2010/dyson-sphere:latest - ports: - - "8004:8080" - environment: - - ConnectionStrings__Etcd=http://etcd:2379 - - Etcd__Insecure=true - - Service__Name=DysonNetwork.Sphere - - Service__Url=http://sphere:8080 - volumes: - - "./keys:/app/keys" - depends_on: - etcd: - condition: service_healthy \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..a4aa669 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,217 @@ +services: + aspire-dashboard: + image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest" + expose: + - "18888" + - "18889" + networks: + - "aspire" + restart: "always" + cache: + image: "docker.io/library/redis:7.4" + command: + - "-c" + - "redis-server --requirepass $$REDIS_PASSWORD" + entrypoint: + - "/bin/sh" + environment: + REDIS_PASSWORD: "${CACHE_PASSWORD}" + expose: + - "6379" + networks: + - "aspire" + queue: + image: "docker.io/library/nats:2.11" + command: + - "--user" + - "nats" + - "--pass" + - "${QUEUE_PASSWORD}" + - "-js" + expose: + - "4222" + networks: + - "aspire" + ring: + image: "${RING_IMAGE}" + environment: + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" + HTTP_PORTS: "${RING_PORT}" + HTTPS_PORTS: "5001" + ConnectionStrings__queue: "nats://nats:${QUEUE_PASSWORD}@queue:4222" + services__pass__http__0: "http://pass:${PASS_PORT}" + services__pass__grpc__0: "https://pass:5001" + OTEL_EXPORTER_OTLP_ENDPOINT: "http://aspire-dashboard:18889" + OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" + OTEL_SERVICE_NAME: "ring" + volumes: + - "./Keys:/app/keys" + - "./settings/ring.json:/app/appsettings.json" + expose: + - "${RING_PORT}" + - "5001" + networks: + - "aspire" + pass: + image: "${PASS_IMAGE}" + environment: + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" + HTTP_PORTS: "${PASS_PORT}" + HTTPS_PORTS: "5001" + ConnectionStrings__cache: "cache:6379,password=${CACHE_PASSWORD}" + ConnectionStrings__queue: "nats://nats:${QUEUE_PASSWORD}@queue:4222" + services__ring__http__0: "http://ring:${RING_PORT}" + services__ring__grpc__0: "https://ring:5001" + OTEL_EXPORTER_OTLP_ENDPOINT: "http://aspire-dashboard:18889" + OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" + OTEL_SERVICE_NAME: "pass" + volumes: + - "./Keys:/app/keys" + - "./settings/pass.json:/app/appsettings.json" + expose: + - "${PASS_PORT}" + - "5001" + networks: + - "aspire" + drive: + image: "${DRIVE_IMAGE}" + environment: + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" + HTTP_PORTS: "${DRIVE_PORT}" + HTTPS_PORTS: "5001" + ConnectionStrings__cache: "cache:6379,password=${CACHE_PASSWORD}" + ConnectionStrings__queue: "nats://nats:${QUEUE_PASSWORD}@queue:4222" + services__pass__http__0: "http://pass:${PASS_PORT}" + services__pass__grpc__0: "https://pass:5001" + services__ring__http__0: "http://ring:${RING_PORT}" + services__ring__grpc__0: "https://ring:5001" + OTEL_EXPORTER_OTLP_ENDPOINT: "http://aspire-dashboard:18889" + OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" + OTEL_SERVICE_NAME: "drive" + expose: + - "${DRIVE_PORT}" + - "5001" + volumes: + - "./settings/drive.json:/app/appsettings.json" + networks: + - "aspire" + sphere: + image: "${SPHERE_IMAGE}" + environment: + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" + HTTP_PORTS: "${SPHERE_PORT}" + HTTPS_PORTS: "5001" + ConnectionStrings__cache: "cache:6379,password=${CACHE_PASSWORD}" + ConnectionStrings__queue: "nats://nats:${QUEUE_PASSWORD}@queue:4222" + services__pass__http__0: "http://pass:${PASS_PORT}" + services__pass__grpc__0: "https://pass:5001" + services__ring__http__0: "http://ring:${RING_PORT}" + services__ring__grpc__0: "https://ring:5001" + OTEL_EXPORTER_OTLP_ENDPOINT: "http://aspire-dashboard:18889" + OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" + OTEL_SERVICE_NAME: "sphere" + volumes: + - "./Keys:/app/keys" + - "./settings/sphere.json:/app/appsettings.json" + expose: + - "${SPHERE_PORT}" + - "5001" + networks: + - "aspire" + develop: + image: "${DEVELOP_IMAGE}" + environment: + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" + HTTP_PORTS: "${DEVELOP_PORT}" + HTTPS_PORTS: "5001" + ConnectionStrings__cache: "cache:6379,password=${CACHE_PASSWORD}" + services__pass__http__0: "http://pass:${PASS_PORT}" + services__pass__grpc__0: "https://pass:5001" + services__ring__http__0: "http://ring:${RING_PORT}" + services__ring__grpc__0: "https://ring:5001" + OTEL_EXPORTER_OTLP_ENDPOINT: "http://aspire-dashboard:18889" + OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" + OTEL_SERVICE_NAME: "develop" + expose: + - "${DEVELOP_PORT}" + - "5001" + networks: + - "aspire" + gateway: + image: "mcr.microsoft.com/dotnet/nightly/yarp:2.3.0-preview.4" + command: + - "/app/yarp.dll" + entrypoint: + - "dotnet" + environment: + ASPNETCORE_ENVIRONMENT: "Production" + services__ring__http__0: "http://ring:${RING_PORT}" + services__ring__grpc__0: "https://ring:5001" + REVERSEPROXY__ROUTES__route0__MATCH__PATH: "/ws" + REVERSEPROXY__ROUTES__route0__CLUSTERID: "cluster_ring" + REVERSEPROXY__ROUTES__route1__MATCH__PATH: "/ring/{**catch-all}" + REVERSEPROXY__ROUTES__route1__CLUSTERID: "cluster_ring" + REVERSEPROXY__ROUTES__route1__TRANSFORMS__0__PathRemovePrefix: "/ring" + REVERSEPROXY__ROUTES__route1__TRANSFORMS__1__PathPrefix: "/api" + REVERSEPROXY__ROUTES__route2__MATCH__PATH: "/.well-known/openid-configuration" + REVERSEPROXY__ROUTES__route2__CLUSTERID: "cluster_pass" + REVERSEPROXY__ROUTES__route3__MATCH__PATH: "/.well-known/jwks" + REVERSEPROXY__ROUTES__route3__CLUSTERID: "cluster_pass" + REVERSEPROXY__ROUTES__route4__MATCH__PATH: "/id/{**catch-all}" + REVERSEPROXY__ROUTES__route4__CLUSTERID: "cluster_pass" + REVERSEPROXY__ROUTES__route4__TRANSFORMS__0__PathRemovePrefix: "/id" + REVERSEPROXY__ROUTES__route4__TRANSFORMS__1__PathPrefix: "/api" + REVERSEPROXY__ROUTES__route5__MATCH__PATH: "/api/tus" + REVERSEPROXY__ROUTES__route5__CLUSTERID: "cluster_drive" + REVERSEPROXY__ROUTES__route6__MATCH__PATH: "/drive/{**catch-all}" + REVERSEPROXY__ROUTES__route6__CLUSTERID: "cluster_drive" + REVERSEPROXY__ROUTES__route6__TRANSFORMS__0__PathRemovePrefix: "/drive" + REVERSEPROXY__ROUTES__route6__TRANSFORMS__1__PathPrefix: "/api" + REVERSEPROXY__ROUTES__route7__MATCH__PATH: "/sphere/{**catch-all}" + REVERSEPROXY__ROUTES__route7__CLUSTERID: "cluster_sphere" + REVERSEPROXY__ROUTES__route7__TRANSFORMS__0__PathRemovePrefix: "/sphere" + REVERSEPROXY__ROUTES__route7__TRANSFORMS__1__PathPrefix: "/api" + REVERSEPROXY__ROUTES__route8__MATCH__PATH: "/develop/{**catch-all}" + REVERSEPROXY__ROUTES__route8__CLUSTERID: "cluster_develop" + REVERSEPROXY__ROUTES__route8__TRANSFORMS__0__PathRemovePrefix: "/develop" + REVERSEPROXY__ROUTES__route8__TRANSFORMS__1__PathPrefix: "/api" + REVERSEPROXY__CLUSTERS__cluster_ring__DESTINATIONS__destination1__ADDRESS: "http://_http.ring" + REVERSEPROXY__CLUSTERS__cluster_pass__DESTINATIONS__destination1__ADDRESS: "http://_http.pass" + REVERSEPROXY__CLUSTERS__cluster_drive__DESTINATIONS__destination1__ADDRESS: "http://_http.drive" + REVERSEPROXY__CLUSTERS__cluster_sphere__DESTINATIONS__destination1__ADDRESS: "http://_http.sphere" + REVERSEPROXY__CLUSTERS__cluster_develop__DESTINATIONS__destination1__ADDRESS: "http://_http.develop" + services__pass__http__0: "http://pass:${PASS_PORT}" + services__pass__grpc__0: "https://pass:5001" + services__drive__http__0: "http://drive:${DRIVE_PORT}" + services__drive__grpc__0: "https://drive:5001" + services__sphere__http__0: "http://sphere:${SPHERE_PORT}" + services__sphere__grpc__0: "https://sphere:5001" + services__develop__http__0: "http://develop:${DEVELOP_PORT}" + services__develop__grpc__0: "https://develop:5001" + OTEL_EXPORTER_OTLP_ENDPOINT: "http://aspire-dashboard:18889" + OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" + OTEL_SERVICE_NAME: "gateway" + expose: + - "5000" + ports: + - "5001:5000" + networks: + - "aspire" +networks: + aspire: + driver: "bridge" diff --git a/publish/.env b/publish/.env new file mode 100644 index 0000000..13c2b1d --- /dev/null +++ b/publish/.env @@ -0,0 +1,35 @@ +# Default container port for ring +RING_PORT=8080 + +# Default container port for pass +PASS_PORT=8080 + +# Default container port for drive +DRIVE_PORT=8080 + +# Default container port for sphere +SPHERE_PORT=8080 + +# Default container port for develop +DEVELOP_PORT=8080 + +# Parameter cache-password +CACHE_PASSWORD=KS3jSPaU9e + +# Parameter queue-password +QUEUE_PASSWORD=8xEECa4ckz + +# Container image name for ring +RING_IMAGE=ring:latest + +# Container image name for pass +PASS_IMAGE=pass:latest + +# Container image name for drive +DRIVE_IMAGE=drive:latest + +# Container image name for sphere +SPHERE_IMAGE=sphere:latest + +# Container image name for develop +DEVELOP_IMAGE=develop:latest diff --git a/publish/docker-compose.yaml b/publish/docker-compose.yaml new file mode 100644 index 0000000..8b4f695 --- /dev/null +++ b/publish/docker-compose.yaml @@ -0,0 +1,191 @@ +services: + docker-compose-dashboard: + image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest" + expose: + - "18888" + - "18889" + networks: + - "aspire" + restart: "always" + cache: + image: "docker.io/library/redis:7.4" + command: + - "-c" + - "redis-server --requirepass $$REDIS_PASSWORD" + entrypoint: + - "/bin/sh" + environment: + REDIS_PASSWORD: "${CACHE_PASSWORD}" + expose: + - "6379" + networks: + - "aspire" + queue: + image: "docker.io/library/nats:2.11" + command: + - "--user" + - "nats" + - "--pass" + - "${QUEUE_PASSWORD}" + - "-js" + expose: + - "4222" + networks: + - "aspire" + ring: + image: "${RING_IMAGE}" + environment: + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" + HTTP_PORTS: "${RING_PORT}" + HTTPS_PORTS: "5001" + ConnectionStrings__queue: "nats://nats:${QUEUE_PASSWORD}@queue:4222" + services__pass__http__0: "http://pass:${PASS_PORT}" + OTEL_EXPORTER_OTLP_ENDPOINT: "http://docker-compose-dashboard:18889" + OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" + OTEL_SERVICE_NAME: "ring" + expose: + - "${RING_PORT}" + - "5001" + networks: + - "aspire" + pass: + image: "${PASS_IMAGE}" + environment: + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" + HTTP_PORTS: "${PASS_PORT}" + HTTPS_PORTS: "5001" + ConnectionStrings__cache: "cache:6379,password=${CACHE_PASSWORD}" + ConnectionStrings__queue: "nats://nats:${QUEUE_PASSWORD}@queue:4222" + services__ring__http__0: "http://ring:${RING_PORT}" + OTEL_EXPORTER_OTLP_ENDPOINT: "http://docker-compose-dashboard:18889" + OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" + OTEL_SERVICE_NAME: "pass" + expose: + - "${PASS_PORT}" + - "5001" + networks: + - "aspire" + drive: + image: "${DRIVE_IMAGE}" + environment: + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" + HTTP_PORTS: "${DRIVE_PORT}" + HTTPS_PORTS: "5001" + ConnectionStrings__cache: "cache:6379,password=${CACHE_PASSWORD}" + ConnectionStrings__queue: "nats://nats:${QUEUE_PASSWORD}@queue:4222" + services__pass__http__0: "http://pass:${PASS_PORT}" + services__ring__http__0: "http://ring:${RING_PORT}" + OTEL_EXPORTER_OTLP_ENDPOINT: "http://docker-compose-dashboard:18889" + OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" + OTEL_SERVICE_NAME: "drive" + expose: + - "${DRIVE_PORT}" + - "5001" + networks: + - "aspire" + sphere: + image: "${SPHERE_IMAGE}" + environment: + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" + HTTP_PORTS: "${SPHERE_PORT}" + HTTPS_PORTS: "5001" + ConnectionStrings__cache: "cache:6379,password=${CACHE_PASSWORD}" + ConnectionStrings__queue: "nats://nats:${QUEUE_PASSWORD}@queue:4222" + services__pass__http__0: "http://pass:${PASS_PORT}" + services__ring__http__0: "http://ring:${RING_PORT}" + OTEL_EXPORTER_OTLP_ENDPOINT: "http://docker-compose-dashboard:18889" + OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" + OTEL_SERVICE_NAME: "sphere" + expose: + - "${SPHERE_PORT}" + - "5001" + networks: + - "aspire" + develop: + image: "${DEVELOP_IMAGE}" + environment: + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" + HTTP_PORTS: "${DEVELOP_PORT}" + HTTPS_PORTS: "5001" + ConnectionStrings__cache: "cache:6379,password=${CACHE_PASSWORD}" + services__pass__http__0: "http://pass:${PASS_PORT}" + services__ring__http__0: "http://ring:${RING_PORT}" + OTEL_EXPORTER_OTLP_ENDPOINT: "http://docker-compose-dashboard:18889" + OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" + OTEL_SERVICE_NAME: "develop" + expose: + - "${DEVELOP_PORT}" + - "5001" + networks: + - "aspire" + gateway: + image: "mcr.microsoft.com/dotnet/nightly/yarp:2.3.0-preview.4" + command: + - "/app/yarp.dll" + entrypoint: + - "dotnet" + environment: + ASPNETCORE_ENVIRONMENT: "Production" + services__ring__http__0: "http://ring:${RING_PORT}" + REVERSEPROXY__ROUTES__route0__MATCH__PATH: "/ws" + REVERSEPROXY__ROUTES__route0__CLUSTERID: "cluster_ring" + REVERSEPROXY__ROUTES__route1__MATCH__PATH: "/ring/{**catch-all}" + REVERSEPROXY__ROUTES__route1__CLUSTERID: "cluster_ring" + REVERSEPROXY__ROUTES__route1__TRANSFORMS__0__PathRemovePrefix: "/ring" + REVERSEPROXY__ROUTES__route1__TRANSFORMS__1__PathPrefix: "/api" + REVERSEPROXY__ROUTES__route2__MATCH__PATH: "/.well-known/openid-configuration" + REVERSEPROXY__ROUTES__route2__CLUSTERID: "cluster_pass" + REVERSEPROXY__ROUTES__route3__MATCH__PATH: "/.well-known/jwks" + REVERSEPROXY__ROUTES__route3__CLUSTERID: "cluster_pass" + REVERSEPROXY__ROUTES__route4__MATCH__PATH: "/id/{**catch-all}" + REVERSEPROXY__ROUTES__route4__CLUSTERID: "cluster_pass" + REVERSEPROXY__ROUTES__route4__TRANSFORMS__0__PathRemovePrefix: "/id" + REVERSEPROXY__ROUTES__route4__TRANSFORMS__1__PathPrefix: "/api" + REVERSEPROXY__ROUTES__route5__MATCH__PATH: "/api/tus" + REVERSEPROXY__ROUTES__route5__CLUSTERID: "cluster_drive" + REVERSEPROXY__ROUTES__route6__MATCH__PATH: "/drive/{**catch-all}" + REVERSEPROXY__ROUTES__route6__CLUSTERID: "cluster_drive" + REVERSEPROXY__ROUTES__route6__TRANSFORMS__0__PathRemovePrefix: "/drive" + REVERSEPROXY__ROUTES__route6__TRANSFORMS__1__PathPrefix: "/api" + REVERSEPROXY__ROUTES__route7__MATCH__PATH: "/sphere/{**catch-all}" + REVERSEPROXY__ROUTES__route7__CLUSTERID: "cluster_sphere" + REVERSEPROXY__ROUTES__route7__TRANSFORMS__0__PathRemovePrefix: "/sphere" + REVERSEPROXY__ROUTES__route7__TRANSFORMS__1__PathPrefix: "/api" + REVERSEPROXY__ROUTES__route8__MATCH__PATH: "/develop/{**catch-all}" + REVERSEPROXY__ROUTES__route8__CLUSTERID: "cluster_develop" + REVERSEPROXY__ROUTES__route8__TRANSFORMS__0__PathRemovePrefix: "/develop" + REVERSEPROXY__ROUTES__route8__TRANSFORMS__1__PathPrefix: "/api" + REVERSEPROXY__CLUSTERS__cluster_ring__DESTINATIONS__destination1__ADDRESS: "http://_http.ring" + REVERSEPROXY__CLUSTERS__cluster_pass__DESTINATIONS__destination1__ADDRESS: "http://_http.pass" + REVERSEPROXY__CLUSTERS__cluster_drive__DESTINATIONS__destination1__ADDRESS: "http://_http.drive" + REVERSEPROXY__CLUSTERS__cluster_sphere__DESTINATIONS__destination1__ADDRESS: "http://_http.sphere" + REVERSEPROXY__CLUSTERS__cluster_develop__DESTINATIONS__destination1__ADDRESS: "http://_http.develop" + services__pass__http__0: "http://pass:${PASS_PORT}" + services__drive__http__0: "http://drive:${DRIVE_PORT}" + services__sphere__http__0: "http://sphere:${SPHERE_PORT}" + services__develop__http__0: "http://develop:${DEVELOP_PORT}" + OTEL_EXPORTER_OTLP_ENDPOINT: "http://docker-compose-dashboard:18889" + OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" + OTEL_SERVICE_NAME: "gateway" + expose: + - "5000" + networks: + - "aspire" +networks: + aspire: + driver: "bridge" diff --git a/publish/settings/develop.json b/publish/settings/develop.json new file mode 100644 index 0000000..a70297c --- /dev/null +++ b/publish/settings/develop.json @@ -0,0 +1,23 @@ +{ + "Debug": true, + "BaseUrl": "http://localhost:5071", + "SiteUrl": "https://solian.app", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "App": "Host=host.docker.internal;Port=5432;Database=dyson_network_dev;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" + }, + "KnownProxies": ["127.0.0.1", "::1"], + "Etcd": { + "Insecure": true + }, + "Service": { + "Name": "DysonNetwork.Develop", + "Url": "https://localhost:7192" + } +} diff --git a/publish/settings/drive.json b/publish/settings/drive.json new file mode 100644 index 0000000..b3bc501 --- /dev/null +++ b/publish/settings/drive.json @@ -0,0 +1,127 @@ +{ + "Debug": true, + "BaseUrl": "http://localhost:5090", + "GatewayUrl": "http://localhost:5094", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "App": "Host=host.docker.internal;Port=5432;Database=dyson_drive;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" + }, + "Authentication": { + "Schemes": { + "Bearer": { + "ValidAudiences": ["http://localhost:5071", "https://localhost:7099"], + "ValidIssuer": "solar-network" + } + } + }, + "AuthToken": { + "PublicKeyPath": "Keys/PublicKey.pem", + "PrivateKeyPath": "Keys/PrivateKey.pem" + }, + "OidcProvider": { + "IssuerUri": "https://nt.solian.app", + "PublicKeyPath": "Keys/PublicKey.pem", + "PrivateKeyPath": "Keys/PrivateKey.pem", + "AccessTokenLifetime": "01:00:00", + "RefreshTokenLifetime": "30.00:00:00", + "AuthorizationCodeLifetime": "00:30:00", + "RequireHttpsMetadata": true + }, + "Tus": { + "StorePath": "Uploads" + }, + "Storage": { + "PreferredRemote": "2adceae3-981a-4564-9b8d-5d71a211c873", + "Remote": [ + { + "Id": "minio", + "Label": "Minio", + "Region": "auto", + "Bucket": "solar-network-development", + "Endpoint": "localhost:9000", + "SecretId": "littlesheep", + "SecretKey": "password", + "EnabledSigned": true, + "EnableSsl": false + }, + { + "Id": "cloudflare", + "Label": "Cloudflare R2", + "Region": "auto", + "Bucket": "solar-network", + "Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com", + "SecretId": "8ff5d06c7b1639829d60bc6838a542e6", + "SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67", + "EnableSigned": true, + "EnableSsl": true + } + ] + }, + "Captcha": { + "Provider": "cloudflare", + "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", + "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" + }, + "Notifications": { + "Topic": "dev.solsynth.solian", + "Endpoint": "http://localhost:8088" + }, + "Email": { + "Server": "smtp4dev.orb.local", + "Port": 25, + "UseSsl": false, + "Username": "no-reply@mail.solsynth.dev", + "Password": "password", + "FromAddress": "no-reply@mail.solsynth.dev", + "FromName": "Alphabot", + "SubjectPrefix": "Solar Network" + }, + "RealtimeChat": { + "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", + "ApiKey": "APIs6TiL8wj3A4j", + "ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE" + }, + "GeoIp": { + "DatabasePath": "./Keys/GeoLite2-City.mmdb" + }, + "Oidc": { + "Google": { + "ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", + "ClientSecret": "" + }, + "Apple": { + "ClientId": "dev.solsynth.solian", + "TeamId": "W7HPZ53V6B", + "KeyId": "B668YP4KBG", + "PrivateKeyPath": "./Keys/Solarpass.p8" + }, + "Microsoft": { + "ClientId": "YOUR_MICROSOFT_CLIENT_ID", + "ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET", + "DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT" + } + }, + "Payment": { + "Auth": { + "Afdian": "" + }, + "Subscriptions": { + "Afdian": { + "7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary", + "7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova", + "141713ee3d6211f085b352540025c377": "solian.stellar.supernova" + } + } + }, + "KnownProxies": ["127.0.0.1", "::1"], + "Service": { + "Name": "DysonNetwork.Drive", + "Url": "https://localhost:7092" + } +} diff --git a/publish/settings/pass.json b/publish/settings/pass.json new file mode 100644 index 0000000..dea5e59 --- /dev/null +++ b/publish/settings/pass.json @@ -0,0 +1,82 @@ +{ + "Debug": true, + "BaseUrl": "http://localhost:5216", + "SiteUrl": "https://id.solian.app", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "App": "Host=host.docker.internal;Port=5432;Database=dyson_pass;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" + }, + "Authentication": { + "Schemes": { + "Bearer": { + "ValidAudiences": ["http://localhost:5071", "https://localhost:7099"], + "ValidIssuer": "solar-network" + } + } + }, + "AuthToken": { + "CookieDomain": "localhost", + "PublicKeyPath": "Keys/PublicKey.pem", + "PrivateKeyPath": "Keys/PrivateKey.pem" + }, + "OidcProvider": { + "IssuerUri": "https://nt.solian.app", + "PublicKeyPath": "Keys/PublicKey.pem", + "PrivateKeyPath": "Keys/PrivateKey.pem", + "AccessTokenLifetime": "01:00:00", + "RefreshTokenLifetime": "30.00:00:00", + "AuthorizationCodeLifetime": "00:30:00", + "RequireHttpsMetadata": true + }, + "Captcha": { + "Provider": "cloudflare", + "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", + "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" + }, + "GeoIp": { + "DatabasePath": "./Keys/GeoLite2-City.mmdb" + }, + "Oidc": { + "Google": { + "ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", + "ClientSecret": "" + }, + "Apple": { + "ClientId": "dev.solsynth.solian", + "TeamId": "W7HPZ53V6B", + "KeyId": "B668YP4KBG", + "PrivateKeyPath": "./Keys/Solarpass.p8" + }, + "Microsoft": { + "ClientId": "YOUR_MICROSOFT_CLIENT_ID", + "ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET", + "DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT" + } + }, + "Payment": { + "Auth": { + "Afdian": "" + }, + "Subscriptions": { + "Afdian": { + "7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary", + "7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova", + "141713ee3d6211f085b352540025c377": "solian.stellar.supernova" + } + } + }, + "KnownProxies": ["127.0.0.1", "::1"], + "Service": { + "Name": "DysonNetwork.Pass", + "Url": "https://localhost:7058" + }, + "Etcd": { + "Insecure": true + } +} diff --git a/publish/settings/ring.json b/publish/settings/ring.json new file mode 100644 index 0000000..3a15453 --- /dev/null +++ b/publish/settings/ring.json @@ -0,0 +1,47 @@ +{ + "Debug": true, + "BaseUrl": "http://localhost:5212", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "App": "Host=host.docker.internal;Port=5432;Database=dyson_pusher;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" + }, + "Notifications": { + "Push": { + "Production": true, + "Google": "./Keys/Solian.json", + "Apple": { + "PrivateKey": "./Keys/Solian.p8", + "PrivateKeyId": "4US4KSX4W6", + "TeamId": "W7HPZ53V6B", + "BundleIdentifier": "dev.solsynth.solian" + } + } + }, + "Email": { + "Server": "smtp4dev.orb.local", + "Port": 25, + "UseSsl": false, + "Username": "no-reply@mail.solsynth.dev", + "Password": "password", + "FromAddress": "no-reply@mail.solsynth.dev", + "FromName": "Alphabot", + "SubjectPrefix": "Solar Network" + }, + "GeoIp": { + "DatabasePath": "./Keys/GeoLite2-City.mmdb" + }, + "KnownProxies": ["127.0.0.1", "::1"], + "Service": { + "Name": "DysonNetwork.Ring", + "Url": "https://localhost:7259" + }, + "Etcd": { + "Insecure": true + } +} diff --git a/publish/settings/sphere.json b/publish/settings/sphere.json new file mode 100644 index 0000000..22a3a07 --- /dev/null +++ b/publish/settings/sphere.json @@ -0,0 +1,38 @@ +{ + "Debug": true, + "BaseUrl": "http://localhost:5071", + "SiteUrl": "https://solian.app", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "App": "Host=host.docker.internal;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" + }, + "GeoIp": { + "DatabasePath": "./Keys/GeoLite2-City.mmdb" + }, + "RealtimeChat": { + "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", + "ApiKey": "", + "ApiSecret": "" + }, + "Translation": { + "Provider": "Tencent", + "Region": "ap-hongkong", + "ProjectId": "0", + "SecretId": "", + "SecretKey": "" + }, + "KnownProxies": ["127.0.0.1", "::1"], + "Etcd": { + "Insecure": true + }, + "Service": { + "Name": "DysonNetwork.Sphere", + "Url": "https://localhost:7099" + } +} diff --git a/settings/develop.json b/settings/develop.json new file mode 100644 index 0000000..a70297c --- /dev/null +++ b/settings/develop.json @@ -0,0 +1,23 @@ +{ + "Debug": true, + "BaseUrl": "http://localhost:5071", + "SiteUrl": "https://solian.app", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "App": "Host=host.docker.internal;Port=5432;Database=dyson_network_dev;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" + }, + "KnownProxies": ["127.0.0.1", "::1"], + "Etcd": { + "Insecure": true + }, + "Service": { + "Name": "DysonNetwork.Develop", + "Url": "https://localhost:7192" + } +} diff --git a/settings/drive.json b/settings/drive.json new file mode 100644 index 0000000..90c148c --- /dev/null +++ b/settings/drive.json @@ -0,0 +1,127 @@ +{ + "Debug": true, + "BaseUrl": "http://localhost:5090", + "GatewayUrl": "http://localhost:5094", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "App": "Host=host.docker.internal;Port=5432;Database=dyson_drive;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" + }, + "Authentication": { + "Schemes": { + "Bearer": { + "ValidAudiences": ["http://localhost:5071", "https://localhost:7099"], + "ValidIssuer": "solar-network" + } + } + }, + "AuthToken": { + "PublicKeyPath": "app/keys/PublicKey.pem", + "PrivateKeyPath": "app/keys/PrivateKey.pem" + }, + "OidcProvider": { + "IssuerUri": "https://nt.solian.app", + "PublicKeyPath": "Keys/PublicKey.pem", + "PrivateKeyPath": "Keys/PrivateKey.pem", + "AccessTokenLifetime": "01:00:00", + "RefreshTokenLifetime": "30.00:00:00", + "AuthorizationCodeLifetime": "00:30:00", + "RequireHttpsMetadata": true + }, + "Tus": { + "StorePath": "Uploads" + }, + "Storage": { + "PreferredRemote": "2adceae3-981a-4564-9b8d-5d71a211c873", + "Remote": [ + { + "Id": "minio", + "Label": "Minio", + "Region": "auto", + "Bucket": "solar-network-development", + "Endpoint": "localhost:9000", + "SecretId": "littlesheep", + "SecretKey": "password", + "EnabledSigned": true, + "EnableSsl": false + }, + { + "Id": "cloudflare", + "Label": "Cloudflare R2", + "Region": "auto", + "Bucket": "solar-network", + "Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com", + "SecretId": "8ff5d06c7b1639829d60bc6838a542e6", + "SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67", + "EnableSigned": true, + "EnableSsl": true + } + ] + }, + "Captcha": { + "Provider": "cloudflare", + "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", + "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" + }, + "Notifications": { + "Topic": "dev.solsynth.solian", + "Endpoint": "http://localhost:8088" + }, + "Email": { + "Server": "smtp4dev.orb.local", + "Port": 25, + "UseSsl": false, + "Username": "no-reply@mail.solsynth.dev", + "Password": "password", + "FromAddress": "no-reply@mail.solsynth.dev", + "FromName": "Alphabot", + "SubjectPrefix": "Solar Network" + }, + "RealtimeChat": { + "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", + "ApiKey": "APIs6TiL8wj3A4j", + "ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE" + }, + "GeoIp": { + "DatabasePath": "./Keys/GeoLite2-City.mmdb" + }, + "Oidc": { + "Google": { + "ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", + "ClientSecret": "" + }, + "Apple": { + "ClientId": "dev.solsynth.solian", + "TeamId": "W7HPZ53V6B", + "KeyId": "B668YP4KBG", + "PrivateKeyPath": "./Keys/Solarpass.p8" + }, + "Microsoft": { + "ClientId": "YOUR_MICROSOFT_CLIENT_ID", + "ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET", + "DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT" + } + }, + "Payment": { + "Auth": { + "Afdian": "" + }, + "Subscriptions": { + "Afdian": { + "7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary", + "7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova", + "141713ee3d6211f085b352540025c377": "solian.stellar.supernova" + } + } + }, + "KnownProxies": ["127.0.0.1", "::1"], + "Service": { + "Name": "DysonNetwork.Drive", + "Url": "https://localhost:7092" + } +} diff --git a/settings/pass.json b/settings/pass.json new file mode 100644 index 0000000..dea5e59 --- /dev/null +++ b/settings/pass.json @@ -0,0 +1,82 @@ +{ + "Debug": true, + "BaseUrl": "http://localhost:5216", + "SiteUrl": "https://id.solian.app", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "App": "Host=host.docker.internal;Port=5432;Database=dyson_pass;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" + }, + "Authentication": { + "Schemes": { + "Bearer": { + "ValidAudiences": ["http://localhost:5071", "https://localhost:7099"], + "ValidIssuer": "solar-network" + } + } + }, + "AuthToken": { + "CookieDomain": "localhost", + "PublicKeyPath": "Keys/PublicKey.pem", + "PrivateKeyPath": "Keys/PrivateKey.pem" + }, + "OidcProvider": { + "IssuerUri": "https://nt.solian.app", + "PublicKeyPath": "Keys/PublicKey.pem", + "PrivateKeyPath": "Keys/PrivateKey.pem", + "AccessTokenLifetime": "01:00:00", + "RefreshTokenLifetime": "30.00:00:00", + "AuthorizationCodeLifetime": "00:30:00", + "RequireHttpsMetadata": true + }, + "Captcha": { + "Provider": "cloudflare", + "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", + "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" + }, + "GeoIp": { + "DatabasePath": "./Keys/GeoLite2-City.mmdb" + }, + "Oidc": { + "Google": { + "ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", + "ClientSecret": "" + }, + "Apple": { + "ClientId": "dev.solsynth.solian", + "TeamId": "W7HPZ53V6B", + "KeyId": "B668YP4KBG", + "PrivateKeyPath": "./Keys/Solarpass.p8" + }, + "Microsoft": { + "ClientId": "YOUR_MICROSOFT_CLIENT_ID", + "ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET", + "DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT" + } + }, + "Payment": { + "Auth": { + "Afdian": "" + }, + "Subscriptions": { + "Afdian": { + "7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary", + "7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova", + "141713ee3d6211f085b352540025c377": "solian.stellar.supernova" + } + } + }, + "KnownProxies": ["127.0.0.1", "::1"], + "Service": { + "Name": "DysonNetwork.Pass", + "Url": "https://localhost:7058" + }, + "Etcd": { + "Insecure": true + } +} diff --git a/settings/ring.json b/settings/ring.json new file mode 100644 index 0000000..58332b9 --- /dev/null +++ b/settings/ring.json @@ -0,0 +1,47 @@ +{ + "Debug": true, + "BaseUrl": "http://localhost:5212", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "App": "Host=host.docker.internal;Port=5432;Database=dyson_pusher;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" + }, + "Notifications": { + "Push": { + "Production": true, + "Google": "/app/keys/Solian.json", + "Apple": { + "PrivateKey": "./Keys/Solian.p8", + "PrivateKeyId": "4US4KSX4W6", + "TeamId": "W7HPZ53V6B", + "BundleIdentifier": "dev.solsynth.solian" + } + } + }, + "Email": { + "Server": "smtp4dev.orb.local", + "Port": 25, + "UseSsl": false, + "Username": "no-reply@mail.solsynth.dev", + "Password": "password", + "FromAddress": "no-reply@mail.solsynth.dev", + "FromName": "Alphabot", + "SubjectPrefix": "Solar Network" + }, + "GeoIp": { + "DatabasePath": "/app/keys/GeoLite2-City.mmdb" + }, + "KnownProxies": ["127.0.0.1", "::1"], + "Service": { + "Name": "DysonNetwork.Ring", + "Url": "https://localhost:7259" + }, + "Etcd": { + "Insecure": true + } +} diff --git a/settings/sphere.json b/settings/sphere.json new file mode 100644 index 0000000..18055c5 --- /dev/null +++ b/settings/sphere.json @@ -0,0 +1,38 @@ +{ + "Debug": true, + "BaseUrl": "http://localhost:5071", + "SiteUrl": "https://solian.app", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "App": "Host=host.docker.internal;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" + }, + "GeoIp": { + "DatabasePath": "/app/keys/GeoLite2-City.mmdb" + }, + "RealtimeChat": { + "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", + "ApiKey": "", + "ApiSecret": "" + }, + "Translation": { + "Provider": "Tencent", + "Region": "ap-hongkong", + "ProjectId": "0", + "SecretId": "", + "SecretKey": "" + }, + "KnownProxies": ["127.0.0.1", "::1"], + "Etcd": { + "Insecure": true + }, + "Service": { + "Name": "DysonNetwork.Sphere", + "Url": "https://localhost:7099" + } +}