소스 검색

Merge pull request #308 from Moonlight-Panel/AddAutoLetsEncrypt

Added auto lets encrypt certificate issuing
Marcel Baumgartner 1 년 전
부모
커밋
564745b4a2

+ 72 - 39
Moonlight/App/Configuration/ConfigV1.cs

@@ -8,8 +8,7 @@ using Newtonsoft.Json;
 
 
 public class ConfigV1
 public class ConfigV1
 {
 {
-    [JsonProperty("Moonlight")]
-    public MoonlightData Moonlight { get; set; } = new();
+    [JsonProperty("Moonlight")] public MoonlightData Moonlight { get; set; } = new();
 
 
     public class MoonlightData
     public class MoonlightData
     {
     {
@@ -26,6 +25,8 @@ public class ConfigV1
         [Description("Specify the latency threshold which has to be reached in order to trigger the warning message")]
         [Description("Specify the latency threshold which has to be reached in order to trigger the warning message")]
         public int LatencyCheckThreshold { get; set; } = 1000;
         public int LatencyCheckThreshold { get; set; } = 1000;
 
 
+        [JsonProperty("LetsEncrypt")] public LetsEncrypt LetsEncrypt { get; set; } = new();
+
         [JsonProperty("Auth")] public AuthData Auth { get; set; } = new();
         [JsonProperty("Auth")] public AuthData Auth { get; set; } = new();
 
 
         [JsonProperty("Database")] public DatabaseData Database { get; set; } = new();
         [JsonProperty("Database")] public DatabaseData Database { get; set; } = new();
@@ -47,8 +48,9 @@ public class ConfigV1
         [JsonProperty("Mail")] public MailData Mail { get; set; } = new();
         [JsonProperty("Mail")] public MailData Mail { get; set; } = new();
 
 
         [JsonProperty("Cleanup")] public CleanupData Cleanup { get; set; } = new();
         [JsonProperty("Cleanup")] public CleanupData Cleanup { get; set; } = new();
-        
-        [JsonProperty("DiscordNotifications")] public DiscordNotificationsData DiscordNotifications { get; set; } = new();
+
+        [JsonProperty("DiscordNotifications")]
+        public DiscordNotificationsData DiscordNotifications { get; set; } = new();
 
 
         [JsonProperty("Statistics")] public StatisticsData Statistics { get; set; } = new();
         [JsonProperty("Statistics")] public StatisticsData Statistics { get; set; } = new();
 
 
@@ -62,27 +64,55 @@ public class ConfigV1
 
 
         [JsonProperty("Tickets")] public TicketsData Tickets { get; set; } = new();
         [JsonProperty("Tickets")] public TicketsData Tickets { get; set; } = new();
     }
     }
-    
+
+    public class LetsEncrypt
+    {
+        [JsonProperty("Enable")]
+        [Description("Enable automatic lets encrypt certificate issuing. This is a beta feature")]
+        public bool Enable { get; set; } = false;
+
+        [JsonProperty("ExpireEmail")]
+        [Description("Lets encrypt will send you an email upon certificate expiration to this address")]
+        public string ExpireEmail { get; set; } = "your@email.test";
+
+        [JsonProperty("CountryCode")]
+        [Description("Country code to use for generating the certificate")]
+        public string CountryCode { get; set; } = "DE";
+
+        [JsonProperty("State")]
+        [Description("State to use for generating the certificate")]
+        public string State { get; set; } = "Germany";
+
+        [JsonProperty("Locality")]
+        [Description("Locality to use for generating the certificate")]
+        public string Locality { get; set; } = "Bavaria";
+
+        [JsonProperty("Organization")]
+        [Description("Organization to use for generating the certificate")]
+        public string Organization { get; set; } = "Moonlight Panel";
+    }
+
     public class TicketsData
     public class TicketsData
     {
     {
         [JsonProperty("WelcomeMessage")]
         [JsonProperty("WelcomeMessage")]
         [Description("The message that will be sent when a user created a ticket")]
         [Description("The message that will be sent when a user created a ticket")]
         public string WelcomeMessage { get; set; } = "Welcome to the support";
         public string WelcomeMessage { get; set; } = "Welcome to the support";
     }
     }
-    
+
     public class StripeData
     public class StripeData
     {
     {
         [JsonProperty("ApiKey")]
         [JsonProperty("ApiKey")]
-        [Description("Put here your stripe api key if you add subscriptions. Currently the only billing option is stripe which is enabled by default and cannot be turned off. This feature is still experimental")]
+        [Description(
+            "Put here your stripe api key if you add subscriptions. Currently the only billing option is stripe which is enabled by default and cannot be turned off. This feature is still experimental")]
         public string ApiKey { get; set; } = "";
         public string ApiKey { get; set; } = "";
     }
     }
-    
+
     public class AuthData
     public class AuthData
     {
     {
         [JsonProperty("DenyLogin")]
         [JsonProperty("DenyLogin")]
         [Description("Prevent every new login")]
         [Description("Prevent every new login")]
         public bool DenyLogin { get; set; } = false;
         public bool DenyLogin { get; set; } = false;
-        
+
         [JsonProperty("DenyRegister")]
         [JsonProperty("DenyRegister")]
         [Description("Prevent every new user to register")]
         [Description("Prevent every new user to register")]
         public bool DenyRegister { get; set; } = false;
         public bool DenyRegister { get; set; } = false;
@@ -107,7 +137,8 @@ public class ConfigV1
         public long Uptime { get; set; } = 6;
         public long Uptime { get; set; } = 6;
 
 
         [JsonProperty("Enable")]
         [JsonProperty("Enable")]
-        [Description("The cleanup system provides a fair way for stopping unused servers and staying stable even with overallocation. A detailed explanation: docs.endelon-hosting.de/erklaerungen/cleanup")]
+        [Description(
+            "The cleanup system provides a fair way for stopping unused servers and staying stable even with overallocation. A detailed explanation: docs.endelon-hosting.de/erklaerungen/cleanup")]
         public bool Enable { get; set; } = false;
         public bool Enable { get; set; } = false;
 
 
         [JsonProperty("MinUptime")]
         [JsonProperty("MinUptime")]
@@ -120,10 +151,8 @@ public class ConfigV1
         [JsonProperty("Database")] public string Database { get; set; } = "moonlight_db";
         [JsonProperty("Database")] public string Database { get; set; } = "moonlight_db";
 
 
         [JsonProperty("Host")] public string Host { get; set; } = "your.database.host";
         [JsonProperty("Host")] public string Host { get; set; } = "your.database.host";
-        
-        [JsonProperty("Password")]
-        [Blur]
-        public string Password { get; set; } = "secret";
+
+        [JsonProperty("Password")] [Blur] public string Password { get; set; } = "secret";
 
 
         [JsonProperty("Port")] public long Port { get; set; } = 3306;
         [JsonProperty("Port")] public long Port { get; set; } = 3306;
 
 
@@ -141,6 +170,7 @@ public class ConfigV1
         [Blur]
         [Blur]
         public string Token { get; set; } = Guid.NewGuid().ToString();
         public string Token { get; set; } = Guid.NewGuid().ToString();
     }
     }
+
     public class DiscordBotData
     public class DiscordBotData
     {
     {
         [JsonProperty("Enable")]
         [JsonProperty("Enable")]
@@ -155,7 +185,7 @@ public class ConfigV1
         [JsonProperty("PowerActions")]
         [JsonProperty("PowerActions")]
         [Description("Enable actions like starting and stopping servers")]
         [Description("Enable actions like starting and stopping servers")]
         public bool PowerActions { get; set; } = false;
         public bool PowerActions { get; set; } = false;
-        
+
         [JsonProperty("SendCommands")]
         [JsonProperty("SendCommands")]
         [Description("Allow users to send commands to their servers")]
         [Description("Allow users to send commands to their servers")]
         public bool SendCommands { get; set; } = false;
         public bool SendCommands { get; set; } = false;
@@ -164,7 +194,8 @@ public class ConfigV1
     public class DiscordNotificationsData
     public class DiscordNotificationsData
     {
     {
         [JsonProperty("Enable")]
         [JsonProperty("Enable")]
-        [Description("The discord notification system sends you a message everytime a event like a new support chat message is triggered with usefull data describing the event")]
+        [Description(
+            "The discord notification system sends you a message everytime a event like a new support chat message is triggered with usefull data describing the event")]
         public bool Enable { get; set; } = false;
         public bool Enable { get; set; } = false;
 
 
         [JsonProperty("WebHook")]
         [JsonProperty("WebHook")]
@@ -178,7 +209,7 @@ public class ConfigV1
         [JsonProperty("Enable")]
         [JsonProperty("Enable")]
         [Description("This enables the domain system")]
         [Description("This enables the domain system")]
         public bool Enable { get; set; } = false;
         public bool Enable { get; set; } = false;
-        
+
         [JsonProperty("AccountId")]
         [JsonProperty("AccountId")]
         [Description("This option specifies the cloudflare account id")]
         [Description("This option specifies the cloudflare account id")]
         public string AccountId { get; set; } = "cloudflare acc id";
         public string AccountId { get; set; } = "cloudflare acc id";
@@ -201,19 +232,23 @@ public class ConfigV1
     public class HeadersData
     public class HeadersData
     {
     {
         [JsonProperty("Color")]
         [JsonProperty("Color")]
-        [Description("This specifies the color of the embed generated by platforms like discord when someone posts a link to your moonlight instance")]
+        [Description(
+            "This specifies the color of the embed generated by platforms like discord when someone posts a link to your moonlight instance")]
         public string Color { get; set; } = "#4b27e8";
         public string Color { get; set; } = "#4b27e8";
 
 
         [JsonProperty("Description")]
         [JsonProperty("Description")]
-        [Description("This specifies the description text of the embed generated by platforms like discord when someone posts a link to your moonlight instance and can also help google to index your moonlight instance correctly")]
+        [Description(
+            "This specifies the description text of the embed generated by platforms like discord when someone posts a link to your moonlight instance and can also help google to index your moonlight instance correctly")]
         public string Description { get; set; } = "the next generation hosting panel";
         public string Description { get; set; } = "the next generation hosting panel";
 
 
         [JsonProperty("Keywords")]
         [JsonProperty("Keywords")]
-        [Description("To help search engines like google to index your moonlight instance correctly you can specify keywords seperated by a comma here")]
+        [Description(
+            "To help search engines like google to index your moonlight instance correctly you can specify keywords seperated by a comma here")]
         public string Keywords { get; set; } = "moonlight";
         public string Keywords { get; set; } = "moonlight";
 
 
         [JsonProperty("Title")]
         [JsonProperty("Title")]
-        [Description("This specifies the title of the embed generated by platforms like discord when someone posts a link to your moonlight instance")]
+        [Description(
+            "This specifies the title of the embed generated by platforms like discord when someone posts a link to your moonlight instance")]
         public string Title { get; set; } = "Moonlight - endelon.link";
         public string Title { get; set; } = "Moonlight - endelon.link";
     }
     }
 
 
@@ -223,9 +258,7 @@ public class ConfigV1
 
 
         [JsonProperty("Server")] public string Server { get; set; } = "your.mail.host";
         [JsonProperty("Server")] public string Server { get; set; } = "your.mail.host";
 
 
-        [JsonProperty("Password")]
-        [Blur]
-        public string Password { get; set; } = "secret";
+        [JsonProperty("Password")] [Blur] public string Password { get; set; } = "secret";
 
 
         [JsonProperty("Port")] public int Port { get; set; } = 465;
         [JsonProperty("Port")] public int Port { get; set; } = 465;
 
 
@@ -263,19 +296,19 @@ public class ConfigV1
 
 
         [JsonProperty("ClientId")] public string ClientId { get; set; }
         [JsonProperty("ClientId")] public string ClientId { get; set; }
 
 
-        [JsonProperty("ClientSecret")]
-        [Blur]
-        public string ClientSecret { get; set; }
+        [JsonProperty("ClientSecret")] [Blur] public string ClientSecret { get; set; }
     }
     }
 
 
     public class RatingData
     public class RatingData
     {
     {
         [JsonProperty("Enabled")]
         [JsonProperty("Enabled")]
-        [Description("The rating systems shows a user who is registered longer than the set amout of days a popup to rate this platform if he hasnt rated it before")]
+        [Description(
+            "The rating systems shows a user who is registered longer than the set amout of days a popup to rate this platform if he hasnt rated it before")]
         public bool Enabled { get; set; } = false;
         public bool Enabled { get; set; } = false;
 
 
         [JsonProperty("Url")]
         [JsonProperty("Url")]
-        [Description("This is the url a user who rated above a set limit is shown to rate you again. Its recommended to put your google or trustpilot rate link here")]
+        [Description(
+            "This is the url a user who rated above a set limit is shown to rate you again. Its recommended to put your google or trustpilot rate link here")]
         public string Url { get; set; } = "https://link-to-google-or-smth";
         public string Url { get; set; } = "https://link-to-google-or-smth";
 
 
         [JsonProperty("MinRating")]
         [JsonProperty("MinRating")]
@@ -290,7 +323,8 @@ public class ConfigV1
     public class SecurityData
     public class SecurityData
     {
     {
         [JsonProperty("Token")]
         [JsonProperty("Token")]
-        [Description("This is the moonlight app token. It is used to encrypt and decrypt data and validate tokens and sessions")]
+        [Description(
+            "This is the moonlight app token. It is used to encrypt and decrypt data and validate tokens and sessions")]
         [Blur]
         [Blur]
         public string Token { get; set; } = Guid.NewGuid().ToString();
         public string Token { get; set; } = Guid.NewGuid().ToString();
 
 
@@ -306,7 +340,8 @@ public class ConfigV1
         [JsonProperty("ReCaptcha")] public ReCaptchaData ReCaptcha { get; set; } = new();
         [JsonProperty("ReCaptcha")] public ReCaptchaData ReCaptcha { get; set; } = new();
 
 
         [JsonProperty("BlockDatacenterIps")]
         [JsonProperty("BlockDatacenterIps")]
-        [Description("If this option is enabled, users with an ip from datacenters will not be able to access the panel")]
+        [Description(
+            "If this option is enabled, users with an ip from datacenters will not be able to access the panel")]
         public bool BlockDatacenterIps { get; set; } = true;
         public bool BlockDatacenterIps { get; set; } = true;
 
 
         [JsonProperty("AllowCloudflareIps")]
         [JsonProperty("AllowCloudflareIps")]
@@ -317,22 +352,20 @@ public class ConfigV1
     public class ReCaptchaData
     public class ReCaptchaData
     {
     {
         [JsonProperty("Enable")]
         [JsonProperty("Enable")]
-        [Description("Enables repatcha at places like the register page. For information how to get your recaptcha credentails go to google.com/recaptcha/about/")]
+        [Description(
+            "Enables repatcha at places like the register page. For information how to get your recaptcha credentails go to google.com/recaptcha/about/")]
         public bool Enable { get; set; } = false;
         public bool Enable { get; set; } = false;
 
 
-        [JsonProperty("SiteKey")]
-        [Blur]
-        public string SiteKey { get; set; } = "recaptcha site key here";
+        [JsonProperty("SiteKey")] [Blur] public string SiteKey { get; set; } = "recaptcha site key here";
 
 
-        [JsonProperty("SecretKey")]
-        [Blur]
-        public string SecretKey { get; set; } = "recaptcha secret here";
+        [JsonProperty("SecretKey")] [Blur] public string SecretKey { get; set; } = "recaptcha secret here";
     }
     }
 
 
     public class SentryData
     public class SentryData
     {
     {
         [JsonProperty("Enable")]
         [JsonProperty("Enable")]
-        [Description("Sentry is a way to monitor application crashes and performance issues in real time. Enable this option only if you set a sentry dsn")]
+        [Description(
+            "Sentry is a way to monitor application crashes and performance issues in real time. Enable this option only if you set a sentry dsn")]
         public bool Enable { get; set; } = false;
         public bool Enable { get; set; } = false;
 
 
         [JsonProperty("Dsn")]
         [JsonProperty("Dsn")]

+ 7 - 2
Moonlight/App/Events/EventSystem.cs

@@ -113,13 +113,18 @@ public class EventSystem
         return Task.CompletedTask;
         return Task.CompletedTask;
     }
     }
     
     
-    public Task<T> WaitForEvent<T>(string id, object handle, Func<T, bool> filter)
+    public Task<T> WaitForEvent<T>(string id, object handle, Func<T, bool>? filter = null)
     {
     {
         var taskCompletionSource = new TaskCompletionSource<T>();
         var taskCompletionSource = new TaskCompletionSource<T>();
     
     
         Func<T, Task> action = async data =>
         Func<T, Task> action = async data =>
         {
         {
-            if (filter.Invoke(data))
+            if (filter == null)
+            {
+                taskCompletionSource.SetResult(data);
+                await Off(id, handle);
+            }
+            else if(filter.Invoke(data))
             {
             {
                 taskCompletionSource.SetResult(data);
                 taskCompletionSource.SetResult(data);
                 await Off(id, handle);
                 await Off(id, handle);

+ 33 - 0
Moonlight/App/Http/Controllers/WellKnown/AcmeController.cs

@@ -0,0 +1,33 @@
+using Microsoft.AspNetCore.Mvc;
+using Moonlight.App.Events;
+using Moonlight.App.Services;
+
+namespace Moonlight.App.Http.Controllers.WellKnown;
+
+[ApiController]
+[Route(".well-known/acme-challenge")]
+public class AcmeController : Controller
+{
+    private readonly LetsEncryptService LetsEncryptService;
+    private readonly EventSystem Event;
+
+    public AcmeController(LetsEncryptService letsEncryptService, EventSystem eventSystem)
+    {
+        LetsEncryptService = letsEncryptService;
+        Event = eventSystem;
+    }
+
+    [HttpGet("{token}")]
+    public async Task<ActionResult> Get([FromRoute] string token)
+    {
+        if (string.IsNullOrEmpty(LetsEncryptService.HttpChallenge) || string.IsNullOrEmpty(LetsEncryptService.HttpChallengeToken))
+            return Problem();
+
+        if (string.IsNullOrEmpty(token) || LetsEncryptService.HttpChallengeToken != token)
+            return Problem();
+
+        await Event.Emit("letsEncrypt.challengeFetched");
+
+        return Ok(LetsEncryptService.HttpChallenge);
+    }
+}

+ 0 - 1
Moonlight/App/Services/ConfigService.cs

@@ -17,7 +17,6 @@ public class ConfigService
     public ConfigService(StorageService storageService)
     public ConfigService(StorageService storageService)
     {
     {
         StorageService = storageService;
         StorageService = storageService;
-        StorageService.EnsureCreated();
 
 
         if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ML_CONFIG_PATH")))
         if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ML_CONFIG_PATH")))
             Path = Environment.GetEnvironmentVariable("ML_CONFIG_PATH")!;
             Path = Environment.GetEnvironmentVariable("ML_CONFIG_PATH")!;

+ 1 - 0
Moonlight/App/Services/Files/StorageService.cs

@@ -13,6 +13,7 @@ public class StorageService
         Directory.CreateDirectory(PathBuilder.Dir("storage", "backups"));
         Directory.CreateDirectory(PathBuilder.Dir("storage", "backups"));
         Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
         Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
         Directory.CreateDirectory(PathBuilder.Dir("storage", "plugins"));
         Directory.CreateDirectory(PathBuilder.Dir("storage", "plugins"));
+        Directory.CreateDirectory(PathBuilder.Dir("storage", "certs"));
 
 
         await UpdateResources();
         await UpdateResources();
 
 

+ 171 - 0
Moonlight/App/Services/LetsEncryptService.cs

@@ -0,0 +1,171 @@
+using System.Security.Cryptography.X509Certificates;
+using Certes;
+using Certes.Acme;
+using Microsoft.AspNetCore.Connections;
+using Microsoft.AspNetCore.Http.Connections;
+using Moonlight.App.Events;
+using Moonlight.App.Helpers;
+
+namespace Moonlight.App.Services;
+
+public class LetsEncryptService
+{
+    private readonly ConfigService ConfigService;
+    private readonly string LetsEncryptCertPath;
+    private readonly EventSystem Event;
+    private X509Certificate2 Certificate;
+
+    public string HttpChallenge { get; private set; } = "";
+    public string HttpChallengeToken { get; private set; } = "";
+
+    public LetsEncryptService(ConfigService configService, EventSystem eventSystem)
+    {
+        ConfigService = configService;
+        Event = eventSystem;
+        LetsEncryptCertPath = PathBuilder.File("storage", "certs", "letsencrypt.pfx");
+    }
+
+    public async Task AutoProcess()
+    {
+        if (!ConfigService.Get().Moonlight.LetsEncrypt.Enable)
+            return;
+
+        if (await CheckNeedsRenewal())
+        {
+            try
+            {
+                await Renew();
+            }
+            catch (Exception e)
+            {
+                Logger.Error("Unable to issue lets encrypt certificate");
+                Logger.Error(e);
+            }
+        }
+        else
+            Logger.Info("Skipping lets encrypt renewal");
+
+        await LoadCertificate();
+    }
+
+    private Task LoadCertificate()
+    {
+        try
+        {
+            Certificate = new X509Certificate2(
+                LetsEncryptCertPath, 
+                ConfigService.Get().Moonlight.Security.Token
+            );
+            
+            Logger.Info($"Loaded ssl certificate. '{Certificate.FriendlyName}' issued by '{Certificate.IssuerName.Name}'");
+        }
+        catch (Exception e)
+        {
+            Logger.Warn("Unable to load ssl certificates");
+            Logger.Warn(e);
+        }
+        
+        return Task.CompletedTask;
+    }
+    
+    private async Task Renew()
+    {
+        Logger.Info("Renewing lets encrypt certificate");
+        
+        var uri = new Uri(ConfigService.Get().Moonlight.AppUrl);
+        var config = ConfigService.Get().Moonlight.LetsEncrypt;
+
+        if (uri.HostNameType == UriHostNameType.IPv4 || uri.HostNameType == UriHostNameType.IPv6)
+        {
+            Logger.Warn("You cannot use an ip to issue a lets encrypt certificate");
+            return;
+        }
+
+        var acmeContext = new AcmeContext(WellKnownServers.LetsEncryptV2);
+
+        Logger.Info($"Starting lets encrypt certificate issuing. Using acme server '{acmeContext.DirectoryUri}'");
+
+        var account = await acmeContext.NewAccount(config.ExpireEmail, true);
+
+        Logger.Info("Creating order");
+        var order = await acmeContext.NewOrder(new[] { uri.Host });
+        var authZ = (await order.Authorizations()).First();
+
+        var challenge = await authZ.Http();
+
+        HttpChallengeToken = challenge.Token;
+        HttpChallenge = challenge.KeyAuthz;
+
+        Logger.Info("Waiting for http challenge to complete");
+
+        Task.Run(async () =>
+        {
+            await Task.Delay(TimeSpan.FromSeconds(3));
+
+            try
+            {
+                await challenge.Validate();
+            }
+            catch (Exception e)
+            {
+                Logger.Error("Unable to validate challenge");
+                Logger.Error(e);
+            }
+        });
+
+        await Event.WaitForEvent<Object>("letsEncrypt.challengeFetched", this);
+
+        Logger.Info("Generating certificate");
+
+        var privateKey = KeyFactory.NewKey(KeyAlgorithm.ES256);
+
+        var certificate = await order.Generate(new CsrInfo
+        {
+            CountryName = config.CountryCode,
+            State = config.State,
+            Locality = config.Locality,
+            Organization = config.Organization,
+            OrganizationUnit = "Dev",
+            CommonName = uri.Host
+        }, privateKey);
+
+        var builder = certificate.ToPfx(privateKey);
+
+        var certBytes = builder.Build(
+            uri.Host,
+            ConfigService.Get().Moonlight.Security.Token
+        );
+
+        Logger.Info($"Saved lets encrypt certificate to '{LetsEncryptCertPath}'");
+        await File.WriteAllBytesAsync(LetsEncryptCertPath, certBytes);
+    }
+
+    private Task<bool> CheckNeedsRenewal()
+    {
+        if (!File.Exists(LetsEncryptCertPath))
+        {
+            Logger.Info("No lets encrypt certificate found");
+            return Task.FromResult(true);
+        }
+
+        var existingCert = new X509Certificate2(LetsEncryptCertPath, ConfigService.Get().Moonlight.Security.Token);
+        var expirationDate = existingCert.NotAfter;
+
+        if (DateTime.Now < expirationDate)
+        {
+            Logger.Info($"Lets encrypt certificate valid until {Formatter.FormatDate(expirationDate)}");
+            return Task.FromResult(false);
+        }
+
+        Logger.Info("Lets encrypt certificate expired");
+        return Task.FromResult(true);
+    }
+
+    public X509Certificate2? SelectCertificate(ConnectionContext? context, string? domain)
+    {
+        if (context == null)
+            return null;
+
+        return Certificate;
+    }
+}

+ 1 - 0
Moonlight/Moonlight.csproj

@@ -17,6 +17,7 @@
     <PackageReference Include="BlazorDownloadFile" Version="2.4.0.2" />
     <PackageReference Include="BlazorDownloadFile" Version="2.4.0.2" />
     <PackageReference Include="BlazorMonaco" Version="2.1.0" />
     <PackageReference Include="BlazorMonaco" Version="2.1.0" />
     <PackageReference Include="BlazorTable" Version="1.17.0" />
     <PackageReference Include="BlazorTable" Version="1.17.0" />
+    <PackageReference Include="Certes" Version="3.0.4" />
     <PackageReference Include="CloudFlare.Client" Version="6.1.4" />
     <PackageReference Include="CloudFlare.Client" Version="6.1.4" />
     <PackageReference Include="CurrieTechnologies.Razor.SweetAlert2" Version="5.5.0" />
     <PackageReference Include="CurrieTechnologies.Razor.SweetAlert2" Version="5.5.0" />
     <PackageReference Include="Discord.Net" Version="3.10.0" />
     <PackageReference Include="Discord.Net" Version="3.10.0" />

+ 20 - 3
Moonlight/Program.cs

@@ -114,6 +114,17 @@ namespace Moonlight
 
 
             var builder = WebApplication.CreateBuilder(args);
             var builder = WebApplication.CreateBuilder(args);
 
 
+            var eventSystem = new EventSystem();
+            var letsEncryptService = new LetsEncryptService(configService, eventSystem);
+
+            builder.WebHost.ConfigureKestrel(options =>
+            {
+                options.ConfigureHttpsDefaults(httpsOptions =>
+                {
+                    httpsOptions.ServerCertificateSelector = letsEncryptService.SelectCertificate;
+                }); 
+            });
+
             var pluginService = new PluginService();
             var pluginService = new PluginService();
             await pluginService.BuildServices(builder.Services);
             await pluginService.BuildServices(builder.Services);
 
 
@@ -176,7 +187,7 @@ namespace Moonlight
             builder.Services.AddScoped(typeof(Repository<>));
             builder.Services.AddScoped(typeof(Repository<>));
 
 
             // Services
             // Services
-            builder.Services.AddSingleton<ConfigService>();
+            builder.Services.AddSingleton(configService);
             builder.Services.AddSingleton<StorageService>();
             builder.Services.AddSingleton<StorageService>();
             builder.Services.AddScoped<CookieService>();
             builder.Services.AddScoped<CookieService>();
             builder.Services.AddScoped<IdentityService>();
             builder.Services.AddScoped<IdentityService>();
@@ -200,7 +211,7 @@ namespace Moonlight
             builder.Services.AddScoped<WebSpaceService>();
             builder.Services.AddScoped<WebSpaceService>();
             builder.Services.AddScoped<StatisticsViewService>();
             builder.Services.AddScoped<StatisticsViewService>();
             builder.Services.AddSingleton<DateTimeService>();
             builder.Services.AddSingleton<DateTimeService>();
-            builder.Services.AddSingleton<EventSystem>();
+            builder.Services.AddSingleton(eventSystem);
             builder.Services.AddScoped<FileDownloadService>();
             builder.Services.AddScoped<FileDownloadService>();
             builder.Services.AddScoped<ForgeService>();
             builder.Services.AddScoped<ForgeService>();
             builder.Services.AddScoped<FabricService>();
             builder.Services.AddScoped<FabricService>();
@@ -254,6 +265,7 @@ namespace Moonlight
             builder.Services.AddSingleton<TempMailService>();
             builder.Services.AddSingleton<TempMailService>();
             builder.Services.AddSingleton<DdosProtectionService>();
             builder.Services.AddSingleton<DdosProtectionService>();
             builder.Services.AddSingleton(pluginService);
             builder.Services.AddSingleton(pluginService);
+            builder.Services.AddSingleton(letsEncryptService);
             
             
             // Other
             // Other
             builder.Services.AddSingleton<MoonlightService>();
             builder.Services.AddSingleton<MoonlightService>();
@@ -310,7 +322,12 @@ namespace Moonlight
             // Discord bot service
             // Discord bot service
             //var discordBotService = app.Services.GetRequiredService<DiscordBotService>();
             //var discordBotService = app.Services.GetRequiredService<DiscordBotService>();
 
 
-            await app.RunAsync();
+            Task.Run(async () =>
+            {
+                await letsEncryptService.AutoProcess();
+            });
+            
+            await app.RunAsync(configService.Get().Moonlight.AppUrl);
         }
         }
     }
     }
 }
 }