# -*- 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 . from __future__ import absolute_import from future import standard_library standard_library.install_aliases() from builtins import str from builtins import object import os import sys from urllib.parse import urlencode import cherrypy import plexpy from plexpy import logger from plexpy import webauth from plexpy.helpers import create_https_certificates from plexpy.webserve import WebInterface def start(): logger.info("Tautulli WebStart :: Initializing Tautulli web server...") web_config = { 'http_port': plexpy.HTTP_PORT, 'http_host': plexpy.CONFIG.HTTP_HOST, 'http_root': plexpy.CONFIG.HTTP_ROOT, 'http_environment': plexpy.CONFIG.HTTP_ENVIRONMENT, 'http_proxy': plexpy.CONFIG.HTTP_PROXY, 'enable_https': plexpy.CONFIG.ENABLE_HTTPS, 'https_cert': plexpy.CONFIG.HTTPS_CERT, 'https_cert_chain': plexpy.CONFIG.HTTPS_CERT_CHAIN, 'https_key': plexpy.CONFIG.HTTPS_KEY, 'http_username': plexpy.CONFIG.HTTP_USERNAME, 'http_password': plexpy.CONFIG.HTTP_PASSWORD, 'http_basic_auth': plexpy.CONFIG.HTTP_BASIC_AUTH } initialize(web_config) def stop(): logger.info("Tautulli WebStart :: Stopping Tautulli web server...") cherrypy.engine.exit() def restart(): logger.info("Tautulli WebStart :: Restarting Tautulli web server...") stop() start() def initialize(options): # HTTPS stuff stolen from sickbeard enable_https = options['enable_https'] https_cert = options['https_cert'] https_cert_chain = options['https_cert_chain'] https_key = options['https_key'] if enable_https: # If either the HTTPS certificate or key do not exist, try to make self-signed ones. if plexpy.CONFIG.HTTPS_CREATE_CERT and \ (not (https_cert and os.path.exists(https_cert)) or not (https_key and os.path.exists(https_key))): if not create_https_certificates(https_cert, https_key): logger.warn("Tautulli WebStart :: Unable to create certificate and key. Disabling HTTPS") enable_https = False if not (os.path.exists(https_cert) and os.path.exists(https_key)): logger.warn("Tautulli WebStart :: Disabled HTTPS because of missing certificate and key.") enable_https = False options_dict = { 'server.socket_port': options['http_port'], 'server.socket_host': options['http_host'], 'environment': options['http_environment'], 'server.thread_pool': 10, 'tools.encode.on': True, 'tools.encode.encoding': 'utf-8', 'tools.decode.on': True } if plexpy.DEV: options_dict['environment'] = "test_suite" options_dict['engine.autoreload.on'] = True if enable_https: options_dict['server.ssl_certificate'] = https_cert options_dict['server.ssl_certificate_chain'] = https_cert_chain options_dict['server.ssl_private_key'] = https_key protocol = "https" else: protocol = "http" if options['http_proxy']: # Overwrite cherrypy.tools.proxy with our own proxy handler cherrypy.tools.proxy = cherrypy.Tool('before_handler', proxy, priority=1) if options['http_password']: login_allowed = ["Tautulli admin (username is '%s')" % options['http_username']] if plexpy.CONFIG.HTTP_PLEX_ADMIN: login_allowed.append("Plex admin") logger.info("Tautulli WebStart :: Web server authentication is enabled: %s.", ' and '.join(login_allowed)) if options['http_basic_auth']: plexpy.AUTH_ENABLED = False basic_auth_enabled = True else: plexpy.AUTH_ENABLED = True basic_auth_enabled = False cherrypy.tools.auth = cherrypy.Tool('before_handler', webauth.check_auth, priority=2) else: plexpy.AUTH_ENABLED = False basic_auth_enabled = False if options['http_root'].strip('/'): plexpy.HTTP_ROOT = options['http_root'] = '/' + options['http_root'].strip('/') + '/' else: plexpy.HTTP_ROOT = options['http_root'] = '/' cherrypy.config.update(options_dict) conf = { '/': { 'tools.staticdir.root': os.path.join(plexpy.PROG_DIR, 'data'), 'tools.proxy.on': bool(options['http_proxy']), 'tools.gzip.on': True, 'tools.gzip.mime_types': ['text/html', 'text/plain', 'text/css', 'text/javascript', 'application/json', 'application/javascript'], 'tools.auth.on': plexpy.AUTH_ENABLED, 'tools.auth_basic.on': basic_auth_enabled, 'tools.auth_basic.realm': 'Tautulli web server', 'tools.auth_basic.checkpassword': cherrypy.lib.auth_basic.checkpassword_dict({ options['http_username']: options['http_password']}) }, '/api': { 'tools.auth_basic.on': False }, '/status': { 'tools.auth_basic.on': False }, '/interfaces': { 'tools.staticdir.on': True, 'tools.staticdir.dir': "interfaces", 'tools.caching.on': True, 'tools.caching.force': True, 'tools.caching.delay': 0, 'tools.expires.on': True, 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days 'tools.sessions.on': False, 'tools.auth.on': False }, '/images': { 'tools.staticdir.on': True, 'tools.staticdir.dir': "interfaces/default/images", 'tools.staticdir.content_types': {'svg': 'image/svg+xml'}, 'tools.caching.on': True, 'tools.caching.force': True, 'tools.caching.delay': 0, 'tools.expires.on': True, 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days 'tools.sessions.on': False, 'tools.auth.on': False }, '/css': { 'tools.staticdir.on': True, 'tools.staticdir.dir': "interfaces/default/css", 'tools.caching.on': True, 'tools.caching.force': True, 'tools.caching.delay': 0, 'tools.expires.on': True, 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days 'tools.sessions.on': False, 'tools.auth.on': False }, '/fonts': { 'tools.staticdir.on': True, 'tools.staticdir.dir': "interfaces/default/fonts", 'tools.caching.on': True, 'tools.caching.force': True, 'tools.caching.delay': 0, 'tools.expires.on': True, 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days 'tools.sessions.on': False, 'tools.auth.on': False }, '/js': { 'tools.staticdir.on': True, 'tools.staticdir.dir': "interfaces/default/js", 'tools.caching.on': True, 'tools.caching.force': True, 'tools.caching.delay': 0, 'tools.expires.on': True, 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days 'tools.sessions.on': False, 'tools.auth.on': False }, '/cache': { 'tools.staticdir.on': True, 'tools.staticdir.dir': plexpy.CONFIG.CACHE_DIR, 'tools.caching.on': True, 'tools.caching.force': True, 'tools.caching.delay': 0, 'tools.expires.on': True, 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days 'tools.sessions.on': False, 'tools.auth.on': False }, #'/pms_image_proxy': { # 'tools.staticdir.on': True, # 'tools.staticdir.dir': os.path.join(plexpy.CONFIG.CACHE_DIR, 'images'), # 'tools.caching.on': True, # 'tools.caching.force': True, # 'tools.caching.delay': 0, # 'tools.expires.on': True, # 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days # 'tools.auth.on': False, # 'tools.sessions.on': False #}, '/favicon.ico': { 'tools.staticfile.on': True, 'tools.staticfile.filename': os.path.abspath(os.path.join(plexpy.PROG_DIR, 'data/interfaces/default/images/favicon/favicon.ico')), 'tools.caching.on': True, 'tools.caching.force': True, 'tools.caching.delay': 0, 'tools.expires.on': True, 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days 'tools.sessions.on': False, 'tools.auth.on': False } } # Prevent time-outs cherrypy.engine.timeout_monitor.unsubscribe() cherrypy.tree.mount(WebInterface(), options['http_root'], config=conf) if plexpy.HTTP_ROOT != '/': cherrypy.tree.mount(BaseRedirect(), '/') try: logger.info("Tautulli WebStart :: Starting Tautulli web server on %s://%s:%d%s", protocol, options['http_host'], options['http_port'], options['http_root']) cherrypy.process.servers.check_port(str(options['http_host']), options['http_port']) if not plexpy.DEV: cherrypy.server.start() else: cherrypy.engine.signals.subscribe() cherrypy.engine.start() cherrypy.engine.block() except IOError: sys.stderr.write('Failed to start on port: %i. Is something else running?\n' % (options['http_port'])) sys.exit(1) cherrypy.server.wait() class BaseRedirect(object): @cherrypy.expose def index(self): raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT) @cherrypy.expose def status(self, *args, **kwargs): path = '/' + '/'.join(args) if args else '' query = '?' + urlencode(kwargs) if kwargs else '' raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + 'status' + path + query) def proxy(): # logger.debug("REQUEST URI: %s, HEADER [X-Forwarded-Host]: %s, [X-Host]: %s, [Origin]: %s, [Host]: %s", # cherrypy.request.wsgi_environ['REQUEST_URI'], # cherrypy.request.headers.get('X-Forwarded-Host'), # cherrypy.request.headers.get('X-Host'), # cherrypy.request.headers.get('Origin'), # cherrypy.request.headers.get('Host')) # Change cherrpy.tools.proxy.local header if X-Forwarded-Host header is not present local = 'X-Forwarded-Host' if not cherrypy.request.headers.get('X-Forwarded-Host'): if cherrypy.request.headers.get('X-Host'): # lighttpd local = 'X-Host' elif cherrypy.request.headers.get('Origin'): # Squid local = 'Origin' elif cherrypy.request.headers.get('Host'): # nginx local = 'Host' # logger.debug("cherrypy.tools.proxy.local set to [%s]", local) # Call original cherrypy proxy tool with the new local cherrypy.lib.cptools.proxy(local=local)