codekingpro/portable-devtools
114k
1##########################################################################2#3# pgAdmin 4 - PostgreSQL Tools4#5# Copyright (C) 2013 - 2024, The pgAdmin Development Team6# This software is released under the PostgreSQL Licence7#8##########################################################################9 10"""This is the main application entry point for pgAdmin 4. If running on11a webserver, this will provide the WSGI interface, otherwise, we're going12to start a web server."""13 14import sys15if sys.version_info <= (3, 9):16 import select17 18if sys.version_info < (3, 4):19 raise RuntimeError('This application must be run under Python 3.4 '20 'or later.')21import builtins22import os23 24# We need to include the root directory in sys.path to ensure that we can25# find everything we need when running in the standalone runtime.26if sys.path[0] != os.path.dirname(os.path.realpath(__file__)):27 sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))28 29# Grab the SERVER_MODE if it's been set by the runtime30if 'PGADMIN_SERVER_MODE' in os.environ:31 if os.environ['PGADMIN_SERVER_MODE'] == 'OFF':32 builtins.SERVER_MODE = False33 else:34 builtins.SERVER_MODE = True35else:36 builtins.SERVER_MODE = None37 38if (3, 10) > sys.version_info > (3, 8, 99) and os.name == 'posix':39 # Fix eventlet issue with Python 3.9.40 # Ref: https://github.com/eventlet/eventlet/issues/67041 # This was causing issue in psycopg342 from eventlet import hubs43 hubs.use_hub("poll")44 45 import selectors46 selectors.DefaultSelector = selectors.PollSelector47 48import config49import setup50from pgadmin import create_app, socketio51from pgadmin.utils.constants import INTERNAL52# Get the config database schema version. We store this in pgadmin.model53# as it turns out that putting it in the config files isn't a great idea54from pgadmin.model import SCHEMA_VERSION55 56 57##########################################################################58# Support reverse proxying59##########################################################################60class ReverseProxied():61 def __init__(self, app):62 self.app = app63 # https://werkzeug.palletsprojects.com/en/0.15.x/middleware/proxy_fix64 try:65 from werkzeug.middleware.proxy_fix import ProxyFix66 self.app = ProxyFix(app,67 x_for=config.PROXY_X_FOR_COUNT,68 x_proto=config.PROXY_X_PROTO_COUNT,69 x_host=config.PROXY_X_HOST_COUNT,70 x_port=config.PROXY_X_PORT_COUNT,71 x_prefix=config.PROXY_X_PREFIX_COUNT72 )73 except ImportError:74 pass75 76 def __call__(self, environ, start_response):77 script_name = environ.get("HTTP_X_SCRIPT_NAME", "")78 if script_name:79 environ["SCRIPT_NAME"] = script_name80 path_info = environ["PATH_INFO"]81 if path_info.startswith(script_name):82 environ["PATH_INFO"] = path_info[len(script_name):]83 scheme = environ.get("HTTP_X_SCHEME", "")84 if scheme:85 environ["wsgi.url_scheme"] = scheme86 return self.app(environ, start_response)87 88 89##########################################################################90# Sanity checks91##########################################################################92config.SETTINGS_SCHEMA_VERSION = SCHEMA_VERSION93 94# Check if the database exists. If it does not, create it.95setup_db_required = False96if not os.path.isfile(config.SQLITE_PATH):97 setup_db_required = True98 99##########################################################################100# Create the app and configure it. It is created outside main so that101# it can be imported102##########################################################################103app = create_app()104app.config['sessions'] = dict()105 106if setup_db_required:107 setup.setup_db(app)108 109# Authentication sources110if len(config.AUTHENTICATION_SOURCES) > 0:111 # Creating a temporary auth source list removing INTERNAL112 # This change is done to avoid selecting INTERNAL authentication when user113 # mistakenly keeps that the first option.114 auth_source = [x for x in config.AUTHENTICATION_SOURCES115 if x != INTERNAL]116 app.PGADMIN_EXTERNAL_AUTH_SOURCE = auth_source[0] \117 if len(auth_source) > 0 else INTERNAL118else:119 app.PGADMIN_EXTERNAL_AUTH_SOURCE = INTERNAL120 121# Start the web server. The port number should have already been set by the122# runtime if we're running in desktop mode, otherwise we'll just use the123# Flask default.124app.PGADMIN_RUNTIME = False125app.logger.debug(126 'Config server mode: %s', config.SERVER_MODE127)128config.EFFECTIVE_SERVER_PORT = None129if 'PGADMIN_INT_PORT' in os.environ:130 port = os.environ['PGADMIN_INT_PORT']131 app.logger.debug(132 'Running under the desktop runtime, port: %s',133 port134 )135 config.EFFECTIVE_SERVER_PORT = int(port)136else:137 app.logger.debug(138 'Not running under the desktop runtime, port: %s',139 config.DEFAULT_SERVER_PORT140 )141 config.EFFECTIVE_SERVER_PORT = config.DEFAULT_SERVER_PORT142 143# Set the key if appropriate144if 'PGADMIN_INT_KEY' in os.environ:145 app.PGADMIN_INT_KEY = os.environ['PGADMIN_INT_KEY']146 app.logger.debug("Desktop security key: %s" % app.PGADMIN_INT_KEY)147 app.PGADMIN_RUNTIME = True148else:149 app.PGADMIN_INT_KEY = ''150 151if not app.PGADMIN_RUNTIME:152 app.wsgi_app = ReverseProxied(app.wsgi_app)153 154app.run_before_app_start()155 156 157##########################################################################158# The entry point159##########################################################################160def main():161 # Set null device file path to stdout, stdin, stderr if they are None162 for _name in ('stdin', 'stdout', 'stderr'):163 if getattr(sys, _name) is None:164 setattr(sys, _name,165 open(os.devnull, 'r' if _name == 'stdin' else 'w'))166 167 # Output a startup message if we're not under the runtime and startup.168 # If we're under WSGI, we don't need to worry about this169 if not app.PGADMIN_RUNTIME:170 print(171 "Starting %s. Please navigate to http://%s:%d in your browser." %172 (config.APP_NAME, config.DEFAULT_SERVER,173 config.EFFECTIVE_SERVER_PORT)174 )175 sys.stdout.flush()176 else:177 # For unknown reason the runtime does not pass the environment178 # variables (i.e. PYTHONHOME, and PYTHONPATH), to the Python179 # sub-processes, leading to failures executing background processes.180 #181 # This has been observed only on windows. On *nix systems, it is likely182 # picking the system python environment, which is good enough to run183 # the process-executor.184 #185 # Setting PYTHONHOME launch them properly.186 from pgadmin.utils import IS_WIN187 188 if IS_WIN:189 os.environ['PYTHONHOME'] = sys.prefix190 191 # Initialize Flask service only once192 # If `WERKZEUG_RUN_MAIN` is None, i.e: app is initializing for first time193 # so set `use_reloader` = False, thus reload won't call.194 # Reference:195 # https://github.com/pallets/werkzeug/issues/220#issuecomment-11176538196 try:197 if config.DEBUG:198 app.run(199 host=config.DEFAULT_SERVER,200 port=config.EFFECTIVE_SERVER_PORT,201 debug=config.DEBUG,202 use_reloader=(203 (not app.PGADMIN_RUNTIME) and204 os.environ.get("WERKZEUG_RUN_MAIN") is not None205 ),206 threaded=config.THREADED_MODE207 )208 else:209 try:210 socketio.run(211 app,212 debug=config.DEBUG,213 allow_unsafe_werkzeug=True,214 host=config.DEFAULT_SERVER,215 port=config.EFFECTIVE_SERVER_PORT,216 )217 except KeyboardInterrupt:218 print("CLOSE SERVER")219 socketio.stop()220 221 except IOError:222 app.logger.error("Error starting the app server: %s", sys.exc_info())223 224 225##########################################################################226# Server startup227##########################################################################228if __name__ == '__main__':229 main()230 