    async def start(self):
        """Start the whole thing"""
        self.io_loop = loop = IOLoop.current()

        if self.subapp:
            self.subapp.start()
            loop.stop()
            return

        if self.generate_config:
            self.write_config_file()
            loop.stop()
            return

        if self.generate_certs:
            self.load_config_file(self.config_file)
            if not self.internal_ssl:
                self.log.warning(
                    "You'll need to enable `internal_ssl` "
                    "in the `jupyterhub_config` file to use "
                    "these certs."
                )
                self.internal_ssl = True
            self.init_internal_ssl()
            self.log.info(
                f"Certificates written to directory `{self.internal_certs_location}`"
            )
            loop.stop()
            return

        # start collecting metrics
        self.metrics_collector.start()

        # start the proxy
        if self.proxy.should_start:
            try:
                await self.proxy.start()
            except Exception as e:
                self.log.critical("Failed to start proxy", exc_info=True)
                self.exit(1)
        else:
            self.log.info("Not starting proxy")

        # verify that we can talk to the proxy before listening.
        # avoids delayed failure if we can't talk to the proxy
        await self.proxy.get_all_routes()

        ssl_context = make_ssl_context(
            self.internal_ssl_key,
            self.internal_ssl_cert,
            cafile=self.internal_ssl_ca,
            purpose=ssl.Purpose.CLIENT_AUTH,
        )

        # start the webserver
        self.http_server = tornado.httpserver.HTTPServer(
            self.tornado_application,
            ssl_options=ssl_context,
            xheaders=True,
            trusted_downstream=self.trusted_downstream_ips,
        )
        bind_url = urlparse(self.hub.bind_url)
        try:
            if bind_url.scheme.startswith('unix+'):
                from tornado.netutil import bind_unix_socket

                socket = bind_unix_socket(unquote(bind_url.netloc))
                self.http_server.add_socket(socket)
            else:
                ip = bind_url.hostname
                port = bind_url.port
                if not port:
                    if bind_url.scheme == 'https':
                        port = 443
                    else:
                        port = 80
                self.http_server.listen(port, address=ip)
            self.log.info("Hub API listening on %s", self.hub.bind_url)
            if self.hub.url != self.hub.bind_url:
                self.log.info("Private Hub API connect url %s", self.hub.url)
        except Exception:
            self.log.error("Failed to bind hub to %s", self.hub.bind_url)
            raise

        # start the service(s)
        for service_name, service in self._service_map.items():
            service_ready = await self.start_service(service_name, service, ssl_context)
            if not service_ready and service.managed:
                self.exit(1)

        await self.proxy.check_routes(self.users, self._service_map)

        # Check services health
        if self.service_check_interval:
            pc = PeriodicCallback(
                self.check_services_health, 1e3 * self.service_check_interval
            )
            self._periodic_callbacks["service_check"] = pc
            pc.start()

        if self.last_activity_interval:
            pc = PeriodicCallback(
                self.update_last_activity, 1e3 * self.last_activity_interval
            )
            self._periodic_callbacks["last_activity"] = pc
            pc.start()

        if self.proxy.should_start:
            self.log.info("JupyterHub is now running at %s", self.proxy.public_url)
        else:
            self.log.info(
                "JupyterHub is now running, internal Hub API at %s", self.hub.url
            )
        # Use atexit for Windows, it doesn't have signal handling support
        if _mswindows:
            atexit.register(self.atexit)
        # register cleanup on both TERM and INT
        self.init_signal()
        self._start_future.set_result(None)
Start the whole thing
jupyterhub/app.py