2017-04-15 08:45:10 +00:00
using CommandLine ;
2017-11-13 08:38:38 +00:00
using CommandLine.Text ;
using Jackett.Common.Models.Config ;
2018-06-24 01:31:08 +00:00
using Jackett.Common.Services ;
using Jackett.Common.Services.Interfaces ;
using Jackett.Common.Utils ;
using NLog ;
2017-04-15 08:45:10 +00:00
using System ;
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
2018-06-24 01:31:08 +00:00
using System.Net ;
2017-04-15 08:45:10 +00:00
using System.Reflection ;
namespace Jackett.Updater
{
2018-06-24 01:31:08 +00:00
public class Program
2017-04-15 08:45:10 +00:00
{
2018-06-24 01:31:08 +00:00
private IProcessService processService ;
private IServiceConfigService windowsService ;
private Logger logger ;
2019-03-06 09:20:32 +00:00
private Variants . JackettVariant variant = Variants . JackettVariant . NotFound ;
2018-06-24 01:31:08 +00:00
public static void Main ( string [ ] args )
2017-04-15 08:45:10 +00:00
{
new Program ( ) . Run ( args ) ;
}
private void Run ( string [ ] args )
{
2018-06-24 01:31:08 +00:00
RuntimeSettings runtimeSettings = new RuntimeSettings ( )
2017-11-13 08:38:38 +00:00
{
CustomLogFileName = "updater.txt"
2018-06-24 01:31:08 +00:00
} ;
LogManager . Configuration = LoggingSetup . GetLoggingConfiguration ( runtimeSettings ) ;
logger = LogManager . GetCurrentClassLogger ( ) ;
logger . Info ( "Jackett Updater v" + GetCurrentVersion ( ) ) ;
logger . Info ( "Options \"" + string . Join ( "\" \"" , args ) + "\"" ) ;
2019-03-03 04:44:18 +00:00
Variants variants = new Variants ( ) ;
variant = variants . GetVariant ( ) ;
logger . Info ( "Jackett variant: " + variant . ToString ( ) ) ;
2018-06-26 09:44:12 +00:00
bool isWindows = Environment . OSVersion . Platform = = PlatformID . Win32NT ;
if ( isWindows )
{
//The updater starts before Jackett closes
logger . Info ( "Pausing for 3 seconds to give Jackett & tray time to shutdown" ) ;
2018-06-27 12:02:51 +00:00
System . Threading . Thread . Sleep ( 3000 ) ;
2018-06-26 09:44:12 +00:00
}
2019-03-06 09:20:32 +00:00
2018-06-24 01:31:08 +00:00
processService = new ProcessService ( logger ) ;
windowsService = new WindowsServiceConfigService ( processService , logger ) ;
var commandLineParser = new Parser ( settings = > settings . CaseSensitive = false ) ;
try
{
var optionsResult = commandLineParser . ParseArguments < UpdaterConsoleOptions > ( args ) ;
2017-11-13 08:38:38 +00:00
optionsResult . WithParsed ( options = >
2017-04-15 08:45:10 +00:00
{
ProcessUpdate ( options ) ;
}
2017-11-13 08:38:38 +00:00
) ;
optionsResult . WithNotParsed ( errors = >
2017-04-15 08:45:10 +00:00
{
2018-06-24 01:31:08 +00:00
logger . Error ( HelpText . AutoBuild ( optionsResult ) ) ;
logger . Error ( "Failed to process update arguments!" ) ;
2019-03-06 09:20:32 +00:00
logger . Error ( errors . ToString ( ) ) ;
2017-04-15 08:45:10 +00:00
Console . ReadKey ( ) ;
2017-11-13 08:38:38 +00:00
} ) ;
2017-04-15 08:45:10 +00:00
}
catch ( Exception e )
{
2018-06-24 01:31:08 +00:00
logger . Error ( e , "Exception applying update!" ) ;
2017-04-15 08:45:10 +00:00
}
}
private string GetCurrentVersion ( )
{
2017-11-13 08:38:38 +00:00
var assembly = Assembly . GetExecutingAssembly ( ) ;
2017-04-15 08:45:10 +00:00
var fvi = FileVersionInfo . GetVersionInfo ( assembly . Location ) ;
return fvi . FileVersion ;
}
private void KillPids ( int [ ] pids )
{
foreach ( var pid in pids )
{
try
{
var proc = Process . GetProcessById ( pid ) ;
2018-06-24 01:31:08 +00:00
logger . Info ( "Killing process " + proc . Id ) ;
2018-09-03 14:35:56 +00:00
// try to kill gracefully (on unix) first, see #3692
var exited = false ;
if ( Environment . OSVersion . Platform = = PlatformID . Unix )
{
try
{
2019-03-06 09:20:32 +00:00
var startInfo = new ProcessStartInfo
{
Arguments = "-15 " + pid ,
FileName = "kill"
} ;
2018-09-03 14:35:56 +00:00
Process . Start ( startInfo ) ;
2018-09-03 15:23:18 +00:00
System . Threading . Thread . Sleep ( 1000 ) ; // just sleep, WaitForExit() doesn't seem to work on mono/linux (returns immediantly), https://bugzilla.xamarin.com/show_bug.cgi?id=51742
2018-09-03 15:10:43 +00:00
exited = proc . WaitForExit ( 2000 ) ;
2018-09-03 14:35:56 +00:00
}
catch ( Exception e )
{
logger . Error ( e , "Error while sending SIGTERM to " + pid . ToString ( ) ) ;
}
if ( ! exited )
2018-09-03 15:10:43 +00:00
logger . Info ( "Process " + pid . ToString ( ) + " didn't exit within 2 seconds after a SIGTERM" ) ;
2018-09-03 14:35:56 +00:00
}
if ( ! exited )
{
proc . Kill ( ) ; // send SIGKILL
}
exited = proc . WaitForExit ( 5000 ) ;
2017-04-15 08:45:10 +00:00
if ( ! exited )
2018-09-03 14:35:56 +00:00
logger . Info ( "Process " + pid . ToString ( ) + " didn't exit within 5 seconds after a SIGKILL" ) ;
2017-04-15 08:45:10 +00:00
}
2017-06-28 05:31:38 +00:00
catch ( ArgumentException )
2017-04-15 08:45:10 +00:00
{
2018-06-24 01:31:08 +00:00
logger . Info ( "Process " + pid . ToString ( ) + " is already dead" ) ;
2017-04-15 08:45:10 +00:00
}
catch ( Exception e )
{
2018-06-24 01:31:08 +00:00
logger . Info ( "Error killing process " + pid . ToString ( ) ) ;
logger . Info ( e ) ;
2017-04-15 08:45:10 +00:00
}
}
}
private void ProcessUpdate ( UpdaterConsoleOptions options )
{
var updateLocation = GetUpdateLocation ( ) ;
2018-06-24 01:31:08 +00:00
if ( ! ( updateLocation . EndsWith ( "\\" ) | | updateLocation . EndsWith ( "/" ) ) )
2017-04-15 08:45:10 +00:00
{
updateLocation + = Path . DirectorySeparatorChar ;
}
var pids = new int [ ] { } ;
if ( options . KillPids ! = null )
{
var pidsStr = options . KillPids . Split ( ',' ) . Where ( pid = > ! string . IsNullOrWhiteSpace ( pid ) ) . ToArray ( ) ;
pids = Array . ConvertAll ( pidsStr , pid = > int . Parse ( pid ) ) ;
}
2018-06-24 01:31:08 +00:00
var isWindows = Environment . OSVersion . Platform = = PlatformID . Win32NT ;
2017-04-15 08:45:10 +00:00
var trayRunning = false ;
var trayProcesses = Process . GetProcessesByName ( "JackettTray" ) ;
if ( isWindows )
{
2019-03-06 09:20:32 +00:00
if ( trayProcesses . Length > 0 )
2018-06-24 01:31:08 +00:00
{
2017-04-15 08:45:10 +00:00
foreach ( var proc in trayProcesses )
{
try
{
2018-06-24 01:31:08 +00:00
logger . Info ( "Killing tray process " + proc . Id ) ;
2017-04-15 08:45:10 +00:00
proc . Kill ( ) ;
trayRunning = true ;
}
catch { }
}
}
// on unix we don't have to wait (we can overwrite files which are in use)
// On unix we kill the PIDs after the update so e.g. systemd can automatically restart the process
KillPids ( pids ) ;
}
2018-06-24 01:31:08 +00:00
logger . Info ( "Finding files in: " + updateLocation ) ;
2017-04-15 08:45:10 +00:00
var files = Directory . GetFiles ( updateLocation , "*.*" , SearchOption . AllDirectories ) ;
2018-06-24 01:31:08 +00:00
foreach ( var file in files )
2017-04-15 08:45:10 +00:00
{
var fileName = Path . GetFileName ( file ) . ToLowerInvariant ( ) ;
2019-03-06 09:20:32 +00:00
if ( fileName . EndsWith ( ".zip" )
| | fileName . EndsWith ( ".tar" )
| | fileName . EndsWith ( ".gz" ) )
2017-04-15 08:45:10 +00:00
{
continue ;
}
2018-06-24 01:31:08 +00:00
try
{
logger . Info ( "Copying " + fileName ) ;
2017-04-15 08:45:10 +00:00
var dest = Path . Combine ( options . Path , file . Substring ( updateLocation . Length ) ) ;
var destDir = Path . GetDirectoryName ( dest ) ;
if ( ! Directory . Exists ( destDir ) )
{
2018-06-24 01:31:08 +00:00
logger . Info ( "Creating directory " + destDir ) ;
2017-04-15 08:45:10 +00:00
Directory . CreateDirectory ( destDir ) ;
}
File . Copy ( file , dest , true ) ;
}
2018-06-24 01:31:08 +00:00
catch ( Exception e )
2017-04-15 08:45:10 +00:00
{
2018-06-24 01:31:08 +00:00
logger . Error ( e ) ;
2017-04-15 08:45:10 +00:00
}
}
// delete old dirs
string [ ] oldDirs = new string [ ] { "Content/logos" } ;
foreach ( var oldDir in oldDirs )
{
try
{
var deleteDir = Path . Combine ( options . Path , oldDir ) ;
if ( Directory . Exists ( deleteDir ) )
{
2018-06-24 01:31:08 +00:00
logger . Info ( "Deleting directory " + deleteDir ) ;
2017-04-15 08:45:10 +00:00
Directory . Delete ( deleteDir , true ) ;
}
}
catch ( Exception e )
{
2018-06-24 01:31:08 +00:00
logger . Error ( e ) ;
2017-04-15 08:45:10 +00:00
}
}
// delete old files
string [ ] oldFiles = new string [ ] {
"Content/css/jquery.dataTables.css" ,
"Content/css/jquery.dataTables_themeroller.css" ,
"Definitions/tspate.yml" ,
"Definitions/freakstrackingsystem.yml" ,
"Definitions/rarbg.yml" ,
"Definitions/t411.yml" ,
"Definitions/hdbc.yml" , // renamed to hdbitscom
2017-08-11 11:45:49 +00:00
"Definitions/maniatorrent.yml" ,
2017-05-06 13:30:00 +00:00
"Definitions/nyaa.yml" ,
2017-06-06 17:06:30 +00:00
"Definitions/nachtwerk.yml" ,
2017-08-18 15:59:40 +00:00
"Definitions/leparadisdunet.yml" ,
2017-08-18 16:28:39 +00:00
"Definitions/qctorrent.yml" ,
2017-08-28 12:51:33 +00:00
"Definitions/dragonworld.yml" ,
2017-09-03 10:16:37 +00:00
"Definitions/hdclub.yml" ,
2017-09-11 13:10:54 +00:00
"Definitions/polishtracker.yml" ,
2017-09-15 15:08:33 +00:00
"Definitions/zetorrents.yml" ,
2017-09-22 06:04:52 +00:00
"Definitions/rapidetracker.yml" ,
2017-10-03 12:04:29 +00:00
"Definitions/isohunt.yml" ,
2017-10-31 21:05:54 +00:00
"Definitions/t411v2.yml" ,
2017-11-06 14:12:42 +00:00
"Definitions/bithq.yml" ,
2017-11-06 14:15:02 +00:00
"Definitions/blubits.yml" ,
2017-11-07 18:44:34 +00:00
"Definitions/torrentproject.yml" ,
2017-12-02 04:09:55 +00:00
"Definitions/torrentvault.yml" ,
2017-12-05 15:15:19 +00:00
"Definitions/apollo.yml" , // migrated to C# gazelle base tracker
2018-01-03 18:51:00 +00:00
"Definitions/secretcinema.yml" , // migrated to C# gazelle base tracker
2018-01-10 13:19:35 +00:00
"Definitions/utorrents.yml" , // same as SzeneFZ now
2018-01-10 17:20:52 +00:00
"Definitions/ultrahdclub.yml" ,
2018-01-10 18:04:53 +00:00
"Definitions/infinityt.yml" ,
2018-01-31 18:43:53 +00:00
"Definitions/hachede-c.yml" ,
2018-02-21 18:11:48 +00:00
"Definitions/skytorrents.yml" ,
2018-03-05 17:51:53 +00:00
"Definitions/gormogon.yml" ,
2018-03-05 17:56:01 +00:00
"Definitions/czteam.yml" ,
2018-03-06 10:10:51 +00:00
"Definitions/rockhardlossless.yml" ,
2018-03-29 15:37:27 +00:00
"Definitions/oxtorrent.yml" ,
2018-05-13 12:58:44 +00:00
"Definitions/tehconnection.yml" ,
2018-06-14 17:28:36 +00:00
"Definitions/torrentwtf.yml" ,
2018-07-30 16:06:57 +00:00
"Definitions/eotforum.yml" ,
2018-07-31 09:00:34 +00:00
"Definitions/nexttorrent.yml" ,
2018-12-28 15:16:18 +00:00
"Definitions/torrentsmd.yml" ,
2018-12-28 16:52:32 +00:00
"Definitions/scenehd.yml" , // migrated to C# (use JSON API)
2018-08-14 09:58:11 +00:00
"appsettings.Development.json" ,
2018-08-18 08:22:28 +00:00
"CurlSharp.dll" ,
"CurlSharp.pdb" ,
"Autofac.Integration.WebApi.dll" ,
"Microsoft.Owin.dll" ,
"Microsoft.Owin.FileSystems.dll" ,
"Microsoft.Owin.Host.HttpListener.dll" ,
"Microsoft.Owin.Hosting.dll" ,
"Microsoft.Owin.StaticFiles.dll" ,
"Owin.dll" ,
"System.Web.Http.dll" ,
"System.Web.Http.Owin.dll" ,
"System.Web.Http.Tracing.dll" ,
2018-09-14 23:33:22 +00:00
"Definitions/torrentkim.yml" ,
2018-09-15 05:22:38 +00:00
"Definitions/horriblesubs.yml" ,
2018-09-19 00:34:03 +00:00
"Definitions/idope.yml" ,
2018-09-26 08:08:47 +00:00
"Definitions/bt-scene.yml" ,
2018-11-18 03:23:48 +00:00
"Definitions/extratorrentclone.yml" ,
2019-03-08 05:19:28 +00:00
"Definitions/torrentcouch.yml" ,
2019-03-09 02:53:48 +00:00
"Definitions/idopeclone.yml" ,
2019-03-12 19:33:01 +00:00
"Definitions/torrof.yml" ,
2019-03-18 18:22:27 +00:00
"Definitions/archetorrent.yml" ,
2019-03-19 14:15:21 +00:00
"Definitions/420files.yml" ,
2019-03-19 14:17:59 +00:00
"Definitions/redtopia.yml" ,
2019-03-19 14:20:47 +00:00
"Definitions/btxpress.yml" ,
2019-03-20 08:28:08 +00:00
"Definitions/btstornet.yml" ,
2017-04-15 08:45:10 +00:00
} ;
2018-06-24 01:31:08 +00:00
foreach ( var oldFile in oldFiles )
2017-04-15 08:45:10 +00:00
{
try
{
2018-06-24 01:31:08 +00:00
var deleteFile = Path . Combine ( options . Path , oldFile ) ;
2017-04-15 08:45:10 +00:00
if ( File . Exists ( deleteFile ) )
{
2018-06-24 01:31:08 +00:00
logger . Info ( "Deleting file " + deleteFile ) ;
2017-04-15 08:45:10 +00:00
File . Delete ( deleteFile ) ;
}
}
catch ( Exception e )
{
2018-06-24 01:31:08 +00:00
logger . Error ( e ) ;
2017-04-15 08:45:10 +00:00
}
}
// kill pids after the update on UNIX
if ( ! isWindows )
KillPids ( pids ) ;
2019-03-06 09:20:32 +00:00
if ( ! options . NoRestart )
2017-04-15 08:45:10 +00:00
{
2018-06-27 12:02:51 +00:00
if ( isWindows & & ( trayRunning | | options . StartTray ) & & ! string . Equals ( options . Type , "WindowsService" , StringComparison . OrdinalIgnoreCase ) )
2017-04-15 08:45:10 +00:00
{
var startInfo = new ProcessStartInfo ( )
{
2018-06-26 09:44:12 +00:00
Arguments = $"--UpdatedVersion \" { EnvironmentUtil . JackettVersion } \ "" ,
2017-04-15 08:45:10 +00:00
FileName = Path . Combine ( options . Path , "JackettTray.exe" ) ,
UseShellExecute = true
} ;
2018-06-26 09:44:12 +00:00
logger . Info ( "Starting Tray: " + startInfo . FileName + " " + startInfo . Arguments ) ;
2017-04-15 08:45:10 +00:00
Process . Start ( startInfo ) ;
2018-06-24 01:31:08 +00:00
if ( ! windowsService . ServiceExists ( ) )
{
//User was running the tray icon, but not using the Windows service, starting Tray icon will start JackettConsole as well
return ;
}
2017-04-15 08:45:10 +00:00
}
2018-06-27 12:02:51 +00:00
if ( string . Equals ( options . Type , "WindowsService" , StringComparison . OrdinalIgnoreCase ) )
2017-04-15 08:45:10 +00:00
{
2018-06-26 09:44:12 +00:00
logger . Info ( "Starting Windows service" ) ;
if ( ServerUtil . IsUserAdministrator ( ) )
2017-04-15 08:45:10 +00:00
{
2018-06-24 01:31:08 +00:00
windowsService . Start ( ) ;
2017-04-15 08:45:10 +00:00
}
2018-06-26 09:44:12 +00:00
else
{
try
{
var consolePath = Path . Combine ( options . Path , "JackettConsole.exe" ) ;
processService . StartProcessAndLog ( consolePath , "--Start" , true ) ;
}
catch
{
logger . Error ( "Failed to get admin rights to start the service." ) ;
}
}
2018-06-24 01:31:08 +00:00
}
else
2017-04-15 08:45:10 +00:00
{
var startInfo = new ProcessStartInfo ( )
{
Arguments = options . Args ,
2019-03-03 04:44:18 +00:00
FileName = GetJackettConsolePath ( options . Path ) ,
2017-04-15 08:45:10 +00:00
UseShellExecute = true
} ;
2018-06-30 12:49:11 +00:00
if ( isWindows )
{
//User didn't initiate the update from Windows service and wasn't running Jackett via the tray, must have started from the console
startInfo . Arguments = $"/K {startInfo.FileName} {startInfo.Arguments}" ;
startInfo . FileName = "cmd.exe" ;
startInfo . CreateNoWindow = false ;
startInfo . WindowStyle = ProcessWindowStyle . Normal ;
}
2019-03-06 09:20:32 +00:00
2019-03-03 04:44:18 +00:00
if ( variant = = Variants . JackettVariant . Mono )
2017-04-15 08:45:10 +00:00
{
startInfo . Arguments = startInfo . FileName + " " + startInfo . Arguments ;
startInfo . FileName = "mono" ;
}
2019-03-06 10:16:20 +00:00
if ( variant = = Variants . JackettVariant . CoreMacOs | | variant = = Variants . JackettVariant . CoreLinuxAmdx64
| | variant = = Variants . JackettVariant . CoreLinuxArm32 | | variant = = Variants . JackettVariant . CoreLinuxArm64 )
{
startInfo . UseShellExecute = false ;
startInfo . CreateNoWindow = true ;
}
2018-06-24 01:31:08 +00:00
logger . Info ( "Starting Jackett: " + startInfo . FileName + " " + startInfo . Arguments ) ;
2017-04-15 08:45:10 +00:00
Process . Start ( startInfo ) ;
}
}
}
private string GetUpdateLocation ( )
{
var location = new Uri ( Assembly . GetEntryAssembly ( ) . GetName ( ) . CodeBase ) ;
2018-06-24 01:31:08 +00:00
return new FileInfo ( WebUtility . UrlDecode ( location . AbsolutePath ) ) . DirectoryName ;
2017-04-15 08:45:10 +00:00
}
2019-03-03 04:44:18 +00:00
private string GetJackettConsolePath ( string directoryPath )
{
2019-03-06 09:20:32 +00:00
if ( variant = = Variants . JackettVariant . CoreMacOs | | variant = = Variants . JackettVariant . CoreLinuxAmdx64
| | variant = = Variants . JackettVariant . CoreLinuxArm32 | | variant = = Variants . JackettVariant . CoreLinuxArm64 )
2019-03-03 04:44:18 +00:00
{
return Path . Combine ( directoryPath , "jackett" ) ;
}
else
{
return Path . Combine ( directoryPath , "JackettConsole.exe" ) ;
}
}
2017-04-15 08:45:10 +00:00
}
}