1 """Provide methods to bootstrap a Home Assistant instance."""
3 from __future__
import annotations
6 from collections
import defaultdict
8 from functools
import partial
9 from itertools
import chain
13 from operator
import contains, itemgetter
18 from time
import monotonic
19 from typing
import TYPE_CHECKING, Any
23 import cryptography.hazmat.backends.openssl.backend
24 import voluptuous
as vol
41 from .components
import (
42 api
as api_pre_import,
43 auth
as auth_pre_import,
44 config
as config_pre_import,
45 default_config
as default_config_pre_import,
46 device_automation
as device_automation_pre_import,
47 diagnostics
as diagnostics_pre_import,
48 file_upload
as file_upload_pre_import,
49 group
as group_pre_import,
50 history
as history_pre_import,
52 image_upload
as image_upload_import,
53 logbook
as logbook_pre_import,
54 lovelace
as lovelace_pre_import,
55 onboarding
as onboarding_pre_import,
56 recorder
as recorder_import,
57 repairs
as repairs_pre_import,
58 search
as search_pre_import,
59 sensor
as sensor_pre_import,
60 system_log
as system_log_pre_import,
61 webhook
as webhook_pre_import,
62 websocket_api
as websocket_api_pre_import,
64 from .components.sensor
import recorder
as sensor_recorder
68 KEY_DATA_LOGGING
as DATA_LOGGING,
69 REQUIRED_NEXT_PYTHON_HA_RELEASE,
70 REQUIRED_NEXT_PYTHON_VER,
71 SIGNAL_BOOTSTRAP_INTEGRATIONS,
73 from .core_config
import async_process_ha_core_config
74 from .exceptions
import HomeAssistantError
75 from .helpers
import (
78 config_validation
as cv,
90 from .helpers.dispatcher
import async_dispatcher_send_internal
91 from .helpers.storage
import get_internal_store_manager
92 from .helpers.system_info
import async_get_system_info, is_official_image
93 from .helpers.typing
import ConfigType
100 async_get_setup_timings,
101 async_notify_setup_error,
102 async_set_domains_to_be_loaded,
103 async_setup_component,
105 from .util.async_
import create_eager_task
106 from .util.hass_dict
import HassKey
107 from .util.logging
import async_activate_log_queue_handler
108 from .util.package
import async_get_user_site, is_docker_env, is_virtual_env
110 with contextlib.suppress(ImportError):
112 from anyio._backends
import _asyncio
116 from .runner
import RuntimeConfig
118 _LOGGER = logging.getLogger(__name__)
120 SETUP_ORDER_SORT_KEY = partial(contains, BASE_PLATFORMS)
123 ERROR_LOG_FILENAME =
"home-assistant.log"
126 DATA_REGISTRIES_LOADED: HassKey[
None] = HassKey(
"bootstrap_registries_loaded")
128 LOG_SLOW_STARTUP_INTERVAL = 60
129 SLOW_STARTUP_CHECK_INTERVAL = 1
131 STAGE_1_TIMEOUT = 120
132 STAGE_2_TIMEOUT = 300
133 WRAP_UP_TIMEOUT = 300
137 DEBUGGER_INTEGRATIONS = {
"debugpy"}
140 CORE_INTEGRATIONS = {
"homeassistant",
"persistent_notification"}
143 LOGGING_AND_HTTP_DEPS_INTEGRATIONS = {
153 FRONTEND_INTEGRATIONS = {
159 RECORDER_INTEGRATIONS = {
164 DISCOVERY_INTEGRATIONS = (
"bluetooth",
"dhcp",
"ssdp",
"usb",
"zeroconf")
165 STAGE_1_INTEGRATIONS = {
171 *DISCOVERY_INTEGRATIONS,
179 DEFAULT_INTEGRATIONS = {
184 "application_credentials",
211 DEFAULT_INTEGRATIONS_RECOVERY_MODE = {
215 DEFAULT_INTEGRATIONS_SUPERVISOR = {
219 CRITICAL_INTEGRATIONS = {
226 (
"logging, http deps", LOGGING_AND_HTTP_DEPS_INTEGRATIONS),
228 (
"frontend", FRONTEND_INTEGRATIONS),
230 (
"recorder", RECORDER_INTEGRATIONS),
232 (
"debugger", DEBUGGER_INTEGRATIONS),
246 "lovelace_dashboards",
247 "lovelace_resources",
250 "bluetooth.passive_update_processor",
251 "bluetooth.remote_scanners",
252 "assist_pipeline.pipelines",
259 runtime_config: RuntimeConfig,
261 """Set up Home Assistant."""
263 async
def create_hass() -> core.HomeAssistant:
264 """Create the hass object and do basic setup."""
266 loader.async_setup(hass)
270 runtime_config.verbose,
271 runtime_config.log_rotate_days,
272 runtime_config.log_file,
273 runtime_config.log_no_color,
276 if runtime_config.debug
or hass.loop.get_debug():
277 hass.config.debug =
True
279 hass.config.safe_mode = runtime_config.safe_mode
280 hass.config.skip_pip = runtime_config.skip_pip
281 hass.config.skip_pip_packages = runtime_config.skip_pip_packages
285 async
def stop_hass(hass: core.HomeAssistant) ->
None:
289 with contextlib.suppress(TimeoutError):
290 async
with hass.timeout.async_timeout(10):
291 await hass.async_stop()
293 hass = await create_hass()
295 if runtime_config.skip_pip
or runtime_config.skip_pip_packages:
297 "Skipping pip installation of required modules. This may cause issues"
300 if not await conf_util.async_ensure_config_exists(hass):
301 _LOGGER.error(
"Error getting configuration path")
304 _LOGGER.info(
"Config directory: %s", runtime_config.config_dir)
306 block_async_io.enable()
309 basic_setup_success =
False
311 if not (recovery_mode := runtime_config.recovery_mode):
312 await hass.async_add_executor_job(conf_util.process_ha_config_upgrade, hass)
315 config_dict = await conf_util.async_hass_config_yaml(hass)
316 except HomeAssistantError
as err:
318 "Failed to parse configuration.yaml: %s. Activating recovery mode",
325 basic_setup_success = (
329 if config_dict
is None:
331 await stop_hass(hass)
332 hass = await create_hass()
334 elif not basic_setup_success:
335 _LOGGER.warning(
"Unable to set up core integrations. Activating recovery mode")
337 await stop_hass(hass)
338 hass = await create_hass()
340 elif any(domain
not in hass.config.components
for domain
in CRITICAL_INTEGRATIONS):
342 "Detected that %s did not load. Activating recovery mode",
343 ",".join(CRITICAL_INTEGRATIONS),
346 old_config = hass.config
347 old_logging = hass.data.get(DATA_LOGGING)
350 await stop_hass(hass)
351 hass = await create_hass()
354 hass.data[DATA_LOGGING] = old_logging
355 hass.config.debug = old_config.debug
356 hass.config.skip_pip = old_config.skip_pip
357 hass.config.skip_pip_packages = old_config.skip_pip_packages
358 hass.config.internal_url = old_config.internal_url
359 hass.config.external_url = old_config.external_url
361 loader.async_setup(hass)
364 _LOGGER.info(
"Starting in recovery mode")
365 hass.config.recovery_mode =
True
367 http_conf = (await http.async_get_last_config(hass))
or {}
370 {
"recovery_mode": {},
"http": http_conf},
373 elif hass.config.safe_mode:
374 _LOGGER.info(
"Starting in safe mode")
376 if runtime_config.open_ui:
377 hass.add_job(open_hass_ui, hass)
386 if hass.config.api
is None or "frontend" not in hass.config.components:
387 _LOGGER.warning(
"Cannot launch the UI because frontend not loaded")
390 scheme =
"https" if hass.config.api.use_ssl
else "http"
392 yarl.URL.build(scheme=scheme, host=
"127.0.0.1", port=hass.config.api.port)
395 if not webbrowser.open(url):
397 "Unable to open the Home Assistant UI in a browser. Open it yourself at %s",
403 """Initialize modules that do blocking I/O in executor."""
409 _ = platform.uname().processor
420 """Load the registries and modules that will do blocking I/O."""
421 if DATA_REGISTRIES_LOADED
in hass.data:
423 hass.data[DATA_REGISTRIES_LOADED] =
None
424 translation.async_setup(hass)
425 entity.async_setup(hass)
426 template.async_setup(hass)
427 await asyncio.gather(
429 create_eager_task(area_registry.async_load(hass)),
430 create_eager_task(category_registry.async_load(hass)),
431 create_eager_task(device_registry.async_load(hass)),
432 create_eager_task(entity_registry.async_load(hass)),
433 create_eager_task(floor_registry.async_load(hass)),
434 create_eager_task(issue_registry.async_load(hass)),
435 create_eager_task(label_registry.async_load(hass)),
436 hass.async_add_executor_job(_init_blocking_io_modules_in_executor),
437 create_eager_task(template.async_load_custom_templates(hass)),
438 create_eager_task(restore_state.async_load(hass)),
439 create_eager_task(hass.config_entries.async_initialize()),
447 """Try to configure Home Assistant from a configuration dictionary.
449 Dynamically loads required components and its dependencies.
450 This method is a coroutine.
457 await loader.async_get_custom_components(hass)
461 _LOGGER.debug(
"Setting up %s", CORE_INTEGRATIONS)
464 await asyncio.gather(
468 name=f
"bootstrap setup {domain}",
471 for domain
in CORE_INTEGRATIONS
475 _LOGGER.error(
"Home Assistant core failed to initialize. ")
478 _LOGGER.debug(
"Home Assistant core initialized")
480 core_config = config.get(core.DOMAIN, {})
484 except vol.Invalid
as config_err:
485 conf_util.async_log_schema_error(config_err, core.DOMAIN, core_config, hass)
488 except HomeAssistantError:
490 "Home Assistant core failed to initialize. Further initialization aborted"
497 _LOGGER.info(
"Home Assistant initialized in %.2fs", stop - start)
500 REQUIRED_NEXT_PYTHON_HA_RELEASE
501 and sys.version_info[:3] < REQUIRED_NEXT_PYTHON_VER
503 current_python_version =
".".join(
str(x)
for x
in sys.version_info[:3])
504 required_python_version =
".".join(
str(x)
for x
in REQUIRED_NEXT_PYTHON_VER[:2])
507 "Support for the running Python version %s is deprecated and "
508 "will be removed in Home Assistant %s; "
509 "Please upgrade Python to %s"
511 current_python_version,
512 REQUIRED_NEXT_PYTHON_HA_RELEASE,
513 required_python_version,
515 issue_registry.async_create_issue(
518 f
"python_version_{required_python_version}",
520 severity=issue_registry.IssueSeverity.WARNING,
521 breaks_in_ha_version=REQUIRED_NEXT_PYTHON_HA_RELEASE,
522 translation_key=
"python_version",
523 translation_placeholders={
524 "current_python_version": current_python_version,
525 "required_python_version": required_python_version,
526 "breaks_in_ha_version": REQUIRED_NEXT_PYTHON_HA_RELEASE,
534 hass: core.HomeAssistant,
535 verbose: bool =
False,
536 log_rotate_days: int |
None =
None,
537 log_file: str |
None =
None,
538 log_no_color: bool =
False,
540 """Set up the logging.
542 This method must be run in the event loop.
545 "%(asctime)s.%(msecs)03d %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
551 from colorlog
import ColoredFormatter
555 logging.basicConfig(level=logging.INFO)
557 colorfmt = f
"%(log_color)s{fmt}%(reset)s"
558 logging.getLogger().handlers[0].setFormatter(
561 datefmt=FORMAT_DATETIME,
577 logging.basicConfig(format=fmt, datefmt=FORMAT_DATETIME, level=logging.INFO)
582 logging.captureWarnings(
True)
585 logging.getLogger(
"requests").setLevel(logging.WARNING)
586 logging.getLogger(
"urllib3").setLevel(logging.WARNING)
587 logging.getLogger(
"aiohttp.access").setLevel(logging.WARNING)
588 logging.getLogger(
"httpx").setLevel(logging.WARNING)
590 sys.excepthook =
lambda *args: logging.getLogger().exception(
591 "Uncaught exception", exc_info=args
593 threading.excepthook =
lambda args: logging.getLogger().exception(
594 "Uncaught thread exception",
604 err_log_path = hass.config.path(ERROR_LOG_FILENAME)
606 err_log_path = os.path.abspath(log_file)
608 err_path_exists = os.path.isfile(err_log_path)
609 err_dir = os.path.dirname(err_log_path)
613 if (err_path_exists
and os.access(err_log_path, os.W_OK))
or (
614 not err_path_exists
and os.access(err_dir, os.W_OK)
616 err_handler = await hass.async_add_executor_job(
617 _create_log_file, err_log_path, log_rotate_days
620 err_handler.setFormatter(logging.Formatter(fmt, datefmt=FORMAT_DATETIME))
622 logger = logging.getLogger()
623 logger.addHandler(err_handler)
624 logger.setLevel(logging.INFO
if verbose
else logging.WARNING)
627 hass.data[DATA_LOGGING] = err_log_path
629 _LOGGER.error(
"Unable to set up error log %s (access denied)", err_log_path)
635 err_log_path: str, log_rotate_days: int |
None
636 ) -> RotatingFileHandler | TimedRotatingFileHandler:
637 """Create log file and do roll over."""
638 err_handler: RotatingFileHandler | TimedRotatingFileHandler
640 err_handler = TimedRotatingFileHandler(
641 err_log_path, when=
"midnight", backupCount=log_rotate_days
645 err_log_path, backupCount=1
649 err_handler.doRollover()
650 except OSError
as err:
651 _LOGGER.error(
"Error rolling over log file: %s", err)
657 """RotatingFileHandler that does not check if it should roll over on every log."""
662 The shouldRollover check is expensive because it has to stat
663 the log file for every log record. Since we do not set maxBytes
664 the result of this check is always False.
670 """Add local library to Python Path.
672 This function is a coroutine.
674 deps_dir = os.path.join(config_dir,
"deps")
676 sys.path.insert(0, lib_dir)
681 def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
682 """Get domains of components to set up."""
685 domain
for key
in config
if (domain := cv.domain_key(key)) != core.DOMAIN
689 if not hass.config.recovery_mode:
690 domains.update(DEFAULT_INTEGRATIONS)
691 domains.update(hass.config_entries.async_domains())
693 domains.update(DEFAULT_INTEGRATIONS_RECOVERY_MODE)
696 if "SUPERVISOR" in os.environ:
697 domains.update(DEFAULT_INTEGRATIONS_SUPERVISOR)
703 """Periodic log and dispatch of setups that are pending."""
707 hass: core.HomeAssistant,
708 setup_started: dict[tuple[str, str |
None], float],
710 """Initialize the WatchPendingSetups class."""
714 self.
_handle_handle: asyncio.TimerHandle |
None =
None
719 """Periodic log of setups that are pending."""
723 remaining_with_setup_started: defaultdict[str, float] = defaultdict(float)
724 for integration_group, start_time
in self.
_setup_started_setup_started.items():
725 domain, _ = integration_group
726 remaining_with_setup_started[domain] += now - start_time
728 if remaining_with_setup_started:
729 _LOGGER.debug(
"Integration remaining: %s", remaining_with_setup_started)
730 elif waiting_tasks := self.
_hass_hass._active_tasks:
731 _LOGGER.debug(
"Waiting on tasks: %s", waiting_tasks)
735 and self.
_duration_count_duration_count % LOG_SLOW_STARTUP_INTERVAL == 0
740 "Waiting on integrations to complete setup: %s",
744 _LOGGER.debug(
"Running timeout Zones: %s", self.
_hass_hass.timeout.zones)
748 """Dispatch the signal."""
750 async_dispatcher_send_internal(
751 self.
_hass_hass, SIGNAL_BOOTSTRAP_INTEGRATIONS, remaining_with_setup_started
756 """Schedule the next call."""
758 SLOW_STARTUP_CHECK_INTERVAL, self.
_async_watch_async_watch
762 """Start watching."""
774 hass: core.HomeAssistant,
776 config: dict[str, Any],
778 """Set up multiple domains. Log on failure."""
780 domains_not_yet_setup = domains - hass.config.components
785 domain: hass.async_create_task_internal(
787 f
"setup component {domain}",
790 for domain
in sorted(
791 domains_not_yet_setup, key=SETUP_ORDER_SORT_KEY, reverse=
True
794 results = await asyncio.gather(*futures.values(), return_exceptions=
True)
795 for idx, domain
in enumerate(futures):
796 result = results[idx]
797 if isinstance(result, BaseException):
799 "Error setting up integration %s - received exception",
801 exc_info=(type(result), result, result.__traceback__),
806 hass: core.HomeAssistant, config: dict[str, Any]
808 """Resolve all dependencies and return list of domains to set up."""
810 needed_requirements: set[str] = set()
811 platform_integrations = conf_util.extract_platform_integrations(
812 config, BASE_PLATFORMS
832 domains_to_setup.update(platform_integrations)
838 additional_manifests_to_load = {
840 *chain.from_iterable(platform_integrations.values()),
843 translations_to_load = additional_manifests_to_load.copy()
848 to_resolve: set[str] = domains_to_setup
849 while to_resolve
or additional_manifests_to_load:
850 old_to_resolve: set[str] = to_resolve
853 if additional_manifests_to_load:
854 to_get = {*old_to_resolve, *additional_manifests_to_load}
855 additional_manifests_to_load.clear()
857 to_get = old_to_resolve
859 manifest_deps: set[str] = set()
860 resolve_dependencies_tasks: list[asyncio.Task[bool]] = []
863 for domain, itg
in (await loader.async_get_integrations(hass, to_get)).items():
866 integration_cache[domain] = itg
867 needed_requirements.update(itg.requirements)
873 additional_manifests_to_load.update(
875 for dep
in chain(itg.dependencies, itg.after_dependencies)
876 if dep
not in integration_cache
879 if domain
not in old_to_resolve:
882 integrations_to_process.append(itg)
883 manifest_deps.update(itg.dependencies)
884 manifest_deps.update(itg.after_dependencies)
885 if not itg.all_dependencies_resolved:
886 resolve_dependencies_tasks.append(
888 itg.resolve_dependencies(),
889 name=f
"resolve dependencies {domain}",
894 if unseen_deps := manifest_deps - integration_cache.keys():
901 deps = await loader.async_get_integrations(hass, unseen_deps)
902 for dependant_domain, dependant_itg
in deps.items():
904 integration_cache[dependant_domain] = dependant_itg
905 needed_requirements.update(dependant_itg.requirements)
907 if resolve_dependencies_tasks:
908 await asyncio.gather(*resolve_dependencies_tasks)
910 for itg
in integrations_to_process:
912 all_deps = itg.all_dependencies
918 if dep
in domains_to_setup:
920 domains_to_setup.add(dep)
923 _LOGGER.info(
"Domains to be set up: %s", domains_to_setup)
928 hass.async_create_background_task(
929 requirements.async_load_installed_versions(hass, needed_requirements),
930 "check installed requirements",
938 translations_to_load.update(domains_to_setup)
949 hass.async_create_background_task(
950 translation.async_load_integrations(hass, translations_to_load),
958 hass.async_create_background_task(
960 [*PRELOAD_STORAGE, *domains_to_setup]
966 return domains_to_setup, integration_cache
970 hass: core.HomeAssistant, config: dict[str, Any]
972 """Set up all the integrations."""
974 watcher.async_start()
981 if "recorder" in domains_to_setup:
982 recorder.async_initialize_recorder(hass)
984 pre_stage_domains = [
985 (name, domains_to_setup & domain_group)
for name, domain_group
in SETUP_ORDER
989 stage_1_domains: set[str] = set()
994 deps_promotion: set[str] = STAGE_1_INTEGRATIONS
995 while deps_promotion:
996 old_deps_promotion = deps_promotion
997 deps_promotion = set()
999 for domain
in old_deps_promotion:
1000 if domain
not in domains_to_setup
or domain
in stage_1_domains:
1003 stage_1_domains.add(domain)
1005 if (dep_itg := integration_cache.get(domain))
is None:
1008 deps_promotion.update(dep_itg.all_dependencies)
1010 stage_2_domains = domains_to_setup - stage_1_domains
1012 for name, domain_group
in pre_stage_domains:
1014 stage_2_domains -= domain_group
1015 _LOGGER.info(
"Setting up %s: %s", name, domain_group)
1016 to_be_loaded = domain_group.copy()
1017 to_be_loaded.update(
1019 for domain
in domain_group
1020 if (integration := integration_cache.get(domain))
is not None
1021 for dep
in integration.all_dependencies
1031 _LOGGER.info(
"Setting up stage 1: %s", stage_1_domains)
1033 async
with hass.timeout.async_timeout(
1034 STAGE_1_TIMEOUT, cool_down=COOLDOWN_TIME
1037 except TimeoutError:
1039 "Setup timed out for stage 1 waiting on %s - moving forward",
1047 _LOGGER.info(
"Setting up stage 2: %s", stage_2_domains)
1049 async
with hass.timeout.async_timeout(
1050 STAGE_2_TIMEOUT, cool_down=COOLDOWN_TIME
1053 except TimeoutError:
1055 "Setup timed out for stage 2 waiting on %s - moving forward",
1060 _LOGGER.debug(
"Waiting for startup to wrap up")
1062 async
with hass.timeout.async_timeout(WRAP_UP_TIMEOUT, cool_down=COOLDOWN_TIME):
1063 await hass.async_block_till_done()
1064 except TimeoutError:
1066 "Setup timed out for bootstrap waiting on %s - moving forward",
1070 watcher.async_stop()
1072 if _LOGGER.isEnabledFor(logging.DEBUG):
1075 "Integration setup times: %s",
1076 dict(sorted(setup_time.items(), key=itemgetter(1), reverse=
True)),
bool shouldRollover(self, logging.LogRecord record)
None __init__(self, core.HomeAssistant hass, dict[tuple[str, str|None], float] setup_started)
None _async_schedule_next(self)
None _async_dispatch(self, dict[str, float] remaining_with_setup_started)
RotatingFileHandler|TimedRotatingFileHandler _create_log_file(str err_log_path, int|None log_rotate_days)
set[str] _get_domains(core.HomeAssistant hass, dict[str, Any] config)
None async_enable_logging(core.HomeAssistant hass, bool verbose=False, int|None log_rotate_days=None, str|None log_file=None, bool log_no_color=False)
None open_hass_ui(core.HomeAssistant hass)
None _init_blocking_io_modules_in_executor()
str async_mount_local_lib_path(str config_dir)
None _async_set_up_integrations(core.HomeAssistant hass, dict[str, Any] config)
tuple[set[str], dict[str, loader.Integration]] _async_resolve_domains_to_setup(core.HomeAssistant hass, dict[str, Any] config)
None async_load_base_functionality(core.HomeAssistant hass)
core.HomeAssistant|None async_from_config_dict(ConfigType config, core.HomeAssistant hass)
None async_setup_multi_components(core.HomeAssistant hass, set[str] domains, dict[str, Any] config)
core.HomeAssistant|None async_setup_hass(RuntimeConfig runtime_config)
None async_process_ha_core_config(HomeAssistant hass, dict config)
_StoreManager get_internal_store_manager(HomeAssistant hass)
dict[str, Any] async_get_system_info(HomeAssistant hass)
None async_notify_setup_error(HomeAssistant hass, str component, str|None display_link=None)
None async_set_domains_to_be_loaded(core.HomeAssistant hass, set[str] domains)
dict[str, float] async_get_setup_timings(core.HomeAssistant hass)
dict[tuple[str, str|None], float] _setup_started(core.HomeAssistant hass)
bool async_setup_component(core.HomeAssistant hass, str domain, ConfigType config)
None async_activate_log_queue_handler(HomeAssistant hass)
str async_get_user_site(str deps_dir)