Example #1
0
        private static void GenerateX509Certificate(string password, string filename)
        {
            Log.Information("Generating X509 certificate...");
            filename = Path.Combine(AppContext.BaseDirectory, filename);

            var cert = X509.Generate(subject: AppName, password, X509KeyStorageFlags.Exportable);

            IOFile.WriteAllBytes(filename, cert.Export(X509ContentType.Pkcs12, password));

            Log.Information($"Password: {password}");
            Log.Information($"Certificate exported to {filename}");
        }
Example #2
0
        /// <summary>
        ///     Entrypoint.
        /// </summary>
        /// <param name="args">Command line arguments.</param>
        public static void Main(string[] args)
        {
            // populate the properties above so that we can override the default config file if needed, and to
            // check if the application is being run in command mode (run task and quit).
            EnvironmentVariables.Populate(prefix: EnvironmentVariablePrefix);
            Arguments.Populate(clearExistingValues: false);

            // if a user has used one of the arguments above, perform the requested task, then quit
            if (ShowVersion)
            {
                Log.Information(FullVersion);
                return;
            }

            if (ShowHelp || ShowEnvironmentVariables)
            {
                if (!NoLogo)
                {
                    PrintLogo(FullVersion);
                }

                if (ShowHelp)
                {
                    PrintCommandLineArguments(typeof(Options));
                }

                if (ShowEnvironmentVariables)
                {
                    PrintEnvironmentVariables(typeof(Options), EnvironmentVariablePrefix);
                }

                return;
            }

            if (GenerateCertificate)
            {
                GenerateX509Certificate(password: Cryptography.Random.GetBytes(16).ToBase62String(), filename: $"{AppName}.pfx");
                return;
            }

            // the application isn't being run in command mode. derive the application directory value
            // and defaults that are dependent upon it
            AppDirectory ??= DefaultAppDirectory;

            DefaultConfigurationFile   = Path.Combine(AppDirectory, $"{AppName}.yml");
            DefaultDownloadsDirectory  = Path.Combine(AppDirectory, "downloads");
            DefaultIncompleteDirectory = Path.Combine(AppDirectory, "incomplete");

            // the location of the configuration file might have been overriden by command line or envar.
            // if not, set it to the default.
            ConfigurationFile ??= DefaultConfigurationFile;

            // verify(create if needed) default application directories. if the downloads or complete
            // directories are overridden in config, those will be validated after the config is loaded.
            try
            {
                VerifyDirectory(AppDirectory, createIfMissing: true, verifyWriteable: true);
                VerifyDirectory(Path.Combine(AppDirectory, "data"), createIfMissing: true, verifyWriteable: true);
                VerifyDirectory(DefaultDownloadsDirectory, createIfMissing: true, verifyWriteable: true);
                VerifyDirectory(DefaultIncompleteDirectory, createIfMissing: true, verifyWriteable: true);
            }
            catch (Exception ex)
            {
                Log.Information($"Filesystem exception: {ex.Message}");
                return;
            }

            // load and validate the configuration
            try
            {
                Configuration = new ConfigurationBuilder()
                                .AddConfigurationProviders(EnvironmentVariablePrefix, ConfigurationFile)
                                .Build();

                Configuration.GetSection(AppName)
                .Bind(OptionsAtStartup, (o) => { o.BindNonPublicProperties = true; });

                if (OptionsAtStartup.Debug)
                {
                    Log.Information($"Configuration:\n{Configuration.GetDebugView()}");
                }

                if (!OptionsAtStartup.TryValidate(out var result))
                {
                    Log.Information(result.GetResultView());
                    return;
                }
            }
            catch (Exception ex)
            {
                Log.Information($"Invalid configuration: {(!OptionsAtStartup.Debug ? ex : ex.Message)}");
                return;
            }

            ConfigureGlobalLogger();
            Log = Serilog.Log.ForContext(typeof(Program));

            if (!OptionsAtStartup.Flags.NoLogo)
            {
                PrintLogo(FullVersion);
            }

            Log.Information("Version: {Version}", FullVersion);

            if (IsDevelopment)
            {
                Log.Warning("This is a Development build; YMMV");
            }

            if (IsCanary)
            {
                Log.Warning("This is a canary build");
                Log.Warning("Canary builds are considered UNSTABLE and may be completely BROKEN");
                Log.Warning($"Please report any issues here: {IssuesUrl}");
            }

            Log.Information("Invocation ID: {InvocationId}", InvocationId);
            Log.Information("Process ID: {ProcessId}", ProcessId);
            Log.Information("Instance Name: {InstanceName}", OptionsAtStartup.InstanceName);

            Log.Information("Using application directory {AppDirectory}", AppDirectory);
            Log.Information("Using configuration file {ConfigurationFile}", ConfigurationFile);

            RecreateConfigurationFileIfMissing(ConfigurationFile);

            if (!string.IsNullOrEmpty(OptionsAtStartup.Logger.Loki))
            {
                Log.Information("Forwarding logs to Grafana Loki instance at {LoggerLokiUrl}", OptionsAtStartup.Logger.Loki);
            }

            // bootstrap the ASP.NET application
            try
            {
                if (OptionsAtStartup.Feature.Prometheus)
                {
                    using var runtimeMetrics = DotNetRuntimeStatsBuilder.Default().StartCollecting();
                }

                var builder = WebApplication.CreateBuilder(args);

                builder.Configuration
                .AddConfigurationProviders(EnvironmentVariablePrefix, ConfigurationFile);

                builder.Host
                .UseSerilog();

                builder.WebHost
                .UseUrls()
                .UseKestrel(options =>
                {
                    Log.Information($"Listening for HTTP requests at http://{IPAddress.Any}:{OptionsAtStartup.Web.Port}/");
                    options.Listen(IPAddress.Any, OptionsAtStartup.Web.Port);

                    if (!OptionsAtStartup.Web.Https.Disabled)
                    {
                        Log.Information($"Listening for HTTPS requests at https://{IPAddress.Any}:{OptionsAtStartup.Web.Https.Port}/");
                        options.Listen(IPAddress.Any, OptionsAtStartup.Web.Https.Port, listenOptions =>
                        {
                            var cert = OptionsAtStartup.Web.Https.Certificate;

                            if (!string.IsNullOrEmpty(cert.Pfx))
                            {
                                Log.Information($"Using certificate from {cert.Pfx}");
                                listenOptions.UseHttps(cert.Pfx, cert.Password);
                            }
                            else
                            {
                                Log.Information($"Using randomly generated self-signed certificate");
                                listenOptions.UseHttps(X509.Generate(subject: AppName));
                            }
                        });
                    }
                });

                builder.Services
                .ConfigureAspDotNetServices()
                .ConfigureDependencyInjectionContainer();

                var app = builder.Build();

                app.ConfigureAspDotNetPipeline();

                if (OptionsAtStartup.Flags.NoStart)
                {
                    Log.Information("Qutting because 'no-start' option is enabled");
                    return;
                }

                app.Run();
            }
            catch (Exception ex)
            {
                Log.Fatal(ex, "Application terminated unexpectedly");
            }
            finally
            {
                Serilog.Log.CloseAndFlush();
            }
        }
Example #3
0
        /// <summary>
        ///     Entrypoint.
        /// </summary>
        /// <param name="args">Command line arguments.</param>
        public static void Main(string[] args)
        {
            // populate the properties above so that we can override the default config file if needed, and to
            // check if the application is being run in command mode (run task and quit).
            EnvironmentVariables.Populate(prefix: EnvironmentVariablePrefix);
            Arguments.Populate(clearExistingValues: false);

            if (ShowVersion)
            {
                Console.WriteLine(Version);
                return;
            }

            if (ShowHelp || ShowEnvironmentVariables)
            {
                if (!NoLogo)
                {
                    PrintLogo(Version);
                }

                if (ShowHelp)
                {
                    PrintCommandLineArguments(typeof(Options));
                }

                if (ShowEnvironmentVariables)
                {
                    PrintEnvironmentVariables(typeof(Options), EnvironmentVariablePrefix);
                }

                return;
            }

            if (GenerateCertificate)
            {
                GenerateX509Certificate(password: Guid.NewGuid().ToString(), filename: $"{AppName}.pfx");
                return;
            }

            // the application isn't bein run in command mode. load all configuration values and proceed
            // with bootstrapping.
            try
            {
                Configuration = new ConfigurationBuilder()
                                .AddConfigurationProviders(EnvironmentVariablePrefix, ConfigurationFile)
                                .Build();

                Configuration.GetSection(AppName)
                .Bind(Options, (o) => { o.BindNonPublicProperties = true; });

                if (!Options.TryValidate(out var result))
                {
                    Console.WriteLine(result.GetResultView("Invalid configuration:"));
                    return;
                }

                var cert = Options.Web.Https.Certificate;

                if (!string.IsNullOrEmpty(cert.Pfx) && !X509.TryValidate(cert.Pfx, cert.Password, out var certResult))
                {
                    Console.WriteLine($"Invalid HTTPs certificate: {certResult}");
                    return;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Invalid configuration: {(!Options.Debug ? ex : ex.Message)}");
                return;
            }

            if (!Options.NoLogo)
            {
                PrintLogo(Version);
            }

            if (Options.Debug)
            {
                Console.WriteLine("Configuration:");
                Console.WriteLine(Configuration.GetDebugView());

                Log.Logger = new LoggerConfiguration()
                             .MinimumLevel.Debug()
                             .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
                             .MinimumLevel.Override("slskd.API.Authentication.PassthroughAuthenticationHandler", LogEventLevel.Information)
                             .Enrich.WithProperty("Version", Version)
                             .Enrich.WithProperty("InstanceName", Options.InstanceName)
                             .Enrich.WithProperty("InvocationId", InvocationId)
                             .Enrich.WithProperty("ProcessId", ProcessId)
                             .Enrich.FromLogContext()
                             .WriteTo.Console(
                    outputTemplate: "[{SourceContext}] [{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
                             .WriteTo.Async(config =>
                                            config.File(
                                                Path.Combine(AppContext.BaseDirectory, "logs", $"{AppName}-.log"),
                                                outputTemplate: "[{SourceContext}] [{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}",
                                                rollingInterval: RollingInterval.Day))
                             .WriteTo.Conditional(
                    e => !string.IsNullOrEmpty(Options.Logger.Loki),
                    config => config.GrafanaLoki(
                        Options.Logger.Loki ?? string.Empty,
                        outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"))
                             .CreateLogger();
            }
            else
            {
                Log.Logger = new LoggerConfiguration()
                             .MinimumLevel.Information()
                             .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
                             .MinimumLevel.Override("slskd.API.Authentication.PassthroughAuthenticationHandler", LogEventLevel.Information)
                             .Enrich.WithProperty("Version", Version)
                             .Enrich.WithProperty("InstanceName", Options.InstanceName)
                             .Enrich.WithProperty("InvocationId", InvocationId)
                             .Enrich.WithProperty("ProcessId", ProcessId)
                             .Enrich.FromLogContext()
                             .WriteTo.Console(
                    outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
                             .WriteTo.Async(config =>
                                            config.File(
                                                Path.Combine(AppContext.BaseDirectory, "logs", $"{AppName}-.log"),
                                                outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}",
                                                rollingInterval: RollingInterval.Day))
                             .WriteTo.Conditional(
                    e => !string.IsNullOrEmpty(Options.Logger.Loki),
                    config => config.GrafanaLoki(
                        Options.Logger.Loki ?? string.Empty,
                        outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"))
                             .CreateLogger();
            }

            var logger = Log.ForContext(typeof(Program));

            if (ConfigurationFile != DefaultConfigurationFile && !File.Exists(ConfigurationFile))
            {
                logger.Warning($"Specified configuration file '{ConfigurationFile}' could not be found and was not loaded.");
            }

            logger.Information("Version: {Version}", Version);
            logger.Information("Instance Name: {InstanceName}", Options.InstanceName);
            logger.Information("Invocation ID: {InvocationId}", InvocationId);
            logger.Information("Process ID: {ProcessId}", ProcessId);

            if (!string.IsNullOrEmpty(Options.Logger.Loki))
            {
                logger.Information("Forwarding logs to Grafana Loki instance at {LoggerLokiUrl}", Options.Logger.Loki);
            }

            try
            {
                if (Options.Feature.Prometheus)
                {
                    using var runtimeMetrics = DotNetRuntimeStatsBuilder.Default().StartCollecting();
                }

                WebHost.CreateDefaultBuilder(args)
                .SuppressStatusMessages(true)
                .ConfigureAppConfiguration((hostingContext, builder) =>
                {
                    builder.Sources.Clear();
                    builder.AddConfigurationProviders(EnvironmentVariablePrefix, ConfigurationFile);
                })
                .UseSerilog()
                .UseUrls()
                .UseKestrel(options =>
                {
                    logger.Information($"Listening for HTTP requests at http://{IPAddress.Any}:{Options.Web.Port}/");
                    options.Listen(IPAddress.Any, Options.Web.Port);

                    logger.Information($"Listening for HTTPS requests at https://{IPAddress.Any}:{Options.Web.Https.Port}/");
                    options.Listen(IPAddress.Any, Options.Web.Https.Port, listenOptions =>
                    {
                        var cert = Options.Web.Https.Certificate;

                        if (!string.IsNullOrEmpty(cert.Pfx))
                        {
                            logger.Information($"Using certificate from {cert.Pfx}");
                            listenOptions.UseHttps(cert.Pfx, cert.Password);
                        }
                        else
                        {
                            logger.Information($"Using randomly generated self-signed certificate");
                            listenOptions.UseHttps(X509.Generate(subject: AppName));
                        }
                    });
                })
                .UseStartup <Startup>()
                .Build()
                .Run();
            }
            catch (Exception ex)
            {
                logger.Fatal(ex, "Application terminated unexpectedly");
            }
            finally
            {
                Log.CloseAndFlush();
            }
        }