2021-04-29 15:10:34 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
2022-04-01 12:04:32 -04:00
using System.Diagnostics ;
2021-04-29 15:10:34 -04:00
using System.IO ;
using System.Reflection ;
using System.Runtime.InteropServices ;
using System.Threading.Tasks ;
using EpicGames.Core ;
2022-03-23 14:50:23 -04:00
using Horde.Build.Commands ;
2021-04-29 15:10:34 -04:00
using Microsoft.Extensions.Configuration ;
2022-03-23 14:50:23 -04:00
using Microsoft.Extensions.DependencyInjection ;
2021-04-29 15:10:34 -04:00
using Microsoft.Extensions.Hosting ;
2021-08-08 09:07:38 -04:00
using OpenTracing ;
2022-03-23 14:50:23 -04:00
using OpenTracing.Propagation ;
2021-08-07 13:08:14 -04:00
using OpenTracing.Util ;
2021-04-29 15:10:34 -04:00
using Serilog ;
using Serilog.Configuration ;
2021-08-08 09:07:38 -04:00
using Serilog.Core ;
2021-04-29 15:10:34 -04:00
using Serilog.Events ;
using Serilog.Formatting.Json ;
using Serilog.Sinks.SystemConsole.Themes ;
2022-03-16 11:18:39 -04:00
namespace Horde.Build
2021-04-29 15:10:34 -04:00
{
static class LoggerExtensions
{
2022-03-23 14:50:23 -04:00
public static LoggerConfiguration Console ( this LoggerSinkConfiguration sinkConfig , ServerSettings settings )
2021-04-29 15:10:34 -04:00
{
2022-03-23 14:50:23 -04:00
if ( settings . LogJsonToStdOut )
2021-04-29 15:10:34 -04:00
{
2022-03-23 14:50:23 -04:00
return sinkConfig . Console ( new JsonFormatter ( renderMessage : true ) ) ;
2021-04-29 15:10:34 -04:00
}
else
{
2022-03-23 14:50:23 -04:00
ConsoleTheme theme ;
2021-04-29 15:10:34 -04:00
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) & & Environment . OSVersion . Version < new Version ( 10 , 0 ) )
{
2022-03-23 14:50:23 -04:00
theme = SystemConsoleTheme . Literate ;
2021-04-29 15:10:34 -04:00
}
else
{
2022-03-23 14:50:23 -04:00
theme = AnsiConsoleTheme . Code ;
2021-04-29 15:10:34 -04:00
}
2022-03-23 14:50:23 -04:00
return sinkConfig . Console ( outputTemplate : "[{Timestamp:HH:mm:ss} {Level:w3}] {Indent}{Message:l}{NewLine}{Exception}" , theme : theme , restrictedToMinimumLevel : LogEventLevel . Debug ) ;
2021-04-29 15:10:34 -04:00
}
}
2021-07-22 21:41:56 -04:00
2022-03-23 14:50:23 -04:00
public static LoggerConfiguration WithHordeConfig ( this LoggerConfiguration configuration , ServerSettings settings )
2021-07-22 21:41:56 -04:00
{
2022-03-23 14:50:23 -04:00
if ( settings . WithDatadog )
2021-08-31 13:34:39 -04:00
{
2022-03-23 14:50:23 -04:00
configuration = configuration . Enrich . With < DatadogLogEnricher > ( ) ;
2021-08-31 13:34:39 -04:00
}
2022-03-23 14:50:23 -04:00
return configuration ;
2021-07-22 21:41:56 -04:00
}
2021-04-29 15:10:34 -04:00
}
2021-08-08 09:07:38 -04:00
class DatadogLogEnricher : ILogEventEnricher
{
2022-03-23 14:50:23 -04:00
public void Enrich ( Serilog . Events . LogEvent logEvent , ILogEventPropertyFactory propertyFactory )
2021-08-08 09:07:38 -04:00
{
2022-03-23 14:50:23 -04:00
ISpan ? span = GlobalTracer . Instance ? . ActiveSpan ;
if ( span ! = null )
2021-08-08 09:07:38 -04:00
{
2022-03-23 14:50:23 -04:00
logEvent . AddPropertyIfAbsent ( propertyFactory . CreateProperty ( "dd.trace_id" , span . Context . TraceId ) ) ;
logEvent . AddPropertyIfAbsent ( propertyFactory . CreateProperty ( "dd.span_id" , span . Context . SpanId ) ) ;
2021-08-08 09:07:38 -04:00
}
}
}
2021-10-13 11:35:32 -04:00
class TestTracer : ITracer
{
2022-03-23 14:50:23 -04:00
readonly ITracer _inner ;
2021-10-13 11:35:32 -04:00
2022-03-23 14:50:23 -04:00
public TestTracer ( ITracer inner )
2021-10-13 11:35:32 -04:00
{
2022-03-23 14:50:23 -04:00
_inner = inner ;
2021-10-13 11:35:32 -04:00
}
2022-03-23 14:50:23 -04:00
public IScopeManager ScopeManager = > _inner . ScopeManager ;
2021-10-13 11:35:32 -04:00
2022-03-23 14:50:23 -04:00
public ISpan ActiveSpan = > _inner . ActiveSpan ;
2021-10-13 11:35:32 -04:00
public ISpanBuilder BuildSpan ( string operationName )
{
Serilog . Log . Debug ( "Creating span {Name}" , operationName ) ;
2022-03-23 14:50:23 -04:00
return _inner . BuildSpan ( operationName ) ;
2021-10-13 11:35:32 -04:00
}
public ISpanContext Extract < TCarrier > ( IFormat < TCarrier > format , TCarrier carrier )
{
2022-03-23 14:50:23 -04:00
return _inner . Extract < TCarrier > ( format , carrier ) ;
2021-10-13 11:35:32 -04:00
}
public void Inject < TCarrier > ( ISpanContext spanContext , IFormat < TCarrier > format , TCarrier carrier )
{
2022-03-23 14:50:23 -04:00
_inner . Inject < TCarrier > ( spanContext , format , carrier ) ;
2021-10-13 11:35:32 -04:00
}
}
2021-04-29 15:10:34 -04:00
class Program
{
2022-04-01 12:04:32 -04:00
public static SemVer Version = > _version ;
2021-06-03 23:22:04 -04:00
public static DirectoryReference AppDir { get ; } = GetAppDir ( ) ;
public static DirectoryReference DataDir { get ; } = GetDefaultDataDir ( ) ;
2021-06-03 16:16:09 -04:00
public static FileReference UserConfigFile { get ; } = FileReference . Combine ( GetDefaultDataDir ( ) , "Horde.json" ) ;
2021-06-02 15:16:14 -04:00
public static Type [ ] ConfigSchemas = FindSchemaTypes ( ) ;
2022-04-01 12:04:32 -04:00
static SemVer _version ;
2021-06-02 15:16:14 -04:00
static Type [ ] FindSchemaTypes ( )
2021-06-02 09:39:20 -04:00
{
2022-03-23 14:50:23 -04:00
List < Type > schemaTypes = new List < Type > ( ) ;
foreach ( Type type in Assembly . GetExecutingAssembly ( ) . GetTypes ( ) )
2021-06-02 15:16:14 -04:00
{
2022-03-23 14:50:23 -04:00
if ( type . GetCustomAttribute < JsonSchemaAttribute > ( ) ! = null )
2021-06-02 15:16:14 -04:00
{
2022-03-23 14:50:23 -04:00
schemaTypes . Add ( type ) ;
2021-06-02 15:16:14 -04:00
}
}
2022-03-23 14:50:23 -04:00
return schemaTypes . ToArray ( ) ;
2021-06-02 15:16:14 -04:00
}
2021-06-02 09:39:20 -04:00
2022-03-23 14:50:23 -04:00
public static async Task < int > Main ( string [ ] args )
2021-04-29 15:10:34 -04:00
{
2022-04-01 12:04:32 -04:00
FileVersionInfo versionInfo = FileVersionInfo . GetVersionInfo ( Assembly . GetExecutingAssembly ( ) . Location ) ;
if ( String . IsNullOrEmpty ( versionInfo . ProductVersion ) )
{
_version = SemVer . Parse ( "0.0.0" ) ;
}
else
{
_version = SemVer . Parse ( versionInfo . ProductVersion ) ;
}
2022-03-23 14:50:23 -04:00
CommandLineArguments arguments = new CommandLineArguments ( args ) ;
2021-06-02 09:39:20 -04:00
2022-03-23 14:50:23 -04:00
IConfiguration config = new ConfigurationBuilder ( )
2021-04-29 15:10:34 -04:00
. SetBasePath ( AppDir . FullName )
2021-08-19 13:39:38 -04:00
. AddJsonFile ( "appsettings.json" , optional : false )
. AddJsonFile ( "appsettings.Build.json" , optional : true ) // specific settings for builds (installer/dockerfile)
. AddJsonFile ( $"appsettings.{Environment.GetEnvironmentVariable(" ASPNETCORE_ENVIRONMENT ")}.json" , optional : true ) // environment variable overrides, also used in k8s setups with Helm
2021-05-11 13:12:19 -04:00
. AddJsonFile ( "appsettings.User.json" , optional : true )
2021-08-19 13:39:38 -04:00
. AddJsonFile ( UserConfigFile . FullName , optional : true , reloadOnChange : true )
2021-04-29 15:10:34 -04:00
. AddEnvironmentVariables ( )
. Build ( ) ;
2022-03-23 14:50:23 -04:00
ServerSettings hordeSettings = new ServerSettings ( ) ;
config . GetSection ( "Horde" ) . Bind ( hordeSettings ) ;
2021-04-29 15:10:34 -04:00
2022-03-23 14:50:23 -04:00
InitializeDefaults ( hordeSettings ) ;
2021-08-19 13:39:38 -04:00
2022-03-23 14:50:23 -04:00
DirectoryReference logDir = AppDir ;
2021-06-03 19:11:00 -04:00
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
{
2022-03-23 14:50:23 -04:00
logDir = DirectoryReference . Combine ( DataDir ) ;
2021-06-03 19:11:00 -04:00
}
2021-05-13 21:01:59 -04:00
Serilog . Log . Logger = new LoggerConfiguration ( )
2022-03-23 14:50:23 -04:00
. WithHordeConfig ( hordeSettings )
2021-04-29 15:10:34 -04:00
. Enrich . FromLogContext ( )
2022-03-23 14:50:23 -04:00
. WriteTo . Console ( hordeSettings )
. WriteTo . File ( Path . Combine ( logDir . FullName , "Log.txt" ) , outputTemplate : "[{Timestamp:HH:mm:ss} {Level:w3}] {Indent}{Message:l}{NewLine}{Exception} [{SourceContext}]" , rollingInterval : RollingInterval . Day , rollOnFileSizeLimit : true , fileSizeLimitBytes : 20 * 1024 * 1024 , retainedFileCountLimit : 10 )
. WriteTo . File ( new JsonFormatter ( renderMessage : true ) , Path . Combine ( logDir . FullName , "Log.json" ) , rollingInterval : RollingInterval . Day , rollOnFileSizeLimit : true , fileSizeLimitBytes : 20 * 1024 * 1024 , retainedFileCountLimit : 10 )
. ReadFrom . Configuration ( config )
2021-04-29 15:10:34 -04:00
. CreateLogger ( ) ;
2022-04-01 12:04:32 -04:00
Serilog . Log . Logger . Information ( "Server version: {Version}" , Version ) ;
2022-03-23 14:50:23 -04:00
if ( hordeSettings . WithDatadog )
2021-09-16 09:59:18 -04:00
{
2021-12-10 15:34:07 -05:00
GlobalTracer . Register ( Datadog . Trace . OpenTracing . OpenTracingTracerFactory . WrapTracer ( Datadog . Trace . Tracer . Instance ) ) ;
2021-10-13 11:35:32 -04:00
Serilog . Log . Logger . Information ( "Enabling datadog tracing (OpenTrace)" ) ;
2021-09-16 09:59:18 -04:00
}
2022-03-23 14:50:23 -04:00
IServiceCollection services = new ServiceCollection ( ) ;
services . AddCommandsFromAssembly ( Assembly . GetExecutingAssembly ( ) ) ;
services . AddLogging ( builder = > builder . AddSerilog ( ) ) ;
services . AddSingleton < IConfiguration > ( config ) ;
services . AddSingleton < ServerSettings > ( hordeSettings ) ;
2021-06-02 09:39:20 -04:00
2022-02-27 12:53:25 -05:00
#pragma warning disable ASP0000 // Do not call 'IServiceCollection.BuildServiceProvider' in 'ConfigureServices'
2022-03-23 14:50:23 -04:00
IServiceProvider serviceProvider = services . BuildServiceProvider ( ) ;
return await CommandHost . RunAsync ( arguments , serviceProvider , typeof ( ServerCommand ) ) ;
2022-02-27 12:53:25 -05:00
#pragma warning restore ASP0000 // Do not call 'IServiceCollection.BuildServiceProvider' in 'ConfigureServices'
2021-04-29 15:10:34 -04:00
}
2021-06-03 15:50:39 -04:00
2022-02-28 11:09:00 -05:00
// Used by WebApplicationFactory in controller tests. Uses reflection to call this exact function signature.
2022-03-23 14:50:23 -04:00
public static IHostBuilder CreateHostBuilder ( string [ ] args ) = > ServerCommand . CreateHostBuilderForTesting ( args ) ;
2022-02-28 11:09:00 -05:00
2021-06-03 23:22:04 -04:00
/// <summary>
/// Get the application directory
/// </summary>
/// <returns></returns>
static DirectoryReference GetAppDir ( )
{
return new FileReference ( Assembly . GetExecutingAssembly ( ) . Location ) . Directory ;
}
2021-06-03 15:50:39 -04:00
/// <summary>
/// Gets the default directory for storing application data
/// </summary>
/// <returns>The default data directory</returns>
2021-06-03 16:16:09 -04:00
static DirectoryReference GetDefaultDataDir ( )
2021-06-03 15:50:39 -04:00
{
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
{
2022-03-23 14:50:23 -04:00
DirectoryReference ? dir = DirectoryReference . GetSpecialFolder ( Environment . SpecialFolder . CommonApplicationData ) ;
if ( dir ! = null )
2021-06-03 15:50:39 -04:00
{
2022-03-23 14:50:23 -04:00
return DirectoryReference . Combine ( dir , "HordeServer" ) ;
2021-06-03 15:50:39 -04:00
}
}
2021-06-03 23:22:04 -04:00
return DirectoryReference . Combine ( GetAppDir ( ) , "Data" ) ;
2021-06-03 15:50:39 -04:00
}
2021-08-19 13:39:38 -04:00
2021-09-01 13:04:36 -04:00
/// <summary>
/// Handles bootstrapping of defaults for local servers, which can't be generated during build/installation process (or are better handled here where they can be updated)
/// This stuff will change as we get settings into database and could be considered discovery for installer/dockerfile builds
/// </summary>
2022-03-23 14:50:23 -04:00
static void InitializeDefaults ( ServerSettings settings )
2021-09-01 13:04:36 -04:00
{
2022-03-23 14:50:23 -04:00
if ( settings . SingleInstance )
2021-08-19 13:39:38 -04:00
{
2022-03-23 14:50:23 -04:00
FileReference globalConfig = FileReference . Combine ( Program . DataDir , "Config/globals.json" ) ;
2021-08-19 13:39:38 -04:00
2022-03-23 14:50:23 -04:00
if ( ! FileReference . Exists ( globalConfig ) )
2021-08-19 13:39:38 -04:00
{
2022-03-23 14:50:23 -04:00
DirectoryReference . CreateDirectory ( globalConfig . Directory ) ;
FileReference . WriteAllText ( globalConfig , "{}" ) ;
2021-08-19 13:39:38 -04:00
}
2022-03-23 14:50:23 -04:00
FileReference privateCertFile = FileReference . Combine ( Program . DataDir , "Agent/ServerToAgent.pfx" ) ;
string privateCertFileJsonPath = privateCertFile . ToString ( ) . Replace ( "\\" , "/" , StringComparison . Ordinal ) ;
2021-08-19 13:39:38 -04:00
2021-09-01 13:04:36 -04:00
if ( ! FileReference . Exists ( UserConfigFile ) )
{
// create new user configuration
DirectoryReference . CreateDirectory ( UserConfigFile . Directory ) ;
2022-03-23 14:50:23 -04:00
FileReference . WriteAllText ( UserConfigFile , $"{{\" Horde \ ": {{ \"ConfigPath\" : \"{globalConfig.ToString().Replace(" \ \ ", " / ", StringComparison.Ordinal)}\", \"ServerPrivateCert\" : \"{privateCertFileJsonPath}\", \"HttpPort\": 8080}}}}" ) ;
2021-09-01 13:04:36 -04:00
}
// make sure the cert exists
2022-03-23 14:50:23 -04:00
if ( ! FileReference . Exists ( privateCertFile ) )
2021-09-01 13:04:36 -04:00
{
2022-03-23 14:50:23 -04:00
string dnsName = System . Net . Dns . GetHostName ( ) ;
Serilog . Log . Logger . Information ( "Creating certificate for {DnsName}" , dnsName ) ;
2021-09-01 13:04:36 -04:00
2022-03-23 14:50:23 -04:00
byte [ ] privateCertData = CertificateUtils . CreateSelfSignedCert ( dnsName , "Horde Server" ) ;
2021-09-01 13:04:36 -04:00
2022-03-23 14:50:23 -04:00
Serilog . Log . Logger . Information ( "Writing private cert: {PrivateCert}" , privateCertFile . FullName ) ;
2021-09-01 13:04:36 -04:00
2022-03-23 14:50:23 -04:00
if ( ! DirectoryReference . Exists ( privateCertFile . Directory ) )
2021-09-01 13:04:36 -04:00
{
2022-03-23 14:50:23 -04:00
DirectoryReference . CreateDirectory ( privateCertFile . Directory ) ;
2021-09-01 13:04:36 -04:00
}
2022-03-23 14:50:23 -04:00
FileReference . WriteAllBytes ( privateCertFile , privateCertData ) ;
2021-09-01 13:04:36 -04:00
}
// note: this isn't great, though we need it early in server startup, and this is only hit on first server boot where the grpc cert isn't generated/set
2022-03-23 14:50:23 -04:00
if ( settings . ServerPrivateCert = = null )
2021-09-01 13:04:36 -04:00
{
2022-03-23 14:50:23 -04:00
settings . ServerPrivateCert = privateCertFile . ToString ( ) ;
2021-09-01 13:04:36 -04:00
}
}
2021-08-19 13:39:38 -04:00
}
2021-04-29 15:10:34 -04:00
}
}