diff --git a/src/Accounts/Hearth.Accounts.Contracts/Hearth.Accounts.Contracts.csproj b/src/Accounts/Hearth.Accounts.Contracts/Hearth.Accounts.Contracts.csproj
new file mode 100644
index 0000000..61718a1
--- /dev/null
+++ b/src/Accounts/Hearth.Accounts.Contracts/Hearth.Accounts.Contracts.csproj
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/Accounts/Hearth.Accounts/Hearth.Accounts.csproj b/src/Accounts/Hearth.Accounts/Hearth.Accounts.csproj
new file mode 100644
index 0000000..6032ef1
--- /dev/null
+++ b/src/Accounts/Hearth.Accounts/Hearth.Accounts.csproj
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/src/Characters/Hearth.Characters.Contracts/Hearth.Characters.Contracts.csproj b/src/Characters/Hearth.Characters.Contracts/Hearth.Characters.Contracts.csproj
new file mode 100644
index 0000000..61718a1
--- /dev/null
+++ b/src/Characters/Hearth.Characters.Contracts/Hearth.Characters.Contracts.csproj
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/Characters/Hearth.Characters/Hearth.Characters.csproj b/src/Characters/Hearth.Characters/Hearth.Characters.csproj
new file mode 100644
index 0000000..52528c3
--- /dev/null
+++ b/src/Characters/Hearth.Characters/Hearth.Characters.csproj
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/src/Commons/Hearth.Commons/Hearth.Commons.csproj b/src/Commons/Hearth.Commons/Hearth.Commons.csproj
new file mode 100644
index 0000000..61718a1
--- /dev/null
+++ b/src/Commons/Hearth.Commons/Hearth.Commons.csproj
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
new file mode 100644
index 0000000..3de9aa2
--- /dev/null
+++ b/src/Directory.Build.props
@@ -0,0 +1,10 @@
+
+
+
+ net10.0
+ enable
+ enable
+ true
+
+
+
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
new file mode 100644
index 0000000..51ec396
--- /dev/null
+++ b/src/Directory.Packages.props
@@ -0,0 +1,15 @@
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/External/Hearth.External/Hearth.External.csproj b/src/External/Hearth.External/Hearth.External.csproj
new file mode 100644
index 0000000..61718a1
--- /dev/null
+++ b/src/External/Hearth.External/Hearth.External.csproj
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/Guilds/Hearth.Guilds.Contracts/Hearth.Guilds.Contracts.csproj b/src/Guilds/Hearth.Guilds.Contracts/Hearth.Guilds.Contracts.csproj
new file mode 100644
index 0000000..61718a1
--- /dev/null
+++ b/src/Guilds/Hearth.Guilds.Contracts/Hearth.Guilds.Contracts.csproj
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/Guilds/Hearth.Guilds/Hearth.Guilds.csproj b/src/Guilds/Hearth.Guilds/Hearth.Guilds.csproj
new file mode 100644
index 0000000..cbf5b0a
--- /dev/null
+++ b/src/Guilds/Hearth.Guilds/Hearth.Guilds.csproj
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/src/Hearth.slnx b/src/Hearth.slnx
new file mode 100644
index 0000000..19a0f6f
--- /dev/null
+++ b/src/Hearth.slnx
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Hosts/Hearth.Api/Hearth.Api.csproj b/src/Hosts/Hearth.Api/Hearth.Api.csproj
new file mode 100644
index 0000000..49c94d3
--- /dev/null
+++ b/src/Hosts/Hearth.Api/Hearth.Api.csproj
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Hosts/Hearth.Api/Program.cs b/src/Hosts/Hearth.Api/Program.cs
new file mode 100644
index 0000000..fa5c301
--- /dev/null
+++ b/src/Hosts/Hearth.Api/Program.cs
@@ -0,0 +1,11 @@
+using Hearth.Hosting;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.AddHostDefaults();
+
+var app = builder.Build();
+
+app.MapHostEndpoints();
+
+app.Run();
diff --git a/src/Hosts/Hearth.Api/Properties/launchSettings.json b/src/Hosts/Hearth.Api/Properties/launchSettings.json
new file mode 100644
index 0000000..804e684
--- /dev/null
+++ b/src/Hosts/Hearth.Api/Properties/launchSettings.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "launchBrowser": false,
+ "applicationUrl": "http://localhost:5080",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/src/Hosts/Hearth.Api/appsettings.json b/src/Hosts/Hearth.Api/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/src/Hosts/Hearth.Api/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/src/Hosts/Hearth.AppHost/AppHost.cs b/src/Hosts/Hearth.AppHost/AppHost.cs
new file mode 100644
index 0000000..94e40ed
--- /dev/null
+++ b/src/Hosts/Hearth.AppHost/AppHost.cs
@@ -0,0 +1,5 @@
+var builder = DistributedApplication.CreateBuilder(args);
+
+builder.AddProject("api");
+
+builder.Build().Run();
diff --git a/src/Hosts/Hearth.AppHost/Hearth.AppHost.csproj b/src/Hosts/Hearth.AppHost/Hearth.AppHost.csproj
new file mode 100644
index 0000000..1c0bb5c
--- /dev/null
+++ b/src/Hosts/Hearth.AppHost/Hearth.AppHost.csproj
@@ -0,0 +1,13 @@
+
+
+
+ Exe
+ true
+ 388fbdf7-df04-4c75-858e-1bdc720d2a91
+
+
+
+
+
+
+
diff --git a/src/Hosts/Hearth.AppHost/Properties/launchSettings.json b/src/Hosts/Hearth.AppHost/Properties/launchSettings.json
new file mode 100644
index 0000000..f0e1b3d
--- /dev/null
+++ b/src/Hosts/Hearth.AppHost/Properties/launchSettings.json
@@ -0,0 +1,30 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "https://localhost:17277;http://localhost:15247",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DOTNET_ENVIRONMENT": "Development",
+ "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21265",
+ "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22059"
+ }
+ },
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "http://localhost:15247",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DOTNET_ENVIRONMENT": "Development",
+ "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19112",
+ "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20192",
+ "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true"
+ }
+ }
+ }
+}
diff --git a/src/Hosts/Hearth.AppHost/appsettings.json b/src/Hosts/Hearth.AppHost/appsettings.json
new file mode 100644
index 0000000..31c092a
--- /dev/null
+++ b/src/Hosts/Hearth.AppHost/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "Aspire.Hosting.Dcp": "Warning"
+ }
+ }
+}
diff --git a/src/Hosts/Hearth.AppHost/aspire.config.json b/src/Hosts/Hearth.AppHost/aspire.config.json
new file mode 100644
index 0000000..e03be6b
--- /dev/null
+++ b/src/Hosts/Hearth.AppHost/aspire.config.json
@@ -0,0 +1,5 @@
+{
+ "appHost": {
+ "path": "Hearth.AppHost.csproj"
+ }
+}
diff --git a/src/Hosts/Hearth.Hosting/Hearth.Hosting.csproj b/src/Hosts/Hearth.Hosting/Hearth.Hosting.csproj
new file mode 100644
index 0000000..4380514
--- /dev/null
+++ b/src/Hosts/Hearth.Hosting/Hearth.Hosting.csproj
@@ -0,0 +1,21 @@
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Hosts/Hearth.Hosting/HostingExtensions.cs b/src/Hosts/Hearth.Hosting/HostingExtensions.cs
new file mode 100644
index 0000000..2b6658c
--- /dev/null
+++ b/src/Hosts/Hearth.Hosting/HostingExtensions.cs
@@ -0,0 +1,85 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Diagnostics.HealthChecks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using OpenTelemetry;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Trace;
+
+namespace Hearth.Hosting;
+
+// Setup every Hearth host needs the same way: logging and telemetry, health endpoints, service discovery and HTTP resilience.
+// Only hosts reference this project; anything a single host or a module needs belongs elsewhere.
+public static class HostingExtensions
+{
+ private const string HealthEndpointPath = "/health";
+ private const string AlivenessEndpointPath = "/alive";
+ private const string LiveTag = "live";
+
+ public static TBuilder AddHostDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder
+ {
+ builder.ConfigureOpenTelemetry();
+ builder.AddDefaultHealthChecks();
+
+ builder.Services.AddServiceDiscovery();
+ builder.Services.ConfigureHttpClientDefaults(http =>
+ {
+ http.AddStandardResilienceHandler();
+ http.AddServiceDiscovery();
+ });
+
+ return builder;
+ }
+
+ public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder
+ {
+ builder.Logging.AddOpenTelemetry(logging =>
+ {
+ logging.IncludeFormattedMessage = true;
+ logging.IncludeScopes = true;
+ });
+
+ var openTelemetry = builder.Services
+ .AddOpenTelemetry()
+ .WithMetrics(metrics => metrics
+ .AddAspNetCoreInstrumentation()
+ .AddHttpClientInstrumentation()
+ .AddRuntimeInstrumentation())
+ .WithTracing(tracing => tracing
+ .AddSource(builder.Environment.ApplicationName)
+ .AddAspNetCoreInstrumentation(options => options.Filter = context =>
+ !context.Request.Path.StartsWithSegments(HealthEndpointPath)
+ && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath))
+ .AddHttpClientInstrumentation());
+
+ if (!string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]))
+ {
+ openTelemetry.UseOtlpExporter();
+ }
+
+ return builder;
+ }
+
+ public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder
+ {
+ builder.Services
+ .AddHealthChecks()
+ .AddCheck("self", () => HealthCheckResult.Healthy(), [LiveTag]);
+
+ return builder;
+ }
+
+ // Mapped in every environment, unlike the Aspire template: the Kubernetes probes need them in production.
+ public static WebApplication MapHostEndpoints(this WebApplication app)
+ {
+ app.MapHealthChecks(HealthEndpointPath);
+ app.MapHealthChecks(
+ AlivenessEndpointPath,
+ new HealthCheckOptions { Predicate = registration => registration.Tags.Contains(LiveTag) }
+ );
+
+ return app;
+ }
+}
diff --git a/src/global.json b/src/global.json
new file mode 100644
index 0000000..1d364c6
--- /dev/null
+++ b/src/global.json
@@ -0,0 +1,9 @@
+{
+ "sdk": {
+ "version": "10.0.100",
+ "rollForward": "latestFeature"
+ },
+ "test": {
+ "runner": "Microsoft.Testing.Platform"
+ }
+}