diff --git a/src/Jackett.Common/Jackett.Common.csproj b/src/Jackett.Common/Jackett.Common.csproj
index 32a8a4d68..c49fbccad 100644
--- a/src/Jackett.Common/Jackett.Common.csproj
+++ b/src/Jackett.Common/Jackett.Common.csproj
@@ -193,6 +193,7 @@
+
@@ -208,6 +209,9 @@
2.0.0
+
+ 4.5.0
+
diff --git a/src/Jackett.Common/Services/WindowsServiceConfigService.cs b/src/Jackett.Common/Services/WindowsServiceConfigService.cs
new file mode 100644
index 000000000..0fec4f3dc
--- /dev/null
+++ b/src/Jackett.Common/Services/WindowsServiceConfigService.cs
@@ -0,0 +1,119 @@
+using Jackett.Common.Services.Interfaces;
+using NLog;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.ServiceProcess;
+
+namespace Jackett.Common.Services
+{
+ public class WindowsServiceConfigService : IServiceConfigService
+ {
+ private const string NAME = "Jackett";
+ private const string DESCRIPTION = "API Support for your favorite torrent trackers";
+ private const string SERVICEEXE = "JackettService.exe";
+
+ private IProcessService processService;
+ private Logger logger;
+
+ public WindowsServiceConfigService(IProcessService p, Logger l)
+ {
+ processService = p;
+ logger = l;
+ }
+
+ public bool ServiceExists()
+ {
+ return GetService(NAME) != null;
+ }
+
+ public bool ServiceRunning()
+ {
+ var service = GetService(NAME);
+ if (service == null)
+ return false;
+ return service.Status == ServiceControllerStatus.Running;
+ }
+
+ public void Start()
+ {
+ var service = GetService(NAME);
+ service.Start();
+ }
+
+ public void Stop()
+ {
+ var service = GetService(NAME);
+ service.Stop();
+ }
+
+ public ServiceController GetService(string serviceName)
+ {
+ return ServiceController.GetServices().FirstOrDefault(c => String.Equals(c.ServiceName, serviceName, StringComparison.InvariantCultureIgnoreCase));
+ }
+
+ public void Install()
+ {
+ if (ServiceExists())
+ {
+ logger.Warn("The service is already installed!");
+ }
+ else
+ {
+ string applicationFolder = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath);
+
+ var exePath = Path.Combine(applicationFolder, SERVICEEXE);
+ if (!File.Exists(exePath) && Debugger.IsAttached)
+ {
+ exePath = Path.Combine(applicationFolder, "..\\..\\..\\Jackett.Service\\bin\\Debug", SERVICEEXE);
+ }
+
+ string arg = $"create {NAME} start= auto binpath= \"{exePath}\" DisplayName= {NAME}";
+
+ processService.StartProcessAndLog("sc.exe", arg, true);
+
+ processService.StartProcessAndLog("sc.exe", $"description {NAME} \"{DESCRIPTION}\"", true);
+ }
+ }
+
+ public void Uninstall()
+ {
+ RemoveService();
+
+ processService.StartProcessAndLog("sc.exe", $"delete {NAME}", true);
+
+ logger.Info("The service was uninstalled.");
+ }
+
+ public void RemoveService()
+ {
+ var service = GetService(NAME);
+ if (service == null)
+ {
+ logger.Warn("The service is already uninstalled");
+ return;
+ }
+ if (service.Status != ServiceControllerStatus.Stopped)
+ {
+ service.Stop();
+ service.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(60));
+
+ service.Refresh();
+ if (service.Status == ServiceControllerStatus.Stopped)
+ {
+ logger.Info("Service stopped.");
+ }
+ else
+ {
+ logger.Error("Failed to stop the service");
+ }
+ }
+ else
+ {
+ logger.Warn("The service was already stopped");
+ }
+ }
+ }
+}
diff --git a/src/Jackett.Common/Utils/LoggingSetup.cs b/src/Jackett.Common/Utils/LoggingSetup.cs
new file mode 100644
index 000000000..e73a5ecf8
--- /dev/null
+++ b/src/Jackett.Common/Utils/LoggingSetup.cs
@@ -0,0 +1,49 @@
+using Jackett.Common.Models.Config;
+using Jackett.Common.Services;
+using NLog.Config;
+using NLog.Targets;
+using System.IO;
+
+namespace Jackett.Common.Utils
+{
+ public static class LoggingSetup
+ {
+ public static LoggingConfiguration GetLoggingConfiguration(RuntimeSettings settings, bool fileOnly = false)
+ {
+ var logFileName = settings.CustomLogFileName ?? "log.txt";
+ var logLevel = settings.TracingEnabled ? NLog.LogLevel.Debug : NLog.LogLevel.Info;
+ // Add custom date time format renderer as the default is too long
+ ConfigurationItemFactory.Default.LayoutRenderers.RegisterDefinition("simpledatetime", typeof(SimpleDateTimeRenderer));
+
+ var logConfig = new LoggingConfiguration();
+ var logFile = new FileTarget();
+ logConfig.AddTarget("file", logFile);
+ logFile.Layout = "${longdate} ${level} ${message} ${exception:format=ToString}";
+ logFile.FileName = Path.Combine(settings.DataFolder, logFileName);
+ logFile.ArchiveFileName = Path.Combine(settings.DataFolder, logFileName + ".{#####}.txt");
+ logFile.ArchiveAboveSize = 500000;
+ logFile.MaxArchiveFiles = 5;
+ logFile.KeepFileOpen = false;
+ logFile.ArchiveNumbering = ArchiveNumberingMode.DateAndSequence;
+ var logFileRule = new LoggingRule("*", logLevel, logFile);
+ logConfig.LoggingRules.Add(logFileRule);
+
+ if (!fileOnly)
+ {
+ var logConsole = new ColoredConsoleTarget();
+ logConfig.AddTarget("console", logConsole);
+
+ logConsole.Layout = "${simpledatetime} ${level} ${message} ${exception:format=ToString}";
+ var logConsoleRule = new LoggingRule("*", logLevel, logConsole);
+ logConfig.LoggingRules.Add(logConsoleRule);
+
+ var logService = new LogCacheService();
+ logConfig.AddTarget("service", logService);
+ var serviceRule = new LoggingRule("*", logLevel, logService);
+ logConfig.LoggingRules.Add(serviceRule);
+ }
+
+ return logConfig;
+ }
+ }
+}