Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ homeassistant/components/repetier/* @MTrab
homeassistant/components/rfxtrx/* @danielhiversen
homeassistant/components/rmvtransport/* @cgtobi
homeassistant/components/roomba/* @pschmitt
homeassistant/components/safe_mode/* @home-assistant/core
homeassistant/components/saj/* @fredericvl
homeassistant/components/samsungtv/* @escoand
homeassistant/components/scene/* @home-assistant/core
Expand Down
64 changes: 17 additions & 47 deletions homeassistant/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@
import subprocess
import sys
import threading
from typing import TYPE_CHECKING, Any, Dict, List
from typing import List

from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__

if TYPE_CHECKING:
from homeassistant import core


def set_loop() -> None:
"""Attempt to use different loop."""
Expand Down Expand Up @@ -78,19 +75,6 @@ def ensure_config_path(config_dir: str) -> None:
sys.exit(1)


async def ensure_config_file(hass: "core.HomeAssistant", config_dir: str) -> str:
"""Ensure configuration file exists."""
import homeassistant.config as config_util

config_path = await config_util.async_ensure_config_exists(hass, config_dir)

if config_path is None:
print("Error getting configuration path")
sys.exit(1)

return config_path


def get_arguments() -> argparse.Namespace:
"""Get parsed passed in arguments."""
import homeassistant.config as config_util
Expand All @@ -107,7 +91,7 @@ def get_arguments() -> argparse.Namespace:
help="Directory that contains the Home Assistant configuration",
)
parser.add_argument(
"--demo-mode", action="store_true", help="Start Home Assistant in demo mode"
"--safe-mode", action="store_true", help="Start Home Assistant in safe mode"
)
parser.add_argument(
"--debug", action="store_true", help="Start Home Assistant in debug mode"
Expand Down Expand Up @@ -253,34 +237,20 @@ def cmdline() -> List[str]:

async def setup_and_run_hass(config_dir: str, args: argparse.Namespace) -> int:
"""Set up Home Assistant and run."""
from homeassistant import bootstrap, core

hass = core.HomeAssistant()

if args.demo_mode:
config: Dict[str, Any] = {"frontend": {}, "demo": {}}
bootstrap.async_from_config_dict(
config,
hass,
config_dir=config_dir,
verbose=args.verbose,
skip_pip=args.skip_pip,
log_rotate_days=args.log_rotate_days,
log_file=args.log_file,
log_no_color=args.log_no_color,
)
else:
config_file = await ensure_config_file(hass, config_dir)
print("Config directory:", config_dir)
await bootstrap.async_from_config_file(
config_file,
hass,
verbose=args.verbose,
skip_pip=args.skip_pip,
log_rotate_days=args.log_rotate_days,
log_file=args.log_file,
log_no_color=args.log_no_color,
)
from homeassistant import bootstrap

hass = await bootstrap.async_setup_hass(
config_dir=config_dir,
verbose=args.verbose,
log_rotate_days=args.log_rotate_days,
log_file=args.log_file,
log_no_color=args.log_no_color,
skip_pip=args.skip_pip,
safe_mode=args.safe_mode,
)

if hass is None:
return 1

if args.open_ui and hass.config.api is not None:
import webbrowser
Expand Down Expand Up @@ -358,7 +328,7 @@ def main() -> int:

return scripts.run(args.script)

config_dir = os.path.join(os.getcwd(), args.config)
config_dir = os.path.abspath(os.path.join(os.getcwd(), args.config))
ensure_config_path(config_dir)

# Daemon functions
Expand Down
132 changes: 64 additions & 68 deletions homeassistant/bootstrap.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Provide methods to bootstrap a Home Assistant instance."""
import asyncio
from collections import OrderedDict
import logging
import logging.handlers
import os
Expand All @@ -11,6 +10,7 @@
import voluptuous as vol

from homeassistant import config as conf_util, config_entries, core, loader
from homeassistant.components import http
from homeassistant.const import (
EVENT_HOMEASSISTANT_CLOSE,
REQUIRED_NEXT_PYTHON_DATE,
Expand Down Expand Up @@ -42,33 +42,76 @@
}


async def async_from_config_dict(
config: Dict[str, Any],
hass: core.HomeAssistant,
config_dir: Optional[str] = None,
enable_log: bool = True,
verbose: bool = False,
skip_pip: bool = False,
log_rotate_days: Any = None,
log_file: Any = None,
log_no_color: bool = False,
async def async_setup_hass(
*,
config_dir: str,
verbose: bool,
log_rotate_days: int,
log_file: str,
log_no_color: bool,
skip_pip: bool,
safe_mode: bool,
) -> Optional[core.HomeAssistant]:
"""Try to configure Home Assistant from a configuration dictionary.

Dynamically loads required components and its dependencies.
This method is a coroutine.
"""
start = time()
"""Set up Home Assistant."""
hass = core.HomeAssistant()
hass.config.config_dir = config_dir

if enable_log:
async_enable_logging(hass, verbose, log_rotate_days, log_file, log_no_color)
async_enable_logging(hass, verbose, log_rotate_days, log_file, log_no_color)

hass.config.skip_pip = skip_pip
if skip_pip:
_LOGGER.warning(
"Skipping pip installation of required modules. This may cause issues"
)

if not await conf_util.async_ensure_config_exists(hass):
_LOGGER.error("Error getting configuration path")
return None

_LOGGER.info("Config directory: %s", config_dir)

config_dict = None

if not safe_mode:
await hass.async_add_executor_job(conf_util.process_ha_config_upgrade, hass)

try:
config_dict = await conf_util.async_hass_config_yaml(hass)
except HomeAssistantError as err:
_LOGGER.error(
"Failed to parse configuration.yaml: %s. Falling back to safe mode",
err,
)
else:
if not is_virtual_env():
await async_mount_local_lib_path(config_dir)

await async_from_config_dict(config_dict, hass)
finally:
clear_secret_cache()

if safe_mode or config_dict is None:
_LOGGER.info("Starting in safe mode")

http_conf = (await http.async_get_last_config(hass)) or {}

await async_from_config_dict(
{"safe_mode": {}, "http": http_conf}, hass,
)

return hass


async def async_from_config_dict(
config: Dict[str, Any], hass: core.HomeAssistant
) -> Optional[core.HomeAssistant]:
"""Try to configure Home Assistant from a configuration dictionary.

Dynamically loads required components and its dependencies.
This method is a coroutine.
"""
start = time()

core_config = config.get(core.DOMAIN, {})

try:
Expand All @@ -83,14 +126,6 @@ async def async_from_config_dict(
)
return None

# Make a copy because we are mutating it.
config = OrderedDict(config)

# Merge packages
await conf_util.merge_packages_config(
hass, config, core_config.get(conf_util.CONF_PACKAGES, {})
)

hass.config_entries = config_entries.ConfigEntries(hass, config)
await hass.config_entries.async_initialize()

Expand All @@ -116,46 +151,6 @@ async def async_from_config_dict(
return hass


async def async_from_config_file(
config_path: str,
hass: core.HomeAssistant,
verbose: bool = False,
skip_pip: bool = True,
log_rotate_days: Any = None,
log_file: Any = None,
log_no_color: bool = False,
) -> Optional[core.HomeAssistant]:
"""Read the configuration file and try to start all the functionality.

Will add functionality to 'hass' parameter.
This method is a coroutine.
"""
# Set config dir to directory holding config file
config_dir = os.path.abspath(os.path.dirname(config_path))
hass.config.config_dir = config_dir

if not is_virtual_env():
await async_mount_local_lib_path(config_dir)

async_enable_logging(hass, verbose, log_rotate_days, log_file, log_no_color)

await hass.async_add_executor_job(conf_util.process_ha_config_upgrade, hass)

try:
config_dict = await hass.async_add_executor_job(
conf_util.load_yaml_config_file, config_path
)
except HomeAssistantError as err:
_LOGGER.error("Error loading %s: %s", config_path, err)
return None
finally:
clear_secret_cache()

return await async_from_config_dict(
config_dict, hass, enable_log=False, skip_pip=skip_pip
)


@core.callback
def async_enable_logging(
hass: core.HomeAssistant,
Expand Down Expand Up @@ -269,7 +264,8 @@ def _get_domains(hass: core.HomeAssistant, config: Dict[str, Any]) -> Set[str]:
domains = set(key.split(" ")[0] for key in config.keys() if key != core.DOMAIN)

# Add config entry domains
domains.update(hass.config_entries.async_domains())
if "safe_mode" not in config:
domains.update(hass.config_entries.async_domains())

# Make sure the Hass.io component is loaded
if "HASSIO" in os.environ:
Expand Down
9 changes: 4 additions & 5 deletions homeassistant/components/frontend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from homeassistant.components import websocket_api
from homeassistant.components.http.view import HomeAssistantView
from homeassistant.config import find_config_file, load_yaml_config_file
from homeassistant.config import async_hass_config_yaml
from homeassistant.const import CONF_NAME, EVENT_THEMES_UPDATED
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
Expand Down Expand Up @@ -362,11 +362,10 @@ def set_theme(call):
else:
_LOGGER.warning("Theme %s is not defined.", name)

@callback
def reload_themes(_):
async def reload_themes(_):
"""Reload themes."""
path = find_config_file(hass.config.config_dir)
new_themes = load_yaml_config_file(path)[DOMAIN].get(CONF_THEMES, {})
config = await async_hass_config_yaml(hass)
new_themes = config[DOMAIN].get(CONF_THEMES, {})
hass.data[DATA_THEMES] = new_themes
if hass.data[DATA_DEFAULT_THEME] not in new_themes:
hass.data[DATA_DEFAULT_THEME] = DEFAULT_THEME
Expand Down
19 changes: 18 additions & 1 deletion homeassistant/components/http/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging
import os
import ssl
from typing import Optional
from typing import Optional, cast

from aiohttp import web
from aiohttp.web_exceptions import HTTPMovedPermanently
Expand All @@ -14,7 +14,10 @@
EVENT_HOMEASSISTANT_STOP,
SERVER_PORT,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import storage
import homeassistant.helpers.config_validation as cv
from homeassistant.loader import bind_hass
import homeassistant.util as hass_util
from homeassistant.util import ssl as ssl_util

Expand Down Expand Up @@ -56,6 +59,9 @@

MAX_CLIENT_SIZE: int = 1024 ** 2 * 16

STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1


HTTP_SCHEMA = vol.Schema(
{
Expand Down Expand Up @@ -85,6 +91,13 @@
CONFIG_SCHEMA = vol.Schema({DOMAIN: HTTP_SCHEMA}, extra=vol.ALLOW_EXTRA)


@bind_hass
async def async_get_last_config(hass: HomeAssistant) -> Optional[dict]:
"""Return the last known working config."""
store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY)
return cast(Optional[dict], await store.async_load())


class ApiConfig:
"""Configuration settings for API server."""

Expand Down Expand Up @@ -151,6 +164,10 @@ async def start_server(event):
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_server)
await server.start()

# If we are set up successful, we store the HTTP settings for safe mode.
store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY)
await store.async_save(conf)

hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_server)

hass.http = server
Expand Down
15 changes: 15 additions & 0 deletions homeassistant/components/safe_mode/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""The Safe Mode integration."""
from homeassistant.components import persistent_notification
from homeassistant.core import HomeAssistant

DOMAIN = "safe_mode"


async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the Safe Mode component."""
persistent_notification.async_create(
hass,
"Home Assistant is running in safe mode. Check [the error log](/developer-tools/logs) to see what went wrong.",
"Safe Mode",
)
return True
12 changes: 12 additions & 0 deletions homeassistant/components/safe_mode/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"domain": "safe_mode",
"name": "Safe Mode",
"config_flow": false,
"documentation": "https://www.home-assistant.io/integrations/safe_mode",
"requirements": [],
"ssdp": [],
"zeroconf": [],
"homekit": {},
"dependencies": ["frontend", "config", "persistent_notification", "cloud"],
"codeowners": ["@home-assistant/core"]
}
Loading