Add deployment
This commit is contained in:
@@ -25,14 +25,7 @@ public sealed class LocalEscalationWorkerService : BackgroundService
|
||||
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
|
||||
|
||||
var o = _opts.Value;
|
||||
var factory = new ConnectionFactory
|
||||
{
|
||||
HostName = o.Host,
|
||||
Port = o.Port,
|
||||
UserName = o.Username,
|
||||
Password = o.Password,
|
||||
DispatchConsumersAsync = true,
|
||||
};
|
||||
var factory = RabbitMqConnectionFactory.Create(o, dispatchConsumersAsync: true);
|
||||
|
||||
using var connection = factory.CreateConnection("gateway-escalation-worker");
|
||||
using var channel = connection.CreateModel();
|
||||
|
||||
@@ -26,14 +26,7 @@ public sealed class LocalPagingWorkerService : BackgroundService
|
||||
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
|
||||
|
||||
var o = _opts.Value;
|
||||
var factory = new ConnectionFactory
|
||||
{
|
||||
HostName = o.Host,
|
||||
Port = o.Port,
|
||||
UserName = o.Username,
|
||||
Password = o.Password,
|
||||
DispatchConsumersAsync = true,
|
||||
};
|
||||
var factory = RabbitMqConnectionFactory.Create(o, dispatchConsumersAsync: true);
|
||||
|
||||
using var connection = factory.CreateConnection("gateway-paging-worker");
|
||||
using var channel = connection.CreateModel();
|
||||
|
||||
@@ -8,4 +8,11 @@ public sealed class GatewayOptions
|
||||
public int CentralReachabilityIntervalSeconds { get; init; } = 30;
|
||||
public int HeartbeatIntervalSeconds { get; init; } = 60;
|
||||
public int SyncBatchSize { get; init; } = 500;
|
||||
|
||||
/// <summary>
|
||||
/// When true (default), applies EF migrations at startup. The gateway is
|
||||
/// single-instance by design, so startup migration is safe. Set false if
|
||||
/// migrations are applied out-of-band (e.g. an EF migration bundle).
|
||||
/// </summary>
|
||||
public bool AutoMigrate { get; init; } = true;
|
||||
}
|
||||
@@ -8,4 +8,9 @@ public sealed class RabbitMqOptions
|
||||
// Drives both the paging worker poll timeout and the DLQ x-message-ttl.
|
||||
// In production: 300000 (5 min). In tests: 5000 (5 sec).
|
||||
public int PagingAckTimeoutMs { get; init; } = 300000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When true, enables TLS on the AMQP connection (typical production port 5671).
|
||||
/// </summary>
|
||||
public bool UseSsl { get; init; } = false;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,37 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
COPY VigilCareClinical.sln ./
|
||||
COPY VigilCare.ClinicalContracts/VigilCare.ClinicalContracts.csproj VigilCare.ClinicalContracts/
|
||||
COPY VigilCare.WardGateway/VigilCare.WardGateway.csproj VigilCare.WardGateway/
|
||||
RUN dotnet restore VigilCare.WardGateway/VigilCare.WardGateway.csproj
|
||||
|
||||
COPY VigilCare.ClinicalContracts/ VigilCare.ClinicalContracts/
|
||||
COPY VigilCare.WardGateway/ VigilCare.WardGateway/
|
||||
RUN dotnet restore VigilCare.WardGateway/VigilCare.WardGateway.csproj
|
||||
RUN dotnet publish VigilCare.WardGateway/VigilCare.WardGateway.csproj -c Release -o /app/publish --no-restore
|
||||
RUN dotnet publish VigilCare.WardGateway/VigilCare.WardGateway.csproj \
|
||||
-c Release -o /app/publish --no-restore
|
||||
|
||||
# Scrub development secrets (Step 4) — production supplies Jwt / ApiKey via env.
|
||||
RUN sed -i \
|
||||
-e 's/"SigningKey": "[^"]*"/"SigningKey": ""/' \
|
||||
-e 's/"Gateway": "dev-[^"]*"/"Gateway": ""/' \
|
||||
/app/publish/appsettings.json
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=build /app/publish .
|
||||
RUN chown -R app:app /app
|
||||
USER app
|
||||
|
||||
ENV ASPNETCORE_URLS=http://+:8080
|
||||
EXPOSE 8080
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
|
||||
CMD curl -fsS http://localhost:8080/health/live || exit 1
|
||||
|
||||
ENTRYPOINT ["dotnet", "VigilCare.WardGateway.dll"]
|
||||
@@ -11,13 +11,7 @@ public sealed class RabbitMqHealthCheck : IHealthCheck
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(
|
||||
HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var factory = new ConnectionFactory
|
||||
{
|
||||
HostName = _options.Host,
|
||||
Port = _options.Port,
|
||||
UserName = _options.Username,
|
||||
Password = _options.Password
|
||||
};
|
||||
var factory = RabbitMqConnectionFactory.Create(_options);
|
||||
|
||||
using var connection = await Task.Run(() => factory.CreateConnection(), cancellationToken);
|
||||
var data = new Dictionary<string, object> { ["endpoint"] = connection.Endpoint.ToString() };
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using RabbitMQ.Client;
|
||||
|
||||
public static class RabbitMqConnectionFactory
|
||||
{
|
||||
public static ConnectionFactory Create(RabbitMqOptions o, bool dispatchConsumersAsync = false)
|
||||
{
|
||||
var factory = new ConnectionFactory
|
||||
{
|
||||
HostName = o.Host,
|
||||
Port = o.Port,
|
||||
UserName = o.Username,
|
||||
Password = o.Password,
|
||||
DispatchConsumersAsync = dispatchConsumersAsync,
|
||||
};
|
||||
|
||||
if (o.UseSsl)
|
||||
{
|
||||
factory.Ssl = new SslOption
|
||||
{
|
||||
Enabled = true,
|
||||
ServerName = o.Host,
|
||||
};
|
||||
}
|
||||
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
@@ -85,12 +85,6 @@ public sealed class RabbitMqTopologyProvisioner : IHostedService
|
||||
|
||||
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
|
||||
|
||||
public ConnectionFactory BuildFactory() => new()
|
||||
{
|
||||
HostName = _opts.Host,
|
||||
Port = _opts.Port,
|
||||
UserName = _opts.Username,
|
||||
Password = _opts.Password,
|
||||
DispatchConsumersAsync = true,
|
||||
};
|
||||
public ConnectionFactory BuildFactory() =>
|
||||
RabbitMqConnectionFactory.Create(_opts, dispatchConsumersAsync: true);
|
||||
}
|
||||
|
||||
@@ -120,13 +120,15 @@ try
|
||||
ResponseWriter = HealthCheckResponseWriter.WriteAsync
|
||||
});
|
||||
|
||||
if (!app.Environment.IsEnvironment("Testing"))
|
||||
// Startup migration is the proven path for this single-instance edge host.
|
||||
// Gate with Gateway:AutoMigrate so production can switch to an out-of-band
|
||||
// EF migration bundle later without a code change (Phase 36 Step 6).
|
||||
if (!app.Environment.IsEnvironment("Testing")
|
||||
&& builder.Configuration.GetValue("Gateway:AutoMigrate", true))
|
||||
{
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<GatewayDbContext>();
|
||||
await db.Database.MigrateAsync();
|
||||
}
|
||||
using var scope = app.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<GatewayDbContext>();
|
||||
await db.Database.MigrateAsync();
|
||||
}
|
||||
|
||||
app.Run();
|
||||
|
||||
@@ -18,13 +18,7 @@ public sealed class LocalPagingPublisher
|
||||
{
|
||||
try
|
||||
{
|
||||
var factory = new ConnectionFactory
|
||||
{
|
||||
HostName = _opts.Host,
|
||||
Port = _opts.Port,
|
||||
UserName = _opts.Username,
|
||||
Password = _opts.Password
|
||||
};
|
||||
var factory = RabbitMqConnectionFactory.Create(_opts);
|
||||
using var conn = factory.CreateConnection("gateway-publisher");
|
||||
using var channel = conn.CreateModel();
|
||||
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1" />
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.3.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
||||
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Seq" Version="9.1.0" />
|
||||
<PackageReference Include="StackExchange.Redis" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.27" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.27" />
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"Serilog": {
|
||||
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.Seq" ],
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"Microsoft.EntityFrameworkCore.Database.Command": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{ "Name": "Console", "Args": { "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact" } },
|
||||
{ "Name": "Seq", "Args": { "serverUrl": "http://localhost:5341" } }
|
||||
],
|
||||
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
|
||||
"Properties": {
|
||||
"Application": "VigilCare.WardGateway"
|
||||
}
|
||||
},
|
||||
"Gateway": {
|
||||
"AutoMigrate": true
|
||||
},
|
||||
"RabbitMq": {
|
||||
"UseSsl": true
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,8 @@
|
||||
"Port": 5675,
|
||||
"Username": "guest",
|
||||
"Password": "guest",
|
||||
"PagingAckTimeoutMs": 300000
|
||||
"PagingAckTimeoutMs": 300000,
|
||||
"UseSsl": false
|
||||
},
|
||||
"CentralApi": {
|
||||
"BaseUrl": "http://localhost:5270"
|
||||
@@ -22,7 +23,8 @@
|
||||
"EncounterSyncIntervalMinutes": 5,
|
||||
"CentralReachabilityIntervalSeconds": 30,
|
||||
"HeartbeatIntervalSeconds": 60,
|
||||
"SyncBatchSize": 500
|
||||
"SyncBatchSize": 500,
|
||||
"AutoMigrate": true
|
||||
},
|
||||
"ApiKey": {
|
||||
"Gateway": "dev-gateway-key-change-in-production"
|
||||
|
||||
Reference in New Issue
Block a user