Skip to content

Commit a67bbae

Browse files
authored
Identity Lab Launch (#138)
* Identity Lab Launch * feat(identitylab): add production Docker image and deployment configuration * Add More Configuration * Migration and Caddy Arrangements * Improve Image Creation
1 parent dce343d commit a67bbae

6 files changed

Lines changed: 198 additions & 0 deletions

File tree

‎.docker/.env.identitylab.example‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
IDENTITYLAB_IMAGE=ghcr.io/codebeamorg/ultimateauth-identitylab:REPLACE_WITH_SHA
2+
3+
CADDY_PROXY_IP=172.20.0.4
4+
IDENTITYLAB_CONNECTION_STRING=Host=postgres;Port=5432;Database=identitylab;Username=identitylab_user;Password=REPLACE_WITH_PASSWORD
5+
6+
LAB_ADMIN_USERNAME=Ladmin
7+
LAB_ADMIN_EMAIL=REPLACE_WITH_EMAIL
8+
LAB_ADMIN_PASSWORD=REPLACE_WITH_PASSWORD

‎.docker/Dockerfile.identitylab‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Build
2+
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
3+
WORKDIR /src
4+
5+
COPY . .
6+
7+
RUN dotnet restore \
8+
apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/CodeBeam.UltimateAuth.IdentityLab.csproj
9+
10+
RUN dotnet publish \
11+
apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/CodeBeam.UltimateAuth.IdentityLab.csproj \
12+
-c Release \
13+
--no-restore \
14+
-o /app/publish
15+
16+
# Runtime
17+
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
18+
WORKDIR /app
19+
20+
ENV ASPNETCORE_HTTP_PORTS=8080
21+
ENV ASPNETCORE_ENVIRONMENT=Production
22+
23+
COPY --from=build /app/publish .
24+
25+
EXPOSE 8080
26+
27+
USER $APP_UID
28+
29+
ENTRYPOINT ["dotnet", "CodeBeam.UltimateAuth.IdentityLab.dll"]
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
services:
2+
identitylab:
3+
image: ${IDENTITYLAB_IMAGE:?Identity Lab image is required}
4+
container_name: identitylab
5+
restart: unless-stopped
6+
7+
environment:
8+
ASPNETCORE_ENVIRONMENT: Production
9+
ASPNETCORE_HTTP_PORTS: "8080"
10+
11+
ReverseProxy__KnownProxy: ${CADDY_PROXY_IP:?Caddy proxy IP is required}
12+
ConnectionStrings__IdentityLab: ${IDENTITYLAB_CONNECTION_STRING:?Connection string is required}
13+
14+
LabAdmin__Username: ${LAB_ADMIN_USERNAME:?Admin username is required}
15+
LabAdmin__Email: ${LAB_ADMIN_EMAIL:?Admin email is required}
16+
LabAdmin__Password: ${LAB_ADMIN_PASSWORD:?Admin password is required}
17+
18+
DataProtection__KeysPath: /app/keys
19+
20+
volumes:
21+
- /opt/identitylab/keys:/app/keys
22+
23+
networks:
24+
- edge
25+
- postgres-internal
26+
27+
security_opt:
28+
- no-new-privileges:true
29+
30+
networks:
31+
edge:
32+
external: true
33+
34+
postgres-internal:
35+
external: true
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Identity Lab Docker Image
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- "apps/identity-lab/**"
7+
- "src/**"
8+
- "samples/**"
9+
- ".docker/Dockerfile.identitylab"
10+
- ".github/workflows/identitylab-image.yml"
11+
12+
push:
13+
branches:
14+
- master
15+
paths:
16+
- "apps/identity-lab/**"
17+
- "src/**"
18+
- "samples/**"
19+
- ".docker/Dockerfile.identitylab"
20+
- ".github/workflows/identitylab-image.yml"
21+
22+
workflow_dispatch:
23+
24+
permissions:
25+
contents: read
26+
packages: write
27+
28+
jobs:
29+
build:
30+
runs-on: ubuntu-latest
31+
32+
steps:
33+
- name: Checkout
34+
uses: actions/checkout@v4
35+
36+
- name: Set up Docker Buildx
37+
uses: docker/setup-buildx-action@v3
38+
39+
- name: Log in to GHCR
40+
uses: docker/login-action@v3
41+
if: github.event_name != 'pull_request'
42+
with:
43+
registry: ghcr.io
44+
username: ${{ github.actor }}
45+
password: ${{ secrets.GITHUB_TOKEN }}
46+
47+
- name: Build and push Identity Lab
48+
uses: docker/build-push-action@v6
49+
with:
50+
context: .
51+
file: .docker/Dockerfile.identitylab
52+
push: ${{ github.event_name != 'pull_request' }}
53+
tags: ghcr.io/codebeamorg/ultimateauth-identitylab:${{ github.sha }}
54+
cache-from: type=gha
55+
cache-to: type=gha,mode=max
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using CodeBeam.UltimateAuth.EntityFrameworkCore;
2+
using Microsoft.EntityFrameworkCore;
3+
4+
namespace CodeBeam.UltimateAuth.IdentityLab.Persistence;
5+
6+
public static class LabDatabaseMigrator
7+
{
8+
public static async Task MigrateAsync(IServiceProvider services, CancellationToken cancellationToken = default)
9+
{
10+
await using var scope = services.CreateAsyncScope();
11+
12+
var logger = scope.ServiceProvider
13+
.GetRequiredService<ILoggerFactory>()
14+
.CreateLogger("IdentityLab.Migrations");
15+
16+
logger.LogInformation("Starting Identity Lab database migrations.");
17+
18+
var uauthDb = scope.ServiceProvider.GetRequiredService<UAuthDbContext>();
19+
20+
await uauthDb.Database.MigrateAsync(cancellationToken);
21+
22+
logger.LogInformation("UltimateAuth database migrations completed.");
23+
24+
var labDbFactory = scope.ServiceProvider.GetRequiredService<IDbContextFactory<LabDbContext>>();
25+
26+
await using var labDb = await labDbFactory.CreateDbContextAsync(cancellationToken);
27+
28+
await labDb.Database.MigrateAsync(cancellationToken);
29+
30+
logger.LogInformation("Identity Lab database migrations completed.");
31+
}
32+
}

‎apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Program.cs‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
using CodeBeam.UltimateAuth.IdentityLab.Services;
1717
using CodeBeam.UltimateAuth.Sample.Shared.Infrastructure;
1818
using CodeBeam.UltimateAuth.Server.Extensions;
19+
using Microsoft.AspNetCore.DataProtection;
20+
using Microsoft.AspNetCore.HttpOverrides;
1921
using Microsoft.EntityFrameworkCore;
2022
using MudBlazor.Services;
2123
using MudExtensions.Services;
@@ -108,6 +110,36 @@
108110
builder.Services.AddScoped<ISeedContributor, LabDemoRoleSeedContributor>();
109111
builder.Services.AddLogging();
110112

113+
builder.Services.Configure<ForwardedHeadersOptions>(options =>
114+
{
115+
options.ForwardedHeaders =
116+
ForwardedHeaders.XForwardedFor |
117+
ForwardedHeaders.XForwardedProto;
118+
119+
if (builder.Environment.IsProduction())
120+
{
121+
var trustedProxy = builder.Configuration["ReverseProxy:KnownProxy"]
122+
?? throw new InvalidOperationException("ReverseProxy:KnownProxy must be configured in production.");
123+
124+
if (!System.Net.IPAddress.TryParse(trustedProxy, out var proxyAddress))
125+
{
126+
throw new InvalidOperationException("ReverseProxy:KnownProxy must be a valid IP address.");
127+
}
128+
129+
options.KnownProxies.Add(proxyAddress);
130+
}
131+
});
132+
133+
if (builder.Environment.IsProduction())
134+
{
135+
var keysPath = builder.Configuration["DataProtection:KeysPath"]
136+
?? throw new InvalidOperationException("DataProtection:KeysPath must be configured in production.");
137+
138+
builder.Services.AddDataProtection()
139+
.SetApplicationName("UltimateAuth.IdentityLab")
140+
.PersistKeysToFileSystem(new DirectoryInfo(keysPath));
141+
}
142+
111143
var app = builder.Build();
112144

113145
activityEventBridge.Initialize(app.Services);
@@ -120,6 +152,7 @@
120152
app.UseHsts();
121153
}
122154
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
155+
app.UseForwardedHeaders();
123156
app.UseHttpsRedirection();
124157

125158
app.UseUltimateAuthWithAspNetCore();
@@ -133,6 +166,12 @@
133166
.AddInteractiveServerRenderMode()
134167
.AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient());
135168

169+
if (args.Contains("--migrate", StringComparer.OrdinalIgnoreCase))
170+
{
171+
await LabDatabaseMigrator.MigrateAsync(app.Services);
172+
return;
173+
}
174+
136175
await app.SeedIdentityLabAsync();
137176

138177
app.Run();

0 commit comments

Comments
 (0)