mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-08-19 04:49:36 -07:00
Add Tautulli Windows exe updater
This commit is contained in:
parent
7e850dd88d
commit
0b845294fb
4 changed files with 209 additions and 3 deletions
|
@ -3,6 +3,7 @@
|
||||||
import sys
|
import sys
|
||||||
sys.modules['FixTk'] = None
|
sys.modules['FixTk'] = None
|
||||||
|
|
||||||
|
excludes = ['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter']
|
||||||
block_cipher = None
|
block_cipher = None
|
||||||
|
|
||||||
analysis = Analysis(
|
analysis = Analysis(
|
||||||
|
@ -12,13 +13,27 @@ analysis = Analysis(
|
||||||
('..\\data', 'data'),
|
('..\\data', 'data'),
|
||||||
('..\\CHANGELOG.md', '.'),
|
('..\\CHANGELOG.md', '.'),
|
||||||
('..\\LICENSE', '.'),
|
('..\\LICENSE', '.'),
|
||||||
|
('..\\branch.txt', '.'),
|
||||||
('..\\version.txt', '.'),
|
('..\\version.txt', '.'),
|
||||||
('..\\lib\\ipwhois\\data', 'data')
|
('..\\lib\\ipwhois\\data', 'data'),
|
||||||
|
('TautulliUpdateTask.xml', '.')
|
||||||
],
|
],
|
||||||
excludes=['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter'],
|
excludes=excludes,
|
||||||
hiddenimports=['pkg_resources.py2_warn', 'cheroot.ssl', 'cheroot.ssl.builtin'],
|
hiddenimports=['pkg_resources.py2_warn', 'cheroot.ssl', 'cheroot.ssl.builtin'],
|
||||||
cipher=block_cipher,
|
cipher=block_cipher
|
||||||
)
|
)
|
||||||
|
updater_analysis = Analysis(
|
||||||
|
['updater-windows.py'],
|
||||||
|
pathex=['lib'],
|
||||||
|
excludes=excludes,
|
||||||
|
cipher=block_cipher
|
||||||
|
)
|
||||||
|
|
||||||
|
MERGE(
|
||||||
|
(analysis, 'Tautulli', 'Tautulli'),
|
||||||
|
(updater_analysis, 'updater', 'updater')
|
||||||
|
)
|
||||||
|
|
||||||
pyz = PYZ(
|
pyz = PYZ(
|
||||||
analysis.pure,
|
analysis.pure,
|
||||||
analysis.zipped_data,
|
analysis.zipped_data,
|
||||||
|
@ -39,3 +54,24 @@ coll = COLLECT(
|
||||||
analysis.datas,
|
analysis.datas,
|
||||||
name='Tautulli'
|
name='Tautulli'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
updater_pyz = PYZ(
|
||||||
|
updater_analysis.pure,
|
||||||
|
updater_analysis.zipped_data,
|
||||||
|
cipher=block_cipher
|
||||||
|
)
|
||||||
|
updater_exe = EXE(
|
||||||
|
updater_pyz,
|
||||||
|
updater_analysis.scripts,
|
||||||
|
exclude_binaries=True,
|
||||||
|
name='updater',
|
||||||
|
console=False,
|
||||||
|
icon='..\\data\\interfaces\\default\\images\\logo-circle.ico'
|
||||||
|
)
|
||||||
|
coll = COLLECT(
|
||||||
|
updater_exe,
|
||||||
|
updater_analysis.binaries,
|
||||||
|
updater_analysis.zipfiles,
|
||||||
|
updater_analysis.datas,
|
||||||
|
name='updater'
|
||||||
|
)
|
||||||
|
|
|
@ -123,6 +123,8 @@ SetOverwrite ifnewer
|
||||||
SetOutPath "$INSTDIR"
|
SetOutPath "$INSTDIR"
|
||||||
File /nonfatal /a /r "..\dist\${APP_NAME}\"
|
File /nonfatal /a /r "..\dist\${APP_NAME}\"
|
||||||
|
|
||||||
|
nsExec::Exec '$SYSDIR\SCHTASKS /Create /TN TautulliUpdateTask /XML "$INSTDIR\TautulliUpdateTask.xml" /F'
|
||||||
|
|
||||||
IfSilent 0 +2
|
IfSilent 0 +2
|
||||||
ExecShell "" "$INSTDIR\${MAIN_APP_EXE}" $nolaunch
|
ExecShell "" "$INSTDIR\${MAIN_APP_EXE}" $nolaunch
|
||||||
SectionEnd
|
SectionEnd
|
||||||
|
@ -208,6 +210,9 @@ RmDir "$SMPROGRAMS\${APP_NAME}"
|
||||||
|
|
||||||
DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}"
|
DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}"
|
||||||
DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}"
|
DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}"
|
||||||
|
|
||||||
|
nsExec::Exec "$SYSDIR\SCHTASKS /Delete /TN TautulliUpdateTask /F"
|
||||||
|
|
||||||
SectionEnd
|
SectionEnd
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
BIN
package/TautulliUpdateTask.xml
Normal file
BIN
package/TautulliUpdateTask.xml
Normal file
Binary file not shown.
165
package/updater-windows.py
Normal file
165
package/updater-windows.py
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# This file is part of Tautulli.
|
||||||
|
#
|
||||||
|
# Tautulli is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Tautulli is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Tautulli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from logging import handlers
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
CREATE_NO_WINDOW = 0x08000000
|
||||||
|
REPO_URL = 'https://api.github.com/repos/Tautulli/Tautulli'
|
||||||
|
|
||||||
|
LOGFILE = 'updater.log'
|
||||||
|
LOGPATH = os.path.join(SCRIPT_PATH, LOGFILE)
|
||||||
|
MAX_SIZE = 5000000
|
||||||
|
MAX_FILES = 1
|
||||||
|
|
||||||
|
logger = logging.getLogger('updater')
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
file_formatter = logging.Formatter(
|
||||||
|
'%(asctime)s - %(levelname)-7s :: %(threadName)s : Tautulli Updater :: %(message)s',
|
||||||
|
'%Y-%m-%d %H:%M:%S')
|
||||||
|
file_handler = handlers.RotatingFileHandler(
|
||||||
|
LOGPATH, maxBytes=MAX_SIZE, backupCount=MAX_FILES, encoding='utf-8')
|
||||||
|
file_handler.setFormatter(file_formatter)
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
|
||||||
|
def kill_if_exists(process_name):
|
||||||
|
output = subprocess.check_output(
|
||||||
|
['TASKLIST', '/FI', 'IMAGENAME eq {}'.format(process_name)],
|
||||||
|
creationflags=CREATE_NO_WINDOW).decode()
|
||||||
|
output = output.strip().split('\n')[-1]
|
||||||
|
if output.lower().startswith(process_name.lower()):
|
||||||
|
return subprocess.check_call(
|
||||||
|
['TASKKILL', '/IM', process_name],
|
||||||
|
creationflags=CREATE_NO_WINDOW)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def update_tautulli():
|
||||||
|
logger.info('Starting Tautulli update check')
|
||||||
|
|
||||||
|
with open(os.path.join(SCRIPT_PATH, 'branch.txt'), 'r') as f:
|
||||||
|
branch = f.read()
|
||||||
|
logger.info('Branch: %s', branch)
|
||||||
|
|
||||||
|
with open(os.path.join(SCRIPT_PATH, 'version.txt'), 'r') as f:
|
||||||
|
current_version = f.read()
|
||||||
|
logger.info('Current version: %s', current_version)
|
||||||
|
|
||||||
|
logger.info('Retrieving latest version from GitHub')
|
||||||
|
try:
|
||||||
|
response = requests.get('{}/commits/{}'.format(REPO_URL, branch))
|
||||||
|
response.raise_for_status()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Request error: %s', e)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
try:
|
||||||
|
commits = response.json()
|
||||||
|
latest_version = commits['sha']
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Failed to retrieve latest version: %s', e)
|
||||||
|
return 1
|
||||||
|
logger.info('Latest version: %s', latest_version)
|
||||||
|
|
||||||
|
if current_version == latest_version:
|
||||||
|
logger.info('Tautulli is already up to date')
|
||||||
|
return 0
|
||||||
|
|
||||||
|
logger.info('Comparing version on GitHub')
|
||||||
|
try:
|
||||||
|
response = requests.get('{}/compare/{}...{}'.format(REPO_URL, latest_version, current_version))
|
||||||
|
response.raise_for_status()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Request error: %s', e)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
try:
|
||||||
|
compare = response.json()
|
||||||
|
commits_behind = compare['behind_by']
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Failed to compare commits: %s', e)
|
||||||
|
return 1
|
||||||
|
logger.info('Commits behind: %s', commits_behind)
|
||||||
|
|
||||||
|
if commits_behind > 0:
|
||||||
|
logger.info('Retrieving releases on GitHub')
|
||||||
|
try:
|
||||||
|
response = requests.get('{}/releases'.format(REPO_URL))
|
||||||
|
response.raise_for_status()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Request error: %s', e)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
try:
|
||||||
|
releases = response.json()
|
||||||
|
|
||||||
|
if branch == 'master':
|
||||||
|
release = next((r for r in releases if not r['prerelease']), releases[0])
|
||||||
|
else:
|
||||||
|
release = next((r for r in releases), releases[0])
|
||||||
|
|
||||||
|
version = release['tag_name']
|
||||||
|
asset = next((a for a in release['assets'] if a['content_type'] == 'application/vnd.microsoft.portable-executable'), None)
|
||||||
|
download_url = asset['browser_download_url']
|
||||||
|
download_file = asset['name']
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Failed to retrieve releases: %s', e)
|
||||||
|
return 1
|
||||||
|
logger.info('Release: %s', version)
|
||||||
|
|
||||||
|
file_path = os.path.join(tempfile.gettempdir(), download_file)
|
||||||
|
logger.info('Downloading installer to temporary directory: %s', file_path)
|
||||||
|
with requests.get(download_url, stream=True) as r:
|
||||||
|
with open(file_path, 'wb') as f:
|
||||||
|
shutil.copyfileobj(r.raw, f)
|
||||||
|
|
||||||
|
logger.info('Stopping Tautulli')
|
||||||
|
try:
|
||||||
|
killed = kill_if_exists('Tautulli.exe')
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Failed to stop Tautulli: %s', e)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if killed != 0:
|
||||||
|
logger.error('Failed to stop Tautulli')
|
||||||
|
return 1
|
||||||
|
|
||||||
|
logger.info('Running %s', download_file)
|
||||||
|
try:
|
||||||
|
subprocess.call(
|
||||||
|
[file_path, '/S'],
|
||||||
|
creationflags=CREATE_NO_WINDOW)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception('Failed to install Tautulli: %s', e)
|
||||||
|
return -1
|
||||||
|
|
||||||
|
logger.info('Tautulli updated to %s', version)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
status = update_tautulli()
|
||||||
|
logger.debug('Update function returned %s', status)
|
Loading…
Add table
Add a link
Reference in a new issue